diff --git a/.eslintrc b/.eslintrc index 7159808919..1bc5b32dd3 100644 --- a/.eslintrc +++ b/.eslintrc @@ -23,7 +23,22 @@ "globals": { "document": true, "navigator": true, - "window": true + "window": true, + "customElements": true, + "alert": true, + "uwg": true, + "DatArchive": true, + "location": true, + "beaker": true, + "prompt": true, + "CSSStyleSheet": true, + "confirm": true, + "fetch": true, + "HTMLElement": true, + "CustomEvent": true, + "localStorage": true, + "FileReader": true, + "Blob": true }, "rules": { @@ -88,14 +103,7 @@ "no-label-var": "error", "no-labels": ["error", { "allowLoop": false, "allowSwitch": false }], "no-lone-blocks": "error", - "no-mixed-operators": ["error", { - "groups": [ - ["==", "!=", "===", "!==", ">", ">=", "<", "<="], - ["&&", "||"], - ["in", "instanceof"] - ], - "allowSamePrecedence": true - }], + "no-mixed-operators": "off", "no-mixed-spaces-and-tabs": "error", "no-multi-spaces": "off", "no-multi-str": "error", @@ -127,7 +135,7 @@ "no-throw-literal": "error", "no-trailing-spaces": "error", "no-undef": "error", - "no-undef-init": "error", + "no-undef-init": "off", "no-unexpected-multiline": "error", "no-unmodified-loop-condition": "error", "no-unneeded-ternary": ["off", { "defaultAssignment": false }], @@ -140,7 +148,7 @@ "no-useless-call": "error", "no-useless-computed-key": "error", "no-useless-constructor": "error", - "no-useless-escape": "error", + "no-useless-escape": "off", "no-useless-rename": "error", "no-useless-return": "off", "no-whitespace-before-property": "error", @@ -186,6 +194,6 @@ "standard/array-bracket-even-spacing": ["error", "either"], "standard/computed-property-even-spacing": ["error", "even"], "standard/no-callback-literal": "off", - "standard/object-curly-even-spacing": ["error", "either"] + "standard/object-curly-even-spacing": "off" } } \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index e001b56446..0000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,2 +0,0 @@ -Operation System: -Beaker version: diff --git a/.github/ISSUE_TEMPLATE/ISSUE_TEMPLATE_0.8_BETA.md b/.github/ISSUE_TEMPLATE/ISSUE_TEMPLATE_0.8_BETA.md deleted file mode 100644 index f01072f49d..0000000000 --- a/.github/ISSUE_TEMPLATE/ISSUE_TEMPLATE_0.8_BETA.md +++ /dev/null @@ -1,6 +0,0 @@ -Operating system: - -This is: - -- [ ] a bug -- [ ] general feedback diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 65d847e734..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,52 +0,0 @@ -notifications: - email: false - -env: - - ELECTRON_CACHE=$HOME/.cache/electron - -os: - - osx -# - linux -language: node_js -node_js: "8" - -cache: - directories: - - node_modules - - app/node_modules - - tests/node_modules - - $HOME/.cache/electron - -install: - - npm install - - npm run rebuild - - cd ./tests/ - - npm install - - cd .. - -before_script: - - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then - export DISPLAY=:99.0 && sh -e /etc/init.d/xvfb start && sleep 3; - fi - -script: - - cd ./tests/ - - npm test - - cd .. - - if [ ! -z "$TRAVIS_TAG" ]; then npm run release; fi - - -#deploy: -# provider: releases -# api_key: -# secure: RNbGl8IGSI4jJxdz3f7wj8c1oAQZZ8ewDkK/pjjTimxXbmRUKgeV1/GSkJOxOCmWKXRvYq1/qPYjW2nE99dpg9fd9+q+NxaIMeEOty6ZtNLXRvut46yqhsJIY8Hy2lzFbTSzJ3crUwCLPD1m4NdXPHvMzXiGO+BFYtrwiCsVcg6VDGlj7x2vkfaux16VCbuJzPqVoslTzVmFE8nwCU8zFudvGyWJttn5PqLit1OZ6NB+5T+haBN+y8zLW1q7XuULFkz5OEoaBz3l/tW54sXKeopOm7ccRSMsk4oZXjn12Vu9cErc22zX4zw6OlWHDWecNShcxOoOPmNQqoPyzGqeGqRqduwDcKYoAgPCLNgE2x9aAwfOrGJlv2YSlTzKF5c/ocQGrvdm8wUXS6XiEKRyGu9g2ljMW2I2qnBPbJjTFNCxLCmvmZVZHf182K6+CbHLdrLVi5uWRo7aypdi0t9+FmZAA0btJw27+v8qnOeaQFuU2mfwMbyTMUc3+5DlLsXR2FDY9weokptHimNtbJjgx3kxFvBSJCpIN4YpOwFJ+mlXXOf4l23I3RMaxKUSKWQE2YI3r+WJiVW9YcN9DctXzOMxjHFVSCiEtiAdAAjbmvafWdE5dm+ojA70P3HpwLVHA0pP8/KkRjXcIajfZ/l0vtOkGYufhQRSmgmY14iWn4A= -# file: -# - dist/*.AppImage -# - dist/*.dmg -# - dist/*.zip -# - dist/*.exe -# file_glob: true -# skip_cleanup: true -# on: -# repo: beakerbrowser/beaker -# tags: true diff --git a/README.md b/README.md index 950ce05ec0..2c9a7af04b 100644 --- a/README.md +++ b/README.md @@ -80,9 +80,9 @@ To build: ```bash git clone https://github.com/beakerbrowser/beaker.git -cd beaker -npm install -npm run rebuild # see https://github.com/electron/electron/issues/5851 +cd beaker/scripts +npm install # don't worry about v8 api errors building native modules - rebuild will fix +npm run rebuild # needed after each install. see https://github.com/electron/electron/issues/5851 npm start ``` @@ -93,6 +93,7 @@ npm run burnthemall ``` This invokes [the mad king](http://nerdist.com/wp-content/uploads/2016/05/the-mad-king-game-of-thrones.jpg), who will torch your `node_modules/`, and do the full install/rebuild process for you. +(We chose that command name when GoT was still cool.) `npm start` should work afterward. If you're doing development, `npm run watch` to have assets build automatically. diff --git a/app/README.md b/app/README.md new file mode 100644 index 0000000000..9ba214f608 --- /dev/null +++ b/app/README.md @@ -0,0 +1,7 @@ +# App + +- `/main.js` - Electron entrypoint +- `/assets` - Static assets such as images, fonts, etc +- `/bg` - Electron main process code (background) +- `/fg` - Beaker's frontend/UI code (foreground) +- `/userland` - Frontend code that runs in the userland environment \ No newline at end of file diff --git a/app/assets/css/fa-all.min.css b/app/assets/css/fa-all.min.css new file mode 100644 index 0000000000..f37bbc7ae9 --- /dev/null +++ b/app/assets/css/fa-all.min.css @@ -0,0 +1,5 @@ +/*! + * Font Awesome Free 5.11.2 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + */ +.fa,.fab,.fad,.fal,.far,.fas{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:.08em solid #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-webkit-transform:scaleY(-1);transform:scaleY(-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical,.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{-webkit-transform:scale(-1);transform:scale(-1)}:root .fa-flip-both,:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{-webkit-filter:none;filter:none}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2.5em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-500px:before{content:"\f26e"}.fa-accessible-icon:before{content:"\f368"}.fa-accusoft:before{content:"\f369"}.fa-acquisitions-incorporated:before{content:"\f6af"}.fa-ad:before{content:"\f641"}.fa-address-book:before{content:"\f2b9"}.fa-address-card:before{content:"\f2bb"}.fa-adjust:before{content:"\f042"}.fa-adn:before{content:"\f170"}.fa-adobe:before{content:"\f778"}.fa-adversal:before{content:"\f36a"}.fa-affiliatetheme:before{content:"\f36b"}.fa-air-freshener:before{content:"\f5d0"}.fa-airbnb:before{content:"\f834"}.fa-algolia:before{content:"\f36c"}.fa-align-center:before{content:"\f037"}.fa-align-justify:before{content:"\f039"}.fa-align-left:before{content:"\f036"}.fa-align-right:before{content:"\f038"}.fa-alipay:before{content:"\f642"}.fa-allergies:before{content:"\f461"}.fa-amazon:before{content:"\f270"}.fa-amazon-pay:before{content:"\f42c"}.fa-ambulance:before{content:"\f0f9"}.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-amilia:before{content:"\f36d"}.fa-anchor:before{content:"\f13d"}.fa-android:before{content:"\f17b"}.fa-angellist:before{content:"\f209"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-down:before{content:"\f107"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angry:before{content:"\f556"}.fa-angrycreative:before{content:"\f36e"}.fa-angular:before{content:"\f420"}.fa-ankh:before{content:"\f644"}.fa-app-store:before{content:"\f36f"}.fa-app-store-ios:before{content:"\f370"}.fa-apper:before{content:"\f371"}.fa-apple:before{content:"\f179"}.fa-apple-alt:before{content:"\f5d1"}.fa-apple-pay:before{content:"\f415"}.fa-archive:before{content:"\f187"}.fa-archway:before{content:"\f557"}.fa-arrow-alt-circle-down:before{content:"\f358"}.fa-arrow-alt-circle-left:before{content:"\f359"}.fa-arrow-alt-circle-right:before{content:"\f35a"}.fa-arrow-alt-circle-up:before{content:"\f35b"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-down:before{content:"\f063"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrows-alt:before{content:"\f0b2"}.fa-arrows-alt-h:before{content:"\f337"}.fa-arrows-alt-v:before{content:"\f338"}.fa-artstation:before{content:"\f77a"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asterisk:before{content:"\f069"}.fa-asymmetrik:before{content:"\f372"}.fa-at:before{content:"\f1fa"}.fa-atlas:before{content:"\f558"}.fa-atlassian:before{content:"\f77b"}.fa-atom:before{content:"\f5d2"}.fa-audible:before{content:"\f373"}.fa-audio-description:before{content:"\f29e"}.fa-autoprefixer:before{content:"\f41c"}.fa-avianex:before{content:"\f374"}.fa-aviato:before{content:"\f421"}.fa-award:before{content:"\f559"}.fa-aws:before{content:"\f375"}.fa-baby:before{content:"\f77c"}.fa-baby-carriage:before{content:"\f77d"}.fa-backspace:before{content:"\f55a"}.fa-backward:before{content:"\f04a"}.fa-bacon:before{content:"\f7e5"}.fa-balance-scale:before{content:"\f24e"}.fa-balance-scale-left:before{content:"\f515"}.fa-balance-scale-right:before{content:"\f516"}.fa-ban:before{content:"\f05e"}.fa-band-aid:before{content:"\f462"}.fa-bandcamp:before{content:"\f2d5"}.fa-barcode:before{content:"\f02a"}.fa-bars:before{content:"\f0c9"}.fa-baseball-ball:before{content:"\f433"}.fa-basketball-ball:before{content:"\f434"}.fa-bath:before{content:"\f2cd"}.fa-battery-empty:before{content:"\f244"}.fa-battery-full:before{content:"\f240"}.fa-battery-half:before{content:"\f242"}.fa-battery-quarter:before{content:"\f243"}.fa-battery-three-quarters:before{content:"\f241"}.fa-battle-net:before{content:"\f835"}.fa-bed:before{content:"\f236"}.fa-beer:before{content:"\f0fc"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-bell:before{content:"\f0f3"}.fa-bell-slash:before{content:"\f1f6"}.fa-bezier-curve:before{content:"\f55b"}.fa-bible:before{content:"\f647"}.fa-bicycle:before{content:"\f206"}.fa-biking:before{content:"\f84a"}.fa-bimobject:before{content:"\f378"}.fa-binoculars:before{content:"\f1e5"}.fa-biohazard:before{content:"\f780"}.fa-birthday-cake:before{content:"\f1fd"}.fa-bitbucket:before{content:"\f171"}.fa-bitcoin:before{content:"\f379"}.fa-bity:before{content:"\f37a"}.fa-black-tie:before{content:"\f27e"}.fa-blackberry:before{content:"\f37b"}.fa-blender:before{content:"\f517"}.fa-blender-phone:before{content:"\f6b6"}.fa-blind:before{content:"\f29d"}.fa-blog:before{content:"\f781"}.fa-blogger:before{content:"\f37c"}.fa-blogger-b:before{content:"\f37d"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-bold:before{content:"\f032"}.fa-bolt:before{content:"\f0e7"}.fa-bomb:before{content:"\f1e2"}.fa-bone:before{content:"\f5d7"}.fa-bong:before{content:"\f55c"}.fa-book:before{content:"\f02d"}.fa-book-dead:before{content:"\f6b7"}.fa-book-medical:before{content:"\f7e6"}.fa-book-open:before{content:"\f518"}.fa-book-reader:before{content:"\f5da"}.fa-bookmark:before{content:"\f02e"}.fa-bootstrap:before{content:"\f836"}.fa-border-all:before{content:"\f84c"}.fa-border-none:before{content:"\f850"}.fa-border-style:before{content:"\f853"}.fa-bowling-ball:before{content:"\f436"}.fa-box:before{content:"\f466"}.fa-box-open:before{content:"\f49e"}.fa-boxes:before{content:"\f468"}.fa-braille:before{content:"\f2a1"}.fa-brain:before{content:"\f5dc"}.fa-bread-slice:before{content:"\f7ec"}.fa-briefcase:before{content:"\f0b1"}.fa-briefcase-medical:before{content:"\f469"}.fa-broadcast-tower:before{content:"\f519"}.fa-broom:before{content:"\f51a"}.fa-brush:before{content:"\f55d"}.fa-btc:before{content:"\f15a"}.fa-buffer:before{content:"\f837"}.fa-bug:before{content:"\f188"}.fa-building:before{content:"\f1ad"}.fa-bullhorn:before{content:"\f0a1"}.fa-bullseye:before{content:"\f140"}.fa-burn:before{content:"\f46a"}.fa-buromobelexperte:before{content:"\f37f"}.fa-bus:before{content:"\f207"}.fa-bus-alt:before{content:"\f55e"}.fa-business-time:before{content:"\f64a"}.fa-buy-n-large:before{content:"\f8a6"}.fa-buysellads:before{content:"\f20d"}.fa-calculator:before{content:"\f1ec"}.fa-calendar:before{content:"\f133"}.fa-calendar-alt:before{content:"\f073"}.fa-calendar-check:before{content:"\f274"}.fa-calendar-day:before{content:"\f783"}.fa-calendar-minus:before{content:"\f272"}.fa-calendar-plus:before{content:"\f271"}.fa-calendar-times:before{content:"\f273"}.fa-calendar-week:before{content:"\f784"}.fa-camera:before{content:"\f030"}.fa-camera-retro:before{content:"\f083"}.fa-campground:before{content:"\f6bb"}.fa-canadian-maple-leaf:before{content:"\f785"}.fa-candy-cane:before{content:"\f786"}.fa-cannabis:before{content:"\f55f"}.fa-capsules:before{content:"\f46b"}.fa-car:before{content:"\f1b9"}.fa-car-alt:before{content:"\f5de"}.fa-car-battery:before{content:"\f5df"}.fa-car-crash:before{content:"\f5e1"}.fa-car-side:before{content:"\f5e4"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-caret-square-down:before{content:"\f150"}.fa-caret-square-left:before{content:"\f191"}.fa-caret-square-right:before{content:"\f152"}.fa-caret-square-up:before{content:"\f151"}.fa-caret-up:before{content:"\f0d8"}.fa-carrot:before{content:"\f787"}.fa-cart-arrow-down:before{content:"\f218"}.fa-cart-plus:before{content:"\f217"}.fa-cash-register:before{content:"\f788"}.fa-cat:before{content:"\f6be"}.fa-cc-amazon-pay:before{content:"\f42d"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-apple-pay:before{content:"\f416"}.fa-cc-diners-club:before{content:"\f24c"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-cc-visa:before{content:"\f1f0"}.fa-centercode:before{content:"\f380"}.fa-centos:before{content:"\f789"}.fa-certificate:before{content:"\f0a3"}.fa-chair:before{content:"\f6c0"}.fa-chalkboard:before{content:"\f51b"}.fa-chalkboard-teacher:before{content:"\f51c"}.fa-charging-station:before{content:"\f5e7"}.fa-chart-area:before{content:"\f1fe"}.fa-chart-bar:before{content:"\f080"}.fa-chart-line:before{content:"\f201"}.fa-chart-pie:before{content:"\f200"}.fa-check:before{content:"\f00c"}.fa-check-circle:before{content:"\f058"}.fa-check-double:before{content:"\f560"}.fa-check-square:before{content:"\f14a"}.fa-cheese:before{content:"\f7ef"}.fa-chess:before{content:"\f439"}.fa-chess-bishop:before{content:"\f43a"}.fa-chess-board:before{content:"\f43c"}.fa-chess-king:before{content:"\f43f"}.fa-chess-knight:before{content:"\f441"}.fa-chess-pawn:before{content:"\f443"}.fa-chess-queen:before{content:"\f445"}.fa-chess-rook:before{content:"\f447"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-down:before{content:"\f078"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-chevron-up:before{content:"\f077"}.fa-child:before{content:"\f1ae"}.fa-chrome:before{content:"\f268"}.fa-chromecast:before{content:"\f838"}.fa-church:before{content:"\f51d"}.fa-circle:before{content:"\f111"}.fa-circle-notch:before{content:"\f1ce"}.fa-city:before{content:"\f64f"}.fa-clinic-medical:before{content:"\f7f2"}.fa-clipboard:before{content:"\f328"}.fa-clipboard-check:before{content:"\f46c"}.fa-clipboard-list:before{content:"\f46d"}.fa-clock:before{content:"\f017"}.fa-clone:before{content:"\f24d"}.fa-closed-captioning:before{content:"\f20a"}.fa-cloud:before{content:"\f0c2"}.fa-cloud-download-alt:before{content:"\f381"}.fa-cloud-meatball:before{content:"\f73b"}.fa-cloud-moon:before{content:"\f6c3"}.fa-cloud-moon-rain:before{content:"\f73c"}.fa-cloud-rain:before{content:"\f73d"}.fa-cloud-showers-heavy:before{content:"\f740"}.fa-cloud-sun:before{content:"\f6c4"}.fa-cloud-sun-rain:before{content:"\f743"}.fa-cloud-upload-alt:before{content:"\f382"}.fa-cloudscale:before{content:"\f383"}.fa-cloudsmith:before{content:"\f384"}.fa-cloudversify:before{content:"\f385"}.fa-cocktail:before{content:"\f561"}.fa-code:before{content:"\f121"}.fa-code-branch:before{content:"\f126"}.fa-codepen:before{content:"\f1cb"}.fa-codiepie:before{content:"\f284"}.fa-coffee:before{content:"\f0f4"}.fa-cog:before{content:"\f013"}.fa-cogs:before{content:"\f085"}.fa-coins:before{content:"\f51e"}.fa-columns:before{content:"\f0db"}.fa-comment:before{content:"\f075"}.fa-comment-alt:before{content:"\f27a"}.fa-comment-dollar:before{content:"\f651"}.fa-comment-dots:before{content:"\f4ad"}.fa-comment-medical:before{content:"\f7f5"}.fa-comment-slash:before{content:"\f4b3"}.fa-comments:before{content:"\f086"}.fa-comments-dollar:before{content:"\f653"}.fa-compact-disc:before{content:"\f51f"}.fa-compass:before{content:"\f14e"}.fa-compress:before{content:"\f066"}.fa-compress-arrows-alt:before{content:"\f78c"}.fa-concierge-bell:before{content:"\f562"}.fa-confluence:before{content:"\f78d"}.fa-connectdevelop:before{content:"\f20e"}.fa-contao:before{content:"\f26d"}.fa-cookie:before{content:"\f563"}.fa-cookie-bite:before{content:"\f564"}.fa-copy:before{content:"\f0c5"}.fa-copyright:before{content:"\f1f9"}.fa-cotton-bureau:before{content:"\f89e"}.fa-couch:before{content:"\f4b8"}.fa-cpanel:before{content:"\f388"}.fa-creative-commons:before{content:"\f25e"}.fa-creative-commons-by:before{content:"\f4e7"}.fa-creative-commons-nc:before{content:"\f4e8"}.fa-creative-commons-nc-eu:before{content:"\f4e9"}.fa-creative-commons-nc-jp:before{content:"\f4ea"}.fa-creative-commons-nd:before{content:"\f4eb"}.fa-creative-commons-pd:before{content:"\f4ec"}.fa-creative-commons-pd-alt:before{content:"\f4ed"}.fa-creative-commons-remix:before{content:"\f4ee"}.fa-creative-commons-sa:before{content:"\f4ef"}.fa-creative-commons-sampling:before{content:"\f4f0"}.fa-creative-commons-sampling-plus:before{content:"\f4f1"}.fa-creative-commons-share:before{content:"\f4f2"}.fa-creative-commons-zero:before{content:"\f4f3"}.fa-credit-card:before{content:"\f09d"}.fa-critical-role:before{content:"\f6c9"}.fa-crop:before{content:"\f125"}.fa-crop-alt:before{content:"\f565"}.fa-cross:before{content:"\f654"}.fa-crosshairs:before{content:"\f05b"}.fa-crow:before{content:"\f520"}.fa-crown:before{content:"\f521"}.fa-crutch:before{content:"\f7f7"}.fa-css3:before{content:"\f13c"}.fa-css3-alt:before{content:"\f38b"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-cut:before{content:"\f0c4"}.fa-cuttlefish:before{content:"\f38c"}.fa-d-and-d:before{content:"\f38d"}.fa-d-and-d-beyond:before{content:"\f6ca"}.fa-dashcube:before{content:"\f210"}.fa-database:before{content:"\f1c0"}.fa-deaf:before{content:"\f2a4"}.fa-delicious:before{content:"\f1a5"}.fa-democrat:before{content:"\f747"}.fa-deploydog:before{content:"\f38e"}.fa-deskpro:before{content:"\f38f"}.fa-desktop:before{content:"\f108"}.fa-dev:before{content:"\f6cc"}.fa-deviantart:before{content:"\f1bd"}.fa-dharmachakra:before{content:"\f655"}.fa-dhl:before{content:"\f790"}.fa-diagnoses:before{content:"\f470"}.fa-diaspora:before{content:"\f791"}.fa-dice:before{content:"\f522"}.fa-dice-d20:before{content:"\f6cf"}.fa-dice-d6:before{content:"\f6d1"}.fa-dice-five:before{content:"\f523"}.fa-dice-four:before{content:"\f524"}.fa-dice-one:before{content:"\f525"}.fa-dice-six:before{content:"\f526"}.fa-dice-three:before{content:"\f527"}.fa-dice-two:before{content:"\f528"}.fa-digg:before{content:"\f1a6"}.fa-digital-ocean:before{content:"\f391"}.fa-digital-tachograph:before{content:"\f566"}.fa-directions:before{content:"\f5eb"}.fa-discord:before{content:"\f392"}.fa-discourse:before{content:"\f393"}.fa-divide:before{content:"\f529"}.fa-dizzy:before{content:"\f567"}.fa-dna:before{content:"\f471"}.fa-dochub:before{content:"\f394"}.fa-docker:before{content:"\f395"}.fa-dog:before{content:"\f6d3"}.fa-dollar-sign:before{content:"\f155"}.fa-dolly:before{content:"\f472"}.fa-dolly-flatbed:before{content:"\f474"}.fa-donate:before{content:"\f4b9"}.fa-door-closed:before{content:"\f52a"}.fa-door-open:before{content:"\f52b"}.fa-dot-circle:before{content:"\f192"}.fa-dove:before{content:"\f4ba"}.fa-download:before{content:"\f019"}.fa-draft2digital:before{content:"\f396"}.fa-drafting-compass:before{content:"\f568"}.fa-dragon:before{content:"\f6d5"}.fa-draw-polygon:before{content:"\f5ee"}.fa-dribbble:before{content:"\f17d"}.fa-dribbble-square:before{content:"\f397"}.fa-dropbox:before{content:"\f16b"}.fa-drum:before{content:"\f569"}.fa-drum-steelpan:before{content:"\f56a"}.fa-drumstick-bite:before{content:"\f6d7"}.fa-drupal:before{content:"\f1a9"}.fa-dumbbell:before{content:"\f44b"}.fa-dumpster:before{content:"\f793"}.fa-dumpster-fire:before{content:"\f794"}.fa-dungeon:before{content:"\f6d9"}.fa-dyalog:before{content:"\f399"}.fa-earlybirds:before{content:"\f39a"}.fa-ebay:before{content:"\f4f4"}.fa-edge:before{content:"\f282"}.fa-edit:before{content:"\f044"}.fa-egg:before{content:"\f7fb"}.fa-eject:before{content:"\f052"}.fa-elementor:before{content:"\f430"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-ello:before{content:"\f5f1"}.fa-ember:before{content:"\f423"}.fa-empire:before{content:"\f1d1"}.fa-envelope:before{content:"\f0e0"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-text:before{content:"\f658"}.fa-envelope-square:before{content:"\f199"}.fa-envira:before{content:"\f299"}.fa-equals:before{content:"\f52c"}.fa-eraser:before{content:"\f12d"}.fa-erlang:before{content:"\f39d"}.fa-ethereum:before{content:"\f42e"}.fa-ethernet:before{content:"\f796"}.fa-etsy:before{content:"\f2d7"}.fa-euro-sign:before{content:"\f153"}.fa-evernote:before{content:"\f839"}.fa-exchange-alt:before{content:"\f362"}.fa-exclamation:before{content:"\f12a"}.fa-exclamation-circle:before{content:"\f06a"}.fa-exclamation-triangle:before{content:"\f071"}.fa-expand:before{content:"\f065"}.fa-expand-arrows-alt:before{content:"\f31e"}.fa-expeditedssl:before{content:"\f23e"}.fa-external-link-alt:before{content:"\f35d"}.fa-external-link-square-alt:before{content:"\f360"}.fa-eye:before{content:"\f06e"}.fa-eye-dropper:before{content:"\f1fb"}.fa-eye-slash:before{content:"\f070"}.fa-facebook:before{content:"\f09a"}.fa-facebook-f:before{content:"\f39e"}.fa-facebook-messenger:before{content:"\f39f"}.fa-facebook-square:before{content:"\f082"}.fa-fan:before{content:"\f863"}.fa-fantasy-flight-games:before{content:"\f6dc"}.fa-fast-backward:before{content:"\f049"}.fa-fast-forward:before{content:"\f050"}.fa-fax:before{content:"\f1ac"}.fa-feather:before{content:"\f52d"}.fa-feather-alt:before{content:"\f56b"}.fa-fedex:before{content:"\f797"}.fa-fedora:before{content:"\f798"}.fa-female:before{content:"\f182"}.fa-fighter-jet:before{content:"\f0fb"}.fa-figma:before{content:"\f799"}.fa-file:before{content:"\f15b"}.fa-file-alt:before{content:"\f15c"}.fa-file-archive:before{content:"\f1c6"}.fa-file-audio:before{content:"\f1c7"}.fa-file-code:before{content:"\f1c9"}.fa-file-contract:before{content:"\f56c"}.fa-file-csv:before{content:"\f6dd"}.fa-file-download:before{content:"\f56d"}.fa-file-excel:before{content:"\f1c3"}.fa-file-export:before{content:"\f56e"}.fa-file-image:before{content:"\f1c5"}.fa-file-import:before{content:"\f56f"}.fa-file-invoice:before{content:"\f570"}.fa-file-invoice-dollar:before{content:"\f571"}.fa-file-medical:before{content:"\f477"}.fa-file-medical-alt:before{content:"\f478"}.fa-file-pdf:before{content:"\f1c1"}.fa-file-powerpoint:before{content:"\f1c4"}.fa-file-prescription:before{content:"\f572"}.fa-file-signature:before{content:"\f573"}.fa-file-upload:before{content:"\f574"}.fa-file-video:before{content:"\f1c8"}.fa-file-word:before{content:"\f1c2"}.fa-fill:before{content:"\f575"}.fa-fill-drip:before{content:"\f576"}.fa-film:before{content:"\f008"}.fa-filter:before{content:"\f0b0"}.fa-fingerprint:before{content:"\f577"}.fa-fire:before{content:"\f06d"}.fa-fire-alt:before{content:"\f7e4"}.fa-fire-extinguisher:before{content:"\f134"}.fa-firefox:before{content:"\f269"}.fa-first-aid:before{content:"\f479"}.fa-first-order:before{content:"\f2b0"}.fa-first-order-alt:before{content:"\f50a"}.fa-firstdraft:before{content:"\f3a1"}.fa-fish:before{content:"\f578"}.fa-fist-raised:before{content:"\f6de"}.fa-flag:before{content:"\f024"}.fa-flag-checkered:before{content:"\f11e"}.fa-flag-usa:before{content:"\f74d"}.fa-flask:before{content:"\f0c3"}.fa-flickr:before{content:"\f16e"}.fa-flipboard:before{content:"\f44d"}.fa-flushed:before{content:"\f579"}.fa-fly:before{content:"\f417"}.fa-folder:before{content:"\f07b"}.fa-folder-minus:before{content:"\f65d"}.fa-folder-open:before{content:"\f07c"}.fa-folder-plus:before{content:"\f65e"}.fa-font:before{content:"\f031"}.fa-font-awesome:before{content:"\f2b4"}.fa-font-awesome-alt:before{content:"\f35c"}.fa-font-awesome-flag:before{content:"\f425"}.fa-font-awesome-logo-full:before{content:"\f4e6"}.fa-fonticons:before{content:"\f280"}.fa-fonticons-fi:before{content:"\f3a2"}.fa-football-ball:before{content:"\f44e"}.fa-fort-awesome:before{content:"\f286"}.fa-fort-awesome-alt:before{content:"\f3a3"}.fa-forumbee:before{content:"\f211"}.fa-forward:before{content:"\f04e"}.fa-foursquare:before{content:"\f180"}.fa-free-code-camp:before{content:"\f2c5"}.fa-freebsd:before{content:"\f3a4"}.fa-frog:before{content:"\f52e"}.fa-frown:before{content:"\f119"}.fa-frown-open:before{content:"\f57a"}.fa-fulcrum:before{content:"\f50b"}.fa-funnel-dollar:before{content:"\f662"}.fa-futbol:before{content:"\f1e3"}.fa-galactic-republic:before{content:"\f50c"}.fa-galactic-senate:before{content:"\f50d"}.fa-gamepad:before{content:"\f11b"}.fa-gas-pump:before{content:"\f52f"}.fa-gavel:before{content:"\f0e3"}.fa-gem:before{content:"\f3a5"}.fa-genderless:before{content:"\f22d"}.fa-get-pocket:before{content:"\f265"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-ghost:before{content:"\f6e2"}.fa-gift:before{content:"\f06b"}.fa-gifts:before{content:"\f79c"}.fa-git:before{content:"\f1d3"}.fa-git-alt:before{content:"\f841"}.fa-git-square:before{content:"\f1d2"}.fa-github:before{content:"\f09b"}.fa-github-alt:before{content:"\f113"}.fa-github-square:before{content:"\f092"}.fa-gitkraken:before{content:"\f3a6"}.fa-gitlab:before{content:"\f296"}.fa-gitter:before{content:"\f426"}.fa-glass-cheers:before{content:"\f79f"}.fa-glass-martini:before{content:"\f000"}.fa-glass-martini-alt:before{content:"\f57b"}.fa-glass-whiskey:before{content:"\f7a0"}.fa-glasses:before{content:"\f530"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-globe:before{content:"\f0ac"}.fa-globe-africa:before{content:"\f57c"}.fa-globe-americas:before{content:"\f57d"}.fa-globe-asia:before{content:"\f57e"}.fa-globe-europe:before{content:"\f7a2"}.fa-gofore:before{content:"\f3a7"}.fa-golf-ball:before{content:"\f450"}.fa-goodreads:before{content:"\f3a8"}.fa-goodreads-g:before{content:"\f3a9"}.fa-google:before{content:"\f1a0"}.fa-google-drive:before{content:"\f3aa"}.fa-google-play:before{content:"\f3ab"}.fa-google-plus:before{content:"\f2b3"}.fa-google-plus-g:before{content:"\f0d5"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-wallet:before{content:"\f1ee"}.fa-gopuram:before{content:"\f664"}.fa-graduation-cap:before{content:"\f19d"}.fa-gratipay:before{content:"\f184"}.fa-grav:before{content:"\f2d6"}.fa-greater-than:before{content:"\f531"}.fa-greater-than-equal:before{content:"\f532"}.fa-grimace:before{content:"\f57f"}.fa-grin:before{content:"\f580"}.fa-grin-alt:before{content:"\f581"}.fa-grin-beam:before{content:"\f582"}.fa-grin-beam-sweat:before{content:"\f583"}.fa-grin-hearts:before{content:"\f584"}.fa-grin-squint:before{content:"\f585"}.fa-grin-squint-tears:before{content:"\f586"}.fa-grin-stars:before{content:"\f587"}.fa-grin-tears:before{content:"\f588"}.fa-grin-tongue:before{content:"\f589"}.fa-grin-tongue-squint:before{content:"\f58a"}.fa-grin-tongue-wink:before{content:"\f58b"}.fa-grin-wink:before{content:"\f58c"}.fa-grip-horizontal:before{content:"\f58d"}.fa-grip-lines:before{content:"\f7a4"}.fa-grip-lines-vertical:before{content:"\f7a5"}.fa-grip-vertical:before{content:"\f58e"}.fa-gripfire:before{content:"\f3ac"}.fa-grunt:before{content:"\f3ad"}.fa-guitar:before{content:"\f7a6"}.fa-gulp:before{content:"\f3ae"}.fa-h-square:before{content:"\f0fd"}.fa-hacker-news:before{content:"\f1d4"}.fa-hacker-news-square:before{content:"\f3af"}.fa-hackerrank:before{content:"\f5f7"}.fa-hamburger:before{content:"\f805"}.fa-hammer:before{content:"\f6e3"}.fa-hamsa:before{content:"\f665"}.fa-hand-holding:before{content:"\f4bd"}.fa-hand-holding-heart:before{content:"\f4be"}.fa-hand-holding-usd:before{content:"\f4c0"}.fa-hand-lizard:before{content:"\f258"}.fa-hand-middle-finger:before{content:"\f806"}.fa-hand-paper:before{content:"\f256"}.fa-hand-peace:before{content:"\f25b"}.fa-hand-point-down:before{content:"\f0a7"}.fa-hand-point-left:before{content:"\f0a5"}.fa-hand-point-right:before{content:"\f0a4"}.fa-hand-point-up:before{content:"\f0a6"}.fa-hand-pointer:before{content:"\f25a"}.fa-hand-rock:before{content:"\f255"}.fa-hand-scissors:before{content:"\f257"}.fa-hand-spock:before{content:"\f259"}.fa-hands:before{content:"\f4c2"}.fa-hands-helping:before{content:"\f4c4"}.fa-handshake:before{content:"\f2b5"}.fa-hanukiah:before{content:"\f6e6"}.fa-hard-hat:before{content:"\f807"}.fa-hashtag:before{content:"\f292"}.fa-hat-cowboy:before{content:"\f8c0"}.fa-hat-cowboy-side:before{content:"\f8c1"}.fa-hat-wizard:before{content:"\f6e8"}.fa-haykal:before{content:"\f666"}.fa-hdd:before{content:"\f0a0"}.fa-heading:before{content:"\f1dc"}.fa-headphones:before{content:"\f025"}.fa-headphones-alt:before{content:"\f58f"}.fa-headset:before{content:"\f590"}.fa-heart:before{content:"\f004"}.fa-heart-broken:before{content:"\f7a9"}.fa-heartbeat:before{content:"\f21e"}.fa-helicopter:before{content:"\f533"}.fa-highlighter:before{content:"\f591"}.fa-hiking:before{content:"\f6ec"}.fa-hippo:before{content:"\f6ed"}.fa-hips:before{content:"\f452"}.fa-hire-a-helper:before{content:"\f3b0"}.fa-history:before{content:"\f1da"}.fa-hockey-puck:before{content:"\f453"}.fa-holly-berry:before{content:"\f7aa"}.fa-home:before{content:"\f015"}.fa-hooli:before{content:"\f427"}.fa-hornbill:before{content:"\f592"}.fa-horse:before{content:"\f6f0"}.fa-horse-head:before{content:"\f7ab"}.fa-hospital:before{content:"\f0f8"}.fa-hospital-alt:before{content:"\f47d"}.fa-hospital-symbol:before{content:"\f47e"}.fa-hot-tub:before{content:"\f593"}.fa-hotdog:before{content:"\f80f"}.fa-hotel:before{content:"\f594"}.fa-hotjar:before{content:"\f3b1"}.fa-hourglass:before{content:"\f254"}.fa-hourglass-end:before{content:"\f253"}.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-start:before{content:"\f251"}.fa-house-damage:before{content:"\f6f1"}.fa-houzz:before{content:"\f27c"}.fa-hryvnia:before{content:"\f6f2"}.fa-html5:before{content:"\f13b"}.fa-hubspot:before{content:"\f3b2"}.fa-i-cursor:before{content:"\f246"}.fa-ice-cream:before{content:"\f810"}.fa-icicles:before{content:"\f7ad"}.fa-icons:before{content:"\f86d"}.fa-id-badge:before{content:"\f2c1"}.fa-id-card:before{content:"\f2c2"}.fa-id-card-alt:before{content:"\f47f"}.fa-igloo:before{content:"\f7ae"}.fa-image:before{content:"\f03e"}.fa-images:before{content:"\f302"}.fa-imdb:before{content:"\f2d8"}.fa-inbox:before{content:"\f01c"}.fa-indent:before{content:"\f03c"}.fa-industry:before{content:"\f275"}.fa-infinity:before{content:"\f534"}.fa-info:before{content:"\f129"}.fa-info-circle:before{content:"\f05a"}.fa-instagram:before{content:"\f16d"}.fa-intercom:before{content:"\f7af"}.fa-internet-explorer:before{content:"\f26b"}.fa-invision:before{content:"\f7b0"}.fa-ioxhost:before{content:"\f208"}.fa-italic:before{content:"\f033"}.fa-itch-io:before{content:"\f83a"}.fa-itunes:before{content:"\f3b4"}.fa-itunes-note:before{content:"\f3b5"}.fa-java:before{content:"\f4e4"}.fa-jedi:before{content:"\f669"}.fa-jedi-order:before{content:"\f50e"}.fa-jenkins:before{content:"\f3b6"}.fa-jira:before{content:"\f7b1"}.fa-joget:before{content:"\f3b7"}.fa-joint:before{content:"\f595"}.fa-joomla:before{content:"\f1aa"}.fa-journal-whills:before{content:"\f66a"}.fa-js:before{content:"\f3b8"}.fa-js-square:before{content:"\f3b9"}.fa-jsfiddle:before{content:"\f1cc"}.fa-kaaba:before{content:"\f66b"}.fa-kaggle:before{content:"\f5fa"}.fa-key:before{content:"\f084"}.fa-keybase:before{content:"\f4f5"}.fa-keyboard:before{content:"\f11c"}.fa-keycdn:before{content:"\f3ba"}.fa-khanda:before{content:"\f66d"}.fa-kickstarter:before{content:"\f3bb"}.fa-kickstarter-k:before{content:"\f3bc"}.fa-kiss:before{content:"\f596"}.fa-kiss-beam:before{content:"\f597"}.fa-kiss-wink-heart:before{content:"\f598"}.fa-kiwi-bird:before{content:"\f535"}.fa-korvue:before{content:"\f42f"}.fa-landmark:before{content:"\f66f"}.fa-language:before{content:"\f1ab"}.fa-laptop:before{content:"\f109"}.fa-laptop-code:before{content:"\f5fc"}.fa-laptop-medical:before{content:"\f812"}.fa-laravel:before{content:"\f3bd"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-laugh:before{content:"\f599"}.fa-laugh-beam:before{content:"\f59a"}.fa-laugh-squint:before{content:"\f59b"}.fa-laugh-wink:before{content:"\f59c"}.fa-layer-group:before{content:"\f5fd"}.fa-leaf:before{content:"\f06c"}.fa-leanpub:before{content:"\f212"}.fa-lemon:before{content:"\f094"}.fa-less:before{content:"\f41d"}.fa-less-than:before{content:"\f536"}.fa-less-than-equal:before{content:"\f537"}.fa-level-down-alt:before{content:"\f3be"}.fa-level-up-alt:before{content:"\f3bf"}.fa-life-ring:before{content:"\f1cd"}.fa-lightbulb:before{content:"\f0eb"}.fa-line:before{content:"\f3c0"}.fa-link:before{content:"\f0c1"}.fa-linkedin:before{content:"\f08c"}.fa-linkedin-in:before{content:"\f0e1"}.fa-linode:before{content:"\f2b8"}.fa-linux:before{content:"\f17c"}.fa-lira-sign:before{content:"\f195"}.fa-list:before{content:"\f03a"}.fa-list-alt:before{content:"\f022"}.fa-list-ol:before{content:"\f0cb"}.fa-list-ul:before{content:"\f0ca"}.fa-location-arrow:before{content:"\f124"}.fa-lock:before{content:"\f023"}.fa-lock-open:before{content:"\f3c1"}.fa-long-arrow-alt-down:before{content:"\f309"}.fa-long-arrow-alt-left:before{content:"\f30a"}.fa-long-arrow-alt-right:before{content:"\f30b"}.fa-long-arrow-alt-up:before{content:"\f30c"}.fa-low-vision:before{content:"\f2a8"}.fa-luggage-cart:before{content:"\f59d"}.fa-lyft:before{content:"\f3c3"}.fa-magento:before{content:"\f3c4"}.fa-magic:before{content:"\f0d0"}.fa-magnet:before{content:"\f076"}.fa-mail-bulk:before{content:"\f674"}.fa-mailchimp:before{content:"\f59e"}.fa-male:before{content:"\f183"}.fa-mandalorian:before{content:"\f50f"}.fa-map:before{content:"\f279"}.fa-map-marked:before{content:"\f59f"}.fa-map-marked-alt:before{content:"\f5a0"}.fa-map-marker:before{content:"\f041"}.fa-map-marker-alt:before{content:"\f3c5"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-markdown:before{content:"\f60f"}.fa-marker:before{content:"\f5a1"}.fa-mars:before{content:"\f222"}.fa-mars-double:before{content:"\f227"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mask:before{content:"\f6fa"}.fa-mastodon:before{content:"\f4f6"}.fa-maxcdn:before{content:"\f136"}.fa-mdb:before{content:"\f8ca"}.fa-medal:before{content:"\f5a2"}.fa-medapps:before{content:"\f3c6"}.fa-medium:before{content:"\f23a"}.fa-medium-m:before{content:"\f3c7"}.fa-medkit:before{content:"\f0fa"}.fa-medrt:before{content:"\f3c8"}.fa-meetup:before{content:"\f2e0"}.fa-megaport:before{content:"\f5a3"}.fa-meh:before{content:"\f11a"}.fa-meh-blank:before{content:"\f5a4"}.fa-meh-rolling-eyes:before{content:"\f5a5"}.fa-memory:before{content:"\f538"}.fa-mendeley:before{content:"\f7b3"}.fa-menorah:before{content:"\f676"}.fa-mercury:before{content:"\f223"}.fa-meteor:before{content:"\f753"}.fa-microchip:before{content:"\f2db"}.fa-microphone:before{content:"\f130"}.fa-microphone-alt:before{content:"\f3c9"}.fa-microphone-alt-slash:before{content:"\f539"}.fa-microphone-slash:before{content:"\f131"}.fa-microscope:before{content:"\f610"}.fa-microsoft:before{content:"\f3ca"}.fa-minus:before{content:"\f068"}.fa-minus-circle:before{content:"\f056"}.fa-minus-square:before{content:"\f146"}.fa-mitten:before{content:"\f7b5"}.fa-mix:before{content:"\f3cb"}.fa-mixcloud:before{content:"\f289"}.fa-mizuni:before{content:"\f3cc"}.fa-mobile:before{content:"\f10b"}.fa-mobile-alt:before{content:"\f3cd"}.fa-modx:before{content:"\f285"}.fa-monero:before{content:"\f3d0"}.fa-money-bill:before{content:"\f0d6"}.fa-money-bill-alt:before{content:"\f3d1"}.fa-money-bill-wave:before{content:"\f53a"}.fa-money-bill-wave-alt:before{content:"\f53b"}.fa-money-check:before{content:"\f53c"}.fa-money-check-alt:before{content:"\f53d"}.fa-monument:before{content:"\f5a6"}.fa-moon:before{content:"\f186"}.fa-mortar-pestle:before{content:"\f5a7"}.fa-mosque:before{content:"\f678"}.fa-motorcycle:before{content:"\f21c"}.fa-mountain:before{content:"\f6fc"}.fa-mouse:before{content:"\f8cc"}.fa-mouse-pointer:before{content:"\f245"}.fa-mug-hot:before{content:"\f7b6"}.fa-music:before{content:"\f001"}.fa-napster:before{content:"\f3d2"}.fa-neos:before{content:"\f612"}.fa-network-wired:before{content:"\f6ff"}.fa-neuter:before{content:"\f22c"}.fa-newspaper:before{content:"\f1ea"}.fa-nimblr:before{content:"\f5a8"}.fa-node:before{content:"\f419"}.fa-node-js:before{content:"\f3d3"}.fa-not-equal:before{content:"\f53e"}.fa-notes-medical:before{content:"\f481"}.fa-npm:before{content:"\f3d4"}.fa-ns8:before{content:"\f3d5"}.fa-nutritionix:before{content:"\f3d6"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-oil-can:before{content:"\f613"}.fa-old-republic:before{content:"\f510"}.fa-om:before{content:"\f679"}.fa-opencart:before{content:"\f23d"}.fa-openid:before{content:"\f19b"}.fa-opera:before{content:"\f26a"}.fa-optin-monster:before{content:"\f23c"}.fa-orcid:before{content:"\f8d2"}.fa-osi:before{content:"\f41a"}.fa-otter:before{content:"\f700"}.fa-outdent:before{content:"\f03b"}.fa-page4:before{content:"\f3d7"}.fa-pagelines:before{content:"\f18c"}.fa-pager:before{content:"\f815"}.fa-paint-brush:before{content:"\f1fc"}.fa-paint-roller:before{content:"\f5aa"}.fa-palette:before{content:"\f53f"}.fa-palfed:before{content:"\f3d8"}.fa-pallet:before{content:"\f482"}.fa-paper-plane:before{content:"\f1d8"}.fa-paperclip:before{content:"\f0c6"}.fa-parachute-box:before{content:"\f4cd"}.fa-paragraph:before{content:"\f1dd"}.fa-parking:before{content:"\f540"}.fa-passport:before{content:"\f5ab"}.fa-pastafarianism:before{content:"\f67b"}.fa-paste:before{content:"\f0ea"}.fa-patreon:before{content:"\f3d9"}.fa-pause:before{content:"\f04c"}.fa-pause-circle:before{content:"\f28b"}.fa-paw:before{content:"\f1b0"}.fa-paypal:before{content:"\f1ed"}.fa-peace:before{content:"\f67c"}.fa-pen:before{content:"\f304"}.fa-pen-alt:before{content:"\f305"}.fa-pen-fancy:before{content:"\f5ac"}.fa-pen-nib:before{content:"\f5ad"}.fa-pen-square:before{content:"\f14b"}.fa-pencil-alt:before{content:"\f303"}.fa-pencil-ruler:before{content:"\f5ae"}.fa-penny-arcade:before{content:"\f704"}.fa-people-carry:before{content:"\f4ce"}.fa-pepper-hot:before{content:"\f816"}.fa-percent:before{content:"\f295"}.fa-percentage:before{content:"\f541"}.fa-periscope:before{content:"\f3da"}.fa-person-booth:before{content:"\f756"}.fa-phabricator:before{content:"\f3db"}.fa-phoenix-framework:before{content:"\f3dc"}.fa-phoenix-squadron:before{content:"\f511"}.fa-phone:before{content:"\f095"}.fa-phone-alt:before{content:"\f879"}.fa-phone-slash:before{content:"\f3dd"}.fa-phone-square:before{content:"\f098"}.fa-phone-square-alt:before{content:"\f87b"}.fa-phone-volume:before{content:"\f2a0"}.fa-photo-video:before{content:"\f87c"}.fa-php:before{content:"\f457"}.fa-pied-piper:before{content:"\f2ae"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-pied-piper-hat:before{content:"\f4e5"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-piggy-bank:before{content:"\f4d3"}.fa-pills:before{content:"\f484"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-p:before{content:"\f231"}.fa-pinterest-square:before{content:"\f0d3"}.fa-pizza-slice:before{content:"\f818"}.fa-place-of-worship:before{content:"\f67f"}.fa-plane:before{content:"\f072"}.fa-plane-arrival:before{content:"\f5af"}.fa-plane-departure:before{content:"\f5b0"}.fa-play:before{content:"\f04b"}.fa-play-circle:before{content:"\f144"}.fa-playstation:before{content:"\f3df"}.fa-plug:before{content:"\f1e6"}.fa-plus:before{content:"\f067"}.fa-plus-circle:before{content:"\f055"}.fa-plus-square:before{content:"\f0fe"}.fa-podcast:before{content:"\f2ce"}.fa-poll:before{content:"\f681"}.fa-poll-h:before{content:"\f682"}.fa-poo:before{content:"\f2fe"}.fa-poo-storm:before{content:"\f75a"}.fa-poop:before{content:"\f619"}.fa-portrait:before{content:"\f3e0"}.fa-pound-sign:before{content:"\f154"}.fa-power-off:before{content:"\f011"}.fa-pray:before{content:"\f683"}.fa-praying-hands:before{content:"\f684"}.fa-prescription:before{content:"\f5b1"}.fa-prescription-bottle:before{content:"\f485"}.fa-prescription-bottle-alt:before{content:"\f486"}.fa-print:before{content:"\f02f"}.fa-procedures:before{content:"\f487"}.fa-product-hunt:before{content:"\f288"}.fa-project-diagram:before{content:"\f542"}.fa-pushed:before{content:"\f3e1"}.fa-puzzle-piece:before{content:"\f12e"}.fa-python:before{content:"\f3e2"}.fa-qq:before{content:"\f1d6"}.fa-qrcode:before{content:"\f029"}.fa-question:before{content:"\f128"}.fa-question-circle:before{content:"\f059"}.fa-quidditch:before{content:"\f458"}.fa-quinscape:before{content:"\f459"}.fa-quora:before{content:"\f2c4"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-quran:before{content:"\f687"}.fa-r-project:before{content:"\f4f7"}.fa-radiation:before{content:"\f7b9"}.fa-radiation-alt:before{content:"\f7ba"}.fa-rainbow:before{content:"\f75b"}.fa-random:before{content:"\f074"}.fa-raspberry-pi:before{content:"\f7bb"}.fa-ravelry:before{content:"\f2d9"}.fa-react:before{content:"\f41b"}.fa-reacteurope:before{content:"\f75d"}.fa-readme:before{content:"\f4d5"}.fa-rebel:before{content:"\f1d0"}.fa-receipt:before{content:"\f543"}.fa-record-vinyl:before{content:"\f8d9"}.fa-recycle:before{content:"\f1b8"}.fa-red-river:before{content:"\f3e3"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-alien:before{content:"\f281"}.fa-reddit-square:before{content:"\f1a2"}.fa-redhat:before{content:"\f7bc"}.fa-redo:before{content:"\f01e"}.fa-redo-alt:before{content:"\f2f9"}.fa-registered:before{content:"\f25d"}.fa-remove-format:before{content:"\f87d"}.fa-renren:before{content:"\f18b"}.fa-reply:before{content:"\f3e5"}.fa-reply-all:before{content:"\f122"}.fa-replyd:before{content:"\f3e6"}.fa-republican:before{content:"\f75e"}.fa-researchgate:before{content:"\f4f8"}.fa-resolving:before{content:"\f3e7"}.fa-restroom:before{content:"\f7bd"}.fa-retweet:before{content:"\f079"}.fa-rev:before{content:"\f5b2"}.fa-ribbon:before{content:"\f4d6"}.fa-ring:before{content:"\f70b"}.fa-road:before{content:"\f018"}.fa-robot:before{content:"\f544"}.fa-rocket:before{content:"\f135"}.fa-rocketchat:before{content:"\f3e8"}.fa-rockrms:before{content:"\f3e9"}.fa-route:before{content:"\f4d7"}.fa-rss:before{content:"\f09e"}.fa-rss-square:before{content:"\f143"}.fa-ruble-sign:before{content:"\f158"}.fa-ruler:before{content:"\f545"}.fa-ruler-combined:before{content:"\f546"}.fa-ruler-horizontal:before{content:"\f547"}.fa-ruler-vertical:before{content:"\f548"}.fa-running:before{content:"\f70c"}.fa-rupee-sign:before{content:"\f156"}.fa-sad-cry:before{content:"\f5b3"}.fa-sad-tear:before{content:"\f5b4"}.fa-safari:before{content:"\f267"}.fa-salesforce:before{content:"\f83b"}.fa-sass:before{content:"\f41e"}.fa-satellite:before{content:"\f7bf"}.fa-satellite-dish:before{content:"\f7c0"}.fa-save:before{content:"\f0c7"}.fa-schlix:before{content:"\f3ea"}.fa-school:before{content:"\f549"}.fa-screwdriver:before{content:"\f54a"}.fa-scribd:before{content:"\f28a"}.fa-scroll:before{content:"\f70e"}.fa-sd-card:before{content:"\f7c2"}.fa-search:before{content:"\f002"}.fa-search-dollar:before{content:"\f688"}.fa-search-location:before{content:"\f689"}.fa-search-minus:before{content:"\f010"}.fa-search-plus:before{content:"\f00e"}.fa-searchengin:before{content:"\f3eb"}.fa-seedling:before{content:"\f4d8"}.fa-sellcast:before{content:"\f2da"}.fa-sellsy:before{content:"\f213"}.fa-server:before{content:"\f233"}.fa-servicestack:before{content:"\f3ec"}.fa-shapes:before{content:"\f61f"}.fa-share:before{content:"\f064"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-share-square:before{content:"\f14d"}.fa-shekel-sign:before{content:"\f20b"}.fa-shield-alt:before{content:"\f3ed"}.fa-ship:before{content:"\f21a"}.fa-shipping-fast:before{content:"\f48b"}.fa-shirtsinbulk:before{content:"\f214"}.fa-shoe-prints:before{content:"\f54b"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-shopping-cart:before{content:"\f07a"}.fa-shopware:before{content:"\f5b5"}.fa-shower:before{content:"\f2cc"}.fa-shuttle-van:before{content:"\f5b6"}.fa-sign:before{content:"\f4d9"}.fa-sign-in-alt:before{content:"\f2f6"}.fa-sign-language:before{content:"\f2a7"}.fa-sign-out-alt:before{content:"\f2f5"}.fa-signal:before{content:"\f012"}.fa-signature:before{content:"\f5b7"}.fa-sim-card:before{content:"\f7c4"}.fa-simplybuilt:before{content:"\f215"}.fa-sistrix:before{content:"\f3ee"}.fa-sitemap:before{content:"\f0e8"}.fa-sith:before{content:"\f512"}.fa-skating:before{content:"\f7c5"}.fa-sketch:before{content:"\f7c6"}.fa-skiing:before{content:"\f7c9"}.fa-skiing-nordic:before{content:"\f7ca"}.fa-skull:before{content:"\f54c"}.fa-skull-crossbones:before{content:"\f714"}.fa-skyatlas:before{content:"\f216"}.fa-skype:before{content:"\f17e"}.fa-slack:before{content:"\f198"}.fa-slack-hash:before{content:"\f3ef"}.fa-slash:before{content:"\f715"}.fa-sleigh:before{content:"\f7cc"}.fa-sliders-h:before{content:"\f1de"}.fa-slideshare:before{content:"\f1e7"}.fa-smile:before{content:"\f118"}.fa-smile-beam:before{content:"\f5b8"}.fa-smile-wink:before{content:"\f4da"}.fa-smog:before{content:"\f75f"}.fa-smoking:before{content:"\f48d"}.fa-smoking-ban:before{content:"\f54d"}.fa-sms:before{content:"\f7cd"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-snowboarding:before{content:"\f7ce"}.fa-snowflake:before{content:"\f2dc"}.fa-snowman:before{content:"\f7d0"}.fa-snowplow:before{content:"\f7d2"}.fa-socks:before{content:"\f696"}.fa-solar-panel:before{content:"\f5ba"}.fa-sort:before{content:"\f0dc"}.fa-sort-alpha-down:before{content:"\f15d"}.fa-sort-alpha-down-alt:before{content:"\f881"}.fa-sort-alpha-up:before{content:"\f15e"}.fa-sort-alpha-up-alt:before{content:"\f882"}.fa-sort-amount-down:before{content:"\f160"}.fa-sort-amount-down-alt:before{content:"\f884"}.fa-sort-amount-up:before{content:"\f161"}.fa-sort-amount-up-alt:before{content:"\f885"}.fa-sort-down:before{content:"\f0dd"}.fa-sort-numeric-down:before{content:"\f162"}.fa-sort-numeric-down-alt:before{content:"\f886"}.fa-sort-numeric-up:before{content:"\f163"}.fa-sort-numeric-up-alt:before{content:"\f887"}.fa-sort-up:before{content:"\f0de"}.fa-soundcloud:before{content:"\f1be"}.fa-sourcetree:before{content:"\f7d3"}.fa-spa:before{content:"\f5bb"}.fa-space-shuttle:before{content:"\f197"}.fa-speakap:before{content:"\f3f3"}.fa-speaker-deck:before{content:"\f83c"}.fa-spell-check:before{content:"\f891"}.fa-spider:before{content:"\f717"}.fa-spinner:before{content:"\f110"}.fa-splotch:before{content:"\f5bc"}.fa-spotify:before{content:"\f1bc"}.fa-spray-can:before{content:"\f5bd"}.fa-square:before{content:"\f0c8"}.fa-square-full:before{content:"\f45c"}.fa-square-root-alt:before{content:"\f698"}.fa-squarespace:before{content:"\f5be"}.fa-stack-exchange:before{content:"\f18d"}.fa-stack-overflow:before{content:"\f16c"}.fa-stackpath:before{content:"\f842"}.fa-stamp:before{content:"\f5bf"}.fa-star:before{content:"\f005"}.fa-star-and-crescent:before{content:"\f699"}.fa-star-half:before{content:"\f089"}.fa-star-half-alt:before{content:"\f5c0"}.fa-star-of-david:before{content:"\f69a"}.fa-star-of-life:before{content:"\f621"}.fa-staylinked:before{content:"\f3f5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-steam-symbol:before{content:"\f3f6"}.fa-step-backward:before{content:"\f048"}.fa-step-forward:before{content:"\f051"}.fa-stethoscope:before{content:"\f0f1"}.fa-sticker-mule:before{content:"\f3f7"}.fa-sticky-note:before{content:"\f249"}.fa-stop:before{content:"\f04d"}.fa-stop-circle:before{content:"\f28d"}.fa-stopwatch:before{content:"\f2f2"}.fa-store:before{content:"\f54e"}.fa-store-alt:before{content:"\f54f"}.fa-strava:before{content:"\f428"}.fa-stream:before{content:"\f550"}.fa-street-view:before{content:"\f21d"}.fa-strikethrough:before{content:"\f0cc"}.fa-stripe:before{content:"\f429"}.fa-stripe-s:before{content:"\f42a"}.fa-stroopwafel:before{content:"\f551"}.fa-studiovinari:before{content:"\f3f8"}.fa-stumbleupon:before{content:"\f1a4"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-subscript:before{content:"\f12c"}.fa-subway:before{content:"\f239"}.fa-suitcase:before{content:"\f0f2"}.fa-suitcase-rolling:before{content:"\f5c1"}.fa-sun:before{content:"\f185"}.fa-superpowers:before{content:"\f2dd"}.fa-superscript:before{content:"\f12b"}.fa-supple:before{content:"\f3f9"}.fa-surprise:before{content:"\f5c2"}.fa-suse:before{content:"\f7d6"}.fa-swatchbook:before{content:"\f5c3"}.fa-swift:before{content:"\f8e1"}.fa-swimmer:before{content:"\f5c4"}.fa-swimming-pool:before{content:"\f5c5"}.fa-symfony:before{content:"\f83d"}.fa-synagogue:before{content:"\f69b"}.fa-sync:before{content:"\f021"}.fa-sync-alt:before{content:"\f2f1"}.fa-syringe:before{content:"\f48e"}.fa-table:before{content:"\f0ce"}.fa-table-tennis:before{content:"\f45d"}.fa-tablet:before{content:"\f10a"}.fa-tablet-alt:before{content:"\f3fa"}.fa-tablets:before{content:"\f490"}.fa-tachometer-alt:before{content:"\f3fd"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-tape:before{content:"\f4db"}.fa-tasks:before{content:"\f0ae"}.fa-taxi:before{content:"\f1ba"}.fa-teamspeak:before{content:"\f4f9"}.fa-teeth:before{content:"\f62e"}.fa-teeth-open:before{content:"\f62f"}.fa-telegram:before{content:"\f2c6"}.fa-telegram-plane:before{content:"\f3fe"}.fa-temperature-high:before{content:"\f769"}.fa-temperature-low:before{content:"\f76b"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-tenge:before{content:"\f7d7"}.fa-terminal:before{content:"\f120"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-th:before{content:"\f00a"}.fa-th-large:before{content:"\f009"}.fa-th-list:before{content:"\f00b"}.fa-the-red-yeti:before{content:"\f69d"}.fa-theater-masks:before{content:"\f630"}.fa-themeco:before{content:"\f5c6"}.fa-themeisle:before{content:"\f2b2"}.fa-thermometer:before{content:"\f491"}.fa-thermometer-empty:before{content:"\f2cb"}.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-think-peaks:before{content:"\f731"}.fa-thumbs-down:before{content:"\f165"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbtack:before{content:"\f08d"}.fa-ticket-alt:before{content:"\f3ff"}.fa-times:before{content:"\f00d"}.fa-times-circle:before{content:"\f057"}.fa-tint:before{content:"\f043"}.fa-tint-slash:before{content:"\f5c7"}.fa-tired:before{content:"\f5c8"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-toilet:before{content:"\f7d8"}.fa-toilet-paper:before{content:"\f71e"}.fa-toolbox:before{content:"\f552"}.fa-tools:before{content:"\f7d9"}.fa-tooth:before{content:"\f5c9"}.fa-torah:before{content:"\f6a0"}.fa-torii-gate:before{content:"\f6a1"}.fa-tractor:before{content:"\f722"}.fa-trade-federation:before{content:"\f513"}.fa-trademark:before{content:"\f25c"}.fa-traffic-light:before{content:"\f637"}.fa-train:before{content:"\f238"}.fa-tram:before{content:"\f7da"}.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-trash:before{content:"\f1f8"}.fa-trash-alt:before{content:"\f2ed"}.fa-trash-restore:before{content:"\f829"}.fa-trash-restore-alt:before{content:"\f82a"}.fa-tree:before{content:"\f1bb"}.fa-trello:before{content:"\f181"}.fa-tripadvisor:before{content:"\f262"}.fa-trophy:before{content:"\f091"}.fa-truck:before{content:"\f0d1"}.fa-truck-loading:before{content:"\f4de"}.fa-truck-monster:before{content:"\f63b"}.fa-truck-moving:before{content:"\f4df"}.fa-truck-pickup:before{content:"\f63c"}.fa-tshirt:before{content:"\f553"}.fa-tty:before{content:"\f1e4"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-tv:before{content:"\f26c"}.fa-twitch:before{content:"\f1e8"}.fa-twitter:before{content:"\f099"}.fa-twitter-square:before{content:"\f081"}.fa-typo3:before{content:"\f42b"}.fa-uber:before{content:"\f402"}.fa-ubuntu:before{content:"\f7df"}.fa-uikit:before{content:"\f403"}.fa-umbraco:before{content:"\f8e8"}.fa-umbrella:before{content:"\f0e9"}.fa-umbrella-beach:before{content:"\f5ca"}.fa-underline:before{content:"\f0cd"}.fa-undo:before{content:"\f0e2"}.fa-undo-alt:before{content:"\f2ea"}.fa-uniregistry:before{content:"\f404"}.fa-universal-access:before{content:"\f29a"}.fa-university:before{content:"\f19c"}.fa-unlink:before{content:"\f127"}.fa-unlock:before{content:"\f09c"}.fa-unlock-alt:before{content:"\f13e"}.fa-untappd:before{content:"\f405"}.fa-upload:before{content:"\f093"}.fa-ups:before{content:"\f7e0"}.fa-usb:before{content:"\f287"}.fa-user:before{content:"\f007"}.fa-user-alt:before{content:"\f406"}.fa-user-alt-slash:before{content:"\f4fa"}.fa-user-astronaut:before{content:"\f4fb"}.fa-user-check:before{content:"\f4fc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-clock:before{content:"\f4fd"}.fa-user-cog:before{content:"\f4fe"}.fa-user-edit:before{content:"\f4ff"}.fa-user-friends:before{content:"\f500"}.fa-user-graduate:before{content:"\f501"}.fa-user-injured:before{content:"\f728"}.fa-user-lock:before{content:"\f502"}.fa-user-md:before{content:"\f0f0"}.fa-user-minus:before{content:"\f503"}.fa-user-ninja:before{content:"\f504"}.fa-user-nurse:before{content:"\f82f"}.fa-user-plus:before{content:"\f234"}.fa-user-secret:before{content:"\f21b"}.fa-user-shield:before{content:"\f505"}.fa-user-slash:before{content:"\f506"}.fa-user-tag:before{content:"\f507"}.fa-user-tie:before{content:"\f508"}.fa-user-times:before{content:"\f235"}.fa-users:before{content:"\f0c0"}.fa-users-cog:before{content:"\f509"}.fa-usps:before{content:"\f7e1"}.fa-ussunnah:before{content:"\f407"}.fa-utensil-spoon:before{content:"\f2e5"}.fa-utensils:before{content:"\f2e7"}.fa-vaadin:before{content:"\f408"}.fa-vector-square:before{content:"\f5cb"}.fa-venus:before{content:"\f221"}.fa-venus-double:before{content:"\f226"}.fa-venus-mars:before{content:"\f228"}.fa-viacoin:before{content:"\f237"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-vial:before{content:"\f492"}.fa-vials:before{content:"\f493"}.fa-viber:before{content:"\f409"}.fa-video:before{content:"\f03d"}.fa-video-slash:before{content:"\f4e2"}.fa-vihara:before{content:"\f6a7"}.fa-vimeo:before{content:"\f40a"}.fa-vimeo-square:before{content:"\f194"}.fa-vimeo-v:before{content:"\f27d"}.fa-vine:before{content:"\f1ca"}.fa-vk:before{content:"\f189"}.fa-vnv:before{content:"\f40b"}.fa-voicemail:before{content:"\f897"}.fa-volleyball-ball:before{content:"\f45f"}.fa-volume-down:before{content:"\f027"}.fa-volume-mute:before{content:"\f6a9"}.fa-volume-off:before{content:"\f026"}.fa-volume-up:before{content:"\f028"}.fa-vote-yea:before{content:"\f772"}.fa-vr-cardboard:before{content:"\f729"}.fa-vuejs:before{content:"\f41f"}.fa-walking:before{content:"\f554"}.fa-wallet:before{content:"\f555"}.fa-warehouse:before{content:"\f494"}.fa-water:before{content:"\f773"}.fa-wave-square:before{content:"\f83e"}.fa-waze:before{content:"\f83f"}.fa-weebly:before{content:"\f5cc"}.fa-weibo:before{content:"\f18a"}.fa-weight:before{content:"\f496"}.fa-weight-hanging:before{content:"\f5cd"}.fa-weixin:before{content:"\f1d7"}.fa-whatsapp:before{content:"\f232"}.fa-whatsapp-square:before{content:"\f40c"}.fa-wheelchair:before{content:"\f193"}.fa-whmcs:before{content:"\f40d"}.fa-wifi:before{content:"\f1eb"}.fa-wikipedia-w:before{content:"\f266"}.fa-wind:before{content:"\f72e"}.fa-window-close:before{content:"\f410"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-windows:before{content:"\f17a"}.fa-wine-bottle:before{content:"\f72f"}.fa-wine-glass:before{content:"\f4e3"}.fa-wine-glass-alt:before{content:"\f5ce"}.fa-wix:before{content:"\f5cf"}.fa-wizards-of-the-coast:before{content:"\f730"}.fa-wolf-pack-battalion:before{content:"\f514"}.fa-won-sign:before{content:"\f159"}.fa-wordpress:before{content:"\f19a"}.fa-wordpress-simple:before{content:"\f411"}.fa-wpbeginner:before{content:"\f297"}.fa-wpexplorer:before{content:"\f2de"}.fa-wpforms:before{content:"\f298"}.fa-wpressr:before{content:"\f3e4"}.fa-wrench:before{content:"\f0ad"}.fa-x-ray:before{content:"\f497"}.fa-xbox:before{content:"\f412"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-y-combinator:before{content:"\f23b"}.fa-yahoo:before{content:"\f19e"}.fa-yammer:before{content:"\f840"}.fa-yandex:before{content:"\f413"}.fa-yandex-international:before{content:"\f414"}.fa-yarn:before{content:"\f7e3"}.fa-yelp:before{content:"\f1e9"}.fa-yen-sign:before{content:"\f157"}.fa-yin-yang:before{content:"\f6ad"}.fa-yoast:before{content:"\f2b1"}.fa-youtube:before{content:"\f167"}.fa-youtube-square:before{content:"\f431"}.fa-zhihu:before{content:"\f63f"}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}@font-face{font-family:"Font Awesome 5 Brands";font-style:normal;font-weight:normal;font-display:auto;src:url(../webfonts/fa-brands-400.eot);src:url(../webfonts/fa-brands-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.woff) format("woff"),url(../webfonts/fa-brands-400.ttf) format("truetype"),url(../webfonts/fa-brands-400.svg#fontawesome) format("svg")}.fab{font-family:"Font Awesome 5 Brands"}@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:400;font-display:auto;src:url(../webfonts/fa-regular-400.eot);src:url(../webfonts/fa-regular-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.woff) format("woff"),url(../webfonts/fa-regular-400.ttf) format("truetype"),url(../webfonts/fa-regular-400.svg#fontawesome) format("svg")}.far{font-weight:400}@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:900;font-display:auto;src:url(../webfonts/fa-solid-900.eot);src:url(../webfonts/fa-solid-900.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.woff) format("woff"),url(../webfonts/fa-solid-900.ttf) format("truetype"),url(../webfonts/fa-solid-900.svg#fontawesome) format("svg")}.fa,.far,.fas{font-family:"Font Awesome 5 Free"}.fa,.fas{font-weight:900} \ No newline at end of file diff --git a/app/assets/fonts/fa-brands-400.eot b/app/assets/fonts/fa-brands-400.eot index 1a675a4f72..f6b7c7dc13 100644 Binary files a/app/assets/fonts/fa-brands-400.eot and b/app/assets/fonts/fa-brands-400.eot differ diff --git a/app/assets/fonts/fa-brands-400.svg b/app/assets/fonts/fa-brands-400.svg index 5b60e84a2c..cf4d7ceb1d 100644 --- a/app/assets/fonts/fa-brands-400.svg +++ b/app/assets/fonts/fa-brands-400.svg @@ -1,1184 +1,3496 @@ - + + - + + +Created by FontForge 20190801 at Mon Sep 23 12:53:49 2019 + By Robert Madole +Copyright (c) Font Awesome + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + diff --git a/app/assets/fonts/fa-brands-400.ttf b/app/assets/fonts/fa-brands-400.ttf index 953d567b28..82e255ac57 100644 Binary files a/app/assets/fonts/fa-brands-400.ttf and b/app/assets/fonts/fa-brands-400.ttf differ diff --git a/app/assets/fonts/fa-brands-400.woff b/app/assets/fonts/fa-brands-400.woff index 1ae526344b..b0021db2eb 100644 Binary files a/app/assets/fonts/fa-brands-400.woff and b/app/assets/fonts/fa-brands-400.woff differ diff --git a/app/assets/fonts/fa-brands-400.woff2 b/app/assets/fonts/fa-brands-400.woff2 index 4a07e4075a..b53cbbf562 100644 Binary files a/app/assets/fonts/fa-brands-400.woff2 and b/app/assets/fonts/fa-brands-400.woff2 differ diff --git a/app/assets/fonts/fa-regular-400.eot b/app/assets/fonts/fa-regular-400.eot index db3ed40646..e1bcc447d3 100644 Binary files a/app/assets/fonts/fa-regular-400.eot and b/app/assets/fonts/fa-regular-400.eot differ diff --git a/app/assets/fonts/fa-regular-400.svg b/app/assets/fonts/fa-regular-400.svg index cf3d0653c1..f32e41e305 100644 --- a/app/assets/fonts/fa-regular-400.svg +++ b/app/assets/fonts/fa-regular-400.svg @@ -1,467 +1,803 @@ - + + - + + +Created by FontForge 20190801 at Mon Sep 23 12:53:49 2019 + By Robert Madole +Copyright (c) Font Awesome + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + diff --git a/app/assets/fonts/fa-regular-400.ttf b/app/assets/fonts/fa-regular-400.ttf index 235101c2d7..5267d851bf 100644 Binary files a/app/assets/fonts/fa-regular-400.ttf and b/app/assets/fonts/fa-regular-400.ttf differ diff --git a/app/assets/fonts/fa-regular-400.woff b/app/assets/fonts/fa-regular-400.woff index 9058e2912d..f513c1c3c2 100644 Binary files a/app/assets/fonts/fa-regular-400.woff and b/app/assets/fonts/fa-regular-400.woff differ diff --git a/app/assets/fonts/fa-regular-400.woff2 b/app/assets/fonts/fa-regular-400.woff2 index 1489f649d5..2e72e872c6 100644 Binary files a/app/assets/fonts/fa-regular-400.woff2 and b/app/assets/fonts/fa-regular-400.woff2 differ diff --git a/app/assets/fonts/fa-solid-900.eot b/app/assets/fonts/fa-solid-900.eot index cb8d3f0747..c867e7ebb5 100644 Binary files a/app/assets/fonts/fa-solid-900.eot and b/app/assets/fonts/fa-solid-900.eot differ diff --git a/app/assets/fonts/fa-solid-900.svg b/app/assets/fonts/fa-solid-900.svg index bd7565a1b6..401b7f7b32 100644 --- a/app/assets/fonts/fa-solid-900.svg +++ b/app/assets/fonts/fa-solid-900.svg @@ -1,2618 +1,4667 @@ - + + - + + +Created by FontForge 20190801 at Mon Sep 23 12:53:50 2019 + By Robert Madole +Copyright (c) Font Awesome + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + diff --git a/app/assets/fonts/fa-solid-900.ttf b/app/assets/fonts/fa-solid-900.ttf index 7c92e986f2..16d4469b02 100644 Binary files a/app/assets/fonts/fa-solid-900.ttf and b/app/assets/fonts/fa-solid-900.ttf differ diff --git a/app/assets/fonts/fa-solid-900.woff b/app/assets/fonts/fa-solid-900.woff index b7d52cf242..608f9e1b03 100644 Binary files a/app/assets/fonts/fa-solid-900.woff and b/app/assets/fonts/fa-solid-900.woff differ diff --git a/app/assets/fonts/fa-solid-900.woff2 b/app/assets/fonts/fa-solid-900.woff2 index 59d92b2b0e..5a60d47c11 100644 Binary files a/app/assets/fonts/fa-solid-900.woff2 and b/app/assets/fonts/fa-solid-900.woff2 differ diff --git a/app/assets/frontends/beaker-code-snippet/.beaker-ui b/app/assets/frontends/beaker-code-snippet/.beaker-ui new file mode 100644 index 0000000000..fa1b907e6f --- /dev/null +++ b/app/assets/frontends/beaker-code-snippet/.beaker-ui @@ -0,0 +1 @@ +builtin:beaker-code-snippet \ No newline at end of file diff --git a/app/assets/frontends/beaker-code-snippet/ace/ace.js b/app/assets/frontends/beaker-code-snippet/ace/ace.js new file mode 100644 index 0000000000..3393275781 --- /dev/null +++ b/app/assets/frontends/beaker-code-snippet/ace/ace.js @@ -0,0 +1,16 @@ +(function(){function o(n){var i=e;n&&(e[n]||(e[n]={}),i=e[n]);if(!i.define||!i.define.packaged)t.original=i.define,i.define=t,i.define.packaged=!0;if(!i.require||!i.require.packaged)r.original=i.require,i.require=r,i.require.packaged=!0}var ACE_NAMESPACE="",e=function(){return this}();!e&&typeof window!="undefined"&&(e=window);if(!ACE_NAMESPACE&&typeof requirejs!="undefined")return;var t=function(e,n,r){if(typeof e!="string"){t.original?t.original.apply(this,arguments):(console.error("dropping module because define wasn't a string."),console.trace());return}arguments.length==2&&(r=n),t.modules[e]||(t.payloads[e]=r,t.modules[e]=null)};t.modules={},t.payloads={};var n=function(e,t,n){if(typeof t=="string"){var i=s(e,t);if(i!=undefined)return n&&n(),i}else if(Object.prototype.toString.call(t)==="[object Array]"){var o=[];for(var u=0,a=t.length;u1&&u(t,"")>-1&&(a=RegExp(this.source,r.replace.call(o(this),"g","")),r.replace.call(e.slice(t.index),a,function(){for(var e=1;et.index&&this.lastIndex--}return t},s||(RegExp.prototype.test=function(e){var t=r.exec.call(this,e);return t&&this.global&&!t[0].length&&this.lastIndex>t.index&&this.lastIndex--,!!t})}),define("ace/lib/es5-shim",["require","exports","module"],function(e,t,n){function r(){}function w(e){try{return Object.defineProperty(e,"sentinel",{}),"sentinel"in e}catch(t){}}function H(e){return e=+e,e!==e?e=0:e!==0&&e!==1/0&&e!==-1/0&&(e=(e>0||-1)*Math.floor(Math.abs(e))),e}function B(e){var t=typeof e;return e===null||t==="undefined"||t==="boolean"||t==="number"||t==="string"}function j(e){var t,n,r;if(B(e))return e;n=e.valueOf;if(typeof n=="function"){t=n.call(e);if(B(t))return t}r=e.toString;if(typeof r=="function"){t=r.call(e);if(B(t))return t}throw new TypeError}Function.prototype.bind||(Function.prototype.bind=function(t){var n=this;if(typeof n!="function")throw new TypeError("Function.prototype.bind called on incompatible "+n);var i=u.call(arguments,1),s=function(){if(this instanceof s){var e=n.apply(this,i.concat(u.call(arguments)));return Object(e)===e?e:this}return n.apply(t,i.concat(u.call(arguments)))};return n.prototype&&(r.prototype=n.prototype,s.prototype=new r,r.prototype=null),s});var i=Function.prototype.call,s=Array.prototype,o=Object.prototype,u=s.slice,a=i.bind(o.toString),f=i.bind(o.hasOwnProperty),l,c,h,p,d;if(d=f(o,"__defineGetter__"))l=i.bind(o.__defineGetter__),c=i.bind(o.__defineSetter__),h=i.bind(o.__lookupGetter__),p=i.bind(o.__lookupSetter__);if([1,2].splice(0).length!=2)if(!function(){function e(e){var t=new Array(e+2);return t[0]=t[1]=0,t}var t=[],n;t.splice.apply(t,e(20)),t.splice.apply(t,e(26)),n=t.length,t.splice(5,0,"XXX"),n+1==t.length;if(n+1==t.length)return!0}())Array.prototype.splice=function(e,t){var n=this.length;e>0?e>n&&(e=n):e==void 0?e=0:e<0&&(e=Math.max(n+e,0)),e+ta)for(h=l;h--;)this[f+h]=this[a+h];if(s&&e===c)this.length=c,this.push.apply(this,i);else{this.length=c+s;for(h=0;h>>0;if(a(t)!="[object Function]")throw new TypeError;while(++s>>0,s=Array(i),o=arguments[1];if(a(t)!="[object Function]")throw new TypeError(t+" is not a function");for(var u=0;u>>0,s=[],o,u=arguments[1];if(a(t)!="[object Function]")throw new TypeError(t+" is not a function");for(var f=0;f>>0,s=arguments[1];if(a(t)!="[object Function]")throw new TypeError(t+" is not a function");for(var o=0;o>>0,s=arguments[1];if(a(t)!="[object Function]")throw new TypeError(t+" is not a function");for(var o=0;o>>0;if(a(t)!="[object Function]")throw new TypeError(t+" is not a function");if(!i&&arguments.length==1)throw new TypeError("reduce of empty array with no initial value");var s=0,o;if(arguments.length>=2)o=arguments[1];else do{if(s in r){o=r[s++];break}if(++s>=i)throw new TypeError("reduce of empty array with no initial value")}while(!0);for(;s>>0;if(a(t)!="[object Function]")throw new TypeError(t+" is not a function");if(!i&&arguments.length==1)throw new TypeError("reduceRight of empty array with no initial value");var s,o=i-1;if(arguments.length>=2)s=arguments[1];else do{if(o in r){s=r[o--];break}if(--o<0)throw new TypeError("reduceRight of empty array with no initial value")}while(!0);do o in this&&(s=t.call(void 0,s,r[o],o,n));while(o--);return s});if(!Array.prototype.indexOf||[0,1].indexOf(1,2)!=-1)Array.prototype.indexOf=function(t){var n=g&&a(this)=="[object String]"?this.split(""):F(this),r=n.length>>>0;if(!r)return-1;var i=0;arguments.length>1&&(i=H(arguments[1])),i=i>=0?i:Math.max(0,r+i);for(;i>>0;if(!r)return-1;var i=r-1;arguments.length>1&&(i=Math.min(i,H(arguments[1]))),i=i>=0?i:r-Math.abs(i);for(;i>=0;i--)if(i in n&&t===n[i])return i;return-1};Object.getPrototypeOf||(Object.getPrototypeOf=function(t){return t.__proto__||(t.constructor?t.constructor.prototype:o)});if(!Object.getOwnPropertyDescriptor){var y="Object.getOwnPropertyDescriptor called on a non-object: ";Object.getOwnPropertyDescriptor=function(t,n){if(typeof t!="object"&&typeof t!="function"||t===null)throw new TypeError(y+t);if(!f(t,n))return;var r,i,s;r={enumerable:!0,configurable:!0};if(d){var u=t.__proto__;t.__proto__=o;var i=h(t,n),s=p(t,n);t.__proto__=u;if(i||s)return i&&(r.get=i),s&&(r.set=s),r}return r.value=t[n],r}}Object.getOwnPropertyNames||(Object.getOwnPropertyNames=function(t){return Object.keys(t)});if(!Object.create){var b;Object.prototype.__proto__===null?b=function(){return{__proto__:null}}:b=function(){var e={};for(var t in e)e[t]=null;return e.constructor=e.hasOwnProperty=e.propertyIsEnumerable=e.isPrototypeOf=e.toLocaleString=e.toString=e.valueOf=e.__proto__=null,e},Object.create=function(t,n){var r;if(t===null)r=b();else{if(typeof t!="object")throw new TypeError("typeof prototype["+typeof t+"] != 'object'");var i=function(){};i.prototype=t,r=new i,r.__proto__=t}return n!==void 0&&Object.defineProperties(r,n),r}}if(Object.defineProperty){var E=w({}),S=typeof document=="undefined"||w(document.createElement("div"));if(!E||!S)var x=Object.defineProperty}if(!Object.defineProperty||x){var T="Property description must be an object: ",N="Object.defineProperty called on non-object: ",C="getters & setters can not be defined on this javascript engine";Object.defineProperty=function(t,n,r){if(typeof t!="object"&&typeof t!="function"||t===null)throw new TypeError(N+t);if(typeof r!="object"&&typeof r!="function"||r===null)throw new TypeError(T+r);if(x)try{return x.call(Object,t,n,r)}catch(i){}if(f(r,"value"))if(d&&(h(t,n)||p(t,n))){var s=t.__proto__;t.__proto__=o,delete t[n],t[n]=r.value,t.__proto__=s}else t[n]=r.value;else{if(!d)throw new TypeError(C);f(r,"get")&&l(t,n,r.get),f(r,"set")&&c(t,n,r.set)}return t}}Object.defineProperties||(Object.defineProperties=function(t,n){for(var r in n)f(n,r)&&Object.defineProperty(t,r,n[r]);return t}),Object.seal||(Object.seal=function(t){return t}),Object.freeze||(Object.freeze=function(t){return t});try{Object.freeze(function(){})}catch(k){Object.freeze=function(t){return function(n){return typeof n=="function"?n:t(n)}}(Object.freeze)}Object.preventExtensions||(Object.preventExtensions=function(t){return t}),Object.isSealed||(Object.isSealed=function(t){return!1}),Object.isFrozen||(Object.isFrozen=function(t){return!1}),Object.isExtensible||(Object.isExtensible=function(t){if(Object(t)===t)throw new TypeError;var n="";while(f(t,n))n+="?";t[n]=!0;var r=f(t,n);return delete t[n],r});if(!Object.keys){var L=!0,A=["toString","toLocaleString","valueOf","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","constructor"],O=A.length;for(var M in{toString:null})L=!1;Object.keys=function I(e){if(typeof e!="object"&&typeof e!="function"||e===null)throw new TypeError("Object.keys called on a non-object");var I=[];for(var t in e)f(e,t)&&I.push(t);if(L)for(var n=0,r=O;n=0?parseFloat((s.match(/(?:MSIE |Trident\/[0-9]+[\.0-9]+;.*rv:)([0-9]+[\.0-9]+)/)||[])[1]):parseFloat((s.match(/(?:Trident\/[0-9]+[\.0-9]+;.*rv:)([0-9]+[\.0-9]+)/)||[])[1]),t.isOldIE=t.isIE&&t.isIE<9,t.isGecko=t.isMozilla=s.match(/ Gecko\/\d+/),t.isOpera=typeof opera=="object"&&Object.prototype.toString.call(window.opera)=="[object Opera]",t.isWebKit=parseFloat(s.split("WebKit/")[1])||undefined,t.isChrome=parseFloat(s.split(" Chrome/")[1])||undefined,t.isEdge=parseFloat(s.split(" Edge/")[1])||undefined,t.isAIR=s.indexOf("AdobeAIR")>=0,t.isAndroid=s.indexOf("Android")>=0,t.isChromeOS=s.indexOf(" CrOS ")>=0,t.isIOS=/iPad|iPhone|iPod/.test(s)&&!window.MSStream,t.isIOS&&(t.isMac=!0),t.isMobile=t.isIOS||t.isAndroid}),define("ace/lib/dom",["require","exports","module","ace/lib/useragent"],function(e,t,n){"use strict";var r=e("./useragent"),i="http://www.w3.org/1999/xhtml";t.buildDom=function o(e,t,n){if(typeof e=="string"&&e){var r=document.createTextNode(e);return t&&t.appendChild(r),r}if(!Array.isArray(e))return e;if(typeof e[0]!="string"||!e[0]){var i=[];for(var s=0;s=1.5:!0;if(typeof document!="undefined"){var s=document.createElement("div");t.HI_DPI&&s.style.transform!==undefined&&(t.HAS_CSS_TRANSFORMS=!0),!r.isEdge&&typeof s.style.animationName!="undefined"&&(t.HAS_CSS_ANIMATION=!0),s=null}t.HAS_CSS_TRANSFORMS?t.translate=function(e,t,n){e.style.transform="translate("+Math.round(t)+"px, "+Math.round(n)+"px)"}:t.translate=function(e,t,n){e.style.top=Math.round(n)+"px",e.style.left=Math.round(t)+"px"}}),define("ace/lib/oop",["require","exports","module"],function(e,t,n){"use strict";t.inherits=function(e,t){e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}})},t.mixin=function(e,t){for(var n in t)e[n]=t[n];return e},t.implement=function(e,n){t.mixin(e,n)}}),define("ace/lib/keys",["require","exports","module","ace/lib/oop"],function(e,t,n){"use strict";var r=e("./oop"),i=function(){var e={MODIFIER_KEYS:{16:"Shift",17:"Ctrl",18:"Alt",224:"Meta",91:"MetaLeft",92:"MetaRight",93:"ContextMenu"},KEY_MODS:{ctrl:1,alt:2,option:2,shift:4,"super":8,meta:8,command:8,cmd:8},FUNCTION_KEYS:{8:"Backspace",9:"Tab",13:"Return",19:"Pause",27:"Esc",32:"Space",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"Left",38:"Up",39:"Right",40:"Down",44:"Print",45:"Insert",46:"Delete",96:"Numpad0",97:"Numpad1",98:"Numpad2",99:"Numpad3",100:"Numpad4",101:"Numpad5",102:"Numpad6",103:"Numpad7",104:"Numpad8",105:"Numpad9","-13":"NumpadEnter",112:"F1",113:"F2",114:"F3",115:"F4",116:"F5",117:"F6",118:"F7",119:"F8",120:"F9",121:"F10",122:"F11",123:"F12",144:"Numlock",145:"Scrolllock"},PRINTABLE_KEYS:{32:" ",48:"0",49:"1",50:"2",51:"3",52:"4",53:"5",54:"6",55:"7",56:"8",57:"9",59:";",61:"=",65:"a",66:"b",67:"c",68:"d",69:"e",70:"f",71:"g",72:"h",73:"i",74:"j",75:"k",76:"l",77:"m",78:"n",79:"o",80:"p",81:"q",82:"r",83:"s",84:"t",85:"u",86:"v",87:"w",88:"x",89:"y",90:"z",107:"+",109:"-",110:".",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'",111:"/",106:"*"}},t,n;for(n in e.FUNCTION_KEYS)t=e.FUNCTION_KEYS[n].toLowerCase(),e[t]=parseInt(n,10);for(n in e.PRINTABLE_KEYS)t=e.PRINTABLE_KEYS[n].toLowerCase(),e[t]=parseInt(n,10);return r.mixin(e,e.MODIFIER_KEYS),r.mixin(e,e.PRINTABLE_KEYS),r.mixin(e,e.FUNCTION_KEYS),e.enter=e["return"],e.escape=e.esc,e.del=e["delete"],e[173]="-",function(){var t=["cmd","ctrl","alt","shift"];for(var n=Math.pow(2,t.length);n--;)e.KEY_MODS[n]=t.filter(function(t){return n&e.KEY_MODS[t]}).join("-")+"-"}(),e.KEY_MODS[0]="",e.KEY_MODS[-1]="input-",e}();r.mixin(t,i),t.keyCodeToString=function(e){var t=i[e];return typeof t!="string"&&(t=String.fromCharCode(e)),t.toLowerCase()}}),define("ace/lib/event",["require","exports","module","ace/lib/keys","ace/lib/useragent"],function(e,t,n){"use strict";function a(){u=!1;try{document.createComment("").addEventListener("test",function(){},{get passive(){u={passive:!1}}})}catch(e){}}function f(){return u==undefined&&a(),u}function c(e,t,n){var u=l(t);if(!i.isMac&&s){t.getModifierState&&(t.getModifierState("OS")||t.getModifierState("Win"))&&(u|=8);if(s.altGr){if((3&u)==3)return;s.altGr=0}if(n===18||n===17){var a="location"in t?t.location:t.keyLocation;if(n===17&&a===1)s[n]==1&&(o=t.timeStamp);else if(n===18&&u===3&&a===2){var f=t.timeStamp-o;f<50&&(s.altGr=!0)}}}n in r.MODIFIER_KEYS&&(n=-1);if(!u&&n===13){var a="location"in t?t.location:t.keyLocation;if(a===3){e(t,u,-n);if(t.defaultPrevented)return}}if(i.isChromeOS&&u&8){e(t,u,n);if(t.defaultPrevented)return;u&=-9}return!!u||n in r.FUNCTION_KEYS||n in r.PRINTABLE_KEYS?e(t,u,n):!1}function h(){s=Object.create(null)}var r=e("./keys"),i=e("./useragent"),s=null,o=0,u;t.addListener=function(e,t,n){return e.addEventListener(t,n,f())},t.removeListener=function(e,t,n){return e.removeEventListener(t,n,f())},t.stopEvent=function(e){return t.stopPropagation(e),t.preventDefault(e),!1},t.stopPropagation=function(e){e.stopPropagation&&e.stopPropagation()},t.preventDefault=function(e){e.preventDefault&&e.preventDefault()},t.getButton=function(e){return e.type=="dblclick"?0:e.type=="contextmenu"||i.isMac&&e.ctrlKey&&!e.altKey&&!e.shiftKey?2:e.button},t.capture=function(e,n,r){function i(e){n&&n(e),r&&r(e),t.removeListener(document,"mousemove",n,!0),t.removeListener(document,"mouseup",i,!0),t.removeListener(document,"dragstart",i,!0)}return t.addListener(document,"mousemove",n,!0),t.addListener(document,"mouseup",i,!0),t.addListener(document,"dragstart",i,!0),i},t.addMouseWheelListener=function(e,n){"onmousewheel"in e?t.addListener(e,"mousewheel",function(e){var t=8;e.wheelDeltaX!==undefined?(e.wheelX=-e.wheelDeltaX/t,e.wheelY=-e.wheelDeltaY/t):(e.wheelX=0,e.wheelY=-e.wheelDelta/t),n(e)}):"onwheel"in e?t.addListener(e,"wheel",function(e){var t=.35;switch(e.deltaMode){case e.DOM_DELTA_PIXEL:e.wheelX=e.deltaX*t||0,e.wheelY=e.deltaY*t||0;break;case e.DOM_DELTA_LINE:case e.DOM_DELTA_PAGE:e.wheelX=(e.deltaX||0)*5,e.wheelY=(e.deltaY||0)*5}n(e)}):t.addListener(e,"DOMMouseScroll",function(e){e.axis&&e.axis==e.HORIZONTAL_AXIS?(e.wheelX=(e.detail||0)*5,e.wheelY=0):(e.wheelX=0,e.wheelY=(e.detail||0)*5),n(e)})},t.addMultiMouseDownListener=function(e,n,r,s){function c(e){t.getButton(e)!==0?o=0:e.detail>1?(o++,o>4&&(o=1)):o=1;if(i.isIE){var c=Math.abs(e.clientX-u)>5||Math.abs(e.clientY-a)>5;if(!f||c)o=1;f&&clearTimeout(f),f=setTimeout(function(){f=null},n[o-1]||600),o==1&&(u=e.clientX,a=e.clientY)}e._clicks=o,r[s]("mousedown",e);if(o>4)o=0;else if(o>1)return r[s](l[o],e)}var o=0,u,a,f,l={2:"dblclick",3:"tripleclick",4:"quadclick"};Array.isArray(e)||(e=[e]),e.forEach(function(e){t.addListener(e,"mousedown",c)})};var l=function(e){return 0|(e.ctrlKey?1:0)|(e.altKey?2:0)|(e.shiftKey?4:0)|(e.metaKey?8:0)};t.getModifierString=function(e){return r.KEY_MODS[l(e)]},t.addCommandKeyListener=function(e,n){var r=t.addListener;if(i.isOldGecko||i.isOpera&&!("KeyboardEvent"in window)){var o=null;r(e,"keydown",function(e){o=e.keyCode}),r(e,"keypress",function(e){return c(n,e,o)})}else{var u=null;r(e,"keydown",function(e){s[e.keyCode]=(s[e.keyCode]||0)+1;var t=c(n,e,e.keyCode);return u=e.defaultPrevented,t}),r(e,"keypress",function(e){u&&(e.ctrlKey||e.altKey||e.shiftKey||e.metaKey)&&(t.stopEvent(e),u=null)}),r(e,"keyup",function(e){s[e.keyCode]=null}),s||(h(),r(window,"focus",h))}};if(typeof window=="object"&&window.postMessage&&!i.isOldIE){var p=1;t.nextTick=function(e,n){n=n||window;var r="zero-timeout-message-"+p++,i=function(s){s.data==r&&(t.stopPropagation(s),t.removeListener(n,"message",i),e())};t.addListener(n,"message",i),n.postMessage(r,"*")}}t.$idleBlocked=!1,t.onIdle=function(e,n){return setTimeout(function r(){t.$idleBlocked?setTimeout(r,100):e()},n)},t.$idleBlockId=null,t.blockIdle=function(e){t.$idleBlockId&&clearTimeout(t.$idleBlockId),t.$idleBlocked=!0,t.$idleBlockId=setTimeout(function(){t.$idleBlocked=!1},e||100)},t.nextFrame=typeof window=="object"&&(window.requestAnimationFrame||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame||window.msRequestAnimationFrame||window.oRequestAnimationFrame),t.nextFrame?t.nextFrame=t.nextFrame.bind(window):t.nextFrame=function(e){setTimeout(e,17)}}),define("ace/range",["require","exports","module"],function(e,t,n){"use strict";var r=function(e,t){return e.row-t.row||e.column-t.column},i=function(e,t,n,r){this.start={row:e,column:t},this.end={row:n,column:r}};(function(){this.isEqual=function(e){return this.start.row===e.start.row&&this.end.row===e.end.row&&this.start.column===e.start.column&&this.end.column===e.end.column},this.toString=function(){return"Range: ["+this.start.row+"/"+this.start.column+"] -> ["+this.end.row+"/"+this.end.column+"]"},this.contains=function(e,t){return this.compare(e,t)==0},this.compareRange=function(e){var t,n=e.end,r=e.start;return t=this.compare(n.row,n.column),t==1?(t=this.compare(r.row,r.column),t==1?2:t==0?1:0):t==-1?-2:(t=this.compare(r.row,r.column),t==-1?-1:t==1?42:0)},this.comparePoint=function(e){return this.compare(e.row,e.column)},this.containsRange=function(e){return this.comparePoint(e.start)==0&&this.comparePoint(e.end)==0},this.intersects=function(e){var t=this.compareRange(e);return t==-1||t==0||t==1},this.isEnd=function(e,t){return this.end.row==e&&this.end.column==t},this.isStart=function(e,t){return this.start.row==e&&this.start.column==t},this.setStart=function(e,t){typeof e=="object"?(this.start.column=e.column,this.start.row=e.row):(this.start.row=e,this.start.column=t)},this.setEnd=function(e,t){typeof e=="object"?(this.end.column=e.column,this.end.row=e.row):(this.end.row=e,this.end.column=t)},this.inside=function(e,t){return this.compare(e,t)==0?this.isEnd(e,t)||this.isStart(e,t)?!1:!0:!1},this.insideStart=function(e,t){return this.compare(e,t)==0?this.isEnd(e,t)?!1:!0:!1},this.insideEnd=function(e,t){return this.compare(e,t)==0?this.isStart(e,t)?!1:!0:!1},this.compare=function(e,t){return!this.isMultiLine()&&e===this.start.row?tthis.end.column?1:0:ethis.end.row?1:this.start.row===e?t>=this.start.column?0:-1:this.end.row===e?t<=this.end.column?0:1:0},this.compareStart=function(e,t){return this.start.row==e&&this.start.column==t?-1:this.compare(e,t)},this.compareEnd=function(e,t){return this.end.row==e&&this.end.column==t?1:this.compare(e,t)},this.compareInside=function(e,t){return this.end.row==e&&this.end.column==t?1:this.start.row==e&&this.start.column==t?-1:this.compare(e,t)},this.clipRows=function(e,t){if(this.end.row>t)var n={row:t+1,column:0};else if(this.end.rowt)var r={row:t+1,column:0};else if(this.start.row0){t&1&&(n+=e);if(t>>=1)e+=e}return n};var r=/^\s\s*/,i=/\s\s*$/;t.stringTrimLeft=function(e){return e.replace(r,"")},t.stringTrimRight=function(e){return e.replace(i,"")},t.copyObject=function(e){var t={};for(var n in e)t[n]=e[n];return t},t.copyArray=function(e){var t=[];for(var n=0,r=e.length;nDate.now()-50?!0:r=!1},cancel:function(){r=Date.now()}}}),define("ace/keyboard/textinput",["require","exports","module","ace/lib/event","ace/lib/useragent","ace/lib/dom","ace/lib/lang","ace/clipboard","ace/lib/keys"],function(e,t,n){"use strict";var r=e("../lib/event"),i=e("../lib/useragent"),s=e("../lib/dom"),o=e("../lib/lang"),u=e("../clipboard"),a=i.isChrome<18,f=i.isIE,l=i.isChrome>63,c=400,h=e("../lib/keys"),p=h.KEY_MODS,d=i.isIOS,v=d?/\s/:/\n/,m=function(e,t){function W(){S=!0,n.blur(),n.focus(),S=!1}function V(e){e.keyCode==27&&n.value.lengthN&&x[s]=="\n")o=h.end;else if(rN&&x.slice(0,s).split("\n").length>2)o=h.down;else if(s>N&&x[s-1]==" ")o=h.right,u=p.option;else if(s>N||s==N&&N!=T&&r==s)o=h.right;r!==s&&(u|=p.shift);if(o){var a=t.onCommandKey({},u,o);if(!a&&t.commands){o=h.keyCodeToString(o);var f=t.commands.findKeyCommand(u,o);f&&t.execCommand(f)}T=r,N=s,A("")}};document.addEventListener("selectionchange",s),t.on("destroy",function(){document.removeEventListener("selectionchange",s)})}var n=s.createElement("textarea");n.className="ace_text-input",n.setAttribute("wrap","off"),n.setAttribute("autocorrect","off"),n.setAttribute("autocapitalize","off"),n.setAttribute("spellcheck",!1),n.style.opacity="0",e.insertBefore(n,e.firstChild);var m=!1,g=!1,y=!1,b=!1,w="";i.isMobile||(n.style.fontSize="1px");var E=!1,S=!1,x="",T=0,N=0,C=0;try{var k=document.activeElement===n}catch(L){}r.addListener(n,"blur",function(e){if(S)return;t.onBlur(e),k=!1}),r.addListener(n,"focus",function(e){if(S)return;k=!0;if(i.isEdge)try{if(!document.hasFocus())return}catch(e){}t.onFocus(e),i.isEdge?setTimeout(A):A()}),this.$focusScroll=!1,this.focus=function(){if(w||l||this.$focusScroll=="browser")return n.focus({preventScroll:!0});var e=n.style.top;n.style.position="fixed",n.style.top="0px";try{var t=n.getBoundingClientRect().top!=0}catch(r){return}var i=[];if(t){var s=n.parentElement;while(s&&s.nodeType==1)i.push(s),s.setAttribute("ace_nocontext",!0),!s.parentElement&&s.getRootNode?s=s.getRootNode().host:s=s.parentElement}n.focus({preventScroll:!0}),t&&i.forEach(function(e){e.removeAttribute("ace_nocontext")}),setTimeout(function(){n.style.position="",n.style.top=="0px"&&(n.style.top=e)},0)},this.blur=function(){n.blur()},this.isFocused=function(){return k},t.on("beforeEndOperation",function(){if(t.curOp&&t.curOp.command.name=="insertstring")return;y&&(x=n.value="",z()),A()});var A=d?function(e){if(!k||m&&!e||b)return;e||(e="");var r="\n ab"+e+"cde fg\n";r!=n.value&&(n.value=x=r);var i=4,s=4+(e.length||(t.selection.isEmpty()?0:1));(T!=i||N!=s)&&n.setSelectionRange(i,s),T=i,N=s}:function(){if(y||b)return;if(!k&&!D)return;y=!0;var e=t.selection,r=e.getRange(),i=e.cursor.row,s=r.start.column,o=r.end.column,u=t.session.getLine(i);if(r.start.row!=i){var a=t.session.getLine(i-1);s=r.start.rowi+1?f.length:o,o+=u.length+1,u=u+"\n"+f}u.length>c&&(s=x.length&&e.value===x&&x&&e.selectionEnd!==N},M=function(e){if(y)return;m?m=!1:O(n)&&(t.selectAll(),A())},_=null;this.setInputHandler=function(e){_=e},this.getInputHandler=function(){return _};var D=!1,P=function(e,r){D&&(D=!1);if(g)return A(),e&&t.onPaste(e),g=!1,"";var i=n.selectionStart,s=n.selectionEnd,o=T,u=x.length-N,a=e,f=e.length-i,l=e.length-s,c=0;while(o>0&&x[c]==e[c])c++,o--;a=a.slice(c),c=1;while(u>0&&x.length-c>T-1&&x[x.length-c]==e[e.length-c])c++,u--;f-=c-1,l-=c-1;var h=a.length-c+1;return h<0&&(o=-h,h=0),a=a.slice(0,h),!r&&!a&&!f&&!o&&!u&&!l?"":(b=!0,a&&!o&&!u&&!f&&!l||E?t.onTextInput(a):t.onTextInput(a,{extendLeft:o,extendRight:u,restoreStart:f,restoreEnd:l}),b=!1,x=e,T=i,N=s,C=l,a)},H=function(e){if(y)return U();if(e&&e.inputType){if(e.inputType=="historyUndo")return t.execCommand("undo");if(e.inputType=="historyRedo")return t.execCommand("redo")}var r=n.value,i=P(r,!0);(r.length>c+100||v.test(i))&&A()},B=function(e,t,n){var r=e.clipboardData||window.clipboardData;if(!r||a)return;var i=f||n?"Text":"text/plain";try{return t?r.setData(i,t)!==!1:r.getData(i)}catch(e){if(!n)return B(e,t,!0)}},j=function(e,i){var s=t.getCopyText();if(!s)return r.preventDefault(e);B(e,s)?(d&&(A(s),m=s,setTimeout(function(){m=!1},10)),i?t.onCut():t.onCopy(),r.preventDefault(e)):(m=!0,n.value=s,n.select(),setTimeout(function(){m=!1,A(),i?t.onCut():t.onCopy()}))},F=function(e){j(e,!0)},I=function(e){j(e,!1)},q=function(e){var s=B(e);if(u.pasteCancelled())return;typeof s=="string"?(s&&t.onPaste(s,e),i.isIE&&setTimeout(A),r.preventDefault(e)):(n.value="",g=!0)};r.addCommandKeyListener(n,t.onCommandKey.bind(t)),r.addListener(n,"select",M),r.addListener(n,"input",H),r.addListener(n,"cut",F),r.addListener(n,"copy",I),r.addListener(n,"paste",q),(!("oncut"in n)||!("oncopy"in n)||!("onpaste"in n))&&r.addListener(e,"keydown",function(e){if(i.isMac&&!e.metaKey||!e.ctrlKey)return;switch(e.keyCode){case 67:I(e);break;case 86:q(e);break;case 88:F(e)}});var R=function(e){if(y||!t.onCompositionStart||t.$readOnly)return;y={};if(E)return;setTimeout(U,0),t.on("mousedown",W);var r=t.getSelectionRange();r.end.row=r.start.row,r.end.column=r.start.column,y.markerRange=r,y.selectionStart=T,t.onCompositionStart(y),y.useTextareaForIME?(n.value="",x="",T=0,N=0):(n.msGetInputContext&&(y.context=n.msGetInputContext()),n.getInputContext&&(y.context=n.getInputContext()))},U=function(){if(!y||!t.onCompositionUpdate||t.$readOnly)return;if(E)return W();if(y.useTextareaForIME)t.onCompositionUpdate(n.value);else{var e=n.value;P(e),y.markerRange&&(y.context&&(y.markerRange.start.column=y.selectionStart=y.context.compositionStartOffset),y.markerRange.end.column=y.markerRange.start.column+N-y.selectionStart+C)}},z=function(e){if(!t.onCompositionEnd||t.$readOnly)return;y=!1,t.onCompositionEnd(),t.off("mousedown",W),e&&H()},X=o.delayedCall(U,50).schedule.bind(null,null);r.addListener(n,"compositionstart",R),r.addListener(n,"compositionupdate",U),r.addListener(n,"keyup",V),r.addListener(n,"keydown",X),r.addListener(n,"compositionend",z),this.getElement=function(){return n},this.setCommandMode=function(e){E=e,n.readOnly=!1},this.setReadOnly=function(e){E||(n.readOnly=e)},this.setCopyWithEmptySelection=function(e){},this.onContextMenu=function(e){D=!0,A(),t._emit("nativecontextmenu",{target:t,domEvent:e}),this.moveToMouse(e,!0)},this.moveToMouse=function(e,o){w||(w=n.style.cssText),n.style.cssText=(o?"z-index:100000;":"")+(i.isIE?"opacity:0.1;":"")+"text-indent: -"+(T+N)*t.renderer.characterWidth*.5+"px;";var u=t.container.getBoundingClientRect(),a=s.computedStyle(t.container),f=u.top+(parseInt(a.borderTopWidth)||0),l=u.left+(parseInt(u.borderLeftWidth)||0),c=u.bottom-f-n.clientHeight-2,h=function(e){s.translate(n,e.clientX-l-2,Math.min(e.clientY-f-2,c))};h(e);if(e.type!="mousedown")return;t.renderer.$isMousePressed=!0,clearTimeout($),i.isWin&&r.capture(t.container,h,J)},this.onContextMenuClose=J;var $,K=function(e){t.textInput.onContextMenu(e),J()};r.addListener(n,"mouseup",K),r.addListener(n,"mousedown",function(e){e.preventDefault(),J()}),r.addListener(t.renderer.scroller,"contextmenu",K),r.addListener(n,"contextmenu",K),d&&Q(e,t,n)};t.TextInput=m}),define("ace/mouse/default_handlers",["require","exports","module","ace/lib/useragent"],function(e,t,n){"use strict";function o(e){e.$clickSelection=null;var t=e.editor;t.setDefaultHandler("mousedown",this.onMouseDown.bind(e)),t.setDefaultHandler("dblclick",this.onDoubleClick.bind(e)),t.setDefaultHandler("tripleclick",this.onTripleClick.bind(e)),t.setDefaultHandler("quadclick",this.onQuadClick.bind(e)),t.setDefaultHandler("mousewheel",this.onMouseWheel.bind(e));var n=["select","startSelect","selectEnd","selectAllEnd","selectByWordsEnd","selectByLinesEnd","dragWait","dragWaitEnd","focusWait"];n.forEach(function(t){e[t]=this[t]},this),e.selectByLines=this.extendSelectionBy.bind(e,"getLineRange"),e.selectByWords=this.extendSelectionBy.bind(e,"getWordRange")}function u(e,t,n,r){return Math.sqrt(Math.pow(n-e,2)+Math.pow(r-t,2))}function a(e,t){if(e.start.row==e.end.row)var n=2*t.column-e.start.column-e.end.column;else if(e.start.row==e.end.row-1&&!e.start.column&&!e.end.column)var n=t.column-4;else var n=2*t.row-e.start.row-e.end.row;return n<0?{cursor:e.start,anchor:e.end}:{cursor:e.end,anchor:e.start}}var r=e("../lib/useragent"),i=0,s=550;(function(){this.onMouseDown=function(e){var t=e.inSelection(),n=e.getDocumentPosition();this.mousedownEvent=e;var i=this.editor,s=e.getButton();if(s!==0){var o=i.getSelectionRange(),u=o.isEmpty();(u||s==1)&&i.selection.moveToPosition(n),s==2&&(i.textInput.onContextMenu(e.domEvent),r.isMozilla||e.preventDefault());return}this.mousedownEvent.time=Date.now();if(t&&!i.isFocused()){i.focus();if(this.$focusTimeout&&!this.$clickSelection&&!i.inMultiSelectMode){this.setState("focusWait"),this.captureMouse(e);return}}return this.captureMouse(e),this.startSelect(n,e.domEvent._clicks>1),e.preventDefault()},this.startSelect=function(e,t){e=e||this.editor.renderer.screenToTextCoordinates(this.x,this.y);var n=this.editor;if(!this.mousedownEvent)return;this.mousedownEvent.getShiftKey()?n.selection.selectToPosition(e):t||n.selection.moveToPosition(e),t||this.select(),n.renderer.scroller.setCapture&&n.renderer.scroller.setCapture(),n.setStyle("ace_selecting"),this.setState("select")},this.select=function(){var e,t=this.editor,n=t.renderer.screenToTextCoordinates(this.x,this.y);if(this.$clickSelection){var r=this.$clickSelection.comparePoint(n);if(r==-1)e=this.$clickSelection.end;else if(r==1)e=this.$clickSelection.start;else{var i=a(this.$clickSelection,n);n=i.cursor,e=i.anchor}t.selection.setSelectionAnchor(e.row,e.column)}t.selection.selectToPosition(n),t.renderer.scrollCursorIntoView()},this.extendSelectionBy=function(e){var t,n=this.editor,r=n.renderer.screenToTextCoordinates(this.x,this.y),i=n.selection[e](r.row,r.column);if(this.$clickSelection){var s=this.$clickSelection.comparePoint(i.start),o=this.$clickSelection.comparePoint(i.end);if(s==-1&&o<=0){t=this.$clickSelection.end;if(i.end.row!=r.row||i.end.column!=r.column)r=i.start}else if(o==1&&s>=0){t=this.$clickSelection.start;if(i.start.row!=r.row||i.start.column!=r.column)r=i.end}else if(s==-1&&o==1)r=i.end,t=i.start;else{var u=a(this.$clickSelection,r);r=u.cursor,t=u.anchor}n.selection.setSelectionAnchor(t.row,t.column)}n.selection.selectToPosition(r),n.renderer.scrollCursorIntoView()},this.selectEnd=this.selectAllEnd=this.selectByWordsEnd=this.selectByLinesEnd=function(){this.$clickSelection=null,this.editor.unsetStyle("ace_selecting"),this.editor.renderer.scroller.releaseCapture&&this.editor.renderer.scroller.releaseCapture()},this.focusWait=function(){var e=u(this.mousedownEvent.x,this.mousedownEvent.y,this.x,this.y),t=Date.now();(e>i||t-this.mousedownEvent.time>this.$focusTimeout)&&this.startSelect(this.mousedownEvent.getDocumentPosition())},this.onDoubleClick=function(e){var t=e.getDocumentPosition(),n=this.editor,r=n.session,i=r.getBracketRange(t);i?(i.isEmpty()&&(i.start.column--,i.end.column++),this.setState("select")):(i=n.selection.getWordRange(t.row,t.column),this.setState("selectByWords")),this.$clickSelection=i,this.select()},this.onTripleClick=function(e){var t=e.getDocumentPosition(),n=this.editor;this.setState("selectByLines");var r=n.getSelectionRange();r.isMultiLine()&&r.contains(t.row,t.column)?(this.$clickSelection=n.selection.getLineRange(r.start.row),this.$clickSelection.end=n.selection.getLineRange(r.end.row).end):this.$clickSelection=n.selection.getLineRange(t.row),this.select()},this.onQuadClick=function(e){var t=this.editor;t.selectAll(),this.$clickSelection=t.getSelectionRange(),this.setState("selectAll")},this.onMouseWheel=function(e){if(e.getAccelKey())return;e.getShiftKey()&&e.wheelY&&!e.wheelX&&(e.wheelX=e.wheelY,e.wheelY=0);var t=this.editor;this.$lastScroll||(this.$lastScroll={t:0,vx:0,vy:0,allowed:0});var n=this.$lastScroll,r=e.domEvent.timeStamp,i=r-n.t,o=i?e.wheelX/i:n.vx,u=i?e.wheelY/i:n.vy;i=1&&t.renderer.isScrollableBy(e.wheelX*e.speed,0)&&(f=!0),a<=1&&t.renderer.isScrollableBy(0,e.wheelY*e.speed)&&(f=!0);if(f)n.allowed=r;else if(r-n.allowedt.session.documentToScreenRow(l.row,l.column))return c()}if(f==s)return;f=s.text.join("
"),i.setHtml(f),i.show(),t._signal("showGutterTooltip",i),t.on("mousewheel",c);if(e.$tooltipFollowsMouse)h(u);else{var p=u.domEvent.target,d=p.getBoundingClientRect(),v=i.getElement().style;v.left=d.right+"px",v.top=d.bottom+"px"}}function c(){o&&(o=clearTimeout(o)),f&&(i.hide(),f=null,t._signal("hideGutterTooltip",i),t.removeEventListener("mousewheel",c))}function h(e){i.setPosition(e.x,e.y)}var t=e.editor,n=t.renderer.$gutterLayer,i=new a(t.container);e.editor.setDefaultHandler("guttermousedown",function(r){if(!t.isFocused()||r.getButton()!=0)return;var i=n.getRegion(r);if(i=="foldWidgets")return;var s=r.getDocumentPosition().row,o=t.session.selection;if(r.getShiftKey())o.selectTo(s,0);else{if(r.domEvent.detail==2)return t.selectAll(),r.preventDefault();e.$clickSelection=t.selection.getLineRange(s)}return e.setState("selectByLines"),e.captureMouse(r),r.preventDefault()});var o,u,f;e.editor.setDefaultHandler("guttermousemove",function(t){var n=t.domEvent.target||t.domEvent.srcElement;if(r.hasCssClass(n,"ace_fold-widget"))return c();f&&e.$tooltipFollowsMouse&&h(t),u=t;if(o)return;o=setTimeout(function(){o=null,u&&!e.isMousePressed?l():c()},50)}),s.addListener(t.renderer.$gutter,"mouseout",function(e){u=null;if(!f||o)return;o=setTimeout(function(){o=null,c()},50)}),t.on("changeSession",c)}function a(e){o.call(this,e)}var r=e("../lib/dom"),i=e("../lib/oop"),s=e("../lib/event"),o=e("../tooltip").Tooltip;i.inherits(a,o),function(){this.setPosition=function(e,t){var n=window.innerWidth||document.documentElement.clientWidth,r=window.innerHeight||document.documentElement.clientHeight,i=this.getWidth(),s=this.getHeight();e+=15,t+=15,e+i>n&&(e-=e+i-n),t+s>r&&(t-=20+s),o.prototype.setPosition.call(this,e,t)}}.call(a.prototype),t.GutterHandler=u}),define("ace/mouse/mouse_event",["require","exports","module","ace/lib/event","ace/lib/useragent"],function(e,t,n){"use strict";var r=e("../lib/event"),i=e("../lib/useragent"),s=t.MouseEvent=function(e,t){this.domEvent=e,this.editor=t,this.x=this.clientX=e.clientX,this.y=this.clientY=e.clientY,this.$pos=null,this.$inSelection=null,this.propagationStopped=!1,this.defaultPrevented=!1};(function(){this.stopPropagation=function(){r.stopPropagation(this.domEvent),this.propagationStopped=!0},this.preventDefault=function(){r.preventDefault(this.domEvent),this.defaultPrevented=!0},this.stop=function(){this.stopPropagation(),this.preventDefault()},this.getDocumentPosition=function(){return this.$pos?this.$pos:(this.$pos=this.editor.renderer.screenToTextCoordinates(this.clientX,this.clientY),this.$pos)},this.inSelection=function(){if(this.$inSelection!==null)return this.$inSelection;var e=this.editor,t=e.getSelectionRange();if(t.isEmpty())this.$inSelection=!1;else{var n=this.getDocumentPosition();this.$inSelection=t.contains(n.row,n.column)}return this.$inSelection},this.getButton=function(){return r.getButton(this.domEvent)},this.getShiftKey=function(){return this.domEvent.shiftKey},this.getAccelKey=i.isMac?function(){return this.domEvent.metaKey}:function(){return this.domEvent.ctrlKey}}).call(s.prototype)}),define("ace/mouse/dragdrop_handler",["require","exports","module","ace/lib/dom","ace/lib/event","ace/lib/useragent"],function(e,t,n){"use strict";function f(e){function T(e,n){var r=Date.now(),i=!n||e.row!=n.row,s=!n||e.column!=n.column;if(!S||i||s)t.moveCursorToPosition(e),S=r,x={x:p,y:d};else{var o=l(x.x,x.y,p,d);o>a?S=null:r-S>=u&&(t.renderer.scrollCursorIntoView(),S=null)}}function N(e,n){var r=Date.now(),i=t.renderer.layerConfig.lineHeight,s=t.renderer.layerConfig.characterWidth,u=t.renderer.scroller.getBoundingClientRect(),a={x:{left:p-u.left,right:u.right-p},y:{top:d-u.top,bottom:u.bottom-d}},f=Math.min(a.x.left,a.x.right),l=Math.min(a.y.top,a.y.bottom),c={row:e.row,column:e.column};f/s<=2&&(c.column+=a.x.left=o&&t.renderer.scrollCursorIntoView(c):E=r:E=null}function C(){var e=g;g=t.renderer.screenToTextCoordinates(p,d),T(g,e),N(g,e)}function k(){m=t.selection.toOrientedRange(),h=t.session.addMarker(m,"ace_selection",t.getSelectionStyle()),t.clearSelection(),t.isFocused()&&t.renderer.$cursorLayer.setBlinking(!1),clearInterval(v),C(),v=setInterval(C,20),y=0,i.addListener(document,"mousemove",O)}function L(){clearInterval(v),t.session.removeMarker(h),h=null,t.selection.fromOrientedRange(m),t.isFocused()&&!w&&t.$resetCursorStyle(),m=null,g=null,y=0,E=null,S=null,i.removeListener(document,"mousemove",O)}function O(){A==null&&(A=setTimeout(function(){A!=null&&h&&L()},20))}function M(e){var t=e.types;return!t||Array.prototype.some.call(t,function(e){return e=="text/plain"||e=="Text"})}function _(e){var t=["copy","copymove","all","uninitialized"],n=["move","copymove","linkmove","all","uninitialized"],r=s.isMac?e.altKey:e.ctrlKey,i="uninitialized";try{i=e.dataTransfer.effectAllowed.toLowerCase()}catch(e){}var o="none";return r&&t.indexOf(i)>=0?o="copy":n.indexOf(i)>=0?o="move":t.indexOf(i)>=0&&(o="copy"),o}var t=e.editor,n=r.createElement("img");n.src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==",s.isOpera&&(n.style.cssText="width:1px;height:1px;position:fixed;top:0;left:0;z-index:2147483647;opacity:0;");var f=["dragWait","dragWaitEnd","startDrag","dragReadyEnd","onMouseDrag"];f.forEach(function(t){e[t]=this[t]},this),t.addEventListener("mousedown",this.onMouseDown.bind(e));var c=t.container,h,p,d,v,m,g,y=0,b,w,E,S,x;this.onDragStart=function(e){if(this.cancelDrag||!c.draggable){var r=this;return setTimeout(function(){r.startSelect(),r.captureMouse(e)},0),e.preventDefault()}m=t.getSelectionRange();var i=e.dataTransfer;i.effectAllowed=t.getReadOnly()?"copy":"copyMove",s.isOpera&&(t.container.appendChild(n),n.scrollTop=0),i.setDragImage&&i.setDragImage(n,0,0),s.isOpera&&t.container.removeChild(n),i.clearData(),i.setData("Text",t.session.getTextRange()),w=!0,this.setState("drag")},this.onDragEnd=function(e){c.draggable=!1,w=!1,this.setState(null);if(!t.getReadOnly()){var n=e.dataTransfer.dropEffect;!b&&n=="move"&&t.session.remove(t.getSelectionRange()),t.$resetCursorStyle()}this.editor.unsetStyle("ace_dragging"),this.editor.renderer.setCursorStyle("")},this.onDragEnter=function(e){if(t.getReadOnly()||!M(e.dataTransfer))return;return p=e.clientX,d=e.clientY,h||k(),y++,e.dataTransfer.dropEffect=b=_(e),i.preventDefault(e)},this.onDragOver=function(e){if(t.getReadOnly()||!M(e.dataTransfer))return;return p=e.clientX,d=e.clientY,h||(k(),y++),A!==null&&(A=null),e.dataTransfer.dropEffect=b=_(e),i.preventDefault(e)},this.onDragLeave=function(e){y--;if(y<=0&&h)return L(),b=null,i.preventDefault(e)},this.onDrop=function(e){if(!g)return;var n=e.dataTransfer;if(w)switch(b){case"move":m.contains(g.row,g.column)?m={start:g,end:g}:m=t.moveText(m,g);break;case"copy":m=t.moveText(m,g,!0)}else{var r=n.getData("Text");m={start:g,end:t.session.insert(g,r)},t.focus(),b=null}return L(),i.preventDefault(e)},i.addListener(c,"dragstart",this.onDragStart.bind(e)),i.addListener(c,"dragend",this.onDragEnd.bind(e)),i.addListener(c,"dragenter",this.onDragEnter.bind(e)),i.addListener(c,"dragover",this.onDragOver.bind(e)),i.addListener(c,"dragleave",this.onDragLeave.bind(e)),i.addListener(c,"drop",this.onDrop.bind(e));var A=null}function l(e,t,n,r){return Math.sqrt(Math.pow(n-e,2)+Math.pow(r-t,2))}var r=e("../lib/dom"),i=e("../lib/event"),s=e("../lib/useragent"),o=200,u=200,a=5;(function(){this.dragWait=function(){var e=Date.now()-this.mousedownEvent.time;e>this.editor.getDragDelay()&&this.startDrag()},this.dragWaitEnd=function(){var e=this.editor.container;e.draggable=!1,this.startSelect(this.mousedownEvent.getDocumentPosition()),this.selectEnd()},this.dragReadyEnd=function(e){this.editor.$resetCursorStyle(),this.editor.unsetStyle("ace_dragging"),this.editor.renderer.setCursorStyle(""),this.dragWaitEnd()},this.startDrag=function(){this.cancelDrag=!1;var e=this.editor,t=e.container;t.draggable=!0,e.renderer.$cursorLayer.setBlinking(!1),e.setStyle("ace_dragging");var n=s.isWin?"default":"move";e.renderer.setCursorStyle(n),this.setState("dragReady")},this.onMouseDrag=function(e){var t=this.editor.container;if(s.isIE&&this.state=="dragReady"){var n=l(this.mousedownEvent.x,this.mousedownEvent.y,this.x,this.y);n>3&&t.dragDrop()}if(this.state==="dragWait"){var n=l(this.mousedownEvent.x,this.mousedownEvent.y,this.x,this.y);n>0&&(t.draggable=!1,this.startSelect(this.mousedownEvent.getDocumentPosition()))}},this.onMouseDown=function(e){if(!this.$dragEnabled)return;this.mousedownEvent=e;var t=this.editor,n=e.inSelection(),r=e.getButton(),i=e.domEvent.detail||1;if(i===1&&r===0&&n){if(e.editor.inMultiSelectMode&&(e.getAccelKey()||e.getShiftKey()))return;this.mousedownEvent.time=Date.now();var o=e.domEvent.target||e.domEvent.srcElement;"unselectable"in o&&(o.unselectable="on");if(t.getDragDelay()){if(s.isWebKit){this.cancelDrag=!0;var u=t.container;u.draggable=!0}this.setState("dragWait")}else this.startDrag();this.captureMouse(e,this.onMouseDrag.bind(this)),e.defaultPrevented=!0}}}).call(f.prototype),t.DragdropHandler=f}),define("ace/mouse/touch_handler",["require","exports","module","ace/mouse/mouse_event","ace/lib/event","ace/lib/dom"],function(e,t,n){"use strict";var r=e("./mouse_event").MouseEvent,i=e("../lib/event"),s=e("../lib/dom");t.addTouchListeners=function(e,t){function b(){var e=window.navigator&&window.navigator.clipboard,r=!1,i=function(){var n=t.getCopyText(),i=t.session.getUndoManager().hasUndo();y.replaceChild(s.buildDom(r?["span",!n&&["span",{"class":"ace_mobile-button",action:"selectall"},"Select All"],n&&["span",{"class":"ace_mobile-button",action:"copy"},"Copy"],n&&["span",{"class":"ace_mobile-button",action:"cut"},"Cut"],e&&["span",{"class":"ace_mobile-button",action:"paste"},"Paste"],i&&["span",{"class":"ace_mobile-button",action:"undo"},"Undo"],["span",{"class":"ace_mobile-button",action:"find"},"Find"],["span",{"class":"ace_mobile-button",action:"openCommandPallete"},"Pallete"]]:["span"]),y.firstChild)},o=function(n){var s=n.target.getAttribute("action");if(s=="more"||!r)return r=!r,i();if(s=="paste")e.readText().then(function(e){t.execCommand(s,e)});else if(s){if(s=="cut"||s=="copy")e?e.writeText(t.getCopyText()):document.execCommand("copy");t.execCommand(s)}y.firstChild.style.display="none",r=!1,s!="openCommandPallete"&&t.focus()};y=s.buildDom(["div",{"class":"ace_mobile-menu",ontouchstart:function(e){n="menu",e.stopPropagation(),e.preventDefault(),t.textInput.focus()},ontouchend:function(e){e.stopPropagation(),e.preventDefault(),o(e)},onclick:o},["span"],["span",{"class":"ace_mobile-button",action:"more"},"..."]],t.container)}function w(){y||b();var e=t.selection.cursor,n=t.renderer.textToScreenCoordinates(e.row,e.column),r=t.container.getBoundingClientRect();y.style.top=n.pageY-r.top-3+"px",y.style.right="10px",y.style.display="",y.firstChild.style.display="none",t.on("input",E)}function E(e){y&&(y.style.display="none"),t.off("input",E)}function S(){l=null,clearTimeout(l);var e=t.selection.getRange(),r=e.contains(p.row,p.column);if(e.isEmpty()||!r)t.selection.moveToPosition(p),t.selection.selectWord();n="wait",w()}function x(){l=null,clearTimeout(l),t.selection.moveToPosition(p);var e=d>=2?t.selection.getLineRange(p.row):t.session.getBracketRange(p);e&&!e.isEmpty()?t.selection.setRange(e):t.selection.selectWord(),n="wait"}function T(){h+=60,c=setInterval(function(){h--<=0&&(clearInterval(c),c=null),Math.abs(v)<.01&&(v=0),Math.abs(m)<.01&&(m=0),h<20&&(v=.9*v),h<20&&(m=.9*m);var e=t.session.getScrollTop();t.renderer.scrollBy(10*v,10*m),e==t.session.getScrollTop()&&(h=0)},10)}var n="scroll",o,u,a,f,l,c,h=0,p,d=0,v=0,m=0,g,y;i.addListener(e,"contextmenu",function(e){if(!g)return;var n=t.textInput.getElement();n.focus()}),i.addListener(e,"touchstart",function(e){var i=e.touches;if(l||i.length>1){clearTimeout(l),l=null,a=-1,n="zoom";return}g=t.$mouseHandler.isMousePressed=!0;var s=t.renderer.layerConfig.lineHeight,c=t.renderer.layerConfig.lineHeight,y=e.timeStamp;f=y;var b=i[0],w=b.clientX,E=b.clientY;Math.abs(o-w)+Math.abs(u-E)>s&&(a=-1),o=e.clientX=w,u=e.clientY=E,v=m=0;var T=new r(e,t);p=T.getDocumentPosition();if(y-a<500&&i.length==1&&!h)d++,e.preventDefault(),e.button=0,x();else{d=0;var N=t.selection.cursor,C=t.selection.isEmpty()?N:t.selection.anchor,k=t.renderer.$cursorLayer.getPixelPosition(N,!0),L=t.renderer.$cursorLayer.getPixelPosition(C,!0),A=t.renderer.scroller.getBoundingClientRect(),O=function(e,t){return e/=c,t=t/s-.75,e*e+t*t};if(e.clientX_?"cursor":"anchor"),_<3.5?n="anchor":M<3.5?n="cursor":n="scroll",l=setTimeout(S,450)}a=y}),i.addListener(e,"touchend",function(e){g=t.$mouseHandler.isMousePressed=!1,c&&clearInterval(c),n=="zoom"?(n="",h=0):l?(t.selection.moveToPosition(p),h=0,w()):n=="scroll"?(T(),e.preventDefault(),E()):w(),clearTimeout(l),l=null}),i.addListener(e,"touchmove",function(e){l&&(clearTimeout(l),l=null);var i=e.touches;if(i.length>1||n=="zoom")return;var s=i[0],a=o-s.clientX,c=u-s.clientY;if(n=="wait"){if(!(a*a+c*c>4))return e.preventDefault();n="cursor"}o=s.clientX,u=s.clientY,e.clientX=s.clientX,e.clientY=s.clientY;var h=e.timeStamp,p=h-f;f=h;if(n=="scroll"){var d=new r(e,t);d.speed=1,d.wheelX=a,d.wheelY=c,10*Math.abs(a)1&&(i=n[n.length-2]);var o=a[t+"Path"];return o==null?o=a.basePath:r=="/"&&(t=r=""),o&&o.slice(-1)!="/"&&(o+="/"),o+t+r+i+this.get("suffix")},t.setModuleUrl=function(e,t){return a.$moduleUrls[e]=t},t.$loading={},t.loadModule=function(n,r){var i,o;Array.isArray(n)&&(o=n[0],n=n[1]);try{i=e(n)}catch(u){}if(i&&!t.$loading[n])return r&&r(i);t.$loading[n]||(t.$loading[n]=[]),t.$loading[n].push(r);if(t.$loading[n].length>1)return;var a=function(){e([n],function(e){t._emit("load.module",{name:n,module:e});var r=t.$loading[n];t.$loading[n]=null,r.forEach(function(t){t&&t(e)})})};if(!t.get("packaged"))return a();s.loadScript(t.moduleUrl(n,o),a),f()};var f=function(){!a.basePath&&!a.workerPath&&!a.modePath&&!a.themePath&&!Object.keys(a.$moduleUrls).length&&(console.error("Unable to infer path to ace from script src,","use ace.config.set('basePath', 'path') to enable dynamic loading of modes and themes","or with webpack use ace/webpack-resolver"),f=function(){})};t.init=l,t.version="1.4.8"}),define("ace/mouse/mouse_handler",["require","exports","module","ace/lib/event","ace/lib/useragent","ace/mouse/default_handlers","ace/mouse/default_gutter_handler","ace/mouse/mouse_event","ace/mouse/dragdrop_handler","ace/mouse/touch_handler","ace/config"],function(e,t,n){"use strict";var r=e("../lib/event"),i=e("../lib/useragent"),s=e("./default_handlers").DefaultHandlers,o=e("./default_gutter_handler").GutterHandler,u=e("./mouse_event").MouseEvent,a=e("./dragdrop_handler").DragdropHandler,f=e("./touch_handler").addTouchListeners,l=e("../config"),c=function(e){var t=this;this.editor=e,new s(this),new o(this),new a(this);var n=function(t){var n=!document.hasFocus||!document.hasFocus()||!e.isFocused()&&document.activeElement==(e.textInput&&e.textInput.getElement());n&&window.focus(),e.focus()},u=e.renderer.getMouseEventTarget();r.addListener(u,"click",this.onMouseEvent.bind(this,"click")),r.addListener(u,"mousemove",this.onMouseMove.bind(this,"mousemove")),r.addMultiMouseDownListener([u,e.renderer.scrollBarV&&e.renderer.scrollBarV.inner,e.renderer.scrollBarH&&e.renderer.scrollBarH.inner,e.textInput&&e.textInput.getElement()].filter(Boolean),[400,300,250],this,"onMouseEvent"),r.addMouseWheelListener(e.container,this.onMouseWheel.bind(this,"mousewheel")),f(e.container,e);var l=e.renderer.$gutter;r.addListener(l,"mousedown",this.onMouseEvent.bind(this,"guttermousedown")),r.addListener(l,"click",this.onMouseEvent.bind(this,"gutterclick")),r.addListener(l,"dblclick",this.onMouseEvent.bind(this,"gutterdblclick")),r.addListener(l,"mousemove",this.onMouseEvent.bind(this,"guttermousemove")),r.addListener(u,"mousedown",n),r.addListener(l,"mousedown",n),i.isIE&&e.renderer.scrollBarV&&(r.addListener(e.renderer.scrollBarV.element,"mousedown",n),r.addListener(e.renderer.scrollBarH.element,"mousedown",n)),e.on("mousemove",function(n){if(t.state||t.$dragDelay||!t.$dragEnabled)return;var r=e.renderer.screenToTextCoordinates(n.x,n.y),i=e.session.selection.getRange(),s=e.renderer;!i.isEmpty()&&i.insideStart(r.row,r.column)?s.setCursorStyle("default"):s.setCursorStyle("")})};(function(){this.onMouseEvent=function(e,t){this.editor._emit(e,new u(t,this.editor))},this.onMouseMove=function(e,t){var n=this.editor._eventRegistry&&this.editor._eventRegistry.mousemove;if(!n||!n.length)return;this.editor._emit(e,new u(t,this.editor))},this.onMouseWheel=function(e,t){var n=new u(t,this.editor);n.speed=this.$scrollSpeed*2,n.wheelX=t.wheelX,n.wheelY=t.wheelY,this.editor._emit(e,n)},this.setState=function(e){this.state=e},this.captureMouse=function(e,t){this.x=e.x,this.y=e.y,this.isMousePressed=!0;var n=this.editor,s=this.editor.renderer;s.$isMousePressed=!0;var o=this,a=function(e){if(!e)return;if(i.isWebKit&&!e.which&&o.releaseMouse)return o.releaseMouse();o.x=e.clientX,o.y=e.clientY,t&&t(e),o.mouseEvent=new u(e,o.editor),o.$mouseMoved=!0},f=function(e){n.off("beforeEndOperation",c),clearInterval(h),l(),o[o.state+"End"]&&o[o.state+"End"](e),o.state="",o.isMousePressed=s.$isMousePressed=!1,s.$keepTextAreaAtCursor&&s.$moveTextAreaToCursor(),o.$onCaptureMouseMove=o.releaseMouse=null,e&&o.onMouseEvent("mouseup",e),n.endOperation()},l=function(){o[o.state]&&o[o.state](),o.$mouseMoved=!1};if(i.isOldIE&&e.domEvent.type=="dblclick")return setTimeout(function(){f(e)});var c=function(e){if(!o.releaseMouse)return;n.curOp.command.name&&n.curOp.selectionChanged&&(o[o.state+"End"]&&o[o.state+"End"](),o.state="",o.releaseMouse())};n.on("beforeEndOperation",c),n.startOperation({command:{name:"mouse"}}),o.$onCaptureMouseMove=a,o.releaseMouse=r.capture(this.editor.container,a,f);var h=setInterval(l,20)},this.releaseMouse=null,this.cancelContextMenu=function(){var e=function(t){if(t&&t.domEvent&&t.domEvent.type!="contextmenu")return;this.editor.off("nativecontextmenu",e),t&&t.domEvent&&r.stopEvent(t.domEvent)}.bind(this);setTimeout(e,10),this.editor.on("nativecontextmenu",e)}}).call(c.prototype),l.defineOptions(c.prototype,"mouseHandler",{scrollSpeed:{initialValue:2},dragDelay:{initialValue:i.isMac?150:0},dragEnabled:{initialValue:!0},focusTimeout:{initialValue:0},tooltipFollowsMouse:{initialValue:!0}}),t.MouseHandler=c}),define("ace/mouse/fold_handler",["require","exports","module","ace/lib/dom"],function(e,t,n){"use strict";function i(e){e.on("click",function(t){var n=t.getDocumentPosition(),i=e.session,s=i.getFoldAt(n.row,n.column,1);s&&(t.getAccelKey()?i.removeFold(s):i.expandFold(s),t.stop());var o=t.domEvent&&t.domEvent.target;o&&r.hasCssClass(o,"ace_inline_button")&&r.hasCssClass(o,"ace_toggle_wrap")&&(i.setOption("wrap",!i.getUseWrapMode()),e.renderer.scrollCursorIntoView())}),e.on("gutterclick",function(t){var n=e.renderer.$gutterLayer.getRegion(t);if(n=="foldWidgets"){var r=t.getDocumentPosition().row,i=e.session;i.foldWidgets&&i.foldWidgets[r]&&e.session.onFoldWidgetClick(r,t),e.isFocused()||e.focus(),t.stop()}}),e.on("gutterdblclick",function(t){var n=e.renderer.$gutterLayer.getRegion(t);if(n=="foldWidgets"){var r=t.getDocumentPosition().row,i=e.session,s=i.getParentFoldRangeData(r,!0),o=s.range||s.firstRange;if(o){r=o.start.row;var u=i.getFoldAt(r,i.getLine(r).length,1);u?i.removeFold(u):(i.addFold("...",o),e.renderer.scrollCursorIntoView({row:o.start.row,column:0}))}t.stop()}})}var r=e("../lib/dom");t.FoldHandler=i}),define("ace/keyboard/keybinding",["require","exports","module","ace/lib/keys","ace/lib/event"],function(e,t,n){"use strict";var r=e("../lib/keys"),i=e("../lib/event"),s=function(e){this.$editor=e,this.$data={editor:e},this.$handlers=[],this.setDefaultHandler(e.commands)};(function(){this.setDefaultHandler=function(e){this.removeKeyboardHandler(this.$defaultHandler),this.$defaultHandler=e,this.addKeyboardHandler(e,0)},this.setKeyboardHandler=function(e){var t=this.$handlers;if(t[t.length-1]==e)return;while(t[t.length-1]&&t[t.length-1]!=this.$defaultHandler)this.removeKeyboardHandler(t[t.length-1]);this.addKeyboardHandler(e,1)},this.addKeyboardHandler=function(e,t){if(!e)return;typeof e=="function"&&!e.handleKeyboard&&(e.handleKeyboard=e);var n=this.$handlers.indexOf(e);n!=-1&&this.$handlers.splice(n,1),t==undefined?this.$handlers.push(e):this.$handlers.splice(t,0,e),n==-1&&e.attach&&e.attach(this.$editor)},this.removeKeyboardHandler=function(e){var t=this.$handlers.indexOf(e);return t==-1?!1:(this.$handlers.splice(t,1),e.detach&&e.detach(this.$editor),!0)},this.getKeyboardHandler=function(){return this.$handlers[this.$handlers.length-1]},this.getStatusText=function(){var e=this.$data,t=e.editor;return this.$handlers.map(function(n){return n.getStatusText&&n.getStatusText(t,e)||""}).filter(Boolean).join(" ")},this.$callKeyboardHandlers=function(e,t,n,r){var s,o=!1,u=this.$editor.commands;for(var a=this.$handlers.length;a--;){s=this.$handlers[a].handleKeyboard(this.$data,e,t,n,r);if(!s||!s.command)continue;s.command=="null"?o=!0:o=u.exec(s.command,this.$editor,s.args,r),o&&r&&e!=-1&&s.passEvent!=1&&s.command.passEvent!=1&&i.stopEvent(r);if(o)break}return!o&&e==-1&&(s={command:"insertstring"},o=u.exec("insertstring",this.$editor,t)),o&&this.$editor._signal&&this.$editor._signal("keyboardActivity",s),o},this.onCommandKey=function(e,t,n){var i=r.keyCodeToString(n);return this.$callKeyboardHandlers(t,i,n,e)},this.onTextInput=function(e){return this.$callKeyboardHandlers(-1,e)}}).call(s.prototype),t.KeyBinding=s}),define("ace/lib/bidiutil",["require","exports","module"],function(e,t,n){"use strict";function F(e,t,n,r){var i=s?d:p,c=null,h=null,v=null,m=0,g=null,y=null,b=-1,w=null,E=null,T=[];if(!r)for(w=0,r=[];w0)if(g==16){for(w=b;w-1){for(w=b;w=0;C--){if(r[C]!=N)break;t[C]=s}}}function I(e,t,n){if(o=e){u=i+1;while(u=e)u++;for(a=i,l=u-1;a=t.length||(o=n[r-1])!=b&&o!=w||(c=t[r+1])!=b&&c!=w)return E;return u&&(c=w),c==o?c:E;case k:o=r>0?n[r-1]:S;if(o==b&&r+10&&n[r-1]==b)return b;if(u)return E;p=r+1,h=t.length;while(p=1425&&d<=2303||d==64286;o=t[p];if(v&&(o==y||o==T))return y}if(r<1||(o=t[r-1])==S)return E;return n[r-1];case S:return u=!1,f=!0,s;case x:return l=!0,E;case O:case M:case D:case P:case _:u=!1;case H:return E}}function R(e){var t=e.charCodeAt(0),n=t>>8;return n==0?t>191?g:B[t]:n==5?/[\u0591-\u05f4]/.test(e)?y:g:n==6?/[\u0610-\u061a\u064b-\u065f\u06d6-\u06e4\u06e7-\u06ed]/.test(e)?A:/[\u0660-\u0669\u066b-\u066c]/.test(e)?w:t==1642?L:/[\u06f0-\u06f9]/.test(e)?b:T:n==32&&t<=8287?j[t&255]:n==254?t>=65136?T:E:E}function U(e){return e>="\u064b"&&e<="\u0655"}var r=["\u0621","\u0641"],i=["\u063a","\u064a"],s=0,o=0,u=!1,a=!1,f=!1,l=!1,c=!1,h=!1,p=[[0,3,0,1,0,0,0],[0,3,0,1,2,2,0],[0,3,0,17,2,0,1],[0,3,5,5,4,1,0],[0,3,21,21,4,0,1],[0,3,5,5,4,2,0]],d=[[2,0,1,1,0,1,0],[2,0,1,1,0,2,0],[2,0,2,1,3,2,0],[2,0,2,33,3,1,1]],v=0,m=1,g=0,y=1,b=2,w=3,E=4,S=5,x=6,T=7,N=8,C=9,k=10,L=11,A=12,O=13,M=14,_=15,D=16,P=17,H=18,B=[H,H,H,H,H,H,H,H,H,x,S,x,N,S,H,H,H,H,H,H,H,H,H,H,H,H,H,H,S,S,S,x,N,E,E,L,L,L,E,E,E,E,E,k,C,k,C,C,b,b,b,b,b,b,b,b,b,b,C,E,E,E,E,E,E,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,E,E,E,E,E,E,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,g,E,E,E,E,H,H,H,H,H,H,S,H,H,H,H,H,H,H,H,H,H,H,H,H,H,H,H,H,H,H,H,H,H,H,H,H,H,C,E,L,L,L,L,E,E,E,E,g,E,E,H,E,E,L,L,b,b,E,g,E,E,E,b,g,E,E,E,E,E],j=[N,N,N,N,N,N,N,N,N,N,N,H,H,H,g,y,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,N,S,O,M,_,D,P,C,L,L,L,L,L,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,C,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,E,N];t.L=g,t.R=y,t.EN=b,t.ON_R=3,t.AN=4,t.R_H=5,t.B=6,t.RLE=7,t.DOT="\u00b7",t.doBidiReorder=function(e,n,r){if(e.length<2)return{};var i=e.split(""),o=new Array(i.length),u=new Array(i.length),a=[];s=r?m:v,F(i,a,i.length,n);for(var f=0;fT&&n[f]0&&i[f-1]==="\u0644"&&/\u0622|\u0623|\u0625|\u0627/.test(i[f])&&(a[f-1]=a[f]=t.R_H,f++);i[i.length-1]===t.DOT&&(a[i.length-1]=t.B),i[0]==="\u202b"&&(a[0]=t.RLE);for(var f=0;f=0&&(e=this.session.$docRowCache[n])}return e},this.getSplitIndex=function(){var e=0,t=this.session.$screenRowCache;if(t.length){var n,r=this.session.$getRowCacheIndex(t,this.currentRow);while(this.currentRow-e>0){n=this.session.$getRowCacheIndex(t,this.currentRow-e-1);if(n!==r)break;r=n,e++}}else e=this.currentRow;return e},this.updateRowLine=function(e,t){e===undefined&&(e=this.getDocumentRow());var n=e===this.session.getLength()-1,s=n?this.EOF:this.EOL;this.wrapIndent=0,this.line=this.session.getLine(e),this.isRtlDir=this.$isRtl||this.line.charAt(0)===this.RLE;if(this.session.$useWrapMode){var o=this.session.$wrapData[e];o&&(t===undefined&&(t=this.getSplitIndex()),t>0&&o.length?(this.wrapIndent=o.indent,this.wrapOffset=this.wrapIndent*this.charWidths[r.L],this.line=tt?this.session.getOverwrite()?e:e-1:t,i=r.getVisualFromLogicalIdx(n,this.bidiMap),s=this.bidiMap.bidiLevels,o=0;!this.session.getOverwrite()&&e<=t&&s[i]%2!==0&&i++;for(var u=0;ut&&s[i]%2===0&&(o+=this.charWidths[s[i]]),this.wrapIndent&&(o+=this.isRtlDir?-1*this.wrapOffset:this.wrapOffset),this.isRtlDir&&(o+=this.rtlLineOffset),o},this.getSelections=function(e,t){var n=this.bidiMap,r=n.bidiLevels,i,s=[],o=0,u=Math.min(e,t)-this.wrapIndent,a=Math.max(e,t)-this.wrapIndent,f=!1,l=!1,c=0;this.wrapIndent&&(o+=this.isRtlDir?-1*this.wrapOffset:this.wrapOffset);for(var h,p=0;p=u&&hn+s/2){n+=s;if(r===i.length-1){s=0;break}s=this.charWidths[i[++r]]}return r>0&&i[r-1]%2!==0&&i[r]%2===0?(e0&&i[r-1]%2===0&&i[r]%2!==0?t=1+(e>n?this.bidiMap.logicalFromVisual[r]:this.bidiMap.logicalFromVisual[r-1]):this.isRtlDir&&r===i.length-1&&s===0&&i[r-1]%2===0||!this.isRtlDir&&r===0&&i[r]%2!==0?t=1+this.bidiMap.logicalFromVisual[r]:(r>0&&i[r-1]%2!==0&&s!==0&&r--,t=this.bidiMap.logicalFromVisual[r]),t===0&&this.isRtlDir&&t++,t+this.wrapIndent}}).call(o.prototype),t.BidiHandler=o}),define("ace/selection",["require","exports","module","ace/lib/oop","ace/lib/lang","ace/lib/event_emitter","ace/range"],function(e,t,n){"use strict";var r=e("./lib/oop"),i=e("./lib/lang"),s=e("./lib/event_emitter").EventEmitter,o=e("./range").Range,u=function(e){this.session=e,this.doc=e.getDocument(),this.clearSelection(),this.cursor=this.lead=this.doc.createAnchor(0,0),this.anchor=this.doc.createAnchor(0,0),this.$silent=!1;var t=this;this.cursor.on("change",function(e){t.$cursorChanged=!0,t.$silent||t._emit("changeCursor"),!t.$isEmpty&&!t.$silent&&t._emit("changeSelection"),!t.$keepDesiredColumnOnChange&&e.old.column!=e.value.column&&(t.$desiredColumn=null)}),this.anchor.on("change",function(){t.$anchorChanged=!0,!t.$isEmpty&&!t.$silent&&t._emit("changeSelection")})};(function(){r.implement(this,s),this.isEmpty=function(){return this.$isEmpty||this.anchor.row==this.lead.row&&this.anchor.column==this.lead.column},this.isMultiLine=function(){return!this.$isEmpty&&this.anchor.row!=this.cursor.row},this.getCursor=function(){return this.lead.getPosition()},this.setSelectionAnchor=function(e,t){this.$isEmpty=!1,this.anchor.setPosition(e,t)},this.getAnchor=this.getSelectionAnchor=function(){return this.$isEmpty?this.getSelectionLead():this.anchor.getPosition()},this.getSelectionLead=function(){return this.lead.getPosition()},this.isBackwards=function(){var e=this.anchor,t=this.lead;return e.row>t.row||e.row==t.row&&e.column>t.column},this.getRange=function(){var e=this.anchor,t=this.lead;return this.$isEmpty?o.fromPoints(t,t):this.isBackwards()?o.fromPoints(t,e):o.fromPoints(e,t)},this.clearSelection=function(){this.$isEmpty||(this.$isEmpty=!0,this._emit("changeSelection"))},this.selectAll=function(){this.$setSelection(0,0,Number.MAX_VALUE,Number.MAX_VALUE)},this.setRange=this.setSelectionRange=function(e,t){var n=t?e.end:e.start,r=t?e.start:e.end;this.$setSelection(n.row,n.column,r.row,r.column)},this.$setSelection=function(e,t,n,r){if(this.$silent)return;var i=this.$isEmpty,s=this.inMultiSelectMode;this.$silent=!0,this.$cursorChanged=this.$anchorChanged=!1,this.anchor.setPosition(e,t),this.cursor.setPosition(n,r),this.$isEmpty=!o.comparePoints(this.anchor,this.cursor),this.$silent=!1,this.$cursorChanged&&this._emit("changeCursor"),(this.$cursorChanged||this.$anchorChanged||i!=this.$isEmpty||s)&&this._emit("changeSelection")},this.$moveSelection=function(e){var t=this.lead;this.$isEmpty&&this.setSelectionAnchor(t.row,t.column),e.call(this)},this.selectTo=function(e,t){this.$moveSelection(function(){this.moveCursorTo(e,t)})},this.selectToPosition=function(e){this.$moveSelection(function(){this.moveCursorToPosition(e)})},this.moveTo=function(e,t){this.clearSelection(),this.moveCursorTo(e,t)},this.moveToPosition=function(e){this.clearSelection(),this.moveCursorToPosition(e)},this.selectUp=function(){this.$moveSelection(this.moveCursorUp)},this.selectDown=function(){this.$moveSelection(this.moveCursorDown)},this.selectRight=function(){this.$moveSelection(this.moveCursorRight)},this.selectLeft=function(){this.$moveSelection(this.moveCursorLeft)},this.selectLineStart=function(){this.$moveSelection(this.moveCursorLineStart)},this.selectLineEnd=function(){this.$moveSelection(this.moveCursorLineEnd)},this.selectFileEnd=function(){this.$moveSelection(this.moveCursorFileEnd)},this.selectFileStart=function(){this.$moveSelection(this.moveCursorFileStart)},this.selectWordRight=function(){this.$moveSelection(this.moveCursorWordRight)},this.selectWordLeft=function(){this.$moveSelection(this.moveCursorWordLeft)},this.getWordRange=function(e,t){if(typeof t=="undefined"){var n=e||this.lead;e=n.row,t=n.column}return this.session.getWordRange(e,t)},this.selectWord=function(){this.setSelectionRange(this.getWordRange())},this.selectAWord=function(){var e=this.getCursor(),t=this.session.getAWordRange(e.row,e.column);this.setSelectionRange(t)},this.getLineRange=function(e,t){var n=typeof e=="number"?e:this.lead.row,r,i=this.session.getFoldLine(n);return i?(n=i.start.row,r=i.end.row):r=n,t===!0?new o(n,0,r,this.session.getLine(r).length):new o(n,0,r+1,0)},this.selectLine=function(){this.setSelectionRange(this.getLineRange())},this.moveCursorUp=function(){this.moveCursorBy(-1,0)},this.moveCursorDown=function(){this.moveCursorBy(1,0)},this.wouldMoveIntoSoftTab=function(e,t,n){var r=e.column,i=e.column+t;return n<0&&(r=e.column-t,i=e.column),this.session.isTabStop(e)&&this.doc.getLine(e.row).slice(r,i).split(" ").length-1==t},this.moveCursorLeft=function(){var e=this.lead.getPosition(),t;if(t=this.session.getFoldAt(e.row,e.column,-1))this.moveCursorTo(t.start.row,t.start.column);else if(e.column===0)e.row>0&&this.moveCursorTo(e.row-1,this.doc.getLine(e.row-1).length);else{var n=this.session.getTabSize();this.wouldMoveIntoSoftTab(e,n,-1)&&!this.session.getNavigateWithinSoftTabs()?this.moveCursorBy(0,-n):this.moveCursorBy(0,-1)}},this.moveCursorRight=function(){var e=this.lead.getPosition(),t;if(t=this.session.getFoldAt(e.row,e.column,1))this.moveCursorTo(t.end.row,t.end.column);else if(this.lead.column==this.doc.getLine(this.lead.row).length)this.lead.row0&&(t.column=r)}}this.moveCursorTo(t.row,t.column)},this.moveCursorFileEnd=function(){var e=this.doc.getLength()-1,t=this.doc.getLine(e).length;this.moveCursorTo(e,t)},this.moveCursorFileStart=function(){this.moveCursorTo(0,0)},this.moveCursorLongWordRight=function(){var e=this.lead.row,t=this.lead.column,n=this.doc.getLine(e),r=n.substring(t);this.session.nonTokenRe.lastIndex=0,this.session.tokenRe.lastIndex=0;var i=this.session.getFoldAt(e,t,1);if(i){this.moveCursorTo(i.end.row,i.end.column);return}this.session.nonTokenRe.exec(r)&&(t+=this.session.nonTokenRe.lastIndex,this.session.nonTokenRe.lastIndex=0,r=n.substring(t));if(t>=n.length){this.moveCursorTo(e,n.length),this.moveCursorRight(),e0&&this.moveCursorWordLeft();return}this.session.tokenRe.exec(s)&&(t-=this.session.tokenRe.lastIndex,this.session.tokenRe.lastIndex=0),this.moveCursorTo(e,t)},this.$shortWordEndIndex=function(e){var t=0,n,r=/\s/,i=this.session.tokenRe;i.lastIndex=0;if(this.session.tokenRe.exec(e))t=this.session.tokenRe.lastIndex;else{while((n=e[t])&&r.test(n))t++;if(t<1){i.lastIndex=0;while((n=e[t])&&!i.test(n)){i.lastIndex=0,t++;if(r.test(n)){if(t>2){t--;break}while((n=e[t])&&r.test(n))t++;if(t>2)break}}}}return i.lastIndex=0,t},this.moveCursorShortWordRight=function(){var e=this.lead.row,t=this.lead.column,n=this.doc.getLine(e),r=n.substring(t),i=this.session.getFoldAt(e,t,1);if(i)return this.moveCursorTo(i.end.row,i.end.column);if(t==n.length){var s=this.doc.getLength();do e++,r=this.doc.getLine(e);while(e0&&/^\s*$/.test(r));t=r.length,/\s+$/.test(r)||(r="")}var s=i.stringReverse(r),o=this.$shortWordEndIndex(s);return this.moveCursorTo(e,t-o)},this.moveCursorWordRight=function(){this.session.$selectLongWords?this.moveCursorLongWordRight():this.moveCursorShortWordRight()},this.moveCursorWordLeft=function(){this.session.$selectLongWords?this.moveCursorLongWordLeft():this.moveCursorShortWordLeft()},this.moveCursorBy=function(e,t){var n=this.session.documentToScreenPosition(this.lead.row,this.lead.column),r;t===0&&(e!==0&&(this.session.$bidiHandler.isBidiRow(n.row,this.lead.row)?(r=this.session.$bidiHandler.getPosLeft(n.column),n.column=Math.round(r/this.session.$bidiHandler.charWidths[0])):r=n.column*this.session.$bidiHandler.charWidths[0]),this.$desiredColumn?n.column=this.$desiredColumn:this.$desiredColumn=n.column);if(e!=0&&this.session.lineWidgets&&this.session.lineWidgets[this.lead.row]){var i=this.session.lineWidgets[this.lead.row];e<0?e-=i.rowsAbove||0:e>0&&(e+=i.rowCount-(i.rowsAbove||0))}var s=this.session.screenToDocumentPosition(n.row+e,n.column,r);e!==0&&t===0&&s.row===this.lead.row&&s.column===this.lead.column,this.moveCursorTo(s.row,s.column+t,t===0)},this.moveCursorToPosition=function(e){this.moveCursorTo(e.row,e.column)},this.moveCursorTo=function(e,t,n){var r=this.session.getFoldAt(e,t,1);r&&(e=r.start.row,t=r.start.column),this.$keepDesiredColumnOnChange=!0;var i=this.session.getLine(e);/[\uDC00-\uDFFF]/.test(i.charAt(t))&&i.charAt(t-1)&&(this.lead.row==e&&this.lead.column==t+1?t-=1:t+=1),this.lead.setPosition(e,t),this.$keepDesiredColumnOnChange=!1,n||(this.$desiredColumn=null)},this.moveCursorToScreen=function(e,t,n){var r=this.session.screenToDocumentPosition(e,t);this.moveCursorTo(r.row,r.column,n)},this.detach=function(){this.lead.detach(),this.anchor.detach(),this.session=this.doc=null},this.fromOrientedRange=function(e){this.setSelectionRange(e,e.cursor==e.start),this.$desiredColumn=e.desiredColumn||this.$desiredColumn},this.toOrientedRange=function(e){var t=this.getRange();return e?(e.start.column=t.start.column,e.start.row=t.start.row,e.end.column=t.end.column,e.end.row=t.end.row):e=t,e.cursor=this.isBackwards()?e.start:e.end,e.desiredColumn=this.$desiredColumn,e},this.getRangeOfMovements=function(e){var t=this.getCursor();try{e(this);var n=this.getCursor();return o.fromPoints(t,n)}catch(r){return o.fromPoints(t,t)}finally{this.moveCursorToPosition(t)}},this.toJSON=function(){if(this.rangeCount)var e=this.ranges.map(function(e){var t=e.clone();return t.isBackwards=e.cursor==e.start,t});else{var e=this.getRange();e.isBackwards=this.isBackwards()}return e},this.fromJSON=function(e){if(e.start==undefined){if(this.rangeList&&e.length>1){this.toSingleRange(e[0]);for(var t=e.length;t--;){var n=o.fromPoints(e[t].start,e[t].end);e[t].isBackwards&&(n.cursor=n.start),this.addRange(n,!0)}return}e=e[0]}this.rangeList&&this.toSingleRange(e),this.setSelectionRange(e,e.isBackwards)},this.isEqual=function(e){if((e.length||this.rangeCount)&&e.length!=this.rangeCount)return!1;if(!e.length||!this.ranges)return this.getRange().isEqual(e);for(var t=this.ranges.length;t--;)if(!this.ranges[t].isEqual(e[t]))return!1;return!0}}).call(u.prototype),t.Selection=u}),define("ace/tokenizer",["require","exports","module","ace/config"],function(e,t,n){"use strict";var r=e("./config"),i=2e3,s=function(e){this.states=e,this.regExps={},this.matchMappings={};for(var t in this.states){var n=this.states[t],r=[],i=0,s=this.matchMappings[t]={defaultToken:"text"},o="g",u=[];for(var a=0;a1?f.onMatch=this.$applyToken:f.onMatch=f.token),c>1&&(/\\\d/.test(f.regex)?l=f.regex.replace(/\\([0-9]+)/g,function(e,t){return"\\"+(parseInt(t,10)+i+1)}):(c=1,l=this.removeCapturingGroups(f.regex)),!f.splitRegex&&typeof f.token!="string"&&u.push(f)),s[i]=a,i+=c,r.push(l),f.onMatch||(f.onMatch=null)}r.length||(s[0]=0,r.push("$")),u.forEach(function(e){e.splitRegex=this.createSplitterRegexp(e.regex,o)},this),this.regExps[t]=new RegExp("("+r.join(")|(")+")|($)",o)}};(function(){this.$setMaxTokenCount=function(e){i=e|0},this.$applyToken=function(e){var t=this.splitRegex.exec(e).slice(1),n=this.token.apply(this,t);if(typeof n=="string")return[{type:n,value:e}];var r=[];for(var i=0,s=n.length;il){var g=e.substring(l,m-v.length);h.type==p?h.value+=g:(h.type&&f.push(h),h={type:p,value:g})}for(var y=0;yi){c>2*e.length&&this.reportError("infinite loop with in ace tokenizer",{startState:t,line:e});while(l1&&n[0]!==r&&n.unshift("#tmp",r),{tokens:f,state:n.length?n:r}},this.reportError=r.reportError}).call(s.prototype),t.Tokenizer=s}),define("ace/mode/text_highlight_rules",["require","exports","module","ace/lib/lang"],function(e,t,n){"use strict";var r=e("../lib/lang"),i=function(){this.$rules={start:[{token:"empty_line",regex:"^$"},{defaultToken:"text"}]}};(function(){this.addRules=function(e,t){if(!t){for(var n in e)this.$rules[n]=e[n];return}for(var n in e){var r=e[n];for(var i=0;i=this.$rowTokens.length){this.$row+=1,e||(e=this.$session.getLength());if(this.$row>=e)return this.$row=e-1,null;this.$rowTokens=this.$session.getTokens(this.$row),this.$tokenIndex=0}return this.$rowTokens[this.$tokenIndex]},this.getCurrentToken=function(){return this.$rowTokens[this.$tokenIndex]},this.getCurrentTokenRow=function(){return this.$row},this.getCurrentTokenColumn=function(){var e=this.$rowTokens,t=this.$tokenIndex,n=e[t].start;if(n!==undefined)return n;n=0;while(t>0)t-=1,n+=e[t].value.length;return n},this.getCurrentTokenPosition=function(){return{row:this.$row,column:this.getCurrentTokenColumn()}},this.getCurrentTokenRange=function(){var e=this.$rowTokens[this.$tokenIndex],t=this.getCurrentTokenColumn();return new r(this.$row,t,this.$row,t+e.value.length)}}).call(i.prototype),t.TokenIterator=i}),define("ace/mode/behaviour/cstyle",["require","exports","module","ace/lib/oop","ace/mode/behaviour","ace/token_iterator","ace/lib/lang"],function(e,t,n){"use strict";var r=e("../../lib/oop"),i=e("../behaviour").Behaviour,s=e("../../token_iterator").TokenIterator,o=e("../../lib/lang"),u=["text","paren.rparen","rparen","paren","punctuation.operator"],a=["text","paren.rparen","rparen","paren","punctuation.operator","comment"],f,l={},c={'"':'"',"'":"'"},h=function(e){var t=-1;e.multiSelect&&(t=e.selection.index,l.rangeCount!=e.multiSelect.rangeCount&&(l={rangeCount:e.multiSelect.rangeCount}));if(l[t])return f=l[t];f=l[t]={autoInsertedBrackets:0,autoInsertedRow:-1,autoInsertedLineEnd:"",maybeInsertedBrackets:0,maybeInsertedRow:-1,maybeInsertedLineStart:"",maybeInsertedLineEnd:""}},p=function(e,t,n,r){var i=e.end.row-e.start.row;return{text:n+t+r,selection:[0,e.start.column+1,i,e.end.column+(i?0:1)]}},d=function(e){this.add("braces","insertion",function(t,n,r,i,s){var u=r.getCursorPosition(),a=i.doc.getLine(u.row);if(s=="{"){h(r);var l=r.getSelectionRange(),c=i.doc.getTextRange(l);if(c!==""&&c!=="{"&&r.getWrapBehavioursEnabled())return p(l,c,"{","}");if(d.isSaneInsertion(r,i))return/[\]\}\)]/.test(a[u.column])||r.inMultiSelectMode||e&&e.braces?(d.recordAutoInsert(r,i,"}"),{text:"{}",selection:[1,1]}):(d.recordMaybeInsert(r,i,"{"),{text:"{",selection:[1,1]})}else if(s=="}"){h(r);var v=a.substring(u.column,u.column+1);if(v=="}"){var m=i.$findOpeningBracket("}",{column:u.column+1,row:u.row});if(m!==null&&d.isAutoInsertedClosing(u,a,s))return d.popAutoInsertedClosing(),{text:"",selection:[1,1]}}}else{if(s=="\n"||s=="\r\n"){h(r);var g="";d.isMaybeInsertedClosing(u,a)&&(g=o.stringRepeat("}",f.maybeInsertedBrackets),d.clearMaybeInsertedClosing());var v=a.substring(u.column,u.column+1);if(v==="}"){var y=i.findMatchingBracket({row:u.row,column:u.column+1},"}");if(!y)return null;var b=this.$getIndent(i.getLine(y.row))}else{if(!g){d.clearMaybeInsertedClosing();return}var b=this.$getIndent(a)}var w=b+i.getTabString();return{text:"\n"+w+"\n"+b+g,selection:[1,w.length,1,w.length]}}d.clearMaybeInsertedClosing()}}),this.add("braces","deletion",function(e,t,n,r,i){var s=r.doc.getTextRange(i);if(!i.isMultiLine()&&s=="{"){h(n);var o=r.doc.getLine(i.start.row),u=o.substring(i.end.column,i.end.column+1);if(u=="}")return i.end.column++,i;f.maybeInsertedBrackets--}}),this.add("parens","insertion",function(e,t,n,r,i){if(i=="("){h(n);var s=n.getSelectionRange(),o=r.doc.getTextRange(s);if(o!==""&&n.getWrapBehavioursEnabled())return p(s,o,"(",")");if(d.isSaneInsertion(n,r))return d.recordAutoInsert(n,r,")"),{text:"()",selection:[1,1]}}else if(i==")"){h(n);var u=n.getCursorPosition(),a=r.doc.getLine(u.row),f=a.substring(u.column,u.column+1);if(f==")"){var l=r.$findOpeningBracket(")",{column:u.column+1,row:u.row});if(l!==null&&d.isAutoInsertedClosing(u,a,i))return d.popAutoInsertedClosing(),{text:"",selection:[1,1]}}}}),this.add("parens","deletion",function(e,t,n,r,i){var s=r.doc.getTextRange(i);if(!i.isMultiLine()&&s=="("){h(n);var o=r.doc.getLine(i.start.row),u=o.substring(i.start.column+1,i.start.column+2);if(u==")")return i.end.column++,i}}),this.add("brackets","insertion",function(e,t,n,r,i){if(i=="["){h(n);var s=n.getSelectionRange(),o=r.doc.getTextRange(s);if(o!==""&&n.getWrapBehavioursEnabled())return p(s,o,"[","]");if(d.isSaneInsertion(n,r))return d.recordAutoInsert(n,r,"]"),{text:"[]",selection:[1,1]}}else if(i=="]"){h(n);var u=n.getCursorPosition(),a=r.doc.getLine(u.row),f=a.substring(u.column,u.column+1);if(f=="]"){var l=r.$findOpeningBracket("]",{column:u.column+1,row:u.row});if(l!==null&&d.isAutoInsertedClosing(u,a,i))return d.popAutoInsertedClosing(),{text:"",selection:[1,1]}}}}),this.add("brackets","deletion",function(e,t,n,r,i){var s=r.doc.getTextRange(i);if(!i.isMultiLine()&&s=="["){h(n);var o=r.doc.getLine(i.start.row),u=o.substring(i.start.column+1,i.start.column+2);if(u=="]")return i.end.column++,i}}),this.add("string_dquotes","insertion",function(e,t,n,r,i){var s=r.$mode.$quotes||c;if(i.length==1&&s[i]){if(this.lineCommentStart&&this.lineCommentStart.indexOf(i)!=-1)return;h(n);var o=i,u=n.getSelectionRange(),a=r.doc.getTextRange(u);if(a!==""&&(a.length!=1||!s[a])&&n.getWrapBehavioursEnabled())return p(u,a,o,o);if(!a){var f=n.getCursorPosition(),l=r.doc.getLine(f.row),d=l.substring(f.column-1,f.column),v=l.substring(f.column,f.column+1),m=r.getTokenAt(f.row,f.column),g=r.getTokenAt(f.row,f.column+1);if(d=="\\"&&m&&/escape/.test(m.type))return null;var y=m&&/string|escape/.test(m.type),b=!g||/string|escape/.test(g.type),w;if(v==o)w=y!==b,w&&/string\.end/.test(g.type)&&(w=!1);else{if(y&&!b)return null;if(y&&b)return null;var E=r.$mode.tokenRe;E.lastIndex=0;var S=E.test(d);E.lastIndex=0;var x=E.test(d);if(S||x)return null;if(v&&!/[\s;,.})\]\\]/.test(v))return null;var T=l[f.column-2];if(!(d!=o||T!=o&&!E.test(T)))return null;w=!0}return{text:w?o+o:"",selection:[1,1]}}}}),this.add("string_dquotes","deletion",function(e,t,n,r,i){var s=r.$mode.$quotes||c,o=r.doc.getTextRange(i);if(!i.isMultiLine()&&s.hasOwnProperty(o)){h(n);var u=r.doc.getLine(i.start.row),a=u.substring(i.start.column+1,i.start.column+2);if(a==o)return i.end.column++,i}})};d.isSaneInsertion=function(e,t){var n=e.getCursorPosition(),r=new s(t,n.row,n.column);if(!this.$matchTokenType(r.getCurrentToken()||"text",u)){if(/[)}\]]/.test(e.session.getLine(n.row)[n.column]))return!0;var i=new s(t,n.row,n.column+1);if(!this.$matchTokenType(i.getCurrentToken()||"text",u))return!1}return r.stepForward(),r.getCurrentTokenRow()!==n.row||this.$matchTokenType(r.getCurrentToken()||"text",a)},d.$matchTokenType=function(e,t){return t.indexOf(e.type||e)>-1},d.recordAutoInsert=function(e,t,n){var r=e.getCursorPosition(),i=t.doc.getLine(r.row);this.isAutoInsertedClosing(r,i,f.autoInsertedLineEnd[0])||(f.autoInsertedBrackets=0),f.autoInsertedRow=r.row,f.autoInsertedLineEnd=n+i.substr(r.column),f.autoInsertedBrackets++},d.recordMaybeInsert=function(e,t,n){var r=e.getCursorPosition(),i=t.doc.getLine(r.row);this.isMaybeInsertedClosing(r,i)||(f.maybeInsertedBrackets=0),f.maybeInsertedRow=r.row,f.maybeInsertedLineStart=i.substr(0,r.column)+n,f.maybeInsertedLineEnd=i.substr(r.column),f.maybeInsertedBrackets++},d.isAutoInsertedClosing=function(e,t,n){return f.autoInsertedBrackets>0&&e.row===f.autoInsertedRow&&n===f.autoInsertedLineEnd[0]&&t.substr(e.column)===f.autoInsertedLineEnd},d.isMaybeInsertedClosing=function(e,t){return f.maybeInsertedBrackets>0&&e.row===f.maybeInsertedRow&&t.substr(e.column)===f.maybeInsertedLineEnd&&t.substr(0,e.column)==f.maybeInsertedLineStart},d.popAutoInsertedClosing=function(){f.autoInsertedLineEnd=f.autoInsertedLineEnd.substr(1),f.autoInsertedBrackets--},d.clearMaybeInsertedClosing=function(){f&&(f.maybeInsertedBrackets=0,f.maybeInsertedRow=-1)},r.inherits(d,i),t.CstyleBehaviour=d}),define("ace/unicode",["require","exports","module"],function(e,t,n){"use strict";var r=[48,9,8,25,5,0,2,25,48,0,11,0,5,0,6,22,2,30,2,457,5,11,15,4,8,0,2,0,18,116,2,1,3,3,9,0,2,2,2,0,2,19,2,82,2,138,2,4,3,155,12,37,3,0,8,38,10,44,2,0,2,1,2,1,2,0,9,26,6,2,30,10,7,61,2,9,5,101,2,7,3,9,2,18,3,0,17,58,3,100,15,53,5,0,6,45,211,57,3,18,2,5,3,11,3,9,2,1,7,6,2,2,2,7,3,1,3,21,2,6,2,0,4,3,3,8,3,1,3,3,9,0,5,1,2,4,3,11,16,2,2,5,5,1,3,21,2,6,2,1,2,1,2,1,3,0,2,4,5,1,3,2,4,0,8,3,2,0,8,15,12,2,2,8,2,2,2,21,2,6,2,1,2,4,3,9,2,2,2,2,3,0,16,3,3,9,18,2,2,7,3,1,3,21,2,6,2,1,2,4,3,8,3,1,3,2,9,1,5,1,2,4,3,9,2,0,17,1,2,5,4,2,2,3,4,1,2,0,2,1,4,1,4,2,4,11,5,4,4,2,2,3,3,0,7,0,15,9,18,2,2,7,2,2,2,22,2,9,2,4,4,7,2,2,2,3,8,1,2,1,7,3,3,9,19,1,2,7,2,2,2,22,2,9,2,4,3,8,2,2,2,3,8,1,8,0,2,3,3,9,19,1,2,7,2,2,2,22,2,15,4,7,2,2,2,3,10,0,9,3,3,9,11,5,3,1,2,17,4,23,2,8,2,0,3,6,4,0,5,5,2,0,2,7,19,1,14,57,6,14,2,9,40,1,2,0,3,1,2,0,3,0,7,3,2,6,2,2,2,0,2,0,3,1,2,12,2,2,3,4,2,0,2,5,3,9,3,1,35,0,24,1,7,9,12,0,2,0,2,0,5,9,2,35,5,19,2,5,5,7,2,35,10,0,58,73,7,77,3,37,11,42,2,0,4,328,2,3,3,6,2,0,2,3,3,40,2,3,3,32,2,3,3,6,2,0,2,3,3,14,2,56,2,3,3,66,5,0,33,15,17,84,13,619,3,16,2,25,6,74,22,12,2,6,12,20,12,19,13,12,2,2,2,1,13,51,3,29,4,0,5,1,3,9,34,2,3,9,7,87,9,42,6,69,11,28,4,11,5,11,11,39,3,4,12,43,5,25,7,10,38,27,5,62,2,28,3,10,7,9,14,0,89,75,5,9,18,8,13,42,4,11,71,55,9,9,4,48,83,2,2,30,14,230,23,280,3,5,3,37,3,5,3,7,2,0,2,0,2,0,2,30,3,52,2,6,2,0,4,2,2,6,4,3,3,5,5,12,6,2,2,6,67,1,20,0,29,0,14,0,17,4,60,12,5,0,4,11,18,0,5,0,3,9,2,0,4,4,7,0,2,0,2,0,2,3,2,10,3,3,6,4,5,0,53,1,2684,46,2,46,2,132,7,6,15,37,11,53,10,0,17,22,10,6,2,6,2,6,2,6,2,6,2,6,2,6,2,6,2,31,48,0,470,1,36,5,2,4,6,1,5,85,3,1,3,2,2,89,2,3,6,40,4,93,18,23,57,15,513,6581,75,20939,53,1164,68,45,3,268,4,27,21,31,3,13,13,1,2,24,9,69,11,1,38,8,3,102,3,1,111,44,25,51,13,68,12,9,7,23,4,0,5,45,3,35,13,28,4,64,15,10,39,54,10,13,3,9,7,22,4,1,5,66,25,2,227,42,2,1,3,9,7,11171,13,22,5,48,8453,301,3,61,3,105,39,6,13,4,6,11,2,12,2,4,2,0,2,1,2,1,2,107,34,362,19,63,3,53,41,11,5,15,17,6,13,1,25,2,33,4,2,134,20,9,8,25,5,0,2,25,12,88,4,5,3,5,3,5,3,2],i=0,s=[];for(var o=0;o2?r%f!=f-1:r%f==0}}var E=Infinity;w(function(e,t){var n=e.search(/\S/);n!==-1?(ne.length&&(E=e.length)}),u==Infinity&&(u=E,s=!1,o=!1),l&&u%f!=0&&(u=Math.floor(u/f)*f),w(o?m:v)},this.toggleBlockComment=function(e,t,n,r){var i=this.blockComment;if(!i)return;!i.start&&i[0]&&(i=i[0]);var s=new f(t,r.row,r.column),o=s.getCurrentToken(),u=t.selection,a=t.selection.toOrientedRange(),c,h;if(o&&/comment/.test(o.type)){var p,d;while(o&&/comment/.test(o.type)){var v=o.value.indexOf(i.start);if(v!=-1){var m=s.getCurrentTokenRow(),g=s.getCurrentTokenColumn()+v;p=new l(m,g,m,g+i.start.length);break}o=s.stepBackward()}var s=new f(t,r.row,r.column),o=s.getCurrentToken();while(o&&/comment/.test(o.type)){var v=o.value.indexOf(i.end);if(v!=-1){var m=s.getCurrentTokenRow(),g=s.getCurrentTokenColumn()+v;d=new l(m,g,m,g+i.end.length);break}o=s.stepForward()}d&&t.remove(d),p&&(t.remove(p),c=p.start.row,h=-i.start.length)}else h=i.start.length,c=n.start.row,t.insert(n.end,i.end),t.insert(n.start,i.start);a.start.row==c&&(a.start.column+=h),a.end.row==c&&(a.end.column+=h),t.selection.fromOrientedRange(a)},this.getNextLineIndent=function(e,t,n){return this.$getIndent(t)},this.checkOutdent=function(e,t,n){return!1},this.autoOutdent=function(e,t,n){},this.$getIndent=function(e){return e.match(/^\s*/)[0]},this.createWorker=function(e){return null},this.createModeDelegates=function(e){this.$embeds=[],this.$modes={};for(var t in e)if(e[t]){var n=e[t],i=n.prototype.$id,s=r.$modes[i];s||(r.$modes[i]=s=new n),r.$modes[t]||(r.$modes[t]=s),this.$embeds.push(t),this.$modes[t]=s}var o=["toggleBlockComment","toggleCommentLines","getNextLineIndent","checkOutdent","autoOutdent","transformAction","getCompletions"];for(var t=0;t=0&&t.row=0&&t.column<=e[t.row].length}function s(e,t){t.action!="insert"&&t.action!="remove"&&r(t,"delta.action must be 'insert' or 'remove'"),t.lines instanceof Array||r(t,"delta.lines must be an Array"),(!t.start||!t.end)&&r(t,"delta.start/end must be an present");var n=t.start;i(e,t.start)||r(t,"delta.start must be contained in document");var s=t.end;t.action=="remove"&&!i(e,s)&&r(t,"delta.end must contained in document for 'remove' actions");var o=s.row-n.row,u=s.column-(o==0?n.column:0);(o!=t.lines.length-1||t.lines[o].length!=u)&&r(t,"delta.range must match delta lines")}t.applyDelta=function(e,t,n){var r=t.start.row,i=t.start.column,s=e[r]||"";switch(t.action){case"insert":var o=t.lines;if(o.length===1)e[r]=s.substring(0,i)+t.lines[0]+s.substring(i);else{var u=[r,1].concat(t.lines);e.splice.apply(e,u),e[r]=s.substring(0,i)+e[r],e[r+t.lines.length-1]+=s.substring(i)}break;case"remove":var a=t.end.column,f=t.end.row;r===f?e[r]=s.substring(0,i)+s.substring(a):e.splice(r,f-r+1,s.substring(0,i)+e[f].substring(a))}}}),define("ace/anchor",["require","exports","module","ace/lib/oop","ace/lib/event_emitter"],function(e,t,n){"use strict";var r=e("./lib/oop"),i=e("./lib/event_emitter").EventEmitter,s=t.Anchor=function(e,t,n){this.$onChange=this.onChange.bind(this),this.attach(e),typeof n=="undefined"?this.setPosition(t.row,t.column):this.setPosition(t,n)};(function(){function e(e,t,n){var r=n?e.column<=t.column:e.columnthis.row)return;var n=t(e,{row:this.row,column:this.column},this.$insertRight);this.setPosition(n.row,n.column,!0)},this.setPosition=function(e,t,n){var r;n?r={row:e,column:t}:r=this.$clipPositionToDocument(e,t);if(this.row==r.row&&this.column==r.column)return;var i={row:this.row,column:this.column};this.row=r.row,this.column=r.column,this._signal("change",{old:i,value:r})},this.detach=function(){this.document.removeEventListener("change",this.$onChange)},this.attach=function(e){this.document=e||this.document,this.document.on("change",this.$onChange)},this.$clipPositionToDocument=function(e,t){var n={};return e>=this.document.getLength()?(n.row=Math.max(0,this.document.getLength()-1),n.column=this.document.getLine(n.row).length):e<0?(n.row=0,n.column=0):(n.row=e,n.column=Math.min(this.document.getLine(n.row).length,Math.max(0,t))),t<0&&(n.column=0),n}}).call(s.prototype)}),define("ace/document",["require","exports","module","ace/lib/oop","ace/apply_delta","ace/lib/event_emitter","ace/range","ace/anchor"],function(e,t,n){"use strict";var r=e("./lib/oop"),i=e("./apply_delta").applyDelta,s=e("./lib/event_emitter").EventEmitter,o=e("./range").Range,u=e("./anchor").Anchor,a=function(e){this.$lines=[""],e.length===0?this.$lines=[""]:Array.isArray(e)?this.insertMergedLines({row:0,column:0},e):this.insert({row:0,column:0},e)};(function(){r.implement(this,s),this.setValue=function(e){var t=this.getLength()-1;this.remove(new o(0,0,t,this.getLine(t).length)),this.insert({row:0,column:0},e)},this.getValue=function(){return this.getAllLines().join(this.getNewLineCharacter())},this.createAnchor=function(e,t){return new u(this,e,t)},"aaa".split(/a/).length===0?this.$split=function(e){return e.replace(/\r\n|\r/g,"\n").split("\n")}:this.$split=function(e){return e.split(/\r\n|\r|\n/)},this.$detectNewLine=function(e){var t=e.match(/^.*?(\r\n|\r|\n)/m);this.$autoNewLine=t?t[1]:"\n",this._signal("changeNewLineMode")},this.getNewLineCharacter=function(){switch(this.$newLineMode){case"windows":return"\r\n";case"unix":return"\n";default:return this.$autoNewLine||"\n"}},this.$autoNewLine="",this.$newLineMode="auto",this.setNewLineMode=function(e){if(this.$newLineMode===e)return;this.$newLineMode=e,this._signal("changeNewLineMode")},this.getNewLineMode=function(){return this.$newLineMode},this.isNewLine=function(e){return e=="\r\n"||e=="\r"||e=="\n"},this.getLine=function(e){return this.$lines[e]||""},this.getLines=function(e,t){return this.$lines.slice(e,t+1)},this.getAllLines=function(){return this.getLines(0,this.getLength())},this.getLength=function(){return this.$lines.length},this.getTextRange=function(e){return this.getLinesForRange(e).join(this.getNewLineCharacter())},this.getLinesForRange=function(e){var t;if(e.start.row===e.end.row)t=[this.getLine(e.start.row).substring(e.start.column,e.end.column)];else{t=this.getLines(e.start.row,e.end.row),t[0]=(t[0]||"").substring(e.start.column);var n=t.length-1;e.end.row-e.start.row==n&&(t[n]=t[n].substring(0,e.end.column))}return t},this.insertLines=function(e,t){return console.warn("Use of document.insertLines is deprecated. Use the insertFullLines method instead."),this.insertFullLines(e,t)},this.removeLines=function(e,t){return console.warn("Use of document.removeLines is deprecated. Use the removeFullLines method instead."),this.removeFullLines(e,t)},this.insertNewLine=function(e){return console.warn("Use of document.insertNewLine is deprecated. Use insertMergedLines(position, ['', '']) instead."),this.insertMergedLines(e,["",""])},this.insert=function(e,t){return this.getLength()<=1&&this.$detectNewLine(t),this.insertMergedLines(e,this.$split(t))},this.insertInLine=function(e,t){var n=this.clippedPos(e.row,e.column),r=this.pos(e.row,e.column+t.length);return this.applyDelta({start:n,end:r,action:"insert",lines:[t]},!0),this.clonePos(r)},this.clippedPos=function(e,t){var n=this.getLength();e===undefined?e=n:e<0?e=0:e>=n&&(e=n-1,t=undefined);var r=this.getLine(e);return t==undefined&&(t=r.length),t=Math.min(Math.max(t,0),r.length),{row:e,column:t}},this.clonePos=function(e){return{row:e.row,column:e.column}},this.pos=function(e,t){return{row:e,column:t}},this.$clipPosition=function(e){var t=this.getLength();return e.row>=t?(e.row=Math.max(0,t-1),e.column=this.getLine(t-1).length):(e.row=Math.max(0,e.row),e.column=Math.min(Math.max(e.column,0),this.getLine(e.row).length)),e},this.insertFullLines=function(e,t){e=Math.min(Math.max(e,0),this.getLength());var n=0;e0,r=t=0&&this.applyDelta({start:this.pos(e,this.getLine(e).length),end:this.pos(e+1,0),action:"remove",lines:["",""]})},this.replace=function(e,t){e instanceof o||(e=o.fromPoints(e.start,e.end));if(t.length===0&&e.isEmpty())return e.start;if(t==this.getTextRange(e))return e.end;this.remove(e);var n;return t?n=this.insert(e.start,t):n=e.start,n},this.applyDeltas=function(e){for(var t=0;t=0;t--)this.revertDelta(e[t])},this.applyDelta=function(e,t){var n=e.action=="insert";if(n?e.lines.length<=1&&!e.lines[0]:!o.comparePoints(e.start,e.end))return;n&&e.lines.length>2e4?this.$splitAndapplyLargeDelta(e,2e4):(i(this.$lines,e,t),this._signal("change",e))},this.$splitAndapplyLargeDelta=function(e,t){var n=e.lines,r=n.length-t+1,i=e.start.row,s=e.start.column;for(var o=0,u=0;o20){n.running=setTimeout(n.$worker,20);break}}n.currentLine=t,r==-1&&(r=t),s<=r&&n.fireUpdateEvent(s,r)}};(function(){r.implement(this,i),this.setTokenizer=function(e){this.tokenizer=e,this.lines=[],this.states=[],this.start(0)},this.setDocument=function(e){this.doc=e,this.lines=[],this.states=[],this.stop()},this.fireUpdateEvent=function(e,t){var n={first:e,last:t};this._signal("update",{data:n})},this.start=function(e){this.currentLine=Math.min(e||0,this.currentLine,this.doc.getLength()),this.lines.splice(this.currentLine,this.lines.length),this.states.splice(this.currentLine,this.states.length),this.stop(),this.running=setTimeout(this.$worker,700)},this.scheduleStart=function(){this.running||(this.running=setTimeout(this.$worker,700))},this.$updateOnChange=function(e){var t=e.start.row,n=e.end.row-t;if(n===0)this.lines[t]=null;else if(e.action=="remove")this.lines.splice(t,n+1,null),this.states.splice(t,n+1,null);else{var r=Array(n+1);r.unshift(t,1),this.lines.splice.apply(this.lines,r),this.states.splice.apply(this.states,r)}this.currentLine=Math.min(t,this.currentLine,this.doc.getLength()),this.stop()},this.stop=function(){this.running&&clearTimeout(this.running),this.running=!1},this.getTokens=function(e){return this.lines[e]||this.$tokenizeRow(e)},this.getState=function(e){return this.currentLine==e&&this.$tokenizeRow(e),this.states[e]||"start"},this.$tokenizeRow=function(e){var t=this.doc.getLine(e),n=this.states[e-1],r=this.tokenizer.getLineTokens(t,n,e);return this.states[e]+""!=r.state+""?(this.states[e]=r.state,this.lines[e+1]=null,this.currentLine>e+1&&(this.currentLine=e+1)):this.currentLine==e&&(this.currentLine=e+1),this.lines[e]=r.tokens}}).call(s.prototype),t.BackgroundTokenizer=s}),define("ace/search_highlight",["require","exports","module","ace/lib/lang","ace/lib/oop","ace/range"],function(e,t,n){"use strict";var r=e("./lib/lang"),i=e("./lib/oop"),s=e("./range").Range,o=function(e,t,n){this.setRegexp(e),this.clazz=t,this.type=n||"text"};(function(){this.MAX_RANGES=500,this.setRegexp=function(e){if(this.regExp+""==e+"")return;this.regExp=e,this.cache=[]},this.update=function(e,t,n,i){if(!this.regExp)return;var o=i.firstRow,u=i.lastRow;for(var a=o;a<=u;a++){var f=this.cache[a];f==null&&(f=r.getMatchOffsets(n.getLine(a),this.regExp),f.length>this.MAX_RANGES&&(f=f.slice(0,this.MAX_RANGES)),f=f.map(function(e){return new s(a,e.offset,a,e.offset+e.length)}),this.cache[a]=f.length?f:"");for(var l=f.length;l--;)t.drawSingleLineMarker(e,f[l].toScreenRange(n),this.clazz,i)}}}).call(o.prototype),t.SearchHighlight=o}),define("ace/edit_session/fold_line",["require","exports","module","ace/range"],function(e,t,n){"use strict";function i(e,t){this.foldData=e,Array.isArray(t)?this.folds=t:t=this.folds=[t];var n=t[t.length-1];this.range=new r(t[0].start.row,t[0].start.column,n.end.row,n.end.column),this.start=this.range.start,this.end=this.range.end,this.folds.forEach(function(e){e.setFoldLine(this)},this)}var r=e("../range").Range;(function(){this.shiftRow=function(e){this.start.row+=e,this.end.row+=e,this.folds.forEach(function(t){t.start.row+=e,t.end.row+=e})},this.addFold=function(e){if(e.sameRow){if(e.start.rowthis.endRow)throw new Error("Can't add a fold to this FoldLine as it has no connection");this.folds.push(e),this.folds.sort(function(e,t){return-e.range.compareEnd(t.start.row,t.start.column)}),this.range.compareEnd(e.start.row,e.start.column)>0?(this.end.row=e.end.row,this.end.column=e.end.column):this.range.compareStart(e.end.row,e.end.column)<0&&(this.start.row=e.start.row,this.start.column=e.start.column)}else if(e.start.row==this.end.row)this.folds.push(e),this.end.row=e.end.row,this.end.column=e.end.column;else{if(e.end.row!=this.start.row)throw new Error("Trying to add fold to FoldRow that doesn't have a matching row");this.folds.unshift(e),this.start.row=e.start.row,this.start.column=e.start.column}e.foldLine=this},this.containsRow=function(e){return e>=this.start.row&&e<=this.end.row},this.walk=function(e,t,n){var r=0,i=this.folds,s,o,u,a=!0;t==null&&(t=this.end.row,n=this.end.column);for(var f=0;f0)continue;var a=i(e,o.start);return u===0?t&&a!==0?-s-2:s:a>0||a===0&&!t?s:-s-1}return-s-1},this.add=function(e){var t=!e.isEmpty(),n=this.pointIndex(e.start,t);n<0&&(n=-n-1);var r=this.pointIndex(e.end,t,n);return r<0?r=-r-1:r++,this.ranges.splice(n,r-n,e)},this.addList=function(e){var t=[];for(var n=e.length;n--;)t.push.apply(t,this.add(e[n]));return t},this.substractPoint=function(e){var t=this.pointIndex(e);if(t>=0)return this.ranges.splice(t,1)},this.merge=function(){var e=[],t=this.ranges;t=t.sort(function(e,t){return i(e.start,t.start)});var n=t[0],r;for(var s=1;s=0},this.containsPoint=function(e){return this.pointIndex(e)>=0},this.rangeAtPoint=function(e){var t=this.pointIndex(e);if(t>=0)return this.ranges[t]},this.clipRows=function(e,t){var n=this.ranges;if(n[0].start.row>t||n[n.length-1].start.row=r)break}if(e.action=="insert"){var f=i-r,l=-t.column+n.column;for(;or)break;a.start.row==r&&a.start.column>=t.column&&(a.start.column==t.column&&this.$bias<=0||(a.start.column+=l,a.start.row+=f));if(a.end.row==r&&a.end.column>=t.column){if(a.end.column==t.column&&this.$bias<0)continue;a.end.column==t.column&&l>0&&oa.start.column&&a.end.column==s[o+1].start.column&&(a.end.column-=l),a.end.column+=l,a.end.row+=f}}}else{var f=r-i,l=t.column-n.column;for(;oi)break;if(a.end.rowt.column)a.end.column=t.column,a.end.row=t.row}else a.end.column+=l,a.end.row+=f;else a.end.row>i&&(a.end.row+=f);if(a.start.rowt.column)a.start.column=t.column,a.start.row=t.row}else a.start.column+=l,a.start.row+=f;else a.start.row>i&&(a.start.row+=f)}}if(f!=0&&o=e)return i;if(i.end.row>e)return null}return null},this.getNextFoldLine=function(e,t){var n=this.$foldData,r=0;t&&(r=n.indexOf(t)),r==-1&&(r=0);for(r;r=e)return i}return null},this.getFoldedRowCount=function(e,t){var n=this.$foldData,r=t-e+1;for(var i=0;i=t){u=e?r-=t-u:r=0);break}o>=e&&(u>=e?r-=o-u:r-=o-e+1)}return r},this.$addFoldLine=function(e){return this.$foldData.push(e),this.$foldData.sort(function(e,t){return e.start.row-t.start.row}),e},this.addFold=function(e,t){var n=this.$foldData,r=!1,o;e instanceof s?o=e:(o=new s(t,e),o.collapseChildren=t.collapseChildren),this.$clipRangeToDocument(o.range);var u=o.start.row,a=o.start.column,f=o.end.row,l=o.end.column,c=this.getFoldAt(u,a,1),h=this.getFoldAt(f,l,-1);if(c&&h==c)return c.addSubFold(o);c&&!c.range.isStart(u,a)&&this.removeFold(c),h&&!h.range.isEnd(f,l)&&this.removeFold(h);var p=this.getFoldsInRange(o.range);p.length>0&&(this.removeFolds(p),p.forEach(function(e){o.addSubFold(e)}));for(var d=0;d0&&this.foldAll(e.start.row+1,e.end.row,e.collapseChildren-1),e.subFolds=[]},this.expandFolds=function(e){e.forEach(function(e){this.expandFold(e)},this)},this.unfold=function(e,t){var n,i;e==null?(n=new r(0,0,this.getLength(),0),t=!0):typeof e=="number"?n=new r(e,0,e,this.getLine(e).length):"row"in e?n=r.fromPoints(e,e):n=e,i=this.getFoldsInRangeList(n);if(t)this.removeFolds(i);else{var s=i;while(s.length)this.expandFolds(s),s=this.getFoldsInRangeList(n)}if(i.length)return i},this.isRowFolded=function(e,t){return!!this.getFoldLine(e,t)},this.getRowFoldEnd=function(e,t){var n=this.getFoldLine(e,t);return n?n.end.row:e},this.getRowFoldStart=function(e,t){var n=this.getFoldLine(e,t);return n?n.start.row:e},this.getFoldDisplayLine=function(e,t,n,r,i){r==null&&(r=e.start.row),i==null&&(i=0),t==null&&(t=e.end.row),n==null&&(n=this.getLine(t).length);var s=this.doc,o="";return e.walk(function(e,t,n,u){if(tl)break}while(s&&a.test(s.type));s=i.stepBackward()}else s=i.getCurrentToken();return f.end.row=i.getCurrentTokenRow(),f.end.column=i.getCurrentTokenColumn()+s.value.length-2,f}},this.foldAll=function(e,t,n){n==undefined&&(n=1e5);var r=this.foldWidgets;if(!r)return;t=t||this.getLength(),e=e||0;for(var i=e;i=e){i=s.end.row;try{var o=this.addFold("...",s);o&&(o.collapseChildren=n)}catch(u){}}}},this.$foldStyles={manual:1,markbegin:1,markbeginend:1},this.$foldStyle="markbegin",this.setFoldStyle=function(e){if(!this.$foldStyles[e])throw new Error("invalid fold style: "+e+"["+Object.keys(this.$foldStyles).join(", ")+"]");if(this.$foldStyle==e)return;this.$foldStyle=e,e=="manual"&&this.unfold();var t=this.$foldMode;this.$setFolding(null),this.$setFolding(t)},this.$setFolding=function(e){if(this.$foldMode==e)return;this.$foldMode=e,this.off("change",this.$updateFoldWidgets),this.off("tokenizerUpdate",this.$tokenizerUpdateFoldWidgets),this._signal("changeAnnotation");if(!e||this.$foldStyle=="manual"){this.foldWidgets=null;return}this.foldWidgets=[],this.getFoldWidget=e.getFoldWidget.bind(e,this,this.$foldStyle),this.getFoldWidgetRange=e.getFoldWidgetRange.bind(e,this,this.$foldStyle),this.$updateFoldWidgets=this.updateFoldWidgets.bind(this),this.$tokenizerUpdateFoldWidgets=this.tokenizerUpdateFoldWidgets.bind(this),this.on("change",this.$updateFoldWidgets),this.on("tokenizerUpdate",this.$tokenizerUpdateFoldWidgets)},this.getParentFoldRangeData=function(e,t){var n=this.foldWidgets;if(!n||t&&n[e])return{};var r=e-1,i;while(r>=0){var s=n[r];s==null&&(s=n[r]=this.getFoldWidget(r));if(s=="start"){var o=this.getFoldWidgetRange(r);i||(i=o);if(o&&o.end.row>=e)break}r--}return{range:r!==-1&&o,firstRange:i}},this.onFoldWidgetClick=function(e,t){t=t.domEvent;var n={children:t.shiftKey,all:t.ctrlKey||t.metaKey,siblings:t.altKey},r=this.$toggleFoldWidget(e,n);if(!r){var i=t.target||t.srcElement;i&&/ace_fold-widget/.test(i.className)&&(i.className+=" ace_invalid")}},this.$toggleFoldWidget=function(e,t){if(!this.getFoldWidget)return;var n=this.getFoldWidget(e),r=this.getLine(e),i=n==="end"?-1:1,s=this.getFoldAt(e,i===-1?0:r.length,i);if(s)return t.children||t.all?this.removeFold(s):this.expandFold(s),s;var o=this.getFoldWidgetRange(e,!0);if(o&&!o.isMultiLine()){s=this.getFoldAt(o.start.row,o.start.column,1);if(s&&o.isEqual(s.range))return this.removeFold(s),s}if(t.siblings){var u=this.getParentFoldRangeData(e);if(u.range)var a=u.range.start.row+1,f=u.range.end.row;this.foldAll(a,f,t.all?1e4:0)}else t.children?(f=o?o.end.row:this.getLength(),this.foldAll(e+1,f,t.all?1e4:0)):o&&(t.all&&(o.collapseChildren=1e4),this.addFold("...",o));return o},this.toggleFoldWidget=function(e){var t=this.selection.getCursor().row;t=this.getRowFoldStart(t);var n=this.$toggleFoldWidget(t,{});if(n)return;var r=this.getParentFoldRangeData(t,!0);n=r.range||r.firstRange;if(n){t=n.start.row;var i=this.getFoldAt(t,this.getLine(t).length,1);i?this.removeFold(i):this.addFold("...",n)}},this.updateFoldWidgets=function(e){var t=e.start.row,n=e.end.row-t;if(n===0)this.foldWidgets[t]=null;else if(e.action=="remove")this.foldWidgets.splice(t,n+1,null);else{var r=Array(n+1);r.unshift(t,1),this.foldWidgets.splice.apply(this.foldWidgets,r)}},this.tokenizerUpdateFoldWidgets=function(e){var t=e.data;t.first!=t.last&&this.foldWidgets.length>t.first&&this.foldWidgets.splice(t.first,this.foldWidgets.length)}}var r=e("../range").Range,i=e("./fold_line").FoldLine,s=e("./fold").Fold,o=e("../token_iterator").TokenIterator;t.Folding=u}),define("ace/edit_session/bracket_match",["require","exports","module","ace/token_iterator","ace/range"],function(e,t,n){"use strict";function s(){this.findMatchingBracket=function(e,t){if(e.column==0)return null;var n=t||this.getLine(e.row).charAt(e.column-1);if(n=="")return null;var r=n.match(/([\(\[\{])|([\)\]\}])/);return r?r[1]?this.$findClosingBracket(r[1],e):this.$findOpeningBracket(r[2],e):null},this.getBracketRange=function(e){var t=this.getLine(e.row),n=!0,r,s=t.charAt(e.column-1),o=s&&s.match(/([\(\[\{])|([\)\]\}])/);o||(s=t.charAt(e.column),e={row:e.row,column:e.column+1},o=s&&s.match(/([\(\[\{])|([\)\]\}])/),n=!1);if(!o)return null;if(o[1]){var u=this.$findClosingBracket(o[1],e);if(!u)return null;r=i.fromPoints(e,u),n||(r.end.column++,r.start.column--),r.cursor=r.end}else{var u=this.$findOpeningBracket(o[2],e);if(!u)return null;r=i.fromPoints(u,e),n||(r.start.column++,r.end.column--),r.cursor=r.start}return r},this.getMatchingBracketRanges=function(e){var t=this.getLine(e.row),n=t.charAt(e.column-1),r=n&&n.match(/([\(\[\{])|([\)\]\}])/);r||(n=t.charAt(e.column),e={row:e.row,column:e.column+1},r=n&&n.match(/([\(\[\{])|([\)\]\}])/));if(!r)return null;var s=new i(e.row,e.column-1,e.row,e.column),o=r[1]?this.$findClosingBracket(r[1],e):this.$findOpeningBracket(r[2],e);if(!o)return[s];var u=new i(o.row,o.column,o.row,o.column+1);return[s,u]},this.$brackets={")":"(","(":")","]":"[","[":"]","{":"}","}":"{","<":">",">":"<"},this.$findOpeningBracket=function(e,t,n){var i=this.$brackets[e],s=1,o=new r(this,t.row,t.column),u=o.getCurrentToken();u||(u=o.stepForward());if(!u)return;n||(n=new RegExp("(\\.?"+u.type.replace(".","\\.").replace("rparen",".paren").replace(/\b(?:end)\b/,"(?:start|begin|end)")+")+"));var a=t.column-o.getCurrentTokenColumn()-2,f=u.value;for(;;){while(a>=0){var l=f.charAt(a);if(l==i){s-=1;if(s==0)return{row:o.getCurrentTokenRow(),column:a+o.getCurrentTokenColumn()}}else l==e&&(s+=1);a-=1}do u=o.stepBackward();while(u&&!n.test(u.type));if(u==null)break;f=u.value,a=f.length-1}return null},this.$findClosingBracket=function(e,t,n){var i=this.$brackets[e],s=1,o=new r(this,t.row,t.column),u=o.getCurrentToken();u||(u=o.stepForward());if(!u)return;n||(n=new RegExp("(\\.?"+u.type.replace(".","\\.").replace("lparen",".paren").replace(/\b(?:start|begin)\b/,"(?:start|begin|end)")+")+"));var a=t.column-o.getCurrentTokenColumn();for(;;){var f=u.value,l=f.length;while(a=4352&&e<=4447||e>=4515&&e<=4519||e>=4602&&e<=4607||e>=9001&&e<=9002||e>=11904&&e<=11929||e>=11931&&e<=12019||e>=12032&&e<=12245||e>=12272&&e<=12283||e>=12288&&e<=12350||e>=12353&&e<=12438||e>=12441&&e<=12543||e>=12549&&e<=12589||e>=12593&&e<=12686||e>=12688&&e<=12730||e>=12736&&e<=12771||e>=12784&&e<=12830||e>=12832&&e<=12871||e>=12880&&e<=13054||e>=13056&&e<=19903||e>=19968&&e<=42124||e>=42128&&e<=42182||e>=43360&&e<=43388||e>=44032&&e<=55203||e>=55216&&e<=55238||e>=55243&&e<=55291||e>=63744&&e<=64255||e>=65040&&e<=65049||e>=65072&&e<=65106||e>=65108&&e<=65126||e>=65128&&e<=65131||e>=65281&&e<=65376||e>=65504&&e<=65510}r.implement(this,u),this.setDocument=function(e){this.doc&&this.doc.removeListener("change",this.$onChange),this.doc=e,e.on("change",this.$onChange),this.bgTokenizer&&this.bgTokenizer.setDocument(this.getDocument()),this.resetCaches()},this.getDocument=function(){return this.doc},this.$resetRowCache=function(e){if(!e){this.$docRowCache=[],this.$screenRowCache=[];return}var t=this.$docRowCache.length,n=this.$getRowCacheIndex(this.$docRowCache,e)+1;t>n&&(this.$docRowCache.splice(n,t),this.$screenRowCache.splice(n,t))},this.$getRowCacheIndex=function(e,t){var n=0,r=e.length-1;while(n<=r){var i=n+r>>1,s=e[i];if(t>s)n=i+1;else{if(!(t=t)break}return r=n[s],r?(r.index=s,r.start=i-r.value.length,r):null},this.setUndoManager=function(e){this.$undoManager=e,this.$informUndoManager&&this.$informUndoManager.cancel();if(e){var t=this;e.addSession(this),this.$syncInformUndoManager=function(){t.$informUndoManager.cancel(),t.mergeUndoDeltas=!1},this.$informUndoManager=i.delayedCall(this.$syncInformUndoManager)}else this.$syncInformUndoManager=function(){}},this.markUndoGroup=function(){this.$syncInformUndoManager&&this.$syncInformUndoManager()},this.$defaultUndoManager={undo:function(){},redo:function(){},hasUndo:function(){},hasRedo:function(){},reset:function(){},add:function(){},addSelection:function(){},startNewGroup:function(){},addSession:function(){}},this.getUndoManager=function(){return this.$undoManager||this.$defaultUndoManager},this.getTabString=function(){return this.getUseSoftTabs()?i.stringRepeat(" ",this.getTabSize()):" "},this.setUseSoftTabs=function(e){this.setOption("useSoftTabs",e)},this.getUseSoftTabs=function(){return this.$useSoftTabs&&!this.$mode.$indentWithTabs},this.setTabSize=function(e){this.setOption("tabSize",e)},this.getTabSize=function(){return this.$tabSize},this.isTabStop=function(e){return this.$useSoftTabs&&e.column%this.$tabSize===0},this.setNavigateWithinSoftTabs=function(e){this.setOption("navigateWithinSoftTabs",e)},this.getNavigateWithinSoftTabs=function(){return this.$navigateWithinSoftTabs},this.$overwrite=!1,this.setOverwrite=function(e){this.setOption("overwrite",e)},this.getOverwrite=function(){return this.$overwrite},this.toggleOverwrite=function(){this.setOverwrite(!this.$overwrite)},this.addGutterDecoration=function(e,t){this.$decorations[e]||(this.$decorations[e]=""),this.$decorations[e]+=" "+t,this._signal("changeBreakpoint",{})},this.removeGutterDecoration=function(e,t){this.$decorations[e]=(this.$decorations[e]||"").replace(" "+t,""),this._signal("changeBreakpoint",{})},this.getBreakpoints=function(){return this.$breakpoints},this.setBreakpoints=function(e){this.$breakpoints=[];for(var t=0;t0&&(r=!!n.charAt(t-1).match(this.tokenRe)),r||(r=!!n.charAt(t).match(this.tokenRe));if(r)var i=this.tokenRe;else if(/^\s+$/.test(n.slice(t-1,t+1)))var i=/\s/;else var i=this.nonTokenRe;var s=t;if(s>0){do s--;while(s>=0&&n.charAt(s).match(i));s++}var o=t;while(oe&&(e=t.screenWidth)}),this.lineWidgetWidth=e},this.$computeWidth=function(e){if(this.$modified||e){this.$modified=!1;if(this.$useWrapMode)return this.screenWidth=this.$wrapLimit;var t=this.doc.getAllLines(),n=this.$rowLengthCache,r=0,i=0,s=this.$foldData[i],o=s?s.start.row:Infinity,u=t.length;for(var a=0;ao){a=s.end.row+1;if(a>=u)break;s=this.$foldData[i++],o=s?s.start.row:Infinity}n[a]==null&&(n[a]=this.$getStringScreenWidth(t[a])[0]),n[a]>r&&(r=n[a])}this.screenWidth=r}},this.getLine=function(e){return this.doc.getLine(e)},this.getLines=function(e,t){return this.doc.getLines(e,t)},this.getLength=function(){return this.doc.getLength()},this.getTextRange=function(e){return this.doc.getTextRange(e||this.selection.getRange())},this.insert=function(e,t){return this.doc.insert(e,t)},this.remove=function(e){return this.doc.remove(e)},this.removeFullLines=function(e,t){return this.doc.removeFullLines(e,t)},this.undoChanges=function(e,t){if(!e.length)return;this.$fromUndo=!0;for(var n=e.length-1;n!=-1;n--){var r=e[n];r.action=="insert"||r.action=="remove"?this.doc.revertDelta(r):r.folds&&this.addFolds(r.folds)}!t&&this.$undoSelect&&(e.selectionBefore?this.selection.fromJSON(e.selectionBefore):this.selection.setRange(this.$getUndoSelection(e,!0))),this.$fromUndo=!1},this.redoChanges=function(e,t){if(!e.length)return;this.$fromUndo=!0;for(var n=0;ne.end.column&&(s.start.column+=u),s.end.row==e.end.row&&s.end.column>e.end.column&&(s.end.column+=u)),o&&s.start.row>=e.end.row&&(s.start.row+=o,s.end.row+=o)}s.end=this.insert(s.start,r);if(i.length){var a=e.start,f=s.start,o=f.row-a.row,u=f.column-a.column;this.addFolds(i.map(function(e){return e=e.clone(),e.start.row==a.row&&(e.start.column+=u),e.end.row==a.row&&(e.end.column+=u),e.start.row+=o,e.end.row+=o,e}))}return s},this.indentRows=function(e,t,n){n=n.replace(/\t/g,this.getTabString());for(var r=e;r<=t;r++)this.doc.insertInLine({row:r,column:0},n)},this.outdentRows=function(e){var t=e.collapseRows(),n=new l(0,0,0,0),r=this.getTabSize();for(var i=t.start.row;i<=t.end.row;++i){var s=this.getLine(i);n.start.row=i,n.end.row=i;for(var o=0;o0){var r=this.getRowFoldEnd(t+n);if(r>this.doc.getLength()-1)return 0;var i=r-t}else{e=this.$clipRowToDocument(e),t=this.$clipRowToDocument(t);var i=t-e+1}var s=new l(e,0,t,Number.MAX_VALUE),o=this.getFoldsInRange(s).map(function(e){return e=e.clone(),e.start.row+=i,e.end.row+=i,e}),u=n==0?this.doc.getLines(e,t):this.doc.removeFullLines(e,t);return this.doc.insertFullLines(e+i,u),o.length&&this.addFolds(o),i},this.moveLinesUp=function(e,t){return this.$moveLines(e,t,-1)},this.moveLinesDown=function(e,t){return this.$moveLines(e,t,1)},this.duplicateLines=function(e,t){return this.$moveLines(e,t,0)},this.$clipRowToDocument=function(e){return Math.max(0,Math.min(e,this.doc.getLength()-1))},this.$clipColumnToRow=function(e,t){return t<0?0:Math.min(this.doc.getLine(e).length,t)},this.$clipPositionToDocument=function(e,t){t=Math.max(0,t);if(e<0)e=0,t=0;else{var n=this.doc.getLength();e>=n?(e=n-1,t=this.doc.getLine(n-1).length):t=Math.min(this.doc.getLine(e).length,t)}return{row:e,column:t}},this.$clipRangeToDocument=function(e){e.start.row<0?(e.start.row=0,e.start.column=0):e.start.column=this.$clipColumnToRow(e.start.row,e.start.column);var t=this.doc.getLength()-1;return e.end.row>t?(e.end.row=t,e.end.column=this.doc.getLine(t).length):e.end.column=this.$clipColumnToRow(e.end.row,e.end.column),e},this.$wrapLimit=80,this.$useWrapMode=!1,this.$wrapLimitRange={min:null,max:null},this.setUseWrapMode=function(e){if(e!=this.$useWrapMode){this.$useWrapMode=e,this.$modified=!0,this.$resetRowCache(0);if(e){var t=this.getLength();this.$wrapData=Array(t),this.$updateWrapData(0,t-1)}this._signal("changeWrapMode")}},this.getUseWrapMode=function(){return this.$useWrapMode},this.setWrapLimitRange=function(e,t){if(this.$wrapLimitRange.min!==e||this.$wrapLimitRange.max!==t)this.$wrapLimitRange={min:e,max:t},this.$modified=!0,this.$bidiHandler.markAsDirty(),this.$useWrapMode&&this._signal("changeWrapMode")},this.adjustWrapLimit=function(e,t){var n=this.$wrapLimitRange;n.max<0&&(n={min:t,max:t});var r=this.$constrainWrapLimit(e,n.min,n.max);return r!=this.$wrapLimit&&r>1?(this.$wrapLimit=r,this.$modified=!0,this.$useWrapMode&&(this.$updateWrapData(0,this.getLength()-1),this.$resetRowCache(0),this._signal("changeWrapLimit")),!0):!1},this.$constrainWrapLimit=function(e,t,n){return t&&(e=Math.max(t,e)),n&&(e=Math.min(n,e)),e},this.getWrapLimit=function(){return this.$wrapLimit},this.setWrapLimit=function(e){this.setWrapLimitRange(e,e)},this.getWrapLimitRange=function(){return{min:this.$wrapLimitRange.min,max:this.$wrapLimitRange.max}},this.$updateInternalDataOnChange=function(e){var t=this.$useWrapMode,n=e.action,r=e.start,i=e.end,s=r.row,o=i.row,u=o-s,a=null;this.$updating=!0;if(u!=0)if(n==="remove"){this[t?"$wrapData":"$rowLengthCache"].splice(s,u);var f=this.$foldData;a=this.getFoldsInRange(e),this.removeFolds(a);var l=this.getFoldLine(i.row),c=0;if(l){l.addRemoveChars(i.row,i.column,r.column-i.column),l.shiftRow(-u);var h=this.getFoldLine(s);h&&h!==l&&(h.merge(l),l=h),c=f.indexOf(l)+1}for(c;c=i.row&&l.shiftRow(-u)}o=s}else{var p=Array(u);p.unshift(s,0);var d=t?this.$wrapData:this.$rowLengthCache;d.splice.apply(d,p);var f=this.$foldData,l=this.getFoldLine(s),c=0;if(l){var v=l.range.compareInside(r.row,r.column);v==0?(l=l.split(r.row,r.column),l&&(l.shiftRow(u),l.addRemoveChars(o,0,i.column-r.column))):v==-1&&(l.addRemoveChars(s,0,i.column-r.column),l.shiftRow(u)),c=f.indexOf(l)+1}for(c;c=s&&l.shiftRow(u)}}else{u=Math.abs(e.start.column-e.end.column),n==="remove"&&(a=this.getFoldsInRange(e),this.removeFolds(a),u=-u);var l=this.getFoldLine(s);l&&l.addRemoveChars(s,r.column,u)}return t&&this.$wrapData.length!=this.doc.getLength()&&console.error("doc.getLength() and $wrapData.length have to be the same!"),this.$updating=!1,t?this.$updateWrapData(s,o):this.$updateRowLengthCache(s,o),a},this.$updateRowLengthCache=function(e,t,n){this.$rowLengthCache[e]=null,this.$rowLengthCache[t]=null},this.$updateWrapData=function(e,t){var r=this.doc.getAllLines(),i=this.getTabSize(),o=this.$wrapData,u=this.$wrapLimit,a,f,l=e;t=Math.min(t,r.length-1);while(l<=t)f=this.getFoldLine(l,f),f?(a=[],f.walk(function(e,t,i,o){var u;if(e!=null){u=this.$getDisplayTokens(e,a.length),u[0]=n;for(var f=1;fr-b){var w=f+r-b;if(e[w-1]>=c&&e[w]>=c){y(w);continue}if(e[w]==n||e[w]==s){for(w;w!=f-1;w--)if(e[w]==n)break;if(w>f){y(w);continue}w=f+r;for(w;w>2)),f-1);while(w>E&&e[w]E&&e[w]E&&e[w]==a)w--}else while(w>E&&e[w]E){y(++w);continue}w=f+r,e[w]==t&&w--,y(w-b)}return o},this.$getDisplayTokens=function(n,r){var i=[],s;r=r||0;for(var o=0;o39&&u<48||u>57&&u<64?i.push(a):u>=4352&&m(u)?i.push(e,t):i.push(e)}return i},this.$getStringScreenWidth=function(e,t,n){if(t==0)return[0,0];t==null&&(t=Infinity),n=n||0;var r,i;for(i=0;i=4352&&m(r)?n+=2:n+=1;if(n>t)break}return[n,i]},this.lineWidgets=null,this.getRowLength=function(e){var t=1;return this.lineWidgets&&(t+=this.lineWidgets[e]&&this.lineWidgets[e].rowCount||0),!this.$useWrapMode||!this.$wrapData[e]?t:this.$wrapData[e].length+t},this.getRowLineCount=function(e){return!this.$useWrapMode||!this.$wrapData[e]?1:this.$wrapData[e].length+1},this.getRowWrapIndent=function(e){if(this.$useWrapMode){var t=this.screenToDocumentPosition(e,Number.MAX_VALUE),n=this.$wrapData[t.row];return n.length&&n[0]=0)var u=f[l],i=this.$docRowCache[l],h=e>f[c-1];else var h=!c;var p=this.getLength()-1,d=this.getNextFoldLine(i),v=d?d.start.row:Infinity;while(u<=e){a=this.getRowLength(i);if(u+a>e||i>=p)break;u+=a,i++,i>v&&(i=d.end.row+1,d=this.getNextFoldLine(i,d),v=d?d.start.row:Infinity),h&&(this.$docRowCache.push(i),this.$screenRowCache.push(u))}if(d&&d.start.row<=i)r=this.getFoldDisplayLine(d),i=d.start.row;else{if(u+a<=e||i>p)return{row:p,column:this.getLine(p).length};r=this.getLine(i),d=null}var m=0,g=Math.floor(e-u);if(this.$useWrapMode){var y=this.$wrapData[i];y&&(o=y[g],g>0&&y.length&&(m=y.indent,s=y[g-1]||y[y.length-1],r=r.substring(s)))}return n!==undefined&&this.$bidiHandler.isBidiRow(u+g,i,g)&&(t=this.$bidiHandler.offsetToCol(n)),s+=this.$getStringScreenWidth(r,t-m)[1],this.$useWrapMode&&s>=o&&(s=o-1),d?d.idxToPosition(s):{row:i,column:s}},this.documentToScreenPosition=function(e,t){if(typeof t=="undefined")var n=this.$clipPositionToDocument(e.row,e.column);else n=this.$clipPositionToDocument(e,t);e=n.row,t=n.column;var r=0,i=null,s=null;s=this.getFoldAt(e,t,1),s&&(e=s.start.row,t=s.start.column);var o,u=0,a=this.$docRowCache,f=this.$getRowCacheIndex(a,e),l=a.length;if(l&&f>=0)var u=a[f],r=this.$screenRowCache[f],c=e>a[l-1];else var c=!l;var h=this.getNextFoldLine(u),p=h?h.start.row:Infinity;while(u=p){o=h.end.row+1;if(o>e)break;h=this.getNextFoldLine(o,h),p=h?h.start.row:Infinity}else o=u+1;r+=this.getRowLength(u),u=o,c&&(this.$docRowCache.push(u),this.$screenRowCache.push(r))}var d="";h&&u>=p?(d=this.getFoldDisplayLine(h,e,t),i=h.start.row):(d=this.getLine(e).substring(0,t),i=e);var v=0;if(this.$useWrapMode){var m=this.$wrapData[i];if(m){var g=0;while(d.length>=m[g])r++,g++;d=d.substring(m[g-1]||0,d.length),v=g>0?m.indent:0}}return this.lineWidgets&&this.lineWidgets[u]&&this.lineWidgets[u].rowsAbove&&(r+=this.lineWidgets[u].rowsAbove),{row:r,column:v+this.$getStringScreenWidth(d)[0]}},this.documentToScreenColumn=function(e,t){return this.documentToScreenPosition(e,t).column},this.documentToScreenRow=function(e,t){return this.documentToScreenPosition(e,t).row},this.getScreenLength=function(){var e=0,t=null;if(!this.$useWrapMode){e=this.getLength();var n=this.$foldData;for(var r=0;ro&&(s=t.end.row+1,t=this.$foldData[r++],o=t?t.start.row:Infinity)}}return this.lineWidgets&&(e+=this.$getWidgetScreenLength()),e},this.$setFontMetrics=function(e){if(!this.$enableVarChar)return;this.$getStringScreenWidth=function(t,n,r){if(n===0)return[0,0];n||(n=Infinity),r=r||0;var i,s;for(s=0;sn)break}return[r,s]}},this.destroy=function(){this.bgTokenizer&&(this.bgTokenizer.setDocument(null),this.bgTokenizer=null),this.$stopWorker()},this.isFullWidth=m}.call(d.prototype),e("./edit_session/folding").Folding.call(d.prototype),e("./edit_session/bracket_match").BracketMatch.call(d.prototype),o.defineOptions(d.prototype,"session",{wrap:{set:function(e){!e||e=="off"?e=!1:e=="free"?e=!0:e=="printMargin"?e=-1:typeof e=="string"&&(e=parseInt(e,10)||!1);if(this.$wrap==e)return;this.$wrap=e;if(!e)this.setUseWrapMode(!1);else{var t=typeof e=="number"?e:null;this.setWrapLimitRange(t,t),this.setUseWrapMode(!0)}},get:function(){return this.getUseWrapMode()?this.$wrap==-1?"printMargin":this.getWrapLimitRange().min?this.$wrap:"free":"off"},handlesSet:!0},wrapMethod:{set:function(e){e=e=="auto"?this.$mode.type!="text":e!="text",e!=this.$wrapAsCode&&(this.$wrapAsCode=e,this.$useWrapMode&&(this.$useWrapMode=!1,this.setUseWrapMode(!0)))},initialValue:"auto"},indentedSoftWrap:{set:function(){this.$useWrapMode&&(this.$useWrapMode=!1,this.setUseWrapMode(!0))},initialValue:!0},firstLineNumber:{set:function(){this._signal("changeBreakpoint")},initialValue:1},useWorker:{set:function(e){this.$useWorker=e,this.$stopWorker(),e&&this.$startWorker()},initialValue:!0},useSoftTabs:{initialValue:!0},tabSize:{set:function(e){e=parseInt(e),e>0&&this.$tabSize!==e&&(this.$modified=!0,this.$rowLengthCache=[],this.$tabSize=e,this._signal("changeTabSize"))},initialValue:4,handlesSet:!0},navigateWithinSoftTabs:{initialValue:!1},foldStyle:{set:function(e){this.setFoldStyle(e)},handlesSet:!0},overwrite:{set:function(e){this._signal("changeOverwrite")},initialValue:!1},newLineMode:{set:function(e){this.doc.setNewLineMode(e)},get:function(){return this.doc.getNewLineMode()},handlesSet:!0},mode:{set:function(e){this.setMode(e)},get:function(){return this.$modeId},handlesSet:!0}}),t.EditSession=d}),define("ace/search",["require","exports","module","ace/lib/lang","ace/lib/oop","ace/range"],function(e,t,n){"use strict";function u(e,t){function n(e){return/\w/.test(e)||t.regExp?"\\b":""}return n(e[0])+e+n(e[e.length-1])}var r=e("./lib/lang"),i=e("./lib/oop"),s=e("./range").Range,o=function(){this.$options={}};(function(){this.set=function(e){return i.mixin(this.$options,e),this},this.getOptions=function(){return r.copyObject(this.$options)},this.setOptions=function(e){this.$options=e},this.find=function(e){var t=this.$options,n=this.$matchIterator(e,t);if(!n)return!1;var r=null;return n.forEach(function(e,n,i,o){return r=new s(e,n,i,o),n==o&&t.start&&t.start.start&&t.skipCurrent!=0&&r.isEqual(t.start)?(r=null,!1):!0}),r},this.findAll=function(e){var t=this.$options;if(!t.needle)return[];this.$assembleRegExp(t);var n=t.range,i=n?e.getLines(n.start.row,n.end.row):e.doc.getAllLines(),o=[],u=t.re;if(t.$isMultiLine){var a=u.length,f=i.length-a,l;e:for(var c=u.offset||0;c<=f;c++){for(var h=0;hv)continue;o.push(l=new s(c,v,c+a-1,m)),a>2&&(c=c+a-2)}}else for(var g=0;gE&&o[h].end.row==n.end.row)h--;o=o.slice(g,h+1);for(g=0,h=o.length;g=u;n--)if(c(n,Number.MAX_VALUE,e))return;if(t.wrap==0)return;for(n=a,u=o.row;n>=u;n--)if(c(n,Number.MAX_VALUE,e))return};else var f=function(e){var n=o.row;if(c(n,o.column,e))return;for(n+=1;n<=a;n++)if(c(n,0,e))return;if(t.wrap==0)return;for(n=u,a=o.row;n<=a;n++)if(c(n,0,e))return};if(t.$isMultiLine)var l=n.length,c=function(t,i,s){var o=r?t-l+1:t;if(o<0)return;var u=e.getLine(o),a=u.search(n[0]);if(!r&&ai)return;if(s(o,a,o+l-1,c))return!0};else if(r)var c=function(t,r,i){var s=e.getLine(t),o=[],u,a=0;n.lastIndex=0;while(u=n.exec(s)){var f=u[0].length;a=u.index;if(!f){if(a>=s.length)break;n.lastIndex=a+=1}if(u.index+f>r)break;o.push(u.index,f)}for(var l=o.length-1;l>=0;l-=2){var c=o[l-1],f=o[l];if(i(t,c,t,c+f))return!0}};else var c=function(t,r,i){var s=e.getLine(t),o,u;n.lastIndex=r;while(u=n.exec(s)){var a=u[0].length;o=u.index;if(i(t,o,t,o+a))return!0;if(!a){n.lastIndex=o+=1;if(o>=s.length)return!1}}};return{forEach:f}}}).call(o.prototype),t.Search=o}),define("ace/keyboard/hash_handler",["require","exports","module","ace/lib/keys","ace/lib/useragent"],function(e,t,n){"use strict";function o(e,t){this.platform=t||(i.isMac?"mac":"win"),this.commands={},this.commandKeyBinding={},this.addCommands(e),this.$singleCommand=!0}function u(e,t){o.call(this,e,t),this.$singleCommand=!1}var r=e("../lib/keys"),i=e("../lib/useragent"),s=r.KEY_MODS;u.prototype=o.prototype,function(){function e(e){return typeof e=="object"&&e.bindKey&&e.bindKey.position||(e.isDefault?-100:0)}this.addCommand=function(e){this.commands[e.name]&&this.removeCommand(e),this.commands[e.name]=e,e.bindKey&&this._buildKeyHash(e)},this.removeCommand=function(e,t){var n=e&&(typeof e=="string"?e:e.name);e=this.commands[n],t||delete this.commands[n];var r=this.commandKeyBinding;for(var i in r){var s=r[i];if(s==e)delete r[i];else if(Array.isArray(s)){var o=s.indexOf(e);o!=-1&&(s.splice(o,1),s.length==1&&(r[i]=s[0]))}}},this.bindKey=function(e,t,n){typeof e=="object"&&e&&(n==undefined&&(n=e.position),e=e[this.platform]);if(!e)return;if(typeof t=="function")return this.addCommand({exec:t,bindKey:e,name:t.name||e});e.split("|").forEach(function(e){var r="";if(e.indexOf(" ")!=-1){var i=e.split(/\s+/);e=i.pop(),i.forEach(function(e){var t=this.parseKeys(e),n=s[t.hashId]+t.key;r+=(r?" ":"")+n,this._addCommandToBinding(r,"chainKeys")},this),r+=" "}var o=this.parseKeys(e),u=s[o.hashId]+o.key;this._addCommandToBinding(r+u,t,n)},this)},this._addCommandToBinding=function(t,n,r){var i=this.commandKeyBinding,s;if(!n)delete i[t];else if(!i[t]||this.$singleCommand)i[t]=n;else{Array.isArray(i[t])?(s=i[t].indexOf(n))!=-1&&i[t].splice(s,1):i[t]=[i[t]],typeof r!="number"&&(r=e(n));var o=i[t];for(s=0;sr)break}o.splice(s,0,n)}},this.addCommands=function(e){e&&Object.keys(e).forEach(function(t){var n=e[t];if(!n)return;if(typeof n=="string")return this.bindKey(n,t);typeof n=="function"&&(n={exec:n});if(typeof n!="object")return;n.name||(n.name=t),this.addCommand(n)},this)},this.removeCommands=function(e){Object.keys(e).forEach(function(t){this.removeCommand(e[t])},this)},this.bindKeys=function(e){Object.keys(e).forEach(function(t){this.bindKey(t,e[t])},this)},this._buildKeyHash=function(e){this.bindKey(e.bindKey,e)},this.parseKeys=function(e){var t=e.toLowerCase().split(/[\-\+]([\-\+])?/).filter(function(e){return e}),n=t.pop(),i=r[n];if(r.FUNCTION_KEYS[i])n=r.FUNCTION_KEYS[i].toLowerCase();else{if(!t.length)return{key:n,hashId:-1};if(t.length==1&&t[0]=="shift")return{key:n.toUpperCase(),hashId:-1}}var s=0;for(var o=t.length;o--;){var u=r.KEY_MODS[t[o]];if(u==null)return typeof console!="undefined"&&console.error("invalid modifier "+t[o]+" in "+e),!1;s|=u}return{key:n,hashId:s}},this.findKeyCommand=function(t,n){var r=s[t]+n;return this.commandKeyBinding[r]},this.handleKeyboard=function(e,t,n,r){if(r<0)return;var i=s[t]+n,o=this.commandKeyBinding[i];e.$keyChain&&(e.$keyChain+=" "+i,o=this.commandKeyBinding[e.$keyChain]||o);if(o)if(o=="chainKeys"||o[o.length-1]=="chainKeys")return e.$keyChain=e.$keyChain||i,{command:"null"};if(e.$keyChain)if(!!t&&t!=4||n.length!=1){if(t==-1||r>0)e.$keyChain=""}else e.$keyChain=e.$keyChain.slice(0,-i.length-1);return{command:o}},this.getStatusText=function(e,t){return t.$keyChain||""}}.call(o.prototype),t.HashHandler=o,t.MultiHashHandler=u}),define("ace/commands/command_manager",["require","exports","module","ace/lib/oop","ace/keyboard/hash_handler","ace/lib/event_emitter"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("../keyboard/hash_handler").MultiHashHandler,s=e("../lib/event_emitter").EventEmitter,o=function(e,t){i.call(this,t,e),this.byName=this.commands,this.setDefaultHandler("exec",function(e){return e.command.exec(e.editor,e.args||{})})};r.inherits(o,i),function(){r.implement(this,s),this.exec=function(e,t,n){if(Array.isArray(e)){for(var r=e.length;r--;)if(this.exec(e[r],t,n))return!0;return!1}typeof e=="string"&&(e=this.commands[e]);if(!e)return!1;if(t&&t.$readOnly&&!e.readOnly)return!1;if(this.$checkCommandState!=0&&e.isAvailable&&!e.isAvailable(t))return!1;var i={editor:t,command:e,args:n};return i.returnValue=this._emit("exec",i),this._signal("afterExec",i),i.returnValue===!1?!1:!0},this.toggleRecording=function(e){if(this.$inReplay)return;return e&&e._emit("changeStatus"),this.recording?(this.macro.pop(),this.removeEventListener("exec",this.$addCommandToMacro),this.macro.length||(this.macro=this.oldMacro),this.recording=!1):(this.$addCommandToMacro||(this.$addCommandToMacro=function(e){this.macro.push([e.command,e.args])}.bind(this)),this.oldMacro=this.macro,this.macro=[],this.on("exec",this.$addCommandToMacro),this.recording=!0)},this.replay=function(e){if(this.$inReplay||!this.macro)return;if(this.recording)return this.toggleRecording(e);try{this.$inReplay=!0,this.macro.forEach(function(t){typeof t=="string"?this.exec(t,e):this.exec(t[0],e,t[1])},this)}finally{this.$inReplay=!1}},this.trimMacro=function(e){return e.map(function(e){return typeof e[0]!="string"&&(e[0]=e[0].name),e[1]||(e=e[0]),e})}}.call(o.prototype),t.CommandManager=o}),define("ace/commands/default_commands",["require","exports","module","ace/lib/lang","ace/config","ace/range"],function(e,t,n){"use strict";function o(e,t){return{win:e,mac:t}}var r=e("../lib/lang"),i=e("../config"),s=e("../range").Range;t.commands=[{name:"showSettingsMenu",bindKey:o("Ctrl-,","Command-,"),exec:function(e){i.loadModule("ace/ext/settings_menu",function(t){t.init(e),e.showSettingsMenu()})},readOnly:!0},{name:"goToNextError",bindKey:o("Alt-E","F4"),exec:function(e){i.loadModule("./ext/error_marker",function(t){t.showErrorMarker(e,1)})},scrollIntoView:"animate",readOnly:!0},{name:"goToPreviousError",bindKey:o("Alt-Shift-E","Shift-F4"),exec:function(e){i.loadModule("./ext/error_marker",function(t){t.showErrorMarker(e,-1)})},scrollIntoView:"animate",readOnly:!0},{name:"selectall",description:"Select all",bindKey:o("Ctrl-A","Command-A"),exec:function(e){e.selectAll()},readOnly:!0},{name:"centerselection",description:"Center selection",bindKey:o(null,"Ctrl-L"),exec:function(e){e.centerSelection()},readOnly:!0},{name:"gotoline",description:"Go to line...",bindKey:o("Ctrl-L","Command-L"),exec:function(e,t){typeof t=="number"&&!isNaN(t)&&e.gotoLine(t),e.prompt({$type:"gotoLine"})},readOnly:!0},{name:"fold",bindKey:o("Alt-L|Ctrl-F1","Command-Alt-L|Command-F1"),exec:function(e){e.session.toggleFold(!1)},multiSelectAction:"forEach",scrollIntoView:"center",readOnly:!0},{name:"unfold",bindKey:o("Alt-Shift-L|Ctrl-Shift-F1","Command-Alt-Shift-L|Command-Shift-F1"),exec:function(e){e.session.toggleFold(!0)},multiSelectAction:"forEach",scrollIntoView:"center",readOnly:!0},{name:"toggleFoldWidget",bindKey:o("F2","F2"),exec:function(e){e.session.toggleFoldWidget()},multiSelectAction:"forEach",scrollIntoView:"center",readOnly:!0},{name:"toggleParentFoldWidget",bindKey:o("Alt-F2","Alt-F2"),exec:function(e){e.session.toggleFoldWidget(!0)},multiSelectAction:"forEach",scrollIntoView:"center",readOnly:!0},{name:"foldall",description:"Fold all",bindKey:o(null,"Ctrl-Command-Option-0"),exec:function(e){e.session.foldAll()},scrollIntoView:"center",readOnly:!0},{name:"foldOther",description:"Fold other",bindKey:o("Alt-0","Command-Option-0"),exec:function(e){e.session.foldAll(),e.session.unfold(e.selection.getAllRanges())},scrollIntoView:"center",readOnly:!0},{name:"unfoldall",description:"Unfold all",bindKey:o("Alt-Shift-0","Command-Option-Shift-0"),exec:function(e){e.session.unfold()},scrollIntoView:"center",readOnly:!0},{name:"findnext",description:"Find next",bindKey:o("Ctrl-K","Command-G"),exec:function(e){e.findNext()},multiSelectAction:"forEach",scrollIntoView:"center",readOnly:!0},{name:"findprevious",description:"Find previous",bindKey:o("Ctrl-Shift-K","Command-Shift-G"),exec:function(e){e.findPrevious()},multiSelectAction:"forEach",scrollIntoView:"center",readOnly:!0},{name:"selectOrFindNext",description:"Select or find next",bindKey:o("Alt-K","Ctrl-G"),exec:function(e){e.selection.isEmpty()?e.selection.selectWord():e.findNext()},readOnly:!0},{name:"selectOrFindPrevious",description:"Select or find previous",bindKey:o("Alt-Shift-K","Ctrl-Shift-G"),exec:function(e){e.selection.isEmpty()?e.selection.selectWord():e.findPrevious()},readOnly:!0},{name:"find",description:"Find",bindKey:o("Ctrl-F","Command-F"),exec:function(e){i.loadModule("ace/ext/searchbox",function(t){t.Search(e)})},readOnly:!0},{name:"overwrite",description:"Overwrite",bindKey:"Insert",exec:function(e){e.toggleOverwrite()},readOnly:!0},{name:"selecttostart",description:"Select to start",bindKey:o("Ctrl-Shift-Home","Command-Shift-Home|Command-Shift-Up"),exec:function(e){e.getSelection().selectFileStart()},multiSelectAction:"forEach",readOnly:!0,scrollIntoView:"animate",aceCommandGroup:"fileJump"},{name:"gotostart",description:"Go to start",bindKey:o("Ctrl-Home","Command-Home|Command-Up"),exec:function(e){e.navigateFileStart()},multiSelectAction:"forEach",readOnly:!0,scrollIntoView:"animate",aceCommandGroup:"fileJump"},{name:"selectup",description:"Select up",bindKey:o("Shift-Up","Shift-Up|Ctrl-Shift-P"),exec:function(e){e.getSelection().selectUp()},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"golineup",description:"Go line up",bindKey:o("Up","Up|Ctrl-P"),exec:function(e,t){e.navigateUp(t.times)},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"selecttoend",description:"Select to end",bindKey:o("Ctrl-Shift-End","Command-Shift-End|Command-Shift-Down"),exec:function(e){e.getSelection().selectFileEnd()},multiSelectAction:"forEach",readOnly:!0,scrollIntoView:"animate",aceCommandGroup:"fileJump"},{name:"gotoend",description:"Go to end",bindKey:o("Ctrl-End","Command-End|Command-Down"),exec:function(e){e.navigateFileEnd()},multiSelectAction:"forEach",readOnly:!0,scrollIntoView:"animate",aceCommandGroup:"fileJump"},{name:"selectdown",description:"Select down",bindKey:o("Shift-Down","Shift-Down|Ctrl-Shift-N"),exec:function(e){e.getSelection().selectDown()},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"golinedown",description:"Go line down",bindKey:o("Down","Down|Ctrl-N"),exec:function(e,t){e.navigateDown(t.times)},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"selectwordleft",description:"Select word left",bindKey:o("Ctrl-Shift-Left","Option-Shift-Left"),exec:function(e){e.getSelection().selectWordLeft()},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"gotowordleft",description:"Go to word left",bindKey:o("Ctrl-Left","Option-Left"),exec:function(e){e.navigateWordLeft()},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"selecttolinestart",description:"Select to line start",bindKey:o("Alt-Shift-Left","Command-Shift-Left|Ctrl-Shift-A"),exec:function(e){e.getSelection().selectLineStart()},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"gotolinestart",description:"Go to line start",bindKey:o("Alt-Left|Home","Command-Left|Home|Ctrl-A"),exec:function(e){e.navigateLineStart()},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"selectleft",description:"Select left",bindKey:o("Shift-Left","Shift-Left|Ctrl-Shift-B"),exec:function(e){e.getSelection().selectLeft()},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"gotoleft",description:"Go to left",bindKey:o("Left","Left|Ctrl-B"),exec:function(e,t){e.navigateLeft(t.times)},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"selectwordright",description:"Select word right",bindKey:o("Ctrl-Shift-Right","Option-Shift-Right"),exec:function(e){e.getSelection().selectWordRight()},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"gotowordright",description:"Go to word right",bindKey:o("Ctrl-Right","Option-Right"),exec:function(e){e.navigateWordRight()},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"selecttolineend",description:"Select to line end",bindKey:o("Alt-Shift-Right","Command-Shift-Right|Shift-End|Ctrl-Shift-E"),exec:function(e){e.getSelection().selectLineEnd()},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"gotolineend",description:"Go to line end",bindKey:o("Alt-Right|End","Command-Right|End|Ctrl-E"),exec:function(e){e.navigateLineEnd()},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"selectright",description:"Select right",bindKey:o("Shift-Right","Shift-Right"),exec:function(e){e.getSelection().selectRight()},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"gotoright",description:"Go to right",bindKey:o("Right","Right|Ctrl-F"),exec:function(e,t){e.navigateRight(t.times)},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"selectpagedown",description:"Select page down",bindKey:"Shift-PageDown",exec:function(e){e.selectPageDown()},readOnly:!0},{name:"pagedown",description:"Page down",bindKey:o(null,"Option-PageDown"),exec:function(e){e.scrollPageDown()},readOnly:!0},{name:"gotopagedown",description:"Go to page down",bindKey:o("PageDown","PageDown|Ctrl-V"),exec:function(e){e.gotoPageDown()},readOnly:!0},{name:"selectpageup",description:"Select page up",bindKey:"Shift-PageUp",exec:function(e){e.selectPageUp()},readOnly:!0},{name:"pageup",description:"Page up",bindKey:o(null,"Option-PageUp"),exec:function(e){e.scrollPageUp()},readOnly:!0},{name:"gotopageup",description:"Go to page up",bindKey:"PageUp",exec:function(e){e.gotoPageUp()},readOnly:!0},{name:"scrollup",description:"Scroll up",bindKey:o("Ctrl-Up",null),exec:function(e){e.renderer.scrollBy(0,-2*e.renderer.layerConfig.lineHeight)},readOnly:!0},{name:"scrolldown",description:"Scroll down",bindKey:o("Ctrl-Down",null),exec:function(e){e.renderer.scrollBy(0,2*e.renderer.layerConfig.lineHeight)},readOnly:!0},{name:"selectlinestart",description:"Select line start",bindKey:"Shift-Home",exec:function(e){e.getSelection().selectLineStart()},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"selectlineend",description:"Select line end",bindKey:"Shift-End",exec:function(e){e.getSelection().selectLineEnd()},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"togglerecording",description:"Toggle recording",bindKey:o("Ctrl-Alt-E","Command-Option-E"),exec:function(e){e.commands.toggleRecording(e)},readOnly:!0},{name:"replaymacro",description:"Replay macro",bindKey:o("Ctrl-Shift-E","Command-Shift-E"),exec:function(e){e.commands.replay(e)},readOnly:!0},{name:"jumptomatching",description:"Jump to matching",bindKey:o("Ctrl-\\|Ctrl-P","Command-\\"),exec:function(e){e.jumpToMatching()},multiSelectAction:"forEach",scrollIntoView:"animate",readOnly:!0},{name:"selecttomatching",description:"Select to matching",bindKey:o("Ctrl-Shift-\\|Ctrl-Shift-P","Command-Shift-\\"),exec:function(e){e.jumpToMatching(!0)},multiSelectAction:"forEach",scrollIntoView:"animate",readOnly:!0},{name:"expandToMatching",description:"Expand to matching",bindKey:o("Ctrl-Shift-M","Ctrl-Shift-M"),exec:function(e){e.jumpToMatching(!0,!0)},multiSelectAction:"forEach",scrollIntoView:"animate",readOnly:!0},{name:"passKeysToBrowser",description:"Pass keys to browser",bindKey:o(null,null),exec:function(){},passEvent:!0,readOnly:!0},{name:"copy",description:"Copy",exec:function(e){},readOnly:!0},{name:"cut",description:"Cut",exec:function(e){var t=e.$copyWithEmptySelection&&e.selection.isEmpty(),n=t?e.selection.getLineRange():e.selection.getRange();e._emit("cut",n),n.isEmpty()||e.session.remove(n),e.clearSelection()},scrollIntoView:"cursor",multiSelectAction:"forEach"},{name:"paste",description:"Paste",exec:function(e,t){e.$handlePaste(t)},scrollIntoView:"cursor"},{name:"removeline",description:"Remove line",bindKey:o("Ctrl-D","Command-D"),exec:function(e){e.removeLines()},scrollIntoView:"cursor",multiSelectAction:"forEachLine"},{name:"duplicateSelection",description:"Duplicate selection",bindKey:o("Ctrl-Shift-D","Command-Shift-D"),exec:function(e){e.duplicateSelection()},scrollIntoView:"cursor",multiSelectAction:"forEach"},{name:"sortlines",description:"Sort lines",bindKey:o("Ctrl-Alt-S","Command-Alt-S"),exec:function(e){e.sortLines()},scrollIntoView:"selection",multiSelectAction:"forEachLine"},{name:"togglecomment",description:"Toggle comment",bindKey:o("Ctrl-/","Command-/"),exec:function(e){e.toggleCommentLines()},multiSelectAction:"forEachLine",scrollIntoView:"selectionPart"},{name:"toggleBlockComment",description:"Toggle block comment",bindKey:o("Ctrl-Shift-/","Command-Shift-/"),exec:function(e){e.toggleBlockComment()},multiSelectAction:"forEach",scrollIntoView:"selectionPart"},{name:"modifyNumberUp",description:"Modify number up",bindKey:o("Ctrl-Shift-Up","Alt-Shift-Up"),exec:function(e){e.modifyNumber(1)},scrollIntoView:"cursor",multiSelectAction:"forEach"},{name:"modifyNumberDown",description:"Modify number down",bindKey:o("Ctrl-Shift-Down","Alt-Shift-Down"),exec:function(e){e.modifyNumber(-1)},scrollIntoView:"cursor",multiSelectAction:"forEach"},{name:"replace",description:"Replace",bindKey:o("Ctrl-H","Command-Option-F"),exec:function(e){i.loadModule("ace/ext/searchbox",function(t){t.Search(e,!0)})}},{name:"undo",description:"Undo",bindKey:o("Ctrl-Z","Command-Z"),exec:function(e){e.undo()}},{name:"redo",description:"Redo",bindKey:o("Ctrl-Shift-Z|Ctrl-Y","Command-Shift-Z|Command-Y"),exec:function(e){e.redo()}},{name:"copylinesup",description:"Copy lines up",bindKey:o("Alt-Shift-Up","Command-Option-Up"),exec:function(e){e.copyLinesUp()},scrollIntoView:"cursor"},{name:"movelinesup",description:"Move lines up",bindKey:o("Alt-Up","Option-Up"),exec:function(e){e.moveLinesUp()},scrollIntoView:"cursor"},{name:"copylinesdown",description:"Copy lines down",bindKey:o("Alt-Shift-Down","Command-Option-Down"),exec:function(e){e.copyLinesDown()},scrollIntoView:"cursor"},{name:"movelinesdown",description:"Move lines down",bindKey:o("Alt-Down","Option-Down"),exec:function(e){e.moveLinesDown()},scrollIntoView:"cursor"},{name:"del",description:"Delete",bindKey:o("Delete","Delete|Ctrl-D|Shift-Delete"),exec:function(e){e.remove("right")},multiSelectAction:"forEach",scrollIntoView:"cursor"},{name:"backspace",description:"Backspace",bindKey:o("Shift-Backspace|Backspace","Ctrl-Backspace|Shift-Backspace|Backspace|Ctrl-H"),exec:function(e){e.remove("left")},multiSelectAction:"forEach",scrollIntoView:"cursor"},{name:"cut_or_delete",description:"Cut or delete",bindKey:o("Shift-Delete",null),exec:function(e){if(!e.selection.isEmpty())return!1;e.remove("left")},multiSelectAction:"forEach",scrollIntoView:"cursor"},{name:"removetolinestart",description:"Remove to line start",bindKey:o("Alt-Backspace","Command-Backspace"),exec:function(e){e.removeToLineStart()},multiSelectAction:"forEach",scrollIntoView:"cursor"},{name:"removetolineend",description:"Remove to line end",bindKey:o("Alt-Delete","Ctrl-K|Command-Delete"),exec:function(e){e.removeToLineEnd()},multiSelectAction:"forEach",scrollIntoView:"cursor"},{name:"removetolinestarthard",description:"Remove to line start hard",bindKey:o("Ctrl-Shift-Backspace",null),exec:function(e){var t=e.selection.getRange();t.start.column=0,e.session.remove(t)},multiSelectAction:"forEach",scrollIntoView:"cursor"},{name:"removetolineendhard",description:"Remove to line end hard",bindKey:o("Ctrl-Shift-Delete",null),exec:function(e){var t=e.selection.getRange();t.end.column=Number.MAX_VALUE,e.session.remove(t)},multiSelectAction:"forEach",scrollIntoView:"cursor"},{name:"removewordleft",description:"Remove word left",bindKey:o("Ctrl-Backspace","Alt-Backspace|Ctrl-Alt-Backspace"),exec:function(e){e.removeWordLeft()},multiSelectAction:"forEach",scrollIntoView:"cursor"},{name:"removewordright",description:"Remove word right",bindKey:o("Ctrl-Delete","Alt-Delete"),exec:function(e){e.removeWordRight()},multiSelectAction:"forEach",scrollIntoView:"cursor"},{name:"outdent",description:"Outdent",bindKey:o("Shift-Tab","Shift-Tab"),exec:function(e){e.blockOutdent()},multiSelectAction:"forEach",scrollIntoView:"selectionPart"},{name:"indent",description:"Indent",bindKey:o("Tab","Tab"),exec:function(e){e.indent()},multiSelectAction:"forEach",scrollIntoView:"selectionPart"},{name:"blockoutdent",description:"Block outdent",bindKey:o("Ctrl-[","Ctrl-["),exec:function(e){e.blockOutdent()},multiSelectAction:"forEachLine",scrollIntoView:"selectionPart"},{name:"blockindent",description:"Block indent",bindKey:o("Ctrl-]","Ctrl-]"),exec:function(e){e.blockIndent()},multiSelectAction:"forEachLine",scrollIntoView:"selectionPart"},{name:"insertstring",description:"Insert string",exec:function(e,t){e.insert(t)},multiSelectAction:"forEach",scrollIntoView:"cursor"},{name:"inserttext",description:"Insert text",exec:function(e,t){e.insert(r.stringRepeat(t.text||"",t.times||1))},multiSelectAction:"forEach",scrollIntoView:"cursor"},{name:"splitline",description:"Split line",bindKey:o(null,"Ctrl-O"),exec:function(e){e.splitLine()},multiSelectAction:"forEach",scrollIntoView:"cursor"},{name:"transposeletters",description:"Transpose letters",bindKey:o("Alt-Shift-X","Ctrl-T"),exec:function(e){e.transposeLetters()},multiSelectAction:function(e){e.transposeSelections(1)},scrollIntoView:"cursor"},{name:"touppercase",description:"To uppercase",bindKey:o("Ctrl-U","Ctrl-U"),exec:function(e){e.toUpperCase()},multiSelectAction:"forEach",scrollIntoView:"cursor"},{name:"tolowercase",description:"To lowercase",bindKey:o("Ctrl-Shift-U","Ctrl-Shift-U"),exec:function(e){e.toLowerCase()},multiSelectAction:"forEach",scrollIntoView:"cursor"},{name:"expandtoline",description:"Expand to line",bindKey:o("Ctrl-Shift-L","Command-Shift-L"),exec:function(e){var t=e.selection.getRange();t.start.column=t.end.column=0,t.end.row++,e.selection.setRange(t,!1)},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"joinlines",description:"Join lines",bindKey:o(null,null),exec:function(e){var t=e.selection.isBackwards(),n=t?e.selection.getSelectionLead():e.selection.getSelectionAnchor(),i=t?e.selection.getSelectionAnchor():e.selection.getSelectionLead(),o=e.session.doc.getLine(n.row).length,u=e.session.doc.getTextRange(e.selection.getRange()),a=u.replace(/\n\s*/," ").length,f=e.session.doc.getLine(n.row);for(var l=n.row+1;l<=i.row+1;l++){var c=r.stringTrimLeft(r.stringTrimRight(e.session.doc.getLine(l)));c.length!==0&&(c=" "+c),f+=c}i.row+10?(e.selection.moveCursorTo(n.row,n.column),e.selection.selectTo(n.row,n.column+a)):(o=e.session.doc.getLine(n.row).length>o?o+1:o,e.selection.moveCursorTo(n.row,o))},multiSelectAction:"forEach",readOnly:!0},{name:"invertSelection",description:"Invert selection",bindKey:o(null,null),exec:function(e){var t=e.session.doc.getLength()-1,n=e.session.doc.getLine(t).length,r=e.selection.rangeList.ranges,i=[];r.length<1&&(r=[e.selection.getRange()]);for(var o=0;o=i.lastRow||r.end.row<=i.firstRow)&&this.renderer.scrollSelectionIntoView(this.selection.anchor,this.selection.lead);break;default:}n=="animate"&&this.renderer.animateScrolling(this.curOp.scrollTop)}var s=this.selection.toJSON();this.curOp.selectionAfter=s,this.$lastSel=this.selection.toJSON(),this.session.getUndoManager().addSelection(s),this.prevOp=this.curOp,this.curOp=null}},this.$mergeableCommands=["backspace","del","insertstring"],this.$historyTracker=function(e){if(!this.$mergeUndoDeltas)return;var t=this.prevOp,n=this.$mergeableCommands,r=t.command&&e.command.name==t.command.name;if(e.command.name=="insertstring"){var i=e.args;this.mergeNextCommand===undefined&&(this.mergeNextCommand=!0),r=r&&this.mergeNextCommand&&(!/\s/.test(i)||/\s/.test(t.args)),this.mergeNextCommand=!0}else r=r&&n.indexOf(e.command.name)!==-1;this.$mergeUndoDeltas!="always"&&Date.now()-this.sequenceStartTime>2e3&&(r=!1),r?this.session.mergeUndoDeltas=!0:n.indexOf(e.command.name)!==-1&&(this.sequenceStartTime=Date.now())},this.setKeyboardHandler=function(e,t){if(e&&typeof e=="string"&&e!="ace"){this.$keybindingId=e;var n=this;g.loadModule(["keybinding",e],function(r){n.$keybindingId==e&&n.keyBinding.setKeyboardHandler(r&&r.handler),t&&t()})}else this.$keybindingId=null,this.keyBinding.setKeyboardHandler(e),t&&t()},this.getKeyboardHandler=function(){return this.keyBinding.getKeyboardHandler()},this.setSession=function(e){if(this.session==e)return;this.curOp&&this.endOperation(),this.curOp={};var t=this.session;if(t){this.session.off("change",this.$onDocumentChange),this.session.off("changeMode",this.$onChangeMode),this.session.off("tokenizerUpdate",this.$onTokenizerUpdate),this.session.off("changeTabSize",this.$onChangeTabSize),this.session.off("changeWrapLimit",this.$onChangeWrapLimit),this.session.off("changeWrapMode",this.$onChangeWrapMode),this.session.off("changeFold",this.$onChangeFold),this.session.off("changeFrontMarker",this.$onChangeFrontMarker),this.session.off("changeBackMarker",this.$onChangeBackMarker),this.session.off("changeBreakpoint",this.$onChangeBreakpoint),this.session.off("changeAnnotation",this.$onChangeAnnotation),this.session.off("changeOverwrite",this.$onCursorChange),this.session.off("changeScrollTop",this.$onScrollTopChange),this.session.off("changeScrollLeft",this.$onScrollLeftChange);var n=this.session.getSelection();n.off("changeCursor",this.$onCursorChange),n.off("changeSelection",this.$onSelectionChange)}this.session=e,e?(this.$onDocumentChange=this.onDocumentChange.bind(this),e.on("change",this.$onDocumentChange),this.renderer.setSession(e),this.$onChangeMode=this.onChangeMode.bind(this),e.on("changeMode",this.$onChangeMode),this.$onTokenizerUpdate=this.onTokenizerUpdate.bind(this),e.on("tokenizerUpdate",this.$onTokenizerUpdate),this.$onChangeTabSize=this.renderer.onChangeTabSize.bind(this.renderer),e.on("changeTabSize",this.$onChangeTabSize),this.$onChangeWrapLimit=this.onChangeWrapLimit.bind(this),e.on("changeWrapLimit",this.$onChangeWrapLimit),this.$onChangeWrapMode=this.onChangeWrapMode.bind(this),e.on("changeWrapMode",this.$onChangeWrapMode),this.$onChangeFold=this.onChangeFold.bind(this),e.on("changeFold",this.$onChangeFold),this.$onChangeFrontMarker=this.onChangeFrontMarker.bind(this),this.session.on("changeFrontMarker",this.$onChangeFrontMarker),this.$onChangeBackMarker=this.onChangeBackMarker.bind(this),this.session.on("changeBackMarker",this.$onChangeBackMarker),this.$onChangeBreakpoint=this.onChangeBreakpoint.bind(this),this.session.on("changeBreakpoint",this.$onChangeBreakpoint),this.$onChangeAnnotation=this.onChangeAnnotation.bind(this),this.session.on("changeAnnotation",this.$onChangeAnnotation),this.$onCursorChange=this.onCursorChange.bind(this),this.session.on("changeOverwrite",this.$onCursorChange),this.$onScrollTopChange=this.onScrollTopChange.bind(this),this.session.on("changeScrollTop",this.$onScrollTopChange),this.$onScrollLeftChange=this.onScrollLeftChange.bind(this),this.session.on("changeScrollLeft",this.$onScrollLeftChange),this.selection=e.getSelection(),this.selection.on("changeCursor",this.$onCursorChange),this.$onSelectionChange=this.onSelectionChange.bind(this),this.selection.on("changeSelection",this.$onSelectionChange),this.onChangeMode(),this.onCursorChange(),this.onScrollTopChange(),this.onScrollLeftChange(),this.onSelectionChange(),this.onChangeFrontMarker(),this.onChangeBackMarker(),this.onChangeBreakpoint(),this.onChangeAnnotation(),this.session.getUseWrapMode()&&this.renderer.adjustWrapLimit(),this.renderer.updateFull()):(this.selection=null,this.renderer.setSession(e)),this._signal("changeSession",{session:e,oldSession:t}),this.curOp=null,t&&t._signal("changeEditor",{oldEditor:this}),e&&e._signal("changeEditor",{editor:this}),e&&e.bgTokenizer&&e.bgTokenizer.scheduleStart()},this.getSession=function(){return this.session},this.setValue=function(e,t){return this.session.doc.setValue(e),t?t==1?this.navigateFileEnd():t==-1&&this.navigateFileStart():this.selectAll(),e},this.getValue=function(){return this.session.getValue()},this.getSelection=function(){return this.selection},this.resize=function(e){this.renderer.onResize(e)},this.setTheme=function(e,t){this.renderer.setTheme(e,t)},this.getTheme=function(){return this.renderer.getTheme()},this.setStyle=function(e){this.renderer.setStyle(e)},this.unsetStyle=function(e){this.renderer.unsetStyle(e)},this.getFontSize=function(){return this.getOption("fontSize")||i.computedStyle(this.container).fontSize},this.setFontSize=function(e){this.setOption("fontSize",e)},this.$highlightBrackets=function(){if(this.$highlightPending)return;var e=this;this.$highlightPending=!0,setTimeout(function(){e.$highlightPending=!1;var t=e.session;if(!t||!t.bgTokenizer)return;t.$bracketHighlight&&(t.$bracketHighlight.markerIds.forEach(function(e){t.removeMarker(e)}),t.$bracketHighlight=null);var n=t.getMatchingBracketRanges(e.getCursorPosition());!n&&t.$mode.getMatching&&(n=t.$mode.getMatching(e.session));if(!n)return;var r="ace_bracket";Array.isArray(n)?n.length==1&&(r="ace_error_bracket"):n=[n],n.length==2&&(p.comparePoints(n[0].end,n[1].start)==0?n=[p.fromPoints(n[0].start,n[1].end)]:p.comparePoints(n[0].start,n[1].end)==0&&(n=[p.fromPoints(n[1].start,n[0].end)])),t.$bracketHighlight={ranges:n,markerIds:n.map(function(e){return t.addMarker(e,r,"text")})}},50)},this.$highlightTags=function(){if(this.$highlightTagPending)return;var e=this;this.$highlightTagPending=!0,setTimeout(function(){e.$highlightTagPending=!1;var t=e.session;if(!t||!t.bgTokenizer)return;var n=e.getCursorPosition(),r=new y(e.session,n.row,n.column),i=r.getCurrentToken();if(!i||!/\b(?:tag-open|tag-name)/.test(i.type)){t.removeMarker(t.$tagHighlight),t.$tagHighlight=null;return}if(i.type.indexOf("tag-open")!=-1){i=r.stepForward();if(!i)return}var s=i.value,o=0,u=r.stepBackward();if(u.value=="<"){do u=i,i=r.stepForward(),i&&i.value===s&&i.type.indexOf("tag-name")!==-1&&(u.value==="<"?o++:u.value==="=0)}else{do i=u,u=r.stepBackward(),i&&i.value===s&&i.type.indexOf("tag-name")!==-1&&(u.value==="<"?o++:u.value==="1)&&(t=!1)}if(e.$highlightLineMarker&&!t)e.removeMarker(e.$highlightLineMarker.id),e.$highlightLineMarker=null;else if(!e.$highlightLineMarker&&t){var n=new p(t.row,t.column,t.row,Infinity);n.id=e.addMarker(n,"ace_active-line","screenLine"),e.$highlightLineMarker=n}else t&&(e.$highlightLineMarker.start.row=t.row,e.$highlightLineMarker.end.row=t.row,e.$highlightLineMarker.start.column=t.column,e._signal("changeBackMarker"))},this.onSelectionChange=function(e){var t=this.session;t.$selectionMarker&&t.removeMarker(t.$selectionMarker),t.$selectionMarker=null;if(!this.selection.isEmpty()){var n=this.selection.getRange(),r=this.getSelectionStyle();t.$selectionMarker=t.addMarker(n,"ace_selection",r)}else this.$updateHighlightActiveLine();var i=this.$highlightSelectedWord&&this.$getSelectionHighLightRegexp();this.session.highlight(i),this._signal("changeSelection")},this.$getSelectionHighLightRegexp=function(){var e=this.session,t=this.getSelectionRange();if(t.isEmpty()||t.isMultiLine())return;var n=t.start.column,r=t.end.column,i=e.getLine(t.start.row),s=i.substring(n,r);if(s.length>5e3||!/[\w\d]/.test(s))return;var o=this.$search.$assembleRegExp({wholeWord:!0,caseSensitive:!0,needle:s}),u=i.substring(n-1,r+1);if(!o.test(u))return;return o},this.onChangeFrontMarker=function(){this.renderer.updateFrontMarkers()},this.onChangeBackMarker=function(){this.renderer.updateBackMarkers()},this.onChangeBreakpoint=function(){this.renderer.updateBreakpoints()},this.onChangeAnnotation=function(){this.renderer.setAnnotations(this.session.getAnnotations())},this.onChangeMode=function(e){this.renderer.updateText(),this._emit("changeMode",e)},this.onChangeWrapLimit=function(){this.renderer.updateFull()},this.onChangeWrapMode=function(){this.renderer.onResize(!0)},this.onChangeFold=function(){this.$updateHighlightActiveLine(),this.renderer.updateFull()},this.getSelectedText=function(){return this.session.getTextRange(this.getSelectionRange())},this.getCopyText=function(){var e=this.getSelectedText(),t=this.session.doc.getNewLineCharacter(),n=!1;if(!e&&this.$copyWithEmptySelection){n=!0;var r=this.selection.getAllRanges();for(var i=0;iu.search(/\S|$/)){var a=u.substr(i.column).search(/\S|$/);n.doc.removeInLine(i.row,i.column,i.column+a)}}this.clearSelection();var f=i.column,l=n.getState(i.row),u=n.getLine(i.row),c=r.checkOutdent(l,u,e);n.insert(i,e),s&&s.selection&&(s.selection.length==2?this.selection.setSelectionRange(new p(i.row,f+s.selection[0],i.row,f+s.selection[1])):this.selection.setSelectionRange(new p(i.row+s.selection[0],s.selection[1],i.row+s.selection[2],s.selection[3])));if(n.getDocument().isNewLine(e)){var h=r.getNextLineIndent(l,u.slice(0,i.column),n.getTabString());n.insert({row:i.row+1,column:0},h)}c&&r.autoOutdent(l,n,i.row)},this.onTextInput=function(e,t){if(!t)return this.keyBinding.onTextInput(e);this.startOperation({command:{name:"insertstring"}});var n=this.applyComposition.bind(this,e,t);this.selection.rangeCount?this.forEachSelection(n):n(),this.endOperation()},this.applyComposition=function(e,t){if(t.extendLeft||t.extendRight){var n=this.selection.getRange();n.start.column-=t.extendLeft,n.end.column+=t.extendRight,this.selection.setRange(n),!e&&!n.isEmpty()&&this.remove()}(e||!this.selection.isEmpty())&&this.insert(e,!0);if(t.restoreStart||t.restoreEnd){var n=this.selection.getRange();n.start.column-=t.restoreStart,n.end.column-=t.restoreEnd,this.selection.setRange(n)}},this.onCommandKey=function(e,t,n){return this.keyBinding.onCommandKey(e,t,n)},this.setOverwrite=function(e){this.session.setOverwrite(e)},this.getOverwrite=function(){return this.session.getOverwrite()},this.toggleOverwrite=function(){this.session.toggleOverwrite()},this.setScrollSpeed=function(e){this.setOption("scrollSpeed",e)},this.getScrollSpeed=function(){return this.getOption("scrollSpeed")},this.setDragDelay=function(e){this.setOption("dragDelay",e)},this.getDragDelay=function(){return this.getOption("dragDelay")},this.setSelectionStyle=function(e){this.setOption("selectionStyle",e)},this.getSelectionStyle=function(){return this.getOption("selectionStyle")},this.setHighlightActiveLine=function(e){this.setOption("highlightActiveLine",e)},this.getHighlightActiveLine=function(){return this.getOption("highlightActiveLine")},this.setHighlightGutterLine=function(e){this.setOption("highlightGutterLine",e)},this.getHighlightGutterLine=function(){return this.getOption("highlightGutterLine")},this.setHighlightSelectedWord=function(e){this.setOption("highlightSelectedWord",e)},this.getHighlightSelectedWord=function(){return this.$highlightSelectedWord},this.setAnimatedScroll=function(e){this.renderer.setAnimatedScroll(e)},this.getAnimatedScroll=function(){return this.renderer.getAnimatedScroll()},this.setShowInvisibles=function(e){this.renderer.setShowInvisibles(e)},this.getShowInvisibles=function(){return this.renderer.getShowInvisibles()},this.setDisplayIndentGuides=function(e){this.renderer.setDisplayIndentGuides(e)},this.getDisplayIndentGuides=function(){return this.renderer.getDisplayIndentGuides()},this.setShowPrintMargin=function(e){this.renderer.setShowPrintMargin(e)},this.getShowPrintMargin=function(){return this.renderer.getShowPrintMargin()},this.setPrintMarginColumn=function(e){this.renderer.setPrintMarginColumn(e)},this.getPrintMarginColumn=function(){return this.renderer.getPrintMarginColumn()},this.setReadOnly=function(e){this.setOption("readOnly",e)},this.getReadOnly=function(){return this.getOption("readOnly")},this.setBehavioursEnabled=function(e){this.setOption("behavioursEnabled",e)},this.getBehavioursEnabled=function(){return this.getOption("behavioursEnabled")},this.setWrapBehavioursEnabled=function(e){this.setOption("wrapBehavioursEnabled",e)},this.getWrapBehavioursEnabled=function(){return this.getOption("wrapBehavioursEnabled")},this.setShowFoldWidgets=function(e){this.setOption("showFoldWidgets",e)},this.getShowFoldWidgets=function(){return this.getOption("showFoldWidgets")},this.setFadeFoldWidgets=function(e){this.setOption("fadeFoldWidgets",e)},this.getFadeFoldWidgets=function(){return this.getOption("fadeFoldWidgets")},this.remove=function(e){this.selection.isEmpty()&&(e=="left"?this.selection.selectLeft():this.selection.selectRight());var t=this.getSelectionRange();if(this.getBehavioursEnabled()){var n=this.session,r=n.getState(t.start.row),i=n.getMode().transformAction(r,"deletion",this,n,t);if(t.end.column===0){var s=n.getTextRange(t);if(s[s.length-1]=="\n"){var o=n.getLine(t.end.row);/^\s+$/.test(o)&&(t.end.column=o.length)}}i&&(t=i)}this.session.remove(t),this.clearSelection()},this.removeWordRight=function(){this.selection.isEmpty()&&this.selection.selectWordRight(),this.session.remove(this.getSelectionRange()),this.clearSelection()},this.removeWordLeft=function(){this.selection.isEmpty()&&this.selection.selectWordLeft(),this.session.remove(this.getSelectionRange()),this.clearSelection()},this.removeToLineStart=function(){this.selection.isEmpty()&&this.selection.selectLineStart(),this.selection.isEmpty()&&this.selection.selectLeft(),this.session.remove(this.getSelectionRange()),this.clearSelection()},this.removeToLineEnd=function(){this.selection.isEmpty()&&this.selection.selectLineEnd();var e=this.getSelectionRange();e.start.column==e.end.column&&e.start.row==e.end.row&&(e.end.column=0,e.end.row++),this.session.remove(e),this.clearSelection()},this.splitLine=function(){this.selection.isEmpty()||(this.session.remove(this.getSelectionRange()),this.clearSelection());var e=this.getCursorPosition();this.insert("\n"),this.moveCursorToPosition(e)},this.transposeLetters=function(){if(!this.selection.isEmpty())return;var e=this.getCursorPosition(),t=e.column;if(t===0)return;var n=this.session.getLine(e.row),r,i;tt.toLowerCase()?1:0});var i=new p(0,0,0,0);for(var r=e.first;r<=e.last;r++){var s=t.getLine(r);i.start.row=r,i.end.row=r,i.end.column=s.length,t.replace(i,n[r-e.first])}},this.toggleCommentLines=function(){var e=this.session.getState(this.getCursorPosition().row),t=this.$getSelectedRows();this.session.getMode().toggleCommentLines(e,this.session,t.first,t.last)},this.toggleBlockComment=function(){var e=this.getCursorPosition(),t=this.session.getState(e.row),n=this.getSelectionRange();this.session.getMode().toggleBlockComment(t,this.session,n,e)},this.getNumberAt=function(e,t){var n=/[\-]?[0-9]+(?:\.[0-9]+)?/g;n.lastIndex=0;var r=this.session.getLine(e);while(n.lastIndex=t){var s={value:i[0],start:i.index,end:i.index+i[0].length};return s}}return null},this.modifyNumber=function(e){var t=this.selection.getCursor().row,n=this.selection.getCursor().column,r=new p(t,n-1,t,n),i=this.session.getTextRange(r);if(!isNaN(parseFloat(i))&&isFinite(i)){var s=this.getNumberAt(t,n);if(s){var o=s.value.indexOf(".")>=0?s.start+s.value.indexOf(".")+1:s.end,u=s.start+s.value.length-o,a=parseFloat(s.value);a*=Math.pow(10,u),o!==s.end&&n=u&&o<=a&&(n=t,f.selection.clearSelection(),f.moveCursorTo(e,u+r),f.selection.selectTo(e,a+r)),u=a});var l=this.$toggleWordPairs,c;for(var h=0;hp+1)break;p=d.last}l--,u=this.session.$moveLines(h,p,t?0:e),t&&e==-1&&(c=l+1);while(c<=l)o[c].moveBy(u,0),c++;t||(u=0),a+=u}i.fromOrientedRange(i.ranges[0]),i.rangeList.attach(this.session),this.inVirtualSelectionMode=!1}},this.$getSelectedRows=function(e){return e=(e||this.getSelectionRange()).collapseRows(),{first:this.session.getRowFoldStart(e.start.row),last:this.session.getRowFoldEnd(e.end.row)}},this.onCompositionStart=function(e){this.renderer.showComposition(e)},this.onCompositionUpdate=function(e){this.renderer.setCompositionText(e)},this.onCompositionEnd=function(){this.renderer.hideComposition()},this.getFirstVisibleRow=function(){return this.renderer.getFirstVisibleRow()},this.getLastVisibleRow=function(){return this.renderer.getLastVisibleRow()},this.isRowVisible=function(e){return e>=this.getFirstVisibleRow()&&e<=this.getLastVisibleRow()},this.isRowFullyVisible=function(e){return e>=this.renderer.getFirstFullyVisibleRow()&&e<=this.renderer.getLastFullyVisibleRow()},this.$getVisibleRowCount=function(){return this.renderer.getScrollBottomRow()-this.renderer.getScrollTopRow()+1},this.$moveByPage=function(e,t){var n=this.renderer,r=this.renderer.layerConfig,i=e*Math.floor(r.height/r.lineHeight);t===!0?this.selection.$moveSelection(function(){this.moveCursorBy(i,0)}):t===!1&&(this.selection.moveCursorBy(i,0),this.selection.clearSelection());var s=n.scrollTop;n.scrollBy(0,i*r.lineHeight),t!=null&&n.scrollCursorIntoView(null,.5),n.animateScrolling(s)},this.selectPageDown=function(){this.$moveByPage(1,!0)},this.selectPageUp=function(){this.$moveByPage(-1,!0)},this.gotoPageDown=function(){this.$moveByPage(1,!1)},this.gotoPageUp=function(){this.$moveByPage(-1,!1)},this.scrollPageDown=function(){this.$moveByPage(1)},this.scrollPageUp=function(){this.$moveByPage(-1)},this.scrollToRow=function(e){this.renderer.scrollToRow(e)},this.scrollToLine=function(e,t,n,r){this.renderer.scrollToLine(e,t,n,r)},this.centerSelection=function(){var e=this.getSelectionRange(),t={row:Math.floor(e.start.row+(e.end.row-e.start.row)/2),column:Math.floor(e.start.column+(e.end.column-e.start.column)/2)};this.renderer.alignCursor(t,.5)},this.getCursorPosition=function(){return this.selection.getCursor()},this.getCursorPositionScreen=function(){return this.session.documentToScreenPosition(this.getCursorPosition())},this.getSelectionRange=function(){return this.selection.getRange()},this.selectAll=function(){this.selection.selectAll()},this.clearSelection=function(){this.selection.clearSelection()},this.moveCursorTo=function(e,t){this.selection.moveCursorTo(e,t)},this.moveCursorToPosition=function(e){this.selection.moveCursorToPosition(e)},this.jumpToMatching=function(e,t){var n=this.getCursorPosition(),r=new y(this.session,n.row,n.column),i=r.getCurrentToken(),s=i||r.stepForward();if(!s)return;var o,u=!1,a={},f=n.column-s.start,l,c={")":"(","(":"(","]":"[","[":"[","{":"{","}":"{"};do{if(s.value.match(/[{}()\[\]]/g))for(;f=0;--s)this.$tryReplace(n[s],e)&&r++;return this.selection.setSelectionRange(i),r},this.$tryReplace=function(e,t){var n=this.session.getTextRange(e);return t=this.$search.replace(n,t),t!==null?(e.end=this.session.replace(e,t),e):null},this.getLastSearchOptions=function(){return this.$search.getOptions()},this.find=function(e,t,n){t||(t={}),typeof e=="string"||e instanceof RegExp?t.needle=e:typeof e=="object"&&r.mixin(t,e);var i=this.selection.getRange();t.needle==null&&(e=this.session.getTextRange(i)||this.$search.$options.needle,e||(i=this.session.getWordRange(i.start.row,i.start.column),e=this.session.getTextRange(i)),this.$search.set({needle:e})),this.$search.set(t),t.start||this.$search.set({start:i});var s=this.$search.find(this.session);if(t.preventScroll)return s;if(s)return this.revealRange(s,n),s;t.backwards?i.start=i.end:i.end=i.start,this.selection.setRange(i)},this.findNext=function(e,t){this.find({skipCurrent:!0,backwards:!1},e,t)},this.findPrevious=function(e,t){this.find(e,{skipCurrent:!0,backwards:!0},t)},this.revealRange=function(e,t){this.session.unfold(e),this.selection.setSelectionRange(e);var n=this.renderer.scrollTop;this.renderer.scrollSelectionIntoView(e.start,e.end,.5),t!==!1&&this.renderer.animateScrolling(n)},this.undo=function(){this.session.getUndoManager().undo(this.session),this.renderer.scrollCursorIntoView(null,.5)},this.redo=function(){this.session.getUndoManager().redo(this.session),this.renderer.scrollCursorIntoView(null,.5)},this.destroy=function(){this.renderer.destroy(),this._signal("destroy",this),this.session&&this.session.destroy(),this._$emitInputEvent&&this._$emitInputEvent.cancel(),this.session=null},this.setAutoScrollEditorIntoView=function(e){if(!e)return;var t,n=this,r=!1;this.$scrollAnchor||(this.$scrollAnchor=document.createElement("div"));var i=this.$scrollAnchor;i.style.cssText="position:absolute",this.container.insertBefore(i,this.container.firstChild);var s=this.on("changeSelection",function(){r=!0}),o=this.renderer.on("beforeRender",function(){r&&(t=n.renderer.container.getBoundingClientRect())}),u=this.renderer.on("afterRender",function(){if(r&&t&&(n.isFocused()||n.searchBox&&n.searchBox.isFocused())){var e=n.renderer,s=e.$cursorLayer.$pixelPos,o=e.layerConfig,u=s.top-o.offset;s.top>=0&&u+t.top<0?r=!0:s.topwindow.innerHeight?r=!1:r=null,r!=null&&(i.style.top=u+"px",i.style.left=s.left+"px",i.style.height=o.lineHeight+"px",i.scrollIntoView(r)),r=t=null}});this.setAutoScrollEditorIntoView=function(e){if(e)return;delete this.setAutoScrollEditorIntoView,this.off("changeSelection",s),this.renderer.off("afterRender",u),this.renderer.off("beforeRender",o)}},this.$resetCursorStyle=function(){var e=this.$cursorStyle||"ace",t=this.renderer.$cursorLayer;if(!t)return;t.setSmoothBlinking(/smooth/.test(e)),t.isBlinking=!this.$readOnly&&e!="wide",i.setCssClass(t.element,"ace_slim-cursors",/slim/.test(e))},this.prompt=function(e,t,n){var r=this;g.loadModule("./ext/prompt",function(i){i.prompt(r,e,t,n)})}}.call(w.prototype),g.defineOptions(w.prototype,"editor",{selectionStyle:{set:function(e){this.onSelectionChange(),this._signal("changeSelectionStyle",{data:e})},initialValue:"line"},highlightActiveLine:{set:function(){this.$updateHighlightActiveLine()},initialValue:!0},highlightSelectedWord:{set:function(e){this.$onSelectionChange()},initialValue:!0},readOnly:{set:function(e){this.textInput.setReadOnly(e),this.$resetCursorStyle()},initialValue:!1},copyWithEmptySelection:{set:function(e){this.textInput.setCopyWithEmptySelection(e)},initialValue:!1},cursorStyle:{set:function(e){this.$resetCursorStyle()},values:["ace","slim","smooth","wide"],initialValue:"ace"},mergeUndoDeltas:{values:[!1,!0,"always"],initialValue:!0},behavioursEnabled:{initialValue:!0},wrapBehavioursEnabled:{initialValue:!0},autoScrollEditorIntoView:{set:function(e){this.setAutoScrollEditorIntoView(e)}},keyboardHandler:{set:function(e){this.setKeyboardHandler(e)},get:function(){return this.$keybindingId},handlesSet:!0},value:{set:function(e){this.session.setValue(e)},get:function(){return this.getValue()},handlesSet:!0,hidden:!0},session:{set:function(e){this.setSession(e)},get:function(){return this.session},handlesSet:!0,hidden:!0},showLineNumbers:{set:function(e){this.renderer.$gutterLayer.setShowLineNumbers(e),this.renderer.$loop.schedule(this.renderer.CHANGE_GUTTER),e&&this.$relativeLineNumbers?E.attach(this):E.detach(this)},initialValue:!0},relativeLineNumbers:{set:function(e){this.$showLineNumbers&&e?E.attach(this):E.detach(this)}},placeholder:{set:function(e){this.$updatePlaceholder||(this.$updatePlaceholder=function(){var e=this.renderer.$composition||this.getValue();if(e&&this.renderer.placeholderNode)this.renderer.off("afterRender",this.$updatePlaceholder),i.removeCssClass(this.container,"ace_hasPlaceholder"),this.renderer.placeholderNode.remove(),this.renderer.placeholderNode=null;else if(!e&&!this.renderer.placeholderNode){this.renderer.on("afterRender",this.$updatePlaceholder),i.addCssClass(this.container,"ace_hasPlaceholder");var t=i.createElement("div");t.className="ace_placeholder",t.textContent=this.$placeholder||"",this.renderer.placeholderNode=t,this.renderer.content.appendChild(this.renderer.placeholderNode)}}.bind(this),this.on("input",this.$updatePlaceholder)),this.$updatePlaceholder()}},hScrollBarAlwaysVisible:"renderer",vScrollBarAlwaysVisible:"renderer",highlightGutterLine:"renderer",animatedScroll:"renderer",showInvisibles:"renderer",showPrintMargin:"renderer",printMarginColumn:"renderer",printMargin:"renderer",fadeFoldWidgets:"renderer",showFoldWidgets:"renderer",displayIndentGuides:"renderer",showGutter:"renderer",fontSize:"renderer",fontFamily:"renderer",maxLines:"renderer",minLines:"renderer",scrollPastEnd:"renderer",fixedWidthGutter:"renderer",theme:"renderer",hasCssTransforms:"renderer",maxPixelHeight:"renderer",useTextareaForIME:"renderer",scrollSpeed:"$mouseHandler",dragDelay:"$mouseHandler",dragEnabled:"$mouseHandler",focusTimeout:"$mouseHandler",tooltipFollowsMouse:"$mouseHandler",firstLineNumber:"session",overwrite:"session",newLineMode:"session",useWorker:"session",useSoftTabs:"session",navigateWithinSoftTabs:"session",tabSize:"session",wrap:"session",indentedSoftWrap:"session",foldStyle:"session",mode:"session"});var E={getText:function(e,t){return(Math.abs(e.selection.lead.row-t)||t+1+(t<9?"\u00b7":""))+""},getWidth:function(e,t,n){return Math.max(t.toString().length,(n.lastRow+1).toString().length,2)*n.characterWidth},update:function(e,t){t.renderer.$loop.schedule(t.renderer.CHANGE_GUTTER)},attach:function(e){e.renderer.$gutterLayer.$renderer=this,e.on("changeSelection",this.update),this.update(null,e)},detach:function(e){e.renderer.$gutterLayer.$renderer==this&&(e.renderer.$gutterLayer.$renderer=null),e.off("changeSelection",this.update),this.update(null,e)}};t.Editor=w}),define("ace/undomanager",["require","exports","module","ace/range"],function(e,t,n){"use strict";function i(e,t){for(var n=t;n--;){var r=e[n];if(r&&!r[0].ignore){while(n0){a.row+=i,a.column+=a.row==r.row?s:0;continue}!t&&l<=0&&(a.row=n.row,a.column=n.column,l===0&&(a.bias=1))}}function f(e){return{row:e.row,column:e.column}}function l(e){return{start:f(e.start),end:f(e.end),action:e.action,lines:e.lines.slice()}}function c(e){e=e||this;if(Array.isArray(e))return e.map(c).join("\n");var t="";e.action?(t=e.action=="insert"?"+":"-",t+="["+e.lines+"]"):e.value&&(Array.isArray(e.value)?t=e.value.map(h).join("\n"):t=h(e.value)),e.start&&(t+=h(e));if(e.id||e.rev)t+=" ("+(e.id||e.rev)+")";return t}function h(e){return e.start.row+":"+e.start.column+"=>"+e.end.row+":"+e.end.column}function p(e,t){var n=e.action=="insert",r=t.action=="insert";if(n&&r)if(o(t.start,e.end)>=0)m(t,e,-1);else{if(!(o(t.start,e.start)<=0))return null;m(e,t,1)}else if(n&&!r)if(o(t.start,e.end)>=0)m(t,e,-1);else{if(!(o(t.end,e.start)<=0))return null;m(e,t,-1)}else if(!n&&r)if(o(t.start,e.start)>=0)m(t,e,1);else{if(!(o(t.start,e.start)<=0))return null;m(e,t,1)}else if(!n&&!r)if(o(t.start,e.start)>=0)m(t,e,1);else{if(!(o(t.end,e.start)<=0))return null;m(e,t,-1)}return[t,e]}function d(e,t){for(var n=e.length;n--;)for(var r=0;r=0?m(e,t,-1):o(e.start,t.start)<=0?m(t,e,1):(m(e,s.fromPoints(t.start,e.start),-1),m(t,e,1));else if(!n&&r)o(t.start,e.end)>=0?m(t,e,-1):o(t.start,e.start)<=0?m(e,t,1):(m(t,s.fromPoints(e.start,t.start),-1),m(e,t,1));else if(!n&&!r)if(o(t.start,e.end)>=0)m(t,e,-1);else{if(!(o(t.end,e.start)<=0)){var i,u;return o(e.start,t.start)<0&&(i=e,e=y(e,t.start)),o(e.end,t.end)>0&&(u=y(e,t.end)),g(t.end,e.start,e.end,-1),u&&!i&&(e.lines=u.lines,e.start=u.start,e.end=u.end,u=e),[t,i,u].filter(Boolean)}m(e,t,-1)}return[t,e]}function m(e,t,n){g(e.start,t.start,t.end,n),g(e.end,t.start,t.end,n)}function g(e,t,n,r){e.row==(r==1?t:n).row&&(e.column+=r*(n.column-t.column)),e.row+=r*(n.row-t.row)}function y(e,t){var n=e.lines,r=e.end;e.end=f(t);var i=e.end.row-e.start.row,s=n.splice(i,n.length),o=i?t.column:t.column-e.start.column;n.push(s[0].substring(0,o)),s[0]=s[0].substr(o);var u={start:f(t),end:r,lines:s,action:e.action};return u}function b(e,t){t=l(t);for(var n=e.length;n--;){var r=e[n];for(var i=0;i0},this.canRedo=function(){return this.$redoStack.length>0},this.bookmark=function(e){e==undefined&&(e=this.$rev),this.mark=e},this.isAtBookmark=function(){return this.$rev===this.mark},this.toJSON=function(){},this.fromJSON=function(){},this.hasUndo=this.canUndo,this.hasRedo=this.canRedo,this.isClean=this.isAtBookmark,this.markClean=this.bookmark,this.$prettyPrint=function(e){return e?c(e):c(this.$undoStack)+"\n---\n"+c(this.$redoStack)}}).call(r.prototype);var s=e("./range").Range,o=s.comparePoints,u=s.comparePoints;t.UndoManager=r}),define("ace/layer/lines",["require","exports","module","ace/lib/dom"],function(e,t,n){"use strict";var r=e("../lib/dom"),i=function(e,t){this.element=e,this.canvasHeight=t||5e5,this.element.style.height=this.canvasHeight*2+"px",this.cells=[],this.cellCache=[],this.$offsetCoefficient=0};(function(){this.moveContainer=function(e){r.translate(this.element,0,-(e.firstRowScreen*e.lineHeight%this.canvasHeight)-e.offset*this.$offsetCoefficient)},this.pageChanged=function(e,t){return Math.floor(e.firstRowScreen*e.lineHeight/this.canvasHeight)!==Math.floor(t.firstRowScreen*t.lineHeight/this.canvasHeight)},this.computeLineTop=function(e,t,n){var r=t.firstRowScreen*t.lineHeight,i=Math.floor(r/this.canvasHeight),s=n.documentToScreenRow(e,0)*t.lineHeight;return s-i*this.canvasHeight},this.computeLineHeight=function(e,t,n){return t.lineHeight*n.getRowLineCount(e)},this.getLength=function(){return this.cells.length},this.get=function(e){return this.cells[e]},this.shift=function(){this.$cacheCell(this.cells.shift())},this.pop=function(){this.$cacheCell(this.cells.pop())},this.push=function(e){if(Array.isArray(e)){this.cells.push.apply(this.cells,e);var t=r.createFragment(this.element);for(var n=0;ns&&(a=i.end.row+1,i=t.getNextFoldLine(a,i),s=i?i.start.row:Infinity);if(a>r){while(this.$lines.getLength()>u+1)this.$lines.pop();break}o=this.$lines.get(++u),o?o.row=a:(o=this.$lines.createCell(a,e,this.session,f),this.$lines.push(o)),this.$renderCell(o,e,i,a),a++}this._signal("afterRender"),this.$updateGutterWidth(e)},this.$updateGutterWidth=function(e){var t=this.session,n=t.gutterRenderer||this.$renderer,r=t.$firstLineNumber,i=this.$lines.last()?this.$lines.last().text:"";if(this.$fixedWidth||t.$useWrapMode)i=t.getLength()+r-1;var s=n?n.getWidth(t,i,e):i.toString().length*e.characterWidth,o=this.$padding||this.$computePadding();s+=o.left+o.right,s!==this.gutterWidth&&!isNaN(s)&&(this.gutterWidth=s,this.element.parentNode.style.width=this.element.style.width=Math.ceil(this.gutterWidth)+"px",this._signal("changeGutterWidth",s))},this.$updateCursorRow=function(){if(!this.$highlightGutterLine)return;var e=this.session.selection.getCursor();if(this.$cursorRow===e.row)return;this.$cursorRow=e.row},this.updateLineHighlight=function(){if(!this.$highlightGutterLine)return;var e=this.session.selection.cursor.row;this.$cursorRow=e;if(this.$cursorCell&&this.$cursorCell.row==e)return;this.$cursorCell&&(this.$cursorCell.element.className=this.$cursorCell.element.className.replace("ace_gutter-active-line ",""));var t=this.$lines.cells;this.$cursorCell=null;for(var n=0;n=this.$cursorRow){if(r.row>this.$cursorRow){var i=this.session.getFoldLine(this.$cursorRow);if(!(n>0&&i&&i.start.row==t[n-1].row))break;r=t[n-1]}r.element.className="ace_gutter-active-line "+r.element.className,this.$cursorCell=r;break}}},this.scrollLines=function(e){var t=this.config;this.config=e,this.$updateCursorRow();if(this.$lines.pageChanged(t,e))return this.update(e);this.$lines.moveContainer(e);var n=Math.min(e.lastRow+e.gutterOffset,this.session.getLength()-1),r=this.oldLastRow;this.oldLastRow=n;if(!t||r0;i--)this.$lines.shift();if(r>n)for(var i=this.session.getFoldedRowCount(n+1,r);i>0;i--)this.$lines.pop();e.firstRowr&&this.$lines.push(this.$renderLines(e,r+1,n)),this.updateLineHighlight(),this._signal("afterRender"),this.$updateGutterWidth(e)},this.$renderLines=function(e,t,n){var r=[],i=t,s=this.session.getNextFoldLine(i),o=s?s.start.row:Infinity;for(;;){i>o&&(i=s.end.row+1,s=this.session.getNextFoldLine(i,s),o=s?s.start.row:Infinity);if(i>n)break;var u=this.$lines.createCell(i,e,this.session,f);this.$renderCell(u,e,s,i),r.push(u),i++}return r},this.$renderCell=function(e,t,n,i){var s=e.element,o=this.session,u=s.childNodes[0],a=s.childNodes[1],f=o.$firstLineNumber,l=o.$breakpoints,c=o.$decorations,h=o.gutterRenderer||this.$renderer,p=this.$showFoldWidgets&&o.foldWidgets,d=n?n.start.row:Number.MAX_VALUE,v="ace_gutter-cell ";this.$highlightGutterLine&&(i==this.$cursorRow||n&&i=d&&this.$cursorRow<=n.end.row)&&(v+="ace_gutter-active-line ",this.$cursorCell!=e&&(this.$cursorCell&&(this.$cursorCell.element.className=this.$cursorCell.element.className.replace("ace_gutter-active-line ","")),this.$cursorCell=e)),l[i]&&(v+=l[i]),c[i]&&(v+=c[i]),this.$annotations[i]&&(v+=this.$annotations[i].className),s.className!=v&&(s.className=v);if(p){var m=p[i];m==null&&(m=p[i]=o.getFoldWidget(i))}if(m){var v="ace_fold-widget ace_"+m;m=="start"&&i==d&&in.right-t.right)return"foldWidgets"}}).call(a.prototype),t.Gutter=a}),define("ace/layer/marker",["require","exports","module","ace/range","ace/lib/dom"],function(e,t,n){"use strict";var r=e("../range").Range,i=e("../lib/dom"),s=function(e){this.element=i.createElement("div"),this.element.className="ace_layer ace_marker-layer",e.appendChild(this.element)};(function(){function e(e,t,n,r){return(e?1:0)|(t?2:0)|(n?4:0)|(r?8:0)}this.$padding=0,this.setPadding=function(e){this.$padding=e},this.setSession=function(e){this.session=e},this.setMarkers=function(e){this.markers=e},this.elt=function(e,t){var n=this.i!=-1&&this.element.childNodes[this.i];n?this.i++:(n=document.createElement("div"),this.element.appendChild(n),this.i=-1),n.style.cssText=t,n.className=e},this.update=function(e){if(!e)return;this.config=e,this.i=0;var t;for(var n in this.markers){var r=this.markers[n];if(!r.range){r.update(t,this,this.session,e);continue}var i=r.range.clipRows(e.firstRow,e.lastRow);if(i.isEmpty())continue;i=i.toScreenRange(this.session);if(r.renderer){var s=this.$getTop(i.start.row,e),o=this.$padding+i.start.column*e.characterWidth;r.renderer(t,i,o,s,e)}else r.type=="fullLine"?this.drawFullLineMarker(t,i,r.clazz,e):r.type=="screenLine"?this.drawScreenLineMarker(t,i,r.clazz,e):i.isMultiLine()?r.type=="text"?this.drawTextMarker(t,i,r.clazz,e):this.drawMultiLineMarker(t,i,r.clazz,e):this.drawSingleLineMarker(t,i,r.clazz+" ace_start"+" ace_br15",e)}if(this.i!=-1)while(this.ip,l==f),s,l==f?0:1,o)},this.drawMultiLineMarker=function(e,t,n,r,i){var s=this.$padding,o=r.lineHeight,u=this.$getTop(t.start.row,r),a=s+t.start.column*r.characterWidth;i=i||"";if(this.session.$bidiHandler.isBidiRow(t.start.row)){var f=t.clone();f.end.row=f.start.row,f.end.column=this.session.getLine(f.start.row).length,this.drawBidiSingleLineMarker(e,f,n+" ace_br1 ace_start",r,null,i)}else this.elt(n+" ace_br1 ace_start","height:"+o+"px;"+"right:0;"+"top:"+u+"px;left:"+a+"px;"+(i||""));if(this.session.$bidiHandler.isBidiRow(t.end.row)){var f=t.clone();f.start.row=f.end.row,f.start.column=0,this.drawBidiSingleLineMarker(e,f,n+" ace_br12",r,null,i)}else{u=this.$getTop(t.end.row,r);var l=t.end.column*r.characterWidth;this.elt(n+" ace_br12","height:"+o+"px;"+"width:"+l+"px;"+"top:"+u+"px;"+"left:"+s+"px;"+(i||""))}o=(t.end.row-t.start.row-1)*r.lineHeight;if(o<=0)return;u=this.$getTop(t.start.row+1,r);var c=(t.start.column?1:0)|(t.end.column?0:8);this.elt(n+(c?" ace_br"+c:""),"height:"+o+"px;"+"right:0;"+"top:"+u+"px;"+"left:"+s+"px;"+(i||""))},this.drawSingleLineMarker=function(e,t,n,r,i,s){if(this.session.$bidiHandler.isBidiRow(t.start.row))return this.drawBidiSingleLineMarker(e,t,n,r,i,s);var o=r.lineHeight,u=(t.end.column+(i||0)-t.start.column)*r.characterWidth,a=this.$getTop(t.start.row,r),f=this.$padding+t.start.column*r.characterWidth;this.elt(n,"height:"+o+"px;"+"width:"+u+"px;"+"top:"+a+"px;"+"left:"+f+"px;"+(s||""))},this.drawBidiSingleLineMarker=function(e,t,n,r,i,s){var o=r.lineHeight,u=this.$getTop(t.start.row,r),a=this.$padding,f=this.session.$bidiHandler.getSelections(t.start.column,t.end.column);f.forEach(function(e){this.elt(n,"height:"+o+"px;"+"width:"+e.width+(i||0)+"px;"+"top:"+u+"px;"+"left:"+(a+e.left)+"px;"+(s||""))},this)},this.drawFullLineMarker=function(e,t,n,r,i){var s=this.$getTop(t.start.row,r),o=r.lineHeight;t.start.row!=t.end.row&&(o+=this.$getTop(t.end.row,r)-s),this.elt(n,"height:"+o+"px;"+"top:"+s+"px;"+"left:0;right:0;"+(i||""))},this.drawScreenLineMarker=function(e,t,n,r,i){var s=this.$getTop(t.start.row,r),o=r.lineHeight;this.elt(n,"height:"+o+"px;"+"top:"+s+"px;"+"left:0;right:0;"+(i||""))}}).call(s.prototype),t.Marker=s}),define("ace/layer/text",["require","exports","module","ace/lib/oop","ace/lib/dom","ace/lib/lang","ace/layer/lines","ace/lib/event_emitter"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("../lib/dom"),s=e("../lib/lang"),o=e("./lines").Lines,u=e("../lib/event_emitter").EventEmitter,a=function(e){this.dom=i,this.element=this.dom.createElement("div"),this.element.className="ace_layer ace_text-layer",e.appendChild(this.element),this.$updateEolChar=this.$updateEolChar.bind(this),this.$lines=new o(this.element)};(function(){r.implement(this,u),this.EOF_CHAR="\u00b6",this.EOL_CHAR_LF="\u00ac",this.EOL_CHAR_CRLF="\u00a4",this.EOL_CHAR=this.EOL_CHAR_LF,this.TAB_CHAR="\u2014",this.SPACE_CHAR="\u00b7",this.$padding=0,this.MAX_LINE_LENGTH=1e4,this.$updateEolChar=function(){var e=this.session.doc,t=e.getNewLineCharacter()=="\n"&&e.getNewLineMode()!="windows",n=t?this.EOL_CHAR_LF:this.EOL_CHAR_CRLF;if(this.EOL_CHAR!=n)return this.EOL_CHAR=n,!0},this.setPadding=function(e){this.$padding=e,this.element.style.margin="0 "+e+"px"},this.getLineHeight=function(){return this.$fontMetrics.$characterSize.height||0},this.getCharacterWidth=function(){return this.$fontMetrics.$characterSize.width||0},this.$setFontMetrics=function(e){this.$fontMetrics=e,this.$fontMetrics.on("changeCharacterSize",function(e){this._signal("changeCharacterSize",e)}.bind(this)),this.$pollSizeChanges()},this.checkForSizeChanges=function(){this.$fontMetrics.checkForSizeChanges()},this.$pollSizeChanges=function(){return this.$pollSizeChangesTimer=this.$fontMetrics.$pollSizeChanges()},this.setSession=function(e){this.session=e,e&&this.$computeTabString()},this.showInvisibles=!1,this.setShowInvisibles=function(e){return this.showInvisibles==e?!1:(this.showInvisibles=e,this.$computeTabString(),!0)},this.displayIndentGuides=!0,this.setDisplayIndentGuides=function(e){return this.displayIndentGuides==e?!1:(this.displayIndentGuides=e,this.$computeTabString(),!0)},this.$tabStrings=[],this.onChangeTabSize=this.$computeTabString=function(){var e=this.session.getTabSize();this.tabSize=e;var t=this.$tabStrings=[0];for(var n=1;nl&&(u=a.end.row+1,a=this.session.getNextFoldLine(u,a),l=a?a.start.row:Infinity);if(u>i)break;var c=s[o++];if(c){this.dom.removeChildren(c),this.$renderLine(c,u,u==l?a:!1),f&&(c.style.top=this.$lines.computeLineTop(u,e,this.session)+"px");var h=e.lineHeight*this.session.getRowLength(u)+"px";c.style.height!=h&&(f=!0,c.style.height=h)}u++}if(f)while(o0;i--)this.$lines.shift();if(t.lastRow>e.lastRow)for(var i=this.session.getFoldedRowCount(e.lastRow+1,t.lastRow);i>0;i--)this.$lines.pop();e.firstRowt.lastRow&&this.$lines.push(this.$renderLinesFragment(e,t.lastRow+1,e.lastRow))},this.$renderLinesFragment=function(e,t,n){var r=[],s=t,o=this.session.getNextFoldLine(s),u=o?o.start.row:Infinity;for(;;){s>u&&(s=o.end.row+1,o=this.session.getNextFoldLine(s,o),u=o?o.start.row:Infinity);if(s>n)break;var a=this.$lines.createCell(s,e,this.session),f=a.element;this.dom.removeChildren(f),i.setStyle(f.style,"height",this.$lines.computeLineHeight(s,e,this.session)+"px"),i.setStyle(f.style,"top",this.$lines.computeLineTop(s,e,this.session)+"px"),this.$renderLine(f,s,s==u?o:!1),this.$useLineGroups()?f.className="ace_line_group":f.className="ace_line",r.push(a),s++}return r},this.update=function(e){this.$lines.moveContainer(e),this.config=e;var t=e.firstRow,n=e.lastRow,r=this.$lines;while(r.getLength())r.pop();r.push(this.$renderLinesFragment(e,t,n))},this.$textToken={text:!0,rparen:!0,lparen:!0},this.$renderToken=function(e,t,n,r){var i=this,o=/(\t)|( +)|([\x00-\x1f\x80-\xa0\xad\u1680\u180E\u2000-\u200f\u2028\u2029\u202F\u205F\uFEFF\uFFF9-\uFFFC]+)|(\u3000)|([\u1100-\u115F\u11A3-\u11A7\u11FA-\u11FF\u2329-\u232A\u2E80-\u2E99\u2E9B-\u2EF3\u2F00-\u2FD5\u2FF0-\u2FFB\u3001-\u303E\u3041-\u3096\u3099-\u30FF\u3105-\u312D\u3131-\u318E\u3190-\u31BA\u31C0-\u31E3\u31F0-\u321E\u3220-\u3247\u3250-\u32FE\u3300-\u4DBF\u4E00-\uA48C\uA490-\uA4C6\uA960-\uA97C\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFAFF\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE66\uFE68-\uFE6B\uFF01-\uFF60\uFFE0-\uFFE6]|[\uD800-\uDBFF][\uDC00-\uDFFF])/g,u=this.dom.createFragment(this.element),a,f=0;while(a=o.exec(r)){var l=a[1],c=a[2],h=a[3],p=a[4],d=a[5];if(!i.showInvisibles&&c)continue;var v=f!=a.index?r.slice(f,a.index):"";f=a.index+a[0].length,v&&u.appendChild(this.dom.createTextNode(v,this.element));if(l){var m=i.session.getScreenTabSize(t+a.index);u.appendChild(i.$tabStrings[m].cloneNode(!0)),t+=m-1}else if(c)if(i.showInvisibles){var g=this.dom.createElement("span");g.className="ace_invisible ace_invisible_space",g.textContent=s.stringRepeat(i.SPACE_CHAR,c.length),u.appendChild(g)}else u.appendChild(this.com.createTextNode(c,this.element));else if(h){var g=this.dom.createElement("span");g.className="ace_invisible ace_invisible_space ace_invalid",g.textContent=s.stringRepeat(i.SPACE_CHAR,h.length),u.appendChild(g)}else if(p){t+=1;var g=this.dom.createElement("span");g.style.width=i.config.characterWidth*2+"px",g.className=i.showInvisibles?"ace_cjk ace_invisible ace_invisible_space":"ace_cjk",g.textContent=i.showInvisibles?i.SPACE_CHAR:p,u.appendChild(g)}else if(d){t+=1;var g=this.dom.createElement("span");g.style.width=i.config.characterWidth*2+"px",g.className="ace_cjk",g.textContent=d,u.appendChild(g)}}u.appendChild(this.dom.createTextNode(f?r.slice(f):r,this.element));if(!this.$textToken[n.type]){var y="ace_"+n.type.replace(/\./g," ace_"),g=this.dom.createElement("span");n.type=="fold"&&(g.style.width=n.value.length*this.config.characterWidth+"px"),g.className=y,g.appendChild(u),e.appendChild(g)}else e.appendChild(u);return t+r.length},this.renderIndentGuide=function(e,t,n){var r=t.search(this.$indentGuideRe);if(r<=0||r>=n)return t;if(t[0]==" "){r-=r%this.tabSize;var i=r/this.tabSize;for(var s=0;s=o)u=this.$renderToken(a,u,l,c.substring(0,o-r)),c=c.substring(o-r),r=o,a=this.$createLineElement(),e.appendChild(a),a.appendChild(this.dom.createTextNode(s.stringRepeat("\u00a0",n.indent),this.element)),i++,u=0,o=n[i]||Number.MAX_VALUE;c.length!=0&&(r+=c.length,u=this.$renderToken(a,u,l,c))}}n[n.length-1]>this.MAX_LINE_LENGTH&&this.$renderOverflowMessage(a,u,null,"",!0)},this.$renderSimpleLine=function(e,t){var n=0,r=t[0],i=r.value;this.displayIndentGuides&&(i=this.renderIndentGuide(e,i)),i&&(n=this.$renderToken(e,n,r,i));for(var s=1;sthis.MAX_LINE_LENGTH)return this.$renderOverflowMessage(e,n,r,i);n=this.$renderToken(e,n,r,i)}},this.$renderOverflowMessage=function(e,t,n,r,i){n&&this.$renderToken(e,t,n,r.slice(0,this.MAX_LINE_LENGTH-t));var s=this.dom.createElement("span");s.className="ace_inline_button ace_keyword ace_toggle_wrap",s.textContent=i?"":"",e.appendChild(s)},this.$renderLine=function(e,t,n){!n&&n!=0&&(n=this.session.getFoldLine(t));if(n)var r=this.$getFoldLineTokens(t,n);else var r=this.session.getTokens(t);var i=e;if(r.length){var s=this.session.getRowSplitData(t);if(s&&s.length){this.$renderWrappedLine(e,r,s);var i=e.lastChild}else{var i=e;this.$useLineGroups()&&(i=this.$createLineElement(),e.appendChild(i)),this.$renderSimpleLine(i,r)}}else this.$useLineGroups()&&(i=this.$createLineElement(),e.appendChild(i));if(this.showInvisibles&&i){n&&(t=n.end.row);var o=this.dom.createElement("span");o.className="ace_invisible ace_invisible_eol",o.textContent=t==this.session.getLength()-1?this.EOF_CHAR:this.EOL_CHAR,i.appendChild(o)}},this.$getFoldLineTokens=function(e,t){function i(e,t,n){var i=0,s=0;while(s+e[i].value.lengthn-t&&(o=o.substring(0,n-t)),r.push({type:e[i].type,value:o}),s=t+o.length,i+=1}while(sn?r.push({type:e[i].type,value:o.substring(0,n-s)}):r.push(e[i]),s+=o.length,i+=1}}var n=this.session,r=[],s=n.getTokens(e);return t.walk(function(e,t,o,u,a){e!=null?r.push({type:"fold",value:e}):(a&&(s=n.getTokens(t)),s.length&&i(s,u,o))},t.end.row,this.session.getLine(t.end.row).length),r},this.$useLineGroups=function(){return this.session.getUseWrapMode()},this.destroy=function(){}}).call(a.prototype),t.Text=a}),define("ace/layer/cursor",["require","exports","module","ace/lib/dom"],function(e,t,n){"use strict";var r=e("../lib/dom"),i=function(e){this.element=r.createElement("div"),this.element.className="ace_layer ace_cursor-layer",e.appendChild(this.element),this.isVisible=!1,this.isBlinking=!0,this.blinkInterval=1e3,this.smoothBlinking=!1,this.cursors=[],this.cursor=this.addCursor(),r.addCssClass(this.element,"ace_hidden-cursors"),this.$updateCursors=this.$updateOpacity.bind(this)};(function(){this.$updateOpacity=function(e){var t=this.cursors;for(var n=t.length;n--;)r.setStyle(t[n].style,"opacity",e?"":"0")},this.$startCssAnimation=function(){var e=this.cursors;for(var t=e.length;t--;)e[t].style.animationDuration=this.blinkInterval+"ms";setTimeout(function(){r.addCssClass(this.element,"ace_animate-blinking")}.bind(this))},this.$stopCssAnimation=function(){r.removeCssClass(this.element,"ace_animate-blinking")},this.$padding=0,this.setPadding=function(e){this.$padding=e},this.setSession=function(e){this.session=e},this.setBlinking=function(e){e!=this.isBlinking&&(this.isBlinking=e,this.restartTimer())},this.setBlinkInterval=function(e){e!=this.blinkInterval&&(this.blinkInterval=e,this.restartTimer())},this.setSmoothBlinking=function(e){e!=this.smoothBlinking&&(this.smoothBlinking=e,r.setCssClass(this.element,"ace_smooth-blinking",e),this.$updateCursors(!0),this.restartTimer())},this.addCursor=function(){var e=r.createElement("div");return e.className="ace_cursor",this.element.appendChild(e),this.cursors.push(e),e},this.removeCursor=function(){if(this.cursors.length>1){var e=this.cursors.pop();return e.parentNode.removeChild(e),e}},this.hideCursor=function(){this.isVisible=!1,r.addCssClass(this.element,"ace_hidden-cursors"),this.restartTimer()},this.showCursor=function(){this.isVisible=!0,r.removeCssClass(this.element,"ace_hidden-cursors"),this.restartTimer()},this.restartTimer=function(){var e=this.$updateCursors;clearInterval(this.intervalId),clearTimeout(this.timeoutId),this.$stopCssAnimation(),this.smoothBlinking&&r.removeCssClass(this.element,"ace_smooth-blinking"),e(!0);if(!this.isBlinking||!this.blinkInterval||!this.isVisible){this.$stopCssAnimation();return}this.smoothBlinking&&setTimeout(function(){r.addCssClass(this.element,"ace_smooth-blinking")}.bind(this));if(r.HAS_CSS_ANIMATION)this.$startCssAnimation();else{var t=function(){this.timeoutId=setTimeout(function(){e(!1)},.6*this.blinkInterval)}.bind(this);this.intervalId=setInterval(function(){e(!0),t()},this.blinkInterval),t()}},this.getPixelPosition=function(e,t){if(!this.config||!this.session)return{left:0,top:0};e||(e=this.session.selection.getCursor());var n=this.session.documentToScreenPosition(e),r=this.$padding+(this.session.$bidiHandler.isBidiRow(n.row,e.row)?this.session.$bidiHandler.getPosLeft(n.column):n.column*this.config.characterWidth),i=(n.row-(t?this.config.firstRowScreen:0))*this.config.lineHeight;return{left:r,top:i}},this.isCursorInView=function(e,t){return e.top>=0&&e.tope.height+e.offset||o.top<0)&&n>1)continue;var u=this.cursors[i++]||this.addCursor(),a=u.style;this.drawCursor?this.drawCursor(u,o,e,t[n],this.session):this.isCursorInView(o,e)?(r.setStyle(a,"display","block"),r.translate(u,o.left,o.top),r.setStyle(a,"width",Math.round(e.characterWidth)+"px"),r.setStyle(a,"height",e.lineHeight+"px")):r.setStyle(a,"display","none")}while(this.cursors.length>i)this.removeCursor();var f=this.session.getOverwrite();this.$setOverwrite(f),this.$pixelPos=o,this.restartTimer()},this.drawCursor=null,this.$setOverwrite=function(e){e!=this.overwrite&&(this.overwrite=e,e?r.addCssClass(this.element,"ace_overwrite-cursors"):r.removeCssClass(this.element,"ace_overwrite-cursors"))},this.destroy=function(){clearInterval(this.intervalId),clearTimeout(this.timeoutId)}}).call(i.prototype),t.Cursor=i}),define("ace/scrollbar",["require","exports","module","ace/lib/oop","ace/lib/dom","ace/lib/event","ace/lib/event_emitter"],function(e,t,n){"use strict";var r=e("./lib/oop"),i=e("./lib/dom"),s=e("./lib/event"),o=e("./lib/event_emitter").EventEmitter,u=32768,a=function(e){this.element=i.createElement("div"),this.element.className="ace_scrollbar ace_scrollbar"+this.classSuffix,this.inner=i.createElement("div"),this.inner.className="ace_scrollbar-inner",this.inner.textContent="\u00a0",this.element.appendChild(this.inner),e.appendChild(this.element),this.setVisible(!1),this.skipEvent=!1,s.addListener(this.element,"scroll",this.onScroll.bind(this)),s.addListener(this.element,"mousedown",s.preventDefault)};(function(){r.implement(this,o),this.setVisible=function(e){this.element.style.display=e?"":"none",this.isVisible=e,this.coeff=1}}).call(a.prototype);var f=function(e,t){a.call(this,e),this.scrollTop=0,this.scrollHeight=0,t.$scrollbarWidth=this.width=i.scrollbarWidth(e.ownerDocument),this.inner.style.width=this.element.style.width=(this.width||15)+5+"px",this.$minWidth=0};r.inherits(f,a),function(){this.classSuffix="-v",this.onScroll=function(){if(!this.skipEvent){this.scrollTop=this.element.scrollTop;if(this.coeff!=1){var e=this.element.clientHeight/this.scrollHeight;this.scrollTop=this.scrollTop*(1-e)/(this.coeff-e)}this._emit("scroll",{data:this.scrollTop})}this.skipEvent=!1},this.getWidth=function(){return Math.max(this.isVisible?this.width:0,this.$minWidth||0)},this.setHeight=function(e){this.element.style.height=e+"px"},this.setInnerHeight=this.setScrollHeight=function(e){this.scrollHeight=e,e>u?(this.coeff=u/e,e=u):this.coeff!=1&&(this.coeff=1),this.inner.style.height=e+"px"},this.setScrollTop=function(e){this.scrollTop!=e&&(this.skipEvent=!0,this.scrollTop=e,this.element.scrollTop=e*this.coeff)}}.call(f.prototype);var l=function(e,t){a.call(this,e),this.scrollLeft=0,this.height=t.$scrollbarWidth,this.inner.style.height=this.element.style.height=(this.height||15)+5+"px"};r.inherits(l,a),function(){this.classSuffix="-h",this.onScroll=function(){this.skipEvent||(this.scrollLeft=this.element.scrollLeft,this._emit("scroll",{data:this.scrollLeft})),this.skipEvent=!1},this.getHeight=function(){return this.isVisible?this.height:0},this.setWidth=function(e){this.element.style.width=e+"px"},this.setInnerWidth=function(e){this.inner.style.width=e+"px"},this.setScrollWidth=function(e){this.inner.style.width=e+"px"},this.setScrollLeft=function(e){this.scrollLeft!=e&&(this.skipEvent=!0,this.scrollLeft=this.element.scrollLeft=e)}}.call(l.prototype),t.ScrollBar=f,t.ScrollBarV=f,t.ScrollBarH=l,t.VScrollBar=f,t.HScrollBar=l}),define("ace/renderloop",["require","exports","module","ace/lib/event"],function(e,t,n){"use strict";var r=e("./lib/event"),i=function(e,t){this.onRender=e,this.pending=!1,this.changes=0,this.$recursionLimit=2,this.window=t||window;var n=this;this._flush=function(e){n.pending=!1;var t=n.changes;t&&(r.blockIdle(100),n.changes=0,n.onRender(t));if(n.changes){if(n.$recursionLimit--<0)return;n.schedule()}else n.$recursionLimit=2}};(function(){this.schedule=function(e){this.changes=this.changes|e,this.changes&&!this.pending&&(r.nextFrame(this._flush),this.pending=!0)},this.clear=function(e){var t=this.changes;return this.changes=0,t}}).call(i.prototype),t.RenderLoop=i}),define("ace/layer/font_metrics",["require","exports","module","ace/lib/oop","ace/lib/dom","ace/lib/lang","ace/lib/event","ace/lib/useragent","ace/lib/event_emitter"],function(e,t,n){var r=e("../lib/oop"),i=e("../lib/dom"),s=e("../lib/lang"),o=e("../lib/event"),u=e("../lib/useragent"),a=e("../lib/event_emitter").EventEmitter,f=256,l=typeof ResizeObserver=="function",c=200,h=t.FontMetrics=function(e){this.el=i.createElement("div"),this.$setMeasureNodeStyles(this.el.style,!0),this.$main=i.createElement("div"),this.$setMeasureNodeStyles(this.$main.style),this.$measureNode=i.createElement("div"),this.$setMeasureNodeStyles(this.$measureNode.style),this.el.appendChild(this.$main),this.el.appendChild(this.$measureNode),e.appendChild(this.el),this.$measureNode.innerHTML=s.stringRepeat("X",f),this.$characterSize={width:0,height:0},l?this.$addObserver():this.checkForSizeChanges()};(function(){r.implement(this,a),this.$characterSize={width:0,height:0},this.$setMeasureNodeStyles=function(e,t){e.width=e.height="auto",e.left=e.top="0px",e.visibility="hidden",e.position="absolute",e.whiteSpace="pre",u.isIE<8?e["font-family"]="inherit":e.font="inherit",e.overflow=t?"hidden":"visible"},this.checkForSizeChanges=function(e){e===undefined&&(e=this.$measureSizes());if(e&&(this.$characterSize.width!==e.width||this.$characterSize.height!==e.height)){this.$measureNode.style.fontWeight="bold";var t=this.$measureSizes();this.$measureNode.style.fontWeight="",this.$characterSize=e,this.charSizes=Object.create(null),this.allowBoldFonts=t&&t.width===e.width&&t.height===e.height,this._emit("changeCharacterSize",{data:e})}},this.$addObserver=function(){var e=this;this.$observer=new window.ResizeObserver(function(t){var n=t[0].contentRect;e.checkForSizeChanges({height:n.height,width:n.width/f})}),this.$observer.observe(this.$measureNode)},this.$pollSizeChanges=function(){if(this.$pollSizeChangesTimer||this.$observer)return this.$pollSizeChangesTimer;var e=this;return this.$pollSizeChangesTimer=o.onIdle(function t(){e.checkForSizeChanges(),o.onIdle(t,500)},500)},this.setPolling=function(e){e?this.$pollSizeChanges():this.$pollSizeChangesTimer&&(clearInterval(this.$pollSizeChangesTimer),this.$pollSizeChangesTimer=0)},this.$measureSizes=function(e){var t={height:(e||this.$measureNode).clientHeight,width:(e||this.$measureNode).clientWidth/f};return t.width===0||t.height===0?null:t},this.$measureCharWidth=function(e){this.$main.innerHTML=s.stringRepeat(e,f);var t=this.$main.getBoundingClientRect();return t.width/f},this.getCharacterWidth=function(e){var t=this.charSizes[e];return t===undefined&&(t=this.charSizes[e]=this.$measureCharWidth(e)/this.$characterSize.width),t},this.destroy=function(){clearInterval(this.$pollSizeChangesTimer),this.$observer&&this.$observer.disconnect(),this.el&&this.el.parentNode&&this.el.parentNode.removeChild(this.el)},this.$getZoom=function e(t){return t?(window.getComputedStyle(t).zoom||1)*e(t.parentElement):1},this.$initTransformMeasureNodes=function(){var e=function(e,t){return["div",{style:"position: absolute;top:"+e+"px;left:"+t+"px;"}]};this.els=i.buildDom([e(0,0),e(c,0),e(0,c),e(c,c)],this.el)},this.transformCoordinates=function(e,t){function r(e,t,n){var r=e[1]*t[0]-e[0]*t[1];return[(-t[1]*n[0]+t[0]*n[1])/r,(+e[1]*n[0]-e[0]*n[1])/r]}function i(e,t){return[e[0]-t[0],e[1]-t[1]]}function s(e,t){return[e[0]+t[0],e[1]+t[1]]}function o(e,t){return[e*t[0],e*t[1]]}function u(e){var t=e.getBoundingClientRect();return[t.left,t.top]}if(e){var n=this.$getZoom(this.el);e=o(1/n,e)}this.els||this.$initTransformMeasureNodes();var a=u(this.els[0]),f=u(this.els[1]),l=u(this.els[2]),h=u(this.els[3]),p=r(i(h,f),i(h,l),i(s(f,l),s(h,a))),d=o(1+p[0],i(f,a)),v=o(1+p[1],i(l,a));if(t){var m=t,g=p[0]*m[0]/c+p[1]*m[1]/c+1,y=s(o(m[0],d),o(m[1],v));return s(o(1/g/c,y),a)}var b=i(e,a),w=r(i(d,o(p[0],b)),i(v,o(p[1],b)),b);return o(c,w)}}).call(h.prototype)}),define("ace/virtual_renderer",["require","exports","module","ace/lib/oop","ace/lib/dom","ace/config","ace/layer/gutter","ace/layer/marker","ace/layer/text","ace/layer/cursor","ace/scrollbar","ace/scrollbar","ace/renderloop","ace/layer/font_metrics","ace/lib/event_emitter","ace/lib/useragent"],function(e,t,n){"use strict";var r=e("./lib/oop"),i=e("./lib/dom"),s=e("./config"),o=e("./layer/gutter").Gutter,u=e("./layer/marker").Marker,a=e("./layer/text").Text,f=e("./layer/cursor").Cursor,l=e("./scrollbar").HScrollBar,c=e("./scrollbar").VScrollBar,h=e("./renderloop").RenderLoop,p=e("./layer/font_metrics").FontMetrics,d=e("./lib/event_emitter").EventEmitter,v='.ace_br1 {border-top-left-radius : 3px;}.ace_br2 {border-top-right-radius : 3px;}.ace_br3 {border-top-left-radius : 3px; border-top-right-radius: 3px;}.ace_br4 {border-bottom-right-radius: 3px;}.ace_br5 {border-top-left-radius : 3px; border-bottom-right-radius: 3px;}.ace_br6 {border-top-right-radius : 3px; border-bottom-right-radius: 3px;}.ace_br7 {border-top-left-radius : 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px;}.ace_br8 {border-bottom-left-radius : 3px;}.ace_br9 {border-top-left-radius : 3px; border-bottom-left-radius: 3px;}.ace_br10{border-top-right-radius : 3px; border-bottom-left-radius: 3px;}.ace_br11{border-top-left-radius : 3px; border-top-right-radius: 3px; border-bottom-left-radius: 3px;}.ace_br12{border-bottom-right-radius: 3px; border-bottom-left-radius: 3px;}.ace_br13{border-top-left-radius : 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px;}.ace_br14{border-top-right-radius : 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px;}.ace_br15{border-top-left-radius : 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px;}.ace_editor {position: relative;overflow: hidden;font: 12px/normal \'Monaco\', \'Menlo\', \'Ubuntu Mono\', \'Consolas\', \'source-code-pro\', monospace;direction: ltr;text-align: left;-webkit-tap-highlight-color: rgba(0, 0, 0, 0);}.ace_scroller {position: absolute;overflow: hidden;top: 0;bottom: 0;background-color: inherit;-ms-user-select: none;-moz-user-select: none;-webkit-user-select: none;user-select: none;cursor: text;}.ace_content {position: absolute;box-sizing: border-box;min-width: 100%;contain: style size layout;font-variant-ligatures: no-common-ligatures;}.ace_dragging .ace_scroller:before{position: absolute;top: 0;left: 0;right: 0;bottom: 0;content: \'\';background: rgba(250, 250, 250, 0.01);z-index: 1000;}.ace_dragging.ace_dark .ace_scroller:before{background: rgba(0, 0, 0, 0.01);}.ace_selecting, .ace_selecting * {cursor: text !important;}.ace_gutter {position: absolute;overflow : hidden;width: auto;top: 0;bottom: 0;left: 0;cursor: default;z-index: 4;-ms-user-select: none;-moz-user-select: none;-webkit-user-select: none;user-select: none;contain: style size layout;}.ace_gutter-active-line {position: absolute;left: 0;right: 0;}.ace_scroller.ace_scroll-left {box-shadow: 17px 0 16px -16px rgba(0, 0, 0, 0.4) inset;}.ace_gutter-cell {position: absolute;top: 0;left: 0;right: 0;padding-left: 19px;padding-right: 6px;background-repeat: no-repeat;}.ace_gutter-cell.ace_error {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAABOFBMVEX/////////QRswFAb/Ui4wFAYwFAYwFAaWGAfDRymzOSH/PxswFAb/SiUwFAYwFAbUPRvjQiDllog5HhHdRybsTi3/Tyv9Tir+Syj/UC3////XurebMBIwFAb/RSHbPx/gUzfdwL3kzMivKBAwFAbbvbnhPx66NhowFAYwFAaZJg8wFAaxKBDZurf/RB6mMxb/SCMwFAYwFAbxQB3+RB4wFAb/Qhy4Oh+4QifbNRcwFAYwFAYwFAb/QRzdNhgwFAYwFAbav7v/Uy7oaE68MBK5LxLewr/r2NXewLswFAaxJw4wFAbkPRy2PyYwFAaxKhLm1tMwFAazPiQwFAaUGAb/QBrfOx3bvrv/VC/maE4wFAbRPBq6MRO8Qynew8Dp2tjfwb0wFAbx6eju5+by6uns4uH9/f36+vr/GkHjAAAAYnRSTlMAGt+64rnWu/bo8eAA4InH3+DwoN7j4eLi4xP99Nfg4+b+/u9B/eDs1MD1mO7+4PHg2MXa347g7vDizMLN4eG+Pv7i5evs/v79yu7S3/DV7/498Yv24eH+4ufQ3Ozu/v7+y13sRqwAAADLSURBVHjaZc/XDsFgGIBhtDrshlitmk2IrbHFqL2pvXf/+78DPokj7+Fz9qpU/9UXJIlhmPaTaQ6QPaz0mm+5gwkgovcV6GZzd5JtCQwgsxoHOvJO15kleRLAnMgHFIESUEPmawB9ngmelTtipwwfASilxOLyiV5UVUyVAfbG0cCPHig+GBkzAENHS0AstVF6bacZIOzgLmxsHbt2OecNgJC83JERmePUYq8ARGkJx6XtFsdddBQgZE2nPR6CICZhawjA4Fb/chv+399kfR+MMMDGOQAAAABJRU5ErkJggg==");background-repeat: no-repeat;background-position: 2px center;}.ace_gutter-cell.ace_warning {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAmVBMVEX///8AAAD///8AAAAAAABPSzb/5sAAAAB/blH/73z/ulkAAAAAAAD85pkAAAAAAAACAgP/vGz/rkDerGbGrV7/pkQICAf////e0IsAAAD/oED/qTvhrnUAAAD/yHD/njcAAADuv2r/nz//oTj/p064oGf/zHAAAAA9Nir/tFIAAAD/tlTiuWf/tkIAAACynXEAAAAAAAAtIRW7zBpBAAAAM3RSTlMAABR1m7RXO8Ln31Z36zT+neXe5OzooRDfn+TZ4p3h2hTf4t3k3ucyrN1K5+Xaks52Sfs9CXgrAAAAjklEQVR42o3PbQ+CIBQFYEwboPhSYgoYunIqqLn6/z8uYdH8Vmdnu9vz4WwXgN/xTPRD2+sgOcZjsge/whXZgUaYYvT8QnuJaUrjrHUQreGczuEafQCO/SJTufTbroWsPgsllVhq3wJEk2jUSzX3CUEDJC84707djRc5MTAQxoLgupWRwW6UB5fS++NV8AbOZgnsC7BpEAAAAABJRU5ErkJggg==");background-position: 2px center;}.ace_gutter-cell.ace_info {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAAAAAA6mKC9AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAJ0Uk5TAAB2k804AAAAPklEQVQY02NgIB68QuO3tiLznjAwpKTgNyDbMegwisCHZUETUZV0ZqOquBpXj2rtnpSJT1AEnnRmL2OgGgAAIKkRQap2htgAAAAASUVORK5CYII=");background-position: 2px center;}.ace_dark .ace_gutter-cell.ace_info {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAJFBMVEUAAAChoaGAgIAqKiq+vr6tra1ZWVmUlJSbm5s8PDxubm56enrdgzg3AAAAAXRSTlMAQObYZgAAAClJREFUeNpjYMAPdsMYHegyJZFQBlsUlMFVCWUYKkAZMxZAGdxlDMQBAG+TBP4B6RyJAAAAAElFTkSuQmCC");}.ace_scrollbar {contain: strict;position: absolute;right: 0;bottom: 0;z-index: 6;}.ace_scrollbar-inner {position: absolute;cursor: text;left: 0;top: 0;}.ace_scrollbar-v{overflow-x: hidden;overflow-y: scroll;top: 0;}.ace_scrollbar-h {overflow-x: scroll;overflow-y: hidden;left: 0;}.ace_print-margin {position: absolute;height: 100%;}.ace_text-input {position: absolute;z-index: 0;width: 0.5em;height: 1em;opacity: 0;background: transparent;-moz-appearance: none;appearance: none;border: none;resize: none;outline: none;overflow: hidden;font: inherit;padding: 0 1px;margin: 0 -1px;contain: strict;-ms-user-select: text;-moz-user-select: text;-webkit-user-select: text;user-select: text;white-space: pre!important;}.ace_text-input.ace_composition {background: transparent;color: inherit;z-index: 1000;opacity: 1;}.ace_composition_placeholder { color: transparent }.ace_composition_marker { border-bottom: 1px solid;position: absolute;border-radius: 0;margin-top: 1px;}[ace_nocontext=true] {transform: none!important;filter: none!important;clip-path: none!important;mask : none!important;contain: none!important;perspective: none!important;mix-blend-mode: initial!important;z-index: auto;}.ace_layer {z-index: 1;position: absolute;overflow: hidden;word-wrap: normal;white-space: pre;height: 100%;width: 100%;box-sizing: border-box;pointer-events: none;}.ace_gutter-layer {position: relative;width: auto;text-align: right;pointer-events: auto;height: 1000000px;contain: style size layout;}.ace_text-layer {font: inherit !important;position: absolute;height: 1000000px;width: 1000000px;contain: style size layout;}.ace_text-layer > .ace_line, .ace_text-layer > .ace_line_group {contain: style size layout;position: absolute;top: 0;left: 0;right: 0;}.ace_hidpi .ace_text-layer,.ace_hidpi .ace_gutter-layer,.ace_hidpi .ace_content,.ace_hidpi .ace_gutter {contain: strict;will-change: transform;}.ace_hidpi .ace_text-layer > .ace_line, .ace_hidpi .ace_text-layer > .ace_line_group {contain: strict;}.ace_cjk {display: inline-block;text-align: center;}.ace_cursor-layer {z-index: 4;}.ace_cursor {z-index: 4;position: absolute;box-sizing: border-box;border-left: 2px solid;transform: translatez(0);}.ace_multiselect .ace_cursor {border-left-width: 1px;}.ace_slim-cursors .ace_cursor {border-left-width: 1px;}.ace_overwrite-cursors .ace_cursor {border-left-width: 0;border-bottom: 1px solid;}.ace_hidden-cursors .ace_cursor {opacity: 0.2;}.ace_hasPlaceholder .ace_hidden-cursors .ace_cursor {opacity: 0;}.ace_smooth-blinking .ace_cursor {transition: opacity 0.18s;}.ace_animate-blinking .ace_cursor {animation-duration: 1000ms;animation-timing-function: step-end;animation-name: blink-ace-animate;animation-iteration-count: infinite;}.ace_animate-blinking.ace_smooth-blinking .ace_cursor {animation-duration: 1000ms;animation-timing-function: ease-in-out;animation-name: blink-ace-animate-smooth;}@keyframes blink-ace-animate {from, to { opacity: 1; }60% { opacity: 0; }}@keyframes blink-ace-animate-smooth {from, to { opacity: 1; }45% { opacity: 1; }60% { opacity: 0; }85% { opacity: 0; }}.ace_marker-layer .ace_step, .ace_marker-layer .ace_stack {position: absolute;z-index: 3;}.ace_marker-layer .ace_selection {position: absolute;z-index: 5;}.ace_marker-layer .ace_bracket {position: absolute;z-index: 6;}.ace_marker-layer .ace_error_bracket {position: absolute;border-bottom: 1px solid #DE5555;border-radius: 0;}.ace_marker-layer .ace_active-line {position: absolute;z-index: 2;}.ace_marker-layer .ace_selected-word {position: absolute;z-index: 4;box-sizing: border-box;}.ace_line .ace_fold {box-sizing: border-box;display: inline-block;height: 11px;margin-top: -2px;vertical-align: middle;background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAJCAYAAADU6McMAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAJpJREFUeNpi/P//PwOlgAXGYGRklAVSokD8GmjwY1wasKljQpYACtpCFeADcHVQfQyMQAwzwAZI3wJKvCLkfKBaMSClBlR7BOQikCFGQEErIH0VqkabiGCAqwUadAzZJRxQr/0gwiXIal8zQQPnNVTgJ1TdawL0T5gBIP1MUJNhBv2HKoQHHjqNrA4WO4zY0glyNKLT2KIfIMAAQsdgGiXvgnYAAAAASUVORK5CYII="),url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAA3CAYAAADNNiA5AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAACJJREFUeNpi+P//fxgTAwPDBxDxD078RSX+YeEyDFMCIMAAI3INmXiwf2YAAAAASUVORK5CYII=");background-repeat: no-repeat, repeat-x;background-position: center center, top left;color: transparent;border: 1px solid black;border-radius: 2px;cursor: pointer;pointer-events: auto;}.ace_dark .ace_fold {}.ace_fold:hover{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAJCAYAAADU6McMAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAJpJREFUeNpi/P//PwOlgAXGYGRklAVSokD8GmjwY1wasKljQpYACtpCFeADcHVQfQyMQAwzwAZI3wJKvCLkfKBaMSClBlR7BOQikCFGQEErIH0VqkabiGCAqwUadAzZJRxQr/0gwiXIal8zQQPnNVTgJ1TdawL0T5gBIP1MUJNhBv2HKoQHHjqNrA4WO4zY0glyNKLT2KIfIMAAQsdgGiXvgnYAAAAASUVORK5CYII="),url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAA3CAYAAADNNiA5AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAACBJREFUeNpi+P//fz4TAwPDZxDxD5X4i5fLMEwJgAADAEPVDbjNw87ZAAAAAElFTkSuQmCC");}.ace_tooltip {background-color: #FFF;background-image: linear-gradient(to bottom, transparent, rgba(0, 0, 0, 0.1));border: 1px solid gray;border-radius: 1px;box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);color: black;max-width: 100%;padding: 3px 4px;position: fixed;z-index: 999999;box-sizing: border-box;cursor: default;white-space: pre;word-wrap: break-word;line-height: normal;font-style: normal;font-weight: normal;letter-spacing: normal;pointer-events: none;}.ace_folding-enabled > .ace_gutter-cell {padding-right: 13px;}.ace_fold-widget {box-sizing: border-box;margin: 0 -12px 0 1px;display: none;width: 11px;vertical-align: top;background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAANElEQVR42mWKsQ0AMAzC8ixLlrzQjzmBiEjp0A6WwBCSPgKAXoLkqSot7nN3yMwR7pZ32NzpKkVoDBUxKAAAAABJRU5ErkJggg==");background-repeat: no-repeat;background-position: center;border-radius: 3px;border: 1px solid transparent;cursor: pointer;}.ace_folding-enabled .ace_fold-widget {display: inline-block; }.ace_fold-widget.ace_end {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAANElEQVR42m3HwQkAMAhD0YzsRchFKI7sAikeWkrxwScEB0nh5e7KTPWimZki4tYfVbX+MNl4pyZXejUO1QAAAABJRU5ErkJggg==");}.ace_fold-widget.ace_closed {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAGCAYAAAAG5SQMAAAAOUlEQVR42jXKwQkAMAgDwKwqKD4EwQ26sSOkVWjgIIHAzPiCgaqiqnJHZnKICBERHN194O5b9vbLuAVRL+l0YWnZAAAAAElFTkSuQmCCXA==");}.ace_fold-widget:hover {border: 1px solid rgba(0, 0, 0, 0.3);background-color: rgba(255, 255, 255, 0.2);box-shadow: 0 1px 1px rgba(255, 255, 255, 0.7);}.ace_fold-widget:active {border: 1px solid rgba(0, 0, 0, 0.4);background-color: rgba(0, 0, 0, 0.05);box-shadow: 0 1px 1px rgba(255, 255, 255, 0.8);}.ace_dark .ace_fold-widget {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHklEQVQIW2P4//8/AzoGEQ7oGCaLLAhWiSwB146BAQCSTPYocqT0AAAAAElFTkSuQmCC");}.ace_dark .ace_fold-widget.ace_end {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAH0lEQVQIW2P4//8/AxQ7wNjIAjDMgC4AxjCVKBirIAAF0kz2rlhxpAAAAABJRU5ErkJggg==");}.ace_dark .ace_fold-widget.ace_closed {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAFCAYAAACAcVaiAAAAHElEQVQIW2P4//+/AxAzgDADlOOAznHAKgPWAwARji8UIDTfQQAAAABJRU5ErkJggg==");}.ace_dark .ace_fold-widget:hover {box-shadow: 0 1px 1px rgba(255, 255, 255, 0.2);background-color: rgba(255, 255, 255, 0.1);}.ace_dark .ace_fold-widget:active {box-shadow: 0 1px 1px rgba(255, 255, 255, 0.2);}.ace_inline_button {border: 1px solid lightgray;display: inline-block;margin: -1px 8px;padding: 0 5px;pointer-events: auto;cursor: pointer;}.ace_inline_button:hover {border-color: gray;background: rgba(200,200,200,0.2);display: inline-block;pointer-events: auto;}.ace_fold-widget.ace_invalid {background-color: #FFB4B4;border-color: #DE5555;}.ace_fade-fold-widgets .ace_fold-widget {transition: opacity 0.4s ease 0.05s;opacity: 0;}.ace_fade-fold-widgets:hover .ace_fold-widget {transition: opacity 0.05s ease 0.05s;opacity:1;}.ace_underline {text-decoration: underline;}.ace_bold {font-weight: bold;}.ace_nobold .ace_bold {font-weight: normal;}.ace_italic {font-style: italic;}.ace_error-marker {background-color: rgba(255, 0, 0,0.2);position: absolute;z-index: 9;}.ace_highlight-marker {background-color: rgba(255, 255, 0,0.2);position: absolute;z-index: 8;}.ace_mobile-menu {position: absolute;line-height: 1.5;border-radius: 4px;-ms-user-select: none;-moz-user-select: none;-webkit-user-select: none;user-select: none;background: white;box-shadow: 1px 3px 2px grey;border: 1px solid #dcdcdc;color: black;}.ace_dark > .ace_mobile-menu {background: #333;color: #ccc;box-shadow: 1px 3px 2px grey;border: 1px solid #444;}.ace_mobile-button {padding: 2px;cursor: pointer;overflow: hidden;}.ace_mobile-button:hover {background-color: #eee;opacity:1;}.ace_mobile-button:active {background-color: #ddd;}.ace_placeholder {font-family: arial;transform: scale(0.9);transform-origin: left;white-space: pre;opacity: 0.7;margin: 0 10px;}',m=e("./lib/useragent"),g=m.isIE;i.importCssString(v,"ace_editor.css");var y=function(e,t){var n=this;this.container=e||i.createElement("div"),i.addCssClass(this.container,"ace_editor"),i.HI_DPI&&i.addCssClass(this.container,"ace_hidpi"),this.setTheme(t),this.$gutter=i.createElement("div"),this.$gutter.className="ace_gutter",this.container.appendChild(this.$gutter),this.$gutter.setAttribute("aria-hidden",!0),this.scroller=i.createElement("div"),this.scroller.className="ace_scroller",this.container.appendChild(this.scroller),this.content=i.createElement("div"),this.content.className="ace_content",this.scroller.appendChild(this.content),this.$gutterLayer=new o(this.$gutter),this.$gutterLayer.on("changeGutterWidth",this.onGutterResize.bind(this)),this.$markerBack=new u(this.content);var r=this.$textLayer=new a(this.content);this.canvas=r.element,this.$markerFront=new u(this.content),this.$cursorLayer=new f(this.content),this.$horizScroll=!1,this.$vScroll=!1,this.scrollBar=this.scrollBarV=new c(this.container,this),this.scrollBarH=new l(this.container,this),this.scrollBarV.addEventListener("scroll",function(e){n.$scrollAnimation||n.session.setScrollTop(e.data-n.scrollMargin.top)}),this.scrollBarH.addEventListener("scroll",function(e){n.$scrollAnimation||n.session.setScrollLeft(e.data-n.scrollMargin.left)}),this.scrollTop=0,this.scrollLeft=0,this.cursorPos={row:0,column:0},this.$fontMetrics=new p(this.container),this.$textLayer.$setFontMetrics(this.$fontMetrics),this.$textLayer.addEventListener("changeCharacterSize",function(e){n.updateCharacterSize(),n.onResize(!0,n.gutterWidth,n.$size.width,n.$size.height),n._signal("changeCharacterSize",e)}),this.$size={width:0,height:0,scrollerHeight:0,scrollerWidth:0,$dirty:!0},this.layerConfig={width:1,padding:0,firstRow:0,firstRowScreen:0,lastRow:0,lineHeight:0,characterWidth:0,minHeight:1,maxHeight:1,offset:0,height:1,gutterOffset:1},this.scrollMargin={left:0,right:0,top:0,bottom:0,v:0,h:0},this.margin={left:0,right:0,top:0,bottom:0,v:0,h:0},this.$keepTextAreaAtCursor=!m.isIOS,this.$loop=new h(this.$renderChanges.bind(this),this.container.ownerDocument.defaultView),this.$loop.schedule(this.CHANGE_FULL),this.updateCharacterSize(),this.setPadding(4),s.resetOptions(this),s._signal("renderer",this)};(function(){this.CHANGE_CURSOR=1,this.CHANGE_MARKER=2,this.CHANGE_GUTTER=4,this.CHANGE_SCROLL=8,this.CHANGE_LINES=16,this.CHANGE_TEXT=32,this.CHANGE_SIZE=64,this.CHANGE_MARKER_BACK=128,this.CHANGE_MARKER_FRONT=256,this.CHANGE_FULL=512,this.CHANGE_H_SCROLL=1024,r.implement(this,d),this.updateCharacterSize=function(){this.$textLayer.allowBoldFonts!=this.$allowBoldFonts&&(this.$allowBoldFonts=this.$textLayer.allowBoldFonts,this.setStyle("ace_nobold",!this.$allowBoldFonts)),this.layerConfig.characterWidth=this.characterWidth=this.$textLayer.getCharacterWidth(),this.layerConfig.lineHeight=this.lineHeight=this.$textLayer.getLineHeight(),this.$updatePrintMargin(),i.setStyle(this.scroller.style,"line-height",this.lineHeight+"px")},this.setSession=function(e){this.session&&this.session.doc.off("changeNewLineMode",this.onChangeNewLineMode),this.session=e,e&&this.scrollMargin.top&&e.getScrollTop()<=0&&e.setScrollTop(-this.scrollMargin.top),this.$cursorLayer.setSession(e),this.$markerBack.setSession(e),this.$markerFront.setSession(e),this.$gutterLayer.setSession(e),this.$textLayer.setSession(e);if(!e)return;this.$loop.schedule(this.CHANGE_FULL),this.session.$setFontMetrics(this.$fontMetrics),this.scrollBarH.scrollLeft=this.scrollBarV.scrollTop=null,this.onChangeNewLineMode=this.onChangeNewLineMode.bind(this),this.onChangeNewLineMode(),this.session.doc.on("changeNewLineMode",this.onChangeNewLineMode)},this.updateLines=function(e,t,n){t===undefined&&(t=Infinity),this.$changedLines?(this.$changedLines.firstRow>e&&(this.$changedLines.firstRow=e),this.$changedLines.lastRowthis.layerConfig.lastRow)return;this.$loop.schedule(this.CHANGE_LINES)},this.onChangeNewLineMode=function(){this.$loop.schedule(this.CHANGE_TEXT),this.$textLayer.$updateEolChar(),this.session.$bidiHandler.setEolChar(this.$textLayer.EOL_CHAR)},this.onChangeTabSize=function(){this.$loop.schedule(this.CHANGE_TEXT|this.CHANGE_MARKER),this.$textLayer.onChangeTabSize()},this.updateText=function(){this.$loop.schedule(this.CHANGE_TEXT)},this.updateFull=function(e){e?this.$renderChanges(this.CHANGE_FULL,!0):this.$loop.schedule(this.CHANGE_FULL)},this.updateFontSize=function(){this.$textLayer.checkForSizeChanges()},this.$changes=0,this.$updateSizeAsync=function(){this.$loop.pending?this.$size.$dirty=!0:this.onResize()},this.onResize=function(e,t,n,r){if(this.resizing>2)return;this.resizing>0?this.resizing++:this.resizing=e?1:0;var i=this.container;r||(r=i.clientHeight||i.scrollHeight),n||(n=i.clientWidth||i.scrollWidth);var s=this.$updateCachedSize(e,t,n,r);if(!this.$size.scrollerHeight||!n&&!r)return this.resizing=0;e&&(this.$gutterLayer.$padding=null),e?this.$renderChanges(s|this.$changes,!0):this.$loop.schedule(s|this.$changes),this.resizing&&(this.resizing=0),this.scrollBarV.scrollLeft=this.scrollBarV.scrollTop=null},this.$updateCachedSize=function(e,t,n,r){r-=this.$extraHeight||0;var s=0,o=this.$size,u={width:o.width,height:o.height,scrollerHeight:o.scrollerHeight,scrollerWidth:o.scrollerWidth};r&&(e||o.height!=r)&&(o.height=r,s|=this.CHANGE_SIZE,o.scrollerHeight=o.height,this.$horizScroll&&(o.scrollerHeight-=this.scrollBarH.getHeight()),this.scrollBarV.element.style.bottom=this.scrollBarH.getHeight()+"px",s|=this.CHANGE_SCROLL);if(n&&(e||o.width!=n)){s|=this.CHANGE_SIZE,o.width=n,t==null&&(t=this.$showGutter?this.$gutter.offsetWidth:0),this.gutterWidth=t,i.setStyle(this.scrollBarH.element.style,"left",t+"px"),i.setStyle(this.scroller.style,"left",t+this.margin.left+"px"),o.scrollerWidth=Math.max(0,n-t-this.scrollBarV.getWidth()-this.margin.h),i.setStyle(this.$gutter.style,"left",this.margin.left+"px");var a=this.scrollBarV.getWidth()+"px";i.setStyle(this.scrollBarH.element.style,"right",a),i.setStyle(this.scroller.style,"right",a),i.setStyle(this.scroller.style,"bottom",this.scrollBarH.getHeight());if(this.session&&this.session.getUseWrapMode()&&this.adjustWrapLimit()||e)s|=this.CHANGE_FULL}return o.$dirty=!n||!r,s&&this._signal("resize",u),s},this.onGutterResize=function(e){var t=this.$showGutter?e:0;t!=this.gutterWidth&&(this.$changes|=this.$updateCachedSize(!0,t,this.$size.width,this.$size.height)),this.session.getUseWrapMode()&&this.adjustWrapLimit()?this.$loop.schedule(this.CHANGE_FULL):this.$size.$dirty?this.$loop.schedule(this.CHANGE_FULL):this.$computeLayerConfig()},this.adjustWrapLimit=function(){var e=this.$size.scrollerWidth-this.$padding*2,t=Math.floor(e/this.characterWidth);return this.session.adjustWrapLimit(t,this.$showPrintMargin&&this.$printMarginColumn)},this.setAnimatedScroll=function(e){this.setOption("animatedScroll",e)},this.getAnimatedScroll=function(){return this.$animatedScroll},this.setShowInvisibles=function(e){this.setOption("showInvisibles",e),this.session.$bidiHandler.setShowInvisibles(e)},this.getShowInvisibles=function(){return this.getOption("showInvisibles")},this.getDisplayIndentGuides=function(){return this.getOption("displayIndentGuides")},this.setDisplayIndentGuides=function(e){this.setOption("displayIndentGuides",e)},this.setShowPrintMargin=function(e){this.setOption("showPrintMargin",e)},this.getShowPrintMargin=function(){return this.getOption("showPrintMargin")},this.setPrintMarginColumn=function(e){this.setOption("printMarginColumn",e)},this.getPrintMarginColumn=function(){return this.getOption("printMarginColumn")},this.getShowGutter=function(){return this.getOption("showGutter")},this.setShowGutter=function(e){return this.setOption("showGutter",e)},this.getFadeFoldWidgets=function(){return this.getOption("fadeFoldWidgets")},this.setFadeFoldWidgets=function(e){this.setOption("fadeFoldWidgets",e)},this.setHighlightGutterLine=function(e){this.setOption("highlightGutterLine",e)},this.getHighlightGutterLine=function(){return this.getOption("highlightGutterLine")},this.$updatePrintMargin=function(){if(!this.$showPrintMargin&&!this.$printMarginEl)return;if(!this.$printMarginEl){var e=i.createElement("div");e.className="ace_layer ace_print-margin-layer",this.$printMarginEl=i.createElement("div"),this.$printMarginEl.className="ace_print-margin",e.appendChild(this.$printMarginEl),this.content.insertBefore(e,this.content.firstChild)}var t=this.$printMarginEl.style;t.left=Math.round(this.characterWidth*this.$printMarginColumn+this.$padding)+"px",t.visibility=this.$showPrintMargin?"visible":"hidden",this.session&&this.session.$wrap==-1&&this.adjustWrapLimit()},this.getContainerElement=function(){return this.container},this.getMouseEventTarget=function(){return this.scroller},this.getTextAreaContainer=function(){return this.container},this.$moveTextAreaToCursor=function(){if(this.$isMousePressed)return;var e=this.textarea.style,t=this.$composition;if(!this.$keepTextAreaAtCursor&&!t){i.translate(this.textarea,-100,0);return}var n=this.$cursorLayer.$pixelPos;if(!n)return;t&&t.markerRange&&(n=this.$cursorLayer.getPixelPosition(t.markerRange.start,!0));var r=this.layerConfig,s=n.top,o=n.left;s-=r.offset;var u=t&&t.useTextareaForIME?this.lineHeight:g?0:1;if(s<0||s>r.height-u){i.translate(this.textarea,0,0);return}var a=1,f=this.$size.height-u;if(!t)s+=this.lineHeight;else if(t.useTextareaForIME){var l=this.textarea.value;a=this.characterWidth*this.session.$getStringScreenWidth(l)[0]}else s+=this.lineHeight+2;o-=this.scrollLeft,o>this.$size.scrollerWidth-a&&(o=this.$size.scrollerWidth-a),o+=this.gutterWidth+this.margin.left,i.setStyle(e,"height",u+"px"),i.setStyle(e,"width",a+"px"),i.translate(this.textarea,Math.min(o,this.$size.scrollerWidth-a),Math.min(s,f))},this.getFirstVisibleRow=function(){return this.layerConfig.firstRow},this.getFirstFullyVisibleRow=function(){return this.layerConfig.firstRow+(this.layerConfig.offset===0?0:1)},this.getLastFullyVisibleRow=function(){var e=this.layerConfig,t=e.lastRow,n=this.session.documentToScreenRow(t,0)*e.lineHeight;return n-this.session.getScrollTop()>e.height-e.lineHeight?t-1:t},this.getLastVisibleRow=function(){return this.layerConfig.lastRow},this.$padding=null,this.setPadding=function(e){this.$padding=e,this.$textLayer.setPadding(e),this.$cursorLayer.setPadding(e),this.$markerFront.setPadding(e),this.$markerBack.setPadding(e),this.$loop.schedule(this.CHANGE_FULL),this.$updatePrintMargin()},this.setScrollMargin=function(e,t,n,r){var i=this.scrollMargin;i.top=e|0,i.bottom=t|0,i.right=r|0,i.left=n|0,i.v=i.top+i.bottom,i.h=i.left+i.right,i.top&&this.scrollTop<=0&&this.session&&this.session.setScrollTop(-i.top),this.updateFull()},this.setMargin=function(e,t,n,r){var i=this.margin;i.top=e|0,i.bottom=t|0,i.right=r|0,i.left=n|0,i.v=i.top+i.bottom,i.h=i.left+i.right,this.$updateCachedSize(!0,this.gutterWidth,this.$size.width,this.$size.height),this.updateFull()},this.getHScrollBarAlwaysVisible=function(){return this.$hScrollBarAlwaysVisible},this.setHScrollBarAlwaysVisible=function(e){this.setOption("hScrollBarAlwaysVisible",e)},this.getVScrollBarAlwaysVisible=function(){return this.$vScrollBarAlwaysVisible},this.setVScrollBarAlwaysVisible=function(e){this.setOption("vScrollBarAlwaysVisible",e)},this.$updateScrollBarV=function(){var e=this.layerConfig.maxHeight,t=this.$size.scrollerHeight;!this.$maxLines&&this.$scrollPastEnd&&(e-=(t-this.lineHeight)*this.$scrollPastEnd,this.scrollTop>e-t&&(e=this.scrollTop+t,this.scrollBarV.scrollTop=null)),this.scrollBarV.setScrollHeight(e+this.scrollMargin.v),this.scrollBarV.setScrollTop(this.scrollTop+this.scrollMargin.top)},this.$updateScrollBarH=function(){this.scrollBarH.setScrollWidth(this.layerConfig.width+2*this.$padding+this.scrollMargin.h),this.scrollBarH.setScrollLeft(this.scrollLeft+this.scrollMargin.left)},this.$frozen=!1,this.freeze=function(){this.$frozen=!0},this.unfreeze=function(){this.$frozen=!1},this.$renderChanges=function(e,t){this.$changes&&(e|=this.$changes,this.$changes=0);if(!this.session||!this.container.offsetWidth||this.$frozen||!e&&!t){this.$changes|=e;return}if(this.$size.$dirty)return this.$changes|=e,this.onResize(!0);this.lineHeight||this.$textLayer.checkForSizeChanges(),this._signal("beforeRender",e),this.session&&this.session.$bidiHandler&&this.session.$bidiHandler.updateCharacterWidths(this.$fontMetrics);var n=this.layerConfig;if(e&this.CHANGE_FULL||e&this.CHANGE_SIZE||e&this.CHANGE_TEXT||e&this.CHANGE_LINES||e&this.CHANGE_SCROLL||e&this.CHANGE_H_SCROLL){e|=this.$computeLayerConfig()|this.$loop.clear();if(n.firstRow!=this.layerConfig.firstRow&&n.firstRowScreen==this.layerConfig.firstRowScreen){var r=this.scrollTop+(n.firstRow-this.layerConfig.firstRow)*this.lineHeight;r>0&&(this.scrollTop=r,e|=this.CHANGE_SCROLL,e|=this.$computeLayerConfig()|this.$loop.clear())}n=this.layerConfig,this.$updateScrollBarV(),e&this.CHANGE_H_SCROLL&&this.$updateScrollBarH(),i.translate(this.content,-this.scrollLeft,-n.offset);var s=n.width+2*this.$padding+"px",o=n.minHeight+"px";i.setStyle(this.content.style,"width",s),i.setStyle(this.content.style,"height",o)}e&this.CHANGE_H_SCROLL&&(i.translate(this.content,-this.scrollLeft,-n.offset),this.scroller.className=this.scrollLeft<=0?"ace_scroller":"ace_scroller ace_scroll-left");if(e&this.CHANGE_FULL){this.$changedLines=null,this.$textLayer.update(n),this.$showGutter&&this.$gutterLayer.update(n),this.$markerBack.update(n),this.$markerFront.update(n),this.$cursorLayer.update(n),this.$moveTextAreaToCursor(),this._signal("afterRender",e);return}if(e&this.CHANGE_SCROLL){this.$changedLines=null,e&this.CHANGE_TEXT||e&this.CHANGE_LINES?this.$textLayer.update(n):this.$textLayer.scrollLines(n),this.$showGutter&&(e&this.CHANGE_GUTTER||e&this.CHANGE_LINES?this.$gutterLayer.update(n):this.$gutterLayer.scrollLines(n)),this.$markerBack.update(n),this.$markerFront.update(n),this.$cursorLayer.update(n),this.$moveTextAreaToCursor(),this._signal("afterRender",e);return}e&this.CHANGE_TEXT?(this.$changedLines=null,this.$textLayer.update(n),this.$showGutter&&this.$gutterLayer.update(n)):e&this.CHANGE_LINES?(this.$updateLines()||e&this.CHANGE_GUTTER&&this.$showGutter)&&this.$gutterLayer.update(n):e&this.CHANGE_TEXT||e&this.CHANGE_GUTTER?this.$showGutter&&this.$gutterLayer.update(n):e&this.CHANGE_CURSOR&&this.$highlightGutterLine&&this.$gutterLayer.updateLineHighlight(n),e&this.CHANGE_CURSOR&&(this.$cursorLayer.update(n),this.$moveTextAreaToCursor()),e&(this.CHANGE_MARKER|this.CHANGE_MARKER_FRONT)&&this.$markerFront.update(n),e&(this.CHANGE_MARKER|this.CHANGE_MARKER_BACK)&&this.$markerBack.update(n),this._signal("afterRender",e)},this.$autosize=function(){var e=this.session.getScreenLength()*this.lineHeight,t=this.$maxLines*this.lineHeight,n=Math.min(t,Math.max((this.$minLines||1)*this.lineHeight,e))+this.scrollMargin.v+(this.$extraHeight||0);this.$horizScroll&&(n+=this.scrollBarH.getHeight()),this.$maxPixelHeight&&n>this.$maxPixelHeight&&(n=this.$maxPixelHeight);var r=n<=2*this.lineHeight,i=!r&&e>t;if(n!=this.desiredHeight||this.$size.height!=this.desiredHeight||i!=this.$vScroll){i!=this.$vScroll&&(this.$vScroll=i,this.scrollBarV.setVisible(i));var s=this.container.clientWidth;this.container.style.height=n+"px",this.$updateCachedSize(!0,this.$gutterWidth,s,n),this.desiredHeight=n,this._signal("autosize")}},this.$computeLayerConfig=function(){var e=this.session,t=this.$size,n=t.height<=2*this.lineHeight,r=this.session.getScreenLength(),i=r*this.lineHeight,s=this.$getLongestLine(),o=!n&&(this.$hScrollBarAlwaysVisible||t.scrollerWidth-s-2*this.$padding<0),u=this.$horizScroll!==o;u&&(this.$horizScroll=o,this.scrollBarH.setVisible(o));var a=this.$vScroll;this.$maxLines&&this.lineHeight>1&&this.$autosize();var f=t.scrollerHeight+this.lineHeight,l=!this.$maxLines&&this.$scrollPastEnd?(t.scrollerHeight-this.lineHeight)*this.$scrollPastEnd:0;i+=l;var c=this.scrollMargin;this.session.setScrollTop(Math.max(-c.top,Math.min(this.scrollTop,i-t.scrollerHeight+c.bottom))),this.session.setScrollLeft(Math.max(-c.left,Math.min(this.scrollLeft,s+2*this.$padding-t.scrollerWidth+c.right)));var h=!n&&(this.$vScrollBarAlwaysVisible||t.scrollerHeight-i+l<0||this.scrollTop>c.top),p=a!==h;p&&(this.$vScroll=h,this.scrollBarV.setVisible(h));var d=this.scrollTop%this.lineHeight,v=Math.ceil(f/this.lineHeight)-1,m=Math.max(0,Math.round((this.scrollTop-d)/this.lineHeight)),g=m+v,y,b,w=this.lineHeight;m=e.screenToDocumentRow(m,0);var E=e.getFoldLine(m);E&&(m=E.start.row),y=e.documentToScreenRow(m,0),b=e.getRowLength(m)*w,g=Math.min(e.screenToDocumentRow(g,0),e.getLength()-1),f=t.scrollerHeight+e.getRowLength(g)*w+b,d=this.scrollTop-y*w;var S=0;if(this.layerConfig.width!=s||u)S=this.CHANGE_H_SCROLL;if(u||p)S|=this.$updateCachedSize(!0,this.gutterWidth,t.width,t.height),this._signal("scrollbarVisibilityChanged"),p&&(s=this.$getLongestLine());return this.layerConfig={width:s,padding:this.$padding,firstRow:m,firstRowScreen:y,lastRow:g,lineHeight:w,characterWidth:this.characterWidth,minHeight:f,maxHeight:i,offset:d,gutterOffset:w?Math.max(0,Math.ceil((d+t.height-t.scrollerHeight)/w)):0,height:this.$size.scrollerHeight},this.session.$bidiHandler&&this.session.$bidiHandler.setContentWidth(s-this.$padding),S},this.$updateLines=function(){if(!this.$changedLines)return;var e=this.$changedLines.firstRow,t=this.$changedLines.lastRow;this.$changedLines=null;var n=this.layerConfig;if(e>n.lastRow+1)return;if(tthis.$textLayer.MAX_LINE_LENGTH&&(e=this.$textLayer.MAX_LINE_LENGTH+30),Math.max(this.$size.scrollerWidth-2*this.$padding,Math.round(e*this.characterWidth))},this.updateFrontMarkers=function(){this.$markerFront.setMarkers(this.session.getMarkers(!0)),this.$loop.schedule(this.CHANGE_MARKER_FRONT)},this.updateBackMarkers=function(){this.$markerBack.setMarkers(this.session.getMarkers()),this.$loop.schedule(this.CHANGE_MARKER_BACK)},this.addGutterDecoration=function(e,t){this.$gutterLayer.addGutterDecoration(e,t)},this.removeGutterDecoration=function(e,t){this.$gutterLayer.removeGutterDecoration(e,t)},this.updateBreakpoints=function(e){this.$loop.schedule(this.CHANGE_GUTTER)},this.setAnnotations=function(e){this.$gutterLayer.setAnnotations(e),this.$loop.schedule(this.CHANGE_GUTTER)},this.updateCursor=function(){this.$loop.schedule(this.CHANGE_CURSOR)},this.hideCursor=function(){this.$cursorLayer.hideCursor()},this.showCursor=function(){this.$cursorLayer.showCursor()},this.scrollSelectionIntoView=function(e,t,n){this.scrollCursorIntoView(e,n),this.scrollCursorIntoView(t,n)},this.scrollCursorIntoView=function(e,t,n){if(this.$size.scrollerHeight===0)return;var r=this.$cursorLayer.getPixelPosition(e),i=r.left,s=r.top,o=n&&n.top||0,u=n&&n.bottom||0,a=this.$scrollAnimation?this.session.getScrollTop():this.scrollTop;a+o>s?(t&&a+o>s+this.lineHeight&&(s-=t*this.$size.scrollerHeight),s===0&&(s=-this.scrollMargin.top),this.session.setScrollTop(s)):a+this.$size.scrollerHeight-ui?(i=1-this.scrollMargin.top)return!0;if(t>0&&this.session.getScrollTop()+this.$size.scrollerHeight-this.layerConfig.maxHeight<-1+this.scrollMargin.bottom)return!0;if(e<0&&this.session.getScrollLeft()>=1-this.scrollMargin.left)return!0;if(e>0&&this.session.getScrollLeft()+this.$size.scrollerWidth-this.layerConfig.width<-1+this.scrollMargin.right)return!0},this.pixelToScreenCoordinates=function(e,t){var n;if(this.$hasCssTransforms){n={top:0,left:0};var r=this.$fontMetrics.transformCoordinates([e,t]);e=r[1]-this.gutterWidth-this.margin.left,t=r[0]}else n=this.scroller.getBoundingClientRect();var i=e+this.scrollLeft-n.left-this.$padding,s=i/this.characterWidth,o=Math.floor((t+this.scrollTop-n.top)/this.lineHeight),u=this.$blockCursor?Math.floor(s):Math.round(s);return{row:o,column:u,side:s-u>0?1:-1,offsetX:i}},this.screenToTextCoordinates=function(e,t){var n;if(this.$hasCssTransforms){n={top:0,left:0};var r=this.$fontMetrics.transformCoordinates([e,t]);e=r[1]-this.gutterWidth-this.margin.left,t=r[0]}else n=this.scroller.getBoundingClientRect();var i=e+this.scrollLeft-n.left-this.$padding,s=i/this.characterWidth,o=this.$blockCursor?Math.floor(s):Math.round(s),u=Math.floor((t+this.scrollTop-n.top)/this.lineHeight);return this.session.screenToDocumentPosition(u,Math.max(o,0),i)},this.textToScreenCoordinates=function(e,t){var n=this.scroller.getBoundingClientRect(),r=this.session.documentToScreenPosition(e,t),i=this.$padding+(this.session.$bidiHandler.isBidiRow(r.row,e)?this.session.$bidiHandler.getPosLeft(r.column):Math.round(r.column*this.characterWidth)),s=r.row*this.lineHeight;return{pageX:n.left+i-this.scrollLeft,pageY:n.top+s-this.scrollTop}},this.visualizeFocus=function(){i.addCssClass(this.container,"ace_focus")},this.visualizeBlur=function(){i.removeCssClass(this.container,"ace_focus")},this.showComposition=function(e){this.$composition=e,e.cssText||(e.cssText=this.textarea.style.cssText),e.useTextareaForIME=this.$useTextareaForIME,this.$useTextareaForIME?(i.addCssClass(this.textarea,"ace_composition"),this.textarea.style.cssText="",this.$moveTextAreaToCursor(),this.$cursorLayer.element.style.display="none"):e.markerId=this.session.addMarker(e.markerRange,"ace_composition_marker","text")},this.setCompositionText=function(e){var t=this.session.selection.cursor;this.addToken(e,"composition_placeholder",t.row,t.column),this.$moveTextAreaToCursor()},this.hideComposition=function(){if(!this.$composition)return;this.$composition.markerId&&this.session.removeMarker(this.$composition.markerId),i.removeCssClass(this.textarea,"ace_composition"),this.textarea.style.cssText=this.$composition.cssText,this.$composition=null,this.$cursorLayer.element.style.display=""},this.addToken=function(e,t,n,r){var i=this.session;i.bgTokenizer.lines[n]=null;var s={type:t,value:e},o=i.getTokens(n);if(r==null)o.push(s);else{var u=0;for(var a=0;a50&&e.length>this.$doc.getLength()>>1?this.call("setValue",[this.$doc.getValue()]):this.emit("change",{data:e})}}).call(f.prototype);var l=function(e,t,n){var r=null,i=!1,u=Object.create(s),a=[],l=new f({messageBuffer:a,terminate:function(){},postMessage:function(e){a.push(e);if(!r)return;i?setTimeout(c):c()}});l.setEmitSync=function(e){i=e};var c=function(){var e=a.shift();e.command?r[e.command].apply(r,e.args):e.event&&u._signal(e.event,e.data)};return u.postMessage=function(e){l.onMessage({data:e})},u.callback=function(e,t){this.postMessage({type:"call",id:t,data:e})},u.emit=function(e,t){this.postMessage({type:"event",name:e,data:t})},o.loadModule(["worker",t],function(e){r=new e[n](u);while(a.length)c()}),l};t.UIWorkerClient=l,t.WorkerClient=f,t.createWorker=a}),define("ace/placeholder",["require","exports","module","ace/range","ace/lib/event_emitter","ace/lib/oop"],function(e,t,n){"use strict";var r=e("./range").Range,i=e("./lib/event_emitter").EventEmitter,s=e("./lib/oop"),o=function(e,t,n,r,i,s){var o=this;this.length=t,this.session=e,this.doc=e.getDocument(),this.mainClass=i,this.othersClass=s,this.$onUpdate=this.onUpdate.bind(this),this.doc.on("change",this.$onUpdate),this.$others=r,this.$onCursorChange=function(){setTimeout(function(){o.onCursorChange()})},this.$pos=n;var u=e.getUndoManager().$undoStack||e.getUndoManager().$undostack||{length:-1};this.$undoStackDepth=u.length,this.setup(),e.selection.on("changeCursor",this.$onCursorChange)};(function(){s.implement(this,i),this.setup=function(){var e=this,t=this.doc,n=this.session;this.selectionBefore=n.selection.toJSON(),n.selection.inMultiSelectMode&&n.selection.toSingleRange(),this.pos=t.createAnchor(this.$pos.row,this.$pos.column);var i=this.pos;i.$insertRight=!0,i.detach(),i.markerId=n.addMarker(new r(i.row,i.column,i.row,i.column+this.length),this.mainClass,null,!1),this.others=[],this.$others.forEach(function(n){var r=t.createAnchor(n.row,n.column);r.$insertRight=!0,r.detach(),e.others.push(r)}),n.setUndoSelect(!1)},this.showOtherMarkers=function(){if(this.othersActive)return;var e=this.session,t=this;this.othersActive=!0,this.others.forEach(function(n){n.markerId=e.addMarker(new r(n.row,n.column,n.row,n.column+t.length),t.othersClass,null,!1)})},this.hideOtherMarkers=function(){if(!this.othersActive)return;this.othersActive=!1;for(var e=0;e=this.pos.column&&t.start.column<=this.pos.column+this.length+1,s=t.start.column-this.pos.column;this.updateAnchors(e),i&&(this.length+=n);if(i&&!this.session.$fromUndo)if(e.action==="insert")for(var o=this.others.length-1;o>=0;o--){var u=this.others[o],a={row:u.row,column:u.column+s};this.doc.insertMergedLines(a,e.lines)}else if(e.action==="remove")for(var o=this.others.length-1;o>=0;o--){var u=this.others[o],a={row:u.row,column:u.column+s};this.doc.remove(new r(a.row,a.column,a.row,a.column-n))}this.$updating=!1,this.updateMarkers()},this.updateAnchors=function(e){this.pos.onChange(e);for(var t=this.others.length;t--;)this.others[t].onChange(e);this.updateMarkers()},this.updateMarkers=function(){if(this.$updating)return;var e=this,t=this.session,n=function(n,i){t.removeMarker(n.markerId),n.markerId=t.addMarker(new r(n.row,n.column,n.row,n.column+e.length),i,null,!1)};n(this.pos,this.mainClass);for(var i=this.others.length;i--;)n(this.others[i],this.othersClass)},this.onCursorChange=function(e){if(this.$updating||!this.session)return;var t=this.session.selection.getCursor();t.row===this.pos.row&&t.column>=this.pos.column&&t.column<=this.pos.column+this.length?(this.showOtherMarkers(),this._emit("cursorEnter",e)):(this.hideOtherMarkers(),this._emit("cursorLeave",e))},this.detach=function(){this.session.removeMarker(this.pos&&this.pos.markerId),this.hideOtherMarkers(),this.doc.removeEventListener("change",this.$onUpdate),this.session.selection.removeEventListener("changeCursor",this.$onCursorChange),this.session.setUndoSelect(!0),this.session=null},this.cancel=function(){if(this.$undoStackDepth===-1)return;var e=this.session.getUndoManager(),t=(e.$undoStack||e.$undostack).length-this.$undoStackDepth;for(var n=0;n1?e.multiSelect.joinSelections():e.multiSelect.splitIntoLines()},bindKey:{win:"Ctrl-Alt-L",mac:"Ctrl-Alt-L"},readOnly:!0},{name:"splitSelectionIntoLines",description:"Split into lines",exec:function(e){e.multiSelect.splitIntoLines()},readOnly:!0},{name:"alignCursors",description:"Align cursors",exec:function(e){e.alignCursors()},bindKey:{win:"Ctrl-Alt-A",mac:"Ctrl-Alt-A"},scrollIntoView:"cursor"},{name:"findAll",description:"Find all",exec:function(e){e.findAll()},bindKey:{win:"Ctrl-Alt-K",mac:"Ctrl-Alt-G"},scrollIntoView:"cursor",readOnly:!0}],t.multiSelectCommands=[{name:"singleSelection",description:"Single selection",bindKey:"esc",exec:function(e){e.exitMultiSelectMode()},scrollIntoView:"cursor",readOnly:!0,isAvailable:function(e){return e&&e.inMultiSelectMode}}];var r=e("../keyboard/hash_handler").HashHandler;t.keyboardHandler=new r(t.multiSelectCommands)}),define("ace/multi_select",["require","exports","module","ace/range_list","ace/range","ace/selection","ace/mouse/multi_select_handler","ace/lib/event","ace/lib/lang","ace/commands/multi_select_commands","ace/search","ace/edit_session","ace/editor","ace/config"],function(e,t,n){function h(e,t,n){return c.$options.wrap=!0,c.$options.needle=t,c.$options.backwards=n==-1,c.find(e)}function v(e,t){return e.row==t.row&&e.column==t.column}function m(e){if(e.$multiselectOnSessionChange)return;e.$onAddRange=e.$onAddRange.bind(e),e.$onRemoveRange=e.$onRemoveRange.bind(e),e.$onMultiSelect=e.$onMultiSelect.bind(e),e.$onSingleSelect=e.$onSingleSelect.bind(e),e.$multiselectOnSessionChange=t.onSessionChange.bind(e),e.$checkMultiselectChange=e.$checkMultiselectChange.bind(e),e.$multiselectOnSessionChange(e),e.on("changeSession",e.$multiselectOnSessionChange),e.on("mousedown",o),e.commands.addCommands(f.defaultCommands),g(e)}function g(e){function r(t){n&&(e.renderer.setMouseCursor(""),n=!1)}if(!e.textInput)return;var t=e.textInput.getElement(),n=!1;u.addListener(t,"keydown",function(t){var i=t.keyCode==18&&!(t.ctrlKey||t.shiftKey||t.metaKey);e.$blockSelectEnabled&&i?n||(e.renderer.setMouseCursor("crosshair"),n=!0):n&&r()}),u.addListener(t,"keyup",r),u.addListener(t,"blur",r)}var r=e("./range_list").RangeList,i=e("./range").Range,s=e("./selection").Selection,o=e("./mouse/multi_select_handler").onMouseDown,u=e("./lib/event"),a=e("./lib/lang"),f=e("./commands/multi_select_commands");t.commands=f.defaultCommands.concat(f.multiSelectCommands);var l=e("./search").Search,c=new l,p=e("./edit_session").EditSession;(function(){this.getSelectionMarkers=function(){return this.$selectionMarkers}}).call(p.prototype),function(){this.ranges=null,this.rangeList=null,this.addRange=function(e,t){if(!e)return;if(!this.inMultiSelectMode&&this.rangeCount===0){var n=this.toOrientedRange();this.rangeList.add(n),this.rangeList.add(e);if(this.rangeList.ranges.length!=2)return this.rangeList.removeAll(),t||this.fromOrientedRange(e);this.rangeList.removeAll(),this.rangeList.add(n),this.$onAddRange(n)}e.cursor||(e.cursor=e.end);var r=this.rangeList.add(e);return this.$onAddRange(e),r.length&&this.$onRemoveRange(r),this.rangeCount>1&&!this.inMultiSelectMode&&(this._signal("multiSelect"),this.inMultiSelectMode=!0,this.session.$undoSelect=!1,this.rangeList.attach(this.session)),t||this.fromOrientedRange(e)},this.toSingleRange=function(e){e=e||this.ranges[0];var t=this.rangeList.removeAll();t.length&&this.$onRemoveRange(t),e&&this.fromOrientedRange(e)},this.substractPoint=function(e){var t=this.rangeList.substractPoint(e);if(t)return this.$onRemoveRange(t),t[0]},this.mergeOverlappingRanges=function(){var e=this.rangeList.merge();e.length&&this.$onRemoveRange(e)},this.$onAddRange=function(e){this.rangeCount=this.rangeList.ranges.length,this.ranges.unshift(e),this._signal("addRange",{range:e})},this.$onRemoveRange=function(e){this.rangeCount=this.rangeList.ranges.length;if(this.rangeCount==1&&this.inMultiSelectMode){var t=this.rangeList.ranges.pop();e.push(t),this.rangeCount=0}for(var n=e.length;n--;){var r=this.ranges.indexOf(e[n]);this.ranges.splice(r,1)}this._signal("removeRange",{ranges:e}),this.rangeCount===0&&this.inMultiSelectMode&&(this.inMultiSelectMode=!1,this._signal("singleSelect"),this.session.$undoSelect=!0,this.rangeList.detach(this.session)),t=t||this.ranges[0],t&&!t.isEqual(this.getRange())&&this.fromOrientedRange(t)},this.$initRangeList=function(){if(this.rangeList)return;this.rangeList=new r,this.ranges=[],this.rangeCount=0},this.getAllRanges=function(){return this.rangeCount?this.rangeList.ranges.concat():[this.getRange()]},this.splitIntoLines=function(){var e=this.ranges.length?this.ranges:[this.getRange()],t=[];for(var n=0;n1){var e=this.rangeList.ranges,t=e[e.length-1],n=i.fromPoints(e[0].start,t.end);this.toSingleRange(),this.setSelectionRange(n,t.cursor==t.start)}else{var r=this.session.documentToScreenPosition(this.cursor),s=this.session.documentToScreenPosition(this.anchor),o=this.rectangularRangeBlock(r,s);o.forEach(this.addRange,this)}},this.rectangularRangeBlock=function(e,t,n){var r=[],s=e.column0)g--;if(g>0){var y=0;while(r[y].isEmpty())y++}for(var b=g;b>=y;b--)r[b].isEmpty()&&r.splice(b,1)}return r}}.call(s.prototype);var d=e("./editor").Editor;(function(){this.updateSelectionMarkers=function(){this.renderer.updateCursor(),this.renderer.updateBackMarkers()},this.addSelectionMarker=function(e){e.cursor||(e.cursor=e.end);var t=this.getSelectionStyle();return e.marker=this.session.addMarker(e,"ace_selection",t),this.session.$selectionMarkers.push(e),this.session.selectionMarkerCount=this.session.$selectionMarkers.length,e},this.removeSelectionMarker=function(e){if(!e.marker)return;this.session.removeMarker(e.marker);var t=this.session.$selectionMarkers.indexOf(e);t!=-1&&this.session.$selectionMarkers.splice(t,1),this.session.selectionMarkerCount=this.session.$selectionMarkers.length},this.removeSelectionMarkers=function(e){var t=this.session.$selectionMarkers;for(var n=e.length;n--;){var r=e[n];if(!r.marker)continue;this.session.removeMarker(r.marker);var i=t.indexOf(r);i!=-1&&t.splice(i,1)}this.session.selectionMarkerCount=t.length},this.$onAddRange=function(e){this.addSelectionMarker(e.range),this.renderer.updateCursor(),this.renderer.updateBackMarkers()},this.$onRemoveRange=function(e){this.removeSelectionMarkers(e.ranges),this.renderer.updateCursor(),this.renderer.updateBackMarkers()},this.$onMultiSelect=function(e){if(this.inMultiSelectMode)return;this.inMultiSelectMode=!0,this.setStyle("ace_multiselect"),this.keyBinding.addKeyboardHandler(f.keyboardHandler),this.commands.setDefaultHandler("exec",this.$onMultiSelectExec),this.renderer.updateCursor(),this.renderer.updateBackMarkers()},this.$onSingleSelect=function(e){if(this.session.multiSelect.inVirtualMode)return;this.inMultiSelectMode=!1,this.unsetStyle("ace_multiselect"),this.keyBinding.removeKeyboardHandler(f.keyboardHandler),this.commands.removeDefaultHandler("exec",this.$onMultiSelectExec),this.renderer.updateCursor(),this.renderer.updateBackMarkers(),this._emit("changeSelection")},this.$onMultiSelectExec=function(e){var t=e.command,n=e.editor;if(!n.multiSelect)return;if(!t.multiSelectAction){var r=t.exec(n,e.args||{});n.multiSelect.addRange(n.multiSelect.toOrientedRange()),n.multiSelect.mergeOverlappingRanges()}else t.multiSelectAction=="forEach"?r=n.forEachSelection(t,e.args):t.multiSelectAction=="forEachLine"?r=n.forEachSelection(t,e.args,!0):t.multiSelectAction=="single"?(n.exitMultiSelectMode(),r=t.exec(n,e.args||{})):r=t.multiSelectAction(n,e.args||{});return r},this.forEachSelection=function(e,t,n){if(this.inVirtualSelectionMode)return;var r=n&&n.keepOrder,i=n==1||n&&n.$byLines,o=this.session,u=this.selection,a=u.rangeList,f=(r?u:a).ranges,l;if(!f.length)return e.exec?e.exec(this,t||{}):e(this,t||{});var c=u._eventRegistry;u._eventRegistry={};var h=new s(o);this.inVirtualSelectionMode=!0;for(var p=f.length;p--;){if(i)while(p>0&&f[p].start.row==f[p-1].end.row)p--;h.fromOrientedRange(f[p]),h.index=p,this.selection=o.selection=h;var d=e.exec?e.exec(this,t||{}):e(this,t||{});!l&&d!==undefined&&(l=d),h.toOrientedRange(f[p])}h.detach(),this.selection=o.selection=u,this.inVirtualSelectionMode=!1,u._eventRegistry=c,u.mergeOverlappingRanges(),u.ranges[0]&&u.fromOrientedRange(u.ranges[0]);var v=this.renderer.$scrollAnimation;return this.onCursorChange(),this.onSelectionChange(),v&&v.from==v.to&&this.renderer.animateScrolling(v.from),l},this.exitMultiSelectMode=function(){if(!this.inMultiSelectMode||this.inVirtualSelectionMode)return;this.multiSelect.toSingleRange()},this.getSelectedText=function(){var e="";if(this.inMultiSelectMode&&!this.inVirtualSelectionMode){var t=this.multiSelect.rangeList.ranges,n=[];for(var r=0;r0);u<0&&(u=0),f>=c&&(f=c-1)}var p=this.session.removeFullLines(u,f);p=this.$reAlignText(p,l),this.session.insert({row:u,column:0},p.join("\n")+"\n"),l||(o.start.column=0,o.end.column=p[p.length-1].length),this.selection.setRange(o)}else{s.forEach(function(e){t.substractPoint(e.cursor)});var d=0,v=Infinity,m=n.map(function(t){var n=t.cursor,r=e.getLine(n.row),i=r.substr(n.column).search(/\S/g);return i==-1&&(i=0),n.column>d&&(d=n.column),io?e.insert(r,a.stringRepeat(" ",s-o)):e.remove(new i(r.row,r.column,r.row,r.column-s+o)),t.start.column=t.end.column=d,t.start.row=t.end.row=r.row,t.cursor=t.end}),t.fromOrientedRange(n[0]),this.renderer.updateCursor(),this.renderer.updateBackMarkers()}},this.$reAlignText=function(e,t){function u(e){return a.stringRepeat(" ",e)}function f(e){return e[2]?u(i)+e[2]+u(s-e[2].length+o)+e[4].replace(/^([=:])\s+/,"$1 "):e[0]}function l(e){return e[2]?u(i+s-e[2].length)+e[2]+u(o)+e[4].replace(/^([=:])\s+/,"$1 "):e[0]}function c(e){return e[2]?u(i)+e[2]+u(o)+e[4].replace(/^([=:])\s+/,"$1 "):e[0]}var n=!0,r=!0,i,s,o;return e.map(function(e){var t=e.match(/(\s*)(.*?)(\s*)([=:].*)/);return t?i==null?(i=t[1].length,s=t[2].length,o=t[3].length,t):(i+s+o!=t[1].length+t[2].length+t[3].length&&(r=!1),i!=t[1].length&&(n=!1),i>t[1].length&&(i=t[1].length),st[3].length&&(o=t[3].length),t):[e]}).map(t?f:n?r?l:f:c)}}).call(d.prototype),t.onSessionChange=function(e){var t=e.session;t&&!t.multiSelect&&(t.$selectionMarkers=[],t.selection.$initRangeList(),t.multiSelect=t.selection),this.multiSelect=t&&t.multiSelect;var n=e.oldSession;n&&(n.multiSelect.off("addRange",this.$onAddRange),n.multiSelect.off("removeRange",this.$onRemoveRange),n.multiSelect.off("multiSelect",this.$onMultiSelect),n.multiSelect.off("singleSelect",this.$onSingleSelect),n.multiSelect.lead.off("change",this.$checkMultiselectChange),n.multiSelect.anchor.off("change",this.$checkMultiselectChange)),t&&(t.multiSelect.on("addRange",this.$onAddRange),t.multiSelect.on("removeRange",this.$onRemoveRange),t.multiSelect.on("multiSelect",this.$onMultiSelect),t.multiSelect.on("singleSelect",this.$onSingleSelect),t.multiSelect.lead.on("change",this.$checkMultiselectChange),t.multiSelect.anchor.on("change",this.$checkMultiselectChange)),t&&this.inMultiSelectMode!=t.selection.inMultiSelectMode&&(t.selection.inMultiSelectMode?this.$onMultiSelect():this.$onSingleSelect())},t.MultiSelect=m,e("./config").defineOptions(d.prototype,"editor",{enableMultiselect:{set:function(e){m(this),e?(this.on("changeSession",this.$multiselectOnSessionChange),this.on("mousedown",o)):(this.off("changeSession",this.$multiselectOnSessionChange),this.off("mousedown",o))},value:!0},enableBlockSelect:{set:function(e){this.$blockSelectEnabled=e},value:!0}})}),define("ace/mode/folding/fold_mode",["require","exports","module","ace/range"],function(e,t,n){"use strict";var r=e("../../range").Range,i=t.FoldMode=function(){};(function(){this.foldingStartMarker=null,this.foldingStopMarker=null,this.getFoldWidget=function(e,t,n){var r=e.getLine(n);return this.foldingStartMarker.test(r)?"start":t=="markbeginend"&&this.foldingStopMarker&&this.foldingStopMarker.test(r)?"end":""},this.getFoldWidgetRange=function(e,t,n){return null},this.indentationBlock=function(e,t,n){var i=/\S/,s=e.getLine(t),o=s.search(i);if(o==-1)return;var u=n||s.length,a=e.getLength(),f=t,l=t;while(++tf){var p=e.getLine(l).length;return new r(f,u,l,p)}},this.openingBracketBlock=function(e,t,n,i,s){var o={row:n,column:i+1},u=e.$findClosingBracket(t,o,s);if(!u)return;var a=e.foldWidgets[u.row];return a==null&&(a=e.getFoldWidget(u.row)),a=="start"&&u.row>o.row&&(u.row--,u.column=e.getLine(u.row).length),r.fromPoints(o,u)},this.closingBracketBlock=function(e,t,n,i,s){var o={row:n,column:i},u=e.$findOpeningBracket(t,o);if(!u)return;return u.column++,o.column--,r.fromPoints(u,o)}}).call(i.prototype)}),define("ace/theme/textmate",["require","exports","module","ace/lib/dom"],function(e,t,n){"use strict";t.isDark=!1,t.cssClass="ace-tm",t.cssText='.ace-tm .ace_gutter {background: #f0f0f0;color: #333;}.ace-tm .ace_print-margin {width: 1px;background: #e8e8e8;}.ace-tm .ace_fold {background-color: #6B72E6;}.ace-tm {background-color: #FFFFFF;color: black;}.ace-tm .ace_cursor {color: black;}.ace-tm .ace_invisible {color: rgb(191, 191, 191);}.ace-tm .ace_storage,.ace-tm .ace_keyword {color: blue;}.ace-tm .ace_constant {color: rgb(197, 6, 11);}.ace-tm .ace_constant.ace_buildin {color: rgb(88, 72, 246);}.ace-tm .ace_constant.ace_language {color: rgb(88, 92, 246);}.ace-tm .ace_constant.ace_library {color: rgb(6, 150, 14);}.ace-tm .ace_invalid {background-color: rgba(255, 0, 0, 0.1);color: red;}.ace-tm .ace_support.ace_function {color: rgb(60, 76, 114);}.ace-tm .ace_support.ace_constant {color: rgb(6, 150, 14);}.ace-tm .ace_support.ace_type,.ace-tm .ace_support.ace_class {color: rgb(109, 121, 222);}.ace-tm .ace_keyword.ace_operator {color: rgb(104, 118, 135);}.ace-tm .ace_string {color: rgb(3, 106, 7);}.ace-tm .ace_comment {color: rgb(76, 136, 107);}.ace-tm .ace_comment.ace_doc {color: rgb(0, 102, 255);}.ace-tm .ace_comment.ace_doc.ace_tag {color: rgb(128, 159, 191);}.ace-tm .ace_constant.ace_numeric {color: rgb(0, 0, 205);}.ace-tm .ace_variable {color: rgb(49, 132, 149);}.ace-tm .ace_xml-pe {color: rgb(104, 104, 91);}.ace-tm .ace_entity.ace_name.ace_function {color: #0000A2;}.ace-tm .ace_heading {color: rgb(12, 7, 255);}.ace-tm .ace_list {color:rgb(185, 6, 144);}.ace-tm .ace_meta.ace_tag {color:rgb(0, 22, 142);}.ace-tm .ace_string.ace_regex {color: rgb(255, 0, 0)}.ace-tm .ace_marker-layer .ace_selection {background: rgb(181, 213, 255);}.ace-tm.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px white;}.ace-tm .ace_marker-layer .ace_step {background: rgb(252, 255, 0);}.ace-tm .ace_marker-layer .ace_stack {background: rgb(164, 229, 101);}.ace-tm .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid rgb(192, 192, 192);}.ace-tm .ace_marker-layer .ace_active-line {background: rgba(0, 0, 0, 0.07);}.ace-tm .ace_gutter-active-line {background-color : #dcdcdc;}.ace-tm .ace_marker-layer .ace_selected-word {background: rgb(250, 250, 255);border: 1px solid rgb(200, 200, 250);}.ace-tm .ace_indent-guide {background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==") right repeat-y;}',t.$id="ace/theme/textmate";var r=e("../lib/dom");r.importCssString(t.cssText,t.cssClass)}),define("ace/line_widgets",["require","exports","module","ace/lib/dom"],function(e,t,n){"use strict";function i(e){this.session=e,this.session.widgetManager=this,this.session.getRowLength=this.getRowLength,this.session.$getWidgetScreenLength=this.$getWidgetScreenLength,this.updateOnChange=this.updateOnChange.bind(this),this.renderWidgets=this.renderWidgets.bind(this),this.measureWidgets=this.measureWidgets.bind(this),this.session._changedWidgets=[],this.$onChangeEditor=this.$onChangeEditor.bind(this),this.session.on("change",this.updateOnChange),this.session.on("changeFold",this.updateOnFold),this.session.on("changeEditor",this.$onChangeEditor)}var r=e("./lib/dom");(function(){this.getRowLength=function(e){var t;return this.lineWidgets?t=this.lineWidgets[e]&&this.lineWidgets[e].rowCount||0:t=0,!this.$useWrapMode||!this.$wrapData[e]?1+t:this.$wrapData[e].length+1+t},this.$getWidgetScreenLength=function(){var e=0;return this.lineWidgets.forEach(function(t){t&&t.rowCount&&!t.hidden&&(e+=t.rowCount)}),e},this.$onChangeEditor=function(e){this.attach(e.editor)},this.attach=function(e){e&&e.widgetManager&&e.widgetManager!=this&&e.widgetManager.detach();if(this.editor==e)return;this.detach(),this.editor=e,e&&(e.widgetManager=this,e.renderer.on("beforeRender",this.measureWidgets),e.renderer.on("afterRender",this.renderWidgets))},this.detach=function(e){var t=this.editor;if(!t)return;this.editor=null,t.widgetManager=null,t.renderer.off("beforeRender",this.measureWidgets),t.renderer.off("afterRender",this.renderWidgets);var n=this.session.lineWidgets;n&&n.forEach(function(e){e&&e.el&&e.el.parentNode&&(e._inDocument=!1,e.el.parentNode.removeChild(e.el))})},this.updateOnFold=function(e,t){var n=t.lineWidgets;if(!n||!e.action)return;var r=e.data,i=r.start.row,s=r.end.row,o=e.action=="add";for(var u=i+1;ut[n].column&&n++,s.unshift(n,0),t.splice.apply(t,s),this.$updateRows()}},this.$updateRows=function(){var e=this.session.lineWidgets;if(!e)return;var t=!0;e.forEach(function(e,n){if(e){t=!1,e.row=n;while(e.$oldWidget)e.$oldWidget.row=n,e=e.$oldWidget}}),t&&(this.session.lineWidgets=null)},this.$registerLineWidget=function(e){this.session.lineWidgets||(this.session.lineWidgets=new Array(this.session.getLength()));var t=this.session.lineWidgets[e.row];return t&&(e.$oldWidget=t,t.el&&t.el.parentNode&&(t.el.parentNode.removeChild(t.el),t._inDocument=!1)),this.session.lineWidgets[e.row]=e,e},this.addLineWidget=function(e){this.$registerLineWidget(e),e.session=this.session;if(!this.editor)return e;var t=this.editor.renderer;e.html&&!e.el&&(e.el=r.createElement("div"),e.el.innerHTML=e.html),e.el&&(r.addCssClass(e.el,"ace_lineWidgetContainer"),e.el.style.position="absolute",e.el.style.zIndex=5,t.container.appendChild(e.el),e._inDocument=!0,e.coverGutter||(e.el.style.zIndex=3),e.pixelHeight==null&&(e.pixelHeight=e.el.offsetHeight)),e.rowCount==null&&(e.rowCount=e.pixelHeight/t.layerConfig.lineHeight);var n=this.session.getFoldAt(e.row,0);e.$fold=n;if(n){var i=this.session.lineWidgets;e.row==n.end.row&&!i[n.start.row]?i[n.start.row]=e:e.hidden=!0}return this.session._emit("changeFold",{data:{start:{row:e.row}}}),this.$updateRows(),this.renderWidgets(null,t),this.onWidgetChanged(e),e},this.removeLineWidget=function(e){e._inDocument=!1,e.session=null,e.el&&e.el.parentNode&&e.el.parentNode.removeChild(e.el);if(e.editor&&e.editor.destroy)try{e.editor.destroy()}catch(t){}if(this.session.lineWidgets){var n=this.session.lineWidgets[e.row];if(n==e)this.session.lineWidgets[e.row]=e.$oldWidget,e.$oldWidget&&this.onWidgetChanged(e.$oldWidget);else while(n){if(n.$oldWidget==e){n.$oldWidget=e.$oldWidget;break}n=n.$oldWidget}}this.session._emit("changeFold",{data:{start:{row:e.row}}}),this.$updateRows()},this.getWidgetsAtRow=function(e){var t=this.session.lineWidgets,n=t&&t[e],r=[];while(n)r.push(n),n=n.$oldWidget;return r},this.onWidgetChanged=function(e){this.session._changedWidgets.push(e),this.editor&&this.editor.renderer.updateFull()},this.measureWidgets=function(e,t){var n=this.session._changedWidgets,r=t.layerConfig;if(!n||!n.length)return;var i=Infinity;for(var s=0;s0&&!r[i])i--;this.firstRow=n.firstRow,this.lastRow=n.lastRow,t.$cursorLayer.config=n;for(var o=i;o<=s;o++){var u=r[o];if(!u||!u.el)continue;if(u.hidden){u.el.style.top=-100-(u.pixelHeight||0)+"px";continue}u._inDocument||(u._inDocument=!0,t.container.appendChild(u.el));var a=t.$cursorLayer.getPixelPosition({row:o,column:0},!0).top;u.coverLine||(a+=n.lineHeight*this.session.getRowLineCount(u.row)),u.el.style.top=a-n.offset+"px";var f=u.coverGutter?0:t.gutterWidth;u.fixedWidth||(f-=t.scrollLeft),u.el.style.left=f+"px",u.fullWidth&&u.screenWidth&&(u.el.style.minWidth=n.width+2*n.padding+"px"),u.fixedWidth?u.el.style.right=t.scrollBar.getWidth()+"px":u.el.style.right=""}}}).call(i.prototype),t.LineWidgets=i}),define("ace/ext/error_marker",["require","exports","module","ace/line_widgets","ace/lib/dom","ace/range"],function(e,t,n){"use strict";function o(e,t,n){var r=0,i=e.length-1;while(r<=i){var s=r+i>>1,o=n(t,e[s]);if(o>0)r=s+1;else{if(!(o<0))return s;i=s-1}}return-(r+1)}function u(e,t,n){var r=e.getAnnotations().sort(s.comparePoints);if(!r.length)return;var i=o(r,{row:t,column:-1},s.comparePoints);i<0&&(i=-i-1),i>=r.length?i=n>0?0:r.length-1:i===0&&n<0&&(i=r.length-1);var u=r[i];if(!u||!n)return;if(u.row===t){do u=r[i+=n];while(u&&u.row===t);if(!u)return r.slice()}var a=[];t=u.row;do a[n<0?"unshift":"push"](u),u=r[i+=n];while(u&&u.row==t);return a.length&&a}var r=e("../line_widgets").LineWidgets,i=e("../lib/dom"),s=e("../range").Range;t.showErrorMarker=function(e,t){var n=e.session;n.widgetManager||(n.widgetManager=new r(n),n.widgetManager.attach(e));var s=e.getCursorPosition(),o=s.row,a=n.widgetManager.getWidgetsAtRow(o).filter(function(e){return e.type=="errorMarker"})[0];a?a.destroy():o-=t;var f=u(n,o,t),l;if(f){var c=f[0];s.column=(c.pos&&typeof c.column!="number"?c.pos.sc:c.column)||0,s.row=c.row,l=e.renderer.$gutterLayer.$annotations[s.row]}else{if(a)return;l={text:["Looks good!"],className:"ace_ok"}}e.session.unfold(s.row),e.selection.moveToPosition(s);var h={row:s.row,fixedWidth:!0,coverGutter:!0,el:i.createElement("div"),type:"errorMarker"},p=h.el.appendChild(i.createElement("div")),d=h.el.appendChild(i.createElement("div"));d.className="error_widget_arrow "+l.className;var v=e.renderer.$cursorLayer.getPixelPosition(s).left;d.style.left=v+e.renderer.gutterWidth-5+"px",h.el.className="error_widget_wrapper",p.className="error_widget "+l.className,p.innerHTML=l.text.join("
"),p.appendChild(i.createElement("div"));var m=function(e,t,n){if(t===0&&(n==="esc"||n==="return"))return h.destroy(),{command:"null"}};h.destroy=function(){if(e.$mouseHandler.isMousePressed)return;e.keyBinding.removeKeyboardHandler(m),n.widgetManager.removeLineWidget(h),e.off("changeSelection",h.destroy),e.off("changeSession",h.destroy),e.off("mouseup",h.destroy),e.off("change",h.destroy)},e.keyBinding.addKeyboardHandler(m),e.on("changeSelection",h.destroy),e.on("changeSession",h.destroy),e.on("mouseup",h.destroy),e.on("change",h.destroy),e.session.widgetManager.addLineWidget(h),h.el.onmousedown=e.focus.bind(e),e.renderer.scrollCursorIntoView(null,.5,{bottom:h.el.offsetHeight})},i.importCssString(" .error_widget_wrapper { background: inherit; color: inherit; border:none } .error_widget { border-top: solid 2px; border-bottom: solid 2px; margin: 5px 0; padding: 10px 40px; white-space: pre-wrap; } .error_widget.ace_error, .error_widget_arrow.ace_error{ border-color: #ff5a5a } .error_widget.ace_warning, .error_widget_arrow.ace_warning{ border-color: #F1D817 } .error_widget.ace_info, .error_widget_arrow.ace_info{ border-color: #5a5a5a } .error_widget.ace_ok, .error_widget_arrow.ace_ok{ border-color: #5aaa5a } .error_widget_arrow { position: absolute; border: solid 5px; border-top-color: transparent!important; border-right-color: transparent!important; border-left-color: transparent!important; top: -5px; }","")}),define("ace/ace",["require","exports","module","ace/lib/fixoldbrowsers","ace/lib/dom","ace/lib/event","ace/range","ace/editor","ace/edit_session","ace/undomanager","ace/virtual_renderer","ace/worker/worker_client","ace/keyboard/hash_handler","ace/placeholder","ace/multi_select","ace/mode/folding/fold_mode","ace/theme/textmate","ace/ext/error_marker","ace/config"],function(e,t,n){"use strict";e("./lib/fixoldbrowsers");var r=e("./lib/dom"),i=e("./lib/event"),s=e("./range").Range,o=e("./editor").Editor,u=e("./edit_session").EditSession,a=e("./undomanager").UndoManager,f=e("./virtual_renderer").VirtualRenderer;e("./worker/worker_client"),e("./keyboard/hash_handler"),e("./placeholder"),e("./multi_select"),e("./mode/folding/fold_mode"),e("./theme/textmate"),e("./ext/error_marker"),t.config=e("./config"),t.require=e,typeof define=="function"&&(t.define=define),t.edit=function(e,n){if(typeof e=="string"){var s=e;e=document.getElementById(s);if(!e)throw new Error("ace.edit can't find div #"+s)}if(e&&e.env&&e.env.editor instanceof o)return e.env.editor;var u="";if(e&&/input|textarea/i.test(e.tagName)){var a=e;u=a.value,e=r.createElement("pre"),a.parentNode.replaceChild(e,a)}else e&&(u=e.textContent,e.innerHTML="");var l=t.createEditSession(u),c=new o(new f(e),l,n),h={document:l,editor:c,onResize:c.resize.bind(c,null)};return a&&(h.textarea=a),i.addListener(window,"resize",h.onResize),c.on("destroy",function(){i.removeListener(window,"resize",h.onResize),h.editor.container.env=null}),c.container.env=c.env=h,c},t.createEditSession=function(e,t){var n=new u(e,t);return n.setUndoManager(new a),n},t.Range=s,t.Editor=o,t.EditSession=u,t.UndoManager=a,t.VirtualRenderer=f,t.version=t.config.version}); (function() { + window.require(["ace/ace"], function(a) { + if (a) { + a.config.init(true); + a.define = window.define; + } + if (!window.ace) + window.ace = a; + for (var key in a) if (a.hasOwnProperty(key)) + window.ace[key] = a[key]; + window.ace["default"] = window.ace; + if (typeof module == "object" && typeof exports == "object" && module) { + module.exports = window.ace; + } + }); + })(); \ No newline at end of file diff --git a/app/assets/frontends/beaker-code-snippet/ace/mode-css.js b/app/assets/frontends/beaker-code-snippet/ace/mode-css.js new file mode 100644 index 0000000000..f312250a59 --- /dev/null +++ b/app/assets/frontends/beaker-code-snippet/ace/mode-css.js @@ -0,0 +1,7 @@ +define("ace/mode/css_highlight_rules",["require","exports","module","ace/lib/oop","ace/lib/lang","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("../lib/lang"),s=e("./text_highlight_rules").TextHighlightRules,o=t.supportType="align-content|align-items|align-self|all|animation|animation-delay|animation-direction|animation-duration|animation-fill-mode|animation-iteration-count|animation-name|animation-play-state|animation-timing-function|backface-visibility|background|background-attachment|background-blend-mode|background-clip|background-color|background-image|background-origin|background-position|background-repeat|background-size|border|border-bottom|border-bottom-color|border-bottom-left-radius|border-bottom-right-radius|border-bottom-style|border-bottom-width|border-collapse|border-color|border-image|border-image-outset|border-image-repeat|border-image-slice|border-image-source|border-image-width|border-left|border-left-color|border-left-style|border-left-width|border-radius|border-right|border-right-color|border-right-style|border-right-width|border-spacing|border-style|border-top|border-top-color|border-top-left-radius|border-top-right-radius|border-top-style|border-top-width|border-width|bottom|box-shadow|box-sizing|caption-side|clear|clip|color|column-count|column-fill|column-gap|column-rule|column-rule-color|column-rule-style|column-rule-width|column-span|column-width|columns|content|counter-increment|counter-reset|cursor|direction|display|empty-cells|filter|flex|flex-basis|flex-direction|flex-flow|flex-grow|flex-shrink|flex-wrap|float|font|font-family|font-size|font-size-adjust|font-stretch|font-style|font-variant|font-weight|hanging-punctuation|height|justify-content|left|letter-spacing|line-height|list-style|list-style-image|list-style-position|list-style-type|margin|margin-bottom|margin-left|margin-right|margin-top|max-height|max-width|max-zoom|min-height|min-width|min-zoom|nav-down|nav-index|nav-left|nav-right|nav-up|opacity|order|outline|outline-color|outline-offset|outline-style|outline-width|overflow|overflow-x|overflow-y|padding|padding-bottom|padding-left|padding-right|padding-top|page-break-after|page-break-before|page-break-inside|perspective|perspective-origin|position|quotes|resize|right|tab-size|table-layout|text-align|text-align-last|text-decoration|text-decoration-color|text-decoration-line|text-decoration-style|text-indent|text-justify|text-overflow|text-shadow|text-transform|top|transform|transform-origin|transform-style|transition|transition-delay|transition-duration|transition-property|transition-timing-function|unicode-bidi|user-select|user-zoom|vertical-align|visibility|white-space|width|word-break|word-spacing|word-wrap|z-index",u=t.supportFunction="rgb|rgba|url|attr|counter|counters",a=t.supportConstant="absolute|after-edge|after|all-scroll|all|alphabetic|always|antialiased|armenian|auto|avoid-column|avoid-page|avoid|balance|baseline|before-edge|before|below|bidi-override|block-line-height|block|bold|bolder|border-box|both|bottom|box|break-all|break-word|capitalize|caps-height|caption|center|central|char|circle|cjk-ideographic|clone|close-quote|col-resize|collapse|column|consider-shifts|contain|content-box|cover|crosshair|cubic-bezier|dashed|decimal-leading-zero|decimal|default|disabled|disc|disregard-shifts|distribute-all-lines|distribute-letter|distribute-space|distribute|dotted|double|e-resize|ease-in|ease-in-out|ease-out|ease|ellipsis|end|exclude-ruby|fill|fixed|georgian|glyphs|grid-height|groove|hand|hanging|hebrew|help|hidden|hiragana-iroha|hiragana|horizontal|icon|ideograph-alpha|ideograph-numeric|ideograph-parenthesis|ideograph-space|ideographic|inactive|include-ruby|inherit|initial|inline-block|inline-box|inline-line-height|inline-table|inline|inset|inside|inter-ideograph|inter-word|invert|italic|justify|katakana-iroha|katakana|keep-all|last|left|lighter|line-edge|line-through|line|linear|list-item|local|loose|lower-alpha|lower-greek|lower-latin|lower-roman|lowercase|lr-tb|ltr|mathematical|max-height|max-size|medium|menu|message-box|middle|move|n-resize|ne-resize|newspaper|no-change|no-close-quote|no-drop|no-open-quote|no-repeat|none|normal|not-allowed|nowrap|nw-resize|oblique|open-quote|outset|outside|overline|padding-box|page|pointer|pre-line|pre-wrap|pre|preserve-3d|progress|relative|repeat-x|repeat-y|repeat|replaced|reset-size|ridge|right|round|row-resize|rtl|s-resize|scroll|se-resize|separate|slice|small-caps|small-caption|solid|space|square|start|static|status-bar|step-end|step-start|steps|stretch|strict|sub|super|sw-resize|table-caption|table-cell|table-column-group|table-column|table-footer-group|table-header-group|table-row-group|table-row|table|tb-rl|text-after-edge|text-before-edge|text-bottom|text-size|text-top|text|thick|thin|transparent|underline|upper-alpha|upper-latin|upper-roman|uppercase|use-script|vertical-ideographic|vertical-text|visible|w-resize|wait|whitespace|z-index|zero|zoom",f=t.supportConstantColor="aliceblue|antiquewhite|aqua|aquamarine|azure|beige|bisque|black|blanchedalmond|blue|blueviolet|brown|burlywood|cadetblue|chartreuse|chocolate|coral|cornflowerblue|cornsilk|crimson|cyan|darkblue|darkcyan|darkgoldenrod|darkgray|darkgreen|darkgrey|darkkhaki|darkmagenta|darkolivegreen|darkorange|darkorchid|darkred|darksalmon|darkseagreen|darkslateblue|darkslategray|darkslategrey|darkturquoise|darkviolet|deeppink|deepskyblue|dimgray|dimgrey|dodgerblue|firebrick|floralwhite|forestgreen|fuchsia|gainsboro|ghostwhite|gold|goldenrod|gray|green|greenyellow|grey|honeydew|hotpink|indianred|indigo|ivory|khaki|lavender|lavenderblush|lawngreen|lemonchiffon|lightblue|lightcoral|lightcyan|lightgoldenrodyellow|lightgray|lightgreen|lightgrey|lightpink|lightsalmon|lightseagreen|lightskyblue|lightslategray|lightslategrey|lightsteelblue|lightyellow|lime|limegreen|linen|magenta|maroon|mediumaquamarine|mediumblue|mediumorchid|mediumpurple|mediumseagreen|mediumslateblue|mediumspringgreen|mediumturquoise|mediumvioletred|midnightblue|mintcream|mistyrose|moccasin|navajowhite|navy|oldlace|olive|olivedrab|orange|orangered|orchid|palegoldenrod|palegreen|paleturquoise|palevioletred|papayawhip|peachpuff|peru|pink|plum|powderblue|purple|rebeccapurple|red|rosybrown|royalblue|saddlebrown|salmon|sandybrown|seagreen|seashell|sienna|silver|skyblue|slateblue|slategray|slategrey|snow|springgreen|steelblue|tan|teal|thistle|tomato|turquoise|violet|wheat|white|whitesmoke|yellow|yellowgreen",l=t.supportConstantFonts="arial|century|comic|courier|cursive|fantasy|garamond|georgia|helvetica|impact|lucida|symbol|system|tahoma|times|trebuchet|utopia|verdana|webdings|sans-serif|serif|monospace",c=t.numRe="\\-?(?:(?:[0-9]+(?:\\.[0-9]+)?)|(?:\\.[0-9]+))",h=t.pseudoElements="(\\:+)\\b(after|before|first-letter|first-line|moz-selection|selection)\\b",p=t.pseudoClasses="(:)\\b(active|checked|disabled|empty|enabled|first-child|first-of-type|focus|hover|indeterminate|invalid|last-child|last-of-type|link|not|nth-child|nth-last-child|nth-last-of-type|nth-of-type|only-child|only-of-type|required|root|target|valid|visited)\\b",d=function(){var e=this.createKeywordMapper({"support.function":u,"support.constant":a,"support.type":o,"support.constant.color":f,"support.constant.fonts":l},"text",!0);this.$rules={start:[{include:["strings","url","comments"]},{token:"paren.lparen",regex:"\\{",next:"ruleset"},{token:"paren.rparen",regex:"\\}"},{token:"string",regex:"@(?!viewport)",next:"media"},{token:"keyword",regex:"#[a-z0-9-_]+"},{token:"keyword",regex:"%"},{token:"variable",regex:"\\.[a-z0-9-_]+"},{token:"string",regex:":[a-z0-9-_]+"},{token:"constant.numeric",regex:c},{token:"constant",regex:"[a-z0-9-_]+"},{caseInsensitive:!0}],media:[{include:["strings","url","comments"]},{token:"paren.lparen",regex:"\\{",next:"start"},{token:"paren.rparen",regex:"\\}",next:"start"},{token:"string",regex:";",next:"start"},{token:"keyword",regex:"(?:media|supports|document|charset|import|namespace|media|supports|document|page|font|keyframes|viewport|counter-style|font-feature-values|swash|ornaments|annotation|stylistic|styleset|character-variant)"}],comments:[{token:"comment",regex:"\\/\\*",push:[{token:"comment",regex:"\\*\\/",next:"pop"},{defaultToken:"comment"}]}],ruleset:[{regex:"-(webkit|ms|moz|o)-",token:"text"},{token:"punctuation.operator",regex:"[:;]"},{token:"paren.rparen",regex:"\\}",next:"start"},{include:["strings","url","comments"]},{token:["constant.numeric","keyword"],regex:"("+c+")(ch|cm|deg|em|ex|fr|gd|grad|Hz|in|kHz|mm|ms|pc|pt|px|rad|rem|s|turn|vh|vmax|vmin|vm|vw|%)"},{token:"constant.numeric",regex:c},{token:"constant.numeric",regex:"#[a-f0-9]{6}"},{token:"constant.numeric",regex:"#[a-f0-9]{3}"},{token:["punctuation","entity.other.attribute-name.pseudo-element.css"],regex:h},{token:["punctuation","entity.other.attribute-name.pseudo-class.css"],regex:p},{include:"url"},{token:e,regex:"\\-?[a-zA-Z_][a-zA-Z0-9_\\-]*"},{caseInsensitive:!0}],url:[{token:"support.function",regex:"(?:url(:?-prefix)?|domain|regexp)\\(",push:[{token:"support.function",regex:"\\)",next:"pop"},{defaultToken:"string"}]}],strings:[{token:"string.start",regex:"'",push:[{token:"string.end",regex:"'|$",next:"pop"},{include:"escapes"},{token:"constant.language.escape",regex:/\\$/,consumeLineEnd:!0},{defaultToken:"string"}]},{token:"string.start",regex:'"',push:[{token:"string.end",regex:'"|$',next:"pop"},{include:"escapes"},{token:"constant.language.escape",regex:/\\$/,consumeLineEnd:!0},{defaultToken:"string"}]}],escapes:[{token:"constant.language.escape",regex:/\\([a-fA-F\d]{1,6}|[^a-fA-F\d])/}]},this.normalizeRules()};r.inherits(d,s),t.CssHighlightRules=d}),define("ace/mode/matching_brace_outdent",["require","exports","module","ace/range"],function(e,t,n){"use strict";var r=e("../range").Range,i=function(){};(function(){this.checkOutdent=function(e,t){return/^\s+$/.test(e)?/^\s*\}/.test(t):!1},this.autoOutdent=function(e,t){var n=e.getLine(t),i=n.match(/^(\s*\})/);if(!i)return 0;var s=i[1].length,o=e.findMatchingBracket({row:t,column:s});if(!o||o.row==t)return 0;var u=this.$getIndent(e.getLine(o.row));e.replace(new r(t,0,t,s-1),u)},this.$getIndent=function(e){return e.match(/^\s*/)[0]}}).call(i.prototype),t.MatchingBraceOutdent=i}),define("ace/mode/css_completions",["require","exports","module"],function(e,t,n){"use strict";var r={background:{"#$0":1},"background-color":{"#$0":1,transparent:1,fixed:1},"background-image":{"url('/$0')":1},"background-repeat":{repeat:1,"repeat-x":1,"repeat-y":1,"no-repeat":1,inherit:1},"background-position":{bottom:2,center:2,left:2,right:2,top:2,inherit:2},"background-attachment":{scroll:1,fixed:1},"background-size":{cover:1,contain:1},"background-clip":{"border-box":1,"padding-box":1,"content-box":1},"background-origin":{"border-box":1,"padding-box":1,"content-box":1},border:{"solid $0":1,"dashed $0":1,"dotted $0":1,"#$0":1},"border-color":{"#$0":1},"border-style":{solid:2,dashed:2,dotted:2,"double":2,groove:2,hidden:2,inherit:2,inset:2,none:2,outset:2,ridged:2},"border-collapse":{collapse:1,separate:1},bottom:{px:1,em:1,"%":1},clear:{left:1,right:1,both:1,none:1},color:{"#$0":1,"rgb(#$00,0,0)":1},cursor:{"default":1,pointer:1,move:1,text:1,wait:1,help:1,progress:1,"n-resize":1,"ne-resize":1,"e-resize":1,"se-resize":1,"s-resize":1,"sw-resize":1,"w-resize":1,"nw-resize":1},display:{none:1,block:1,inline:1,"inline-block":1,"table-cell":1},"empty-cells":{show:1,hide:1},"float":{left:1,right:1,none:1},"font-family":{Arial:2,"Comic Sans MS":2,Consolas:2,"Courier New":2,Courier:2,Georgia:2,Monospace:2,"Sans-Serif":2,"Segoe UI":2,Tahoma:2,"Times New Roman":2,"Trebuchet MS":2,Verdana:1},"font-size":{px:1,em:1,"%":1},"font-weight":{bold:1,normal:1},"font-style":{italic:1,normal:1},"font-variant":{normal:1,"small-caps":1},height:{px:1,em:1,"%":1},left:{px:1,em:1,"%":1},"letter-spacing":{normal:1},"line-height":{normal:1},"list-style-type":{none:1,disc:1,circle:1,square:1,decimal:1,"decimal-leading-zero":1,"lower-roman":1,"upper-roman":1,"lower-greek":1,"lower-latin":1,"upper-latin":1,georgian:1,"lower-alpha":1,"upper-alpha":1},margin:{px:1,em:1,"%":1},"margin-right":{px:1,em:1,"%":1},"margin-left":{px:1,em:1,"%":1},"margin-top":{px:1,em:1,"%":1},"margin-bottom":{px:1,em:1,"%":1},"max-height":{px:1,em:1,"%":1},"max-width":{px:1,em:1,"%":1},"min-height":{px:1,em:1,"%":1},"min-width":{px:1,em:1,"%":1},overflow:{hidden:1,visible:1,auto:1,scroll:1},"overflow-x":{hidden:1,visible:1,auto:1,scroll:1},"overflow-y":{hidden:1,visible:1,auto:1,scroll:1},padding:{px:1,em:1,"%":1},"padding-top":{px:1,em:1,"%":1},"padding-right":{px:1,em:1,"%":1},"padding-bottom":{px:1,em:1,"%":1},"padding-left":{px:1,em:1,"%":1},"page-break-after":{auto:1,always:1,avoid:1,left:1,right:1},"page-break-before":{auto:1,always:1,avoid:1,left:1,right:1},position:{absolute:1,relative:1,fixed:1,"static":1},right:{px:1,em:1,"%":1},"table-layout":{fixed:1,auto:1},"text-decoration":{none:1,underline:1,"line-through":1,blink:1},"text-align":{left:1,right:1,center:1,justify:1},"text-transform":{capitalize:1,uppercase:1,lowercase:1,none:1},top:{px:1,em:1,"%":1},"vertical-align":{top:1,bottom:1},visibility:{hidden:1,visible:1},"white-space":{nowrap:1,normal:1,pre:1,"pre-line":1,"pre-wrap":1},width:{px:1,em:1,"%":1},"word-spacing":{normal:1},filter:{"alpha(opacity=$0100)":1},"text-shadow":{"$02px 2px 2px #777":1},"text-overflow":{"ellipsis-word":1,clip:1,ellipsis:1},"-moz-border-radius":1,"-moz-border-radius-topright":1,"-moz-border-radius-bottomright":1,"-moz-border-radius-topleft":1,"-moz-border-radius-bottomleft":1,"-webkit-border-radius":1,"-webkit-border-top-right-radius":1,"-webkit-border-top-left-radius":1,"-webkit-border-bottom-right-radius":1,"-webkit-border-bottom-left-radius":1,"-moz-box-shadow":1,"-webkit-box-shadow":1,transform:{"rotate($00deg)":1,"skew($00deg)":1},"-moz-transform":{"rotate($00deg)":1,"skew($00deg)":1},"-webkit-transform":{"rotate($00deg)":1,"skew($00deg)":1}},i=function(){};(function(){this.completionsDefined=!1,this.defineCompletions=function(){if(document){var e=document.createElement("c").style;for(var t in e){if(typeof e[t]!="string")continue;var n=t.replace(/[A-Z]/g,function(e){return"-"+e.toLowerCase()});r.hasOwnProperty(n)||(r[n]=1)}}this.completionsDefined=!0},this.getCompletions=function(e,t,n,r){this.completionsDefined||this.defineCompletions();if(e==="ruleset"||t.$mode.$id=="ace/mode/scss"){var i=t.getLine(n.row).substr(0,n.column);return/:[^;]+$/.test(i)?(/([\w\-]+):[^:]*$/.test(i),this.getPropertyValueCompletions(e,t,n,r)):this.getPropertyCompletions(e,t,n,r)}return[]},this.getPropertyCompletions=function(e,t,n,i){var s=Object.keys(r);return s.map(function(e){return{caption:e,snippet:e+": $0;",meta:"property",score:1e6}})},this.getPropertyValueCompletions=function(e,t,n,i){var s=t.getLine(n.row).substr(0,n.column),o=(/([\w\-]+):[^:]*$/.exec(s)||{})[1];if(!o)return[];var u=[];return o in r&&typeof r[o]=="object"&&(u=Object.keys(r[o])),u.map(function(e){return{caption:e,snippet:e,meta:"property value",score:1e6}})}}).call(i.prototype),t.CssCompletions=i}),define("ace/mode/behaviour/css",["require","exports","module","ace/lib/oop","ace/mode/behaviour","ace/mode/behaviour/cstyle","ace/token_iterator"],function(e,t,n){"use strict";var r=e("../../lib/oop"),i=e("../behaviour").Behaviour,s=e("./cstyle").CstyleBehaviour,o=e("../../token_iterator").TokenIterator,u=function(){this.inherit(s),this.add("colon","insertion",function(e,t,n,r,i){if(i===":"&&n.selection.isEmpty()){var s=n.getCursorPosition(),u=new o(r,s.row,s.column),a=u.getCurrentToken();a&&a.value.match(/\s+/)&&(a=u.stepBackward());if(a&&a.type==="support.type"){var f=r.doc.getLine(s.row),l=f.substring(s.column,s.column+1);if(l===":")return{text:"",selection:[1,1]};if(/^(\s+[^;]|\s*$)/.test(f.substring(s.column)))return{text:":;",selection:[1,1]}}}}),this.add("colon","deletion",function(e,t,n,r,i){var s=r.doc.getTextRange(i);if(!i.isMultiLine()&&s===":"){var u=n.getCursorPosition(),a=new o(r,u.row,u.column),f=a.getCurrentToken();f&&f.value.match(/\s+/)&&(f=a.stepBackward());if(f&&f.type==="support.type"){var l=r.doc.getLine(i.start.row),c=l.substring(i.end.column,i.end.column+1);if(c===";")return i.end.column++,i}}}),this.add("semicolon","insertion",function(e,t,n,r,i){if(i===";"&&n.selection.isEmpty()){var s=n.getCursorPosition(),o=r.doc.getLine(s.row),u=o.substring(s.column,s.column+1);if(u===";")return{text:"",selection:[1,1]}}}),this.add("!important","insertion",function(e,t,n,r,i){if(i==="!"&&n.selection.isEmpty()){var s=n.getCursorPosition(),o=r.doc.getLine(s.row);if(/^\s*(;|}|$)/.test(o.substring(s.column)))return{text:"!important",selection:[10,10]}}})};r.inherits(u,s),t.CssBehaviour=u}),define("ace/mode/folding/cstyle",["require","exports","module","ace/lib/oop","ace/range","ace/mode/folding/fold_mode"],function(e,t,n){"use strict";var r=e("../../lib/oop"),i=e("../../range").Range,s=e("./fold_mode").FoldMode,o=t.FoldMode=function(e){e&&(this.foldingStartMarker=new RegExp(this.foldingStartMarker.source.replace(/\|[^|]*?$/,"|"+e.start)),this.foldingStopMarker=new RegExp(this.foldingStopMarker.source.replace(/\|[^|]*?$/,"|"+e.end)))};r.inherits(o,s),function(){this.foldingStartMarker=/([\{\[\(])[^\}\]\)]*$|^\s*(\/\*)/,this.foldingStopMarker=/^[^\[\{\(]*([\}\]\)])|^[\s\*]*(\*\/)/,this.singleLineBlockCommentRe=/^\s*(\/\*).*\*\/\s*$/,this.tripleStarBlockCommentRe=/^\s*(\/\*\*\*).*\*\/\s*$/,this.startRegionRe=/^\s*(\/\*|\/\/)#?region\b/,this._getFoldWidgetBase=this.getFoldWidget,this.getFoldWidget=function(e,t,n){var r=e.getLine(n);if(this.singleLineBlockCommentRe.test(r)&&!this.startRegionRe.test(r)&&!this.tripleStarBlockCommentRe.test(r))return"";var i=this._getFoldWidgetBase(e,t,n);return!i&&this.startRegionRe.test(r)?"start":i},this.getFoldWidgetRange=function(e,t,n,r){var i=e.getLine(n);if(this.startRegionRe.test(i))return this.getCommentRegionBlock(e,i,n);var s=i.match(this.foldingStartMarker);if(s){var o=s.index;if(s[1])return this.openingBracketBlock(e,s[1],n,o);var u=e.getCommentFoldRange(n,o+s[0].length,1);return u&&!u.isMultiLine()&&(r?u=this.getSectionRange(e,n):t!="all"&&(u=null)),u}if(t==="markbegin")return;var s=i.match(this.foldingStopMarker);if(s){var o=s.index+s[0].length;return s[1]?this.closingBracketBlock(e,s[1],n,o):e.getCommentFoldRange(n,o,-1)}},this.getSectionRange=function(e,t){var n=e.getLine(t),r=n.search(/\S/),s=t,o=n.length;t+=1;var u=t,a=e.getLength();while(++tf)break;var l=this.getFoldWidgetRange(e,"all",t);if(l){if(l.start.row<=s)break;if(l.isMultiLine())t=l.end.row;else if(r==f)break}u=t}return new i(s,o,u,e.getLine(u).length)},this.getCommentRegionBlock=function(e,t,n){var r=t.search(/\s*$/),s=e.getLength(),o=n,u=/^\s*(?:\/\*|\/\/|--)#?(end)?region\b/,a=1;while(++no)return new i(o,r,l,t.length)}}.call(o.prototype)}),define("ace/mode/css",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/css_highlight_rules","ace/mode/matching_brace_outdent","ace/worker/worker_client","ace/mode/css_completions","ace/mode/behaviour/css","ace/mode/folding/cstyle"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./css_highlight_rules").CssHighlightRules,o=e("./matching_brace_outdent").MatchingBraceOutdent,u=e("../worker/worker_client").WorkerClient,a=e("./css_completions").CssCompletions,f=e("./behaviour/css").CssBehaviour,l=e("./folding/cstyle").FoldMode,c=function(){this.HighlightRules=s,this.$outdent=new o,this.$behaviour=new f,this.$completer=new a,this.foldingRules=new l};r.inherits(c,i),function(){this.foldingRules="cStyle",this.blockComment={start:"/*",end:"*/"},this.getNextLineIndent=function(e,t,n){var r=this.$getIndent(t),i=this.getTokenizer().getLineTokens(t,e).tokens;if(i.length&&i[i.length-1].type=="comment")return r;var s=t.match(/^.*\{\s*$/);return s&&(r+=n),r},this.checkOutdent=function(e,t,n){return this.$outdent.checkOutdent(t,n)},this.autoOutdent=function(e,t,n){this.$outdent.autoOutdent(t,n)},this.getCompletions=function(e,t,n,r){return this.$completer.getCompletions(e,t,n,r)},this.createWorker=function(e){var t=new u(["ace"],"ace/mode/css_worker","Worker");return t.attachToDocument(e.getDocument()),t.on("annotate",function(t){e.setAnnotations(t.data)}),t.on("terminate",function(){e.clearAnnotations()}),t},this.$id="ace/mode/css"}.call(c.prototype),t.Mode=c}); (function() { + window.require(["ace/mode/css"], function(m) { + if (typeof module == "object" && typeof exports == "object" && module) { + module.exports = m; + } + }); + })(); \ No newline at end of file diff --git a/app/assets/frontends/beaker-code-snippet/ace/mode-html.js b/app/assets/frontends/beaker-code-snippet/ace/mode-html.js new file mode 100644 index 0000000000..a9350f9f8d --- /dev/null +++ b/app/assets/frontends/beaker-code-snippet/ace/mode-html.js @@ -0,0 +1,7 @@ +define("ace/mode/doc_comment_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text_highlight_rules").TextHighlightRules,s=function(){this.$rules={start:[{token:"comment.doc.tag",regex:"@[\\w\\d_]+"},s.getTagRule(),{defaultToken:"comment.doc",caseInsensitive:!0}]}};r.inherits(s,i),s.getTagRule=function(e){return{token:"comment.doc.tag.storage.type",regex:"\\b(?:TODO|FIXME|XXX|HACK)\\b"}},s.getStartRule=function(e){return{token:"comment.doc",regex:"\\/\\*(?=\\*)",next:e}},s.getEndRule=function(e){return{token:"comment.doc",regex:"\\*\\/",next:e}},t.DocCommentHighlightRules=s}),define("ace/mode/javascript_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/doc_comment_highlight_rules","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";function a(){var e=o.replace("\\d","\\d\\-"),t={onMatch:function(e,t,n){var r=e.charAt(1)=="/"?2:1;if(r==1)t!=this.nextState?n.unshift(this.next,this.nextState,0):n.unshift(this.next),n[2]++;else if(r==2&&t==this.nextState){n[1]--;if(!n[1]||n[1]<0)n.shift(),n.shift()}return[{type:"meta.tag.punctuation."+(r==1?"":"end-")+"tag-open.xml",value:e.slice(0,r)},{type:"meta.tag.tag-name.xml",value:e.substr(r)}]},regex:"",onMatch:function(e,t,n){return t==n[0]&&n.shift(),e.length==2&&(n[0]==this.nextState&&n[1]--,(!n[1]||n[1]<0)&&n.splice(0,2)),this.next=n[0]||"start",[{type:this.token,value:e}]},nextState:"jsx"},n,f("jsxAttributes"),{token:"entity.other.attribute-name.xml",regex:e},{token:"keyword.operator.attribute-equals.xml",regex:"="},{token:"text.tag-whitespace.xml",regex:"\\s+"},{token:"string.attribute-value.xml",regex:"'",stateName:"jsx_attr_q",push:[{token:"string.attribute-value.xml",regex:"'",next:"pop"},{include:"reference"},{defaultToken:"string.attribute-value.xml"}]},{token:"string.attribute-value.xml",regex:'"',stateName:"jsx_attr_qq",push:[{token:"string.attribute-value.xml",regex:'"',next:"pop"},{include:"reference"},{defaultToken:"string.attribute-value.xml"}]},t],this.$rules.reference=[{token:"constant.language.escape.reference.xml",regex:"(?:&#[0-9]+;)|(?:&#x[0-9a-fA-F]+;)|(?:&[a-zA-Z0-9_:\\.-]+;)"}]}function f(e){return[{token:"comment",regex:/\/\*/,next:[i.getTagRule(),{token:"comment",regex:"\\*\\/",next:e||"pop"},{defaultToken:"comment",caseInsensitive:!0}]},{token:"comment",regex:"\\/\\/",next:[i.getTagRule(),{token:"comment",regex:"$|^",next:e||"pop"},{defaultToken:"comment",caseInsensitive:!0}]}]}var r=e("../lib/oop"),i=e("./doc_comment_highlight_rules").DocCommentHighlightRules,s=e("./text_highlight_rules").TextHighlightRules,o="[a-zA-Z\\$_\u00a1-\uffff][a-zA-Z\\d\\$_\u00a1-\uffff]*",u=function(e){var t=this.createKeywordMapper({"variable.language":"Array|Boolean|Date|Function|Iterator|Number|Object|RegExp|String|Proxy|Namespace|QName|XML|XMLList|ArrayBuffer|Float32Array|Float64Array|Int16Array|Int32Array|Int8Array|Uint16Array|Uint32Array|Uint8Array|Uint8ClampedArray|Error|EvalError|InternalError|RangeError|ReferenceError|StopIteration|SyntaxError|TypeError|URIError|decodeURI|decodeURIComponent|encodeURI|encodeURIComponent|eval|isFinite|isNaN|parseFloat|parseInt|JSON|Math|this|arguments|prototype|window|document",keyword:"const|yield|import|get|set|async|await|break|case|catch|continue|default|delete|do|else|finally|for|function|if|in|of|instanceof|new|return|switch|throw|try|typeof|let|var|while|with|debugger|__parent__|__count__|escape|unescape|with|__proto__|class|enum|extends|super|export|implements|private|public|interface|package|protected|static","storage.type":"const|let|var|function","constant.language":"null|Infinity|NaN|undefined","support.function":"alert","constant.language.boolean":"true|false"},"identifier"),n="case|do|else|finally|in|instanceof|return|throw|try|typeof|yield|void",r="\\\\(?:x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|u{[0-9a-fA-F]{1,6}}|[0-2][0-7]{0,2}|3[0-7][0-7]?|[4-7][0-7]?|.)";this.$rules={no_regex:[i.getStartRule("doc-start"),f("no_regex"),{token:"string",regex:"'(?=.)",next:"qstring"},{token:"string",regex:'"(?=.)',next:"qqstring"},{token:"constant.numeric",regex:/0(?:[xX][0-9a-fA-F]+|[oO][0-7]+|[bB][01]+)\b/},{token:"constant.numeric",regex:/(?:\d\d*(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+\b)?/},{token:["storage.type","punctuation.operator","support.function","punctuation.operator","entity.name.function","text","keyword.operator"],regex:"("+o+")(\\.)(prototype)(\\.)("+o+")(\\s*)(=)",next:"function_arguments"},{token:["storage.type","punctuation.operator","entity.name.function","text","keyword.operator","text","storage.type","text","paren.lparen"],regex:"("+o+")(\\.)("+o+")(\\s*)(=)(\\s*)(function)(\\s*)(\\()",next:"function_arguments"},{token:["entity.name.function","text","keyword.operator","text","storage.type","text","paren.lparen"],regex:"("+o+")(\\s*)(=)(\\s*)(function)(\\s*)(\\()",next:"function_arguments"},{token:["storage.type","punctuation.operator","entity.name.function","text","keyword.operator","text","storage.type","text","entity.name.function","text","paren.lparen"],regex:"("+o+")(\\.)("+o+")(\\s*)(=)(\\s*)(function)(\\s+)(\\w+)(\\s*)(\\()",next:"function_arguments"},{token:["storage.type","text","entity.name.function","text","paren.lparen"],regex:"(function)(\\s+)("+o+")(\\s*)(\\()",next:"function_arguments"},{token:["entity.name.function","text","punctuation.operator","text","storage.type","text","paren.lparen"],regex:"("+o+")(\\s*)(:)(\\s*)(function)(\\s*)(\\()",next:"function_arguments"},{token:["text","text","storage.type","text","paren.lparen"],regex:"(:)(\\s*)(function)(\\s*)(\\()",next:"function_arguments"},{token:"keyword",regex:"from(?=\\s*('|\"))"},{token:"keyword",regex:"(?:"+n+")\\b",next:"start"},{token:["support.constant"],regex:/that\b/},{token:["storage.type","punctuation.operator","support.function.firebug"],regex:/(console)(\.)(warn|info|log|error|time|trace|timeEnd|assert)\b/},{token:t,regex:o},{token:"punctuation.operator",regex:/[.](?![.])/,next:"property"},{token:"storage.type",regex:/=>/,next:"start"},{token:"keyword.operator",regex:/--|\+\+|\.{3}|===|==|=|!=|!==|<+=?|>+=?|!|&&|\|\||\?:|[!$%&*+\-~\/^]=?/,next:"start"},{token:"punctuation.operator",regex:/[?:,;.]/,next:"start"},{token:"paren.lparen",regex:/[\[({]/,next:"start"},{token:"paren.rparen",regex:/[\])}]/},{token:"comment",regex:/^#!.*$/}],property:[{token:"text",regex:"\\s+"},{token:["storage.type","punctuation.operator","entity.name.function","text","keyword.operator","text","storage.type","text","entity.name.function","text","paren.lparen"],regex:"("+o+")(\\.)("+o+")(\\s*)(=)(\\s*)(function)(?:(\\s+)(\\w+))?(\\s*)(\\()",next:"function_arguments"},{token:"punctuation.operator",regex:/[.](?![.])/},{token:"support.function",regex:/(s(?:h(?:ift|ow(?:Mod(?:elessDialog|alDialog)|Help))|croll(?:X|By(?:Pages|Lines)?|Y|To)?|t(?:op|rike)|i(?:n|zeToContent|debar|gnText)|ort|u(?:p|b(?:str(?:ing)?)?)|pli(?:ce|t)|e(?:nd|t(?:Re(?:sizable|questHeader)|M(?:i(?:nutes|lliseconds)|onth)|Seconds|Ho(?:tKeys|urs)|Year|Cursor|Time(?:out)?|Interval|ZOptions|Date|UTC(?:M(?:i(?:nutes|lliseconds)|onth)|Seconds|Hours|Date|FullYear)|FullYear|Active)|arch)|qrt|lice|avePreferences|mall)|h(?:ome|andleEvent)|navigate|c(?:har(?:CodeAt|At)|o(?:s|n(?:cat|textual|firm)|mpile)|eil|lear(?:Timeout|Interval)?|a(?:ptureEvents|ll)|reate(?:StyleSheet|Popup|EventObject))|t(?:o(?:GMTString|S(?:tring|ource)|U(?:TCString|pperCase)|Lo(?:caleString|werCase))|est|a(?:n|int(?:Enabled)?))|i(?:s(?:NaN|Finite)|ndexOf|talics)|d(?:isableExternalCapture|ump|etachEvent)|u(?:n(?:shift|taint|escape|watch)|pdateCommands)|j(?:oin|avaEnabled)|p(?:o(?:p|w)|ush|lugins.refresh|a(?:ddings|rse(?:Int|Float)?)|r(?:int|ompt|eference))|e(?:scape|nableExternalCapture|val|lementFromPoint|x(?:p|ec(?:Script|Command)?))|valueOf|UTC|queryCommand(?:State|Indeterm|Enabled|Value)|f(?:i(?:nd|le(?:ModifiedDate|Size|CreatedDate|UpdatedDate)|xed)|o(?:nt(?:size|color)|rward)|loor|romCharCode)|watch|l(?:ink|o(?:ad|g)|astIndexOf)|a(?:sin|nchor|cos|t(?:tachEvent|ob|an(?:2)?)|pply|lert|b(?:s|ort))|r(?:ou(?:nd|teEvents)|e(?:size(?:By|To)|calc|turnValue|place|verse|l(?:oad|ease(?:Capture|Events)))|andom)|g(?:o|et(?:ResponseHeader|M(?:i(?:nutes|lliseconds)|onth)|Se(?:conds|lection)|Hours|Year|Time(?:zoneOffset)?|Da(?:y|te)|UTC(?:M(?:i(?:nutes|lliseconds)|onth)|Seconds|Hours|Da(?:y|te)|FullYear)|FullYear|A(?:ttention|llResponseHeaders)))|m(?:in|ove(?:B(?:y|elow)|To(?:Absolute)?|Above)|ergeAttributes|a(?:tch|rgins|x))|b(?:toa|ig|o(?:ld|rderWidths)|link|ack))\b(?=\()/},{token:"support.function.dom",regex:/(s(?:ub(?:stringData|mit)|plitText|e(?:t(?:NamedItem|Attribute(?:Node)?)|lect))|has(?:ChildNodes|Feature)|namedItem|c(?:l(?:ick|o(?:se|neNode))|reate(?:C(?:omment|DATASection|aption)|T(?:Head|extNode|Foot)|DocumentFragment|ProcessingInstruction|E(?:ntityReference|lement)|Attribute))|tabIndex|i(?:nsert(?:Row|Before|Cell|Data)|tem)|open|delete(?:Row|C(?:ell|aption)|T(?:Head|Foot)|Data)|focus|write(?:ln)?|a(?:dd|ppend(?:Child|Data))|re(?:set|place(?:Child|Data)|move(?:NamedItem|Child|Attribute(?:Node)?)?)|get(?:NamedItem|Element(?:sBy(?:Name|TagName|ClassName)|ById)|Attribute(?:Node)?)|blur)\b(?=\()/},{token:"support.constant",regex:/(s(?:ystemLanguage|cr(?:ipts|ollbars|een(?:X|Y|Top|Left))|t(?:yle(?:Sheets)?|atus(?:Text|bar)?)|ibling(?:Below|Above)|ource|uffixes|e(?:curity(?:Policy)?|l(?:ection|f)))|h(?:istory|ost(?:name)?|as(?:h|Focus))|y|X(?:MLDocument|SLDocument)|n(?:ext|ame(?:space(?:s|URI)|Prop))|M(?:IN_VALUE|AX_VALUE)|c(?:haracterSet|o(?:n(?:structor|trollers)|okieEnabled|lorDepth|mp(?:onents|lete))|urrent|puClass|l(?:i(?:p(?:boardData)?|entInformation)|osed|asses)|alle(?:e|r)|rypto)|t(?:o(?:olbar|p)|ext(?:Transform|Indent|Decoration|Align)|ags)|SQRT(?:1_2|2)|i(?:n(?:ner(?:Height|Width)|put)|ds|gnoreCase)|zIndex|o(?:scpu|n(?:readystatechange|Line)|uter(?:Height|Width)|p(?:sProfile|ener)|ffscreenBuffering)|NEGATIVE_INFINITY|d(?:i(?:splay|alog(?:Height|Top|Width|Left|Arguments)|rectories)|e(?:scription|fault(?:Status|Ch(?:ecked|arset)|View)))|u(?:ser(?:Profile|Language|Agent)|n(?:iqueID|defined)|pdateInterval)|_content|p(?:ixelDepth|ort|ersonalbar|kcs11|l(?:ugins|atform)|a(?:thname|dding(?:Right|Bottom|Top|Left)|rent(?:Window|Layer)?|ge(?:X(?:Offset)?|Y(?:Offset)?))|r(?:o(?:to(?:col|type)|duct(?:Sub)?|mpter)|e(?:vious|fix)))|e(?:n(?:coding|abledPlugin)|x(?:ternal|pando)|mbeds)|v(?:isibility|endor(?:Sub)?|Linkcolor)|URLUnencoded|P(?:I|OSITIVE_INFINITY)|f(?:ilename|o(?:nt(?:Size|Family|Weight)|rmName)|rame(?:s|Element)|gColor)|E|whiteSpace|l(?:i(?:stStyleType|n(?:eHeight|kColor))|o(?:ca(?:tion(?:bar)?|lName)|wsrc)|e(?:ngth|ft(?:Context)?)|a(?:st(?:M(?:odified|atch)|Index|Paren)|yer(?:s|X)|nguage))|a(?:pp(?:MinorVersion|Name|Co(?:deName|re)|Version)|vail(?:Height|Top|Width|Left)|ll|r(?:ity|guments)|Linkcolor|bove)|r(?:ight(?:Context)?|e(?:sponse(?:XML|Text)|adyState))|global|x|m(?:imeTypes|ultiline|enubar|argin(?:Right|Bottom|Top|Left))|L(?:N(?:10|2)|OG(?:10E|2E))|b(?:o(?:ttom|rder(?:Width|RightWidth|BottomWidth|Style|Color|TopWidth|LeftWidth))|ufferDepth|elow|ackground(?:Color|Image)))\b/},{token:"identifier",regex:o},{regex:"",token:"empty",next:"no_regex"}],start:[i.getStartRule("doc-start"),f("start"),{token:"string.regexp",regex:"\\/",next:"regex"},{token:"text",regex:"\\s+|^$",next:"start"},{token:"empty",regex:"",next:"no_regex"}],regex:[{token:"regexp.keyword.operator",regex:"\\\\(?:u[\\da-fA-F]{4}|x[\\da-fA-F]{2}|.)"},{token:"string.regexp",regex:"/[sxngimy]*",next:"no_regex"},{token:"invalid",regex:/\{\d+\b,?\d*\}[+*]|[+*$^?][+*]|[$^][?]|\?{3,}/},{token:"constant.language.escape",regex:/\(\?[:=!]|\)|\{\d+\b,?\d*\}|[+*]\?|[()$^+*?.]/},{token:"constant.language.delimiter",regex:/\|/},{token:"constant.language.escape",regex:/\[\^?/,next:"regex_character_class"},{token:"empty",regex:"$",next:"no_regex"},{defaultToken:"string.regexp"}],regex_character_class:[{token:"regexp.charclass.keyword.operator",regex:"\\\\(?:u[\\da-fA-F]{4}|x[\\da-fA-F]{2}|.)"},{token:"constant.language.escape",regex:"]",next:"regex"},{token:"constant.language.escape",regex:"-"},{token:"empty",regex:"$",next:"no_regex"},{defaultToken:"string.regexp.charachterclass"}],function_arguments:[{token:"variable.parameter",regex:o},{token:"punctuation.operator",regex:"[, ]+"},{token:"punctuation.operator",regex:"$"},{token:"empty",regex:"",next:"no_regex"}],qqstring:[{token:"constant.language.escape",regex:r},{token:"string",regex:"\\\\$",consumeLineEnd:!0},{token:"string",regex:'"|$',next:"no_regex"},{defaultToken:"string"}],qstring:[{token:"constant.language.escape",regex:r},{token:"string",regex:"\\\\$",consumeLineEnd:!0},{token:"string",regex:"'|$",next:"no_regex"},{defaultToken:"string"}]};if(!e||!e.noES6)this.$rules.no_regex.unshift({regex:"[{}]",onMatch:function(e,t,n){this.next=e=="{"?this.nextState:"";if(e=="{"&&n.length)n.unshift("start",t);else if(e=="}"&&n.length){n.shift(),this.next=n.shift();if(this.next.indexOf("string")!=-1||this.next.indexOf("jsx")!=-1)return"paren.quasi.end"}return e=="{"?"paren.lparen":"paren.rparen"},nextState:"start"},{token:"string.quasi.start",regex:/`/,push:[{token:"constant.language.escape",regex:r},{token:"paren.quasi.start",regex:/\${/,push:"start"},{token:"string.quasi.end",regex:/`/,next:"pop"},{defaultToken:"string.quasi"}]}),(!e||e.jsx!=0)&&a.call(this);this.embedRules(i,"doc-",[i.getEndRule("no_regex")]),this.normalizeRules()};r.inherits(u,s),t.JavaScriptHighlightRules=u}),define("ace/mode/matching_brace_outdent",["require","exports","module","ace/range"],function(e,t,n){"use strict";var r=e("../range").Range,i=function(){};(function(){this.checkOutdent=function(e,t){return/^\s+$/.test(e)?/^\s*\}/.test(t):!1},this.autoOutdent=function(e,t){var n=e.getLine(t),i=n.match(/^(\s*\})/);if(!i)return 0;var s=i[1].length,o=e.findMatchingBracket({row:t,column:s});if(!o||o.row==t)return 0;var u=this.$getIndent(e.getLine(o.row));e.replace(new r(t,0,t,s-1),u)},this.$getIndent=function(e){return e.match(/^\s*/)[0]}}).call(i.prototype),t.MatchingBraceOutdent=i}),define("ace/mode/folding/cstyle",["require","exports","module","ace/lib/oop","ace/range","ace/mode/folding/fold_mode"],function(e,t,n){"use strict";var r=e("../../lib/oop"),i=e("../../range").Range,s=e("./fold_mode").FoldMode,o=t.FoldMode=function(e){e&&(this.foldingStartMarker=new RegExp(this.foldingStartMarker.source.replace(/\|[^|]*?$/,"|"+e.start)),this.foldingStopMarker=new RegExp(this.foldingStopMarker.source.replace(/\|[^|]*?$/,"|"+e.end)))};r.inherits(o,s),function(){this.foldingStartMarker=/([\{\[\(])[^\}\]\)]*$|^\s*(\/\*)/,this.foldingStopMarker=/^[^\[\{\(]*([\}\]\)])|^[\s\*]*(\*\/)/,this.singleLineBlockCommentRe=/^\s*(\/\*).*\*\/\s*$/,this.tripleStarBlockCommentRe=/^\s*(\/\*\*\*).*\*\/\s*$/,this.startRegionRe=/^\s*(\/\*|\/\/)#?region\b/,this._getFoldWidgetBase=this.getFoldWidget,this.getFoldWidget=function(e,t,n){var r=e.getLine(n);if(this.singleLineBlockCommentRe.test(r)&&!this.startRegionRe.test(r)&&!this.tripleStarBlockCommentRe.test(r))return"";var i=this._getFoldWidgetBase(e,t,n);return!i&&this.startRegionRe.test(r)?"start":i},this.getFoldWidgetRange=function(e,t,n,r){var i=e.getLine(n);if(this.startRegionRe.test(i))return this.getCommentRegionBlock(e,i,n);var s=i.match(this.foldingStartMarker);if(s){var o=s.index;if(s[1])return this.openingBracketBlock(e,s[1],n,o);var u=e.getCommentFoldRange(n,o+s[0].length,1);return u&&!u.isMultiLine()&&(r?u=this.getSectionRange(e,n):t!="all"&&(u=null)),u}if(t==="markbegin")return;var s=i.match(this.foldingStopMarker);if(s){var o=s.index+s[0].length;return s[1]?this.closingBracketBlock(e,s[1],n,o):e.getCommentFoldRange(n,o,-1)}},this.getSectionRange=function(e,t){var n=e.getLine(t),r=n.search(/\S/),s=t,o=n.length;t+=1;var u=t,a=e.getLength();while(++tf)break;var l=this.getFoldWidgetRange(e,"all",t);if(l){if(l.start.row<=s)break;if(l.isMultiLine())t=l.end.row;else if(r==f)break}u=t}return new i(s,o,u,e.getLine(u).length)},this.getCommentRegionBlock=function(e,t,n){var r=t.search(/\s*$/),s=e.getLength(),o=n,u=/^\s*(?:\/\*|\/\/|--)#?(end)?region\b/,a=1;while(++no)return new i(o,r,l,t.length)}}.call(o.prototype)}),define("ace/mode/javascript",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/javascript_highlight_rules","ace/mode/matching_brace_outdent","ace/worker/worker_client","ace/mode/behaviour/cstyle","ace/mode/folding/cstyle"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./javascript_highlight_rules").JavaScriptHighlightRules,o=e("./matching_brace_outdent").MatchingBraceOutdent,u=e("../worker/worker_client").WorkerClient,a=e("./behaviour/cstyle").CstyleBehaviour,f=e("./folding/cstyle").FoldMode,l=function(){this.HighlightRules=s,this.$outdent=new o,this.$behaviour=new a,this.foldingRules=new f};r.inherits(l,i),function(){this.lineCommentStart="//",this.blockComment={start:"/*",end:"*/"},this.$quotes={'"':'"',"'":"'","`":"`"},this.getNextLineIndent=function(e,t,n){var r=this.$getIndent(t),i=this.getTokenizer().getLineTokens(t,e),s=i.tokens,o=i.state;if(s.length&&s[s.length-1].type=="comment")return r;if(e=="start"||e=="no_regex"){var u=t.match(/^.*(?:\bcase\b.*:|[\{\(\[])\s*$/);u&&(r+=n)}else if(e=="doc-start"){if(o=="start"||o=="no_regex")return"";var u=t.match(/^\s*(\/?)\*/);u&&(u[1]&&(r+=" "),r+="* ")}return r},this.checkOutdent=function(e,t,n){return this.$outdent.checkOutdent(t,n)},this.autoOutdent=function(e,t,n){this.$outdent.autoOutdent(t,n)},this.createWorker=function(e){var t=new u(["ace"],"ace/mode/javascript_worker","JavaScriptWorker");return t.attachToDocument(e.getDocument()),t.on("annotate",function(t){e.setAnnotations(t.data)}),t.on("terminate",function(){e.clearAnnotations()}),t},this.$id="ace/mode/javascript"}.call(l.prototype),t.Mode=l}),define("ace/mode/css_highlight_rules",["require","exports","module","ace/lib/oop","ace/lib/lang","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("../lib/lang"),s=e("./text_highlight_rules").TextHighlightRules,o=t.supportType="align-content|align-items|align-self|all|animation|animation-delay|animation-direction|animation-duration|animation-fill-mode|animation-iteration-count|animation-name|animation-play-state|animation-timing-function|backface-visibility|background|background-attachment|background-blend-mode|background-clip|background-color|background-image|background-origin|background-position|background-repeat|background-size|border|border-bottom|border-bottom-color|border-bottom-left-radius|border-bottom-right-radius|border-bottom-style|border-bottom-width|border-collapse|border-color|border-image|border-image-outset|border-image-repeat|border-image-slice|border-image-source|border-image-width|border-left|border-left-color|border-left-style|border-left-width|border-radius|border-right|border-right-color|border-right-style|border-right-width|border-spacing|border-style|border-top|border-top-color|border-top-left-radius|border-top-right-radius|border-top-style|border-top-width|border-width|bottom|box-shadow|box-sizing|caption-side|clear|clip|color|column-count|column-fill|column-gap|column-rule|column-rule-color|column-rule-style|column-rule-width|column-span|column-width|columns|content|counter-increment|counter-reset|cursor|direction|display|empty-cells|filter|flex|flex-basis|flex-direction|flex-flow|flex-grow|flex-shrink|flex-wrap|float|font|font-family|font-size|font-size-adjust|font-stretch|font-style|font-variant|font-weight|hanging-punctuation|height|justify-content|left|letter-spacing|line-height|list-style|list-style-image|list-style-position|list-style-type|margin|margin-bottom|margin-left|margin-right|margin-top|max-height|max-width|max-zoom|min-height|min-width|min-zoom|nav-down|nav-index|nav-left|nav-right|nav-up|opacity|order|outline|outline-color|outline-offset|outline-style|outline-width|overflow|overflow-x|overflow-y|padding|padding-bottom|padding-left|padding-right|padding-top|page-break-after|page-break-before|page-break-inside|perspective|perspective-origin|position|quotes|resize|right|tab-size|table-layout|text-align|text-align-last|text-decoration|text-decoration-color|text-decoration-line|text-decoration-style|text-indent|text-justify|text-overflow|text-shadow|text-transform|top|transform|transform-origin|transform-style|transition|transition-delay|transition-duration|transition-property|transition-timing-function|unicode-bidi|user-select|user-zoom|vertical-align|visibility|white-space|width|word-break|word-spacing|word-wrap|z-index",u=t.supportFunction="rgb|rgba|url|attr|counter|counters",a=t.supportConstant="absolute|after-edge|after|all-scroll|all|alphabetic|always|antialiased|armenian|auto|avoid-column|avoid-page|avoid|balance|baseline|before-edge|before|below|bidi-override|block-line-height|block|bold|bolder|border-box|both|bottom|box|break-all|break-word|capitalize|caps-height|caption|center|central|char|circle|cjk-ideographic|clone|close-quote|col-resize|collapse|column|consider-shifts|contain|content-box|cover|crosshair|cubic-bezier|dashed|decimal-leading-zero|decimal|default|disabled|disc|disregard-shifts|distribute-all-lines|distribute-letter|distribute-space|distribute|dotted|double|e-resize|ease-in|ease-in-out|ease-out|ease|ellipsis|end|exclude-ruby|fill|fixed|georgian|glyphs|grid-height|groove|hand|hanging|hebrew|help|hidden|hiragana-iroha|hiragana|horizontal|icon|ideograph-alpha|ideograph-numeric|ideograph-parenthesis|ideograph-space|ideographic|inactive|include-ruby|inherit|initial|inline-block|inline-box|inline-line-height|inline-table|inline|inset|inside|inter-ideograph|inter-word|invert|italic|justify|katakana-iroha|katakana|keep-all|last|left|lighter|line-edge|line-through|line|linear|list-item|local|loose|lower-alpha|lower-greek|lower-latin|lower-roman|lowercase|lr-tb|ltr|mathematical|max-height|max-size|medium|menu|message-box|middle|move|n-resize|ne-resize|newspaper|no-change|no-close-quote|no-drop|no-open-quote|no-repeat|none|normal|not-allowed|nowrap|nw-resize|oblique|open-quote|outset|outside|overline|padding-box|page|pointer|pre-line|pre-wrap|pre|preserve-3d|progress|relative|repeat-x|repeat-y|repeat|replaced|reset-size|ridge|right|round|row-resize|rtl|s-resize|scroll|se-resize|separate|slice|small-caps|small-caption|solid|space|square|start|static|status-bar|step-end|step-start|steps|stretch|strict|sub|super|sw-resize|table-caption|table-cell|table-column-group|table-column|table-footer-group|table-header-group|table-row-group|table-row|table|tb-rl|text-after-edge|text-before-edge|text-bottom|text-size|text-top|text|thick|thin|transparent|underline|upper-alpha|upper-latin|upper-roman|uppercase|use-script|vertical-ideographic|vertical-text|visible|w-resize|wait|whitespace|z-index|zero|zoom",f=t.supportConstantColor="aliceblue|antiquewhite|aqua|aquamarine|azure|beige|bisque|black|blanchedalmond|blue|blueviolet|brown|burlywood|cadetblue|chartreuse|chocolate|coral|cornflowerblue|cornsilk|crimson|cyan|darkblue|darkcyan|darkgoldenrod|darkgray|darkgreen|darkgrey|darkkhaki|darkmagenta|darkolivegreen|darkorange|darkorchid|darkred|darksalmon|darkseagreen|darkslateblue|darkslategray|darkslategrey|darkturquoise|darkviolet|deeppink|deepskyblue|dimgray|dimgrey|dodgerblue|firebrick|floralwhite|forestgreen|fuchsia|gainsboro|ghostwhite|gold|goldenrod|gray|green|greenyellow|grey|honeydew|hotpink|indianred|indigo|ivory|khaki|lavender|lavenderblush|lawngreen|lemonchiffon|lightblue|lightcoral|lightcyan|lightgoldenrodyellow|lightgray|lightgreen|lightgrey|lightpink|lightsalmon|lightseagreen|lightskyblue|lightslategray|lightslategrey|lightsteelblue|lightyellow|lime|limegreen|linen|magenta|maroon|mediumaquamarine|mediumblue|mediumorchid|mediumpurple|mediumseagreen|mediumslateblue|mediumspringgreen|mediumturquoise|mediumvioletred|midnightblue|mintcream|mistyrose|moccasin|navajowhite|navy|oldlace|olive|olivedrab|orange|orangered|orchid|palegoldenrod|palegreen|paleturquoise|palevioletred|papayawhip|peachpuff|peru|pink|plum|powderblue|purple|rebeccapurple|red|rosybrown|royalblue|saddlebrown|salmon|sandybrown|seagreen|seashell|sienna|silver|skyblue|slateblue|slategray|slategrey|snow|springgreen|steelblue|tan|teal|thistle|tomato|turquoise|violet|wheat|white|whitesmoke|yellow|yellowgreen",l=t.supportConstantFonts="arial|century|comic|courier|cursive|fantasy|garamond|georgia|helvetica|impact|lucida|symbol|system|tahoma|times|trebuchet|utopia|verdana|webdings|sans-serif|serif|monospace",c=t.numRe="\\-?(?:(?:[0-9]+(?:\\.[0-9]+)?)|(?:\\.[0-9]+))",h=t.pseudoElements="(\\:+)\\b(after|before|first-letter|first-line|moz-selection|selection)\\b",p=t.pseudoClasses="(:)\\b(active|checked|disabled|empty|enabled|first-child|first-of-type|focus|hover|indeterminate|invalid|last-child|last-of-type|link|not|nth-child|nth-last-child|nth-last-of-type|nth-of-type|only-child|only-of-type|required|root|target|valid|visited)\\b",d=function(){var e=this.createKeywordMapper({"support.function":u,"support.constant":a,"support.type":o,"support.constant.color":f,"support.constant.fonts":l},"text",!0);this.$rules={start:[{include:["strings","url","comments"]},{token:"paren.lparen",regex:"\\{",next:"ruleset"},{token:"paren.rparen",regex:"\\}"},{token:"string",regex:"@(?!viewport)",next:"media"},{token:"keyword",regex:"#[a-z0-9-_]+"},{token:"keyword",regex:"%"},{token:"variable",regex:"\\.[a-z0-9-_]+"},{token:"string",regex:":[a-z0-9-_]+"},{token:"constant.numeric",regex:c},{token:"constant",regex:"[a-z0-9-_]+"},{caseInsensitive:!0}],media:[{include:["strings","url","comments"]},{token:"paren.lparen",regex:"\\{",next:"start"},{token:"paren.rparen",regex:"\\}",next:"start"},{token:"string",regex:";",next:"start"},{token:"keyword",regex:"(?:media|supports|document|charset|import|namespace|media|supports|document|page|font|keyframes|viewport|counter-style|font-feature-values|swash|ornaments|annotation|stylistic|styleset|character-variant)"}],comments:[{token:"comment",regex:"\\/\\*",push:[{token:"comment",regex:"\\*\\/",next:"pop"},{defaultToken:"comment"}]}],ruleset:[{regex:"-(webkit|ms|moz|o)-",token:"text"},{token:"punctuation.operator",regex:"[:;]"},{token:"paren.rparen",regex:"\\}",next:"start"},{include:["strings","url","comments"]},{token:["constant.numeric","keyword"],regex:"("+c+")(ch|cm|deg|em|ex|fr|gd|grad|Hz|in|kHz|mm|ms|pc|pt|px|rad|rem|s|turn|vh|vmax|vmin|vm|vw|%)"},{token:"constant.numeric",regex:c},{token:"constant.numeric",regex:"#[a-f0-9]{6}"},{token:"constant.numeric",regex:"#[a-f0-9]{3}"},{token:["punctuation","entity.other.attribute-name.pseudo-element.css"],regex:h},{token:["punctuation","entity.other.attribute-name.pseudo-class.css"],regex:p},{include:"url"},{token:e,regex:"\\-?[a-zA-Z_][a-zA-Z0-9_\\-]*"},{caseInsensitive:!0}],url:[{token:"support.function",regex:"(?:url(:?-prefix)?|domain|regexp)\\(",push:[{token:"support.function",regex:"\\)",next:"pop"},{defaultToken:"string"}]}],strings:[{token:"string.start",regex:"'",push:[{token:"string.end",regex:"'|$",next:"pop"},{include:"escapes"},{token:"constant.language.escape",regex:/\\$/,consumeLineEnd:!0},{defaultToken:"string"}]},{token:"string.start",regex:'"',push:[{token:"string.end",regex:'"|$',next:"pop"},{include:"escapes"},{token:"constant.language.escape",regex:/\\$/,consumeLineEnd:!0},{defaultToken:"string"}]}],escapes:[{token:"constant.language.escape",regex:/\\([a-fA-F\d]{1,6}|[^a-fA-F\d])/}]},this.normalizeRules()};r.inherits(d,s),t.CssHighlightRules=d}),define("ace/mode/css_completions",["require","exports","module"],function(e,t,n){"use strict";var r={background:{"#$0":1},"background-color":{"#$0":1,transparent:1,fixed:1},"background-image":{"url('/$0')":1},"background-repeat":{repeat:1,"repeat-x":1,"repeat-y":1,"no-repeat":1,inherit:1},"background-position":{bottom:2,center:2,left:2,right:2,top:2,inherit:2},"background-attachment":{scroll:1,fixed:1},"background-size":{cover:1,contain:1},"background-clip":{"border-box":1,"padding-box":1,"content-box":1},"background-origin":{"border-box":1,"padding-box":1,"content-box":1},border:{"solid $0":1,"dashed $0":1,"dotted $0":1,"#$0":1},"border-color":{"#$0":1},"border-style":{solid:2,dashed:2,dotted:2,"double":2,groove:2,hidden:2,inherit:2,inset:2,none:2,outset:2,ridged:2},"border-collapse":{collapse:1,separate:1},bottom:{px:1,em:1,"%":1},clear:{left:1,right:1,both:1,none:1},color:{"#$0":1,"rgb(#$00,0,0)":1},cursor:{"default":1,pointer:1,move:1,text:1,wait:1,help:1,progress:1,"n-resize":1,"ne-resize":1,"e-resize":1,"se-resize":1,"s-resize":1,"sw-resize":1,"w-resize":1,"nw-resize":1},display:{none:1,block:1,inline:1,"inline-block":1,"table-cell":1},"empty-cells":{show:1,hide:1},"float":{left:1,right:1,none:1},"font-family":{Arial:2,"Comic Sans MS":2,Consolas:2,"Courier New":2,Courier:2,Georgia:2,Monospace:2,"Sans-Serif":2,"Segoe UI":2,Tahoma:2,"Times New Roman":2,"Trebuchet MS":2,Verdana:1},"font-size":{px:1,em:1,"%":1},"font-weight":{bold:1,normal:1},"font-style":{italic:1,normal:1},"font-variant":{normal:1,"small-caps":1},height:{px:1,em:1,"%":1},left:{px:1,em:1,"%":1},"letter-spacing":{normal:1},"line-height":{normal:1},"list-style-type":{none:1,disc:1,circle:1,square:1,decimal:1,"decimal-leading-zero":1,"lower-roman":1,"upper-roman":1,"lower-greek":1,"lower-latin":1,"upper-latin":1,georgian:1,"lower-alpha":1,"upper-alpha":1},margin:{px:1,em:1,"%":1},"margin-right":{px:1,em:1,"%":1},"margin-left":{px:1,em:1,"%":1},"margin-top":{px:1,em:1,"%":1},"margin-bottom":{px:1,em:1,"%":1},"max-height":{px:1,em:1,"%":1},"max-width":{px:1,em:1,"%":1},"min-height":{px:1,em:1,"%":1},"min-width":{px:1,em:1,"%":1},overflow:{hidden:1,visible:1,auto:1,scroll:1},"overflow-x":{hidden:1,visible:1,auto:1,scroll:1},"overflow-y":{hidden:1,visible:1,auto:1,scroll:1},padding:{px:1,em:1,"%":1},"padding-top":{px:1,em:1,"%":1},"padding-right":{px:1,em:1,"%":1},"padding-bottom":{px:1,em:1,"%":1},"padding-left":{px:1,em:1,"%":1},"page-break-after":{auto:1,always:1,avoid:1,left:1,right:1},"page-break-before":{auto:1,always:1,avoid:1,left:1,right:1},position:{absolute:1,relative:1,fixed:1,"static":1},right:{px:1,em:1,"%":1},"table-layout":{fixed:1,auto:1},"text-decoration":{none:1,underline:1,"line-through":1,blink:1},"text-align":{left:1,right:1,center:1,justify:1},"text-transform":{capitalize:1,uppercase:1,lowercase:1,none:1},top:{px:1,em:1,"%":1},"vertical-align":{top:1,bottom:1},visibility:{hidden:1,visible:1},"white-space":{nowrap:1,normal:1,pre:1,"pre-line":1,"pre-wrap":1},width:{px:1,em:1,"%":1},"word-spacing":{normal:1},filter:{"alpha(opacity=$0100)":1},"text-shadow":{"$02px 2px 2px #777":1},"text-overflow":{"ellipsis-word":1,clip:1,ellipsis:1},"-moz-border-radius":1,"-moz-border-radius-topright":1,"-moz-border-radius-bottomright":1,"-moz-border-radius-topleft":1,"-moz-border-radius-bottomleft":1,"-webkit-border-radius":1,"-webkit-border-top-right-radius":1,"-webkit-border-top-left-radius":1,"-webkit-border-bottom-right-radius":1,"-webkit-border-bottom-left-radius":1,"-moz-box-shadow":1,"-webkit-box-shadow":1,transform:{"rotate($00deg)":1,"skew($00deg)":1},"-moz-transform":{"rotate($00deg)":1,"skew($00deg)":1},"-webkit-transform":{"rotate($00deg)":1,"skew($00deg)":1}},i=function(){};(function(){this.completionsDefined=!1,this.defineCompletions=function(){if(document){var e=document.createElement("c").style;for(var t in e){if(typeof e[t]!="string")continue;var n=t.replace(/[A-Z]/g,function(e){return"-"+e.toLowerCase()});r.hasOwnProperty(n)||(r[n]=1)}}this.completionsDefined=!0},this.getCompletions=function(e,t,n,r){this.completionsDefined||this.defineCompletions();if(e==="ruleset"||t.$mode.$id=="ace/mode/scss"){var i=t.getLine(n.row).substr(0,n.column);return/:[^;]+$/.test(i)?(/([\w\-]+):[^:]*$/.test(i),this.getPropertyValueCompletions(e,t,n,r)):this.getPropertyCompletions(e,t,n,r)}return[]},this.getPropertyCompletions=function(e,t,n,i){var s=Object.keys(r);return s.map(function(e){return{caption:e,snippet:e+": $0;",meta:"property",score:1e6}})},this.getPropertyValueCompletions=function(e,t,n,i){var s=t.getLine(n.row).substr(0,n.column),o=(/([\w\-]+):[^:]*$/.exec(s)||{})[1];if(!o)return[];var u=[];return o in r&&typeof r[o]=="object"&&(u=Object.keys(r[o])),u.map(function(e){return{caption:e,snippet:e,meta:"property value",score:1e6}})}}).call(i.prototype),t.CssCompletions=i}),define("ace/mode/behaviour/css",["require","exports","module","ace/lib/oop","ace/mode/behaviour","ace/mode/behaviour/cstyle","ace/token_iterator"],function(e,t,n){"use strict";var r=e("../../lib/oop"),i=e("../behaviour").Behaviour,s=e("./cstyle").CstyleBehaviour,o=e("../../token_iterator").TokenIterator,u=function(){this.inherit(s),this.add("colon","insertion",function(e,t,n,r,i){if(i===":"&&n.selection.isEmpty()){var s=n.getCursorPosition(),u=new o(r,s.row,s.column),a=u.getCurrentToken();a&&a.value.match(/\s+/)&&(a=u.stepBackward());if(a&&a.type==="support.type"){var f=r.doc.getLine(s.row),l=f.substring(s.column,s.column+1);if(l===":")return{text:"",selection:[1,1]};if(/^(\s+[^;]|\s*$)/.test(f.substring(s.column)))return{text:":;",selection:[1,1]}}}}),this.add("colon","deletion",function(e,t,n,r,i){var s=r.doc.getTextRange(i);if(!i.isMultiLine()&&s===":"){var u=n.getCursorPosition(),a=new o(r,u.row,u.column),f=a.getCurrentToken();f&&f.value.match(/\s+/)&&(f=a.stepBackward());if(f&&f.type==="support.type"){var l=r.doc.getLine(i.start.row),c=l.substring(i.end.column,i.end.column+1);if(c===";")return i.end.column++,i}}}),this.add("semicolon","insertion",function(e,t,n,r,i){if(i===";"&&n.selection.isEmpty()){var s=n.getCursorPosition(),o=r.doc.getLine(s.row),u=o.substring(s.column,s.column+1);if(u===";")return{text:"",selection:[1,1]}}}),this.add("!important","insertion",function(e,t,n,r,i){if(i==="!"&&n.selection.isEmpty()){var s=n.getCursorPosition(),o=r.doc.getLine(s.row);if(/^\s*(;|}|$)/.test(o.substring(s.column)))return{text:"!important",selection:[10,10]}}})};r.inherits(u,s),t.CssBehaviour=u}),define("ace/mode/css",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/css_highlight_rules","ace/mode/matching_brace_outdent","ace/worker/worker_client","ace/mode/css_completions","ace/mode/behaviour/css","ace/mode/folding/cstyle"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./css_highlight_rules").CssHighlightRules,o=e("./matching_brace_outdent").MatchingBraceOutdent,u=e("../worker/worker_client").WorkerClient,a=e("./css_completions").CssCompletions,f=e("./behaviour/css").CssBehaviour,l=e("./folding/cstyle").FoldMode,c=function(){this.HighlightRules=s,this.$outdent=new o,this.$behaviour=new f,this.$completer=new a,this.foldingRules=new l};r.inherits(c,i),function(){this.foldingRules="cStyle",this.blockComment={start:"/*",end:"*/"},this.getNextLineIndent=function(e,t,n){var r=this.$getIndent(t),i=this.getTokenizer().getLineTokens(t,e).tokens;if(i.length&&i[i.length-1].type=="comment")return r;var s=t.match(/^.*\{\s*$/);return s&&(r+=n),r},this.checkOutdent=function(e,t,n){return this.$outdent.checkOutdent(t,n)},this.autoOutdent=function(e,t,n){this.$outdent.autoOutdent(t,n)},this.getCompletions=function(e,t,n,r){return this.$completer.getCompletions(e,t,n,r)},this.createWorker=function(e){var t=new u(["ace"],"ace/mode/css_worker","Worker");return t.attachToDocument(e.getDocument()),t.on("annotate",function(t){e.setAnnotations(t.data)}),t.on("terminate",function(){e.clearAnnotations()}),t},this.$id="ace/mode/css"}.call(c.prototype),t.Mode=c}),define("ace/mode/xml_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text_highlight_rules").TextHighlightRules,s=function(e){var t="[_:a-zA-Z\u00c0-\uffff][-_:.a-zA-Z0-9\u00c0-\uffff]*";this.$rules={start:[{token:"string.cdata.xml",regex:"<\\!\\[CDATA\\[",next:"cdata"},{token:["punctuation.instruction.xml","keyword.instruction.xml"],regex:"(<\\?)("+t+")",next:"processing_instruction"},{token:"comment.start.xml",regex:"<\\!--",next:"comment"},{token:["xml-pe.doctype.xml","xml-pe.doctype.xml"],regex:"(<\\!)(DOCTYPE)(?=[\\s])",next:"doctype",caseInsensitive:!0},{include:"tag"},{token:"text.end-tag-open.xml",regex:"",next:"start"}],doctype:[{include:"whitespace"},{include:"string"},{token:"xml-pe.doctype.xml",regex:">",next:"start"},{token:"xml-pe.xml",regex:"[-_a-zA-Z0-9:]+"},{token:"punctuation.int-subset",regex:"\\[",push:"int_subset"}],int_subset:[{token:"text.xml",regex:"\\s+"},{token:"punctuation.int-subset.xml",regex:"]",next:"pop"},{token:["punctuation.markup-decl.xml","keyword.markup-decl.xml"],regex:"(<\\!)("+t+")",push:[{token:"text",regex:"\\s+"},{token:"punctuation.markup-decl.xml",regex:">",next:"pop"},{include:"string"}]}],cdata:[{token:"string.cdata.xml",regex:"\\]\\]>",next:"start"},{token:"text.xml",regex:"\\s+"},{token:"text.xml",regex:"(?:[^\\]]|\\](?!\\]>))+"}],comment:[{token:"comment.end.xml",regex:"-->",next:"start"},{defaultToken:"comment.xml"}],reference:[{token:"constant.language.escape.reference.xml",regex:"(?:&#[0-9]+;)|(?:&#x[0-9a-fA-F]+;)|(?:&[a-zA-Z0-9_:\\.-]+;)"}],attr_reference:[{token:"constant.language.escape.reference.attribute-value.xml",regex:"(?:&#[0-9]+;)|(?:&#x[0-9a-fA-F]+;)|(?:&[a-zA-Z0-9_:\\.-]+;)"}],tag:[{token:["meta.tag.punctuation.tag-open.xml","meta.tag.punctuation.end-tag-open.xml","meta.tag.tag-name.xml"],regex:"(?:(<)|(",next:"start"}]}],tag_whitespace:[{token:"text.tag-whitespace.xml",regex:"\\s+"}],whitespace:[{token:"text.whitespace.xml",regex:"\\s+"}],string:[{token:"string.xml",regex:"'",push:[{token:"string.xml",regex:"'",next:"pop"},{defaultToken:"string.xml"}]},{token:"string.xml",regex:'"',push:[{token:"string.xml",regex:'"',next:"pop"},{defaultToken:"string.xml"}]}],attributes:[{token:"entity.other.attribute-name.xml",regex:t},{token:"keyword.operator.attribute-equals.xml",regex:"="},{include:"tag_whitespace"},{include:"attribute_value"}],attribute_value:[{token:"string.attribute-value.xml",regex:"'",push:[{token:"string.attribute-value.xml",regex:"'",next:"pop"},{include:"attr_reference"},{defaultToken:"string.attribute-value.xml"}]},{token:"string.attribute-value.xml",regex:'"',push:[{token:"string.attribute-value.xml",regex:'"',next:"pop"},{include:"attr_reference"},{defaultToken:"string.attribute-value.xml"}]}]},this.constructor===s&&this.normalizeRules()};(function(){this.embedTagRules=function(e,t,n){this.$rules.tag.unshift({token:["meta.tag.punctuation.tag-open.xml","meta.tag."+n+".tag-name.xml"],regex:"(<)("+n+"(?=\\s|>|$))",next:[{include:"attributes"},{token:"meta.tag.punctuation.tag-close.xml",regex:"/?>",next:t+"start"}]}),this.$rules[n+"-end"]=[{include:"attributes"},{token:"meta.tag.punctuation.tag-close.xml",regex:"/?>",next:"start",onMatch:function(e,t,n){return n.splice(0),this.token}}],this.embedRules(e,t,[{token:["meta.tag.punctuation.end-tag-open.xml","meta.tag."+n+".tag-name.xml"],regex:"(|$))",next:n+"-end"},{token:"string.cdata.xml",regex:"<\\!\\[CDATA\\["},{token:"string.cdata.xml",regex:"\\]\\]>"}])}}).call(i.prototype),r.inherits(s,i),t.XmlHighlightRules=s}),define("ace/mode/html_highlight_rules",["require","exports","module","ace/lib/oop","ace/lib/lang","ace/mode/css_highlight_rules","ace/mode/javascript_highlight_rules","ace/mode/xml_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("../lib/lang"),s=e("./css_highlight_rules").CssHighlightRules,o=e("./javascript_highlight_rules").JavaScriptHighlightRules,u=e("./xml_highlight_rules").XmlHighlightRules,a=i.createMap({a:"anchor",button:"form",form:"form",img:"image",input:"form",label:"form",option:"form",script:"script",select:"form",textarea:"form",style:"style",table:"table",tbody:"table",td:"table",tfoot:"table",th:"table",tr:"table"}),f=function(){u.call(this),this.addRules({attributes:[{include:"tag_whitespace"},{token:"entity.other.attribute-name.xml",regex:"[-_a-zA-Z0-9:.]+"},{token:"keyword.operator.attribute-equals.xml",regex:"=",push:[{include:"tag_whitespace"},{token:"string.unquoted.attribute-value.html",regex:"[^<>='\"`\\s]+",next:"pop"},{token:"empty",regex:"",next:"pop"}]},{include:"attribute_value"}],tag:[{token:function(e,t){var n=a[t];return["meta.tag.punctuation."+(e=="<"?"":"end-")+"tag-open.xml","meta.tag"+(n?"."+n:"")+".tag-name.xml"]},regex:"(",next:"start"}]}),this.embedTagRules(s,"css-","style"),this.embedTagRules((new o({jsx:!1})).getRules(),"js-","script"),this.constructor===f&&this.normalizeRules()};r.inherits(f,u),t.HtmlHighlightRules=f}),define("ace/mode/behaviour/xml",["require","exports","module","ace/lib/oop","ace/mode/behaviour","ace/token_iterator","ace/lib/lang"],function(e,t,n){"use strict";function u(e,t){return e&&e.type.lastIndexOf(t+".xml")>-1}var r=e("../../lib/oop"),i=e("../behaviour").Behaviour,s=e("../../token_iterator").TokenIterator,o=e("../../lib/lang"),a=function(){this.add("string_dquotes","insertion",function(e,t,n,r,i){if(i=='"'||i=="'"){var o=i,a=r.doc.getTextRange(n.getSelectionRange());if(a!==""&&a!=="'"&&a!='"'&&n.getWrapBehavioursEnabled())return{text:o+a+o,selection:!1};var f=n.getCursorPosition(),l=r.doc.getLine(f.row),c=l.substring(f.column,f.column+1),h=new s(r,f.row,f.column),p=h.getCurrentToken();if(c==o&&(u(p,"attribute-value")||u(p,"string")))return{text:"",selection:[1,1]};p||(p=h.stepBackward());if(!p)return;while(u(p,"tag-whitespace")||u(p,"whitespace"))p=h.stepBackward();var d=!c||c.match(/\s/);if(u(p,"attribute-equals")&&(d||c==">")||u(p,"decl-attribute-equals")&&(d||c=="?"))return{text:o+o,selection:[1,1]}}}),this.add("string_dquotes","deletion",function(e,t,n,r,i){var s=r.doc.getTextRange(i);if(!i.isMultiLine()&&(s=='"'||s=="'")){var o=r.doc.getLine(i.start.row),u=o.substring(i.start.column+1,i.start.column+2);if(u==s)return i.end.column++,i}}),this.add("autoclosing","insertion",function(e,t,n,r,i){if(i==">"){var o=n.getSelectionRange().start,a=new s(r,o.row,o.column),f=a.getCurrentToken()||a.stepBackward();if(!f||!(u(f,"tag-name")||u(f,"tag-whitespace")||u(f,"attribute-name")||u(f,"attribute-equals")||u(f,"attribute-value")))return;if(u(f,"reference.attribute-value"))return;if(u(f,"attribute-value")){var l=a.getCurrentTokenColumn()+f.value.length;if(o.column/.test(r.getLine(o.row).slice(o.column)))return;while(!u(f,"tag-name")){f=a.stepBackward();if(f.value=="<"){f=a.stepForward();break}}var h=a.getCurrentTokenRow(),p=a.getCurrentTokenColumn();if(u(a.stepBackward(),"end-tag-open"))return;var d=f.value;h==o.row&&(d=d.substring(0,o.column-p));if(this.voidElements.hasOwnProperty(d.toLowerCase()))return;return{text:">",selection:[1,1]}}}),this.add("autoindent","insertion",function(e,t,n,r,i){if(i=="\n"){var o=n.getCursorPosition(),u=r.getLine(o.row),a=new s(r,o.row,o.column),f=a.getCurrentToken();if(f&&f.type.indexOf("tag-close")!==-1){if(f.value=="/>")return;while(f&&f.type.indexOf("tag-name")===-1)f=a.stepBackward();if(!f)return;var l=f.value,c=a.getCurrentTokenRow();f=a.stepBackward();if(!f||f.type.indexOf("end-tag")!==-1)return;if(this.voidElements&&!this.voidElements[l]){var h=r.getTokenAt(o.row,o.column+1),u=r.getLine(c),p=this.$getIndent(u),d=p+r.getTabString();return h&&h.value==="-1}var r=e("../../lib/oop"),i=e("../../lib/lang"),s=e("../../range").Range,o=e("./fold_mode").FoldMode,u=e("../../token_iterator").TokenIterator,a=t.FoldMode=function(e,t){o.call(this),this.voidElements=e||{},this.optionalEndTags=r.mixin({},this.voidElements),t&&r.mixin(this.optionalEndTags,t)};r.inherits(a,o);var f=function(){this.tagName="",this.closing=!1,this.selfClosing=!1,this.start={row:0,column:0},this.end={row:0,column:0}};(function(){this.getFoldWidget=function(e,t,n){var r=this._getFirstTagInLine(e,n);return r?r.closing||!r.tagName&&r.selfClosing?t=="markbeginend"?"end":"":!r.tagName||r.selfClosing||this.voidElements.hasOwnProperty(r.tagName.toLowerCase())?"":this._findEndTagInLine(e,n,r.tagName,r.end.column)?"":"start":this.getCommentFoldWidget(e,n)},this.getCommentFoldWidget=function(e,t){return/comment/.test(e.getState(t))&&/";break}}return r}if(l(s,"tag-close"))return r.selfClosing=s.value=="/>",r;r.start.column+=s.value.length}return null},this._findEndTagInLine=function(e,t,n,r){var i=e.getTokens(t),s=0;for(var o=0;o",n.end.row=e.getCurrentTokenRow(),n.end.column=e.getCurrentTokenColumn()+t.value.length,e.stepForward(),n;while(t=e.stepForward());return null},this._readTagBackward=function(e){var t=e.getCurrentToken();if(!t)return null;var n=new f;do{if(l(t,"tag-open"))return n.closing=l(t,"end-tag-open"),n.start.row=e.getCurrentTokenRow(),n.start.column=e.getCurrentTokenColumn(),e.stepBackward(),n;l(t,"tag-name")?n.tagName=t.value:l(t,"tag-close")&&(n.selfClosing=t.value=="/>",n.end.row=e.getCurrentTokenRow(),n.end.column=e.getCurrentTokenColumn()+t.value.length)}while(t=e.stepBackward());return null},this._pop=function(e,t){while(e.length){var n=e[e.length-1];if(!t||n.tagName==t.tagName)return e.pop();if(this.optionalEndTags.hasOwnProperty(n.tagName)){e.pop();continue}return null}},this.getFoldWidgetRange=function(e,t,n){var r=this._getFirstTagInLine(e,n);if(!r)return this.getCommentFoldWidget(e,n)&&e.getCommentFoldRange(n,e.getLine(n).length);var i=r.closing||r.selfClosing,o=[],a;if(!i){var f=new u(e,n,r.start.column),l={row:n,column:r.start.column+r.tagName.length+2};r.start.row==r.end.row&&(l.column=r.end.column);while(a=this._readTagForward(f)){if(a.selfClosing){if(!o.length)return a.start.column+=a.tagName.length+2,a.end.column-=2,s.fromPoints(a.start,a.end);continue}if(a.closing){this._pop(o,a);if(o.length==0)return s.fromPoints(l,a.start)}else o.push(a)}}else{var f=new u(e,n,r.end.column),c={row:n,column:r.start.column};while(a=this._readTagBackward(f)){if(a.selfClosing){if(!o.length)return a.start.column+=a.tagName.length+2,a.end.column-=2,s.fromPoints(a.start,a.end);continue}if(!a.closing){this._pop(o,a);if(o.length==0)return a.start.column+=a.tagName.length+2,a.start.row==a.end.row&&a.start.column-1}function l(e,t){var n=new r(e,t.row,t.column),i=n.getCurrentToken();while(i&&!f(i,"tag-name"))i=n.stepBackward();if(i)return i.value}function c(e,t){var n=new r(e,t.row,t.column),i=n.getCurrentToken();while(i&&!f(i,"attribute-name"))i=n.stepBackward();if(i)return i.value}var r=e("../token_iterator").TokenIterator,i=["accesskey","class","contenteditable","contextmenu","dir","draggable","dropzone","hidden","id","inert","itemid","itemprop","itemref","itemscope","itemtype","lang","spellcheck","style","tabindex","title","translate"],s=["onabort","onblur","oncancel","oncanplay","oncanplaythrough","onchange","onclick","onclose","oncontextmenu","oncuechange","ondblclick","ondrag","ondragend","ondragenter","ondragleave","ondragover","ondragstart","ondrop","ondurationchange","onemptied","onended","onerror","onfocus","oninput","oninvalid","onkeydown","onkeypress","onkeyup","onload","onloadeddata","onloadedmetadata","onloadstart","onmousedown","onmousemove","onmouseout","onmouseover","onmouseup","onmousewheel","onpause","onplay","onplaying","onprogress","onratechange","onreset","onscroll","onseeked","onseeking","onselect","onshow","onstalled","onsubmit","onsuspend","ontimeupdate","onvolumechange","onwaiting"],o=i.concat(s),u={a:{href:1,target:{_blank:1,top:1},ping:1,rel:{nofollow:1,alternate:1,author:1,bookmark:1,help:1,license:1,next:1,noreferrer:1,prefetch:1,prev:1,search:1,tag:1},media:1,hreflang:1,type:1},abbr:{},address:{},area:{shape:1,coords:1,href:1,hreflang:1,alt:1,target:1,media:1,rel:1,ping:1,type:1},article:{pubdate:1},aside:{},audio:{src:1,autobuffer:1,autoplay:{autoplay:1},loop:{loop:1},controls:{controls:1},muted:{muted:1},preload:{auto:1,metadata:1,none:1}},b:{},base:{href:1,target:1},bdi:{},bdo:{},blockquote:{cite:1},body:{onafterprint:1,onbeforeprint:1,onbeforeunload:1,onhashchange:1,onmessage:1,onoffline:1,onpopstate:1,onredo:1,onresize:1,onstorage:1,onundo:1,onunload:1},br:{},button:{autofocus:1,disabled:{disabled:1},form:1,formaction:1,formenctype:1,formmethod:1,formnovalidate:1,formtarget:1,name:1,value:1,type:{button:1,submit:1}},canvas:{width:1,height:1},caption:{},cite:{},code:{},col:{span:1},colgroup:{span:1},command:{type:1,label:1,icon:1,disabled:1,checked:1,radiogroup:1,command:1},data:{},datalist:{},dd:{},del:{cite:1,datetime:1},details:{open:1},dfn:{},dialog:{open:1},div:{},dl:{},dt:{},em:{},embed:{src:1,height:1,width:1,type:1},fieldset:{disabled:1,form:1,name:1},figcaption:{},figure:{},footer:{},form:{"accept-charset":1,action:1,autocomplete:1,enctype:{"multipart/form-data":1,"application/x-www-form-urlencoded":1},method:{get:1,post:1},name:1,novalidate:1,target:{_blank:1,top:1}},h1:{},h2:{},h3:{},h4:{},h5:{},h6:{},head:{},header:{},hr:{},html:{manifest:1},i:{},iframe:{name:1,src:1,height:1,width:1,sandbox:{"allow-same-origin":1,"allow-top-navigation":1,"allow-forms":1,"allow-scripts":1},seamless:{seamless:1}},img:{alt:1,src:1,height:1,width:1,usemap:1,ismap:1},input:{type:{text:1,password:1,hidden:1,checkbox:1,submit:1,radio:1,file:1,button:1,reset:1,image:31,color:1,date:1,datetime:1,"datetime-local":1,email:1,month:1,number:1,range:1,search:1,tel:1,time:1,url:1,week:1},accept:1,alt:1,autocomplete:{on:1,off:1},autofocus:{autofocus:1},checked:{checked:1},disabled:{disabled:1},form:1,formaction:1,formenctype:{"application/x-www-form-urlencoded":1,"multipart/form-data":1,"text/plain":1},formmethod:{get:1,post:1},formnovalidate:{formnovalidate:1},formtarget:{_blank:1,_self:1,_parent:1,_top:1},height:1,list:1,max:1,maxlength:1,min:1,multiple:{multiple:1},name:1,pattern:1,placeholder:1,readonly:{readonly:1},required:{required:1},size:1,src:1,step:1,width:1,files:1,value:1},ins:{cite:1,datetime:1},kbd:{},keygen:{autofocus:1,challenge:{challenge:1},disabled:{disabled:1},form:1,keytype:{rsa:1,dsa:1,ec:1},name:1},label:{form:1,"for":1},legend:{},li:{value:1},link:{href:1,hreflang:1,rel:{stylesheet:1,icon:1},media:{all:1,screen:1,print:1},type:{"text/css":1,"image/png":1,"image/jpeg":1,"image/gif":1},sizes:1},main:{},map:{name:1},mark:{},math:{},menu:{type:1,label:1},meta:{"http-equiv":{"content-type":1},name:{description:1,keywords:1},content:{"text/html; charset=UTF-8":1},charset:1},meter:{value:1,min:1,max:1,low:1,high:1,optimum:1},nav:{},noscript:{href:1},object:{param:1,data:1,type:1,height:1,width:1,usemap:1,name:1,form:1,classid:1},ol:{start:1,reversed:1},optgroup:{disabled:1,label:1},option:{disabled:1,selected:1,label:1,value:1},output:{"for":1,form:1,name:1},p:{},param:{name:1,value:1},pre:{},progress:{value:1,max:1},q:{cite:1},rp:{},rt:{},ruby:{},s:{},samp:{},script:{charset:1,type:{"text/javascript":1},src:1,defer:1,async:1},select:{autofocus:1,disabled:1,form:1,multiple:{multiple:1},name:1,size:1,readonly:{readonly:1}},small:{},source:{src:1,type:1,media:1},span:{},strong:{},style:{type:1,media:{all:1,screen:1,print:1},scoped:1},sub:{},sup:{},svg:{},table:{summary:1},tbody:{},td:{headers:1,rowspan:1,colspan:1},textarea:{autofocus:{autofocus:1},disabled:{disabled:1},form:1,maxlength:1,name:1,placeholder:1,readonly:{readonly:1},required:{required:1},rows:1,cols:1,wrap:{on:1,off:1,hard:1,soft:1}},tfoot:{},th:{headers:1,rowspan:1,colspan:1,scope:1},thead:{},time:{datetime:1},title:{},tr:{},track:{kind:1,src:1,srclang:1,label:1,"default":1},section:{},summary:{},u:{},ul:{},"var":{},video:{src:1,autobuffer:1,autoplay:{autoplay:1},loop:{loop:1},controls:{controls:1},width:1,height:1,poster:1,muted:{muted:1},preload:{auto:1,metadata:1,none:1}},wbr:{}},a=Object.keys(u),h=function(){};(function(){this.getCompletions=function(e,t,n,r){var i=t.getTokenAt(n.row,n.column);if(!i)return[];if(f(i,"tag-name")||f(i,"tag-open")||f(i,"end-tag-open"))return this.getTagCompletions(e,t,n,r);if(f(i,"tag-whitespace")||f(i,"attribute-name"))return this.getAttributeCompletions(e,t,n,r);if(f(i,"attribute-value"))return this.getAttributeValueCompletions(e,t,n,r);var s=t.getLine(n.row).substr(0,n.column);return/&[a-z]*$/i.test(s)?this.getHTMLEntityCompletions(e,t,n,r):[]},this.getTagCompletions=function(e,t,n,r){return a.map(function(e){return{value:e,meta:"tag",score:1e6}})},this.getAttributeCompletions=function(e,t,n,r){var i=l(t,n);if(!i)return[];var s=o;return i in u&&(s=s.concat(Object.keys(u[i]))),s.map(function(e){return{caption:e,snippet:e+'="$0"',meta:"attribute",score:1e6}})},this.getAttributeValueCompletions=function(e,t,n,r){var i=l(t,n),s=c(t,n);if(!i)return[];var o=[];return i in u&&s in u[i]&&typeof u[i][s]=="object"&&(o=Object.keys(u[i][s])),o.map(function(e){return{caption:e,snippet:e,meta:"attribute value",score:1e6}})},this.getHTMLEntityCompletions=function(e,t,n,r){var i=["Aacute;","aacute;","Acirc;","acirc;","acute;","AElig;","aelig;","Agrave;","agrave;","alefsym;","Alpha;","alpha;","amp;","and;","ang;","Aring;","aring;","asymp;","Atilde;","atilde;","Auml;","auml;","bdquo;","Beta;","beta;","brvbar;","bull;","cap;","Ccedil;","ccedil;","cedil;","cent;","Chi;","chi;","circ;","clubs;","cong;","copy;","crarr;","cup;","curren;","Dagger;","dagger;","dArr;","darr;","deg;","Delta;","delta;","diams;","divide;","Eacute;","eacute;","Ecirc;","ecirc;","Egrave;","egrave;","empty;","emsp;","ensp;","Epsilon;","epsilon;","equiv;","Eta;","eta;","ETH;","eth;","Euml;","euml;","euro;","exist;","fnof;","forall;","frac12;","frac14;","frac34;","frasl;","Gamma;","gamma;","ge;","gt;","hArr;","harr;","hearts;","hellip;","Iacute;","iacute;","Icirc;","icirc;","iexcl;","Igrave;","igrave;","image;","infin;","int;","Iota;","iota;","iquest;","isin;","Iuml;","iuml;","Kappa;","kappa;","Lambda;","lambda;","lang;","laquo;","lArr;","larr;","lceil;","ldquo;","le;","lfloor;","lowast;","loz;","lrm;","lsaquo;","lsquo;","lt;","macr;","mdash;","micro;","middot;","minus;","Mu;","mu;","nabla;","nbsp;","ndash;","ne;","ni;","not;","notin;","nsub;","Ntilde;","ntilde;","Nu;","nu;","Oacute;","oacute;","Ocirc;","ocirc;","OElig;","oelig;","Ograve;","ograve;","oline;","Omega;","omega;","Omicron;","omicron;","oplus;","or;","ordf;","ordm;","Oslash;","oslash;","Otilde;","otilde;","otimes;","Ouml;","ouml;","para;","part;","permil;","perp;","Phi;","phi;","Pi;","pi;","piv;","plusmn;","pound;","Prime;","prime;","prod;","prop;","Psi;","psi;","quot;","radic;","rang;","raquo;","rArr;","rarr;","rceil;","rdquo;","real;","reg;","rfloor;","Rho;","rho;","rlm;","rsaquo;","rsquo;","sbquo;","Scaron;","scaron;","sdot;","sect;","shy;","Sigma;","sigma;","sigmaf;","sim;","spades;","sub;","sube;","sum;","sup;","sup1;","sup2;","sup3;","supe;","szlig;","Tau;","tau;","there4;","Theta;","theta;","thetasym;","thinsp;","THORN;","thorn;","tilde;","times;","trade;","Uacute;","uacute;","uArr;","uarr;","Ucirc;","ucirc;","Ugrave;","ugrave;","uml;","upsih;","Upsilon;","upsilon;","Uuml;","uuml;","weierp;","Xi;","xi;","Yacute;","yacute;","yen;","Yuml;","yuml;","Zeta;","zeta;","zwj;","zwnj;"];return i.map(function(e){return{caption:e,snippet:e,meta:"html entity",score:1e6}})}}).call(h.prototype),t.HtmlCompletions=h}),define("ace/mode/html",["require","exports","module","ace/lib/oop","ace/lib/lang","ace/mode/text","ace/mode/javascript","ace/mode/css","ace/mode/html_highlight_rules","ace/mode/behaviour/xml","ace/mode/folding/html","ace/mode/html_completions","ace/worker/worker_client"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("../lib/lang"),s=e("./text").Mode,o=e("./javascript").Mode,u=e("./css").Mode,a=e("./html_highlight_rules").HtmlHighlightRules,f=e("./behaviour/xml").XmlBehaviour,l=e("./folding/html").FoldMode,c=e("./html_completions").HtmlCompletions,h=e("../worker/worker_client").WorkerClient,p=["area","base","br","col","embed","hr","img","input","keygen","link","meta","menuitem","param","source","track","wbr"],d=["li","dt","dd","p","rt","rp","optgroup","option","colgroup","td","th"],v=function(e){this.fragmentContext=e&&e.fragmentContext,this.HighlightRules=a,this.$behaviour=new f,this.$completer=new c,this.createModeDelegates({"js-":o,"css-":u}),this.foldingRules=new l(this.voidElements,i.arrayToMap(d))};r.inherits(v,s),function(){this.blockComment={start:""},this.voidElements=i.arrayToMap(p),this.getNextLineIndent=function(e,t,n){return this.$getIndent(t)},this.checkOutdent=function(e,t,n){return!1},this.getCompletions=function(e,t,n,r){return this.$completer.getCompletions(e,t,n,r)},this.createWorker=function(e){if(this.constructor!=v)return;var t=new h(["ace"],"ace/mode/html_worker","Worker");return t.attachToDocument(e.getDocument()),this.fragmentContext&&t.call("setOptions",[{context:this.fragmentContext}]),t.on("error",function(t){e.setAnnotations(t.data)}),t.on("terminate",function(){e.clearAnnotations()}),t},this.$id="ace/mode/html"}.call(v.prototype),t.Mode=v}); (function() { + window.require(["ace/mode/html"], function(m) { + if (typeof module == "object" && typeof exports == "object" && module) { + module.exports = m; + } + }); + })(); \ No newline at end of file diff --git a/app/assets/frontends/beaker-code-snippet/ace/mode-javascript.js b/app/assets/frontends/beaker-code-snippet/ace/mode-javascript.js new file mode 100644 index 0000000000..210fb5767e --- /dev/null +++ b/app/assets/frontends/beaker-code-snippet/ace/mode-javascript.js @@ -0,0 +1,7 @@ +define("ace/mode/doc_comment_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text_highlight_rules").TextHighlightRules,s=function(){this.$rules={start:[{token:"comment.doc.tag",regex:"@[\\w\\d_]+"},s.getTagRule(),{defaultToken:"comment.doc",caseInsensitive:!0}]}};r.inherits(s,i),s.getTagRule=function(e){return{token:"comment.doc.tag.storage.type",regex:"\\b(?:TODO|FIXME|XXX|HACK)\\b"}},s.getStartRule=function(e){return{token:"comment.doc",regex:"\\/\\*(?=\\*)",next:e}},s.getEndRule=function(e){return{token:"comment.doc",regex:"\\*\\/",next:e}},t.DocCommentHighlightRules=s}),define("ace/mode/javascript_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/doc_comment_highlight_rules","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";function a(){var e=o.replace("\\d","\\d\\-"),t={onMatch:function(e,t,n){var r=e.charAt(1)=="/"?2:1;if(r==1)t!=this.nextState?n.unshift(this.next,this.nextState,0):n.unshift(this.next),n[2]++;else if(r==2&&t==this.nextState){n[1]--;if(!n[1]||n[1]<0)n.shift(),n.shift()}return[{type:"meta.tag.punctuation."+(r==1?"":"end-")+"tag-open.xml",value:e.slice(0,r)},{type:"meta.tag.tag-name.xml",value:e.substr(r)}]},regex:"",onMatch:function(e,t,n){return t==n[0]&&n.shift(),e.length==2&&(n[0]==this.nextState&&n[1]--,(!n[1]||n[1]<0)&&n.splice(0,2)),this.next=n[0]||"start",[{type:this.token,value:e}]},nextState:"jsx"},n,f("jsxAttributes"),{token:"entity.other.attribute-name.xml",regex:e},{token:"keyword.operator.attribute-equals.xml",regex:"="},{token:"text.tag-whitespace.xml",regex:"\\s+"},{token:"string.attribute-value.xml",regex:"'",stateName:"jsx_attr_q",push:[{token:"string.attribute-value.xml",regex:"'",next:"pop"},{include:"reference"},{defaultToken:"string.attribute-value.xml"}]},{token:"string.attribute-value.xml",regex:'"',stateName:"jsx_attr_qq",push:[{token:"string.attribute-value.xml",regex:'"',next:"pop"},{include:"reference"},{defaultToken:"string.attribute-value.xml"}]},t],this.$rules.reference=[{token:"constant.language.escape.reference.xml",regex:"(?:&#[0-9]+;)|(?:&#x[0-9a-fA-F]+;)|(?:&[a-zA-Z0-9_:\\.-]+;)"}]}function f(e){return[{token:"comment",regex:/\/\*/,next:[i.getTagRule(),{token:"comment",regex:"\\*\\/",next:e||"pop"},{defaultToken:"comment",caseInsensitive:!0}]},{token:"comment",regex:"\\/\\/",next:[i.getTagRule(),{token:"comment",regex:"$|^",next:e||"pop"},{defaultToken:"comment",caseInsensitive:!0}]}]}var r=e("../lib/oop"),i=e("./doc_comment_highlight_rules").DocCommentHighlightRules,s=e("./text_highlight_rules").TextHighlightRules,o="[a-zA-Z\\$_\u00a1-\uffff][a-zA-Z\\d\\$_\u00a1-\uffff]*",u=function(e){var t=this.createKeywordMapper({"variable.language":"Array|Boolean|Date|Function|Iterator|Number|Object|RegExp|String|Proxy|Namespace|QName|XML|XMLList|ArrayBuffer|Float32Array|Float64Array|Int16Array|Int32Array|Int8Array|Uint16Array|Uint32Array|Uint8Array|Uint8ClampedArray|Error|EvalError|InternalError|RangeError|ReferenceError|StopIteration|SyntaxError|TypeError|URIError|decodeURI|decodeURIComponent|encodeURI|encodeURIComponent|eval|isFinite|isNaN|parseFloat|parseInt|JSON|Math|this|arguments|prototype|window|document",keyword:"const|yield|import|get|set|async|await|break|case|catch|continue|default|delete|do|else|finally|for|function|if|in|of|instanceof|new|return|switch|throw|try|typeof|let|var|while|with|debugger|__parent__|__count__|escape|unescape|with|__proto__|class|enum|extends|super|export|implements|private|public|interface|package|protected|static","storage.type":"const|let|var|function","constant.language":"null|Infinity|NaN|undefined","support.function":"alert","constant.language.boolean":"true|false"},"identifier"),n="case|do|else|finally|in|instanceof|return|throw|try|typeof|yield|void",r="\\\\(?:x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|u{[0-9a-fA-F]{1,6}}|[0-2][0-7]{0,2}|3[0-7][0-7]?|[4-7][0-7]?|.)";this.$rules={no_regex:[i.getStartRule("doc-start"),f("no_regex"),{token:"string",regex:"'(?=.)",next:"qstring"},{token:"string",regex:'"(?=.)',next:"qqstring"},{token:"constant.numeric",regex:/0(?:[xX][0-9a-fA-F]+|[oO][0-7]+|[bB][01]+)\b/},{token:"constant.numeric",regex:/(?:\d\d*(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+\b)?/},{token:["storage.type","punctuation.operator","support.function","punctuation.operator","entity.name.function","text","keyword.operator"],regex:"("+o+")(\\.)(prototype)(\\.)("+o+")(\\s*)(=)",next:"function_arguments"},{token:["storage.type","punctuation.operator","entity.name.function","text","keyword.operator","text","storage.type","text","paren.lparen"],regex:"("+o+")(\\.)("+o+")(\\s*)(=)(\\s*)(function)(\\s*)(\\()",next:"function_arguments"},{token:["entity.name.function","text","keyword.operator","text","storage.type","text","paren.lparen"],regex:"("+o+")(\\s*)(=)(\\s*)(function)(\\s*)(\\()",next:"function_arguments"},{token:["storage.type","punctuation.operator","entity.name.function","text","keyword.operator","text","storage.type","text","entity.name.function","text","paren.lparen"],regex:"("+o+")(\\.)("+o+")(\\s*)(=)(\\s*)(function)(\\s+)(\\w+)(\\s*)(\\()",next:"function_arguments"},{token:["storage.type","text","entity.name.function","text","paren.lparen"],regex:"(function)(\\s+)("+o+")(\\s*)(\\()",next:"function_arguments"},{token:["entity.name.function","text","punctuation.operator","text","storage.type","text","paren.lparen"],regex:"("+o+")(\\s*)(:)(\\s*)(function)(\\s*)(\\()",next:"function_arguments"},{token:["text","text","storage.type","text","paren.lparen"],regex:"(:)(\\s*)(function)(\\s*)(\\()",next:"function_arguments"},{token:"keyword",regex:"from(?=\\s*('|\"))"},{token:"keyword",regex:"(?:"+n+")\\b",next:"start"},{token:["support.constant"],regex:/that\b/},{token:["storage.type","punctuation.operator","support.function.firebug"],regex:/(console)(\.)(warn|info|log|error|time|trace|timeEnd|assert)\b/},{token:t,regex:o},{token:"punctuation.operator",regex:/[.](?![.])/,next:"property"},{token:"storage.type",regex:/=>/,next:"start"},{token:"keyword.operator",regex:/--|\+\+|\.{3}|===|==|=|!=|!==|<+=?|>+=?|!|&&|\|\||\?:|[!$%&*+\-~\/^]=?/,next:"start"},{token:"punctuation.operator",regex:/[?:,;.]/,next:"start"},{token:"paren.lparen",regex:/[\[({]/,next:"start"},{token:"paren.rparen",regex:/[\])}]/},{token:"comment",regex:/^#!.*$/}],property:[{token:"text",regex:"\\s+"},{token:["storage.type","punctuation.operator","entity.name.function","text","keyword.operator","text","storage.type","text","entity.name.function","text","paren.lparen"],regex:"("+o+")(\\.)("+o+")(\\s*)(=)(\\s*)(function)(?:(\\s+)(\\w+))?(\\s*)(\\()",next:"function_arguments"},{token:"punctuation.operator",regex:/[.](?![.])/},{token:"support.function",regex:/(s(?:h(?:ift|ow(?:Mod(?:elessDialog|alDialog)|Help))|croll(?:X|By(?:Pages|Lines)?|Y|To)?|t(?:op|rike)|i(?:n|zeToContent|debar|gnText)|ort|u(?:p|b(?:str(?:ing)?)?)|pli(?:ce|t)|e(?:nd|t(?:Re(?:sizable|questHeader)|M(?:i(?:nutes|lliseconds)|onth)|Seconds|Ho(?:tKeys|urs)|Year|Cursor|Time(?:out)?|Interval|ZOptions|Date|UTC(?:M(?:i(?:nutes|lliseconds)|onth)|Seconds|Hours|Date|FullYear)|FullYear|Active)|arch)|qrt|lice|avePreferences|mall)|h(?:ome|andleEvent)|navigate|c(?:har(?:CodeAt|At)|o(?:s|n(?:cat|textual|firm)|mpile)|eil|lear(?:Timeout|Interval)?|a(?:ptureEvents|ll)|reate(?:StyleSheet|Popup|EventObject))|t(?:o(?:GMTString|S(?:tring|ource)|U(?:TCString|pperCase)|Lo(?:caleString|werCase))|est|a(?:n|int(?:Enabled)?))|i(?:s(?:NaN|Finite)|ndexOf|talics)|d(?:isableExternalCapture|ump|etachEvent)|u(?:n(?:shift|taint|escape|watch)|pdateCommands)|j(?:oin|avaEnabled)|p(?:o(?:p|w)|ush|lugins.refresh|a(?:ddings|rse(?:Int|Float)?)|r(?:int|ompt|eference))|e(?:scape|nableExternalCapture|val|lementFromPoint|x(?:p|ec(?:Script|Command)?))|valueOf|UTC|queryCommand(?:State|Indeterm|Enabled|Value)|f(?:i(?:nd|le(?:ModifiedDate|Size|CreatedDate|UpdatedDate)|xed)|o(?:nt(?:size|color)|rward)|loor|romCharCode)|watch|l(?:ink|o(?:ad|g)|astIndexOf)|a(?:sin|nchor|cos|t(?:tachEvent|ob|an(?:2)?)|pply|lert|b(?:s|ort))|r(?:ou(?:nd|teEvents)|e(?:size(?:By|To)|calc|turnValue|place|verse|l(?:oad|ease(?:Capture|Events)))|andom)|g(?:o|et(?:ResponseHeader|M(?:i(?:nutes|lliseconds)|onth)|Se(?:conds|lection)|Hours|Year|Time(?:zoneOffset)?|Da(?:y|te)|UTC(?:M(?:i(?:nutes|lliseconds)|onth)|Seconds|Hours|Da(?:y|te)|FullYear)|FullYear|A(?:ttention|llResponseHeaders)))|m(?:in|ove(?:B(?:y|elow)|To(?:Absolute)?|Above)|ergeAttributes|a(?:tch|rgins|x))|b(?:toa|ig|o(?:ld|rderWidths)|link|ack))\b(?=\()/},{token:"support.function.dom",regex:/(s(?:ub(?:stringData|mit)|plitText|e(?:t(?:NamedItem|Attribute(?:Node)?)|lect))|has(?:ChildNodes|Feature)|namedItem|c(?:l(?:ick|o(?:se|neNode))|reate(?:C(?:omment|DATASection|aption)|T(?:Head|extNode|Foot)|DocumentFragment|ProcessingInstruction|E(?:ntityReference|lement)|Attribute))|tabIndex|i(?:nsert(?:Row|Before|Cell|Data)|tem)|open|delete(?:Row|C(?:ell|aption)|T(?:Head|Foot)|Data)|focus|write(?:ln)?|a(?:dd|ppend(?:Child|Data))|re(?:set|place(?:Child|Data)|move(?:NamedItem|Child|Attribute(?:Node)?)?)|get(?:NamedItem|Element(?:sBy(?:Name|TagName|ClassName)|ById)|Attribute(?:Node)?)|blur)\b(?=\()/},{token:"support.constant",regex:/(s(?:ystemLanguage|cr(?:ipts|ollbars|een(?:X|Y|Top|Left))|t(?:yle(?:Sheets)?|atus(?:Text|bar)?)|ibling(?:Below|Above)|ource|uffixes|e(?:curity(?:Policy)?|l(?:ection|f)))|h(?:istory|ost(?:name)?|as(?:h|Focus))|y|X(?:MLDocument|SLDocument)|n(?:ext|ame(?:space(?:s|URI)|Prop))|M(?:IN_VALUE|AX_VALUE)|c(?:haracterSet|o(?:n(?:structor|trollers)|okieEnabled|lorDepth|mp(?:onents|lete))|urrent|puClass|l(?:i(?:p(?:boardData)?|entInformation)|osed|asses)|alle(?:e|r)|rypto)|t(?:o(?:olbar|p)|ext(?:Transform|Indent|Decoration|Align)|ags)|SQRT(?:1_2|2)|i(?:n(?:ner(?:Height|Width)|put)|ds|gnoreCase)|zIndex|o(?:scpu|n(?:readystatechange|Line)|uter(?:Height|Width)|p(?:sProfile|ener)|ffscreenBuffering)|NEGATIVE_INFINITY|d(?:i(?:splay|alog(?:Height|Top|Width|Left|Arguments)|rectories)|e(?:scription|fault(?:Status|Ch(?:ecked|arset)|View)))|u(?:ser(?:Profile|Language|Agent)|n(?:iqueID|defined)|pdateInterval)|_content|p(?:ixelDepth|ort|ersonalbar|kcs11|l(?:ugins|atform)|a(?:thname|dding(?:Right|Bottom|Top|Left)|rent(?:Window|Layer)?|ge(?:X(?:Offset)?|Y(?:Offset)?))|r(?:o(?:to(?:col|type)|duct(?:Sub)?|mpter)|e(?:vious|fix)))|e(?:n(?:coding|abledPlugin)|x(?:ternal|pando)|mbeds)|v(?:isibility|endor(?:Sub)?|Linkcolor)|URLUnencoded|P(?:I|OSITIVE_INFINITY)|f(?:ilename|o(?:nt(?:Size|Family|Weight)|rmName)|rame(?:s|Element)|gColor)|E|whiteSpace|l(?:i(?:stStyleType|n(?:eHeight|kColor))|o(?:ca(?:tion(?:bar)?|lName)|wsrc)|e(?:ngth|ft(?:Context)?)|a(?:st(?:M(?:odified|atch)|Index|Paren)|yer(?:s|X)|nguage))|a(?:pp(?:MinorVersion|Name|Co(?:deName|re)|Version)|vail(?:Height|Top|Width|Left)|ll|r(?:ity|guments)|Linkcolor|bove)|r(?:ight(?:Context)?|e(?:sponse(?:XML|Text)|adyState))|global|x|m(?:imeTypes|ultiline|enubar|argin(?:Right|Bottom|Top|Left))|L(?:N(?:10|2)|OG(?:10E|2E))|b(?:o(?:ttom|rder(?:Width|RightWidth|BottomWidth|Style|Color|TopWidth|LeftWidth))|ufferDepth|elow|ackground(?:Color|Image)))\b/},{token:"identifier",regex:o},{regex:"",token:"empty",next:"no_regex"}],start:[i.getStartRule("doc-start"),f("start"),{token:"string.regexp",regex:"\\/",next:"regex"},{token:"text",regex:"\\s+|^$",next:"start"},{token:"empty",regex:"",next:"no_regex"}],regex:[{token:"regexp.keyword.operator",regex:"\\\\(?:u[\\da-fA-F]{4}|x[\\da-fA-F]{2}|.)"},{token:"string.regexp",regex:"/[sxngimy]*",next:"no_regex"},{token:"invalid",regex:/\{\d+\b,?\d*\}[+*]|[+*$^?][+*]|[$^][?]|\?{3,}/},{token:"constant.language.escape",regex:/\(\?[:=!]|\)|\{\d+\b,?\d*\}|[+*]\?|[()$^+*?.]/},{token:"constant.language.delimiter",regex:/\|/},{token:"constant.language.escape",regex:/\[\^?/,next:"regex_character_class"},{token:"empty",regex:"$",next:"no_regex"},{defaultToken:"string.regexp"}],regex_character_class:[{token:"regexp.charclass.keyword.operator",regex:"\\\\(?:u[\\da-fA-F]{4}|x[\\da-fA-F]{2}|.)"},{token:"constant.language.escape",regex:"]",next:"regex"},{token:"constant.language.escape",regex:"-"},{token:"empty",regex:"$",next:"no_regex"},{defaultToken:"string.regexp.charachterclass"}],function_arguments:[{token:"variable.parameter",regex:o},{token:"punctuation.operator",regex:"[, ]+"},{token:"punctuation.operator",regex:"$"},{token:"empty",regex:"",next:"no_regex"}],qqstring:[{token:"constant.language.escape",regex:r},{token:"string",regex:"\\\\$",consumeLineEnd:!0},{token:"string",regex:'"|$',next:"no_regex"},{defaultToken:"string"}],qstring:[{token:"constant.language.escape",regex:r},{token:"string",regex:"\\\\$",consumeLineEnd:!0},{token:"string",regex:"'|$",next:"no_regex"},{defaultToken:"string"}]};if(!e||!e.noES6)this.$rules.no_regex.unshift({regex:"[{}]",onMatch:function(e,t,n){this.next=e=="{"?this.nextState:"";if(e=="{"&&n.length)n.unshift("start",t);else if(e=="}"&&n.length){n.shift(),this.next=n.shift();if(this.next.indexOf("string")!=-1||this.next.indexOf("jsx")!=-1)return"paren.quasi.end"}return e=="{"?"paren.lparen":"paren.rparen"},nextState:"start"},{token:"string.quasi.start",regex:/`/,push:[{token:"constant.language.escape",regex:r},{token:"paren.quasi.start",regex:/\${/,push:"start"},{token:"string.quasi.end",regex:/`/,next:"pop"},{defaultToken:"string.quasi"}]}),(!e||e.jsx!=0)&&a.call(this);this.embedRules(i,"doc-",[i.getEndRule("no_regex")]),this.normalizeRules()};r.inherits(u,s),t.JavaScriptHighlightRules=u}),define("ace/mode/matching_brace_outdent",["require","exports","module","ace/range"],function(e,t,n){"use strict";var r=e("../range").Range,i=function(){};(function(){this.checkOutdent=function(e,t){return/^\s+$/.test(e)?/^\s*\}/.test(t):!1},this.autoOutdent=function(e,t){var n=e.getLine(t),i=n.match(/^(\s*\})/);if(!i)return 0;var s=i[1].length,o=e.findMatchingBracket({row:t,column:s});if(!o||o.row==t)return 0;var u=this.$getIndent(e.getLine(o.row));e.replace(new r(t,0,t,s-1),u)},this.$getIndent=function(e){return e.match(/^\s*/)[0]}}).call(i.prototype),t.MatchingBraceOutdent=i}),define("ace/mode/folding/cstyle",["require","exports","module","ace/lib/oop","ace/range","ace/mode/folding/fold_mode"],function(e,t,n){"use strict";var r=e("../../lib/oop"),i=e("../../range").Range,s=e("./fold_mode").FoldMode,o=t.FoldMode=function(e){e&&(this.foldingStartMarker=new RegExp(this.foldingStartMarker.source.replace(/\|[^|]*?$/,"|"+e.start)),this.foldingStopMarker=new RegExp(this.foldingStopMarker.source.replace(/\|[^|]*?$/,"|"+e.end)))};r.inherits(o,s),function(){this.foldingStartMarker=/([\{\[\(])[^\}\]\)]*$|^\s*(\/\*)/,this.foldingStopMarker=/^[^\[\{\(]*([\}\]\)])|^[\s\*]*(\*\/)/,this.singleLineBlockCommentRe=/^\s*(\/\*).*\*\/\s*$/,this.tripleStarBlockCommentRe=/^\s*(\/\*\*\*).*\*\/\s*$/,this.startRegionRe=/^\s*(\/\*|\/\/)#?region\b/,this._getFoldWidgetBase=this.getFoldWidget,this.getFoldWidget=function(e,t,n){var r=e.getLine(n);if(this.singleLineBlockCommentRe.test(r)&&!this.startRegionRe.test(r)&&!this.tripleStarBlockCommentRe.test(r))return"";var i=this._getFoldWidgetBase(e,t,n);return!i&&this.startRegionRe.test(r)?"start":i},this.getFoldWidgetRange=function(e,t,n,r){var i=e.getLine(n);if(this.startRegionRe.test(i))return this.getCommentRegionBlock(e,i,n);var s=i.match(this.foldingStartMarker);if(s){var o=s.index;if(s[1])return this.openingBracketBlock(e,s[1],n,o);var u=e.getCommentFoldRange(n,o+s[0].length,1);return u&&!u.isMultiLine()&&(r?u=this.getSectionRange(e,n):t!="all"&&(u=null)),u}if(t==="markbegin")return;var s=i.match(this.foldingStopMarker);if(s){var o=s.index+s[0].length;return s[1]?this.closingBracketBlock(e,s[1],n,o):e.getCommentFoldRange(n,o,-1)}},this.getSectionRange=function(e,t){var n=e.getLine(t),r=n.search(/\S/),s=t,o=n.length;t+=1;var u=t,a=e.getLength();while(++tf)break;var l=this.getFoldWidgetRange(e,"all",t);if(l){if(l.start.row<=s)break;if(l.isMultiLine())t=l.end.row;else if(r==f)break}u=t}return new i(s,o,u,e.getLine(u).length)},this.getCommentRegionBlock=function(e,t,n){var r=t.search(/\s*$/),s=e.getLength(),o=n,u=/^\s*(?:\/\*|\/\/|--)#?(end)?region\b/,a=1;while(++no)return new i(o,r,l,t.length)}}.call(o.prototype)}),define("ace/mode/javascript",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/javascript_highlight_rules","ace/mode/matching_brace_outdent","ace/worker/worker_client","ace/mode/behaviour/cstyle","ace/mode/folding/cstyle"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./javascript_highlight_rules").JavaScriptHighlightRules,o=e("./matching_brace_outdent").MatchingBraceOutdent,u=e("../worker/worker_client").WorkerClient,a=e("./behaviour/cstyle").CstyleBehaviour,f=e("./folding/cstyle").FoldMode,l=function(){this.HighlightRules=s,this.$outdent=new o,this.$behaviour=new a,this.foldingRules=new f};r.inherits(l,i),function(){this.lineCommentStart="//",this.blockComment={start:"/*",end:"*/"},this.$quotes={'"':'"',"'":"'","`":"`"},this.getNextLineIndent=function(e,t,n){var r=this.$getIndent(t),i=this.getTokenizer().getLineTokens(t,e),s=i.tokens,o=i.state;if(s.length&&s[s.length-1].type=="comment")return r;if(e=="start"||e=="no_regex"){var u=t.match(/^.*(?:\bcase\b.*:|[\{\(\[])\s*$/);u&&(r+=n)}else if(e=="doc-start"){if(o=="start"||o=="no_regex")return"";var u=t.match(/^\s*(\/?)\*/);u&&(u[1]&&(r+=" "),r+="* ")}return r},this.checkOutdent=function(e,t,n){return this.$outdent.checkOutdent(t,n)},this.autoOutdent=function(e,t,n){this.$outdent.autoOutdent(t,n)},this.createWorker=function(e){var t=new u(["ace"],"ace/mode/javascript_worker","JavaScriptWorker");return t.attachToDocument(e.getDocument()),t.on("annotate",function(t){e.setAnnotations(t.data)}),t.on("terminate",function(){e.clearAnnotations()}),t},this.$id="ace/mode/javascript"}.call(l.prototype),t.Mode=l}); (function() { + window.require(["ace/mode/javascript"], function(m) { + if (typeof module == "object" && typeof exports == "object" && module) { + module.exports = m; + } + }); + })(); \ No newline at end of file diff --git a/app/assets/frontends/beaker-code-snippet/ui.css b/app/assets/frontends/beaker-code-snippet/ui.css new file mode 100644 index 0000000000..6fbabd2a93 --- /dev/null +++ b/app/assets/frontends/beaker-code-snippet/ui.css @@ -0,0 +1,71 @@ +body { + --border-color: #aab; + --header-background: #f3f3f8; + --header-height: 28px; + + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; +} + +main { + display: grid; + grid-template-columns: 1fr 1fr; + grid-template-rows: 1fr 1fr; + width: 100vw; + height: 100vh; +} + +main section { + position: relative; + min-width: 0; + min-height: 0; +} + +main section:nth-child(1) { + border-right: 1px solid var(--border-color); + border-bottom: 1px solid var(--border-color); +} + +main section:nth-child(2) { + border-bottom: 1px solid var(--border-color); +} + +main section:nth-child(3) { + border-right: 1px solid var(--border-color); +} + +main section header { + background: var(--header-background); + border-bottom: 1px solid #dde; + padding: 0 10px; + height: var(--header-height); + line-height: var(--header-height); + box-sizing: border-box; + font-weight: bold; + letter-spacing: 0.5px; + font-size: 13px; +} + +.editor { + position: absolute; + top: var(--header-height); + left: 0; + bottom: 0; + right: 0; + /* width: 100%; */ + /* height: calc(100% - var(--header-height)); + border: 0; + resize: none; + outline: 0; + box-sizing: border-box; + padding: 10px; + font-size: 13px; + font-family: Consolas, 'Lucida Console', Monaco, monospace;*/ +} + +iframe { + width: 100%; + height: calc(100% - var(--header-height)); + border: 0; + overflow: auto; +} \ No newline at end of file diff --git a/app/assets/frontends/beaker-code-snippet/ui.html b/app/assets/frontends/beaker-code-snippet/ui.html new file mode 100644 index 0000000000..8dafcbec8f --- /dev/null +++ b/app/assets/frontends/beaker-code-snippet/ui.html @@ -0,0 +1,29 @@ + + + + + + + +
+
+
index.js
+
+
+
+
index.html
+
+
+
+
index.css
+
+
+
+
result
+
+
+
+ + + + diff --git a/app/assets/frontends/beaker-code-snippet/ui.js b/app/assets/frontends/beaker-code-snippet/ui.js new file mode 100644 index 0000000000..1a639ccd8b --- /dev/null +++ b/app/assets/frontends/beaker-code-snippet/ui.js @@ -0,0 +1,71 @@ +var self = hyperdrive.self + +var editors = { + js: ace.edit('js'), + css: ace.edit('css'), + html: ace.edit('html') +} +window.editors = editors +editors.js.session.setMode('ace/mode/javascript') +editors.css.session.setMode('ace/mode/css') +editors.html.session.setMode('ace/mode/html') +for (let k in editors) { + editors[k].session.setUseWorker(false) + editors[k].session.setTabSize(2) + editors[k].session.setUseSoftTabs(true) +} + +var result = document.getElementById('result') +var iframe = document.createElement('iframe') +iframe.setAttribute('sandbox', 'allow-scripts') + +setup() + +async function setup () { + var info = await self.getInfo() + + editors.js.setValue(await self.readFile('/index.js').catch(e => '')) + editors.css.setValue(await self.readFile('/index.css').catch(e => '')) + editors.html.setValue(await self.readFile('/index.html').catch(e => '')) + for (let k in editors) { + editors[k].selection.moveTo(0,0) + } + result.append(iframe) + + for (let k in editors) { + editors[k].session.on('change', debounce(e => onChangeEditor(k), 1e3)) + } + + runSnippet() +} + +function runSnippet () { + iframe.src = `data:text/html;base64,${btoa(` + + ${editors.html.getValue()} + + `)}` +} + +function debounce (fn, timeout) { + var timer + return () => { + if (timer) clearTimeout(timer) + timer = setTimeout(fn, timeout) + } +} + +async function onChangeEditor (id) { + var editor = editors[id] + var filename = `/index.${id}` + var current = await self.readFile(filename).catch(e => '') + if (current !== editor.getValue()) { + runSnippet() + self.writeFile(filename, editor.getValue()).catch(e => undefined) + } +} \ No newline at end of file diff --git a/app/assets/frontends/beaker-forum/.beaker-ui b/app/assets/frontends/beaker-forum/.beaker-ui new file mode 100644 index 0000000000..8fec92140d --- /dev/null +++ b/app/assets/frontends/beaker-forum/.beaker-ui @@ -0,0 +1 @@ +builtin:beaker-forum \ No newline at end of file diff --git a/app/assets/frontends/beaker-forum/css/buttons.css.js b/app/assets/frontends/beaker-forum/css/buttons.css.js new file mode 100644 index 0000000000..e7e82bf763 --- /dev/null +++ b/app/assets/frontends/beaker-forum/css/buttons.css.js @@ -0,0 +1,143 @@ +import {css} from '../vendor/lit-element/lit-element.js' +import colorscss from './colors.css.js' + +const cssStr = css` +${colorscss} + +button { + background: var(--button-background); + color: var(--button-color); + border: 1px solid var(--button-border-color); + border-radius: 3px; + box-shadow: 0 1px 1px rgba(0,0,0,.05); + padding: 5px 10px; + outline: 0; + cursor: pointer; +} + +button:hover { + background: var(--button-background--hover); + color: var(--button-color--hover); +} + +button:active { + background: var(--button-background--hover); + color: var(--button-color--hover); +} + +button.big { + padding: 6px 12px; +} + +button.block { + display: block; + width: 100%; +} + +button.pressed { + box-shadow: inset 0 1px 1px rgba(0,0,0,.5); + background: #6d6d79; + color: rgba(255,255,255,1); + border-color: transparent; + border-radius: 4px; +} + +button.primary { + background: var(--button-background--primary); + color: var(--button-color--primary); + border-color: var(--button-border-color--primary); + box-shadow: 0 1px 1px rgba(0,0,0,.1); +} + +button.primary:hover { + background: var(--button-background--primary--hover); + color: var(--button-color--primary--hover); +} + +button.gray { + background: #fafafa; +} + +button.gray:hover { + background: #f5f5f5; +} + +button[disabled] { + border-color: var(--border-color); + background: #fff; + color: #999; + cursor: default; +} + +button.rounded { + border-radius: 16px; +} + +button.flat { + box-shadow: none; +} + +button.noborder { + border-color: transparent; +} + +button.transparent { + background: transparent; + border-color: transparent; + box-shadow: none; +} + +button.transparent:hover { + background: #f5f5fa; +} + +button.transparent.pressed { + background: rgba(0,0,0,.1); + box-shadow: inset 0 1px 2px rgba(0,0,0,.25); + color: inherit; +} + +.radio-group button { + background: transparent; + border: 0; + box-shadow: none; +} + +.radio-group button.pressed { + background: #6d6d79; + border-radius: 30px; +} + +.btn-group { + display: inline-flex; +} + +.btn-group button { + border-radius: 0; + border-right-width: 0; +} + +.btn-group button:first-child { + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; +} + +.btn-group button:last-child { + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; + border-right-width: 1px; +} + +.btn-group.rounded button:first-child { + border-top-left-radius: 14px; + border-bottom-left-radius: 14px; + padding-left: 14px; +} + +.btn-group.rounded button:last-child { + border-top-right-radius: 14px; + border-bottom-right-radius: 14px; + padding-right: 14px; +} +` +export default cssStr diff --git a/app/assets/frontends/beaker-forum/css/colors.css.js b/app/assets/frontends/beaker-forum/css/colors.css.js new file mode 100644 index 0000000000..ec6592bc46 --- /dev/null +++ b/app/assets/frontends/beaker-forum/css/colors.css.js @@ -0,0 +1,27 @@ +import {css} from '../vendor/lit-element/lit-element.js' + +const cssStr = css` +body { + /* common simple colors */ + --red: rgb(255, 59, 48); + --orange: rgb(255, 149, 0); + --yellow: rgb(255, 204, 0); + --lime: #E6EE9C; + --green: rgb(51, 167, 71); + --teal: rgb(90, 200, 250); + --blue: #2864dc; + --purple: rgb(88, 86, 214); + --pink: rgb(255, 45, 85); + + /* common element colors */ + --color-text: #333; + --color-text--muted: gray; + --color-text--light: #aaa; + --color-text--dark: #111; + --color-link: #295fcb; + --color-focus-box-shadow: rgba(41, 95, 203, 0.8); + --border-color: #d4d7dc; + --light-border-color: #e4e7ec; +} +` +export default cssStr diff --git a/app/assets/frontends/beaker-forum/css/com/about.css.js b/app/assets/frontends/beaker-forum/css/com/about.css.js new file mode 100644 index 0000000000..9a16bf546c --- /dev/null +++ b/app/assets/frontends/beaker-forum/css/com/about.css.js @@ -0,0 +1,164 @@ +import {css} from '../../vendor/lit-element/lit-element.js' +import buttonsCSS from '../buttons.css.js' + +const cssStr = css` +${buttonsCSS} + +:host { + display: block; +} + +:host > h4, +.sidebar-md h1 { + font-size: 14px; + margin: 0; + padding: 0 4px; + border-bottom: 1px solid #ccd; + height: 29px; + line-height: 29px; + font-weight: 500; +} + +.description, +.admin { + padding: 12px 4px; + font-size: 15px; +} + +.description, +.sidebar-md p, +.sidebar-md ul { + opacity: 0.75; + letter-spacing: 0.4px; +} + +.groupware-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + grid-gap: 10px; + padding: 15px 5px 10px; +} + +.groupware-grid a { + display: block; + text-align: center; + padding: 5px 0; + text-decoration: none; + color: inherit; + border-radius: 4px; +} + +.groupware-grid a:hover { + text-decoration: underline; +} + +.groupware-grid a img { + display: block; + width: 40px; + height: 40px; + margin: 0 auto 8px; + border-radius: 4px; +} + +.groupware-grid a span { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-size: 12px; +} + +.counts { + display: flex; + padding: 8px 12px; + background: var(--gray-background); + border-radius: 4px; + margin-bottom: 10px; +} + +.counts a { + display: flex; + flex-direction: column-reverse; + align-items: baseline; + color: inherit; + font-weight: 500; + text-decoration: none; + padding: 0 0px; +} + +.counts a:hover { + color: var(--link-color); +} + +.counts a .number { + font-size: 16px; + margin-right: 4px; + letter-spacing: 2px; +} + +.counts a .label { + font-size: 10px; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.sidebar-md h2 { + font-size: 14px; +} + +.sidebar-md h3 { + font-size: 13px; +} + +.sidebar-md h4 { + font-size: 12px; +} + +.sidebar-md a { + text-decoration: none; + color: var(--link-color); +} + +.sidebar-md a:hover { + text-decoration: underline; +} + +.sidebar-md blockquote { + border-left: 5px solid var(--gray-background); + padding: 1px 1em; + margin-left: 0; +} + +.sidebar-md pre { + background: #f3f3f7; + padding: 1em; + overflow: auto; + max-width: 100%; +} + +.sidebar-md code { + background: #f3f3f7; + padding: 0 4px; +} + +.sidebar-md hr { + border: 0; + border-top: 1px solid #ccd; +} + +.sidebar-md table { + margin: 1em 0; + width: 100%; + border-collapse: collapse; +} + +.sidebar-md table th { + background: var(--gray-background); +} + +.sidebar-md table th, +.sidebar-md table td { + border: 1px solid #ccd; + padding: 5px; +} +` +export default cssStr diff --git a/app/assets/frontends/beaker-forum/css/com/comments/composer.css.js b/app/assets/frontends/beaker-forum/css/com/comments/composer.css.js new file mode 100644 index 0000000000..e98e542291 --- /dev/null +++ b/app/assets/frontends/beaker-forum/css/com/comments/composer.css.js @@ -0,0 +1,60 @@ +import {css} from '../../../vendor/lit-element/lit-element.js' +import inputscss from '../../inputs.css.js' +import buttons2css from '../../buttons.css.js' +const cssStr = css` +${inputscss} +${buttons2css} + +:host { + display: block; + position: relative; + background: #fff; + padding: 14px 18px; + border: 1px solid #ccd; + border-radius: 4px; + overflow: hidden; + --input-font-size: 14px; +} + +.input-placeholder, +textarea { + padding: 0; + font-size: var(--input-font-size); +} + +textarea::-webkit-input-placeholder { + line-height: inherit; + font-size: var(--input-font-size); +} + +.input-placeholder { + cursor: text; + color: #aaa; + font-weight: 400; +} + +textarea { + display: block; + width: 100%; + box-sizing: border-box; + height: auto; + min-height: 70px; + resize: none; + border: 0 !important; + outline: 0 !important; + box-shadow: none !important; +} + +.actions { + position: absolute; + right: 10px; + bottom: 10px; + display: flex; + align-items: center; +} + +.actions button { + margin-left: auto; +} +` +export default cssStr diff --git a/app/assets/frontends/beaker-forum/css/com/comments/feed.css.js b/app/assets/frontends/beaker-forum/css/com/comments/feed.css.js new file mode 100644 index 0000000000..17a5f347df --- /dev/null +++ b/app/assets/frontends/beaker-forum/css/com/comments/feed.css.js @@ -0,0 +1,102 @@ +import {css} from '../../../vendor/lit-element/lit-element.js' +import emptyCSS from '../empty.css.js' +import spinnerCSS from '../spinner.css.js' +import buttonsCSS from '../../buttons.css.js' + +const cssStr = css` +${emptyCSS} +${spinnerCSS} +${buttonsCSS} + +:host { + --body-font-size: 15px; + --header-font-size: 14px; + --title-font-size: 14px; + --footer-font-size: 13px; + --title-color: var(--color-link); + --header-color: #888; + + display: block; + margin-bottom: 50px; + margin-top: -10px; +} + +a { + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +.comment { + display: block; + padding: 8px 14px 12px; + border-bottom: 1px solid #eef; +} + +.header { + display: flex; + align-items: center; + padding: 4px 16px 4px; + font-size: var(--header-font-size); + line-height: var(--header-font-size); + color: var(--header-color); +} + +.header .menu { + padding: 2px 4px; +} + +.title { + font-size: var(--title-font-size); + color: var(--title-color); + margin-right: 10px; + line-height: 17px; +} + +.permalink { + color: inherit; +} + +.body { + color: rgba(0, 0, 0, 0.9); + padding: 0 16px; + margin: 0 0 4px; + font-size: var(--body-font-size); + letter-spacing: 0.25px; + line-height: 1.4; + white-space: pre-line; +} + +.footer { + padding: 4px 16px 0; + font-size: var(--footer-font-size); +} + +.view-context { + background: #f0f0f5; + color: #778; + padding: 2px 6px; + border-radius: 4px; +} + +.view-context:hover { + text-decoration: none; + background: #eaeaef; +} + +beaker-paginator { + font-size: 16px; + margin: 20px 10px; +} + +.error { + padding: 30px; + color: red; + background: #fff7f7; + letter-spacing: 0.5px; + font-size: 16px; +} +` +export default cssStr diff --git a/app/assets/frontends/beaker-forum/css/com/comments/thread.css.js b/app/assets/frontends/beaker-forum/css/com/comments/thread.css.js new file mode 100644 index 0000000000..0fe18d3f30 --- /dev/null +++ b/app/assets/frontends/beaker-forum/css/com/comments/thread.css.js @@ -0,0 +1,123 @@ +import {css} from '../../../vendor/lit-element/lit-element.js' +import buttonsCSS from '../../buttons.css.js' + +const cssStr = css` +${buttonsCSS} + +:host { + --body-font-size: 15px; + --header-font-size: 12px; + --title-font-size: 13px; + --footer-font-size: 12px; + --title-color: var(--color-link); + --header-color: #888; + --footer-color: #888; + --footer-background: #fff; + --replies-left-margin: 12px; + --comment-top-margin: 16px; + --comment-left-margin: 2px; + --composer-padding: 14px 18px; + --composer-margin: 0; + --composer-border: 1px solid #eef; + --composer-border--focused: 1px solid #bbc; + + display: block; + border-radius: 4px; + background: #fff; +} + +a { + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +beaker-comment-composer { + border: var(--composer-border); + padding: var(--composer-padding); + margin: var(--composer-margin); +} + +beaker-comment-composer.focused { + border: var(--composer-border--focused); +} + +.comments { +} + +.comments .comments { + margin-left: var(--replies-left-margin); +} + +.comment { + display: block; + margin-top: var(--comment-top-margin); + margin-left: var(--comment-left-margin); + border-left: 2px solid #f5f5f5; +} + +.header { + display: flex; + align-items: center; + padding: 4px 16px 4px; + font-size: var(--header-font-size); + line-height: var(--header-font-size); + color: var(--header-color); +} + +.header .menu { + padding: 2px 4px; +} + +.title { + font-size: var(--title-font-size); + color: var(--title-color); + margin-right: 10px; + line-height: 17px; +} + +.body { + color: rgba(0, 0, 0, 0.9); + padding: 0 16px; + margin: 0; + max-width: 50em; + font-size: var(--body-font-size); + line-height: 1.4; + letter-spacing: 0.25px; + white-space: pre-line; +} + +.footer { + display: flex; + align-items: center; + font-size: var(--footer-font-size); + color: var(--footer-color); + background: var(--footer-background); + padding: 8px 14px 4px; +} + +.footer > a, +.footer > span { + margin: 0 5px; + color: inherit; + font-weight: 500; +} + +.footer > a:first-child, +.footer > span:first-child { + margin-left: 0; +} + +.permalink { + color: inherit; +} + +.comment beaker-comment-composer { + margin: 10px 16px; + --input-font-size: var(--body-font-size); +} + +` +export default cssStr diff --git a/app/assets/frontends/beaker-forum/css/com/dropdown.css.js b/app/assets/frontends/beaker-forum/css/com/dropdown.css.js new file mode 100644 index 0000000000..eb9b9766bb --- /dev/null +++ b/app/assets/frontends/beaker-forum/css/com/dropdown.css.js @@ -0,0 +1,237 @@ +import {css} from '../../vendor/lit-element/lit-element.js' + +const cssStr = css` +.dropdown { + position: relative; +} + +.dropdown.open .toggleable:not(.primary) { + background: #dadada; + box-shadow: inset 0 0 3px rgba(0, 0, 0, 0.1); + border-color: transparent; + outline: 0; +} + +.toggleable-container .dropdown-items { + display: none; +} + +.toggleable-container.hover:hover .dropdown-items, +.toggleable-container.open .dropdown-items { + display: block; +} + +.dropdown-items { + width: 270px; + position: absolute; + right: 0px; + z-index: 3000; + background: #fff; + border: 1px solid #dadada; + border-radius: 10px; + box-shadow: 0 2px 15px rgba(0, 0, 0, 0.3); + overflow: hidden; +} + +.dropdown-items .section { + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + padding: 5px 0; +} + +.dropdown-items .section-header { + padding: 2px 10px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.dropdown-items .section-header.light { + color: var(--color-text--light); + font-weight: 500; +} + +.dropdown-items .section-header.small { + font-size: 12px; +} + +.dropdown-items hr { + border: 0; + border-bottom: 1px solid #ddd; +} + +.dropdown-items.thin { + width: 170px; +} + +.dropdown-items.wide { + width: 400px; +} + +.dropdown-items.compact .dropdown-item { + padding: 2px 15px; + border-bottom: 0; +} + +.dropdown-items.compact .description { + margin-left: 0; +} + +.dropdown-items.compact hr { + margin: 5px 0; +} + +.dropdown-items.roomy .dropdown-item { + padding: 10px 15px; +} + +.dropdown-items.very-roomy .dropdown-item { + padding: 20px 30px; +} + +.dropdown-items.no-border .dropdown-item { + border-bottom: 0; +} + +.dropdown-items.center { + left: 50%; + transform: translateX(-50%); +} + +.dropdown-items.left { + right: initial; + left: 0; +} + +.dropdown-items.over { + top: 0; +} + +.dropdown-items.top { + bottom: calc(100% + 5px); +} + +.dropdown-items.with-triangle:before { + content: ''; + position: absolute; + top: -8px; + right: 10px; + width: 12px; + height: 12px; + z-index: 3; + width: 0; + height: 0; + border-left: 8px solid transparent; + border-right: 8px solid transparent; + border-bottom: 8px solid #fff; +} + +.dropdown-items.with-triangle.left:before { + left: 10px; +} + +.dropdown-items.with-triangle.center:before { + left: 43%; +} + +.dropdown-title { + border-bottom: 1px solid #eee; + padding: 2px 8px; + font-size: 11px; + color: gray; +} + +.dropdown-item { + display: block; + padding: 7px 15px; + border-bottom: 1px solid #eee; +} + +.dropdown-item.disabled { + opacity: 0.25; +} + +.dropdown-item .fa-check-square { + color: var(--color-blue); +} + +.dropdown-item .fa-check-square, +.dropdown-item .fa-square-o { + font-size: 14px; +} + +.dropdown-item .fa-check { + font-size: 11.5px; +} + +.dropdown-item.no-border { + border-bottom: 0; +} + +.dropdown-item:hover:not(.no-hover) { + background: #eee; + cursor: pointer; +} + +.dropdown-item:hover:not(.no-hover) i:not(.fa-check-square) { + color: var(--color-text); +} + +.dropdown-item:hover:not(.no-hover) .description { + color: var(--color-text); +} + +.dropdown-item:hover:not(.no-hover).disabled { + background: inherit; + cursor: default; +} + +.dropdown-item .fa, +.dropdown-item i { + display: inline-block; + width: 20px; + color: rgba(0, 0, 0, 0.65); +} + +.dropdown-item .fa-fw { + margin-left: -3px; + margin-right: 3px; +} + +.dropdown-item img { + display: inline-block; + width: 16px; + position: relative; + top: 3px; + margin-right: 6px; +} + +.dropdown-item .btn .fa { + color: inherit; +} + +.dropdown-item .label { + font-weight: 500; + margin-bottom: 3px; +} + +.dropdown-item .description { + color: var(--color-text--muted); + margin: 0; + margin-left: 23px; + margin-bottom: 3px; + line-height: 1.5; +} + +.dropdown-item .description.small { + font-size: 12.5px; +} + +.dropdown-item:first-of-type { + border-radius: 2px 2px 0 0; +} + +.dropdown-item:last-of-type { + border-radius: 0 0 2px 2px; +} +` +export default cssStr diff --git a/app/assets/frontends/beaker-forum/css/com/empty.css.js b/app/assets/frontends/beaker-forum/css/com/empty.css.js new file mode 100644 index 0000000000..87dfc638cb --- /dev/null +++ b/app/assets/frontends/beaker-forum/css/com/empty.css.js @@ -0,0 +1,18 @@ +import {css} from '../../vendor/lit-element/lit-element.js' + +const cssStr = css` +.empty { + padding: 80px 0; + color: #889; + text-align: center; + border-radius: 8px; +} + +.empty .far, +.empty .fas { + font-size: 85px; + margin-bottom: 30px; + color: #0002; +} +` +export default cssStr \ No newline at end of file diff --git a/app/assets/frontends/beaker-forum/css/com/notifications/feed.css.js b/app/assets/frontends/beaker-forum/css/com/notifications/feed.css.js new file mode 100644 index 0000000000..1bd571d49a --- /dev/null +++ b/app/assets/frontends/beaker-forum/css/com/notifications/feed.css.js @@ -0,0 +1,60 @@ +import {css} from '../../../vendor/lit-element/lit-element.js' +import emptyCSS from '../empty.css.js' +import spinnerCSS from '../spinner.css.js' +import buttonsCSS from '../../buttons.css.js' + +const cssStr = css` +${emptyCSS} +${spinnerCSS} +${buttonsCSS} + +:host { + display: block; +} + +.notification { + display: grid; + grid-template-columns: 40px 1fr; + align-items: center; + padding: 14px 16px; + text-decoration: none; + color: inherit; +} + +.notification:hover { + background: #f8f8fa; +} + +.notification.unread { + background: #dcedff; +} + +.notification.unread:hover { + background: #ccddef; +} + +.icon { + font-size: 24px; + color: #0002; +} + +.notification .description, +.notification .target { + display: block; +} + +.notification .target .post { + font-size: 17px; + font-weight: bold; +} + +.notification .target .comment { + display: block; + margin-top: 2px; + padding: 10px; + border-radius: 4px; + border: 1px solid #0002; +} + +` +export default cssStr diff --git a/app/assets/frontends/beaker-forum/css/com/paginator.css.js b/app/assets/frontends/beaker-forum/css/com/paginator.css.js new file mode 100644 index 0000000000..98ad523dc3 --- /dev/null +++ b/app/assets/frontends/beaker-forum/css/com/paginator.css.js @@ -0,0 +1,8 @@ +import {css} from '../../vendor/lit-element/lit-element.js' + +const cssStr = css` +:host { + display: flex; +} +` +export default cssStr \ No newline at end of file diff --git a/app/assets/frontends/beaker-forum/css/com/pinned-message.css.js b/app/assets/frontends/beaker-forum/css/com/pinned-message.css.js new file mode 100644 index 0000000000..b8384ed3fd --- /dev/null +++ b/app/assets/frontends/beaker-forum/css/com/pinned-message.css.js @@ -0,0 +1,82 @@ +import {css} from '../../vendor/lit-element/lit-element.js' + +const cssStr = css` +:host { + display: block; + position: relative; +} + +.md { + display: block; + padding: 20px 24px 22px; + margin-bottom: 10px; + background: var(--body-background); + border: 1px solid var(--button-border-color); + border-radius: 4px; + box-shadow: 0 1px 2px #0001; +} + +.hide-btn { + position: absolute; + top: 6px; + right: 10px; + color: rgba(0,0,0,.5); +} + +.md > :first-child { + margin-top: 0; +} + +.md > :last-child { + margin-bottom: 0; +} + +a { + text-decoration: none; + color: var(--link-color); +} + +a:hover { + text-decoration: underline; +} + +blockquote { + border-left: 5px solid var(--header-background); + padding: 1px 1em; + margin-left: 0; +} + +pre { + background: #f3f3f7; + padding: 1em; + overflow: auto; + max-width: 100%; +} + +code { + background: #f3f3f7; + padding: 0 4px; +} + +hr { + border: 0; + border-top: 1px solid #ccd; +} + +table { + margin: 1em 0; + width: 100%; + border-collapse: collapse; +} + +table th { + background: var(--header-background); +} + +table th, +table td { + border: 1px solid #ccd; + padding: 5px; +} +` +export default cssStr diff --git a/app/assets/frontends/beaker-forum/css/com/popups.css.js b/app/assets/frontends/beaker-forum/css/com/popups.css.js new file mode 100644 index 0000000000..7988dd7f89 --- /dev/null +++ b/app/assets/frontends/beaker-forum/css/com/popups.css.js @@ -0,0 +1,139 @@ +import {css} from '../../vendor/lit-element/lit-element.js' +import buttonscss from '../buttons.css.js' +import inputscss from '../inputs.css.js' +const cssStr = css` +${buttonscss} +${inputscss} + +.popup-wrapper { + position: fixed; + left: 0; + top: 0; + width: 100%; + height: 100%; + z-index: 6000; + background: rgba(0, 0, 0, 0.45); + font-style: normal; + overflow-y: auto; +} + +.popup-inner { + background: #fff; + box-shadow: 0 2px 25px rgba(0, 0, 0, 0.3); + border: 1px solid rgba(0, 0, 0, 0.55); + border-radius: 4px; + width: 450px; + margin: 80px auto; + overflow: hidden; +} + +.popup-inner .error { + color: #d80b00 !important; + margin: -12px 0 10px !important; +} + +.popup-inner .help { + margin: -6px 0 14px; + opacity: 0.75; + letter-spacing: 0.4px; + margin-bottom: 14px; + font-size: 11px; +} + +.popup-inner .head { + position: relative; + background: #f1f1f6; + padding: 7px 12px; + box-sizing: border-box; + width: 100%; + border-bottom: 1px solid #e0e0ee; + border-radius: 4px 4px 0 0; +} + +.popup-inner .head .title { + font-size: 0.95rem; + font-weight: 500; +} + +.popup-inner .head .close-btn { + position: absolute; + top: 8px; + right: 12px; + cursor: pointer; +} + +.popup-inner .body { + padding: 16px; +} + +.popup-inner .body > div:not(:first-child) { + margin-top: 20px; +} + +.popup-inner p:first-child { + margin-top: 0; +} + +.popup-inner p:last-child { + margin-bottom: 0; +} + +.popup-inner select { + height: 28px; +} + +.popup-inner textarea, +.popup-inner label:not(.checkbox-container), +.popup-inner select, +.popup-inner input { + display: block; + width: 100%; + box-sizing: border-box; +} + +.popup-inner label.toggle { + display: flex; + justify-content: flex-start; +} + +.popup-inner label.toggle .text { + margin-right: 10px; +} + +.popup-inner label.toggle input { + display: none; +} + +.popup-inner label { + margin-bottom: 3px; + color: rgba(51, 51, 51, 0.9); +} + +.popup-inner textarea, +.popup-inner input { + font-size: 15px; + margin-bottom: 10px; +} + +.popup-inner input { + padding: 0 10px; + height: 36px; +} + +.popup-inner textarea { + height: 100px; + resize: vertical; + padding: 10px; +} + +.popup-inner .form-actions button { + font-size: 14px; +} + +.popup-inner .actions .spinner { + width: 10px; + height: 10px; + border-width: 1.2px; +} +` +export default cssStr diff --git a/app/assets/frontends/beaker-forum/css/com/post-buttons.css.js b/app/assets/frontends/beaker-forum/css/com/post-buttons.css.js new file mode 100644 index 0000000000..f21c7270b8 --- /dev/null +++ b/app/assets/frontends/beaker-forum/css/com/post-buttons.css.js @@ -0,0 +1,25 @@ +import {css} from '../../vendor/lit-element/lit-element.js' +import buttonscss from '../buttons.css.js' + +const cssStr = css` +${buttonscss} + +:host { + display: block; +} + +button { + display: block; + font-size: 14px; + font-weight: 500; + margin-bottom: 6px; + padding: 10px 8px 10px 16px; + width: 100%; + text-align: left; +} + +button .fa-fw { + margin-right: 8px; +} +` +export default cssStr diff --git a/app/assets/frontends/beaker-forum/css/com/posts/composer.css.js b/app/assets/frontends/beaker-forum/css/com/posts/composer.css.js new file mode 100644 index 0000000000..d7512889c3 --- /dev/null +++ b/app/assets/frontends/beaker-forum/css/com/posts/composer.css.js @@ -0,0 +1,148 @@ +import {css} from '../../../vendor/lit-element/lit-element.js' +import inputscss from '../../inputs.css.js' +import buttonscss from '../../buttons.css.js' +import spinnercss from '../spinner.css.js' + +const cssStr = css` +${inputscss} +${buttonscss} +${spinnercss} + +:host { + display: block; + background: #fff; + border: 1px solid #ccd; + border-radius: 8px; + overflow: hidden; + max-width: 800px; + margin: 10px auto; +} + +a { + color: var(--color-link); +} + +.type-selector { + display: flex; + border-bottom: 1px solid #ccd; +} + +.type-selector a { + flex: 1; + border-right: 1px solid #ccd; + border-bottom: 2px solid transparent; + text-align: center; + cursor: pointer; + padding: 16px 0; + font-size: 13px; + font-weight: bold; + color: #778; +} + +.type-selector a:last-child { + border-right: 0; +} + +.type-selector a:hover, +.type-selector a.selected { + color: var(--color-link); + background: #fafaff; +} + +.type-selector a.selected { + border-bottom: 2px solid var(--color-link); +} + +form { + padding: 20px; +} + +.form-group { + margin-bottom: 14px; +} + +textarea, +input { + font-size: 16px; + font-weight: 500; + width: 100%; + box-sizing: border-box; +} + +input { + height: 36px; + padding: 0 12px; +} + +textarea { + min-height: 200px; + padding: 10px 12px; + resize: vertical; +} + +.file-input { + border: 1px solid #ccd; + border-radius: 4px; + padding: 12px 12px; + color: rgba(0, 0, 0, 0.5); + font-weight: 500; +} + +.file-input .selection { + color: #556; + font-size: 16px; + border-radius: 4px; + margin-bottom: 6px; +} + +#native-file-input { + display: none; +} + +.link-metadata { + display: inline-flex; + align-items: center; +} + +.link-metadata > * { + margin-right: 5px; +} + +input.success, +textarea.success, +.file-input.success { + border-color: var(--green); +} + +input.error, +textarea.error, +.file-input.error { + border-color: var(--red); +} + +div.error { + color: var(--red); +} + +input#title { + font-size: 18px; + height: 44px; + font-weight: bold; +} + +::-webkit-input-placeholder { + font-size: inherit; +} + +.actions { + display: flex; + align-items: center; +} + +.actions button { + margin-left: auto; + padding: 6px 10px; + font-size: 14px; +} +` +export default cssStr diff --git a/app/assets/frontends/beaker-forum/css/com/posts/feed.css.js b/app/assets/frontends/beaker-forum/css/com/posts/feed.css.js new file mode 100644 index 0000000000..e8b8a8d29b --- /dev/null +++ b/app/assets/frontends/beaker-forum/css/com/posts/feed.css.js @@ -0,0 +1,34 @@ +import {css} from '../../../vendor/lit-element/lit-element.js' +import emptyCSS from '../empty.css.js' +import spinnerCSS from '../spinner.css.js' + +const cssStr = css` +${emptyCSS} +${spinnerCSS} + +:host { + display: block; + margin-bottom: 40px; + margin-top: -10px; +} + +beaker-post { + padding: 16px 6px 10px; + margin: 0; + border-bottom: 1px solid #eef; +} + +beaker-paginator { + font-size: 16px; + margin: 20px 10px; +} + +.error { + padding: 30px; + color: red; + background: #fff7f7; + letter-spacing: 0.5px; + font-size: 16px; +} +` +export default cssStr diff --git a/app/assets/frontends/beaker-forum/css/com/posts/post.css.js b/app/assets/frontends/beaker-forum/css/com/posts/post.css.js new file mode 100644 index 0000000000..35dbe8e80c --- /dev/null +++ b/app/assets/frontends/beaker-forum/css/com/posts/post.css.js @@ -0,0 +1,167 @@ +import {css} from '../../../vendor/lit-element/lit-element.js' +import buttonsCSS from '../../buttons.css.js' +import tooltipCSS from '../tooltip.css.js' + +const cssStr = css` +${buttonsCSS} +${tooltipCSS} + +:host { + display: grid; + grid-template-columns: 50px minmax(0, 1fr); + align-items: flex-start; + letter-spacing: 0.5px; + font-size: 14px; + margin-bottom: 10px; + color: var(--post-color); +} + +:host([singlepage]) { + grid-template-columns: 50px minmax(0, 1fr); +} + +a { + text-decoration: none; + color: var(--post-link-color); + font-weight: 500; + cursor: pointer; +} + +a:hover { + text-decoration: underline; +} + +.icon { + text-align: center; + margin-right: 8px; + font-size: 21px; + line-height: 44px; +} + +.icon .fa-file, +.icon .fa-image { + font-size: 24px; +} + +.content > div { + margin-bottom: 2px; +} + +.title { + font-size: 17px; + font-weight: 600; + color: var(--post-title-color--unread); +} + +:host(.read) .title { + font-weight: 500; + color: var(--post-title-color--read); +} + +:host([singlepage]) .title { + font-size: 22px; +} + +.drive-type, +.domain { + color: var(--post-link-color); +} + +.drive-type .far, +.drive-type .fas, +.domain .far, +.domain .fas { + position: relative; + top: -1px; + font-size: 10px; +} + +button.menu { + padding: 0; +} + +.author { + color: green; +} + +.text-post-content { + color: #333; + font-size: 15px; + letter-spacing: 0.25px; + margin-top: 10px; +} + +.text-post-content > * { + max-width: 50em; +} + +.text-post-content > :first-child { + margin-top: 0; +} + +.text-post-content > :last-child { + margin-bottom: 0; +} + +.text-post-content a { + color: var(--link-color); +} + +.text-post-content pre { + background: #f3f3f7; + padding: 1em; + overflow: auto; + max-width: 100%; +} + +.text-post-content code { + background: #f3f3f7; + padding: 0 4px; +} + +.text-post-content hr { + border: 0; + border-top: 1px solid #ccd; +} + +.text-post-content p, +.text-post-content ul, +.text-post-content ol { + line-height: 1.5; +} + +.text-post-content table { + margin: 1em 0; +} + +.text-post-content blockquote { + border-left: 10px solid #f3f3f7; + margin: 1em 0; + padding: 1px 1.5em; + color: #667; +} + +.file-content { + margin-top: 14px; +} + +.file-content h3 { + margin: 0; +} + +.file-content h3 a { + display: inline-block; + padding: 10px 16px; + border: 1px solid #ccd; + border-radius: 4px; +} + +.file-content h3 + * { + margin-top: 10px; +} + +.file-content > * { + max-width: 100%; +} +` +export default cssStr diff --git a/app/assets/frontends/beaker-forum/css/com/profiles/aside.css.js b/app/assets/frontends/beaker-forum/css/com/profiles/aside.css.js new file mode 100644 index 0000000000..a5c5a1ba48 --- /dev/null +++ b/app/assets/frontends/beaker-forum/css/com/profiles/aside.css.js @@ -0,0 +1,100 @@ +import {css} from '../../../vendor/lit-element/lit-element.js' +import buttonsCSS from '../../buttons.css.js' +import spinnerCSS from '../spinner.css.js' + +const cssStr = css` +${buttonsCSS} +${spinnerCSS} + +:host { + display: block; + position: relative; + border: 1px solid #ccd; + border-radius: 4px; + box-sizing: border-box; + padding: 16px 12px 16px 16px; + margin: 0px 0 10px; +} + +:host(.dark) { + background: #f9f9fc; + border: 0; +} + +a { + color: #889; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +button.menu { + position: absolute; + top: 5px; + right: 5px; +} + +img { + display: block; + margin: 0 auto; + width: 80px; + height: 80px; + border-radius: 50%; + object-fit: cover; + border: 2px solid #fff; + box-shadow: 0 0 0 1px #ccd; +} + +.title { + font-size: 24px; + margin: 12px 0 0; + line-height: 1; + text-align: center; +} + +.title a { + color: inherit; +} + +.info { + font-size: 14px; + margin: 6px 0 0; + text-align: center; +} + +.id { + font-size: 15px; +} + +.description { + letter-spacing: 0.5px; + line-height: 1.3; +} + +.ctrls { + margin: 14px 0 0; +} + +button:not(.menu) { + display: block; + font-size: 14px; + width: 100%; + padding: 8px 12px !important; +} + +button:not(.menu) .fa-fw { + margin-right: 4px; +} + +button:not(.menu):hover { + background: #eef !important; +} + +button:not(.menu) + button { + margin-top: 5px; +} + +` +export default cssStr diff --git a/app/assets/frontends/beaker-forum/css/com/profiles/header.css.js b/app/assets/frontends/beaker-forum/css/com/profiles/header.css.js new file mode 100644 index 0000000000..3cc6e9aff3 --- /dev/null +++ b/app/assets/frontends/beaker-forum/css/com/profiles/header.css.js @@ -0,0 +1,75 @@ +import {css} from '../../../vendor/lit-element/lit-element.js' +import buttonsCSS from '../../buttons.css.js' +import spinnerCSS from '../spinner.css.js' + +const cssStr = css` +${buttonsCSS} +${spinnerCSS} + +:host { + display: grid; + border-radius: 4px; + grid-template-columns: 150px 1fr; + align-items: center; + grid-gap: 20px; + border: 1px solid #ccd; + overflow: hidden; +} + +a { + color: var(--color-link); + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +img { + display: block; + margin: 0 auto; + width: 150px; + height: 150px; + object-fit: cover; +} + +.title, +.info { + margin: 0 0 4px; +} + +.title { + font-size: 31px; + letter-spacing: 0.65px; +} + +.title a { + color: inherit; +} + +.info { + font-size: 15px; + letter-spacing: 0.35px; +} + +.ctrls { + margin: 10px 0 0; +} + +.info .fa-fw { + font-size: 11px; + color: #778; +} + +button { + font-size: 14px; + padding: 6px 12px; +} + +button .fa-fw { + font-size: 13px; + margin-right: 2px; +} + +` +export default cssStr diff --git a/app/assets/frontends/beaker-forum/css/com/profiles/list.css.js b/app/assets/frontends/beaker-forum/css/com/profiles/list.css.js new file mode 100644 index 0000000000..7c4341d43f --- /dev/null +++ b/app/assets/frontends/beaker-forum/css/com/profiles/list.css.js @@ -0,0 +1,92 @@ +import {css} from '../../../vendor/lit-element/lit-element.js' +import buttonsCSS from '../../buttons.css.js' +import spinnerCSS from '../spinner.css.js' + +const cssStr = css` +${buttonsCSS} +${spinnerCSS} + +:host { + display: block; + margin: 20px 10px 40px; +} + +a { + color: var(--color-link); + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +.profiles { + display: grid; + grid-template-columns: repeat(auto-fill, 400px); + grid-gap: 20px; +} + +.profile { + display: flex; + border-radius: 4px; + border: 1px solid #ccd; + align-items: center; +} + +.avatar { + align-self: stretch; +} + +img { + display: block; + width: 120px; + min-height: 120px; + height: 100%; + object-fit: cover; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; +} + +.main { + padding: 16px; +} + +.title, +.info { + margin: 0; +} + +.title { + font-size: 17px; + font-weight: 500; + letter-spacing: 0.65px; + margin-bottom: 4px; +} + +.title a { + color: inherit; +} + +.info { + font-size: 13px; + letter-spacing: 0.35px; + opacity: 0.85; +} + +.info .fa-fw { + font-size: 11px; + color: #778; +} + +button { + font-size: 14px; + padding: 6px 12px; +} + +button .fa-fw { + font-size: 13px; + margin-right: 2px; +} + +` +export default cssStr diff --git a/app/assets/frontends/beaker-forum/css/com/search-input.css.js b/app/assets/frontends/beaker-forum/css/com/search-input.css.js new file mode 100644 index 0000000000..fb720d1228 --- /dev/null +++ b/app/assets/frontends/beaker-forum/css/com/search-input.css.js @@ -0,0 +1,145 @@ +import {css} from '../../vendor/lit-element/lit-element.js' +import inputsCSS from '../inputs.css.js' +import spinnerCSS from './spinner.css.js' + +const cssStr = css` +${inputsCSS} +${spinnerCSS} + +:host { + --input-bg-color: #f1f1f6; + --input-border: 0; + --input-border-radius: 16px; + --input-color: #555; + display: block; + margin-right: 16px; +} + +.search-container { + position: relative; + height: 36px; + width: 210px; + font-size: 13px; +} + +.spinner, +.close-btn, +.search { + position: absolute; +} + +input.search { + background: var(--input-bg-color); + border-radius: var(--input-border-radius); + border: var(--input-border); + color: var(--input-color); + left: 0; + top: 0; + width: 100%; + height: 29px; + padding: 0 10px; + padding-left: 32px; + margin-top: 4px; + box-sizing: border-box; + font-size: 13px; +} + +input.search::-webkit-input-placeholder { + font-size: 13px; + color: var(--input-color); +} + +input:focus { + box-shadow: none; +} + +.search-container > i.fa-search { + position: absolute; + left: 12px; + font-size: 13px; + top: 13px; + color: var(--input-color); + z-index: 1; +} + +.autocomplete-container { + position: relative; + width: 100%; +} + +.autocomplete-results { + position: absolute; + left: 0; + top: 30px; + z-index: 5; + width: 100%; + margin-bottom: 10px; + overflow: hidden; + background: #fff; + border-radius: 4px; + border: 1px solid #ddd; + box-shadow: 0 6px 20px rgba(0,0,0,.05); +} + +.autocomplete-result-group { + margin-bottom: 6px; +} + +.autocomplete-result-group-title { + padding: 4px 10px; + border-bottom: 1px solid #ddd; + color: rgba(0,0,0,.5); +} + +.autocomplete-result { + display: flex; + flex-wrap: nowrap; + align-items: center; + height: 40px; + padding: 0 10px; + border-left: 3px solid transparent; + cursor: pointer; + color: inherit; + text-decoration: none; +} + +.autocomplete-result .icon { + width: 24px; + height: 24px; + text-align: center; + margin-right: 10px; +} + +.autocomplete-result .icon.rounded { + border-radius: 50%; + object-fit: cover; +} + +.autocomplete-result .title, +.autocomplete-result .label { + white-space: pre; + overflow: hidden; + text-overflow: ellipsis; +} + +.autocomplete-result .title { + margin-right: 5px; + flex: auto 0; +} + +.autocomplete-result .label { + color: rgba(0,0,0,.475); + flex: 1; +} + +.autocomplete-result:hover { + background: #f7f7f7; + border-color: #ddd; +} + +.autocomplete-result.active { + background: rgba(40, 100, 220, 0.07); + border-color: #2864dc; +} +` +export default cssStr diff --git a/app/assets/frontends/beaker-forum/css/com/search/results.css.js b/app/assets/frontends/beaker-forum/css/com/search/results.css.js new file mode 100644 index 0000000000..86cb234787 --- /dev/null +++ b/app/assets/frontends/beaker-forum/css/com/search/results.css.js @@ -0,0 +1,59 @@ +import {css} from '../../../vendor/lit-element/lit-element.js' +import emptyCSS from '../empty.css.js' +import spinnerCSS from '../spinner.css.js' + +const cssStr = css` +${emptyCSS} +${spinnerCSS} + +:host { + display: block; + padding-right: 10px; +} + +a { + text-decoration: none; + color: #667; + cursor: pointer; +} + +a:hover { + text-decoration: underline; +} + +.result { + padding: 10px 20px; + font-size: 15px; + line-height: 1.4; +} + +.result h4 { + margin: 0; + font-size: 21px; + font-weight: normal; +} + +.result h4 small { + font-size: 14px; + font-weight: 400; +} + +.result h4 small a { + color: #889; +} + +.result .title { + color: var(--color-link); +} + +.result .author { + color: green; +} + +beaker-paginator { + margin: 20px 10px; + padding-top: 20px; + border-top: 1px solid #dde; +} +` +export default cssStr diff --git a/app/assets/frontends/beaker-forum/css/com/spinner.css.js b/app/assets/frontends/beaker-forum/css/com/spinner.css.js new file mode 100644 index 0000000000..13ad28289e --- /dev/null +++ b/app/assets/frontends/beaker-forum/css/com/spinner.css.js @@ -0,0 +1,25 @@ +import {css} from '../../vendor/lit-element/lit-element.js' + +const cssStr = css` +.spinner { + display: inline-block; + height: 14px; + width: 14px; + animation: rotate 1s infinite linear; + color: #aaa; + border: 1.5px solid; + border-right-color: transparent; + border-radius: 50%; + transition: color 0.25s; +} + +.spinner.reverse { + animation: rotate 2s infinite linear reverse; +} + +@keyframes rotate { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} +` +export default cssStr diff --git a/app/assets/frontends/beaker-forum/css/com/toast.css.js b/app/assets/frontends/beaker-forum/css/com/toast.css.js new file mode 100644 index 0000000000..95581845d6 --- /dev/null +++ b/app/assets/frontends/beaker-forum/css/com/toast.css.js @@ -0,0 +1,67 @@ +import {css} from '../../vendor/lit-element/lit-element.js' + +const cssStr = css` +.toast-wrapper { + position: fixed; + top: 20px; + right: 20px; + z-index: 20000; + transition: opacity 0.1s ease; +} +.toast-wrapper.hidden { + opacity: 0; +} +.toast { + position: relative; + min-width: 350px; + max-width: 450px; + background: #ddd; + margin: 0; + padding: 10px 15px; + border-radius: 4px; + font-size: 16px; + color: #fff; + background: rgba(0, 0, 0, 0.75); + -webkit-font-smoothing: antialiased; + font-weight: 600; +} +.toast.error { + padding-left: 38px; +} +.toast.success { + padding-left: 48px; +} +.toast.success:before, +.toast.error:before { + position: absolute; + left: 18px; + top: 5px; + display: block; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Ubuntu, Cantarell, "Oxygen Sans", "Helvetica Neue", sans-serif; + font-size: 22px; + font-weight: bold; +} +.toast.primary { + background: var(--color-blue); +} +.toast.success { + background: #26b33e; +} +.toast.success:before { + content: '✓'; +} +.toast.error { + background: #c72e25; +} +.toast.error:before { + content: '!'; +} +.toast .toast-btn { + position: absolute; + right: 15px; + color: inherit; + text-decoration: underline; + cursor: pointer; +} +` +export default cssStr diff --git a/app/assets/frontends/beaker-forum/css/com/tooltip.css.js b/app/assets/frontends/beaker-forum/css/com/tooltip.css.js new file mode 100644 index 0000000000..206b2d443e --- /dev/null +++ b/app/assets/frontends/beaker-forum/css/com/tooltip.css.js @@ -0,0 +1,103 @@ +import {css} from '../../vendor/lit-element/lit-element.js' + +const cssStr = css` +*[data-tooltip] { + position: relative; +} + +*[data-tooltip]:hover:before, +*[data-tooltip]:hover:after { + display: block; + z-index: 1000; + transition: opacity 0.01s ease; + transition-delay: 0.2s; +} + +*[data-tooltip]:hover:after { + opacity: 1; +} + +*[data-tooltip]:hover:before { + transform: translate(-50%, 0); + opacity: 1; +} + +*[data-tooltip]:before { + opacity: 0; + transform: translate(-50%, 0); + position: absolute; + top: 33px; + left: 50%; + z-index: 3000; + content: attr(data-tooltip); + background: rgba(17, 17, 17, 0.95); + font-size: 0.7rem; + border: 0; + border-radius: 4px; + padding: 7px 10px; + color: rgba(255, 255, 255, 0.925); + text-transform: none; + text-align: center; + font-weight: 500; + white-space: pre; + line-height: 1; + pointer-events: none; + max-width: 300px; +} + +*[data-tooltip]:after { + opacity: 0; + position: absolute; + left: calc(50% - 6px); + top: 28px; + content: ''; + width: 0; + height: 0; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid rgba(17, 17, 17, 0.95); + pointer-events: none; +} + +.tooltip-nodelay[data-tooltip]:hover:before, +.tooltip-nodelay[data-tooltip]:hover:after { + transition-delay: initial; +} + +.tooltip-right[data-tooltip]:before { + top: 50%; + left: calc(100% + 6px); + transform: translate(0, -50%); + line-height: 0.9; +} + +.tooltip-right[data-tooltip]:after { + top: 50%; + left: calc(100% + 0px); + transform: translate(0, -50%); + border: 0; + border-top: 6px solid transparent; + border-bottom: 6px solid transparent; + border-right: 6px solid rgba(17, 17, 17, 0.95); +} + +.tooltip-left[data-tooltip]:before { + top: 50%; + left: auto; + right: calc(100% + 6px); + transform: translate(0, -50%); + line-height: 0.9; +} + +.tooltip-left[data-tooltip]:after { + top: 50%; + left: auto; + right: calc(100% + 0px); + transform: translate(0, -50%); + border: 0; + border-top: 6px solid transparent; + border-bottom: 6px solid transparent; + border-left: 6px solid rgba(17, 17, 17, 0.95); +} +` +export default cssStr diff --git a/app/assets/frontends/beaker-forum/css/com/votectrl.css.js b/app/assets/frontends/beaker-forum/css/com/votectrl.css.js new file mode 100644 index 0000000000..22e071077c --- /dev/null +++ b/app/assets/frontends/beaker-forum/css/com/votectrl.css.js @@ -0,0 +1,43 @@ +import {css} from '../../vendor/lit-element/lit-element.js' + +const cssStr = css` +.votectrl { + position: relative; + top: 0; +} + +.votectrl > * { + display: block; + line-height: 0.8; + color: #bbc; +} + +.votectrl .karma, +.votectrl .fas { + width: 30px; + text-align: center; +} + +.votectrl .karma { + color: #889; + font-weight: 600; +} + +.votectrl .fas { + font-size: 16px; + cursor: pointer; +} + +.votectrl .upvoted, +.votectrl .upvote:hover, +.votectrl .upvote.selected { + color: var(--vote-color); +} + +.votectrl .downvoted, +.votectrl .downvote:hover, +.votectrl .downvote.selected { + color: var(--vote-color); +} +` +export default cssStr diff --git a/app/assets/frontends/beaker-forum/css/inputs.css.js b/app/assets/frontends/beaker-forum/css/inputs.css.js new file mode 100644 index 0000000000..110776c737 --- /dev/null +++ b/app/assets/frontends/beaker-forum/css/inputs.css.js @@ -0,0 +1,265 @@ +import {css} from '../vendor/lit-element/lit-element.js' + +const cssStr = css` +textarea { + line-height: 1.4; +} + +input, +textarea { + height: 30px; + padding: 0 7px; + border-radius: 4px; + color: rgba(51, 51, 51, 0.95); + border: 1px solid #d9d9d9; +} +textarea { + padding: 7px; +} + +input[type="checkbox"], +input[type="radio"], +input[type="range"] { + padding: 0; +} + +input[type="checkbox"]:focus, +input[type="radio"]:focus, +input[type="range"]:focus { + box-shadow: none; +} + +input[type="radio"] { + width: 14px; + height: 14px; + outline: none; + -webkit-appearance: none; + border-radius: 50%; + cursor: pointer; + transition: border 0.1s ease; +} + +input[type="radio"]:hover { + border: 1px solid var(--color-blue); +} + +input[type="radio"]:checked { + border: 4.5px solid var(--color-blue); +} + +input[type="file"] { + padding: 0; + border: 0; + line-height: 1; +} + +input[type="file"]:focus { + border: 0; + box-shadow: none; +} + +input:focus, +textarea:focus, +select:focus { + outline: 0; + border: 1px solid rgba(41, 95, 203, 0.8); + box-shadow: 0 0 0 2px rgba(41, 95, 203, 0.2); +} + +input.has-error, +textarea.has-error, +select.has-error { + border: 1px solid rgba(209, 48, 39, 0.75); +} + +input.has-error:focus, +textarea.has-error:focus, +select.has-error:focus { + box-shadow: 0 0 0 2px rgba(204, 47, 38, 0.15); +} + +input.nofocus:focus, +textarea.nofocus:focus, +select.nofocus:focus { + outline: 0; + box-shadow: none; + border: initial; +} + +input.inline { + height: auto; + border: 1px solid transparent; + border-radius: 0; + background: transparent; + cursor: text; + padding: 3px 5px; + line-height: 1; +} + +input.big, +textarea.big { + height: 38px; + padding: 0 10px; + font-size: 14px; +} + +textarea.big { + padding: 5px 10px; +} + +input.huge, +textarea.huge { + height: 40px; + padding: 0 10px; + font-size: 18px; +} + +textarea.huge { + padding: 5px 10px; +} + +input.inline:focus, +input.inline:hover { + border: 1px solid #ccc; + box-shadow: none; +} + +input.inline:focus { + background: #fff; +} + +.input-file-picker { + display: flex; + align-items: center; + padding: 3px; + border-radius: 2px; + border: 1px solid #d9d9d9; + color: var(--color-text--muted); +} + +.input-file-picker span { + flex: 1; + padding-left: 3px; +} + +::-webkit-input-placeholder { + color: rgba(0, 0, 0, 0.5); + font-size: 0.8rem; +} + +.big::-webkit-input-placeholder, +.huge::-webkit-input-placeholder { + font-size: 0.9em; +} + +label { + font-weight: 500; +} + +input[disabled][data-tooltip], +label[disabled][data-tooltip] { + cursor: help; +} + +input[disabled][data-tooltip] *, +label[disabled][data-tooltip] * { + cursor: help; +} + +label.required:after { + content: '*'; + color: red; +} + +.toggle { + display: flex; + align-items: center; + flex-direction: row; + margin-bottom: 10px; + cursor: pointer; + overflow: initial; +} + +.toggle .switch { + margin-right: 10px; +} + +.toggle * { + cursor: pointer; +} + +.toggle.disabled { + cursor: default; +} + +.toggle.disabled * { + cursor: default; +} + +.toggle input { + display: none; +} + +.toggle .text { + font-weight: 400; +} + +.toggle .switch { + display: inline-block; + position: relative; + width: 32px; + height: 17px; +} + +.toggle .switch:before, +.toggle .switch:after { + position: absolute; + display: block; + content: ''; +} + +.toggle .switch:before { + width: 100%; + height: 100%; + border-radius: 40px; + background: #dadada; +} + +.toggle .switch:after { + width: 11px; + height: 11px; + border-radius: 50%; + left: 3px; + top: 3px; + background: #fafafa; + transition: transform 0.15s ease; +} + +.toggle input:checked:not(:disabled) + .switch:before { + background: #41b855; +} + +.toggle input:checked:not(:disabled) + .switch:after { + transform: translateX(15px); +} + +.toggle.disabled { + color: var(--color-text--light); +} + +label.checkbox-container { + display: flex; + align-items: center; + height: 15px; + font-weight: 400; +} + +label.checkbox-container input[type="checkbox"] { + width: 15px; + height: 15px; + margin: 0 5px 0 0; +} + + +` +export default cssStr diff --git a/app/assets/frontends/beaker-forum/css/main.css.js b/app/assets/frontends/beaker-forum/css/main.css.js new file mode 100644 index 0000000000..5546ca9c56 --- /dev/null +++ b/app/assets/frontends/beaker-forum/css/main.css.js @@ -0,0 +1,264 @@ +import {css} from '../vendor/lit-element/lit-element.js' +import emptyCSS from './com/empty.css.js' +import spinnerCSS from './com/spinner.css.js' +import tooltipCSS from './com/tooltip.css.js' + +const cssStr = css` +${emptyCSS} +${spinnerCSS} +${tooltipCSS} + +:host { + display: block; +} + +.layout { + margin: 0 auto; + padding: 0 10px; +} + +.layout.narrow { + max-width: 640px; +} + +.layout.left-col { + display: grid; + grid-template-columns: 240px minmax(0, 1fr); + grid-gap: 10px; +} + +.layout.right-col { + display: grid; + grid-template-columns: minmax(0, 1fr) 340px; + grid-gap: 50px; +} + +.layout.split-col { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); + grid-gap: 10px; +} + +.layout.three-col { + display: grid; + grid-template-columns: 240px minmax(0, 1fr) 340px; + grid-gap: 10px; +} + +@media (max-width: 800px) { + .layout.right-col { + grid-template-columns: minmax(0, 1fr); + } + .layout.right-col > :last-child { + display: none; + } +} + +@media (max-width: 1200px) { + .layout.three-col { + grid-template-columns: 240px minmax(0, 1fr); + } + .layout.three-col > :last-child { + display: none; + } +} + +header { + display: flex; + align-items: center; + letter-spacing: 0.75px; + height: 52px; + background: var(--header-background); + color: var(--header-color); + padding: 0 20px 0 10px; + margin-bottom: 10px; +} + +header a { + display: block; + color: inherit; + font-weight: 500; + text-decoration: none; + font-size: 14px; + padding: 2px 0; + margin-right: 14px; +} + +header a:last-child { + margin-right: 0px; +} + +header a:hover { + color: var(--link-color); +} + +header .brand { + display: flex; + align-items: center; + font-family: "Segoe UI", Ubuntu, Cantarell, "Oxygen Sans", "Helvetica Neue", sans-serif; + font-size: 23px; + line-height: 1; +} + +header .brand img { + display: block; + margin: 0 10px 0 4px; + width: 38px; + height: 38px; + object-fit: cover; + border-radius: 50%; +} + +header .avatar { + width: 38px; + height: 38px; + object-fit: cover; + border-radius: 50%; +} + +header .circle-btn { + position: relative; + display: block; + box-sizing: border-box; + padding: 0; + line-height: 26px; + font-size: 18px; + text-align: center; + color: var(--header-notifications-color); +} + +header .circle-btn:hover { + color: var(--link-color); +} + +header .circle-btn.highlighted { +} + +header .circle-btn small { + position: absolute; + right: -1px; + top: 0px; + color: var(--header-notifications-color--highlighted); + line-height: 1; + font-size: 8px; +} + +header beaker-search-input { + --input-bg-color: var(--header-search-background); + --input-color: var(--header-search-color); + --input-border: 1px solid var(--header-search-border-color); +} + +header .compose-btn { + background: var(--button-background--primary); + color: var(--button-color--primary); + border-radius: 20px; + font-size: 12px; + padding: 6px 14px; + font-weight: normal; +} + +header .compose-btn:hover { + background: var(--button-background--primary--hover); + color: var(--button-color--primary--hover); +} + +header .logout { + cursor: pointer; + color: var(--header-search-color); +} + +header .spacer { + flex: 1; +} + +nav.pills { + display: flex; + padding-left: 8px; + margin: 0 0 10px; + font-size: 13px; + letter-spacing: 0.5px; + border-bottom: 1px solid #ccd; +} + +nav.pills a { + padding: 6px 16px; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + margin-right: 4px; + color: inherit; + text-decoration: none; +} + +nav.pills a:hover { + cursor: pointer; + background: #f0f0f8; +} + +nav.pills a.selected { + position: relative; + border: 1px solid #ccd; + border-bottom: 0; + background: #fff; +} + +nav.pills a.selected:after { + content: ''; + display: block; + position: absolute; + bottom: -1px; + left: 0; + right: 0; + height: 2px; + background: #fff; +} + +.flash-message { + padding: 20px; + background: #C5E1A5; + color: #113113; + font-size: 15px; + border-radius: 4px; + margin: 20px 10px 20px; +} + +.flash-message > :first-child { + margin-top: 0; +} + +.flash-message > :last-child { + margin-bottom: 0; +} + +.flash-message h2 { + color: #1b3e0e; +} + +.flash-message a.copy-btn { + display: flex; + justify-content: space-between; + font-size: 15px; + background: #fff; + color: inherit; + padding: 10px 20px; + border-radius: 24px; + box-sizing: border-box; + color: #556; + cursor: pointer; + box-shadow: 0 1px 2px #0002; + transition: box-shadow 0.2s; + max-width: 640px; +} + +.flash-message a.copy-btn:hover { + box-shadow: 0 2px 4px #0002; +} + +@media(max-width: 840px) { + header beaker-search-input { + display: none; + } +} + +` +export default cssStr \ No newline at end of file diff --git a/app/assets/frontends/beaker-forum/img/default-group-banner.png b/app/assets/frontends/beaker-forum/img/default-group-banner.png new file mode 100644 index 0000000000..6f97863272 Binary files /dev/null and b/app/assets/frontends/beaker-forum/img/default-group-banner.png differ diff --git a/app/assets/frontends/beaker-forum/img/default-group-thumb.jpg b/app/assets/frontends/beaker-forum/img/default-group-thumb.jpg new file mode 100644 index 0000000000..301d211260 Binary files /dev/null and b/app/assets/frontends/beaker-forum/img/default-group-thumb.jpg differ diff --git a/app/assets/frontends/beaker-forum/img/default-icon.png b/app/assets/frontends/beaker-forum/img/default-icon.png new file mode 100644 index 0000000000..7dc6d7d729 Binary files /dev/null and b/app/assets/frontends/beaker-forum/img/default-icon.png differ diff --git a/app/assets/frontends/beaker-forum/img/default-user-thumb.jpg b/app/assets/frontends/beaker-forum/img/default-user-thumb.jpg new file mode 100644 index 0000000000..301d211260 Binary files /dev/null and b/app/assets/frontends/beaker-forum/img/default-user-thumb.jpg differ diff --git a/app/assets/frontends/beaker-forum/img/help-copy-profile-url.jpg b/app/assets/frontends/beaker-forum/img/help-copy-profile-url.jpg new file mode 100644 index 0000000000..b8e6838138 Binary files /dev/null and b/app/assets/frontends/beaker-forum/img/help-copy-profile-url.jpg differ diff --git a/app/assets/frontends/beaker-forum/img/help-join-group.jpg b/app/assets/frontends/beaker-forum/img/help-join-group.jpg new file mode 100644 index 0000000000..f51df6af7e Binary files /dev/null and b/app/assets/frontends/beaker-forum/img/help-join-group.jpg differ diff --git a/app/assets/frontends/beaker-forum/img/logo-16x16.png b/app/assets/frontends/beaker-forum/img/logo-16x16.png new file mode 100644 index 0000000000..965b8a3928 Binary files /dev/null and b/app/assets/frontends/beaker-forum/img/logo-16x16.png differ diff --git a/app/assets/frontends/beaker-forum/img/logo.png b/app/assets/frontends/beaker-forum/img/logo.png new file mode 100644 index 0000000000..670d6771ff Binary files /dev/null and b/app/assets/frontends/beaker-forum/img/logo.png differ diff --git a/app/assets/frontends/beaker-forum/img/logo.svg b/app/assets/frontends/beaker-forum/img/logo.svg new file mode 100644 index 0000000000..99bbf02723 --- /dev/null +++ b/app/assets/frontends/beaker-forum/img/logo.svg @@ -0,0 +1,62 @@ + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/app/assets/frontends/beaker-forum/js/com/about.js b/app/assets/frontends/beaker-forum/js/com/about.js new file mode 100644 index 0000000000..58f80abf1c --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/com/about.js @@ -0,0 +1,102 @@ +import { LitElement, html } from '../../vendor/lit-element/lit-element.js' +import { repeat } from '../../vendor/lit-element/lit-html/directives/repeat.js' +import { unsafeHTML } from '../../vendor/lit-element/lit-html/directives/unsafe-html.js' +import { GroupSettingsPopup } from './popups/group-settings.js' +import { AddUserPopup } from './popups/add-user.js' +import css from '../../css/com/about.css.js' +import * as uwg from '../lib/uwg.js' +import { pluralize } from '../lib/strings.js' +import MarkdownIt from '../../vendor/markdown-it.js' + +const md = MarkdownIt({ + html: false, // Enable HTML tags in source + xhtmlOut: false, // Use '/' to close single tags (
) + breaks: true, // Convert '\n' in paragraphs into
+ linkify: false, // Autoconvert URL-like text to links + typographer: true, + quotes: '“”‘’' +}) + +export class About extends LitElement { + static get styles () { + return css + } + + constructor () { + super() + this.info = undefined + this.groupware = undefined + this.userCount = undefined + this.sidebarMd = undefined + } + + async load () { + var drive = hyperdrive.self + this.info = await drive.getInfo() + this.requestUpdate() + this.groupware = await drive.readFile('/beaker-forum/groupware.json').then(JSON.parse).catch(e => undefined) + this.requestUpdate() + this.userCount = await uwg.users.count() + this.requestUpdate() + this.sidebarMd = await drive.readFile('/beaker-forum/sidebar.md').catch(e => undefined) + this.requestUpdate() + } + + render () { + return html` + + ${this.groupware?.applications?.length ? html` +

Groupware

+
+ ${repeat(this.groupware.applications, app => html` + + + ${app.title} + + `)} +
+ ` : ''} +

About This Group

+
${this.info ? (this.info.description || html`No description`) : ''}
+
+ ${typeof this.userCount === 'undefined' ? '' : html` + +
${this.userCount}
+
${pluralize(this.userCount || 0, 'User')}
+
+ `} +
+ ${this.sidebarMd ? html` + + ` : ''} + ${this.info?.writable ? html` +

Admin Tools

+
+ + +
+ ` : ''} + ` + } + + // events + // = + + async onEditSettings () { + try { + await GroupSettingsPopup.create(document.body) + location.reload() + } catch (e) { + // ignore + throw e + } + } + + async onAddUser () { + AddUserPopup.create(document.body) + } +} + +customElements.define('beaker-about', About) diff --git a/app/assets/frontends/beaker-forum/js/com/comments/composer.js b/app/assets/frontends/beaker-forum/js/com/comments/composer.js new file mode 100644 index 0000000000..fc5a4bd5d5 --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/com/comments/composer.js @@ -0,0 +1,98 @@ +import {LitElement, html} from '../../../vendor/lit-element/lit-element.js' +import commentComposerCSS from '../../../css/com/comments/composer.css.js' +import { emit } from '../../lib/dom.js' + +export class CommentComposer extends LitElement { + static get properties () { + return { + isEditing: {type: Boolean, attribute: 'editing'}, + href: {type: String}, + parent: {type: String}, + comment: {type: Object}, + isFocused: {type: Boolean}, + draftText: {type: String}, + placeholder: {type: String} + } + } + + constructor () { + super() + this.isEditing = false + this.href = '' + this.parent = '' + this.comment = undefined + this.isFocused = false + this.draftText = '' + this.placeholder = 'Write a new comment' + } + + updated (changedProperties) { + if (this.isEditing && changedProperties.has('comment')) { + this.draftText = this.comment.content + } + } + + _submit () { + if (!this.draftText) return + var detail = { + isEditing: this.isEditing, + editTarget: this.comment, + href: this.href, + parent: this.parent || undefined, + content: this.draftText + } + emit(this, 'submit-comment', {bubbles: true, detail}) + this.draftText = '' + } + + focus () { + this.shadowRoot.querySelector('textarea').focus() + } + + // rendering + // = + + render () { + return html` + +
+ +
+ ` + } + + // events + // = + + onKeydownTextarea (e) { + // check for cmd/ctrl+enter + if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) { + e.preventDefault() + e.currentTarget.value = '' + e.currentTarget.blur() + return this._submit() + } + this.onChangeTextarea(e) + } + + onChangeTextarea (e) { + this.draftText = e.currentTarget.value + } + + onClickPost () { + this._submit() + } +} +CommentComposer.styles = commentComposerCSS + +customElements.define('beaker-comment-composer', CommentComposer) \ No newline at end of file diff --git a/app/assets/frontends/beaker-forum/js/com/comments/feed.js b/app/assets/frontends/beaker-forum/js/com/comments/feed.js new file mode 100644 index 0000000000..06f6d7e647 --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/com/comments/feed.js @@ -0,0 +1,157 @@ +import { LitElement, html } from '../../../vendor/lit-element/lit-element.js' +import { repeat } from '../../../vendor/lit-element/lit-html/directives/repeat.js' +import * as uwg from '../../lib/uwg.js' +import { writeToClipboard } from '../../lib/clipboard.js' +import * as contextMenu from '../context-menu.js' +import * as toast from '../toast.js' +import { timeDifference } from '../../lib/time.js' +import { fromPostUrlToAppRoute } from '../../lib/strings.js' +import feedCSS from '../../../css/com/comments/feed.css.js' +import '../paginator.js' + +const PAGE_SIZE = 25 + +export class CommentsFeed extends LitElement { + static get properties () { + return { + user: {type: Object}, + author: {type: String}, + comments: {type: Array}, + error: {type: String} + } + } + + static get styles () { + return feedCSS + } + + constructor () { + super() + this.user = undefined + this.author = undefined + this.comments = undefined + this.error = false + this.page = 0 + } + + async load () { + try { + var authorProfile = this.author ? await uwg.users.getByUserID(this.author) : undefined + var comments = await uwg.comments.list({ + author: this.author ? authorProfile.url : undefined, + offset: this.page * PAGE_SIZE, + limit: PAGE_SIZE, + sort: 'name', + reverse: true + }, {includeProfiles: true, includeContent: true}) + this.comments = comments + console.log(this.comments) + } catch (e) { + this.error = e.toString() + } + } + + render () { + return html` + +
+ ${this.error ? html` +
+ ${this.error} +
+ ` : typeof this.comments === 'undefined' ? html` +
+ +
+ ` : html` + ${repeat(this.comments, comment => { + var contextUrl = fromPostUrlToAppRoute(comment.stat.metadata.href) + return html` +
+
+ +
${comment.content}
+ +
+
+ ` + })} + ${this.comments.length === 0 + ? html` +
+
+
+ ${this.author + ? 'This user has not made any comments.' + : 'This group has not made any comments.'} +
+
+ ` : ''} + ${this.page > 0 || this.comments.length === PAGE_SIZE ? html` + + ` : ''} + `} +
+ ` + } + + // events + // = + + onClickMenu (e, comment) { + e.preventDefault() + e.stopPropagation() + + var items = [ + { + icon: 'fas fa-fw fa-link', + label: 'Copy comment URL', + click: () => { + writeToClipboard(comment.url) + toast.create('Copied to your clipboard') + } + } + ] + + if (this.user && this.user.url === comment.drive.url) { + items.push({icon: 'fas fa-fw fa-trash', label: 'Delete comment', click: () => this.onClickDelete(comment) }) + } + + var rect = e.currentTarget.getClientRects()[0] + contextMenu.create({ + x: rect.left, + y: rect.bottom + 8, + left: true, + roomy: true, + noBorders: true, + style: `padding: 4px 0`, + items + }) + } + + onClickDelete (comment) { + if (!confirm('Are you sure?')) return + + // TODO + + this.comments = this.comments.filter(c => c.url !== comment.url) + } + + onChangePage (e) { + this.page = e.detail.page + this.comments = undefined + this.load() + } +} + +customElements.define('beaker-comments-feed', CommentsFeed) diff --git a/app/assets/frontends/beaker-forum/js/com/comments/thread.js b/app/assets/frontends/beaker-forum/js/com/comments/thread.js new file mode 100644 index 0000000000..28f9a3eb93 --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/com/comments/thread.js @@ -0,0 +1,171 @@ +import { LitElement, html } from '../../../vendor/lit-element/lit-element.js' +import { repeat } from '../../../vendor/lit-element/lit-html/directives/repeat.js' +import commentsThreadCSS from '../../../css/com/comments/thread.css.js' +import { timeDifference } from '../../lib/time.js' +import { writeToClipboard } from '../../lib/clipboard.js' +import * as uwg from '../../lib/uwg.js' +import { emit } from '../../lib/dom.js' +import * as contextMenu from '../context-menu.js' +import * as toast from '../toast.js' +import './composer.js' + +export class CommentsThread extends LitElement { + static get properties () { + return { + comments: {type: Array}, + href: {type: String}, + userUrl: {type: String, attribute: 'user-url'}, + activeReplies: {type: Object}, + activeEdits: {type: Object}, + composerPlaceholder: {type: String, attribute: 'composer-placeholder'} + } + } + + constructor () { + super() + this.comments = null + this.href = '' + this.userUrl = '' + this.activeReplies = {} + this.activeEdits = {} + this.composerPlaceholder = undefined + } + + render () { + return html` + + + ${this.renderComments(this.comments)} + ` + } + + renderComments (comments) { + if (!comments.length) return '' + return html` +
+ ${repeat(comments, c => c.url, c => this.renderComment(c))} +
+ ` + } + + renderComment (comment) { + return html` +
+
+ +
${comment.content}
+ + ${this.activeReplies[comment.url] ? html` + this.onSubmitComment(e, comment.url)} + > + ` : ''} + ${this.activeEdits[comment.url] ? html` + this.onSubmitEdit(e, comment.url)} + > + ` : ''} + ${comment.replies && comment.replies.length ? this.renderComments(comment.replies) : ''} +
+
+ ` + } + + // events + // = + + async onClickToggleReply (e, url) { + this.activeReplies[url] = !this.activeReplies[url] + this.activeEdits[url] = false + await this.requestUpdate() + if (this.activeReplies[url]) { + this.shadowRoot.querySelector(`beaker-comment-composer[parent="${url}"]`).focus() + } + } + + async onClickToggleEdit (e, url) { + this.activeEdits[url] = !this.activeEdits[url] + this.activeReplies[url] = false + await this.requestUpdate() + if (this.activeEdits[url]) { + this.shadowRoot.querySelector(`beaker-comment-composer[parent="${url}"]`).focus() + } + } + + onSubmitComment (e, url) { + this.activeReplies[url] = false + this.requestUpdate() + } + + onSubmitEdit (e, url) { + this.activeEdits[url] = false + this.requestUpdate() + } + + onClickMenu (e, comment) { + e.preventDefault() + e.stopPropagation() + + var items = [ + { + icon: 'fas fa-fw fa-link', + label: 'Copy comment URL', + click: () => { + writeToClipboard(comment.url) + toast.create('Copied to your clipboard') + } + } + ] + + if (this.userUrl === comment.drive.url) { + items.push({icon: 'fas fa-fw fa-trash', label: 'Delete comment', click: () => this.onClickDelete(comment) }) + } + + var rect = e.currentTarget.getClientRects()[0] + contextMenu.create({ + x: rect.left, + y: rect.bottom + 8, + left: true, + roomy: true, + noBorders: true, + style: `padding: 4px 0`, + items + }) + } + + onClickDelete (comment) { + if (!confirm('Are you sure?')) return + emit(this, 'delete-comment', {bubbles: true, composed: true, detail: {comment}}) + } +} +CommentsThread.styles = commentsThreadCSS + +customElements.define('beaker-comments-thread', CommentsThread) \ No newline at end of file diff --git a/app/assets/frontends/beaker-forum/js/com/context-menu.js b/app/assets/frontends/beaker-forum/js/com/context-menu.js new file mode 100644 index 0000000000..5e13952710 --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/com/context-menu.js @@ -0,0 +1,228 @@ +import {LitElement, html, css} from '../../vendor/lit-element/lit-element.js' +import {classMap} from '../../vendor/lit-element/lit-html/directives/class-map.js' +import {ifDefined} from '../../vendor/lit-element/lit-html/directives/if-defined.js' +import {findParent} from '../lib/dom.js' +import dropdownCSS from '../../css/com/dropdown.css.js' + +// globals +// = + +var resolve + +// exported api +// = + +// create a new context menu +// - returns a promise that will resolve to undefined when the menu goes away +// - example usage: +/* +create({ + // where to put the menu + x: e.clientX, + y: e.clientY, + + // align edge to right instead of left + right: true, + + // use triangle + withTriangle: true, + + // roomy style + roomy: true, + + // no borders on items + noBorders: false, + + // additional styles on dropdown-items + style: 'font-size: 14px', + + // parent element to append to + parent: document.body, + + // menu items + items: [ + // icon from font-awesome + {icon: 'fa fa-link', label: 'Copy link', click: () => writeToClipboard('...')} + ] + + // instead of items, can give render() + render () { + return html` + + ` + } +} +*/ +export function create (opts) { + // destroy any existing + destroy() + + // extract attrs + var parent = opts.parent || document.body + + // render interface + parent.appendChild(new BeakerContextMenu(opts)) + document.addEventListener('keyup', onKeyUp) + document.addEventListener('click', onClickAnywhere) + + // return promise + return new Promise(_resolve => { + resolve = _resolve + }) +} + +export function destroy (value) { + const el = document.querySelector('beaker-context-menu') + if (el) { + el.parentNode.removeChild(el) + document.removeEventListener('keyup', onKeyUp) + document.removeEventListener('click', onClickAnywhere) + resolve(value) + } +} + +// global event handlers +// = + +function onKeyUp (e) { + e.preventDefault() + e.stopPropagation() + + if (e.keyCode === 27) { + destroy() + } +} + +function onClickAnywhere (e) { + if (!findParent(e.target, el => el.tagName === 'BEAKER-CONTEXT-MENU')) { + // click is outside the context-menu, destroy + destroy() + } +} + +// internal +// = + +export class BeakerContextMenu extends LitElement { + constructor ({x, y, right, center, top, withTriangle, roomy, noBorders, style, items, render}) { + super() + this.x = x + this.y = y + this.right = right || false + this.center = center || false + this.top = top || false + this.withTriangle = withTriangle || false + this.roomy = roomy || false + this.noBorders = noBorders || false + this.customStyle = style || undefined + this.items = items + this.customRender = render + } + + // calls the global destroy + // (this function exists so that custom renderers can destroy with this.destroy) + destroy () { + destroy() + } + + // rendering + // = + + render () { + const cls = classMap({ + 'dropdown-items': true, + right: this.right, + center: this.center, + left: !this.right, + top: this.top, + 'with-triangle': this.withTriangle, + roomy: this.roomy, + 'no-border': this.noBorders + }) + var style = '' + if (this.x) style += `left: ${this.x}px; ` + if (this.y) style += `top: ${this.y}px; ` + return html` + + ` + } +} + +BeakerContextMenu.styles = css` +${dropdownCSS} + +.context-menu { + position: fixed; + z-index: 10000; +} + +.dropdown-items { + width: auto; + white-space: nowrap; +} + +a.dropdown-item { + color: inherit; + text-decoration: none; +} + +.dropdown-item, +.dropdown-items.roomy .dropdown-item { + padding-right: 30px; /* add a little cushion to the right */ +} + +/* custom icon css */ +.fa-long-arrow-alt-right.custom-link-icon { + position: relative; + transform: rotate(-45deg); + left: 1px; +} +.fa-custom-path-icon:after { + content: './'; + letter-spacing: -1px; + font-family: var(--code-font); +} +` + +customElements.define('beaker-context-menu', BeakerContextMenu) \ No newline at end of file diff --git a/app/assets/frontends/beaker-forum/js/com/img-fallbacks.js b/app/assets/frontends/beaker-forum/js/com/img-fallbacks.js new file mode 100644 index 0000000000..5033d5e367 --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/com/img-fallbacks.js @@ -0,0 +1,39 @@ +import {LitElement, html} from '../../vendor/lit-element/lit-element.js' + +/* +Usage: + + + + + + +*/ + +export class ImgFallbacks extends LitElement { + static get properties () { + return { + currentImage: {type: Number} + } + } + + constructor () { + super() + this.currentImage = 1 + } + + render () { + return html`` + } + + onSlotChange (e) { + var img = this.shadowRoot.querySelector('slot').assignedElements()[0] + if (img) img.addEventListener('error', this.onError.bind(this)) + } + + onError (e) { + this.currentImage = this.currentImage + 1 + } +} + +customElements.define('beaker-img-fallbacks', ImgFallbacks) diff --git a/app/assets/frontends/beaker-forum/js/com/notifications/feed.js b/app/assets/frontends/beaker-forum/js/com/notifications/feed.js new file mode 100644 index 0000000000..13079d05de --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/com/notifications/feed.js @@ -0,0 +1,194 @@ +import { LitElement, html } from '../../../vendor/lit-element/lit-element.js' +import { repeat } from '../../../vendor/lit-element/lit-html/directives/repeat.js' +import * as notificationsIndex from '../../lib/notifications.js' +import * as uwg from '../../lib/uwg.js' +import { toNiceUrl } from '../../lib/strings.js' +import { timeDifference } from '../../lib/time.js' +import feedCSS from '../../../css/com/notifications/feed.css.js' +import '../paginator.js' + +const PAGE_SIZE = 50 + +export class NotificationsFeed extends LitElement { + static get properties () { + return { + user: {type: Object}, + notifications: {type: Array} + } + } + + static get styles () { + return feedCSS + } + + constructor () { + super() + this.user = undefined + this.notifications = undefined + this.page = 0 + } + + async load () { + var notifications = await notificationsIndex.list({ + offset: this.page * PAGE_SIZE, + limit: PAGE_SIZE + }) + /* dont await */ this.loadFeedInformation(notifications) + this.notifications = notifications + console.log(this.notifications) + } + + async loadFeedInformation (notifications) { + for (let notification of notifications) { + try { + let [authorProfile, targetContent] = await Promise.all([ + uwg.profiles.get(notification.author), + this.fetchTargetContent(notification) + ]) + notification.authorProfile = authorProfile + notification.targetContent = targetContent + this.requestUpdate() + } catch (e) { + console.error('Failed to fetch notification content', e) + } + } + } + + fetchTargetContent (notification) { + if (notification.event === 'comment') { + if (notification.detail.href.includes('/comments/')) { + let urlp = new URL(notification.detail.href) + return uwg.comments.get(urlp.origin, urlp.pathname).catch(err => notification.detail.href) + } + if (notification.detail.href.includes('/posts/')) { + let urlp = new URL(notification.detail.href) + return uwg.posts.get(urlp.origin, urlp.pathname).catch(err => notification.detail.href) + } + } + return 'your content' + } + + getHref (notification) { + if (notification.event === 'comment') { + let urlp = new URL(notification.detail.href) + let author = urlp.hostname + let filename = urlp.pathname.split('/').pop() + if (notification.detail.href.includes('/comments/')) { + return `/users/${author}/comments/${filename}` + } + if (notification.detail.href.includes('/posts/')) { + return `/users/${author}/posts/${filename}` + } + } + } + + getIcon (notification) { + if (notification.event === 'comment') { + return 'far fa-comment' + } + return '' + } + + getPastTenseAction (notification) { + if (notification.event === 'comment') { + return 'replied to' + } + return 'did something? to' + } + + getContentType (notification) { + if (notification.event === 'comment') { + if (notification.detail.href.includes('/comments/')) { + return 'your comment' + } + if (notification.detail.href.includes('/posts/')) { + return 'your post' + } + } + return 'your content' + } + + render () { + return html` + +
+ ${typeof this.notifications === 'undefined' ? html` +
+ +
+ ` : html` + ${repeat(this.notifications, notification => { + return html` + + + + + + + ${notification.authorProfile ? notification.authorProfile.title : toNiceUrl(notification.author)} + ${this.getPastTenseAction(notification)} + ${this.getContentType(notification)} + ${timeDifference(+notification.timestamp, false, 'ago')} + + + ${typeof notification.targetContent === 'string' ? html` + ${toNiceUrl(notification.targetContent)} + ` : notification.targetContent ? html` + ${this.renderTargetContent(notification.targetContent)} + ` : html` + + `} + + + + ` + })} + ${this.notifications.length === 0 + ? html` +
+
+
+ You have no notifications. +
+
+ ` : ''} + ${this.page > 0 || this.notifications.length === PAGE_SIZE ? html` + + ` : ''} + `} +
+ ` + } + + renderTargetContent (targetContent) { + if (targetContent.path.includes('/posts/')) { + return html` + + ${targetContent.stat.metadata.title} + + ` + } + if (targetContent.path.includes('/comments/')) { + return html` + + ${targetContent.content} + + ` + } + } + + // events + // = + + onChangePage (e) { + this.page = e.detail.page + this.notifications = undefined + this.load() + } +} + +customElements.define('beaker-notifications-feed', NotificationsFeed) diff --git a/app/assets/frontends/beaker-forum/js/com/paginator.js b/app/assets/frontends/beaker-forum/js/com/paginator.js new file mode 100644 index 0000000000..2c8d43fb61 --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/com/paginator.js @@ -0,0 +1,46 @@ +import { LitElement, html } from '../../vendor/lit-element/lit-element.js' +import css from '../../css/com/paginator.css.js' +import { emit } from '../lib/dom.js' + +export class PostButtons extends LitElement { + static get properties () { + return { + page: {type: Number}, + label: {type: String}, + atEnd: {type: Boolean, attribute: 'at-end'} + } + } + + static get styles () { + return css + } + + constructor () { + super() + this.page = 0 + this.label = '' + this.atEnd = false + } + + render () { + return html` + + ${this.page > 0 ? html`` : ''} + ${this.label || this.page} + ${!this.atEnd ? html`` : ''} + ` + } + + // events + // = + + onClickLeft () { + emit(this, 'change-page', {detail: {page: this.page - 1}}) + } + + onClickRight () { + emit(this, 'change-page', {detail: {page: this.page + 1}}) + } +} + +customElements.define('beaker-paginator', PostButtons) diff --git a/app/assets/frontends/beaker-forum/js/com/pinned-message.js b/app/assets/frontends/beaker-forum/js/com/pinned-message.js new file mode 100644 index 0000000000..2f84d2780a --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/com/pinned-message.js @@ -0,0 +1,47 @@ +import { LitElement, html } from '../../vendor/lit-element/lit-element.js' +import { unsafeHTML } from '../../vendor/lit-element/lit-html/directives/unsafe-html.js' +import css from '../../css/com/pinned-message.css.js' +import MarkdownIt from '../../vendor/markdown-it.js' + +const md = MarkdownIt({ + html: false, // Enable HTML tags in source + xhtmlOut: false, // Use '/' to close single tags (
) + breaks: true, // Convert '\n' in paragraphs into
+ linkify: false, // Autoconvert URL-like text to links + typographer: true, + quotes: '“”‘’' +}) + +export class PinnedMessage extends LitElement { + static get styles () { + return css + } + + constructor () { + super() + this.md = undefined + } + + async load () { + if (localStorage.pinnedMessageHidden) return + var drive = hyperdrive.self + this.md = await drive.readFile('/beaker-forum/pinned-message.md').catch(e => undefined) + this.requestUpdate() + } + + render () { + if (!this.md) return '' + return html` + + +
${unsafeHTML(md.render(this.md))}
+ ` + } + + onClickHide (e) { + localStorage.pinnedMessageHidden = 1 + this.remove() + } +} + +customElements.define('beaker-pinned-message', PinnedMessage) diff --git a/app/assets/frontends/beaker-forum/js/com/popups/add-user.js b/app/assets/frontends/beaker-forum/js/com/popups/add-user.js new file mode 100644 index 0000000000..fe604b4515 --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/com/popups/add-user.js @@ -0,0 +1,343 @@ +/* globals beaker */ +import { html, css } from '../../../vendor/lit-element/lit-element.js' +import { BasePopup } from './base.js' +import popupsCSS from '../../../css/com/popups.css.js' +import spinnerCSS from '../../../css/com/spinner.css.js' +import { emit } from '../../lib/dom.js' +import * as toast from '../toast.js' +import * as uwg from '../../lib/uwg.js' +import { writeToClipboard } from '../../lib/clipboard.js' + +// exported api +// = + +export class AddUserPopup extends BasePopup { + static get properties () { + return { + page: {type: Number}, + userUrl: {type: String}, + userId: {type: String}, + errors: {type: Object}, + currentTask: {type: String} + } + } + + static get styles () { + return [popupsCSS, spinnerCSS, css` + .popup-inner .body { + padding: 0; + } + + section { + padding: 16px; + border-bottom: 1px solid #dde; + } + + h3 { + margin-top: 0; + font-weight: normal; + letter-spacing: 1px; + } + + h3 .step-number { + position: relative; + top: -1px; + left: -1px; + display: inline-block; + color: #556; + background: #f1f1f6; + border-radius: 50%; + width: 20px; + height: 20px; + font-size: 12px; + line-height: 20px; + text-align: center; + font-weight: 500; + font-variant: tabular-nums; + margin-right: 2px; + } + + a.copy-btn { + display: flex; + justify-content: space-between; + font-size: 13px; + background: #f1f1f6; + color: inherit; + padding: 10px 20px; + border-radius: 24px; + box-sizing: border-box; + color: #556; + cursor: pointer; + } + + a.copy-btn > :first-child { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + padding-right: 5px; + } + + a.copy-btn:hover { + background: #eaeaef; + } + + img { + display: block; + margin: 0 auto; + width: 390px; + border-radius: 4px; + box-shadow: 0 1px 2px #0003; + } + + .instructions { + margin: 14px 14px 0; + color: #889; + letter-spacing: 0.3px; + font-weight: 300; + } + + .long-error { + display: flex; + align-items: baseline; + background: #fffafa; + padding: 10px; + border-radius: 4px; + color: #d80b00; + letter-spacing: 0.5px; + line-height: 1.3; + } + + .long-error .fa-fw { + margin-right: 8px; + font-size: 12px; + } + + .form-actions { + display: flex; + justify-content: space-between; + align-items: center; + padding: 16px; + text-align: left; + } + + .task { + display: flex; + align-items: center; + background: #f1f1f6; + padding: 10px; + border-top: 1px solid #ccd; + } + + .task .spinner { + margin-left: 5px; + margin-right: 10px; + } + `] + } + + constructor () { + super() + this.page = 1 + this.userUrl = '' + this.userId = '' + this.errors = {} + this.currentTask = '' + } + + async attempt (task, fn) { + this.currentTask = task + try { + return await fn() + } finally { + this.currentTask = undefined + } + } + + async validateUserDrive (url) { + var urlp + try { + urlp = new URL(url) + } catch (e) { + return {success: false, message: 'This is not a valid URL. Make sure you input it correctly.'} + } + if (urlp.protocol !== 'hyper:') { + return {success: false, message: `You must provide a "hyper:" URL. This is "${urlp.protocol}".`} + } + + var drive = hyperdrive.load(url) + var info + try { + info = await this.attempt( + 'Finding the profile on the network (this may take a moment)...', + () => drive.readFile('/index.json').then(JSON.parse) + ) + } catch (e) { + console.log('Failed to read manifest', e) + return {success: false, message: 'This profile does not have a valid manifest (the index.json file). Ask your friend to make sure they sent the correct URL.'} + } + + if (info.type !== 'user') { + return {success: false, message: `This profile is not a "user" type (found "${info.type}"). Ask your friend to make sure they sent the correct URL.`} + } + + var isRightMemberOf = false + try { + let memberOf = new URL(info.memberOf) + isRightMemberOf = memberOf.hostname === location.hostname + } catch (e) {} + if (isRightMemberOf) { + return {success: false, message: `This profile was not created for this group. Ask your friend to make sure they sent the correct URL and that they joined the correct group.`} + } + + return {success: true} + } + + // management + // + + static async create (parentEl) { + return BasePopup.coreCreate(parentEl, AddUserPopup) + } + + static destroy () { + return BasePopup.destroy('beaker-add-user-popup') + } + + // rendering + // = + + renderTitle () { + return 'Add User' + } + + renderBody () { + return html` +
+ ${this.page === 1 ? html` +
+

1 Send this group's URL to your friend

+ +
+ +
+

2 Ask them to create a new profile

+ +
+ Tell them: "Visit the group and click Join this Group. It will help you create a profile." +
+
+ +
+

3 Ask them for their Profile URL

+ +
+ Tell them: "You should see a prompt to send me your URL. Copy it and send it over." +
+
+ +
+ + +
+ ` : html` +
+

4 Enter their URL and ID

+ + + +
This is the URL of the profile which they generated.
+ ${this.errors.userUrl ? html`
${this.errors.userUrl}
` : ''} + + ${this.errors.userDrive ? html` +
+ + ${this.errors.userDrive} +
+ ` : ''} + + + +
This will be their ID in the group.
+ ${this.errors.userId ? html`
${this.errors.userId}
` : ''} +
+ +
+ + +
+ `} + ${this.currentTask ? html` +
+ ${this.currentTask} +
+ ` : ''} +
+ ` + } + + // events + // = + + onClickCopyDriveUrl (e) { + e.preventDefault() + writeToClipboard(location.origin) + toast.create('Copied to your clipboard') + } + + onChangeUserUrl (e) { + this.userUrl = e.target.value.trim() + } + + onChangeUserId (e) { + this.userId = e.target.value.trim() + } + + onClickNext (e) { + e.preventDefault() + this.page = 2 + } + + onClickBack (e) { + e.preventDefault() + this.page = 1 + } + + onClickCancel (e) { + e.preventDefault() + emit(this, 'reject') + } + + async onSubmit (e) { + e.preventDefault() + + // validate + this.errors = {} + if (!this.userUrl) this.errors.userUrl = 'Required' + if (!this.userId) this.errors.userId = 'Required' + if (Object.keys(this.errors).length > 0) { + return this.requestUpdate() + } + var userDriveValidation = await this.validateUserDrive(this.userUrl) + if (!userDriveValidation.success) { + this.errors.userDrive = userDriveValidation.message + return this.requestUpdate() + } + + try { + await uwg.users.add(this.userUrl, this.userId) + toast.create('User added', 'success') + setTimeout(() => {window.location = `/users/${this.userId}`}, 1e3) + emit(this, 'resolve') + } catch (e) { + console.log(e) + toast.create(e.toString(), 'error') + } + } +} + +customElements.define('beaker-add-user-popup', AddUserPopup) \ No newline at end of file diff --git a/app/assets/frontends/beaker-forum/js/com/popups/base.js b/app/assets/frontends/beaker-forum/js/com/popups/base.js new file mode 100644 index 0000000000..34611e2c7b --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/com/popups/base.js @@ -0,0 +1,111 @@ +import {LitElement, html} from '../../../vendor/lit-element/lit-element.js' +import popupsCSS from '../../../css/com/popups.css.js' + +// exported api +// = + +export class BasePopup extends LitElement { + constructor () { + super() + + const onGlobalKeyUp = e => { + // listen for the escape key + if (e.keyCode === 27) { + this.onReject() + } + } + document.addEventListener('keyup', onGlobalKeyUp) + + // cleanup function called on cancel + this.cleanup = () => { + document.removeEventListener('keyup', onGlobalKeyUp) + } + } + + get shouldCloseOnOuterClick () { + return true + } + + // management + // + + static async coreCreate (parentEl, Class, ...args) { + var popupEl = new Class(...args) + parentEl.appendChild(popupEl) + + const cleanup = () => { + popupEl.cleanup() + popupEl.remove() + } + + // return a promise that resolves with resolve/reject events + return new Promise((resolve, reject) => { + popupEl.addEventListener('resolve', e => { + resolve(e.detail) + cleanup() + }) + + popupEl.addEventListener('reject', e => { + reject() + cleanup() + }) + }) + } + + static async create (Class, ...args) { + return BasePopup.coreCreate(document.body, Class, ...args) + } + + static destroy (tagName) { + var popup = document.querySelector(tagName) + if (popup) popup.onReject() + } + + // rendering + // = + + render () { + let title = this.renderTitle() + return html` + + + ` + } + + renderTitle () { + // should be overridden by subclasses + return false + } + + renderBody () { + // should be overridden by subclasses + } + + // events + // = + + onClickWrapper (e) { + if (e.target.classList.contains('popup-wrapper') && this.shouldCloseOnOuterClick) { + this.onReject() + } + } + + onReject (e) { + if (e) e.preventDefault() + this.dispatchEvent(new CustomEvent('reject')) + } +} + +BasePopup.styles = [popupsCSS] \ No newline at end of file diff --git a/app/assets/frontends/beaker-forum/js/com/popups/edit-profile.js b/app/assets/frontends/beaker-forum/js/com/popups/edit-profile.js new file mode 100644 index 0000000000..c74889713c --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/com/popups/edit-profile.js @@ -0,0 +1,184 @@ +/* globals beaker */ +import { html, css } from '../../../vendor/lit-element/lit-element.js' +import { BasePopup } from './base.js' +import popupsCSS from '../../../css/com/popups.css.js' +import { emit } from '../../lib/dom.js' +import * as toast from '../toast.js' + +// exported api +// = + +export class EditProfilePopup extends BasePopup { + static get properties () { + return { + thumbDataURL: {type: String}, + thumbExt: {type: String}, + title: {type: String}, + description: {type: String}, + errors: {type: Object} + } + } + + static get styles () { + return [popupsCSS, css` + .img-ctrl { + display: flex; + flex-direction: column; + align-items: center; + } + + img { + border-radius: 50%; + object-fit: cover; + width: 130px; + height: 130px; + margin-bottom: 10px; + } + + hr { + border: 0; + border-top: 1px solid #ccc; + margin: 20px 0; + } + + input[type="file"] { + display: none; + } + + .toggle .text { + font-size: 13px; + margin-left: 8px; + } + + .form-actions { + display: flex; + justify-content: space-between; + align-items: center; + text-align: left; + } + `] + } + + constructor ({user}) { + super() + this.user = user + this.title = user.title || '' + this.description = user.description || '' + this.errors = {} + } + + // management + // + + static async create (parentEl, {user}) { + return BasePopup.coreCreate(parentEl, EditProfilePopup, {user}) + } + + static destroy () { + return BasePopup.destroy('beaker-edit-profile-popup') + } + + // rendering + // = + + renderTitle () { + return 'Edit your profile' + } + + renderBody () { + return html` +
+
+ ${this.thumbDataURL ? html` + + ` : html` + + + + + `} + + +
+ + + + ${this.errors.title ? html`
${this.errors.title}
` : ''} + + + + ${this.errors.description ? html`
${this.errors.description}
` : ''} + +
+ + +
+
+ ` + } + + // events + // = + + onClickChangeThumb (e) { + e.preventDefault() + this.shadowRoot.querySelector('input[type="file"]').click() + } + + onChooseThumbFile (e) { + var file = e.currentTarget.files[0] + if (!file) return + var fr = new FileReader() + fr.onload = () => { + this.thumbExt = file.name.split('.').pop() + this.thumbDataURL = /** @type string */(fr.result) + } + fr.readAsDataURL(file) + } + + onChangeTitle (e) { + this.title = e.target.value.trim() + } + + onChangeDescription (e) { + this.description = e.target.value.trim() + } + + onClickCancel (e) { + e.preventDefault() + emit(this, 'reject') + } + + async onSubmit (e) { + e.preventDefault() + + // validate + this.errors = {} + if (!this.title) this.errors.title = 'Required' + if (Object.keys(this.errors).length > 0) { + return this.requestUpdate() + } + + try { + let drive = hyperdrive.load(this.user.url) + await drive.configure({ + title: this.title, + description: this.description + }) + if (this.thumbDataURL) { + await Promise.all([ + drive.unlink('/thumb.jpg').catch(e => undefined), + drive.unlink('/thumb.jpeg').catch(e => undefined), + drive.unlink('/thumb.png').catch(e => undefined) + ]) + var thumbBase64 = this.thumbDataURL ? this.thumbDataURL.split(',').pop() : undefined + await drive.writeFile(`/thumb.${this.thumbExt}`, thumbBase64, 'base64') + } + emit(this, 'resolve') + } catch (e) { + toast.create(e.toString(), 'error') + } + } +} + +customElements.define('beaker-edit-profile-popup', EditProfilePopup) diff --git a/app/assets/frontends/beaker-forum/js/com/popups/group-settings.js b/app/assets/frontends/beaker-forum/js/com/popups/group-settings.js new file mode 100644 index 0000000000..1b7de7c65f --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/com/popups/group-settings.js @@ -0,0 +1,320 @@ +/* globals beaker */ +import { html, css } from '../../../vendor/lit-element/lit-element.js' +import { BasePopup } from './base.js' +import popupsCSS from '../../../css/com/popups.css.js' +import { emit } from '../../lib/dom.js' +import * as toast from '../toast.js' + +// exported api +// = + +export class GroupSettingsPopup extends BasePopup { + static get properties () { + return { + thumbDataURL: {type: String}, + thumbExt: {type: String}, + bannerDataURL: {type: String}, + bannerExt: {type: String}, + title: {type: String}, + description: {type: String}, + sidebarMd: {type: String}, + pinnedMessageMd: {type: String}, + errors: {type: Object} + } + } + + static get styles () { + return [popupsCSS, css` + hr { + border: 0; + border-top: 1px solid #ccd; + margin: 25px 0; + } + + .img-ctrls { + display: flex; + align-items: center; + margin-bottom: 10px; + } + + .img-ctrl { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + } + + .img-ctrl beaker-img-fallbacks { + height: 90px; + } + + .img-ctrl.thumb img { + border-radius: 50%; + object-fit: cover; + width: 80px; + height: 80px; + margin-bottom: 10px; + } + + .img-ctrl.banner img { + border-radius: 4px; + object-fit: cover; + width: 210px; + height: 80px; + margin-bottom: 10px; + } + + input[type="file"] { + display: none; + } + + .toggle .text { + font-size: 13px; + margin-left: 8px; + } + + .form-actions { + display: flex; + justify-content: space-between; + align-items: center; + text-align: left; + } + `] + } + + constructor () { + super() + this.load() + this.errors = {} + } + + async load () { + var self = hyperdrive.self + var info = await self.getInfo() + var sidebarMd = await self.readFile('/beaker-forum/sidebar.md').catch(e => '') + var pinnedMessageMd = await self.readFile('/beaker-forum/pinned-message.md').catch(e => '') + + this.thumbDataURL = undefined + this.bannerDataURL = undefined + this.title = info?.title + this.description = info?.description + this.sidebarMd = sidebarMd + this.pinnedMessageMd = pinnedMessageMd + } + + // management + // + + static async create (parentEl) { + return BasePopup.coreCreate(parentEl, GroupSettingsPopup) + } + + static destroy () { + return BasePopup.destroy('beaker-edit-profile-popup') + } + + // rendering + // = + + renderTitle () { + return 'Group Settings' + } + + renderBody () { + return html` + +
+
+
+ ${this.thumbDataURL === 'none' ? html` + + ` : this.thumbDataURL ? html` + + ` : html` + + + + + `} + +
+ + +
+
+ + +
+ +
+ + + + ${this.errors.title ? html`
${this.errors.title}
` : ''} + + + + ${this.errors.description ? html`
${this.errors.description}
` : ''} + +
+ + + + ${this.errors.sidebarMd ? html`
${this.errors.sidebarMd}
` : ''} + +
+ + + + ${this.errors.pinnedMessageMd ? html`
${this.errors.pinnedMessageMd}
` : ''} + +
+ +
+ + +
+
+ ` + } + + // events + // = + + onClickChangeThumb (e) { + e.preventDefault() + this.shadowRoot.querySelector('.thumb input[type="file"]').click() + } + + onChooseThumbFile (e) { + var file = e.currentTarget.files[0] + if (!file) return + var fr = new FileReader() + fr.onload = () => { + this.thumbExt = file.name.split('.').pop() + this.thumbDataURL = /** @type string */(fr.result) + } + fr.readAsDataURL(file) + } + + onClearThumb (e) { + e.preventDefault() + this.thumbDataURL = 'none' + } + + onClickChangeBanner (e) { + e.preventDefault() + this.shadowRoot.querySelector('.banner input[type="file"]').click() + } + + onChooseBannerFile (e) { + var file = e.currentTarget.files[0] + if (!file) return + var fr = new FileReader() + fr.onload = () => { + this.bannerExt = file.name.split('.').pop() + this.bannerDataURL = /** @type string */(fr.result) + } + fr.readAsDataURL(file) + } + + onClearBanner (e) { + e.preventDefault() + this.bannerDataURL = 'none' + } + + onChangeTitle (e) { + this.title = e.target.value.trim() + } + + onChangeDescription (e) { + this.description = e.target.value.trim() + } + + onChangeSidebarMd (e) { + this.sidebarMd = e.target.value + } + + onChangePinnedMessageMd (e) { + this.pinnedMessageMd = e.target.value + } + + onClickCancel (e) { + e.preventDefault() + emit(this, 'reject') + } + + async onSubmit (e) { + e.preventDefault() + + // validate + this.errors = {} + if (!this.title) this.errors.title = 'Required' + if (Object.keys(this.errors).length > 0) { + return this.requestUpdate() + } + + try { + let drive = hyperdrive.self + await drive.configure({ + title: this.title, + description: this.description + }) + if (this.sidebarMd) { + await drive.mkdir('/beaker-forum').catch(e => undefined) + await drive.writeFile('/beaker-forum/sidebar.md', this.sidebarMd) + } else { + await drive.unlink('/beaker-forum/sidebar.md').catch(e => undefined) + } + if (this.pinnedMessageMd) { + await drive.mkdir('/beaker-forum').catch(e => undefined) + await drive.writeFile('/beaker-forum/pinned-message.md', this.pinnedMessageMd) + } else { + await drive.unlink('/beaker-forum/pinned-message.md').catch(e => undefined) + } + if (this.bannerDataURL) { + await Promise.all([ + drive.unlink('/banner.jpg').catch(e => undefined), + drive.unlink('/banner.jpeg').catch(e => undefined), + drive.unlink('/banner.png').catch(e => undefined) + ]) + if (this.bannerDataURL !== 'none') { + var bannerBase64 = this.bannerDataURL ? this.bannerDataURL.split(',').pop() : undefined + await drive.writeFile(`/banner.${this.bannerExt}`, bannerBase64, 'base64') + } + } + if (this.thumbDataURL) { + await Promise.all([ + drive.unlink('/thumb.jpg').catch(e => undefined), + drive.unlink('/thumb.jpeg').catch(e => undefined), + drive.unlink('/thumb.png').catch(e => undefined) + ]) + if (this.thumbDataURL !== 'none') { + var thumbBase64 = this.thumbDataURL ? this.thumbDataURL.split(',').pop() : undefined + await drive.writeFile(`/thumb.${this.thumbExt}`, thumbBase64, 'base64') + } + } + emit(this, 'resolve') + } catch (e) { + toast.create(e.toString(), 'error') + } + } +} + +customElements.define('beaker-group-settings-popup', GroupSettingsPopup) diff --git a/app/assets/frontends/beaker-forum/js/com/popups/view-status.js b/app/assets/frontends/beaker-forum/js/com/popups/view-status.js new file mode 100644 index 0000000000..0809ae7dd2 --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/com/popups/view-status.js @@ -0,0 +1,84 @@ +/* globals beaker */ +import { html, css } from '../../../vendor/lit-element/lit-element.js' +import { BasePopup } from './base.js' +import popupsCSS from '../../../css/com/popups.css.js' +import '../status/status.js' +import '../comments/thread.js' + +// exported api +// = + +export class ViewStatusPopup extends BasePopup { + static get styles () { + return [popupsCSS, css` + .popup-inner { + width: 100%; + max-width: 640px; + overflow: visible; + } + + .popup-inner > .body { + padding: 4px 4px 8px; + } + + beaker-status { + margin: 0; + border: 0; + } + + beaker-comments-thread { + --border-color: #fff; + --body-font-size: 14px; + --composer-margin: 0 10px; + --composer-padding: 10px 16px; + --replies-left-margin: 4px; + margin-bottom: 10px; + } + `] + } + + constructor ({user, status}) { + super() + this.user = user + this.status = status + } + + // management + // + + static async create (parentEl, {user, status}) { + return BasePopup.coreCreate(parentEl, ViewStatusPopup, {user, status}) + } + + static destroy () { + return BasePopup.destroy('beaker-view-status-popup') + } + + // rendering + // = + + renderTitle () { + return undefined + } + + renderBody () { + return html` + + + ` + } + + // events + // = +} + +customElements.define('beaker-view-status-popup', ViewStatusPopup) diff --git a/app/assets/frontends/beaker-forum/js/com/post-buttons.js b/app/assets/frontends/beaker-forum/js/com/post-buttons.js new file mode 100644 index 0000000000..e787fd3532 --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/com/post-buttons.js @@ -0,0 +1,30 @@ +import { LitElement, html } from '../../vendor/lit-element/lit-element.js' +import css from '../../css/com/post-buttons.css.js' + +export class PostButtons extends LitElement { + static get styles () { + return css + } + + constructor () { + super() + } + + render () { + return html` + + + + + ` + } + + // events + // = + + onClickBtn (type) { + window.location = '/compose?type=' + type + } +} + +customElements.define('beaker-post-buttons', PostButtons) diff --git a/app/assets/frontends/beaker-forum/js/com/posts/composer.js b/app/assets/frontends/beaker-forum/js/com/posts/composer.js new file mode 100644 index 0000000000..b5eba66f47 --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/com/posts/composer.js @@ -0,0 +1,337 @@ +import { LitElement, html } from '../../../vendor/lit-element/lit-element.js' +import composerCSS from '../../../css/com/posts/composer.css.js' +import { ucfirst } from '../../lib/strings.js' +import * as uwg from '../../lib/uwg.js' +import * as toast from '../toast.js' + +export class PostComposer extends LitElement { + static get properties () { + return { + type: {type: String}, + validation: {type: Object}, + linkMetadata: {type: Object}, + file: {type: Object} + } + } + + constructor () { + super() + let qp = new URLSearchParams(location.search) + this.type = qp.get('type') || 'link' + this.validation = {} + this.linkMetadata = undefined + this.file = undefined + } + + async load () { + if (location.search && location.search.includes('from-cli')) { + let params = new URLSearchParams(location.search) + this.setType(params.get('type') || 'link') + await this.requestUpdate() + + this.shadowRoot.querySelector('input#title').value = params.get('title') + if (params.get('url')) { + this.shadowRoot.querySelector('input#url').value = params.get('url') + this.queueReadUrlMetadata() + } else if (params.get('file')) { + let url = params.get('file') + let urlp = new URL(url) + let drive = hyperdrive.load(urlp.hostname) + let base64buf = await drive.readFile(urlp.pathname, 'base64') + this.file = {source: 'hyperdrive', name: urlp.pathname.split('/').pop(), base64buf} + } + this.queueValidation() + } + } + + setType (type) { + if (type === this.type) return + this.type = type + this.linkMetadata = undefined + this.queueValidation() + + let url = new URL(window.location) + let qp = new URLSearchParams(location.search) + qp.set('type', type) + url.search = qp.toString() + history.replaceState({}, null, url.toString()) + } + + getInputClass (id) { + if (this.validation[id] && !this.validation[id].unset) { + return this.validation[id].success ? 'success' : 'error' + } + return '' + } + + queueValidation () { + clearTimeout(this.qvto) + this.qvto = setTimeout(this.runValidation.bind(this), 500) + } + + runValidation () { + var validation = {} + + // validate standard inputs + var inputEls = Array.from(this.shadowRoot.querySelectorAll('input, textarea')) + for (let el of inputEls) { + if (el.getAttribute('type') === 'file') continue + + let {id, value} = el + if (value) { + if (id === 'url') { + if (!isValidUrl(value)) { + validation[id] = {success: false, error: 'Please input a valid URL'} + continue + } + } + validation[id] = {success: true} + } else { + validation[id] = {unset: true} + } + } + + // validate file input + if (this.type === 'file') { + if (!this.file) { + validation.file = {unset: true} + } else { + validation.file = {success: true} + } + } + + this.validation = validation + } + + queueReadUrlMetadata () { + this.linkMetadata = undefined + clearTimeout(this.metato) + this.metato = setTimeout(this.readUrlMetadata.bind(this), 500) + } + + async readUrlMetadata () { + this.linkMetadata = {loading: true} + var url = this.shadowRoot.querySelector('input#url').value + var urlp = new URL(url) + if (urlp.protocol === 'hyper:') { + if (urlp.pathname === '/') { + try { + let info = await hyperdrive.load(urlp.hostname).getInfo({timeout: 10e3}) + this.linkMetadata = { + success: true, + driveType: info.type + } + return + } catch (e) { + this.linkMetadata = { + success: false, + message: 'Failed to read metadata from URL' + } + return + } + } + } + this.linkMetadata = {none: true} + } + + canSubmit () { + if (this.type === 'link' && (!this.linkMetadata || this.linkMetadata.loading)) { + return false + } + var inputs = Object.values(this.validation) + return inputs.length > 0 && inputs.reduce((acc, input) => acc && input.success && !input.unset, true) + } + + // rendering + // = + + render () { + const typeSelector = (id, label) => html` + this.setType(id)}>${label} + ` + const input = (id, placeholder) => html` + + ${this.renderValidationError(id)} + ` + const textarea = (id, placeholder) => html` + + ${this.renderValidationError(id)} + ` + return html` + +
+ ${typeSelector('link', html` Link`)} + ${typeSelector('text', html` Text Post`)} + ${typeSelector('file', html` File`)} +
+
+
${input('title', 'Title')}
+ ${this.type === 'link' ? html`
${input('url', 'URL')}
` : ''} + ${this.type === 'text' ? html`
${textarea('content', 'Post body (markdown is supported)')}
` : ''} + ${this.type === 'file' ? this.renderFileInput() : ''} + ${typeof this.linkMetadata !== 'undefined' ? this.renderLinkMetadata() : ''} +
+ +
+
+ ` + } + + renderFileInput () { + var selection = undefined + if (this.file) { + selection = html`
${this.file.name}
` + } + var success = this.validation && this.validation.file && this.validation.file.success + return html` +
+ +
+ ${selection} +
+ Select a ${this.file ? 'different' : ''} file from + your hyperdrive + or + your OS filesystem +
+
+
+ ` + } + + renderLinkMetadata () { + if (this.linkMetadata.loading) { + return html` + + ` + } + if (this.linkMetadata.none) { + return html` + + ` + } + if (!this.linkMetadata.success) { + return html` + + ` + } + return html` + + ` + } + + renderValidationError (id) { + if (this.validation[id] && this.validation[id].error) { + return html`
${this.validation[id].error}
` + } + } + + // events + // = + + onKeyup (e) { + this.queueValidation() + if (e.target.id === 'url') { + this.queueReadUrlMetadata() + } + } + + async onClickSelectHyperdriveFile (e) { + e.preventDefault() + e.stopPropagation() + + var sels = await navigator.selectFileDialog({ + select: ['file'], + allowMultiple: false, + disallowCreate: true + }) + var base64buf = await hyperdrive.load(sels[0].origin).readFile(sels[0].path, 'base64') + this.file = {source: 'hyperdrive', name: sels[0].path.split('/').pop(), base64buf} + this.runValidation() + } + + onClickSelectOSFile (e) { + e.preventDefault() + e.stopPropagation() + this.shadowRoot.querySelector('#native-file-input').click() + } + + onChooseFileNative (e) { + var file = e.currentTarget.files[0] + if (!file) return + var fr = new FileReader() + fr.onload = () => { + var base64buf = fr.result.split(',').pop() + this.file = {source: 'os', name: file.name, base64buf} + this.runValidation() + } + fr.readAsDataURL(file) + } + + async onSubmitPost (e) { + e.preventDefault() + e.stopPropagation() + + this.runValidation() + if (!this.canSubmit()) return + + const getValue = id => this.shadowRoot.querySelector(`#${id}`).value + + var path + try { + if (this.type === 'link') { + path = await uwg.posts.addLink({ + title: getValue('title'), + href: getValue('url'), + driveType: this.linkMetadata.driveType + }) + } else if (this.type === 'text') { + path = await uwg.posts.addTextPost({ + title: getValue('title'), + content: getValue('content') + }) + } else if (this.type === 'file') { + let ext = this.file.name.split('.').pop().toLowerCase() + if (this.file.name.indexOf('.') === -1) ext = 'txt' + path = await uwg.posts.addFile({ + title: getValue('title'), + ext, + base64buf: this.file.base64buf + }) + } + } catch (e) { + toast.create(e.toString(), 'error') + console.error(e) + return + } + + toast.create(`${ucfirst(this.type)} posted`) + var user = uwg.profiles.getUser() + var filename = path.split('/').pop() + window.location = `/users/${user.id}/posts/${filename}` + } +} +PostComposer.styles = composerCSS + +customElements.define('beaker-post-composer', PostComposer) + +function isValidUrl (v) { + try { + let url = new URL(v) + return true + } catch (e) { + return false + } +} \ No newline at end of file diff --git a/app/assets/frontends/beaker-forum/js/com/posts/feed.js b/app/assets/frontends/beaker-forum/js/com/posts/feed.js new file mode 100644 index 0000000000..04839231cc --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/com/posts/feed.js @@ -0,0 +1,131 @@ +import { LitElement, html } from '../../../vendor/lit-element/lit-element.js' +import { repeat } from '../../../vendor/lit-element/lit-html/directives/repeat.js' +import * as uwg from '../../lib/uwg.js' +import * as isreadDb from '../../lib/isread-db.js' +import feedCSS from '../../../css/com/posts/feed.css.js' +import './post.js' +import '../paginator.js' + +const PAGE_SIZE = 25 + +export class PostsFeed extends LitElement { + static get properties () { + return { + user: {type: Object}, + author: {type: String}, + posts: {type: Array}, + error: {type: String} + } + } + + static get styles () { + return feedCSS + } + + constructor () { + super() + this.user = undefined + this.author = undefined + this.posts = undefined + this.error = false + this.page = 0 + } + + async load () { + try { + var authorProfile = this.author ? await uwg.users.getByUserID(this.author) : undefined + var posts = await uwg.posts.list({ + author: this.author ? authorProfile.url : undefined, + offset: this.page * PAGE_SIZE, + limit: PAGE_SIZE, + sort: 'name', + reverse: true + }, {includeProfiles: true}) + for (let post of posts) { + post.isRead = await isreadDb.get(`${post.drive.id}:${post.path.split('/').pop()}`) + } + /* dont await */ this.loadFeedAnnotations(posts) + this.posts = posts + } catch (e) { + this.error = e.toString() + } + console.log(this.posts) + } + + requestFeedPostsUpdate () { + Array.from(this.shadowRoot.querySelectorAll('beaker-post'), el => el.requestUpdate()) + } + + async refreshFeed () { + this.loadFeedAnnotations(this.posts) + } + + async loadFeedAnnotations (posts) { + for (let post of posts) { + ;[post.numComments] = await Promise.all([ + uwg.comments.count({href: post.url}) + ]) + this.requestFeedPostsUpdate() + } + } + + render () { + return html` + +
+ ${this.error ? html` +
+ ${this.error} +
+ ` : typeof this.posts === 'undefined' ? html` +
+ +
+ ` : html` + ${repeat(this.posts, post => html` + + `)} + ${this.posts.length === 0 + ? html` +
+
+
+ ${this.author + ? 'This user has not posted anything.' + : 'This group has not posted anything.'} +
+
+ ` : ''} + ${this.page > 0 || this.posts.length === PAGE_SIZE ? html` + + ` : ''} + `} +
+ ` + } + + // events + // = + + onChangePage (e) { + this.page = e.detail.page + this.posts = undefined + this.load() + } + + async onPostDeleted (e) { + let post = e.detail.post + this.posts = this.posts.filter(p => p.url !== post.url) + } +} + +customElements.define('beaker-posts-feed', PostsFeed) diff --git a/app/assets/frontends/beaker-forum/js/com/posts/post.js b/app/assets/frontends/beaker-forum/js/com/posts/post.js new file mode 100644 index 0000000000..ddc2e920db --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/com/posts/post.js @@ -0,0 +1,220 @@ +import { LitElement, html } from '../../../vendor/lit-element/lit-element.js' +import { unsafeHTML } from '../../../vendor/lit-element/lit-html/directives/unsafe-html.js' +import postCSS from '../../../css/com/posts/post.css.js' +import { timeDifference } from '../../lib/time.js' +import { emit } from '../../lib/dom.js' +import { writeToClipboard } from '../../lib/clipboard.js' +import { toNiceDomain, toNiceDriveType, pluralize } from '../../lib/strings.js' +import MarkdownIt from '../../../vendor/markdown-it.js' +import * as uwg from '../../lib/uwg.js' +import * as isreadDb from '../../lib/isread-db.js' +import * as contextMenu from '../context-menu.js' +import * as toast from '../toast.js' + +const md = MarkdownIt({ + html: false, // Enable HTML tags in source + xhtmlOut: false, // Use '/' to close single tags (
) + breaks: true, // Convert '\n' in paragraphs into
+ langPrefix: 'language-', // CSS language prefix for fenced blocks + linkify: false, // Autoconvert URL-like text to links + + // Enable some language-neutral replacement + quotes beautification + typographer: true, + + // Double + single quotes replacement pairs, when typographer enabled, + // and smartquotes on. Set doubles to '«»' for Russian, '„“' for German. + quotes: '“”‘’', + + // Highlighter function. Should return escaped HTML, + // or '' if the source string is not changed + highlight: undefined +}) + +export class Post extends LitElement { + static get properties () { + return { + post: {type: Object}, + userUrl: {type: String, attribute: 'user-url'} + } + } + + static get styles () { + return postCSS + } + + constructor () { + super() + this.post = null + this.userUrl = '' + } + + getDriveTypeIcon (dt) { + switch (dt) { + case 'user': return 'fas fa-user' + case 'module': return 'fas fa-cube' + case 'frontend': return 'fas fa-drafting-compass' + case 'webterm.sh/cmd-pkg': return 'fas fa-terminal' + default: return 'far fa-hdd' + } + } + + render () { + if (!this.post) return + + var isLink = this.post.path.endsWith('.goto') + var isTextPost = /\.(md|txt)$/.test(this.post.path) + var isMarkdown = this.post.path.endsWith('.md') + var isFile = !isLink && !isTextPost + + var postMeta = this.post.stat.metadata + var viewProfileUrl = '/users/' + this.post.drive.id + var viewPostUrl = viewProfileUrl + '/posts/' + this.post.url.split('/').pop() + var href = isLink ? postMeta.href : viewPostUrl + var author = this.post.drive + var ctime = this.post.stat.ctime // TODO replace with rtime + var isFullpage = this.hasAttribute('fullpage') + var icon = isLink ? 'fas fa-link' : isTextPost ? 'far fa-comment-alt' : 'far fa-file' + if (isFile) { + if (/\.(png|jpe?g|gif)$/i.test(this.post.path)) { + icon = 'far fa-image' + } else if (/\.(mp4|webm|mov)$/i.test(this.post.path)) { + icon = 'fas fa-film' + } else if (/\.(mp3|ogg)$/i.test(this.post.path)) { + icon = 'far fa-play-circle' + } + } + + return html` + +
+
+
+ ${postMeta.title} + ${isFullpage ? '' : html` + + `} + ${postMeta['drive-type'] ? html` + + + ${toNiceDriveType(postMeta['drive-type'])} + + ` : ''} + + ${isLink ? toNiceDomain(postMeta.href) : ''} + + +
+ + ${isFullpage && isTextPost ? html` +
+ ${isMarkdown ? unsafeHTML(md.render(this.post.content)) : html`
${this.post.content}
`} +
+ ` : ''} + ${isFullpage && isFile ? html` +
+ ${this.renderFile()} +
+ ` : undefined} +
+ ` + } + + renderFile () { + if (/\.(png|jpe?g|gif)$/i.test(this.post.path)) { + return html`` + } + if (/\.(mp4|webm|mov)$/i.test(this.post.path)) { + return html`` + } + if (/\.(mp3|ogg)$/i.test(this.post.path)) { + return html`` + } + return html`

.${this.post.url.split('.').pop()} Link

` + } + + // events + // = + + onClickMenu (e) { + e.preventDefault() + e.stopPropagation() + + var items = [ + {icon: 'far fa-fw fa-file-alt', label: 'View post file', click: () => window.open(this.post.url) }, + { + icon: 'fas fa-fw fa-link', + label: 'Copy post file URL', + click: () => { + writeToClipboard(this.post.url) + toast.create('Copied to your clipboard') + } + } + ] + + if (this.userUrl === this.post.drive.url) { + items.push('-') + items.push({icon: 'fas fa-fw fa-paragraph', label: 'Change post title', click: () => this.onClickChangeTitle() }) + items.push({icon: 'fas fa-fw fa-trash', label: 'Delete post', click: () => this.onClickDelete() }) + } + + var rect = e.currentTarget.getClientRects()[0] + contextMenu.create({ + x: rect.left + 8, + y: rect.bottom + 4, + center: true, + withTriangle: true, + roomy: true, + noBorders: true, + style: `padding: 4px 0`, + items + }) + } + + async onToggleIsread () { + this.post.isRead = !this.post.isRead + if (this.post.isRead) { + await isreadDb.put(`${this.post.drive.id}:${this.post.path.split('/').pop()}`) + this.classList.add('read') + this.classList.remove('unread') + } else { + await isreadDb.remove(`${this.post.drive.id}:${this.post.path.split('/').pop()}`) + this.classList.remove('read') + this.classList.add('unread') + } + this.requestUpdate() + } + + async onClickChangeTitle () { + var newTitle = prompt('New post title', this.post.stat.metadata.title) + if (!newTitle) return + newTitle = newTitle.trim() + if (!newTitle) return + await uwg.posts.changeTitle(this.post, newTitle) + this.post.stat.metadata.title = newTitle + this.requestUpdate() + } + + async onClickDelete () { + if (!confirm('Are you sure?')) return + try { + await uwg.posts.remove(this.post) + } catch (e) { + console.error(e) + toast.create(e.toString(), 'error') + return + } + toast.create('Post deleted') + emit(this, 'deleted', {bubbles: true, composed: true, detail: {post: this.post}}) + } + + onClickComments (e) { + e.preventDefault() + this.parentNode.querySelector('beaker-comments-thread').scrollIntoView() + } +} + +customElements.define('beaker-post', Post) \ No newline at end of file diff --git a/app/assets/frontends/beaker-forum/js/com/profiles/aside.js b/app/assets/frontends/beaker-forum/js/com/profiles/aside.js new file mode 100644 index 0000000000..92d881ad82 --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/com/profiles/aside.js @@ -0,0 +1,10 @@ +import { ProfileHeader } from './header.js' +import asideCSS from '../../../css/com/profiles/aside.css.js' + +export class ProfileAside extends ProfileHeader { + static get styles () { + return asideCSS + } +} + +customElements.define('beaker-profile-aside', ProfileAside) diff --git a/app/assets/frontends/beaker-forum/js/com/profiles/header.js b/app/assets/frontends/beaker-forum/js/com/profiles/header.js new file mode 100644 index 0000000000..29af9ba9a4 --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/com/profiles/header.js @@ -0,0 +1,151 @@ +import { LitElement, html } from '../../../vendor/lit-element/lit-element.js' +import * as uwg from '../../lib/uwg.js' +import { writeToClipboard } from '../../lib/clipboard.js' +import * as contextMenu from '../context-menu.js' +import { EditProfilePopup } from '../popups/edit-profile.js' +import * as toast from '../toast.js' +import headerCSS from '../../../css/com/profiles/header.css.js' +import '../img-fallbacks.js' + +export class ProfileHeader extends LitElement { + static get properties () { + return { + showAdminCtrls: {type: Boolean, attribute: 'admin-ctrls'}, + id: {type: String}, + profile: {type: Object}, + error: {type: String} + } + } + + static get styles () { + return headerCSS + } + + constructor () { + super() + this.showAdminCtrls = false + this.id = undefined + this.profile = undefined + } + + async load () { + try { + this.profile = await uwg.users.getByUserID(this.id) + } catch (e) { + this.error = e.toString() + } + await this.requestUpdate() + } + + render () { + if (this.error) { + return html`
${this.error}
` + } + if (!this.profile) return html`` + return html` + + + + + + + + +
+

${this.profile.title}

+

+ ${this.profile.id} +

+

+ ${this.profile.description} +

+

+ ${this.profile.isUser ? html` + + ` : ''} + ${this.showAdminCtrls ? html` +

Admin Tools

+ + + ` : ''} +

+
+ ` + } + + // events + // = + + onClickMenu (e) { + e.preventDefault() + e.stopPropagation() + + var rect = e.currentTarget.getClientRects()[0] + contextMenu.create({ + x: rect.right, + y: rect.bottom, + right: true, + roomy: true, + noBorders: true, + style: `padding: 4px 0`, + items: [ + { + icon: 'fas fa-fw fa-link', + label: 'Copy profile URL', + click: () => { + writeToClipboard(this.profile.url) + toast.create('Copied to your clipboard') + } + } + ] + }) + } + + async onEditProfile (e) { + try { + await EditProfilePopup.create(document.body, {user: this.profile}) + location.reload() + } catch (e) { + // ignore + } + } + + async onChangeUserId () { + var newId = prompt('Change this user\'s id to:', this.id) + if (!newId) return + try { + await uwg.users.rename(this.id, newId) + toast.create('User renamed', 'success') + setTimeout(() => {window.location = `/users/${newId}`}, 1e3) + } catch (e) { + console.log(e) + toast.create(e.toString(), 'error') + } + } + + async onRemoveUser () { + if (!confirm('Are you sure you want to remove this user?')) { + return + } + try { + await uwg.users.removeByUserID(this.id) + toast.create('User removed', 'success') + setTimeout(() => {window.location = '/'}, 1e3) + } catch (e) { + console.log(e) + toast.create(e.toString(), 'error') + } + } + +} + +customElements.define('beaker-profile-header', ProfileHeader) diff --git a/app/assets/frontends/beaker-forum/js/com/profiles/list.js b/app/assets/frontends/beaker-forum/js/com/profiles/list.js new file mode 100644 index 0000000000..1bb5a46ca3 --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/com/profiles/list.js @@ -0,0 +1,72 @@ +import { LitElement, html } from '../../../vendor/lit-element/lit-element.js' +import { repeat } from '../../../vendor/lit-element/lit-html/directives/repeat.js' +import * as uwg from '../../lib/uwg.js' +import { shorten } from '../../lib/strings.js' +import listCSS from '../../../css/com/profiles/list.css.js' +import '../img-fallbacks.js' + +export class ProfileList extends LitElement { + static get properties () { + return { + user: {type: Object}, + query: {type: String}, + source: {type: String}, + profiles: {type: Array} + } + } + + static get styles () { + return listCSS + } + + constructor () { + super() + this.user = undefined + this.profiles = undefined + this.query = undefined + this.source = undefined + } + + async load () { + this.profiles = await uwg.users.list({}, {includeProfiles: true}) + console.log(this.profiles) + await this.requestUpdate() + } + + render () { + if (!this.profiles) return html`` + return html` + +
+ ${repeat(this.profiles, profile => this.renderProfile(profile))} +
+ ` + } + + renderProfile (profile) { + return html` +
+ + + + + + +
+

+ ${profile.title} +

+

+ ${shorten(profile.description, 100)} +

+
+
+ ` + } + + // events + // = + +} + +customElements.define('beaker-profile-list', ProfileList) diff --git a/app/assets/frontends/beaker-forum/js/com/search-input.js b/app/assets/frontends/beaker-forum/js/com/search-input.js new file mode 100644 index 0000000000..e570eca878 --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/com/search-input.js @@ -0,0 +1,63 @@ +import {LitElement, html} from '../../vendor/lit-element/lit-element.js' +import {repeat} from '../../vendor/lit-element/lit-html/directives/repeat.js' +import {classMap} from '../../vendor/lit-element/lit-html/directives/class-map.js' +import * as QP from '../lib/query-params.js' +import searchInputCSS from '../../css/com/search-input.css.js' + +export class SearchInput extends LitElement { + static get properties () { + return { + placeholder: {type: String}, + query: {type: String}, + } + } + + static get styles () { + return searchInputCSS + } + + constructor () { + super() + this.placeholder = '' + this.query = QP.getParam('q', undefined) + } + + get value () { + return this.query + } + + // rendering + // = + + render () { + return html` + +
+ + +
+ ` + } + + // events + // = + + onKeyupInput (e) { + if (e.key === 'Enter') { + e.preventDefault() + e.stopPropagation() + + window.location = `/search?q=${encodeURIComponent(this.query)}` + return + } + this.query = e.target.value + } +} + +customElements.define('beaker-search-input', SearchInput) \ No newline at end of file diff --git a/app/assets/frontends/beaker-forum/js/com/search/results.js b/app/assets/frontends/beaker-forum/js/com/search/results.js new file mode 100644 index 0000000000..15c9f07d5a --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/com/search/results.js @@ -0,0 +1,236 @@ +import { LitElement, html } from '../../../vendor/lit-element/lit-element.js' +import { repeat } from '../../../vendor/lit-element/lit-html/directives/repeat.js' +import * as uwg from '../../lib/uwg.js' +import { timeDifference } from '../../lib/time.js' +import { toNiceUrl, pluralize } from '../../lib/strings.js' +import resultsCSS from '../../../css/com/search/results.css.js' +import '../posts/post.js' +import '../profiles/list.js' +import '../paginator.js' + +const QUERY_PAGE_SIZE = 100 +const PAGE_SIZE = 25 + +export class SearchResults extends LitElement { + static get properties () { + return { + user: {type: Object}, + driveType: {type: String, attribute: 'drive-type'}, + query: {type: String}, + results: {type: Array} + } + } + + static get styles () { + return resultsCSS + } + + constructor () { + super() + this.user = undefined + this.driveType = undefined + this.query = undefined + this.results = undefined + this.page = 0 + } + + async load () { + var results = [] + if (!this.driveType || this.driveType === 'user') { + results = results.concat(await this.runUsersQuery()) + } + if (results.length < PAGE_SIZE) { + results = results.concat(await this.runPostsQuery(results.length)) + } + /* dont await */ this.loadResultAnnotations(results) + this.results = results + console.log(this.query, this.driveType, this.results) + + await this.requestUpdate() + Array.from(this.shadowRoot.querySelectorAll('[loadable]'), el => el.load()) + } + + async runPostsQuery (numExistingResults = 0) { + var sliceStart = this.page * PAGE_SIZE + var sliceEnd = sliceStart + PAGE_SIZE - numExistingResults + var results = [] + var offset = 0 + var query = this.query ? this.query.toLowerCase() : undefined + while (1) { + let candidates = await uwg.posts.list({ + driveType: this.driveType || undefined, + offset, + limit: QUERY_PAGE_SIZE, + sort: 'name', + reverse: true + }, { + includeContent: false, + includeProfiles: false + }) + if (candidates.length === 0) { + break + } + if (query) { + candidates = candidates.filter(candidate => ( + candidate.stat.metadata.title.toLowerCase().includes(query) + )) + } + results = results.concat(candidates) + if (results.length >= sliceEnd) break + offset += QUERY_PAGE_SIZE + } + results = results.slice(sliceStart, sliceEnd) + await uwg.profiles.readAllProfiles(results) + return results.map(fromPostToResult) + } + + async runUsersQuery (numExistingResults = 0) { + var sliceStart = this.page * PAGE_SIZE + var sliceEnd = sliceStart + PAGE_SIZE - numExistingResults + var query = this.query ? this.query.toLowerCase() : undefined + let results = await uwg.users.list(undefined, {includeProfiles: true}) + if (query) { + results = results.filter(candidate => ( + candidate.id.toLowerCase().includes(query) + || candidate.title.toLowerCase().includes(query) + || candidate.description.toLowerCase().includes(query) + )) + } + results = results.slice(sliceStart, sliceEnd) + return results.map(fromProfileToResult) + } + + async loadResultAnnotations (results) { + for (let result of results) { + if (result.type === 'post') { + ;[result.postMeta.numComments] = await Promise.all([ + uwg.comments.count({href: result.url}) + ]) + } else if (result.type === 'user') { + } + this.requestUpdate() + } + } + + render () { + var hasMore = this.results && this.results.length >= PAGE_SIZE + var queryRe = new RegExp(`(${this.query})`, 'gi') + return html` + +
+ ${typeof this.results === 'undefined' ? html` +
+ +
+ ` : html` + ${repeat(this.results, result => { + if (result.type === 'post') { + let viewProfileUrl = `/users/${result.postMeta.author.id}` + let numComments = result.postMeta.numComments || 0 + return html` + + ` + } + if (result.type === 'user') { + return html` +
+

+ ${facetize(result.title, queryRe, this.query)} + ${result.userMeta.id} +

+
${facetize(result.userMeta.description, queryRe, this.query)}
+
+ ` + } + })} + ${this.results.length === 0 + ? html` +
+
+
+ No results found. +
+
+ ` : ''} + + `} +
+ ` + } + + renderUsersList (users) { + var els = [] + for (let i = 0; i < users.length; i++) { + let profile = users[i] + let comma = (i !== users.length - 1) ? ', ' : '' + els.push(html` + ${profile.title}${comma} + `) + } + return els + } + + // events + // = + + onChangePage (e) { + this.page = e.detail.page + this.results = undefined + this.load() + } +} + +customElements.define('beaker-search-results', SearchResults) + +function fromPostToResult (post) { + var metadata = post.stat.metadata + var viewUrl = `/users/${post.drive.id}/posts/${post.url.split('/').pop()}` + return { + type: 'post', + viewUrl, + url: metadata.href || viewUrl, + title: metadata.title, + postMeta: { + href: metadata.href, + ctime: post.stat.ctime, // TODO replace with rtime + 'drive-type': metadata['drive-type'], + author: post.drive, + numComments: undefined + } + } +} + +function fromProfileToResult (profile) { + return { + type: 'user', + viewUrl: `/users/${profile.id}`, + url: profile.url, + title: profile.title, + userMeta: { + id: profile.id, + description: profile.description + } + } +} + +function facetize (str, queryRe, query) { + let parts = str.split(queryRe) + return parts.map(part => part.toLowerCase() === query ? html`${part}` : part) +} \ No newline at end of file diff --git a/app/assets/frontends/beaker-forum/js/com/toast.js b/app/assets/frontends/beaker-forum/js/com/toast.js new file mode 100644 index 0000000000..3783840a55 --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/com/toast.js @@ -0,0 +1,47 @@ +import {LitElement, html} from '../../vendor/lit-element/lit-element.js' +import toastCSS from '../../css/com/toast.css.js' + +// exported api +// = + +export function create (message, type = '', time = 5000, button = null) { + // destroy existing + destroy() + + // render toast + document.body.appendChild(new BeakerToast({message, type, button})) + setTimeout(destroy, time) +} + +// internal +// = + +function destroy () { + var toast = document.querySelector('beaker-toast') + + if (toast) { + toast.remove() + } +} + +class BeakerToast extends LitElement { + constructor ({message, type, button}) { + super() + this.message = message + this.type = type + this.button = button + } + + render () { + const onClick = e => destroy() + const onButtonClick = this.button ? (e) => { destroy(); this.button.click(e) } : undefined + return html` +
+

${this.message} ${this.button ? html`${this.button.label}` : ''}

+
+ ` + } +} +BeakerToast.styles = toastCSS + +customElements.define('beaker-toast', BeakerToast) diff --git a/app/assets/frontends/beaker-forum/js/lib/clipboard.js b/app/assets/frontends/beaker-forum/js/lib/clipboard.js new file mode 100644 index 0000000000..a1a140d666 --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/lib/clipboard.js @@ -0,0 +1,8 @@ +export function writeToClipboard (str) { + var textarea = document.createElement('textarea') + textarea.textContent = str + document.body.appendChild(textarea) + textarea.select() + document.execCommand('copy') + document.body.removeChild(textarea) +} \ No newline at end of file diff --git a/app/assets/frontends/beaker-forum/js/lib/dom.js b/app/assets/frontends/beaker-forum/js/lib/dom.js new file mode 100644 index 0000000000..bf1b37b4ae --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/lib/dom.js @@ -0,0 +1,49 @@ +export function findParent (node, test) { + if (typeof test === 'string') { + // classname default + var cls = test + test = el => el.classList && el.classList.contains(cls) + } + + while (node) { + if (test(node)) { + return node + } + node = node.parentNode + } +} + +export function on (el, event, fn, opts) { + el.addEventListener(event, fn, opts) +} + +export function once (el, event, fn, opts) { + opts = opts || {} + opts.once = true + el.addEventListener(event, fn, opts) +} + +export function emit (el, evt, opts = {}) { + opts.bubbles = ('bubbles' in opts) ? opts.bubbles : true + opts.composed = ('composed' in opts) ? opts.composed : true + el.dispatchEvent(new CustomEvent(evt, opts)) +} + +/*! + * Dynamically changing favicons with JavaScript + * Works in all A-grade browsers except Safari and Internet Explorer + * Demo: http://mathiasbynens.be/demo/dynamic-favicons + */ + +var _head = document.head || document.getElementsByTagName('head')[0] // https://stackoverflow.com/a/2995536 +export function changeFavicon (src) { + var link = document.createElement('link') + var oldLink = document.getElementById('dynamic-favicon') + link.id = 'dynamic-favicon' + link.rel = 'shortcut icon' + link.href = src + if (oldLink) { + _head.removeChild(oldLink) + } + _head.appendChild(link) +} \ No newline at end of file diff --git a/app/assets/frontends/beaker-forum/js/lib/fs.js b/app/assets/frontends/beaker-forum/js/lib/fs.js new file mode 100644 index 0000000000..687f76f2c8 --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/lib/fs.js @@ -0,0 +1,188 @@ +import { isFilenameBinary } from './is-ext-binary.js' +import { urlToKey, joinPath, slugify } from './strings.js' +import { chunkMapAsync } from './functions.js' + +// typedefs +// = + +/** + * @typedef {Object} FSQueryOpts + * @prop {string|string[]} path + * @prop {string} [type] + * @prop {string} [mount] + * @prop {Object} [metadata] + * @prop {string} [sort] - 'name', 'ctime', 'mtime' + * @prop {boolean} [reverse] + * @prop {number} [limit] + * @prop {number} [offset] + * + * @typedef {Object} Stat + * @prop {number} mode + * @prop {number} size + * @prop {number} offset + * @prop {number} blocks + * @prop {Date} atime + * @prop {Date} mtime + * @prop {Date} ctime + * @prop {Object} metadata + * @prop {Object} [mount] + * @prop {string} [mount.key] + * @prop {string} linkname + * + * @typedef {Object} FSQueryResult + * @prop {string} type + * @prop {string} path + * @prop {string} url + * @prop {Stat} stat + * @prop {string} drive + * @prop {string} [mount] + * @prop {any} [content] + */ + +// exported +// = + +/** + * @param {FSQueryOpts} query + * @param {Hyperdrive} [drive] + * @returns {Promise} + */ +export async function queryRead (query, drive = navigator.filesystem) { + var files = await drive.query(query) + await chunkMapAsync(files, 10, async (file) => { + if (isFilenameBinary(file.path)) return + file.content = await drive.readFile(file.path, 'utf8').catch(err => undefined) + if (file.path.endsWith('.json')) { + try { + file.content = JSON.parse(file.content) + } catch (e) { + // ignore + } + } + }) + return files +} + +/** + * @param {FSQueryOpts} query + * @param {Hyperdrive} [drive] + * @returns {Promise} + */ +export async function queryHas (query, drive = navigator.filesystem) { + var files = await drive.query(query) + return files.length > 0 +} + +/** + * @param {string} path + * @param {Object} [drive] + */ +export async function ensureDir (path, drive = navigator.filesystem) { + try { + let st = await drive.stat(path).catch(e => null) + if (!st) { + await drive.mkdir(path) + } else if (!st.isDirectory()) { + console.error('Warning! Filesystem expects a folder but an unexpected file exists at this location.', {path}) + } + } catch (e) { + console.error('Filesystem failed to make directory', {path, error: e}) + } +} + +/** + * @param {string} path + * @param {Object} [drive] + */ +export async function ensureParentDir (path, drive = navigator.filesystem) { + var acc = [] + for (let part of path.split('/').slice(0, -1)) { + acc.push(part) + await ensureDir(acc.join('/'), drive) + } +} + +/** + * @param {string} path + * @param {string} url + * @param {Object} [drive] + * @return {Promise} + */ +export async function ensureMount (path, url, drive = navigator.filesystem) { + try { + let st = await drive.stat(path).catch(e => null) + let key = urlToKey(url) + if (!st) { + // add mount + await drive.mount(path, key) + } else if (st.mount) { + if (st.mount.key !== key) { + // change mount + await drive.unmount(path) + await drive.mount(path, key) + } + } else { + console.error('Warning! Filesystem expects a mount but an unexpected file exists at this location.', {path}) + } + } catch (e) { + console.error('Filesystem failed to mount drive', {path, url, error: e}) + } +} + +/** + * @param {string} path + * @param {Object} [drive] + * @return {Promise} + */ +export async function ensureUnmount (path, drive = navigator.filesystem) { + try { + let st = await drive.stat(path).catch(e => null) + if (st && st.mount) { + // remove mount + await drive.unmount(path) + } + } catch (e) { + console.error('Filesystem failed to unmount drive', {path, error: e}) + } +} + +/** + * @param {string} pathSelector + * @param {string} url + * @param {Object} [drive] + * @return {Promise} + */ +export async function ensureUnmountByUrl (pathSelector, url, drive = navigator.filesystem) { + try { + let mounts = await drive.query({ + path: pathSelector, + type: 'mount' + }) + let mount = mounts.find(item => item.mount === url) + if (mount) { + // remove mount + await drive.unmount(mount.path) + } else { + throw "Mount not found" + } + } catch (e) { + console.error('Filesystem failed to unmount drive', {pathSelector, url, error: e}) + } +} + +/** + * @param {string} containingPath + * @param {string} title + * @param {Object} [drive] + * @returns {Promise} + */ +export async function getAvailableName (containingPath, title, drive = navigator.filesystem) { + var basename = slugify((title || '').trim() || 'untitled').toLowerCase() + for (let i = 1; i < 1e9; i++) { + let name = (i === 1) ? basename : `${basename}-${i}` + let st = await drive.stat(joinPath(containingPath, name)).catch(e => null) + if (!st) return name + } + // yikes if this happens + throw new Error('Unable to find an available name for ' + title) +} diff --git a/app/assets/frontends/beaker-forum/js/lib/functions.js b/app/assets/frontends/beaker-forum/js/lib/functions.js new file mode 100644 index 0000000000..4e7878cfb7 --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/lib/functions.js @@ -0,0 +1,55 @@ +/** + * Helper to make node-style CBs into promises + * @example + * cbPromise(cb => myNodeStyleMethod(cb)).then(...) + * @param {function(Function): any} method + * @returns {Promise} + */ +export function cbPromise (method) { + return new Promise((resolve, reject) => { + method((err, value) => { + if (err) reject(err) + else resolve(value) + }) + }) +} + +/** + * Helper to run an async operation against an array in chunks + * @example + * var res = await chunkAsync(values, 3, v => fetchAsync(v)) // chunks of 3s + * @param {any[]} arr + * @param {Number} chunkSize + * @param {(value: any, index: number, array: any[]) => Promise} cb + * @returns {Promise} + */ +export async function chunkMapAsync (arr, chunkSize, cb) { + const resultChunks = [] + for (let chunk of chunkArray(arr, chunkSize)) { + resultChunks.push(await Promise.all(chunk.map(cb))) + } + return resultChunks.flat() + +} + +/** + * Helper to split an array into chunks + * @param {any[]} arr + * @param {Number} chunkSize + * @returns {Array} + */ +export function chunkArray (arr, chunkSize) { + const result = [] + for (let i = 0; i < arr.length; i += chunkSize) { + result.push(arr.slice(i, i + chunkSize)) + } + return result +} + +/** + * Async function which resolves after the given ms + * @param {Number} ms + */ +export async function wait (ms = 1) { + return new Promise(resolve => setTimeout(resolve, ms)) +} \ No newline at end of file diff --git a/app/assets/frontends/beaker-forum/js/lib/is-ext-binary.js b/app/assets/frontends/beaker-forum/js/lib/is-ext-binary.js new file mode 100644 index 0000000000..df65c8c58f --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/lib/is-ext-binary.js @@ -0,0 +1,256 @@ +const BIN_EXTS = [ + '3dm', + '3ds', + '3g2', + '3gp', + '7z', + 'a', + 'aac', + 'adp', + 'ai', + 'aif', + 'aiff', + 'alz', + 'ape', + 'apk', + 'ar', + 'arj', + 'asf', + 'au', + 'avi', + 'bak', + 'baml', + 'bh', + 'bin', + 'bk', + 'bmp', + 'btif', + 'bz2', + 'bzip2', + 'cab', + 'caf', + 'cgm', + 'class', + 'cmx', + 'cpio', + 'cr2', + 'cur', + 'dat', + 'dcm', + 'deb', + 'dex', + 'djvu', + 'dll', + 'dmg', + 'dng', + 'doc', + 'docm', + 'docx', + 'dot', + 'dotm', + 'dra', + 'DS_Store', + 'dsk', + 'dts', + 'dtshd', + 'dvb', + 'dwg', + 'dxf', + 'ecelp4800', + 'ecelp7470', + 'ecelp9600', + 'egg', + 'eol', + 'eot', + 'epub', + 'exe', + 'f4v', + 'fbs', + 'fh', + 'fla', + 'flac', + 'fli', + 'flv', + 'fpx', + 'fst', + 'fvt', + 'g3', + 'gh', + 'gif', + 'graffle', + 'gz', + 'gzip', + 'h261', + 'h263', + 'h264', + 'icns', + 'ico', + 'ief', + 'img', + 'ipa', + 'iso', + 'jar', + 'jpeg', + 'jpg', + 'jpgv', + 'jpm', + 'jxr', + 'key', + 'ktx', + 'lha', + 'lib', + 'lvp', + 'lz', + 'lzh', + 'lzma', + 'lzo', + 'm3u', + 'm4a', + 'm4v', + 'mar', + 'mdi', + 'mht', + 'mid', + 'midi', + 'mj2', + 'mka', + 'mkv', + 'mmr', + 'mng', + 'mobi', + 'mov', + 'movie', + 'mp3', + 'mp4', + 'mp4a', + 'mpeg', + 'mpg', + 'mpga', + 'mxu', + 'nef', + 'npx', + 'numbers', + 'nupkg', + 'o', + 'oga', + 'ogg', + 'ogv', + 'otf', + 'pages', + 'pbm', + 'pcx', + 'pdb', + 'pdf', + 'pea', + 'pgm', + 'pic', + 'png', + 'pnm', + 'pot', + 'potm', + 'potx', + 'ppa', + 'ppam', + 'ppm', + 'pps', + 'ppsm', + 'ppsx', + 'ppt', + 'pptm', + 'pptx', + 'psd', + 'pya', + 'pyc', + 'pyo', + 'pyv', + 'qt', + 'rar', + 'ras', + 'raw', + 'resources', + 'rgb', + 'rip', + 'rlc', + 'rmf', + 'rmvb', + 'rtf', + 'rz', + 's3m', + 's7z', + 'scpt', + 'sgi', + 'shar', + 'sil', + 'sketch', + 'slk', + 'smv', + 'snk', + 'so', + 'stl', + 'suo', + 'sub', + 'swf', + 'tar', + 'tbz', + 'tbz2', + 'tga', + 'tgz', + 'thmx', + 'tif', + 'tiff', + 'tlz', + 'ttc', + 'ttf', + 'txz', + 'udf', + 'uvh', + 'uvi', + 'uvm', + 'uvp', + 'uvs', + 'uvu', + 'viv', + 'vob', + 'war', + 'wav', + 'wax', + 'wbmp', + 'wdp', + 'weba', + 'webm', + 'webp', + 'whl', + 'wim', + 'wm', + 'wma', + 'wmv', + 'wmx', + 'woff', + 'woff2', + 'wrm', + 'wvx', + 'xbm', + 'xif', + 'xla', + 'xlam', + 'xls', + 'xlsb', + 'xlsm', + 'xlsx', + 'xlt', + 'xltm', + 'xltx', + 'xm', + 'xmind', + 'xpi', + 'xpm', + 'xwd', + 'xz', + 'z', + 'zip', + 'zipx' +] + +export function isFilenameBinary (str = '') { + return BIN_EXTS.includes(str.split('.').pop().toLowerCase()) +} \ No newline at end of file diff --git a/app/assets/frontends/beaker-forum/js/lib/isread-db.js b/app/assets/frontends/beaker-forum/js/lib/isread-db.js new file mode 100644 index 0000000000..af56034c05 --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/lib/isread-db.js @@ -0,0 +1,67 @@ +/** + * Isread DB + * + * A table of URLs and "is read" flags + */ + +import { openDB } from '../../vendor/idb/index.js' +import '../../vendor/idb/async-iterators.js' +import { lock } from './lock.js' + +// typedefs +// = + +/** + */ + +// exported api +// = + +export var db = undefined + +/** + * @returns {void} + */ +export async function setup () { + db = await openDB('index:isread', 1, { + upgrade (db, oldVersion, newVersion, transaction) { + var itemsStore = db.createObjectStore('items', {keyPath: 'key'}) + }, + blocked () { + // TODO do we need to handle this? + console.debug('index:isread DB is blocked') + }, + blocking () { + // TODO do we need to handle this? + console.debug('index:isread DB is blocking') + } + }) +} + +/** + * @param {String} key + * @returns {Promise} + */ +export async function get (key) { + if (!db) await setup() + var value = await db.get('items', key) + return !!value +} + +/** + * @param {String} key + * @returns {Promise} + */ +export async function put (key) { + if (!db) await setup() + await db.put('items', {key}) +} + +/** + * @param {String} key + * @returns {Promise} + */ +export async function remove (key) { + if (!db) await setup() + await db.delete('items', key) +} \ No newline at end of file diff --git a/app/assets/frontends/beaker-forum/js/lib/lock.js b/app/assets/frontends/beaker-forum/js/lib/lock.js new file mode 100644 index 0000000000..e5c149e5cf --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/lib/lock.js @@ -0,0 +1,54 @@ +class AwaitLock { + constructor() { + this._acquired = false; + this._waitingResolvers = []; + } + /** + * Acquires the lock, waiting if necessary for it to become free if it is already locked. The + * returned promise is fulfilled once the lock is acquired. + * + * After acquiring the lock, you **must** call `release` when you are done with it. + */ + acquireAsync() { + if (!this._acquired) { + this._acquired = true; + return Promise.resolve(); + } + return new Promise(resolve => { + this._waitingResolvers.push(resolve); + }); + } + /** + * Acquires the lock if it is free and otherwise returns immediately without waiting. Returns + * `true` if the lock was free and is now acquired, and `false` otherwise + */ + tryAcquire() { + if (!this._acquired) { + this._acquired = true; + return true; + } + return false; + } + /** + * Releases the lock and gives it to the next waiting acquirer, if there is one. Each acquirer + * must release the lock exactly once. + */ + release() { + if (this._waitingResolvers.length > 0) { + let resolve = this._waitingResolvers.shift(); + resolve(); + } + else { + this._acquired = false; + } + } +} + +var locks = {} +export async function lock (key) { + if (!(key in locks)) locks[key] = new AwaitLock() + + var lock = locks[key] + await lock.acquireAsync() + return lock.release.bind(lock) +}; \ No newline at end of file diff --git a/app/assets/frontends/beaker-forum/js/lib/notifications.js b/app/assets/frontends/beaker-forum/js/lib/notifications.js new file mode 100644 index 0000000000..e6506a4222 --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/lib/notifications.js @@ -0,0 +1,213 @@ +/** + * Notifications Index + * + * We track the version of each user drive. + * The index is updated by diffing from the last "checked" version. + * All updates are filtered for relevance (e.g. likes on my posts) and then recorded. + * + * Indexes are stored in IndexedDB + * TODO: The number of events kept should be truncated to 300 to avoid performance degradation. + * + * The index should be scheduled to run in the background during idle time. + * It will update the index-files periodically so that interruption is not an issue. + * New notifications are updated as-found so that the UI can alert the user asap. + */ + +import { openDB } from '../../vendor/idb/index.js' +import '../../vendor/idb/async-iterators.js' +import { lock } from './lock.js' +import * as uwg from './uwg.js' + +// typedefs +// = + +/** + * @typedef {Object} NotificationEvent + * @prop {string} event + * @prop {string} author + * @prop {number} timestamp + * @prop {Object} detail + * @prop {boolean} isRead + * + * @typedef {Object} NotificationsIndex + * @param {Object} drives + * @param {Array} events + * + * @typedef {Object} IndexDefinition + * @param {string} path + * @param {function (object, object): boolean} filterFn + * @param {function (object, object): NotificationEvent} toEvent + */ + +// exported api +// = + +export var db = undefined +export const events = new EventTarget() + +export const STORED_EVENT_LIMIT = 300 // TODO +export const INDEXES = /** @type IndexDefinition[] */([ + { + path: '/beaker-forum/comments/', + filterFn (change, {userUrl}) { + if (change.type !== 'put') return false + if (!change.value.stat) return false + var {href, parent} = change.value.stat.metadata + if (typeof href !== 'string') return false + return href.startsWith(userUrl) || (parent && parent.startsWith(userUrl)) + }, + toEvent (change, drive) { + var {href, parent} = change.value.stat.metadata + return { + event: 'comment', + author: drive.url, + timestamp: basename(change.name), + detail: {href, parent}, + isRead: false + } + } + } +]) + +/** + * @returns {void} + */ +export async function setup () { + db = await openDB('index:notifications', 1, { + upgrade (db, oldVersion, newVersion, transaction) { + var eventsStore = db.createObjectStore('events', {keyPath: 'timestamp'}) + var drivesStore = db.createObjectStore('drives', {keyPath: 'url'}) + }, + blocked () { + // TODO do we need to handle this? + console.debug('index:notifications DB is blocked') + }, + blocking () { + // TODO do we need to handle this? + console.debug('index:notifications DB is blocking') + } + }) +} + +/** + * @param {Object} [opts] + * @param {number} [opts.offset] + * @param {number} [opts.limit] + * @returns {Promise} + */ +export async function list ({offset, limit} = {offset: 0, limit: 50}) { + if (!db) await setup() + var end = offset + limit + var index = 0 + var results = [] + var tx = db.transaction('events', 'readonly') + for await (let cursor of tx.store.iterate(undefined, 'prev')) { + if (index >= offset) results.push(cursor.value) + index++ + if (index >= end) break + } + return results +} + +/** + * @param {Object} [opts] + * @param {boolean} [opts.isUnread] + */ +export async function count ({isUnread} = {isUnread: false}) { + if (!db) await setup() + if (!isUnread) return db.count('events') + var count = 0 + var tx = db.transaction('events', 'readonly') + for await (let cursor of tx.store) { + if (!cursor.value.isRead) { + count++ + } + } + return count +} + +/** + * @returns {Promise} + */ +export async function markAllRead () { + if (!db) await setup() + var release = await lock('notifications-update') + try { + var tx = db.transaction('events', 'readwrite') + for await (let cursor of tx.store) { + if (!cursor.value.isRead) { + cursor.value.isRead = true + cursor.update(cursor.value) + } + } + await tx.done + } finally { + release() + } +} + + +/** + * @param {string} userUrl + * @returns {Promise} + */ +export async function updateIndex (userUrl) { + if (!userUrl) return + if (!db) await setup() + var release = await lock('notifications-update') + try { + var filterOpts = {userUrl} + var groupUsers = await uwg.users.list() + var userKeySet = new Set(groupUsers.map(f => f.url)) + userKeySet.delete(userUrl) + + for (let userKey of userKeySet) { + let drive = hyperdrive.load(userKey) + let driveMeta = await db.get('drives', drive.url) + let lastVersion = driveMeta ? driveMeta.version : undefined + let currentVersion = (await drive.getInfo()).version + if (typeof lastVersion !== 'number') { + lastVersion = currentVersion + } + + let numNewEvents = 0 + for (let INDEX of INDEXES) { + let changes = await drive.diff(lastVersion, INDEX.path) + for (let change of changes) { + if (!INDEX.filterFn(change, filterOpts)) continue + let evt = INDEX.toEvent(change, drive) + await db.put('events', evt) + numNewEvents++ + } + } + + await db.put('drives', {url: drive.url.toString(), version: currentVersion}) + if (numNewEvents > 0) { + events.dispatchEvent(new CustomEvent('new-events', {detail: {numNewEvents}})) + } + } + } finally { + release() + } +} + +// internal methods +// = + +var keyRegex = /([0-9a-f]{64})/i +/** + * @param {string} a + * @param {string} b + * @returns {Boolean} + */ +function isKeyEq (a = '', b = '') { + return keyRegex.exec(a)[0] === keyRegex.exec(b)[0] +} + +/** + * @param {string} value + * @returns {string} + */ +function basename (value) { + return value.split('/').pop().split('.')[0] +} \ No newline at end of file diff --git a/app/assets/frontends/beaker-forum/js/lib/query-params.js b/app/assets/frontends/beaker-forum/js/lib/query-params.js new file mode 100644 index 0000000000..c6112c0cd8 --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/lib/query-params.js @@ -0,0 +1,20 @@ +export function setParams (kv, clear = false, replaceState = false) { + var url = (new URL(window.location)) + if (clear) url.search = '' + for (var k in kv) { + if (kv[k]) { + url.searchParams.set(k, kv[k]) + } else { + url.searchParams.delete(k) + } + } + if (replaceState) { + window.history.replaceState({}, null, url) + } else { + window.history.pushState({}, null, url) + } +} + +export function getParam (k, fallback = '') { + return (new URL(window.location)).searchParams.get(k) || fallback +} \ No newline at end of file diff --git a/app/assets/frontends/beaker-forum/js/lib/strings.js b/app/assets/frontends/beaker-forum/js/lib/strings.js new file mode 100644 index 0000000000..cbb3243826 --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/lib/strings.js @@ -0,0 +1,130 @@ +export const DRIVE_KEY_REGEX = /[0-9a-f]{64}/i +export const KNOWN_DRIVE_TYPES = { + 'webterm.sh/cmd-pkg': 'webterm command' +} + +export function urlToKey (str) { + try { + return DRIVE_KEY_REGEX.exec(str)[0] + } catch (e) { + return '' + } +} +export function ucfirst (str) { + if (!str) str = '' + if (typeof str !== 'string') str = '' + str + return str.charAt(0).toUpperCase() + str.slice(1) +} + +export function pluralize (num, base, suffix = 's') { + if (num === 1) { return base } + return base + suffix +} + +export function shorten (str, n = 6) { + if (str.length > (n + 3)) { + return str.slice(0, n) + '...' + } + return str +} + +export function joinPath (...args) { + var str = args[0] + for (let v of args.slice(1)) { + v = v && typeof v === 'string' ? v : '' + let left = str.endsWith('/') + let right = v.startsWith('/') + if (left !== right) str += v + else if (left) str += v.slice(1) + else str += '/' + v + } + return str +} + +export function toDomain (str) { + if (!str) return '' + try { + var urlParsed = new URL(str) + return urlParsed.hostname + } catch (e) { + // ignore, not a url + } + return str +} + +export function toNiceDomain (str, len=4) { + var domain = toDomain(str) + if (DRIVE_KEY_REGEX.test(domain)) { + domain = `${domain.slice(0, len)}..${domain.slice(-2)}` + } + return domain +} + +export function toNiceUrl (str) { + if (!str) return '' + try { + var urlParsed = new URL(str) + if (DRIVE_KEY_REGEX.test(urlParsed.hostname)) { + urlParsed.hostname = `${urlParsed.hostname.slice(0, 4)}..${urlParsed.hostname.slice(-2)}` + } + return urlParsed.toString() + } catch (e) { + // ignore, not a url + } + return str +} + +export function makeSafe (str = '') { + return str.replace(//g, '>').replace(/&/g, '&').replace(/"/g, '"') +} + +// search results are returned from beaker's search APIs with nonces wrapping the highlighted sections +// e.g. a search for "test" might return "the {500}test{/500} result" +// this enables us to safely escape the HTML, then replace the nonces with tags +export function highlightSearchResult (str = '', nonce = 0) { + var start = new RegExp(`\\{${nonce}\\}`, 'g') // eg {500} + var end = new RegExp(`\\{/${nonce}\\}`, 'g') // eg {/500} + return makeSafe(str).replace(start, '').replace(end, '') +} + +export function normalizeUrl (str = '') { + try { + let url = new URL(str) + let res = url.protocol + '//' + url.hostname + if (url.port) res += ':' + url.port + res += url.pathname.replace(/(\/)$/, '') || '/' + if (url.search && url.search !== '?') res += url.search + if (url.hash && url.hash !== '#') res += url.hash + return res + } catch (e) { + return str + } +} + +export function slugifyUrl (str = '') { + try { + let url = new URL(str) + str = url.protocol + url.hostname + url.pathname + url.search + url.hash + } catch (e) { + // ignore + } + return slugify(normalizeUrl(str)) +} + +const reservedChars = /[ <>:"/\\|?*\x00-\x1F]/g +const endingDashes = /([-]+$)/g +export function slugify (str = '') { + return str.replace(reservedChars, '-').replace(endingDashes, '') +} + +export function toNiceDriveType (dt) { + if (!dt) return '' + return KNOWN_DRIVE_TYPES[dt] || dt +} + +export function fromPostUrlToAppRoute (postUrl) { + var url = new URL(postUrl) + var pathParts = url.pathname.split('/') + var filename = pathParts[3] + return `/users/${url.hostname}/posts/${filename}` +} \ No newline at end of file diff --git a/app/assets/frontends/beaker-forum/js/lib/test-utils.js b/app/assets/frontends/beaker-forum/js/lib/test-utils.js new file mode 100644 index 0000000000..2b98e0c53d --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/lib/test-utils.js @@ -0,0 +1,327 @@ +import { ensureUnmountByUrl } from './fs.js' +import { slugify } from './strings.js' +import * as uwg from './uwg.js' + +var debugDrives = createPersistedArray('debug-drives') + +export function init () { +} + +export function listDrives () { + return Array.from(debugDrives) +} + +export async function generateDrives (num = 10) { + if (!confirm('This will generate a lot of test drives. Continue?')) { + return + } + + for (let i = 0; i < num; i++) { + let profile = FAKE_PROFILES[(i + debugDrives.length) % FAKE_PROFILES.length] + let drive = hyperdrive.create(Object.assign(profile, {type: 'user', prompt: false})) + debugDrives.push(drive.url) + await uwg.users.add(drive.url, slugify(profile.title)) + } +} + +export async function generatePosts (numPosts = 10) { + var driveUrls = Array.from(debugDrives) + var fake_post_words = FAKE_POST.split(' ') + for (let i = 0; i < numPosts; i++) { + for (let driveUrl of driveUrls) { + let drive = hyperdrive.load(driveUrl) + let numWords = Math.min(Math.floor(Math.random() * fake_post_words.length), 30) + 1 + let startWord = Math.floor(Math.random() * numWords) + let title = fake_post_words.slice(startWord, numWords).join(' ') + await uwg.posts.addLink({ + href: 'https://beakerbrowser.com', + title + }, drive) + } + } +} + +export async function generateComments (numComments = 10) { + var driveUrls = Array.from(debugDrives) + var fake_post_words = FAKE_POST.split(' ') + for (let i = 0; i < numComments; i++) { + for (let driveUrl of driveUrls) { + let drive = hyperdrive.load(driveUrl) + let numWords = Math.min(Math.floor(Math.random() * fake_post_words.length)) + 1 + let startWord = Math.floor(Math.random() * numWords) + let content = fake_post_words.slice(startWord, numWords).join(' ') + let post = await getRandomPost() + let parentComment = (Math.random() > 0.5) ? await getRandomCommentOnPost(post) : undefined + await uwg.comments.add({ + href: post.url, + parent: parentComment ? parentComment.url : undefined, + content + }, drive) + } + } +} + +export async function deleteDrives () { + if (!confirm('Delete all test drives?')) { + return + } + + for (let url of debugDrives) { + console.debug('Unlinking', url) + await uwg.users.removeByKey(url) + } + debugDrives.length = 0 +} + +// internal +// = + +async function getRandomPost () { + var posts = await uwg.posts.list({limit: 50}, {includeProfiles: true}) + return posts[Math.floor(Math.random() * posts.length)] +} + +async function getRandomCommentOnPost (post) { + var comments = await uwg.comments.list({limit: 50, href: post.url}, {includeContent: false}) + return comments[Math.floor(Math.random() * comments.length)] +} + +async function getRandomPostOrComment () { + var [posts, comments] = await Promise.all([ + uwg.posts.list({}, {includeContent: false}), + uwg.comments.list({}, {includeContent: false}) + ]) + var candidates = posts.concat(comments) + return candidates[Math.floor(Math.random() * candidates.length)] +} + +function getRandomOtherThan (values, valueNotToTake) { + let v = undefined + while (!v || v === valueNotToTake) { + v = values[Math.floor(values.length * Math.random())] + } + return v +} + +/** + * @param {string} id + * @returns {Array} + */ +function createPersistedArray (id) { + function read () { try { return JSON.parse(localStorage[id]) } catch (e) { return [] } } + function write (values) { localStorage[id] = JSON.stringify(values) } + return /** @type Array */(new Proxy({}, { + get (obj, k) { return read()[k] }, + set (obj, k, v) { var values = read(); values[k] = v; write(values); return true }, + deleteProperty (obj, k) { var values = read(); delete values[k]; write(values); return true } + })) +} + +const FAKE_POST = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." + +const FAKE_PROFILES = [ + { + "title": "Deirdre Richardson", + "description": "In fugiat reprehenderit voluptate magna ipsum quis ullamco. Officia aute in ad exercitation adipisicing. Sit incididunt Lorem quis sunt aliquip reprehenderit sint magna proident tempor nisi fugiat. Minim elit magna commodo adipisicing fugiat minim aliquip adipisicing cupidatat amet sint ut deserunt duis. Ea cillum enim reprehenderit est labore aliqua minim.\r\n" + }, + { + "title": "Swanson Perry", + "description": "Dolore culpa cillum adipisicing do. Est non magna culpa do qui reprehenderit. Reprehenderit sunt ad nulla ullamco. Cillum cupidatat velit sit officia nostrud elit nisi veniam ut deserunt ad. Ullamco et deserunt velit Lorem laboris. Esse et eu commodo excepteur labore duis tempor ad ea labore exercitation.\r\n" + }, + { + "title": "Yesenia Anthony", + "description": "Nostrud voluptate laborum occaecat minim ea dolore id qui officia ullamco magna. Aliqua quis excepteur minim eiusmod deserunt incididunt sit deserunt dolor minim. Esse veniam reprehenderit labore enim magna ad Lorem proident et esse ullamco et.\r\n" + }, + { + "title": "Jamie Glenn", + "description": "Ea est culpa anim esse ullamco sint velit do ipsum nostrud aute. Exercitation voluptate id elit sunt. Excepteur anim officia aute do ex voluptate occaecat ullamco consequat labore ad nulla. Id do exercitation eu ipsum adipisicing esse cillum duis sunt culpa ipsum. Consectetur et laborum in culpa laboris ea. Occaecat minim id dolor id.\r\n" + }, + { + "title": "Blackwell Ryan", + "description": "Laboris ut aute qui consequat in excepteur amet ullamco proident enim. Id aliqua eu adipisicing aliquip pariatur. Adipisicing irure voluptate aute magna consequat do labore esse minim commodo exercitation amet. Et commodo do elit est deserunt sit elit ipsum velit laboris dolor id laborum esse. Ut cillum occaecat id cupidatat esse fugiat nostrud enim. Pariatur non enim non qui sunt et excepteur enim ad cillum. Sunt laboris exercitation cupidatat excepteur dolor nisi.\r\n" + }, + { + "title": "Kris Grimes", + "description": "Magna proident aliquip non cupidatat tempor adipisicing Lorem. Pariatur eiusmod cupidatat elit et nulla amet tempor voluptate. In culpa nisi amet amet duis mollit adipisicing consequat magna officia consequat labore anim.\r\n" + }, + { + "title": "Gena Manning", + "description": "Commodo tempor nulla laborum consectetur ullamco. Quis quis consectetur amet do ex pariatur anim aliqua deserunt. Et dolor sint anim velit non labore dolore consequat officia ad nisi pariatur. Ut duis est enim nisi aliqua eiusmod do enim. Id minim adipisicing excepteur velit et cupidatat voluptate aliquip. Amet minim sunt culpa qui minim incididunt dolor velit ullamco sit anim enim mollit quis.\r\n" + }, + { + "title": "Whitehead Barker", + "description": "Ut magna tempor ullamco excepteur laborum commodo voluptate laborum commodo deserunt dolore consequat mollit. Est veniam proident adipisicing nisi laborum excepteur nulla id ea. Sit sint pariatur in id in aliqua eiusmod non labore qui aute incididunt. Do et culpa aliqua quis tempor amet. Est consectetur aliqua aliqua velit labore ullamco eu. Exercitation nulla est elit elit sint nulla occaecat dolor. Sit id exercitation ut id.\r\n" + }, + { + "title": "Marina Hyde", + "description": "Deserunt velit aute ipsum qui mollit cillum deserunt eiusmod elit commodo. Duis veniam exercitation irure id laborum. Adipisicing Lorem id Lorem consectetur commodo consequat sunt mollit eiusmod ut exercitation ut labore. Lorem reprehenderit eiusmod commodo ad sit est consectetur consequat exercitation nisi id adipisicing. Aliqua incididunt nostrud excepteur tempor tempor pariatur in esse amet. Consequat est voluptate cillum non aute qui ea ad Lorem.\r\n" + }, + { + "title": "Cabrera Joyce", + "description": "Lorem nulla commodo aliqua ullamco. Fugiat incididunt laboris minim consectetur laborum sunt adipisicing magna. Culpa ex non ex tempor qui enim id ea dolor reprehenderit nulla ullamco. Exercitation incididunt magna ad dolore consequat anim. Eiusmod nulla ullamco cillum exercitation velit mollit veniam cupidatat ut eu reprehenderit id. Sunt laborum pariatur ut cillum anim proident eiusmod reprehenderit proident veniam duis veniam labore. Labore veniam fugiat aliqua cupidatat cupidatat anim anim exercitation minim in nostrud.\r\n" + }, + { + "title": "Inez Bryant", + "description": "Sit adipisicing aute aliquip officia amet amet qui anim enim laboris ex tempor sint. Tempor incididunt consectetur id non laborum. Eiusmod consequat labore quis sint non eu ad sint culpa ut. Consectetur in enim laborum officia veniam cillum anim excepteur ullamco esse eu culpa fugiat et. Amet aliqua laboris eu dolor non et excepteur do laboris est. In ut labore eu anim dolore officia nisi cupidatat ea irure aliqua. Sunt exercitation officia officia pariatur sint nisi culpa consectetur do Lorem officia nisi fugiat ut.\r\n" + }, + { + "title": "Dianne Trevino", + "description": "Non sint magna nisi occaecat Lorem incididunt cupidatat occaecat ut. Dolore aliqua non exercitation culpa magna Lorem. Occaecat qui anim non do dolore est cillum et enim et dolore voluptate cillum cillum. Minim sit occaecat in irure minim velit enim voluptate est pariatur ad voluptate. Ad reprehenderit laborum non dolor irure eu cupidatat ex proident excepteur minim velit voluptate. Nisi nostrud amet anim cupidatat id. Ipsum dolor aliquip adipisicing veniam qui dolore qui laboris duis dolore cupidatat dolor.\r\n" + }, + { + "title": "Mavis Frost", + "description": "Non laborum Lorem cillum quis enim. Velit irure voluptate deserunt et sit anim qui. Ad est fugiat enim incididunt aliqua dolore qui. Elit cupidatat magna ullamco do Lorem cupidatat esse id ipsum labore id mollit irure. Mollit fugiat id dolor laborum. Mollit esse amet occaecat sit ipsum elit mollit eiusmod deserunt deserunt. Non labore nisi dolore incididunt laboris occaecat.\r\n" + }, + { + "title": "Lawson Hunt", + "description": "Mollit Lorem occaecat ullamco eiusmod amet dolor fugiat. Commodo duis laboris laboris duis nisi pariatur. Adipisicing deserunt mollit velit ut nostrud voluptate nulla incididunt elit veniam deserunt. Non do minim excepteur culpa.\r\n" + }, + { + "title": "Hester Kirkland", + "description": "Enim aliqua ex nisi aute eiusmod ullamco dolor. Non id culpa consectetur elit in Lorem amet quis veniam in aliquip est nisi. Elit velit commodo occaecat cillum. Sit in ut fugiat amet tempor sit adipisicing commodo et id aliquip incididunt. Irure eiusmod proident duis duis officia tempor ipsum voluptate labore do est ad.\r\n" + }, + { + "title": "Riddle Whitney", + "description": "Ipsum eiusmod fugiat minim nisi sint eiusmod mollit laborum nulla duis nisi aute do. Deserunt dolor ea sunt officia adipisicing irure enim aliquip magna ut veniam. Reprehenderit tempor amet ullamco aliqua proident mollit officia.\r\n" + }, + { + "title": "Ericka Hammond", + "description": "Aliqua dolor culpa esse proident fugiat mollit labore nisi veniam fugiat id. Adipisicing sint nisi deserunt enim proident eu nostrud. Incididunt est incididunt irure non ex magna esse aliqua commodo voluptate enim incididunt fugiat. Laboris ipsum cupidatat ad sunt occaecat.\r\n" + }, + { + "title": "Becker Lowe", + "description": "Excepteur amet incididunt exercitation deserunt pariatur est quis irure. Consequat in occaecat enim labore mollit. Pariatur culpa amet Lorem labore laborum amet.\r\n" + }, + { + "title": "Kaufman Houston", + "description": "Ut laboris eu minim fugiat laborum cillum eiusmod. Sunt labore Lorem ex ut excepteur aliqua aliqua fugiat quis consequat dolore ea Lorem. Tempor mollit occaecat officia non est ipsum nisi mollit veniam aliqua quis.\r\n" + }, + { + "title": "Chris Wallace", + "description": "Mollit elit qui consequat amet. Ad consequat minim veniam proident ut sint. Excepteur occaecat ex consectetur adipisicing amet. Ullamco cupidatat est dolore dolore ex mollit labore est. Labore mollit esse magna reprehenderit in reprehenderit veniam cupidatat nulla culpa ipsum dolore. Ex aliquip excepteur incididunt quis cupidatat aute esse.\r\n" + }, + { + "title": "Elinor Barnes", + "description": "Exercitation incididunt ex deserunt est exercitation. Minim veniam non officia reprehenderit mollit quis consequat consectetur officia amet irure sunt eiusmod fugiat. Adipisicing consequat irure culpa id Lorem.\r\n" + }, + { + "title": "Barker Moreno", + "description": "Deserunt proident mollit commodo exercitation officia nulla Lorem. Amet deserunt sit in velit magna adipisicing Lorem sunt cupidatat commodo ullamco. Velit consectetur ex velit reprehenderit labore. Consectetur proident ea aliqua officia cupidatat commodo minim culpa cupidatat voluptate pariatur excepteur. Dolor et et et sunt sit ut excepteur anim do sint ipsum. Pariatur deserunt aute tempor eu esse.\r\n" + }, + { + "title": "Silvia Carney", + "description": "Mollit aliqua amet non ex eu qui tempor mollit consectetur tempor nisi occaecat aliquip. Reprehenderit voluptate et est enim est nostrud nisi dolore do nisi excepteur. Sint id dolor irure irure duis dolor est duis. Minim nostrud aliqua laboris sunt excepteur occaecat magna occaecat anim pariatur mollit do. Dolore esse nulla est irure ea pariatur nostrud fugiat non dolore amet exercitation dolor. Excepteur mollit deserunt ea quis proident nulla.\r\n" + }, + { + "title": "Gray Gomez", + "description": "Labore eiusmod nostrud ad officia ex ad dolore. Occaecat sit ex ex incididunt ea ullamco qui minim veniam. Ipsum irure velit adipisicing ad.\r\n" + }, + { + "title": "Cara Vasquez", + "description": "Commodo aliquip minim dolor aliqua aliquip tempor irure. Nulla amet eu minim mollit deserunt magna magna id. Duis pariatur mollit irure consequat eu. Excepteur anim sint veniam non et laboris id proident ex laborum irure.\r\n" + }, + { + "title": "Myrtle Chang", + "description": "Non ea et sunt id proident ipsum. Proident aute dolore fugiat ullamco deserunt quis dolore dolor voluptate aliqua pariatur mollit tempor eu. Nisi sint adipisicing deserunt cillum adipisicing non consequat est irure voluptate in. Minim id dolor incididunt duis excepteur cillum laborum enim aliquip eiusmod qui et minim consectetur. Exercitation ullamco nisi eiusmod non aliqua. Velit est in eiusmod sunt sit. Ut quis sit amet ullamco proident aute nisi occaecat enim.\r\n" + }, + { + "title": "Lakeisha Hooper", + "description": "Cillum nostrud laboris eiusmod ea aliqua est nostrud excepteur aliquip pariatur adipisicing occaecat. Cillum in mollit id culpa nostrud ex enim commodo qui et ex nostrud laboris aliquip. Exercitation do nulla voluptate sit officia dolore. Id pariatur esse duis commodo adipisicing Lorem. Sunt eiusmod minim consequat do cupidatat esse do cupidatat eu. Minim enim sunt nulla culpa. Incididunt eu tempor do commodo aliqua enim sint voluptate proident dolor.\r\n" + }, + { + "title": "Ross Graham", + "description": "Minim eu amet dolore proident dolore dolore elit enim sint incididunt sit anim voluptate anim. Et amet eiusmod voluptate eiusmod. Ipsum labore duis velit qui consequat nulla cupidatat officia duis. Culpa eiusmod proident ex voluptate.\r\n" + }, + { + "title": "Shepherd Miller", + "description": "Ipsum minim magna ullamco dolore duis cupidatat non nulla. Ullamco nulla consequat amet nisi esse minim velit exercitation. Mollit reprehenderit elit officia laboris amet aliqua sunt laborum sunt fugiat sit do. Exercitation sunt amet laborum mollit ea. Deserunt sint incididunt officia sunt.\r\n" + }, + { + "title": "England Wilkinson", + "description": "Pariatur incididunt aliquip ex veniam eu duis ad elit pariatur pariatur ipsum amet officia eiusmod. Ea tempor Lorem aliqua cillum aliqua quis. Veniam aliquip pariatur pariatur exercitation cupidatat aute aliquip reprehenderit in ex aute occaecat labore minim. Irure velit tempor ullamco exercitation est officia do non veniam laboris labore et irure.\r\n" + }, + { + "title": "Nadia Schultz", + "description": "Consequat ut nisi excepteur consequat sunt officia mollit. Labore in magna eiusmod nulla nulla. Cupidatat nostrud laborum voluptate ut occaecat occaecat nostrud sit. Irure nostrud dolor excepteur id nisi nulla eiusmod excepteur dolor consequat do qui.\r\n" + }, + { + "title": "Courtney Neal", + "description": "Anim cillum in do occaecat incididunt deserunt eu qui dolore cillum amet. Reprehenderit amet culpa officia amet. Tempor eiusmod nisi amet Lorem esse consectetur non laboris labore deserunt commodo cupidatat magna ullamco. Tempor magna elit enim eu ut velit officia adipisicing nostrud amet tempor. In ad excepteur veniam consectetur ipsum ex dolore.\r\n" + }, + { + "title": "Julie Robles", + "description": "Elit officia ea ut eiusmod deserunt mollit adipisicing mollit. Ea nisi qui ullamco ea veniam laborum fugiat eu voluptate non adipisicing dolore ut. Pariatur adipisicing id fugiat esse velit cupidatat laborum exercitation fugiat sint pariatur proident.\r\n" + }, + { + "title": "Darlene Little", + "description": "Sint fugiat commodo nostrud veniam sit ex cillum. Commodo est veniam reprehenderit nostrud pariatur quis sint pariatur. Laboris nisi proident cupidatat amet laboris quis. Est irure occaecat eu esse aute.\r\n" + }, + { + "title": "Ella Campos", + "description": "Culpa Lorem incididunt dolor sunt commodo magna. Incididunt ipsum anim nisi ex consectetur. Fugiat aliquip est pariatur elit ipsum sit nisi nostrud est ea adipisicing. Est ipsum culpa aliquip est ullamco anim. Sunt sint cillum dolore quis aliqua occaecat. Anim commodo nulla ipsum velit consectetur amet mollit sit. Cillum ex et cupidatat voluptate tempor voluptate mollit sunt.\r\n" + }, + { + "title": "Rose Gilmore", + "description": "Fugiat ullamco dolore nostrud qui exercitation nisi ipsum est. In id aliqua veniam reprehenderit nostrud culpa dolore proident ullamco ex. Et elit cillum dolore proident reprehenderit fugiat pariatur minim. Reprehenderit mollit minim quis incididunt et. Dolore in sit reprehenderit irure cupidatat tempor nulla irure. Laboris reprehenderit quis aliqua excepteur.\r\n" + }, + { + "title": "Lesa Valencia", + "description": "Mollit qui excepteur tempor esse aliqua enim consequat officia cillum commodo nisi duis. Id sint excepteur fugiat in consectetur. Deserunt quis nulla consequat ea ipsum occaecat nulla.\r\n" + }, + { + "title": "Bright Spears", + "description": "Lorem velit irure eu quis in Lorem aliqua culpa. Enim Lorem non occaecat dolor. Ut nostrud ullamco pariatur sunt fugiat tempor exercitation incididunt occaecat voluptate. Exercitation esse ipsum voluptate aute Lorem. Tempor et laboris laboris magna cillum aliqua dolor eiusmod. Laborum excepteur adipisicing dolore proident aliquip tempor aute duis. Ex fugiat culpa laboris minim culpa culpa ullamco labore ullamco.\r\n" + }, + { + "title": "Lorie Short", + "description": "Quis dolore aliqua aute enim aliquip laboris pariatur commodo dolore dolore. Commodo reprehenderit sint magna eu laborum magna fugiat laboris minim mollit. Anim ipsum ut deserunt incididunt cupidatat sunt fugiat mollit eiusmod. Excepteur consectetur adipisicing enim ex adipisicing aliqua dolore velit tempor sint dolor laborum commodo reprehenderit. Proident fugiat nisi nisi sint officia elit. Pariatur mollit reprehenderit aliquip ad officia. Et do laborum proident eiusmod aliquip tempor pariatur sint velit pariatur ipsum et.\r\n" + }, + { + "title": "Wilkins Hardy", + "description": "Nisi nulla fugiat pariatur officia eu veniam quis sit commodo eiusmod. Fugiat irure sint labore consequat incididunt adipisicing commodo fugiat sunt nisi. Et consequat deserunt labore proident qui ad quis officia est. Nostrud anim elit culpa nulla enim. Nulla Lorem pariatur proident aliquip veniam qui occaecat.\r\n" + }, + { + "title": "Davis Mueller", + "description": "Minim labore Lorem culpa cupidatat ullamco in laborum pariatur. Enim aliqua laborum occaecat enim. Eiusmod fugiat in voluptate non Lorem id reprehenderit amet consectetur pariatur qui. Id laboris eiusmod ex adipisicing ut eiusmod aliqua. Ex ut dolore elit labore deserunt enim in consequat. Duis nostrud incididunt excepteur dolore minim minim in velit officia adipisicing et do duis labore. Qui laboris minim deserunt esse ea aliqua.\r\n" + }, + { + "title": "Nola Kramer", + "description": "Consequat velit non ex laborum sunt consequat officia elit nulla irure fugiat ut Lorem. Nostrud reprehenderit ex cillum ut non culpa voluptate. Et nostrud duis consectetur voluptate enim. Dolor non eu ut voluptate esse quis minim incididunt tempor ullamco magna labore sint do. Et ea pariatur adipisicing sint. Elit commodo labore veniam irure.\r\n" + }, + { + "title": "Roy Sellers", + "description": "Lorem consectetur laboris aliqua reprehenderit commodo et Lorem laborum dolor aliquip pariatur. Mollit occaecat enim officia proident reprehenderit nulla aliquip aliqua ipsum. Duis fugiat et aute occaecat ea aliquip velit dolor. Mollit sunt occaecat aliqua adipisicing enim dolor veniam ex eu fugiat amet et dolore.\r\n" + }, + { + "title": "Beverley Miranda", + "description": "Quis consectetur ea id dolor Lorem ea ad ipsum incididunt officia amet. Excepteur fugiat pariatur reprehenderit ut. Labore exercitation nulla dolor tempor tempor. Eu id excepteur veniam aute. Do nulla commodo labore minim elit dolore minim eiusmod pariatur nulla cupidatat et laborum labore.\r\n" + }, + { + "title": "Dawn Bates", + "description": "Irure laboris pariatur culpa esse exercitation. Aliquip tempor nulla laboris duis consectetur ipsum exercitation excepteur cupidatat. Id ullamco consequat adipisicing officia eiusmod adipisicing fugiat in dolore aliqua aliquip. Ut officia proident consequat ea mollit reprehenderit culpa elit minim.\r\n" + }, + { + "title": "Hannah Lang", + "description": "Ex magna tempor in tempor id. Fugiat ut ipsum do ad nostrud officia cupidatat aliquip deserunt dolor. Pariatur veniam excepteur reprehenderit proident velit fugiat ipsum. Nulla irure minim nostrud quis. Do duis est nostrud duis amet anim tempor voluptate sit anim anim anim ea.\r\n" + }, + { + "title": "Cortez Slater", + "description": "Ad anim ullamco ipsum ex adipisicing. Commodo in exercitation veniam est. Ipsum excepteur qui aute ex. Minim ea dolor et labore do non laboris sit ullamco.\r\n" + }, + { + "title": "Bradshaw Jordan", + "description": "Pariatur pariatur ut occaecat fugiat minim magna est commodo cillum esse. Nostrud nulla et magna excepteur sit do irure cillum amet adipisicing non culpa id esse. Velit tempor exercitation nostrud occaecat aliquip reprehenderit aliquip culpa laborum ut veniam id voluptate. Adipisicing duis mollit ipsum aliqua est. Aute esse officia fugiat veniam quis nostrud et veniam laboris. Enim magna Lorem fugiat magna amet qui dolor eu magna occaecat. Officia tempor irure irure consequat enim cupidatat proident proident dolore ipsum ut reprehenderit cillum.\r\n" + }, + { + "title": "Waller Mathews", + "description": "Labore eiusmod proident exercitation Lorem officia officia adipisicing. Incididunt occaecat sint eiusmod nisi qui tempor ullamco duis adipisicing Lorem eu Lorem minim voluptate. Duis aliquip irure laborum nulla laborum aute ipsum ut occaecat eu. Exercitation sunt do do sint dolor cillum fugiat et mollit pariatur veniam.\r\n" + }, + { + "title": "Taylor Griffith", + "description": "Laborum culpa sint elit sit aliquip qui est deserunt. Esse sint tempor mollit amet labore in nostrud aliquip ex minim anim. Ullamco et enim officia dolor dolor qui in minim nostrud. Incididunt sint eu sit eu. Exercitation eiusmod aliquip nulla aute minim exercitation id.\r\n" + }, + { + "title": "Elba Stevens", + "description": "Esse ad veniam voluptate consequat. Enim adipisicing ea cupidatat in minim nulla anim cillum laboris ad ea. Dolore pariatur sint proident velit officia reprehenderit ipsum reprehenderit id deserunt elit fugiat anim ea. Occaecat ex pariatur officia veniam elit fugiat id aliqua qui.\r\n" + } +] \ No newline at end of file diff --git a/app/assets/frontends/beaker-forum/js/lib/time.js b/app/assets/frontends/beaker-forum/js/lib/time.js new file mode 100644 index 0000000000..c66cfb13fa --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/lib/time.js @@ -0,0 +1,54 @@ +import {pluralize} from './strings.js' + +const shortFormatter = new Intl.DateTimeFormat('en-US', { + month: 'short', + day: 'numeric' +}) +const longFormatter = new Intl.DateTimeFormat('en-US', { + month: 'short', + year: 'numeric', + day: 'numeric' +}) +const yearFormatter = new Intl.DateTimeFormat('en-US', {year: 'numeric'}) +const CURRENT_YEAR = yearFormatter.format(new Date()) + +export function shortDate (ts) { + ts = new Date(ts) + var year = yearFormatter.format(ts) + var formatter = (year === CURRENT_YEAR) ? shortFormatter : longFormatter + return formatter.format(ts) +} + +// simple timediff fn +// replace this with Intl.RelativeTimeFormat when it lands in Beaker +// https://stackoverflow.com/questions/6108819/javascript-timestamp-to-relative-time-eg-2-seconds-ago-one-week-ago-etc-best +const msPerMinute = 60 * 1000 +const msPerHour = msPerMinute * 60 +const msPerDay = msPerHour * 24 +const msPerMonth = msPerDay * 30 +const msPerYear = msPerDay * 365 +const now = Date.now() +export function timeDifference (ts, short = false, postfix = 'ago') { + ts = Number(new Date(ts)) + var elapsed = now - ts + if (elapsed < 1) elapsed = 1 // let's avoid 0 and negative values + if (elapsed < msPerMinute) { + let n = Math.round(elapsed/1000) + return `${n}${short ? 's' : pluralize(n, ' second')} ${postfix}` + } else if (elapsed < msPerHour) { + let n = Math.round(elapsed/msPerMinute) + return `${n}${short ? 'm' : pluralize(n, ' minute')} ${postfix}` + } else if (elapsed < msPerDay) { + let n = Math.round(elapsed/msPerHour) + return `${n}${short ? 'h' : pluralize(n, ' hour')} ${postfix}` + } else if (elapsed < msPerMonth) { + let n = Math.round(elapsed/msPerDay) + return `${n}${short ? 'd' : pluralize(n, ' day')} ${postfix}` + } else if (elapsed < msPerYear) { + let n = Math.round(elapsed/msPerMonth) + return `${n}${short ? 'mo' : pluralize(n, ' month')} ${postfix}` + } else { + let n = Math.round(elapsed/msPerYear) + return `${n}${short ? 'yr' : pluralize(n, ' year')} ${postfix}` + } +} \ No newline at end of file diff --git a/app/assets/frontends/beaker-forum/js/lib/uwg.js b/app/assets/frontends/beaker-forum/js/lib/uwg.js new file mode 100644 index 0000000000..a99353e928 --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/lib/uwg.js @@ -0,0 +1,764 @@ +import { normalizeUrl, DRIVE_KEY_REGEX, joinPath } from './strings.js' +import { queryRead, ensureDir, ensureParentDir, ensureMount, ensureUnmount, getAvailableName } from './fs.js' +import { lock } from './lock.js' +import { isFilenameBinary } from './is-ext-binary.js' + +// typedefs +// = + +/** + * @typedef {import('./fs.js').FSQueryResult} FSQueryResult + * @typedef {import('./fs.js').DriveInfo} DriveInfo + * + * @typedef {DriveInfo} UserProfile + * @prop {string} id + * @prop {boolean} isUser + * + * @typedef {FSQueryResult} Post + * + * @typedef {FSQueryResult} Comment + * @prop {string} content + * + * @typedef {Comment} ThreadedComment + * @prop {ThreadedComment} parent + * @prop {ThreadedComment[]} [replies] + * @prop {number} replyCount + * + * @typedef {Object} TabulatedVotes + * @prop {DriveInfo[]} upvotes + * @prop {DriveInfo[]} downvotes + */ + +// exported +// = + +var user = undefined +var userDrive = undefined +var groupDrive = hyperdrive.self +var profileCache = {} +export const profiles = { + async setUser (url) { + userDrive = hyperdrive.load(url) + user = await profiles.get(url) + user.isUser = true + return user + }, + + getUser () { + return user + }, + + /** + * @param {string} key + * @returns {Promise} + */ + async get (key) { + key = await toKey(key) + + // check cache + if (profileCache[key]) { + return await profileCache[key] + } + + profileCache[key] = (async function () { + var drive = hyperdrive.load(key) + var profile = await drive.getInfo() + profile.isUser = false + profile.id = await groupDrive.query({path: '/users/*', mount: profile.url}) + .then(res => res[0].path.split('/').pop()) + .catch(e => undefined) + return profile + })() + + return await profileCache[key] + }, + + async readProfile (item) { + item.drive = typeof item.drive === 'string' ? await profiles.get(item.drive) : item.drive + item.mount = typeof item.mount === 'string' ? await profiles.get(item.mount) : item.mount + }, + + async readAllProfiles (items) { + await Promise.all(items.map(profiles.readProfile)) + } +} + +export const users = { + /** + * @param {Object} [query] + * @param {string} [query.sort] + * @param {boolean} [query.reverse] + * @param {number} [query.offset] + * @param {number} [query.limit] + * @param {Object} [opts] + * @param {boolean} [opts.includeProfiles] + * @returns {Promise} + */ + async list ( + {sort, reverse, offset, limit} = {sort: undefined, reverse: undefined, offset: undefined, limit: undefined}, + {includeProfiles} = {includeProfiles: false} + ) { + var mounts = await groupDrive.query({ + path: '/users/*', + type: 'mount', + sort, + reverse, + offset, + limit + }) + + if (includeProfiles) { + return Promise.all(mounts.map(mount => profiles.get(mount.stat.mount.key))) + } else { + return mounts.map(mount => { + return { + url: `hyper://${mount.stat.mount.key}`, + id: mount.path.split('/').pop() + } + }) + } + }, + + /** + * @returns {Promise} + */ + async count () { + var mounts = await groupDrive.query({ + path: '/users/*', + type: 'mount' + }) + return mounts.length + }, + + /** + * @param {string} id + * @returns {Promise} + */ + async getByUserID (id) { + var stat = await groupDrive.stat(`/users/${id}`).catch(e => undefined) + if (stat && stat.mount.key) { + return profiles.get(stat.mount.key) + } else { + throw new Error('User not found') + } + }, + + /** + * @param {string} key + * @returns {Promise} + */ + async getByKey (key) { + key = await toKey(key) + let res = await groupDrive.query({path: '/users/*', mount: key}) + if (!res[0]) throw new Error('User not found') + return profiles.get(key) + }, + + /** + * @param {string} key + * @param {string} id + * @returns {Promise} + */ + async add (key, id) { + if (!key) throw new Error('The user URL is required') + if (!id) throw new Error('The user ID is required') + if (!isValidUserID(id)) throw new Error(`The user ID "${id}" is not valid`) + + var st = await groupDrive.stat(`/users/${id}`).catch(e => undefined) + if (st) throw new Error(`The user ID "${id}" is already taken`) + + var mounts = await groupDrive.query({ + path: '/users/*', + mount: key + }) + if (mounts[0]) { + throw new Error(`This user is already a member of the group`) + } + + await ensureDir('/users', groupDrive) + await groupDrive.mount(`/users/${id}`, key) + }, + + /** + * @param {string} oldId + * @param {string} newId + * @returns {Promise} + */ + async rename (oldId, newId) { + if (!oldId) throw new Error('The previous user ID is required') + if (!newId) throw new Error('The new user ID is required') + if (!isValidUserID(newId)) throw new Error(`The user ID "${newId}" is not valid`) + + var st = await groupDrive.stat(`/users/${oldId}`) + .catch(e => { throw new Error(`There is no user named "${oldId}"`) }) + if (!st.mount.key) throw new Error('The specified user ID does not point to a user drive') + await groupDrive.unmount(`/users/${oldId}`) + await groupDrive.mount(`/users/${newId}`, st.mount.key) + }, + + /** + * @param {string} id + * @returns {Promise} + */ + async removeByUserID (id) { + if (!id) throw new Error('The user ID is required') + await groupDrive.unmount(`/users/${id}`) + }, + + /** + * @param {string} key + * @returns {Promise} + */ + async removeByKey (key) { + if (!key) throw new Error('The key is required') + key = await toKey(key) + var mounts = await groupDrive.query({ + path: '/users/*', + mount: key + }) + if (mounts[0]) { + await groupDrive.unmount(mounts[0].path) + } + } +} + +export const posts = { + /** + * @param {Object} [query] + * @param {string} [query.author] + * @param {string} [query.driveType] + * @param {string} [query.sort] + * @param {boolean} [query.reverse] + * @param {number} [query.offset] + * @param {number} [query.limit] + * @param {Object} [opts] + * @param {boolean} [opts.includeProfiles] + * @param {boolean} [opts.includeContent] + * @returns {Promise} + */ + async list ( + {author, driveType, sort, reverse, offset, limit} = {author: undefined, driveType: undefined, sort: undefined, reverse: undefined, offset: undefined, limit: undefined}, + {includeProfiles, includeContent} = {includeProfiles: false, includeContent: true} + ) { + var drive = hyperdrive.load(author || location) + var queryFn = includeContent ? queryRead : (q, drive) => drive.query(q) + var posts = await queryFn({ + path: getPostsPaths(author), + metadata: driveType ? {'drive-type': driveType} : undefined, + sort, + reverse, + offset, + limit + }, drive) + posts = posts.filter(post => { + if (!isNonemptyString(post.stat.metadata.title)) { + return false + } + if (post.path.endsWith('.goto')) { + return ( + isNonemptyString(post.stat.metadata.href) + && isUrl(post.stat.metadata.href) + ) + } + if (includeContent && post.path.endsWith('.md') || post.path.endsWith('.txt')) { + return isNonemptyString(post.content) + } + return true + }) + if (includeProfiles) { + await profiles.readAllProfiles(posts) + } + return posts + }, + + /** + * + * @param {string} author + * @param {string} path + * @returns {Promise} + */ + async get (author, path) { + let drive = hyperdrive.load(author) + let url = drive.url + path + return { + type: 'file', + path, + url, + stat: await drive.stat(path), + drive: await profiles.get(author), + mount: undefined, + content: isFilenameBinary(path) ? undefined : await drive.readFile(path) + } + }, + + /** + * @param {Object} post + * @param {string} post.href + * @param {string} post.title + * @param {string} [post.driveType] + * @param {Object} [drive] + * @returns {Promise} + */ + async addLink ({href, title, driveType}, drive = undefined) { + if (!isNonemptyString(href)) throw new Error('URL is required') + if (!isUrl(href)) throw new Error('Invalid URL') + if (!isNonemptyString(title)) throw new Error('Title is required') + if (driveType && !isNonemptyString(driveType)) throw new Error('DriveType must be a string') + + href = normalizeUrl(href) + drive = drive || userDrive + var path = `/beaker-forum/posts/${Date.now()}.goto` + await ensureParentDir(path, drive) + await drive.writeFile(path, '', {metadata: {href, title, 'drive-type': driveType}}) + return path + }, + + /** + * @param {Object} post + * @param {string} post.title + * @param {string} post.content + * @param {Object} [drive] + * @returns {Promise} + */ + async addTextPost ({title, content}, drive = undefined) { + if (!isNonemptyString(content)) throw new Error('Content is required') + if (!isNonemptyString(title)) throw new Error('Title is required') + drive = drive || userDrive + var path = `/beaker-forum/posts/${Date.now()}.md` + await ensureParentDir(path, drive) + await drive.writeFile(path, content, {metadata: {title}}) + return path + }, + + /** + * @param {Object} post + * @param {string} post.title + * @param {string} post.ext + * @param {string} post.base64buf + * @param {Object} [drive] + * @returns {Promise} + */ + async addFile ({title, ext, base64buf}, drive = undefined) { + if (!isNonemptyString(base64buf)) throw new Error('Base64buf is required') + if (!isNonemptyString(ext)) throw new Error('File extension is required') + if (!isNonemptyString(title)) throw new Error('Title is required') + + drive = drive || userDrive + var path = `/beaker-forum/posts/${Date.now()}.${ext}` + await ensureParentDir(path, drive) + await drive.writeFile(path, base64buf, {encoding: 'base64', metadata: {title}}) + return path + }, + + /** + * @param {Post} post + * @param {string} newTitle + */ + async changeTitle (post, newTitle) { + if (!isNonemptyString(newTitle)) throw new Error('Title is required') + var filename = post.path.split('/').pop() + var path = `/beaker-forum/posts/${filename}` + var metadata = Object.assign({}, post.stat.metadata, {title: newTitle}) + await userDrive.writeFile(path, post.content || '', {metadata}) + }, + + /** + * @param {Post} post + * @returns {Promise} + */ + async remove (post) { + var filename = post.path.split('/').pop() + var path = `/beaker-forum/posts/${filename}` + await userDrive.unlink(path) + } +} + +var commentCache = {} +export const comments = { + /** + * @param {Object} query + * @param {string} [query.author] + * @param {string} [query.href] + * @param {string} [query.sort] + * @param {boolean} [query.reverse] + * @param {number} [query.offset] + * @param {number} [query.limit] + * @param {Object} [opts] + * @param {boolean} [opts.includeProfiles] + * @param {boolean} [opts.includeContent] + * @returns {Promise} + */ + async list ( + {author, href, sort, reverse, offset, limit} = {author: undefined, href: undefined, sort: undefined, reverse: undefined, offset: undefined, limit: undefined}, + {includeProfiles, includeContent} = {includeProfiles: false, includeContent: true} + ) { + var drive = hyperdrive.load(author || location) + href = href ? normalizeUrl(href) : undefined + var queryFn = includeContent ? queryRead : (q, drive) => drive.query(q) + var comments = await queryFn({ + path: getCommentsPaths(author), + metadata: href ? {href} : undefined, + sort, + reverse, + offset, + limit + }, drive) + if (includeContent) { + comments = comments.filter(c => isNonemptyString(c.content)) + } + if (includeProfiles) { + await profiles.readAllProfiles(comments) + } + return comments + }, + + /** + * @param {Object} query + * @param {string} [query.author] + * @param {string} [query.href] + * @param {string} [query.sort] + * @param {boolean} [query.reverse] + * @returns {Promise} + */ + async count ({author, href, sort, reverse} = {author: undefined, href: undefined, sort: undefined, reverse: undefined}) { + href = href ? normalizeUrl(href) : undefined + let drive = hyperdrive.load(author || location) + // commented out in favor of the cache + // var comments = await drive.query({ + // path: getCommentsPaths(author), + // metadata: href ? {href} : undefined, + // sort, + // reverse + // }) + var ckey = author || 'default' + if (!commentCache[ckey]) { + commentCache[ckey] = await drive.query({ + path: getCommentsPaths(author) + }) + } + var comments = commentCache[ckey] + if (href) comments = comments.filter(comment => comment.stat.metadata.href === href) + return comments.length + }, + + /** + * @param {string} href + * @param {Object} query + * @param {string} [query.author] + * @param {string} [query.parent] + * @param {number} [query.depth] + * @returns {Promise} + */ + async thread (href, {author, parent, depth} = {author: undefined, parent: undefined, depth: undefined}) { + href = normalizeUrl(href) + var drive = hyperdrive.load(author || location) + var comments = await queryRead({ + path: getCommentsPaths(author), + metadata: href ? {href} : undefined + }, drive) + comments = comments.filter(c => isNonemptyString(c.content)) + await profiles.readAllProfiles(comments) + + // create a map of comments by their URL + var commentsByUrl = {} + comments.forEach(comment => { commentsByUrl[comment.url] = comment }) + + // attach each comment to its parent, forming a tree + var rootComments = [] + comments.forEach(comment => { + if (comment.stat.metadata.parent) { + let parent = commentsByUrl[comment.stat.metadata.parent] + if (!parent) { + // TODO note this? somehow? + return + } + if (!parent.replies) { + parent.replies = [] + parent.replyCount = 0 + } + parent.replies.push(comment) + parent.replyCount++ + } else { + rootComments.push(comment) + } + }) + + // apply the parent filter + if (parent) { + rootComments = [] + comments.forEach(comment => { + if (comment.stat.metadata.parent === parent) { + rootComments.push(comment) + } + }) + } + + // apply the depth limit + if (depth) { + let recursiveApplyDepth = (currentDepth, comment) => { + if (!comment.replies) return + if (currentDepth === depth) { + comment.replies = null + } else { + comment.replies.forEach(reply => recursiveApplyDepth(currentDepth + 1, reply)) + } + } + rootComments.forEach(comment => recursiveApplyDepth(1, comment)) + } + + return rootComments + }, + + /** + * + * @param {string} author + * @param {string} path + * @returns {Promise} + */ + async get (author, path) { + let drive = hyperdrive.load(author) + let url = drive.url + path + return { + type: 'file', + path, + url, + stat: await drive.stat(path), + drive: await profiles.get(author), + mount: undefined, + content: await drive.readFile(path), + } + }, + + /** + * @param {Object} comment + * @param {string} comment.href + * @param {string} [comment.parent] + * @param {string} comment.content + * @returns {Promise} + */ + async add ({href, parent, content}, drive = undefined) { + if (!isNonemptyString(href)) throw new Error('URL is required') + if (!isUrl(href)) throw new Error('Invalid URL') + if (!isNonemptyString(content)) throw new Error('Content is required') + + href = normalizeUrl(href) + + var path = `/beaker-forum/comments/${Date.now()}.md` + drive = drive || userDrive + await ensureParentDir(path, drive) + await drive.writeFile(path, content, {metadata: {href, parent}}) + return path + }, + + /** + * @param {Comment} comment + * @param {Object} updates + * @param {string} [updates.content] + * @returns {Promise} + */ + async update (comment, {content}) { + if (!isNonemptyString(content)) throw new Error('Content is required') + var commentPath = `/beaker-forum/comments/${comment.path.split('/').pop()}` + + var stat + try { + stat = await userDrive.stat(commentPath) + } catch (e) { + throw new Error(`Failed to read comment-file for update: ${e.toString()}`) + } + + await userDrive.writeFile(commentPath, content, {metadata: stat.metadata}) + return commentPath + }, + + /** + * @param {Comment} comment + * @returns {Promise} + */ + async remove (comment) { + var commentPath = `/beaker-forum/comments/${comment.path.split('/').pop()}` + await userDrive.unlink(commentPath) + } +} + +var voteCache = {} +export const votes = { + /** + * @param {Object} query + * @param {string} [query.author] + * @param {string} [query.href] + * @param {string} [query.sort] + * @param {boolean} [query.reverse] + * @returns {Promise} + */ + async list ({author, href, sort, reverse} = {author: undefined, href: undefined, sort: undefined, reverse: undefined}) { + href = href ? normalizeUrl(href) : undefined + var drive = hyperdrive.load(author || location) + var res = await drive.query({ + path: getVotesPaths(author), + metadata: href ? {href} : undefined, + sort, + reverse + }) + await profiles.readAllProfiles(res) + return res + }, + + /** + * @param {string} href + * @param {Object} query + * @param {string} [query.author] + * @param {Object} [opts] + * @param {boolean} [opts.includeProfiles] + * @param {boolean} [opts.noCache] + * @returns {Promise} + */ + async tabulate (href, {author} = {author: undefined}, {includeProfiles, noCache} = {includeProfiles: false, noCache: false}) { + href = normalizeUrl(href) + var drive = hyperdrive.load(author || location) + // commented out in favor of the cache + // var votes = await drive.query({ + // path: getVotesPaths(author), + // metadata: {href} + // }) + if (!voteCache[author] || noCache) { + voteCache[author] = await drive.query({ + path: getVotesPaths(author) + }) + } + var votes = voteCache[author].filter(item => item.stat.metadata.href === href) + + if (includeProfiles) { + await profiles.readAllProfiles(votes) + } + + // construct tabulated list + var upvotes = new Set() + var downvotes = new Set() + for (let vote of votes) { + if (Number(vote.stat.metadata.vote) === -1) { + upvotes.delete(vote.drive) + downvotes.add(vote.drive) + } else { + upvotes.add(vote.drive) + downvotes.delete(vote.drive) + } + } + + return { + upvotes: Array.from(upvotes), + downvotes: Array.from(downvotes) + } + }, + + /** + * @param {string} href + * @returns {Promise} + */ + async get (author, href) { + href = normalizeUrl(href) + var drive = hyperdrive.load(author || location) + var votes = await drive.query({ + path: getVotesPaths(author), + metadata: {href} + }) + return votes[0] ? votes[0] : undefined + }, + + /** + * @param {string} href + * @param {number} vote + * @returns {Promise} + */ + async put (href, vote, drive = undefined) { + if (!isNonemptyString(href)) throw new Error('URL is required') + if (!isUrl(href)) throw new Error('Invalid URL') + + href = normalizeUrl(href) + vote = vote == 1 ? 1 : vote == -1 ? -1 : 0 + drive = drive || userDrive + + var existingVote = await votes.get(drive.url, href) + if (existingVote) await drive.unlink(existingVote.path) + + if (!vote) return + + var path = `/beaker-forum/votes/${Date.now()}.goto` + await ensureParentDir(path, drive) + await drive.writeFile(path, '', {metadata: {href, vote}}) + return path + }, + + /** + * @param {Object} votes + * @param {string} subjectUrl + */ + getVoteBy (votes, subjectUrl) { + if (!votes) return 0 + if (votes.upvotes.find(url => (url.url || url) === subjectUrl)) return 1 + if (votes.downvotes.find(url => (url.url || url) === subjectUrl)) return -1 + return 0 + } +} + +// internal +// = + +function isNonemptyString (v) { + return v && typeof v === 'string' +} + +function isUrl (v) { + try { + var u = new URL(v) + return true + } catch (e) { + return false + } +} + +function isValidUserID (v) { + return isNonemptyString(v) && /^[a-z][a-z0-9-\.]*$/i.test(v) +} + +async function toKey (key) { + var match = DRIVE_KEY_REGEX.exec(key) + if (match) return match[0] + return '' +} + +/** + * @param {string} author + * @returns {string|string[]} + */ +function getPostsPaths (author) { + if (author) { + return `/beaker-forum/posts/*` + } else { + return `/users/*/beaker-forum/posts/*` + } +} + +/** + * @param {string} author + * @returns {string|string[]} + */ +function getCommentsPaths (author) { + if (author) { + return `/beaker-forum/comments/*.md` + } else { + return `/users/*/beaker-forum/comments/*.md` + } +} + +/** + * @param {string} author + * @returns {string|string[]} + */ +function getVotesPaths (author) { + if (author) { + return `/beaker-forum/votes/*.goto` + } else { + return `/users/*/beaker-forum/votes/*.goto` + } +} diff --git a/app/assets/frontends/beaker-forum/js/view/comments.js b/app/assets/frontends/beaker-forum/js/view/comments.js new file mode 100644 index 0000000000..99fa5391fc --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/view/comments.js @@ -0,0 +1,49 @@ +import { LitElement, html } from '../../vendor/lit-element/lit-element.js' +import '../com/comments/feed.js' +import '../com/about.js' + +export class CommentsView extends LitElement { + static get properties () { + return { + user: {type: Object} + } + } + + createRenderRoot () { + return this // no shadow dom + } + + constructor () { + super() + this.user = undefined + } + + async load () { + await this.requestUpdate() + // Array.from(this.querySelectorAll('[loadable]'), el => el.load()) + } + + render () { + return html` +
+
+ + +
+ +
+ ` + } + + // events + // = + +} + +customElements.define('beaker-comments-view', CommentsView) diff --git a/app/assets/frontends/beaker-forum/js/view/compose.js b/app/assets/frontends/beaker-forum/js/view/compose.js new file mode 100644 index 0000000000..0e6c0e8135 --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/view/compose.js @@ -0,0 +1,45 @@ +import { LitElement, html } from '../../vendor/lit-element/lit-element.js' +import '../com/posts/composer.js' +import '../com/about.js' + +export class ComposeView extends LitElement { + static get properties () { + return { + user: {type: Object} + } + } + + createRenderRoot () { + return this // no shadow dom + } + + constructor () { + super() + this.user = undefined + } + + async load () { + await this.requestUpdate() + Array.from(this.querySelectorAll('[loadable]'), el => el.load()) + } + + render () { + if (!this.user) return html`` + return html` +
+
+ +
+ +
+ ` + } + + // events + // = + +} + +customElements.define('beaker-compose-view', ComposeView) diff --git a/app/assets/frontends/beaker-forum/js/view/notifications.js b/app/assets/frontends/beaker-forum/js/view/notifications.js new file mode 100644 index 0000000000..06ab6b253e --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/view/notifications.js @@ -0,0 +1,54 @@ +import { LitElement, html } from '../../vendor/lit-element/lit-element.js' +import * as notificationsIndex from '../lib/notifications.js' +import '../com/notifications/feed.js' +import '../com/about.js' + +export class NotificationsView extends LitElement { + static get properties () { + return { + user: {type: Object} + } + } + + createRenderRoot () { + return this // no shadow dom + } + + constructor () { + super() + this.user = undefined + } + + async load () { + await this.requestUpdate() + // Array.from(this.querySelectorAll('[loadable]'), el => el.load()) + setTimeout(() => { + notificationsIndex.markAllRead() + }, 1e3) + } + + render () { + if (!this.user) return html`` + return html` +
+
+ + +
+ +
+ ` + } + + // events + // = + +} + +customElements.define('beaker-notifications-view', NotificationsView) diff --git a/app/assets/frontends/beaker-forum/js/view/post.js b/app/assets/frontends/beaker-forum/js/view/post.js new file mode 100644 index 0000000000..e139e32c87 --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/view/post.js @@ -0,0 +1,157 @@ +import { LitElement, html } from '../../vendor/lit-element/lit-element.js' +import * as uwg from '../lib/uwg.js' +import * as isreadDb from '../lib/isread-db.js' +import * as toast from '../com/toast.js' +import '../com/profiles/aside.js' +import '../com/posts/post.js' +import '../com/comments/thread.js' +import '../com/about.js' + +export class PostView extends LitElement { + static get properties () { + return { + user: {type: Object}, + author: {type: String}, + filename: {type: String}, + post: {type: Object} + } + } + + createRenderRoot () { + return this // no shadow dom + } + + constructor () { + super() + this.user = undefined + this.author = undefined + this.filename = undefined + this.post = undefined + } + + get path () { + return `/beaker-forum/posts/${this.filename}` + } + + async load () { + var authorProfile = await uwg.users.getByUserID(this.author) + var post = await uwg.posts.get(authorProfile.url, this.path) + ;[post.numComments] = await Promise.all([ + uwg.comments.count({href: post.url}) + ]) + this.post = post + console.log(this.post) + + await this.requestUpdate() + Array.from(this.querySelectorAll('[loadable]'), el => el.load()) + + var comments = await uwg.comments.thread(post.url) + await loadCommentAnnotations(comments) + post.comments = comments + await this.requestUpdate() + + /* dont await */ isreadDb.put(`${post.drive.id}:${post.path.split('/').pop()}`) + } + + render () { + if (!this.post) return html`` + return html` + +
+
+ + + ${this.post.comments ? html` + + ` : html`
`} +
+ +
+ ` + } + + // events + // = + + async onClickNav (id) { + this.subview = id + await this.requestUpdate() + Array.from(this.querySelectorAll('[loadable]'), el => el.load()) + } + + async onSubmitComment (e) { + // add the new comment + try { + var {isEditing, editTarget, href, parent, content} = e.detail + if (isEditing) { + await uwg.comments.update(editTarget, {content}) + } else { + await uwg.comments.add({href, parent, content}) + } + } catch (e) { + alert('Something went wrong. Please let the Beaker team know! (An error is logged in the console.)') + console.error('Failed to add comment') + console.error(e) + return + } + this.load() + } + + async onDeleteComment (e) { + let comment = e.detail.comment + + // delete the comment + try { + await uwg.comments.remove(comment) + } catch (e) { + alert('Something went wrong. Please let the Beaker team know! (An error is logged in the console.)') + console.error('Failed to delete comment') + console.error(e) + return + } + toast.create('Comment deleted') + this.load() + } + + async onPostDeleted (e) { + window.location = `/users/${this.author}` + } +} + +customElements.define('beaker-post-view', PostView) + +async function loadCommentAnnotations (comments) { + await Promise.all(comments.map(async (comment) => { + if (comment.replies) await loadCommentAnnotations(comment.replies) + })) +} \ No newline at end of file diff --git a/app/assets/frontends/beaker-forum/js/view/posts.js b/app/assets/frontends/beaker-forum/js/view/posts.js new file mode 100644 index 0000000000..908b1001fa --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/view/posts.js @@ -0,0 +1,51 @@ +import { LitElement, html } from '../../vendor/lit-element/lit-element.js' +import '../com/posts/feed.js' +import '../com/about.js' +import '../com/pinned-message.js' + +export class PostsView extends LitElement { + static get properties () { + return { + user: {type: Object} + } + } + + createRenderRoot () { + return this // no shadow dom + } + + constructor () { + super() + this.user = undefined + } + + async load () { + await this.requestUpdate() + // Array.from(this.querySelectorAll('[loadable]'), el => el.load()) + } + + render () { + return html` +
+
+ + + +
+ +
+ ` + } + + // events + // = + +} + +customElements.define('beaker-posts-view', PostsView) diff --git a/app/assets/frontends/beaker-forum/js/view/profile.js b/app/assets/frontends/beaker-forum/js/view/profile.js new file mode 100644 index 0000000000..b7603a7ee6 --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/view/profile.js @@ -0,0 +1,76 @@ +import { LitElement, html } from '../../vendor/lit-element/lit-element.js' +import { repeat } from '../../vendor/lit-element/lit-html/directives/repeat.js' +import * as uwg from '../lib/uwg.js' +import '../com/profiles/aside.js' +import '../com/posts/feed.js' +import '../com/comments/feed.js' +import '../com/profiles/list.js' + +export class ProfileView extends LitElement { + static get properties () { + return { + showAdminCtrls: {type: Boolean, attribute: 'admin-ctrls'}, + user: {type: Object}, + profileId: {type: String, attribute: 'profile-id'}, + subview: {type: String} + } + } + + createRenderRoot () { + return this // no shadow dom + } + + constructor () { + super() + this.showAdminCtrls = false + this.user = undefined + this.profileId = undefined + this.subview = 'posts' + } + + async load () { + await this.requestUpdate() + // Array.from(this.querySelectorAll('[loadable]'), el => el.load()) + } + + render () { + const navItem = (id, label) => html` + ${label} + ` + return html` +
+ +
+ + ${this.renderSubview()} +
+ +
+ ` + } + + renderSubview () { + if (this.subview === 'posts') { + return html`` + } + if (this.subview === 'comments') { + return html`` + } + } + + // events + // = + +} + +customElements.define('beaker-profile-view', ProfileView) diff --git a/app/assets/frontends/beaker-forum/js/view/search.js b/app/assets/frontends/beaker-forum/js/view/search.js new file mode 100644 index 0000000000..1f6702981c --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/view/search.js @@ -0,0 +1,64 @@ +import { LitElement, html } from '../../vendor/lit-element/lit-element.js' +import { toNiceDriveType } from '../lib/strings.js' +import '../com/search/results.js' +import '../com/about.js' + +export class SearchView extends LitElement { + static get properties () { + return { + user: {type: Object} + } + } + + createRenderRoot () { + return this // no shadow dom + } + + constructor () { + super() + this.user = undefined + var qp = new URLSearchParams(location.search) + this.driveType = qp.get('drive-type') || undefined + this.query = qp.get('q') || undefined + } + + async load () { + await this.requestUpdate() + } + + render () { + var title = `Search results ${this.driveType ? `for ${toNiceDriveType(this.driveType)} matching` : 'for'} "${this.query}"` + // `${this.query ? 'Searching for' : 'Listing all'} ${toNiceDriveType(this.driveType) || 'post'}s ${this.query ? `matching "${this.query}"` : ''}` + return html` + +
+
+

+ + ${title} +

+ +
+ +
+ ` + } + + // events + // = + +} + +customElements.define('beaker-search-view', SearchView) diff --git a/app/assets/frontends/beaker-forum/js/view/users.js b/app/assets/frontends/beaker-forum/js/view/users.js new file mode 100644 index 0000000000..5e9bc0c93a --- /dev/null +++ b/app/assets/frontends/beaker-forum/js/view/users.js @@ -0,0 +1,49 @@ +import { LitElement, html } from '../../vendor/lit-element/lit-element.js' +import '../com/profiles/list.js' +import '../com/about.js' + +export class UsersView extends LitElement { + static get properties () { + return { + user: {type: Object} + } + } + + createRenderRoot () { + return this // no shadow dom + } + + constructor () { + super() + this.user = undefined + } + + async load () { + await this.requestUpdate() + // Array.from(this.querySelectorAll('[loadable]'), el => el.load()) + } + + render () { + return html` +
+
+ + +
+ +
+ ` + } + + // events + // = + +} + +customElements.define('beaker-users-view', UsersView) diff --git a/app/assets/frontends/beaker-forum/ui.css b/app/assets/frontends/beaker-forum/ui.css new file mode 100644 index 0000000000..a961a45bf5 --- /dev/null +++ b/app/assets/frontends/beaker-forum/ui.css @@ -0,0 +1,80 @@ +@import "./webfonts/fontawesome.css"; + +body { + /* theme settings */ + --system-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Ubuntu, Cantarell, "Oxygen Sans", "Helvetica Neue", sans-serif; + --code-font: Consolas, 'Lucida Console', Monaco, monospace; + --red: rgb(255, 59, 48); + --orange: rgb(255, 149, 0); + --yellow: rgb(255, 204, 0); + --lime: #E6EE9C; + --green: rgb(51, 167, 71); + --teal: rgb(90, 200, 250); + --blue: #2864dc; + --purple: rgb(88, 86, 214); + --pink: rgb(255, 45, 85); + + --body-background: #fff; + --body-color: #333; + + --gray-background: #f5f5fb; + + --header-background: #fff; + --header-color: #445; + --header-search-color: #889; + --header-search-background: #fff; + --header-search-border-color: #ccd; + --header-notifications-color: #889; + --header-notifications-border-color: #aab; + --header-notifications-color--highlighted: var(--blue); + + --link-color: var(--blue); + + --button-background: #fff; + --button-color: #333; + --button-border-color: #ccd; + --button-background--hover: #f5f5f5; + --button-color--hover: #333; + + --button-background--primary: #4d84f3; + --button-color--primary: #fff; + --button-border-color--primary: #3d74e3; + --button-background--primary--hover: #3d74e3; + --button-color--primary--hover: #fff; + + --post-color: #889; + --post-title-color--read: #778; + --post-title-color--unread: #444; + --post-link-color: #778; + + background: var(--body-background); + color: var(--body-color); + font-family: var(--system-font); + font-size: 14px; +} + +*, +*:before, +*:after { + box-sizing: border-box; +} + +body { + margin: 0; +} + +a { + text-decoration: none; + color: inherit; +} + +button { + background: none; + outline-color: transparent; + border: none; +} + +code { + font-family: var(--code-font); + font-style: normal; +} \ No newline at end of file diff --git a/app/assets/frontends/beaker-forum/ui.html b/app/assets/frontends/beaker-forum/ui.html new file mode 100644 index 0000000000..ce0f1a8b23 --- /dev/null +++ b/app/assets/frontends/beaker-forum/ui.html @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/assets/frontends/beaker-forum/ui.js b/app/assets/frontends/beaker-forum/ui.js new file mode 100644 index 0000000000..44b1e466bf --- /dev/null +++ b/app/assets/frontends/beaker-forum/ui.js @@ -0,0 +1,252 @@ +import { LitElement, html } from './vendor/lit-element/lit-element.js' +import { classMap } from './vendor/lit-element/lit-html/directives/class-map.js' +import * as uwg from './js/lib/uwg.js' +import * as notificationsIndex from './js/lib/notifications.js' +import * as tutil from './js/lib/test-utils.js' +import { pluralize, fromPostUrlToAppRoute, slugify } from './js/lib/strings.js' +import { writeToClipboard } from './js/lib/clipboard.js' +import * as toast from './js/com/toast.js' +import mainCSS from './css/main.css.js' +import './js/view/posts.js' +import './js/view/comments.js' +import './js/view/compose.js' +import './js/view/post.js' +import './js/view/profile.js' +import './js/view/users.js' +import './js/view/notifications.js' +import './js/view/search.js' +import './js/com/search-input.js' + +const NOTIFICATIONS_INTERVAL = 15e3 + +const ROUTES = { + 'home': /^\/(index.html)?$/i, + 'compose': /^\/compose\/?$/i, + 'comments': /^\/comments\/?$/i, + 'users': /^\/users\/?$/i, + 'notifications': /^\/notifications\/?$/i, + 'search': /^\/search\/?$/i, + 'userProfile': /^\/users\/(?[^\/]+)\/?$/i, + 'userPosts': /^\/users\/(?[^\/]+)\/posts\/?$/i, + 'userComments': /^\/users\/(?[^\/]+)\/comments\/?$/i, + 'post': /^\/users\/(?[^\/]+)\/posts\/(?[^\/]+)$/i, + 'comment': /^\/users\/(?[^\/]+)\/comments\/(?[^\/]+)$/i +} + +window.tutil = tutil +tutil.init() + +export class App extends LitElement { + static get properties () { + return { + currentView: {type: String}, + session: {type: Object} + } + } + + static get styles () { + return mainCSS + } + + constructor () { + super() + this.groupInfo = {title: ''} + this.route = '404' + this.routeParams = undefined + this.session = undefined + this.notificationCount = undefined + this.load() + } + + async load () { + for (let route in ROUTES) { + let match = ROUTES[route].exec(window.location.pathname) + if (match) { + this.route = route + this.routeParams = match + break + } + } + console.log(this.route, this.routeParams) + + if (this.route === 'comment') { + await this.doCommentRedirect() + return + } + + // reroute from user keys to their id + if (this.routeParams?.groups?.id && /[0-9a-f]{64}/i.test(this.routeParams.groups.id)) { + try { + console.log('redirecting from', this.routeParams.groups.id) + let userProfile = await uwg.users.getByKey(this.routeParams.groups.id) + location.pathname = location.pathname.replace(this.routeParams.groups.id, userProfile.id) + return + } catch (e) { + toast.create('That user is not a member of this group') + console.log(e) + this.route = '404' + } + } + + // load group data + var self = hyperdrive.self + this.groupInfo = await self.getInfo() + this.session = await navigator.session.get() + if (this.session) { + await uwg.profiles.setUser(this.session.user.url) + } + console.log(this.session) + + await this.requestUpdate() + Array.from(this.shadowRoot.querySelectorAll('[loadable]'), el => el.load()) + + this.notificationCount = await notificationsIndex.count({isUnread: true}) + await this.requestUpdate() + notificationsIndex.events.addEventListener('new-events', e => { + this.notificationCount += e.detail.numNewEvents + this.requestUpdate() + }) + setTimeout(this.checkNotifications.bind(this), 5e3) + } + + async checkNotifications () { + if (this.session?.user?.group?.isMember) { + await notificationsIndex.updateIndex(this.session.user.url) + } + setTimeout(this.checkNotifications.bind(this), NOTIFICATIONS_INTERVAL) + } + + async doCommentRedirect () { + try { + var comment = await uwg.comments.get(this.routeParams.groups.id, `/beaker-forum/comments/${this.routeParams.groups.filename}`) + window.location = fromPostUrlToAppRoute(comment.stat.metadata.href) + } catch (e) { + console.error('Failed to load comment', e) + } + } + + // rendering + // = + + render () { + return html` + +
+ + + ${this.groupInfo.title} + + + ${this.session?.user?.group?.isMember ? html` + New Post + + + + ${this.notificationCount ? html`` : ''} + + + + + + + + ` : this.session?.user ? html` + ` : html` + Login / Join This Group + `} +
+ ${this.session?.user && !this.session?.user?.group?.isMember ? html` +
+

Next Step

+

+ Send the group admin your URL via text or email. + Once they've added your profile, you'll be a part of the group! +

+ + ${this.session.user.url} + + +
+ ` : ''} + ${this.renderView()} + ` + } + + renderView () { + switch (this.route) { + case 'home': return html` + + ` + case 'compose': return html` + + ` + case 'comments': return html` + + ` + case 'users': return html` + + ` + case 'notifications': return html` + + ` + case 'search': return html` + + ` + case 'userProfile': + case 'userPosts': return html` + + ` + case 'userComments': return html` + + ` + case 'post': return html` + + ` + case '404': return html`

404 not found

` + } + } + + // events + // = + + onErrorBrandThumb (e) { + e.currentTarget.setAttribute('src', '/.ui/img/default-group-thumb.jpg') + } + + async onClickJoin (e) { + e.preventDefault() + + var session = await navigator.session.request() + if (this.groupInfo.writable) { + if (!(await uwg.users.getByKey(session.user.url).catch(e => undefined))) { + await uwg.users.add(session.user.url, slugify(session.user.title)) + } + } + + location.reload() + } + + async onClickLogout (e) { + e.preventDefault() + await navigator.session.destroy() + location.reload() + } + + onClickCopyUserUrl (e) { + e.preventDefault() + writeToClipboard(this.session?.user?.url) + toast.create('Copied to your clipboard') + } +} + +customElements.define('app-main', App) \ No newline at end of file diff --git a/app/assets/frontends/beaker-forum/vendor/idb/LICENSE b/app/assets/frontends/beaker-forum/vendor/idb/LICENSE new file mode 100644 index 0000000000..f8b22cee11 --- /dev/null +++ b/app/assets/frontends/beaker-forum/vendor/idb/LICENSE @@ -0,0 +1,6 @@ +ISC License (ISC) +Copyright (c) 2016, Jake Archibald + +Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/app/assets/frontends/beaker-forum/vendor/idb/README.md b/app/assets/frontends/beaker-forum/vendor/idb/README.md new file mode 100644 index 0000000000..814a449fda --- /dev/null +++ b/app/assets/frontends/beaker-forum/vendor/idb/README.md @@ -0,0 +1,450 @@ +# IndexedDB with usability. + +This is a tiny (~1.16k) library that mostly mirrors the IndexedDB API, but with small improvements that make a big difference to usability. + +1. [Installation](#installation) +1. [Changes](#changes) +1. [API](#api) + 1. [`openDB`](#opendb) + 1. [`deleteDB`](#deletedb) + 1. [`unwrap`](#unwrap) + 1. [`wrap`](#wrap) + 1. [General enhancements](#general-enhancements) + 1. [`IDBDatabase` enhancements](#idbdatabase-enhancements) + 1. [`IDBTransaction` enhancements](#idbtransaction-enhancements) + 1. [`IDBCursor` enhancements](#idbcursor-enhancements) + 1. [Async iterators](#async-iterators) +1. [Examples](#examples) +1. [TypeScript](#typescript) + +# Installation + +```sh +npm install idb +``` + +Then, assuming you're using a module-compatible system (like webpack, Rollup etc): + +```js +import { openDB, deleteDB, wrap, unwrap } from 'idb'; + +async function doDatabaseStuff() { + const db = await openDB(…); +} +``` + +Or, use it directly via unpkg: + +```html + +``` + +# Changes + +This is a rewrite from 3.x with substantial changes. [See details](changes.md). + +# API + +## `openDB` + +This method opens a database, and returns a promise for an enhanced [`IDBDatabase`](https://w3c.github.io/IndexedDB/#database-interface). + +```js +const db = await openDB(name, version, { + upgrade(db, oldVersion, newVersion, transaction) { + // … + }, + blocked() { + // … + }, + blocking() { + // … + }, +}); +``` + +- `name`: Name of the database. +- `version`: Schema version. +- `upgrade` (optional): Called if this version of the database has never been opened before. Use it to specify the schema for the database. This is similar to the [`upgradeneeded` event](https://developer.mozilla.org/en-US/docs/Web/API/IDBOpenDBRequest/upgradeneeded_event) in plain IndexedDB. + - `db`: An enhanced `IDBDatabase`. + - `oldVersion`: Last version of the database opened by the user. + - `newVersion`: Whatever new version you provided. + - `transaction`: An enhanced transaction for this upgrade. This is useful if you need to get data from other stores as part of a migration. +- `blocked` (optional): Called if there are older versions of the database open on the origin, so this version cannot open. This is similar to the [`blocked` event](https://developer.mozilla.org/en-US/docs/Web/API/IDBOpenDBRequest/blocked_event) in plain IndexedDB. +- `blocking` (optional): Called if this connection is blocking a future version of the database from opening. This is similar to the [`versionchange` event](https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase/versionchange_event) in plain IndexedDB. + +## `deleteDB` + +Deletes a database. + +```js +await deleteDB(name, { + blocked() { + // … + }, +}); +``` + +- `name`: Name of the database. +- `blocked` (optional): Called if the database already exists and there are open connections that don’t close in response to a versionchange event, the request will be blocked until all they close. + +## `unwrap` + +Takes an enhanced IndexedDB object and returns the plain unmodified one. + +```js +const unwrapped = unwrap(wrapped); +``` + +This is useful if, for some reason, you want to drop back into plain IndexedDB. Promises will also be converted back into `IDBRequest` objects. + +## `wrap` + +Takes an IDB object and returns a version enhanced by this library. + +```js +const wrapped = wrap(unwrapped); +``` + +This is useful if some third party code gives you an `IDBDatabase` object and you want it to have the features of this library. + +This doesn't work with `IDBCursor`, [due to missing primitives](https://github.com/w3c/IndexedDB/issues/255). Also, if you wrap an `IDBTransaction`, `tx.store` and `tx.objectStoreNames` won't work in Edge. To avoid these issues, wrap the `IDBDatabase` object, and use the wrapped object to create a new transaction. + +## General enhancements + +Once you've opened the database the API is the same as IndexedDB, except for a few changes to make things easier. + +Firstly, any method that usually returns an `IDBRequest` object will now return a promise for the result. + +```js +const store = db.transaction(storeName).objectStore(storeName); +const value = await store.get(key); +``` + +### Promises & throwing + +The library turns all `IDBRequest` objects into promises, but it doesn't know in advance which methods may return promises. + +As a result, methods such as `store.put` may throw instead of returning a promise. + +If you're using async functions, there's no observable difference. + +### Transaction lifetime + +TL;DR: **Do not `await` other things between the start and end of your transaction**, otherwise the transaction will close before you're done. + +An IDB transaction auto-closes if it doesn't have anything left do once microtasks have been processed. As a result, this works fine: + +```js +const tx = db.transaction('keyval', 'readwrite'); +const store = tx.objectStore('keyval'); +const val = (await store.get('counter')) || 0; +store.put(val + 1, 'counter'); +await tx.done; +``` + +But this doesn't: + +```js +const tx = db.transaction('keyval', 'readwrite'); +const store = tx.objectStore('keyval'); +const val = (await store.get('counter')) || 0; +// This is where things go wrong: +const newVal = await fetch('/increment?val=' + val); +// And this throws an error: +store.put(newVal, 'counter'); +await tx.done; +``` + +In this case, the transaction closes while the browser is fetching, so `store.put` fails. + +## `IDBDatabase` enhancements + +### Shortcuts to get/set from an object store + +It's common to create a transaction for a single action, so helper methods are included for this: + +```js +// Get a value from a store: +const value = await db.get(storeName, key); +// Set a value in a store: +await db.put(storeName, value, key); +``` + +The shortcuts are: `get`, `getKey`, `getAll`, `getAllKeys`, `count`, `put`, `add`, `delete`, and `clear`. Each method takes a `storeName` argument, the name of the object store, and the rest of the arguments are the same as the equivalent `IDBObjectStore` method. + +These methods depend on the same methods on `IDBObjectStore`, therefore `getKey`, `getAll`, and `getAllKeys` are missing in Edge. + +### Shortcuts to get from an index + +The shortcuts are: `getFromIndex`, `getKeyFromIndex`, `getAllFromIndex`, `getAllKeysFromIndex`, and `countFromIndex`. + +```js +// Get a value from an index: +const value = await db.getFromIndex(storeName, indexName, key); +``` + +Each method takes `storeName` and `indexName` arguments, followed by the rest of the arguments from the equivalent `IDBIndex` method. Again, these methods depend on the equivalent methods on `IDBIndex`, so Edge does not support `getKeyFromIndex`, `getAllFromIndex`, or `getAllKeysFromIndex`. + +## `IDBTransaction` enhancements + +### `tx.store` + +If a transaction involves a single store, the `store` property will reference that store. + +```js +const tx = db.transaction('whatever'); +const store = tx.store; +``` + +If a transaction involves multiple stores, `tx.store` is undefined, you need to use `tx.objectStore(storeName)` to get the stores. + +### `tx.done` + +Transactions have a `.done` promise which resolves when the transaction completes successfully, and otherwise rejects with the [transaction error](https://developer.mozilla.org/en-US/docs/Web/API/IDBTransaction/error). + +```js +const tx = db.transaction(storeName, 'readwrite'); +tx.store.put('foo', 'bar'); +await tx.done; +``` + +## `IDBCursor` enhancements + +Cursor advance methods (`advance`, `continue`, `continuePrimaryKey`) return a promise for the cursor, or null if there are no further values to provide. + +```js +let cursor = await db.transaction(storeName).store.openCursor(); + +while (cursor) { + console.log(cursor.key, cursor.value); + cursor = await cursor.continue(); +} +``` + +## Async iterators + +Async iterator support isn't included by default (Edge doesn't support them). To include them, import `idb/with-async-ittr.js` instead of `idb` (this increases the library size to ~1.38k): + +```js +import { openDB } from 'idb/with-async-ittr.js'; +``` + +Now you can iterate over stores, indexes, and cursors: + +```js +const tx = db.transaction(storeName); + +for await (const cursor of tx.store) { + // … +} +``` + +Each yielded object is an `IDBCursor`. You can optionally use the advance methods to skip items (within an async iterator they return void): + +```js +const tx = db.transaction(storeName); + +for await (const cursor of tx.store) { + console.log(cursor.value); + // Skip the next item + cursor.advance(2); +} +``` + +If you don't manually advance the cursor, `cursor.continue()` is called for you. + +Stores and indexes also have an `iterate` method which has the same signature as `openCursor`, but returns an async iterator: + +```js +const index = db.transaction('books').store.index('author'); + +for await (const cursor of index.iterate('Douglas Adams')) { + console.log(cursor.value); +} +``` + +# Examples + +## Keyval store + +This is very similar to `localStorage`, but async. If this is _all_ you need, you may be interested in [idb-keyval](https://www.npmjs.com/package/idb-keyval). You can always upgrade to this library later. + +```js +const dbPromise = openDB('keyval-store', 1, { + upgrade(db) { + db.createObjectStore('keyval'); + }, +}); + +const idbKeyval = { + async get(key) { + return (await dbPromise).get('keyval', key); + }, + async set(key, val) { + return (await dbPromise).put('keyval', val, key); + }, + async delete(key) { + return (await dbPromise).delete('keyval', key); + }, + async clear() { + return (await dbPromise).clear('keyval'); + }, + async keys() { + return (await dbPromise).getAllKeys('keyval'); + }, +}; +``` + +## Article store + +```js +async function demo() { + const db = await openDB('Articles', 1, { + upgrade(db) { + // Create a store of objects + const store = db.createObjectStore('articles', { + // The 'id' property of the object will be the key. + keyPath: 'id', + // If it isn't explicitly set, create a value by auto incrementing. + autoIncrement: true, + }); + // Create an index on the 'date' property of the objects. + store.createIndex('date', 'date'); + }, + }); + + // Add an article: + await db.add('articles', { + title: 'Article 1', + date: new Date('2019-01-01'), + body: '…', + }); + + // Add multiple articles in one transaction: + { + const tx = db.transaction('articles', 'readwrite'); + tx.store.add({ + title: 'Article 2', + date: new Date('2019-01-01'), + body: '…', + }); + tx.store.add({ + title: 'Article 3', + date: new Date('2019-01-02'), + body: '…', + }); + await tx.done; + } + + // Get all the articles in date order: + console.log(await db.getAllFromIndex('articles', 'date')); + + // Add 'And, happy new year!' to all articles on 2019-01-01: + { + const tx = db.transaction('articles', 'readwrite'); + const index = tx.store.index('date'); + + for await (const cursor of index.iterate(new Date('2019-01-01'))) { + const article = { ...cursor.value }; + article.body += ' And, happy new year!'; + cursor.update(article); + } + + await tx.done; + } +} +``` + +# TypeScript + +This library is fully typed, and you can improve things by providing types for your database: + +```ts +import { openDB, DBSchema } from 'idb'; + +interface MyDB extends DBSchema { + 'favourite-number': { + key: string; + value: number; + }; + products: { + value: { + name: string; + price: number; + productCode: string; + }; + key: string; + indexes: { 'by-price': number }; + }; +} + +async function demo() { + const db = await openDB('my-db', 1, { + upgrade(db) { + db.createObjectStore('favourite-number'); + + const productStore = db.createObjectStore('products', { + keyPath: 'productCode', + }); + productStore.createIndex('by-price', 'price'); + }, + }); + + // This works + await db.put('favourite-number', 7, 'Jen'); + // This fails at compile time, as the 'favourite-number' store expects a number. + await db.put('favourite-number', 'Twelve', 'Jake'); +} +``` + +To define types for your database, extend `DBSchema` with an interface where the keys are the names of your object stores. + +For each value, provide an object where `value` is the type of values within the store, and `key` is the type of keys within the store. + +Optionally, `indexes` can contain a map of index names, to the type of key within that index. + +Provide this interface when calling `openDB`, and from then on your database will be strongly typed. This also allows your IDE to autocomplete the names of stores and indexes. + +## Opting out of types + +If you call `openDB` without providing types, your database will use basic types. However, sometimes you'll need to interact with stores that aren't in your schema, perhaps during upgrades. In that case you can cast. + +Let's say we were renaming the 'favourite-number' store to 'fave-nums': + +```ts +import { openDB, DBSchema, IDBPDatabase } from 'idb'; + +interface MyDBV1 extends DBSchema { + 'favourite-number': { key: string; value: number }; +} + +interface MyDBV2 extends DBSchema { + 'fave-num': { key: string; value: number }; +} + +const db = await openDB('my-db', 2, { + async upgrade(db, oldVersion) { + // Cast a reference of the database to the old schema. + const v1Db = (db as unknown) as IDBPDatabase; + + if (oldVersion < 1) { + v1Db.createObjectStore('favourite-number'); + } + if (oldVersion < 2) { + const store = v1Db.createObjectStore('favourite-number'); + store.name = 'fave-num'; + } + }, +}); +``` + +You can also cast to a typeless database by omitting the type, eg `db as IDBPDatabase`. + +Note: Types like `IDBPDatabase` are used by TypeScript only. The implementation uses proxies under the hood. diff --git a/app/assets/frontends/beaker-forum/vendor/idb/async-iterators.js b/app/assets/frontends/beaker-forum/vendor/idb/async-iterators.js new file mode 100644 index 0000000000..c4f54f0ee7 --- /dev/null +++ b/app/assets/frontends/beaker-forum/vendor/idb/async-iterators.js @@ -0,0 +1,54 @@ +import { b as addTraps, c as instanceOfAny, d as reverseTransformCache, e as unwrap } from './chunk.js'; + +const advanceMethodProps = ['continue', 'continuePrimaryKey', 'advance']; +const methodMap = {}; +const advanceResults = new WeakMap(); +const ittrProxiedCursorToOriginalProxy = new WeakMap(); +const cursorIteratorTraps = { + get(target, prop) { + if (!advanceMethodProps.includes(prop)) + return target[prop]; + let cachedFunc = methodMap[prop]; + if (!cachedFunc) { + cachedFunc = methodMap[prop] = function (...args) { + advanceResults.set(this, ittrProxiedCursorToOriginalProxy.get(this)[prop](...args)); + }; + } + return cachedFunc; + }, +}; +async function* iterate(...args) { + // tslint:disable-next-line:no-this-assignment + let cursor = this; + if (!(cursor instanceof IDBCursor)) { + cursor = await cursor.openCursor(...args); + } + if (!cursor) + return; + cursor = cursor; + const proxiedCursor = new Proxy(cursor, cursorIteratorTraps); + ittrProxiedCursorToOriginalProxy.set(proxiedCursor, cursor); + // Map this double-proxy back to the original, so other cursor methods work. + reverseTransformCache.set(proxiedCursor, unwrap(cursor)); + while (cursor) { + yield proxiedCursor; + // If one of the advancing methods was not called, call continue(). + cursor = await (advanceResults.get(proxiedCursor) || cursor.continue()); + advanceResults.delete(proxiedCursor); + } +} +function isIteratorProp(target, prop) { + return ((prop === Symbol.asyncIterator && + instanceOfAny(target, [IDBIndex, IDBObjectStore, IDBCursor])) || + (prop === 'iterate' && instanceOfAny(target, [IDBIndex, IDBObjectStore]))); +} +addTraps(oldTraps => ({ + get(target, prop, receiver) { + if (isIteratorProp(target, prop)) + return iterate; + return oldTraps.get(target, prop, receiver); + }, + has(target, prop) { + return isIteratorProp(target, prop) || oldTraps.has(target, prop); + }, +})); diff --git a/app/assets/frontends/beaker-forum/vendor/idb/chunk.js b/app/assets/frontends/beaker-forum/vendor/idb/chunk.js new file mode 100644 index 0000000000..c16dfdc396 --- /dev/null +++ b/app/assets/frontends/beaker-forum/vendor/idb/chunk.js @@ -0,0 +1,181 @@ +const instanceOfAny = (object, constructors) => constructors.some(c => object instanceof c); + +let idbProxyableTypes; +let cursorAdvanceMethods; +// This is a function to prevent it throwing up in node environments. +function getIdbProxyableTypes() { + return (idbProxyableTypes || + (idbProxyableTypes = [ + IDBDatabase, + IDBObjectStore, + IDBIndex, + IDBCursor, + IDBTransaction, + ])); +} +// This is a function to prevent it throwing up in node environments. +function getCursorAdvanceMethods() { + return (cursorAdvanceMethods || + (cursorAdvanceMethods = [ + IDBCursor.prototype.advance, + IDBCursor.prototype.continue, + IDBCursor.prototype.continuePrimaryKey, + ])); +} +const cursorRequestMap = new WeakMap(); +const transactionDoneMap = new WeakMap(); +const transactionStoreNamesMap = new WeakMap(); +const transformCache = new WeakMap(); +const reverseTransformCache = new WeakMap(); +function promisifyRequest(request) { + const promise = new Promise((resolve, reject) => { + const unlisten = () => { + request.removeEventListener('success', success); + request.removeEventListener('error', error); + }; + const success = () => { + resolve(wrap(request.result)); + unlisten(); + }; + const error = () => { + reject(request.error); + unlisten(); + }; + request.addEventListener('success', success); + request.addEventListener('error', error); + }); + promise + .then(value => { + // Since cursoring reuses the IDBRequest (*sigh*), we cache it for later retrieval + // (see wrapFunction). + if (value instanceof IDBCursor) { + cursorRequestMap.set(value, request); + } + // Catching to avoid "Uncaught Promise exceptions" + }) + .catch(() => { }); + // This mapping exists in reverseTransformCache but doesn't doesn't exist in transformCache. This + // is because we create many promises from a single IDBRequest. + reverseTransformCache.set(promise, request); + return promise; +} +function cacheDonePromiseForTransaction(tx) { + // Early bail if we've already created a done promise for this transaction. + if (transactionDoneMap.has(tx)) + return; + const done = new Promise((resolve, reject) => { + const unlisten = () => { + tx.removeEventListener('complete', complete); + tx.removeEventListener('error', error); + tx.removeEventListener('abort', error); + }; + const complete = () => { + resolve(); + unlisten(); + }; + const error = () => { + reject(tx.error); + unlisten(); + }; + tx.addEventListener('complete', complete); + tx.addEventListener('error', error); + tx.addEventListener('abort', error); + }); + // Cache it for later retrieval. + transactionDoneMap.set(tx, done); +} +let idbProxyTraps = { + get(target, prop, receiver) { + if (target instanceof IDBTransaction) { + // Special handling for transaction.done. + if (prop === 'done') + return transactionDoneMap.get(target); + // Polyfill for objectStoreNames because of Edge. + if (prop === 'objectStoreNames') { + return target.objectStoreNames || transactionStoreNamesMap.get(target); + } + // Make tx.store return the only store in the transaction, or undefined if there are many. + if (prop === 'store') { + return receiver.objectStoreNames[1] + ? undefined + : receiver.objectStore(receiver.objectStoreNames[0]); + } + } + // Else transform whatever we get back. + return wrap(target[prop]); + }, + has(target, prop) { + if (target instanceof IDBTransaction && + (prop === 'done' || prop === 'store')) { + return true; + } + return prop in target; + }, +}; +function addTraps(callback) { + idbProxyTraps = callback(idbProxyTraps); +} +function wrapFunction(func) { + // Due to expected object equality (which is enforced by the caching in `wrap`), we + // only create one new func per func. + // Edge doesn't support objectStoreNames (booo), so we polyfill it here. + if (func === IDBDatabase.prototype.transaction && + !('objectStoreNames' in IDBTransaction.prototype)) { + return function (storeNames, ...args) { + const tx = func.call(unwrap(this), storeNames, ...args); + transactionStoreNamesMap.set(tx, storeNames.sort ? storeNames.sort() : [storeNames]); + return wrap(tx); + }; + } + // Cursor methods are special, as the behaviour is a little more different to standard IDB. In + // IDB, you advance the cursor and wait for a new 'success' on the IDBRequest that gave you the + // cursor. It's kinda like a promise that can resolve with many values. That doesn't make sense + // with real promises, so each advance methods returns a new promise for the cursor object, or + // undefined if the end of the cursor has been reached. + if (getCursorAdvanceMethods().includes(func)) { + return function (...args) { + // Calling the original function with the proxy as 'this' causes ILLEGAL INVOCATION, so we use + // the original object. + func.apply(unwrap(this), args); + return wrap(cursorRequestMap.get(this)); + }; + } + return function (...args) { + // Calling the original function with the proxy as 'this' causes ILLEGAL INVOCATION, so we use + // the original object. + return wrap(func.apply(unwrap(this), args)); + }; +} +function transformCachableValue(value) { + if (typeof value === 'function') + return wrapFunction(value); + // This doesn't return, it just creates a 'done' promise for the transaction, + // which is later returned for transaction.done (see idbObjectHandler). + if (value instanceof IDBTransaction) + cacheDonePromiseForTransaction(value); + if (instanceOfAny(value, getIdbProxyableTypes())) + return new Proxy(value, idbProxyTraps); + // Return the same value back if we're not going to transform it. + return value; +} +function wrap(value) { + // We sometimes generate multiple promises from a single IDBRequest (eg when cursoring), because + // IDB is weird and a single IDBRequest can yield many responses, so these can't be cached. + if (value instanceof IDBRequest) + return promisifyRequest(value); + // If we've already transformed this value before, reuse the transformed value. + // This is faster, but it also provides object equality. + if (transformCache.has(value)) + return transformCache.get(value); + const newValue = transformCachableValue(value); + // Not all types are transformed. + // These may be primitive types, so they can't be WeakMap keys. + if (newValue !== value) { + transformCache.set(value, newValue); + reverseTransformCache.set(newValue, value); + } + return newValue; +} +const unwrap = (value) => reverseTransformCache.get(value); + +export { wrap as a, addTraps as b, instanceOfAny as c, reverseTransformCache as d, unwrap as e }; diff --git a/app/assets/frontends/beaker-forum/vendor/idb/index.js b/app/assets/frontends/beaker-forum/vendor/idb/index.js new file mode 100644 index 0000000000..7ee487319c --- /dev/null +++ b/app/assets/frontends/beaker-forum/vendor/idb/index.js @@ -0,0 +1,77 @@ +import { a as wrap, b as addTraps } from './chunk.js'; +export { e as unwrap, a as wrap } from './chunk.js'; + +/** + * Open a database. + * + * @param name Name of the database. + * @param version Schema version. + * @param callbacks Additional callbacks. + */ +function openDB(name, version, { blocked, upgrade, blocking } = {}) { + const request = indexedDB.open(name, version); + const openPromise = wrap(request); + if (upgrade) { + request.addEventListener('upgradeneeded', event => { + upgrade(wrap(request.result), event.oldVersion, event.newVersion, wrap(request.transaction)); + }); + } + if (blocked) + request.addEventListener('blocked', () => blocked()); + if (blocking) { + openPromise.then(db => db.addEventListener('versionchange', blocking)).catch(() => { }); + } + return openPromise; +} +/** + * Delete a database. + * + * @param name Name of the database. + */ +function deleteDB(name, { blocked } = {}) { + const request = indexedDB.deleteDatabase(name); + if (blocked) + request.addEventListener('blocked', () => blocked()); + return wrap(request).then(() => undefined); +} + +const readMethods = ['get', 'getKey', 'getAll', 'getAllKeys', 'count']; +const writeMethods = ['put', 'add', 'delete', 'clear']; +const cachedMethods = new Map(); +function getMethod(target, prop) { + if (!(target instanceof IDBDatabase && + !(prop in target) && + typeof prop === 'string')) { + return; + } + if (cachedMethods.get(prop)) + return cachedMethods.get(prop); + const targetFuncName = prop.replace(/FromIndex$/, ''); + const useIndex = prop !== targetFuncName; + const isWrite = writeMethods.includes(targetFuncName); + if ( + // Bail if the target doesn't exist on the target. Eg, getAll isn't in Edge. + !(targetFuncName in (useIndex ? IDBIndex : IDBObjectStore).prototype) || + !(isWrite || readMethods.includes(targetFuncName))) { + return; + } + const method = async function (storeName, ...args) { + // isWrite ? 'readwrite' : undefined gzipps better, but fails in Edge :( + const tx = this.transaction(storeName, isWrite ? 'readwrite' : 'readonly'); + let target = tx.store; + if (useIndex) + target = target.index(args.shift()); + const returnVal = target[targetFuncName](...args); + if (isWrite) + await tx.done; + return returnVal; + }; + cachedMethods.set(prop, method); + return method; +} +addTraps(oldTraps => ({ + get: (target, prop, receiver) => getMethod(target, prop) || oldTraps.get(target, prop, receiver), + has: (target, prop) => !!getMethod(target, prop) || oldTraps.has(target, prop), +})); + +export { openDB, deleteDB }; diff --git a/app/assets/templates/litelement/vendor/lit-element/CHANGELOG.md b/app/assets/frontends/beaker-forum/vendor/lit-element/CHANGELOG.md similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/CHANGELOG.md rename to app/assets/frontends/beaker-forum/vendor/lit-element/CHANGELOG.md diff --git a/app/assets/templates/litelement/vendor/lit-element/CONTRIBUTING.md b/app/assets/frontends/beaker-forum/vendor/lit-element/CONTRIBUTING.md similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/CONTRIBUTING.md rename to app/assets/frontends/beaker-forum/vendor/lit-element/CONTRIBUTING.md diff --git a/app/assets/templates/litelement/vendor/lit-element/LICENSE b/app/assets/frontends/beaker-forum/vendor/lit-element/LICENSE similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/LICENSE rename to app/assets/frontends/beaker-forum/vendor/lit-element/LICENSE diff --git a/app/assets/templates/litelement/vendor/lit-element/README.md b/app/assets/frontends/beaker-forum/vendor/lit-element/README.md similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/README.md rename to app/assets/frontends/beaker-forum/vendor/lit-element/README.md diff --git a/app/assets/templates/litelement/vendor/lit-element/lib/css-tag.js b/app/assets/frontends/beaker-forum/vendor/lit-element/lib/css-tag.js similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lib/css-tag.js rename to app/assets/frontends/beaker-forum/vendor/lit-element/lib/css-tag.js diff --git a/app/assets/templates/litelement/vendor/lit-element/lib/css-tag.js.map b/app/assets/frontends/beaker-forum/vendor/lit-element/lib/css-tag.js.map similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lib/css-tag.js.map rename to app/assets/frontends/beaker-forum/vendor/lit-element/lib/css-tag.js.map diff --git a/app/assets/templates/litelement/vendor/lit-element/lib/decorators.js b/app/assets/frontends/beaker-forum/vendor/lit-element/lib/decorators.js similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lib/decorators.js rename to app/assets/frontends/beaker-forum/vendor/lit-element/lib/decorators.js diff --git a/app/assets/templates/litelement/vendor/lit-element/lib/decorators.js.map b/app/assets/frontends/beaker-forum/vendor/lit-element/lib/decorators.js.map similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lib/decorators.js.map rename to app/assets/frontends/beaker-forum/vendor/lit-element/lib/decorators.js.map diff --git a/app/assets/templates/litelement/vendor/lit-element/lib/updating-element.js b/app/assets/frontends/beaker-forum/vendor/lit-element/lib/updating-element.js similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lib/updating-element.js rename to app/assets/frontends/beaker-forum/vendor/lit-element/lib/updating-element.js diff --git a/app/assets/templates/litelement/vendor/lit-element/lib/updating-element.js.map b/app/assets/frontends/beaker-forum/vendor/lit-element/lib/updating-element.js.map similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lib/updating-element.js.map rename to app/assets/frontends/beaker-forum/vendor/lit-element/lib/updating-element.js.map diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-element.js b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-element.js similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-element.js rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-element.js diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-element.js.map b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-element.js.map similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-element.js.map rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-element.js.map diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/CHANGELOG.md b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/CHANGELOG.md similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/CHANGELOG.md rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/CHANGELOG.md diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/CODE_OF_CONDUCT.md b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/CODE_OF_CONDUCT.md similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/CODE_OF_CONDUCT.md rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/CODE_OF_CONDUCT.md diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/CONTRIBUTING.md b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/CONTRIBUTING.md similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/CONTRIBUTING.md rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/CONTRIBUTING.md diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/LICENSE b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/LICENSE similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/LICENSE rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/LICENSE diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/README.md b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/README.md similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/README.md rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/README.md diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/directives/async-append.js b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/directives/async-append.js similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/directives/async-append.js rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/directives/async-append.js diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/directives/async-append.js.map b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/directives/async-append.js.map similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/directives/async-append.js.map rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/directives/async-append.js.map diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/directives/async-replace.js b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/directives/async-replace.js similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/directives/async-replace.js rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/directives/async-replace.js diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/directives/async-replace.js.map b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/directives/async-replace.js.map similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/directives/async-replace.js.map rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/directives/async-replace.js.map diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/directives/cache.js b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/directives/cache.js similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/directives/cache.js rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/directives/cache.js diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/directives/cache.js.map b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/directives/cache.js.map similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/directives/cache.js.map rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/directives/cache.js.map diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/directives/class-map.js b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/directives/class-map.js similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/directives/class-map.js rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/directives/class-map.js diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/directives/class-map.js.map b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/directives/class-map.js.map similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/directives/class-map.js.map rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/directives/class-map.js.map diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/directives/guard.js b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/directives/guard.js similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/directives/guard.js rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/directives/guard.js diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/directives/guard.js.map b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/directives/guard.js.map similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/directives/guard.js.map rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/directives/guard.js.map diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/directives/if-defined.js b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/directives/if-defined.js similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/directives/if-defined.js rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/directives/if-defined.js diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/directives/if-defined.js.map b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/directives/if-defined.js.map similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/directives/if-defined.js.map rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/directives/if-defined.js.map diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/directives/repeat.js b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/directives/repeat.js similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/directives/repeat.js rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/directives/repeat.js diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/directives/repeat.js.map b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/directives/repeat.js.map similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/directives/repeat.js.map rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/directives/repeat.js.map diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/directives/style-map.js b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/directives/style-map.js similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/directives/style-map.js rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/directives/style-map.js diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/directives/style-map.js.map b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/directives/style-map.js.map similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/directives/style-map.js.map rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/directives/style-map.js.map diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/directives/unsafe-html.js b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/directives/unsafe-html.js similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/directives/unsafe-html.js rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/directives/unsafe-html.js diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/directives/unsafe-html.js.map b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/directives/unsafe-html.js.map similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/directives/unsafe-html.js.map rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/directives/unsafe-html.js.map diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/directives/until.js b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/directives/until.js similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/directives/until.js rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/directives/until.js diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/directives/until.js.map b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/directives/until.js.map similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/directives/until.js.map rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/directives/until.js.map diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/lib/default-template-processor.js b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/default-template-processor.js similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/lib/default-template-processor.js rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/default-template-processor.js diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/lib/default-template-processor.js.map b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/default-template-processor.js.map similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/lib/default-template-processor.js.map rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/default-template-processor.js.map diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/lib/directive.js b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/directive.js similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/lib/directive.js rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/directive.js diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/lib/directive.js.map b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/directive.js.map similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/lib/directive.js.map rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/directive.js.map diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/lib/dom.js b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/dom.js similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/lib/dom.js rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/dom.js diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/lib/dom.js.map b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/dom.js.map similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/lib/dom.js.map rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/dom.js.map diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/lib/modify-template.js b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/modify-template.js similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/lib/modify-template.js rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/modify-template.js diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/lib/modify-template.js.map b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/modify-template.js.map similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/lib/modify-template.js.map rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/modify-template.js.map diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/lib/part.js b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/part.js similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/lib/part.js rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/part.js diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/lib/part.js.map b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/part.js.map similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/lib/part.js.map rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/part.js.map diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/lib/parts.js b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/parts.js similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/lib/parts.js rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/parts.js diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/lib/parts.js.map b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/parts.js.map similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/lib/parts.js.map rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/parts.js.map diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/lib/render-options.js b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/render-options.js similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/lib/render-options.js rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/render-options.js diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/lib/render-options.js.map b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/render-options.js.map similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/lib/render-options.js.map rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/render-options.js.map diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/lib/render.js b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/render.js similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/lib/render.js rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/render.js diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/lib/render.js.map b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/render.js.map similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/lib/render.js.map rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/render.js.map diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/lib/shady-render.js b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/shady-render.js similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/lib/shady-render.js rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/shady-render.js diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/lib/shady-render.js.map b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/shady-render.js.map similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/lib/shady-render.js.map rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/shady-render.js.map diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/lib/template-factory.js b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/template-factory.js similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/lib/template-factory.js rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/template-factory.js diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/lib/template-factory.js.map b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/template-factory.js.map similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/lib/template-factory.js.map rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/template-factory.js.map diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/lib/template-instance.js b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/template-instance.js similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/lib/template-instance.js rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/template-instance.js diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/lib/template-instance.js.map b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/template-instance.js.map similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/lib/template-instance.js.map rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/template-instance.js.map diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/lib/template-processor.js b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/template-processor.js similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/lib/template-processor.js rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/template-processor.js diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/lib/template-processor.js.map b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/template-processor.js.map similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/lib/template-processor.js.map rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/template-processor.js.map diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/lib/template-result.js b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/template-result.js similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/lib/template-result.js rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/template-result.js diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/lib/template-result.js.map b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/template-result.js.map similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/lib/template-result.js.map rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/template-result.js.map diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/lib/template.js b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/template.js similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/lib/template.js rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/template.js diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/lib/template.js.map b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/template.js.map similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/lib/template.js.map rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lib/template.js.map diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/lit-html.js b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lit-html.js similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/lit-html.js rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lit-html.js diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/lit-html.js.map b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lit-html.js.map similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/lit-html.js.map rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/lit-html.js.map diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/polyfills/template_polyfill.js b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/polyfills/template_polyfill.js similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/polyfills/template_polyfill.js rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/polyfills/template_polyfill.js diff --git a/app/assets/templates/litelement/vendor/lit-element/lit-html/polyfills/template_polyfill.js.map b/app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/polyfills/template_polyfill.js.map similarity index 100% rename from app/assets/templates/litelement/vendor/lit-element/lit-html/polyfills/template_polyfill.js.map rename to app/assets/frontends/beaker-forum/vendor/lit-element/lit-html/polyfills/template_polyfill.js.map diff --git a/app/assets/frontends/beaker-forum/vendor/markdown-it.js b/app/assets/frontends/beaker-forum/vendor/markdown-it.js new file mode 100644 index 0000000000..1a8f4a6476 --- /dev/null +++ b/app/assets/frontends/beaker-forum/vendor/markdown-it.js @@ -0,0 +1,8157 @@ +/*! markdown-it 10.0.0 https://github.com//markdown-it/markdown-it @license MIT */ +const define = (function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i utf16string } + // + 'use strict'; + + /*eslint quotes:0*/ + module.exports = require('entities/lib/maps/entities.json'); + + },{"entities/lib/maps/entities.json":52}],2:[function(require,module,exports){ + // List of valid html blocks names, accorting to commonmark spec + // http://jgm.github.io/CommonMark/spec.html#html-blocks + + 'use strict'; + + + module.exports = [ + 'address', + 'article', + 'aside', + 'base', + 'basefont', + 'blockquote', + 'body', + 'caption', + 'center', + 'col', + 'colgroup', + 'dd', + 'details', + 'dialog', + 'dir', + 'div', + 'dl', + 'dt', + 'fieldset', + 'figcaption', + 'figure', + 'footer', + 'form', + 'frame', + 'frameset', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'head', + 'header', + 'hr', + 'html', + 'iframe', + 'legend', + 'li', + 'link', + 'main', + 'menu', + 'menuitem', + 'meta', + 'nav', + 'noframes', + 'ol', + 'optgroup', + 'option', + 'p', + 'param', + 'section', + 'source', + 'summary', + 'table', + 'tbody', + 'td', + 'tfoot', + 'th', + 'thead', + 'title', + 'tr', + 'track', + 'ul' + ]; + + },{}],3:[function(require,module,exports){ + // Regexps to match html elements + + 'use strict'; + + var attr_name = '[a-zA-Z_:][a-zA-Z0-9:._-]*'; + + var unquoted = '[^"\'=<>`\\x00-\\x20]+'; + var single_quoted = "'[^']*'"; + var double_quoted = '"[^"]*"'; + + var attr_value = '(?:' + unquoted + '|' + single_quoted + '|' + double_quoted + ')'; + + var attribute = '(?:\\s+' + attr_name + '(?:\\s*=\\s*' + attr_value + ')?)'; + + var open_tag = '<[A-Za-z][A-Za-z0-9\\-]*' + attribute + '*\\s*\\/?>'; + + var close_tag = '<\\/[A-Za-z][A-Za-z0-9\\-]*\\s*>'; + var comment = '|'; + var processing = '<[?].*?[?]>'; + var declaration = ']*>'; + var cdata = ''; + + var HTML_TAG_RE = new RegExp('^(?:' + open_tag + '|' + close_tag + '|' + comment + + '|' + processing + '|' + declaration + '|' + cdata + ')'); + var HTML_OPEN_CLOSE_TAG_RE = new RegExp('^(?:' + open_tag + '|' + close_tag + ')'); + + module.exports.HTML_TAG_RE = HTML_TAG_RE; + module.exports.HTML_OPEN_CLOSE_TAG_RE = HTML_OPEN_CLOSE_TAG_RE; + + },{}],4:[function(require,module,exports){ + // Utilities + // + 'use strict'; + + + function _class(obj) { return Object.prototype.toString.call(obj); } + + function isString(obj) { return _class(obj) === '[object String]'; } + + var _hasOwnProperty = Object.prototype.hasOwnProperty; + + function has(object, key) { + return _hasOwnProperty.call(object, key); + } + + // Merge objects + // + function assign(obj /*from1, from2, from3, ...*/) { + var sources = Array.prototype.slice.call(arguments, 1); + + sources.forEach(function (source) { + if (!source) { return; } + + if (typeof source !== 'object') { + throw new TypeError(source + 'must be object'); + } + + Object.keys(source).forEach(function (key) { + obj[key] = source[key]; + }); + }); + + return obj; + } + + // Remove element from array and put another array at those position. + // Useful for some operations with tokens + function arrayReplaceAt(src, pos, newElements) { + return [].concat(src.slice(0, pos), newElements, src.slice(pos + 1)); + } + + //////////////////////////////////////////////////////////////////////////////// + + function isValidEntityCode(c) { + /*eslint no-bitwise:0*/ + // broken sequence + if (c >= 0xD800 && c <= 0xDFFF) { return false; } + // never used + if (c >= 0xFDD0 && c <= 0xFDEF) { return false; } + if ((c & 0xFFFF) === 0xFFFF || (c & 0xFFFF) === 0xFFFE) { return false; } + // control codes + if (c >= 0x00 && c <= 0x08) { return false; } + if (c === 0x0B) { return false; } + if (c >= 0x0E && c <= 0x1F) { return false; } + if (c >= 0x7F && c <= 0x9F) { return false; } + // out of range + if (c > 0x10FFFF) { return false; } + return true; + } + + function fromCodePoint(c) { + /*eslint no-bitwise:0*/ + if (c > 0xffff) { + c -= 0x10000; + var surrogate1 = 0xd800 + (c >> 10), + surrogate2 = 0xdc00 + (c & 0x3ff); + + return String.fromCharCode(surrogate1, surrogate2); + } + return String.fromCharCode(c); + } + + + var UNESCAPE_MD_RE = /\\([!"#$%&'()*+,\-.\/:;<=>?@[\\\]^_`{|}~])/g; + var ENTITY_RE = /&([a-z#][a-z0-9]{1,31});/gi; + var UNESCAPE_ALL_RE = new RegExp(UNESCAPE_MD_RE.source + '|' + ENTITY_RE.source, 'gi'); + + var DIGITAL_ENTITY_TEST_RE = /^#((?:x[a-f0-9]{1,8}|[0-9]{1,8}))/i; + + var entities = require('./entities'); + + function replaceEntityPattern(match, name) { + var code = 0; + + if (has(entities, name)) { + return entities[name]; + } + + if (name.charCodeAt(0) === 0x23/* # */ && DIGITAL_ENTITY_TEST_RE.test(name)) { + code = name[1].toLowerCase() === 'x' ? + parseInt(name.slice(2), 16) : parseInt(name.slice(1), 10); + + if (isValidEntityCode(code)) { + return fromCodePoint(code); + } + } + + return match; + } + + /*function replaceEntities(str) { + if (str.indexOf('&') < 0) { return str; } + + return str.replace(ENTITY_RE, replaceEntityPattern); + }*/ + + function unescapeMd(str) { + if (str.indexOf('\\') < 0) { return str; } + return str.replace(UNESCAPE_MD_RE, '$1'); + } + + function unescapeAll(str) { + if (str.indexOf('\\') < 0 && str.indexOf('&') < 0) { return str; } + + return str.replace(UNESCAPE_ALL_RE, function (match, escaped, entity) { + if (escaped) { return escaped; } + return replaceEntityPattern(match, entity); + }); + } + + //////////////////////////////////////////////////////////////////////////////// + + var HTML_ESCAPE_TEST_RE = /[&<>"]/; + var HTML_ESCAPE_REPLACE_RE = /[&<>"]/g; + var HTML_REPLACEMENTS = { + '&': '&', + '<': '<', + '>': '>', + '"': '"' + }; + + function replaceUnsafeChar(ch) { + return HTML_REPLACEMENTS[ch]; + } + + function escapeHtml(str) { + if (HTML_ESCAPE_TEST_RE.test(str)) { + return str.replace(HTML_ESCAPE_REPLACE_RE, replaceUnsafeChar); + } + return str; + } + + //////////////////////////////////////////////////////////////////////////////// + + var REGEXP_ESCAPE_RE = /[.?*+^$[\]\\(){}|-]/g; + + function escapeRE(str) { + return str.replace(REGEXP_ESCAPE_RE, '\\$&'); + } + + //////////////////////////////////////////////////////////////////////////////// + + function isSpace(code) { + switch (code) { + case 0x09: + case 0x20: + return true; + } + return false; + } + + // Zs (unicode class) || [\t\f\v\r\n] + function isWhiteSpace(code) { + if (code >= 0x2000 && code <= 0x200A) { return true; } + switch (code) { + case 0x09: // \t + case 0x0A: // \n + case 0x0B: // \v + case 0x0C: // \f + case 0x0D: // \r + case 0x20: + case 0xA0: + case 0x1680: + case 0x202F: + case 0x205F: + case 0x3000: + return true; + } + return false; + } + + //////////////////////////////////////////////////////////////////////////////// + + /*eslint-disable max-len*/ + var UNICODE_PUNCT_RE = require('uc.micro/categories/P/regex'); + + // Currently without astral characters support. + function isPunctChar(ch) { + return UNICODE_PUNCT_RE.test(ch); + } + + + // Markdown ASCII punctuation characters. + // + // !, ", #, $, %, &, ', (, ), *, +, ,, -, ., /, :, ;, <, =, >, ?, @, [, \, ], ^, _, `, {, |, }, or ~ + // http://spec.commonmark.org/0.15/#ascii-punctuation-character + // + // Don't confuse with unicode punctuation !!! It lacks some chars in ascii range. + // + function isMdAsciiPunct(ch) { + switch (ch) { + case 0x21/* ! */: + case 0x22/* " */: + case 0x23/* # */: + case 0x24/* $ */: + case 0x25/* % */: + case 0x26/* & */: + case 0x27/* ' */: + case 0x28/* ( */: + case 0x29/* ) */: + case 0x2A/* * */: + case 0x2B/* + */: + case 0x2C/* , */: + case 0x2D/* - */: + case 0x2E/* . */: + case 0x2F/* / */: + case 0x3A/* : */: + case 0x3B/* ; */: + case 0x3C/* < */: + case 0x3D/* = */: + case 0x3E/* > */: + case 0x3F/* ? */: + case 0x40/* @ */: + case 0x5B/* [ */: + case 0x5C/* \ */: + case 0x5D/* ] */: + case 0x5E/* ^ */: + case 0x5F/* _ */: + case 0x60/* ` */: + case 0x7B/* { */: + case 0x7C/* | */: + case 0x7D/* } */: + case 0x7E/* ~ */: + return true; + default: + return false; + } + } + + // Hepler to unify [reference labels]. + // + function normalizeReference(str) { + // Trim and collapse whitespace + // + str = str.trim().replace(/\s+/g, ' '); + + // In node v10 'ẞ'.toLowerCase() === 'Ṿ', which is presumed to be a bug + // fixed in v12 (couldn't find any details). + // + // So treat this one as a special case + // (remove this when node v10 is no longer supported). + // + if ('ẞ'.toLowerCase() === 'Ṿ') { + str = str.replace(/ẞ/g, 'ß'); + } + + // .toLowerCase().toUpperCase() should get rid of all differences + // between letter variants. + // + // Simple .toLowerCase() doesn't normalize 125 code points correctly, + // and .toUpperCase doesn't normalize 6 of them (list of exceptions: + // İ, ϴ, ẞ, Ω, K, Å - those are already uppercased, but have differently + // uppercased versions). + // + // Here's an example showing how it happens. Lets take greek letter omega: + // uppercase U+0398 (Θ), U+03f4 (ϴ) and lowercase U+03b8 (θ), U+03d1 (ϑ) + // + // Unicode entries: + // 0398;GREEK CAPITAL LETTER THETA;Lu;0;L;;;;;N;;;;03B8; + // 03B8;GREEK SMALL LETTER THETA;Ll;0;L;;;;;N;;;0398;;0398 + // 03D1;GREEK THETA SYMBOL;Ll;0;L; 03B8;;;;N;GREEK SMALL LETTER SCRIPT THETA;;0398;;0398 + // 03F4;GREEK CAPITAL THETA SYMBOL;Lu;0;L; 0398;;;;N;;;;03B8; + // + // Case-insensitive comparison should treat all of them as equivalent. + // + // But .toLowerCase() doesn't change ϑ (it's already lowercase), + // and .toUpperCase() doesn't change ϴ (already uppercase). + // + // Applying first lower then upper case normalizes any character: + // '\u0398\u03f4\u03b8\u03d1'.toLowerCase().toUpperCase() === '\u0398\u0398\u0398\u0398' + // + // Note: this is equivalent to unicode case folding; unicode normalization + // is a different step that is not required here. + // + // Final result should be uppercased, because it's later stored in an object + // (this avoid a conflict with Object.prototype members, + // most notably, `__proto__`) + // + return str.toLowerCase().toUpperCase(); + } + + //////////////////////////////////////////////////////////////////////////////// + + // Re-export libraries commonly used in both markdown-it and its plugins, + // so plugins won't have to depend on them explicitly, which reduces their + // bundled size (e.g. a browser build). + // + exports.lib = {}; + exports.lib.mdurl = require('mdurl'); + exports.lib.ucmicro = require('uc.micro'); + + exports.assign = assign; + exports.isString = isString; + exports.has = has; + exports.unescapeMd = unescapeMd; + exports.unescapeAll = unescapeAll; + exports.isValidEntityCode = isValidEntityCode; + exports.fromCodePoint = fromCodePoint; + // exports.replaceEntities = replaceEntities; + exports.escapeHtml = escapeHtml; + exports.arrayReplaceAt = arrayReplaceAt; + exports.isSpace = isSpace; + exports.isWhiteSpace = isWhiteSpace; + exports.isMdAsciiPunct = isMdAsciiPunct; + exports.isPunctChar = isPunctChar; + exports.escapeRE = escapeRE; + exports.normalizeReference = normalizeReference; + + },{"./entities":1,"mdurl":58,"uc.micro":65,"uc.micro/categories/P/regex":63}],5:[function(require,module,exports){ + // Just a shortcut for bulk export + 'use strict'; + + + exports.parseLinkLabel = require('./parse_link_label'); + exports.parseLinkDestination = require('./parse_link_destination'); + exports.parseLinkTitle = require('./parse_link_title'); + + },{"./parse_link_destination":6,"./parse_link_label":7,"./parse_link_title":8}],6:[function(require,module,exports){ + // Parse link destination + // + 'use strict'; + + + var unescapeAll = require('../common/utils').unescapeAll; + + + module.exports = function parseLinkDestination(str, pos, max) { + var code, level, + lines = 0, + start = pos, + result = { + ok: false, + pos: 0, + lines: 0, + str: '' + }; + + if (str.charCodeAt(pos) === 0x3C /* < */) { + pos++; + while (pos < max) { + code = str.charCodeAt(pos); + if (code === 0x0A /* \n */) { return result; } + if (code === 0x3E /* > */) { + result.pos = pos + 1; + result.str = unescapeAll(str.slice(start + 1, pos)); + result.ok = true; + return result; + } + if (code === 0x5C /* \ */ && pos + 1 < max) { + pos += 2; + continue; + } + + pos++; + } + + // no closing '>' + return result; + } + + // this should be ... } else { ... branch + + level = 0; + while (pos < max) { + code = str.charCodeAt(pos); + + if (code === 0x20) { break; } + + // ascii control characters + if (code < 0x20 || code === 0x7F) { break; } + + if (code === 0x5C /* \ */ && pos + 1 < max) { + pos += 2; + continue; + } + + if (code === 0x28 /* ( */) { + level++; + } + + if (code === 0x29 /* ) */) { + if (level === 0) { break; } + level--; + } + + pos++; + } + + if (start === pos) { return result; } + if (level !== 0) { return result; } + + result.str = unescapeAll(str.slice(start, pos)); + result.lines = lines; + result.pos = pos; + result.ok = true; + return result; + }; + + },{"../common/utils":4}],7:[function(require,module,exports){ + // Parse link label + // + // this function assumes that first character ("[") already matches; + // returns the end of the label + // + 'use strict'; + + module.exports = function parseLinkLabel(state, start, disableNested) { + var level, found, marker, prevPos, + labelEnd = -1, + max = state.posMax, + oldPos = state.pos; + + state.pos = start + 1; + level = 1; + + while (state.pos < max) { + marker = state.src.charCodeAt(state.pos); + if (marker === 0x5D /* ] */) { + level--; + if (level === 0) { + found = true; + break; + } + } + + prevPos = state.pos; + state.md.inline.skipToken(state); + if (marker === 0x5B /* [ */) { + if (prevPos === state.pos - 1) { + // increase level if we find text `[`, which is not a part of any token + level++; + } else if (disableNested) { + state.pos = oldPos; + return -1; + } + } + } + + if (found) { + labelEnd = state.pos; + } + + // restore old state + state.pos = oldPos; + + return labelEnd; + }; + + },{}],8:[function(require,module,exports){ + // Parse link title + // + 'use strict'; + + + var unescapeAll = require('../common/utils').unescapeAll; + + + module.exports = function parseLinkTitle(str, pos, max) { + var code, + marker, + lines = 0, + start = pos, + result = { + ok: false, + pos: 0, + lines: 0, + str: '' + }; + + if (pos >= max) { return result; } + + marker = str.charCodeAt(pos); + + if (marker !== 0x22 /* " */ && marker !== 0x27 /* ' */ && marker !== 0x28 /* ( */) { return result; } + + pos++; + + // if opening marker is "(", switch it to closing marker ")" + if (marker === 0x28) { marker = 0x29; } + + while (pos < max) { + code = str.charCodeAt(pos); + if (code === marker) { + result.pos = pos + 1; + result.lines = lines; + result.str = unescapeAll(str.slice(start + 1, pos)); + result.ok = true; + return result; + } else if (code === 0x0A) { + lines++; + } else if (code === 0x5C /* \ */ && pos + 1 < max) { + pos++; + if (str.charCodeAt(pos) === 0x0A) { + lines++; + } + } + + pos++; + } + + return result; + }; + + },{"../common/utils":4}],9:[function(require,module,exports){ + // Main parser class + + 'use strict'; + + + var utils = require('./common/utils'); + var helpers = require('./helpers'); + var Renderer = require('./renderer'); + var ParserCore = require('./parser_core'); + var ParserBlock = require('./parser_block'); + var ParserInline = require('./parser_inline'); + var LinkifyIt = require('linkify-it'); + var mdurl = require('mdurl'); + var punycode = require('punycode'); + + + var config = { + 'default': require('./presets/default'), + zero: require('./presets/zero'), + commonmark: require('./presets/commonmark') + }; + + //////////////////////////////////////////////////////////////////////////////// + // + // This validator can prohibit more than really needed to prevent XSS. It's a + // tradeoff to keep code simple and to be secure by default. + // + // If you need different setup - override validator method as you wish. Or + // replace it with dummy function and use external sanitizer. + // + + var BAD_PROTO_RE = /^(vbscript|javascript|file|data):/; + var GOOD_DATA_RE = /^data:image\/(gif|png|jpeg|webp);/; + + function validateLink(url) { + // url should be normalized at this point, and existing entities are decoded + var str = url.trim().toLowerCase(); + + return BAD_PROTO_RE.test(str) ? (GOOD_DATA_RE.test(str) ? true : false) : true; + } + + //////////////////////////////////////////////////////////////////////////////// + + + var RECODE_HOSTNAME_FOR = [ 'http:', 'https:', 'mailto:' ]; + + function normalizeLink(url) { + var parsed = mdurl.parse(url, true); + + if (parsed.hostname) { + // Encode hostnames in urls like: + // `http://host/`, `https://host/`, `mailto:user@host`, `//host/` + // + // We don't encode unknown schemas, because it's likely that we encode + // something we shouldn't (e.g. `skype:name` treated as `skype:host`) + // + if (!parsed.protocol || RECODE_HOSTNAME_FOR.indexOf(parsed.protocol) >= 0) { + try { + parsed.hostname = punycode.toASCII(parsed.hostname); + } catch (er) { /**/ } + } + } + + return mdurl.encode(mdurl.format(parsed)); + } + + function normalizeLinkText(url) { + var parsed = mdurl.parse(url, true); + + if (parsed.hostname) { + // Encode hostnames in urls like: + // `http://host/`, `https://host/`, `mailto:user@host`, `//host/` + // + // We don't encode unknown schemas, because it's likely that we encode + // something we shouldn't (e.g. `skype:name` treated as `skype:host`) + // + if (!parsed.protocol || RECODE_HOSTNAME_FOR.indexOf(parsed.protocol) >= 0) { + try { + parsed.hostname = punycode.toUnicode(parsed.hostname); + } catch (er) { /**/ } + } + } + + return mdurl.decode(mdurl.format(parsed)); + } + + + /** + * class MarkdownIt + * + * Main parser/renderer class. + * + * ##### Usage + * + * ```javascript + * // node.js, "classic" way: + * var MarkdownIt = require('markdown-it'), + * md = new MarkdownIt(); + * var result = md.render('# markdown-it rulezz!'); + * + * // node.js, the same, but with sugar: + * var md = require('markdown-it')(); + * var result = md.render('# markdown-it rulezz!'); + * + * // browser without AMD, added to "window" on script load + * // Note, there are no dash. + * var md = window.markdownit(); + * var result = md.render('# markdown-it rulezz!'); + * ``` + * + * Single line rendering, without paragraph wrap: + * + * ```javascript + * var md = require('markdown-it')(); + * var result = md.renderInline('__markdown-it__ rulezz!'); + * ``` + **/ + + /** + * new MarkdownIt([presetName, options]) + * - presetName (String): optional, `commonmark` / `zero` + * - options (Object) + * + * Creates parser instanse with given config. Can be called without `new`. + * + * ##### presetName + * + * MarkdownIt provides named presets as a convenience to quickly + * enable/disable active syntax rules and options for common use cases. + * + * - ["commonmark"](https://github.com/markdown-it/markdown-it/blob/master/lib/presets/commonmark.js) - + * configures parser to strict [CommonMark](http://commonmark.org/) mode. + * - [default](https://github.com/markdown-it/markdown-it/blob/master/lib/presets/default.js) - + * similar to GFM, used when no preset name given. Enables all available rules, + * but still without html, typographer & autolinker. + * - ["zero"](https://github.com/markdown-it/markdown-it/blob/master/lib/presets/zero.js) - + * all rules disabled. Useful to quickly setup your config via `.enable()`. + * For example, when you need only `bold` and `italic` markup and nothing else. + * + * ##### options: + * + * - __html__ - `false`. Set `true` to enable HTML tags in source. Be careful! + * That's not safe! You may need external sanitizer to protect output from XSS. + * It's better to extend features via plugins, instead of enabling HTML. + * - __xhtmlOut__ - `false`. Set `true` to add '/' when closing single tags + * (`
`). This is needed only for full CommonMark compatibility. In real + * world you will need HTML output. + * - __breaks__ - `false`. Set `true` to convert `\n` in paragraphs into `
`. + * - __langPrefix__ - `language-`. CSS language class prefix for fenced blocks. + * Can be useful for external highlighters. + * - __linkify__ - `false`. Set `true` to autoconvert URL-like text to links. + * - __typographer__ - `false`. Set `true` to enable [some language-neutral + * replacement](https://github.com/markdown-it/markdown-it/blob/master/lib/rules_core/replacements.js) + + * quotes beautification (smartquotes). + * - __quotes__ - `“”‘’`, String or Array. Double + single quotes replacement + * pairs, when typographer enabled and smartquotes on. For example, you can + * use `'«»„“'` for Russian, `'„“‚‘'` for German, and + * `['«\xA0', '\xA0»', '‹\xA0', '\xA0›']` for French (including nbsp). + * - __highlight__ - `null`. Highlighter function for fenced code blocks. + * Highlighter `function (str, lang)` should return escaped HTML. It can also + * return empty string if the source was not changed and should be escaped + * externaly. If result starts with `): + * + * ```javascript + * var hljs = require('highlight.js') // https://highlightjs.org/ + * + * // Actual default values + * var md = require('markdown-it')({ + * highlight: function (str, lang) { + * if (lang && hljs.getLanguage(lang)) { + * try { + * return '
' +
+   *                hljs.highlight(lang, str, true).value +
+   *                '
'; + * } catch (__) {} + * } + * + * return '
' + md.utils.escapeHtml(str) + '
'; + * } + * }); + * ``` + * + **/ + function MarkdownIt(presetName, options) { + if (!(this instanceof MarkdownIt)) { + return new MarkdownIt(presetName, options); + } + + if (!options) { + if (!utils.isString(presetName)) { + options = presetName || {}; + presetName = 'default'; + } + } + + /** + * MarkdownIt#inline -> ParserInline + * + * Instance of [[ParserInline]]. You may need it to add new rules when + * writing plugins. For simple rules control use [[MarkdownIt.disable]] and + * [[MarkdownIt.enable]]. + **/ + this.inline = new ParserInline(); + + /** + * MarkdownIt#block -> ParserBlock + * + * Instance of [[ParserBlock]]. You may need it to add new rules when + * writing plugins. For simple rules control use [[MarkdownIt.disable]] and + * [[MarkdownIt.enable]]. + **/ + this.block = new ParserBlock(); + + /** + * MarkdownIt#core -> Core + * + * Instance of [[Core]] chain executor. You may need it to add new rules when + * writing plugins. For simple rules control use [[MarkdownIt.disable]] and + * [[MarkdownIt.enable]]. + **/ + this.core = new ParserCore(); + + /** + * MarkdownIt#renderer -> Renderer + * + * Instance of [[Renderer]]. Use it to modify output look. Or to add rendering + * rules for new token types, generated by plugins. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')(); + * + * function myToken(tokens, idx, options, env, self) { + * //... + * return result; + * }; + * + * md.renderer.rules['my_token'] = myToken + * ``` + * + * See [[Renderer]] docs and [source code](https://github.com/markdown-it/markdown-it/blob/master/lib/renderer.js). + **/ + this.renderer = new Renderer(); + + /** + * MarkdownIt#linkify -> LinkifyIt + * + * [linkify-it](https://github.com/markdown-it/linkify-it) instance. + * Used by [linkify](https://github.com/markdown-it/markdown-it/blob/master/lib/rules_core/linkify.js) + * rule. + **/ + this.linkify = new LinkifyIt(); + + /** + * MarkdownIt#validateLink(url) -> Boolean + * + * Link validation function. CommonMark allows too much in links. By default + * we disable `javascript:`, `vbscript:`, `file:` schemas, and almost all `data:...` schemas + * except some embedded image types. + * + * You can change this behaviour: + * + * ```javascript + * var md = require('markdown-it')(); + * // enable everything + * md.validateLink = function () { return true; } + * ``` + **/ + this.validateLink = validateLink; + + /** + * MarkdownIt#normalizeLink(url) -> String + * + * Function used to encode link url to a machine-readable format, + * which includes url-encoding, punycode, etc. + **/ + this.normalizeLink = normalizeLink; + + /** + * MarkdownIt#normalizeLinkText(url) -> String + * + * Function used to decode link url to a human-readable format` + **/ + this.normalizeLinkText = normalizeLinkText; + + + // Expose utils & helpers for easy acces from plugins + + /** + * MarkdownIt#utils -> utils + * + * Assorted utility functions, useful to write plugins. See details + * [here](https://github.com/markdown-it/markdown-it/blob/master/lib/common/utils.js). + **/ + this.utils = utils; + + /** + * MarkdownIt#helpers -> helpers + * + * Link components parser functions, useful to write plugins. See details + * [here](https://github.com/markdown-it/markdown-it/blob/master/lib/helpers). + **/ + this.helpers = utils.assign({}, helpers); + + + this.options = {}; + this.configure(presetName); + + if (options) { this.set(options); } + } + + + /** chainable + * MarkdownIt.set(options) + * + * Set parser options (in the same format as in constructor). Probably, you + * will never need it, but you can change options after constructor call. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')() + * .set({ html: true, breaks: true }) + * .set({ typographer, true }); + * ``` + * + * __Note:__ To achieve the best possible performance, don't modify a + * `markdown-it` instance options on the fly. If you need multiple configurations + * it's best to create multiple instances and initialize each with separate + * config. + **/ + MarkdownIt.prototype.set = function (options) { + utils.assign(this.options, options); + return this; + }; + + + /** chainable, internal + * MarkdownIt.configure(presets) + * + * Batch load of all options and compenent settings. This is internal method, + * and you probably will not need it. But if you with - see available presets + * and data structure [here](https://github.com/markdown-it/markdown-it/tree/master/lib/presets) + * + * We strongly recommend to use presets instead of direct config loads. That + * will give better compatibility with next versions. + **/ + MarkdownIt.prototype.configure = function (presets) { + var self = this, presetName; + + if (utils.isString(presets)) { + presetName = presets; + presets = config[presetName]; + if (!presets) { throw new Error('Wrong `markdown-it` preset "' + presetName + '", check name'); } + } + + if (!presets) { throw new Error('Wrong `markdown-it` preset, can\'t be empty'); } + + if (presets.options) { self.set(presets.options); } + + if (presets.components) { + Object.keys(presets.components).forEach(function (name) { + if (presets.components[name].rules) { + self[name].ruler.enableOnly(presets.components[name].rules); + } + if (presets.components[name].rules2) { + self[name].ruler2.enableOnly(presets.components[name].rules2); + } + }); + } + return this; + }; + + + /** chainable + * MarkdownIt.enable(list, ignoreInvalid) + * - list (String|Array): rule name or list of rule names to enable + * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. + * + * Enable list or rules. It will automatically find appropriate components, + * containing rules with given names. If rule not found, and `ignoreInvalid` + * not set - throws exception. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')() + * .enable(['sub', 'sup']) + * .disable('smartquotes'); + * ``` + **/ + MarkdownIt.prototype.enable = function (list, ignoreInvalid) { + var result = []; + + if (!Array.isArray(list)) { list = [ list ]; } + + [ 'core', 'block', 'inline' ].forEach(function (chain) { + result = result.concat(this[chain].ruler.enable(list, true)); + }, this); + + result = result.concat(this.inline.ruler2.enable(list, true)); + + var missed = list.filter(function (name) { return result.indexOf(name) < 0; }); + + if (missed.length && !ignoreInvalid) { + throw new Error('MarkdownIt. Failed to enable unknown rule(s): ' + missed); + } + + return this; + }; + + + /** chainable + * MarkdownIt.disable(list, ignoreInvalid) + * - list (String|Array): rule name or list of rule names to disable. + * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. + * + * The same as [[MarkdownIt.enable]], but turn specified rules off. + **/ + MarkdownIt.prototype.disable = function (list, ignoreInvalid) { + var result = []; + + if (!Array.isArray(list)) { list = [ list ]; } + + [ 'core', 'block', 'inline' ].forEach(function (chain) { + result = result.concat(this[chain].ruler.disable(list, true)); + }, this); + + result = result.concat(this.inline.ruler2.disable(list, true)); + + var missed = list.filter(function (name) { return result.indexOf(name) < 0; }); + + if (missed.length && !ignoreInvalid) { + throw new Error('MarkdownIt. Failed to disable unknown rule(s): ' + missed); + } + return this; + }; + + + /** chainable + * MarkdownIt.use(plugin, params) + * + * Load specified plugin with given params into current parser instance. + * It's just a sugar to call `plugin(md, params)` with curring. + * + * ##### Example + * + * ```javascript + * var iterator = require('markdown-it-for-inline'); + * var md = require('markdown-it')() + * .use(iterator, 'foo_replace', 'text', function (tokens, idx) { + * tokens[idx].content = tokens[idx].content.replace(/foo/g, 'bar'); + * }); + * ``` + **/ + MarkdownIt.prototype.use = function (plugin /*, params, ... */) { + var args = [ this ].concat(Array.prototype.slice.call(arguments, 1)); + plugin.apply(plugin, args); + return this; + }; + + + /** internal + * MarkdownIt.parse(src, env) -> Array + * - src (String): source string + * - env (Object): environment sandbox + * + * Parse input string and returns list of block tokens (special token type + * "inline" will contain list of inline tokens). You should not call this + * method directly, until you write custom renderer (for example, to produce + * AST). + * + * `env` is used to pass data between "distributed" rules and return additional + * metadata like reference info, needed for the renderer. It also can be used to + * inject data in specific cases. Usually, you will be ok to pass `{}`, + * and then pass updated object to renderer. + **/ + MarkdownIt.prototype.parse = function (src, env) { + if (typeof src !== 'string') { + throw new Error('Input data should be a String'); + } + + var state = new this.core.State(src, this, env); + + this.core.process(state); + + return state.tokens; + }; + + + /** + * MarkdownIt.render(src [, env]) -> String + * - src (String): source string + * - env (Object): environment sandbox + * + * Render markdown string into html. It does all magic for you :). + * + * `env` can be used to inject additional metadata (`{}` by default). + * But you will not need it with high probability. See also comment + * in [[MarkdownIt.parse]]. + **/ + MarkdownIt.prototype.render = function (src, env) { + env = env || {}; + + return this.renderer.render(this.parse(src, env), this.options, env); + }; + + + /** internal + * MarkdownIt.parseInline(src, env) -> Array + * - src (String): source string + * - env (Object): environment sandbox + * + * The same as [[MarkdownIt.parse]] but skip all block rules. It returns the + * block tokens list with the single `inline` element, containing parsed inline + * tokens in `children` property. Also updates `env` object. + **/ + MarkdownIt.prototype.parseInline = function (src, env) { + var state = new this.core.State(src, this, env); + + state.inlineMode = true; + this.core.process(state); + + return state.tokens; + }; + + + /** + * MarkdownIt.renderInline(src [, env]) -> String + * - src (String): source string + * - env (Object): environment sandbox + * + * Similar to [[MarkdownIt.render]] but for single paragraph content. Result + * will NOT be wrapped into `

` tags. + **/ + MarkdownIt.prototype.renderInline = function (src, env) { + env = env || {}; + + return this.renderer.render(this.parseInline(src, env), this.options, env); + }; + + + module.exports = MarkdownIt; + + },{"./common/utils":4,"./helpers":5,"./parser_block":10,"./parser_core":11,"./parser_inline":12,"./presets/commonmark":13,"./presets/default":14,"./presets/zero":15,"./renderer":16,"linkify-it":53,"mdurl":58,"punycode":60}],10:[function(require,module,exports){ + /** internal + * class ParserBlock + * + * Block-level tokenizer. + **/ + 'use strict'; + + + var Ruler = require('./ruler'); + + + var _rules = [ + // First 2 params - rule name & source. Secondary array - list of rules, + // which can be terminated by this one. + [ 'table', require('./rules_block/table'), [ 'paragraph', 'reference' ] ], + [ 'code', require('./rules_block/code') ], + [ 'fence', require('./rules_block/fence'), [ 'paragraph', 'reference', 'blockquote', 'list' ] ], + [ 'blockquote', require('./rules_block/blockquote'), [ 'paragraph', 'reference', 'blockquote', 'list' ] ], + [ 'hr', require('./rules_block/hr'), [ 'paragraph', 'reference', 'blockquote', 'list' ] ], + [ 'list', require('./rules_block/list'), [ 'paragraph', 'reference', 'blockquote' ] ], + [ 'reference', require('./rules_block/reference') ], + [ 'heading', require('./rules_block/heading'), [ 'paragraph', 'reference', 'blockquote' ] ], + [ 'lheading', require('./rules_block/lheading') ], + [ 'html_block', require('./rules_block/html_block'), [ 'paragraph', 'reference', 'blockquote' ] ], + [ 'paragraph', require('./rules_block/paragraph') ] + ]; + + + /** + * new ParserBlock() + **/ + function ParserBlock() { + /** + * ParserBlock#ruler -> Ruler + * + * [[Ruler]] instance. Keep configuration of block rules. + **/ + this.ruler = new Ruler(); + + for (var i = 0; i < _rules.length; i++) { + this.ruler.push(_rules[i][0], _rules[i][1], { alt: (_rules[i][2] || []).slice() }); + } + } + + + // Generate tokens for input range + // + ParserBlock.prototype.tokenize = function (state, startLine, endLine) { + var ok, i, + rules = this.ruler.getRules(''), + len = rules.length, + line = startLine, + hasEmptyLines = false, + maxNesting = state.md.options.maxNesting; + + while (line < endLine) { + state.line = line = state.skipEmptyLines(line); + if (line >= endLine) { break; } + + // Termination condition for nested calls. + // Nested calls currently used for blockquotes & lists + if (state.sCount[line] < state.blkIndent) { break; } + + // If nesting level exceeded - skip tail to the end. That's not ordinary + // situation and we should not care about content. + if (state.level >= maxNesting) { + state.line = endLine; + break; + } + + // Try all possible rules. + // On success, rule should: + // + // - update `state.line` + // - update `state.tokens` + // - return true + + for (i = 0; i < len; i++) { + ok = rules[i](state, line, endLine, false); + if (ok) { break; } + } + + // set state.tight if we had an empty line before current tag + // i.e. latest empty line should not count + state.tight = !hasEmptyLines; + + // paragraph might "eat" one newline after it in nested lists + if (state.isEmpty(state.line - 1)) { + hasEmptyLines = true; + } + + line = state.line; + + if (line < endLine && state.isEmpty(line)) { + hasEmptyLines = true; + line++; + state.line = line; + } + } + }; + + + /** + * ParserBlock.parse(str, md, env, outTokens) + * + * Process input string and push block tokens into `outTokens` + **/ + ParserBlock.prototype.parse = function (src, md, env, outTokens) { + var state; + + if (!src) { return; } + + state = new this.State(src, md, env, outTokens); + + this.tokenize(state, state.line, state.lineMax); + }; + + + ParserBlock.prototype.State = require('./rules_block/state_block'); + + + module.exports = ParserBlock; + + },{"./ruler":17,"./rules_block/blockquote":18,"./rules_block/code":19,"./rules_block/fence":20,"./rules_block/heading":21,"./rules_block/hr":22,"./rules_block/html_block":23,"./rules_block/lheading":24,"./rules_block/list":25,"./rules_block/paragraph":26,"./rules_block/reference":27,"./rules_block/state_block":28,"./rules_block/table":29}],11:[function(require,module,exports){ + /** internal + * class Core + * + * Top-level rules executor. Glues block/inline parsers and does intermediate + * transformations. + **/ + 'use strict'; + + + var Ruler = require('./ruler'); + + + var _rules = [ + [ 'normalize', require('./rules_core/normalize') ], + [ 'block', require('./rules_core/block') ], + [ 'inline', require('./rules_core/inline') ], + [ 'linkify', require('./rules_core/linkify') ], + [ 'replacements', require('./rules_core/replacements') ], + [ 'smartquotes', require('./rules_core/smartquotes') ] + ]; + + + /** + * new Core() + **/ + function Core() { + /** + * Core#ruler -> Ruler + * + * [[Ruler]] instance. Keep configuration of core rules. + **/ + this.ruler = new Ruler(); + + for (var i = 0; i < _rules.length; i++) { + this.ruler.push(_rules[i][0], _rules[i][1]); + } + } + + + /** + * Core.process(state) + * + * Executes core chain rules. + **/ + Core.prototype.process = function (state) { + var i, l, rules; + + rules = this.ruler.getRules(''); + + for (i = 0, l = rules.length; i < l; i++) { + rules[i](state); + } + }; + + Core.prototype.State = require('./rules_core/state_core'); + + + module.exports = Core; + + },{"./ruler":17,"./rules_core/block":30,"./rules_core/inline":31,"./rules_core/linkify":32,"./rules_core/normalize":33,"./rules_core/replacements":34,"./rules_core/smartquotes":35,"./rules_core/state_core":36}],12:[function(require,module,exports){ + /** internal + * class ParserInline + * + * Tokenizes paragraph content. + **/ + 'use strict'; + + + var Ruler = require('./ruler'); + + + //////////////////////////////////////////////////////////////////////////////// + // Parser rules + + var _rules = [ + [ 'text', require('./rules_inline/text') ], + [ 'newline', require('./rules_inline/newline') ], + [ 'escape', require('./rules_inline/escape') ], + [ 'backticks', require('./rules_inline/backticks') ], + [ 'strikethrough', require('./rules_inline/strikethrough').tokenize ], + [ 'emphasis', require('./rules_inline/emphasis').tokenize ], + [ 'link', require('./rules_inline/link') ], + [ 'image', require('./rules_inline/image') ], + [ 'autolink', require('./rules_inline/autolink') ], + [ 'html_inline', require('./rules_inline/html_inline') ], + [ 'entity', require('./rules_inline/entity') ] + ]; + + var _rules2 = [ + [ 'balance_pairs', require('./rules_inline/balance_pairs') ], + [ 'strikethrough', require('./rules_inline/strikethrough').postProcess ], + [ 'emphasis', require('./rules_inline/emphasis').postProcess ], + [ 'text_collapse', require('./rules_inline/text_collapse') ] + ]; + + + /** + * new ParserInline() + **/ + function ParserInline() { + var i; + + /** + * ParserInline#ruler -> Ruler + * + * [[Ruler]] instance. Keep configuration of inline rules. + **/ + this.ruler = new Ruler(); + + for (i = 0; i < _rules.length; i++) { + this.ruler.push(_rules[i][0], _rules[i][1]); + } + + /** + * ParserInline#ruler2 -> Ruler + * + * [[Ruler]] instance. Second ruler used for post-processing + * (e.g. in emphasis-like rules). + **/ + this.ruler2 = new Ruler(); + + for (i = 0; i < _rules2.length; i++) { + this.ruler2.push(_rules2[i][0], _rules2[i][1]); + } + } + + + // Skip single token by running all rules in validation mode; + // returns `true` if any rule reported success + // + ParserInline.prototype.skipToken = function (state) { + var ok, i, pos = state.pos, + rules = this.ruler.getRules(''), + len = rules.length, + maxNesting = state.md.options.maxNesting, + cache = state.cache; + + + if (typeof cache[pos] !== 'undefined') { + state.pos = cache[pos]; + return; + } + + if (state.level < maxNesting) { + for (i = 0; i < len; i++) { + // Increment state.level and decrement it later to limit recursion. + // It's harmless to do here, because no tokens are created. But ideally, + // we'd need a separate private state variable for this purpose. + // + state.level++; + ok = rules[i](state, true); + state.level--; + + if (ok) { break; } + } + } else { + // Too much nesting, just skip until the end of the paragraph. + // + // NOTE: this will cause links to behave incorrectly in the following case, + // when an amount of `[` is exactly equal to `maxNesting + 1`: + // + // [[[[[[[[[[[[[[[[[[[[[foo]() + // + // TODO: remove this workaround when CM standard will allow nested links + // (we can replace it by preventing links from being parsed in + // validation mode) + // + state.pos = state.posMax; + } + + if (!ok) { state.pos++; } + cache[pos] = state.pos; + }; + + + // Generate tokens for input range + // + ParserInline.prototype.tokenize = function (state) { + var ok, i, + rules = this.ruler.getRules(''), + len = rules.length, + end = state.posMax, + maxNesting = state.md.options.maxNesting; + + while (state.pos < end) { + // Try all possible rules. + // On success, rule should: + // + // - update `state.pos` + // - update `state.tokens` + // - return true + + if (state.level < maxNesting) { + for (i = 0; i < len; i++) { + ok = rules[i](state, false); + if (ok) { break; } + } + } + + if (ok) { + if (state.pos >= end) { break; } + continue; + } + + state.pending += state.src[state.pos++]; + } + + if (state.pending) { + state.pushPending(); + } + }; + + + /** + * ParserInline.parse(str, md, env, outTokens) + * + * Process input string and push inline tokens into `outTokens` + **/ + ParserInline.prototype.parse = function (str, md, env, outTokens) { + var i, rules, len; + var state = new this.State(str, md, env, outTokens); + + this.tokenize(state); + + rules = this.ruler2.getRules(''); + len = rules.length; + + for (i = 0; i < len; i++) { + rules[i](state); + } + }; + + + ParserInline.prototype.State = require('./rules_inline/state_inline'); + + + module.exports = ParserInline; + + },{"./ruler":17,"./rules_inline/autolink":37,"./rules_inline/backticks":38,"./rules_inline/balance_pairs":39,"./rules_inline/emphasis":40,"./rules_inline/entity":41,"./rules_inline/escape":42,"./rules_inline/html_inline":43,"./rules_inline/image":44,"./rules_inline/link":45,"./rules_inline/newline":46,"./rules_inline/state_inline":47,"./rules_inline/strikethrough":48,"./rules_inline/text":49,"./rules_inline/text_collapse":50}],13:[function(require,module,exports){ + // Commonmark default options + + 'use strict'; + + + module.exports = { + options: { + html: true, // Enable HTML tags in source + xhtmlOut: true, // Use '/' to close single tags (
) + breaks: false, // Convert '\n' in paragraphs into
+ langPrefix: 'language-', // CSS language prefix for fenced blocks + linkify: false, // autoconvert URL-like texts to links + + // Enable some language-neutral replacements + quotes beautification + typographer: false, + + // Double + single quotes replacement pairs, when typographer enabled, + // and smartquotes on. Could be either a String or an Array. + // + // For example, you can use '«»„“' for Russian, '„“‚‘' for German, + // and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp). + quotes: '\u201c\u201d\u2018\u2019', /* “”‘’ */ + + // Highlighter function. Should return escaped HTML, + // or '' if the source string is not changed and should be escaped externaly. + // If result starts with ) + breaks: false, // Convert '\n' in paragraphs into
+ langPrefix: 'language-', // CSS language prefix for fenced blocks + linkify: false, // autoconvert URL-like texts to links + + // Enable some language-neutral replacements + quotes beautification + typographer: false, + + // Double + single quotes replacement pairs, when typographer enabled, + // and smartquotes on. Could be either a String or an Array. + // + // For example, you can use '«»„“' for Russian, '„“‚‘' for German, + // and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp). + quotes: '\u201c\u201d\u2018\u2019', /* “”‘’ */ + + // Highlighter function. Should return escaped HTML, + // or '' if the source string is not changed and should be escaped externaly. + // If result starts with ) + breaks: false, // Convert '\n' in paragraphs into
+ langPrefix: 'language-', // CSS language prefix for fenced blocks + linkify: false, // autoconvert URL-like texts to links + + // Enable some language-neutral replacements + quotes beautification + typographer: false, + + // Double + single quotes replacement pairs, when typographer enabled, + // and smartquotes on. Could be either a String or an Array. + // + // For example, you can use '«»„“' for Russian, '„“‚‘' for German, + // and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp). + quotes: '\u201c\u201d\u2018\u2019', /* “”‘’ */ + + // Highlighter function. Should return escaped HTML, + // or '' if the source string is not changed and should be escaped externaly. + // If result starts with ' + + escapeHtml(tokens[idx].content) + + ''; + }; + + + default_rules.code_block = function (tokens, idx, options, env, slf) { + var token = tokens[idx]; + + return '' + + escapeHtml(tokens[idx].content) + + '\n'; + }; + + + default_rules.fence = function (tokens, idx, options, env, slf) { + var token = tokens[idx], + info = token.info ? unescapeAll(token.info).trim() : '', + langName = '', + highlighted, i, tmpAttrs, tmpToken; + + if (info) { + langName = info.split(/\s+/g)[0]; + } + + if (options.highlight) { + highlighted = options.highlight(token.content, langName) || escapeHtml(token.content); + } else { + highlighted = escapeHtml(token.content); + } + + if (highlighted.indexOf('' + + highlighted + + '\n'; + } + + + return '

'
+          + highlighted
+          + '
\n'; + }; + + + default_rules.image = function (tokens, idx, options, env, slf) { + var token = tokens[idx]; + + // "alt" attr MUST be set, even if empty. Because it's mandatory and + // should be placed on proper position for tests. + // + // Replace content with actual value + + token.attrs[token.attrIndex('alt')][1] = + slf.renderInlineAsText(token.children, options, env); + + return slf.renderToken(tokens, idx, options); + }; + + + default_rules.hardbreak = function (tokens, idx, options /*, env */) { + return options.xhtmlOut ? '
\n' : '
\n'; + }; + default_rules.softbreak = function (tokens, idx, options /*, env */) { + return options.breaks ? (options.xhtmlOut ? '
\n' : '
\n') : '\n'; + }; + + + default_rules.text = function (tokens, idx /*, options, env */) { + return escapeHtml(tokens[idx].content); + }; + + + default_rules.html_block = function (tokens, idx /*, options, env */) { + return tokens[idx].content; + }; + default_rules.html_inline = function (tokens, idx /*, options, env */) { + return tokens[idx].content; + }; + + + /** + * new Renderer() + * + * Creates new [[Renderer]] instance and fill [[Renderer#rules]] with defaults. + **/ + function Renderer() { + + /** + * Renderer#rules -> Object + * + * Contains render rules for tokens. Can be updated and extended. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')(); + * + * md.renderer.rules.strong_open = function () { return ''; }; + * md.renderer.rules.strong_close = function () { return ''; }; + * + * var result = md.renderInline(...); + * ``` + * + * Each rule is called as independent static function with fixed signature: + * + * ```javascript + * function my_token_render(tokens, idx, options, env, renderer) { + * // ... + * return renderedHTML; + * } + * ``` + * + * See [source code](https://github.com/markdown-it/markdown-it/blob/master/lib/renderer.js) + * for more details and examples. + **/ + this.rules = assign({}, default_rules); + } + + + /** + * Renderer.renderAttrs(token) -> String + * + * Render token attributes to string. + **/ + Renderer.prototype.renderAttrs = function renderAttrs(token) { + var i, l, result; + + if (!token.attrs) { return ''; } + + result = ''; + + for (i = 0, l = token.attrs.length; i < l; i++) { + result += ' ' + escapeHtml(token.attrs[i][0]) + '="' + escapeHtml(token.attrs[i][1]) + '"'; + } + + return result; + }; + + + /** + * Renderer.renderToken(tokens, idx, options) -> String + * - tokens (Array): list of tokens + * - idx (Numbed): token index to render + * - options (Object): params of parser instance + * + * Default token renderer. Can be overriden by custom function + * in [[Renderer#rules]]. + **/ + Renderer.prototype.renderToken = function renderToken(tokens, idx, options) { + var nextToken, + result = '', + needLf = false, + token = tokens[idx]; + + // Tight list paragraphs + if (token.hidden) { + return ''; + } + + // Insert a newline between hidden paragraph and subsequent opening + // block-level tag. + // + // For example, here we should insert a newline before blockquote: + // - a + // > + // + if (token.block && token.nesting !== -1 && idx && tokens[idx - 1].hidden) { + result += '\n'; + } + + // Add token name, e.g. ``. + // + needLf = false; + } + } + } + } + + result += needLf ? '>\n' : '>'; + + return result; + }; + + + /** + * Renderer.renderInline(tokens, options, env) -> String + * - tokens (Array): list on block tokens to renter + * - options (Object): params of parser instance + * - env (Object): additional data from parsed input (references, for example) + * + * The same as [[Renderer.render]], but for single token of `inline` type. + **/ + Renderer.prototype.renderInline = function (tokens, options, env) { + var type, + result = '', + rules = this.rules; + + for (var i = 0, len = tokens.length; i < len; i++) { + type = tokens[i].type; + + if (typeof rules[type] !== 'undefined') { + result += rules[type](tokens, i, options, env, this); + } else { + result += this.renderToken(tokens, i, options); + } + } + + return result; + }; + + + /** internal + * Renderer.renderInlineAsText(tokens, options, env) -> String + * - tokens (Array): list on block tokens to renter + * - options (Object): params of parser instance + * - env (Object): additional data from parsed input (references, for example) + * + * Special kludge for image `alt` attributes to conform CommonMark spec. + * Don't try to use it! Spec requires to show `alt` content with stripped markup, + * instead of simple escaping. + **/ + Renderer.prototype.renderInlineAsText = function (tokens, options, env) { + var result = ''; + + for (var i = 0, len = tokens.length; i < len; i++) { + if (tokens[i].type === 'text') { + result += tokens[i].content; + } else if (tokens[i].type === 'image') { + result += this.renderInlineAsText(tokens[i].children, options, env); + } + } + + return result; + }; + + + /** + * Renderer.render(tokens, options, env) -> String + * - tokens (Array): list on block tokens to renter + * - options (Object): params of parser instance + * - env (Object): additional data from parsed input (references, for example) + * + * Takes token stream and generates HTML. Probably, you will never need to call + * this method directly. + **/ + Renderer.prototype.render = function (tokens, options, env) { + var i, len, type, + result = '', + rules = this.rules; + + for (i = 0, len = tokens.length; i < len; i++) { + type = tokens[i].type; + + if (type === 'inline') { + result += this.renderInline(tokens[i].children, options, env); + } else if (typeof rules[type] !== 'undefined') { + result += rules[tokens[i].type](tokens, i, options, env, this); + } else { + result += this.renderToken(tokens, i, options, env); + } + } + + return result; + }; + + module.exports = Renderer; + + },{"./common/utils":4}],17:[function(require,module,exports){ + /** + * class Ruler + * + * Helper class, used by [[MarkdownIt#core]], [[MarkdownIt#block]] and + * [[MarkdownIt#inline]] to manage sequences of functions (rules): + * + * - keep rules in defined order + * - assign the name to each rule + * - enable/disable rules + * - add/replace rules + * - allow assign rules to additional named chains (in the same) + * - cacheing lists of active rules + * + * You will not need use this class directly until write plugins. For simple + * rules control use [[MarkdownIt.disable]], [[MarkdownIt.enable]] and + * [[MarkdownIt.use]]. + **/ + 'use strict'; + + + /** + * new Ruler() + **/ + function Ruler() { + // List of added rules. Each element is: + // + // { + // name: XXX, + // enabled: Boolean, + // fn: Function(), + // alt: [ name2, name3 ] + // } + // + this.__rules__ = []; + + // Cached rule chains. + // + // First level - chain name, '' for default. + // Second level - diginal anchor for fast filtering by charcodes. + // + this.__cache__ = null; + } + + //////////////////////////////////////////////////////////////////////////////// + // Helper methods, should not be used directly + + + // Find rule index by name + // + Ruler.prototype.__find__ = function (name) { + for (var i = 0; i < this.__rules__.length; i++) { + if (this.__rules__[i].name === name) { + return i; + } + } + return -1; + }; + + + // Build rules lookup cache + // + Ruler.prototype.__compile__ = function () { + var self = this; + var chains = [ '' ]; + + // collect unique names + self.__rules__.forEach(function (rule) { + if (!rule.enabled) { return; } + + rule.alt.forEach(function (altName) { + if (chains.indexOf(altName) < 0) { + chains.push(altName); + } + }); + }); + + self.__cache__ = {}; + + chains.forEach(function (chain) { + self.__cache__[chain] = []; + self.__rules__.forEach(function (rule) { + if (!rule.enabled) { return; } + + if (chain && rule.alt.indexOf(chain) < 0) { return; } + + self.__cache__[chain].push(rule.fn); + }); + }); + }; + + + /** + * Ruler.at(name, fn [, options]) + * - name (String): rule name to replace. + * - fn (Function): new rule function. + * - options (Object): new rule options (not mandatory). + * + * Replace rule by name with new function & options. Throws error if name not + * found. + * + * ##### Options: + * + * - __alt__ - array with names of "alternate" chains. + * + * ##### Example + * + * Replace existing typographer replacement rule with new one: + * + * ```javascript + * var md = require('markdown-it')(); + * + * md.core.ruler.at('replacements', function replace(state) { + * //... + * }); + * ``` + **/ + Ruler.prototype.at = function (name, fn, options) { + var index = this.__find__(name); + var opt = options || {}; + + if (index === -1) { throw new Error('Parser rule not found: ' + name); } + + this.__rules__[index].fn = fn; + this.__rules__[index].alt = opt.alt || []; + this.__cache__ = null; + }; + + + /** + * Ruler.before(beforeName, ruleName, fn [, options]) + * - beforeName (String): new rule will be added before this one. + * - ruleName (String): name of added rule. + * - fn (Function): rule function. + * - options (Object): rule options (not mandatory). + * + * Add new rule to chain before one with given name. See also + * [[Ruler.after]], [[Ruler.push]]. + * + * ##### Options: + * + * - __alt__ - array with names of "alternate" chains. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')(); + * + * md.block.ruler.before('paragraph', 'my_rule', function replace(state) { + * //... + * }); + * ``` + **/ + Ruler.prototype.before = function (beforeName, ruleName, fn, options) { + var index = this.__find__(beforeName); + var opt = options || {}; + + if (index === -1) { throw new Error('Parser rule not found: ' + beforeName); } + + this.__rules__.splice(index, 0, { + name: ruleName, + enabled: true, + fn: fn, + alt: opt.alt || [] + }); + + this.__cache__ = null; + }; + + + /** + * Ruler.after(afterName, ruleName, fn [, options]) + * - afterName (String): new rule will be added after this one. + * - ruleName (String): name of added rule. + * - fn (Function): rule function. + * - options (Object): rule options (not mandatory). + * + * Add new rule to chain after one with given name. See also + * [[Ruler.before]], [[Ruler.push]]. + * + * ##### Options: + * + * - __alt__ - array with names of "alternate" chains. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')(); + * + * md.inline.ruler.after('text', 'my_rule', function replace(state) { + * //... + * }); + * ``` + **/ + Ruler.prototype.after = function (afterName, ruleName, fn, options) { + var index = this.__find__(afterName); + var opt = options || {}; + + if (index === -1) { throw new Error('Parser rule not found: ' + afterName); } + + this.__rules__.splice(index + 1, 0, { + name: ruleName, + enabled: true, + fn: fn, + alt: opt.alt || [] + }); + + this.__cache__ = null; + }; + + /** + * Ruler.push(ruleName, fn [, options]) + * - ruleName (String): name of added rule. + * - fn (Function): rule function. + * - options (Object): rule options (not mandatory). + * + * Push new rule to the end of chain. See also + * [[Ruler.before]], [[Ruler.after]]. + * + * ##### Options: + * + * - __alt__ - array with names of "alternate" chains. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')(); + * + * md.core.ruler.push('my_rule', function replace(state) { + * //... + * }); + * ``` + **/ + Ruler.prototype.push = function (ruleName, fn, options) { + var opt = options || {}; + + this.__rules__.push({ + name: ruleName, + enabled: true, + fn: fn, + alt: opt.alt || [] + }); + + this.__cache__ = null; + }; + + + /** + * Ruler.enable(list [, ignoreInvalid]) -> Array + * - list (String|Array): list of rule names to enable. + * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. + * + * Enable rules with given names. If any rule name not found - throw Error. + * Errors can be disabled by second param. + * + * Returns list of found rule names (if no exception happened). + * + * See also [[Ruler.disable]], [[Ruler.enableOnly]]. + **/ + Ruler.prototype.enable = function (list, ignoreInvalid) { + if (!Array.isArray(list)) { list = [ list ]; } + + var result = []; + + // Search by name and enable + list.forEach(function (name) { + var idx = this.__find__(name); + + if (idx < 0) { + if (ignoreInvalid) { return; } + throw new Error('Rules manager: invalid rule name ' + name); + } + this.__rules__[idx].enabled = true; + result.push(name); + }, this); + + this.__cache__ = null; + return result; + }; + + + /** + * Ruler.enableOnly(list [, ignoreInvalid]) + * - list (String|Array): list of rule names to enable (whitelist). + * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. + * + * Enable rules with given names, and disable everything else. If any rule name + * not found - throw Error. Errors can be disabled by second param. + * + * See also [[Ruler.disable]], [[Ruler.enable]]. + **/ + Ruler.prototype.enableOnly = function (list, ignoreInvalid) { + if (!Array.isArray(list)) { list = [ list ]; } + + this.__rules__.forEach(function (rule) { rule.enabled = false; }); + + this.enable(list, ignoreInvalid); + }; + + + /** + * Ruler.disable(list [, ignoreInvalid]) -> Array + * - list (String|Array): list of rule names to disable. + * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. + * + * Disable rules with given names. If any rule name not found - throw Error. + * Errors can be disabled by second param. + * + * Returns list of found rule names (if no exception happened). + * + * See also [[Ruler.enable]], [[Ruler.enableOnly]]. + **/ + Ruler.prototype.disable = function (list, ignoreInvalid) { + if (!Array.isArray(list)) { list = [ list ]; } + + var result = []; + + // Search by name and disable + list.forEach(function (name) { + var idx = this.__find__(name); + + if (idx < 0) { + if (ignoreInvalid) { return; } + throw new Error('Rules manager: invalid rule name ' + name); + } + this.__rules__[idx].enabled = false; + result.push(name); + }, this); + + this.__cache__ = null; + return result; + }; + + + /** + * Ruler.getRules(chainName) -> Array + * + * Return array of active functions (rules) for given chain name. It analyzes + * rules configuration, compiles caches if not exists and returns result. + * + * Default chain name is `''` (empty string). It can't be skipped. That's + * done intentionally, to keep signature monomorphic for high speed. + **/ + Ruler.prototype.getRules = function (chainName) { + if (this.__cache__ === null) { + this.__compile__(); + } + + // Chain can be empty, if rules disabled. But we still have to return Array. + return this.__cache__[chainName] || []; + }; + + module.exports = Ruler; + + },{}],18:[function(require,module,exports){ + // Block quotes + + 'use strict'; + + var isSpace = require('../common/utils').isSpace; + + + module.exports = function blockquote(state, startLine, endLine, silent) { + var adjustTab, + ch, + i, + initial, + l, + lastLineEmpty, + lines, + nextLine, + offset, + oldBMarks, + oldBSCount, + oldIndent, + oldParentType, + oldSCount, + oldTShift, + spaceAfterMarker, + terminate, + terminatorRules, + token, + wasOutdented, + oldLineMax = state.lineMax, + pos = state.bMarks[startLine] + state.tShift[startLine], + max = state.eMarks[startLine]; + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + + // check the block quote marker + if (state.src.charCodeAt(pos++) !== 0x3E/* > */) { return false; } + + // we know that it's going to be a valid blockquote, + // so no point trying to find the end of it in silent mode + if (silent) { return true; } + + // skip spaces after ">" and re-calculate offset + initial = offset = state.sCount[startLine] + pos - (state.bMarks[startLine] + state.tShift[startLine]); + + // skip one optional space after '>' + if (state.src.charCodeAt(pos) === 0x20 /* space */) { + // ' > test ' + // ^ -- position start of line here: + pos++; + initial++; + offset++; + adjustTab = false; + spaceAfterMarker = true; + } else if (state.src.charCodeAt(pos) === 0x09 /* tab */) { + spaceAfterMarker = true; + + if ((state.bsCount[startLine] + offset) % 4 === 3) { + // ' >\t test ' + // ^ -- position start of line here (tab has width===1) + pos++; + initial++; + offset++; + adjustTab = false; + } else { + // ' >\t test ' + // ^ -- position start of line here + shift bsCount slightly + // to make extra space appear + adjustTab = true; + } + } else { + spaceAfterMarker = false; + } + + oldBMarks = [ state.bMarks[startLine] ]; + state.bMarks[startLine] = pos; + + while (pos < max) { + ch = state.src.charCodeAt(pos); + + if (isSpace(ch)) { + if (ch === 0x09) { + offset += 4 - (offset + state.bsCount[startLine] + (adjustTab ? 1 : 0)) % 4; + } else { + offset++; + } + } else { + break; + } + + pos++; + } + + oldBSCount = [ state.bsCount[startLine] ]; + state.bsCount[startLine] = state.sCount[startLine] + 1 + (spaceAfterMarker ? 1 : 0); + + lastLineEmpty = pos >= max; + + oldSCount = [ state.sCount[startLine] ]; + state.sCount[startLine] = offset - initial; + + oldTShift = [ state.tShift[startLine] ]; + state.tShift[startLine] = pos - state.bMarks[startLine]; + + terminatorRules = state.md.block.ruler.getRules('blockquote'); + + oldParentType = state.parentType; + state.parentType = 'blockquote'; + wasOutdented = false; + + // Search the end of the block + // + // Block ends with either: + // 1. an empty line outside: + // ``` + // > test + // + // ``` + // 2. an empty line inside: + // ``` + // > + // test + // ``` + // 3. another tag: + // ``` + // > test + // - - - + // ``` + for (nextLine = startLine + 1; nextLine < endLine; nextLine++) { + // check if it's outdented, i.e. it's inside list item and indented + // less than said list item: + // + // ``` + // 1. anything + // > current blockquote + // 2. checking this line + // ``` + if (state.sCount[nextLine] < state.blkIndent) wasOutdented = true; + + pos = state.bMarks[nextLine] + state.tShift[nextLine]; + max = state.eMarks[nextLine]; + + if (pos >= max) { + // Case 1: line is not inside the blockquote, and this line is empty. + break; + } + + if (state.src.charCodeAt(pos++) === 0x3E/* > */ && !wasOutdented) { + // This line is inside the blockquote. + + // skip spaces after ">" and re-calculate offset + initial = offset = state.sCount[nextLine] + pos - (state.bMarks[nextLine] + state.tShift[nextLine]); + + // skip one optional space after '>' + if (state.src.charCodeAt(pos) === 0x20 /* space */) { + // ' > test ' + // ^ -- position start of line here: + pos++; + initial++; + offset++; + adjustTab = false; + spaceAfterMarker = true; + } else if (state.src.charCodeAt(pos) === 0x09 /* tab */) { + spaceAfterMarker = true; + + if ((state.bsCount[nextLine] + offset) % 4 === 3) { + // ' >\t test ' + // ^ -- position start of line here (tab has width===1) + pos++; + initial++; + offset++; + adjustTab = false; + } else { + // ' >\t test ' + // ^ -- position start of line here + shift bsCount slightly + // to make extra space appear + adjustTab = true; + } + } else { + spaceAfterMarker = false; + } + + oldBMarks.push(state.bMarks[nextLine]); + state.bMarks[nextLine] = pos; + + while (pos < max) { + ch = state.src.charCodeAt(pos); + + if (isSpace(ch)) { + if (ch === 0x09) { + offset += 4 - (offset + state.bsCount[nextLine] + (adjustTab ? 1 : 0)) % 4; + } else { + offset++; + } + } else { + break; + } + + pos++; + } + + lastLineEmpty = pos >= max; + + oldBSCount.push(state.bsCount[nextLine]); + state.bsCount[nextLine] = state.sCount[nextLine] + 1 + (spaceAfterMarker ? 1 : 0); + + oldSCount.push(state.sCount[nextLine]); + state.sCount[nextLine] = offset - initial; + + oldTShift.push(state.tShift[nextLine]); + state.tShift[nextLine] = pos - state.bMarks[nextLine]; + continue; + } + + // Case 2: line is not inside the blockquote, and the last line was empty. + if (lastLineEmpty) { break; } + + // Case 3: another tag found. + terminate = false; + for (i = 0, l = terminatorRules.length; i < l; i++) { + if (terminatorRules[i](state, nextLine, endLine, true)) { + terminate = true; + break; + } + } + + if (terminate) { + // Quirk to enforce "hard termination mode" for paragraphs; + // normally if you call `tokenize(state, startLine, nextLine)`, + // paragraphs will look below nextLine for paragraph continuation, + // but if blockquote is terminated by another tag, they shouldn't + state.lineMax = nextLine; + + if (state.blkIndent !== 0) { + // state.blkIndent was non-zero, we now set it to zero, + // so we need to re-calculate all offsets to appear as + // if indent wasn't changed + oldBMarks.push(state.bMarks[nextLine]); + oldBSCount.push(state.bsCount[nextLine]); + oldTShift.push(state.tShift[nextLine]); + oldSCount.push(state.sCount[nextLine]); + state.sCount[nextLine] -= state.blkIndent; + } + + break; + } + + oldBMarks.push(state.bMarks[nextLine]); + oldBSCount.push(state.bsCount[nextLine]); + oldTShift.push(state.tShift[nextLine]); + oldSCount.push(state.sCount[nextLine]); + + // A negative indentation means that this is a paragraph continuation + // + state.sCount[nextLine] = -1; + } + + oldIndent = state.blkIndent; + state.blkIndent = 0; + + token = state.push('blockquote_open', 'blockquote', 1); + token.markup = '>'; + token.map = lines = [ startLine, 0 ]; + + state.md.block.tokenize(state, startLine, nextLine); + + token = state.push('blockquote_close', 'blockquote', -1); + token.markup = '>'; + + state.lineMax = oldLineMax; + state.parentType = oldParentType; + lines[1] = state.line; + + // Restore original tShift; this might not be necessary since the parser + // has already been here, but just to make sure we can do that. + for (i = 0; i < oldTShift.length; i++) { + state.bMarks[i + startLine] = oldBMarks[i]; + state.tShift[i + startLine] = oldTShift[i]; + state.sCount[i + startLine] = oldSCount[i]; + state.bsCount[i + startLine] = oldBSCount[i]; + } + state.blkIndent = oldIndent; + + return true; + }; + + },{"../common/utils":4}],19:[function(require,module,exports){ + // Code block (4 spaces padded) + + 'use strict'; + + + module.exports = function code(state, startLine, endLine/*, silent*/) { + var nextLine, last, token; + + if (state.sCount[startLine] - state.blkIndent < 4) { return false; } + + last = nextLine = startLine + 1; + + while (nextLine < endLine) { + if (state.isEmpty(nextLine)) { + nextLine++; + continue; + } + + if (state.sCount[nextLine] - state.blkIndent >= 4) { + nextLine++; + last = nextLine; + continue; + } + break; + } + + state.line = last; + + token = state.push('code_block', 'code', 0); + token.content = state.getLines(startLine, last, 4 + state.blkIndent, true); + token.map = [ startLine, state.line ]; + + return true; + }; + + },{}],20:[function(require,module,exports){ + // fences (``` lang, ~~~ lang) + + 'use strict'; + + + module.exports = function fence(state, startLine, endLine, silent) { + var marker, len, params, nextLine, mem, token, markup, + haveEndMarker = false, + pos = state.bMarks[startLine] + state.tShift[startLine], + max = state.eMarks[startLine]; + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + + if (pos + 3 > max) { return false; } + + marker = state.src.charCodeAt(pos); + + if (marker !== 0x7E/* ~ */ && marker !== 0x60 /* ` */) { + return false; + } + + // scan marker length + mem = pos; + pos = state.skipChars(pos, marker); + + len = pos - mem; + + if (len < 3) { return false; } + + markup = state.src.slice(mem, pos); + params = state.src.slice(pos, max); + + if (marker === 0x60 /* ` */) { + if (params.indexOf(String.fromCharCode(marker)) >= 0) { + return false; + } + } + + // Since start is found, we can report success here in validation mode + if (silent) { return true; } + + // search end of block + nextLine = startLine; + + for (;;) { + nextLine++; + if (nextLine >= endLine) { + // unclosed block should be autoclosed by end of document. + // also block seems to be autoclosed by end of parent + break; + } + + pos = mem = state.bMarks[nextLine] + state.tShift[nextLine]; + max = state.eMarks[nextLine]; + + if (pos < max && state.sCount[nextLine] < state.blkIndent) { + // non-empty line with negative indent should stop the list: + // - ``` + // test + break; + } + + if (state.src.charCodeAt(pos) !== marker) { continue; } + + if (state.sCount[nextLine] - state.blkIndent >= 4) { + // closing fence should be indented less than 4 spaces + continue; + } + + pos = state.skipChars(pos, marker); + + // closing code fence must be at least as long as the opening one + if (pos - mem < len) { continue; } + + // make sure tail has spaces only + pos = state.skipSpaces(pos); + + if (pos < max) { continue; } + + haveEndMarker = true; + // found! + break; + } + + // If a fence has heading spaces, they should be removed from its inner block + len = state.sCount[startLine]; + + state.line = nextLine + (haveEndMarker ? 1 : 0); + + token = state.push('fence', 'code', 0); + token.info = params; + token.content = state.getLines(startLine + 1, nextLine, len, true); + token.markup = markup; + token.map = [ startLine, state.line ]; + + return true; + }; + + },{}],21:[function(require,module,exports){ + // heading (#, ##, ...) + + 'use strict'; + + var isSpace = require('../common/utils').isSpace; + + + module.exports = function heading(state, startLine, endLine, silent) { + var ch, level, tmp, token, + pos = state.bMarks[startLine] + state.tShift[startLine], + max = state.eMarks[startLine]; + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + + ch = state.src.charCodeAt(pos); + + if (ch !== 0x23/* # */ || pos >= max) { return false; } + + // count heading level + level = 1; + ch = state.src.charCodeAt(++pos); + while (ch === 0x23/* # */ && pos < max && level <= 6) { + level++; + ch = state.src.charCodeAt(++pos); + } + + if (level > 6 || (pos < max && !isSpace(ch))) { return false; } + + if (silent) { return true; } + + // Let's cut tails like ' ### ' from the end of string + + max = state.skipSpacesBack(max, pos); + tmp = state.skipCharsBack(max, 0x23, pos); // # + if (tmp > pos && isSpace(state.src.charCodeAt(tmp - 1))) { + max = tmp; + } + + state.line = startLine + 1; + + token = state.push('heading_open', 'h' + String(level), 1); + token.markup = '########'.slice(0, level); + token.map = [ startLine, state.line ]; + + token = state.push('inline', '', 0); + token.content = state.src.slice(pos, max).trim(); + token.map = [ startLine, state.line ]; + token.children = []; + + token = state.push('heading_close', 'h' + String(level), -1); + token.markup = '########'.slice(0, level); + + return true; + }; + + },{"../common/utils":4}],22:[function(require,module,exports){ + // Horizontal rule + + 'use strict'; + + var isSpace = require('../common/utils').isSpace; + + + module.exports = function hr(state, startLine, endLine, silent) { + var marker, cnt, ch, token, + pos = state.bMarks[startLine] + state.tShift[startLine], + max = state.eMarks[startLine]; + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + + marker = state.src.charCodeAt(pos++); + + // Check hr marker + if (marker !== 0x2A/* * */ && + marker !== 0x2D/* - */ && + marker !== 0x5F/* _ */) { + return false; + } + + // markers can be mixed with spaces, but there should be at least 3 of them + + cnt = 1; + while (pos < max) { + ch = state.src.charCodeAt(pos++); + if (ch !== marker && !isSpace(ch)) { return false; } + if (ch === marker) { cnt++; } + } + + if (cnt < 3) { return false; } + + if (silent) { return true; } + + state.line = startLine + 1; + + token = state.push('hr', 'hr', 0); + token.map = [ startLine, state.line ]; + token.markup = Array(cnt + 1).join(String.fromCharCode(marker)); + + return true; + }; + + },{"../common/utils":4}],23:[function(require,module,exports){ + // HTML block + + 'use strict'; + + + var block_names = require('../common/html_blocks'); + var HTML_OPEN_CLOSE_TAG_RE = require('../common/html_re').HTML_OPEN_CLOSE_TAG_RE; + + // An array of opening and corresponding closing sequences for html tags, + // last argument defines whether it can terminate a paragraph or not + // + var HTML_SEQUENCES = [ + [ /^<(script|pre|style)(?=(\s|>|$))/i, /<\/(script|pre|style)>/i, true ], + [ /^/, true ], + [ /^<\?/, /\?>/, true ], + [ /^/, true ], + [ /^/, true ], + [ new RegExp('^|$))', 'i'), /^$/, true ], + [ new RegExp(HTML_OPEN_CLOSE_TAG_RE.source + '\\s*$'), /^$/, false ] + ]; + + + module.exports = function html_block(state, startLine, endLine, silent) { + var i, nextLine, token, lineText, + pos = state.bMarks[startLine] + state.tShift[startLine], + max = state.eMarks[startLine]; + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + + if (!state.md.options.html) { return false; } + + if (state.src.charCodeAt(pos) !== 0x3C/* < */) { return false; } + + lineText = state.src.slice(pos, max); + + for (i = 0; i < HTML_SEQUENCES.length; i++) { + if (HTML_SEQUENCES[i][0].test(lineText)) { break; } + } + + if (i === HTML_SEQUENCES.length) { return false; } + + if (silent) { + // true if this sequence can be a terminator, false otherwise + return HTML_SEQUENCES[i][2]; + } + + nextLine = startLine + 1; + + // If we are here - we detected HTML block. + // Let's roll down till block end. + if (!HTML_SEQUENCES[i][1].test(lineText)) { + for (; nextLine < endLine; nextLine++) { + if (state.sCount[nextLine] < state.blkIndent) { break; } + + pos = state.bMarks[nextLine] + state.tShift[nextLine]; + max = state.eMarks[nextLine]; + lineText = state.src.slice(pos, max); + + if (HTML_SEQUENCES[i][1].test(lineText)) { + if (lineText.length !== 0) { nextLine++; } + break; + } + } + } + + state.line = nextLine; + + token = state.push('html_block', '', 0); + token.map = [ startLine, nextLine ]; + token.content = state.getLines(startLine, nextLine, state.blkIndent, true); + + return true; + }; + + },{"../common/html_blocks":2,"../common/html_re":3}],24:[function(require,module,exports){ + // lheading (---, ===) + + 'use strict'; + + + module.exports = function lheading(state, startLine, endLine/*, silent*/) { + var content, terminate, i, l, token, pos, max, level, marker, + nextLine = startLine + 1, oldParentType, + terminatorRules = state.md.block.ruler.getRules('paragraph'); + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + + oldParentType = state.parentType; + state.parentType = 'paragraph'; // use paragraph to match terminatorRules + + // jump line-by-line until empty one or EOF + for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) { + // this would be a code block normally, but after paragraph + // it's considered a lazy continuation regardless of what's there + if (state.sCount[nextLine] - state.blkIndent > 3) { continue; } + + // + // Check for underline in setext header + // + if (state.sCount[nextLine] >= state.blkIndent) { + pos = state.bMarks[nextLine] + state.tShift[nextLine]; + max = state.eMarks[nextLine]; + + if (pos < max) { + marker = state.src.charCodeAt(pos); + + if (marker === 0x2D/* - */ || marker === 0x3D/* = */) { + pos = state.skipChars(pos, marker); + pos = state.skipSpaces(pos); + + if (pos >= max) { + level = (marker === 0x3D/* = */ ? 1 : 2); + break; + } + } + } + } + + // quirk for blockquotes, this line should already be checked by that rule + if (state.sCount[nextLine] < 0) { continue; } + + // Some tags can terminate paragraph without empty line. + terminate = false; + for (i = 0, l = terminatorRules.length; i < l; i++) { + if (terminatorRules[i](state, nextLine, endLine, true)) { + terminate = true; + break; + } + } + if (terminate) { break; } + } + + if (!level) { + // Didn't find valid underline + return false; + } + + content = state.getLines(startLine, nextLine, state.blkIndent, false).trim(); + + state.line = nextLine + 1; + + token = state.push('heading_open', 'h' + String(level), 1); + token.markup = String.fromCharCode(marker); + token.map = [ startLine, state.line ]; + + token = state.push('inline', '', 0); + token.content = content; + token.map = [ startLine, state.line - 1 ]; + token.children = []; + + token = state.push('heading_close', 'h' + String(level), -1); + token.markup = String.fromCharCode(marker); + + state.parentType = oldParentType; + + return true; + }; + + },{}],25:[function(require,module,exports){ + // Lists + + 'use strict'; + + var isSpace = require('../common/utils').isSpace; + + + // Search `[-+*][\n ]`, returns next pos after marker on success + // or -1 on fail. + function skipBulletListMarker(state, startLine) { + var marker, pos, max, ch; + + pos = state.bMarks[startLine] + state.tShift[startLine]; + max = state.eMarks[startLine]; + + marker = state.src.charCodeAt(pos++); + // Check bullet + if (marker !== 0x2A/* * */ && + marker !== 0x2D/* - */ && + marker !== 0x2B/* + */) { + return -1; + } + + if (pos < max) { + ch = state.src.charCodeAt(pos); + + if (!isSpace(ch)) { + // " -test " - is not a list item + return -1; + } + } + + return pos; + } + + // Search `\d+[.)][\n ]`, returns next pos after marker on success + // or -1 on fail. + function skipOrderedListMarker(state, startLine) { + var ch, + start = state.bMarks[startLine] + state.tShift[startLine], + pos = start, + max = state.eMarks[startLine]; + + // List marker should have at least 2 chars (digit + dot) + if (pos + 1 >= max) { return -1; } + + ch = state.src.charCodeAt(pos++); + + if (ch < 0x30/* 0 */ || ch > 0x39/* 9 */) { return -1; } + + for (;;) { + // EOL -> fail + if (pos >= max) { return -1; } + + ch = state.src.charCodeAt(pos++); + + if (ch >= 0x30/* 0 */ && ch <= 0x39/* 9 */) { + + // List marker should have no more than 9 digits + // (prevents integer overflow in browsers) + if (pos - start >= 10) { return -1; } + + continue; + } + + // found valid marker + if (ch === 0x29/* ) */ || ch === 0x2e/* . */) { + break; + } + + return -1; + } + + + if (pos < max) { + ch = state.src.charCodeAt(pos); + + if (!isSpace(ch)) { + // " 1.test " - is not a list item + return -1; + } + } + return pos; + } + + function markTightParagraphs(state, idx) { + var i, l, + level = state.level + 2; + + for (i = idx + 2, l = state.tokens.length - 2; i < l; i++) { + if (state.tokens[i].level === level && state.tokens[i].type === 'paragraph_open') { + state.tokens[i + 2].hidden = true; + state.tokens[i].hidden = true; + i += 2; + } + } + } + + + module.exports = function list(state, startLine, endLine, silent) { + var ch, + contentStart, + i, + indent, + indentAfterMarker, + initial, + isOrdered, + itemLines, + l, + listLines, + listTokIdx, + markerCharCode, + markerValue, + max, + nextLine, + offset, + oldListIndent, + oldParentType, + oldSCount, + oldTShift, + oldTight, + pos, + posAfterMarker, + prevEmptyEnd, + start, + terminate, + terminatorRules, + token, + isTerminatingParagraph = false, + tight = true; + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + + // Special case: + // - item 1 + // - item 2 + // - item 3 + // - item 4 + // - this one is a paragraph continuation + if (state.listIndent >= 0 && + state.sCount[startLine] - state.listIndent >= 4 && + state.sCount[startLine] < state.blkIndent) { + return false; + } + + // limit conditions when list can interrupt + // a paragraph (validation mode only) + if (silent && state.parentType === 'paragraph') { + // Next list item should still terminate previous list item; + // + // This code can fail if plugins use blkIndent as well as lists, + // but I hope the spec gets fixed long before that happens. + // + if (state.tShift[startLine] >= state.blkIndent) { + isTerminatingParagraph = true; + } + } + + // Detect list type and position after marker + if ((posAfterMarker = skipOrderedListMarker(state, startLine)) >= 0) { + isOrdered = true; + start = state.bMarks[startLine] + state.tShift[startLine]; + markerValue = Number(state.src.substr(start, posAfterMarker - start - 1)); + + // If we're starting a new ordered list right after + // a paragraph, it should start with 1. + if (isTerminatingParagraph && markerValue !== 1) return false; + + } else if ((posAfterMarker = skipBulletListMarker(state, startLine)) >= 0) { + isOrdered = false; + + } else { + return false; + } + + // If we're starting a new unordered list right after + // a paragraph, first line should not be empty. + if (isTerminatingParagraph) { + if (state.skipSpaces(posAfterMarker) >= state.eMarks[startLine]) return false; + } + + // We should terminate list on style change. Remember first one to compare. + markerCharCode = state.src.charCodeAt(posAfterMarker - 1); + + // For validation mode we can terminate immediately + if (silent) { return true; } + + // Start list + listTokIdx = state.tokens.length; + + if (isOrdered) { + token = state.push('ordered_list_open', 'ol', 1); + if (markerValue !== 1) { + token.attrs = [ [ 'start', markerValue ] ]; + } + + } else { + token = state.push('bullet_list_open', 'ul', 1); + } + + token.map = listLines = [ startLine, 0 ]; + token.markup = String.fromCharCode(markerCharCode); + + // + // Iterate list items + // + + nextLine = startLine; + prevEmptyEnd = false; + terminatorRules = state.md.block.ruler.getRules('list'); + + oldParentType = state.parentType; + state.parentType = 'list'; + + while (nextLine < endLine) { + pos = posAfterMarker; + max = state.eMarks[nextLine]; + + initial = offset = state.sCount[nextLine] + posAfterMarker - (state.bMarks[startLine] + state.tShift[startLine]); + + while (pos < max) { + ch = state.src.charCodeAt(pos); + + if (ch === 0x09) { + offset += 4 - (offset + state.bsCount[nextLine]) % 4; + } else if (ch === 0x20) { + offset++; + } else { + break; + } + + pos++; + } + + contentStart = pos; + + if (contentStart >= max) { + // trimming space in "- \n 3" case, indent is 1 here + indentAfterMarker = 1; + } else { + indentAfterMarker = offset - initial; + } + + // If we have more than 4 spaces, the indent is 1 + // (the rest is just indented code block) + if (indentAfterMarker > 4) { indentAfterMarker = 1; } + + // " - test" + // ^^^^^ - calculating total length of this thing + indent = initial + indentAfterMarker; + + // Run subparser & write tokens + token = state.push('list_item_open', 'li', 1); + token.markup = String.fromCharCode(markerCharCode); + token.map = itemLines = [ startLine, 0 ]; + + // change current state, then restore it after parser subcall + oldTight = state.tight; + oldTShift = state.tShift[startLine]; + oldSCount = state.sCount[startLine]; + + // - example list + // ^ listIndent position will be here + // ^ blkIndent position will be here + // + oldListIndent = state.listIndent; + state.listIndent = state.blkIndent; + state.blkIndent = indent; + + state.tight = true; + state.tShift[startLine] = contentStart - state.bMarks[startLine]; + state.sCount[startLine] = offset; + + if (contentStart >= max && state.isEmpty(startLine + 1)) { + // workaround for this case + // (list item is empty, list terminates before "foo"): + // ~~~~~~~~ + // - + // + // foo + // ~~~~~~~~ + state.line = Math.min(state.line + 2, endLine); + } else { + state.md.block.tokenize(state, startLine, endLine, true); + } + + // If any of list item is tight, mark list as tight + if (!state.tight || prevEmptyEnd) { + tight = false; + } + // Item become loose if finish with empty line, + // but we should filter last element, because it means list finish + prevEmptyEnd = (state.line - startLine) > 1 && state.isEmpty(state.line - 1); + + state.blkIndent = state.listIndent; + state.listIndent = oldListIndent; + state.tShift[startLine] = oldTShift; + state.sCount[startLine] = oldSCount; + state.tight = oldTight; + + token = state.push('list_item_close', 'li', -1); + token.markup = String.fromCharCode(markerCharCode); + + nextLine = startLine = state.line; + itemLines[1] = nextLine; + contentStart = state.bMarks[startLine]; + + if (nextLine >= endLine) { break; } + + // + // Try to check if list is terminated or continued. + // + if (state.sCount[nextLine] < state.blkIndent) { break; } + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { break; } + + // fail if terminating block found + terminate = false; + for (i = 0, l = terminatorRules.length; i < l; i++) { + if (terminatorRules[i](state, nextLine, endLine, true)) { + terminate = true; + break; + } + } + if (terminate) { break; } + + // fail if list has another type + if (isOrdered) { + posAfterMarker = skipOrderedListMarker(state, nextLine); + if (posAfterMarker < 0) { break; } + } else { + posAfterMarker = skipBulletListMarker(state, nextLine); + if (posAfterMarker < 0) { break; } + } + + if (markerCharCode !== state.src.charCodeAt(posAfterMarker - 1)) { break; } + } + + // Finalize list + if (isOrdered) { + token = state.push('ordered_list_close', 'ol', -1); + } else { + token = state.push('bullet_list_close', 'ul', -1); + } + token.markup = String.fromCharCode(markerCharCode); + + listLines[1] = nextLine; + state.line = nextLine; + + state.parentType = oldParentType; + + // mark paragraphs tight if needed + if (tight) { + markTightParagraphs(state, listTokIdx); + } + + return true; + }; + + },{"../common/utils":4}],26:[function(require,module,exports){ + // Paragraph + + 'use strict'; + + + module.exports = function paragraph(state, startLine/*, endLine*/) { + var content, terminate, i, l, token, oldParentType, + nextLine = startLine + 1, + terminatorRules = state.md.block.ruler.getRules('paragraph'), + endLine = state.lineMax; + + oldParentType = state.parentType; + state.parentType = 'paragraph'; + + // jump line-by-line until empty one or EOF + for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) { + // this would be a code block normally, but after paragraph + // it's considered a lazy continuation regardless of what's there + if (state.sCount[nextLine] - state.blkIndent > 3) { continue; } + + // quirk for blockquotes, this line should already be checked by that rule + if (state.sCount[nextLine] < 0) { continue; } + + // Some tags can terminate paragraph without empty line. + terminate = false; + for (i = 0, l = terminatorRules.length; i < l; i++) { + if (terminatorRules[i](state, nextLine, endLine, true)) { + terminate = true; + break; + } + } + if (terminate) { break; } + } + + content = state.getLines(startLine, nextLine, state.blkIndent, false).trim(); + + state.line = nextLine; + + token = state.push('paragraph_open', 'p', 1); + token.map = [ startLine, state.line ]; + + token = state.push('inline', '', 0); + token.content = content; + token.map = [ startLine, state.line ]; + token.children = []; + + token = state.push('paragraph_close', 'p', -1); + + state.parentType = oldParentType; + + return true; + }; + + },{}],27:[function(require,module,exports){ + 'use strict'; + + + var normalizeReference = require('../common/utils').normalizeReference; + var isSpace = require('../common/utils').isSpace; + + + module.exports = function reference(state, startLine, _endLine, silent) { + var ch, + destEndPos, + destEndLineNo, + endLine, + href, + i, + l, + label, + labelEnd, + oldParentType, + res, + start, + str, + terminate, + terminatorRules, + title, + lines = 0, + pos = state.bMarks[startLine] + state.tShift[startLine], + max = state.eMarks[startLine], + nextLine = startLine + 1; + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + + if (state.src.charCodeAt(pos) !== 0x5B/* [ */) { return false; } + + // Simple check to quickly interrupt scan on [link](url) at the start of line. + // Can be useful on practice: https://github.com/markdown-it/markdown-it/issues/54 + while (++pos < max) { + if (state.src.charCodeAt(pos) === 0x5D /* ] */ && + state.src.charCodeAt(pos - 1) !== 0x5C/* \ */) { + if (pos + 1 === max) { return false; } + if (state.src.charCodeAt(pos + 1) !== 0x3A/* : */) { return false; } + break; + } + } + + endLine = state.lineMax; + + // jump line-by-line until empty one or EOF + terminatorRules = state.md.block.ruler.getRules('reference'); + + oldParentType = state.parentType; + state.parentType = 'reference'; + + for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) { + // this would be a code block normally, but after paragraph + // it's considered a lazy continuation regardless of what's there + if (state.sCount[nextLine] - state.blkIndent > 3) { continue; } + + // quirk for blockquotes, this line should already be checked by that rule + if (state.sCount[nextLine] < 0) { continue; } + + // Some tags can terminate paragraph without empty line. + terminate = false; + for (i = 0, l = terminatorRules.length; i < l; i++) { + if (terminatorRules[i](state, nextLine, endLine, true)) { + terminate = true; + break; + } + } + if (terminate) { break; } + } + + str = state.getLines(startLine, nextLine, state.blkIndent, false).trim(); + max = str.length; + + for (pos = 1; pos < max; pos++) { + ch = str.charCodeAt(pos); + if (ch === 0x5B /* [ */) { + return false; + } else if (ch === 0x5D /* ] */) { + labelEnd = pos; + break; + } else if (ch === 0x0A /* \n */) { + lines++; + } else if (ch === 0x5C /* \ */) { + pos++; + if (pos < max && str.charCodeAt(pos) === 0x0A) { + lines++; + } + } + } + + if (labelEnd < 0 || str.charCodeAt(labelEnd + 1) !== 0x3A/* : */) { return false; } + + // [label]: destination 'title' + // ^^^ skip optional whitespace here + for (pos = labelEnd + 2; pos < max; pos++) { + ch = str.charCodeAt(pos); + if (ch === 0x0A) { + lines++; + } else if (isSpace(ch)) { + /*eslint no-empty:0*/ + } else { + break; + } + } + + // [label]: destination 'title' + // ^^^^^^^^^^^ parse this + res = state.md.helpers.parseLinkDestination(str, pos, max); + if (!res.ok) { return false; } + + href = state.md.normalizeLink(res.str); + if (!state.md.validateLink(href)) { return false; } + + pos = res.pos; + lines += res.lines; + + // save cursor state, we could require to rollback later + destEndPos = pos; + destEndLineNo = lines; + + // [label]: destination 'title' + // ^^^ skipping those spaces + start = pos; + for (; pos < max; pos++) { + ch = str.charCodeAt(pos); + if (ch === 0x0A) { + lines++; + } else if (isSpace(ch)) { + /*eslint no-empty:0*/ + } else { + break; + } + } + + // [label]: destination 'title' + // ^^^^^^^ parse this + res = state.md.helpers.parseLinkTitle(str, pos, max); + if (pos < max && start !== pos && res.ok) { + title = res.str; + pos = res.pos; + lines += res.lines; + } else { + title = ''; + pos = destEndPos; + lines = destEndLineNo; + } + + // skip trailing spaces until the rest of the line + while (pos < max) { + ch = str.charCodeAt(pos); + if (!isSpace(ch)) { break; } + pos++; + } + + if (pos < max && str.charCodeAt(pos) !== 0x0A) { + if (title) { + // garbage at the end of the line after title, + // but it could still be a valid reference if we roll back + title = ''; + pos = destEndPos; + lines = destEndLineNo; + while (pos < max) { + ch = str.charCodeAt(pos); + if (!isSpace(ch)) { break; } + pos++; + } + } + } + + if (pos < max && str.charCodeAt(pos) !== 0x0A) { + // garbage at the end of the line + return false; + } + + label = normalizeReference(str.slice(1, labelEnd)); + if (!label) { + // CommonMark 0.20 disallows empty labels + return false; + } + + // Reference can not terminate anything. This check is for safety only. + /*istanbul ignore if*/ + if (silent) { return true; } + + if (typeof state.env.references === 'undefined') { + state.env.references = {}; + } + if (typeof state.env.references[label] === 'undefined') { + state.env.references[label] = { title: title, href: href }; + } + + state.parentType = oldParentType; + + state.line = startLine + lines + 1; + return true; + }; + + },{"../common/utils":4}],28:[function(require,module,exports){ + // Parser state class + + 'use strict'; + + var Token = require('../token'); + var isSpace = require('../common/utils').isSpace; + + + function StateBlock(src, md, env, tokens) { + var ch, s, start, pos, len, indent, offset, indent_found; + + this.src = src; + + // link to parser instance + this.md = md; + + this.env = env; + + // + // Internal state vartiables + // + + this.tokens = tokens; + + this.bMarks = []; // line begin offsets for fast jumps + this.eMarks = []; // line end offsets for fast jumps + this.tShift = []; // offsets of the first non-space characters (tabs not expanded) + this.sCount = []; // indents for each line (tabs expanded) + + // An amount of virtual spaces (tabs expanded) between beginning + // of each line (bMarks) and real beginning of that line. + // + // It exists only as a hack because blockquotes override bMarks + // losing information in the process. + // + // It's used only when expanding tabs, you can think about it as + // an initial tab length, e.g. bsCount=21 applied to string `\t123` + // means first tab should be expanded to 4-21%4 === 3 spaces. + // + this.bsCount = []; + + // block parser variables + this.blkIndent = 0; // required block content indent (for example, if we are + // inside a list, it would be positioned after list marker) + this.line = 0; // line index in src + this.lineMax = 0; // lines count + this.tight = false; // loose/tight mode for lists + this.ddIndent = -1; // indent of the current dd block (-1 if there isn't any) + this.listIndent = -1; // indent of the current list block (-1 if there isn't any) + + // can be 'blockquote', 'list', 'root', 'paragraph' or 'reference' + // used in lists to determine if they interrupt a paragraph + this.parentType = 'root'; + + this.level = 0; + + // renderer + this.result = ''; + + // Create caches + // Generate markers. + s = this.src; + indent_found = false; + + for (start = pos = indent = offset = 0, len = s.length; pos < len; pos++) { + ch = s.charCodeAt(pos); + + if (!indent_found) { + if (isSpace(ch)) { + indent++; + + if (ch === 0x09) { + offset += 4 - offset % 4; + } else { + offset++; + } + continue; + } else { + indent_found = true; + } + } + + if (ch === 0x0A || pos === len - 1) { + if (ch !== 0x0A) { pos++; } + this.bMarks.push(start); + this.eMarks.push(pos); + this.tShift.push(indent); + this.sCount.push(offset); + this.bsCount.push(0); + + indent_found = false; + indent = 0; + offset = 0; + start = pos + 1; + } + } + + // Push fake entry to simplify cache bounds checks + this.bMarks.push(s.length); + this.eMarks.push(s.length); + this.tShift.push(0); + this.sCount.push(0); + this.bsCount.push(0); + + this.lineMax = this.bMarks.length - 1; // don't count last fake line + } + + // Push new token to "stream". + // + StateBlock.prototype.push = function (type, tag, nesting) { + var token = new Token(type, tag, nesting); + token.block = true; + + if (nesting < 0) this.level--; // closing tag + token.level = this.level; + if (nesting > 0) this.level++; // opening tag + + this.tokens.push(token); + return token; + }; + + StateBlock.prototype.isEmpty = function isEmpty(line) { + return this.bMarks[line] + this.tShift[line] >= this.eMarks[line]; + }; + + StateBlock.prototype.skipEmptyLines = function skipEmptyLines(from) { + for (var max = this.lineMax; from < max; from++) { + if (this.bMarks[from] + this.tShift[from] < this.eMarks[from]) { + break; + } + } + return from; + }; + + // Skip spaces from given position. + StateBlock.prototype.skipSpaces = function skipSpaces(pos) { + var ch; + + for (var max = this.src.length; pos < max; pos++) { + ch = this.src.charCodeAt(pos); + if (!isSpace(ch)) { break; } + } + return pos; + }; + + // Skip spaces from given position in reverse. + StateBlock.prototype.skipSpacesBack = function skipSpacesBack(pos, min) { + if (pos <= min) { return pos; } + + while (pos > min) { + if (!isSpace(this.src.charCodeAt(--pos))) { return pos + 1; } + } + return pos; + }; + + // Skip char codes from given position + StateBlock.prototype.skipChars = function skipChars(pos, code) { + for (var max = this.src.length; pos < max; pos++) { + if (this.src.charCodeAt(pos) !== code) { break; } + } + return pos; + }; + + // Skip char codes reverse from given position - 1 + StateBlock.prototype.skipCharsBack = function skipCharsBack(pos, code, min) { + if (pos <= min) { return pos; } + + while (pos > min) { + if (code !== this.src.charCodeAt(--pos)) { return pos + 1; } + } + return pos; + }; + + // cut lines range from source. + StateBlock.prototype.getLines = function getLines(begin, end, indent, keepLastLF) { + var i, lineIndent, ch, first, last, queue, lineStart, + line = begin; + + if (begin >= end) { + return ''; + } + + queue = new Array(end - begin); + + for (i = 0; line < end; line++, i++) { + lineIndent = 0; + lineStart = first = this.bMarks[line]; + + if (line + 1 < end || keepLastLF) { + // No need for bounds check because we have fake entry on tail. + last = this.eMarks[line] + 1; + } else { + last = this.eMarks[line]; + } + + while (first < last && lineIndent < indent) { + ch = this.src.charCodeAt(first); + + if (isSpace(ch)) { + if (ch === 0x09) { + lineIndent += 4 - (lineIndent + this.bsCount[line]) % 4; + } else { + lineIndent++; + } + } else if (first - lineStart < this.tShift[line]) { + // patched tShift masked characters to look like spaces (blockquotes, list markers) + lineIndent++; + } else { + break; + } + + first++; + } + + if (lineIndent > indent) { + // partially expanding tabs in code blocks, e.g '\t\tfoobar' + // with indent=2 becomes ' \tfoobar' + queue[i] = new Array(lineIndent - indent + 1).join(' ') + this.src.slice(first, last); + } else { + queue[i] = this.src.slice(first, last); + } + } + + return queue.join(''); + }; + + // re-export Token class to use in block rules + StateBlock.prototype.Token = Token; + + + module.exports = StateBlock; + + },{"../common/utils":4,"../token":51}],29:[function(require,module,exports){ + // GFM table, non-standard + + 'use strict'; + + var isSpace = require('../common/utils').isSpace; + + + function getLine(state, line) { + var pos = state.bMarks[line] + state.blkIndent, + max = state.eMarks[line]; + + return state.src.substr(pos, max - pos); + } + + function escapedSplit(str) { + var result = [], + pos = 0, + max = str.length, + ch, + escapes = 0, + lastPos = 0, + backTicked = false, + lastBackTick = 0; + + ch = str.charCodeAt(pos); + + while (pos < max) { + if (ch === 0x60/* ` */) { + if (backTicked) { + // make \` close code sequence, but not open it; + // the reason is: `\` is correct code block + backTicked = false; + lastBackTick = pos; + } else if (escapes % 2 === 0) { + backTicked = true; + lastBackTick = pos; + } + } else if (ch === 0x7c/* | */ && (escapes % 2 === 0) && !backTicked) { + result.push(str.substring(lastPos, pos)); + lastPos = pos + 1; + } + + if (ch === 0x5c/* \ */) { + escapes++; + } else { + escapes = 0; + } + + pos++; + + // If there was an un-closed backtick, go back to just after + // the last backtick, but as if it was a normal character + if (pos === max && backTicked) { + backTicked = false; + pos = lastBackTick + 1; + } + + ch = str.charCodeAt(pos); + } + + result.push(str.substring(lastPos)); + + return result; + } + + + module.exports = function table(state, startLine, endLine, silent) { + var ch, lineText, pos, i, nextLine, columns, columnCount, token, + aligns, t, tableLines, tbodyLines; + + // should have at least two lines + if (startLine + 2 > endLine) { return false; } + + nextLine = startLine + 1; + + if (state.sCount[nextLine] < state.blkIndent) { return false; } + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[nextLine] - state.blkIndent >= 4) { return false; } + + // first character of the second line should be '|', '-', ':', + // and no other characters are allowed but spaces; + // basically, this is the equivalent of /^[-:|][-:|\s]*$/ regexp + + pos = state.bMarks[nextLine] + state.tShift[nextLine]; + if (pos >= state.eMarks[nextLine]) { return false; } + + ch = state.src.charCodeAt(pos++); + if (ch !== 0x7C/* | */ && ch !== 0x2D/* - */ && ch !== 0x3A/* : */) { return false; } + + while (pos < state.eMarks[nextLine]) { + ch = state.src.charCodeAt(pos); + + if (ch !== 0x7C/* | */ && ch !== 0x2D/* - */ && ch !== 0x3A/* : */ && !isSpace(ch)) { return false; } + + pos++; + } + + lineText = getLine(state, startLine + 1); + + columns = lineText.split('|'); + aligns = []; + for (i = 0; i < columns.length; i++) { + t = columns[i].trim(); + if (!t) { + // allow empty columns before and after table, but not in between columns; + // e.g. allow ` |---| `, disallow ` ---||--- ` + if (i === 0 || i === columns.length - 1) { + continue; + } else { + return false; + } + } + + if (!/^:?-+:?$/.test(t)) { return false; } + if (t.charCodeAt(t.length - 1) === 0x3A/* : */) { + aligns.push(t.charCodeAt(0) === 0x3A/* : */ ? 'center' : 'right'); + } else if (t.charCodeAt(0) === 0x3A/* : */) { + aligns.push('left'); + } else { + aligns.push(''); + } + } + + lineText = getLine(state, startLine).trim(); + if (lineText.indexOf('|') === -1) { return false; } + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + columns = escapedSplit(lineText.replace(/^\||\|$/g, '')); + + // header row will define an amount of columns in the entire table, + // and align row shouldn't be smaller than that (the rest of the rows can) + columnCount = columns.length; + if (columnCount > aligns.length) { return false; } + + if (silent) { return true; } + + token = state.push('table_open', 'table', 1); + token.map = tableLines = [ startLine, 0 ]; + + token = state.push('thead_open', 'thead', 1); + token.map = [ startLine, startLine + 1 ]; + + token = state.push('tr_open', 'tr', 1); + token.map = [ startLine, startLine + 1 ]; + + for (i = 0; i < columns.length; i++) { + token = state.push('th_open', 'th', 1); + token.map = [ startLine, startLine + 1 ]; + if (aligns[i]) { + token.attrs = [ [ 'style', 'text-align:' + aligns[i] ] ]; + } + + token = state.push('inline', '', 0); + token.content = columns[i].trim(); + token.map = [ startLine, startLine + 1 ]; + token.children = []; + + token = state.push('th_close', 'th', -1); + } + + token = state.push('tr_close', 'tr', -1); + token = state.push('thead_close', 'thead', -1); + + token = state.push('tbody_open', 'tbody', 1); + token.map = tbodyLines = [ startLine + 2, 0 ]; + + for (nextLine = startLine + 2; nextLine < endLine; nextLine++) { + if (state.sCount[nextLine] < state.blkIndent) { break; } + + lineText = getLine(state, nextLine).trim(); + if (lineText.indexOf('|') === -1) { break; } + if (state.sCount[nextLine] - state.blkIndent >= 4) { break; } + columns = escapedSplit(lineText.replace(/^\||\|$/g, '')); + + token = state.push('tr_open', 'tr', 1); + for (i = 0; i < columnCount; i++) { + token = state.push('td_open', 'td', 1); + if (aligns[i]) { + token.attrs = [ [ 'style', 'text-align:' + aligns[i] ] ]; + } + + token = state.push('inline', '', 0); + token.content = columns[i] ? columns[i].trim() : ''; + token.children = []; + + token = state.push('td_close', 'td', -1); + } + token = state.push('tr_close', 'tr', -1); + } + token = state.push('tbody_close', 'tbody', -1); + token = state.push('table_close', 'table', -1); + + tableLines[1] = tbodyLines[1] = nextLine; + state.line = nextLine; + return true; + }; + + },{"../common/utils":4}],30:[function(require,module,exports){ + 'use strict'; + + + module.exports = function block(state) { + var token; + + if (state.inlineMode) { + token = new state.Token('inline', '', 0); + token.content = state.src; + token.map = [ 0, 1 ]; + token.children = []; + state.tokens.push(token); + } else { + state.md.block.parse(state.src, state.md, state.env, state.tokens); + } + }; + + },{}],31:[function(require,module,exports){ + 'use strict'; + + module.exports = function inline(state) { + var tokens = state.tokens, tok, i, l; + + // Parse inlines + for (i = 0, l = tokens.length; i < l; i++) { + tok = tokens[i]; + if (tok.type === 'inline') { + state.md.inline.parse(tok.content, state.md, state.env, tok.children); + } + } + }; + + },{}],32:[function(require,module,exports){ + // Replace link-like texts with link nodes. + // + // Currently restricted by `md.validateLink()` to http/https/ftp + // + 'use strict'; + + + var arrayReplaceAt = require('../common/utils').arrayReplaceAt; + + + function isLinkOpen(str) { + return /^\s]/i.test(str); + } + function isLinkClose(str) { + return /^<\/a\s*>/i.test(str); + } + + + module.exports = function linkify(state) { + var i, j, l, tokens, token, currentToken, nodes, ln, text, pos, lastPos, + level, htmlLinkLevel, url, fullUrl, urlText, + blockTokens = state.tokens, + links; + + if (!state.md.options.linkify) { return; } + + for (j = 0, l = blockTokens.length; j < l; j++) { + if (blockTokens[j].type !== 'inline' || + !state.md.linkify.pretest(blockTokens[j].content)) { + continue; + } + + tokens = blockTokens[j].children; + + htmlLinkLevel = 0; + + // We scan from the end, to keep position when new tags added. + // Use reversed logic in links start/end match + for (i = tokens.length - 1; i >= 0; i--) { + currentToken = tokens[i]; + + // Skip content of markdown links + if (currentToken.type === 'link_close') { + i--; + while (tokens[i].level !== currentToken.level && tokens[i].type !== 'link_open') { + i--; + } + continue; + } + + // Skip content of html tag links + if (currentToken.type === 'html_inline') { + if (isLinkOpen(currentToken.content) && htmlLinkLevel > 0) { + htmlLinkLevel--; + } + if (isLinkClose(currentToken.content)) { + htmlLinkLevel++; + } + } + if (htmlLinkLevel > 0) { continue; } + + if (currentToken.type === 'text' && state.md.linkify.test(currentToken.content)) { + + text = currentToken.content; + links = state.md.linkify.match(text); + + // Now split string to nodes + nodes = []; + level = currentToken.level; + lastPos = 0; + + for (ln = 0; ln < links.length; ln++) { + + url = links[ln].url; + fullUrl = state.md.normalizeLink(url); + if (!state.md.validateLink(fullUrl)) { continue; } + + urlText = links[ln].text; + + // Linkifier might send raw hostnames like "example.com", where url + // starts with domain name. So we prepend http:// in those cases, + // and remove it afterwards. + // + if (!links[ln].schema) { + urlText = state.md.normalizeLinkText('http://' + urlText).replace(/^http:\/\//, ''); + } else if (links[ln].schema === 'mailto:' && !/^mailto:/i.test(urlText)) { + urlText = state.md.normalizeLinkText('mailto:' + urlText).replace(/^mailto:/, ''); + } else { + urlText = state.md.normalizeLinkText(urlText); + } + + pos = links[ln].index; + + if (pos > lastPos) { + token = new state.Token('text', '', 0); + token.content = text.slice(lastPos, pos); + token.level = level; + nodes.push(token); + } + + token = new state.Token('link_open', 'a', 1); + token.attrs = [ [ 'href', fullUrl ] ]; + token.level = level++; + token.markup = 'linkify'; + token.info = 'auto'; + nodes.push(token); + + token = new state.Token('text', '', 0); + token.content = urlText; + token.level = level; + nodes.push(token); + + token = new state.Token('link_close', 'a', -1); + token.level = --level; + token.markup = 'linkify'; + token.info = 'auto'; + nodes.push(token); + + lastPos = links[ln].lastIndex; + } + if (lastPos < text.length) { + token = new state.Token('text', '', 0); + token.content = text.slice(lastPos); + token.level = level; + nodes.push(token); + } + + // replace current node + blockTokens[j].children = tokens = arrayReplaceAt(tokens, i, nodes); + } + } + } + }; + + },{"../common/utils":4}],33:[function(require,module,exports){ + // Normalize input string + + 'use strict'; + + + // https://spec.commonmark.org/0.29/#line-ending + var NEWLINES_RE = /\r\n?|\n/g; + var NULL_RE = /\0/g; + + + module.exports = function normalize(state) { + var str; + + // Normalize newlines + str = state.src.replace(NEWLINES_RE, '\n'); + + // Replace NULL characters + str = str.replace(NULL_RE, '\uFFFD'); + + state.src = str; + }; + + },{}],34:[function(require,module,exports){ + // Simple typographic replacements + // + // (c) (C) → © + // (tm) (TM) → ™ + // (r) (R) → ® + // +- → ± + // (p) (P) -> § + // ... → … (also ?.... → ?.., !.... → !..) + // ???????? → ???, !!!!! → !!!, `,,` → `,` + // -- → –, --- → — + // + 'use strict'; + + // TODO: + // - fractionals 1/2, 1/4, 3/4 -> ½, ¼, ¾ + // - miltiplication 2 x 4 -> 2 × 4 + + var RARE_RE = /\+-|\.\.|\?\?\?\?|!!!!|,,|--/; + + // Workaround for phantomjs - need regex without /g flag, + // or root check will fail every second time + var SCOPED_ABBR_TEST_RE = /\((c|tm|r|p)\)/i; + + var SCOPED_ABBR_RE = /\((c|tm|r|p)\)/ig; + var SCOPED_ABBR = { + c: '©', + r: '®', + p: '§', + tm: '™' + }; + + function replaceFn(match, name) { + return SCOPED_ABBR[name.toLowerCase()]; + } + + function replace_scoped(inlineTokens) { + var i, token, inside_autolink = 0; + + for (i = inlineTokens.length - 1; i >= 0; i--) { + token = inlineTokens[i]; + + if (token.type === 'text' && !inside_autolink) { + token.content = token.content.replace(SCOPED_ABBR_RE, replaceFn); + } + + if (token.type === 'link_open' && token.info === 'auto') { + inside_autolink--; + } + + if (token.type === 'link_close' && token.info === 'auto') { + inside_autolink++; + } + } + } + + function replace_rare(inlineTokens) { + var i, token, inside_autolink = 0; + + for (i = inlineTokens.length - 1; i >= 0; i--) { + token = inlineTokens[i]; + + if (token.type === 'text' && !inside_autolink) { + if (RARE_RE.test(token.content)) { + token.content = token.content + .replace(/\+-/g, '±') + // .., ..., ....... -> … + // but ?..... & !..... -> ?.. & !.. + .replace(/\.{2,}/g, '…').replace(/([?!])…/g, '$1..') + .replace(/([?!]){4,}/g, '$1$1$1').replace(/,{2,}/g, ',') + // em-dash + .replace(/(^|[^-])---([^-]|$)/mg, '$1\u2014$2') + // en-dash + .replace(/(^|\s)--(\s|$)/mg, '$1\u2013$2') + .replace(/(^|[^-\s])--([^-\s]|$)/mg, '$1\u2013$2'); + } + } + + if (token.type === 'link_open' && token.info === 'auto') { + inside_autolink--; + } + + if (token.type === 'link_close' && token.info === 'auto') { + inside_autolink++; + } + } + } + + + module.exports = function replace(state) { + var blkIdx; + + if (!state.md.options.typographer) { return; } + + for (blkIdx = state.tokens.length - 1; blkIdx >= 0; blkIdx--) { + + if (state.tokens[blkIdx].type !== 'inline') { continue; } + + if (SCOPED_ABBR_TEST_RE.test(state.tokens[blkIdx].content)) { + replace_scoped(state.tokens[blkIdx].children); + } + + if (RARE_RE.test(state.tokens[blkIdx].content)) { + replace_rare(state.tokens[blkIdx].children); + } + + } + }; + + },{}],35:[function(require,module,exports){ + // Convert straight quotation marks to typographic ones + // + 'use strict'; + + + var isWhiteSpace = require('../common/utils').isWhiteSpace; + var isPunctChar = require('../common/utils').isPunctChar; + var isMdAsciiPunct = require('../common/utils').isMdAsciiPunct; + + var QUOTE_TEST_RE = /['"]/; + var QUOTE_RE = /['"]/g; + var APOSTROPHE = '\u2019'; /* ’ */ + + + function replaceAt(str, index, ch) { + return str.substr(0, index) + ch + str.substr(index + 1); + } + + function process_inlines(tokens, state) { + var i, token, text, t, pos, max, thisLevel, item, lastChar, nextChar, + isLastPunctChar, isNextPunctChar, isLastWhiteSpace, isNextWhiteSpace, + canOpen, canClose, j, isSingle, stack, openQuote, closeQuote; + + stack = []; + + for (i = 0; i < tokens.length; i++) { + token = tokens[i]; + + thisLevel = tokens[i].level; + + for (j = stack.length - 1; j >= 0; j--) { + if (stack[j].level <= thisLevel) { break; } + } + stack.length = j + 1; + + if (token.type !== 'text') { continue; } + + text = token.content; + pos = 0; + max = text.length; + + /*eslint no-labels:0,block-scoped-var:0*/ + OUTER: + while (pos < max) { + QUOTE_RE.lastIndex = pos; + t = QUOTE_RE.exec(text); + if (!t) { break; } + + canOpen = canClose = true; + pos = t.index + 1; + isSingle = (t[0] === "'"); + + // Find previous character, + // default to space if it's the beginning of the line + // + lastChar = 0x20; + + if (t.index - 1 >= 0) { + lastChar = text.charCodeAt(t.index - 1); + } else { + for (j = i - 1; j >= 0; j--) { + if (tokens[j].type === 'softbreak' || tokens[j].type === 'hardbreak') break; // lastChar defaults to 0x20 + if (tokens[j].type !== 'text') continue; + + lastChar = tokens[j].content.charCodeAt(tokens[j].content.length - 1); + break; + } + } + + // Find next character, + // default to space if it's the end of the line + // + nextChar = 0x20; + + if (pos < max) { + nextChar = text.charCodeAt(pos); + } else { + for (j = i + 1; j < tokens.length; j++) { + if (tokens[j].type === 'softbreak' || tokens[j].type === 'hardbreak') break; // nextChar defaults to 0x20 + if (tokens[j].type !== 'text') continue; + + nextChar = tokens[j].content.charCodeAt(0); + break; + } + } + + isLastPunctChar = isMdAsciiPunct(lastChar) || isPunctChar(String.fromCharCode(lastChar)); + isNextPunctChar = isMdAsciiPunct(nextChar) || isPunctChar(String.fromCharCode(nextChar)); + + isLastWhiteSpace = isWhiteSpace(lastChar); + isNextWhiteSpace = isWhiteSpace(nextChar); + + if (isNextWhiteSpace) { + canOpen = false; + } else if (isNextPunctChar) { + if (!(isLastWhiteSpace || isLastPunctChar)) { + canOpen = false; + } + } + + if (isLastWhiteSpace) { + canClose = false; + } else if (isLastPunctChar) { + if (!(isNextWhiteSpace || isNextPunctChar)) { + canClose = false; + } + } + + if (nextChar === 0x22 /* " */ && t[0] === '"') { + if (lastChar >= 0x30 /* 0 */ && lastChar <= 0x39 /* 9 */) { + // special case: 1"" - count first quote as an inch + canClose = canOpen = false; + } + } + + if (canOpen && canClose) { + // treat this as the middle of the word + canOpen = false; + canClose = isNextPunctChar; + } + + if (!canOpen && !canClose) { + // middle of word + if (isSingle) { + token.content = replaceAt(token.content, t.index, APOSTROPHE); + } + continue; + } + + if (canClose) { + // this could be a closing quote, rewind the stack to get a match + for (j = stack.length - 1; j >= 0; j--) { + item = stack[j]; + if (stack[j].level < thisLevel) { break; } + if (item.single === isSingle && stack[j].level === thisLevel) { + item = stack[j]; + + if (isSingle) { + openQuote = state.md.options.quotes[2]; + closeQuote = state.md.options.quotes[3]; + } else { + openQuote = state.md.options.quotes[0]; + closeQuote = state.md.options.quotes[1]; + } + + // replace token.content *before* tokens[item.token].content, + // because, if they are pointing at the same token, replaceAt + // could mess up indices when quote length != 1 + token.content = replaceAt(token.content, t.index, closeQuote); + tokens[item.token].content = replaceAt( + tokens[item.token].content, item.pos, openQuote); + + pos += closeQuote.length - 1; + if (item.token === i) { pos += openQuote.length - 1; } + + text = token.content; + max = text.length; + + stack.length = j; + continue OUTER; + } + } + } + + if (canOpen) { + stack.push({ + token: i, + pos: t.index, + single: isSingle, + level: thisLevel + }); + } else if (canClose && isSingle) { + token.content = replaceAt(token.content, t.index, APOSTROPHE); + } + } + } + } + + + module.exports = function smartquotes(state) { + /*eslint max-depth:0*/ + var blkIdx; + + if (!state.md.options.typographer) { return; } + + for (blkIdx = state.tokens.length - 1; blkIdx >= 0; blkIdx--) { + + if (state.tokens[blkIdx].type !== 'inline' || + !QUOTE_TEST_RE.test(state.tokens[blkIdx].content)) { + continue; + } + + process_inlines(state.tokens[blkIdx].children, state); + } + }; + + },{"../common/utils":4}],36:[function(require,module,exports){ + // Core state object + // + 'use strict'; + + var Token = require('../token'); + + + function StateCore(src, md, env) { + this.src = src; + this.env = env; + this.tokens = []; + this.inlineMode = false; + this.md = md; // link to parser instance + } + + // re-export Token class to use in core rules + StateCore.prototype.Token = Token; + + + module.exports = StateCore; + + },{"../token":51}],37:[function(require,module,exports){ + // Process autolinks '' + + 'use strict'; + + + /*eslint max-len:0*/ + var EMAIL_RE = /^<([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])?)*)>/; + var AUTOLINK_RE = /^<([a-zA-Z][a-zA-Z0-9+.\-]{1,31}):([^<>\x00-\x20]*)>/; + + + module.exports = function autolink(state, silent) { + var tail, linkMatch, emailMatch, url, fullUrl, token, + pos = state.pos; + + if (state.src.charCodeAt(pos) !== 0x3C/* < */) { return false; } + + tail = state.src.slice(pos); + + if (tail.indexOf('>') < 0) { return false; } + + if (AUTOLINK_RE.test(tail)) { + linkMatch = tail.match(AUTOLINK_RE); + + url = linkMatch[0].slice(1, -1); + fullUrl = state.md.normalizeLink(url); + if (!state.md.validateLink(fullUrl)) { return false; } + + if (!silent) { + token = state.push('link_open', 'a', 1); + token.attrs = [ [ 'href', fullUrl ] ]; + token.markup = 'autolink'; + token.info = 'auto'; + + token = state.push('text', '', 0); + token.content = state.md.normalizeLinkText(url); + + token = state.push('link_close', 'a', -1); + token.markup = 'autolink'; + token.info = 'auto'; + } + + state.pos += linkMatch[0].length; + return true; + } + + if (EMAIL_RE.test(tail)) { + emailMatch = tail.match(EMAIL_RE); + + url = emailMatch[0].slice(1, -1); + fullUrl = state.md.normalizeLink('mailto:' + url); + if (!state.md.validateLink(fullUrl)) { return false; } + + if (!silent) { + token = state.push('link_open', 'a', 1); + token.attrs = [ [ 'href', fullUrl ] ]; + token.markup = 'autolink'; + token.info = 'auto'; + + token = state.push('text', '', 0); + token.content = state.md.normalizeLinkText(url); + + token = state.push('link_close', 'a', -1); + token.markup = 'autolink'; + token.info = 'auto'; + } + + state.pos += emailMatch[0].length; + return true; + } + + return false; + }; + + },{}],38:[function(require,module,exports){ + // Parse backticks + + 'use strict'; + + module.exports = function backtick(state, silent) { + var start, max, marker, matchStart, matchEnd, token, + pos = state.pos, + ch = state.src.charCodeAt(pos); + + if (ch !== 0x60/* ` */) { return false; } + + start = pos; + pos++; + max = state.posMax; + + while (pos < max && state.src.charCodeAt(pos) === 0x60/* ` */) { pos++; } + + marker = state.src.slice(start, pos); + + matchStart = matchEnd = pos; + + while ((matchStart = state.src.indexOf('`', matchEnd)) !== -1) { + matchEnd = matchStart + 1; + + while (matchEnd < max && state.src.charCodeAt(matchEnd) === 0x60/* ` */) { matchEnd++; } + + if (matchEnd - matchStart === marker.length) { + if (!silent) { + token = state.push('code_inline', 'code', 0); + token.markup = marker; + token.content = state.src.slice(pos, matchStart) + .replace(/\n/g, ' ') + .replace(/^ (.+) $/, '$1'); + } + state.pos = matchEnd; + return true; + } + } + + if (!silent) { state.pending += marker; } + state.pos += marker.length; + return true; + }; + + },{}],39:[function(require,module,exports){ + // For each opening emphasis-like marker find a matching closing one + // + 'use strict'; + + + function processDelimiters(state, delimiters) { + var closerIdx, openerIdx, closer, opener, minOpenerIdx, newMinOpenerIdx, + isOddMatch, lastJump, + openersBottom = {}, + max = delimiters.length; + + for (closerIdx = 0; closerIdx < max; closerIdx++) { + closer = delimiters[closerIdx]; + + // Length is only used for emphasis-specific "rule of 3", + // if it's not defined (in strikethrough or 3rd party plugins), + // we can default it to 0 to disable those checks. + // + closer.length = closer.length || 0; + + if (!closer.close) continue; + + // Previously calculated lower bounds (previous fails) + // for each marker and each delimiter length modulo 3. + if (!openersBottom.hasOwnProperty(closer.marker)) { + openersBottom[closer.marker] = [ -1, -1, -1 ]; + } + + minOpenerIdx = openersBottom[closer.marker][closer.length % 3]; + newMinOpenerIdx = -1; + + openerIdx = closerIdx - closer.jump - 1; + + for (; openerIdx > minOpenerIdx; openerIdx -= opener.jump + 1) { + opener = delimiters[openerIdx]; + + if (opener.marker !== closer.marker) continue; + + if (newMinOpenerIdx === -1) newMinOpenerIdx = openerIdx; + + if (opener.open && + opener.end < 0 && + opener.level === closer.level) { + + isOddMatch = false; + + // from spec: + // + // If one of the delimiters can both open and close emphasis, then the + // sum of the lengths of the delimiter runs containing the opening and + // closing delimiters must not be a multiple of 3 unless both lengths + // are multiples of 3. + // + if (opener.close || closer.open) { + if ((opener.length + closer.length) % 3 === 0) { + if (opener.length % 3 !== 0 || closer.length % 3 !== 0) { + isOddMatch = true; + } + } + } + + if (!isOddMatch) { + // If previous delimiter cannot be an opener, we can safely skip + // the entire sequence in future checks. This is required to make + // sure algorithm has linear complexity (see *_*_*_*_*_... case). + // + lastJump = openerIdx > 0 && !delimiters[openerIdx - 1].open ? + delimiters[openerIdx - 1].jump + 1 : + 0; + + closer.jump = closerIdx - openerIdx + lastJump; + closer.open = false; + opener.end = closerIdx; + opener.jump = lastJump; + opener.close = false; + newMinOpenerIdx = -1; + break; + } + } + } + + if (newMinOpenerIdx !== -1) { + // If match for this delimiter run failed, we want to set lower bound for + // future lookups. This is required to make sure algorithm has linear + // complexity. + // + // See details here: + // https://github.com/commonmark/cmark/issues/178#issuecomment-270417442 + // + openersBottom[closer.marker][(closer.length || 0) % 3] = newMinOpenerIdx; + } + } + } + + + module.exports = function link_pairs(state) { + var curr, + tokens_meta = state.tokens_meta, + max = state.tokens_meta.length; + + processDelimiters(state, state.delimiters); + + for (curr = 0; curr < max; curr++) { + if (tokens_meta[curr] && tokens_meta[curr].delimiters) { + processDelimiters(state, tokens_meta[curr].delimiters); + } + } + }; + + },{}],40:[function(require,module,exports){ + // Process *this* and _that_ + // + 'use strict'; + + + // Insert each marker as a separate text token, and add it to delimiter list + // + module.exports.tokenize = function emphasis(state, silent) { + var i, scanned, token, + start = state.pos, + marker = state.src.charCodeAt(start); + + if (silent) { return false; } + + if (marker !== 0x5F /* _ */ && marker !== 0x2A /* * */) { return false; } + + scanned = state.scanDelims(state.pos, marker === 0x2A); + + for (i = 0; i < scanned.length; i++) { + token = state.push('text', '', 0); + token.content = String.fromCharCode(marker); + + state.delimiters.push({ + // Char code of the starting marker (number). + // + marker: marker, + + // Total length of these series of delimiters. + // + length: scanned.length, + + // An amount of characters before this one that's equivalent to + // current one. In plain English: if this delimiter does not open + // an emphasis, neither do previous `jump` characters. + // + // Used to skip sequences like "*****" in one step, for 1st asterisk + // value will be 0, for 2nd it's 1 and so on. + // + jump: i, + + // A position of the token this delimiter corresponds to. + // + token: state.tokens.length - 1, + + // If this delimiter is matched as a valid opener, `end` will be + // equal to its position, otherwise it's `-1`. + // + end: -1, + + // Boolean flags that determine if this delimiter could open or close + // an emphasis. + // + open: scanned.can_open, + close: scanned.can_close + }); + } + + state.pos += scanned.length; + + return true; + }; + + + function postProcess(state, delimiters) { + var i, + startDelim, + endDelim, + token, + ch, + isStrong, + max = delimiters.length; + + for (i = max - 1; i >= 0; i--) { + startDelim = delimiters[i]; + + if (startDelim.marker !== 0x5F/* _ */ && startDelim.marker !== 0x2A/* * */) { + continue; + } + + // Process only opening markers + if (startDelim.end === -1) { + continue; + } + + endDelim = delimiters[startDelim.end]; + + // If the previous delimiter has the same marker and is adjacent to this one, + // merge those into one strong delimiter. + // + // `whatever` -> `whatever` + // + isStrong = i > 0 && + delimiters[i - 1].end === startDelim.end + 1 && + delimiters[i - 1].token === startDelim.token - 1 && + delimiters[startDelim.end + 1].token === endDelim.token + 1 && + delimiters[i - 1].marker === startDelim.marker; + + ch = String.fromCharCode(startDelim.marker); + + token = state.tokens[startDelim.token]; + token.type = isStrong ? 'strong_open' : 'em_open'; + token.tag = isStrong ? 'strong' : 'em'; + token.nesting = 1; + token.markup = isStrong ? ch + ch : ch; + token.content = ''; + + token = state.tokens[endDelim.token]; + token.type = isStrong ? 'strong_close' : 'em_close'; + token.tag = isStrong ? 'strong' : 'em'; + token.nesting = -1; + token.markup = isStrong ? ch + ch : ch; + token.content = ''; + + if (isStrong) { + state.tokens[delimiters[i - 1].token].content = ''; + state.tokens[delimiters[startDelim.end + 1].token].content = ''; + i--; + } + } + } + + + // Walk through delimiter list and replace text tokens with tags + // + module.exports.postProcess = function emphasis(state) { + var curr, + tokens_meta = state.tokens_meta, + max = state.tokens_meta.length; + + postProcess(state, state.delimiters); + + for (curr = 0; curr < max; curr++) { + if (tokens_meta[curr] && tokens_meta[curr].delimiters) { + postProcess(state, tokens_meta[curr].delimiters); + } + } + }; + + },{}],41:[function(require,module,exports){ + // Process html entity - {, ¯, ", ... + + 'use strict'; + + var entities = require('../common/entities'); + var has = require('../common/utils').has; + var isValidEntityCode = require('../common/utils').isValidEntityCode; + var fromCodePoint = require('../common/utils').fromCodePoint; + + + var DIGITAL_RE = /^&#((?:x[a-f0-9]{1,6}|[0-9]{1,7}));/i; + var NAMED_RE = /^&([a-z][a-z0-9]{1,31});/i; + + + module.exports = function entity(state, silent) { + var ch, code, match, pos = state.pos, max = state.posMax; + + if (state.src.charCodeAt(pos) !== 0x26/* & */) { return false; } + + if (pos + 1 < max) { + ch = state.src.charCodeAt(pos + 1); + + if (ch === 0x23 /* # */) { + match = state.src.slice(pos).match(DIGITAL_RE); + if (match) { + if (!silent) { + code = match[1][0].toLowerCase() === 'x' ? parseInt(match[1].slice(1), 16) : parseInt(match[1], 10); + state.pending += isValidEntityCode(code) ? fromCodePoint(code) : fromCodePoint(0xFFFD); + } + state.pos += match[0].length; + return true; + } + } else { + match = state.src.slice(pos).match(NAMED_RE); + if (match) { + if (has(entities, match[1])) { + if (!silent) { state.pending += entities[match[1]]; } + state.pos += match[0].length; + return true; + } + } + } + } + + if (!silent) { state.pending += '&'; } + state.pos++; + return true; + }; + + },{"../common/entities":1,"../common/utils":4}],42:[function(require,module,exports){ + // Process escaped chars and hardbreaks + + 'use strict'; + + var isSpace = require('../common/utils').isSpace; + + var ESCAPED = []; + + for (var i = 0; i < 256; i++) { ESCAPED.push(0); } + + '\\!"#$%&\'()*+,./:;<=>?@[]^_`{|}~-' + .split('').forEach(function (ch) { ESCAPED[ch.charCodeAt(0)] = 1; }); + + + module.exports = function escape(state, silent) { + var ch, pos = state.pos, max = state.posMax; + + if (state.src.charCodeAt(pos) !== 0x5C/* \ */) { return false; } + + pos++; + + if (pos < max) { + ch = state.src.charCodeAt(pos); + + if (ch < 256 && ESCAPED[ch] !== 0) { + if (!silent) { state.pending += state.src[pos]; } + state.pos += 2; + return true; + } + + if (ch === 0x0A) { + if (!silent) { + state.push('hardbreak', 'br', 0); + } + + pos++; + // skip leading whitespaces from next line + while (pos < max) { + ch = state.src.charCodeAt(pos); + if (!isSpace(ch)) { break; } + pos++; + } + + state.pos = pos; + return true; + } + } + + if (!silent) { state.pending += '\\'; } + state.pos++; + return true; + }; + + },{"../common/utils":4}],43:[function(require,module,exports){ + // Process html tags + + 'use strict'; + + + var HTML_TAG_RE = require('../common/html_re').HTML_TAG_RE; + + + function isLetter(ch) { + /*eslint no-bitwise:0*/ + var lc = ch | 0x20; // to lower case + return (lc >= 0x61/* a */) && (lc <= 0x7a/* z */); + } + + + module.exports = function html_inline(state, silent) { + var ch, match, max, token, + pos = state.pos; + + if (!state.md.options.html) { return false; } + + // Check start + max = state.posMax; + if (state.src.charCodeAt(pos) !== 0x3C/* < */ || + pos + 2 >= max) { + return false; + } + + // Quick fail on second char + ch = state.src.charCodeAt(pos + 1); + if (ch !== 0x21/* ! */ && + ch !== 0x3F/* ? */ && + ch !== 0x2F/* / */ && + !isLetter(ch)) { + return false; + } + + match = state.src.slice(pos).match(HTML_TAG_RE); + if (!match) { return false; } + + if (!silent) { + token = state.push('html_inline', '', 0); + token.content = state.src.slice(pos, pos + match[0].length); + } + state.pos += match[0].length; + return true; + }; + + },{"../common/html_re":3}],44:[function(require,module,exports){ + // Process ![image]( "title") + + 'use strict'; + + var normalizeReference = require('../common/utils').normalizeReference; + var isSpace = require('../common/utils').isSpace; + + + module.exports = function image(state, silent) { + var attrs, + code, + content, + label, + labelEnd, + labelStart, + pos, + ref, + res, + title, + token, + tokens, + start, + href = '', + oldPos = state.pos, + max = state.posMax; + + if (state.src.charCodeAt(state.pos) !== 0x21/* ! */) { return false; } + if (state.src.charCodeAt(state.pos + 1) !== 0x5B/* [ */) { return false; } + + labelStart = state.pos + 2; + labelEnd = state.md.helpers.parseLinkLabel(state, state.pos + 1, false); + + // parser failed to find ']', so it's not a valid link + if (labelEnd < 0) { return false; } + + pos = labelEnd + 1; + if (pos < max && state.src.charCodeAt(pos) === 0x28/* ( */) { + // + // Inline link + // + + // [link]( "title" ) + // ^^ skipping these spaces + pos++; + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (!isSpace(code) && code !== 0x0A) { break; } + } + if (pos >= max) { return false; } + + // [link]( "title" ) + // ^^^^^^ parsing link destination + start = pos; + res = state.md.helpers.parseLinkDestination(state.src, pos, state.posMax); + if (res.ok) { + href = state.md.normalizeLink(res.str); + if (state.md.validateLink(href)) { + pos = res.pos; + } else { + href = ''; + } + } + + // [link]( "title" ) + // ^^ skipping these spaces + start = pos; + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (!isSpace(code) && code !== 0x0A) { break; } + } + + // [link]( "title" ) + // ^^^^^^^ parsing link title + res = state.md.helpers.parseLinkTitle(state.src, pos, state.posMax); + if (pos < max && start !== pos && res.ok) { + title = res.str; + pos = res.pos; + + // [link]( "title" ) + // ^^ skipping these spaces + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (!isSpace(code) && code !== 0x0A) { break; } + } + } else { + title = ''; + } + + if (pos >= max || state.src.charCodeAt(pos) !== 0x29/* ) */) { + state.pos = oldPos; + return false; + } + pos++; + } else { + // + // Link reference + // + if (typeof state.env.references === 'undefined') { return false; } + + if (pos < max && state.src.charCodeAt(pos) === 0x5B/* [ */) { + start = pos + 1; + pos = state.md.helpers.parseLinkLabel(state, pos); + if (pos >= 0) { + label = state.src.slice(start, pos++); + } else { + pos = labelEnd + 1; + } + } else { + pos = labelEnd + 1; + } + + // covers label === '' and label === undefined + // (collapsed reference link and shortcut reference link respectively) + if (!label) { label = state.src.slice(labelStart, labelEnd); } + + ref = state.env.references[normalizeReference(label)]; + if (!ref) { + state.pos = oldPos; + return false; + } + href = ref.href; + title = ref.title; + } + + // + // We found the end of the link, and know for a fact it's a valid link; + // so all that's left to do is to call tokenizer. + // + if (!silent) { + content = state.src.slice(labelStart, labelEnd); + + state.md.inline.parse( + content, + state.md, + state.env, + tokens = [] + ); + + token = state.push('image', 'img', 0); + token.attrs = attrs = [ [ 'src', href ], [ 'alt', '' ] ]; + token.children = tokens; + token.content = content; + + if (title) { + attrs.push([ 'title', title ]); + } + } + + state.pos = pos; + state.posMax = max; + return true; + }; + + },{"../common/utils":4}],45:[function(require,module,exports){ + // Process [link]( "stuff") + + 'use strict'; + + var normalizeReference = require('../common/utils').normalizeReference; + var isSpace = require('../common/utils').isSpace; + + + module.exports = function link(state, silent) { + var attrs, + code, + label, + labelEnd, + labelStart, + pos, + res, + ref, + title, + token, + href = '', + oldPos = state.pos, + max = state.posMax, + start = state.pos, + parseReference = true; + + if (state.src.charCodeAt(state.pos) !== 0x5B/* [ */) { return false; } + + labelStart = state.pos + 1; + labelEnd = state.md.helpers.parseLinkLabel(state, state.pos, true); + + // parser failed to find ']', so it's not a valid link + if (labelEnd < 0) { return false; } + + pos = labelEnd + 1; + if (pos < max && state.src.charCodeAt(pos) === 0x28/* ( */) { + // + // Inline link + // + + // might have found a valid shortcut link, disable reference parsing + parseReference = false; + + // [link]( "title" ) + // ^^ skipping these spaces + pos++; + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (!isSpace(code) && code !== 0x0A) { break; } + } + if (pos >= max) { return false; } + + // [link]( "title" ) + // ^^^^^^ parsing link destination + start = pos; + res = state.md.helpers.parseLinkDestination(state.src, pos, state.posMax); + if (res.ok) { + href = state.md.normalizeLink(res.str); + if (state.md.validateLink(href)) { + pos = res.pos; + } else { + href = ''; + } + } + + // [link]( "title" ) + // ^^ skipping these spaces + start = pos; + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (!isSpace(code) && code !== 0x0A) { break; } + } + + // [link]( "title" ) + // ^^^^^^^ parsing link title + res = state.md.helpers.parseLinkTitle(state.src, pos, state.posMax); + if (pos < max && start !== pos && res.ok) { + title = res.str; + pos = res.pos; + + // [link]( "title" ) + // ^^ skipping these spaces + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (!isSpace(code) && code !== 0x0A) { break; } + } + } else { + title = ''; + } + + if (pos >= max || state.src.charCodeAt(pos) !== 0x29/* ) */) { + // parsing a valid shortcut link failed, fallback to reference + parseReference = true; + } + pos++; + } + + if (parseReference) { + // + // Link reference + // + if (typeof state.env.references === 'undefined') { return false; } + + if (pos < max && state.src.charCodeAt(pos) === 0x5B/* [ */) { + start = pos + 1; + pos = state.md.helpers.parseLinkLabel(state, pos); + if (pos >= 0) { + label = state.src.slice(start, pos++); + } else { + pos = labelEnd + 1; + } + } else { + pos = labelEnd + 1; + } + + // covers label === '' and label === undefined + // (collapsed reference link and shortcut reference link respectively) + if (!label) { label = state.src.slice(labelStart, labelEnd); } + + ref = state.env.references[normalizeReference(label)]; + if (!ref) { + state.pos = oldPos; + return false; + } + href = ref.href; + title = ref.title; + } + + // + // We found the end of the link, and know for a fact it's a valid link; + // so all that's left to do is to call tokenizer. + // + if (!silent) { + state.pos = labelStart; + state.posMax = labelEnd; + + token = state.push('link_open', 'a', 1); + token.attrs = attrs = [ [ 'href', href ] ]; + if (title) { + attrs.push([ 'title', title ]); + } + + state.md.inline.tokenize(state); + + token = state.push('link_close', 'a', -1); + } + + state.pos = pos; + state.posMax = max; + return true; + }; + + },{"../common/utils":4}],46:[function(require,module,exports){ + // Proceess '\n' + + 'use strict'; + + var isSpace = require('../common/utils').isSpace; + + + module.exports = function newline(state, silent) { + var pmax, max, pos = state.pos; + + if (state.src.charCodeAt(pos) !== 0x0A/* \n */) { return false; } + + pmax = state.pending.length - 1; + max = state.posMax; + + // ' \n' -> hardbreak + // Lookup in pending chars is bad practice! Don't copy to other rules! + // Pending string is stored in concat mode, indexed lookups will cause + // convertion to flat mode. + if (!silent) { + if (pmax >= 0 && state.pending.charCodeAt(pmax) === 0x20) { + if (pmax >= 1 && state.pending.charCodeAt(pmax - 1) === 0x20) { + state.pending = state.pending.replace(/ +$/, ''); + state.push('hardbreak', 'br', 0); + } else { + state.pending = state.pending.slice(0, -1); + state.push('softbreak', 'br', 0); + } + + } else { + state.push('softbreak', 'br', 0); + } + } + + pos++; + + // skip heading spaces for next line + while (pos < max && isSpace(state.src.charCodeAt(pos))) { pos++; } + + state.pos = pos; + return true; + }; + + },{"../common/utils":4}],47:[function(require,module,exports){ + // Inline parser state + + 'use strict'; + + + var Token = require('../token'); + var isWhiteSpace = require('../common/utils').isWhiteSpace; + var isPunctChar = require('../common/utils').isPunctChar; + var isMdAsciiPunct = require('../common/utils').isMdAsciiPunct; + + + function StateInline(src, md, env, outTokens) { + this.src = src; + this.env = env; + this.md = md; + this.tokens = outTokens; + this.tokens_meta = Array(outTokens.length); + + this.pos = 0; + this.posMax = this.src.length; + this.level = 0; + this.pending = ''; + this.pendingLevel = 0; + + // Stores { start: end } pairs. Useful for backtrack + // optimization of pairs parse (emphasis, strikes). + this.cache = {}; + + // List of emphasis-like delimiters for current tag + this.delimiters = []; + + // Stack of delimiter lists for upper level tags + this._prev_delimiters = []; + } + + + // Flush pending text + // + StateInline.prototype.pushPending = function () { + var token = new Token('text', '', 0); + token.content = this.pending; + token.level = this.pendingLevel; + this.tokens.push(token); + this.pending = ''; + return token; + }; + + + // Push new token to "stream". + // If pending text exists - flush it as text token + // + StateInline.prototype.push = function (type, tag, nesting) { + if (this.pending) { + this.pushPending(); + } + + var token = new Token(type, tag, nesting); + var token_meta = null; + + if (nesting < 0) { + // closing tag + this.level--; + this.delimiters = this._prev_delimiters.pop(); + } + + token.level = this.level; + + if (nesting > 0) { + // opening tag + this.level++; + this._prev_delimiters.push(this.delimiters); + this.delimiters = []; + token_meta = { delimiters: this.delimiters }; + } + + this.pendingLevel = this.level; + this.tokens.push(token); + this.tokens_meta.push(token_meta); + return token; + }; + + + // Scan a sequence of emphasis-like markers, and determine whether + // it can start an emphasis sequence or end an emphasis sequence. + // + // - start - position to scan from (it should point at a valid marker); + // - canSplitWord - determine if these markers can be found inside a word + // + StateInline.prototype.scanDelims = function (start, canSplitWord) { + var pos = start, lastChar, nextChar, count, can_open, can_close, + isLastWhiteSpace, isLastPunctChar, + isNextWhiteSpace, isNextPunctChar, + left_flanking = true, + right_flanking = true, + max = this.posMax, + marker = this.src.charCodeAt(start); + + // treat beginning of the line as a whitespace + lastChar = start > 0 ? this.src.charCodeAt(start - 1) : 0x20; + + while (pos < max && this.src.charCodeAt(pos) === marker) { pos++; } + + count = pos - start; + + // treat end of the line as a whitespace + nextChar = pos < max ? this.src.charCodeAt(pos) : 0x20; + + isLastPunctChar = isMdAsciiPunct(lastChar) || isPunctChar(String.fromCharCode(lastChar)); + isNextPunctChar = isMdAsciiPunct(nextChar) || isPunctChar(String.fromCharCode(nextChar)); + + isLastWhiteSpace = isWhiteSpace(lastChar); + isNextWhiteSpace = isWhiteSpace(nextChar); + + if (isNextWhiteSpace) { + left_flanking = false; + } else if (isNextPunctChar) { + if (!(isLastWhiteSpace || isLastPunctChar)) { + left_flanking = false; + } + } + + if (isLastWhiteSpace) { + right_flanking = false; + } else if (isLastPunctChar) { + if (!(isNextWhiteSpace || isNextPunctChar)) { + right_flanking = false; + } + } + + if (!canSplitWord) { + can_open = left_flanking && (!right_flanking || isLastPunctChar); + can_close = right_flanking && (!left_flanking || isNextPunctChar); + } else { + can_open = left_flanking; + can_close = right_flanking; + } + + return { + can_open: can_open, + can_close: can_close, + length: count + }; + }; + + + // re-export Token class to use in block rules + StateInline.prototype.Token = Token; + + + module.exports = StateInline; + + },{"../common/utils":4,"../token":51}],48:[function(require,module,exports){ + // ~~strike through~~ + // + 'use strict'; + + + // Insert each marker as a separate text token, and add it to delimiter list + // + module.exports.tokenize = function strikethrough(state, silent) { + var i, scanned, token, len, ch, + start = state.pos, + marker = state.src.charCodeAt(start); + + if (silent) { return false; } + + if (marker !== 0x7E/* ~ */) { return false; } + + scanned = state.scanDelims(state.pos, true); + len = scanned.length; + ch = String.fromCharCode(marker); + + if (len < 2) { return false; } + + if (len % 2) { + token = state.push('text', '', 0); + token.content = ch; + len--; + } + + for (i = 0; i < len; i += 2) { + token = state.push('text', '', 0); + token.content = ch + ch; + + state.delimiters.push({ + marker: marker, + length: 0, // disable "rule of 3" length checks meant for emphasis + jump: i, + token: state.tokens.length - 1, + end: -1, + open: scanned.can_open, + close: scanned.can_close + }); + } + + state.pos += scanned.length; + + return true; + }; + + + function postProcess(state, delimiters) { + var i, j, + startDelim, + endDelim, + token, + loneMarkers = [], + max = delimiters.length; + + for (i = 0; i < max; i++) { + startDelim = delimiters[i]; + + if (startDelim.marker !== 0x7E/* ~ */) { + continue; + } + + if (startDelim.end === -1) { + continue; + } + + endDelim = delimiters[startDelim.end]; + + token = state.tokens[startDelim.token]; + token.type = 's_open'; + token.tag = 's'; + token.nesting = 1; + token.markup = '~~'; + token.content = ''; + + token = state.tokens[endDelim.token]; + token.type = 's_close'; + token.tag = 's'; + token.nesting = -1; + token.markup = '~~'; + token.content = ''; + + if (state.tokens[endDelim.token - 1].type === 'text' && + state.tokens[endDelim.token - 1].content === '~') { + + loneMarkers.push(endDelim.token - 1); + } + } + + // If a marker sequence has an odd number of characters, it's splitted + // like this: `~~~~~` -> `~` + `~~` + `~~`, leaving one marker at the + // start of the sequence. + // + // So, we have to move all those markers after subsequent s_close tags. + // + while (loneMarkers.length) { + i = loneMarkers.pop(); + j = i + 1; + + while (j < state.tokens.length && state.tokens[j].type === 's_close') { + j++; + } + + j--; + + if (i !== j) { + token = state.tokens[j]; + state.tokens[j] = state.tokens[i]; + state.tokens[i] = token; + } + } + } + + + // Walk through delimiter list and replace text tokens with tags + // + module.exports.postProcess = function strikethrough(state) { + var curr, + tokens_meta = state.tokens_meta, + max = state.tokens_meta.length; + + postProcess(state, state.delimiters); + + for (curr = 0; curr < max; curr++) { + if (tokens_meta[curr] && tokens_meta[curr].delimiters) { + postProcess(state, tokens_meta[curr].delimiters); + } + } + }; + + },{}],49:[function(require,module,exports){ + // Skip text characters for text token, place those to pending buffer + // and increment current pos + + 'use strict'; + + + // Rule to skip pure text + // '{}$%@~+=:' reserved for extentions + + // !, ", #, $, %, &, ', (, ), *, +, ,, -, ., /, :, ;, <, =, >, ?, @, [, \, ], ^, _, `, {, |, }, or ~ + + // !!!! Don't confuse with "Markdown ASCII Punctuation" chars + // http://spec.commonmark.org/0.15/#ascii-punctuation-character + function isTerminatorChar(ch) { + switch (ch) { + case 0x0A/* \n */: + case 0x21/* ! */: + case 0x23/* # */: + case 0x24/* $ */: + case 0x25/* % */: + case 0x26/* & */: + case 0x2A/* * */: + case 0x2B/* + */: + case 0x2D/* - */: + case 0x3A/* : */: + case 0x3C/* < */: + case 0x3D/* = */: + case 0x3E/* > */: + case 0x40/* @ */: + case 0x5B/* [ */: + case 0x5C/* \ */: + case 0x5D/* ] */: + case 0x5E/* ^ */: + case 0x5F/* _ */: + case 0x60/* ` */: + case 0x7B/* { */: + case 0x7D/* } */: + case 0x7E/* ~ */: + return true; + default: + return false; + } + } + + module.exports = function text(state, silent) { + var pos = state.pos; + + while (pos < state.posMax && !isTerminatorChar(state.src.charCodeAt(pos))) { + pos++; + } + + if (pos === state.pos) { return false; } + + if (!silent) { state.pending += state.src.slice(state.pos, pos); } + + state.pos = pos; + + return true; + }; + + // Alternative implementation, for memory. + // + // It costs 10% of performance, but allows extend terminators list, if place it + // to `ParcerInline` property. Probably, will switch to it sometime, such + // flexibility required. + + /* + var TERMINATOR_RE = /[\n!#$%&*+\-:<=>@[\\\]^_`{}~]/; + + module.exports = function text(state, silent) { + var pos = state.pos, + idx = state.src.slice(pos).search(TERMINATOR_RE); + + // first char is terminator -> empty text + if (idx === 0) { return false; } + + // no terminator -> text till end of string + if (idx < 0) { + if (!silent) { state.pending += state.src.slice(pos); } + state.pos = state.src.length; + return true; + } + + if (!silent) { state.pending += state.src.slice(pos, pos + idx); } + + state.pos += idx; + + return true; + };*/ + + },{}],50:[function(require,module,exports){ + // Clean up tokens after emphasis and strikethrough postprocessing: + // merge adjacent text nodes into one and re-calculate all token levels + // + // This is necessary because initially emphasis delimiter markers (*, _, ~) + // are treated as their own separate text tokens. Then emphasis rule either + // leaves them as text (needed to merge with adjacent text) or turns them + // into opening/closing tags (which messes up levels inside). + // + 'use strict'; + + + module.exports = function text_collapse(state) { + var curr, last, + level = 0, + tokens = state.tokens, + max = state.tokens.length; + + for (curr = last = 0; curr < max; curr++) { + // re-calculate levels after emphasis/strikethrough turns some text nodes + // into opening/closing tags + if (tokens[curr].nesting < 0) level--; // closing tag + tokens[curr].level = level; + if (tokens[curr].nesting > 0) level++; // opening tag + + if (tokens[curr].type === 'text' && + curr + 1 < max && + tokens[curr + 1].type === 'text') { + + // collapse two adjacent text nodes + tokens[curr + 1].content = tokens[curr].content + tokens[curr + 1].content; + } else { + if (curr !== last) { tokens[last] = tokens[curr]; } + + last++; + } + } + + if (curr !== last) { + tokens.length = last; + } + }; + + },{}],51:[function(require,module,exports){ + // Token class + + 'use strict'; + + + /** + * class Token + **/ + + /** + * new Token(type, tag, nesting) + * + * Create new token and fill passed properties. + **/ + function Token(type, tag, nesting) { + /** + * Token#type -> String + * + * Type of the token (string, e.g. "paragraph_open") + **/ + this.type = type; + + /** + * Token#tag -> String + * + * html tag name, e.g. "p" + **/ + this.tag = tag; + + /** + * Token#attrs -> Array + * + * Html attributes. Format: `[ [ name1, value1 ], [ name2, value2 ] ]` + **/ + this.attrs = null; + + /** + * Token#map -> Array + * + * Source map info. Format: `[ line_begin, line_end ]` + **/ + this.map = null; + + /** + * Token#nesting -> Number + * + * Level change (number in {-1, 0, 1} set), where: + * + * - `1` means the tag is opening + * - `0` means the tag is self-closing + * - `-1` means the tag is closing + **/ + this.nesting = nesting; + + /** + * Token#level -> Number + * + * nesting level, the same as `state.level` + **/ + this.level = 0; + + /** + * Token#children -> Array + * + * An array of child nodes (inline and img tokens) + **/ + this.children = null; + + /** + * Token#content -> String + * + * In a case of self-closing tag (code, html, fence, etc.), + * it has contents of this tag. + **/ + this.content = ''; + + /** + * Token#markup -> String + * + * '*' or '_' for emphasis, fence string for fence, etc. + **/ + this.markup = ''; + + /** + * Token#info -> String + * + * fence infostring + **/ + this.info = ''; + + /** + * Token#meta -> Object + * + * A place for plugins to store an arbitrary data + **/ + this.meta = null; + + /** + * Token#block -> Boolean + * + * True for block-level tokens, false for inline tokens. + * Used in renderer to calculate line breaks + **/ + this.block = false; + + /** + * Token#hidden -> Boolean + * + * If it's true, ignore this element when rendering. Used for tight lists + * to hide paragraphs. + **/ + this.hidden = false; + } + + + /** + * Token.attrIndex(name) -> Number + * + * Search attribute index by name. + **/ + Token.prototype.attrIndex = function attrIndex(name) { + var attrs, i, len; + + if (!this.attrs) { return -1; } + + attrs = this.attrs; + + for (i = 0, len = attrs.length; i < len; i++) { + if (attrs[i][0] === name) { return i; } + } + return -1; + }; + + + /** + * Token.attrPush(attrData) + * + * Add `[ name, value ]` attribute to list. Init attrs if necessary + **/ + Token.prototype.attrPush = function attrPush(attrData) { + if (this.attrs) { + this.attrs.push(attrData); + } else { + this.attrs = [ attrData ]; + } + }; + + + /** + * Token.attrSet(name, value) + * + * Set `name` attribute to `value`. Override old value if exists. + **/ + Token.prototype.attrSet = function attrSet(name, value) { + var idx = this.attrIndex(name), + attrData = [ name, value ]; + + if (idx < 0) { + this.attrPush(attrData); + } else { + this.attrs[idx] = attrData; + } + }; + + + /** + * Token.attrGet(name) + * + * Get the value of attribute `name`, or null if it does not exist. + **/ + Token.prototype.attrGet = function attrGet(name) { + var idx = this.attrIndex(name), value = null; + if (idx >= 0) { + value = this.attrs[idx][1]; + } + return value; + }; + + + /** + * Token.attrJoin(name, value) + * + * Join value to existing attribute via space. Or create new attribute if not + * exists. Useful to operate with token classes. + **/ + Token.prototype.attrJoin = function attrJoin(name, value) { + var idx = this.attrIndex(name); + + if (idx < 0) { + this.attrPush([ name, value ]); + } else { + this.attrs[idx][1] = this.attrs[idx][1] + ' ' + value; + } + }; + + + module.exports = Token; + + },{}],52:[function(require,module,exports){ + module.exports={ "Aacute": "\u00C1", "aacute": "\u00E1", "Abreve": "\u0102", "abreve": "\u0103", "ac": "\u223E", "acd": "\u223F", "acE": "\u223E\u0333", "Acirc": "\u00C2", "acirc": "\u00E2", "acute": "\u00B4", "Acy": "\u0410", "acy": "\u0430", "AElig": "\u00C6", "aelig": "\u00E6", "af": "\u2061", "Afr": "\uD835\uDD04", "afr": "\uD835\uDD1E", "Agrave": "\u00C0", "agrave": "\u00E0", "alefsym": "\u2135", "aleph": "\u2135", "Alpha": "\u0391", "alpha": "\u03B1", "Amacr": "\u0100", "amacr": "\u0101", "amalg": "\u2A3F", "amp": "&", "AMP": "&", "andand": "\u2A55", "And": "\u2A53", "and": "\u2227", "andd": "\u2A5C", "andslope": "\u2A58", "andv": "\u2A5A", "ang": "\u2220", "ange": "\u29A4", "angle": "\u2220", "angmsdaa": "\u29A8", "angmsdab": "\u29A9", "angmsdac": "\u29AA", "angmsdad": "\u29AB", "angmsdae": "\u29AC", "angmsdaf": "\u29AD", "angmsdag": "\u29AE", "angmsdah": "\u29AF", "angmsd": "\u2221", "angrt": "\u221F", "angrtvb": "\u22BE", "angrtvbd": "\u299D", "angsph": "\u2222", "angst": "\u00C5", "angzarr": "\u237C", "Aogon": "\u0104", "aogon": "\u0105", "Aopf": "\uD835\uDD38", "aopf": "\uD835\uDD52", "apacir": "\u2A6F", "ap": "\u2248", "apE": "\u2A70", "ape": "\u224A", "apid": "\u224B", "apos": "'", "ApplyFunction": "\u2061", "approx": "\u2248", "approxeq": "\u224A", "Aring": "\u00C5", "aring": "\u00E5", "Ascr": "\uD835\uDC9C", "ascr": "\uD835\uDCB6", "Assign": "\u2254", "ast": "*", "asymp": "\u2248", "asympeq": "\u224D", "Atilde": "\u00C3", "atilde": "\u00E3", "Auml": "\u00C4", "auml": "\u00E4", "awconint": "\u2233", "awint": "\u2A11", "backcong": "\u224C", "backepsilon": "\u03F6", "backprime": "\u2035", "backsim": "\u223D", "backsimeq": "\u22CD", "Backslash": "\u2216", "Barv": "\u2AE7", "barvee": "\u22BD", "barwed": "\u2305", "Barwed": "\u2306", "barwedge": "\u2305", "bbrk": "\u23B5", "bbrktbrk": "\u23B6", "bcong": "\u224C", "Bcy": "\u0411", "bcy": "\u0431", "bdquo": "\u201E", "becaus": "\u2235", "because": "\u2235", "Because": "\u2235", "bemptyv": "\u29B0", "bepsi": "\u03F6", "bernou": "\u212C", "Bernoullis": "\u212C", "Beta": "\u0392", "beta": "\u03B2", "beth": "\u2136", "between": "\u226C", "Bfr": "\uD835\uDD05", "bfr": "\uD835\uDD1F", "bigcap": "\u22C2", "bigcirc": "\u25EF", "bigcup": "\u22C3", "bigodot": "\u2A00", "bigoplus": "\u2A01", "bigotimes": "\u2A02", "bigsqcup": "\u2A06", "bigstar": "\u2605", "bigtriangledown": "\u25BD", "bigtriangleup": "\u25B3", "biguplus": "\u2A04", "bigvee": "\u22C1", "bigwedge": "\u22C0", "bkarow": "\u290D", "blacklozenge": "\u29EB", "blacksquare": "\u25AA", "blacktriangle": "\u25B4", "blacktriangledown": "\u25BE", "blacktriangleleft": "\u25C2", "blacktriangleright": "\u25B8", "blank": "\u2423", "blk12": "\u2592", "blk14": "\u2591", "blk34": "\u2593", "block": "\u2588", "bne": "=\u20E5", "bnequiv": "\u2261\u20E5", "bNot": "\u2AED", "bnot": "\u2310", "Bopf": "\uD835\uDD39", "bopf": "\uD835\uDD53", "bot": "\u22A5", "bottom": "\u22A5", "bowtie": "\u22C8", "boxbox": "\u29C9", "boxdl": "\u2510", "boxdL": "\u2555", "boxDl": "\u2556", "boxDL": "\u2557", "boxdr": "\u250C", "boxdR": "\u2552", "boxDr": "\u2553", "boxDR": "\u2554", "boxh": "\u2500", "boxH": "\u2550", "boxhd": "\u252C", "boxHd": "\u2564", "boxhD": "\u2565", "boxHD": "\u2566", "boxhu": "\u2534", "boxHu": "\u2567", "boxhU": "\u2568", "boxHU": "\u2569", "boxminus": "\u229F", "boxplus": "\u229E", "boxtimes": "\u22A0", "boxul": "\u2518", "boxuL": "\u255B", "boxUl": "\u255C", "boxUL": "\u255D", "boxur": "\u2514", "boxuR": "\u2558", "boxUr": "\u2559", "boxUR": "\u255A", "boxv": "\u2502", "boxV": "\u2551", "boxvh": "\u253C", "boxvH": "\u256A", "boxVh": "\u256B", "boxVH": "\u256C", "boxvl": "\u2524", "boxvL": "\u2561", "boxVl": "\u2562", "boxVL": "\u2563", "boxvr": "\u251C", "boxvR": "\u255E", "boxVr": "\u255F", "boxVR": "\u2560", "bprime": "\u2035", "breve": "\u02D8", "Breve": "\u02D8", "brvbar": "\u00A6", "bscr": "\uD835\uDCB7", "Bscr": "\u212C", "bsemi": "\u204F", "bsim": "\u223D", "bsime": "\u22CD", "bsolb": "\u29C5", "bsol": "\\", "bsolhsub": "\u27C8", "bull": "\u2022", "bullet": "\u2022", "bump": "\u224E", "bumpE": "\u2AAE", "bumpe": "\u224F", "Bumpeq": "\u224E", "bumpeq": "\u224F", "Cacute": "\u0106", "cacute": "\u0107", "capand": "\u2A44", "capbrcup": "\u2A49", "capcap": "\u2A4B", "cap": "\u2229", "Cap": "\u22D2", "capcup": "\u2A47", "capdot": "\u2A40", "CapitalDifferentialD": "\u2145", "caps": "\u2229\uFE00", "caret": "\u2041", "caron": "\u02C7", "Cayleys": "\u212D", "ccaps": "\u2A4D", "Ccaron": "\u010C", "ccaron": "\u010D", "Ccedil": "\u00C7", "ccedil": "\u00E7", "Ccirc": "\u0108", "ccirc": "\u0109", "Cconint": "\u2230", "ccups": "\u2A4C", "ccupssm": "\u2A50", "Cdot": "\u010A", "cdot": "\u010B", "cedil": "\u00B8", "Cedilla": "\u00B8", "cemptyv": "\u29B2", "cent": "\u00A2", "centerdot": "\u00B7", "CenterDot": "\u00B7", "cfr": "\uD835\uDD20", "Cfr": "\u212D", "CHcy": "\u0427", "chcy": "\u0447", "check": "\u2713", "checkmark": "\u2713", "Chi": "\u03A7", "chi": "\u03C7", "circ": "\u02C6", "circeq": "\u2257", "circlearrowleft": "\u21BA", "circlearrowright": "\u21BB", "circledast": "\u229B", "circledcirc": "\u229A", "circleddash": "\u229D", "CircleDot": "\u2299", "circledR": "\u00AE", "circledS": "\u24C8", "CircleMinus": "\u2296", "CirclePlus": "\u2295", "CircleTimes": "\u2297", "cir": "\u25CB", "cirE": "\u29C3", "cire": "\u2257", "cirfnint": "\u2A10", "cirmid": "\u2AEF", "cirscir": "\u29C2", "ClockwiseContourIntegral": "\u2232", "CloseCurlyDoubleQuote": "\u201D", "CloseCurlyQuote": "\u2019", "clubs": "\u2663", "clubsuit": "\u2663", "colon": ":", "Colon": "\u2237", "Colone": "\u2A74", "colone": "\u2254", "coloneq": "\u2254", "comma": ",", "commat": "@", "comp": "\u2201", "compfn": "\u2218", "complement": "\u2201", "complexes": "\u2102", "cong": "\u2245", "congdot": "\u2A6D", "Congruent": "\u2261", "conint": "\u222E", "Conint": "\u222F", "ContourIntegral": "\u222E", "copf": "\uD835\uDD54", "Copf": "\u2102", "coprod": "\u2210", "Coproduct": "\u2210", "copy": "\u00A9", "COPY": "\u00A9", "copysr": "\u2117", "CounterClockwiseContourIntegral": "\u2233", "crarr": "\u21B5", "cross": "\u2717", "Cross": "\u2A2F", "Cscr": "\uD835\uDC9E", "cscr": "\uD835\uDCB8", "csub": "\u2ACF", "csube": "\u2AD1", "csup": "\u2AD0", "csupe": "\u2AD2", "ctdot": "\u22EF", "cudarrl": "\u2938", "cudarrr": "\u2935", "cuepr": "\u22DE", "cuesc": "\u22DF", "cularr": "\u21B6", "cularrp": "\u293D", "cupbrcap": "\u2A48", "cupcap": "\u2A46", "CupCap": "\u224D", "cup": "\u222A", "Cup": "\u22D3", "cupcup": "\u2A4A", "cupdot": "\u228D", "cupor": "\u2A45", "cups": "\u222A\uFE00", "curarr": "\u21B7", "curarrm": "\u293C", "curlyeqprec": "\u22DE", "curlyeqsucc": "\u22DF", "curlyvee": "\u22CE", "curlywedge": "\u22CF", "curren": "\u00A4", "curvearrowleft": "\u21B6", "curvearrowright": "\u21B7", "cuvee": "\u22CE", "cuwed": "\u22CF", "cwconint": "\u2232", "cwint": "\u2231", "cylcty": "\u232D", "dagger": "\u2020", "Dagger": "\u2021", "daleth": "\u2138", "darr": "\u2193", "Darr": "\u21A1", "dArr": "\u21D3", "dash": "\u2010", "Dashv": "\u2AE4", "dashv": "\u22A3", "dbkarow": "\u290F", "dblac": "\u02DD", "Dcaron": "\u010E", "dcaron": "\u010F", "Dcy": "\u0414", "dcy": "\u0434", "ddagger": "\u2021", "ddarr": "\u21CA", "DD": "\u2145", "dd": "\u2146", "DDotrahd": "\u2911", "ddotseq": "\u2A77", "deg": "\u00B0", "Del": "\u2207", "Delta": "\u0394", "delta": "\u03B4", "demptyv": "\u29B1", "dfisht": "\u297F", "Dfr": "\uD835\uDD07", "dfr": "\uD835\uDD21", "dHar": "\u2965", "dharl": "\u21C3", "dharr": "\u21C2", "DiacriticalAcute": "\u00B4", "DiacriticalDot": "\u02D9", "DiacriticalDoubleAcute": "\u02DD", "DiacriticalGrave": "`", "DiacriticalTilde": "\u02DC", "diam": "\u22C4", "diamond": "\u22C4", "Diamond": "\u22C4", "diamondsuit": "\u2666", "diams": "\u2666", "die": "\u00A8", "DifferentialD": "\u2146", "digamma": "\u03DD", "disin": "\u22F2", "div": "\u00F7", "divide": "\u00F7", "divideontimes": "\u22C7", "divonx": "\u22C7", "DJcy": "\u0402", "djcy": "\u0452", "dlcorn": "\u231E", "dlcrop": "\u230D", "dollar": "$", "Dopf": "\uD835\uDD3B", "dopf": "\uD835\uDD55", "Dot": "\u00A8", "dot": "\u02D9", "DotDot": "\u20DC", "doteq": "\u2250", "doteqdot": "\u2251", "DotEqual": "\u2250", "dotminus": "\u2238", "dotplus": "\u2214", "dotsquare": "\u22A1", "doublebarwedge": "\u2306", "DoubleContourIntegral": "\u222F", "DoubleDot": "\u00A8", "DoubleDownArrow": "\u21D3", "DoubleLeftArrow": "\u21D0", "DoubleLeftRightArrow": "\u21D4", "DoubleLeftTee": "\u2AE4", "DoubleLongLeftArrow": "\u27F8", "DoubleLongLeftRightArrow": "\u27FA", "DoubleLongRightArrow": "\u27F9", "DoubleRightArrow": "\u21D2", "DoubleRightTee": "\u22A8", "DoubleUpArrow": "\u21D1", "DoubleUpDownArrow": "\u21D5", "DoubleVerticalBar": "\u2225", "DownArrowBar": "\u2913", "downarrow": "\u2193", "DownArrow": "\u2193", "Downarrow": "\u21D3", "DownArrowUpArrow": "\u21F5", "DownBreve": "\u0311", "downdownarrows": "\u21CA", "downharpoonleft": "\u21C3", "downharpoonright": "\u21C2", "DownLeftRightVector": "\u2950", "DownLeftTeeVector": "\u295E", "DownLeftVectorBar": "\u2956", "DownLeftVector": "\u21BD", "DownRightTeeVector": "\u295F", "DownRightVectorBar": "\u2957", "DownRightVector": "\u21C1", "DownTeeArrow": "\u21A7", "DownTee": "\u22A4", "drbkarow": "\u2910", "drcorn": "\u231F", "drcrop": "\u230C", "Dscr": "\uD835\uDC9F", "dscr": "\uD835\uDCB9", "DScy": "\u0405", "dscy": "\u0455", "dsol": "\u29F6", "Dstrok": "\u0110", "dstrok": "\u0111", "dtdot": "\u22F1", "dtri": "\u25BF", "dtrif": "\u25BE", "duarr": "\u21F5", "duhar": "\u296F", "dwangle": "\u29A6", "DZcy": "\u040F", "dzcy": "\u045F", "dzigrarr": "\u27FF", "Eacute": "\u00C9", "eacute": "\u00E9", "easter": "\u2A6E", "Ecaron": "\u011A", "ecaron": "\u011B", "Ecirc": "\u00CA", "ecirc": "\u00EA", "ecir": "\u2256", "ecolon": "\u2255", "Ecy": "\u042D", "ecy": "\u044D", "eDDot": "\u2A77", "Edot": "\u0116", "edot": "\u0117", "eDot": "\u2251", "ee": "\u2147", "efDot": "\u2252", "Efr": "\uD835\uDD08", "efr": "\uD835\uDD22", "eg": "\u2A9A", "Egrave": "\u00C8", "egrave": "\u00E8", "egs": "\u2A96", "egsdot": "\u2A98", "el": "\u2A99", "Element": "\u2208", "elinters": "\u23E7", "ell": "\u2113", "els": "\u2A95", "elsdot": "\u2A97", "Emacr": "\u0112", "emacr": "\u0113", "empty": "\u2205", "emptyset": "\u2205", "EmptySmallSquare": "\u25FB", "emptyv": "\u2205", "EmptyVerySmallSquare": "\u25AB", "emsp13": "\u2004", "emsp14": "\u2005", "emsp": "\u2003", "ENG": "\u014A", "eng": "\u014B", "ensp": "\u2002", "Eogon": "\u0118", "eogon": "\u0119", "Eopf": "\uD835\uDD3C", "eopf": "\uD835\uDD56", "epar": "\u22D5", "eparsl": "\u29E3", "eplus": "\u2A71", "epsi": "\u03B5", "Epsilon": "\u0395", "epsilon": "\u03B5", "epsiv": "\u03F5", "eqcirc": "\u2256", "eqcolon": "\u2255", "eqsim": "\u2242", "eqslantgtr": "\u2A96", "eqslantless": "\u2A95", "Equal": "\u2A75", "equals": "=", "EqualTilde": "\u2242", "equest": "\u225F", "Equilibrium": "\u21CC", "equiv": "\u2261", "equivDD": "\u2A78", "eqvparsl": "\u29E5", "erarr": "\u2971", "erDot": "\u2253", "escr": "\u212F", "Escr": "\u2130", "esdot": "\u2250", "Esim": "\u2A73", "esim": "\u2242", "Eta": "\u0397", "eta": "\u03B7", "ETH": "\u00D0", "eth": "\u00F0", "Euml": "\u00CB", "euml": "\u00EB", "euro": "\u20AC", "excl": "!", "exist": "\u2203", "Exists": "\u2203", "expectation": "\u2130", "exponentiale": "\u2147", "ExponentialE": "\u2147", "fallingdotseq": "\u2252", "Fcy": "\u0424", "fcy": "\u0444", "female": "\u2640", "ffilig": "\uFB03", "fflig": "\uFB00", "ffllig": "\uFB04", "Ffr": "\uD835\uDD09", "ffr": "\uD835\uDD23", "filig": "\uFB01", "FilledSmallSquare": "\u25FC", "FilledVerySmallSquare": "\u25AA", "fjlig": "fj", "flat": "\u266D", "fllig": "\uFB02", "fltns": "\u25B1", "fnof": "\u0192", "Fopf": "\uD835\uDD3D", "fopf": "\uD835\uDD57", "forall": "\u2200", "ForAll": "\u2200", "fork": "\u22D4", "forkv": "\u2AD9", "Fouriertrf": "\u2131", "fpartint": "\u2A0D", "frac12": "\u00BD", "frac13": "\u2153", "frac14": "\u00BC", "frac15": "\u2155", "frac16": "\u2159", "frac18": "\u215B", "frac23": "\u2154", "frac25": "\u2156", "frac34": "\u00BE", "frac35": "\u2157", "frac38": "\u215C", "frac45": "\u2158", "frac56": "\u215A", "frac58": "\u215D", "frac78": "\u215E", "frasl": "\u2044", "frown": "\u2322", "fscr": "\uD835\uDCBB", "Fscr": "\u2131", "gacute": "\u01F5", "Gamma": "\u0393", "gamma": "\u03B3", "Gammad": "\u03DC", "gammad": "\u03DD", "gap": "\u2A86", "Gbreve": "\u011E", "gbreve": "\u011F", "Gcedil": "\u0122", "Gcirc": "\u011C", "gcirc": "\u011D", "Gcy": "\u0413", "gcy": "\u0433", "Gdot": "\u0120", "gdot": "\u0121", "ge": "\u2265", "gE": "\u2267", "gEl": "\u2A8C", "gel": "\u22DB", "geq": "\u2265", "geqq": "\u2267", "geqslant": "\u2A7E", "gescc": "\u2AA9", "ges": "\u2A7E", "gesdot": "\u2A80", "gesdoto": "\u2A82", "gesdotol": "\u2A84", "gesl": "\u22DB\uFE00", "gesles": "\u2A94", "Gfr": "\uD835\uDD0A", "gfr": "\uD835\uDD24", "gg": "\u226B", "Gg": "\u22D9", "ggg": "\u22D9", "gimel": "\u2137", "GJcy": "\u0403", "gjcy": "\u0453", "gla": "\u2AA5", "gl": "\u2277", "glE": "\u2A92", "glj": "\u2AA4", "gnap": "\u2A8A", "gnapprox": "\u2A8A", "gne": "\u2A88", "gnE": "\u2269", "gneq": "\u2A88", "gneqq": "\u2269", "gnsim": "\u22E7", "Gopf": "\uD835\uDD3E", "gopf": "\uD835\uDD58", "grave": "`", "GreaterEqual": "\u2265", "GreaterEqualLess": "\u22DB", "GreaterFullEqual": "\u2267", "GreaterGreater": "\u2AA2", "GreaterLess": "\u2277", "GreaterSlantEqual": "\u2A7E", "GreaterTilde": "\u2273", "Gscr": "\uD835\uDCA2", "gscr": "\u210A", "gsim": "\u2273", "gsime": "\u2A8E", "gsiml": "\u2A90", "gtcc": "\u2AA7", "gtcir": "\u2A7A", "gt": ">", "GT": ">", "Gt": "\u226B", "gtdot": "\u22D7", "gtlPar": "\u2995", "gtquest": "\u2A7C", "gtrapprox": "\u2A86", "gtrarr": "\u2978", "gtrdot": "\u22D7", "gtreqless": "\u22DB", "gtreqqless": "\u2A8C", "gtrless": "\u2277", "gtrsim": "\u2273", "gvertneqq": "\u2269\uFE00", "gvnE": "\u2269\uFE00", "Hacek": "\u02C7", "hairsp": "\u200A", "half": "\u00BD", "hamilt": "\u210B", "HARDcy": "\u042A", "hardcy": "\u044A", "harrcir": "\u2948", "harr": "\u2194", "hArr": "\u21D4", "harrw": "\u21AD", "Hat": "^", "hbar": "\u210F", "Hcirc": "\u0124", "hcirc": "\u0125", "hearts": "\u2665", "heartsuit": "\u2665", "hellip": "\u2026", "hercon": "\u22B9", "hfr": "\uD835\uDD25", "Hfr": "\u210C", "HilbertSpace": "\u210B", "hksearow": "\u2925", "hkswarow": "\u2926", "hoarr": "\u21FF", "homtht": "\u223B", "hookleftarrow": "\u21A9", "hookrightarrow": "\u21AA", "hopf": "\uD835\uDD59", "Hopf": "\u210D", "horbar": "\u2015", "HorizontalLine": "\u2500", "hscr": "\uD835\uDCBD", "Hscr": "\u210B", "hslash": "\u210F", "Hstrok": "\u0126", "hstrok": "\u0127", "HumpDownHump": "\u224E", "HumpEqual": "\u224F", "hybull": "\u2043", "hyphen": "\u2010", "Iacute": "\u00CD", "iacute": "\u00ED", "ic": "\u2063", "Icirc": "\u00CE", "icirc": "\u00EE", "Icy": "\u0418", "icy": "\u0438", "Idot": "\u0130", "IEcy": "\u0415", "iecy": "\u0435", "iexcl": "\u00A1", "iff": "\u21D4", "ifr": "\uD835\uDD26", "Ifr": "\u2111", "Igrave": "\u00CC", "igrave": "\u00EC", "ii": "\u2148", "iiiint": "\u2A0C", "iiint": "\u222D", "iinfin": "\u29DC", "iiota": "\u2129", "IJlig": "\u0132", "ijlig": "\u0133", "Imacr": "\u012A", "imacr": "\u012B", "image": "\u2111", "ImaginaryI": "\u2148", "imagline": "\u2110", "imagpart": "\u2111", "imath": "\u0131", "Im": "\u2111", "imof": "\u22B7", "imped": "\u01B5", "Implies": "\u21D2", "incare": "\u2105", "in": "\u2208", "infin": "\u221E", "infintie": "\u29DD", "inodot": "\u0131", "intcal": "\u22BA", "int": "\u222B", "Int": "\u222C", "integers": "\u2124", "Integral": "\u222B", "intercal": "\u22BA", "Intersection": "\u22C2", "intlarhk": "\u2A17", "intprod": "\u2A3C", "InvisibleComma": "\u2063", "InvisibleTimes": "\u2062", "IOcy": "\u0401", "iocy": "\u0451", "Iogon": "\u012E", "iogon": "\u012F", "Iopf": "\uD835\uDD40", "iopf": "\uD835\uDD5A", "Iota": "\u0399", "iota": "\u03B9", "iprod": "\u2A3C", "iquest": "\u00BF", "iscr": "\uD835\uDCBE", "Iscr": "\u2110", "isin": "\u2208", "isindot": "\u22F5", "isinE": "\u22F9", "isins": "\u22F4", "isinsv": "\u22F3", "isinv": "\u2208", "it": "\u2062", "Itilde": "\u0128", "itilde": "\u0129", "Iukcy": "\u0406", "iukcy": "\u0456", "Iuml": "\u00CF", "iuml": "\u00EF", "Jcirc": "\u0134", "jcirc": "\u0135", "Jcy": "\u0419", "jcy": "\u0439", "Jfr": "\uD835\uDD0D", "jfr": "\uD835\uDD27", "jmath": "\u0237", "Jopf": "\uD835\uDD41", "jopf": "\uD835\uDD5B", "Jscr": "\uD835\uDCA5", "jscr": "\uD835\uDCBF", "Jsercy": "\u0408", "jsercy": "\u0458", "Jukcy": "\u0404", "jukcy": "\u0454", "Kappa": "\u039A", "kappa": "\u03BA", "kappav": "\u03F0", "Kcedil": "\u0136", "kcedil": "\u0137", "Kcy": "\u041A", "kcy": "\u043A", "Kfr": "\uD835\uDD0E", "kfr": "\uD835\uDD28", "kgreen": "\u0138", "KHcy": "\u0425", "khcy": "\u0445", "KJcy": "\u040C", "kjcy": "\u045C", "Kopf": "\uD835\uDD42", "kopf": "\uD835\uDD5C", "Kscr": "\uD835\uDCA6", "kscr": "\uD835\uDCC0", "lAarr": "\u21DA", "Lacute": "\u0139", "lacute": "\u013A", "laemptyv": "\u29B4", "lagran": "\u2112", "Lambda": "\u039B", "lambda": "\u03BB", "lang": "\u27E8", "Lang": "\u27EA", "langd": "\u2991", "langle": "\u27E8", "lap": "\u2A85", "Laplacetrf": "\u2112", "laquo": "\u00AB", "larrb": "\u21E4", "larrbfs": "\u291F", "larr": "\u2190", "Larr": "\u219E", "lArr": "\u21D0", "larrfs": "\u291D", "larrhk": "\u21A9", "larrlp": "\u21AB", "larrpl": "\u2939", "larrsim": "\u2973", "larrtl": "\u21A2", "latail": "\u2919", "lAtail": "\u291B", "lat": "\u2AAB", "late": "\u2AAD", "lates": "\u2AAD\uFE00", "lbarr": "\u290C", "lBarr": "\u290E", "lbbrk": "\u2772", "lbrace": "{", "lbrack": "[", "lbrke": "\u298B", "lbrksld": "\u298F", "lbrkslu": "\u298D", "Lcaron": "\u013D", "lcaron": "\u013E", "Lcedil": "\u013B", "lcedil": "\u013C", "lceil": "\u2308", "lcub": "{", "Lcy": "\u041B", "lcy": "\u043B", "ldca": "\u2936", "ldquo": "\u201C", "ldquor": "\u201E", "ldrdhar": "\u2967", "ldrushar": "\u294B", "ldsh": "\u21B2", "le": "\u2264", "lE": "\u2266", "LeftAngleBracket": "\u27E8", "LeftArrowBar": "\u21E4", "leftarrow": "\u2190", "LeftArrow": "\u2190", "Leftarrow": "\u21D0", "LeftArrowRightArrow": "\u21C6", "leftarrowtail": "\u21A2", "LeftCeiling": "\u2308", "LeftDoubleBracket": "\u27E6", "LeftDownTeeVector": "\u2961", "LeftDownVectorBar": "\u2959", "LeftDownVector": "\u21C3", "LeftFloor": "\u230A", "leftharpoondown": "\u21BD", "leftharpoonup": "\u21BC", "leftleftarrows": "\u21C7", "leftrightarrow": "\u2194", "LeftRightArrow": "\u2194", "Leftrightarrow": "\u21D4", "leftrightarrows": "\u21C6", "leftrightharpoons": "\u21CB", "leftrightsquigarrow": "\u21AD", "LeftRightVector": "\u294E", "LeftTeeArrow": "\u21A4", "LeftTee": "\u22A3", "LeftTeeVector": "\u295A", "leftthreetimes": "\u22CB", "LeftTriangleBar": "\u29CF", "LeftTriangle": "\u22B2", "LeftTriangleEqual": "\u22B4", "LeftUpDownVector": "\u2951", "LeftUpTeeVector": "\u2960", "LeftUpVectorBar": "\u2958", "LeftUpVector": "\u21BF", "LeftVectorBar": "\u2952", "LeftVector": "\u21BC", "lEg": "\u2A8B", "leg": "\u22DA", "leq": "\u2264", "leqq": "\u2266", "leqslant": "\u2A7D", "lescc": "\u2AA8", "les": "\u2A7D", "lesdot": "\u2A7F", "lesdoto": "\u2A81", "lesdotor": "\u2A83", "lesg": "\u22DA\uFE00", "lesges": "\u2A93", "lessapprox": "\u2A85", "lessdot": "\u22D6", "lesseqgtr": "\u22DA", "lesseqqgtr": "\u2A8B", "LessEqualGreater": "\u22DA", "LessFullEqual": "\u2266", "LessGreater": "\u2276", "lessgtr": "\u2276", "LessLess": "\u2AA1", "lesssim": "\u2272", "LessSlantEqual": "\u2A7D", "LessTilde": "\u2272", "lfisht": "\u297C", "lfloor": "\u230A", "Lfr": "\uD835\uDD0F", "lfr": "\uD835\uDD29", "lg": "\u2276", "lgE": "\u2A91", "lHar": "\u2962", "lhard": "\u21BD", "lharu": "\u21BC", "lharul": "\u296A", "lhblk": "\u2584", "LJcy": "\u0409", "ljcy": "\u0459", "llarr": "\u21C7", "ll": "\u226A", "Ll": "\u22D8", "llcorner": "\u231E", "Lleftarrow": "\u21DA", "llhard": "\u296B", "lltri": "\u25FA", "Lmidot": "\u013F", "lmidot": "\u0140", "lmoustache": "\u23B0", "lmoust": "\u23B0", "lnap": "\u2A89", "lnapprox": "\u2A89", "lne": "\u2A87", "lnE": "\u2268", "lneq": "\u2A87", "lneqq": "\u2268", "lnsim": "\u22E6", "loang": "\u27EC", "loarr": "\u21FD", "lobrk": "\u27E6", "longleftarrow": "\u27F5", "LongLeftArrow": "\u27F5", "Longleftarrow": "\u27F8", "longleftrightarrow": "\u27F7", "LongLeftRightArrow": "\u27F7", "Longleftrightarrow": "\u27FA", "longmapsto": "\u27FC", "longrightarrow": "\u27F6", "LongRightArrow": "\u27F6", "Longrightarrow": "\u27F9", "looparrowleft": "\u21AB", "looparrowright": "\u21AC", "lopar": "\u2985", "Lopf": "\uD835\uDD43", "lopf": "\uD835\uDD5D", "loplus": "\u2A2D", "lotimes": "\u2A34", "lowast": "\u2217", "lowbar": "_", "LowerLeftArrow": "\u2199", "LowerRightArrow": "\u2198", "loz": "\u25CA", "lozenge": "\u25CA", "lozf": "\u29EB", "lpar": "(", "lparlt": "\u2993", "lrarr": "\u21C6", "lrcorner": "\u231F", "lrhar": "\u21CB", "lrhard": "\u296D", "lrm": "\u200E", "lrtri": "\u22BF", "lsaquo": "\u2039", "lscr": "\uD835\uDCC1", "Lscr": "\u2112", "lsh": "\u21B0", "Lsh": "\u21B0", "lsim": "\u2272", "lsime": "\u2A8D", "lsimg": "\u2A8F", "lsqb": "[", "lsquo": "\u2018", "lsquor": "\u201A", "Lstrok": "\u0141", "lstrok": "\u0142", "ltcc": "\u2AA6", "ltcir": "\u2A79", "lt": "<", "LT": "<", "Lt": "\u226A", "ltdot": "\u22D6", "lthree": "\u22CB", "ltimes": "\u22C9", "ltlarr": "\u2976", "ltquest": "\u2A7B", "ltri": "\u25C3", "ltrie": "\u22B4", "ltrif": "\u25C2", "ltrPar": "\u2996", "lurdshar": "\u294A", "luruhar": "\u2966", "lvertneqq": "\u2268\uFE00", "lvnE": "\u2268\uFE00", "macr": "\u00AF", "male": "\u2642", "malt": "\u2720", "maltese": "\u2720", "Map": "\u2905", "map": "\u21A6", "mapsto": "\u21A6", "mapstodown": "\u21A7", "mapstoleft": "\u21A4", "mapstoup": "\u21A5", "marker": "\u25AE", "mcomma": "\u2A29", "Mcy": "\u041C", "mcy": "\u043C", "mdash": "\u2014", "mDDot": "\u223A", "measuredangle": "\u2221", "MediumSpace": "\u205F", "Mellintrf": "\u2133", "Mfr": "\uD835\uDD10", "mfr": "\uD835\uDD2A", "mho": "\u2127", "micro": "\u00B5", "midast": "*", "midcir": "\u2AF0", "mid": "\u2223", "middot": "\u00B7", "minusb": "\u229F", "minus": "\u2212", "minusd": "\u2238", "minusdu": "\u2A2A", "MinusPlus": "\u2213", "mlcp": "\u2ADB", "mldr": "\u2026", "mnplus": "\u2213", "models": "\u22A7", "Mopf": "\uD835\uDD44", "mopf": "\uD835\uDD5E", "mp": "\u2213", "mscr": "\uD835\uDCC2", "Mscr": "\u2133", "mstpos": "\u223E", "Mu": "\u039C", "mu": "\u03BC", "multimap": "\u22B8", "mumap": "\u22B8", "nabla": "\u2207", "Nacute": "\u0143", "nacute": "\u0144", "nang": "\u2220\u20D2", "nap": "\u2249", "napE": "\u2A70\u0338", "napid": "\u224B\u0338", "napos": "\u0149", "napprox": "\u2249", "natural": "\u266E", "naturals": "\u2115", "natur": "\u266E", "nbsp": "\u00A0", "nbump": "\u224E\u0338", "nbumpe": "\u224F\u0338", "ncap": "\u2A43", "Ncaron": "\u0147", "ncaron": "\u0148", "Ncedil": "\u0145", "ncedil": "\u0146", "ncong": "\u2247", "ncongdot": "\u2A6D\u0338", "ncup": "\u2A42", "Ncy": "\u041D", "ncy": "\u043D", "ndash": "\u2013", "nearhk": "\u2924", "nearr": "\u2197", "neArr": "\u21D7", "nearrow": "\u2197", "ne": "\u2260", "nedot": "\u2250\u0338", "NegativeMediumSpace": "\u200B", "NegativeThickSpace": "\u200B", "NegativeThinSpace": "\u200B", "NegativeVeryThinSpace": "\u200B", "nequiv": "\u2262", "nesear": "\u2928", "nesim": "\u2242\u0338", "NestedGreaterGreater": "\u226B", "NestedLessLess": "\u226A", "NewLine": "\n", "nexist": "\u2204", "nexists": "\u2204", "Nfr": "\uD835\uDD11", "nfr": "\uD835\uDD2B", "ngE": "\u2267\u0338", "nge": "\u2271", "ngeq": "\u2271", "ngeqq": "\u2267\u0338", "ngeqslant": "\u2A7E\u0338", "nges": "\u2A7E\u0338", "nGg": "\u22D9\u0338", "ngsim": "\u2275", "nGt": "\u226B\u20D2", "ngt": "\u226F", "ngtr": "\u226F", "nGtv": "\u226B\u0338", "nharr": "\u21AE", "nhArr": "\u21CE", "nhpar": "\u2AF2", "ni": "\u220B", "nis": "\u22FC", "nisd": "\u22FA", "niv": "\u220B", "NJcy": "\u040A", "njcy": "\u045A", "nlarr": "\u219A", "nlArr": "\u21CD", "nldr": "\u2025", "nlE": "\u2266\u0338", "nle": "\u2270", "nleftarrow": "\u219A", "nLeftarrow": "\u21CD", "nleftrightarrow": "\u21AE", "nLeftrightarrow": "\u21CE", "nleq": "\u2270", "nleqq": "\u2266\u0338", "nleqslant": "\u2A7D\u0338", "nles": "\u2A7D\u0338", "nless": "\u226E", "nLl": "\u22D8\u0338", "nlsim": "\u2274", "nLt": "\u226A\u20D2", "nlt": "\u226E", "nltri": "\u22EA", "nltrie": "\u22EC", "nLtv": "\u226A\u0338", "nmid": "\u2224", "NoBreak": "\u2060", "NonBreakingSpace": "\u00A0", "nopf": "\uD835\uDD5F", "Nopf": "\u2115", "Not": "\u2AEC", "not": "\u00AC", "NotCongruent": "\u2262", "NotCupCap": "\u226D", "NotDoubleVerticalBar": "\u2226", "NotElement": "\u2209", "NotEqual": "\u2260", "NotEqualTilde": "\u2242\u0338", "NotExists": "\u2204", "NotGreater": "\u226F", "NotGreaterEqual": "\u2271", "NotGreaterFullEqual": "\u2267\u0338", "NotGreaterGreater": "\u226B\u0338", "NotGreaterLess": "\u2279", "NotGreaterSlantEqual": "\u2A7E\u0338", "NotGreaterTilde": "\u2275", "NotHumpDownHump": "\u224E\u0338", "NotHumpEqual": "\u224F\u0338", "notin": "\u2209", "notindot": "\u22F5\u0338", "notinE": "\u22F9\u0338", "notinva": "\u2209", "notinvb": "\u22F7", "notinvc": "\u22F6", "NotLeftTriangleBar": "\u29CF\u0338", "NotLeftTriangle": "\u22EA", "NotLeftTriangleEqual": "\u22EC", "NotLess": "\u226E", "NotLessEqual": "\u2270", "NotLessGreater": "\u2278", "NotLessLess": "\u226A\u0338", "NotLessSlantEqual": "\u2A7D\u0338", "NotLessTilde": "\u2274", "NotNestedGreaterGreater": "\u2AA2\u0338", "NotNestedLessLess": "\u2AA1\u0338", "notni": "\u220C", "notniva": "\u220C", "notnivb": "\u22FE", "notnivc": "\u22FD", "NotPrecedes": "\u2280", "NotPrecedesEqual": "\u2AAF\u0338", "NotPrecedesSlantEqual": "\u22E0", "NotReverseElement": "\u220C", "NotRightTriangleBar": "\u29D0\u0338", "NotRightTriangle": "\u22EB", "NotRightTriangleEqual": "\u22ED", "NotSquareSubset": "\u228F\u0338", "NotSquareSubsetEqual": "\u22E2", "NotSquareSuperset": "\u2290\u0338", "NotSquareSupersetEqual": "\u22E3", "NotSubset": "\u2282\u20D2", "NotSubsetEqual": "\u2288", "NotSucceeds": "\u2281", "NotSucceedsEqual": "\u2AB0\u0338", "NotSucceedsSlantEqual": "\u22E1", "NotSucceedsTilde": "\u227F\u0338", "NotSuperset": "\u2283\u20D2", "NotSupersetEqual": "\u2289", "NotTilde": "\u2241", "NotTildeEqual": "\u2244", "NotTildeFullEqual": "\u2247", "NotTildeTilde": "\u2249", "NotVerticalBar": "\u2224", "nparallel": "\u2226", "npar": "\u2226", "nparsl": "\u2AFD\u20E5", "npart": "\u2202\u0338", "npolint": "\u2A14", "npr": "\u2280", "nprcue": "\u22E0", "nprec": "\u2280", "npreceq": "\u2AAF\u0338", "npre": "\u2AAF\u0338", "nrarrc": "\u2933\u0338", "nrarr": "\u219B", "nrArr": "\u21CF", "nrarrw": "\u219D\u0338", "nrightarrow": "\u219B", "nRightarrow": "\u21CF", "nrtri": "\u22EB", "nrtrie": "\u22ED", "nsc": "\u2281", "nsccue": "\u22E1", "nsce": "\u2AB0\u0338", "Nscr": "\uD835\uDCA9", "nscr": "\uD835\uDCC3", "nshortmid": "\u2224", "nshortparallel": "\u2226", "nsim": "\u2241", "nsime": "\u2244", "nsimeq": "\u2244", "nsmid": "\u2224", "nspar": "\u2226", "nsqsube": "\u22E2", "nsqsupe": "\u22E3", "nsub": "\u2284", "nsubE": "\u2AC5\u0338", "nsube": "\u2288", "nsubset": "\u2282\u20D2", "nsubseteq": "\u2288", "nsubseteqq": "\u2AC5\u0338", "nsucc": "\u2281", "nsucceq": "\u2AB0\u0338", "nsup": "\u2285", "nsupE": "\u2AC6\u0338", "nsupe": "\u2289", "nsupset": "\u2283\u20D2", "nsupseteq": "\u2289", "nsupseteqq": "\u2AC6\u0338", "ntgl": "\u2279", "Ntilde": "\u00D1", "ntilde": "\u00F1", "ntlg": "\u2278", "ntriangleleft": "\u22EA", "ntrianglelefteq": "\u22EC", "ntriangleright": "\u22EB", "ntrianglerighteq": "\u22ED", "Nu": "\u039D", "nu": "\u03BD", "num": "#", "numero": "\u2116", "numsp": "\u2007", "nvap": "\u224D\u20D2", "nvdash": "\u22AC", "nvDash": "\u22AD", "nVdash": "\u22AE", "nVDash": "\u22AF", "nvge": "\u2265\u20D2", "nvgt": ">\u20D2", "nvHarr": "\u2904", "nvinfin": "\u29DE", "nvlArr": "\u2902", "nvle": "\u2264\u20D2", "nvlt": "<\u20D2", "nvltrie": "\u22B4\u20D2", "nvrArr": "\u2903", "nvrtrie": "\u22B5\u20D2", "nvsim": "\u223C\u20D2", "nwarhk": "\u2923", "nwarr": "\u2196", "nwArr": "\u21D6", "nwarrow": "\u2196", "nwnear": "\u2927", "Oacute": "\u00D3", "oacute": "\u00F3", "oast": "\u229B", "Ocirc": "\u00D4", "ocirc": "\u00F4", "ocir": "\u229A", "Ocy": "\u041E", "ocy": "\u043E", "odash": "\u229D", "Odblac": "\u0150", "odblac": "\u0151", "odiv": "\u2A38", "odot": "\u2299", "odsold": "\u29BC", "OElig": "\u0152", "oelig": "\u0153", "ofcir": "\u29BF", "Ofr": "\uD835\uDD12", "ofr": "\uD835\uDD2C", "ogon": "\u02DB", "Ograve": "\u00D2", "ograve": "\u00F2", "ogt": "\u29C1", "ohbar": "\u29B5", "ohm": "\u03A9", "oint": "\u222E", "olarr": "\u21BA", "olcir": "\u29BE", "olcross": "\u29BB", "oline": "\u203E", "olt": "\u29C0", "Omacr": "\u014C", "omacr": "\u014D", "Omega": "\u03A9", "omega": "\u03C9", "Omicron": "\u039F", "omicron": "\u03BF", "omid": "\u29B6", "ominus": "\u2296", "Oopf": "\uD835\uDD46", "oopf": "\uD835\uDD60", "opar": "\u29B7", "OpenCurlyDoubleQuote": "\u201C", "OpenCurlyQuote": "\u2018", "operp": "\u29B9", "oplus": "\u2295", "orarr": "\u21BB", "Or": "\u2A54", "or": "\u2228", "ord": "\u2A5D", "order": "\u2134", "orderof": "\u2134", "ordf": "\u00AA", "ordm": "\u00BA", "origof": "\u22B6", "oror": "\u2A56", "orslope": "\u2A57", "orv": "\u2A5B", "oS": "\u24C8", "Oscr": "\uD835\uDCAA", "oscr": "\u2134", "Oslash": "\u00D8", "oslash": "\u00F8", "osol": "\u2298", "Otilde": "\u00D5", "otilde": "\u00F5", "otimesas": "\u2A36", "Otimes": "\u2A37", "otimes": "\u2297", "Ouml": "\u00D6", "ouml": "\u00F6", "ovbar": "\u233D", "OverBar": "\u203E", "OverBrace": "\u23DE", "OverBracket": "\u23B4", "OverParenthesis": "\u23DC", "para": "\u00B6", "parallel": "\u2225", "par": "\u2225", "parsim": "\u2AF3", "parsl": "\u2AFD", "part": "\u2202", "PartialD": "\u2202", "Pcy": "\u041F", "pcy": "\u043F", "percnt": "%", "period": ".", "permil": "\u2030", "perp": "\u22A5", "pertenk": "\u2031", "Pfr": "\uD835\uDD13", "pfr": "\uD835\uDD2D", "Phi": "\u03A6", "phi": "\u03C6", "phiv": "\u03D5", "phmmat": "\u2133", "phone": "\u260E", "Pi": "\u03A0", "pi": "\u03C0", "pitchfork": "\u22D4", "piv": "\u03D6", "planck": "\u210F", "planckh": "\u210E", "plankv": "\u210F", "plusacir": "\u2A23", "plusb": "\u229E", "pluscir": "\u2A22", "plus": "+", "plusdo": "\u2214", "plusdu": "\u2A25", "pluse": "\u2A72", "PlusMinus": "\u00B1", "plusmn": "\u00B1", "plussim": "\u2A26", "plustwo": "\u2A27", "pm": "\u00B1", "Poincareplane": "\u210C", "pointint": "\u2A15", "popf": "\uD835\uDD61", "Popf": "\u2119", "pound": "\u00A3", "prap": "\u2AB7", "Pr": "\u2ABB", "pr": "\u227A", "prcue": "\u227C", "precapprox": "\u2AB7", "prec": "\u227A", "preccurlyeq": "\u227C", "Precedes": "\u227A", "PrecedesEqual": "\u2AAF", "PrecedesSlantEqual": "\u227C", "PrecedesTilde": "\u227E", "preceq": "\u2AAF", "precnapprox": "\u2AB9", "precneqq": "\u2AB5", "precnsim": "\u22E8", "pre": "\u2AAF", "prE": "\u2AB3", "precsim": "\u227E", "prime": "\u2032", "Prime": "\u2033", "primes": "\u2119", "prnap": "\u2AB9", "prnE": "\u2AB5", "prnsim": "\u22E8", "prod": "\u220F", "Product": "\u220F", "profalar": "\u232E", "profline": "\u2312", "profsurf": "\u2313", "prop": "\u221D", "Proportional": "\u221D", "Proportion": "\u2237", "propto": "\u221D", "prsim": "\u227E", "prurel": "\u22B0", "Pscr": "\uD835\uDCAB", "pscr": "\uD835\uDCC5", "Psi": "\u03A8", "psi": "\u03C8", "puncsp": "\u2008", "Qfr": "\uD835\uDD14", "qfr": "\uD835\uDD2E", "qint": "\u2A0C", "qopf": "\uD835\uDD62", "Qopf": "\u211A", "qprime": "\u2057", "Qscr": "\uD835\uDCAC", "qscr": "\uD835\uDCC6", "quaternions": "\u210D", "quatint": "\u2A16", "quest": "?", "questeq": "\u225F", "quot": "\"", "QUOT": "\"", "rAarr": "\u21DB", "race": "\u223D\u0331", "Racute": "\u0154", "racute": "\u0155", "radic": "\u221A", "raemptyv": "\u29B3", "rang": "\u27E9", "Rang": "\u27EB", "rangd": "\u2992", "range": "\u29A5", "rangle": "\u27E9", "raquo": "\u00BB", "rarrap": "\u2975", "rarrb": "\u21E5", "rarrbfs": "\u2920", "rarrc": "\u2933", "rarr": "\u2192", "Rarr": "\u21A0", "rArr": "\u21D2", "rarrfs": "\u291E", "rarrhk": "\u21AA", "rarrlp": "\u21AC", "rarrpl": "\u2945", "rarrsim": "\u2974", "Rarrtl": "\u2916", "rarrtl": "\u21A3", "rarrw": "\u219D", "ratail": "\u291A", "rAtail": "\u291C", "ratio": "\u2236", "rationals": "\u211A", "rbarr": "\u290D", "rBarr": "\u290F", "RBarr": "\u2910", "rbbrk": "\u2773", "rbrace": "}", "rbrack": "]", "rbrke": "\u298C", "rbrksld": "\u298E", "rbrkslu": "\u2990", "Rcaron": "\u0158", "rcaron": "\u0159", "Rcedil": "\u0156", "rcedil": "\u0157", "rceil": "\u2309", "rcub": "}", "Rcy": "\u0420", "rcy": "\u0440", "rdca": "\u2937", "rdldhar": "\u2969", "rdquo": "\u201D", "rdquor": "\u201D", "rdsh": "\u21B3", "real": "\u211C", "realine": "\u211B", "realpart": "\u211C", "reals": "\u211D", "Re": "\u211C", "rect": "\u25AD", "reg": "\u00AE", "REG": "\u00AE", "ReverseElement": "\u220B", "ReverseEquilibrium": "\u21CB", "ReverseUpEquilibrium": "\u296F", "rfisht": "\u297D", "rfloor": "\u230B", "rfr": "\uD835\uDD2F", "Rfr": "\u211C", "rHar": "\u2964", "rhard": "\u21C1", "rharu": "\u21C0", "rharul": "\u296C", "Rho": "\u03A1", "rho": "\u03C1", "rhov": "\u03F1", "RightAngleBracket": "\u27E9", "RightArrowBar": "\u21E5", "rightarrow": "\u2192", "RightArrow": "\u2192", "Rightarrow": "\u21D2", "RightArrowLeftArrow": "\u21C4", "rightarrowtail": "\u21A3", "RightCeiling": "\u2309", "RightDoubleBracket": "\u27E7", "RightDownTeeVector": "\u295D", "RightDownVectorBar": "\u2955", "RightDownVector": "\u21C2", "RightFloor": "\u230B", "rightharpoondown": "\u21C1", "rightharpoonup": "\u21C0", "rightleftarrows": "\u21C4", "rightleftharpoons": "\u21CC", "rightrightarrows": "\u21C9", "rightsquigarrow": "\u219D", "RightTeeArrow": "\u21A6", "RightTee": "\u22A2", "RightTeeVector": "\u295B", "rightthreetimes": "\u22CC", "RightTriangleBar": "\u29D0", "RightTriangle": "\u22B3", "RightTriangleEqual": "\u22B5", "RightUpDownVector": "\u294F", "RightUpTeeVector": "\u295C", "RightUpVectorBar": "\u2954", "RightUpVector": "\u21BE", "RightVectorBar": "\u2953", "RightVector": "\u21C0", "ring": "\u02DA", "risingdotseq": "\u2253", "rlarr": "\u21C4", "rlhar": "\u21CC", "rlm": "\u200F", "rmoustache": "\u23B1", "rmoust": "\u23B1", "rnmid": "\u2AEE", "roang": "\u27ED", "roarr": "\u21FE", "robrk": "\u27E7", "ropar": "\u2986", "ropf": "\uD835\uDD63", "Ropf": "\u211D", "roplus": "\u2A2E", "rotimes": "\u2A35", "RoundImplies": "\u2970", "rpar": ")", "rpargt": "\u2994", "rppolint": "\u2A12", "rrarr": "\u21C9", "Rrightarrow": "\u21DB", "rsaquo": "\u203A", "rscr": "\uD835\uDCC7", "Rscr": "\u211B", "rsh": "\u21B1", "Rsh": "\u21B1", "rsqb": "]", "rsquo": "\u2019", "rsquor": "\u2019", "rthree": "\u22CC", "rtimes": "\u22CA", "rtri": "\u25B9", "rtrie": "\u22B5", "rtrif": "\u25B8", "rtriltri": "\u29CE", "RuleDelayed": "\u29F4", "ruluhar": "\u2968", "rx": "\u211E", "Sacute": "\u015A", "sacute": "\u015B", "sbquo": "\u201A", "scap": "\u2AB8", "Scaron": "\u0160", "scaron": "\u0161", "Sc": "\u2ABC", "sc": "\u227B", "sccue": "\u227D", "sce": "\u2AB0", "scE": "\u2AB4", "Scedil": "\u015E", "scedil": "\u015F", "Scirc": "\u015C", "scirc": "\u015D", "scnap": "\u2ABA", "scnE": "\u2AB6", "scnsim": "\u22E9", "scpolint": "\u2A13", "scsim": "\u227F", "Scy": "\u0421", "scy": "\u0441", "sdotb": "\u22A1", "sdot": "\u22C5", "sdote": "\u2A66", "searhk": "\u2925", "searr": "\u2198", "seArr": "\u21D8", "searrow": "\u2198", "sect": "\u00A7", "semi": ";", "seswar": "\u2929", "setminus": "\u2216", "setmn": "\u2216", "sext": "\u2736", "Sfr": "\uD835\uDD16", "sfr": "\uD835\uDD30", "sfrown": "\u2322", "sharp": "\u266F", "SHCHcy": "\u0429", "shchcy": "\u0449", "SHcy": "\u0428", "shcy": "\u0448", "ShortDownArrow": "\u2193", "ShortLeftArrow": "\u2190", "shortmid": "\u2223", "shortparallel": "\u2225", "ShortRightArrow": "\u2192", "ShortUpArrow": "\u2191", "shy": "\u00AD", "Sigma": "\u03A3", "sigma": "\u03C3", "sigmaf": "\u03C2", "sigmav": "\u03C2", "sim": "\u223C", "simdot": "\u2A6A", "sime": "\u2243", "simeq": "\u2243", "simg": "\u2A9E", "simgE": "\u2AA0", "siml": "\u2A9D", "simlE": "\u2A9F", "simne": "\u2246", "simplus": "\u2A24", "simrarr": "\u2972", "slarr": "\u2190", "SmallCircle": "\u2218", "smallsetminus": "\u2216", "smashp": "\u2A33", "smeparsl": "\u29E4", "smid": "\u2223", "smile": "\u2323", "smt": "\u2AAA", "smte": "\u2AAC", "smtes": "\u2AAC\uFE00", "SOFTcy": "\u042C", "softcy": "\u044C", "solbar": "\u233F", "solb": "\u29C4", "sol": "/", "Sopf": "\uD835\uDD4A", "sopf": "\uD835\uDD64", "spades": "\u2660", "spadesuit": "\u2660", "spar": "\u2225", "sqcap": "\u2293", "sqcaps": "\u2293\uFE00", "sqcup": "\u2294", "sqcups": "\u2294\uFE00", "Sqrt": "\u221A", "sqsub": "\u228F", "sqsube": "\u2291", "sqsubset": "\u228F", "sqsubseteq": "\u2291", "sqsup": "\u2290", "sqsupe": "\u2292", "sqsupset": "\u2290", "sqsupseteq": "\u2292", "square": "\u25A1", "Square": "\u25A1", "SquareIntersection": "\u2293", "SquareSubset": "\u228F", "SquareSubsetEqual": "\u2291", "SquareSuperset": "\u2290", "SquareSupersetEqual": "\u2292", "SquareUnion": "\u2294", "squarf": "\u25AA", "squ": "\u25A1", "squf": "\u25AA", "srarr": "\u2192", "Sscr": "\uD835\uDCAE", "sscr": "\uD835\uDCC8", "ssetmn": "\u2216", "ssmile": "\u2323", "sstarf": "\u22C6", "Star": "\u22C6", "star": "\u2606", "starf": "\u2605", "straightepsilon": "\u03F5", "straightphi": "\u03D5", "strns": "\u00AF", "sub": "\u2282", "Sub": "\u22D0", "subdot": "\u2ABD", "subE": "\u2AC5", "sube": "\u2286", "subedot": "\u2AC3", "submult": "\u2AC1", "subnE": "\u2ACB", "subne": "\u228A", "subplus": "\u2ABF", "subrarr": "\u2979", "subset": "\u2282", "Subset": "\u22D0", "subseteq": "\u2286", "subseteqq": "\u2AC5", "SubsetEqual": "\u2286", "subsetneq": "\u228A", "subsetneqq": "\u2ACB", "subsim": "\u2AC7", "subsub": "\u2AD5", "subsup": "\u2AD3", "succapprox": "\u2AB8", "succ": "\u227B", "succcurlyeq": "\u227D", "Succeeds": "\u227B", "SucceedsEqual": "\u2AB0", "SucceedsSlantEqual": "\u227D", "SucceedsTilde": "\u227F", "succeq": "\u2AB0", "succnapprox": "\u2ABA", "succneqq": "\u2AB6", "succnsim": "\u22E9", "succsim": "\u227F", "SuchThat": "\u220B", "sum": "\u2211", "Sum": "\u2211", "sung": "\u266A", "sup1": "\u00B9", "sup2": "\u00B2", "sup3": "\u00B3", "sup": "\u2283", "Sup": "\u22D1", "supdot": "\u2ABE", "supdsub": "\u2AD8", "supE": "\u2AC6", "supe": "\u2287", "supedot": "\u2AC4", "Superset": "\u2283", "SupersetEqual": "\u2287", "suphsol": "\u27C9", "suphsub": "\u2AD7", "suplarr": "\u297B", "supmult": "\u2AC2", "supnE": "\u2ACC", "supne": "\u228B", "supplus": "\u2AC0", "supset": "\u2283", "Supset": "\u22D1", "supseteq": "\u2287", "supseteqq": "\u2AC6", "supsetneq": "\u228B", "supsetneqq": "\u2ACC", "supsim": "\u2AC8", "supsub": "\u2AD4", "supsup": "\u2AD6", "swarhk": "\u2926", "swarr": "\u2199", "swArr": "\u21D9", "swarrow": "\u2199", "swnwar": "\u292A", "szlig": "\u00DF", "Tab": "\t", "target": "\u2316", "Tau": "\u03A4", "tau": "\u03C4", "tbrk": "\u23B4", "Tcaron": "\u0164", "tcaron": "\u0165", "Tcedil": "\u0162", "tcedil": "\u0163", "Tcy": "\u0422", "tcy": "\u0442", "tdot": "\u20DB", "telrec": "\u2315", "Tfr": "\uD835\uDD17", "tfr": "\uD835\uDD31", "there4": "\u2234", "therefore": "\u2234", "Therefore": "\u2234", "Theta": "\u0398", "theta": "\u03B8", "thetasym": "\u03D1", "thetav": "\u03D1", "thickapprox": "\u2248", "thicksim": "\u223C", "ThickSpace": "\u205F\u200A", "ThinSpace": "\u2009", "thinsp": "\u2009", "thkap": "\u2248", "thksim": "\u223C", "THORN": "\u00DE", "thorn": "\u00FE", "tilde": "\u02DC", "Tilde": "\u223C", "TildeEqual": "\u2243", "TildeFullEqual": "\u2245", "TildeTilde": "\u2248", "timesbar": "\u2A31", "timesb": "\u22A0", "times": "\u00D7", "timesd": "\u2A30", "tint": "\u222D", "toea": "\u2928", "topbot": "\u2336", "topcir": "\u2AF1", "top": "\u22A4", "Topf": "\uD835\uDD4B", "topf": "\uD835\uDD65", "topfork": "\u2ADA", "tosa": "\u2929", "tprime": "\u2034", "trade": "\u2122", "TRADE": "\u2122", "triangle": "\u25B5", "triangledown": "\u25BF", "triangleleft": "\u25C3", "trianglelefteq": "\u22B4", "triangleq": "\u225C", "triangleright": "\u25B9", "trianglerighteq": "\u22B5", "tridot": "\u25EC", "trie": "\u225C", "triminus": "\u2A3A", "TripleDot": "\u20DB", "triplus": "\u2A39", "trisb": "\u29CD", "tritime": "\u2A3B", "trpezium": "\u23E2", "Tscr": "\uD835\uDCAF", "tscr": "\uD835\uDCC9", "TScy": "\u0426", "tscy": "\u0446", "TSHcy": "\u040B", "tshcy": "\u045B", "Tstrok": "\u0166", "tstrok": "\u0167", "twixt": "\u226C", "twoheadleftarrow": "\u219E", "twoheadrightarrow": "\u21A0", "Uacute": "\u00DA", "uacute": "\u00FA", "uarr": "\u2191", "Uarr": "\u219F", "uArr": "\u21D1", "Uarrocir": "\u2949", "Ubrcy": "\u040E", "ubrcy": "\u045E", "Ubreve": "\u016C", "ubreve": "\u016D", "Ucirc": "\u00DB", "ucirc": "\u00FB", "Ucy": "\u0423", "ucy": "\u0443", "udarr": "\u21C5", "Udblac": "\u0170", "udblac": "\u0171", "udhar": "\u296E", "ufisht": "\u297E", "Ufr": "\uD835\uDD18", "ufr": "\uD835\uDD32", "Ugrave": "\u00D9", "ugrave": "\u00F9", "uHar": "\u2963", "uharl": "\u21BF", "uharr": "\u21BE", "uhblk": "\u2580", "ulcorn": "\u231C", "ulcorner": "\u231C", "ulcrop": "\u230F", "ultri": "\u25F8", "Umacr": "\u016A", "umacr": "\u016B", "uml": "\u00A8", "UnderBar": "_", "UnderBrace": "\u23DF", "UnderBracket": "\u23B5", "UnderParenthesis": "\u23DD", "Union": "\u22C3", "UnionPlus": "\u228E", "Uogon": "\u0172", "uogon": "\u0173", "Uopf": "\uD835\uDD4C", "uopf": "\uD835\uDD66", "UpArrowBar": "\u2912", "uparrow": "\u2191", "UpArrow": "\u2191", "Uparrow": "\u21D1", "UpArrowDownArrow": "\u21C5", "updownarrow": "\u2195", "UpDownArrow": "\u2195", "Updownarrow": "\u21D5", "UpEquilibrium": "\u296E", "upharpoonleft": "\u21BF", "upharpoonright": "\u21BE", "uplus": "\u228E", "UpperLeftArrow": "\u2196", "UpperRightArrow": "\u2197", "upsi": "\u03C5", "Upsi": "\u03D2", "upsih": "\u03D2", "Upsilon": "\u03A5", "upsilon": "\u03C5", "UpTeeArrow": "\u21A5", "UpTee": "\u22A5", "upuparrows": "\u21C8", "urcorn": "\u231D", "urcorner": "\u231D", "urcrop": "\u230E", "Uring": "\u016E", "uring": "\u016F", "urtri": "\u25F9", "Uscr": "\uD835\uDCB0", "uscr": "\uD835\uDCCA", "utdot": "\u22F0", "Utilde": "\u0168", "utilde": "\u0169", "utri": "\u25B5", "utrif": "\u25B4", "uuarr": "\u21C8", "Uuml": "\u00DC", "uuml": "\u00FC", "uwangle": "\u29A7", "vangrt": "\u299C", "varepsilon": "\u03F5", "varkappa": "\u03F0", "varnothing": "\u2205", "varphi": "\u03D5", "varpi": "\u03D6", "varpropto": "\u221D", "varr": "\u2195", "vArr": "\u21D5", "varrho": "\u03F1", "varsigma": "\u03C2", "varsubsetneq": "\u228A\uFE00", "varsubsetneqq": "\u2ACB\uFE00", "varsupsetneq": "\u228B\uFE00", "varsupsetneqq": "\u2ACC\uFE00", "vartheta": "\u03D1", "vartriangleleft": "\u22B2", "vartriangleright": "\u22B3", "vBar": "\u2AE8", "Vbar": "\u2AEB", "vBarv": "\u2AE9", "Vcy": "\u0412", "vcy": "\u0432", "vdash": "\u22A2", "vDash": "\u22A8", "Vdash": "\u22A9", "VDash": "\u22AB", "Vdashl": "\u2AE6", "veebar": "\u22BB", "vee": "\u2228", "Vee": "\u22C1", "veeeq": "\u225A", "vellip": "\u22EE", "verbar": "|", "Verbar": "\u2016", "vert": "|", "Vert": "\u2016", "VerticalBar": "\u2223", "VerticalLine": "|", "VerticalSeparator": "\u2758", "VerticalTilde": "\u2240", "VeryThinSpace": "\u200A", "Vfr": "\uD835\uDD19", "vfr": "\uD835\uDD33", "vltri": "\u22B2", "vnsub": "\u2282\u20D2", "vnsup": "\u2283\u20D2", "Vopf": "\uD835\uDD4D", "vopf": "\uD835\uDD67", "vprop": "\u221D", "vrtri": "\u22B3", "Vscr": "\uD835\uDCB1", "vscr": "\uD835\uDCCB", "vsubnE": "\u2ACB\uFE00", "vsubne": "\u228A\uFE00", "vsupnE": "\u2ACC\uFE00", "vsupne": "\u228B\uFE00", "Vvdash": "\u22AA", "vzigzag": "\u299A", "Wcirc": "\u0174", "wcirc": "\u0175", "wedbar": "\u2A5F", "wedge": "\u2227", "Wedge": "\u22C0", "wedgeq": "\u2259", "weierp": "\u2118", "Wfr": "\uD835\uDD1A", "wfr": "\uD835\uDD34", "Wopf": "\uD835\uDD4E", "wopf": "\uD835\uDD68", "wp": "\u2118", "wr": "\u2240", "wreath": "\u2240", "Wscr": "\uD835\uDCB2", "wscr": "\uD835\uDCCC", "xcap": "\u22C2", "xcirc": "\u25EF", "xcup": "\u22C3", "xdtri": "\u25BD", "Xfr": "\uD835\uDD1B", "xfr": "\uD835\uDD35", "xharr": "\u27F7", "xhArr": "\u27FA", "Xi": "\u039E", "xi": "\u03BE", "xlarr": "\u27F5", "xlArr": "\u27F8", "xmap": "\u27FC", "xnis": "\u22FB", "xodot": "\u2A00", "Xopf": "\uD835\uDD4F", "xopf": "\uD835\uDD69", "xoplus": "\u2A01", "xotime": "\u2A02", "xrarr": "\u27F6", "xrArr": "\u27F9", "Xscr": "\uD835\uDCB3", "xscr": "\uD835\uDCCD", "xsqcup": "\u2A06", "xuplus": "\u2A04", "xutri": "\u25B3", "xvee": "\u22C1", "xwedge": "\u22C0", "Yacute": "\u00DD", "yacute": "\u00FD", "YAcy": "\u042F", "yacy": "\u044F", "Ycirc": "\u0176", "ycirc": "\u0177", "Ycy": "\u042B", "ycy": "\u044B", "yen": "\u00A5", "Yfr": "\uD835\uDD1C", "yfr": "\uD835\uDD36", "YIcy": "\u0407", "yicy": "\u0457", "Yopf": "\uD835\uDD50", "yopf": "\uD835\uDD6A", "Yscr": "\uD835\uDCB4", "yscr": "\uD835\uDCCE", "YUcy": "\u042E", "yucy": "\u044E", "yuml": "\u00FF", "Yuml": "\u0178", "Zacute": "\u0179", "zacute": "\u017A", "Zcaron": "\u017D", "zcaron": "\u017E", "Zcy": "\u0417", "zcy": "\u0437", "Zdot": "\u017B", "zdot": "\u017C", "zeetrf": "\u2128", "ZeroWidthSpace": "\u200B", "Zeta": "\u0396", "zeta": "\u03B6", "zfr": "\uD835\uDD37", "Zfr": "\u2128", "ZHcy": "\u0416", "zhcy": "\u0436", "zigrarr": "\u21DD", "zopf": "\uD835\uDD6B", "Zopf": "\u2124", "Zscr": "\uD835\uDCB5", "zscr": "\uD835\uDCCF", "zwj": "\u200D", "zwnj": "\u200C" } + + },{}],53:[function(require,module,exports){ + 'use strict'; + + + //////////////////////////////////////////////////////////////////////////////// + // Helpers + + // Merge objects + // + function assign(obj /*from1, from2, from3, ...*/) { + var sources = Array.prototype.slice.call(arguments, 1); + + sources.forEach(function (source) { + if (!source) { return; } + + Object.keys(source).forEach(function (key) { + obj[key] = source[key]; + }); + }); + + return obj; + } + + function _class(obj) { return Object.prototype.toString.call(obj); } + function isString(obj) { return _class(obj) === '[object String]'; } + function isObject(obj) { return _class(obj) === '[object Object]'; } + function isRegExp(obj) { return _class(obj) === '[object RegExp]'; } + function isFunction(obj) { return _class(obj) === '[object Function]'; } + + + function escapeRE(str) { return str.replace(/[.?*+^$[\]\\(){}|-]/g, '\\$&'); } + + //////////////////////////////////////////////////////////////////////////////// + + + var defaultOptions = { + fuzzyLink: true, + fuzzyEmail: true, + fuzzyIP: false + }; + + + function isOptionsObj(obj) { + return Object.keys(obj || {}).reduce(function (acc, k) { + return acc || defaultOptions.hasOwnProperty(k); + }, false); + } + + + var defaultSchemas = { + 'http:': { + validate: function (text, pos, self) { + var tail = text.slice(pos); + + if (!self.re.http) { + // compile lazily, because "host"-containing variables can change on tlds update. + self.re.http = new RegExp( + '^\\/\\/' + self.re.src_auth + self.re.src_host_port_strict + self.re.src_path, 'i' + ); + } + if (self.re.http.test(tail)) { + return tail.match(self.re.http)[0].length; + } + return 0; + } + }, + 'https:': 'http:', + 'ftp:': 'http:', + '//': { + validate: function (text, pos, self) { + var tail = text.slice(pos); + + if (!self.re.no_http) { + // compile lazily, because "host"-containing variables can change on tlds update. + self.re.no_http = new RegExp( + '^' + + self.re.src_auth + + // Don't allow single-level domains, because of false positives like '//test' + // with code comments + '(?:localhost|(?:(?:' + self.re.src_domain + ')\\.)+' + self.re.src_domain_root + ')' + + self.re.src_port + + self.re.src_host_terminator + + self.re.src_path, + + 'i' + ); + } + + if (self.re.no_http.test(tail)) { + // should not be `://` & `///`, that protects from errors in protocol name + if (pos >= 3 && text[pos - 3] === ':') { return 0; } + if (pos >= 3 && text[pos - 3] === '/') { return 0; } + return tail.match(self.re.no_http)[0].length; + } + return 0; + } + }, + 'mailto:': { + validate: function (text, pos, self) { + var tail = text.slice(pos); + + if (!self.re.mailto) { + self.re.mailto = new RegExp( + '^' + self.re.src_email_name + '@' + self.re.src_host_strict, 'i' + ); + } + if (self.re.mailto.test(tail)) { + return tail.match(self.re.mailto)[0].length; + } + return 0; + } + } + }; + + /*eslint-disable max-len*/ + + // RE pattern for 2-character tlds (autogenerated by ./support/tlds_2char_gen.js) + var tlds_2ch_src_re = 'a[cdefgilmnoqrstuwxz]|b[abdefghijmnorstvwyz]|c[acdfghiklmnoruvwxyz]|d[ejkmoz]|e[cegrstu]|f[ijkmor]|g[abdefghilmnpqrstuwy]|h[kmnrtu]|i[delmnoqrst]|j[emop]|k[eghimnprwyz]|l[abcikrstuvy]|m[acdeghklmnopqrstuvwxyz]|n[acefgilopruz]|om|p[aefghklmnrstwy]|qa|r[eosuw]|s[abcdeghijklmnortuvxyz]|t[cdfghjklmnortvwz]|u[agksyz]|v[aceginu]|w[fs]|y[et]|z[amw]'; + + // DON'T try to make PRs with changes. Extend TLDs with LinkifyIt.tlds() instead + var tlds_default = 'biz|com|edu|gov|net|org|pro|web|xxx|aero|asia|coop|info|museum|name|shop|рф'.split('|'); + + /*eslint-enable max-len*/ + + //////////////////////////////////////////////////////////////////////////////// + + function resetScanCache(self) { + self.__index__ = -1; + self.__text_cache__ = ''; + } + + function createValidator(re) { + return function (text, pos) { + var tail = text.slice(pos); + + if (re.test(tail)) { + return tail.match(re)[0].length; + } + return 0; + }; + } + + function createNormalizer() { + return function (match, self) { + self.normalize(match); + }; + } + + // Schemas compiler. Build regexps. + // + function compile(self) { + + // Load & clone RE patterns. + var re = self.re = require('./lib/re')(self.__opts__); + + // Define dynamic patterns + var tlds = self.__tlds__.slice(); + + self.onCompile(); + + if (!self.__tlds_replaced__) { + tlds.push(tlds_2ch_src_re); + } + tlds.push(re.src_xn); + + re.src_tlds = tlds.join('|'); + + function untpl(tpl) { return tpl.replace('%TLDS%', re.src_tlds); } + + re.email_fuzzy = RegExp(untpl(re.tpl_email_fuzzy), 'i'); + re.link_fuzzy = RegExp(untpl(re.tpl_link_fuzzy), 'i'); + re.link_no_ip_fuzzy = RegExp(untpl(re.tpl_link_no_ip_fuzzy), 'i'); + re.host_fuzzy_test = RegExp(untpl(re.tpl_host_fuzzy_test), 'i'); + + // + // Compile each schema + // + + var aliases = []; + + self.__compiled__ = {}; // Reset compiled data + + function schemaError(name, val) { + throw new Error('(LinkifyIt) Invalid schema "' + name + '": ' + val); + } + + Object.keys(self.__schemas__).forEach(function (name) { + var val = self.__schemas__[name]; + + // skip disabled methods + if (val === null) { return; } + + var compiled = { validate: null, link: null }; + + self.__compiled__[name] = compiled; + + if (isObject(val)) { + if (isRegExp(val.validate)) { + compiled.validate = createValidator(val.validate); + } else if (isFunction(val.validate)) { + compiled.validate = val.validate; + } else { + schemaError(name, val); + } + + if (isFunction(val.normalize)) { + compiled.normalize = val.normalize; + } else if (!val.normalize) { + compiled.normalize = createNormalizer(); + } else { + schemaError(name, val); + } + + return; + } + + if (isString(val)) { + aliases.push(name); + return; + } + + schemaError(name, val); + }); + + // + // Compile postponed aliases + // + + aliases.forEach(function (alias) { + if (!self.__compiled__[self.__schemas__[alias]]) { + // Silently fail on missed schemas to avoid errons on disable. + // schemaError(alias, self.__schemas__[alias]); + return; + } + + self.__compiled__[alias].validate = + self.__compiled__[self.__schemas__[alias]].validate; + self.__compiled__[alias].normalize = + self.__compiled__[self.__schemas__[alias]].normalize; + }); + + // + // Fake record for guessed links + // + self.__compiled__[''] = { validate: null, normalize: createNormalizer() }; + + // + // Build schema condition + // + var slist = Object.keys(self.__compiled__) + .filter(function (name) { + // Filter disabled & fake schemas + return name.length > 0 && self.__compiled__[name]; + }) + .map(escapeRE) + .join('|'); + // (?!_) cause 1.5x slowdown + self.re.schema_test = RegExp('(^|(?!_)(?:[><\uff5c]|' + re.src_ZPCc + '))(' + slist + ')', 'i'); + self.re.schema_search = RegExp('(^|(?!_)(?:[><\uff5c]|' + re.src_ZPCc + '))(' + slist + ')', 'ig'); + + self.re.pretest = RegExp( + '(' + self.re.schema_test.source + ')|(' + self.re.host_fuzzy_test.source + ')|@', + 'i' + ); + + // + // Cleanup + // + + resetScanCache(self); + } + + /** + * class Match + * + * Match result. Single element of array, returned by [[LinkifyIt#match]] + **/ + function Match(self, shift) { + var start = self.__index__, + end = self.__last_index__, + text = self.__text_cache__.slice(start, end); + + /** + * Match#schema -> String + * + * Prefix (protocol) for matched string. + **/ + this.schema = self.__schema__.toLowerCase(); + /** + * Match#index -> Number + * + * First position of matched string. + **/ + this.index = start + shift; + /** + * Match#lastIndex -> Number + * + * Next position after matched string. + **/ + this.lastIndex = end + shift; + /** + * Match#raw -> String + * + * Matched string. + **/ + this.raw = text; + /** + * Match#text -> String + * + * Notmalized text of matched string. + **/ + this.text = text; + /** + * Match#url -> String + * + * Normalized url of matched string. + **/ + this.url = text; + } + + function createMatch(self, shift) { + var match = new Match(self, shift); + + self.__compiled__[match.schema].normalize(match, self); + + return match; + } + + + /** + * class LinkifyIt + **/ + + /** + * new LinkifyIt(schemas, options) + * - schemas (Object): Optional. Additional schemas to validate (prefix/validator) + * - options (Object): { fuzzyLink|fuzzyEmail|fuzzyIP: true|false } + * + * Creates new linkifier instance with optional additional schemas. + * Can be called without `new` keyword for convenience. + * + * By default understands: + * + * - `http(s)://...` , `ftp://...`, `mailto:...` & `//...` links + * - "fuzzy" links and emails (example.com, foo@bar.com). + * + * `schemas` is an object, where each key/value describes protocol/rule: + * + * - __key__ - link prefix (usually, protocol name with `:` at the end, `skype:` + * for example). `linkify-it` makes shure that prefix is not preceeded with + * alphanumeric char and symbols. Only whitespaces and punctuation allowed. + * - __value__ - rule to check tail after link prefix + * - _String_ - just alias to existing rule + * - _Object_ + * - _validate_ - validator function (should return matched length on success), + * or `RegExp`. + * - _normalize_ - optional function to normalize text & url of matched result + * (for example, for @twitter mentions). + * + * `options`: + * + * - __fuzzyLink__ - recognige URL-s without `http(s):` prefix. Default `true`. + * - __fuzzyIP__ - allow IPs in fuzzy links above. Can conflict with some texts + * like version numbers. Default `false`. + * - __fuzzyEmail__ - recognize emails without `mailto:` prefix. + * + **/ + function LinkifyIt(schemas, options) { + if (!(this instanceof LinkifyIt)) { + return new LinkifyIt(schemas, options); + } + + if (!options) { + if (isOptionsObj(schemas)) { + options = schemas; + schemas = {}; + } + } + + this.__opts__ = assign({}, defaultOptions, options); + + // Cache last tested result. Used to skip repeating steps on next `match` call. + this.__index__ = -1; + this.__last_index__ = -1; // Next scan position + this.__schema__ = ''; + this.__text_cache__ = ''; + + this.__schemas__ = assign({}, defaultSchemas, schemas); + this.__compiled__ = {}; + + this.__tlds__ = tlds_default; + this.__tlds_replaced__ = false; + + this.re = {}; + + compile(this); + } + + + /** chainable + * LinkifyIt#add(schema, definition) + * - schema (String): rule name (fixed pattern prefix) + * - definition (String|RegExp|Object): schema definition + * + * Add new rule definition. See constructor description for details. + **/ + LinkifyIt.prototype.add = function add(schema, definition) { + this.__schemas__[schema] = definition; + compile(this); + return this; + }; + + + /** chainable + * LinkifyIt#set(options) + * - options (Object): { fuzzyLink|fuzzyEmail|fuzzyIP: true|false } + * + * Set recognition options for links without schema. + **/ + LinkifyIt.prototype.set = function set(options) { + this.__opts__ = assign(this.__opts__, options); + return this; + }; + + + /** + * LinkifyIt#test(text) -> Boolean + * + * Searches linkifiable pattern and returns `true` on success or `false` on fail. + **/ + LinkifyIt.prototype.test = function test(text) { + // Reset scan cache + this.__text_cache__ = text; + this.__index__ = -1; + + if (!text.length) { return false; } + + var m, ml, me, len, shift, next, re, tld_pos, at_pos; + + // try to scan for link with schema - that's the most simple rule + if (this.re.schema_test.test(text)) { + re = this.re.schema_search; + re.lastIndex = 0; + while ((m = re.exec(text)) !== null) { + len = this.testSchemaAt(text, m[2], re.lastIndex); + if (len) { + this.__schema__ = m[2]; + this.__index__ = m.index + m[1].length; + this.__last_index__ = m.index + m[0].length + len; + break; + } + } + } + + if (this.__opts__.fuzzyLink && this.__compiled__['http:']) { + // guess schemaless links + tld_pos = text.search(this.re.host_fuzzy_test); + if (tld_pos >= 0) { + // if tld is located after found link - no need to check fuzzy pattern + if (this.__index__ < 0 || tld_pos < this.__index__) { + if ((ml = text.match(this.__opts__.fuzzyIP ? this.re.link_fuzzy : this.re.link_no_ip_fuzzy)) !== null) { + + shift = ml.index + ml[1].length; + + if (this.__index__ < 0 || shift < this.__index__) { + this.__schema__ = ''; + this.__index__ = shift; + this.__last_index__ = ml.index + ml[0].length; + } + } + } + } + } + + if (this.__opts__.fuzzyEmail && this.__compiled__['mailto:']) { + // guess schemaless emails + at_pos = text.indexOf('@'); + if (at_pos >= 0) { + // We can't skip this check, because this cases are possible: + // 192.168.1.1@gmail.com, my.in@example.com + if ((me = text.match(this.re.email_fuzzy)) !== null) { + + shift = me.index + me[1].length; + next = me.index + me[0].length; + + if (this.__index__ < 0 || shift < this.__index__ || + (shift === this.__index__ && next > this.__last_index__)) { + this.__schema__ = 'mailto:'; + this.__index__ = shift; + this.__last_index__ = next; + } + } + } + } + + return this.__index__ >= 0; + }; + + + /** + * LinkifyIt#pretest(text) -> Boolean + * + * Very quick check, that can give false positives. Returns true if link MAY BE + * can exists. Can be used for speed optimization, when you need to check that + * link NOT exists. + **/ + LinkifyIt.prototype.pretest = function pretest(text) { + return this.re.pretest.test(text); + }; + + + /** + * LinkifyIt#testSchemaAt(text, name, position) -> Number + * - text (String): text to scan + * - name (String): rule (schema) name + * - position (Number): text offset to check from + * + * Similar to [[LinkifyIt#test]] but checks only specific protocol tail exactly + * at given position. Returns length of found pattern (0 on fail). + **/ + LinkifyIt.prototype.testSchemaAt = function testSchemaAt(text, schema, pos) { + // If not supported schema check requested - terminate + if (!this.__compiled__[schema.toLowerCase()]) { + return 0; + } + return this.__compiled__[schema.toLowerCase()].validate(text, pos, this); + }; + + + /** + * LinkifyIt#match(text) -> Array|null + * + * Returns array of found link descriptions or `null` on fail. We strongly + * recommend to use [[LinkifyIt#test]] first, for best speed. + * + * ##### Result match description + * + * - __schema__ - link schema, can be empty for fuzzy links, or `//` for + * protocol-neutral links. + * - __index__ - offset of matched text + * - __lastIndex__ - index of next char after mathch end + * - __raw__ - matched text + * - __text__ - normalized text + * - __url__ - link, generated from matched text + **/ + LinkifyIt.prototype.match = function match(text) { + var shift = 0, result = []; + + // Try to take previous element from cache, if .test() called before + if (this.__index__ >= 0 && this.__text_cache__ === text) { + result.push(createMatch(this, shift)); + shift = this.__last_index__; + } + + // Cut head if cache was used + var tail = shift ? text.slice(shift) : text; + + // Scan string until end reached + while (this.test(tail)) { + result.push(createMatch(this, shift)); + + tail = tail.slice(this.__last_index__); + shift += this.__last_index__; + } + + if (result.length) { + return result; + } + + return null; + }; + + + /** chainable + * LinkifyIt#tlds(list [, keepOld]) -> this + * - list (Array): list of tlds + * - keepOld (Boolean): merge with current list if `true` (`false` by default) + * + * Load (or merge) new tlds list. Those are user for fuzzy links (without prefix) + * to avoid false positives. By default this algorythm used: + * + * - hostname with any 2-letter root zones are ok. + * - biz|com|edu|gov|net|org|pro|web|xxx|aero|asia|coop|info|museum|name|shop|рф + * are ok. + * - encoded (`xn--...`) root zones are ok. + * + * If list is replaced, then exact match for 2-chars root zones will be checked. + **/ + LinkifyIt.prototype.tlds = function tlds(list, keepOld) { + list = Array.isArray(list) ? list : [ list ]; + + if (!keepOld) { + this.__tlds__ = list.slice(); + this.__tlds_replaced__ = true; + compile(this); + return this; + } + + this.__tlds__ = this.__tlds__.concat(list) + .sort() + .filter(function (el, idx, arr) { + return el !== arr[idx - 1]; + }) + .reverse(); + + compile(this); + return this; + }; + + /** + * LinkifyIt#normalize(match) + * + * Default normalizer (if schema does not define it's own). + **/ + LinkifyIt.prototype.normalize = function normalize(match) { + + // Do minimal possible changes by default. Need to collect feedback prior + // to move forward https://github.com/markdown-it/linkify-it/issues/1 + + if (!match.schema) { match.url = 'http://' + match.url; } + + if (match.schema === 'mailto:' && !/^mailto:/i.test(match.url)) { + match.url = 'mailto:' + match.url; + } + }; + + + /** + * LinkifyIt#onCompile() + * + * Override to modify basic RegExp-s. + **/ + LinkifyIt.prototype.onCompile = function onCompile() { + }; + + + module.exports = LinkifyIt; + + },{"./lib/re":54}],54:[function(require,module,exports){ + 'use strict'; + + + module.exports = function (opts) { + var re = {}; + + // Use direct extract instead of `regenerate` to reduse browserified size + re.src_Any = require('uc.micro/properties/Any/regex').source; + re.src_Cc = require('uc.micro/categories/Cc/regex').source; + re.src_Z = require('uc.micro/categories/Z/regex').source; + re.src_P = require('uc.micro/categories/P/regex').source; + + // \p{\Z\P\Cc\CF} (white spaces + control + format + punctuation) + re.src_ZPCc = [ re.src_Z, re.src_P, re.src_Cc ].join('|'); + + // \p{\Z\Cc} (white spaces + control) + re.src_ZCc = [ re.src_Z, re.src_Cc ].join('|'); + + // Experimental. List of chars, completely prohibited in links + // because can separate it from other part of text + var text_separators = '[><\uff5c]'; + + // All possible word characters (everything without punctuation, spaces & controls) + // Defined via punctuation & spaces to save space + // Should be something like \p{\L\N\S\M} (\w but without `_`) + re.src_pseudo_letter = '(?:(?!' + text_separators + '|' + re.src_ZPCc + ')' + re.src_Any + ')'; + // The same as abothe but without [0-9] + // var src_pseudo_letter_non_d = '(?:(?![0-9]|' + src_ZPCc + ')' + src_Any + ')'; + + //////////////////////////////////////////////////////////////////////////////// + + re.src_ip4 = + + '(?:(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)'; + + // Prohibit any of "@/[]()" in user/pass to avoid wrong domain fetch. + re.src_auth = '(?:(?:(?!' + re.src_ZCc + '|[@/\\[\\]()]).)+@)?'; + + re.src_port = + + '(?::(?:6(?:[0-4]\\d{3}|5(?:[0-4]\\d{2}|5(?:[0-2]\\d|3[0-5])))|[1-5]?\\d{1,4}))?'; + + re.src_host_terminator = + + '(?=$|' + text_separators + '|' + re.src_ZPCc + ')(?!-|_|:\\d|\\.-|\\.(?!$|' + re.src_ZPCc + '))'; + + re.src_path = + + '(?:' + + '[/?#]' + + '(?:' + + '(?!' + re.src_ZCc + '|' + text_separators + '|[()[\\]{}.,"\'?!\\-]).|' + + '\\[(?:(?!' + re.src_ZCc + '|\\]).)*\\]|' + + '\\((?:(?!' + re.src_ZCc + '|[)]).)*\\)|' + + '\\{(?:(?!' + re.src_ZCc + '|[}]).)*\\}|' + + '\\"(?:(?!' + re.src_ZCc + '|["]).)+\\"|' + + "\\'(?:(?!" + re.src_ZCc + "|[']).)+\\'|" + + "\\'(?=" + re.src_pseudo_letter + '|[-]).|' + // allow `I'm_king` if no pair found + '\\.{2,4}[a-zA-Z0-9%/]|' + // github has ... in commit range links, + // google has .... in links (issue #66) + // Restrict to + // - english + // - percent-encoded + // - parts of file path + // until more examples found. + '\\.(?!' + re.src_ZCc + '|[.]).|' + + (opts && opts['---'] ? + '\\-(?!--(?:[^-]|$))(?:-*)|' // `---` => long dash, terminate + : + '\\-+|' + ) + + '\\,(?!' + re.src_ZCc + ').|' + // allow `,,,` in paths + '\\!(?!' + re.src_ZCc + '|[!]).|' + + '\\?(?!' + re.src_ZCc + '|[?]).' + + ')+' + + '|\\/' + + ')?'; + + // Allow anything in markdown spec, forbid quote (") at the first position + // because emails enclosed in quotes are far more common + re.src_email_name = + + '[\\-;:&=\\+\\$,\\.a-zA-Z0-9_][\\-;:&=\\+\\$,\\"\\.a-zA-Z0-9_]*'; + + re.src_xn = + + 'xn--[a-z0-9\\-]{1,59}'; + + // More to read about domain names + // http://serverfault.com/questions/638260/ + + re.src_domain_root = + + // Allow letters & digits (http://test1) + '(?:' + + re.src_xn + + '|' + + re.src_pseudo_letter + '{1,63}' + + ')'; + + re.src_domain = + + '(?:' + + re.src_xn + + '|' + + '(?:' + re.src_pseudo_letter + ')' + + '|' + + '(?:' + re.src_pseudo_letter + '(?:-|' + re.src_pseudo_letter + '){0,61}' + re.src_pseudo_letter + ')' + + ')'; + + re.src_host = + + '(?:' + + // Don't need IP check, because digits are already allowed in normal domain names + // src_ip4 + + // '|' + + '(?:(?:(?:' + re.src_domain + ')\\.)*' + re.src_domain/*_root*/ + ')' + + ')'; + + re.tpl_host_fuzzy = + + '(?:' + + re.src_ip4 + + '|' + + '(?:(?:(?:' + re.src_domain + ')\\.)+(?:%TLDS%))' + + ')'; + + re.tpl_host_no_ip_fuzzy = + + '(?:(?:(?:' + re.src_domain + ')\\.)+(?:%TLDS%))'; + + re.src_host_strict = + + re.src_host + re.src_host_terminator; + + re.tpl_host_fuzzy_strict = + + re.tpl_host_fuzzy + re.src_host_terminator; + + re.src_host_port_strict = + + re.src_host + re.src_port + re.src_host_terminator; + + re.tpl_host_port_fuzzy_strict = + + re.tpl_host_fuzzy + re.src_port + re.src_host_terminator; + + re.tpl_host_port_no_ip_fuzzy_strict = + + re.tpl_host_no_ip_fuzzy + re.src_port + re.src_host_terminator; + + + //////////////////////////////////////////////////////////////////////////////// + // Main rules + + // Rude test fuzzy links by host, for quick deny + re.tpl_host_fuzzy_test = + + 'localhost|www\\.|\\.\\d{1,3}\\.|(?:\\.(?:%TLDS%)(?:' + re.src_ZPCc + '|>|$))'; + + re.tpl_email_fuzzy = + + '(^|' + text_separators + '|"|\\(|' + re.src_ZCc + ')' + + '(' + re.src_email_name + '@' + re.tpl_host_fuzzy_strict + ')'; + + re.tpl_link_fuzzy = + // Fuzzy link can't be prepended with .:/\- and non punctuation. + // but can start with > (markdown blockquote) + '(^|(?![.:/\\-_@])(?:[$+<=>^`|\uff5c]|' + re.src_ZPCc + '))' + + '((?![$+<=>^`|\uff5c])' + re.tpl_host_port_fuzzy_strict + re.src_path + ')'; + + re.tpl_link_no_ip_fuzzy = + // Fuzzy link can't be prepended with .:/\- and non punctuation. + // but can start with > (markdown blockquote) + '(^|(?![.:/\\-_@])(?:[$+<=>^`|\uff5c]|' + re.src_ZPCc + '))' + + '((?![$+<=>^`|\uff5c])' + re.tpl_host_port_no_ip_fuzzy_strict + re.src_path + ')'; + + return re; + }; + + },{"uc.micro/categories/Cc/regex":61,"uc.micro/categories/P/regex":63,"uc.micro/categories/Z/regex":64,"uc.micro/properties/Any/regex":66}],55:[function(require,module,exports){ + + 'use strict'; + + + /* eslint-disable no-bitwise */ + + var decodeCache = {}; + + function getDecodeCache(exclude) { + var i, ch, cache = decodeCache[exclude]; + if (cache) { return cache; } + + cache = decodeCache[exclude] = []; + + for (i = 0; i < 128; i++) { + ch = String.fromCharCode(i); + cache.push(ch); + } + + for (i = 0; i < exclude.length; i++) { + ch = exclude.charCodeAt(i); + cache[ch] = '%' + ('0' + ch.toString(16).toUpperCase()).slice(-2); + } + + return cache; + } + + + // Decode percent-encoded string. + // + function decode(string, exclude) { + var cache; + + if (typeof exclude !== 'string') { + exclude = decode.defaultChars; + } + + cache = getDecodeCache(exclude); + + return string.replace(/(%[a-f0-9]{2})+/gi, function(seq) { + var i, l, b1, b2, b3, b4, chr, + result = ''; + + for (i = 0, l = seq.length; i < l; i += 3) { + b1 = parseInt(seq.slice(i + 1, i + 3), 16); + + if (b1 < 0x80) { + result += cache[b1]; + continue; + } + + if ((b1 & 0xE0) === 0xC0 && (i + 3 < l)) { + // 110xxxxx 10xxxxxx + b2 = parseInt(seq.slice(i + 4, i + 6), 16); + + if ((b2 & 0xC0) === 0x80) { + chr = ((b1 << 6) & 0x7C0) | (b2 & 0x3F); + + if (chr < 0x80) { + result += '\ufffd\ufffd'; + } else { + result += String.fromCharCode(chr); + } + + i += 3; + continue; + } + } + + if ((b1 & 0xF0) === 0xE0 && (i + 6 < l)) { + // 1110xxxx 10xxxxxx 10xxxxxx + b2 = parseInt(seq.slice(i + 4, i + 6), 16); + b3 = parseInt(seq.slice(i + 7, i + 9), 16); + + if ((b2 & 0xC0) === 0x80 && (b3 & 0xC0) === 0x80) { + chr = ((b1 << 12) & 0xF000) | ((b2 << 6) & 0xFC0) | (b3 & 0x3F); + + if (chr < 0x800 || (chr >= 0xD800 && chr <= 0xDFFF)) { + result += '\ufffd\ufffd\ufffd'; + } else { + result += String.fromCharCode(chr); + } + + i += 6; + continue; + } + } + + if ((b1 & 0xF8) === 0xF0 && (i + 9 < l)) { + // 111110xx 10xxxxxx 10xxxxxx 10xxxxxx + b2 = parseInt(seq.slice(i + 4, i + 6), 16); + b3 = parseInt(seq.slice(i + 7, i + 9), 16); + b4 = parseInt(seq.slice(i + 10, i + 12), 16); + + if ((b2 & 0xC0) === 0x80 && (b3 & 0xC0) === 0x80 && (b4 & 0xC0) === 0x80) { + chr = ((b1 << 18) & 0x1C0000) | ((b2 << 12) & 0x3F000) | ((b3 << 6) & 0xFC0) | (b4 & 0x3F); + + if (chr < 0x10000 || chr > 0x10FFFF) { + result += '\ufffd\ufffd\ufffd\ufffd'; + } else { + chr -= 0x10000; + result += String.fromCharCode(0xD800 + (chr >> 10), 0xDC00 + (chr & 0x3FF)); + } + + i += 9; + continue; + } + } + + result += '\ufffd'; + } + + return result; + }); + } + + + decode.defaultChars = ';/?:@&=+$,#'; + decode.componentChars = ''; + + + module.exports = decode; + + },{}],56:[function(require,module,exports){ + + 'use strict'; + + + var encodeCache = {}; + + + // Create a lookup array where anything but characters in `chars` string + // and alphanumeric chars is percent-encoded. + // + function getEncodeCache(exclude) { + var i, ch, cache = encodeCache[exclude]; + if (cache) { return cache; } + + cache = encodeCache[exclude] = []; + + for (i = 0; i < 128; i++) { + ch = String.fromCharCode(i); + + if (/^[0-9a-z]$/i.test(ch)) { + // always allow unencoded alphanumeric characters + cache.push(ch); + } else { + cache.push('%' + ('0' + i.toString(16).toUpperCase()).slice(-2)); + } + } + + for (i = 0; i < exclude.length; i++) { + cache[exclude.charCodeAt(i)] = exclude[i]; + } + + return cache; + } + + + // Encode unsafe characters with percent-encoding, skipping already + // encoded sequences. + // + // - string - string to encode + // - exclude - list of characters to ignore (in addition to a-zA-Z0-9) + // - keepEscaped - don't encode '%' in a correct escape sequence (default: true) + // + function encode(string, exclude, keepEscaped) { + var i, l, code, nextCode, cache, + result = ''; + + if (typeof exclude !== 'string') { + // encode(string, keepEscaped) + keepEscaped = exclude; + exclude = encode.defaultChars; + } + + if (typeof keepEscaped === 'undefined') { + keepEscaped = true; + } + + cache = getEncodeCache(exclude); + + for (i = 0, l = string.length; i < l; i++) { + code = string.charCodeAt(i); + + if (keepEscaped && code === 0x25 /* % */ && i + 2 < l) { + if (/^[0-9a-f]{2}$/i.test(string.slice(i + 1, i + 3))) { + result += string.slice(i, i + 3); + i += 2; + continue; + } + } + + if (code < 128) { + result += cache[code]; + continue; + } + + if (code >= 0xD800 && code <= 0xDFFF) { + if (code >= 0xD800 && code <= 0xDBFF && i + 1 < l) { + nextCode = string.charCodeAt(i + 1); + if (nextCode >= 0xDC00 && nextCode <= 0xDFFF) { + result += encodeURIComponent(string[i] + string[i + 1]); + i++; + continue; + } + } + result += '%EF%BF%BD'; + continue; + } + + result += encodeURIComponent(string[i]); + } + + return result; + } + + encode.defaultChars = ";/?:@&=+$,-_.!~*'()#"; + encode.componentChars = "-_.!~*'()"; + + + module.exports = encode; + + },{}],57:[function(require,module,exports){ + + 'use strict'; + + + module.exports = function format(url) { + var result = ''; + + result += url.protocol || ''; + result += url.slashes ? '//' : ''; + result += url.auth ? url.auth + '@' : ''; + + if (url.hostname && url.hostname.indexOf(':') !== -1) { + // ipv6 address + result += '[' + url.hostname + ']'; + } else { + result += url.hostname || ''; + } + + result += url.port ? ':' + url.port : ''; + result += url.pathname || ''; + result += url.search || ''; + result += url.hash || ''; + + return result; + }; + + },{}],58:[function(require,module,exports){ + 'use strict'; + + + module.exports.encode = require('./encode'); + module.exports.decode = require('./decode'); + module.exports.format = require('./format'); + module.exports.parse = require('./parse'); + + },{"./decode":55,"./encode":56,"./format":57,"./parse":59}],59:[function(require,module,exports){ + // Copyright Joyent, Inc. and other Node contributors. + // + // Permission is hereby granted, free of charge, to any person obtaining a + // copy of this software and associated documentation files (the + // "Software"), to deal in the Software without restriction, including + // without limitation the rights to use, copy, modify, merge, publish, + // distribute, sublicense, and/or sell copies of the Software, and to permit + // persons to whom the Software is furnished to do so, subject to the + // following conditions: + // + // The above copyright notice and this permission notice shall be included + // in all copies or substantial portions of the Software. + // + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + // USE OR OTHER DEALINGS IN THE SOFTWARE. + + 'use strict'; + + // + // Changes from joyent/node: + // + // 1. No leading slash in paths, + // e.g. in `url.parse('http://foo?bar')` pathname is ``, not `/` + // + // 2. Backslashes are not replaced with slashes, + // so `http:\\example.org\` is treated like a relative path + // + // 3. Trailing colon is treated like a part of the path, + // i.e. in `http://example.org:foo` pathname is `:foo` + // + // 4. Nothing is URL-encoded in the resulting object, + // (in joyent/node some chars in auth and paths are encoded) + // + // 5. `url.parse()` does not have `parseQueryString` argument + // + // 6. Removed extraneous result properties: `host`, `path`, `query`, etc., + // which can be constructed using other parts of the url. + // + + + function Url() { + this.protocol = null; + this.slashes = null; + this.auth = null; + this.port = null; + this.hostname = null; + this.hash = null; + this.search = null; + this.pathname = null; + } + + // Reference: RFC 3986, RFC 1808, RFC 2396 + + // define these here so at least they only have to be + // compiled once on the first module load. + var protocolPattern = /^([a-z0-9.+-]+:)/i, + portPattern = /:[0-9]*$/, + + // Special case for a simple path URL + simplePathPattern = /^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/, + + // RFC 2396: characters reserved for delimiting URLs. + // We actually just auto-escape these. + delims = [ '<', '>', '"', '`', ' ', '\r', '\n', '\t' ], + + // RFC 2396: characters not allowed for various reasons. + unwise = [ '{', '}', '|', '\\', '^', '`' ].concat(delims), + + // Allowed by RFCs, but cause of XSS attacks. Always escape these. + autoEscape = [ '\'' ].concat(unwise), + // Characters that are never ever allowed in a hostname. + // Note that any invalid chars are also handled, but these + // are the ones that are *expected* to be seen, so we fast-path + // them. + nonHostChars = [ '%', '/', '?', ';', '#' ].concat(autoEscape), + hostEndingChars = [ '/', '?', '#' ], + hostnameMaxLen = 255, + hostnamePartPattern = /^[+a-z0-9A-Z_-]{0,63}$/, + hostnamePartStart = /^([+a-z0-9A-Z_-]{0,63})(.*)$/, + // protocols that can allow "unsafe" and "unwise" chars. + /* eslint-disable no-script-url */ + // protocols that never have a hostname. + hostlessProtocol = { + 'javascript': true, + 'javascript:': true + }, + // protocols that always contain a // bit. + slashedProtocol = { + 'http': true, + 'https': true, + 'ftp': true, + 'gopher': true, + 'file': true, + 'http:': true, + 'https:': true, + 'ftp:': true, + 'gopher:': true, + 'file:': true + }; + /* eslint-enable no-script-url */ + + function urlParse(url, slashesDenoteHost) { + if (url && url instanceof Url) { return url; } + + var u = new Url(); + u.parse(url, slashesDenoteHost); + return u; + } + + Url.prototype.parse = function(url, slashesDenoteHost) { + var i, l, lowerProto, hec, slashes, + rest = url; + + // trim before proceeding. + // This is to support parse stuff like " http://foo.com \n" + rest = rest.trim(); + + if (!slashesDenoteHost && url.split('#').length === 1) { + // Try fast path regexp + var simplePath = simplePathPattern.exec(rest); + if (simplePath) { + this.pathname = simplePath[1]; + if (simplePath[2]) { + this.search = simplePath[2]; + } + return this; + } + } + + var proto = protocolPattern.exec(rest); + if (proto) { + proto = proto[0]; + lowerProto = proto.toLowerCase(); + this.protocol = proto; + rest = rest.substr(proto.length); + } + + // figure out if it's got a host + // user@server is *always* interpreted as a hostname, and url + // resolution will treat //foo/bar as host=foo,path=bar because that's + // how the browser resolves relative URLs. + if (slashesDenoteHost || proto || rest.match(/^\/\/[^@\/]+@[^@\/]+/)) { + slashes = rest.substr(0, 2) === '//'; + if (slashes && !(proto && hostlessProtocol[proto])) { + rest = rest.substr(2); + this.slashes = true; + } + } + + if (!hostlessProtocol[proto] && + (slashes || (proto && !slashedProtocol[proto]))) { + + // there's a hostname. + // the first instance of /, ?, ;, or # ends the host. + // + // If there is an @ in the hostname, then non-host chars *are* allowed + // to the left of the last @ sign, unless some host-ending character + // comes *before* the @-sign. + // URLs are obnoxious. + // + // ex: + // http://a@b@c/ => user:a@b host:c + // http://a@b?@c => user:a host:c path:/?@c + + // v0.12 TODO(isaacs): This is not quite how Chrome does things. + // Review our test case against browsers more comprehensively. + + // find the first instance of any hostEndingChars + var hostEnd = -1; + for (i = 0; i < hostEndingChars.length; i++) { + hec = rest.indexOf(hostEndingChars[i]); + if (hec !== -1 && (hostEnd === -1 || hec < hostEnd)) { + hostEnd = hec; + } + } + + // at this point, either we have an explicit point where the + // auth portion cannot go past, or the last @ char is the decider. + var auth, atSign; + if (hostEnd === -1) { + // atSign can be anywhere. + atSign = rest.lastIndexOf('@'); + } else { + // atSign must be in auth portion. + // http://a@b/c@d => host:b auth:a path:/c@d + atSign = rest.lastIndexOf('@', hostEnd); + } + + // Now we have a portion which is definitely the auth. + // Pull that off. + if (atSign !== -1) { + auth = rest.slice(0, atSign); + rest = rest.slice(atSign + 1); + this.auth = auth; + } + + // the host is the remaining to the left of the first non-host char + hostEnd = -1; + for (i = 0; i < nonHostChars.length; i++) { + hec = rest.indexOf(nonHostChars[i]); + if (hec !== -1 && (hostEnd === -1 || hec < hostEnd)) { + hostEnd = hec; + } + } + // if we still have not hit it, then the entire thing is a host. + if (hostEnd === -1) { + hostEnd = rest.length; + } + + if (rest[hostEnd - 1] === ':') { hostEnd--; } + var host = rest.slice(0, hostEnd); + rest = rest.slice(hostEnd); + + // pull out port. + this.parseHost(host); + + // we've indicated that there is a hostname, + // so even if it's empty, it has to be present. + this.hostname = this.hostname || ''; + + // if hostname begins with [ and ends with ] + // assume that it's an IPv6 address. + var ipv6Hostname = this.hostname[0] === '[' && + this.hostname[this.hostname.length - 1] === ']'; + + // validate a little. + if (!ipv6Hostname) { + var hostparts = this.hostname.split(/\./); + for (i = 0, l = hostparts.length; i < l; i++) { + var part = hostparts[i]; + if (!part) { continue; } + if (!part.match(hostnamePartPattern)) { + var newpart = ''; + for (var j = 0, k = part.length; j < k; j++) { + if (part.charCodeAt(j) > 127) { + // we replace non-ASCII char with a temporary placeholder + // we need this to make sure size of hostname is not + // broken by replacing non-ASCII by nothing + newpart += 'x'; + } else { + newpart += part[j]; + } + } + // we test again with ASCII char only + if (!newpart.match(hostnamePartPattern)) { + var validParts = hostparts.slice(0, i); + var notHost = hostparts.slice(i + 1); + var bit = part.match(hostnamePartStart); + if (bit) { + validParts.push(bit[1]); + notHost.unshift(bit[2]); + } + if (notHost.length) { + rest = notHost.join('.') + rest; + } + this.hostname = validParts.join('.'); + break; + } + } + } + } + + if (this.hostname.length > hostnameMaxLen) { + this.hostname = ''; + } + + // strip [ and ] from the hostname + // the host field still retains them, though + if (ipv6Hostname) { + this.hostname = this.hostname.substr(1, this.hostname.length - 2); + } + } + + // chop off from the tail first. + var hash = rest.indexOf('#'); + if (hash !== -1) { + // got a fragment string. + this.hash = rest.substr(hash); + rest = rest.slice(0, hash); + } + var qm = rest.indexOf('?'); + if (qm !== -1) { + this.search = rest.substr(qm); + rest = rest.slice(0, qm); + } + if (rest) { this.pathname = rest; } + if (slashedProtocol[lowerProto] && + this.hostname && !this.pathname) { + this.pathname = ''; + } + + return this; + }; + + Url.prototype.parseHost = function(host) { + var port = portPattern.exec(host); + if (port) { + port = port[0]; + if (port !== ':') { + this.port = port.substr(1); + } + host = host.substr(0, host.length - port.length); + } + if (host) { this.hostname = host; } + }; + + module.exports = urlParse; + + },{}],60:[function(require,module,exports){ + (function (global){ + /*! https://mths.be/punycode v1.4.1 by @mathias */ + ;(function(root) { + + /** Detect free variables */ + var freeExports = typeof exports == 'object' && exports && + !exports.nodeType && exports; + var freeModule = typeof module == 'object' && module && + !module.nodeType && module; + var freeGlobal = typeof global == 'object' && global; + if ( + freeGlobal.global === freeGlobal || + freeGlobal.window === freeGlobal || + freeGlobal.self === freeGlobal + ) { + root = freeGlobal; + } + + /** + * The `punycode` object. + * @name punycode + * @type Object + */ + var punycode, + + /** Highest positive signed 32-bit float value */ + maxInt = 2147483647, // aka. 0x7FFFFFFF or 2^31-1 + + /** Bootstring parameters */ + base = 36, + tMin = 1, + tMax = 26, + skew = 38, + damp = 700, + initialBias = 72, + initialN = 128, // 0x80 + delimiter = '-', // '\x2D' + + /** Regular expressions */ + regexPunycode = /^xn--/, + regexNonASCII = /[^\x20-\x7E]/, // unprintable ASCII chars + non-ASCII chars + regexSeparators = /[\x2E\u3002\uFF0E\uFF61]/g, // RFC 3490 separators + + /** Error messages */ + errors = { + 'overflow': 'Overflow: input needs wider integers to process', + 'not-basic': 'Illegal input >= 0x80 (not a basic code point)', + 'invalid-input': 'Invalid input' + }, + + /** Convenience shortcuts */ + baseMinusTMin = base - tMin, + floor = Math.floor, + stringFromCharCode = String.fromCharCode, + + /** Temporary variable */ + key; + + /*--------------------------------------------------------------------------*/ + + /** + * A generic error utility function. + * @private + * @param {String} type The error type. + * @returns {Error} Throws a `RangeError` with the applicable error message. + */ + function error(type) { + throw new RangeError(errors[type]); + } + + /** + * A generic `Array#map` utility function. + * @private + * @param {Array} array The array to iterate over. + * @param {Function} callback The function that gets called for every array + * item. + * @returns {Array} A new array of values returned by the callback function. + */ + function map(array, fn) { + var length = array.length; + var result = []; + while (length--) { + result[length] = fn(array[length]); + } + return result; + } + + /** + * A simple `Array#map`-like wrapper to work with domain name strings or email + * addresses. + * @private + * @param {String} domain The domain name or email address. + * @param {Function} callback The function that gets called for every + * character. + * @returns {Array} A new string of characters returned by the callback + * function. + */ + function mapDomain(string, fn) { + var parts = string.split('@'); + var result = ''; + if (parts.length > 1) { + // In email addresses, only the domain name should be punycoded. Leave + // the local part (i.e. everything up to `@`) intact. + result = parts[0] + '@'; + string = parts[1]; + } + // Avoid `split(regex)` for IE8 compatibility. See #17. + string = string.replace(regexSeparators, '\x2E'); + var labels = string.split('.'); + var encoded = map(labels, fn).join('.'); + return result + encoded; + } + + /** + * Creates an array containing the numeric code points of each Unicode + * character in the string. While JavaScript uses UCS-2 internally, + * this function will convert a pair of surrogate halves (each of which + * UCS-2 exposes as separate characters) into a single code point, + * matching UTF-16. + * @see `punycode.ucs2.encode` + * @see + * @memberOf punycode.ucs2 + * @name decode + * @param {String} string The Unicode input string (UCS-2). + * @returns {Array} The new array of code points. + */ + function ucs2decode(string) { + var output = [], + counter = 0, + length = string.length, + value, + extra; + while (counter < length) { + value = string.charCodeAt(counter++); + if (value >= 0xD800 && value <= 0xDBFF && counter < length) { + // high surrogate, and there is a next character + extra = string.charCodeAt(counter++); + if ((extra & 0xFC00) == 0xDC00) { // low surrogate + output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000); + } else { + // unmatched surrogate; only append this code unit, in case the next + // code unit is the high surrogate of a surrogate pair + output.push(value); + counter--; + } + } else { + output.push(value); + } + } + return output; + } + + /** + * Creates a string based on an array of numeric code points. + * @see `punycode.ucs2.decode` + * @memberOf punycode.ucs2 + * @name encode + * @param {Array} codePoints The array of numeric code points. + * @returns {String} The new Unicode string (UCS-2). + */ + function ucs2encode(array) { + return map(array, function(value) { + var output = ''; + if (value > 0xFFFF) { + value -= 0x10000; + output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800); + value = 0xDC00 | value & 0x3FF; + } + output += stringFromCharCode(value); + return output; + }).join(''); + } + + /** + * Converts a basic code point into a digit/integer. + * @see `digitToBasic()` + * @private + * @param {Number} codePoint The basic numeric code point value. + * @returns {Number} The numeric value of a basic code point (for use in + * representing integers) in the range `0` to `base - 1`, or `base` if + * the code point does not represent a value. + */ + function basicToDigit(codePoint) { + if (codePoint - 48 < 10) { + return codePoint - 22; + } + if (codePoint - 65 < 26) { + return codePoint - 65; + } + if (codePoint - 97 < 26) { + return codePoint - 97; + } + return base; + } + + /** + * Converts a digit/integer into a basic code point. + * @see `basicToDigit()` + * @private + * @param {Number} digit The numeric value of a basic code point. + * @returns {Number} The basic code point whose value (when used for + * representing integers) is `digit`, which needs to be in the range + * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is + * used; else, the lowercase form is used. The behavior is undefined + * if `flag` is non-zero and `digit` has no uppercase form. + */ + function digitToBasic(digit, flag) { + // 0..25 map to ASCII a..z or A..Z + // 26..35 map to ASCII 0..9 + return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5); + } + + /** + * Bias adaptation function as per section 3.4 of RFC 3492. + * https://tools.ietf.org/html/rfc3492#section-3.4 + * @private + */ + function adapt(delta, numPoints, firstTime) { + var k = 0; + delta = firstTime ? floor(delta / damp) : delta >> 1; + delta += floor(delta / numPoints); + for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) { + delta = floor(delta / baseMinusTMin); + } + return floor(k + (baseMinusTMin + 1) * delta / (delta + skew)); + } + + /** + * Converts a Punycode string of ASCII-only symbols to a string of Unicode + * symbols. + * @memberOf punycode + * @param {String} input The Punycode string of ASCII-only symbols. + * @returns {String} The resulting string of Unicode symbols. + */ + function decode(input) { + // Don't use UCS-2 + var output = [], + inputLength = input.length, + out, + i = 0, + n = initialN, + bias = initialBias, + basic, + j, + index, + oldi, + w, + k, + digit, + t, + /** Cached calculation results */ + baseMinusT; + + // Handle the basic code points: let `basic` be the number of input code + // points before the last delimiter, or `0` if there is none, then copy + // the first basic code points to the output. + + basic = input.lastIndexOf(delimiter); + if (basic < 0) { + basic = 0; + } + + for (j = 0; j < basic; ++j) { + // if it's not a basic code point + if (input.charCodeAt(j) >= 0x80) { + error('not-basic'); + } + output.push(input.charCodeAt(j)); + } + + // Main decoding loop: start just after the last delimiter if any basic code + // points were copied; start at the beginning otherwise. + + for (index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) { + + // `index` is the index of the next character to be consumed. + // Decode a generalized variable-length integer into `delta`, + // which gets added to `i`. The overflow checking is easier + // if we increase `i` as we go, then subtract off its starting + // value at the end to obtain `delta`. + for (oldi = i, w = 1, k = base; /* no condition */; k += base) { + + if (index >= inputLength) { + error('invalid-input'); + } + + digit = basicToDigit(input.charCodeAt(index++)); + + if (digit >= base || digit > floor((maxInt - i) / w)) { + error('overflow'); + } + + i += digit * w; + t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); + + if (digit < t) { + break; + } + + baseMinusT = base - t; + if (w > floor(maxInt / baseMinusT)) { + error('overflow'); + } + + w *= baseMinusT; + + } + + out = output.length + 1; + bias = adapt(i - oldi, out, oldi == 0); + + // `i` was supposed to wrap around from `out` to `0`, + // incrementing `n` each time, so we'll fix that now: + if (floor(i / out) > maxInt - n) { + error('overflow'); + } + + n += floor(i / out); + i %= out; + + // Insert `n` at position `i` of the output + output.splice(i++, 0, n); + + } + + return ucs2encode(output); + } + + /** + * Converts a string of Unicode symbols (e.g. a domain name label) to a + * Punycode string of ASCII-only symbols. + * @memberOf punycode + * @param {String} input The string of Unicode symbols. + * @returns {String} The resulting Punycode string of ASCII-only symbols. + */ + function encode(input) { + var n, + delta, + handledCPCount, + basicLength, + bias, + j, + m, + q, + k, + t, + currentValue, + output = [], + /** `inputLength` will hold the number of code points in `input`. */ + inputLength, + /** Cached calculation results */ + handledCPCountPlusOne, + baseMinusT, + qMinusT; + + // Convert the input in UCS-2 to Unicode + input = ucs2decode(input); + + // Cache the length + inputLength = input.length; + + // Initialize the state + n = initialN; + delta = 0; + bias = initialBias; + + // Handle the basic code points + for (j = 0; j < inputLength; ++j) { + currentValue = input[j]; + if (currentValue < 0x80) { + output.push(stringFromCharCode(currentValue)); + } + } + + handledCPCount = basicLength = output.length; + + // `handledCPCount` is the number of code points that have been handled; + // `basicLength` is the number of basic code points. + + // Finish the basic string - if it is not empty - with a delimiter + if (basicLength) { + output.push(delimiter); + } + + // Main encoding loop: + while (handledCPCount < inputLength) { + + // All non-basic code points < n have been handled already. Find the next + // larger one: + for (m = maxInt, j = 0; j < inputLength; ++j) { + currentValue = input[j]; + if (currentValue >= n && currentValue < m) { + m = currentValue; + } + } + + // Increase `delta` enough to advance the decoder's state to , + // but guard against overflow + handledCPCountPlusOne = handledCPCount + 1; + if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) { + error('overflow'); + } + + delta += (m - n) * handledCPCountPlusOne; + n = m; + + for (j = 0; j < inputLength; ++j) { + currentValue = input[j]; + + if (currentValue < n && ++delta > maxInt) { + error('overflow'); + } + + if (currentValue == n) { + // Represent delta as a generalized variable-length integer + for (q = delta, k = base; /* no condition */; k += base) { + t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); + if (q < t) { + break; + } + qMinusT = q - t; + baseMinusT = base - t; + output.push( + stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0)) + ); + q = floor(qMinusT / baseMinusT); + } + + output.push(stringFromCharCode(digitToBasic(q, 0))); + bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength); + delta = 0; + ++handledCPCount; + } + } + + ++delta; + ++n; + + } + return output.join(''); + } + + /** + * Converts a Punycode string representing a domain name or an email address + * to Unicode. Only the Punycoded parts of the input will be converted, i.e. + * it doesn't matter if you call it on a string that has already been + * converted to Unicode. + * @memberOf punycode + * @param {String} input The Punycoded domain name or email address to + * convert to Unicode. + * @returns {String} The Unicode representation of the given Punycode + * string. + */ + function toUnicode(input) { + return mapDomain(input, function(string) { + return regexPunycode.test(string) + ? decode(string.slice(4).toLowerCase()) + : string; + }); + } + + /** + * Converts a Unicode string representing a domain name or an email address to + * Punycode. Only the non-ASCII parts of the domain name will be converted, + * i.e. it doesn't matter if you call it with a domain that's already in + * ASCII. + * @memberOf punycode + * @param {String} input The domain name or email address to convert, as a + * Unicode string. + * @returns {String} The Punycode representation of the given domain name or + * email address. + */ + function toASCII(input) { + return mapDomain(input, function(string) { + return regexNonASCII.test(string) + ? 'xn--' + encode(string) + : string; + }); + } + + /*--------------------------------------------------------------------------*/ + + /** Define the public API */ + punycode = { + /** + * A string representing the current Punycode.js version number. + * @memberOf punycode + * @type String + */ + 'version': '1.4.1', + /** + * An object of methods to convert from JavaScript's internal character + * representation (UCS-2) to Unicode code points, and back. + * @see + * @memberOf punycode + * @type Object + */ + 'ucs2': { + 'decode': ucs2decode, + 'encode': ucs2encode + }, + 'decode': decode, + 'encode': encode, + 'toASCII': toASCII, + 'toUnicode': toUnicode + }; + + /** Expose `punycode` */ + // Some AMD build optimizers, like r.js, check for specific condition patterns + // like the following: + if ( + typeof define == 'function' && + typeof define.amd == 'object' && + define.amd + ) { + define('punycode', function() { + return punycode; + }); + } else if (freeExports && freeModule) { + if (module.exports == freeExports) { + // in Node.js, io.js, or RingoJS v0.8.0+ + freeModule.exports = punycode; + } else { + // in Narwhal or RingoJS v0.7.0- + for (key in punycode) { + punycode.hasOwnProperty(key) && (freeExports[key] = punycode[key]); + } + } + } else { + // in Rhino or a web browser + root.punycode = punycode; + } + + }(this)); + + }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + },{}],61:[function(require,module,exports){ + module.exports=/[\0-\x1F\x7F-\x9F]/ + },{}],62:[function(require,module,exports){ + module.exports=/[\xAD\u0600-\u0605\u061C\u06DD\u070F\u08E2\u180E\u200B-\u200F\u202A-\u202E\u2060-\u2064\u2066-\u206F\uFEFF\uFFF9-\uFFFB]|\uD804[\uDCBD\uDCCD]|\uD82F[\uDCA0-\uDCA3]|\uD834[\uDD73-\uDD7A]|\uDB40[\uDC01\uDC20-\uDC7F]/ + },{}],63:[function(require,module,exports){ + module.exports=/[!-#%-\*,-\/:;\?@\[-\]_\{\}\xA1\xA7\xAB\xB6\xB7\xBB\xBF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061E\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u09FD\u0A76\u0AF0\u0C84\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166D\u166E\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2010-\u2027\u2030-\u2043\u2045-\u2051\u2053-\u205E\u207D\u207E\u208D\u208E\u2308-\u230B\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E4E\u3001-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65]|\uD800[\uDD00-\uDD02\uDF9F\uDFD0]|\uD801\uDD6F|\uD802[\uDC57\uDD1F\uDD3F\uDE50-\uDE58\uDE7F\uDEF0-\uDEF6\uDF39-\uDF3F\uDF99-\uDF9C]|\uD803[\uDF55-\uDF59]|\uD804[\uDC47-\uDC4D\uDCBB\uDCBC\uDCBE-\uDCC1\uDD40-\uDD43\uDD74\uDD75\uDDC5-\uDDC8\uDDCD\uDDDB\uDDDD-\uDDDF\uDE38-\uDE3D\uDEA9]|\uD805[\uDC4B-\uDC4F\uDC5B\uDC5D\uDCC6\uDDC1-\uDDD7\uDE41-\uDE43\uDE60-\uDE6C\uDF3C-\uDF3E]|\uD806[\uDC3B\uDE3F-\uDE46\uDE9A-\uDE9C\uDE9E-\uDEA2]|\uD807[\uDC41-\uDC45\uDC70\uDC71\uDEF7\uDEF8]|\uD809[\uDC70-\uDC74]|\uD81A[\uDE6E\uDE6F\uDEF5\uDF37-\uDF3B\uDF44]|\uD81B[\uDE97-\uDE9A]|\uD82F\uDC9F|\uD836[\uDE87-\uDE8B]|\uD83A[\uDD5E\uDD5F]/ + },{}],64:[function(require,module,exports){ + module.exports=/[ \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000]/ + },{}],65:[function(require,module,exports){ + 'use strict'; + + exports.Any = require('./properties/Any/regex'); + exports.Cc = require('./categories/Cc/regex'); + exports.Cf = require('./categories/Cf/regex'); + exports.P = require('./categories/P/regex'); + exports.Z = require('./categories/Z/regex'); + + },{"./categories/Cc/regex":61,"./categories/Cf/regex":62,"./categories/P/regex":63,"./categories/Z/regex":64,"./properties/Any/regex":66}],66:[function(require,module,exports){ + module.exports=/[\0-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/ + },{}],67:[function(require,module,exports){ + 'use strict'; + + + module.exports = require('./lib/'); + + },{"./lib/":9}]},{},[67])(67) + }); + const markdownit = define() + export default markdownit \ No newline at end of file diff --git a/app/assets/frontends/beaker-forum/webfonts/fa-brands-400.eot b/app/assets/frontends/beaker-forum/webfonts/fa-brands-400.eot new file mode 100644 index 0000000000..da7bd5eb70 Binary files /dev/null and b/app/assets/frontends/beaker-forum/webfonts/fa-brands-400.eot differ diff --git a/app/assets/frontends/beaker-forum/webfonts/fa-brands-400.svg b/app/assets/frontends/beaker-forum/webfonts/fa-brands-400.svg new file mode 100644 index 0000000000..caa8cc43ca --- /dev/null +++ b/app/assets/frontends/beaker-forum/webfonts/fa-brands-400.svg @@ -0,0 +1,3296 @@ + + + + +Created by FontForge 20190112 at Tue Feb 12 10:24:59 2019 + By Robert Madole +Copyright (c) Font Awesome + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/assets/frontends/beaker-forum/webfonts/fa-brands-400.ttf b/app/assets/frontends/beaker-forum/webfonts/fa-brands-400.ttf new file mode 100644 index 0000000000..5f72e9127f Binary files /dev/null and b/app/assets/frontends/beaker-forum/webfonts/fa-brands-400.ttf differ diff --git a/app/assets/frontends/beaker-forum/webfonts/fa-brands-400.woff b/app/assets/frontends/beaker-forum/webfonts/fa-brands-400.woff new file mode 100644 index 0000000000..c64755a525 Binary files /dev/null and b/app/assets/frontends/beaker-forum/webfonts/fa-brands-400.woff differ diff --git a/app/assets/frontends/beaker-forum/webfonts/fa-brands-400.woff2 b/app/assets/frontends/beaker-forum/webfonts/fa-brands-400.woff2 new file mode 100644 index 0000000000..b5a956765b Binary files /dev/null and b/app/assets/frontends/beaker-forum/webfonts/fa-brands-400.woff2 differ diff --git a/app/assets/frontends/beaker-forum/webfonts/fa-regular-400.eot b/app/assets/frontends/beaker-forum/webfonts/fa-regular-400.eot new file mode 100644 index 0000000000..55085ca95d Binary files /dev/null and b/app/assets/frontends/beaker-forum/webfonts/fa-regular-400.eot differ diff --git a/app/assets/frontends/beaker-forum/webfonts/fa-regular-400.svg b/app/assets/frontends/beaker-forum/webfonts/fa-regular-400.svg new file mode 100644 index 0000000000..bba54466b9 --- /dev/null +++ b/app/assets/frontends/beaker-forum/webfonts/fa-regular-400.svg @@ -0,0 +1,799 @@ + + + + +Created by FontForge 20190112 at Tue Feb 12 10:24:59 2019 + By Robert Madole +Copyright (c) Font Awesome + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/assets/frontends/beaker-forum/webfonts/fa-regular-400.ttf b/app/assets/frontends/beaker-forum/webfonts/fa-regular-400.ttf new file mode 100644 index 0000000000..a309313d5f Binary files /dev/null and b/app/assets/frontends/beaker-forum/webfonts/fa-regular-400.ttf differ diff --git a/app/assets/frontends/beaker-forum/webfonts/fa-regular-400.woff b/app/assets/frontends/beaker-forum/webfonts/fa-regular-400.woff new file mode 100644 index 0000000000..2578261897 Binary files /dev/null and b/app/assets/frontends/beaker-forum/webfonts/fa-regular-400.woff differ diff --git a/app/assets/frontends/beaker-forum/webfonts/fa-regular-400.woff2 b/app/assets/frontends/beaker-forum/webfonts/fa-regular-400.woff2 new file mode 100644 index 0000000000..3ef9c3edb0 Binary files /dev/null and b/app/assets/frontends/beaker-forum/webfonts/fa-regular-400.woff2 differ diff --git a/app/assets/frontends/beaker-forum/webfonts/fa-solid-900.eot b/app/assets/frontends/beaker-forum/webfonts/fa-solid-900.eot new file mode 100644 index 0000000000..68c010a862 Binary files /dev/null and b/app/assets/frontends/beaker-forum/webfonts/fa-solid-900.eot differ diff --git a/app/assets/frontends/beaker-forum/webfonts/fa-solid-900.svg b/app/assets/frontends/beaker-forum/webfonts/fa-solid-900.svg new file mode 100644 index 0000000000..4ef85aa379 --- /dev/null +++ b/app/assets/frontends/beaker-forum/webfonts/fa-solid-900.svg @@ -0,0 +1,4516 @@ + + + + +Created by FontForge 20190112 at Tue Feb 12 10:24:59 2019 + By Robert Madole +Copyright (c) Font Awesome + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/assets/frontends/beaker-forum/webfonts/fa-solid-900.ttf b/app/assets/frontends/beaker-forum/webfonts/fa-solid-900.ttf new file mode 100644 index 0000000000..7ece3282a4 Binary files /dev/null and b/app/assets/frontends/beaker-forum/webfonts/fa-solid-900.ttf differ diff --git a/app/assets/frontends/beaker-forum/webfonts/fa-solid-900.woff b/app/assets/frontends/beaker-forum/webfonts/fa-solid-900.woff new file mode 100644 index 0000000000..a892a7a9c1 Binary files /dev/null and b/app/assets/frontends/beaker-forum/webfonts/fa-solid-900.woff differ diff --git a/app/assets/frontends/beaker-forum/webfonts/fa-solid-900.woff2 b/app/assets/frontends/beaker-forum/webfonts/fa-solid-900.woff2 new file mode 100644 index 0000000000..71b07ce028 Binary files /dev/null and b/app/assets/frontends/beaker-forum/webfonts/fa-solid-900.woff2 differ diff --git a/app/assets/frontends/beaker-forum/webfonts/fontawesome.css b/app/assets/frontends/beaker-forum/webfonts/fontawesome.css new file mode 100644 index 0000000000..a74835e25b --- /dev/null +++ b/app/assets/frontends/beaker-forum/webfonts/fontawesome.css @@ -0,0 +1 @@ +.fa,.fab,.fal,.far,.fas{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:.08em solid #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{animation:fa-spin 2s infinite linear}.fa-pulse{animation:fa-spin 1s infinite steps(8)}@keyframes fa-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";transform:scaleX(-1)}.fa-flip-vertical{transform:scaleY(-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical,.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{transform:scale(-1)}:root .fa-flip-both,:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2.5em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-500px:before{content:"\f26e"}.fa-accessible-icon:before{content:"\f368"}.fa-accusoft:before{content:"\f369"}.fa-acquisitions-incorporated:before{content:"\f6af"}.fa-ad:before{content:"\f641"}.fa-address-book:before{content:"\f2b9"}.fa-address-card:before{content:"\f2bb"}.fa-adjust:before{content:"\f042"}.fa-adn:before{content:"\f170"}.fa-adobe:before{content:"\f778"}.fa-adversal:before{content:"\f36a"}.fa-affiliatetheme:before{content:"\f36b"}.fa-air-freshener:before{content:"\f5d0"}.fa-algolia:before{content:"\f36c"}.fa-align-center:before{content:"\f037"}.fa-align-justify:before{content:"\f039"}.fa-align-left:before{content:"\f036"}.fa-align-right:before{content:"\f038"}.fa-alipay:before{content:"\f642"}.fa-allergies:before{content:"\f461"}.fa-amazon:before{content:"\f270"}.fa-amazon-pay:before{content:"\f42c"}.fa-ambulance:before{content:"\f0f9"}.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-amilia:before{content:"\f36d"}.fa-anchor:before{content:"\f13d"}.fa-android:before{content:"\f17b"}.fa-angellist:before{content:"\f209"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-down:before{content:"\f107"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angry:before{content:"\f556"}.fa-angrycreative:before{content:"\f36e"}.fa-angular:before{content:"\f420"}.fa-ankh:before{content:"\f644"}.fa-app-store:before{content:"\f36f"}.fa-app-store-ios:before{content:"\f370"}.fa-apper:before{content:"\f371"}.fa-apple:before{content:"\f179"}.fa-apple-alt:before{content:"\f5d1"}.fa-apple-pay:before{content:"\f415"}.fa-archive:before{content:"\f187"}.fa-archway:before{content:"\f557"}.fa-arrow-alt-circle-down:before{content:"\f358"}.fa-arrow-alt-circle-left:before{content:"\f359"}.fa-arrow-alt-circle-right:before{content:"\f35a"}.fa-arrow-alt-circle-up:before{content:"\f35b"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-down:before{content:"\f063"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrows-alt:before{content:"\f0b2"}.fa-arrows-alt-h:before{content:"\f337"}.fa-arrows-alt-v:before{content:"\f338"}.fa-artstation:before{content:"\f77a"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asterisk:before{content:"\f069"}.fa-asymmetrik:before{content:"\f372"}.fa-at:before{content:"\f1fa"}.fa-atlas:before{content:"\f558"}.fa-atlassian:before{content:"\f77b"}.fa-atom:before{content:"\f5d2"}.fa-audible:before{content:"\f373"}.fa-audio-description:before{content:"\f29e"}.fa-autoprefixer:before{content:"\f41c"}.fa-avianex:before{content:"\f374"}.fa-aviato:before{content:"\f421"}.fa-award:before{content:"\f559"}.fa-aws:before{content:"\f375"}.fa-baby:before{content:"\f77c"}.fa-baby-carriage:before{content:"\f77d"}.fa-backspace:before{content:"\f55a"}.fa-backward:before{content:"\f04a"}.fa-bacon:before{content:"\f7e5"}.fa-balance-scale:before{content:"\f24e"}.fa-ban:before{content:"\f05e"}.fa-band-aid:before{content:"\f462"}.fa-bandcamp:before{content:"\f2d5"}.fa-barcode:before{content:"\f02a"}.fa-bars:before{content:"\f0c9"}.fa-baseball-ball:before{content:"\f433"}.fa-basketball-ball:before{content:"\f434"}.fa-bath:before{content:"\f2cd"}.fa-battery-empty:before{content:"\f244"}.fa-battery-full:before{content:"\f240"}.fa-battery-half:before{content:"\f242"}.fa-battery-quarter:before{content:"\f243"}.fa-battery-three-quarters:before{content:"\f241"}.fa-bed:before{content:"\f236"}.fa-beer:before{content:"\f0fc"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-bell:before{content:"\f0f3"}.fa-bell-slash:before{content:"\f1f6"}.fa-bezier-curve:before{content:"\f55b"}.fa-bible:before{content:"\f647"}.fa-bicycle:before{content:"\f206"}.fa-bimobject:before{content:"\f378"}.fa-binoculars:before{content:"\f1e5"}.fa-biohazard:before{content:"\f780"}.fa-birthday-cake:before{content:"\f1fd"}.fa-bitbucket:before{content:"\f171"}.fa-bitcoin:before{content:"\f379"}.fa-bity:before{content:"\f37a"}.fa-black-tie:before{content:"\f27e"}.fa-blackberry:before{content:"\f37b"}.fa-blender:before{content:"\f517"}.fa-blender-phone:before{content:"\f6b6"}.fa-blind:before{content:"\f29d"}.fa-blog:before{content:"\f781"}.fa-blogger:before{content:"\f37c"}.fa-blogger-b:before{content:"\f37d"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-bold:before{content:"\f032"}.fa-bolt:before{content:"\f0e7"}.fa-bomb:before{content:"\f1e2"}.fa-bone:before{content:"\f5d7"}.fa-bong:before{content:"\f55c"}.fa-book:before{content:"\f02d"}.fa-book-dead:before{content:"\f6b7"}.fa-book-medical:before{content:"\f7e6"}.fa-book-open:before{content:"\f518"}.fa-book-reader:before{content:"\f5da"}.fa-bookmark:before{content:"\f02e"}.fa-bowling-ball:before{content:"\f436"}.fa-box:before{content:"\f466"}.fa-box-open:before{content:"\f49e"}.fa-boxes:before{content:"\f468"}.fa-braille:before{content:"\f2a1"}.fa-brain:before{content:"\f5dc"}.fa-bread-slice:before{content:"\f7ec"}.fa-briefcase:before{content:"\f0b1"}.fa-briefcase-medical:before{content:"\f469"}.fa-broadcast-tower:before{content:"\f519"}.fa-broom:before{content:"\f51a"}.fa-brush:before{content:"\f55d"}.fa-btc:before{content:"\f15a"}.fa-bug:before{content:"\f188"}.fa-building:before{content:"\f1ad"}.fa-bullhorn:before{content:"\f0a1"}.fa-bullseye:before{content:"\f140"}.fa-burn:before{content:"\f46a"}.fa-buromobelexperte:before{content:"\f37f"}.fa-bus:before{content:"\f207"}.fa-bus-alt:before{content:"\f55e"}.fa-business-time:before{content:"\f64a"}.fa-buysellads:before{content:"\f20d"}.fa-calculator:before{content:"\f1ec"}.fa-calendar:before{content:"\f133"}.fa-calendar-alt:before{content:"\f073"}.fa-calendar-check:before{content:"\f274"}.fa-calendar-day:before{content:"\f783"}.fa-calendar-minus:before{content:"\f272"}.fa-calendar-plus:before{content:"\f271"}.fa-calendar-times:before{content:"\f273"}.fa-calendar-week:before{content:"\f784"}.fa-camera:before{content:"\f030"}.fa-camera-retro:before{content:"\f083"}.fa-campground:before{content:"\f6bb"}.fa-canadian-maple-leaf:before{content:"\f785"}.fa-candy-cane:before{content:"\f786"}.fa-cannabis:before{content:"\f55f"}.fa-capsules:before{content:"\f46b"}.fa-car:before{content:"\f1b9"}.fa-car-alt:before{content:"\f5de"}.fa-car-battery:before{content:"\f5df"}.fa-car-crash:before{content:"\f5e1"}.fa-car-side:before{content:"\f5e4"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-caret-square-down:before{content:"\f150"}.fa-caret-square-left:before{content:"\f191"}.fa-caret-square-right:before{content:"\f152"}.fa-caret-square-up:before{content:"\f151"}.fa-caret-up:before{content:"\f0d8"}.fa-carrot:before{content:"\f787"}.fa-cart-arrow-down:before{content:"\f218"}.fa-cart-plus:before{content:"\f217"}.fa-cash-register:before{content:"\f788"}.fa-cat:before{content:"\f6be"}.fa-cc-amazon-pay:before{content:"\f42d"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-apple-pay:before{content:"\f416"}.fa-cc-diners-club:before{content:"\f24c"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-cc-visa:before{content:"\f1f0"}.fa-centercode:before{content:"\f380"}.fa-centos:before{content:"\f789"}.fa-certificate:before{content:"\f0a3"}.fa-chair:before{content:"\f6c0"}.fa-chalkboard:before{content:"\f51b"}.fa-chalkboard-teacher:before{content:"\f51c"}.fa-charging-station:before{content:"\f5e7"}.fa-chart-area:before{content:"\f1fe"}.fa-chart-bar:before{content:"\f080"}.fa-chart-line:before{content:"\f201"}.fa-chart-pie:before{content:"\f200"}.fa-check:before{content:"\f00c"}.fa-check-circle:before{content:"\f058"}.fa-check-double:before{content:"\f560"}.fa-check-square:before{content:"\f14a"}.fa-cheese:before{content:"\f7ef"}.fa-chess:before{content:"\f439"}.fa-chess-bishop:before{content:"\f43a"}.fa-chess-board:before{content:"\f43c"}.fa-chess-king:before{content:"\f43f"}.fa-chess-knight:before{content:"\f441"}.fa-chess-pawn:before{content:"\f443"}.fa-chess-queen:before{content:"\f445"}.fa-chess-rook:before{content:"\f447"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-down:before{content:"\f078"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-chevron-up:before{content:"\f077"}.fa-child:before{content:"\f1ae"}.fa-chrome:before{content:"\f268"}.fa-church:before{content:"\f51d"}.fa-circle:before{content:"\f111"}.fa-circle-notch:before{content:"\f1ce"}.fa-city:before{content:"\f64f"}.fa-clinic-medical:before{content:"\f7f2"}.fa-clipboard:before{content:"\f328"}.fa-clipboard-check:before{content:"\f46c"}.fa-clipboard-list:before{content:"\f46d"}.fa-clock:before{content:"\f017"}.fa-clone:before{content:"\f24d"}.fa-closed-captioning:before{content:"\f20a"}.fa-cloud:before{content:"\f0c2"}.fa-cloud-download-alt:before{content:"\f381"}.fa-cloud-meatball:before{content:"\f73b"}.fa-cloud-moon:before{content:"\f6c3"}.fa-cloud-moon-rain:before{content:"\f73c"}.fa-cloud-rain:before{content:"\f73d"}.fa-cloud-showers-heavy:before{content:"\f740"}.fa-cloud-sun:before{content:"\f6c4"}.fa-cloud-sun-rain:before{content:"\f743"}.fa-cloud-upload-alt:before{content:"\f382"}.fa-cloudscale:before{content:"\f383"}.fa-cloudsmith:before{content:"\f384"}.fa-cloudversify:before{content:"\f385"}.fa-cocktail:before{content:"\f561"}.fa-code:before{content:"\f121"}.fa-code-branch:before{content:"\f126"}.fa-codepen:before{content:"\f1cb"}.fa-codiepie:before{content:"\f284"}.fa-coffee:before{content:"\f0f4"}.fa-cog:before{content:"\f013"}.fa-cogs:before{content:"\f085"}.fa-coins:before{content:"\f51e"}.fa-columns:before{content:"\f0db"}.fa-comment:before{content:"\f075"}.fa-comment-alt:before{content:"\f27a"}.fa-comment-dollar:before{content:"\f651"}.fa-comment-dots:before{content:"\f4ad"}.fa-comment-medical:before{content:"\f7f5"}.fa-comment-slash:before{content:"\f4b3"}.fa-comments:before{content:"\f086"}.fa-comments-dollar:before{content:"\f653"}.fa-compact-disc:before{content:"\f51f"}.fa-compass:before{content:"\f14e"}.fa-compress:before{content:"\f066"}.fa-compress-arrows-alt:before{content:"\f78c"}.fa-concierge-bell:before{content:"\f562"}.fa-confluence:before{content:"\f78d"}.fa-connectdevelop:before{content:"\f20e"}.fa-contao:before{content:"\f26d"}.fa-cookie:before{content:"\f563"}.fa-cookie-bite:before{content:"\f564"}.fa-copy:before{content:"\f0c5"}.fa-copyright:before{content:"\f1f9"}.fa-couch:before{content:"\f4b8"}.fa-cpanel:before{content:"\f388"}.fa-creative-commons:before{content:"\f25e"}.fa-creative-commons-by:before{content:"\f4e7"}.fa-creative-commons-nc:before{content:"\f4e8"}.fa-creative-commons-nc-eu:before{content:"\f4e9"}.fa-creative-commons-nc-jp:before{content:"\f4ea"}.fa-creative-commons-nd:before{content:"\f4eb"}.fa-creative-commons-pd:before{content:"\f4ec"}.fa-creative-commons-pd-alt:before{content:"\f4ed"}.fa-creative-commons-remix:before{content:"\f4ee"}.fa-creative-commons-sa:before{content:"\f4ef"}.fa-creative-commons-sampling:before{content:"\f4f0"}.fa-creative-commons-sampling-plus:before{content:"\f4f1"}.fa-creative-commons-share:before{content:"\f4f2"}.fa-creative-commons-zero:before{content:"\f4f3"}.fa-credit-card:before{content:"\f09d"}.fa-critical-role:before{content:"\f6c9"}.fa-crop:before{content:"\f125"}.fa-crop-alt:before{content:"\f565"}.fa-cross:before{content:"\f654"}.fa-crosshairs:before{content:"\f05b"}.fa-crow:before{content:"\f520"}.fa-crown:before{content:"\f521"}.fa-crutch:before{content:"\f7f7"}.fa-css3:before{content:"\f13c"}.fa-css3-alt:before{content:"\f38b"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-cut:before{content:"\f0c4"}.fa-cuttlefish:before{content:"\f38c"}.fa-d-and-d:before{content:"\f38d"}.fa-d-and-d-beyond:before{content:"\f6ca"}.fa-dashcube:before{content:"\f210"}.fa-database:before{content:"\f1c0"}.fa-deaf:before{content:"\f2a4"}.fa-delicious:before{content:"\f1a5"}.fa-democrat:before{content:"\f747"}.fa-deploydog:before{content:"\f38e"}.fa-deskpro:before{content:"\f38f"}.fa-desktop:before{content:"\f108"}.fa-dev:before{content:"\f6cc"}.fa-deviantart:before{content:"\f1bd"}.fa-dharmachakra:before{content:"\f655"}.fa-dhl:before{content:"\f790"}.fa-diagnoses:before{content:"\f470"}.fa-diaspora:before{content:"\f791"}.fa-dice:before{content:"\f522"}.fa-dice-d20:before{content:"\f6cf"}.fa-dice-d6:before{content:"\f6d1"}.fa-dice-five:before{content:"\f523"}.fa-dice-four:before{content:"\f524"}.fa-dice-one:before{content:"\f525"}.fa-dice-six:before{content:"\f526"}.fa-dice-three:before{content:"\f527"}.fa-dice-two:before{content:"\f528"}.fa-digg:before{content:"\f1a6"}.fa-digital-ocean:before{content:"\f391"}.fa-digital-tachograph:before{content:"\f566"}.fa-directions:before{content:"\f5eb"}.fa-discord:before{content:"\f392"}.fa-discourse:before{content:"\f393"}.fa-divide:before{content:"\f529"}.fa-dizzy:before{content:"\f567"}.fa-dna:before{content:"\f471"}.fa-dochub:before{content:"\f394"}.fa-docker:before{content:"\f395"}.fa-dog:before{content:"\f6d3"}.fa-dollar-sign:before{content:"\f155"}.fa-dolly:before{content:"\f472"}.fa-dolly-flatbed:before{content:"\f474"}.fa-donate:before{content:"\f4b9"}.fa-door-closed:before{content:"\f52a"}.fa-door-open:before{content:"\f52b"}.fa-dot-circle:before{content:"\f192"}.fa-dove:before{content:"\f4ba"}.fa-download:before{content:"\f019"}.fa-draft2digital:before{content:"\f396"}.fa-drafting-compass:before{content:"\f568"}.fa-dragon:before{content:"\f6d5"}.fa-draw-polygon:before{content:"\f5ee"}.fa-dribbble:before{content:"\f17d"}.fa-dribbble-square:before{content:"\f397"}.fa-dropbox:before{content:"\f16b"}.fa-drum:before{content:"\f569"}.fa-drum-steelpan:before{content:"\f56a"}.fa-drumstick-bite:before{content:"\f6d7"}.fa-drupal:before{content:"\f1a9"}.fa-dumbbell:before{content:"\f44b"}.fa-dumpster:before{content:"\f793"}.fa-dumpster-fire:before{content:"\f794"}.fa-dungeon:before{content:"\f6d9"}.fa-dyalog:before{content:"\f399"}.fa-earlybirds:before{content:"\f39a"}.fa-ebay:before{content:"\f4f4"}.fa-edge:before{content:"\f282"}.fa-edit:before{content:"\f044"}.fa-egg:before{content:"\f7fb"}.fa-eject:before{content:"\f052"}.fa-elementor:before{content:"\f430"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-ello:before{content:"\f5f1"}.fa-ember:before{content:"\f423"}.fa-empire:before{content:"\f1d1"}.fa-envelope:before{content:"\f0e0"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-text:before{content:"\f658"}.fa-envelope-square:before{content:"\f199"}.fa-envira:before{content:"\f299"}.fa-equals:before{content:"\f52c"}.fa-eraser:before{content:"\f12d"}.fa-erlang:before{content:"\f39d"}.fa-ethereum:before{content:"\f42e"}.fa-ethernet:before{content:"\f796"}.fa-etsy:before{content:"\f2d7"}.fa-euro-sign:before{content:"\f153"}.fa-exchange-alt:before{content:"\f362"}.fa-exclamation:before{content:"\f12a"}.fa-exclamation-circle:before{content:"\f06a"}.fa-exclamation-triangle:before{content:"\f071"}.fa-expand:before{content:"\f065"}.fa-expand-arrows-alt:before{content:"\f31e"}.fa-expeditedssl:before{content:"\f23e"}.fa-external-link-alt:before{content:"\f35d"}.fa-external-link-square-alt:before{content:"\f360"}.fa-eye:before{content:"\f06e"}.fa-eye-dropper:before{content:"\f1fb"}.fa-eye-slash:before{content:"\f070"}.fa-facebook:before{content:"\f09a"}.fa-facebook-f:before{content:"\f39e"}.fa-facebook-messenger:before{content:"\f39f"}.fa-facebook-square:before{content:"\f082"}.fa-fantasy-flight-games:before{content:"\f6dc"}.fa-fast-backward:before{content:"\f049"}.fa-fast-forward:before{content:"\f050"}.fa-fax:before{content:"\f1ac"}.fa-feather:before{content:"\f52d"}.fa-feather-alt:before{content:"\f56b"}.fa-fedex:before{content:"\f797"}.fa-fedora:before{content:"\f798"}.fa-female:before{content:"\f182"}.fa-fighter-jet:before{content:"\f0fb"}.fa-figma:before{content:"\f799"}.fa-file:before{content:"\f15b"}.fa-file-alt:before{content:"\f15c"}.fa-file-archive:before{content:"\f1c6"}.fa-file-audio:before{content:"\f1c7"}.fa-file-code:before{content:"\f1c9"}.fa-file-contract:before{content:"\f56c"}.fa-file-csv:before{content:"\f6dd"}.fa-file-download:before{content:"\f56d"}.fa-file-excel:before{content:"\f1c3"}.fa-file-export:before{content:"\f56e"}.fa-file-image:before{content:"\f1c5"}.fa-file-import:before{content:"\f56f"}.fa-file-invoice:before{content:"\f570"}.fa-file-invoice-dollar:before{content:"\f571"}.fa-file-medical:before{content:"\f477"}.fa-file-medical-alt:before{content:"\f478"}.fa-file-pdf:before{content:"\f1c1"}.fa-file-powerpoint:before{content:"\f1c4"}.fa-file-prescription:before{content:"\f572"}.fa-file-signature:before{content:"\f573"}.fa-file-upload:before{content:"\f574"}.fa-file-video:before{content:"\f1c8"}.fa-file-word:before{content:"\f1c2"}.fa-fill:before{content:"\f575"}.fa-fill-drip:before{content:"\f576"}.fa-film:before{content:"\f008"}.fa-filter:before{content:"\f0b0"}.fa-fingerprint:before{content:"\f577"}.fa-fire:before{content:"\f06d"}.fa-fire-alt:before{content:"\f7e4"}.fa-fire-extinguisher:before{content:"\f134"}.fa-firefox:before{content:"\f269"}.fa-first-aid:before{content:"\f479"}.fa-first-order:before{content:"\f2b0"}.fa-first-order-alt:before{content:"\f50a"}.fa-firstdraft:before{content:"\f3a1"}.fa-fish:before{content:"\f578"}.fa-fist-raised:before{content:"\f6de"}.fa-flag:before{content:"\f024"}.fa-flag-checkered:before{content:"\f11e"}.fa-flag-usa:before{content:"\f74d"}.fa-flask:before{content:"\f0c3"}.fa-flickr:before{content:"\f16e"}.fa-flipboard:before{content:"\f44d"}.fa-flushed:before{content:"\f579"}.fa-fly:before{content:"\f417"}.fa-folder:before{content:"\f07b"}.fa-folder-minus:before{content:"\f65d"}.fa-folder-open:before{content:"\f07c"}.fa-folder-plus:before{content:"\f65e"}.fa-font:before{content:"\f031"}.fa-font-awesome:before{content:"\f2b4"}.fa-font-awesome-alt:before{content:"\f35c"}.fa-font-awesome-flag:before{content:"\f425"}.fa-font-awesome-logo-full:before{content:"\f4e6"}.fa-fonticons:before{content:"\f280"}.fa-fonticons-fi:before{content:"\f3a2"}.fa-football-ball:before{content:"\f44e"}.fa-fort-awesome:before{content:"\f286"}.fa-fort-awesome-alt:before{content:"\f3a3"}.fa-forumbee:before{content:"\f211"}.fa-forward:before{content:"\f04e"}.fa-foursquare:before{content:"\f180"}.fa-free-code-camp:before{content:"\f2c5"}.fa-freebsd:before{content:"\f3a4"}.fa-frog:before{content:"\f52e"}.fa-frown:before{content:"\f119"}.fa-frown-open:before{content:"\f57a"}.fa-fulcrum:before{content:"\f50b"}.fa-funnel-dollar:before{content:"\f662"}.fa-futbol:before{content:"\f1e3"}.fa-galactic-republic:before{content:"\f50c"}.fa-galactic-senate:before{content:"\f50d"}.fa-gamepad:before{content:"\f11b"}.fa-gas-pump:before{content:"\f52f"}.fa-gavel:before{content:"\f0e3"}.fa-gem:before{content:"\f3a5"}.fa-genderless:before{content:"\f22d"}.fa-get-pocket:before{content:"\f265"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-ghost:before{content:"\f6e2"}.fa-gift:before{content:"\f06b"}.fa-gifts:before{content:"\f79c"}.fa-git:before{content:"\f1d3"}.fa-git-square:before{content:"\f1d2"}.fa-github:before{content:"\f09b"}.fa-github-alt:before{content:"\f113"}.fa-github-square:before{content:"\f092"}.fa-gitkraken:before{content:"\f3a6"}.fa-gitlab:before{content:"\f296"}.fa-gitter:before{content:"\f426"}.fa-glass-cheers:before{content:"\f79f"}.fa-glass-martini:before{content:"\f000"}.fa-glass-martini-alt:before{content:"\f57b"}.fa-glass-whiskey:before{content:"\f7a0"}.fa-glasses:before{content:"\f530"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-globe:before{content:"\f0ac"}.fa-globe-africa:before{content:"\f57c"}.fa-globe-americas:before{content:"\f57d"}.fa-globe-asia:before{content:"\f57e"}.fa-globe-europe:before{content:"\f7a2"}.fa-gofore:before{content:"\f3a7"}.fa-golf-ball:before{content:"\f450"}.fa-goodreads:before{content:"\f3a8"}.fa-goodreads-g:before{content:"\f3a9"}.fa-google:before{content:"\f1a0"}.fa-google-drive:before{content:"\f3aa"}.fa-google-play:before{content:"\f3ab"}.fa-google-plus:before{content:"\f2b3"}.fa-google-plus-g:before{content:"\f0d5"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-wallet:before{content:"\f1ee"}.fa-gopuram:before{content:"\f664"}.fa-graduation-cap:before{content:"\f19d"}.fa-gratipay:before{content:"\f184"}.fa-grav:before{content:"\f2d6"}.fa-greater-than:before{content:"\f531"}.fa-greater-than-equal:before{content:"\f532"}.fa-grimace:before{content:"\f57f"}.fa-grin:before{content:"\f580"}.fa-grin-alt:before{content:"\f581"}.fa-grin-beam:before{content:"\f582"}.fa-grin-beam-sweat:before{content:"\f583"}.fa-grin-hearts:before{content:"\f584"}.fa-grin-squint:before{content:"\f585"}.fa-grin-squint-tears:before{content:"\f586"}.fa-grin-stars:before{content:"\f587"}.fa-grin-tears:before{content:"\f588"}.fa-grin-tongue:before{content:"\f589"}.fa-grin-tongue-squint:before{content:"\f58a"}.fa-grin-tongue-wink:before{content:"\f58b"}.fa-grin-wink:before{content:"\f58c"}.fa-grip-horizontal:before{content:"\f58d"}.fa-grip-lines:before{content:"\f7a4"}.fa-grip-lines-vertical:before{content:"\f7a5"}.fa-grip-vertical:before{content:"\f58e"}.fa-gripfire:before{content:"\f3ac"}.fa-grunt:before{content:"\f3ad"}.fa-guitar:before{content:"\f7a6"}.fa-gulp:before{content:"\f3ae"}.fa-h-square:before{content:"\f0fd"}.fa-hacker-news:before{content:"\f1d4"}.fa-hacker-news-square:before{content:"\f3af"}.fa-hackerrank:before{content:"\f5f7"}.fa-hamburger:before{content:"\f805"}.fa-hammer:before{content:"\f6e3"}.fa-hamsa:before{content:"\f665"}.fa-hand-holding:before{content:"\f4bd"}.fa-hand-holding-heart:before{content:"\f4be"}.fa-hand-holding-usd:before{content:"\f4c0"}.fa-hand-lizard:before{content:"\f258"}.fa-hand-middle-finger:before{content:"\f806"}.fa-hand-paper:before{content:"\f256"}.fa-hand-peace:before{content:"\f25b"}.fa-hand-point-down:before{content:"\f0a7"}.fa-hand-point-left:before{content:"\f0a5"}.fa-hand-point-right:before{content:"\f0a4"}.fa-hand-point-up:before{content:"\f0a6"}.fa-hand-pointer:before{content:"\f25a"}.fa-hand-rock:before{content:"\f255"}.fa-hand-scissors:before{content:"\f257"}.fa-hand-spock:before{content:"\f259"}.fa-hands:before{content:"\f4c2"}.fa-hands-helping:before{content:"\f4c4"}.fa-handshake:before{content:"\f2b5"}.fa-hanukiah:before{content:"\f6e6"}.fa-hard-hat:before{content:"\f807"}.fa-hashtag:before{content:"\f292"}.fa-hat-wizard:before{content:"\f6e8"}.fa-haykal:before{content:"\f666"}.fa-hdd:before{content:"\f0a0"}.fa-heading:before{content:"\f1dc"}.fa-headphones:before{content:"\f025"}.fa-headphones-alt:before{content:"\f58f"}.fa-headset:before{content:"\f590"}.fa-heart:before{content:"\f004"}.fa-heart-broken:before{content:"\f7a9"}.fa-heartbeat:before{content:"\f21e"}.fa-helicopter:before{content:"\f533"}.fa-highlighter:before{content:"\f591"}.fa-hiking:before{content:"\f6ec"}.fa-hippo:before{content:"\f6ed"}.fa-hips:before{content:"\f452"}.fa-hire-a-helper:before{content:"\f3b0"}.fa-history:before{content:"\f1da"}.fa-hockey-puck:before{content:"\f453"}.fa-holly-berry:before{content:"\f7aa"}.fa-home:before{content:"\f015"}.fa-hooli:before{content:"\f427"}.fa-hornbill:before{content:"\f592"}.fa-horse:before{content:"\f6f0"}.fa-horse-head:before{content:"\f7ab"}.fa-hospital:before{content:"\f0f8"}.fa-hospital-alt:before{content:"\f47d"}.fa-hospital-symbol:before{content:"\f47e"}.fa-hot-tub:before{content:"\f593"}.fa-hotdog:before{content:"\f80f"}.fa-hotel:before{content:"\f594"}.fa-hotjar:before{content:"\f3b1"}.fa-hourglass:before{content:"\f254"}.fa-hourglass-end:before{content:"\f253"}.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-start:before{content:"\f251"}.fa-house-damage:before{content:"\f6f1"}.fa-houzz:before{content:"\f27c"}.fa-hryvnia:before{content:"\f6f2"}.fa-html5:before{content:"\f13b"}.fa-hubspot:before{content:"\f3b2"}.fa-i-cursor:before{content:"\f246"}.fa-ice-cream:before{content:"\f810"}.fa-icicles:before{content:"\f7ad"}.fa-id-badge:before{content:"\f2c1"}.fa-id-card:before{content:"\f2c2"}.fa-id-card-alt:before{content:"\f47f"}.fa-igloo:before{content:"\f7ae"}.fa-image:before{content:"\f03e"}.fa-images:before{content:"\f302"}.fa-imdb:before{content:"\f2d8"}.fa-inbox:before{content:"\f01c"}.fa-indent:before{content:"\f03c"}.fa-industry:before{content:"\f275"}.fa-infinity:before{content:"\f534"}.fa-info:before{content:"\f129"}.fa-info-circle:before{content:"\f05a"}.fa-instagram:before{content:"\f16d"}.fa-intercom:before{content:"\f7af"}.fa-internet-explorer:before{content:"\f26b"}.fa-invision:before{content:"\f7b0"}.fa-ioxhost:before{content:"\f208"}.fa-italic:before{content:"\f033"}.fa-itunes:before{content:"\f3b4"}.fa-itunes-note:before{content:"\f3b5"}.fa-java:before{content:"\f4e4"}.fa-jedi:before{content:"\f669"}.fa-jedi-order:before{content:"\f50e"}.fa-jenkins:before{content:"\f3b6"}.fa-jira:before{content:"\f7b1"}.fa-joget:before{content:"\f3b7"}.fa-joint:before{content:"\f595"}.fa-joomla:before{content:"\f1aa"}.fa-journal-whills:before{content:"\f66a"}.fa-js:before{content:"\f3b8"}.fa-js-square:before{content:"\f3b9"}.fa-jsfiddle:before{content:"\f1cc"}.fa-kaaba:before{content:"\f66b"}.fa-kaggle:before{content:"\f5fa"}.fa-key:before{content:"\f084"}.fa-keybase:before{content:"\f4f5"}.fa-keyboard:before{content:"\f11c"}.fa-keycdn:before{content:"\f3ba"}.fa-khanda:before{content:"\f66d"}.fa-kickstarter:before{content:"\f3bb"}.fa-kickstarter-k:before{content:"\f3bc"}.fa-kiss:before{content:"\f596"}.fa-kiss-beam:before{content:"\f597"}.fa-kiss-wink-heart:before{content:"\f598"}.fa-kiwi-bird:before{content:"\f535"}.fa-korvue:before{content:"\f42f"}.fa-landmark:before{content:"\f66f"}.fa-language:before{content:"\f1ab"}.fa-laptop:before{content:"\f109"}.fa-laptop-code:before{content:"\f5fc"}.fa-laptop-medical:before{content:"\f812"}.fa-laravel:before{content:"\f3bd"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-laugh:before{content:"\f599"}.fa-laugh-beam:before{content:"\f59a"}.fa-laugh-squint:before{content:"\f59b"}.fa-laugh-wink:before{content:"\f59c"}.fa-layer-group:before{content:"\f5fd"}.fa-leaf:before{content:"\f06c"}.fa-leanpub:before{content:"\f212"}.fa-lemon:before{content:"\f094"}.fa-less:before{content:"\f41d"}.fa-less-than:before{content:"\f536"}.fa-less-than-equal:before{content:"\f537"}.fa-level-down-alt:before{content:"\f3be"}.fa-level-up-alt:before{content:"\f3bf"}.fa-life-ring:before{content:"\f1cd"}.fa-lightbulb:before{content:"\f0eb"}.fa-line:before{content:"\f3c0"}.fa-link:before{content:"\f0c1"}.fa-linkedin:before{content:"\f08c"}.fa-linkedin-in:before{content:"\f0e1"}.fa-linode:before{content:"\f2b8"}.fa-linux:before{content:"\f17c"}.fa-lira-sign:before{content:"\f195"}.fa-list:before{content:"\f03a"}.fa-list-alt:before{content:"\f022"}.fa-list-ol:before{content:"\f0cb"}.fa-list-ul:before{content:"\f0ca"}.fa-location-arrow:before{content:"\f124"}.fa-lock:before{content:"\f023"}.fa-lock-open:before{content:"\f3c1"}.fa-long-arrow-alt-down:before{content:"\f309"}.fa-long-arrow-alt-left:before{content:"\f30a"}.fa-long-arrow-alt-right:before{content:"\f30b"}.fa-long-arrow-alt-up:before{content:"\f30c"}.fa-low-vision:before{content:"\f2a8"}.fa-luggage-cart:before{content:"\f59d"}.fa-lyft:before{content:"\f3c3"}.fa-magento:before{content:"\f3c4"}.fa-magic:before{content:"\f0d0"}.fa-magnet:before{content:"\f076"}.fa-mail-bulk:before{content:"\f674"}.fa-mailchimp:before{content:"\f59e"}.fa-male:before{content:"\f183"}.fa-mandalorian:before{content:"\f50f"}.fa-map:before{content:"\f279"}.fa-map-marked:before{content:"\f59f"}.fa-map-marked-alt:before{content:"\f5a0"}.fa-map-marker:before{content:"\f041"}.fa-map-marker-alt:before{content:"\f3c5"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-markdown:before{content:"\f60f"}.fa-marker:before{content:"\f5a1"}.fa-mars:before{content:"\f222"}.fa-mars-double:before{content:"\f227"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mask:before{content:"\f6fa"}.fa-mastodon:before{content:"\f4f6"}.fa-maxcdn:before{content:"\f136"}.fa-medal:before{content:"\f5a2"}.fa-medapps:before{content:"\f3c6"}.fa-medium:before{content:"\f23a"}.fa-medium-m:before{content:"\f3c7"}.fa-medkit:before{content:"\f0fa"}.fa-medrt:before{content:"\f3c8"}.fa-meetup:before{content:"\f2e0"}.fa-megaport:before{content:"\f5a3"}.fa-meh:before{content:"\f11a"}.fa-meh-blank:before{content:"\f5a4"}.fa-meh-rolling-eyes:before{content:"\f5a5"}.fa-memory:before{content:"\f538"}.fa-mendeley:before{content:"\f7b3"}.fa-menorah:before{content:"\f676"}.fa-mercury:before{content:"\f223"}.fa-meteor:before{content:"\f753"}.fa-microchip:before{content:"\f2db"}.fa-microphone:before{content:"\f130"}.fa-microphone-alt:before{content:"\f3c9"}.fa-microphone-alt-slash:before{content:"\f539"}.fa-microphone-slash:before{content:"\f131"}.fa-microscope:before{content:"\f610"}.fa-microsoft:before{content:"\f3ca"}.fa-minus:before{content:"\f068"}.fa-minus-circle:before{content:"\f056"}.fa-minus-square:before{content:"\f146"}.fa-mitten:before{content:"\f7b5"}.fa-mix:before{content:"\f3cb"}.fa-mixcloud:before{content:"\f289"}.fa-mizuni:before{content:"\f3cc"}.fa-mobile:before{content:"\f10b"}.fa-mobile-alt:before{content:"\f3cd"}.fa-modx:before{content:"\f285"}.fa-monero:before{content:"\f3d0"}.fa-money-bill:before{content:"\f0d6"}.fa-money-bill-alt:before{content:"\f3d1"}.fa-money-bill-wave:before{content:"\f53a"}.fa-money-bill-wave-alt:before{content:"\f53b"}.fa-money-check:before{content:"\f53c"}.fa-money-check-alt:before{content:"\f53d"}.fa-monument:before{content:"\f5a6"}.fa-moon:before{content:"\f186"}.fa-mortar-pestle:before{content:"\f5a7"}.fa-mosque:before{content:"\f678"}.fa-motorcycle:before{content:"\f21c"}.fa-mountain:before{content:"\f6fc"}.fa-mouse-pointer:before{content:"\f245"}.fa-mug-hot:before{content:"\f7b6"}.fa-music:before{content:"\f001"}.fa-napster:before{content:"\f3d2"}.fa-neos:before{content:"\f612"}.fa-network-wired:before{content:"\f6ff"}.fa-neuter:before{content:"\f22c"}.fa-newspaper:before{content:"\f1ea"}.fa-nimblr:before{content:"\f5a8"}.fa-nintendo-switch:before{content:"\f418"}.fa-node:before{content:"\f419"}.fa-node-js:before{content:"\f3d3"}.fa-not-equal:before{content:"\f53e"}.fa-notes-medical:before{content:"\f481"}.fa-npm:before{content:"\f3d4"}.fa-ns8:before{content:"\f3d5"}.fa-nutritionix:before{content:"\f3d6"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-oil-can:before{content:"\f613"}.fa-old-republic:before{content:"\f510"}.fa-om:before{content:"\f679"}.fa-opencart:before{content:"\f23d"}.fa-openid:before{content:"\f19b"}.fa-opera:before{content:"\f26a"}.fa-optin-monster:before{content:"\f23c"}.fa-osi:before{content:"\f41a"}.fa-otter:before{content:"\f700"}.fa-outdent:before{content:"\f03b"}.fa-page4:before{content:"\f3d7"}.fa-pagelines:before{content:"\f18c"}.fa-pager:before{content:"\f815"}.fa-paint-brush:before{content:"\f1fc"}.fa-paint-roller:before{content:"\f5aa"}.fa-palette:before{content:"\f53f"}.fa-palfed:before{content:"\f3d8"}.fa-pallet:before{content:"\f482"}.fa-paper-plane:before{content:"\f1d8"}.fa-paperclip:before{content:"\f0c6"}.fa-parachute-box:before{content:"\f4cd"}.fa-paragraph:before{content:"\f1dd"}.fa-parking:before{content:"\f540"}.fa-passport:before{content:"\f5ab"}.fa-pastafarianism:before{content:"\f67b"}.fa-paste:before{content:"\f0ea"}.fa-patreon:before{content:"\f3d9"}.fa-pause:before{content:"\f04c"}.fa-pause-circle:before{content:"\f28b"}.fa-paw:before{content:"\f1b0"}.fa-paypal:before{content:"\f1ed"}.fa-peace:before{content:"\f67c"}.fa-pen:before{content:"\f304"}.fa-pen-alt:before{content:"\f305"}.fa-pen-fancy:before{content:"\f5ac"}.fa-pen-nib:before{content:"\f5ad"}.fa-pen-square:before{content:"\f14b"}.fa-pencil-alt:before{content:"\f303"}.fa-pencil-ruler:before{content:"\f5ae"}.fa-penny-arcade:before{content:"\f704"}.fa-people-carry:before{content:"\f4ce"}.fa-pepper-hot:before{content:"\f816"}.fa-percent:before{content:"\f295"}.fa-percentage:before{content:"\f541"}.fa-periscope:before{content:"\f3da"}.fa-person-booth:before{content:"\f756"}.fa-phabricator:before{content:"\f3db"}.fa-phoenix-framework:before{content:"\f3dc"}.fa-phoenix-squadron:before{content:"\f511"}.fa-phone:before{content:"\f095"}.fa-phone-slash:before{content:"\f3dd"}.fa-phone-square:before{content:"\f098"}.fa-phone-volume:before{content:"\f2a0"}.fa-php:before{content:"\f457"}.fa-pied-piper:before{content:"\f2ae"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-pied-piper-hat:before{content:"\f4e5"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-piggy-bank:before{content:"\f4d3"}.fa-pills:before{content:"\f484"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-p:before{content:"\f231"}.fa-pinterest-square:before{content:"\f0d3"}.fa-pizza-slice:before{content:"\f818"}.fa-place-of-worship:before{content:"\f67f"}.fa-plane:before{content:"\f072"}.fa-plane-arrival:before{content:"\f5af"}.fa-plane-departure:before{content:"\f5b0"}.fa-play:before{content:"\f04b"}.fa-play-circle:before{content:"\f144"}.fa-playstation:before{content:"\f3df"}.fa-plug:before{content:"\f1e6"}.fa-plus:before{content:"\f067"}.fa-plus-circle:before{content:"\f055"}.fa-plus-square:before{content:"\f0fe"}.fa-podcast:before{content:"\f2ce"}.fa-poll:before{content:"\f681"}.fa-poll-h:before{content:"\f682"}.fa-poo:before{content:"\f2fe"}.fa-poo-storm:before{content:"\f75a"}.fa-poop:before{content:"\f619"}.fa-portrait:before{content:"\f3e0"}.fa-pound-sign:before{content:"\f154"}.fa-power-off:before{content:"\f011"}.fa-pray:before{content:"\f683"}.fa-praying-hands:before{content:"\f684"}.fa-prescription:before{content:"\f5b1"}.fa-prescription-bottle:before{content:"\f485"}.fa-prescription-bottle-alt:before{content:"\f486"}.fa-print:before{content:"\f02f"}.fa-procedures:before{content:"\f487"}.fa-product-hunt:before{content:"\f288"}.fa-project-diagram:before{content:"\f542"}.fa-pushed:before{content:"\f3e1"}.fa-puzzle-piece:before{content:"\f12e"}.fa-python:before{content:"\f3e2"}.fa-qq:before{content:"\f1d6"}.fa-qrcode:before{content:"\f029"}.fa-question:before{content:"\f128"}.fa-question-circle:before{content:"\f059"}.fa-quidditch:before{content:"\f458"}.fa-quinscape:before{content:"\f459"}.fa-quora:before{content:"\f2c4"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-quran:before{content:"\f687"}.fa-r-project:before{content:"\f4f7"}.fa-radiation:before{content:"\f7b9"}.fa-radiation-alt:before{content:"\f7ba"}.fa-rainbow:before{content:"\f75b"}.fa-random:before{content:"\f074"}.fa-raspberry-pi:before{content:"\f7bb"}.fa-ravelry:before{content:"\f2d9"}.fa-react:before{content:"\f41b"}.fa-reacteurope:before{content:"\f75d"}.fa-readme:before{content:"\f4d5"}.fa-rebel:before{content:"\f1d0"}.fa-receipt:before{content:"\f543"}.fa-recycle:before{content:"\f1b8"}.fa-red-river:before{content:"\f3e3"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-alien:before{content:"\f281"}.fa-reddit-square:before{content:"\f1a2"}.fa-redhat:before{content:"\f7bc"}.fa-redo:before{content:"\f01e"}.fa-redo-alt:before{content:"\f2f9"}.fa-registered:before{content:"\f25d"}.fa-renren:before{content:"\f18b"}.fa-reply:before{content:"\f3e5"}.fa-reply-all:before{content:"\f122"}.fa-replyd:before{content:"\f3e6"}.fa-republican:before{content:"\f75e"}.fa-researchgate:before{content:"\f4f8"}.fa-resolving:before{content:"\f3e7"}.fa-restroom:before{content:"\f7bd"}.fa-retweet:before{content:"\f079"}.fa-rev:before{content:"\f5b2"}.fa-ribbon:before{content:"\f4d6"}.fa-ring:before{content:"\f70b"}.fa-road:before{content:"\f018"}.fa-robot:before{content:"\f544"}.fa-rocket:before{content:"\f135"}.fa-rocketchat:before{content:"\f3e8"}.fa-rockrms:before{content:"\f3e9"}.fa-route:before{content:"\f4d7"}.fa-rss:before{content:"\f09e"}.fa-rss-square:before{content:"\f143"}.fa-ruble-sign:before{content:"\f158"}.fa-ruler:before{content:"\f545"}.fa-ruler-combined:before{content:"\f546"}.fa-ruler-horizontal:before{content:"\f547"}.fa-ruler-vertical:before{content:"\f548"}.fa-running:before{content:"\f70c"}.fa-rupee-sign:before{content:"\f156"}.fa-sad-cry:before{content:"\f5b3"}.fa-sad-tear:before{content:"\f5b4"}.fa-safari:before{content:"\f267"}.fa-sass:before{content:"\f41e"}.fa-satellite:before{content:"\f7bf"}.fa-satellite-dish:before{content:"\f7c0"}.fa-save:before{content:"\f0c7"}.fa-schlix:before{content:"\f3ea"}.fa-school:before{content:"\f549"}.fa-screwdriver:before{content:"\f54a"}.fa-scribd:before{content:"\f28a"}.fa-scroll:before{content:"\f70e"}.fa-sd-card:before{content:"\f7c2"}.fa-search:before{content:"\f002"}.fa-search-dollar:before{content:"\f688"}.fa-search-location:before{content:"\f689"}.fa-search-minus:before{content:"\f010"}.fa-search-plus:before{content:"\f00e"}.fa-searchengin:before{content:"\f3eb"}.fa-seedling:before{content:"\f4d8"}.fa-sellcast:before{content:"\f2da"}.fa-sellsy:before{content:"\f213"}.fa-server:before{content:"\f233"}.fa-servicestack:before{content:"\f3ec"}.fa-shapes:before{content:"\f61f"}.fa-share:before{content:"\f064"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-share-square:before{content:"\f14d"}.fa-shekel-sign:before{content:"\f20b"}.fa-shield-alt:before{content:"\f3ed"}.fa-ship:before{content:"\f21a"}.fa-shipping-fast:before{content:"\f48b"}.fa-shirtsinbulk:before{content:"\f214"}.fa-shoe-prints:before{content:"\f54b"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-shopping-cart:before{content:"\f07a"}.fa-shopware:before{content:"\f5b5"}.fa-shower:before{content:"\f2cc"}.fa-shuttle-van:before{content:"\f5b6"}.fa-sign:before{content:"\f4d9"}.fa-sign-in-alt:before{content:"\f2f6"}.fa-sign-language:before{content:"\f2a7"}.fa-sign-out-alt:before{content:"\f2f5"}.fa-signal:before{content:"\f012"}.fa-signature:before{content:"\f5b7"}.fa-sim-card:before{content:"\f7c4"}.fa-simplybuilt:before{content:"\f215"}.fa-sistrix:before{content:"\f3ee"}.fa-sitemap:before{content:"\f0e8"}.fa-sith:before{content:"\f512"}.fa-skating:before{content:"\f7c5"}.fa-sketch:before{content:"\f7c6"}.fa-skiing:before{content:"\f7c9"}.fa-skiing-nordic:before{content:"\f7ca"}.fa-skull:before{content:"\f54c"}.fa-skull-crossbones:before{content:"\f714"}.fa-skyatlas:before{content:"\f216"}.fa-skype:before{content:"\f17e"}.fa-slack:before{content:"\f198"}.fa-slack-hash:before{content:"\f3ef"}.fa-slash:before{content:"\f715"}.fa-sleigh:before{content:"\f7cc"}.fa-sliders-h:before{content:"\f1de"}.fa-slideshare:before{content:"\f1e7"}.fa-smile:before{content:"\f118"}.fa-smile-beam:before{content:"\f5b8"}.fa-smile-wink:before{content:"\f4da"}.fa-smog:before{content:"\f75f"}.fa-smoking:before{content:"\f48d"}.fa-smoking-ban:before{content:"\f54d"}.fa-sms:before{content:"\f7cd"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-snowboarding:before{content:"\f7ce"}.fa-snowflake:before{content:"\f2dc"}.fa-snowman:before{content:"\f7d0"}.fa-snowplow:before{content:"\f7d2"}.fa-socks:before{content:"\f696"}.fa-solar-panel:before{content:"\f5ba"}.fa-sort:before{content:"\f0dc"}.fa-sort-alpha-down:before{content:"\f15d"}.fa-sort-alpha-up:before{content:"\f15e"}.fa-sort-amount-down:before{content:"\f160"}.fa-sort-amount-up:before{content:"\f161"}.fa-sort-down:before{content:"\f0dd"}.fa-sort-numeric-down:before{content:"\f162"}.fa-sort-numeric-up:before{content:"\f163"}.fa-sort-up:before{content:"\f0de"}.fa-soundcloud:before{content:"\f1be"}.fa-sourcetree:before{content:"\f7d3"}.fa-spa:before{content:"\f5bb"}.fa-space-shuttle:before{content:"\f197"}.fa-speakap:before{content:"\f3f3"}.fa-spider:before{content:"\f717"}.fa-spinner:before{content:"\f110"}.fa-splotch:before{content:"\f5bc"}.fa-spotify:before{content:"\f1bc"}.fa-spray-can:before{content:"\f5bd"}.fa-square:before{content:"\f0c8"}.fa-square-full:before{content:"\f45c"}.fa-square-root-alt:before{content:"\f698"}.fa-squarespace:before{content:"\f5be"}.fa-stack-exchange:before{content:"\f18d"}.fa-stack-overflow:before{content:"\f16c"}.fa-stamp:before{content:"\f5bf"}.fa-star:before{content:"\f005"}.fa-star-and-crescent:before{content:"\f699"}.fa-star-half:before{content:"\f089"}.fa-star-half-alt:before{content:"\f5c0"}.fa-star-of-david:before{content:"\f69a"}.fa-star-of-life:before{content:"\f621"}.fa-staylinked:before{content:"\f3f5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-steam-symbol:before{content:"\f3f6"}.fa-step-backward:before{content:"\f048"}.fa-step-forward:before{content:"\f051"}.fa-stethoscope:before{content:"\f0f1"}.fa-sticker-mule:before{content:"\f3f7"}.fa-sticky-note:before{content:"\f249"}.fa-stop:before{content:"\f04d"}.fa-stop-circle:before{content:"\f28d"}.fa-stopwatch:before{content:"\f2f2"}.fa-store:before{content:"\f54e"}.fa-store-alt:before{content:"\f54f"}.fa-strava:before{content:"\f428"}.fa-stream:before{content:"\f550"}.fa-street-view:before{content:"\f21d"}.fa-strikethrough:before{content:"\f0cc"}.fa-stripe:before{content:"\f429"}.fa-stripe-s:before{content:"\f42a"}.fa-stroopwafel:before{content:"\f551"}.fa-studiovinari:before{content:"\f3f8"}.fa-stumbleupon:before{content:"\f1a4"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-subscript:before{content:"\f12c"}.fa-subway:before{content:"\f239"}.fa-suitcase:before{content:"\f0f2"}.fa-suitcase-rolling:before{content:"\f5c1"}.fa-sun:before{content:"\f185"}.fa-superpowers:before{content:"\f2dd"}.fa-superscript:before{content:"\f12b"}.fa-supple:before{content:"\f3f9"}.fa-surprise:before{content:"\f5c2"}.fa-suse:before{content:"\f7d6"}.fa-swatchbook:before{content:"\f5c3"}.fa-swimmer:before{content:"\f5c4"}.fa-swimming-pool:before{content:"\f5c5"}.fa-synagogue:before{content:"\f69b"}.fa-sync:before{content:"\f021"}.fa-sync-alt:before{content:"\f2f1"}.fa-syringe:before{content:"\f48e"}.fa-table:before{content:"\f0ce"}.fa-table-tennis:before{content:"\f45d"}.fa-tablet:before{content:"\f10a"}.fa-tablet-alt:before{content:"\f3fa"}.fa-tablets:before{content:"\f490"}.fa-tachometer-alt:before{content:"\f3fd"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-tape:before{content:"\f4db"}.fa-tasks:before{content:"\f0ae"}.fa-taxi:before{content:"\f1ba"}.fa-teamspeak:before{content:"\f4f9"}.fa-teeth:before{content:"\f62e"}.fa-teeth-open:before{content:"\f62f"}.fa-telegram:before{content:"\f2c6"}.fa-telegram-plane:before{content:"\f3fe"}.fa-temperature-high:before{content:"\f769"}.fa-temperature-low:before{content:"\f76b"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-tenge:before{content:"\f7d7"}.fa-terminal:before{content:"\f120"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-th:before{content:"\f00a"}.fa-th-large:before{content:"\f009"}.fa-th-list:before{content:"\f00b"}.fa-the-red-yeti:before{content:"\f69d"}.fa-theater-masks:before{content:"\f630"}.fa-themeco:before{content:"\f5c6"}.fa-themeisle:before{content:"\f2b2"}.fa-thermometer:before{content:"\f491"}.fa-thermometer-empty:before{content:"\f2cb"}.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-think-peaks:before{content:"\f731"}.fa-thumbs-down:before{content:"\f165"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbtack:before{content:"\f08d"}.fa-ticket-alt:before{content:"\f3ff"}.fa-times:before{content:"\f00d"}.fa-times-circle:before{content:"\f057"}.fa-tint:before{content:"\f043"}.fa-tint-slash:before{content:"\f5c7"}.fa-tired:before{content:"\f5c8"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-toilet:before{content:"\f7d8"}.fa-toilet-paper:before{content:"\f71e"}.fa-toolbox:before{content:"\f552"}.fa-tools:before{content:"\f7d9"}.fa-tooth:before{content:"\f5c9"}.fa-torah:before{content:"\f6a0"}.fa-torii-gate:before{content:"\f6a1"}.fa-tractor:before{content:"\f722"}.fa-trade-federation:before{content:"\f513"}.fa-trademark:before{content:"\f25c"}.fa-traffic-light:before{content:"\f637"}.fa-train:before{content:"\f238"}.fa-tram:before{content:"\f7da"}.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-trash:before{content:"\f1f8"}.fa-trash-alt:before{content:"\f2ed"}.fa-trash-restore:before{content:"\f829"}.fa-trash-restore-alt:before{content:"\f82a"}.fa-tree:before{content:"\f1bb"}.fa-trello:before{content:"\f181"}.fa-tripadvisor:before{content:"\f262"}.fa-trophy:before{content:"\f091"}.fa-truck:before{content:"\f0d1"}.fa-truck-loading:before{content:"\f4de"}.fa-truck-monster:before{content:"\f63b"}.fa-truck-moving:before{content:"\f4df"}.fa-truck-pickup:before{content:"\f63c"}.fa-tshirt:before{content:"\f553"}.fa-tty:before{content:"\f1e4"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-tv:before{content:"\f26c"}.fa-twitch:before{content:"\f1e8"}.fa-twitter:before{content:"\f099"}.fa-twitter-square:before{content:"\f081"}.fa-typo3:before{content:"\f42b"}.fa-uber:before{content:"\f402"}.fa-ubuntu:before{content:"\f7df"}.fa-uikit:before{content:"\f403"}.fa-umbrella:before{content:"\f0e9"}.fa-umbrella-beach:before{content:"\f5ca"}.fa-underline:before{content:"\f0cd"}.fa-undo:before{content:"\f0e2"}.fa-undo-alt:before{content:"\f2ea"}.fa-uniregistry:before{content:"\f404"}.fa-universal-access:before{content:"\f29a"}.fa-university:before{content:"\f19c"}.fa-unlink:before{content:"\f127"}.fa-unlock:before{content:"\f09c"}.fa-unlock-alt:before{content:"\f13e"}.fa-untappd:before{content:"\f405"}.fa-upload:before{content:"\f093"}.fa-ups:before{content:"\f7e0"}.fa-usb:before{content:"\f287"}.fa-user:before{content:"\f007"}.fa-user-alt:before{content:"\f406"}.fa-user-alt-slash:before{content:"\f4fa"}.fa-user-astronaut:before{content:"\f4fb"}.fa-user-check:before{content:"\f4fc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-clock:before{content:"\f4fd"}.fa-user-cog:before{content:"\f4fe"}.fa-user-edit:before{content:"\f4ff"}.fa-user-friends:before{content:"\f500"}.fa-user-graduate:before{content:"\f501"}.fa-user-injured:before{content:"\f728"}.fa-user-lock:before{content:"\f502"}.fa-user-md:before{content:"\f0f0"}.fa-user-minus:before{content:"\f503"}.fa-user-ninja:before{content:"\f504"}.fa-user-nurse:before{content:"\f82f"}.fa-user-plus:before{content:"\f234"}.fa-user-secret:before{content:"\f21b"}.fa-user-shield:before{content:"\f505"}.fa-user-slash:before{content:"\f506"}.fa-user-tag:before{content:"\f507"}.fa-user-tie:before{content:"\f508"}.fa-user-times:before{content:"\f235"}.fa-users:before{content:"\f0c0"}.fa-users-cog:before{content:"\f509"}.fa-usps:before{content:"\f7e1"}.fa-ussunnah:before{content:"\f407"}.fa-utensil-spoon:before{content:"\f2e5"}.fa-utensils:before{content:"\f2e7"}.fa-vaadin:before{content:"\f408"}.fa-vector-square:before{content:"\f5cb"}.fa-venus:before{content:"\f221"}.fa-venus-double:before{content:"\f226"}.fa-venus-mars:before{content:"\f228"}.fa-viacoin:before{content:"\f237"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-vial:before{content:"\f492"}.fa-vials:before{content:"\f493"}.fa-viber:before{content:"\f409"}.fa-video:before{content:"\f03d"}.fa-video-slash:before{content:"\f4e2"}.fa-vihara:before{content:"\f6a7"}.fa-vimeo:before{content:"\f40a"}.fa-vimeo-square:before{content:"\f194"}.fa-vimeo-v:before{content:"\f27d"}.fa-vine:before{content:"\f1ca"}.fa-vk:before{content:"\f189"}.fa-vnv:before{content:"\f40b"}.fa-volleyball-ball:before{content:"\f45f"}.fa-volume-down:before{content:"\f027"}.fa-volume-mute:before{content:"\f6a9"}.fa-volume-off:before{content:"\f026"}.fa-volume-up:before{content:"\f028"}.fa-vote-yea:before{content:"\f772"}.fa-vr-cardboard:before{content:"\f729"}.fa-vuejs:before{content:"\f41f"}.fa-walking:before{content:"\f554"}.fa-wallet:before{content:"\f555"}.fa-warehouse:before{content:"\f494"}.fa-water:before{content:"\f773"}.fa-weebly:before{content:"\f5cc"}.fa-weibo:before{content:"\f18a"}.fa-weight:before{content:"\f496"}.fa-weight-hanging:before{content:"\f5cd"}.fa-weixin:before{content:"\f1d7"}.fa-whatsapp:before{content:"\f232"}.fa-whatsapp-square:before{content:"\f40c"}.fa-wheelchair:before{content:"\f193"}.fa-whmcs:before{content:"\f40d"}.fa-wifi:before{content:"\f1eb"}.fa-wikipedia-w:before{content:"\f266"}.fa-wind:before{content:"\f72e"}.fa-window-close:before{content:"\f410"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-windows:before{content:"\f17a"}.fa-wine-bottle:before{content:"\f72f"}.fa-wine-glass:before{content:"\f4e3"}.fa-wine-glass-alt:before{content:"\f5ce"}.fa-wix:before{content:"\f5cf"}.fa-wizards-of-the-coast:before{content:"\f730"}.fa-wolf-pack-battalion:before{content:"\f514"}.fa-won-sign:before{content:"\f159"}.fa-wordpress:before{content:"\f19a"}.fa-wordpress-simple:before{content:"\f411"}.fa-wpbeginner:before{content:"\f297"}.fa-wpexplorer:before{content:"\f2de"}.fa-wpforms:before{content:"\f298"}.fa-wpressr:before{content:"\f3e4"}.fa-wrench:before{content:"\f0ad"}.fa-x-ray:before{content:"\f497"}.fa-xbox:before{content:"\f412"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-y-combinator:before{content:"\f23b"}.fa-yahoo:before{content:"\f19e"}.fa-yandex:before{content:"\f413"}.fa-yandex-international:before{content:"\f414"}.fa-yarn:before{content:"\f7e3"}.fa-yelp:before{content:"\f1e9"}.fa-yen-sign:before{content:"\f157"}.fa-yin-yang:before{content:"\f6ad"}.fa-yoast:before{content:"\f2b1"}.fa-youtube:before{content:"\f167"}.fa-youtube-square:before{content:"\f431"}.fa-zhihu:before{content:"\f63f"}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}@font-face{font-family:"Font Awesome 5 Brands";font-style:normal;font-weight:normal;font-display:auto;src:url(../webfonts/fa-brands-400.eot);src:url(../webfonts/fa-brands-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.woff) format("woff"),url(../webfonts/fa-brands-400.ttf) format("truetype"),url(../webfonts/fa-brands-400.svg#fontawesome) format("svg")}.fab{font-family:"Font Awesome 5 Brands"}@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:400;font-display:auto;src:url(../webfonts/fa-regular-400.eot);src:url(../webfonts/fa-regular-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.woff) format("woff"),url(../webfonts/fa-regular-400.ttf) format("truetype"),url(../webfonts/fa-regular-400.svg#fontawesome) format("svg")}.far{font-weight:400}@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:900;font-display:auto;src:url(../webfonts/fa-solid-900.eot);src:url(../webfonts/fa-solid-900.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.woff) format("woff"),url(../webfonts/fa-solid-900.ttf) format("truetype"),url(../webfonts/fa-solid-900.svg#fontawesome) format("svg")}.fa,.far,.fas{font-family:"Font Awesome 5 Free"}.fa,.fas{font-weight:900} \ No newline at end of file diff --git a/app/assets/frontends/beaker-module/.beaker-ui b/app/assets/frontends/beaker-module/.beaker-ui new file mode 100644 index 0000000000..3b499f8376 --- /dev/null +++ b/app/assets/frontends/beaker-module/.beaker-ui @@ -0,0 +1 @@ +builtin:beaker-module \ No newline at end of file diff --git a/app/assets/frontends/beaker-module/img/file.svg b/app/assets/frontends/beaker-module/img/file.svg new file mode 100644 index 0000000000..a9c6d92837 --- /dev/null +++ b/app/assets/frontends/beaker-module/img/file.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/assets/frontends/beaker-module/img/folder.svg b/app/assets/frontends/beaker-module/img/folder.svg new file mode 100644 index 0000000000..d6a5191b41 --- /dev/null +++ b/app/assets/frontends/beaker-module/img/folder.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/assets/frontends/beaker-module/img/mount.svg b/app/assets/frontends/beaker-module/img/mount.svg new file mode 100644 index 0000000000..b7f1b32544 --- /dev/null +++ b/app/assets/frontends/beaker-module/img/mount.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/assets/frontends/beaker-module/img/props.svg b/app/assets/frontends/beaker-module/img/props.svg new file mode 100644 index 0000000000..09dc1d76fa --- /dev/null +++ b/app/assets/frontends/beaker-module/img/props.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/assets/frontends/beaker-module/index.json b/app/assets/frontends/beaker-module/index.json new file mode 100644 index 0000000000..e07ea6d836 --- /dev/null +++ b/app/assets/frontends/beaker-module/index.json @@ -0,0 +1,10 @@ +{ + "title": "Builtin Module Theme", + "description": "", + "type": "theme", + "theme": { + "drive_types": [ + "module" + ] + } +} \ No newline at end of file diff --git a/app/assets/frontends/beaker-module/ui.css b/app/assets/frontends/beaker-module/ui.css new file mode 100644 index 0000000000..a57d11393e --- /dev/null +++ b/app/assets/frontends/beaker-module/ui.css @@ -0,0 +1,235 @@ +body { + --light-gray: #f7f7fc; + --blue: #2864dc; + --border-radius: 4px; + + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + margin: 0; +} + +a { + color: var(--blue); + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +button, .button { + display: inline-block; + padding: 0.5rem 0.8rem; + background: #fff; + border: 1px solid #88f; + border-radius: 4px; + color: var(--blue); + outline: 0; + font-size: 12px; + box-shadow: 0 0 0 #0003; + transition: box-shadow 0.2s; +} + +button:hover, .button:hover { + box-shadow: 0 1px 2px #0003; + text-decoration: none; +} + +button:active { + background: var(--light-gray); +} + +button.primary { + color: #fff; + background: var(--blue); + border-color: var(--blue); +} + +button img { + display: block; + width: 16px; +} + +nav { + background: var(--light-gray); +} + +nav > *, +main > * { + max-width: 800px; + margin: 0 auto; + display: block; +} + +module-header { + position: relative; + display: flex; + flex-direction: column; + justify-content: center; + height: 100px; + padding: 0 20px; +} + +module-header h1, +module-header p { + margin: 0.25rem 0; + line-height: 1; +} + +module-header h1 { + font-size: 2rem; +} + +module-header h1 a { + color: inherit; +} + +module-header .admin { + display: flex; + position: absolute; + top: 30px; + right: 20px; +} + +module-header .admin > * { + margin-left: 5px; +} + +module-breadcrumbs { + display: flex; + align-items: center; + margin: 1rem 0; + font-size: 15px; +} + +module-breadcrumbs > * { + margin-right: 5px; +} + +module-breadcrumbs a { + letter-spacing: 0.8px; + font-weight: 600; +} + +module-breadcrumbs a:last-of-type { + color: #334; +} + +module-directory-view .listing { + margin: 1rem 0; + border: 1px solid #ccd; + border-radius: var(--border-radius); +} + +module-directory-view .listing .entry { + display: flex; + align-items: center; + padding: 0.6rem 0.8rem; + border-bottom: 1px solid #ccd; +} + +module-directory-view .listing .entry:last-child { + border-bottom: 0; +} + +module-directory-view .listing .entry .icon { + width: 16px; + height: 16px; + margin-right: 10px; +} + +module-directory-view .listing .entry a { + font-size: 15px; + margin-right: auto; +} + +module-directory-view .listing .entry small { + opacity: 0.5; +} + +module-file-view { + display: block; + margin: 1rem 0; + border: 1px solid #ccd; + border-radius: var(--border-radius); +} + +module-file-view .hljs { + background: #fff; + font-size: 14px; + line-height: 1.4; +} + +module-file-view .content { + margin: 0; + padding: 1.25rem 1rem; +} + +.content > :first-child { + margin-top: 0; +} +.content hr { + border: 0; + border-top: 1px solid #ccd; +} +.content h1, +.content h2, +.content h3, +.content h4, +.content h5 { margin: 1.5rem 0; } +.content h1 { font-size: 2em; } +.content h2 { font-size: 1.7em; } +.content h3 { font-size: 1.4em; } +.content h4 { font-size: 1.3em; } +.content h5 { font-size: 1.1em; } +.content pre { + background: var(--light-gray); + padding: 1em; + overflow: auto; +} +.content p, +.content ul, +.content ol { + line-height: 1.5; +} +.content table { + margin: 1em 0; +} +.content blockquote { + border-left: 10px solid var(--light-gray); + margin: 1em 0; + padding: 1px 1.5em; + color: #667; +} + +.content #mocha { + margin: 0; +} + +.content #mocha h1 { + margin-top: 0; + font-weight: 500; +} + +.content #mocha .suite { + margin-bottom: 15px; +} + +.content #mocha #mocha-stats { + background: #fff; + border: 1px solid #ccd; + padding: 9px 6px 4px 12px; + border-radius: 30px; + box-shadow: 0 1px 2px #0002; +} + +.content #mocha #mocha-stats li:not(.progress) { + padding-top: 12px; +} + +@media (max-width: 1460px) { + .content #mocha #mocha-stats { + top: unset; + bottom: 15px; + right: 15px; + } +} \ No newline at end of file diff --git a/app/assets/frontends/beaker-module/ui.html b/app/assets/frontends/beaker-module/ui.html new file mode 100644 index 0000000000..85909ad5af --- /dev/null +++ b/app/assets/frontends/beaker-module/ui.html @@ -0,0 +1,18 @@ + + + + + + + + + + + +
+ +
+ + \ No newline at end of file diff --git a/app/assets/frontends/beaker-module/ui.js b/app/assets/frontends/beaker-module/ui.js new file mode 100644 index 0000000000..8ccaa72ba1 --- /dev/null +++ b/app/assets/frontends/beaker-module/ui.js @@ -0,0 +1,250 @@ +import MarkdownIt from './vendor/markdown-it.js' + +var self = hyperdrive.self +var infoPromise = self.getInfo() + +function h (tag, attrs, ...children) { + var el = document.createElement(tag) + for (let k in attrs) el.setAttribute(k, attrs[k]) + for (let child of children) el.append(child) + return el +} + +function formatBytes (bytes, decimals = 2) { + if (bytes === 0) return '' + const k = 1024 + const dm = decimals < 0 ? 0 : decimals + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] + const i = Math.floor(Math.log(bytes) / Math.log(k)) + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i] +} + +customElements.define('module-header', class extends HTMLElement { + constructor () { + super() + this.render() + } + async render () { + var [info, hasTests, hasDemo] = await Promise.all([ + infoPromise, + self.stat('/tests/index.html').catch(e => false), + self.stat('/demo/index.html').catch(e => false) + ]) + + this.append(h('h1', {}, h('a', {href: '/'}, info.title))) + if (info.writable) { + let editProps = h('a', {title: 'Edit Properties', href: '#'}, 'Edit') + editProps.addEventListener('click', async (e) => { + e.preventDefault() + await navigator.drivePropertiesDialog(self.url) + location.reload() + }) + this.append(h('p', {}, `${info.description} [ `, editProps, ` ]`)) + } else { + this.append(h('p', {}, info.description)) + } + + var buttons = [] + if (hasDemo) { + buttons.push(h('a', {class: 'button', href: '/demo/', title: 'View Demo'}, 'View Demo')) + } + if (hasTests) { + buttons.push(h('a', {class: 'button', href: '/tests/', title: 'Run Tests'}, 'Run Tests')) + } + this.append(h('div', {class: 'admin'}, ...buttons)) + } +}) + +class ModuleBreadcrumbs extends HTMLElement { + constructor () { + super() + this.render() + } + async render () { + var info = await infoPromise + var parts = location.pathname.split('/').filter(Boolean) + var acc = [] + this.append(h('a', {href: '/'}, info.title)) + this.append(h('span', {}, '/')) + for (let part of parts) { + acc.push(part) + let href = '/' + acc.join('/') + this.append(h('a', {href}, part)) + this.append(h('span', {}, '/')) + } + } +} +customElements.define('module-breadcrumbs', ModuleBreadcrumbs) + +class ModuleDirectoryView extends HTMLElement { + constructor () { + super() + this.render() + } + + async render () { + var entries = await self.readdir(location.pathname, {includeStats: true}) + entries.sort((a, b) => { + if (a.stat.isDirectory() && !b.stat.isDirectory()) return -1 + if (!a.stat.isDirectory() && b.stat.isDirectory()) return 1 + return a.name.localeCompare(b.name) + }) + + var listing = h('div', {class: 'listing'}) + for (let entry of entries) { + let isDir = entry.stat.isDirectory() + let isMount = !!entry.stat.mount + let href = isMount ? `hyper://${entry.stat.mount.key}` : `./${entry.name}${isDir ? '/' : ''}` + listing.append(h('div', {class: 'entry'}, + h('img', {class: 'icon', src: `/.ui/img/${isMount ? 'mount' : isDir ? 'folder' : 'file'}.svg`}), + h('a', {href}, entry.name), + h('small', {}, formatBytes(entry.stat.size)) + )) + } + if (entries.length === 0) { + listing.append(h('div', {class: 'entry'}, 'This folder is empty')) + } + this.append(listing) + } +} +customElements.define('module-directory-view', ModuleDirectoryView) + +class ModuleFileView extends HTMLElement { + constructor (pathname, renderHTML = false) { + super() + this.render(pathname, renderHTML) + } + async render (pathname, renderHTML = false) { + // check existence + let stat = await self.stat(pathname).catch(e => undefined) + if (!stat) { + // 404 + this.append(h('div', {class: 'empty'}, h('h2', {}, '404 File Not Found'))) + return + } + + // embed content + if (/\.(png|jpe?g|gif)$/i.test(pathname)) { + this.append(h('img', {src: pathname})) + } else if (/\.(mp4|webm|mov)/i.test(pathname)) { + this.append(h('video', {controls: true}, h('source', {src: pathname}))) + } else if (/\.(mp3|ogg)/i.test(pathname)) { + this.append(h('audio', {controls: true}, h('source', {src: pathname}))) + } else { + // render content + let content = await self.readFile(pathname) + if (renderHTML && /\.(md|html)$/i.test(pathname)) { + if (pathname.endsWith('.md')) { + let md = new MarkdownIt() + content = md.render(content) + } + let contentEl = h('div', {class: 'content'}) + contentEl.innerHTML = content + this.append(contentEl) + executeScripts(contentEl) + } else { + let codeBlock = h('pre', {class: 'content'}, content) + hljs.highlightBlock(codeBlock) + this.append(codeBlock) + } + } + } +} +customElements.define('module-file-view', ModuleFileView) + +class ModuleReadmeView extends HTMLElement { + constructor () { + super() + this.render() + } + async render () { + let files = await self.readdir(location.pathname).catch(e => ([])) + files = files.filter(f => ['index.html', 'index.md'].includes(f.toLowerCase())) + if (files[0]) { + this.append(new ModuleFileView(location.pathname + files[0], true)) + } + } +} +customElements.define('module-readme-view', ModuleReadmeView) + +customElements.define('module-page', class extends HTMLElement { + constructor () { + super() + if (location.pathname !== '/') { + this.append(new ModuleBreadcrumbs()) + } + if (location.pathname.endsWith('/')) { + this.append(new ModuleDirectoryView()) + this.append(new ModuleReadmeView()) + } else { + this.append(new ModuleFileView(location.pathname)) + } + } +}) + +async function executeScripts (el) { + for (let scriptEl of Array.from(el.querySelectorAll('script'))) { + let promise + let newScriptEl = document.createElement('script') + newScriptEl.setAttribute('type', scriptEl.getAttribute('type') || 'module') + newScriptEl.textContent = scriptEl.textContent + if (scriptEl.getAttribute('src')) { + newScriptEl.setAttribute('src', scriptEl.getAttribute('src')) + promise = new Promise((resolve, reject) => { + newScriptEl.onload = resolve + newScriptEl.onerror = resolve + }) + } + document.head.append(newScriptEl) + await promise + } +} + +navigator.terminal.registerCommand({ + name: 'test', + help: 'Run the module tests', + handle () { + if (location.pathname !== '/tests/') { + location.pathname = '/tests/' + } else { + location.reload() + } + } +}) +navigator.terminal.registerCommand({ + name: 'demo', + help: 'View the module demo', + handle () { + if (location.pathname !== '/demo/') { + location.pathname = '/demo/' + } else { + location.reload() + } + } +}) +navigator.terminal.registerCommand({ + name: 'run', + help: 'Run a script in the /scripts directory', + usage: '@run {script} {...args}', + async handle (opts = {}, ...args) { + var scriptName = args[0] + if (!scriptName) throw new Error('Must specify a script to run') + if (!scriptName.endsWith('.js')) { + scriptName += '.js' + } + var scriptPath = `/scripts/${scriptName}` + try { + var script = await import(scriptPath) + } catch (e) { + if (e.message.includes('Failed to fetch')) { + throw new Error(`No script found in /scripts named ${scriptName}`) + } else { + throw e + } + } + if (typeof script.default !== 'function') { + throw new Error('The script must export a default function') + } + return script.default(opts, args.slice(1)) + } +}) \ No newline at end of file diff --git a/app/assets/frontends/beaker-module/vendor/chai.js b/app/assets/frontends/beaker-module/vendor/chai.js new file mode 100644 index 0000000000..36f425d109 --- /dev/null +++ b/app/assets/frontends/beaker-module/vendor/chai.js @@ -0,0 +1,10854 @@ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.chai = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i + * MIT Licensed + */ + +var used = []; + +/*! + * Chai version + */ + +exports.version = '4.2.0'; + +/*! + * Assertion Error + */ + +exports.AssertionError = require('assertion-error'); + +/*! + * Utils for plugins (not exported) + */ + +var util = require('./chai/utils'); + +/** + * # .use(function) + * + * Provides a way to extend the internals of Chai. + * + * @param {Function} + * @returns {this} for chaining + * @api public + */ + +exports.use = function (fn) { + if (!~used.indexOf(fn)) { + fn(exports, util); + used.push(fn); + } + + return exports; +}; + +/*! + * Utility Functions + */ + +exports.util = util; + +/*! + * Configuration + */ + +var config = require('./chai/config'); +exports.config = config; + +/*! + * Primary `Assertion` prototype + */ + +var assertion = require('./chai/assertion'); +exports.use(assertion); + +/*! + * Core Assertions + */ + +var core = require('./chai/core/assertions'); +exports.use(core); + +/*! + * Expect interface + */ + +var expect = require('./chai/interface/expect'); +exports.use(expect); + +/*! + * Should interface + */ + +var should = require('./chai/interface/should'); +exports.use(should); + +/*! + * Assert interface + */ + +var assert = require('./chai/interface/assert'); +exports.use(assert); + +},{"./chai/assertion":3,"./chai/config":4,"./chai/core/assertions":5,"./chai/interface/assert":6,"./chai/interface/expect":7,"./chai/interface/should":8,"./chai/utils":22,"assertion-error":33}],3:[function(require,module,exports){ +/*! + * chai + * http://chaijs.com + * Copyright(c) 2011-2014 Jake Luer + * MIT Licensed + */ + +var config = require('./config'); + +module.exports = function (_chai, util) { + /*! + * Module dependencies. + */ + + var AssertionError = _chai.AssertionError + , flag = util.flag; + + /*! + * Module export. + */ + + _chai.Assertion = Assertion; + + /*! + * Assertion Constructor + * + * Creates object for chaining. + * + * `Assertion` objects contain metadata in the form of flags. Three flags can + * be assigned during instantiation by passing arguments to this constructor: + * + * - `object`: This flag contains the target of the assertion. For example, in + * the assertion `expect(numKittens).to.equal(7);`, the `object` flag will + * contain `numKittens` so that the `equal` assertion can reference it when + * needed. + * + * - `message`: This flag contains an optional custom error message to be + * prepended to the error message that's generated by the assertion when it + * fails. + * + * - `ssfi`: This flag stands for "start stack function indicator". It + * contains a function reference that serves as the starting point for + * removing frames from the stack trace of the error that's created by the + * assertion when it fails. The goal is to provide a cleaner stack trace to + * end users by removing Chai's internal functions. Note that it only works + * in environments that support `Error.captureStackTrace`, and only when + * `Chai.config.includeStack` hasn't been set to `false`. + * + * - `lockSsfi`: This flag controls whether or not the given `ssfi` flag + * should retain its current value, even as assertions are chained off of + * this object. This is usually set to `true` when creating a new assertion + * from within another assertion. It's also temporarily set to `true` before + * an overwritten assertion gets called by the overwriting assertion. + * + * @param {Mixed} obj target of the assertion + * @param {String} msg (optional) custom error message + * @param {Function} ssfi (optional) starting point for removing stack frames + * @param {Boolean} lockSsfi (optional) whether or not the ssfi flag is locked + * @api private + */ + + function Assertion (obj, msg, ssfi, lockSsfi) { + flag(this, 'ssfi', ssfi || Assertion); + flag(this, 'lockSsfi', lockSsfi); + flag(this, 'object', obj); + flag(this, 'message', msg); + + return util.proxify(this); + } + + Object.defineProperty(Assertion, 'includeStack', { + get: function() { + console.warn('Assertion.includeStack is deprecated, use chai.config.includeStack instead.'); + return config.includeStack; + }, + set: function(value) { + console.warn('Assertion.includeStack is deprecated, use chai.config.includeStack instead.'); + config.includeStack = value; + } + }); + + Object.defineProperty(Assertion, 'showDiff', { + get: function() { + console.warn('Assertion.showDiff is deprecated, use chai.config.showDiff instead.'); + return config.showDiff; + }, + set: function(value) { + console.warn('Assertion.showDiff is deprecated, use chai.config.showDiff instead.'); + config.showDiff = value; + } + }); + + Assertion.addProperty = function (name, fn) { + util.addProperty(this.prototype, name, fn); + }; + + Assertion.addMethod = function (name, fn) { + util.addMethod(this.prototype, name, fn); + }; + + Assertion.addChainableMethod = function (name, fn, chainingBehavior) { + util.addChainableMethod(this.prototype, name, fn, chainingBehavior); + }; + + Assertion.overwriteProperty = function (name, fn) { + util.overwriteProperty(this.prototype, name, fn); + }; + + Assertion.overwriteMethod = function (name, fn) { + util.overwriteMethod(this.prototype, name, fn); + }; + + Assertion.overwriteChainableMethod = function (name, fn, chainingBehavior) { + util.overwriteChainableMethod(this.prototype, name, fn, chainingBehavior); + }; + + /** + * ### .assert(expression, message, negateMessage, expected, actual, showDiff) + * + * Executes an expression and check expectations. Throws AssertionError for reporting if test doesn't pass. + * + * @name assert + * @param {Philosophical} expression to be tested + * @param {String|Function} message or function that returns message to display if expression fails + * @param {String|Function} negatedMessage or function that returns negatedMessage to display if negated expression fails + * @param {Mixed} expected value (remember to check for negation) + * @param {Mixed} actual (optional) will default to `this.obj` + * @param {Boolean} showDiff (optional) when set to `true`, assert will display a diff in addition to the message if expression fails + * @api private + */ + + Assertion.prototype.assert = function (expr, msg, negateMsg, expected, _actual, showDiff) { + var ok = util.test(this, arguments); + if (false !== showDiff) showDiff = true; + if (undefined === expected && undefined === _actual) showDiff = false; + if (true !== config.showDiff) showDiff = false; + + if (!ok) { + msg = util.getMessage(this, arguments); + var actual = util.getActual(this, arguments); + throw new AssertionError(msg, { + actual: actual + , expected: expected + , showDiff: showDiff + }, (config.includeStack) ? this.assert : flag(this, 'ssfi')); + } + }; + + /*! + * ### ._obj + * + * Quick reference to stored `actual` value for plugin developers. + * + * @api private + */ + + Object.defineProperty(Assertion.prototype, '_obj', + { get: function () { + return flag(this, 'object'); + } + , set: function (val) { + flag(this, 'object', val); + } + }); +}; + +},{"./config":4}],4:[function(require,module,exports){ +module.exports = { + + /** + * ### config.includeStack + * + * User configurable property, influences whether stack trace + * is included in Assertion error message. Default of false + * suppresses stack trace in the error message. + * + * chai.config.includeStack = true; // enable stack on error + * + * @param {Boolean} + * @api public + */ + + includeStack: false, + + /** + * ### config.showDiff + * + * User configurable property, influences whether or not + * the `showDiff` flag should be included in the thrown + * AssertionErrors. `false` will always be `false`; `true` + * will be true when the assertion has requested a diff + * be shown. + * + * @param {Boolean} + * @api public + */ + + showDiff: true, + + /** + * ### config.truncateThreshold + * + * User configurable property, sets length threshold for actual and + * expected values in assertion errors. If this threshold is exceeded, for + * example for large data structures, the value is replaced with something + * like `[ Array(3) ]` or `{ Object (prop1, prop2) }`. + * + * Set it to zero if you want to disable truncating altogether. + * + * This is especially userful when doing assertions on arrays: having this + * set to a reasonable large value makes the failure messages readily + * inspectable. + * + * chai.config.truncateThreshold = 0; // disable truncating + * + * @param {Number} + * @api public + */ + + truncateThreshold: 40, + + /** + * ### config.useProxy + * + * User configurable property, defines if chai will use a Proxy to throw + * an error when a non-existent property is read, which protects users + * from typos when using property-based assertions. + * + * Set it to false if you want to disable this feature. + * + * chai.config.useProxy = false; // disable use of Proxy + * + * This feature is automatically disabled regardless of this config value + * in environments that don't support proxies. + * + * @param {Boolean} + * @api public + */ + + useProxy: true, + + /** + * ### config.proxyExcludedKeys + * + * User configurable property, defines which properties should be ignored + * instead of throwing an error if they do not exist on the assertion. + * This is only applied if the environment Chai is running in supports proxies and + * if the `useProxy` configuration setting is enabled. + * By default, `then` and `inspect` will not throw an error if they do not exist on the + * assertion object because the `.inspect` property is read by `util.inspect` (for example, when + * using `console.log` on the assertion object) and `.then` is necessary for promise type-checking. + * + * // By default these keys will not throw an error if they do not exist on the assertion object + * chai.config.proxyExcludedKeys = ['then', 'inspect']; + * + * @param {Array} + * @api public + */ + + proxyExcludedKeys: ['then', 'catch', 'inspect', 'toJSON'] +}; + +},{}],5:[function(require,module,exports){ +/*! + * chai + * http://chaijs.com + * Copyright(c) 2011-2014 Jake Luer + * MIT Licensed + */ + +module.exports = function (chai, _) { + var Assertion = chai.Assertion + , AssertionError = chai.AssertionError + , flag = _.flag; + + /** + * ### Language Chains + * + * The following are provided as chainable getters to improve the readability + * of your assertions. + * + * **Chains** + * + * - to + * - be + * - been + * - is + * - that + * - which + * - and + * - has + * - have + * - with + * - at + * - of + * - same + * - but + * - does + * - still + * + * @name language chains + * @namespace BDD + * @api public + */ + + [ 'to', 'be', 'been', 'is' + , 'and', 'has', 'have', 'with' + , 'that', 'which', 'at', 'of' + , 'same', 'but', 'does', 'still' ].forEach(function (chain) { + Assertion.addProperty(chain); + }); + + /** + * ### .not + * + * Negates all assertions that follow in the chain. + * + * expect(function () {}).to.not.throw(); + * expect({a: 1}).to.not.have.property('b'); + * expect([1, 2]).to.be.an('array').that.does.not.include(3); + * + * Just because you can negate any assertion with `.not` doesn't mean you + * should. With great power comes great responsibility. It's often best to + * assert that the one expected output was produced, rather than asserting + * that one of countless unexpected outputs wasn't produced. See individual + * assertions for specific guidance. + * + * expect(2).to.equal(2); // Recommended + * expect(2).to.not.equal(1); // Not recommended + * + * @name not + * @namespace BDD + * @api public + */ + + Assertion.addProperty('not', function () { + flag(this, 'negate', true); + }); + + /** + * ### .deep + * + * Causes all `.equal`, `.include`, `.members`, `.keys`, and `.property` + * assertions that follow in the chain to use deep equality instead of strict + * (`===`) equality. See the `deep-eql` project page for info on the deep + * equality algorithm: https://github.com/chaijs/deep-eql. + * + * // Target object deeply (but not strictly) equals `{a: 1}` + * expect({a: 1}).to.deep.equal({a: 1}); + * expect({a: 1}).to.not.equal({a: 1}); + * + * // Target array deeply (but not strictly) includes `{a: 1}` + * expect([{a: 1}]).to.deep.include({a: 1}); + * expect([{a: 1}]).to.not.include({a: 1}); + * + * // Target object deeply (but not strictly) includes `x: {a: 1}` + * expect({x: {a: 1}}).to.deep.include({x: {a: 1}}); + * expect({x: {a: 1}}).to.not.include({x: {a: 1}}); + * + * // Target array deeply (but not strictly) has member `{a: 1}` + * expect([{a: 1}]).to.have.deep.members([{a: 1}]); + * expect([{a: 1}]).to.not.have.members([{a: 1}]); + * + * // Target set deeply (but not strictly) has key `{a: 1}` + * expect(new Set([{a: 1}])).to.have.deep.keys([{a: 1}]); + * expect(new Set([{a: 1}])).to.not.have.keys([{a: 1}]); + * + * // Target object deeply (but not strictly) has property `x: {a: 1}` + * expect({x: {a: 1}}).to.have.deep.property('x', {a: 1}); + * expect({x: {a: 1}}).to.not.have.property('x', {a: 1}); + * + * @name deep + * @namespace BDD + * @api public + */ + + Assertion.addProperty('deep', function () { + flag(this, 'deep', true); + }); + + /** + * ### .nested + * + * Enables dot- and bracket-notation in all `.property` and `.include` + * assertions that follow in the chain. + * + * expect({a: {b: ['x', 'y']}}).to.have.nested.property('a.b[1]'); + * expect({a: {b: ['x', 'y']}}).to.nested.include({'a.b[1]': 'y'}); + * + * If `.` or `[]` are part of an actual property name, they can be escaped by + * adding two backslashes before them. + * + * expect({'.a': {'[b]': 'x'}}).to.have.nested.property('\\.a.\\[b\\]'); + * expect({'.a': {'[b]': 'x'}}).to.nested.include({'\\.a.\\[b\\]': 'x'}); + * + * `.nested` cannot be combined with `.own`. + * + * @name nested + * @namespace BDD + * @api public + */ + + Assertion.addProperty('nested', function () { + flag(this, 'nested', true); + }); + + /** + * ### .own + * + * Causes all `.property` and `.include` assertions that follow in the chain + * to ignore inherited properties. + * + * Object.prototype.b = 2; + * + * expect({a: 1}).to.have.own.property('a'); + * expect({a: 1}).to.have.property('b'); + * expect({a: 1}).to.not.have.own.property('b'); + * + * expect({a: 1}).to.own.include({a: 1}); + * expect({a: 1}).to.include({b: 2}).but.not.own.include({b: 2}); + * + * `.own` cannot be combined with `.nested`. + * + * @name own + * @namespace BDD + * @api public + */ + + Assertion.addProperty('own', function () { + flag(this, 'own', true); + }); + + /** + * ### .ordered + * + * Causes all `.members` assertions that follow in the chain to require that + * members be in the same order. + * + * expect([1, 2]).to.have.ordered.members([1, 2]) + * .but.not.have.ordered.members([2, 1]); + * + * When `.include` and `.ordered` are combined, the ordering begins at the + * start of both arrays. + * + * expect([1, 2, 3]).to.include.ordered.members([1, 2]) + * .but.not.include.ordered.members([2, 3]); + * + * @name ordered + * @namespace BDD + * @api public + */ + + Assertion.addProperty('ordered', function () { + flag(this, 'ordered', true); + }); + + /** + * ### .any + * + * Causes all `.keys` assertions that follow in the chain to only require that + * the target have at least one of the given keys. This is the opposite of + * `.all`, which requires that the target have all of the given keys. + * + * expect({a: 1, b: 2}).to.not.have.any.keys('c', 'd'); + * + * See the `.keys` doc for guidance on when to use `.any` or `.all`. + * + * @name any + * @namespace BDD + * @api public + */ + + Assertion.addProperty('any', function () { + flag(this, 'any', true); + flag(this, 'all', false); + }); + + /** + * ### .all + * + * Causes all `.keys` assertions that follow in the chain to require that the + * target have all of the given keys. This is the opposite of `.any`, which + * only requires that the target have at least one of the given keys. + * + * expect({a: 1, b: 2}).to.have.all.keys('a', 'b'); + * + * Note that `.all` is used by default when neither `.all` nor `.any` are + * added earlier in the chain. However, it's often best to add `.all` anyway + * because it improves readability. + * + * See the `.keys` doc for guidance on when to use `.any` or `.all`. + * + * @name all + * @namespace BDD + * @api public + */ + + Assertion.addProperty('all', function () { + flag(this, 'all', true); + flag(this, 'any', false); + }); + + /** + * ### .a(type[, msg]) + * + * Asserts that the target's type is equal to the given string `type`. Types + * are case insensitive. See the `type-detect` project page for info on the + * type detection algorithm: https://github.com/chaijs/type-detect. + * + * expect('foo').to.be.a('string'); + * expect({a: 1}).to.be.an('object'); + * expect(null).to.be.a('null'); + * expect(undefined).to.be.an('undefined'); + * expect(new Error).to.be.an('error'); + * expect(Promise.resolve()).to.be.a('promise'); + * expect(new Float32Array).to.be.a('float32array'); + * expect(Symbol()).to.be.a('symbol'); + * + * `.a` supports objects that have a custom type set via `Symbol.toStringTag`. + * + * var myObj = { + * [Symbol.toStringTag]: 'myCustomType' + * }; + * + * expect(myObj).to.be.a('myCustomType').but.not.an('object'); + * + * It's often best to use `.a` to check a target's type before making more + * assertions on the same target. That way, you avoid unexpected behavior from + * any assertion that does different things based on the target's type. + * + * expect([1, 2, 3]).to.be.an('array').that.includes(2); + * expect([]).to.be.an('array').that.is.empty; + * + * Add `.not` earlier in the chain to negate `.a`. However, it's often best to + * assert that the target is the expected type, rather than asserting that it + * isn't one of many unexpected types. + * + * expect('foo').to.be.a('string'); // Recommended + * expect('foo').to.not.be.an('array'); // Not recommended + * + * `.a` accepts an optional `msg` argument which is a custom error message to + * show when the assertion fails. The message can also be given as the second + * argument to `expect`. + * + * expect(1).to.be.a('string', 'nooo why fail??'); + * expect(1, 'nooo why fail??').to.be.a('string'); + * + * `.a` can also be used as a language chain to improve the readability of + * your assertions. + * + * expect({b: 2}).to.have.a.property('b'); + * + * The alias `.an` can be used interchangeably with `.a`. + * + * @name a + * @alias an + * @param {String} type + * @param {String} msg _optional_ + * @namespace BDD + * @api public + */ + + function an (type, msg) { + if (msg) flag(this, 'message', msg); + type = type.toLowerCase(); + var obj = flag(this, 'object') + , article = ~[ 'a', 'e', 'i', 'o', 'u' ].indexOf(type.charAt(0)) ? 'an ' : 'a '; + + this.assert( + type === _.type(obj).toLowerCase() + , 'expected #{this} to be ' + article + type + , 'expected #{this} not to be ' + article + type + ); + } + + Assertion.addChainableMethod('an', an); + Assertion.addChainableMethod('a', an); + + /** + * ### .include(val[, msg]) + * + * When the target is a string, `.include` asserts that the given string `val` + * is a substring of the target. + * + * expect('foobar').to.include('foo'); + * + * When the target is an array, `.include` asserts that the given `val` is a + * member of the target. + * + * expect([1, 2, 3]).to.include(2); + * + * When the target is an object, `.include` asserts that the given object + * `val`'s properties are a subset of the target's properties. + * + * expect({a: 1, b: 2, c: 3}).to.include({a: 1, b: 2}); + * + * When the target is a Set or WeakSet, `.include` asserts that the given `val` is a + * member of the target. SameValueZero equality algorithm is used. + * + * expect(new Set([1, 2])).to.include(2); + * + * When the target is a Map, `.include` asserts that the given `val` is one of + * the values of the target. SameValueZero equality algorithm is used. + * + * expect(new Map([['a', 1], ['b', 2]])).to.include(2); + * + * Because `.include` does different things based on the target's type, it's + * important to check the target's type before using `.include`. See the `.a` + * doc for info on testing a target's type. + * + * expect([1, 2, 3]).to.be.an('array').that.includes(2); + * + * By default, strict (`===`) equality is used to compare array members and + * object properties. Add `.deep` earlier in the chain to use deep equality + * instead (WeakSet targets are not supported). See the `deep-eql` project + * page for info on the deep equality algorithm: https://github.com/chaijs/deep-eql. + * + * // Target array deeply (but not strictly) includes `{a: 1}` + * expect([{a: 1}]).to.deep.include({a: 1}); + * expect([{a: 1}]).to.not.include({a: 1}); + * + * // Target object deeply (but not strictly) includes `x: {a: 1}` + * expect({x: {a: 1}}).to.deep.include({x: {a: 1}}); + * expect({x: {a: 1}}).to.not.include({x: {a: 1}}); + * + * By default, all of the target's properties are searched when working with + * objects. This includes properties that are inherited and/or non-enumerable. + * Add `.own` earlier in the chain to exclude the target's inherited + * properties from the search. + * + * Object.prototype.b = 2; + * + * expect({a: 1}).to.own.include({a: 1}); + * expect({a: 1}).to.include({b: 2}).but.not.own.include({b: 2}); + * + * Note that a target object is always only searched for `val`'s own + * enumerable properties. + * + * `.deep` and `.own` can be combined. + * + * expect({a: {b: 2}}).to.deep.own.include({a: {b: 2}}); + * + * Add `.nested` earlier in the chain to enable dot- and bracket-notation when + * referencing nested properties. + * + * expect({a: {b: ['x', 'y']}}).to.nested.include({'a.b[1]': 'y'}); + * + * If `.` or `[]` are part of an actual property name, they can be escaped by + * adding two backslashes before them. + * + * expect({'.a': {'[b]': 2}}).to.nested.include({'\\.a.\\[b\\]': 2}); + * + * `.deep` and `.nested` can be combined. + * + * expect({a: {b: [{c: 3}]}}).to.deep.nested.include({'a.b[0]': {c: 3}}); + * + * `.own` and `.nested` cannot be combined. + * + * Add `.not` earlier in the chain to negate `.include`. + * + * expect('foobar').to.not.include('taco'); + * expect([1, 2, 3]).to.not.include(4); + * + * However, it's dangerous to negate `.include` when the target is an object. + * The problem is that it creates uncertain expectations by asserting that the + * target object doesn't have all of `val`'s key/value pairs but may or may + * not have some of them. It's often best to identify the exact output that's + * expected, and then write an assertion that only accepts that exact output. + * + * When the target object isn't even expected to have `val`'s keys, it's + * often best to assert exactly that. + * + * expect({c: 3}).to.not.have.any.keys('a', 'b'); // Recommended + * expect({c: 3}).to.not.include({a: 1, b: 2}); // Not recommended + * + * When the target object is expected to have `val`'s keys, it's often best to + * assert that each of the properties has its expected value, rather than + * asserting that each property doesn't have one of many unexpected values. + * + * expect({a: 3, b: 4}).to.include({a: 3, b: 4}); // Recommended + * expect({a: 3, b: 4}).to.not.include({a: 1, b: 2}); // Not recommended + * + * `.include` accepts an optional `msg` argument which is a custom error + * message to show when the assertion fails. The message can also be given as + * the second argument to `expect`. + * + * expect([1, 2, 3]).to.include(4, 'nooo why fail??'); + * expect([1, 2, 3], 'nooo why fail??').to.include(4); + * + * `.include` can also be used as a language chain, causing all `.members` and + * `.keys` assertions that follow in the chain to require the target to be a + * superset of the expected set, rather than an identical set. Note that + * `.members` ignores duplicates in the subset when `.include` is added. + * + * // Target object's keys are a superset of ['a', 'b'] but not identical + * expect({a: 1, b: 2, c: 3}).to.include.all.keys('a', 'b'); + * expect({a: 1, b: 2, c: 3}).to.not.have.all.keys('a', 'b'); + * + * // Target array is a superset of [1, 2] but not identical + * expect([1, 2, 3]).to.include.members([1, 2]); + * expect([1, 2, 3]).to.not.have.members([1, 2]); + * + * // Duplicates in the subset are ignored + * expect([1, 2, 3]).to.include.members([1, 2, 2, 2]); + * + * Note that adding `.any` earlier in the chain causes the `.keys` assertion + * to ignore `.include`. + * + * // Both assertions are identical + * expect({a: 1}).to.include.any.keys('a', 'b'); + * expect({a: 1}).to.have.any.keys('a', 'b'); + * + * The aliases `.includes`, `.contain`, and `.contains` can be used + * interchangeably with `.include`. + * + * @name include + * @alias contain + * @alias includes + * @alias contains + * @param {Mixed} val + * @param {String} msg _optional_ + * @namespace BDD + * @api public + */ + + function SameValueZero(a, b) { + return (_.isNaN(a) && _.isNaN(b)) || a === b; + } + + function includeChainingBehavior () { + flag(this, 'contains', true); + } + + function include (val, msg) { + if (msg) flag(this, 'message', msg); + + var obj = flag(this, 'object') + , objType = _.type(obj).toLowerCase() + , flagMsg = flag(this, 'message') + , negate = flag(this, 'negate') + , ssfi = flag(this, 'ssfi') + , isDeep = flag(this, 'deep') + , descriptor = isDeep ? 'deep ' : ''; + + flagMsg = flagMsg ? flagMsg + ': ' : ''; + + var included = false; + + switch (objType) { + case 'string': + included = obj.indexOf(val) !== -1; + break; + + case 'weakset': + if (isDeep) { + throw new AssertionError( + flagMsg + 'unable to use .deep.include with WeakSet', + undefined, + ssfi + ); + } + + included = obj.has(val); + break; + + case 'map': + var isEql = isDeep ? _.eql : SameValueZero; + obj.forEach(function (item) { + included = included || isEql(item, val); + }); + break; + + case 'set': + if (isDeep) { + obj.forEach(function (item) { + included = included || _.eql(item, val); + }); + } else { + included = obj.has(val); + } + break; + + case 'array': + if (isDeep) { + included = obj.some(function (item) { + return _.eql(item, val); + }) + } else { + included = obj.indexOf(val) !== -1; + } + break; + + default: + // This block is for asserting a subset of properties in an object. + // `_.expectTypes` isn't used here because `.include` should work with + // objects with a custom `@@toStringTag`. + if (val !== Object(val)) { + throw new AssertionError( + flagMsg + 'object tested must be an array, a map, an object,' + + ' a set, a string, or a weakset, but ' + objType + ' given', + undefined, + ssfi + ); + } + + var props = Object.keys(val) + , firstErr = null + , numErrs = 0; + + props.forEach(function (prop) { + var propAssertion = new Assertion(obj); + _.transferFlags(this, propAssertion, true); + flag(propAssertion, 'lockSsfi', true); + + if (!negate || props.length === 1) { + propAssertion.property(prop, val[prop]); + return; + } + + try { + propAssertion.property(prop, val[prop]); + } catch (err) { + if (!_.checkError.compatibleConstructor(err, AssertionError)) { + throw err; + } + if (firstErr === null) firstErr = err; + numErrs++; + } + }, this); + + // When validating .not.include with multiple properties, we only want + // to throw an assertion error if all of the properties are included, + // in which case we throw the first property assertion error that we + // encountered. + if (negate && props.length > 1 && numErrs === props.length) { + throw firstErr; + } + return; + } + + // Assert inclusion in collection or substring in a string. + this.assert( + included + , 'expected #{this} to ' + descriptor + 'include ' + _.inspect(val) + , 'expected #{this} to not ' + descriptor + 'include ' + _.inspect(val)); + } + + Assertion.addChainableMethod('include', include, includeChainingBehavior); + Assertion.addChainableMethod('contain', include, includeChainingBehavior); + Assertion.addChainableMethod('contains', include, includeChainingBehavior); + Assertion.addChainableMethod('includes', include, includeChainingBehavior); + + /** + * ### .ok + * + * Asserts that the target is a truthy value (considered `true` in boolean context). + * However, it's often best to assert that the target is strictly (`===`) or + * deeply equal to its expected value. + * + * expect(1).to.equal(1); // Recommended + * expect(1).to.be.ok; // Not recommended + * + * expect(true).to.be.true; // Recommended + * expect(true).to.be.ok; // Not recommended + * + * Add `.not` earlier in the chain to negate `.ok`. + * + * expect(0).to.equal(0); // Recommended + * expect(0).to.not.be.ok; // Not recommended + * + * expect(false).to.be.false; // Recommended + * expect(false).to.not.be.ok; // Not recommended + * + * expect(null).to.be.null; // Recommended + * expect(null).to.not.be.ok; // Not recommended + * + * expect(undefined).to.be.undefined; // Recommended + * expect(undefined).to.not.be.ok; // Not recommended + * + * A custom error message can be given as the second argument to `expect`. + * + * expect(false, 'nooo why fail??').to.be.ok; + * + * @name ok + * @namespace BDD + * @api public + */ + + Assertion.addProperty('ok', function () { + this.assert( + flag(this, 'object') + , 'expected #{this} to be truthy' + , 'expected #{this} to be falsy'); + }); + + /** + * ### .true + * + * Asserts that the target is strictly (`===`) equal to `true`. + * + * expect(true).to.be.true; + * + * Add `.not` earlier in the chain to negate `.true`. However, it's often best + * to assert that the target is equal to its expected value, rather than not + * equal to `true`. + * + * expect(false).to.be.false; // Recommended + * expect(false).to.not.be.true; // Not recommended + * + * expect(1).to.equal(1); // Recommended + * expect(1).to.not.be.true; // Not recommended + * + * A custom error message can be given as the second argument to `expect`. + * + * expect(false, 'nooo why fail??').to.be.true; + * + * @name true + * @namespace BDD + * @api public + */ + + Assertion.addProperty('true', function () { + this.assert( + true === flag(this, 'object') + , 'expected #{this} to be true' + , 'expected #{this} to be false' + , flag(this, 'negate') ? false : true + ); + }); + + /** + * ### .false + * + * Asserts that the target is strictly (`===`) equal to `false`. + * + * expect(false).to.be.false; + * + * Add `.not` earlier in the chain to negate `.false`. However, it's often + * best to assert that the target is equal to its expected value, rather than + * not equal to `false`. + * + * expect(true).to.be.true; // Recommended + * expect(true).to.not.be.false; // Not recommended + * + * expect(1).to.equal(1); // Recommended + * expect(1).to.not.be.false; // Not recommended + * + * A custom error message can be given as the second argument to `expect`. + * + * expect(true, 'nooo why fail??').to.be.false; + * + * @name false + * @namespace BDD + * @api public + */ + + Assertion.addProperty('false', function () { + this.assert( + false === flag(this, 'object') + , 'expected #{this} to be false' + , 'expected #{this} to be true' + , flag(this, 'negate') ? true : false + ); + }); + + /** + * ### .null + * + * Asserts that the target is strictly (`===`) equal to `null`. + * + * expect(null).to.be.null; + * + * Add `.not` earlier in the chain to negate `.null`. However, it's often best + * to assert that the target is equal to its expected value, rather than not + * equal to `null`. + * + * expect(1).to.equal(1); // Recommended + * expect(1).to.not.be.null; // Not recommended + * + * A custom error message can be given as the second argument to `expect`. + * + * expect(42, 'nooo why fail??').to.be.null; + * + * @name null + * @namespace BDD + * @api public + */ + + Assertion.addProperty('null', function () { + this.assert( + null === flag(this, 'object') + , 'expected #{this} to be null' + , 'expected #{this} not to be null' + ); + }); + + /** + * ### .undefined + * + * Asserts that the target is strictly (`===`) equal to `undefined`. + * + * expect(undefined).to.be.undefined; + * + * Add `.not` earlier in the chain to negate `.undefined`. However, it's often + * best to assert that the target is equal to its expected value, rather than + * not equal to `undefined`. + * + * expect(1).to.equal(1); // Recommended + * expect(1).to.not.be.undefined; // Not recommended + * + * A custom error message can be given as the second argument to `expect`. + * + * expect(42, 'nooo why fail??').to.be.undefined; + * + * @name undefined + * @namespace BDD + * @api public + */ + + Assertion.addProperty('undefined', function () { + this.assert( + undefined === flag(this, 'object') + , 'expected #{this} to be undefined' + , 'expected #{this} not to be undefined' + ); + }); + + /** + * ### .NaN + * + * Asserts that the target is exactly `NaN`. + * + * expect(NaN).to.be.NaN; + * + * Add `.not` earlier in the chain to negate `.NaN`. However, it's often best + * to assert that the target is equal to its expected value, rather than not + * equal to `NaN`. + * + * expect('foo').to.equal('foo'); // Recommended + * expect('foo').to.not.be.NaN; // Not recommended + * + * A custom error message can be given as the second argument to `expect`. + * + * expect(42, 'nooo why fail??').to.be.NaN; + * + * @name NaN + * @namespace BDD + * @api public + */ + + Assertion.addProperty('NaN', function () { + this.assert( + _.isNaN(flag(this, 'object')) + , 'expected #{this} to be NaN' + , 'expected #{this} not to be NaN' + ); + }); + + /** + * ### .exist + * + * Asserts that the target is not strictly (`===`) equal to either `null` or + * `undefined`. However, it's often best to assert that the target is equal to + * its expected value. + * + * expect(1).to.equal(1); // Recommended + * expect(1).to.exist; // Not recommended + * + * expect(0).to.equal(0); // Recommended + * expect(0).to.exist; // Not recommended + * + * Add `.not` earlier in the chain to negate `.exist`. + * + * expect(null).to.be.null; // Recommended + * expect(null).to.not.exist; // Not recommended + * + * expect(undefined).to.be.undefined; // Recommended + * expect(undefined).to.not.exist; // Not recommended + * + * A custom error message can be given as the second argument to `expect`. + * + * expect(null, 'nooo why fail??').to.exist; + * + * @name exist + * @namespace BDD + * @api public + */ + + Assertion.addProperty('exist', function () { + var val = flag(this, 'object'); + this.assert( + val !== null && val !== undefined + , 'expected #{this} to exist' + , 'expected #{this} to not exist' + ); + }); + + /** + * ### .empty + * + * When the target is a string or array, `.empty` asserts that the target's + * `length` property is strictly (`===`) equal to `0`. + * + * expect([]).to.be.empty; + * expect('').to.be.empty; + * + * When the target is a map or set, `.empty` asserts that the target's `size` + * property is strictly equal to `0`. + * + * expect(new Set()).to.be.empty; + * expect(new Map()).to.be.empty; + * + * When the target is a non-function object, `.empty` asserts that the target + * doesn't have any own enumerable properties. Properties with Symbol-based + * keys are excluded from the count. + * + * expect({}).to.be.empty; + * + * Because `.empty` does different things based on the target's type, it's + * important to check the target's type before using `.empty`. See the `.a` + * doc for info on testing a target's type. + * + * expect([]).to.be.an('array').that.is.empty; + * + * Add `.not` earlier in the chain to negate `.empty`. However, it's often + * best to assert that the target contains its expected number of values, + * rather than asserting that it's not empty. + * + * expect([1, 2, 3]).to.have.lengthOf(3); // Recommended + * expect([1, 2, 3]).to.not.be.empty; // Not recommended + * + * expect(new Set([1, 2, 3])).to.have.property('size', 3); // Recommended + * expect(new Set([1, 2, 3])).to.not.be.empty; // Not recommended + * + * expect(Object.keys({a: 1})).to.have.lengthOf(1); // Recommended + * expect({a: 1}).to.not.be.empty; // Not recommended + * + * A custom error message can be given as the second argument to `expect`. + * + * expect([1, 2, 3], 'nooo why fail??').to.be.empty; + * + * @name empty + * @namespace BDD + * @api public + */ + + Assertion.addProperty('empty', function () { + var val = flag(this, 'object') + , ssfi = flag(this, 'ssfi') + , flagMsg = flag(this, 'message') + , itemsCount; + + flagMsg = flagMsg ? flagMsg + ': ' : ''; + + switch (_.type(val).toLowerCase()) { + case 'array': + case 'string': + itemsCount = val.length; + break; + case 'map': + case 'set': + itemsCount = val.size; + break; + case 'weakmap': + case 'weakset': + throw new AssertionError( + flagMsg + '.empty was passed a weak collection', + undefined, + ssfi + ); + case 'function': + var msg = flagMsg + '.empty was passed a function ' + _.getName(val); + throw new AssertionError(msg.trim(), undefined, ssfi); + default: + if (val !== Object(val)) { + throw new AssertionError( + flagMsg + '.empty was passed non-string primitive ' + _.inspect(val), + undefined, + ssfi + ); + } + itemsCount = Object.keys(val).length; + } + + this.assert( + 0 === itemsCount + , 'expected #{this} to be empty' + , 'expected #{this} not to be empty' + ); + }); + + /** + * ### .arguments + * + * Asserts that the target is an `arguments` object. + * + * function test () { + * expect(arguments).to.be.arguments; + * } + * + * test(); + * + * Add `.not` earlier in the chain to negate `.arguments`. However, it's often + * best to assert which type the target is expected to be, rather than + * asserting that its not an `arguments` object. + * + * expect('foo').to.be.a('string'); // Recommended + * expect('foo').to.not.be.arguments; // Not recommended + * + * A custom error message can be given as the second argument to `expect`. + * + * expect({}, 'nooo why fail??').to.be.arguments; + * + * The alias `.Arguments` can be used interchangeably with `.arguments`. + * + * @name arguments + * @alias Arguments + * @namespace BDD + * @api public + */ + + function checkArguments () { + var obj = flag(this, 'object') + , type = _.type(obj); + this.assert( + 'Arguments' === type + , 'expected #{this} to be arguments but got ' + type + , 'expected #{this} to not be arguments' + ); + } + + Assertion.addProperty('arguments', checkArguments); + Assertion.addProperty('Arguments', checkArguments); + + /** + * ### .equal(val[, msg]) + * + * Asserts that the target is strictly (`===`) equal to the given `val`. + * + * expect(1).to.equal(1); + * expect('foo').to.equal('foo'); + * + * Add `.deep` earlier in the chain to use deep equality instead. See the + * `deep-eql` project page for info on the deep equality algorithm: + * https://github.com/chaijs/deep-eql. + * + * // Target object deeply (but not strictly) equals `{a: 1}` + * expect({a: 1}).to.deep.equal({a: 1}); + * expect({a: 1}).to.not.equal({a: 1}); + * + * // Target array deeply (but not strictly) equals `[1, 2]` + * expect([1, 2]).to.deep.equal([1, 2]); + * expect([1, 2]).to.not.equal([1, 2]); + * + * Add `.not` earlier in the chain to negate `.equal`. However, it's often + * best to assert that the target is equal to its expected value, rather than + * not equal to one of countless unexpected values. + * + * expect(1).to.equal(1); // Recommended + * expect(1).to.not.equal(2); // Not recommended + * + * `.equal` accepts an optional `msg` argument which is a custom error message + * to show when the assertion fails. The message can also be given as the + * second argument to `expect`. + * + * expect(1).to.equal(2, 'nooo why fail??'); + * expect(1, 'nooo why fail??').to.equal(2); + * + * The aliases `.equals` and `eq` can be used interchangeably with `.equal`. + * + * @name equal + * @alias equals + * @alias eq + * @param {Mixed} val + * @param {String} msg _optional_ + * @namespace BDD + * @api public + */ + + function assertEqual (val, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + if (flag(this, 'deep')) { + var prevLockSsfi = flag(this, 'lockSsfi'); + flag(this, 'lockSsfi', true); + this.eql(val); + flag(this, 'lockSsfi', prevLockSsfi); + } else { + this.assert( + val === obj + , 'expected #{this} to equal #{exp}' + , 'expected #{this} to not equal #{exp}' + , val + , this._obj + , true + ); + } + } + + Assertion.addMethod('equal', assertEqual); + Assertion.addMethod('equals', assertEqual); + Assertion.addMethod('eq', assertEqual); + + /** + * ### .eql(obj[, msg]) + * + * Asserts that the target is deeply equal to the given `obj`. See the + * `deep-eql` project page for info on the deep equality algorithm: + * https://github.com/chaijs/deep-eql. + * + * // Target object is deeply (but not strictly) equal to {a: 1} + * expect({a: 1}).to.eql({a: 1}).but.not.equal({a: 1}); + * + * // Target array is deeply (but not strictly) equal to [1, 2] + * expect([1, 2]).to.eql([1, 2]).but.not.equal([1, 2]); + * + * Add `.not` earlier in the chain to negate `.eql`. However, it's often best + * to assert that the target is deeply equal to its expected value, rather + * than not deeply equal to one of countless unexpected values. + * + * expect({a: 1}).to.eql({a: 1}); // Recommended + * expect({a: 1}).to.not.eql({b: 2}); // Not recommended + * + * `.eql` accepts an optional `msg` argument which is a custom error message + * to show when the assertion fails. The message can also be given as the + * second argument to `expect`. + * + * expect({a: 1}).to.eql({b: 2}, 'nooo why fail??'); + * expect({a: 1}, 'nooo why fail??').to.eql({b: 2}); + * + * The alias `.eqls` can be used interchangeably with `.eql`. + * + * The `.deep.equal` assertion is almost identical to `.eql` but with one + * difference: `.deep.equal` causes deep equality comparisons to also be used + * for any other assertions that follow in the chain. + * + * @name eql + * @alias eqls + * @param {Mixed} obj + * @param {String} msg _optional_ + * @namespace BDD + * @api public + */ + + function assertEql(obj, msg) { + if (msg) flag(this, 'message', msg); + this.assert( + _.eql(obj, flag(this, 'object')) + , 'expected #{this} to deeply equal #{exp}' + , 'expected #{this} to not deeply equal #{exp}' + , obj + , this._obj + , true + ); + } + + Assertion.addMethod('eql', assertEql); + Assertion.addMethod('eqls', assertEql); + + /** + * ### .above(n[, msg]) + * + * Asserts that the target is a number or a date greater than the given number or date `n` respectively. + * However, it's often best to assert that the target is equal to its expected + * value. + * + * expect(2).to.equal(2); // Recommended + * expect(2).to.be.above(1); // Not recommended + * + * Add `.lengthOf` earlier in the chain to assert that the target's `length` + * or `size` is greater than the given number `n`. + * + * expect('foo').to.have.lengthOf(3); // Recommended + * expect('foo').to.have.lengthOf.above(2); // Not recommended + * + * expect([1, 2, 3]).to.have.lengthOf(3); // Recommended + * expect([1, 2, 3]).to.have.lengthOf.above(2); // Not recommended + * + * Add `.not` earlier in the chain to negate `.above`. + * + * expect(2).to.equal(2); // Recommended + * expect(1).to.not.be.above(2); // Not recommended + * + * `.above` accepts an optional `msg` argument which is a custom error message + * to show when the assertion fails. The message can also be given as the + * second argument to `expect`. + * + * expect(1).to.be.above(2, 'nooo why fail??'); + * expect(1, 'nooo why fail??').to.be.above(2); + * + * The aliases `.gt` and `.greaterThan` can be used interchangeably with + * `.above`. + * + * @name above + * @alias gt + * @alias greaterThan + * @param {Number} n + * @param {String} msg _optional_ + * @namespace BDD + * @api public + */ + + function assertAbove (n, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object') + , doLength = flag(this, 'doLength') + , flagMsg = flag(this, 'message') + , msgPrefix = ((flagMsg) ? flagMsg + ': ' : '') + , ssfi = flag(this, 'ssfi') + , objType = _.type(obj).toLowerCase() + , nType = _.type(n).toLowerCase() + , errorMessage + , shouldThrow = true; + + if (doLength && objType !== 'map' && objType !== 'set') { + new Assertion(obj, flagMsg, ssfi, true).to.have.property('length'); + } + + if (!doLength && (objType === 'date' && nType !== 'date')) { + errorMessage = msgPrefix + 'the argument to above must be a date'; + } else if (nType !== 'number' && (doLength || objType === 'number')) { + errorMessage = msgPrefix + 'the argument to above must be a number'; + } else if (!doLength && (objType !== 'date' && objType !== 'number')) { + var printObj = (objType === 'string') ? "'" + obj + "'" : obj; + errorMessage = msgPrefix + 'expected ' + printObj + ' to be a number or a date'; + } else { + shouldThrow = false; + } + + if (shouldThrow) { + throw new AssertionError(errorMessage, undefined, ssfi); + } + + if (doLength) { + var descriptor = 'length' + , itemsCount; + if (objType === 'map' || objType === 'set') { + descriptor = 'size'; + itemsCount = obj.size; + } else { + itemsCount = obj.length; + } + this.assert( + itemsCount > n + , 'expected #{this} to have a ' + descriptor + ' above #{exp} but got #{act}' + , 'expected #{this} to not have a ' + descriptor + ' above #{exp}' + , n + , itemsCount + ); + } else { + this.assert( + obj > n + , 'expected #{this} to be above #{exp}' + , 'expected #{this} to be at most #{exp}' + , n + ); + } + } + + Assertion.addMethod('above', assertAbove); + Assertion.addMethod('gt', assertAbove); + Assertion.addMethod('greaterThan', assertAbove); + + /** + * ### .least(n[, msg]) + * + * Asserts that the target is a number or a date greater than or equal to the given + * number or date `n` respectively. However, it's often best to assert that the target is equal to + * its expected value. + * + * expect(2).to.equal(2); // Recommended + * expect(2).to.be.at.least(1); // Not recommended + * expect(2).to.be.at.least(2); // Not recommended + * + * Add `.lengthOf` earlier in the chain to assert that the target's `length` + * or `size` is greater than or equal to the given number `n`. + * + * expect('foo').to.have.lengthOf(3); // Recommended + * expect('foo').to.have.lengthOf.at.least(2); // Not recommended + * + * expect([1, 2, 3]).to.have.lengthOf(3); // Recommended + * expect([1, 2, 3]).to.have.lengthOf.at.least(2); // Not recommended + * + * Add `.not` earlier in the chain to negate `.least`. + * + * expect(1).to.equal(1); // Recommended + * expect(1).to.not.be.at.least(2); // Not recommended + * + * `.least` accepts an optional `msg` argument which is a custom error message + * to show when the assertion fails. The message can also be given as the + * second argument to `expect`. + * + * expect(1).to.be.at.least(2, 'nooo why fail??'); + * expect(1, 'nooo why fail??').to.be.at.least(2); + * + * The alias `.gte` can be used interchangeably with `.least`. + * + * @name least + * @alias gte + * @param {Number} n + * @param {String} msg _optional_ + * @namespace BDD + * @api public + */ + + function assertLeast (n, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object') + , doLength = flag(this, 'doLength') + , flagMsg = flag(this, 'message') + , msgPrefix = ((flagMsg) ? flagMsg + ': ' : '') + , ssfi = flag(this, 'ssfi') + , objType = _.type(obj).toLowerCase() + , nType = _.type(n).toLowerCase() + , errorMessage + , shouldThrow = true; + + if (doLength && objType !== 'map' && objType !== 'set') { + new Assertion(obj, flagMsg, ssfi, true).to.have.property('length'); + } + + if (!doLength && (objType === 'date' && nType !== 'date')) { + errorMessage = msgPrefix + 'the argument to least must be a date'; + } else if (nType !== 'number' && (doLength || objType === 'number')) { + errorMessage = msgPrefix + 'the argument to least must be a number'; + } else if (!doLength && (objType !== 'date' && objType !== 'number')) { + var printObj = (objType === 'string') ? "'" + obj + "'" : obj; + errorMessage = msgPrefix + 'expected ' + printObj + ' to be a number or a date'; + } else { + shouldThrow = false; + } + + if (shouldThrow) { + throw new AssertionError(errorMessage, undefined, ssfi); + } + + if (doLength) { + var descriptor = 'length' + , itemsCount; + if (objType === 'map' || objType === 'set') { + descriptor = 'size'; + itemsCount = obj.size; + } else { + itemsCount = obj.length; + } + this.assert( + itemsCount >= n + , 'expected #{this} to have a ' + descriptor + ' at least #{exp} but got #{act}' + , 'expected #{this} to have a ' + descriptor + ' below #{exp}' + , n + , itemsCount + ); + } else { + this.assert( + obj >= n + , 'expected #{this} to be at least #{exp}' + , 'expected #{this} to be below #{exp}' + , n + ); + } + } + + Assertion.addMethod('least', assertLeast); + Assertion.addMethod('gte', assertLeast); + + /** + * ### .below(n[, msg]) + * + * Asserts that the target is a number or a date less than the given number or date `n` respectively. + * However, it's often best to assert that the target is equal to its expected + * value. + * + * expect(1).to.equal(1); // Recommended + * expect(1).to.be.below(2); // Not recommended + * + * Add `.lengthOf` earlier in the chain to assert that the target's `length` + * or `size` is less than the given number `n`. + * + * expect('foo').to.have.lengthOf(3); // Recommended + * expect('foo').to.have.lengthOf.below(4); // Not recommended + * + * expect([1, 2, 3]).to.have.length(3); // Recommended + * expect([1, 2, 3]).to.have.lengthOf.below(4); // Not recommended + * + * Add `.not` earlier in the chain to negate `.below`. + * + * expect(2).to.equal(2); // Recommended + * expect(2).to.not.be.below(1); // Not recommended + * + * `.below` accepts an optional `msg` argument which is a custom error message + * to show when the assertion fails. The message can also be given as the + * second argument to `expect`. + * + * expect(2).to.be.below(1, 'nooo why fail??'); + * expect(2, 'nooo why fail??').to.be.below(1); + * + * The aliases `.lt` and `.lessThan` can be used interchangeably with + * `.below`. + * + * @name below + * @alias lt + * @alias lessThan + * @param {Number} n + * @param {String} msg _optional_ + * @namespace BDD + * @api public + */ + + function assertBelow (n, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object') + , doLength = flag(this, 'doLength') + , flagMsg = flag(this, 'message') + , msgPrefix = ((flagMsg) ? flagMsg + ': ' : '') + , ssfi = flag(this, 'ssfi') + , objType = _.type(obj).toLowerCase() + , nType = _.type(n).toLowerCase() + , errorMessage + , shouldThrow = true; + + if (doLength && objType !== 'map' && objType !== 'set') { + new Assertion(obj, flagMsg, ssfi, true).to.have.property('length'); + } + + if (!doLength && (objType === 'date' && nType !== 'date')) { + errorMessage = msgPrefix + 'the argument to below must be a date'; + } else if (nType !== 'number' && (doLength || objType === 'number')) { + errorMessage = msgPrefix + 'the argument to below must be a number'; + } else if (!doLength && (objType !== 'date' && objType !== 'number')) { + var printObj = (objType === 'string') ? "'" + obj + "'" : obj; + errorMessage = msgPrefix + 'expected ' + printObj + ' to be a number or a date'; + } else { + shouldThrow = false; + } + + if (shouldThrow) { + throw new AssertionError(errorMessage, undefined, ssfi); + } + + if (doLength) { + var descriptor = 'length' + , itemsCount; + if (objType === 'map' || objType === 'set') { + descriptor = 'size'; + itemsCount = obj.size; + } else { + itemsCount = obj.length; + } + this.assert( + itemsCount < n + , 'expected #{this} to have a ' + descriptor + ' below #{exp} but got #{act}' + , 'expected #{this} to not have a ' + descriptor + ' below #{exp}' + , n + , itemsCount + ); + } else { + this.assert( + obj < n + , 'expected #{this} to be below #{exp}' + , 'expected #{this} to be at least #{exp}' + , n + ); + } + } + + Assertion.addMethod('below', assertBelow); + Assertion.addMethod('lt', assertBelow); + Assertion.addMethod('lessThan', assertBelow); + + /** + * ### .most(n[, msg]) + * + * Asserts that the target is a number or a date less than or equal to the given number + * or date `n` respectively. However, it's often best to assert that the target is equal to its + * expected value. + * + * expect(1).to.equal(1); // Recommended + * expect(1).to.be.at.most(2); // Not recommended + * expect(1).to.be.at.most(1); // Not recommended + * + * Add `.lengthOf` earlier in the chain to assert that the target's `length` + * or `size` is less than or equal to the given number `n`. + * + * expect('foo').to.have.lengthOf(3); // Recommended + * expect('foo').to.have.lengthOf.at.most(4); // Not recommended + * + * expect([1, 2, 3]).to.have.lengthOf(3); // Recommended + * expect([1, 2, 3]).to.have.lengthOf.at.most(4); // Not recommended + * + * Add `.not` earlier in the chain to negate `.most`. + * + * expect(2).to.equal(2); // Recommended + * expect(2).to.not.be.at.most(1); // Not recommended + * + * `.most` accepts an optional `msg` argument which is a custom error message + * to show when the assertion fails. The message can also be given as the + * second argument to `expect`. + * + * expect(2).to.be.at.most(1, 'nooo why fail??'); + * expect(2, 'nooo why fail??').to.be.at.most(1); + * + * The alias `.lte` can be used interchangeably with `.most`. + * + * @name most + * @alias lte + * @param {Number} n + * @param {String} msg _optional_ + * @namespace BDD + * @api public + */ + + function assertMost (n, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object') + , doLength = flag(this, 'doLength') + , flagMsg = flag(this, 'message') + , msgPrefix = ((flagMsg) ? flagMsg + ': ' : '') + , ssfi = flag(this, 'ssfi') + , objType = _.type(obj).toLowerCase() + , nType = _.type(n).toLowerCase() + , errorMessage + , shouldThrow = true; + + if (doLength && objType !== 'map' && objType !== 'set') { + new Assertion(obj, flagMsg, ssfi, true).to.have.property('length'); + } + + if (!doLength && (objType === 'date' && nType !== 'date')) { + errorMessage = msgPrefix + 'the argument to most must be a date'; + } else if (nType !== 'number' && (doLength || objType === 'number')) { + errorMessage = msgPrefix + 'the argument to most must be a number'; + } else if (!doLength && (objType !== 'date' && objType !== 'number')) { + var printObj = (objType === 'string') ? "'" + obj + "'" : obj; + errorMessage = msgPrefix + 'expected ' + printObj + ' to be a number or a date'; + } else { + shouldThrow = false; + } + + if (shouldThrow) { + throw new AssertionError(errorMessage, undefined, ssfi); + } + + if (doLength) { + var descriptor = 'length' + , itemsCount; + if (objType === 'map' || objType === 'set') { + descriptor = 'size'; + itemsCount = obj.size; + } else { + itemsCount = obj.length; + } + this.assert( + itemsCount <= n + , 'expected #{this} to have a ' + descriptor + ' at most #{exp} but got #{act}' + , 'expected #{this} to have a ' + descriptor + ' above #{exp}' + , n + , itemsCount + ); + } else { + this.assert( + obj <= n + , 'expected #{this} to be at most #{exp}' + , 'expected #{this} to be above #{exp}' + , n + ); + } + } + + Assertion.addMethod('most', assertMost); + Assertion.addMethod('lte', assertMost); + + /** + * ### .within(start, finish[, msg]) + * + * Asserts that the target is a number or a date greater than or equal to the given + * number or date `start`, and less than or equal to the given number or date `finish` respectively. + * However, it's often best to assert that the target is equal to its expected + * value. + * + * expect(2).to.equal(2); // Recommended + * expect(2).to.be.within(1, 3); // Not recommended + * expect(2).to.be.within(2, 3); // Not recommended + * expect(2).to.be.within(1, 2); // Not recommended + * + * Add `.lengthOf` earlier in the chain to assert that the target's `length` + * or `size` is greater than or equal to the given number `start`, and less + * than or equal to the given number `finish`. + * + * expect('foo').to.have.lengthOf(3); // Recommended + * expect('foo').to.have.lengthOf.within(2, 4); // Not recommended + * + * expect([1, 2, 3]).to.have.lengthOf(3); // Recommended + * expect([1, 2, 3]).to.have.lengthOf.within(2, 4); // Not recommended + * + * Add `.not` earlier in the chain to negate `.within`. + * + * expect(1).to.equal(1); // Recommended + * expect(1).to.not.be.within(2, 4); // Not recommended + * + * `.within` accepts an optional `msg` argument which is a custom error + * message to show when the assertion fails. The message can also be given as + * the second argument to `expect`. + * + * expect(4).to.be.within(1, 3, 'nooo why fail??'); + * expect(4, 'nooo why fail??').to.be.within(1, 3); + * + * @name within + * @param {Number} start lower bound inclusive + * @param {Number} finish upper bound inclusive + * @param {String} msg _optional_ + * @namespace BDD + * @api public + */ + + Assertion.addMethod('within', function (start, finish, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object') + , doLength = flag(this, 'doLength') + , flagMsg = flag(this, 'message') + , msgPrefix = ((flagMsg) ? flagMsg + ': ' : '') + , ssfi = flag(this, 'ssfi') + , objType = _.type(obj).toLowerCase() + , startType = _.type(start).toLowerCase() + , finishType = _.type(finish).toLowerCase() + , errorMessage + , shouldThrow = true + , range = (startType === 'date' && finishType === 'date') + ? start.toUTCString() + '..' + finish.toUTCString() + : start + '..' + finish; + + if (doLength && objType !== 'map' && objType !== 'set') { + new Assertion(obj, flagMsg, ssfi, true).to.have.property('length'); + } + + if (!doLength && (objType === 'date' && (startType !== 'date' || finishType !== 'date'))) { + errorMessage = msgPrefix + 'the arguments to within must be dates'; + } else if ((startType !== 'number' || finishType !== 'number') && (doLength || objType === 'number')) { + errorMessage = msgPrefix + 'the arguments to within must be numbers'; + } else if (!doLength && (objType !== 'date' && objType !== 'number')) { + var printObj = (objType === 'string') ? "'" + obj + "'" : obj; + errorMessage = msgPrefix + 'expected ' + printObj + ' to be a number or a date'; + } else { + shouldThrow = false; + } + + if (shouldThrow) { + throw new AssertionError(errorMessage, undefined, ssfi); + } + + if (doLength) { + var descriptor = 'length' + , itemsCount; + if (objType === 'map' || objType === 'set') { + descriptor = 'size'; + itemsCount = obj.size; + } else { + itemsCount = obj.length; + } + this.assert( + itemsCount >= start && itemsCount <= finish + , 'expected #{this} to have a ' + descriptor + ' within ' + range + , 'expected #{this} to not have a ' + descriptor + ' within ' + range + ); + } else { + this.assert( + obj >= start && obj <= finish + , 'expected #{this} to be within ' + range + , 'expected #{this} to not be within ' + range + ); + } + }); + + /** + * ### .instanceof(constructor[, msg]) + * + * Asserts that the target is an instance of the given `constructor`. + * + * function Cat () { } + * + * expect(new Cat()).to.be.an.instanceof(Cat); + * expect([1, 2]).to.be.an.instanceof(Array); + * + * Add `.not` earlier in the chain to negate `.instanceof`. + * + * expect({a: 1}).to.not.be.an.instanceof(Array); + * + * `.instanceof` accepts an optional `msg` argument which is a custom error + * message to show when the assertion fails. The message can also be given as + * the second argument to `expect`. + * + * expect(1).to.be.an.instanceof(Array, 'nooo why fail??'); + * expect(1, 'nooo why fail??').to.be.an.instanceof(Array); + * + * Due to limitations in ES5, `.instanceof` may not always work as expected + * when using a transpiler such as Babel or TypeScript. In particular, it may + * produce unexpected results when subclassing built-in object such as + * `Array`, `Error`, and `Map`. See your transpiler's docs for details: + * + * - ([Babel](https://babeljs.io/docs/usage/caveats/#classes)) + * - ([TypeScript](https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work)) + * + * The alias `.instanceOf` can be used interchangeably with `.instanceof`. + * + * @name instanceof + * @param {Constructor} constructor + * @param {String} msg _optional_ + * @alias instanceOf + * @namespace BDD + * @api public + */ + + function assertInstanceOf (constructor, msg) { + if (msg) flag(this, 'message', msg); + + var target = flag(this, 'object') + var ssfi = flag(this, 'ssfi'); + var flagMsg = flag(this, 'message'); + + try { + var isInstanceOf = target instanceof constructor; + } catch (err) { + if (err instanceof TypeError) { + flagMsg = flagMsg ? flagMsg + ': ' : ''; + throw new AssertionError( + flagMsg + 'The instanceof assertion needs a constructor but ' + + _.type(constructor) + ' was given.', + undefined, + ssfi + ); + } + throw err; + } + + var name = _.getName(constructor); + if (name === null) { + name = 'an unnamed constructor'; + } + + this.assert( + isInstanceOf + , 'expected #{this} to be an instance of ' + name + , 'expected #{this} to not be an instance of ' + name + ); + }; + + Assertion.addMethod('instanceof', assertInstanceOf); + Assertion.addMethod('instanceOf', assertInstanceOf); + + /** + * ### .property(name[, val[, msg]]) + * + * Asserts that the target has a property with the given key `name`. + * + * expect({a: 1}).to.have.property('a'); + * + * When `val` is provided, `.property` also asserts that the property's value + * is equal to the given `val`. + * + * expect({a: 1}).to.have.property('a', 1); + * + * By default, strict (`===`) equality is used. Add `.deep` earlier in the + * chain to use deep equality instead. See the `deep-eql` project page for + * info on the deep equality algorithm: https://github.com/chaijs/deep-eql. + * + * // Target object deeply (but not strictly) has property `x: {a: 1}` + * expect({x: {a: 1}}).to.have.deep.property('x', {a: 1}); + * expect({x: {a: 1}}).to.not.have.property('x', {a: 1}); + * + * The target's enumerable and non-enumerable properties are always included + * in the search. By default, both own and inherited properties are included. + * Add `.own` earlier in the chain to exclude inherited properties from the + * search. + * + * Object.prototype.b = 2; + * + * expect({a: 1}).to.have.own.property('a'); + * expect({a: 1}).to.have.own.property('a', 1); + * expect({a: 1}).to.have.property('b'); + * expect({a: 1}).to.not.have.own.property('b'); + * + * `.deep` and `.own` can be combined. + * + * expect({x: {a: 1}}).to.have.deep.own.property('x', {a: 1}); + * + * Add `.nested` earlier in the chain to enable dot- and bracket-notation when + * referencing nested properties. + * + * expect({a: {b: ['x', 'y']}}).to.have.nested.property('a.b[1]'); + * expect({a: {b: ['x', 'y']}}).to.have.nested.property('a.b[1]', 'y'); + * + * If `.` or `[]` are part of an actual property name, they can be escaped by + * adding two backslashes before them. + * + * expect({'.a': {'[b]': 'x'}}).to.have.nested.property('\\.a.\\[b\\]'); + * + * `.deep` and `.nested` can be combined. + * + * expect({a: {b: [{c: 3}]}}) + * .to.have.deep.nested.property('a.b[0]', {c: 3}); + * + * `.own` and `.nested` cannot be combined. + * + * Add `.not` earlier in the chain to negate `.property`. + * + * expect({a: 1}).to.not.have.property('b'); + * + * However, it's dangerous to negate `.property` when providing `val`. The + * problem is that it creates uncertain expectations by asserting that the + * target either doesn't have a property with the given key `name`, or that it + * does have a property with the given key `name` but its value isn't equal to + * the given `val`. It's often best to identify the exact output that's + * expected, and then write an assertion that only accepts that exact output. + * + * When the target isn't expected to have a property with the given key + * `name`, it's often best to assert exactly that. + * + * expect({b: 2}).to.not.have.property('a'); // Recommended + * expect({b: 2}).to.not.have.property('a', 1); // Not recommended + * + * When the target is expected to have a property with the given key `name`, + * it's often best to assert that the property has its expected value, rather + * than asserting that it doesn't have one of many unexpected values. + * + * expect({a: 3}).to.have.property('a', 3); // Recommended + * expect({a: 3}).to.not.have.property('a', 1); // Not recommended + * + * `.property` changes the target of any assertions that follow in the chain + * to be the value of the property from the original target object. + * + * expect({a: 1}).to.have.property('a').that.is.a('number'); + * + * `.property` accepts an optional `msg` argument which is a custom error + * message to show when the assertion fails. The message can also be given as + * the second argument to `expect`. When not providing `val`, only use the + * second form. + * + * // Recommended + * expect({a: 1}).to.have.property('a', 2, 'nooo why fail??'); + * expect({a: 1}, 'nooo why fail??').to.have.property('a', 2); + * expect({a: 1}, 'nooo why fail??').to.have.property('b'); + * + * // Not recommended + * expect({a: 1}).to.have.property('b', undefined, 'nooo why fail??'); + * + * The above assertion isn't the same thing as not providing `val`. Instead, + * it's asserting that the target object has a `b` property that's equal to + * `undefined`. + * + * The assertions `.ownProperty` and `.haveOwnProperty` can be used + * interchangeably with `.own.property`. + * + * @name property + * @param {String} name + * @param {Mixed} val (optional) + * @param {String} msg _optional_ + * @returns value of property for chaining + * @namespace BDD + * @api public + */ + + function assertProperty (name, val, msg) { + if (msg) flag(this, 'message', msg); + + var isNested = flag(this, 'nested') + , isOwn = flag(this, 'own') + , flagMsg = flag(this, 'message') + , obj = flag(this, 'object') + , ssfi = flag(this, 'ssfi') + , nameType = typeof name; + + flagMsg = flagMsg ? flagMsg + ': ' : ''; + + if (isNested) { + if (nameType !== 'string') { + throw new AssertionError( + flagMsg + 'the argument to property must be a string when using nested syntax', + undefined, + ssfi + ); + } + } else { + if (nameType !== 'string' && nameType !== 'number' && nameType !== 'symbol') { + throw new AssertionError( + flagMsg + 'the argument to property must be a string, number, or symbol', + undefined, + ssfi + ); + } + } + + if (isNested && isOwn) { + throw new AssertionError( + flagMsg + 'The "nested" and "own" flags cannot be combined.', + undefined, + ssfi + ); + } + + if (obj === null || obj === undefined) { + throw new AssertionError( + flagMsg + 'Target cannot be null or undefined.', + undefined, + ssfi + ); + } + + var isDeep = flag(this, 'deep') + , negate = flag(this, 'negate') + , pathInfo = isNested ? _.getPathInfo(obj, name) : null + , value = isNested ? pathInfo.value : obj[name]; + + var descriptor = ''; + if (isDeep) descriptor += 'deep '; + if (isOwn) descriptor += 'own '; + if (isNested) descriptor += 'nested '; + descriptor += 'property '; + + var hasProperty; + if (isOwn) hasProperty = Object.prototype.hasOwnProperty.call(obj, name); + else if (isNested) hasProperty = pathInfo.exists; + else hasProperty = _.hasProperty(obj, name); + + // When performing a negated assertion for both name and val, merely having + // a property with the given name isn't enough to cause the assertion to + // fail. It must both have a property with the given name, and the value of + // that property must equal the given val. Therefore, skip this assertion in + // favor of the next. + if (!negate || arguments.length === 1) { + this.assert( + hasProperty + , 'expected #{this} to have ' + descriptor + _.inspect(name) + , 'expected #{this} to not have ' + descriptor + _.inspect(name)); + } + + if (arguments.length > 1) { + this.assert( + hasProperty && (isDeep ? _.eql(val, value) : val === value) + , 'expected #{this} to have ' + descriptor + _.inspect(name) + ' of #{exp}, but got #{act}' + , 'expected #{this} to not have ' + descriptor + _.inspect(name) + ' of #{act}' + , val + , value + ); + } + + flag(this, 'object', value); + } + + Assertion.addMethod('property', assertProperty); + + function assertOwnProperty (name, value, msg) { + flag(this, 'own', true); + assertProperty.apply(this, arguments); + } + + Assertion.addMethod('ownProperty', assertOwnProperty); + Assertion.addMethod('haveOwnProperty', assertOwnProperty); + + /** + * ### .ownPropertyDescriptor(name[, descriptor[, msg]]) + * + * Asserts that the target has its own property descriptor with the given key + * `name`. Enumerable and non-enumerable properties are included in the + * search. + * + * expect({a: 1}).to.have.ownPropertyDescriptor('a'); + * + * When `descriptor` is provided, `.ownPropertyDescriptor` also asserts that + * the property's descriptor is deeply equal to the given `descriptor`. See + * the `deep-eql` project page for info on the deep equality algorithm: + * https://github.com/chaijs/deep-eql. + * + * expect({a: 1}).to.have.ownPropertyDescriptor('a', { + * configurable: true, + * enumerable: true, + * writable: true, + * value: 1, + * }); + * + * Add `.not` earlier in the chain to negate `.ownPropertyDescriptor`. + * + * expect({a: 1}).to.not.have.ownPropertyDescriptor('b'); + * + * However, it's dangerous to negate `.ownPropertyDescriptor` when providing + * a `descriptor`. The problem is that it creates uncertain expectations by + * asserting that the target either doesn't have a property descriptor with + * the given key `name`, or that it does have a property descriptor with the + * given key `name` but its not deeply equal to the given `descriptor`. It's + * often best to identify the exact output that's expected, and then write an + * assertion that only accepts that exact output. + * + * When the target isn't expected to have a property descriptor with the given + * key `name`, it's often best to assert exactly that. + * + * // Recommended + * expect({b: 2}).to.not.have.ownPropertyDescriptor('a'); + * + * // Not recommended + * expect({b: 2}).to.not.have.ownPropertyDescriptor('a', { + * configurable: true, + * enumerable: true, + * writable: true, + * value: 1, + * }); + * + * When the target is expected to have a property descriptor with the given + * key `name`, it's often best to assert that the property has its expected + * descriptor, rather than asserting that it doesn't have one of many + * unexpected descriptors. + * + * // Recommended + * expect({a: 3}).to.have.ownPropertyDescriptor('a', { + * configurable: true, + * enumerable: true, + * writable: true, + * value: 3, + * }); + * + * // Not recommended + * expect({a: 3}).to.not.have.ownPropertyDescriptor('a', { + * configurable: true, + * enumerable: true, + * writable: true, + * value: 1, + * }); + * + * `.ownPropertyDescriptor` changes the target of any assertions that follow + * in the chain to be the value of the property descriptor from the original + * target object. + * + * expect({a: 1}).to.have.ownPropertyDescriptor('a') + * .that.has.property('enumerable', true); + * + * `.ownPropertyDescriptor` accepts an optional `msg` argument which is a + * custom error message to show when the assertion fails. The message can also + * be given as the second argument to `expect`. When not providing + * `descriptor`, only use the second form. + * + * // Recommended + * expect({a: 1}).to.have.ownPropertyDescriptor('a', { + * configurable: true, + * enumerable: true, + * writable: true, + * value: 2, + * }, 'nooo why fail??'); + * + * // Recommended + * expect({a: 1}, 'nooo why fail??').to.have.ownPropertyDescriptor('a', { + * configurable: true, + * enumerable: true, + * writable: true, + * value: 2, + * }); + * + * // Recommended + * expect({a: 1}, 'nooo why fail??').to.have.ownPropertyDescriptor('b'); + * + * // Not recommended + * expect({a: 1}) + * .to.have.ownPropertyDescriptor('b', undefined, 'nooo why fail??'); + * + * The above assertion isn't the same thing as not providing `descriptor`. + * Instead, it's asserting that the target object has a `b` property + * descriptor that's deeply equal to `undefined`. + * + * The alias `.haveOwnPropertyDescriptor` can be used interchangeably with + * `.ownPropertyDescriptor`. + * + * @name ownPropertyDescriptor + * @alias haveOwnPropertyDescriptor + * @param {String} name + * @param {Object} descriptor _optional_ + * @param {String} msg _optional_ + * @namespace BDD + * @api public + */ + + function assertOwnPropertyDescriptor (name, descriptor, msg) { + if (typeof descriptor === 'string') { + msg = descriptor; + descriptor = null; + } + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + var actualDescriptor = Object.getOwnPropertyDescriptor(Object(obj), name); + if (actualDescriptor && descriptor) { + this.assert( + _.eql(descriptor, actualDescriptor) + , 'expected the own property descriptor for ' + _.inspect(name) + ' on #{this} to match ' + _.inspect(descriptor) + ', got ' + _.inspect(actualDescriptor) + , 'expected the own property descriptor for ' + _.inspect(name) + ' on #{this} to not match ' + _.inspect(descriptor) + , descriptor + , actualDescriptor + , true + ); + } else { + this.assert( + actualDescriptor + , 'expected #{this} to have an own property descriptor for ' + _.inspect(name) + , 'expected #{this} to not have an own property descriptor for ' + _.inspect(name) + ); + } + flag(this, 'object', actualDescriptor); + } + + Assertion.addMethod('ownPropertyDescriptor', assertOwnPropertyDescriptor); + Assertion.addMethod('haveOwnPropertyDescriptor', assertOwnPropertyDescriptor); + + /** + * ### .lengthOf(n[, msg]) + * + * Asserts that the target's `length` or `size` is equal to the given number + * `n`. + * + * expect([1, 2, 3]).to.have.lengthOf(3); + * expect('foo').to.have.lengthOf(3); + * expect(new Set([1, 2, 3])).to.have.lengthOf(3); + * expect(new Map([['a', 1], ['b', 2], ['c', 3]])).to.have.lengthOf(3); + * + * Add `.not` earlier in the chain to negate `.lengthOf`. However, it's often + * best to assert that the target's `length` property is equal to its expected + * value, rather than not equal to one of many unexpected values. + * + * expect('foo').to.have.lengthOf(3); // Recommended + * expect('foo').to.not.have.lengthOf(4); // Not recommended + * + * `.lengthOf` accepts an optional `msg` argument which is a custom error + * message to show when the assertion fails. The message can also be given as + * the second argument to `expect`. + * + * expect([1, 2, 3]).to.have.lengthOf(2, 'nooo why fail??'); + * expect([1, 2, 3], 'nooo why fail??').to.have.lengthOf(2); + * + * `.lengthOf` can also be used as a language chain, causing all `.above`, + * `.below`, `.least`, `.most`, and `.within` assertions that follow in the + * chain to use the target's `length` property as the target. However, it's + * often best to assert that the target's `length` property is equal to its + * expected length, rather than asserting that its `length` property falls + * within some range of values. + * + * // Recommended + * expect([1, 2, 3]).to.have.lengthOf(3); + * + * // Not recommended + * expect([1, 2, 3]).to.have.lengthOf.above(2); + * expect([1, 2, 3]).to.have.lengthOf.below(4); + * expect([1, 2, 3]).to.have.lengthOf.at.least(3); + * expect([1, 2, 3]).to.have.lengthOf.at.most(3); + * expect([1, 2, 3]).to.have.lengthOf.within(2,4); + * + * Due to a compatibility issue, the alias `.length` can't be chained directly + * off of an uninvoked method such as `.a`. Therefore, `.length` can't be used + * interchangeably with `.lengthOf` in every situation. It's recommended to + * always use `.lengthOf` instead of `.length`. + * + * expect([1, 2, 3]).to.have.a.length(3); // incompatible; throws error + * expect([1, 2, 3]).to.have.a.lengthOf(3); // passes as expected + * + * @name lengthOf + * @alias length + * @param {Number} n + * @param {String} msg _optional_ + * @namespace BDD + * @api public + */ + + function assertLengthChain () { + flag(this, 'doLength', true); + } + + function assertLength (n, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object') + , objType = _.type(obj).toLowerCase() + , flagMsg = flag(this, 'message') + , ssfi = flag(this, 'ssfi') + , descriptor = 'length' + , itemsCount; + + switch (objType) { + case 'map': + case 'set': + descriptor = 'size'; + itemsCount = obj.size; + break; + default: + new Assertion(obj, flagMsg, ssfi, true).to.have.property('length'); + itemsCount = obj.length; + } + + this.assert( + itemsCount == n + , 'expected #{this} to have a ' + descriptor + ' of #{exp} but got #{act}' + , 'expected #{this} to not have a ' + descriptor + ' of #{act}' + , n + , itemsCount + ); + } + + Assertion.addChainableMethod('length', assertLength, assertLengthChain); + Assertion.addChainableMethod('lengthOf', assertLength, assertLengthChain); + + /** + * ### .match(re[, msg]) + * + * Asserts that the target matches the given regular expression `re`. + * + * expect('foobar').to.match(/^foo/); + * + * Add `.not` earlier in the chain to negate `.match`. + * + * expect('foobar').to.not.match(/taco/); + * + * `.match` accepts an optional `msg` argument which is a custom error message + * to show when the assertion fails. The message can also be given as the + * second argument to `expect`. + * + * expect('foobar').to.match(/taco/, 'nooo why fail??'); + * expect('foobar', 'nooo why fail??').to.match(/taco/); + * + * The alias `.matches` can be used interchangeably with `.match`. + * + * @name match + * @alias matches + * @param {RegExp} re + * @param {String} msg _optional_ + * @namespace BDD + * @api public + */ + function assertMatch(re, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + this.assert( + re.exec(obj) + , 'expected #{this} to match ' + re + , 'expected #{this} not to match ' + re + ); + } + + Assertion.addMethod('match', assertMatch); + Assertion.addMethod('matches', assertMatch); + + /** + * ### .string(str[, msg]) + * + * Asserts that the target string contains the given substring `str`. + * + * expect('foobar').to.have.string('bar'); + * + * Add `.not` earlier in the chain to negate `.string`. + * + * expect('foobar').to.not.have.string('taco'); + * + * `.string` accepts an optional `msg` argument which is a custom error + * message to show when the assertion fails. The message can also be given as + * the second argument to `expect`. + * + * expect('foobar').to.have.string('taco', 'nooo why fail??'); + * expect('foobar', 'nooo why fail??').to.have.string('taco'); + * + * @name string + * @param {String} str + * @param {String} msg _optional_ + * @namespace BDD + * @api public + */ + + Assertion.addMethod('string', function (str, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object') + , flagMsg = flag(this, 'message') + , ssfi = flag(this, 'ssfi'); + new Assertion(obj, flagMsg, ssfi, true).is.a('string'); + + this.assert( + ~obj.indexOf(str) + , 'expected #{this} to contain ' + _.inspect(str) + , 'expected #{this} to not contain ' + _.inspect(str) + ); + }); + + /** + * ### .keys(key1[, key2[, ...]]) + * + * Asserts that the target object, array, map, or set has the given keys. Only + * the target's own inherited properties are included in the search. + * + * When the target is an object or array, keys can be provided as one or more + * string arguments, a single array argument, or a single object argument. In + * the latter case, only the keys in the given object matter; the values are + * ignored. + * + * expect({a: 1, b: 2}).to.have.all.keys('a', 'b'); + * expect(['x', 'y']).to.have.all.keys(0, 1); + * + * expect({a: 1, b: 2}).to.have.all.keys(['a', 'b']); + * expect(['x', 'y']).to.have.all.keys([0, 1]); + * + * expect({a: 1, b: 2}).to.have.all.keys({a: 4, b: 5}); // ignore 4 and 5 + * expect(['x', 'y']).to.have.all.keys({0: 4, 1: 5}); // ignore 4 and 5 + * + * When the target is a map or set, each key must be provided as a separate + * argument. + * + * expect(new Map([['a', 1], ['b', 2]])).to.have.all.keys('a', 'b'); + * expect(new Set(['a', 'b'])).to.have.all.keys('a', 'b'); + * + * Because `.keys` does different things based on the target's type, it's + * important to check the target's type before using `.keys`. See the `.a` doc + * for info on testing a target's type. + * + * expect({a: 1, b: 2}).to.be.an('object').that.has.all.keys('a', 'b'); + * + * By default, strict (`===`) equality is used to compare keys of maps and + * sets. Add `.deep` earlier in the chain to use deep equality instead. See + * the `deep-eql` project page for info on the deep equality algorithm: + * https://github.com/chaijs/deep-eql. + * + * // Target set deeply (but not strictly) has key `{a: 1}` + * expect(new Set([{a: 1}])).to.have.all.deep.keys([{a: 1}]); + * expect(new Set([{a: 1}])).to.not.have.all.keys([{a: 1}]); + * + * By default, the target must have all of the given keys and no more. Add + * `.any` earlier in the chain to only require that the target have at least + * one of the given keys. Also, add `.not` earlier in the chain to negate + * `.keys`. It's often best to add `.any` when negating `.keys`, and to use + * `.all` when asserting `.keys` without negation. + * + * When negating `.keys`, `.any` is preferred because `.not.any.keys` asserts + * exactly what's expected of the output, whereas `.not.all.keys` creates + * uncertain expectations. + * + * // Recommended; asserts that target doesn't have any of the given keys + * expect({a: 1, b: 2}).to.not.have.any.keys('c', 'd'); + * + * // Not recommended; asserts that target doesn't have all of the given + * // keys but may or may not have some of them + * expect({a: 1, b: 2}).to.not.have.all.keys('c', 'd'); + * + * When asserting `.keys` without negation, `.all` is preferred because + * `.all.keys` asserts exactly what's expected of the output, whereas + * `.any.keys` creates uncertain expectations. + * + * // Recommended; asserts that target has all the given keys + * expect({a: 1, b: 2}).to.have.all.keys('a', 'b'); + * + * // Not recommended; asserts that target has at least one of the given + * // keys but may or may not have more of them + * expect({a: 1, b: 2}).to.have.any.keys('a', 'b'); + * + * Note that `.all` is used by default when neither `.all` nor `.any` appear + * earlier in the chain. However, it's often best to add `.all` anyway because + * it improves readability. + * + * // Both assertions are identical + * expect({a: 1, b: 2}).to.have.all.keys('a', 'b'); // Recommended + * expect({a: 1, b: 2}).to.have.keys('a', 'b'); // Not recommended + * + * Add `.include` earlier in the chain to require that the target's keys be a + * superset of the expected keys, rather than identical sets. + * + * // Target object's keys are a superset of ['a', 'b'] but not identical + * expect({a: 1, b: 2, c: 3}).to.include.all.keys('a', 'b'); + * expect({a: 1, b: 2, c: 3}).to.not.have.all.keys('a', 'b'); + * + * However, if `.any` and `.include` are combined, only the `.any` takes + * effect. The `.include` is ignored in this case. + * + * // Both assertions are identical + * expect({a: 1}).to.have.any.keys('a', 'b'); + * expect({a: 1}).to.include.any.keys('a', 'b'); + * + * A custom error message can be given as the second argument to `expect`. + * + * expect({a: 1}, 'nooo why fail??').to.have.key('b'); + * + * The alias `.key` can be used interchangeably with `.keys`. + * + * @name keys + * @alias key + * @param {...String|Array|Object} keys + * @namespace BDD + * @api public + */ + + function assertKeys (keys) { + var obj = flag(this, 'object') + , objType = _.type(obj) + , keysType = _.type(keys) + , ssfi = flag(this, 'ssfi') + , isDeep = flag(this, 'deep') + , str + , deepStr = '' + , actual + , ok = true + , flagMsg = flag(this, 'message'); + + flagMsg = flagMsg ? flagMsg + ': ' : ''; + var mixedArgsMsg = flagMsg + 'when testing keys against an object or an array you must give a single Array|Object|String argument or multiple String arguments'; + + if (objType === 'Map' || objType === 'Set') { + deepStr = isDeep ? 'deeply ' : ''; + actual = []; + + // Map and Set '.keys' aren't supported in IE 11. Therefore, use .forEach. + obj.forEach(function (val, key) { actual.push(key) }); + + if (keysType !== 'Array') { + keys = Array.prototype.slice.call(arguments); + } + } else { + actual = _.getOwnEnumerableProperties(obj); + + switch (keysType) { + case 'Array': + if (arguments.length > 1) { + throw new AssertionError(mixedArgsMsg, undefined, ssfi); + } + break; + case 'Object': + if (arguments.length > 1) { + throw new AssertionError(mixedArgsMsg, undefined, ssfi); + } + keys = Object.keys(keys); + break; + default: + keys = Array.prototype.slice.call(arguments); + } + + // Only stringify non-Symbols because Symbols would become "Symbol()" + keys = keys.map(function (val) { + return typeof val === 'symbol' ? val : String(val); + }); + } + + if (!keys.length) { + throw new AssertionError(flagMsg + 'keys required', undefined, ssfi); + } + + var len = keys.length + , any = flag(this, 'any') + , all = flag(this, 'all') + , expected = keys; + + if (!any && !all) { + all = true; + } + + // Has any + if (any) { + ok = expected.some(function(expectedKey) { + return actual.some(function(actualKey) { + if (isDeep) { + return _.eql(expectedKey, actualKey); + } else { + return expectedKey === actualKey; + } + }); + }); + } + + // Has all + if (all) { + ok = expected.every(function(expectedKey) { + return actual.some(function(actualKey) { + if (isDeep) { + return _.eql(expectedKey, actualKey); + } else { + return expectedKey === actualKey; + } + }); + }); + + if (!flag(this, 'contains')) { + ok = ok && keys.length == actual.length; + } + } + + // Key string + if (len > 1) { + keys = keys.map(function(key) { + return _.inspect(key); + }); + var last = keys.pop(); + if (all) { + str = keys.join(', ') + ', and ' + last; + } + if (any) { + str = keys.join(', ') + ', or ' + last; + } + } else { + str = _.inspect(keys[0]); + } + + // Form + str = (len > 1 ? 'keys ' : 'key ') + str; + + // Have / include + str = (flag(this, 'contains') ? 'contain ' : 'have ') + str; + + // Assertion + this.assert( + ok + , 'expected #{this} to ' + deepStr + str + , 'expected #{this} to not ' + deepStr + str + , expected.slice(0).sort(_.compareByInspect) + , actual.sort(_.compareByInspect) + , true + ); + } + + Assertion.addMethod('keys', assertKeys); + Assertion.addMethod('key', assertKeys); + + /** + * ### .throw([errorLike], [errMsgMatcher], [msg]) + * + * When no arguments are provided, `.throw` invokes the target function and + * asserts that an error is thrown. + * + * var badFn = function () { throw new TypeError('Illegal salmon!'); }; + * + * expect(badFn).to.throw(); + * + * When one argument is provided, and it's an error constructor, `.throw` + * invokes the target function and asserts that an error is thrown that's an + * instance of that error constructor. + * + * var badFn = function () { throw new TypeError('Illegal salmon!'); }; + * + * expect(badFn).to.throw(TypeError); + * + * When one argument is provided, and it's an error instance, `.throw` invokes + * the target function and asserts that an error is thrown that's strictly + * (`===`) equal to that error instance. + * + * var err = new TypeError('Illegal salmon!'); + * var badFn = function () { throw err; }; + * + * expect(badFn).to.throw(err); + * + * When one argument is provided, and it's a string, `.throw` invokes the + * target function and asserts that an error is thrown with a message that + * contains that string. + * + * var badFn = function () { throw new TypeError('Illegal salmon!'); }; + * + * expect(badFn).to.throw('salmon'); + * + * When one argument is provided, and it's a regular expression, `.throw` + * invokes the target function and asserts that an error is thrown with a + * message that matches that regular expression. + * + * var badFn = function () { throw new TypeError('Illegal salmon!'); }; + * + * expect(badFn).to.throw(/salmon/); + * + * When two arguments are provided, and the first is an error instance or + * constructor, and the second is a string or regular expression, `.throw` + * invokes the function and asserts that an error is thrown that fulfills both + * conditions as described above. + * + * var err = new TypeError('Illegal salmon!'); + * var badFn = function () { throw err; }; + * + * expect(badFn).to.throw(TypeError, 'salmon'); + * expect(badFn).to.throw(TypeError, /salmon/); + * expect(badFn).to.throw(err, 'salmon'); + * expect(badFn).to.throw(err, /salmon/); + * + * Add `.not` earlier in the chain to negate `.throw`. + * + * var goodFn = function () {}; + * + * expect(goodFn).to.not.throw(); + * + * However, it's dangerous to negate `.throw` when providing any arguments. + * The problem is that it creates uncertain expectations by asserting that the + * target either doesn't throw an error, or that it throws an error but of a + * different type than the given type, or that it throws an error of the given + * type but with a message that doesn't include the given string. It's often + * best to identify the exact output that's expected, and then write an + * assertion that only accepts that exact output. + * + * When the target isn't expected to throw an error, it's often best to assert + * exactly that. + * + * var goodFn = function () {}; + * + * expect(goodFn).to.not.throw(); // Recommended + * expect(goodFn).to.not.throw(ReferenceError, 'x'); // Not recommended + * + * When the target is expected to throw an error, it's often best to assert + * that the error is of its expected type, and has a message that includes an + * expected string, rather than asserting that it doesn't have one of many + * unexpected types, and doesn't have a message that includes some string. + * + * var badFn = function () { throw new TypeError('Illegal salmon!'); }; + * + * expect(badFn).to.throw(TypeError, 'salmon'); // Recommended + * expect(badFn).to.not.throw(ReferenceError, 'x'); // Not recommended + * + * `.throw` changes the target of any assertions that follow in the chain to + * be the error object that's thrown. + * + * var err = new TypeError('Illegal salmon!'); + * err.code = 42; + * var badFn = function () { throw err; }; + * + * expect(badFn).to.throw(TypeError).with.property('code', 42); + * + * `.throw` accepts an optional `msg` argument which is a custom error message + * to show when the assertion fails. The message can also be given as the + * second argument to `expect`. When not providing two arguments, always use + * the second form. + * + * var goodFn = function () {}; + * + * expect(goodFn).to.throw(TypeError, 'x', 'nooo why fail??'); + * expect(goodFn, 'nooo why fail??').to.throw(); + * + * Due to limitations in ES5, `.throw` may not always work as expected when + * using a transpiler such as Babel or TypeScript. In particular, it may + * produce unexpected results when subclassing the built-in `Error` object and + * then passing the subclassed constructor to `.throw`. See your transpiler's + * docs for details: + * + * - ([Babel](https://babeljs.io/docs/usage/caveats/#classes)) + * - ([TypeScript](https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work)) + * + * Beware of some common mistakes when using the `throw` assertion. One common + * mistake is to accidentally invoke the function yourself instead of letting + * the `throw` assertion invoke the function for you. For example, when + * testing if a function named `fn` throws, provide `fn` instead of `fn()` as + * the target for the assertion. + * + * expect(fn).to.throw(); // Good! Tests `fn` as desired + * expect(fn()).to.throw(); // Bad! Tests result of `fn()`, not `fn` + * + * If you need to assert that your function `fn` throws when passed certain + * arguments, then wrap a call to `fn` inside of another function. + * + * expect(function () { fn(42); }).to.throw(); // Function expression + * expect(() => fn(42)).to.throw(); // ES6 arrow function + * + * Another common mistake is to provide an object method (or any stand-alone + * function that relies on `this`) as the target of the assertion. Doing so is + * problematic because the `this` context will be lost when the function is + * invoked by `.throw`; there's no way for it to know what `this` is supposed + * to be. There are two ways around this problem. One solution is to wrap the + * method or function call inside of another function. Another solution is to + * use `bind`. + * + * expect(function () { cat.meow(); }).to.throw(); // Function expression + * expect(() => cat.meow()).to.throw(); // ES6 arrow function + * expect(cat.meow.bind(cat)).to.throw(); // Bind + * + * Finally, it's worth mentioning that it's a best practice in JavaScript to + * only throw `Error` and derivatives of `Error` such as `ReferenceError`, + * `TypeError`, and user-defined objects that extend `Error`. No other type of + * value will generate a stack trace when initialized. With that said, the + * `throw` assertion does technically support any type of value being thrown, + * not just `Error` and its derivatives. + * + * The aliases `.throws` and `.Throw` can be used interchangeably with + * `.throw`. + * + * @name throw + * @alias throws + * @alias Throw + * @param {Error|ErrorConstructor} errorLike + * @param {String|RegExp} errMsgMatcher error message + * @param {String} msg _optional_ + * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types + * @returns error for chaining (null if no error) + * @namespace BDD + * @api public + */ + + function assertThrows (errorLike, errMsgMatcher, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object') + , ssfi = flag(this, 'ssfi') + , flagMsg = flag(this, 'message') + , negate = flag(this, 'negate') || false; + new Assertion(obj, flagMsg, ssfi, true).is.a('function'); + + if (errorLike instanceof RegExp || typeof errorLike === 'string') { + errMsgMatcher = errorLike; + errorLike = null; + } + + var caughtErr; + try { + obj(); + } catch (err) { + caughtErr = err; + } + + // If we have the negate flag enabled and at least one valid argument it means we do expect an error + // but we want it to match a given set of criteria + var everyArgIsUndefined = errorLike === undefined && errMsgMatcher === undefined; + + // If we've got the negate flag enabled and both args, we should only fail if both aren't compatible + // See Issue #551 and PR #683@GitHub + var everyArgIsDefined = Boolean(errorLike && errMsgMatcher); + var errorLikeFail = false; + var errMsgMatcherFail = false; + + // Checking if error was thrown + if (everyArgIsUndefined || !everyArgIsUndefined && !negate) { + // We need this to display results correctly according to their types + var errorLikeString = 'an error'; + if (errorLike instanceof Error) { + errorLikeString = '#{exp}'; + } else if (errorLike) { + errorLikeString = _.checkError.getConstructorName(errorLike); + } + + this.assert( + caughtErr + , 'expected #{this} to throw ' + errorLikeString + , 'expected #{this} to not throw an error but #{act} was thrown' + , errorLike && errorLike.toString() + , (caughtErr instanceof Error ? + caughtErr.toString() : (typeof caughtErr === 'string' ? caughtErr : caughtErr && + _.checkError.getConstructorName(caughtErr))) + ); + } + + if (errorLike && caughtErr) { + // We should compare instances only if `errorLike` is an instance of `Error` + if (errorLike instanceof Error) { + var isCompatibleInstance = _.checkError.compatibleInstance(caughtErr, errorLike); + + if (isCompatibleInstance === negate) { + // These checks were created to ensure we won't fail too soon when we've got both args and a negate + // See Issue #551 and PR #683@GitHub + if (everyArgIsDefined && negate) { + errorLikeFail = true; + } else { + this.assert( + negate + , 'expected #{this} to throw #{exp} but #{act} was thrown' + , 'expected #{this} to not throw #{exp}' + (caughtErr && !negate ? ' but #{act} was thrown' : '') + , errorLike.toString() + , caughtErr.toString() + ); + } + } + } + + var isCompatibleConstructor = _.checkError.compatibleConstructor(caughtErr, errorLike); + if (isCompatibleConstructor === negate) { + if (everyArgIsDefined && negate) { + errorLikeFail = true; + } else { + this.assert( + negate + , 'expected #{this} to throw #{exp} but #{act} was thrown' + , 'expected #{this} to not throw #{exp}' + (caughtErr ? ' but #{act} was thrown' : '') + , (errorLike instanceof Error ? errorLike.toString() : errorLike && _.checkError.getConstructorName(errorLike)) + , (caughtErr instanceof Error ? caughtErr.toString() : caughtErr && _.checkError.getConstructorName(caughtErr)) + ); + } + } + } + + if (caughtErr && errMsgMatcher !== undefined && errMsgMatcher !== null) { + // Here we check compatible messages + var placeholder = 'including'; + if (errMsgMatcher instanceof RegExp) { + placeholder = 'matching' + } + + var isCompatibleMessage = _.checkError.compatibleMessage(caughtErr, errMsgMatcher); + if (isCompatibleMessage === negate) { + if (everyArgIsDefined && negate) { + errMsgMatcherFail = true; + } else { + this.assert( + negate + , 'expected #{this} to throw error ' + placeholder + ' #{exp} but got #{act}' + , 'expected #{this} to throw error not ' + placeholder + ' #{exp}' + , errMsgMatcher + , _.checkError.getMessage(caughtErr) + ); + } + } + } + + // If both assertions failed and both should've matched we throw an error + if (errorLikeFail && errMsgMatcherFail) { + this.assert( + negate + , 'expected #{this} to throw #{exp} but #{act} was thrown' + , 'expected #{this} to not throw #{exp}' + (caughtErr ? ' but #{act} was thrown' : '') + , (errorLike instanceof Error ? errorLike.toString() : errorLike && _.checkError.getConstructorName(errorLike)) + , (caughtErr instanceof Error ? caughtErr.toString() : caughtErr && _.checkError.getConstructorName(caughtErr)) + ); + } + + flag(this, 'object', caughtErr); + }; + + Assertion.addMethod('throw', assertThrows); + Assertion.addMethod('throws', assertThrows); + Assertion.addMethod('Throw', assertThrows); + + /** + * ### .respondTo(method[, msg]) + * + * When the target is a non-function object, `.respondTo` asserts that the + * target has a method with the given name `method`. The method can be own or + * inherited, and it can be enumerable or non-enumerable. + * + * function Cat () {} + * Cat.prototype.meow = function () {}; + * + * expect(new Cat()).to.respondTo('meow'); + * + * When the target is a function, `.respondTo` asserts that the target's + * `prototype` property has a method with the given name `method`. Again, the + * method can be own or inherited, and it can be enumerable or non-enumerable. + * + * function Cat () {} + * Cat.prototype.meow = function () {}; + * + * expect(Cat).to.respondTo('meow'); + * + * Add `.itself` earlier in the chain to force `.respondTo` to treat the + * target as a non-function object, even if it's a function. Thus, it asserts + * that the target has a method with the given name `method`, rather than + * asserting that the target's `prototype` property has a method with the + * given name `method`. + * + * function Cat () {} + * Cat.prototype.meow = function () {}; + * Cat.hiss = function () {}; + * + * expect(Cat).itself.to.respondTo('hiss').but.not.respondTo('meow'); + * + * When not adding `.itself`, it's important to check the target's type before + * using `.respondTo`. See the `.a` doc for info on checking a target's type. + * + * function Cat () {} + * Cat.prototype.meow = function () {}; + * + * expect(new Cat()).to.be.an('object').that.respondsTo('meow'); + * + * Add `.not` earlier in the chain to negate `.respondTo`. + * + * function Dog () {} + * Dog.prototype.bark = function () {}; + * + * expect(new Dog()).to.not.respondTo('meow'); + * + * `.respondTo` accepts an optional `msg` argument which is a custom error + * message to show when the assertion fails. The message can also be given as + * the second argument to `expect`. + * + * expect({}).to.respondTo('meow', 'nooo why fail??'); + * expect({}, 'nooo why fail??').to.respondTo('meow'); + * + * The alias `.respondsTo` can be used interchangeably with `.respondTo`. + * + * @name respondTo + * @alias respondsTo + * @param {String} method + * @param {String} msg _optional_ + * @namespace BDD + * @api public + */ + + function respondTo (method, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object') + , itself = flag(this, 'itself') + , context = ('function' === typeof obj && !itself) + ? obj.prototype[method] + : obj[method]; + + this.assert( + 'function' === typeof context + , 'expected #{this} to respond to ' + _.inspect(method) + , 'expected #{this} to not respond to ' + _.inspect(method) + ); + } + + Assertion.addMethod('respondTo', respondTo); + Assertion.addMethod('respondsTo', respondTo); + + /** + * ### .itself + * + * Forces all `.respondTo` assertions that follow in the chain to behave as if + * the target is a non-function object, even if it's a function. Thus, it + * causes `.respondTo` to assert that the target has a method with the given + * name, rather than asserting that the target's `prototype` property has a + * method with the given name. + * + * function Cat () {} + * Cat.prototype.meow = function () {}; + * Cat.hiss = function () {}; + * + * expect(Cat).itself.to.respondTo('hiss').but.not.respondTo('meow'); + * + * @name itself + * @namespace BDD + * @api public + */ + + Assertion.addProperty('itself', function () { + flag(this, 'itself', true); + }); + + /** + * ### .satisfy(matcher[, msg]) + * + * Invokes the given `matcher` function with the target being passed as the + * first argument, and asserts that the value returned is truthy. + * + * expect(1).to.satisfy(function(num) { + * return num > 0; + * }); + * + * Add `.not` earlier in the chain to negate `.satisfy`. + * + * expect(1).to.not.satisfy(function(num) { + * return num > 2; + * }); + * + * `.satisfy` accepts an optional `msg` argument which is a custom error + * message to show when the assertion fails. The message can also be given as + * the second argument to `expect`. + * + * expect(1).to.satisfy(function(num) { + * return num > 2; + * }, 'nooo why fail??'); + * + * expect(1, 'nooo why fail??').to.satisfy(function(num) { + * return num > 2; + * }); + * + * The alias `.satisfies` can be used interchangeably with `.satisfy`. + * + * @name satisfy + * @alias satisfies + * @param {Function} matcher + * @param {String} msg _optional_ + * @namespace BDD + * @api public + */ + + function satisfy (matcher, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + var result = matcher(obj); + this.assert( + result + , 'expected #{this} to satisfy ' + _.objDisplay(matcher) + , 'expected #{this} to not satisfy' + _.objDisplay(matcher) + , flag(this, 'negate') ? false : true + , result + ); + } + + Assertion.addMethod('satisfy', satisfy); + Assertion.addMethod('satisfies', satisfy); + + /** + * ### .closeTo(expected, delta[, msg]) + * + * Asserts that the target is a number that's within a given +/- `delta` range + * of the given number `expected`. However, it's often best to assert that the + * target is equal to its expected value. + * + * // Recommended + * expect(1.5).to.equal(1.5); + * + * // Not recommended + * expect(1.5).to.be.closeTo(1, 0.5); + * expect(1.5).to.be.closeTo(2, 0.5); + * expect(1.5).to.be.closeTo(1, 1); + * + * Add `.not` earlier in the chain to negate `.closeTo`. + * + * expect(1.5).to.equal(1.5); // Recommended + * expect(1.5).to.not.be.closeTo(3, 1); // Not recommended + * + * `.closeTo` accepts an optional `msg` argument which is a custom error + * message to show when the assertion fails. The message can also be given as + * the second argument to `expect`. + * + * expect(1.5).to.be.closeTo(3, 1, 'nooo why fail??'); + * expect(1.5, 'nooo why fail??').to.be.closeTo(3, 1); + * + * The alias `.approximately` can be used interchangeably with `.closeTo`. + * + * @name closeTo + * @alias approximately + * @param {Number} expected + * @param {Number} delta + * @param {String} msg _optional_ + * @namespace BDD + * @api public + */ + + function closeTo(expected, delta, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object') + , flagMsg = flag(this, 'message') + , ssfi = flag(this, 'ssfi'); + + new Assertion(obj, flagMsg, ssfi, true).is.a('number'); + if (typeof expected !== 'number' || typeof delta !== 'number') { + flagMsg = flagMsg ? flagMsg + ': ' : ''; + throw new AssertionError( + flagMsg + 'the arguments to closeTo or approximately must be numbers', + undefined, + ssfi + ); + } + + this.assert( + Math.abs(obj - expected) <= delta + , 'expected #{this} to be close to ' + expected + ' +/- ' + delta + , 'expected #{this} not to be close to ' + expected + ' +/- ' + delta + ); + } + + Assertion.addMethod('closeTo', closeTo); + Assertion.addMethod('approximately', closeTo); + + // Note: Duplicates are ignored if testing for inclusion instead of sameness. + function isSubsetOf(subset, superset, cmp, contains, ordered) { + if (!contains) { + if (subset.length !== superset.length) return false; + superset = superset.slice(); + } + + return subset.every(function(elem, idx) { + if (ordered) return cmp ? cmp(elem, superset[idx]) : elem === superset[idx]; + + if (!cmp) { + var matchIdx = superset.indexOf(elem); + if (matchIdx === -1) return false; + + // Remove match from superset so not counted twice if duplicate in subset. + if (!contains) superset.splice(matchIdx, 1); + return true; + } + + return superset.some(function(elem2, matchIdx) { + if (!cmp(elem, elem2)) return false; + + // Remove match from superset so not counted twice if duplicate in subset. + if (!contains) superset.splice(matchIdx, 1); + return true; + }); + }); + } + + /** + * ### .members(set[, msg]) + * + * Asserts that the target array has the same members as the given array + * `set`. + * + * expect([1, 2, 3]).to.have.members([2, 1, 3]); + * expect([1, 2, 2]).to.have.members([2, 1, 2]); + * + * By default, members are compared using strict (`===`) equality. Add `.deep` + * earlier in the chain to use deep equality instead. See the `deep-eql` + * project page for info on the deep equality algorithm: + * https://github.com/chaijs/deep-eql. + * + * // Target array deeply (but not strictly) has member `{a: 1}` + * expect([{a: 1}]).to.have.deep.members([{a: 1}]); + * expect([{a: 1}]).to.not.have.members([{a: 1}]); + * + * By default, order doesn't matter. Add `.ordered` earlier in the chain to + * require that members appear in the same order. + * + * expect([1, 2, 3]).to.have.ordered.members([1, 2, 3]); + * expect([1, 2, 3]).to.have.members([2, 1, 3]) + * .but.not.ordered.members([2, 1, 3]); + * + * By default, both arrays must be the same size. Add `.include` earlier in + * the chain to require that the target's members be a superset of the + * expected members. Note that duplicates are ignored in the subset when + * `.include` is added. + * + * // Target array is a superset of [1, 2] but not identical + * expect([1, 2, 3]).to.include.members([1, 2]); + * expect([1, 2, 3]).to.not.have.members([1, 2]); + * + * // Duplicates in the subset are ignored + * expect([1, 2, 3]).to.include.members([1, 2, 2, 2]); + * + * `.deep`, `.ordered`, and `.include` can all be combined. However, if + * `.include` and `.ordered` are combined, the ordering begins at the start of + * both arrays. + * + * expect([{a: 1}, {b: 2}, {c: 3}]) + * .to.include.deep.ordered.members([{a: 1}, {b: 2}]) + * .but.not.include.deep.ordered.members([{b: 2}, {c: 3}]); + * + * Add `.not` earlier in the chain to negate `.members`. However, it's + * dangerous to do so. The problem is that it creates uncertain expectations + * by asserting that the target array doesn't have all of the same members as + * the given array `set` but may or may not have some of them. It's often best + * to identify the exact output that's expected, and then write an assertion + * that only accepts that exact output. + * + * expect([1, 2]).to.not.include(3).and.not.include(4); // Recommended + * expect([1, 2]).to.not.have.members([3, 4]); // Not recommended + * + * `.members` accepts an optional `msg` argument which is a custom error + * message to show when the assertion fails. The message can also be given as + * the second argument to `expect`. + * + * expect([1, 2]).to.have.members([1, 2, 3], 'nooo why fail??'); + * expect([1, 2], 'nooo why fail??').to.have.members([1, 2, 3]); + * + * @name members + * @param {Array} set + * @param {String} msg _optional_ + * @namespace BDD + * @api public + */ + + Assertion.addMethod('members', function (subset, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object') + , flagMsg = flag(this, 'message') + , ssfi = flag(this, 'ssfi'); + + new Assertion(obj, flagMsg, ssfi, true).to.be.an('array'); + new Assertion(subset, flagMsg, ssfi, true).to.be.an('array'); + + var contains = flag(this, 'contains'); + var ordered = flag(this, 'ordered'); + + var subject, failMsg, failNegateMsg; + + if (contains) { + subject = ordered ? 'an ordered superset' : 'a superset'; + failMsg = 'expected #{this} to be ' + subject + ' of #{exp}'; + failNegateMsg = 'expected #{this} to not be ' + subject + ' of #{exp}'; + } else { + subject = ordered ? 'ordered members' : 'members'; + failMsg = 'expected #{this} to have the same ' + subject + ' as #{exp}'; + failNegateMsg = 'expected #{this} to not have the same ' + subject + ' as #{exp}'; + } + + var cmp = flag(this, 'deep') ? _.eql : undefined; + + this.assert( + isSubsetOf(subset, obj, cmp, contains, ordered) + , failMsg + , failNegateMsg + , subset + , obj + , true + ); + }); + + /** + * ### .oneOf(list[, msg]) + * + * Asserts that the target is a member of the given array `list`. However, + * it's often best to assert that the target is equal to its expected value. + * + * expect(1).to.equal(1); // Recommended + * expect(1).to.be.oneOf([1, 2, 3]); // Not recommended + * + * Comparisons are performed using strict (`===`) equality. + * + * Add `.not` earlier in the chain to negate `.oneOf`. + * + * expect(1).to.equal(1); // Recommended + * expect(1).to.not.be.oneOf([2, 3, 4]); // Not recommended + * + * `.oneOf` accepts an optional `msg` argument which is a custom error message + * to show when the assertion fails. The message can also be given as the + * second argument to `expect`. + * + * expect(1).to.be.oneOf([2, 3, 4], 'nooo why fail??'); + * expect(1, 'nooo why fail??').to.be.oneOf([2, 3, 4]); + * + * @name oneOf + * @param {Array<*>} list + * @param {String} msg _optional_ + * @namespace BDD + * @api public + */ + + function oneOf (list, msg) { + if (msg) flag(this, 'message', msg); + var expected = flag(this, 'object') + , flagMsg = flag(this, 'message') + , ssfi = flag(this, 'ssfi'); + new Assertion(list, flagMsg, ssfi, true).to.be.an('array'); + + this.assert( + list.indexOf(expected) > -1 + , 'expected #{this} to be one of #{exp}' + , 'expected #{this} to not be one of #{exp}' + , list + , expected + ); + } + + Assertion.addMethod('oneOf', oneOf); + + /** + * ### .change(subject[, prop[, msg]]) + * + * When one argument is provided, `.change` asserts that the given function + * `subject` returns a different value when it's invoked before the target + * function compared to when it's invoked afterward. However, it's often best + * to assert that `subject` is equal to its expected value. + * + * var dots = '' + * , addDot = function () { dots += '.'; } + * , getDots = function () { return dots; }; + * + * // Recommended + * expect(getDots()).to.equal(''); + * addDot(); + * expect(getDots()).to.equal('.'); + * + * // Not recommended + * expect(addDot).to.change(getDots); + * + * When two arguments are provided, `.change` asserts that the value of the + * given object `subject`'s `prop` property is different before invoking the + * target function compared to afterward. + * + * var myObj = {dots: ''} + * , addDot = function () { myObj.dots += '.'; }; + * + * // Recommended + * expect(myObj).to.have.property('dots', ''); + * addDot(); + * expect(myObj).to.have.property('dots', '.'); + * + * // Not recommended + * expect(addDot).to.change(myObj, 'dots'); + * + * Strict (`===`) equality is used to compare before and after values. + * + * Add `.not` earlier in the chain to negate `.change`. + * + * var dots = '' + * , noop = function () {} + * , getDots = function () { return dots; }; + * + * expect(noop).to.not.change(getDots); + * + * var myObj = {dots: ''} + * , noop = function () {}; + * + * expect(noop).to.not.change(myObj, 'dots'); + * + * `.change` accepts an optional `msg` argument which is a custom error + * message to show when the assertion fails. The message can also be given as + * the second argument to `expect`. When not providing two arguments, always + * use the second form. + * + * var myObj = {dots: ''} + * , addDot = function () { myObj.dots += '.'; }; + * + * expect(addDot).to.not.change(myObj, 'dots', 'nooo why fail??'); + * + * var dots = '' + * , addDot = function () { dots += '.'; } + * , getDots = function () { return dots; }; + * + * expect(addDot, 'nooo why fail??').to.not.change(getDots); + * + * `.change` also causes all `.by` assertions that follow in the chain to + * assert how much a numeric subject was increased or decreased by. However, + * it's dangerous to use `.change.by`. The problem is that it creates + * uncertain expectations by asserting that the subject either increases by + * the given delta, or that it decreases by the given delta. It's often best + * to identify the exact output that's expected, and then write an assertion + * that only accepts that exact output. + * + * var myObj = {val: 1} + * , addTwo = function () { myObj.val += 2; } + * , subtractTwo = function () { myObj.val -= 2; }; + * + * expect(addTwo).to.increase(myObj, 'val').by(2); // Recommended + * expect(addTwo).to.change(myObj, 'val').by(2); // Not recommended + * + * expect(subtractTwo).to.decrease(myObj, 'val').by(2); // Recommended + * expect(subtractTwo).to.change(myObj, 'val').by(2); // Not recommended + * + * The alias `.changes` can be used interchangeably with `.change`. + * + * @name change + * @alias changes + * @param {String} subject + * @param {String} prop name _optional_ + * @param {String} msg _optional_ + * @namespace BDD + * @api public + */ + + function assertChanges (subject, prop, msg) { + if (msg) flag(this, 'message', msg); + var fn = flag(this, 'object') + , flagMsg = flag(this, 'message') + , ssfi = flag(this, 'ssfi'); + new Assertion(fn, flagMsg, ssfi, true).is.a('function'); + + var initial; + if (!prop) { + new Assertion(subject, flagMsg, ssfi, true).is.a('function'); + initial = subject(); + } else { + new Assertion(subject, flagMsg, ssfi, true).to.have.property(prop); + initial = subject[prop]; + } + + fn(); + + var final = prop === undefined || prop === null ? subject() : subject[prop]; + var msgObj = prop === undefined || prop === null ? initial : '.' + prop; + + // This gets flagged because of the .by(delta) assertion + flag(this, 'deltaMsgObj', msgObj); + flag(this, 'initialDeltaValue', initial); + flag(this, 'finalDeltaValue', final); + flag(this, 'deltaBehavior', 'change'); + flag(this, 'realDelta', final !== initial); + + this.assert( + initial !== final + , 'expected ' + msgObj + ' to change' + , 'expected ' + msgObj + ' to not change' + ); + } + + Assertion.addMethod('change', assertChanges); + Assertion.addMethod('changes', assertChanges); + + /** + * ### .increase(subject[, prop[, msg]]) + * + * When one argument is provided, `.increase` asserts that the given function + * `subject` returns a greater number when it's invoked after invoking the + * target function compared to when it's invoked beforehand. `.increase` also + * causes all `.by` assertions that follow in the chain to assert how much + * greater of a number is returned. It's often best to assert that the return + * value increased by the expected amount, rather than asserting it increased + * by any amount. + * + * var val = 1 + * , addTwo = function () { val += 2; } + * , getVal = function () { return val; }; + * + * expect(addTwo).to.increase(getVal).by(2); // Recommended + * expect(addTwo).to.increase(getVal); // Not recommended + * + * When two arguments are provided, `.increase` asserts that the value of the + * given object `subject`'s `prop` property is greater after invoking the + * target function compared to beforehand. + * + * var myObj = {val: 1} + * , addTwo = function () { myObj.val += 2; }; + * + * expect(addTwo).to.increase(myObj, 'val').by(2); // Recommended + * expect(addTwo).to.increase(myObj, 'val'); // Not recommended + * + * Add `.not` earlier in the chain to negate `.increase`. However, it's + * dangerous to do so. The problem is that it creates uncertain expectations + * by asserting that the subject either decreases, or that it stays the same. + * It's often best to identify the exact output that's expected, and then + * write an assertion that only accepts that exact output. + * + * When the subject is expected to decrease, it's often best to assert that it + * decreased by the expected amount. + * + * var myObj = {val: 1} + * , subtractTwo = function () { myObj.val -= 2; }; + * + * expect(subtractTwo).to.decrease(myObj, 'val').by(2); // Recommended + * expect(subtractTwo).to.not.increase(myObj, 'val'); // Not recommended + * + * When the subject is expected to stay the same, it's often best to assert + * exactly that. + * + * var myObj = {val: 1} + * , noop = function () {}; + * + * expect(noop).to.not.change(myObj, 'val'); // Recommended + * expect(noop).to.not.increase(myObj, 'val'); // Not recommended + * + * `.increase` accepts an optional `msg` argument which is a custom error + * message to show when the assertion fails. The message can also be given as + * the second argument to `expect`. When not providing two arguments, always + * use the second form. + * + * var myObj = {val: 1} + * , noop = function () {}; + * + * expect(noop).to.increase(myObj, 'val', 'nooo why fail??'); + * + * var val = 1 + * , noop = function () {} + * , getVal = function () { return val; }; + * + * expect(noop, 'nooo why fail??').to.increase(getVal); + * + * The alias `.increases` can be used interchangeably with `.increase`. + * + * @name increase + * @alias increases + * @param {String|Function} subject + * @param {String} prop name _optional_ + * @param {String} msg _optional_ + * @namespace BDD + * @api public + */ + + function assertIncreases (subject, prop, msg) { + if (msg) flag(this, 'message', msg); + var fn = flag(this, 'object') + , flagMsg = flag(this, 'message') + , ssfi = flag(this, 'ssfi'); + new Assertion(fn, flagMsg, ssfi, true).is.a('function'); + + var initial; + if (!prop) { + new Assertion(subject, flagMsg, ssfi, true).is.a('function'); + initial = subject(); + } else { + new Assertion(subject, flagMsg, ssfi, true).to.have.property(prop); + initial = subject[prop]; + } + + // Make sure that the target is a number + new Assertion(initial, flagMsg, ssfi, true).is.a('number'); + + fn(); + + var final = prop === undefined || prop === null ? subject() : subject[prop]; + var msgObj = prop === undefined || prop === null ? initial : '.' + prop; + + flag(this, 'deltaMsgObj', msgObj); + flag(this, 'initialDeltaValue', initial); + flag(this, 'finalDeltaValue', final); + flag(this, 'deltaBehavior', 'increase'); + flag(this, 'realDelta', final - initial); + + this.assert( + final - initial > 0 + , 'expected ' + msgObj + ' to increase' + , 'expected ' + msgObj + ' to not increase' + ); + } + + Assertion.addMethod('increase', assertIncreases); + Assertion.addMethod('increases', assertIncreases); + + /** + * ### .decrease(subject[, prop[, msg]]) + * + * When one argument is provided, `.decrease` asserts that the given function + * `subject` returns a lesser number when it's invoked after invoking the + * target function compared to when it's invoked beforehand. `.decrease` also + * causes all `.by` assertions that follow in the chain to assert how much + * lesser of a number is returned. It's often best to assert that the return + * value decreased by the expected amount, rather than asserting it decreased + * by any amount. + * + * var val = 1 + * , subtractTwo = function () { val -= 2; } + * , getVal = function () { return val; }; + * + * expect(subtractTwo).to.decrease(getVal).by(2); // Recommended + * expect(subtractTwo).to.decrease(getVal); // Not recommended + * + * When two arguments are provided, `.decrease` asserts that the value of the + * given object `subject`'s `prop` property is lesser after invoking the + * target function compared to beforehand. + * + * var myObj = {val: 1} + * , subtractTwo = function () { myObj.val -= 2; }; + * + * expect(subtractTwo).to.decrease(myObj, 'val').by(2); // Recommended + * expect(subtractTwo).to.decrease(myObj, 'val'); // Not recommended + * + * Add `.not` earlier in the chain to negate `.decrease`. However, it's + * dangerous to do so. The problem is that it creates uncertain expectations + * by asserting that the subject either increases, or that it stays the same. + * It's often best to identify the exact output that's expected, and then + * write an assertion that only accepts that exact output. + * + * When the subject is expected to increase, it's often best to assert that it + * increased by the expected amount. + * + * var myObj = {val: 1} + * , addTwo = function () { myObj.val += 2; }; + * + * expect(addTwo).to.increase(myObj, 'val').by(2); // Recommended + * expect(addTwo).to.not.decrease(myObj, 'val'); // Not recommended + * + * When the subject is expected to stay the same, it's often best to assert + * exactly that. + * + * var myObj = {val: 1} + * , noop = function () {}; + * + * expect(noop).to.not.change(myObj, 'val'); // Recommended + * expect(noop).to.not.decrease(myObj, 'val'); // Not recommended + * + * `.decrease` accepts an optional `msg` argument which is a custom error + * message to show when the assertion fails. The message can also be given as + * the second argument to `expect`. When not providing two arguments, always + * use the second form. + * + * var myObj = {val: 1} + * , noop = function () {}; + * + * expect(noop).to.decrease(myObj, 'val', 'nooo why fail??'); + * + * var val = 1 + * , noop = function () {} + * , getVal = function () { return val; }; + * + * expect(noop, 'nooo why fail??').to.decrease(getVal); + * + * The alias `.decreases` can be used interchangeably with `.decrease`. + * + * @name decrease + * @alias decreases + * @param {String|Function} subject + * @param {String} prop name _optional_ + * @param {String} msg _optional_ + * @namespace BDD + * @api public + */ + + function assertDecreases (subject, prop, msg) { + if (msg) flag(this, 'message', msg); + var fn = flag(this, 'object') + , flagMsg = flag(this, 'message') + , ssfi = flag(this, 'ssfi'); + new Assertion(fn, flagMsg, ssfi, true).is.a('function'); + + var initial; + if (!prop) { + new Assertion(subject, flagMsg, ssfi, true).is.a('function'); + initial = subject(); + } else { + new Assertion(subject, flagMsg, ssfi, true).to.have.property(prop); + initial = subject[prop]; + } + + // Make sure that the target is a number + new Assertion(initial, flagMsg, ssfi, true).is.a('number'); + + fn(); + + var final = prop === undefined || prop === null ? subject() : subject[prop]; + var msgObj = prop === undefined || prop === null ? initial : '.' + prop; + + flag(this, 'deltaMsgObj', msgObj); + flag(this, 'initialDeltaValue', initial); + flag(this, 'finalDeltaValue', final); + flag(this, 'deltaBehavior', 'decrease'); + flag(this, 'realDelta', initial - final); + + this.assert( + final - initial < 0 + , 'expected ' + msgObj + ' to decrease' + , 'expected ' + msgObj + ' to not decrease' + ); + } + + Assertion.addMethod('decrease', assertDecreases); + Assertion.addMethod('decreases', assertDecreases); + + /** + * ### .by(delta[, msg]) + * + * When following an `.increase` assertion in the chain, `.by` asserts that + * the subject of the `.increase` assertion increased by the given `delta`. + * + * var myObj = {val: 1} + * , addTwo = function () { myObj.val += 2; }; + * + * expect(addTwo).to.increase(myObj, 'val').by(2); + * + * When following a `.decrease` assertion in the chain, `.by` asserts that the + * subject of the `.decrease` assertion decreased by the given `delta`. + * + * var myObj = {val: 1} + * , subtractTwo = function () { myObj.val -= 2; }; + * + * expect(subtractTwo).to.decrease(myObj, 'val').by(2); + * + * When following a `.change` assertion in the chain, `.by` asserts that the + * subject of the `.change` assertion either increased or decreased by the + * given `delta`. However, it's dangerous to use `.change.by`. The problem is + * that it creates uncertain expectations. It's often best to identify the + * exact output that's expected, and then write an assertion that only accepts + * that exact output. + * + * var myObj = {val: 1} + * , addTwo = function () { myObj.val += 2; } + * , subtractTwo = function () { myObj.val -= 2; }; + * + * expect(addTwo).to.increase(myObj, 'val').by(2); // Recommended + * expect(addTwo).to.change(myObj, 'val').by(2); // Not recommended + * + * expect(subtractTwo).to.decrease(myObj, 'val').by(2); // Recommended + * expect(subtractTwo).to.change(myObj, 'val').by(2); // Not recommended + * + * Add `.not` earlier in the chain to negate `.by`. However, it's often best + * to assert that the subject changed by its expected delta, rather than + * asserting that it didn't change by one of countless unexpected deltas. + * + * var myObj = {val: 1} + * , addTwo = function () { myObj.val += 2; }; + * + * // Recommended + * expect(addTwo).to.increase(myObj, 'val').by(2); + * + * // Not recommended + * expect(addTwo).to.increase(myObj, 'val').but.not.by(3); + * + * `.by` accepts an optional `msg` argument which is a custom error message to + * show when the assertion fails. The message can also be given as the second + * argument to `expect`. + * + * var myObj = {val: 1} + * , addTwo = function () { myObj.val += 2; }; + * + * expect(addTwo).to.increase(myObj, 'val').by(3, 'nooo why fail??'); + * expect(addTwo, 'nooo why fail??').to.increase(myObj, 'val').by(3); + * + * @name by + * @param {Number} delta + * @param {String} msg _optional_ + * @namespace BDD + * @api public + */ + + function assertDelta(delta, msg) { + if (msg) flag(this, 'message', msg); + + var msgObj = flag(this, 'deltaMsgObj'); + var initial = flag(this, 'initialDeltaValue'); + var final = flag(this, 'finalDeltaValue'); + var behavior = flag(this, 'deltaBehavior'); + var realDelta = flag(this, 'realDelta'); + + var expression; + if (behavior === 'change') { + expression = Math.abs(final - initial) === Math.abs(delta); + } else { + expression = realDelta === Math.abs(delta); + } + + this.assert( + expression + , 'expected ' + msgObj + ' to ' + behavior + ' by ' + delta + , 'expected ' + msgObj + ' to not ' + behavior + ' by ' + delta + ); + } + + Assertion.addMethod('by', assertDelta); + + /** + * ### .extensible + * + * Asserts that the target is extensible, which means that new properties can + * be added to it. Primitives are never extensible. + * + * expect({a: 1}).to.be.extensible; + * + * Add `.not` earlier in the chain to negate `.extensible`. + * + * var nonExtensibleObject = Object.preventExtensions({}) + * , sealedObject = Object.seal({}) + * , frozenObject = Object.freeze({}); + * + * expect(nonExtensibleObject).to.not.be.extensible; + * expect(sealedObject).to.not.be.extensible; + * expect(frozenObject).to.not.be.extensible; + * expect(1).to.not.be.extensible; + * + * A custom error message can be given as the second argument to `expect`. + * + * expect(1, 'nooo why fail??').to.be.extensible; + * + * @name extensible + * @namespace BDD + * @api public + */ + + Assertion.addProperty('extensible', function() { + var obj = flag(this, 'object'); + + // In ES5, if the argument to this method is a primitive, then it will cause a TypeError. + // In ES6, a non-object argument will be treated as if it was a non-extensible ordinary object, simply return false. + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isExtensible + // The following provides ES6 behavior for ES5 environments. + + var isExtensible = obj === Object(obj) && Object.isExtensible(obj); + + this.assert( + isExtensible + , 'expected #{this} to be extensible' + , 'expected #{this} to not be extensible' + ); + }); + + /** + * ### .sealed + * + * Asserts that the target is sealed, which means that new properties can't be + * added to it, and its existing properties can't be reconfigured or deleted. + * However, it's possible that its existing properties can still be reassigned + * to different values. Primitives are always sealed. + * + * var sealedObject = Object.seal({}); + * var frozenObject = Object.freeze({}); + * + * expect(sealedObject).to.be.sealed; + * expect(frozenObject).to.be.sealed; + * expect(1).to.be.sealed; + * + * Add `.not` earlier in the chain to negate `.sealed`. + * + * expect({a: 1}).to.not.be.sealed; + * + * A custom error message can be given as the second argument to `expect`. + * + * expect({a: 1}, 'nooo why fail??').to.be.sealed; + * + * @name sealed + * @namespace BDD + * @api public + */ + + Assertion.addProperty('sealed', function() { + var obj = flag(this, 'object'); + + // In ES5, if the argument to this method is a primitive, then it will cause a TypeError. + // In ES6, a non-object argument will be treated as if it was a sealed ordinary object, simply return true. + // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isSealed + // The following provides ES6 behavior for ES5 environments. + + var isSealed = obj === Object(obj) ? Object.isSealed(obj) : true; + + this.assert( + isSealed + , 'expected #{this} to be sealed' + , 'expected #{this} to not be sealed' + ); + }); + + /** + * ### .frozen + * + * Asserts that the target is frozen, which means that new properties can't be + * added to it, and its existing properties can't be reassigned to different + * values, reconfigured, or deleted. Primitives are always frozen. + * + * var frozenObject = Object.freeze({}); + * + * expect(frozenObject).to.be.frozen; + * expect(1).to.be.frozen; + * + * Add `.not` earlier in the chain to negate `.frozen`. + * + * expect({a: 1}).to.not.be.frozen; + * + * A custom error message can be given as the second argument to `expect`. + * + * expect({a: 1}, 'nooo why fail??').to.be.frozen; + * + * @name frozen + * @namespace BDD + * @api public + */ + + Assertion.addProperty('frozen', function() { + var obj = flag(this, 'object'); + + // In ES5, if the argument to this method is a primitive, then it will cause a TypeError. + // In ES6, a non-object argument will be treated as if it was a frozen ordinary object, simply return true. + // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isFrozen + // The following provides ES6 behavior for ES5 environments. + + var isFrozen = obj === Object(obj) ? Object.isFrozen(obj) : true; + + this.assert( + isFrozen + , 'expected #{this} to be frozen' + , 'expected #{this} to not be frozen' + ); + }); + + /** + * ### .finite + * + * Asserts that the target is a number, and isn't `NaN` or positive/negative + * `Infinity`. + * + * expect(1).to.be.finite; + * + * Add `.not` earlier in the chain to negate `.finite`. However, it's + * dangerous to do so. The problem is that it creates uncertain expectations + * by asserting that the subject either isn't a number, or that it's `NaN`, or + * that it's positive `Infinity`, or that it's negative `Infinity`. It's often + * best to identify the exact output that's expected, and then write an + * assertion that only accepts that exact output. + * + * When the target isn't expected to be a number, it's often best to assert + * that it's the expected type, rather than asserting that it isn't one of + * many unexpected types. + * + * expect('foo').to.be.a('string'); // Recommended + * expect('foo').to.not.be.finite; // Not recommended + * + * When the target is expected to be `NaN`, it's often best to assert exactly + * that. + * + * expect(NaN).to.be.NaN; // Recommended + * expect(NaN).to.not.be.finite; // Not recommended + * + * When the target is expected to be positive infinity, it's often best to + * assert exactly that. + * + * expect(Infinity).to.equal(Infinity); // Recommended + * expect(Infinity).to.not.be.finite; // Not recommended + * + * When the target is expected to be negative infinity, it's often best to + * assert exactly that. + * + * expect(-Infinity).to.equal(-Infinity); // Recommended + * expect(-Infinity).to.not.be.finite; // Not recommended + * + * A custom error message can be given as the second argument to `expect`. + * + * expect('foo', 'nooo why fail??').to.be.finite; + * + * @name finite + * @namespace BDD + * @api public + */ + + Assertion.addProperty('finite', function(msg) { + var obj = flag(this, 'object'); + + this.assert( + typeof obj === 'number' && isFinite(obj) + , 'expected #{this} to be a finite number' + , 'expected #{this} to not be a finite number' + ); + }); +}; + +},{}],6:[function(require,module,exports){ +/*! + * chai + * Copyright(c) 2011-2014 Jake Luer + * MIT Licensed + */ + +module.exports = function (chai, util) { + /*! + * Chai dependencies. + */ + + var Assertion = chai.Assertion + , flag = util.flag; + + /*! + * Module export. + */ + + /** + * ### assert(expression, message) + * + * Write your own test expressions. + * + * assert('foo' !== 'bar', 'foo is not bar'); + * assert(Array.isArray([]), 'empty arrays are arrays'); + * + * @param {Mixed} expression to test for truthiness + * @param {String} message to display on error + * @name assert + * @namespace Assert + * @api public + */ + + var assert = chai.assert = function (express, errmsg) { + var test = new Assertion(null, null, chai.assert, true); + test.assert( + express + , errmsg + , '[ negation message unavailable ]' + ); + }; + + /** + * ### .fail([message]) + * ### .fail(actual, expected, [message], [operator]) + * + * Throw a failure. Node.js `assert` module-compatible. + * + * assert.fail(); + * assert.fail("custom error message"); + * assert.fail(1, 2); + * assert.fail(1, 2, "custom error message"); + * assert.fail(1, 2, "custom error message", ">"); + * assert.fail(1, 2, undefined, ">"); + * + * @name fail + * @param {Mixed} actual + * @param {Mixed} expected + * @param {String} message + * @param {String} operator + * @namespace Assert + * @api public + */ + + assert.fail = function (actual, expected, message, operator) { + if (arguments.length < 2) { + // Comply with Node's fail([message]) interface + + message = actual; + actual = undefined; + } + + message = message || 'assert.fail()'; + throw new chai.AssertionError(message, { + actual: actual + , expected: expected + , operator: operator + }, assert.fail); + }; + + /** + * ### .isOk(object, [message]) + * + * Asserts that `object` is truthy. + * + * assert.isOk('everything', 'everything is ok'); + * assert.isOk(false, 'this will fail'); + * + * @name isOk + * @alias ok + * @param {Mixed} object to test + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.isOk = function (val, msg) { + new Assertion(val, msg, assert.isOk, true).is.ok; + }; + + /** + * ### .isNotOk(object, [message]) + * + * Asserts that `object` is falsy. + * + * assert.isNotOk('everything', 'this will fail'); + * assert.isNotOk(false, 'this will pass'); + * + * @name isNotOk + * @alias notOk + * @param {Mixed} object to test + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.isNotOk = function (val, msg) { + new Assertion(val, msg, assert.isNotOk, true).is.not.ok; + }; + + /** + * ### .equal(actual, expected, [message]) + * + * Asserts non-strict equality (`==`) of `actual` and `expected`. + * + * assert.equal(3, '3', '== coerces values to strings'); + * + * @name equal + * @param {Mixed} actual + * @param {Mixed} expected + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.equal = function (act, exp, msg) { + var test = new Assertion(act, msg, assert.equal, true); + + test.assert( + exp == flag(test, 'object') + , 'expected #{this} to equal #{exp}' + , 'expected #{this} to not equal #{act}' + , exp + , act + , true + ); + }; + + /** + * ### .notEqual(actual, expected, [message]) + * + * Asserts non-strict inequality (`!=`) of `actual` and `expected`. + * + * assert.notEqual(3, 4, 'these numbers are not equal'); + * + * @name notEqual + * @param {Mixed} actual + * @param {Mixed} expected + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.notEqual = function (act, exp, msg) { + var test = new Assertion(act, msg, assert.notEqual, true); + + test.assert( + exp != flag(test, 'object') + , 'expected #{this} to not equal #{exp}' + , 'expected #{this} to equal #{act}' + , exp + , act + , true + ); + }; + + /** + * ### .strictEqual(actual, expected, [message]) + * + * Asserts strict equality (`===`) of `actual` and `expected`. + * + * assert.strictEqual(true, true, 'these booleans are strictly equal'); + * + * @name strictEqual + * @param {Mixed} actual + * @param {Mixed} expected + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.strictEqual = function (act, exp, msg) { + new Assertion(act, msg, assert.strictEqual, true).to.equal(exp); + }; + + /** + * ### .notStrictEqual(actual, expected, [message]) + * + * Asserts strict inequality (`!==`) of `actual` and `expected`. + * + * assert.notStrictEqual(3, '3', 'no coercion for strict equality'); + * + * @name notStrictEqual + * @param {Mixed} actual + * @param {Mixed} expected + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.notStrictEqual = function (act, exp, msg) { + new Assertion(act, msg, assert.notStrictEqual, true).to.not.equal(exp); + }; + + /** + * ### .deepEqual(actual, expected, [message]) + * + * Asserts that `actual` is deeply equal to `expected`. + * + * assert.deepEqual({ tea: 'green' }, { tea: 'green' }); + * + * @name deepEqual + * @param {Mixed} actual + * @param {Mixed} expected + * @param {String} message + * @alias deepStrictEqual + * @namespace Assert + * @api public + */ + + assert.deepEqual = assert.deepStrictEqual = function (act, exp, msg) { + new Assertion(act, msg, assert.deepEqual, true).to.eql(exp); + }; + + /** + * ### .notDeepEqual(actual, expected, [message]) + * + * Assert that `actual` is not deeply equal to `expected`. + * + * assert.notDeepEqual({ tea: 'green' }, { tea: 'jasmine' }); + * + * @name notDeepEqual + * @param {Mixed} actual + * @param {Mixed} expected + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.notDeepEqual = function (act, exp, msg) { + new Assertion(act, msg, assert.notDeepEqual, true).to.not.eql(exp); + }; + + /** + * ### .isAbove(valueToCheck, valueToBeAbove, [message]) + * + * Asserts `valueToCheck` is strictly greater than (>) `valueToBeAbove`. + * + * assert.isAbove(5, 2, '5 is strictly greater than 2'); + * + * @name isAbove + * @param {Mixed} valueToCheck + * @param {Mixed} valueToBeAbove + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.isAbove = function (val, abv, msg) { + new Assertion(val, msg, assert.isAbove, true).to.be.above(abv); + }; + + /** + * ### .isAtLeast(valueToCheck, valueToBeAtLeast, [message]) + * + * Asserts `valueToCheck` is greater than or equal to (>=) `valueToBeAtLeast`. + * + * assert.isAtLeast(5, 2, '5 is greater or equal to 2'); + * assert.isAtLeast(3, 3, '3 is greater or equal to 3'); + * + * @name isAtLeast + * @param {Mixed} valueToCheck + * @param {Mixed} valueToBeAtLeast + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.isAtLeast = function (val, atlst, msg) { + new Assertion(val, msg, assert.isAtLeast, true).to.be.least(atlst); + }; + + /** + * ### .isBelow(valueToCheck, valueToBeBelow, [message]) + * + * Asserts `valueToCheck` is strictly less than (<) `valueToBeBelow`. + * + * assert.isBelow(3, 6, '3 is strictly less than 6'); + * + * @name isBelow + * @param {Mixed} valueToCheck + * @param {Mixed} valueToBeBelow + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.isBelow = function (val, blw, msg) { + new Assertion(val, msg, assert.isBelow, true).to.be.below(blw); + }; + + /** + * ### .isAtMost(valueToCheck, valueToBeAtMost, [message]) + * + * Asserts `valueToCheck` is less than or equal to (<=) `valueToBeAtMost`. + * + * assert.isAtMost(3, 6, '3 is less than or equal to 6'); + * assert.isAtMost(4, 4, '4 is less than or equal to 4'); + * + * @name isAtMost + * @param {Mixed} valueToCheck + * @param {Mixed} valueToBeAtMost + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.isAtMost = function (val, atmst, msg) { + new Assertion(val, msg, assert.isAtMost, true).to.be.most(atmst); + }; + + /** + * ### .isTrue(value, [message]) + * + * Asserts that `value` is true. + * + * var teaServed = true; + * assert.isTrue(teaServed, 'the tea has been served'); + * + * @name isTrue + * @param {Mixed} value + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.isTrue = function (val, msg) { + new Assertion(val, msg, assert.isTrue, true).is['true']; + }; + + /** + * ### .isNotTrue(value, [message]) + * + * Asserts that `value` is not true. + * + * var tea = 'tasty chai'; + * assert.isNotTrue(tea, 'great, time for tea!'); + * + * @name isNotTrue + * @param {Mixed} value + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.isNotTrue = function (val, msg) { + new Assertion(val, msg, assert.isNotTrue, true).to.not.equal(true); + }; + + /** + * ### .isFalse(value, [message]) + * + * Asserts that `value` is false. + * + * var teaServed = false; + * assert.isFalse(teaServed, 'no tea yet? hmm...'); + * + * @name isFalse + * @param {Mixed} value + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.isFalse = function (val, msg) { + new Assertion(val, msg, assert.isFalse, true).is['false']; + }; + + /** + * ### .isNotFalse(value, [message]) + * + * Asserts that `value` is not false. + * + * var tea = 'tasty chai'; + * assert.isNotFalse(tea, 'great, time for tea!'); + * + * @name isNotFalse + * @param {Mixed} value + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.isNotFalse = function (val, msg) { + new Assertion(val, msg, assert.isNotFalse, true).to.not.equal(false); + }; + + /** + * ### .isNull(value, [message]) + * + * Asserts that `value` is null. + * + * assert.isNull(err, 'there was no error'); + * + * @name isNull + * @param {Mixed} value + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.isNull = function (val, msg) { + new Assertion(val, msg, assert.isNull, true).to.equal(null); + }; + + /** + * ### .isNotNull(value, [message]) + * + * Asserts that `value` is not null. + * + * var tea = 'tasty chai'; + * assert.isNotNull(tea, 'great, time for tea!'); + * + * @name isNotNull + * @param {Mixed} value + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.isNotNull = function (val, msg) { + new Assertion(val, msg, assert.isNotNull, true).to.not.equal(null); + }; + + /** + * ### .isNaN + * + * Asserts that value is NaN. + * + * assert.isNaN(NaN, 'NaN is NaN'); + * + * @name isNaN + * @param {Mixed} value + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.isNaN = function (val, msg) { + new Assertion(val, msg, assert.isNaN, true).to.be.NaN; + }; + + /** + * ### .isNotNaN + * + * Asserts that value is not NaN. + * + * assert.isNotNaN(4, '4 is not NaN'); + * + * @name isNotNaN + * @param {Mixed} value + * @param {String} message + * @namespace Assert + * @api public + */ + assert.isNotNaN = function (val, msg) { + new Assertion(val, msg, assert.isNotNaN, true).not.to.be.NaN; + }; + + /** + * ### .exists + * + * Asserts that the target is neither `null` nor `undefined`. + * + * var foo = 'hi'; + * + * assert.exists(foo, 'foo is neither `null` nor `undefined`'); + * + * @name exists + * @param {Mixed} value + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.exists = function (val, msg) { + new Assertion(val, msg, assert.exists, true).to.exist; + }; + + /** + * ### .notExists + * + * Asserts that the target is either `null` or `undefined`. + * + * var bar = null + * , baz; + * + * assert.notExists(bar); + * assert.notExists(baz, 'baz is either null or undefined'); + * + * @name notExists + * @param {Mixed} value + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.notExists = function (val, msg) { + new Assertion(val, msg, assert.notExists, true).to.not.exist; + }; + + /** + * ### .isUndefined(value, [message]) + * + * Asserts that `value` is `undefined`. + * + * var tea; + * assert.isUndefined(tea, 'no tea defined'); + * + * @name isUndefined + * @param {Mixed} value + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.isUndefined = function (val, msg) { + new Assertion(val, msg, assert.isUndefined, true).to.equal(undefined); + }; + + /** + * ### .isDefined(value, [message]) + * + * Asserts that `value` is not `undefined`. + * + * var tea = 'cup of chai'; + * assert.isDefined(tea, 'tea has been defined'); + * + * @name isDefined + * @param {Mixed} value + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.isDefined = function (val, msg) { + new Assertion(val, msg, assert.isDefined, true).to.not.equal(undefined); + }; + + /** + * ### .isFunction(value, [message]) + * + * Asserts that `value` is a function. + * + * function serveTea() { return 'cup of tea'; }; + * assert.isFunction(serveTea, 'great, we can have tea now'); + * + * @name isFunction + * @param {Mixed} value + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.isFunction = function (val, msg) { + new Assertion(val, msg, assert.isFunction, true).to.be.a('function'); + }; + + /** + * ### .isNotFunction(value, [message]) + * + * Asserts that `value` is _not_ a function. + * + * var serveTea = [ 'heat', 'pour', 'sip' ]; + * assert.isNotFunction(serveTea, 'great, we have listed the steps'); + * + * @name isNotFunction + * @param {Mixed} value + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.isNotFunction = function (val, msg) { + new Assertion(val, msg, assert.isNotFunction, true).to.not.be.a('function'); + }; + + /** + * ### .isObject(value, [message]) + * + * Asserts that `value` is an object of type 'Object' (as revealed by `Object.prototype.toString`). + * _The assertion does not match subclassed objects._ + * + * var selection = { name: 'Chai', serve: 'with spices' }; + * assert.isObject(selection, 'tea selection is an object'); + * + * @name isObject + * @param {Mixed} value + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.isObject = function (val, msg) { + new Assertion(val, msg, assert.isObject, true).to.be.a('object'); + }; + + /** + * ### .isNotObject(value, [message]) + * + * Asserts that `value` is _not_ an object of type 'Object' (as revealed by `Object.prototype.toString`). + * + * var selection = 'chai' + * assert.isNotObject(selection, 'tea selection is not an object'); + * assert.isNotObject(null, 'null is not an object'); + * + * @name isNotObject + * @param {Mixed} value + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.isNotObject = function (val, msg) { + new Assertion(val, msg, assert.isNotObject, true).to.not.be.a('object'); + }; + + /** + * ### .isArray(value, [message]) + * + * Asserts that `value` is an array. + * + * var menu = [ 'green', 'chai', 'oolong' ]; + * assert.isArray(menu, 'what kind of tea do we want?'); + * + * @name isArray + * @param {Mixed} value + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.isArray = function (val, msg) { + new Assertion(val, msg, assert.isArray, true).to.be.an('array'); + }; + + /** + * ### .isNotArray(value, [message]) + * + * Asserts that `value` is _not_ an array. + * + * var menu = 'green|chai|oolong'; + * assert.isNotArray(menu, 'what kind of tea do we want?'); + * + * @name isNotArray + * @param {Mixed} value + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.isNotArray = function (val, msg) { + new Assertion(val, msg, assert.isNotArray, true).to.not.be.an('array'); + }; + + /** + * ### .isString(value, [message]) + * + * Asserts that `value` is a string. + * + * var teaOrder = 'chai'; + * assert.isString(teaOrder, 'order placed'); + * + * @name isString + * @param {Mixed} value + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.isString = function (val, msg) { + new Assertion(val, msg, assert.isString, true).to.be.a('string'); + }; + + /** + * ### .isNotString(value, [message]) + * + * Asserts that `value` is _not_ a string. + * + * var teaOrder = 4; + * assert.isNotString(teaOrder, 'order placed'); + * + * @name isNotString + * @param {Mixed} value + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.isNotString = function (val, msg) { + new Assertion(val, msg, assert.isNotString, true).to.not.be.a('string'); + }; + + /** + * ### .isNumber(value, [message]) + * + * Asserts that `value` is a number. + * + * var cups = 2; + * assert.isNumber(cups, 'how many cups'); + * + * @name isNumber + * @param {Number} value + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.isNumber = function (val, msg) { + new Assertion(val, msg, assert.isNumber, true).to.be.a('number'); + }; + + /** + * ### .isNotNumber(value, [message]) + * + * Asserts that `value` is _not_ a number. + * + * var cups = '2 cups please'; + * assert.isNotNumber(cups, 'how many cups'); + * + * @name isNotNumber + * @param {Mixed} value + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.isNotNumber = function (val, msg) { + new Assertion(val, msg, assert.isNotNumber, true).to.not.be.a('number'); + }; + + /** + * ### .isFinite(value, [message]) + * + * Asserts that `value` is a finite number. Unlike `.isNumber`, this will fail for `NaN` and `Infinity`. + * + * var cups = 2; + * assert.isFinite(cups, 'how many cups'); + * + * assert.isFinite(NaN); // throws + * + * @name isFinite + * @param {Number} value + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.isFinite = function (val, msg) { + new Assertion(val, msg, assert.isFinite, true).to.be.finite; + }; + + /** + * ### .isBoolean(value, [message]) + * + * Asserts that `value` is a boolean. + * + * var teaReady = true + * , teaServed = false; + * + * assert.isBoolean(teaReady, 'is the tea ready'); + * assert.isBoolean(teaServed, 'has tea been served'); + * + * @name isBoolean + * @param {Mixed} value + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.isBoolean = function (val, msg) { + new Assertion(val, msg, assert.isBoolean, true).to.be.a('boolean'); + }; + + /** + * ### .isNotBoolean(value, [message]) + * + * Asserts that `value` is _not_ a boolean. + * + * var teaReady = 'yep' + * , teaServed = 'nope'; + * + * assert.isNotBoolean(teaReady, 'is the tea ready'); + * assert.isNotBoolean(teaServed, 'has tea been served'); + * + * @name isNotBoolean + * @param {Mixed} value + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.isNotBoolean = function (val, msg) { + new Assertion(val, msg, assert.isNotBoolean, true).to.not.be.a('boolean'); + }; + + /** + * ### .typeOf(value, name, [message]) + * + * Asserts that `value`'s type is `name`, as determined by + * `Object.prototype.toString`. + * + * assert.typeOf({ tea: 'chai' }, 'object', 'we have an object'); + * assert.typeOf(['chai', 'jasmine'], 'array', 'we have an array'); + * assert.typeOf('tea', 'string', 'we have a string'); + * assert.typeOf(/tea/, 'regexp', 'we have a regular expression'); + * assert.typeOf(null, 'null', 'we have a null'); + * assert.typeOf(undefined, 'undefined', 'we have an undefined'); + * + * @name typeOf + * @param {Mixed} value + * @param {String} name + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.typeOf = function (val, type, msg) { + new Assertion(val, msg, assert.typeOf, true).to.be.a(type); + }; + + /** + * ### .notTypeOf(value, name, [message]) + * + * Asserts that `value`'s type is _not_ `name`, as determined by + * `Object.prototype.toString`. + * + * assert.notTypeOf('tea', 'number', 'strings are not numbers'); + * + * @name notTypeOf + * @param {Mixed} value + * @param {String} typeof name + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.notTypeOf = function (val, type, msg) { + new Assertion(val, msg, assert.notTypeOf, true).to.not.be.a(type); + }; + + /** + * ### .instanceOf(object, constructor, [message]) + * + * Asserts that `value` is an instance of `constructor`. + * + * var Tea = function (name) { this.name = name; } + * , chai = new Tea('chai'); + * + * assert.instanceOf(chai, Tea, 'chai is an instance of tea'); + * + * @name instanceOf + * @param {Object} object + * @param {Constructor} constructor + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.instanceOf = function (val, type, msg) { + new Assertion(val, msg, assert.instanceOf, true).to.be.instanceOf(type); + }; + + /** + * ### .notInstanceOf(object, constructor, [message]) + * + * Asserts `value` is not an instance of `constructor`. + * + * var Tea = function (name) { this.name = name; } + * , chai = new String('chai'); + * + * assert.notInstanceOf(chai, Tea, 'chai is not an instance of tea'); + * + * @name notInstanceOf + * @param {Object} object + * @param {Constructor} constructor + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.notInstanceOf = function (val, type, msg) { + new Assertion(val, msg, assert.notInstanceOf, true) + .to.not.be.instanceOf(type); + }; + + /** + * ### .include(haystack, needle, [message]) + * + * Asserts that `haystack` includes `needle`. Can be used to assert the + * inclusion of a value in an array, a substring in a string, or a subset of + * properties in an object. + * + * assert.include([1,2,3], 2, 'array contains value'); + * assert.include('foobar', 'foo', 'string contains substring'); + * assert.include({ foo: 'bar', hello: 'universe' }, { foo: 'bar' }, 'object contains property'); + * + * Strict equality (===) is used. When asserting the inclusion of a value in + * an array, the array is searched for an element that's strictly equal to the + * given value. When asserting a subset of properties in an object, the object + * is searched for the given property keys, checking that each one is present + * and strictly equal to the given property value. For instance: + * + * var obj1 = {a: 1} + * , obj2 = {b: 2}; + * assert.include([obj1, obj2], obj1); + * assert.include({foo: obj1, bar: obj2}, {foo: obj1}); + * assert.include({foo: obj1, bar: obj2}, {foo: obj1, bar: obj2}); + * + * @name include + * @param {Array|String} haystack + * @param {Mixed} needle + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.include = function (exp, inc, msg) { + new Assertion(exp, msg, assert.include, true).include(inc); + }; + + /** + * ### .notInclude(haystack, needle, [message]) + * + * Asserts that `haystack` does not include `needle`. Can be used to assert + * the absence of a value in an array, a substring in a string, or a subset of + * properties in an object. + * + * assert.notInclude([1,2,3], 4, "array doesn't contain value"); + * assert.notInclude('foobar', 'baz', "string doesn't contain substring"); + * assert.notInclude({ foo: 'bar', hello: 'universe' }, { foo: 'baz' }, 'object doesn't contain property'); + * + * Strict equality (===) is used. When asserting the absence of a value in an + * array, the array is searched to confirm the absence of an element that's + * strictly equal to the given value. When asserting a subset of properties in + * an object, the object is searched to confirm that at least one of the given + * property keys is either not present or not strictly equal to the given + * property value. For instance: + * + * var obj1 = {a: 1} + * , obj2 = {b: 2}; + * assert.notInclude([obj1, obj2], {a: 1}); + * assert.notInclude({foo: obj1, bar: obj2}, {foo: {a: 1}}); + * assert.notInclude({foo: obj1, bar: obj2}, {foo: obj1, bar: {b: 2}}); + * + * @name notInclude + * @param {Array|String} haystack + * @param {Mixed} needle + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.notInclude = function (exp, inc, msg) { + new Assertion(exp, msg, assert.notInclude, true).not.include(inc); + }; + + /** + * ### .deepInclude(haystack, needle, [message]) + * + * Asserts that `haystack` includes `needle`. Can be used to assert the + * inclusion of a value in an array or a subset of properties in an object. + * Deep equality is used. + * + * var obj1 = {a: 1} + * , obj2 = {b: 2}; + * assert.deepInclude([obj1, obj2], {a: 1}); + * assert.deepInclude({foo: obj1, bar: obj2}, {foo: {a: 1}}); + * assert.deepInclude({foo: obj1, bar: obj2}, {foo: {a: 1}, bar: {b: 2}}); + * + * @name deepInclude + * @param {Array|String} haystack + * @param {Mixed} needle + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.deepInclude = function (exp, inc, msg) { + new Assertion(exp, msg, assert.deepInclude, true).deep.include(inc); + }; + + /** + * ### .notDeepInclude(haystack, needle, [message]) + * + * Asserts that `haystack` does not include `needle`. Can be used to assert + * the absence of a value in an array or a subset of properties in an object. + * Deep equality is used. + * + * var obj1 = {a: 1} + * , obj2 = {b: 2}; + * assert.notDeepInclude([obj1, obj2], {a: 9}); + * assert.notDeepInclude({foo: obj1, bar: obj2}, {foo: {a: 9}}); + * assert.notDeepInclude({foo: obj1, bar: obj2}, {foo: {a: 1}, bar: {b: 9}}); + * + * @name notDeepInclude + * @param {Array|String} haystack + * @param {Mixed} needle + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.notDeepInclude = function (exp, inc, msg) { + new Assertion(exp, msg, assert.notDeepInclude, true).not.deep.include(inc); + }; + + /** + * ### .nestedInclude(haystack, needle, [message]) + * + * Asserts that 'haystack' includes 'needle'. + * Can be used to assert the inclusion of a subset of properties in an + * object. + * Enables the use of dot- and bracket-notation for referencing nested + * properties. + * '[]' and '.' in property names can be escaped using double backslashes. + * + * assert.nestedInclude({'.a': {'b': 'x'}}, {'\\.a.[b]': 'x'}); + * assert.nestedInclude({'a': {'[b]': 'x'}}, {'a.\\[b\\]': 'x'}); + * + * @name nestedInclude + * @param {Object} haystack + * @param {Object} needle + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.nestedInclude = function (exp, inc, msg) { + new Assertion(exp, msg, assert.nestedInclude, true).nested.include(inc); + }; + + /** + * ### .notNestedInclude(haystack, needle, [message]) + * + * Asserts that 'haystack' does not include 'needle'. + * Can be used to assert the absence of a subset of properties in an + * object. + * Enables the use of dot- and bracket-notation for referencing nested + * properties. + * '[]' and '.' in property names can be escaped using double backslashes. + * + * assert.notNestedInclude({'.a': {'b': 'x'}}, {'\\.a.b': 'y'}); + * assert.notNestedInclude({'a': {'[b]': 'x'}}, {'a.\\[b\\]': 'y'}); + * + * @name notNestedInclude + * @param {Object} haystack + * @param {Object} needle + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.notNestedInclude = function (exp, inc, msg) { + new Assertion(exp, msg, assert.notNestedInclude, true) + .not.nested.include(inc); + }; + + /** + * ### .deepNestedInclude(haystack, needle, [message]) + * + * Asserts that 'haystack' includes 'needle'. + * Can be used to assert the inclusion of a subset of properties in an + * object while checking for deep equality. + * Enables the use of dot- and bracket-notation for referencing nested + * properties. + * '[]' and '.' in property names can be escaped using double backslashes. + * + * assert.deepNestedInclude({a: {b: [{x: 1}]}}, {'a.b[0]': {x: 1}}); + * assert.deepNestedInclude({'.a': {'[b]': {x: 1}}}, {'\\.a.\\[b\\]': {x: 1}}); + * + * @name deepNestedInclude + * @param {Object} haystack + * @param {Object} needle + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.deepNestedInclude = function(exp, inc, msg) { + new Assertion(exp, msg, assert.deepNestedInclude, true) + .deep.nested.include(inc); + }; + + /** + * ### .notDeepNestedInclude(haystack, needle, [message]) + * + * Asserts that 'haystack' does not include 'needle'. + * Can be used to assert the absence of a subset of properties in an + * object while checking for deep equality. + * Enables the use of dot- and bracket-notation for referencing nested + * properties. + * '[]' and '.' in property names can be escaped using double backslashes. + * + * assert.notDeepNestedInclude({a: {b: [{x: 1}]}}, {'a.b[0]': {y: 1}}) + * assert.notDeepNestedInclude({'.a': {'[b]': {x: 1}}}, {'\\.a.\\[b\\]': {y: 2}}); + * + * @name notDeepNestedInclude + * @param {Object} haystack + * @param {Object} needle + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.notDeepNestedInclude = function(exp, inc, msg) { + new Assertion(exp, msg, assert.notDeepNestedInclude, true) + .not.deep.nested.include(inc); + }; + + /** + * ### .ownInclude(haystack, needle, [message]) + * + * Asserts that 'haystack' includes 'needle'. + * Can be used to assert the inclusion of a subset of properties in an + * object while ignoring inherited properties. + * + * assert.ownInclude({ a: 1 }, { a: 1 }); + * + * @name ownInclude + * @param {Object} haystack + * @param {Object} needle + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.ownInclude = function(exp, inc, msg) { + new Assertion(exp, msg, assert.ownInclude, true).own.include(inc); + }; + + /** + * ### .notOwnInclude(haystack, needle, [message]) + * + * Asserts that 'haystack' includes 'needle'. + * Can be used to assert the absence of a subset of properties in an + * object while ignoring inherited properties. + * + * Object.prototype.b = 2; + * + * assert.notOwnInclude({ a: 1 }, { b: 2 }); + * + * @name notOwnInclude + * @param {Object} haystack + * @param {Object} needle + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.notOwnInclude = function(exp, inc, msg) { + new Assertion(exp, msg, assert.notOwnInclude, true).not.own.include(inc); + }; + + /** + * ### .deepOwnInclude(haystack, needle, [message]) + * + * Asserts that 'haystack' includes 'needle'. + * Can be used to assert the inclusion of a subset of properties in an + * object while ignoring inherited properties and checking for deep equality. + * + * assert.deepOwnInclude({a: {b: 2}}, {a: {b: 2}}); + * + * @name deepOwnInclude + * @param {Object} haystack + * @param {Object} needle + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.deepOwnInclude = function(exp, inc, msg) { + new Assertion(exp, msg, assert.deepOwnInclude, true) + .deep.own.include(inc); + }; + + /** + * ### .notDeepOwnInclude(haystack, needle, [message]) + * + * Asserts that 'haystack' includes 'needle'. + * Can be used to assert the absence of a subset of properties in an + * object while ignoring inherited properties and checking for deep equality. + * + * assert.notDeepOwnInclude({a: {b: 2}}, {a: {c: 3}}); + * + * @name notDeepOwnInclude + * @param {Object} haystack + * @param {Object} needle + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.notDeepOwnInclude = function(exp, inc, msg) { + new Assertion(exp, msg, assert.notDeepOwnInclude, true) + .not.deep.own.include(inc); + }; + + /** + * ### .match(value, regexp, [message]) + * + * Asserts that `value` matches the regular expression `regexp`. + * + * assert.match('foobar', /^foo/, 'regexp matches'); + * + * @name match + * @param {Mixed} value + * @param {RegExp} regexp + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.match = function (exp, re, msg) { + new Assertion(exp, msg, assert.match, true).to.match(re); + }; + + /** + * ### .notMatch(value, regexp, [message]) + * + * Asserts that `value` does not match the regular expression `regexp`. + * + * assert.notMatch('foobar', /^foo/, 'regexp does not match'); + * + * @name notMatch + * @param {Mixed} value + * @param {RegExp} regexp + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.notMatch = function (exp, re, msg) { + new Assertion(exp, msg, assert.notMatch, true).to.not.match(re); + }; + + /** + * ### .property(object, property, [message]) + * + * Asserts that `object` has a direct or inherited property named by + * `property`. + * + * assert.property({ tea: { green: 'matcha' }}, 'tea'); + * assert.property({ tea: { green: 'matcha' }}, 'toString'); + * + * @name property + * @param {Object} object + * @param {String} property + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.property = function (obj, prop, msg) { + new Assertion(obj, msg, assert.property, true).to.have.property(prop); + }; + + /** + * ### .notProperty(object, property, [message]) + * + * Asserts that `object` does _not_ have a direct or inherited property named + * by `property`. + * + * assert.notProperty({ tea: { green: 'matcha' }}, 'coffee'); + * + * @name notProperty + * @param {Object} object + * @param {String} property + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.notProperty = function (obj, prop, msg) { + new Assertion(obj, msg, assert.notProperty, true) + .to.not.have.property(prop); + }; + + /** + * ### .propertyVal(object, property, value, [message]) + * + * Asserts that `object` has a direct or inherited property named by + * `property` with a value given by `value`. Uses a strict equality check + * (===). + * + * assert.propertyVal({ tea: 'is good' }, 'tea', 'is good'); + * + * @name propertyVal + * @param {Object} object + * @param {String} property + * @param {Mixed} value + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.propertyVal = function (obj, prop, val, msg) { + new Assertion(obj, msg, assert.propertyVal, true) + .to.have.property(prop, val); + }; + + /** + * ### .notPropertyVal(object, property, value, [message]) + * + * Asserts that `object` does _not_ have a direct or inherited property named + * by `property` with value given by `value`. Uses a strict equality check + * (===). + * + * assert.notPropertyVal({ tea: 'is good' }, 'tea', 'is bad'); + * assert.notPropertyVal({ tea: 'is good' }, 'coffee', 'is good'); + * + * @name notPropertyVal + * @param {Object} object + * @param {String} property + * @param {Mixed} value + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.notPropertyVal = function (obj, prop, val, msg) { + new Assertion(obj, msg, assert.notPropertyVal, true) + .to.not.have.property(prop, val); + }; + + /** + * ### .deepPropertyVal(object, property, value, [message]) + * + * Asserts that `object` has a direct or inherited property named by + * `property` with a value given by `value`. Uses a deep equality check. + * + * assert.deepPropertyVal({ tea: { green: 'matcha' } }, 'tea', { green: 'matcha' }); + * + * @name deepPropertyVal + * @param {Object} object + * @param {String} property + * @param {Mixed} value + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.deepPropertyVal = function (obj, prop, val, msg) { + new Assertion(obj, msg, assert.deepPropertyVal, true) + .to.have.deep.property(prop, val); + }; + + /** + * ### .notDeepPropertyVal(object, property, value, [message]) + * + * Asserts that `object` does _not_ have a direct or inherited property named + * by `property` with value given by `value`. Uses a deep equality check. + * + * assert.notDeepPropertyVal({ tea: { green: 'matcha' } }, 'tea', { black: 'matcha' }); + * assert.notDeepPropertyVal({ tea: { green: 'matcha' } }, 'tea', { green: 'oolong' }); + * assert.notDeepPropertyVal({ tea: { green: 'matcha' } }, 'coffee', { green: 'matcha' }); + * + * @name notDeepPropertyVal + * @param {Object} object + * @param {String} property + * @param {Mixed} value + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.notDeepPropertyVal = function (obj, prop, val, msg) { + new Assertion(obj, msg, assert.notDeepPropertyVal, true) + .to.not.have.deep.property(prop, val); + }; + + /** + * ### .ownProperty(object, property, [message]) + * + * Asserts that `object` has a direct property named by `property`. Inherited + * properties aren't checked. + * + * assert.ownProperty({ tea: { green: 'matcha' }}, 'tea'); + * + * @name ownProperty + * @param {Object} object + * @param {String} property + * @param {String} message + * @api public + */ + + assert.ownProperty = function (obj, prop, msg) { + new Assertion(obj, msg, assert.ownProperty, true) + .to.have.own.property(prop); + }; + + /** + * ### .notOwnProperty(object, property, [message]) + * + * Asserts that `object` does _not_ have a direct property named by + * `property`. Inherited properties aren't checked. + * + * assert.notOwnProperty({ tea: { green: 'matcha' }}, 'coffee'); + * assert.notOwnProperty({}, 'toString'); + * + * @name notOwnProperty + * @param {Object} object + * @param {String} property + * @param {String} message + * @api public + */ + + assert.notOwnProperty = function (obj, prop, msg) { + new Assertion(obj, msg, assert.notOwnProperty, true) + .to.not.have.own.property(prop); + }; + + /** + * ### .ownPropertyVal(object, property, value, [message]) + * + * Asserts that `object` has a direct property named by `property` and a value + * equal to the provided `value`. Uses a strict equality check (===). + * Inherited properties aren't checked. + * + * assert.ownPropertyVal({ coffee: 'is good'}, 'coffee', 'is good'); + * + * @name ownPropertyVal + * @param {Object} object + * @param {String} property + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.ownPropertyVal = function (obj, prop, value, msg) { + new Assertion(obj, msg, assert.ownPropertyVal, true) + .to.have.own.property(prop, value); + }; + + /** + * ### .notOwnPropertyVal(object, property, value, [message]) + * + * Asserts that `object` does _not_ have a direct property named by `property` + * with a value equal to the provided `value`. Uses a strict equality check + * (===). Inherited properties aren't checked. + * + * assert.notOwnPropertyVal({ tea: 'is better'}, 'tea', 'is worse'); + * assert.notOwnPropertyVal({}, 'toString', Object.prototype.toString); + * + * @name notOwnPropertyVal + * @param {Object} object + * @param {String} property + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.notOwnPropertyVal = function (obj, prop, value, msg) { + new Assertion(obj, msg, assert.notOwnPropertyVal, true) + .to.not.have.own.property(prop, value); + }; + + /** + * ### .deepOwnPropertyVal(object, property, value, [message]) + * + * Asserts that `object` has a direct property named by `property` and a value + * equal to the provided `value`. Uses a deep equality check. Inherited + * properties aren't checked. + * + * assert.deepOwnPropertyVal({ tea: { green: 'matcha' } }, 'tea', { green: 'matcha' }); + * + * @name deepOwnPropertyVal + * @param {Object} object + * @param {String} property + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.deepOwnPropertyVal = function (obj, prop, value, msg) { + new Assertion(obj, msg, assert.deepOwnPropertyVal, true) + .to.have.deep.own.property(prop, value); + }; + + /** + * ### .notDeepOwnPropertyVal(object, property, value, [message]) + * + * Asserts that `object` does _not_ have a direct property named by `property` + * with a value equal to the provided `value`. Uses a deep equality check. + * Inherited properties aren't checked. + * + * assert.notDeepOwnPropertyVal({ tea: { green: 'matcha' } }, 'tea', { black: 'matcha' }); + * assert.notDeepOwnPropertyVal({ tea: { green: 'matcha' } }, 'tea', { green: 'oolong' }); + * assert.notDeepOwnPropertyVal({ tea: { green: 'matcha' } }, 'coffee', { green: 'matcha' }); + * assert.notDeepOwnPropertyVal({}, 'toString', Object.prototype.toString); + * + * @name notDeepOwnPropertyVal + * @param {Object} object + * @param {String} property + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.notDeepOwnPropertyVal = function (obj, prop, value, msg) { + new Assertion(obj, msg, assert.notDeepOwnPropertyVal, true) + .to.not.have.deep.own.property(prop, value); + }; + + /** + * ### .nestedProperty(object, property, [message]) + * + * Asserts that `object` has a direct or inherited property named by + * `property`, which can be a string using dot- and bracket-notation for + * nested reference. + * + * assert.nestedProperty({ tea: { green: 'matcha' }}, 'tea.green'); + * + * @name nestedProperty + * @param {Object} object + * @param {String} property + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.nestedProperty = function (obj, prop, msg) { + new Assertion(obj, msg, assert.nestedProperty, true) + .to.have.nested.property(prop); + }; + + /** + * ### .notNestedProperty(object, property, [message]) + * + * Asserts that `object` does _not_ have a property named by `property`, which + * can be a string using dot- and bracket-notation for nested reference. The + * property cannot exist on the object nor anywhere in its prototype chain. + * + * assert.notNestedProperty({ tea: { green: 'matcha' }}, 'tea.oolong'); + * + * @name notNestedProperty + * @param {Object} object + * @param {String} property + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.notNestedProperty = function (obj, prop, msg) { + new Assertion(obj, msg, assert.notNestedProperty, true) + .to.not.have.nested.property(prop); + }; + + /** + * ### .nestedPropertyVal(object, property, value, [message]) + * + * Asserts that `object` has a property named by `property` with value given + * by `value`. `property` can use dot- and bracket-notation for nested + * reference. Uses a strict equality check (===). + * + * assert.nestedPropertyVal({ tea: { green: 'matcha' }}, 'tea.green', 'matcha'); + * + * @name nestedPropertyVal + * @param {Object} object + * @param {String} property + * @param {Mixed} value + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.nestedPropertyVal = function (obj, prop, val, msg) { + new Assertion(obj, msg, assert.nestedPropertyVal, true) + .to.have.nested.property(prop, val); + }; + + /** + * ### .notNestedPropertyVal(object, property, value, [message]) + * + * Asserts that `object` does _not_ have a property named by `property` with + * value given by `value`. `property` can use dot- and bracket-notation for + * nested reference. Uses a strict equality check (===). + * + * assert.notNestedPropertyVal({ tea: { green: 'matcha' }}, 'tea.green', 'konacha'); + * assert.notNestedPropertyVal({ tea: { green: 'matcha' }}, 'coffee.green', 'matcha'); + * + * @name notNestedPropertyVal + * @param {Object} object + * @param {String} property + * @param {Mixed} value + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.notNestedPropertyVal = function (obj, prop, val, msg) { + new Assertion(obj, msg, assert.notNestedPropertyVal, true) + .to.not.have.nested.property(prop, val); + }; + + /** + * ### .deepNestedPropertyVal(object, property, value, [message]) + * + * Asserts that `object` has a property named by `property` with a value given + * by `value`. `property` can use dot- and bracket-notation for nested + * reference. Uses a deep equality check. + * + * assert.deepNestedPropertyVal({ tea: { green: { matcha: 'yum' } } }, 'tea.green', { matcha: 'yum' }); + * + * @name deepNestedPropertyVal + * @param {Object} object + * @param {String} property + * @param {Mixed} value + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.deepNestedPropertyVal = function (obj, prop, val, msg) { + new Assertion(obj, msg, assert.deepNestedPropertyVal, true) + .to.have.deep.nested.property(prop, val); + }; + + /** + * ### .notDeepNestedPropertyVal(object, property, value, [message]) + * + * Asserts that `object` does _not_ have a property named by `property` with + * value given by `value`. `property` can use dot- and bracket-notation for + * nested reference. Uses a deep equality check. + * + * assert.notDeepNestedPropertyVal({ tea: { green: { matcha: 'yum' } } }, 'tea.green', { oolong: 'yum' }); + * assert.notDeepNestedPropertyVal({ tea: { green: { matcha: 'yum' } } }, 'tea.green', { matcha: 'yuck' }); + * assert.notDeepNestedPropertyVal({ tea: { green: { matcha: 'yum' } } }, 'tea.black', { matcha: 'yum' }); + * + * @name notDeepNestedPropertyVal + * @param {Object} object + * @param {String} property + * @param {Mixed} value + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.notDeepNestedPropertyVal = function (obj, prop, val, msg) { + new Assertion(obj, msg, assert.notDeepNestedPropertyVal, true) + .to.not.have.deep.nested.property(prop, val); + } + + /** + * ### .lengthOf(object, length, [message]) + * + * Asserts that `object` has a `length` or `size` with the expected value. + * + * assert.lengthOf([1,2,3], 3, 'array has length of 3'); + * assert.lengthOf('foobar', 6, 'string has length of 6'); + * assert.lengthOf(new Set([1,2,3]), 3, 'set has size of 3'); + * assert.lengthOf(new Map([['a',1],['b',2],['c',3]]), 3, 'map has size of 3'); + * + * @name lengthOf + * @param {Mixed} object + * @param {Number} length + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.lengthOf = function (exp, len, msg) { + new Assertion(exp, msg, assert.lengthOf, true).to.have.lengthOf(len); + }; + + /** + * ### .hasAnyKeys(object, [keys], [message]) + * + * Asserts that `object` has at least one of the `keys` provided. + * You can also provide a single object instead of a `keys` array and its keys + * will be used as the expected set of keys. + * + * assert.hasAnyKeys({foo: 1, bar: 2, baz: 3}, ['foo', 'iDontExist', 'baz']); + * assert.hasAnyKeys({foo: 1, bar: 2, baz: 3}, {foo: 30, iDontExist: 99, baz: 1337}); + * assert.hasAnyKeys(new Map([[{foo: 1}, 'bar'], ['key', 'value']]), [{foo: 1}, 'key']); + * assert.hasAnyKeys(new Set([{foo: 'bar'}, 'anotherKey']), [{foo: 'bar'}, 'anotherKey']); + * + * @name hasAnyKeys + * @param {Mixed} object + * @param {Array|Object} keys + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.hasAnyKeys = function (obj, keys, msg) { + new Assertion(obj, msg, assert.hasAnyKeys, true).to.have.any.keys(keys); + } + + /** + * ### .hasAllKeys(object, [keys], [message]) + * + * Asserts that `object` has all and only all of the `keys` provided. + * You can also provide a single object instead of a `keys` array and its keys + * will be used as the expected set of keys. + * + * assert.hasAllKeys({foo: 1, bar: 2, baz: 3}, ['foo', 'bar', 'baz']); + * assert.hasAllKeys({foo: 1, bar: 2, baz: 3}, {foo: 30, bar: 99, baz: 1337]); + * assert.hasAllKeys(new Map([[{foo: 1}, 'bar'], ['key', 'value']]), [{foo: 1}, 'key']); + * assert.hasAllKeys(new Set([{foo: 'bar'}, 'anotherKey'], [{foo: 'bar'}, 'anotherKey']); + * + * @name hasAllKeys + * @param {Mixed} object + * @param {String[]} keys + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.hasAllKeys = function (obj, keys, msg) { + new Assertion(obj, msg, assert.hasAllKeys, true).to.have.all.keys(keys); + } + + /** + * ### .containsAllKeys(object, [keys], [message]) + * + * Asserts that `object` has all of the `keys` provided but may have more keys not listed. + * You can also provide a single object instead of a `keys` array and its keys + * will be used as the expected set of keys. + * + * assert.containsAllKeys({foo: 1, bar: 2, baz: 3}, ['foo', 'baz']); + * assert.containsAllKeys({foo: 1, bar: 2, baz: 3}, ['foo', 'bar', 'baz']); + * assert.containsAllKeys({foo: 1, bar: 2, baz: 3}, {foo: 30, baz: 1337}); + * assert.containsAllKeys({foo: 1, bar: 2, baz: 3}, {foo: 30, bar: 99, baz: 1337}); + * assert.containsAllKeys(new Map([[{foo: 1}, 'bar'], ['key', 'value']]), [{foo: 1}]); + * assert.containsAllKeys(new Map([[{foo: 1}, 'bar'], ['key', 'value']]), [{foo: 1}, 'key']); + * assert.containsAllKeys(new Set([{foo: 'bar'}, 'anotherKey'], [{foo: 'bar'}]); + * assert.containsAllKeys(new Set([{foo: 'bar'}, 'anotherKey'], [{foo: 'bar'}, 'anotherKey']); + * + * @name containsAllKeys + * @param {Mixed} object + * @param {String[]} keys + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.containsAllKeys = function (obj, keys, msg) { + new Assertion(obj, msg, assert.containsAllKeys, true) + .to.contain.all.keys(keys); + } + + /** + * ### .doesNotHaveAnyKeys(object, [keys], [message]) + * + * Asserts that `object` has none of the `keys` provided. + * You can also provide a single object instead of a `keys` array and its keys + * will be used as the expected set of keys. + * + * assert.doesNotHaveAnyKeys({foo: 1, bar: 2, baz: 3}, ['one', 'two', 'example']); + * assert.doesNotHaveAnyKeys({foo: 1, bar: 2, baz: 3}, {one: 1, two: 2, example: 'foo'}); + * assert.doesNotHaveAnyKeys(new Map([[{foo: 1}, 'bar'], ['key', 'value']]), [{one: 'two'}, 'example']); + * assert.doesNotHaveAnyKeys(new Set([{foo: 'bar'}, 'anotherKey'], [{one: 'two'}, 'example']); + * + * @name doesNotHaveAnyKeys + * @param {Mixed} object + * @param {String[]} keys + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.doesNotHaveAnyKeys = function (obj, keys, msg) { + new Assertion(obj, msg, assert.doesNotHaveAnyKeys, true) + .to.not.have.any.keys(keys); + } + + /** + * ### .doesNotHaveAllKeys(object, [keys], [message]) + * + * Asserts that `object` does not have at least one of the `keys` provided. + * You can also provide a single object instead of a `keys` array and its keys + * will be used as the expected set of keys. + * + * assert.doesNotHaveAllKeys({foo: 1, bar: 2, baz: 3}, ['one', 'two', 'example']); + * assert.doesNotHaveAllKeys({foo: 1, bar: 2, baz: 3}, {one: 1, two: 2, example: 'foo'}); + * assert.doesNotHaveAllKeys(new Map([[{foo: 1}, 'bar'], ['key', 'value']]), [{one: 'two'}, 'example']); + * assert.doesNotHaveAllKeys(new Set([{foo: 'bar'}, 'anotherKey'], [{one: 'two'}, 'example']); + * + * @name doesNotHaveAllKeys + * @param {Mixed} object + * @param {String[]} keys + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.doesNotHaveAllKeys = function (obj, keys, msg) { + new Assertion(obj, msg, assert.doesNotHaveAllKeys, true) + .to.not.have.all.keys(keys); + } + + /** + * ### .hasAnyDeepKeys(object, [keys], [message]) + * + * Asserts that `object` has at least one of the `keys` provided. + * Since Sets and Maps can have objects as keys you can use this assertion to perform + * a deep comparison. + * You can also provide a single object instead of a `keys` array and its keys + * will be used as the expected set of keys. + * + * assert.hasAnyDeepKeys(new Map([[{one: 'one'}, 'valueOne'], [1, 2]]), {one: 'one'}); + * assert.hasAnyDeepKeys(new Map([[{one: 'one'}, 'valueOne'], [1, 2]]), [{one: 'one'}, {two: 'two'}]); + * assert.hasAnyDeepKeys(new Map([[{one: 'one'}, 'valueOne'], [{two: 'two'}, 'valueTwo']]), [{one: 'one'}, {two: 'two'}]); + * assert.hasAnyDeepKeys(new Set([{one: 'one'}, {two: 'two'}]), {one: 'one'}); + * assert.hasAnyDeepKeys(new Set([{one: 'one'}, {two: 'two'}]), [{one: 'one'}, {three: 'three'}]); + * assert.hasAnyDeepKeys(new Set([{one: 'one'}, {two: 'two'}]), [{one: 'one'}, {two: 'two'}]); + * + * @name doesNotHaveAllKeys + * @param {Mixed} object + * @param {Array|Object} keys + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.hasAnyDeepKeys = function (obj, keys, msg) { + new Assertion(obj, msg, assert.hasAnyDeepKeys, true) + .to.have.any.deep.keys(keys); + } + + /** + * ### .hasAllDeepKeys(object, [keys], [message]) + * + * Asserts that `object` has all and only all of the `keys` provided. + * Since Sets and Maps can have objects as keys you can use this assertion to perform + * a deep comparison. + * You can also provide a single object instead of a `keys` array and its keys + * will be used as the expected set of keys. + * + * assert.hasAllDeepKeys(new Map([[{one: 'one'}, 'valueOne']]), {one: 'one'}); + * assert.hasAllDeepKeys(new Map([[{one: 'one'}, 'valueOne'], [{two: 'two'}, 'valueTwo']]), [{one: 'one'}, {two: 'two'}]); + * assert.hasAllDeepKeys(new Set([{one: 'one'}]), {one: 'one'}); + * assert.hasAllDeepKeys(new Set([{one: 'one'}, {two: 'two'}]), [{one: 'one'}, {two: 'two'}]); + * + * @name hasAllDeepKeys + * @param {Mixed} object + * @param {Array|Object} keys + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.hasAllDeepKeys = function (obj, keys, msg) { + new Assertion(obj, msg, assert.hasAllDeepKeys, true) + .to.have.all.deep.keys(keys); + } + + /** + * ### .containsAllDeepKeys(object, [keys], [message]) + * + * Asserts that `object` contains all of the `keys` provided. + * Since Sets and Maps can have objects as keys you can use this assertion to perform + * a deep comparison. + * You can also provide a single object instead of a `keys` array and its keys + * will be used as the expected set of keys. + * + * assert.containsAllDeepKeys(new Map([[{one: 'one'}, 'valueOne'], [1, 2]]), {one: 'one'}); + * assert.containsAllDeepKeys(new Map([[{one: 'one'}, 'valueOne'], [{two: 'two'}, 'valueTwo']]), [{one: 'one'}, {two: 'two'}]); + * assert.containsAllDeepKeys(new Set([{one: 'one'}, {two: 'two'}]), {one: 'one'}); + * assert.containsAllDeepKeys(new Set([{one: 'one'}, {two: 'two'}]), [{one: 'one'}, {two: 'two'}]); + * + * @name containsAllDeepKeys + * @param {Mixed} object + * @param {Array|Object} keys + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.containsAllDeepKeys = function (obj, keys, msg) { + new Assertion(obj, msg, assert.containsAllDeepKeys, true) + .to.contain.all.deep.keys(keys); + } + + /** + * ### .doesNotHaveAnyDeepKeys(object, [keys], [message]) + * + * Asserts that `object` has none of the `keys` provided. + * Since Sets and Maps can have objects as keys you can use this assertion to perform + * a deep comparison. + * You can also provide a single object instead of a `keys` array and its keys + * will be used as the expected set of keys. + * + * assert.doesNotHaveAnyDeepKeys(new Map([[{one: 'one'}, 'valueOne'], [1, 2]]), {thisDoesNot: 'exist'}); + * assert.doesNotHaveAnyDeepKeys(new Map([[{one: 'one'}, 'valueOne'], [{two: 'two'}, 'valueTwo']]), [{twenty: 'twenty'}, {fifty: 'fifty'}]); + * assert.doesNotHaveAnyDeepKeys(new Set([{one: 'one'}, {two: 'two'}]), {twenty: 'twenty'}); + * assert.doesNotHaveAnyDeepKeys(new Set([{one: 'one'}, {two: 'two'}]), [{twenty: 'twenty'}, {fifty: 'fifty'}]); + * + * @name doesNotHaveAnyDeepKeys + * @param {Mixed} object + * @param {Array|Object} keys + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.doesNotHaveAnyDeepKeys = function (obj, keys, msg) { + new Assertion(obj, msg, assert.doesNotHaveAnyDeepKeys, true) + .to.not.have.any.deep.keys(keys); + } + + /** + * ### .doesNotHaveAllDeepKeys(object, [keys], [message]) + * + * Asserts that `object` does not have at least one of the `keys` provided. + * Since Sets and Maps can have objects as keys you can use this assertion to perform + * a deep comparison. + * You can also provide a single object instead of a `keys` array and its keys + * will be used as the expected set of keys. + * + * assert.doesNotHaveAllDeepKeys(new Map([[{one: 'one'}, 'valueOne'], [1, 2]]), {thisDoesNot: 'exist'}); + * assert.doesNotHaveAllDeepKeys(new Map([[{one: 'one'}, 'valueOne'], [{two: 'two'}, 'valueTwo']]), [{twenty: 'twenty'}, {one: 'one'}]); + * assert.doesNotHaveAllDeepKeys(new Set([{one: 'one'}, {two: 'two'}]), {twenty: 'twenty'}); + * assert.doesNotHaveAllDeepKeys(new Set([{one: 'one'}, {two: 'two'}]), [{one: 'one'}, {fifty: 'fifty'}]); + * + * @name doesNotHaveAllDeepKeys + * @param {Mixed} object + * @param {Array|Object} keys + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.doesNotHaveAllDeepKeys = function (obj, keys, msg) { + new Assertion(obj, msg, assert.doesNotHaveAllDeepKeys, true) + .to.not.have.all.deep.keys(keys); + } + + /** + * ### .throws(fn, [errorLike/string/regexp], [string/regexp], [message]) + * + * If `errorLike` is an `Error` constructor, asserts that `fn` will throw an error that is an + * instance of `errorLike`. + * If `errorLike` is an `Error` instance, asserts that the error thrown is the same + * instance as `errorLike`. + * If `errMsgMatcher` is provided, it also asserts that the error thrown will have a + * message matching `errMsgMatcher`. + * + * assert.throws(fn, 'Error thrown must have this msg'); + * assert.throws(fn, /Error thrown must have a msg that matches this/); + * assert.throws(fn, ReferenceError); + * assert.throws(fn, errorInstance); + * assert.throws(fn, ReferenceError, 'Error thrown must be a ReferenceError and have this msg'); + * assert.throws(fn, errorInstance, 'Error thrown must be the same errorInstance and have this msg'); + * assert.throws(fn, ReferenceError, /Error thrown must be a ReferenceError and match this/); + * assert.throws(fn, errorInstance, /Error thrown must be the same errorInstance and match this/); + * + * @name throws + * @alias throw + * @alias Throw + * @param {Function} fn + * @param {ErrorConstructor|Error} errorLike + * @param {RegExp|String} errMsgMatcher + * @param {String} message + * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types + * @namespace Assert + * @api public + */ + + assert.throws = function (fn, errorLike, errMsgMatcher, msg) { + if ('string' === typeof errorLike || errorLike instanceof RegExp) { + errMsgMatcher = errorLike; + errorLike = null; + } + + var assertErr = new Assertion(fn, msg, assert.throws, true) + .to.throw(errorLike, errMsgMatcher); + return flag(assertErr, 'object'); + }; + + /** + * ### .doesNotThrow(fn, [errorLike/string/regexp], [string/regexp], [message]) + * + * If `errorLike` is an `Error` constructor, asserts that `fn` will _not_ throw an error that is an + * instance of `errorLike`. + * If `errorLike` is an `Error` instance, asserts that the error thrown is _not_ the same + * instance as `errorLike`. + * If `errMsgMatcher` is provided, it also asserts that the error thrown will _not_ have a + * message matching `errMsgMatcher`. + * + * assert.doesNotThrow(fn, 'Any Error thrown must not have this message'); + * assert.doesNotThrow(fn, /Any Error thrown must not match this/); + * assert.doesNotThrow(fn, Error); + * assert.doesNotThrow(fn, errorInstance); + * assert.doesNotThrow(fn, Error, 'Error must not have this message'); + * assert.doesNotThrow(fn, errorInstance, 'Error must not have this message'); + * assert.doesNotThrow(fn, Error, /Error must not match this/); + * assert.doesNotThrow(fn, errorInstance, /Error must not match this/); + * + * @name doesNotThrow + * @param {Function} fn + * @param {ErrorConstructor} errorLike + * @param {RegExp|String} errMsgMatcher + * @param {String} message + * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types + * @namespace Assert + * @api public + */ + + assert.doesNotThrow = function (fn, errorLike, errMsgMatcher, msg) { + if ('string' === typeof errorLike || errorLike instanceof RegExp) { + errMsgMatcher = errorLike; + errorLike = null; + } + + new Assertion(fn, msg, assert.doesNotThrow, true) + .to.not.throw(errorLike, errMsgMatcher); + }; + + /** + * ### .operator(val1, operator, val2, [message]) + * + * Compares two values using `operator`. + * + * assert.operator(1, '<', 2, 'everything is ok'); + * assert.operator(1, '>', 2, 'this will fail'); + * + * @name operator + * @param {Mixed} val1 + * @param {String} operator + * @param {Mixed} val2 + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.operator = function (val, operator, val2, msg) { + var ok; + switch(operator) { + case '==': + ok = val == val2; + break; + case '===': + ok = val === val2; + break; + case '>': + ok = val > val2; + break; + case '>=': + ok = val >= val2; + break; + case '<': + ok = val < val2; + break; + case '<=': + ok = val <= val2; + break; + case '!=': + ok = val != val2; + break; + case '!==': + ok = val !== val2; + break; + default: + msg = msg ? msg + ': ' : msg; + throw new chai.AssertionError( + msg + 'Invalid operator "' + operator + '"', + undefined, + assert.operator + ); + } + var test = new Assertion(ok, msg, assert.operator, true); + test.assert( + true === flag(test, 'object') + , 'expected ' + util.inspect(val) + ' to be ' + operator + ' ' + util.inspect(val2) + , 'expected ' + util.inspect(val) + ' to not be ' + operator + ' ' + util.inspect(val2) ); + }; + + /** + * ### .closeTo(actual, expected, delta, [message]) + * + * Asserts that the target is equal `expected`, to within a +/- `delta` range. + * + * assert.closeTo(1.5, 1, 0.5, 'numbers are close'); + * + * @name closeTo + * @param {Number} actual + * @param {Number} expected + * @param {Number} delta + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.closeTo = function (act, exp, delta, msg) { + new Assertion(act, msg, assert.closeTo, true).to.be.closeTo(exp, delta); + }; + + /** + * ### .approximately(actual, expected, delta, [message]) + * + * Asserts that the target is equal `expected`, to within a +/- `delta` range. + * + * assert.approximately(1.5, 1, 0.5, 'numbers are close'); + * + * @name approximately + * @param {Number} actual + * @param {Number} expected + * @param {Number} delta + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.approximately = function (act, exp, delta, msg) { + new Assertion(act, msg, assert.approximately, true) + .to.be.approximately(exp, delta); + }; + + /** + * ### .sameMembers(set1, set2, [message]) + * + * Asserts that `set1` and `set2` have the same members in any order. Uses a + * strict equality check (===). + * + * assert.sameMembers([ 1, 2, 3 ], [ 2, 1, 3 ], 'same members'); + * + * @name sameMembers + * @param {Array} set1 + * @param {Array} set2 + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.sameMembers = function (set1, set2, msg) { + new Assertion(set1, msg, assert.sameMembers, true) + .to.have.same.members(set2); + } + + /** + * ### .notSameMembers(set1, set2, [message]) + * + * Asserts that `set1` and `set2` don't have the same members in any order. + * Uses a strict equality check (===). + * + * assert.notSameMembers([ 1, 2, 3 ], [ 5, 1, 3 ], 'not same members'); + * + * @name notSameMembers + * @param {Array} set1 + * @param {Array} set2 + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.notSameMembers = function (set1, set2, msg) { + new Assertion(set1, msg, assert.notSameMembers, true) + .to.not.have.same.members(set2); + } + + /** + * ### .sameDeepMembers(set1, set2, [message]) + * + * Asserts that `set1` and `set2` have the same members in any order. Uses a + * deep equality check. + * + * assert.sameDeepMembers([ { a: 1 }, { b: 2 }, { c: 3 } ], [{ b: 2 }, { a: 1 }, { c: 3 }], 'same deep members'); + * + * @name sameDeepMembers + * @param {Array} set1 + * @param {Array} set2 + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.sameDeepMembers = function (set1, set2, msg) { + new Assertion(set1, msg, assert.sameDeepMembers, true) + .to.have.same.deep.members(set2); + } + + /** + * ### .notSameDeepMembers(set1, set2, [message]) + * + * Asserts that `set1` and `set2` don't have the same members in any order. + * Uses a deep equality check. + * + * assert.notSameDeepMembers([ { a: 1 }, { b: 2 }, { c: 3 } ], [{ b: 2 }, { a: 1 }, { f: 5 }], 'not same deep members'); + * + * @name notSameDeepMembers + * @param {Array} set1 + * @param {Array} set2 + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.notSameDeepMembers = function (set1, set2, msg) { + new Assertion(set1, msg, assert.notSameDeepMembers, true) + .to.not.have.same.deep.members(set2); + } + + /** + * ### .sameOrderedMembers(set1, set2, [message]) + * + * Asserts that `set1` and `set2` have the same members in the same order. + * Uses a strict equality check (===). + * + * assert.sameOrderedMembers([ 1, 2, 3 ], [ 1, 2, 3 ], 'same ordered members'); + * + * @name sameOrderedMembers + * @param {Array} set1 + * @param {Array} set2 + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.sameOrderedMembers = function (set1, set2, msg) { + new Assertion(set1, msg, assert.sameOrderedMembers, true) + .to.have.same.ordered.members(set2); + } + + /** + * ### .notSameOrderedMembers(set1, set2, [message]) + * + * Asserts that `set1` and `set2` don't have the same members in the same + * order. Uses a strict equality check (===). + * + * assert.notSameOrderedMembers([ 1, 2, 3 ], [ 2, 1, 3 ], 'not same ordered members'); + * + * @name notSameOrderedMembers + * @param {Array} set1 + * @param {Array} set2 + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.notSameOrderedMembers = function (set1, set2, msg) { + new Assertion(set1, msg, assert.notSameOrderedMembers, true) + .to.not.have.same.ordered.members(set2); + } + + /** + * ### .sameDeepOrderedMembers(set1, set2, [message]) + * + * Asserts that `set1` and `set2` have the same members in the same order. + * Uses a deep equality check. + * + * assert.sameDeepOrderedMembers([ { a: 1 }, { b: 2 }, { c: 3 } ], [ { a: 1 }, { b: 2 }, { c: 3 } ], 'same deep ordered members'); + * + * @name sameDeepOrderedMembers + * @param {Array} set1 + * @param {Array} set2 + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.sameDeepOrderedMembers = function (set1, set2, msg) { + new Assertion(set1, msg, assert.sameDeepOrderedMembers, true) + .to.have.same.deep.ordered.members(set2); + } + + /** + * ### .notSameDeepOrderedMembers(set1, set2, [message]) + * + * Asserts that `set1` and `set2` don't have the same members in the same + * order. Uses a deep equality check. + * + * assert.notSameDeepOrderedMembers([ { a: 1 }, { b: 2 }, { c: 3 } ], [ { a: 1 }, { b: 2 }, { z: 5 } ], 'not same deep ordered members'); + * assert.notSameDeepOrderedMembers([ { a: 1 }, { b: 2 }, { c: 3 } ], [ { b: 2 }, { a: 1 }, { c: 3 } ], 'not same deep ordered members'); + * + * @name notSameDeepOrderedMembers + * @param {Array} set1 + * @param {Array} set2 + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.notSameDeepOrderedMembers = function (set1, set2, msg) { + new Assertion(set1, msg, assert.notSameDeepOrderedMembers, true) + .to.not.have.same.deep.ordered.members(set2); + } + + /** + * ### .includeMembers(superset, subset, [message]) + * + * Asserts that `subset` is included in `superset` in any order. Uses a + * strict equality check (===). Duplicates are ignored. + * + * assert.includeMembers([ 1, 2, 3 ], [ 2, 1, 2 ], 'include members'); + * + * @name includeMembers + * @param {Array} superset + * @param {Array} subset + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.includeMembers = function (superset, subset, msg) { + new Assertion(superset, msg, assert.includeMembers, true) + .to.include.members(subset); + } + + /** + * ### .notIncludeMembers(superset, subset, [message]) + * + * Asserts that `subset` isn't included in `superset` in any order. Uses a + * strict equality check (===). Duplicates are ignored. + * + * assert.notIncludeMembers([ 1, 2, 3 ], [ 5, 1 ], 'not include members'); + * + * @name notIncludeMembers + * @param {Array} superset + * @param {Array} subset + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.notIncludeMembers = function (superset, subset, msg) { + new Assertion(superset, msg, assert.notIncludeMembers, true) + .to.not.include.members(subset); + } + + /** + * ### .includeDeepMembers(superset, subset, [message]) + * + * Asserts that `subset` is included in `superset` in any order. Uses a deep + * equality check. Duplicates are ignored. + * + * assert.includeDeepMembers([ { a: 1 }, { b: 2 }, { c: 3 } ], [ { b: 2 }, { a: 1 }, { b: 2 } ], 'include deep members'); + * + * @name includeDeepMembers + * @param {Array} superset + * @param {Array} subset + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.includeDeepMembers = function (superset, subset, msg) { + new Assertion(superset, msg, assert.includeDeepMembers, true) + .to.include.deep.members(subset); + } + + /** + * ### .notIncludeDeepMembers(superset, subset, [message]) + * + * Asserts that `subset` isn't included in `superset` in any order. Uses a + * deep equality check. Duplicates are ignored. + * + * assert.notIncludeDeepMembers([ { a: 1 }, { b: 2 }, { c: 3 } ], [ { b: 2 }, { f: 5 } ], 'not include deep members'); + * + * @name notIncludeDeepMembers + * @param {Array} superset + * @param {Array} subset + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.notIncludeDeepMembers = function (superset, subset, msg) { + new Assertion(superset, msg, assert.notIncludeDeepMembers, true) + .to.not.include.deep.members(subset); + } + + /** + * ### .includeOrderedMembers(superset, subset, [message]) + * + * Asserts that `subset` is included in `superset` in the same order + * beginning with the first element in `superset`. Uses a strict equality + * check (===). + * + * assert.includeOrderedMembers([ 1, 2, 3 ], [ 1, 2 ], 'include ordered members'); + * + * @name includeOrderedMembers + * @param {Array} superset + * @param {Array} subset + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.includeOrderedMembers = function (superset, subset, msg) { + new Assertion(superset, msg, assert.includeOrderedMembers, true) + .to.include.ordered.members(subset); + } + + /** + * ### .notIncludeOrderedMembers(superset, subset, [message]) + * + * Asserts that `subset` isn't included in `superset` in the same order + * beginning with the first element in `superset`. Uses a strict equality + * check (===). + * + * assert.notIncludeOrderedMembers([ 1, 2, 3 ], [ 2, 1 ], 'not include ordered members'); + * assert.notIncludeOrderedMembers([ 1, 2, 3 ], [ 2, 3 ], 'not include ordered members'); + * + * @name notIncludeOrderedMembers + * @param {Array} superset + * @param {Array} subset + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.notIncludeOrderedMembers = function (superset, subset, msg) { + new Assertion(superset, msg, assert.notIncludeOrderedMembers, true) + .to.not.include.ordered.members(subset); + } + + /** + * ### .includeDeepOrderedMembers(superset, subset, [message]) + * + * Asserts that `subset` is included in `superset` in the same order + * beginning with the first element in `superset`. Uses a deep equality + * check. + * + * assert.includeDeepOrderedMembers([ { a: 1 }, { b: 2 }, { c: 3 } ], [ { a: 1 }, { b: 2 } ], 'include deep ordered members'); + * + * @name includeDeepOrderedMembers + * @param {Array} superset + * @param {Array} subset + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.includeDeepOrderedMembers = function (superset, subset, msg) { + new Assertion(superset, msg, assert.includeDeepOrderedMembers, true) + .to.include.deep.ordered.members(subset); + } + + /** + * ### .notIncludeDeepOrderedMembers(superset, subset, [message]) + * + * Asserts that `subset` isn't included in `superset` in the same order + * beginning with the first element in `superset`. Uses a deep equality + * check. + * + * assert.notIncludeDeepOrderedMembers([ { a: 1 }, { b: 2 }, { c: 3 } ], [ { a: 1 }, { f: 5 } ], 'not include deep ordered members'); + * assert.notIncludeDeepOrderedMembers([ { a: 1 }, { b: 2 }, { c: 3 } ], [ { b: 2 }, { a: 1 } ], 'not include deep ordered members'); + * assert.notIncludeDeepOrderedMembers([ { a: 1 }, { b: 2 }, { c: 3 } ], [ { b: 2 }, { c: 3 } ], 'not include deep ordered members'); + * + * @name notIncludeDeepOrderedMembers + * @param {Array} superset + * @param {Array} subset + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.notIncludeDeepOrderedMembers = function (superset, subset, msg) { + new Assertion(superset, msg, assert.notIncludeDeepOrderedMembers, true) + .to.not.include.deep.ordered.members(subset); + } + + /** + * ### .oneOf(inList, list, [message]) + * + * Asserts that non-object, non-array value `inList` appears in the flat array `list`. + * + * assert.oneOf(1, [ 2, 1 ], 'Not found in list'); + * + * @name oneOf + * @param {*} inList + * @param {Array<*>} list + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.oneOf = function (inList, list, msg) { + new Assertion(inList, msg, assert.oneOf, true).to.be.oneOf(list); + } + + /** + * ### .changes(function, object, property, [message]) + * + * Asserts that a function changes the value of a property. + * + * var obj = { val: 10 }; + * var fn = function() { obj.val = 22 }; + * assert.changes(fn, obj, 'val'); + * + * @name changes + * @param {Function} modifier function + * @param {Object} object or getter function + * @param {String} property name _optional_ + * @param {String} message _optional_ + * @namespace Assert + * @api public + */ + + assert.changes = function (fn, obj, prop, msg) { + if (arguments.length === 3 && typeof obj === 'function') { + msg = prop; + prop = null; + } + + new Assertion(fn, msg, assert.changes, true).to.change(obj, prop); + } + + /** + * ### .changesBy(function, object, property, delta, [message]) + * + * Asserts that a function changes the value of a property by an amount (delta). + * + * var obj = { val: 10 }; + * var fn = function() { obj.val += 2 }; + * assert.changesBy(fn, obj, 'val', 2); + * + * @name changesBy + * @param {Function} modifier function + * @param {Object} object or getter function + * @param {String} property name _optional_ + * @param {Number} change amount (delta) + * @param {String} message _optional_ + * @namespace Assert + * @api public + */ + + assert.changesBy = function (fn, obj, prop, delta, msg) { + if (arguments.length === 4 && typeof obj === 'function') { + var tmpMsg = delta; + delta = prop; + msg = tmpMsg; + } else if (arguments.length === 3) { + delta = prop; + prop = null; + } + + new Assertion(fn, msg, assert.changesBy, true) + .to.change(obj, prop).by(delta); + } + + /** + * ### .doesNotChange(function, object, property, [message]) + * + * Asserts that a function does not change the value of a property. + * + * var obj = { val: 10 }; + * var fn = function() { console.log('foo'); }; + * assert.doesNotChange(fn, obj, 'val'); + * + * @name doesNotChange + * @param {Function} modifier function + * @param {Object} object or getter function + * @param {String} property name _optional_ + * @param {String} message _optional_ + * @namespace Assert + * @api public + */ + + assert.doesNotChange = function (fn, obj, prop, msg) { + if (arguments.length === 3 && typeof obj === 'function') { + msg = prop; + prop = null; + } + + return new Assertion(fn, msg, assert.doesNotChange, true) + .to.not.change(obj, prop); + } + + /** + * ### .changesButNotBy(function, object, property, delta, [message]) + * + * Asserts that a function does not change the value of a property or of a function's return value by an amount (delta) + * + * var obj = { val: 10 }; + * var fn = function() { obj.val += 10 }; + * assert.changesButNotBy(fn, obj, 'val', 5); + * + * @name changesButNotBy + * @param {Function} modifier function + * @param {Object} object or getter function + * @param {String} property name _optional_ + * @param {Number} change amount (delta) + * @param {String} message _optional_ + * @namespace Assert + * @api public + */ + + assert.changesButNotBy = function (fn, obj, prop, delta, msg) { + if (arguments.length === 4 && typeof obj === 'function') { + var tmpMsg = delta; + delta = prop; + msg = tmpMsg; + } else if (arguments.length === 3) { + delta = prop; + prop = null; + } + + new Assertion(fn, msg, assert.changesButNotBy, true) + .to.change(obj, prop).but.not.by(delta); + } + + /** + * ### .increases(function, object, property, [message]) + * + * Asserts that a function increases a numeric object property. + * + * var obj = { val: 10 }; + * var fn = function() { obj.val = 13 }; + * assert.increases(fn, obj, 'val'); + * + * @name increases + * @param {Function} modifier function + * @param {Object} object or getter function + * @param {String} property name _optional_ + * @param {String} message _optional_ + * @namespace Assert + * @api public + */ + + assert.increases = function (fn, obj, prop, msg) { + if (arguments.length === 3 && typeof obj === 'function') { + msg = prop; + prop = null; + } + + return new Assertion(fn, msg, assert.increases, true) + .to.increase(obj, prop); + } + + /** + * ### .increasesBy(function, object, property, delta, [message]) + * + * Asserts that a function increases a numeric object property or a function's return value by an amount (delta). + * + * var obj = { val: 10 }; + * var fn = function() { obj.val += 10 }; + * assert.increasesBy(fn, obj, 'val', 10); + * + * @name increasesBy + * @param {Function} modifier function + * @param {Object} object or getter function + * @param {String} property name _optional_ + * @param {Number} change amount (delta) + * @param {String} message _optional_ + * @namespace Assert + * @api public + */ + + assert.increasesBy = function (fn, obj, prop, delta, msg) { + if (arguments.length === 4 && typeof obj === 'function') { + var tmpMsg = delta; + delta = prop; + msg = tmpMsg; + } else if (arguments.length === 3) { + delta = prop; + prop = null; + } + + new Assertion(fn, msg, assert.increasesBy, true) + .to.increase(obj, prop).by(delta); + } + + /** + * ### .doesNotIncrease(function, object, property, [message]) + * + * Asserts that a function does not increase a numeric object property. + * + * var obj = { val: 10 }; + * var fn = function() { obj.val = 8 }; + * assert.doesNotIncrease(fn, obj, 'val'); + * + * @name doesNotIncrease + * @param {Function} modifier function + * @param {Object} object or getter function + * @param {String} property name _optional_ + * @param {String} message _optional_ + * @namespace Assert + * @api public + */ + + assert.doesNotIncrease = function (fn, obj, prop, msg) { + if (arguments.length === 3 && typeof obj === 'function') { + msg = prop; + prop = null; + } + + return new Assertion(fn, msg, assert.doesNotIncrease, true) + .to.not.increase(obj, prop); + } + + /** + * ### .increasesButNotBy(function, object, property, [message]) + * + * Asserts that a function does not increase a numeric object property or function's return value by an amount (delta). + * + * var obj = { val: 10 }; + * var fn = function() { obj.val = 15 }; + * assert.increasesButNotBy(fn, obj, 'val', 10); + * + * @name increasesButNotBy + * @param {Function} modifier function + * @param {Object} object or getter function + * @param {String} property name _optional_ + * @param {Number} change amount (delta) + * @param {String} message _optional_ + * @namespace Assert + * @api public + */ + + assert.increasesButNotBy = function (fn, obj, prop, delta, msg) { + if (arguments.length === 4 && typeof obj === 'function') { + var tmpMsg = delta; + delta = prop; + msg = tmpMsg; + } else if (arguments.length === 3) { + delta = prop; + prop = null; + } + + new Assertion(fn, msg, assert.increasesButNotBy, true) + .to.increase(obj, prop).but.not.by(delta); + } + + /** + * ### .decreases(function, object, property, [message]) + * + * Asserts that a function decreases a numeric object property. + * + * var obj = { val: 10 }; + * var fn = function() { obj.val = 5 }; + * assert.decreases(fn, obj, 'val'); + * + * @name decreases + * @param {Function} modifier function + * @param {Object} object or getter function + * @param {String} property name _optional_ + * @param {String} message _optional_ + * @namespace Assert + * @api public + */ + + assert.decreases = function (fn, obj, prop, msg) { + if (arguments.length === 3 && typeof obj === 'function') { + msg = prop; + prop = null; + } + + return new Assertion(fn, msg, assert.decreases, true) + .to.decrease(obj, prop); + } + + /** + * ### .decreasesBy(function, object, property, delta, [message]) + * + * Asserts that a function decreases a numeric object property or a function's return value by an amount (delta) + * + * var obj = { val: 10 }; + * var fn = function() { obj.val -= 5 }; + * assert.decreasesBy(fn, obj, 'val', 5); + * + * @name decreasesBy + * @param {Function} modifier function + * @param {Object} object or getter function + * @param {String} property name _optional_ + * @param {Number} change amount (delta) + * @param {String} message _optional_ + * @namespace Assert + * @api public + */ + + assert.decreasesBy = function (fn, obj, prop, delta, msg) { + if (arguments.length === 4 && typeof obj === 'function') { + var tmpMsg = delta; + delta = prop; + msg = tmpMsg; + } else if (arguments.length === 3) { + delta = prop; + prop = null; + } + + new Assertion(fn, msg, assert.decreasesBy, true) + .to.decrease(obj, prop).by(delta); + } + + /** + * ### .doesNotDecrease(function, object, property, [message]) + * + * Asserts that a function does not decreases a numeric object property. + * + * var obj = { val: 10 }; + * var fn = function() { obj.val = 15 }; + * assert.doesNotDecrease(fn, obj, 'val'); + * + * @name doesNotDecrease + * @param {Function} modifier function + * @param {Object} object or getter function + * @param {String} property name _optional_ + * @param {String} message _optional_ + * @namespace Assert + * @api public + */ + + assert.doesNotDecrease = function (fn, obj, prop, msg) { + if (arguments.length === 3 && typeof obj === 'function') { + msg = prop; + prop = null; + } + + return new Assertion(fn, msg, assert.doesNotDecrease, true) + .to.not.decrease(obj, prop); + } + + /** + * ### .doesNotDecreaseBy(function, object, property, delta, [message]) + * + * Asserts that a function does not decreases a numeric object property or a function's return value by an amount (delta) + * + * var obj = { val: 10 }; + * var fn = function() { obj.val = 5 }; + * assert.doesNotDecreaseBy(fn, obj, 'val', 1); + * + * @name doesNotDecrease + * @param {Function} modifier function + * @param {Object} object or getter function + * @param {String} property name _optional_ + * @param {Number} change amount (delta) + * @param {String} message _optional_ + * @namespace Assert + * @api public + */ + + assert.doesNotDecreaseBy = function (fn, obj, prop, delta, msg) { + if (arguments.length === 4 && typeof obj === 'function') { + var tmpMsg = delta; + delta = prop; + msg = tmpMsg; + } else if (arguments.length === 3) { + delta = prop; + prop = null; + } + + return new Assertion(fn, msg, assert.doesNotDecreaseBy, true) + .to.not.decrease(obj, prop).by(delta); + } + + /** + * ### .decreasesButNotBy(function, object, property, delta, [message]) + * + * Asserts that a function does not decreases a numeric object property or a function's return value by an amount (delta) + * + * var obj = { val: 10 }; + * var fn = function() { obj.val = 5 }; + * assert.decreasesButNotBy(fn, obj, 'val', 1); + * + * @name decreasesButNotBy + * @param {Function} modifier function + * @param {Object} object or getter function + * @param {String} property name _optional_ + * @param {Number} change amount (delta) + * @param {String} message _optional_ + * @namespace Assert + * @api public + */ + + assert.decreasesButNotBy = function (fn, obj, prop, delta, msg) { + if (arguments.length === 4 && typeof obj === 'function') { + var tmpMsg = delta; + delta = prop; + msg = tmpMsg; + } else if (arguments.length === 3) { + delta = prop; + prop = null; + } + + new Assertion(fn, msg, assert.decreasesButNotBy, true) + .to.decrease(obj, prop).but.not.by(delta); + } + + /*! + * ### .ifError(object) + * + * Asserts if value is not a false value, and throws if it is a true value. + * This is added to allow for chai to be a drop-in replacement for Node's + * assert class. + * + * var err = new Error('I am a custom error'); + * assert.ifError(err); // Rethrows err! + * + * @name ifError + * @param {Object} object + * @namespace Assert + * @api public + */ + + assert.ifError = function (val) { + if (val) { + throw(val); + } + }; + + /** + * ### .isExtensible(object) + * + * Asserts that `object` is extensible (can have new properties added to it). + * + * assert.isExtensible({}); + * + * @name isExtensible + * @alias extensible + * @param {Object} object + * @param {String} message _optional_ + * @namespace Assert + * @api public + */ + + assert.isExtensible = function (obj, msg) { + new Assertion(obj, msg, assert.isExtensible, true).to.be.extensible; + }; + + /** + * ### .isNotExtensible(object) + * + * Asserts that `object` is _not_ extensible. + * + * var nonExtensibleObject = Object.preventExtensions({}); + * var sealedObject = Object.seal({}); + * var frozenObject = Object.freeze({}); + * + * assert.isNotExtensible(nonExtensibleObject); + * assert.isNotExtensible(sealedObject); + * assert.isNotExtensible(frozenObject); + * + * @name isNotExtensible + * @alias notExtensible + * @param {Object} object + * @param {String} message _optional_ + * @namespace Assert + * @api public + */ + + assert.isNotExtensible = function (obj, msg) { + new Assertion(obj, msg, assert.isNotExtensible, true).to.not.be.extensible; + }; + + /** + * ### .isSealed(object) + * + * Asserts that `object` is sealed (cannot have new properties added to it + * and its existing properties cannot be removed). + * + * var sealedObject = Object.seal({}); + * var frozenObject = Object.seal({}); + * + * assert.isSealed(sealedObject); + * assert.isSealed(frozenObject); + * + * @name isSealed + * @alias sealed + * @param {Object} object + * @param {String} message _optional_ + * @namespace Assert + * @api public + */ + + assert.isSealed = function (obj, msg) { + new Assertion(obj, msg, assert.isSealed, true).to.be.sealed; + }; + + /** + * ### .isNotSealed(object) + * + * Asserts that `object` is _not_ sealed. + * + * assert.isNotSealed({}); + * + * @name isNotSealed + * @alias notSealed + * @param {Object} object + * @param {String} message _optional_ + * @namespace Assert + * @api public + */ + + assert.isNotSealed = function (obj, msg) { + new Assertion(obj, msg, assert.isNotSealed, true).to.not.be.sealed; + }; + + /** + * ### .isFrozen(object) + * + * Asserts that `object` is frozen (cannot have new properties added to it + * and its existing properties cannot be modified). + * + * var frozenObject = Object.freeze({}); + * assert.frozen(frozenObject); + * + * @name isFrozen + * @alias frozen + * @param {Object} object + * @param {String} message _optional_ + * @namespace Assert + * @api public + */ + + assert.isFrozen = function (obj, msg) { + new Assertion(obj, msg, assert.isFrozen, true).to.be.frozen; + }; + + /** + * ### .isNotFrozen(object) + * + * Asserts that `object` is _not_ frozen. + * + * assert.isNotFrozen({}); + * + * @name isNotFrozen + * @alias notFrozen + * @param {Object} object + * @param {String} message _optional_ + * @namespace Assert + * @api public + */ + + assert.isNotFrozen = function (obj, msg) { + new Assertion(obj, msg, assert.isNotFrozen, true).to.not.be.frozen; + }; + + /** + * ### .isEmpty(target) + * + * Asserts that the target does not contain any values. + * For arrays and strings, it checks the `length` property. + * For `Map` and `Set` instances, it checks the `size` property. + * For non-function objects, it gets the count of own + * enumerable string keys. + * + * assert.isEmpty([]); + * assert.isEmpty(''); + * assert.isEmpty(new Map); + * assert.isEmpty({}); + * + * @name isEmpty + * @alias empty + * @param {Object|Array|String|Map|Set} target + * @param {String} message _optional_ + * @namespace Assert + * @api public + */ + + assert.isEmpty = function(val, msg) { + new Assertion(val, msg, assert.isEmpty, true).to.be.empty; + }; + + /** + * ### .isNotEmpty(target) + * + * Asserts that the target contains values. + * For arrays and strings, it checks the `length` property. + * For `Map` and `Set` instances, it checks the `size` property. + * For non-function objects, it gets the count of own + * enumerable string keys. + * + * assert.isNotEmpty([1, 2]); + * assert.isNotEmpty('34'); + * assert.isNotEmpty(new Set([5, 6])); + * assert.isNotEmpty({ key: 7 }); + * + * @name isNotEmpty + * @alias notEmpty + * @param {Object|Array|String|Map|Set} target + * @param {String} message _optional_ + * @namespace Assert + * @api public + */ + + assert.isNotEmpty = function(val, msg) { + new Assertion(val, msg, assert.isNotEmpty, true).to.not.be.empty; + }; + + /*! + * Aliases. + */ + + (function alias(name, as){ + assert[as] = assert[name]; + return alias; + }) + ('isOk', 'ok') + ('isNotOk', 'notOk') + ('throws', 'throw') + ('throws', 'Throw') + ('isExtensible', 'extensible') + ('isNotExtensible', 'notExtensible') + ('isSealed', 'sealed') + ('isNotSealed', 'notSealed') + ('isFrozen', 'frozen') + ('isNotFrozen', 'notFrozen') + ('isEmpty', 'empty') + ('isNotEmpty', 'notEmpty'); +}; + +},{}],7:[function(require,module,exports){ +/*! + * chai + * Copyright(c) 2011-2014 Jake Luer + * MIT Licensed + */ + +module.exports = function (chai, util) { + chai.expect = function (val, message) { + return new chai.Assertion(val, message); + }; + + /** + * ### .fail([message]) + * ### .fail(actual, expected, [message], [operator]) + * + * Throw a failure. + * + * expect.fail(); + * expect.fail("custom error message"); + * expect.fail(1, 2); + * expect.fail(1, 2, "custom error message"); + * expect.fail(1, 2, "custom error message", ">"); + * expect.fail(1, 2, undefined, ">"); + * + * @name fail + * @param {Mixed} actual + * @param {Mixed} expected + * @param {String} message + * @param {String} operator + * @namespace BDD + * @api public + */ + + chai.expect.fail = function (actual, expected, message, operator) { + if (arguments.length < 2) { + message = actual; + actual = undefined; + } + + message = message || 'expect.fail()'; + throw new chai.AssertionError(message, { + actual: actual + , expected: expected + , operator: operator + }, chai.expect.fail); + }; +}; + +},{}],8:[function(require,module,exports){ +/*! + * chai + * Copyright(c) 2011-2014 Jake Luer + * MIT Licensed + */ + +module.exports = function (chai, util) { + var Assertion = chai.Assertion; + + function loadShould () { + // explicitly define this method as function as to have it's name to include as `ssfi` + function shouldGetter() { + if (this instanceof String + || this instanceof Number + || this instanceof Boolean + || typeof Symbol === 'function' && this instanceof Symbol) { + return new Assertion(this.valueOf(), null, shouldGetter); + } + return new Assertion(this, null, shouldGetter); + } + function shouldSetter(value) { + // See https://github.com/chaijs/chai/issues/86: this makes + // `whatever.should = someValue` actually set `someValue`, which is + // especially useful for `global.should = require('chai').should()`. + // + // Note that we have to use [[DefineProperty]] instead of [[Put]] + // since otherwise we would trigger this very setter! + Object.defineProperty(this, 'should', { + value: value, + enumerable: true, + configurable: true, + writable: true + }); + } + // modify Object.prototype to have `should` + Object.defineProperty(Object.prototype, 'should', { + set: shouldSetter + , get: shouldGetter + , configurable: true + }); + + var should = {}; + + /** + * ### .fail([message]) + * ### .fail(actual, expected, [message], [operator]) + * + * Throw a failure. + * + * should.fail(); + * should.fail("custom error message"); + * should.fail(1, 2); + * should.fail(1, 2, "custom error message"); + * should.fail(1, 2, "custom error message", ">"); + * should.fail(1, 2, undefined, ">"); + * + * + * @name fail + * @param {Mixed} actual + * @param {Mixed} expected + * @param {String} message + * @param {String} operator + * @namespace BDD + * @api public + */ + + should.fail = function (actual, expected, message, operator) { + if (arguments.length < 2) { + message = actual; + actual = undefined; + } + + message = message || 'should.fail()'; + throw new chai.AssertionError(message, { + actual: actual + , expected: expected + , operator: operator + }, should.fail); + }; + + /** + * ### .equal(actual, expected, [message]) + * + * Asserts non-strict equality (`==`) of `actual` and `expected`. + * + * should.equal(3, '3', '== coerces values to strings'); + * + * @name equal + * @param {Mixed} actual + * @param {Mixed} expected + * @param {String} message + * @namespace Should + * @api public + */ + + should.equal = function (val1, val2, msg) { + new Assertion(val1, msg).to.equal(val2); + }; + + /** + * ### .throw(function, [constructor/string/regexp], [string/regexp], [message]) + * + * Asserts that `function` will throw an error that is an instance of + * `constructor`, or alternately that it will throw an error with message + * matching `regexp`. + * + * should.throw(fn, 'function throws a reference error'); + * should.throw(fn, /function throws a reference error/); + * should.throw(fn, ReferenceError); + * should.throw(fn, ReferenceError, 'function throws a reference error'); + * should.throw(fn, ReferenceError, /function throws a reference error/); + * + * @name throw + * @alias Throw + * @param {Function} function + * @param {ErrorConstructor} constructor + * @param {RegExp} regexp + * @param {String} message + * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types + * @namespace Should + * @api public + */ + + should.Throw = function (fn, errt, errs, msg) { + new Assertion(fn, msg).to.Throw(errt, errs); + }; + + /** + * ### .exist + * + * Asserts that the target is neither `null` nor `undefined`. + * + * var foo = 'hi'; + * + * should.exist(foo, 'foo exists'); + * + * @name exist + * @namespace Should + * @api public + */ + + should.exist = function (val, msg) { + new Assertion(val, msg).to.exist; + } + + // negation + should.not = {} + + /** + * ### .not.equal(actual, expected, [message]) + * + * Asserts non-strict inequality (`!=`) of `actual` and `expected`. + * + * should.not.equal(3, 4, 'these numbers are not equal'); + * + * @name not.equal + * @param {Mixed} actual + * @param {Mixed} expected + * @param {String} message + * @namespace Should + * @api public + */ + + should.not.equal = function (val1, val2, msg) { + new Assertion(val1, msg).to.not.equal(val2); + }; + + /** + * ### .throw(function, [constructor/regexp], [message]) + * + * Asserts that `function` will _not_ throw an error that is an instance of + * `constructor`, or alternately that it will not throw an error with message + * matching `regexp`. + * + * should.not.throw(fn, Error, 'function does not throw'); + * + * @name not.throw + * @alias not.Throw + * @param {Function} function + * @param {ErrorConstructor} constructor + * @param {RegExp} regexp + * @param {String} message + * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types + * @namespace Should + * @api public + */ + + should.not.Throw = function (fn, errt, errs, msg) { + new Assertion(fn, msg).to.not.Throw(errt, errs); + }; + + /** + * ### .not.exist + * + * Asserts that the target is neither `null` nor `undefined`. + * + * var bar = null; + * + * should.not.exist(bar, 'bar does not exist'); + * + * @name not.exist + * @namespace Should + * @api public + */ + + should.not.exist = function (val, msg) { + new Assertion(val, msg).to.not.exist; + } + + should['throw'] = should['Throw']; + should.not['throw'] = should.not['Throw']; + + return should; + }; + + chai.should = loadShould; + chai.Should = loadShould; +}; + +},{}],9:[function(require,module,exports){ +/*! + * Chai - addChainingMethod utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/*! + * Module dependencies + */ + +var addLengthGuard = require('./addLengthGuard'); +var chai = require('../../chai'); +var flag = require('./flag'); +var proxify = require('./proxify'); +var transferFlags = require('./transferFlags'); + +/*! + * Module variables + */ + +// Check whether `Object.setPrototypeOf` is supported +var canSetPrototype = typeof Object.setPrototypeOf === 'function'; + +// Without `Object.setPrototypeOf` support, this module will need to add properties to a function. +// However, some of functions' own props are not configurable and should be skipped. +var testFn = function() {}; +var excludeNames = Object.getOwnPropertyNames(testFn).filter(function(name) { + var propDesc = Object.getOwnPropertyDescriptor(testFn, name); + + // Note: PhantomJS 1.x includes `callee` as one of `testFn`'s own properties, + // but then returns `undefined` as the property descriptor for `callee`. As a + // workaround, we perform an otherwise unnecessary type-check for `propDesc`, + // and then filter it out if it's not an object as it should be. + if (typeof propDesc !== 'object') + return true; + + return !propDesc.configurable; +}); + +// Cache `Function` properties +var call = Function.prototype.call, + apply = Function.prototype.apply; + +/** + * ### .addChainableMethod(ctx, name, method, chainingBehavior) + * + * Adds a method to an object, such that the method can also be chained. + * + * utils.addChainableMethod(chai.Assertion.prototype, 'foo', function (str) { + * var obj = utils.flag(this, 'object'); + * new chai.Assertion(obj).to.be.equal(str); + * }); + * + * Can also be accessed directly from `chai.Assertion`. + * + * chai.Assertion.addChainableMethod('foo', fn, chainingBehavior); + * + * The result can then be used as both a method assertion, executing both `method` and + * `chainingBehavior`, or as a language chain, which only executes `chainingBehavior`. + * + * expect(fooStr).to.be.foo('bar'); + * expect(fooStr).to.be.foo.equal('foo'); + * + * @param {Object} ctx object to which the method is added + * @param {String} name of method to add + * @param {Function} method function to be used for `name`, when called + * @param {Function} chainingBehavior function to be called every time the property is accessed + * @namespace Utils + * @name addChainableMethod + * @api public + */ + +module.exports = function addChainableMethod(ctx, name, method, chainingBehavior) { + if (typeof chainingBehavior !== 'function') { + chainingBehavior = function () { }; + } + + var chainableBehavior = { + method: method + , chainingBehavior: chainingBehavior + }; + + // save the methods so we can overwrite them later, if we need to. + if (!ctx.__methods) { + ctx.__methods = {}; + } + ctx.__methods[name] = chainableBehavior; + + Object.defineProperty(ctx, name, + { get: function chainableMethodGetter() { + chainableBehavior.chainingBehavior.call(this); + + var chainableMethodWrapper = function () { + // Setting the `ssfi` flag to `chainableMethodWrapper` causes this + // function to be the starting point for removing implementation + // frames from the stack trace of a failed assertion. + // + // However, we only want to use this function as the starting point if + // the `lockSsfi` flag isn't set. + // + // If the `lockSsfi` flag is set, then this assertion is being + // invoked from inside of another assertion. In this case, the `ssfi` + // flag has already been set by the outer assertion. + // + // Note that overwriting a chainable method merely replaces the saved + // methods in `ctx.__methods` instead of completely replacing the + // overwritten assertion. Therefore, an overwriting assertion won't + // set the `ssfi` or `lockSsfi` flags. + if (!flag(this, 'lockSsfi')) { + flag(this, 'ssfi', chainableMethodWrapper); + } + + var result = chainableBehavior.method.apply(this, arguments); + if (result !== undefined) { + return result; + } + + var newAssertion = new chai.Assertion(); + transferFlags(this, newAssertion); + return newAssertion; + }; + + addLengthGuard(chainableMethodWrapper, name, true); + + // Use `Object.setPrototypeOf` if available + if (canSetPrototype) { + // Inherit all properties from the object by replacing the `Function` prototype + var prototype = Object.create(this); + // Restore the `call` and `apply` methods from `Function` + prototype.call = call; + prototype.apply = apply; + Object.setPrototypeOf(chainableMethodWrapper, prototype); + } + // Otherwise, redefine all properties (slow!) + else { + var asserterNames = Object.getOwnPropertyNames(ctx); + asserterNames.forEach(function (asserterName) { + if (excludeNames.indexOf(asserterName) !== -1) { + return; + } + + var pd = Object.getOwnPropertyDescriptor(ctx, asserterName); + Object.defineProperty(chainableMethodWrapper, asserterName, pd); + }); + } + + transferFlags(this, chainableMethodWrapper); + return proxify(chainableMethodWrapper); + } + , configurable: true + }); +}; + +},{"../../chai":2,"./addLengthGuard":10,"./flag":15,"./proxify":30,"./transferFlags":32}],10:[function(require,module,exports){ +var fnLengthDesc = Object.getOwnPropertyDescriptor(function () {}, 'length'); + +/*! + * Chai - addLengthGuard utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/** + * ### .addLengthGuard(fn, assertionName, isChainable) + * + * Define `length` as a getter on the given uninvoked method assertion. The + * getter acts as a guard against chaining `length` directly off of an uninvoked + * method assertion, which is a problem because it references `function`'s + * built-in `length` property instead of Chai's `length` assertion. When the + * getter catches the user making this mistake, it throws an error with a + * helpful message. + * + * There are two ways in which this mistake can be made. The first way is by + * chaining the `length` assertion directly off of an uninvoked chainable + * method. In this case, Chai suggests that the user use `lengthOf` instead. The + * second way is by chaining the `length` assertion directly off of an uninvoked + * non-chainable method. Non-chainable methods must be invoked prior to + * chaining. In this case, Chai suggests that the user consult the docs for the + * given assertion. + * + * If the `length` property of functions is unconfigurable, then return `fn` + * without modification. + * + * Note that in ES6, the function's `length` property is configurable, so once + * support for legacy environments is dropped, Chai's `length` property can + * replace the built-in function's `length` property, and this length guard will + * no longer be necessary. In the mean time, maintaining consistency across all + * environments is the priority. + * + * @param {Function} fn + * @param {String} assertionName + * @param {Boolean} isChainable + * @namespace Utils + * @name addLengthGuard + */ + +module.exports = function addLengthGuard (fn, assertionName, isChainable) { + if (!fnLengthDesc.configurable) return fn; + + Object.defineProperty(fn, 'length', { + get: function () { + if (isChainable) { + throw Error('Invalid Chai property: ' + assertionName + '.length. Due' + + ' to a compatibility issue, "length" cannot directly follow "' + + assertionName + '". Use "' + assertionName + '.lengthOf" instead.'); + } + + throw Error('Invalid Chai property: ' + assertionName + '.length. See' + + ' docs for proper usage of "' + assertionName + '".'); + } + }); + + return fn; +}; + +},{}],11:[function(require,module,exports){ +/*! + * Chai - addMethod utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +var addLengthGuard = require('./addLengthGuard'); +var chai = require('../../chai'); +var flag = require('./flag'); +var proxify = require('./proxify'); +var transferFlags = require('./transferFlags'); + +/** + * ### .addMethod(ctx, name, method) + * + * Adds a method to the prototype of an object. + * + * utils.addMethod(chai.Assertion.prototype, 'foo', function (str) { + * var obj = utils.flag(this, 'object'); + * new chai.Assertion(obj).to.be.equal(str); + * }); + * + * Can also be accessed directly from `chai.Assertion`. + * + * chai.Assertion.addMethod('foo', fn); + * + * Then can be used as any other assertion. + * + * expect(fooStr).to.be.foo('bar'); + * + * @param {Object} ctx object to which the method is added + * @param {String} name of method to add + * @param {Function} method function to be used for name + * @namespace Utils + * @name addMethod + * @api public + */ + +module.exports = function addMethod(ctx, name, method) { + var methodWrapper = function () { + // Setting the `ssfi` flag to `methodWrapper` causes this function to be the + // starting point for removing implementation frames from the stack trace of + // a failed assertion. + // + // However, we only want to use this function as the starting point if the + // `lockSsfi` flag isn't set. + // + // If the `lockSsfi` flag is set, then either this assertion has been + // overwritten by another assertion, or this assertion is being invoked from + // inside of another assertion. In the first case, the `ssfi` flag has + // already been set by the overwriting assertion. In the second case, the + // `ssfi` flag has already been set by the outer assertion. + if (!flag(this, 'lockSsfi')) { + flag(this, 'ssfi', methodWrapper); + } + + var result = method.apply(this, arguments); + if (result !== undefined) + return result; + + var newAssertion = new chai.Assertion(); + transferFlags(this, newAssertion); + return newAssertion; + }; + + addLengthGuard(methodWrapper, name, false); + ctx[name] = proxify(methodWrapper, name); +}; + +},{"../../chai":2,"./addLengthGuard":10,"./flag":15,"./proxify":30,"./transferFlags":32}],12:[function(require,module,exports){ +/*! + * Chai - addProperty utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +var chai = require('../../chai'); +var flag = require('./flag'); +var isProxyEnabled = require('./isProxyEnabled'); +var transferFlags = require('./transferFlags'); + +/** + * ### .addProperty(ctx, name, getter) + * + * Adds a property to the prototype of an object. + * + * utils.addProperty(chai.Assertion.prototype, 'foo', function () { + * var obj = utils.flag(this, 'object'); + * new chai.Assertion(obj).to.be.instanceof(Foo); + * }); + * + * Can also be accessed directly from `chai.Assertion`. + * + * chai.Assertion.addProperty('foo', fn); + * + * Then can be used as any other assertion. + * + * expect(myFoo).to.be.foo; + * + * @param {Object} ctx object to which the property is added + * @param {String} name of property to add + * @param {Function} getter function to be used for name + * @namespace Utils + * @name addProperty + * @api public + */ + +module.exports = function addProperty(ctx, name, getter) { + getter = getter === undefined ? function () {} : getter; + + Object.defineProperty(ctx, name, + { get: function propertyGetter() { + // Setting the `ssfi` flag to `propertyGetter` causes this function to + // be the starting point for removing implementation frames from the + // stack trace of a failed assertion. + // + // However, we only want to use this function as the starting point if + // the `lockSsfi` flag isn't set and proxy protection is disabled. + // + // If the `lockSsfi` flag is set, then either this assertion has been + // overwritten by another assertion, or this assertion is being invoked + // from inside of another assertion. In the first case, the `ssfi` flag + // has already been set by the overwriting assertion. In the second + // case, the `ssfi` flag has already been set by the outer assertion. + // + // If proxy protection is enabled, then the `ssfi` flag has already been + // set by the proxy getter. + if (!isProxyEnabled() && !flag(this, 'lockSsfi')) { + flag(this, 'ssfi', propertyGetter); + } + + var result = getter.call(this); + if (result !== undefined) + return result; + + var newAssertion = new chai.Assertion(); + transferFlags(this, newAssertion); + return newAssertion; + } + , configurable: true + }); +}; + +},{"../../chai":2,"./flag":15,"./isProxyEnabled":25,"./transferFlags":32}],13:[function(require,module,exports){ +/*! + * Chai - compareByInspect utility + * Copyright(c) 2011-2016 Jake Luer + * MIT Licensed + */ + +/*! + * Module dependencies + */ + +var inspect = require('./inspect'); + +/** + * ### .compareByInspect(mixed, mixed) + * + * To be used as a compareFunction with Array.prototype.sort. Compares elements + * using inspect instead of default behavior of using toString so that Symbols + * and objects with irregular/missing toString can still be sorted without a + * TypeError. + * + * @param {Mixed} first element to compare + * @param {Mixed} second element to compare + * @returns {Number} -1 if 'a' should come before 'b'; otherwise 1 + * @name compareByInspect + * @namespace Utils + * @api public + */ + +module.exports = function compareByInspect(a, b) { + return inspect(a) < inspect(b) ? -1 : 1; +}; + +},{"./inspect":23}],14:[function(require,module,exports){ +/*! + * Chai - expectTypes utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/** + * ### .expectTypes(obj, types) + * + * Ensures that the object being tested against is of a valid type. + * + * utils.expectTypes(this, ['array', 'object', 'string']); + * + * @param {Mixed} obj constructed Assertion + * @param {Array} type A list of allowed types for this assertion + * @namespace Utils + * @name expectTypes + * @api public + */ + +var AssertionError = require('assertion-error'); +var flag = require('./flag'); +var type = require('type-detect'); + +module.exports = function expectTypes(obj, types) { + var flagMsg = flag(obj, 'message'); + var ssfi = flag(obj, 'ssfi'); + + flagMsg = flagMsg ? flagMsg + ': ' : ''; + + obj = flag(obj, 'object'); + types = types.map(function (t) { return t.toLowerCase(); }); + types.sort(); + + // Transforms ['lorem', 'ipsum'] into 'a lorem, or an ipsum' + var str = types.map(function (t, index) { + var art = ~[ 'a', 'e', 'i', 'o', 'u' ].indexOf(t.charAt(0)) ? 'an' : 'a'; + var or = types.length > 1 && index === types.length - 1 ? 'or ' : ''; + return or + art + ' ' + t; + }).join(', '); + + var objType = type(obj).toLowerCase(); + + if (!types.some(function (expected) { return objType === expected; })) { + throw new AssertionError( + flagMsg + 'object tested must be ' + str + ', but ' + objType + ' given', + undefined, + ssfi + ); + } +}; + +},{"./flag":15,"assertion-error":33,"type-detect":38}],15:[function(require,module,exports){ +/*! + * Chai - flag utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/** + * ### .flag(object, key, [value]) + * + * Get or set a flag value on an object. If a + * value is provided it will be set, else it will + * return the currently set value or `undefined` if + * the value is not set. + * + * utils.flag(this, 'foo', 'bar'); // setter + * utils.flag(this, 'foo'); // getter, returns `bar` + * + * @param {Object} object constructed Assertion + * @param {String} key + * @param {Mixed} value (optional) + * @namespace Utils + * @name flag + * @api private + */ + +module.exports = function flag(obj, key, value) { + var flags = obj.__flags || (obj.__flags = Object.create(null)); + if (arguments.length === 3) { + flags[key] = value; + } else { + return flags[key]; + } +}; + +},{}],16:[function(require,module,exports){ +/*! + * Chai - getActual utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/** + * ### .getActual(object, [actual]) + * + * Returns the `actual` value for an Assertion. + * + * @param {Object} object (constructed Assertion) + * @param {Arguments} chai.Assertion.prototype.assert arguments + * @namespace Utils + * @name getActual + */ + +module.exports = function getActual(obj, args) { + return args.length > 4 ? args[4] : obj._obj; +}; + +},{}],17:[function(require,module,exports){ +/*! + * Chai - getEnumerableProperties utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/** + * ### .getEnumerableProperties(object) + * + * This allows the retrieval of enumerable property names of an object, + * inherited or not. + * + * @param {Object} object + * @returns {Array} + * @namespace Utils + * @name getEnumerableProperties + * @api public + */ + +module.exports = function getEnumerableProperties(object) { + var result = []; + for (var name in object) { + result.push(name); + } + return result; +}; + +},{}],18:[function(require,module,exports){ +/*! + * Chai - message composition utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/*! + * Module dependencies + */ + +var flag = require('./flag') + , getActual = require('./getActual') + , objDisplay = require('./objDisplay'); + +/** + * ### .getMessage(object, message, negateMessage) + * + * Construct the error message based on flags + * and template tags. Template tags will return + * a stringified inspection of the object referenced. + * + * Message template tags: + * - `#{this}` current asserted object + * - `#{act}` actual value + * - `#{exp}` expected value + * + * @param {Object} object (constructed Assertion) + * @param {Arguments} chai.Assertion.prototype.assert arguments + * @namespace Utils + * @name getMessage + * @api public + */ + +module.exports = function getMessage(obj, args) { + var negate = flag(obj, 'negate') + , val = flag(obj, 'object') + , expected = args[3] + , actual = getActual(obj, args) + , msg = negate ? args[2] : args[1] + , flagMsg = flag(obj, 'message'); + + if(typeof msg === "function") msg = msg(); + msg = msg || ''; + msg = msg + .replace(/#\{this\}/g, function () { return objDisplay(val); }) + .replace(/#\{act\}/g, function () { return objDisplay(actual); }) + .replace(/#\{exp\}/g, function () { return objDisplay(expected); }); + + return flagMsg ? flagMsg + ': ' + msg : msg; +}; + +},{"./flag":15,"./getActual":16,"./objDisplay":26}],19:[function(require,module,exports){ +/*! + * Chai - getOwnEnumerableProperties utility + * Copyright(c) 2011-2016 Jake Luer + * MIT Licensed + */ + +/*! + * Module dependencies + */ + +var getOwnEnumerablePropertySymbols = require('./getOwnEnumerablePropertySymbols'); + +/** + * ### .getOwnEnumerableProperties(object) + * + * This allows the retrieval of directly-owned enumerable property names and + * symbols of an object. This function is necessary because Object.keys only + * returns enumerable property names, not enumerable property symbols. + * + * @param {Object} object + * @returns {Array} + * @namespace Utils + * @name getOwnEnumerableProperties + * @api public + */ + +module.exports = function getOwnEnumerableProperties(obj) { + return Object.keys(obj).concat(getOwnEnumerablePropertySymbols(obj)); +}; + +},{"./getOwnEnumerablePropertySymbols":20}],20:[function(require,module,exports){ +/*! + * Chai - getOwnEnumerablePropertySymbols utility + * Copyright(c) 2011-2016 Jake Luer + * MIT Licensed + */ + +/** + * ### .getOwnEnumerablePropertySymbols(object) + * + * This allows the retrieval of directly-owned enumerable property symbols of an + * object. This function is necessary because Object.getOwnPropertySymbols + * returns both enumerable and non-enumerable property symbols. + * + * @param {Object} object + * @returns {Array} + * @namespace Utils + * @name getOwnEnumerablePropertySymbols + * @api public + */ + +module.exports = function getOwnEnumerablePropertySymbols(obj) { + if (typeof Object.getOwnPropertySymbols !== 'function') return []; + + return Object.getOwnPropertySymbols(obj).filter(function (sym) { + return Object.getOwnPropertyDescriptor(obj, sym).enumerable; + }); +}; + +},{}],21:[function(require,module,exports){ +/*! + * Chai - getProperties utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/** + * ### .getProperties(object) + * + * This allows the retrieval of property names of an object, enumerable or not, + * inherited or not. + * + * @param {Object} object + * @returns {Array} + * @namespace Utils + * @name getProperties + * @api public + */ + +module.exports = function getProperties(object) { + var result = Object.getOwnPropertyNames(object); + + function addProperty(property) { + if (result.indexOf(property) === -1) { + result.push(property); + } + } + + var proto = Object.getPrototypeOf(object); + while (proto !== null) { + Object.getOwnPropertyNames(proto).forEach(addProperty); + proto = Object.getPrototypeOf(proto); + } + + return result; +}; + +},{}],22:[function(require,module,exports){ +/*! + * chai + * Copyright(c) 2011 Jake Luer + * MIT Licensed + */ + +/*! + * Dependencies that are used for multiple exports are required here only once + */ + +var pathval = require('pathval'); + +/*! + * test utility + */ + +exports.test = require('./test'); + +/*! + * type utility + */ + +exports.type = require('type-detect'); + +/*! + * expectTypes utility + */ +exports.expectTypes = require('./expectTypes'); + +/*! + * message utility + */ + +exports.getMessage = require('./getMessage'); + +/*! + * actual utility + */ + +exports.getActual = require('./getActual'); + +/*! + * Inspect util + */ + +exports.inspect = require('./inspect'); + +/*! + * Object Display util + */ + +exports.objDisplay = require('./objDisplay'); + +/*! + * Flag utility + */ + +exports.flag = require('./flag'); + +/*! + * Flag transferring utility + */ + +exports.transferFlags = require('./transferFlags'); + +/*! + * Deep equal utility + */ + +exports.eql = require('deep-eql'); + +/*! + * Deep path info + */ + +exports.getPathInfo = pathval.getPathInfo; + +/*! + * Check if a property exists + */ + +exports.hasProperty = pathval.hasProperty; + +/*! + * Function name + */ + +exports.getName = require('get-func-name'); + +/*! + * add Property + */ + +exports.addProperty = require('./addProperty'); + +/*! + * add Method + */ + +exports.addMethod = require('./addMethod'); + +/*! + * overwrite Property + */ + +exports.overwriteProperty = require('./overwriteProperty'); + +/*! + * overwrite Method + */ + +exports.overwriteMethod = require('./overwriteMethod'); + +/*! + * Add a chainable method + */ + +exports.addChainableMethod = require('./addChainableMethod'); + +/*! + * Overwrite chainable method + */ + +exports.overwriteChainableMethod = require('./overwriteChainableMethod'); + +/*! + * Compare by inspect method + */ + +exports.compareByInspect = require('./compareByInspect'); + +/*! + * Get own enumerable property symbols method + */ + +exports.getOwnEnumerablePropertySymbols = require('./getOwnEnumerablePropertySymbols'); + +/*! + * Get own enumerable properties method + */ + +exports.getOwnEnumerableProperties = require('./getOwnEnumerableProperties'); + +/*! + * Checks error against a given set of criteria + */ + +exports.checkError = require('check-error'); + +/*! + * Proxify util + */ + +exports.proxify = require('./proxify'); + +/*! + * addLengthGuard util + */ + +exports.addLengthGuard = require('./addLengthGuard'); + +/*! + * isProxyEnabled helper + */ + +exports.isProxyEnabled = require('./isProxyEnabled'); + +/*! + * isNaN method + */ + +exports.isNaN = require('./isNaN'); + +},{"./addChainableMethod":9,"./addLengthGuard":10,"./addMethod":11,"./addProperty":12,"./compareByInspect":13,"./expectTypes":14,"./flag":15,"./getActual":16,"./getMessage":18,"./getOwnEnumerableProperties":19,"./getOwnEnumerablePropertySymbols":20,"./inspect":23,"./isNaN":24,"./isProxyEnabled":25,"./objDisplay":26,"./overwriteChainableMethod":27,"./overwriteMethod":28,"./overwriteProperty":29,"./proxify":30,"./test":31,"./transferFlags":32,"check-error":34,"deep-eql":35,"get-func-name":36,"pathval":37,"type-detect":38}],23:[function(require,module,exports){ +// This is (almost) directly from Node.js utils +// https://github.com/joyent/node/blob/f8c335d0caf47f16d31413f89aa28eda3878e3aa/lib/util.js + +var getName = require('get-func-name'); +var getProperties = require('./getProperties'); +var getEnumerableProperties = require('./getEnumerableProperties'); +var config = require('../config'); + +module.exports = inspect; + +/** + * ### .inspect(obj, [showHidden], [depth], [colors]) + * + * Echoes the value of a value. Tries to print the value out + * in the best way possible given the different types. + * + * @param {Object} obj The object to print out. + * @param {Boolean} showHidden Flag that shows hidden (not enumerable) + * properties of objects. Default is false. + * @param {Number} depth Depth in which to descend in object. Default is 2. + * @param {Boolean} colors Flag to turn on ANSI escape codes to color the + * output. Default is false (no coloring). + * @namespace Utils + * @name inspect + */ +function inspect(obj, showHidden, depth, colors) { + var ctx = { + showHidden: showHidden, + seen: [], + stylize: function (str) { return str; } + }; + return formatValue(ctx, obj, (typeof depth === 'undefined' ? 2 : depth)); +} + +// Returns true if object is a DOM element. +var isDOMElement = function (object) { + if (typeof HTMLElement === 'object') { + return object instanceof HTMLElement; + } else { + return object && + typeof object === 'object' && + 'nodeType' in object && + object.nodeType === 1 && + typeof object.nodeName === 'string'; + } +}; + +function formatValue(ctx, value, recurseTimes) { + // Provide a hook for user-specified inspect functions. + // Check that value is an object with an inspect function on it + if (value && typeof value.inspect === 'function' && + // Filter out the util module, it's inspect function is special + value.inspect !== exports.inspect && + // Also filter out any prototype objects using the circular check. + !(value.constructor && value.constructor.prototype === value)) { + var ret = value.inspect(recurseTimes, ctx); + if (typeof ret !== 'string') { + ret = formatValue(ctx, ret, recurseTimes); + } + return ret; + } + + // Primitive types cannot have properties + var primitive = formatPrimitive(ctx, value); + if (primitive) { + return primitive; + } + + // If this is a DOM element, try to get the outer HTML. + if (isDOMElement(value)) { + if ('outerHTML' in value) { + return value.outerHTML; + // This value does not have an outerHTML attribute, + // it could still be an XML element + } else { + // Attempt to serialize it + try { + if (document.xmlVersion) { + var xmlSerializer = new XMLSerializer(); + return xmlSerializer.serializeToString(value); + } else { + // Firefox 11- do not support outerHTML + // It does, however, support innerHTML + // Use the following to render the element + var ns = "http://www.w3.org/1999/xhtml"; + var container = document.createElementNS(ns, '_'); + + container.appendChild(value.cloneNode(false)); + var html = container.innerHTML + .replace('><', '>' + value.innerHTML + '<'); + container.innerHTML = ''; + return html; + } + } catch (err) { + // This could be a non-native DOM implementation, + // continue with the normal flow: + // printing the element as if it is an object. + } + } + } + + // Look up the keys of the object. + var visibleKeys = getEnumerableProperties(value); + var keys = ctx.showHidden ? getProperties(value) : visibleKeys; + + var name, nameSuffix; + + // Some type of object without properties can be shortcut. + // In IE, errors have a single `stack` property, or if they are vanilla `Error`, + // a `stack` plus `description` property; ignore those for consistency. + if (keys.length === 0 || (isError(value) && ( + (keys.length === 1 && keys[0] === 'stack') || + (keys.length === 2 && keys[0] === 'description' && keys[1] === 'stack') + ))) { + if (typeof value === 'function') { + name = getName(value); + nameSuffix = name ? ': ' + name : ''; + return ctx.stylize('[Function' + nameSuffix + ']', 'special'); + } + if (isRegExp(value)) { + return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); + } + if (isDate(value)) { + return ctx.stylize(Date.prototype.toUTCString.call(value), 'date'); + } + if (isError(value)) { + return formatError(value); + } + } + + var base = '' + , array = false + , typedArray = false + , braces = ['{', '}']; + + if (isTypedArray(value)) { + typedArray = true; + braces = ['[', ']']; + } + + // Make Array say that they are Array + if (isArray(value)) { + array = true; + braces = ['[', ']']; + } + + // Make functions say that they are functions + if (typeof value === 'function') { + name = getName(value); + nameSuffix = name ? ': ' + name : ''; + base = ' [Function' + nameSuffix + ']'; + } + + // Make RegExps say that they are RegExps + if (isRegExp(value)) { + base = ' ' + RegExp.prototype.toString.call(value); + } + + // Make dates with properties first say the date + if (isDate(value)) { + base = ' ' + Date.prototype.toUTCString.call(value); + } + + // Make error with message first say the error + if (isError(value)) { + return formatError(value); + } + + if (keys.length === 0 && (!array || value.length == 0)) { + return braces[0] + base + braces[1]; + } + + if (recurseTimes < 0) { + if (isRegExp(value)) { + return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); + } else { + return ctx.stylize('[Object]', 'special'); + } + } + + ctx.seen.push(value); + + var output; + if (array) { + output = formatArray(ctx, value, recurseTimes, visibleKeys, keys); + } else if (typedArray) { + return formatTypedArray(value); + } else { + output = keys.map(function(key) { + return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array); + }); + } + + ctx.seen.pop(); + + return reduceToSingleString(output, base, braces); +} + +function formatPrimitive(ctx, value) { + switch (typeof value) { + case 'undefined': + return ctx.stylize('undefined', 'undefined'); + + case 'string': + var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '') + .replace(/'/g, "\\'") + .replace(/\\"/g, '"') + '\''; + return ctx.stylize(simple, 'string'); + + case 'number': + if (value === 0 && (1/value) === -Infinity) { + return ctx.stylize('-0', 'number'); + } + return ctx.stylize('' + value, 'number'); + + case 'boolean': + return ctx.stylize('' + value, 'boolean'); + + case 'symbol': + return ctx.stylize(value.toString(), 'symbol'); + } + // For some reason typeof null is "object", so special case here. + if (value === null) { + return ctx.stylize('null', 'null'); + } +} + +function formatError(value) { + return '[' + Error.prototype.toString.call(value) + ']'; +} + +function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { + var output = []; + for (var i = 0, l = value.length; i < l; ++i) { + if (Object.prototype.hasOwnProperty.call(value, String(i))) { + output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, + String(i), true)); + } else { + output.push(''); + } + } + + keys.forEach(function(key) { + if (!key.match(/^\d+$/)) { + output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, + key, true)); + } + }); + return output; +} + +function formatTypedArray(value) { + var str = '[ '; + + for (var i = 0; i < value.length; ++i) { + if (str.length >= config.truncateThreshold - 7) { + str += '...'; + break; + } + str += value[i] + ', '; + } + str += ' ]'; + + // Removing trailing `, ` if the array was not truncated + if (str.indexOf(', ]') !== -1) { + str = str.replace(', ]', ' ]'); + } + + return str; +} + +function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) { + var name; + var propDescriptor = Object.getOwnPropertyDescriptor(value, key); + var str; + + if (propDescriptor) { + if (propDescriptor.get) { + if (propDescriptor.set) { + str = ctx.stylize('[Getter/Setter]', 'special'); + } else { + str = ctx.stylize('[Getter]', 'special'); + } + } else { + if (propDescriptor.set) { + str = ctx.stylize('[Setter]', 'special'); + } + } + } + if (visibleKeys.indexOf(key) < 0) { + name = '[' + key + ']'; + } + if (!str) { + if (ctx.seen.indexOf(value[key]) < 0) { + if (recurseTimes === null) { + str = formatValue(ctx, value[key], null); + } else { + str = formatValue(ctx, value[key], recurseTimes - 1); + } + if (str.indexOf('\n') > -1) { + if (array) { + str = str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n').substr(2); + } else { + str = '\n' + str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n'); + } + } + } else { + str = ctx.stylize('[Circular]', 'special'); + } + } + if (typeof name === 'undefined') { + if (array && key.match(/^\d+$/)) { + return str; + } + name = JSON.stringify('' + key); + if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { + name = name.substr(1, name.length - 2); + name = ctx.stylize(name, 'name'); + } else { + name = name.replace(/'/g, "\\'") + .replace(/\\"/g, '"') + .replace(/(^"|"$)/g, "'"); + name = ctx.stylize(name, 'string'); + } + } + + return name + ': ' + str; +} + +function reduceToSingleString(output, base, braces) { + var length = output.reduce(function(prev, cur) { + return prev + cur.length + 1; + }, 0); + + if (length > 60) { + return braces[0] + + (base === '' ? '' : base + '\n ') + + ' ' + + output.join(',\n ') + + ' ' + + braces[1]; + } + + return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; +} + +function isTypedArray(ar) { + // Unfortunately there's no way to check if an object is a TypedArray + // We have to check if it's one of these types + return (typeof ar === 'object' && /\w+Array]$/.test(objectToString(ar))); +} + +function isArray(ar) { + return Array.isArray(ar) || + (typeof ar === 'object' && objectToString(ar) === '[object Array]'); +} + +function isRegExp(re) { + return typeof re === 'object' && objectToString(re) === '[object RegExp]'; +} + +function isDate(d) { + return typeof d === 'object' && objectToString(d) === '[object Date]'; +} + +function isError(e) { + return typeof e === 'object' && objectToString(e) === '[object Error]'; +} + +function objectToString(o) { + return Object.prototype.toString.call(o); +} + +},{"../config":4,"./getEnumerableProperties":17,"./getProperties":21,"get-func-name":36}],24:[function(require,module,exports){ +/*! + * Chai - isNaN utility + * Copyright(c) 2012-2015 Sakthipriyan Vairamani + * MIT Licensed + */ + +/** + * ### .isNaN(value) + * + * Checks if the given value is NaN or not. + * + * utils.isNaN(NaN); // true + * + * @param {Value} The value which has to be checked if it is NaN + * @name isNaN + * @api private + */ + +function isNaN(value) { + // Refer http://www.ecma-international.org/ecma-262/6.0/#sec-isnan-number + // section's NOTE. + return value !== value; +} + +// If ECMAScript 6's Number.isNaN is present, prefer that. +module.exports = Number.isNaN || isNaN; + +},{}],25:[function(require,module,exports){ +var config = require('../config'); + +/*! + * Chai - isProxyEnabled helper + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/** + * ### .isProxyEnabled() + * + * Helper function to check if Chai's proxy protection feature is enabled. If + * proxies are unsupported or disabled via the user's Chai config, then return + * false. Otherwise, return true. + * + * @namespace Utils + * @name isProxyEnabled + */ + +module.exports = function isProxyEnabled() { + return config.useProxy && + typeof Proxy !== 'undefined' && + typeof Reflect !== 'undefined'; +}; + +},{"../config":4}],26:[function(require,module,exports){ +/*! + * Chai - flag utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/*! + * Module dependencies + */ + +var inspect = require('./inspect'); +var config = require('../config'); + +/** + * ### .objDisplay(object) + * + * Determines if an object or an array matches + * criteria to be inspected in-line for error + * messages or should be truncated. + * + * @param {Mixed} javascript object to inspect + * @name objDisplay + * @namespace Utils + * @api public + */ + +module.exports = function objDisplay(obj) { + var str = inspect(obj) + , type = Object.prototype.toString.call(obj); + + if (config.truncateThreshold && str.length >= config.truncateThreshold) { + if (type === '[object Function]') { + return !obj.name || obj.name === '' + ? '[Function]' + : '[Function: ' + obj.name + ']'; + } else if (type === '[object Array]') { + return '[ Array(' + obj.length + ') ]'; + } else if (type === '[object Object]') { + var keys = Object.keys(obj) + , kstr = keys.length > 2 + ? keys.splice(0, 2).join(', ') + ', ...' + : keys.join(', '); + return '{ Object (' + kstr + ') }'; + } else { + return str; + } + } else { + return str; + } +}; + +},{"../config":4,"./inspect":23}],27:[function(require,module,exports){ +/*! + * Chai - overwriteChainableMethod utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +var chai = require('../../chai'); +var transferFlags = require('./transferFlags'); + +/** + * ### .overwriteChainableMethod(ctx, name, method, chainingBehavior) + * + * Overwrites an already existing chainable method + * and provides access to the previous function or + * property. Must return functions to be used for + * name. + * + * utils.overwriteChainableMethod(chai.Assertion.prototype, 'lengthOf', + * function (_super) { + * } + * , function (_super) { + * } + * ); + * + * Can also be accessed directly from `chai.Assertion`. + * + * chai.Assertion.overwriteChainableMethod('foo', fn, fn); + * + * Then can be used as any other assertion. + * + * expect(myFoo).to.have.lengthOf(3); + * expect(myFoo).to.have.lengthOf.above(3); + * + * @param {Object} ctx object whose method / property is to be overwritten + * @param {String} name of method / property to overwrite + * @param {Function} method function that returns a function to be used for name + * @param {Function} chainingBehavior function that returns a function to be used for property + * @namespace Utils + * @name overwriteChainableMethod + * @api public + */ + +module.exports = function overwriteChainableMethod(ctx, name, method, chainingBehavior) { + var chainableBehavior = ctx.__methods[name]; + + var _chainingBehavior = chainableBehavior.chainingBehavior; + chainableBehavior.chainingBehavior = function overwritingChainableMethodGetter() { + var result = chainingBehavior(_chainingBehavior).call(this); + if (result !== undefined) { + return result; + } + + var newAssertion = new chai.Assertion(); + transferFlags(this, newAssertion); + return newAssertion; + }; + + var _method = chainableBehavior.method; + chainableBehavior.method = function overwritingChainableMethodWrapper() { + var result = method(_method).apply(this, arguments); + if (result !== undefined) { + return result; + } + + var newAssertion = new chai.Assertion(); + transferFlags(this, newAssertion); + return newAssertion; + }; +}; + +},{"../../chai":2,"./transferFlags":32}],28:[function(require,module,exports){ +/*! + * Chai - overwriteMethod utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +var addLengthGuard = require('./addLengthGuard'); +var chai = require('../../chai'); +var flag = require('./flag'); +var proxify = require('./proxify'); +var transferFlags = require('./transferFlags'); + +/** + * ### .overwriteMethod(ctx, name, fn) + * + * Overwrites an already existing method and provides + * access to previous function. Must return function + * to be used for name. + * + * utils.overwriteMethod(chai.Assertion.prototype, 'equal', function (_super) { + * return function (str) { + * var obj = utils.flag(this, 'object'); + * if (obj instanceof Foo) { + * new chai.Assertion(obj.value).to.equal(str); + * } else { + * _super.apply(this, arguments); + * } + * } + * }); + * + * Can also be accessed directly from `chai.Assertion`. + * + * chai.Assertion.overwriteMethod('foo', fn); + * + * Then can be used as any other assertion. + * + * expect(myFoo).to.equal('bar'); + * + * @param {Object} ctx object whose method is to be overwritten + * @param {String} name of method to overwrite + * @param {Function} method function that returns a function to be used for name + * @namespace Utils + * @name overwriteMethod + * @api public + */ + +module.exports = function overwriteMethod(ctx, name, method) { + var _method = ctx[name] + , _super = function () { + throw new Error(name + ' is not a function'); + }; + + if (_method && 'function' === typeof _method) + _super = _method; + + var overwritingMethodWrapper = function () { + // Setting the `ssfi` flag to `overwritingMethodWrapper` causes this + // function to be the starting point for removing implementation frames from + // the stack trace of a failed assertion. + // + // However, we only want to use this function as the starting point if the + // `lockSsfi` flag isn't set. + // + // If the `lockSsfi` flag is set, then either this assertion has been + // overwritten by another assertion, or this assertion is being invoked from + // inside of another assertion. In the first case, the `ssfi` flag has + // already been set by the overwriting assertion. In the second case, the + // `ssfi` flag has already been set by the outer assertion. + if (!flag(this, 'lockSsfi')) { + flag(this, 'ssfi', overwritingMethodWrapper); + } + + // Setting the `lockSsfi` flag to `true` prevents the overwritten assertion + // from changing the `ssfi` flag. By this point, the `ssfi` flag is already + // set to the correct starting point for this assertion. + var origLockSsfi = flag(this, 'lockSsfi'); + flag(this, 'lockSsfi', true); + var result = method(_super).apply(this, arguments); + flag(this, 'lockSsfi', origLockSsfi); + + if (result !== undefined) { + return result; + } + + var newAssertion = new chai.Assertion(); + transferFlags(this, newAssertion); + return newAssertion; + } + + addLengthGuard(overwritingMethodWrapper, name, false); + ctx[name] = proxify(overwritingMethodWrapper, name); +}; + +},{"../../chai":2,"./addLengthGuard":10,"./flag":15,"./proxify":30,"./transferFlags":32}],29:[function(require,module,exports){ +/*! + * Chai - overwriteProperty utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +var chai = require('../../chai'); +var flag = require('./flag'); +var isProxyEnabled = require('./isProxyEnabled'); +var transferFlags = require('./transferFlags'); + +/** + * ### .overwriteProperty(ctx, name, fn) + * + * Overwrites an already existing property getter and provides + * access to previous value. Must return function to use as getter. + * + * utils.overwriteProperty(chai.Assertion.prototype, 'ok', function (_super) { + * return function () { + * var obj = utils.flag(this, 'object'); + * if (obj instanceof Foo) { + * new chai.Assertion(obj.name).to.equal('bar'); + * } else { + * _super.call(this); + * } + * } + * }); + * + * + * Can also be accessed directly from `chai.Assertion`. + * + * chai.Assertion.overwriteProperty('foo', fn); + * + * Then can be used as any other assertion. + * + * expect(myFoo).to.be.ok; + * + * @param {Object} ctx object whose property is to be overwritten + * @param {String} name of property to overwrite + * @param {Function} getter function that returns a getter function to be used for name + * @namespace Utils + * @name overwriteProperty + * @api public + */ + +module.exports = function overwriteProperty(ctx, name, getter) { + var _get = Object.getOwnPropertyDescriptor(ctx, name) + , _super = function () {}; + + if (_get && 'function' === typeof _get.get) + _super = _get.get + + Object.defineProperty(ctx, name, + { get: function overwritingPropertyGetter() { + // Setting the `ssfi` flag to `overwritingPropertyGetter` causes this + // function to be the starting point for removing implementation frames + // from the stack trace of a failed assertion. + // + // However, we only want to use this function as the starting point if + // the `lockSsfi` flag isn't set and proxy protection is disabled. + // + // If the `lockSsfi` flag is set, then either this assertion has been + // overwritten by another assertion, or this assertion is being invoked + // from inside of another assertion. In the first case, the `ssfi` flag + // has already been set by the overwriting assertion. In the second + // case, the `ssfi` flag has already been set by the outer assertion. + // + // If proxy protection is enabled, then the `ssfi` flag has already been + // set by the proxy getter. + if (!isProxyEnabled() && !flag(this, 'lockSsfi')) { + flag(this, 'ssfi', overwritingPropertyGetter); + } + + // Setting the `lockSsfi` flag to `true` prevents the overwritten + // assertion from changing the `ssfi` flag. By this point, the `ssfi` + // flag is already set to the correct starting point for this assertion. + var origLockSsfi = flag(this, 'lockSsfi'); + flag(this, 'lockSsfi', true); + var result = getter(_super).call(this); + flag(this, 'lockSsfi', origLockSsfi); + + if (result !== undefined) { + return result; + } + + var newAssertion = new chai.Assertion(); + transferFlags(this, newAssertion); + return newAssertion; + } + , configurable: true + }); +}; + +},{"../../chai":2,"./flag":15,"./isProxyEnabled":25,"./transferFlags":32}],30:[function(require,module,exports){ +var config = require('../config'); +var flag = require('./flag'); +var getProperties = require('./getProperties'); +var isProxyEnabled = require('./isProxyEnabled'); + +/*! + * Chai - proxify utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/** + * ### .proxify(object) + * + * Return a proxy of given object that throws an error when a non-existent + * property is read. By default, the root cause is assumed to be a misspelled + * property, and thus an attempt is made to offer a reasonable suggestion from + * the list of existing properties. However, if a nonChainableMethodName is + * provided, then the root cause is instead a failure to invoke a non-chainable + * method prior to reading the non-existent property. + * + * If proxies are unsupported or disabled via the user's Chai config, then + * return object without modification. + * + * @param {Object} obj + * @param {String} nonChainableMethodName + * @namespace Utils + * @name proxify + */ + +var builtins = ['__flags', '__methods', '_obj', 'assert']; + +module.exports = function proxify(obj, nonChainableMethodName) { + if (!isProxyEnabled()) return obj; + + return new Proxy(obj, { + get: function proxyGetter(target, property) { + // This check is here because we should not throw errors on Symbol properties + // such as `Symbol.toStringTag`. + // The values for which an error should be thrown can be configured using + // the `config.proxyExcludedKeys` setting. + if (typeof property === 'string' && + config.proxyExcludedKeys.indexOf(property) === -1 && + !Reflect.has(target, property)) { + // Special message for invalid property access of non-chainable methods. + if (nonChainableMethodName) { + throw Error('Invalid Chai property: ' + nonChainableMethodName + '.' + + property + '. See docs for proper usage of "' + + nonChainableMethodName + '".'); + } + + // If the property is reasonably close to an existing Chai property, + // suggest that property to the user. Only suggest properties with a + // distance less than 4. + var suggestion = null; + var suggestionDistance = 4; + getProperties(target).forEach(function(prop) { + if ( + !Object.prototype.hasOwnProperty(prop) && + builtins.indexOf(prop) === -1 + ) { + var dist = stringDistanceCapped( + property, + prop, + suggestionDistance + ); + if (dist < suggestionDistance) { + suggestion = prop; + suggestionDistance = dist; + } + } + }); + + if (suggestion !== null) { + throw Error('Invalid Chai property: ' + property + + '. Did you mean "' + suggestion + '"?'); + } else { + throw Error('Invalid Chai property: ' + property); + } + } + + // Use this proxy getter as the starting point for removing implementation + // frames from the stack trace of a failed assertion. For property + // assertions, this prevents the proxy getter from showing up in the stack + // trace since it's invoked before the property getter. For method and + // chainable method assertions, this flag will end up getting changed to + // the method wrapper, which is good since this frame will no longer be in + // the stack once the method is invoked. Note that Chai builtin assertion + // properties such as `__flags` are skipped since this is only meant to + // capture the starting point of an assertion. This step is also skipped + // if the `lockSsfi` flag is set, thus indicating that this assertion is + // being called from within another assertion. In that case, the `ssfi` + // flag is already set to the outer assertion's starting point. + if (builtins.indexOf(property) === -1 && !flag(target, 'lockSsfi')) { + flag(target, 'ssfi', proxyGetter); + } + + return Reflect.get(target, property); + } + }); +}; + +/** + * # stringDistanceCapped(strA, strB, cap) + * Return the Levenshtein distance between two strings, but no more than cap. + * @param {string} strA + * @param {string} strB + * @param {number} number + * @return {number} min(string distance between strA and strB, cap) + * @api private + */ + +function stringDistanceCapped(strA, strB, cap) { + if (Math.abs(strA.length - strB.length) >= cap) { + return cap; + } + + var memo = []; + // `memo` is a two-dimensional array containing distances. + // memo[i][j] is the distance between strA.slice(0, i) and + // strB.slice(0, j). + for (var i = 0; i <= strA.length; i++) { + memo[i] = Array(strB.length + 1).fill(0); + memo[i][0] = i; + } + for (var j = 0; j < strB.length; j++) { + memo[0][j] = j; + } + + for (var i = 1; i <= strA.length; i++) { + var ch = strA.charCodeAt(i - 1); + for (var j = 1; j <= strB.length; j++) { + if (Math.abs(i - j) >= cap) { + memo[i][j] = cap; + continue; + } + memo[i][j] = Math.min( + memo[i - 1][j] + 1, + memo[i][j - 1] + 1, + memo[i - 1][j - 1] + + (ch === strB.charCodeAt(j - 1) ? 0 : 1) + ); + } + } + + return memo[strA.length][strB.length]; +} + +},{"../config":4,"./flag":15,"./getProperties":21,"./isProxyEnabled":25}],31:[function(require,module,exports){ +/*! + * Chai - test utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/*! + * Module dependencies + */ + +var flag = require('./flag'); + +/** + * ### .test(object, expression) + * + * Test and object for expression. + * + * @param {Object} object (constructed Assertion) + * @param {Arguments} chai.Assertion.prototype.assert arguments + * @namespace Utils + * @name test + */ + +module.exports = function test(obj, args) { + var negate = flag(obj, 'negate') + , expr = args[0]; + return negate ? !expr : expr; +}; + +},{"./flag":15}],32:[function(require,module,exports){ +/*! + * Chai - transferFlags utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/** + * ### .transferFlags(assertion, object, includeAll = true) + * + * Transfer all the flags for `assertion` to `object`. If + * `includeAll` is set to `false`, then the base Chai + * assertion flags (namely `object`, `ssfi`, `lockSsfi`, + * and `message`) will not be transferred. + * + * + * var newAssertion = new Assertion(); + * utils.transferFlags(assertion, newAssertion); + * + * var anotherAssertion = new Assertion(myObj); + * utils.transferFlags(assertion, anotherAssertion, false); + * + * @param {Assertion} assertion the assertion to transfer the flags from + * @param {Object} object the object to transfer the flags to; usually a new assertion + * @param {Boolean} includeAll + * @namespace Utils + * @name transferFlags + * @api private + */ + +module.exports = function transferFlags(assertion, object, includeAll) { + var flags = assertion.__flags || (assertion.__flags = Object.create(null)); + + if (!object.__flags) { + object.__flags = Object.create(null); + } + + includeAll = arguments.length === 3 ? includeAll : true; + + for (var flag in flags) { + if (includeAll || + (flag !== 'object' && flag !== 'ssfi' && flag !== 'lockSsfi' && flag != 'message')) { + object.__flags[flag] = flags[flag]; + } + } +}; + +},{}],33:[function(require,module,exports){ +/*! + * assertion-error + * Copyright(c) 2013 Jake Luer + * MIT Licensed + */ + +/*! + * Return a function that will copy properties from + * one object to another excluding any originally + * listed. Returned function will create a new `{}`. + * + * @param {String} excluded properties ... + * @return {Function} + */ + +function exclude () { + var excludes = [].slice.call(arguments); + + function excludeProps (res, obj) { + Object.keys(obj).forEach(function (key) { + if (!~excludes.indexOf(key)) res[key] = obj[key]; + }); + } + + return function extendExclude () { + var args = [].slice.call(arguments) + , i = 0 + , res = {}; + + for (; i < args.length; i++) { + excludeProps(res, args[i]); + } + + return res; + }; +}; + +/*! + * Primary Exports + */ + +module.exports = AssertionError; + +/** + * ### AssertionError + * + * An extension of the JavaScript `Error` constructor for + * assertion and validation scenarios. + * + * @param {String} message + * @param {Object} properties to include (optional) + * @param {callee} start stack function (optional) + */ + +function AssertionError (message, _props, ssf) { + var extend = exclude('name', 'message', 'stack', 'constructor', 'toJSON') + , props = extend(_props || {}); + + // default values + this.message = message || 'Unspecified AssertionError'; + this.showDiff = false; + + // copy from properties + for (var key in props) { + this[key] = props[key]; + } + + // capture stack trace + ssf = ssf || AssertionError; + if (Error.captureStackTrace) { + Error.captureStackTrace(this, ssf); + } else { + try { + throw new Error(); + } catch(e) { + this.stack = e.stack; + } + } +} + +/*! + * Inherit from Error.prototype + */ + +AssertionError.prototype = Object.create(Error.prototype); + +/*! + * Statically set name + */ + +AssertionError.prototype.name = 'AssertionError'; + +/*! + * Ensure correct constructor + */ + +AssertionError.prototype.constructor = AssertionError; + +/** + * Allow errors to be converted to JSON for static transfer. + * + * @param {Boolean} include stack (default: `true`) + * @return {Object} object that can be `JSON.stringify` + */ + +AssertionError.prototype.toJSON = function (stack) { + var extend = exclude('constructor', 'toJSON', 'stack') + , props = extend({ name: this.name }, this); + + // include stack if exists and not turned off + if (false !== stack && this.stack) { + props.stack = this.stack; + } + + return props; +}; + +},{}],34:[function(require,module,exports){ +'use strict'; + +/* ! + * Chai - checkError utility + * Copyright(c) 2012-2016 Jake Luer + * MIT Licensed + */ + +/** + * ### .checkError + * + * Checks that an error conforms to a given set of criteria and/or retrieves information about it. + * + * @api public + */ + +/** + * ### .compatibleInstance(thrown, errorLike) + * + * Checks if two instances are compatible (strict equal). + * Returns false if errorLike is not an instance of Error, because instances + * can only be compatible if they're both error instances. + * + * @name compatibleInstance + * @param {Error} thrown error + * @param {Error|ErrorConstructor} errorLike object to compare against + * @namespace Utils + * @api public + */ + +function compatibleInstance(thrown, errorLike) { + return errorLike instanceof Error && thrown === errorLike; +} + +/** + * ### .compatibleConstructor(thrown, errorLike) + * + * Checks if two constructors are compatible. + * This function can receive either an error constructor or + * an error instance as the `errorLike` argument. + * Constructors are compatible if they're the same or if one is + * an instance of another. + * + * @name compatibleConstructor + * @param {Error} thrown error + * @param {Error|ErrorConstructor} errorLike object to compare against + * @namespace Utils + * @api public + */ + +function compatibleConstructor(thrown, errorLike) { + if (errorLike instanceof Error) { + // If `errorLike` is an instance of any error we compare their constructors + return thrown.constructor === errorLike.constructor || thrown instanceof errorLike.constructor; + } else if (errorLike.prototype instanceof Error || errorLike === Error) { + // If `errorLike` is a constructor that inherits from Error, we compare `thrown` to `errorLike` directly + return thrown.constructor === errorLike || thrown instanceof errorLike; + } + + return false; +} + +/** + * ### .compatibleMessage(thrown, errMatcher) + * + * Checks if an error's message is compatible with a matcher (String or RegExp). + * If the message contains the String or passes the RegExp test, + * it is considered compatible. + * + * @name compatibleMessage + * @param {Error} thrown error + * @param {String|RegExp} errMatcher to look for into the message + * @namespace Utils + * @api public + */ + +function compatibleMessage(thrown, errMatcher) { + var comparisonString = typeof thrown === 'string' ? thrown : thrown.message; + if (errMatcher instanceof RegExp) { + return errMatcher.test(comparisonString); + } else if (typeof errMatcher === 'string') { + return comparisonString.indexOf(errMatcher) !== -1; // eslint-disable-line no-magic-numbers + } + + return false; +} + +/** + * ### .getFunctionName(constructorFn) + * + * Returns the name of a function. + * This also includes a polyfill function if `constructorFn.name` is not defined. + * + * @name getFunctionName + * @param {Function} constructorFn + * @namespace Utils + * @api private + */ + +var functionNameMatch = /\s*function(?:\s|\s*\/\*[^(?:*\/)]+\*\/\s*)*([^\(\/]+)/; +function getFunctionName(constructorFn) { + var name = ''; + if (typeof constructorFn.name === 'undefined') { + // Here we run a polyfill if constructorFn.name is not defined + var match = String(constructorFn).match(functionNameMatch); + if (match) { + name = match[1]; + } + } else { + name = constructorFn.name; + } + + return name; +} + +/** + * ### .getConstructorName(errorLike) + * + * Gets the constructor name for an Error instance or constructor itself. + * + * @name getConstructorName + * @param {Error|ErrorConstructor} errorLike + * @namespace Utils + * @api public + */ + +function getConstructorName(errorLike) { + var constructorName = errorLike; + if (errorLike instanceof Error) { + constructorName = getFunctionName(errorLike.constructor); + } else if (typeof errorLike === 'function') { + // If `err` is not an instance of Error it is an error constructor itself or another function. + // If we've got a common function we get its name, otherwise we may need to create a new instance + // of the error just in case it's a poorly-constructed error. Please see chaijs/chai/issues/45 to know more. + constructorName = getFunctionName(errorLike).trim() || + getFunctionName(new errorLike()); // eslint-disable-line new-cap + } + + return constructorName; +} + +/** + * ### .getMessage(errorLike) + * + * Gets the error message from an error. + * If `err` is a String itself, we return it. + * If the error has no message, we return an empty string. + * + * @name getMessage + * @param {Error|String} errorLike + * @namespace Utils + * @api public + */ + +function getMessage(errorLike) { + var msg = ''; + if (errorLike && errorLike.message) { + msg = errorLike.message; + } else if (typeof errorLike === 'string') { + msg = errorLike; + } + + return msg; +} + +module.exports = { + compatibleInstance: compatibleInstance, + compatibleConstructor: compatibleConstructor, + compatibleMessage: compatibleMessage, + getMessage: getMessage, + getConstructorName: getConstructorName, +}; + +},{}],35:[function(require,module,exports){ +'use strict'; +/* globals Symbol: false, Uint8Array: false, WeakMap: false */ +/*! + * deep-eql + * Copyright(c) 2013 Jake Luer + * MIT Licensed + */ + +var type = require('type-detect'); +function FakeMap() { + this._key = 'chai/deep-eql__' + Math.random() + Date.now(); +} + +FakeMap.prototype = { + get: function getMap(key) { + return key[this._key]; + }, + set: function setMap(key, value) { + if (Object.isExtensible(key)) { + Object.defineProperty(key, this._key, { + value: value, + configurable: true, + }); + } + }, +}; + +var MemoizeMap = typeof WeakMap === 'function' ? WeakMap : FakeMap; +/*! + * Check to see if the MemoizeMap has recorded a result of the two operands + * + * @param {Mixed} leftHandOperand + * @param {Mixed} rightHandOperand + * @param {MemoizeMap} memoizeMap + * @returns {Boolean|null} result +*/ +function memoizeCompare(leftHandOperand, rightHandOperand, memoizeMap) { + // Technically, WeakMap keys can *only* be objects, not primitives. + if (!memoizeMap || isPrimitive(leftHandOperand) || isPrimitive(rightHandOperand)) { + return null; + } + var leftHandMap = memoizeMap.get(leftHandOperand); + if (leftHandMap) { + var result = leftHandMap.get(rightHandOperand); + if (typeof result === 'boolean') { + return result; + } + } + return null; +} + +/*! + * Set the result of the equality into the MemoizeMap + * + * @param {Mixed} leftHandOperand + * @param {Mixed} rightHandOperand + * @param {MemoizeMap} memoizeMap + * @param {Boolean} result +*/ +function memoizeSet(leftHandOperand, rightHandOperand, memoizeMap, result) { + // Technically, WeakMap keys can *only* be objects, not primitives. + if (!memoizeMap || isPrimitive(leftHandOperand) || isPrimitive(rightHandOperand)) { + return; + } + var leftHandMap = memoizeMap.get(leftHandOperand); + if (leftHandMap) { + leftHandMap.set(rightHandOperand, result); + } else { + leftHandMap = new MemoizeMap(); + leftHandMap.set(rightHandOperand, result); + memoizeMap.set(leftHandOperand, leftHandMap); + } +} + +/*! + * Primary Export + */ + +module.exports = deepEqual; +module.exports.MemoizeMap = MemoizeMap; + +/** + * Assert deeply nested sameValue equality between two objects of any type. + * + * @param {Mixed} leftHandOperand + * @param {Mixed} rightHandOperand + * @param {Object} [options] (optional) Additional options + * @param {Array} [options.comparator] (optional) Override default algorithm, determining custom equality. + * @param {Array} [options.memoize] (optional) Provide a custom memoization object which will cache the results of + complex objects for a speed boost. By passing `false` you can disable memoization, but this will cause circular + references to blow the stack. + * @return {Boolean} equal match + */ +function deepEqual(leftHandOperand, rightHandOperand, options) { + // If we have a comparator, we can't assume anything; so bail to its check first. + if (options && options.comparator) { + return extensiveDeepEqual(leftHandOperand, rightHandOperand, options); + } + + var simpleResult = simpleEqual(leftHandOperand, rightHandOperand); + if (simpleResult !== null) { + return simpleResult; + } + + // Deeper comparisons are pushed through to a larger function + return extensiveDeepEqual(leftHandOperand, rightHandOperand, options); +} + +/** + * Many comparisons can be canceled out early via simple equality or primitive checks. + * @param {Mixed} leftHandOperand + * @param {Mixed} rightHandOperand + * @return {Boolean|null} equal match + */ +function simpleEqual(leftHandOperand, rightHandOperand) { + // Equal references (except for Numbers) can be returned early + if (leftHandOperand === rightHandOperand) { + // Handle +-0 cases + return leftHandOperand !== 0 || 1 / leftHandOperand === 1 / rightHandOperand; + } + + // handle NaN cases + if ( + leftHandOperand !== leftHandOperand && // eslint-disable-line no-self-compare + rightHandOperand !== rightHandOperand // eslint-disable-line no-self-compare + ) { + return true; + } + + // Anything that is not an 'object', i.e. symbols, functions, booleans, numbers, + // strings, and undefined, can be compared by reference. + if (isPrimitive(leftHandOperand) || isPrimitive(rightHandOperand)) { + // Easy out b/c it would have passed the first equality check + return false; + } + return null; +} + +/*! + * The main logic of the `deepEqual` function. + * + * @param {Mixed} leftHandOperand + * @param {Mixed} rightHandOperand + * @param {Object} [options] (optional) Additional options + * @param {Array} [options.comparator] (optional) Override default algorithm, determining custom equality. + * @param {Array} [options.memoize] (optional) Provide a custom memoization object which will cache the results of + complex objects for a speed boost. By passing `false` you can disable memoization, but this will cause circular + references to blow the stack. + * @return {Boolean} equal match +*/ +function extensiveDeepEqual(leftHandOperand, rightHandOperand, options) { + options = options || {}; + options.memoize = options.memoize === false ? false : options.memoize || new MemoizeMap(); + var comparator = options && options.comparator; + + // Check if a memoized result exists. + var memoizeResultLeft = memoizeCompare(leftHandOperand, rightHandOperand, options.memoize); + if (memoizeResultLeft !== null) { + return memoizeResultLeft; + } + var memoizeResultRight = memoizeCompare(rightHandOperand, leftHandOperand, options.memoize); + if (memoizeResultRight !== null) { + return memoizeResultRight; + } + + // If a comparator is present, use it. + if (comparator) { + var comparatorResult = comparator(leftHandOperand, rightHandOperand); + // Comparators may return null, in which case we want to go back to default behavior. + if (comparatorResult === false || comparatorResult === true) { + memoizeSet(leftHandOperand, rightHandOperand, options.memoize, comparatorResult); + return comparatorResult; + } + // To allow comparators to override *any* behavior, we ran them first. Since it didn't decide + // what to do, we need to make sure to return the basic tests first before we move on. + var simpleResult = simpleEqual(leftHandOperand, rightHandOperand); + if (simpleResult !== null) { + // Don't memoize this, it takes longer to set/retrieve than to just compare. + return simpleResult; + } + } + + var leftHandType = type(leftHandOperand); + if (leftHandType !== type(rightHandOperand)) { + memoizeSet(leftHandOperand, rightHandOperand, options.memoize, false); + return false; + } + + // Temporarily set the operands in the memoize object to prevent blowing the stack + memoizeSet(leftHandOperand, rightHandOperand, options.memoize, true); + + var result = extensiveDeepEqualByType(leftHandOperand, rightHandOperand, leftHandType, options); + memoizeSet(leftHandOperand, rightHandOperand, options.memoize, result); + return result; +} + +function extensiveDeepEqualByType(leftHandOperand, rightHandOperand, leftHandType, options) { + switch (leftHandType) { + case 'String': + case 'Number': + case 'Boolean': + case 'Date': + // If these types are their instance types (e.g. `new Number`) then re-deepEqual against their values + return deepEqual(leftHandOperand.valueOf(), rightHandOperand.valueOf()); + case 'Promise': + case 'Symbol': + case 'function': + case 'WeakMap': + case 'WeakSet': + case 'Error': + return leftHandOperand === rightHandOperand; + case 'Arguments': + case 'Int8Array': + case 'Uint8Array': + case 'Uint8ClampedArray': + case 'Int16Array': + case 'Uint16Array': + case 'Int32Array': + case 'Uint32Array': + case 'Float32Array': + case 'Float64Array': + case 'Array': + return iterableEqual(leftHandOperand, rightHandOperand, options); + case 'RegExp': + return regexpEqual(leftHandOperand, rightHandOperand); + case 'Generator': + return generatorEqual(leftHandOperand, rightHandOperand, options); + case 'DataView': + return iterableEqual(new Uint8Array(leftHandOperand.buffer), new Uint8Array(rightHandOperand.buffer), options); + case 'ArrayBuffer': + return iterableEqual(new Uint8Array(leftHandOperand), new Uint8Array(rightHandOperand), options); + case 'Set': + return entriesEqual(leftHandOperand, rightHandOperand, options); + case 'Map': + return entriesEqual(leftHandOperand, rightHandOperand, options); + default: + return objectEqual(leftHandOperand, rightHandOperand, options); + } +} + +/*! + * Compare two Regular Expressions for equality. + * + * @param {RegExp} leftHandOperand + * @param {RegExp} rightHandOperand + * @return {Boolean} result + */ + +function regexpEqual(leftHandOperand, rightHandOperand) { + return leftHandOperand.toString() === rightHandOperand.toString(); +} + +/*! + * Compare two Sets/Maps for equality. Faster than other equality functions. + * + * @param {Set} leftHandOperand + * @param {Set} rightHandOperand + * @param {Object} [options] (Optional) + * @return {Boolean} result + */ + +function entriesEqual(leftHandOperand, rightHandOperand, options) { + // IE11 doesn't support Set#entries or Set#@@iterator, so we need manually populate using Set#forEach + if (leftHandOperand.size !== rightHandOperand.size) { + return false; + } + if (leftHandOperand.size === 0) { + return true; + } + var leftHandItems = []; + var rightHandItems = []; + leftHandOperand.forEach(function gatherEntries(key, value) { + leftHandItems.push([ key, value ]); + }); + rightHandOperand.forEach(function gatherEntries(key, value) { + rightHandItems.push([ key, value ]); + }); + return iterableEqual(leftHandItems.sort(), rightHandItems.sort(), options); +} + +/*! + * Simple equality for flat iterable objects such as Arrays, TypedArrays or Node.js buffers. + * + * @param {Iterable} leftHandOperand + * @param {Iterable} rightHandOperand + * @param {Object} [options] (Optional) + * @return {Boolean} result + */ + +function iterableEqual(leftHandOperand, rightHandOperand, options) { + var length = leftHandOperand.length; + if (length !== rightHandOperand.length) { + return false; + } + if (length === 0) { + return true; + } + var index = -1; + while (++index < length) { + if (deepEqual(leftHandOperand[index], rightHandOperand[index], options) === false) { + return false; + } + } + return true; +} + +/*! + * Simple equality for generator objects such as those returned by generator functions. + * + * @param {Iterable} leftHandOperand + * @param {Iterable} rightHandOperand + * @param {Object} [options] (Optional) + * @return {Boolean} result + */ + +function generatorEqual(leftHandOperand, rightHandOperand, options) { + return iterableEqual(getGeneratorEntries(leftHandOperand), getGeneratorEntries(rightHandOperand), options); +} + +/*! + * Determine if the given object has an @@iterator function. + * + * @param {Object} target + * @return {Boolean} `true` if the object has an @@iterator function. + */ +function hasIteratorFunction(target) { + return typeof Symbol !== 'undefined' && + typeof target === 'object' && + typeof Symbol.iterator !== 'undefined' && + typeof target[Symbol.iterator] === 'function'; +} + +/*! + * Gets all iterator entries from the given Object. If the Object has no @@iterator function, returns an empty array. + * This will consume the iterator - which could have side effects depending on the @@iterator implementation. + * + * @param {Object} target + * @returns {Array} an array of entries from the @@iterator function + */ +function getIteratorEntries(target) { + if (hasIteratorFunction(target)) { + try { + return getGeneratorEntries(target[Symbol.iterator]()); + } catch (iteratorError) { + return []; + } + } + return []; +} + +/*! + * Gets all entries from a Generator. This will consume the generator - which could have side effects. + * + * @param {Generator} target + * @returns {Array} an array of entries from the Generator. + */ +function getGeneratorEntries(generator) { + var generatorResult = generator.next(); + var accumulator = [ generatorResult.value ]; + while (generatorResult.done === false) { + generatorResult = generator.next(); + accumulator.push(generatorResult.value); + } + return accumulator; +} + +/*! + * Gets all own and inherited enumerable keys from a target. + * + * @param {Object} target + * @returns {Array} an array of own and inherited enumerable keys from the target. + */ +function getEnumerableKeys(target) { + var keys = []; + for (var key in target) { + keys.push(key); + } + return keys; +} + +/*! + * Determines if two objects have matching values, given a set of keys. Defers to deepEqual for the equality check of + * each key. If any value of the given key is not equal, the function will return false (early). + * + * @param {Mixed} leftHandOperand + * @param {Mixed} rightHandOperand + * @param {Array} keys An array of keys to compare the values of leftHandOperand and rightHandOperand against + * @param {Object} [options] (Optional) + * @return {Boolean} result + */ +function keysEqual(leftHandOperand, rightHandOperand, keys, options) { + var length = keys.length; + if (length === 0) { + return true; + } + for (var i = 0; i < length; i += 1) { + if (deepEqual(leftHandOperand[keys[i]], rightHandOperand[keys[i]], options) === false) { + return false; + } + } + return true; +} + +/*! + * Recursively check the equality of two Objects. Once basic sameness has been established it will defer to `deepEqual` + * for each enumerable key in the object. + * + * @param {Mixed} leftHandOperand + * @param {Mixed} rightHandOperand + * @param {Object} [options] (Optional) + * @return {Boolean} result + */ + +function objectEqual(leftHandOperand, rightHandOperand, options) { + var leftHandKeys = getEnumerableKeys(leftHandOperand); + var rightHandKeys = getEnumerableKeys(rightHandOperand); + if (leftHandKeys.length && leftHandKeys.length === rightHandKeys.length) { + leftHandKeys.sort(); + rightHandKeys.sort(); + if (iterableEqual(leftHandKeys, rightHandKeys) === false) { + return false; + } + return keysEqual(leftHandOperand, rightHandOperand, leftHandKeys, options); + } + + var leftHandEntries = getIteratorEntries(leftHandOperand); + var rightHandEntries = getIteratorEntries(rightHandOperand); + if (leftHandEntries.length && leftHandEntries.length === rightHandEntries.length) { + leftHandEntries.sort(); + rightHandEntries.sort(); + return iterableEqual(leftHandEntries, rightHandEntries, options); + } + + if (leftHandKeys.length === 0 && + leftHandEntries.length === 0 && + rightHandKeys.length === 0 && + rightHandEntries.length === 0) { + return true; + } + + return false; +} + +/*! + * Returns true if the argument is a primitive. + * + * This intentionally returns true for all objects that can be compared by reference, + * including functions and symbols. + * + * @param {Mixed} value + * @return {Boolean} result + */ +function isPrimitive(value) { + return value === null || typeof value !== 'object'; +} + +},{"type-detect":38}],36:[function(require,module,exports){ +'use strict'; + +/* ! + * Chai - getFuncName utility + * Copyright(c) 2012-2016 Jake Luer + * MIT Licensed + */ + +/** + * ### .getFuncName(constructorFn) + * + * Returns the name of a function. + * When a non-function instance is passed, returns `null`. + * This also includes a polyfill function if `aFunc.name` is not defined. + * + * @name getFuncName + * @param {Function} funct + * @namespace Utils + * @api public + */ + +var toString = Function.prototype.toString; +var functionNameMatch = /\s*function(?:\s|\s*\/\*[^(?:*\/)]+\*\/\s*)*([^\s\(\/]+)/; +function getFuncName(aFunc) { + if (typeof aFunc !== 'function') { + return null; + } + + var name = ''; + if (typeof Function.prototype.name === 'undefined' && typeof aFunc.name === 'undefined') { + // Here we run a polyfill if Function does not support the `name` property and if aFunc.name is not defined + var match = toString.call(aFunc).match(functionNameMatch); + if (match) { + name = match[1]; + } + } else { + // If we've got a `name` property we just use it + name = aFunc.name; + } + + return name; +} + +module.exports = getFuncName; + +},{}],37:[function(require,module,exports){ +'use strict'; + +/* ! + * Chai - pathval utility + * Copyright(c) 2012-2014 Jake Luer + * @see https://github.com/logicalparadox/filtr + * MIT Licensed + */ + +/** + * ### .hasProperty(object, name) + * + * This allows checking whether an object has own + * or inherited from prototype chain named property. + * + * Basically does the same thing as the `in` + * operator but works properly with null/undefined values + * and other primitives. + * + * var obj = { + * arr: ['a', 'b', 'c'] + * , str: 'Hello' + * } + * + * The following would be the results. + * + * hasProperty(obj, 'str'); // true + * hasProperty(obj, 'constructor'); // true + * hasProperty(obj, 'bar'); // false + * + * hasProperty(obj.str, 'length'); // true + * hasProperty(obj.str, 1); // true + * hasProperty(obj.str, 5); // false + * + * hasProperty(obj.arr, 'length'); // true + * hasProperty(obj.arr, 2); // true + * hasProperty(obj.arr, 3); // false + * + * @param {Object} object + * @param {String|Symbol} name + * @returns {Boolean} whether it exists + * @namespace Utils + * @name hasProperty + * @api public + */ + +function hasProperty(obj, name) { + if (typeof obj === 'undefined' || obj === null) { + return false; + } + + // The `in` operator does not work with primitives. + return name in Object(obj); +} + +/* ! + * ## parsePath(path) + * + * Helper function used to parse string object + * paths. Use in conjunction with `internalGetPathValue`. + * + * var parsed = parsePath('myobject.property.subprop'); + * + * ### Paths: + * + * * Can be infinitely deep and nested. + * * Arrays are also valid using the formal `myobject.document[3].property`. + * * Literal dots and brackets (not delimiter) must be backslash-escaped. + * + * @param {String} path + * @returns {Object} parsed + * @api private + */ + +function parsePath(path) { + var str = path.replace(/([^\\])\[/g, '$1.['); + var parts = str.match(/(\\\.|[^.]+?)+/g); + return parts.map(function mapMatches(value) { + var regexp = /^\[(\d+)\]$/; + var mArr = regexp.exec(value); + var parsed = null; + if (mArr) { + parsed = { i: parseFloat(mArr[1]) }; + } else { + parsed = { p: value.replace(/\\([.\[\]])/g, '$1') }; + } + + return parsed; + }); +} + +/* ! + * ## internalGetPathValue(obj, parsed[, pathDepth]) + * + * Helper companion function for `.parsePath` that returns + * the value located at the parsed address. + * + * var value = getPathValue(obj, parsed); + * + * @param {Object} object to search against + * @param {Object} parsed definition from `parsePath`. + * @param {Number} depth (nesting level) of the property we want to retrieve + * @returns {Object|Undefined} value + * @api private + */ + +function internalGetPathValue(obj, parsed, pathDepth) { + var temporaryValue = obj; + var res = null; + pathDepth = (typeof pathDepth === 'undefined' ? parsed.length : pathDepth); + + for (var i = 0; i < pathDepth; i++) { + var part = parsed[i]; + if (temporaryValue) { + if (typeof part.p === 'undefined') { + temporaryValue = temporaryValue[part.i]; + } else { + temporaryValue = temporaryValue[part.p]; + } + + if (i === (pathDepth - 1)) { + res = temporaryValue; + } + } + } + + return res; +} + +/* ! + * ## internalSetPathValue(obj, value, parsed) + * + * Companion function for `parsePath` that sets + * the value located at a parsed address. + * + * internalSetPathValue(obj, 'value', parsed); + * + * @param {Object} object to search and define on + * @param {*} value to use upon set + * @param {Object} parsed definition from `parsePath` + * @api private + */ + +function internalSetPathValue(obj, val, parsed) { + var tempObj = obj; + var pathDepth = parsed.length; + var part = null; + // Here we iterate through every part of the path + for (var i = 0; i < pathDepth; i++) { + var propName = null; + var propVal = null; + part = parsed[i]; + + // If it's the last part of the path, we set the 'propName' value with the property name + if (i === (pathDepth - 1)) { + propName = typeof part.p === 'undefined' ? part.i : part.p; + // Now we set the property with the name held by 'propName' on object with the desired val + tempObj[propName] = val; + } else if (typeof part.p !== 'undefined' && tempObj[part.p]) { + tempObj = tempObj[part.p]; + } else if (typeof part.i !== 'undefined' && tempObj[part.i]) { + tempObj = tempObj[part.i]; + } else { + // If the obj doesn't have the property we create one with that name to define it + var next = parsed[i + 1]; + // Here we set the name of the property which will be defined + propName = typeof part.p === 'undefined' ? part.i : part.p; + // Here we decide if this property will be an array or a new object + propVal = typeof next.p === 'undefined' ? [] : {}; + tempObj[propName] = propVal; + tempObj = tempObj[propName]; + } + } +} + +/** + * ### .getPathInfo(object, path) + * + * This allows the retrieval of property info in an + * object given a string path. + * + * The path info consists of an object with the + * following properties: + * + * * parent - The parent object of the property referenced by `path` + * * name - The name of the final property, a number if it was an array indexer + * * value - The value of the property, if it exists, otherwise `undefined` + * * exists - Whether the property exists or not + * + * @param {Object} object + * @param {String} path + * @returns {Object} info + * @namespace Utils + * @name getPathInfo + * @api public + */ + +function getPathInfo(obj, path) { + var parsed = parsePath(path); + var last = parsed[parsed.length - 1]; + var info = { + parent: parsed.length > 1 ? internalGetPathValue(obj, parsed, parsed.length - 1) : obj, + name: last.p || last.i, + value: internalGetPathValue(obj, parsed), + }; + info.exists = hasProperty(info.parent, info.name); + + return info; +} + +/** + * ### .getPathValue(object, path) + * + * This allows the retrieval of values in an + * object given a string path. + * + * var obj = { + * prop1: { + * arr: ['a', 'b', 'c'] + * , str: 'Hello' + * } + * , prop2: { + * arr: [ { nested: 'Universe' } ] + * , str: 'Hello again!' + * } + * } + * + * The following would be the results. + * + * getPathValue(obj, 'prop1.str'); // Hello + * getPathValue(obj, 'prop1.att[2]'); // b + * getPathValue(obj, 'prop2.arr[0].nested'); // Universe + * + * @param {Object} object + * @param {String} path + * @returns {Object} value or `undefined` + * @namespace Utils + * @name getPathValue + * @api public + */ + +function getPathValue(obj, path) { + var info = getPathInfo(obj, path); + return info.value; +} + +/** + * ### .setPathValue(object, path, value) + * + * Define the value in an object at a given string path. + * + * ```js + * var obj = { + * prop1: { + * arr: ['a', 'b', 'c'] + * , str: 'Hello' + * } + * , prop2: { + * arr: [ { nested: 'Universe' } ] + * , str: 'Hello again!' + * } + * }; + * ``` + * + * The following would be acceptable. + * + * ```js + * var properties = require('tea-properties'); + * properties.set(obj, 'prop1.str', 'Hello Universe!'); + * properties.set(obj, 'prop1.arr[2]', 'B'); + * properties.set(obj, 'prop2.arr[0].nested.value', { hello: 'universe' }); + * ``` + * + * @param {Object} object + * @param {String} path + * @param {Mixed} value + * @api private + */ + +function setPathValue(obj, path, val) { + var parsed = parsePath(path); + internalSetPathValue(obj, val, parsed); + return obj; +} + +module.exports = { + hasProperty: hasProperty, + getPathInfo: getPathInfo, + getPathValue: getPathValue, + setPathValue: setPathValue, +}; + +},{}],38:[function(require,module,exports){ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global.typeDetect = factory()); +}(this, (function () { 'use strict'; + +/* ! + * type-detect + * Copyright(c) 2013 jake luer + * MIT Licensed + */ +var promiseExists = typeof Promise === 'function'; + +/* eslint-disable no-undef */ +var globalObject = typeof self === 'object' ? self : global; // eslint-disable-line id-blacklist + +var symbolExists = typeof Symbol !== 'undefined'; +var mapExists = typeof Map !== 'undefined'; +var setExists = typeof Set !== 'undefined'; +var weakMapExists = typeof WeakMap !== 'undefined'; +var weakSetExists = typeof WeakSet !== 'undefined'; +var dataViewExists = typeof DataView !== 'undefined'; +var symbolIteratorExists = symbolExists && typeof Symbol.iterator !== 'undefined'; +var symbolToStringTagExists = symbolExists && typeof Symbol.toStringTag !== 'undefined'; +var setEntriesExists = setExists && typeof Set.prototype.entries === 'function'; +var mapEntriesExists = mapExists && typeof Map.prototype.entries === 'function'; +var setIteratorPrototype = setEntriesExists && Object.getPrototypeOf(new Set().entries()); +var mapIteratorPrototype = mapEntriesExists && Object.getPrototypeOf(new Map().entries()); +var arrayIteratorExists = symbolIteratorExists && typeof Array.prototype[Symbol.iterator] === 'function'; +var arrayIteratorPrototype = arrayIteratorExists && Object.getPrototypeOf([][Symbol.iterator]()); +var stringIteratorExists = symbolIteratorExists && typeof String.prototype[Symbol.iterator] === 'function'; +var stringIteratorPrototype = stringIteratorExists && Object.getPrototypeOf(''[Symbol.iterator]()); +var toStringLeftSliceLength = 8; +var toStringRightSliceLength = -1; +/** + * ### typeOf (obj) + * + * Uses `Object.prototype.toString` to determine the type of an object, + * normalising behaviour across engine versions & well optimised. + * + * @param {Mixed} object + * @return {String} object type + * @api public + */ +function typeDetect(obj) { + /* ! Speed optimisation + * Pre: + * string literal x 3,039,035 ops/sec ±1.62% (78 runs sampled) + * boolean literal x 1,424,138 ops/sec ±4.54% (75 runs sampled) + * number literal x 1,653,153 ops/sec ±1.91% (82 runs sampled) + * undefined x 9,978,660 ops/sec ±1.92% (75 runs sampled) + * function x 2,556,769 ops/sec ±1.73% (77 runs sampled) + * Post: + * string literal x 38,564,796 ops/sec ±1.15% (79 runs sampled) + * boolean literal x 31,148,940 ops/sec ±1.10% (79 runs sampled) + * number literal x 32,679,330 ops/sec ±1.90% (78 runs sampled) + * undefined x 32,363,368 ops/sec ±1.07% (82 runs sampled) + * function x 31,296,870 ops/sec ±0.96% (83 runs sampled) + */ + var typeofObj = typeof obj; + if (typeofObj !== 'object') { + return typeofObj; + } + + /* ! Speed optimisation + * Pre: + * null x 28,645,765 ops/sec ±1.17% (82 runs sampled) + * Post: + * null x 36,428,962 ops/sec ±1.37% (84 runs sampled) + */ + if (obj === null) { + return 'null'; + } + + /* ! Spec Conformance + * Test: `Object.prototype.toString.call(window)`` + * - Node === "[object global]" + * - Chrome === "[object global]" + * - Firefox === "[object Window]" + * - PhantomJS === "[object Window]" + * - Safari === "[object Window]" + * - IE 11 === "[object Window]" + * - IE Edge === "[object Window]" + * Test: `Object.prototype.toString.call(this)`` + * - Chrome Worker === "[object global]" + * - Firefox Worker === "[object DedicatedWorkerGlobalScope]" + * - Safari Worker === "[object DedicatedWorkerGlobalScope]" + * - IE 11 Worker === "[object WorkerGlobalScope]" + * - IE Edge Worker === "[object WorkerGlobalScope]" + */ + if (obj === globalObject) { + return 'global'; + } + + /* ! Speed optimisation + * Pre: + * array literal x 2,888,352 ops/sec ±0.67% (82 runs sampled) + * Post: + * array literal x 22,479,650 ops/sec ±0.96% (81 runs sampled) + */ + if ( + Array.isArray(obj) && + (symbolToStringTagExists === false || !(Symbol.toStringTag in obj)) + ) { + return 'Array'; + } + + // Not caching existence of `window` and related properties due to potential + // for `window` to be unset before tests in quasi-browser environments. + if (typeof window === 'object' && window !== null) { + /* ! Spec Conformance + * (https://html.spec.whatwg.org/multipage/browsers.html#location) + * WhatWG HTML$7.7.3 - The `Location` interface + * Test: `Object.prototype.toString.call(window.location)`` + * - IE <=11 === "[object Object]" + * - IE Edge <=13 === "[object Object]" + */ + if (typeof window.location === 'object' && obj === window.location) { + return 'Location'; + } + + /* ! Spec Conformance + * (https://html.spec.whatwg.org/#document) + * WhatWG HTML$3.1.1 - The `Document` object + * Note: Most browsers currently adher to the W3C DOM Level 2 spec + * (https://www.w3.org/TR/DOM-Level-2-HTML/html.html#ID-26809268) + * which suggests that browsers should use HTMLTableCellElement for + * both TD and TH elements. WhatWG separates these. + * WhatWG HTML states: + * > For historical reasons, Window objects must also have a + * > writable, configurable, non-enumerable property named + * > HTMLDocument whose value is the Document interface object. + * Test: `Object.prototype.toString.call(document)`` + * - Chrome === "[object HTMLDocument]" + * - Firefox === "[object HTMLDocument]" + * - Safari === "[object HTMLDocument]" + * - IE <=10 === "[object Document]" + * - IE 11 === "[object HTMLDocument]" + * - IE Edge <=13 === "[object HTMLDocument]" + */ + if (typeof window.document === 'object' && obj === window.document) { + return 'Document'; + } + + if (typeof window.navigator === 'object') { + /* ! Spec Conformance + * (https://html.spec.whatwg.org/multipage/webappapis.html#mimetypearray) + * WhatWG HTML$8.6.1.5 - Plugins - Interface MimeTypeArray + * Test: `Object.prototype.toString.call(navigator.mimeTypes)`` + * - IE <=10 === "[object MSMimeTypesCollection]" + */ + if (typeof window.navigator.mimeTypes === 'object' && + obj === window.navigator.mimeTypes) { + return 'MimeTypeArray'; + } + + /* ! Spec Conformance + * (https://html.spec.whatwg.org/multipage/webappapis.html#pluginarray) + * WhatWG HTML$8.6.1.5 - Plugins - Interface PluginArray + * Test: `Object.prototype.toString.call(navigator.plugins)`` + * - IE <=10 === "[object MSPluginsCollection]" + */ + if (typeof window.navigator.plugins === 'object' && + obj === window.navigator.plugins) { + return 'PluginArray'; + } + } + + if ((typeof window.HTMLElement === 'function' || + typeof window.HTMLElement === 'object') && + obj instanceof window.HTMLElement) { + /* ! Spec Conformance + * (https://html.spec.whatwg.org/multipage/webappapis.html#pluginarray) + * WhatWG HTML$4.4.4 - The `blockquote` element - Interface `HTMLQuoteElement` + * Test: `Object.prototype.toString.call(document.createElement('blockquote'))`` + * - IE <=10 === "[object HTMLBlockElement]" + */ + if (obj.tagName === 'BLOCKQUOTE') { + return 'HTMLQuoteElement'; + } + + /* ! Spec Conformance + * (https://html.spec.whatwg.org/#htmltabledatacellelement) + * WhatWG HTML$4.9.9 - The `td` element - Interface `HTMLTableDataCellElement` + * Note: Most browsers currently adher to the W3C DOM Level 2 spec + * (https://www.w3.org/TR/DOM-Level-2-HTML/html.html#ID-82915075) + * which suggests that browsers should use HTMLTableCellElement for + * both TD and TH elements. WhatWG separates these. + * Test: Object.prototype.toString.call(document.createElement('td')) + * - Chrome === "[object HTMLTableCellElement]" + * - Firefox === "[object HTMLTableCellElement]" + * - Safari === "[object HTMLTableCellElement]" + */ + if (obj.tagName === 'TD') { + return 'HTMLTableDataCellElement'; + } + + /* ! Spec Conformance + * (https://html.spec.whatwg.org/#htmltableheadercellelement) + * WhatWG HTML$4.9.9 - The `td` element - Interface `HTMLTableHeaderCellElement` + * Note: Most browsers currently adher to the W3C DOM Level 2 spec + * (https://www.w3.org/TR/DOM-Level-2-HTML/html.html#ID-82915075) + * which suggests that browsers should use HTMLTableCellElement for + * both TD and TH elements. WhatWG separates these. + * Test: Object.prototype.toString.call(document.createElement('th')) + * - Chrome === "[object HTMLTableCellElement]" + * - Firefox === "[object HTMLTableCellElement]" + * - Safari === "[object HTMLTableCellElement]" + */ + if (obj.tagName === 'TH') { + return 'HTMLTableHeaderCellElement'; + } + } + } + + /* ! Speed optimisation + * Pre: + * Float64Array x 625,644 ops/sec ±1.58% (80 runs sampled) + * Float32Array x 1,279,852 ops/sec ±2.91% (77 runs sampled) + * Uint32Array x 1,178,185 ops/sec ±1.95% (83 runs sampled) + * Uint16Array x 1,008,380 ops/sec ±2.25% (80 runs sampled) + * Uint8Array x 1,128,040 ops/sec ±2.11% (81 runs sampled) + * Int32Array x 1,170,119 ops/sec ±2.88% (80 runs sampled) + * Int16Array x 1,176,348 ops/sec ±5.79% (86 runs sampled) + * Int8Array x 1,058,707 ops/sec ±4.94% (77 runs sampled) + * Uint8ClampedArray x 1,110,633 ops/sec ±4.20% (80 runs sampled) + * Post: + * Float64Array x 7,105,671 ops/sec ±13.47% (64 runs sampled) + * Float32Array x 5,887,912 ops/sec ±1.46% (82 runs sampled) + * Uint32Array x 6,491,661 ops/sec ±1.76% (79 runs sampled) + * Uint16Array x 6,559,795 ops/sec ±1.67% (82 runs sampled) + * Uint8Array x 6,463,966 ops/sec ±1.43% (85 runs sampled) + * Int32Array x 5,641,841 ops/sec ±3.49% (81 runs sampled) + * Int16Array x 6,583,511 ops/sec ±1.98% (80 runs sampled) + * Int8Array x 6,606,078 ops/sec ±1.74% (81 runs sampled) + * Uint8ClampedArray x 6,602,224 ops/sec ±1.77% (83 runs sampled) + */ + var stringTag = (symbolToStringTagExists && obj[Symbol.toStringTag]); + if (typeof stringTag === 'string') { + return stringTag; + } + + var objPrototype = Object.getPrototypeOf(obj); + /* ! Speed optimisation + * Pre: + * regex literal x 1,772,385 ops/sec ±1.85% (77 runs sampled) + * regex constructor x 2,143,634 ops/sec ±2.46% (78 runs sampled) + * Post: + * regex literal x 3,928,009 ops/sec ±0.65% (78 runs sampled) + * regex constructor x 3,931,108 ops/sec ±0.58% (84 runs sampled) + */ + if (objPrototype === RegExp.prototype) { + return 'RegExp'; + } + + /* ! Speed optimisation + * Pre: + * date x 2,130,074 ops/sec ±4.42% (68 runs sampled) + * Post: + * date x 3,953,779 ops/sec ±1.35% (77 runs sampled) + */ + if (objPrototype === Date.prototype) { + return 'Date'; + } + + /* ! Spec Conformance + * (http://www.ecma-international.org/ecma-262/6.0/index.html#sec-promise.prototype-@@tostringtag) + * ES6$25.4.5.4 - Promise.prototype[@@toStringTag] should be "Promise": + * Test: `Object.prototype.toString.call(Promise.resolve())`` + * - Chrome <=47 === "[object Object]" + * - Edge <=20 === "[object Object]" + * - Firefox 29-Latest === "[object Promise]" + * - Safari 7.1-Latest === "[object Promise]" + */ + if (promiseExists && objPrototype === Promise.prototype) { + return 'Promise'; + } + + /* ! Speed optimisation + * Pre: + * set x 2,222,186 ops/sec ±1.31% (82 runs sampled) + * Post: + * set x 4,545,879 ops/sec ±1.13% (83 runs sampled) + */ + if (setExists && objPrototype === Set.prototype) { + return 'Set'; + } + + /* ! Speed optimisation + * Pre: + * map x 2,396,842 ops/sec ±1.59% (81 runs sampled) + * Post: + * map x 4,183,945 ops/sec ±6.59% (82 runs sampled) + */ + if (mapExists && objPrototype === Map.prototype) { + return 'Map'; + } + + /* ! Speed optimisation + * Pre: + * weakset x 1,323,220 ops/sec ±2.17% (76 runs sampled) + * Post: + * weakset x 4,237,510 ops/sec ±2.01% (77 runs sampled) + */ + if (weakSetExists && objPrototype === WeakSet.prototype) { + return 'WeakSet'; + } + + /* ! Speed optimisation + * Pre: + * weakmap x 1,500,260 ops/sec ±2.02% (78 runs sampled) + * Post: + * weakmap x 3,881,384 ops/sec ±1.45% (82 runs sampled) + */ + if (weakMapExists && objPrototype === WeakMap.prototype) { + return 'WeakMap'; + } + + /* ! Spec Conformance + * (http://www.ecma-international.org/ecma-262/6.0/index.html#sec-dataview.prototype-@@tostringtag) + * ES6$24.2.4.21 - DataView.prototype[@@toStringTag] should be "DataView": + * Test: `Object.prototype.toString.call(new DataView(new ArrayBuffer(1)))`` + * - Edge <=13 === "[object Object]" + */ + if (dataViewExists && objPrototype === DataView.prototype) { + return 'DataView'; + } + + /* ! Spec Conformance + * (http://www.ecma-international.org/ecma-262/6.0/index.html#sec-%mapiteratorprototype%-@@tostringtag) + * ES6$23.1.5.2.2 - %MapIteratorPrototype%[@@toStringTag] should be "Map Iterator": + * Test: `Object.prototype.toString.call(new Map().entries())`` + * - Edge <=13 === "[object Object]" + */ + if (mapExists && objPrototype === mapIteratorPrototype) { + return 'Map Iterator'; + } + + /* ! Spec Conformance + * (http://www.ecma-international.org/ecma-262/6.0/index.html#sec-%setiteratorprototype%-@@tostringtag) + * ES6$23.2.5.2.2 - %SetIteratorPrototype%[@@toStringTag] should be "Set Iterator": + * Test: `Object.prototype.toString.call(new Set().entries())`` + * - Edge <=13 === "[object Object]" + */ + if (setExists && objPrototype === setIteratorPrototype) { + return 'Set Iterator'; + } + + /* ! Spec Conformance + * (http://www.ecma-international.org/ecma-262/6.0/index.html#sec-%arrayiteratorprototype%-@@tostringtag) + * ES6$22.1.5.2.2 - %ArrayIteratorPrototype%[@@toStringTag] should be "Array Iterator": + * Test: `Object.prototype.toString.call([][Symbol.iterator]())`` + * - Edge <=13 === "[object Object]" + */ + if (arrayIteratorExists && objPrototype === arrayIteratorPrototype) { + return 'Array Iterator'; + } + + /* ! Spec Conformance + * (http://www.ecma-international.org/ecma-262/6.0/index.html#sec-%stringiteratorprototype%-@@tostringtag) + * ES6$21.1.5.2.2 - %StringIteratorPrototype%[@@toStringTag] should be "String Iterator": + * Test: `Object.prototype.toString.call(''[Symbol.iterator]())`` + * - Edge <=13 === "[object Object]" + */ + if (stringIteratorExists && objPrototype === stringIteratorPrototype) { + return 'String Iterator'; + } + + /* ! Speed optimisation + * Pre: + * object from null x 2,424,320 ops/sec ±1.67% (76 runs sampled) + * Post: + * object from null x 5,838,000 ops/sec ±0.99% (84 runs sampled) + */ + if (objPrototype === null) { + return 'Object'; + } + + return Object + .prototype + .toString + .call(obj) + .slice(toStringLeftSliceLength, toStringRightSliceLength); +} + +return typeDetect; + +}))); + +},{}]},{},[1])(1) +}); \ No newline at end of file diff --git a/app/assets/frontends/beaker-module/vendor/highlight.css b/app/assets/frontends/beaker-module/vendor/highlight.css new file mode 100644 index 0000000000..791932b87e --- /dev/null +++ b/app/assets/frontends/beaker-module/vendor/highlight.css @@ -0,0 +1,99 @@ +/* + +github.com style (c) Vasily Polovnyov + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + color: #333; + background: #f8f8f8; +} + +.hljs-comment, +.hljs-quote { + color: #998; + font-style: italic; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-subst { + color: #333; + font-weight: bold; +} + +.hljs-number, +.hljs-literal, +.hljs-variable, +.hljs-template-variable, +.hljs-tag .hljs-attr { + color: #008080; +} + +.hljs-string, +.hljs-doctag { + color: #d14; +} + +.hljs-title, +.hljs-section, +.hljs-selector-id { + color: #900; + font-weight: bold; +} + +.hljs-subst { + font-weight: normal; +} + +.hljs-type, +.hljs-class .hljs-title { + color: #458; + font-weight: bold; +} + +.hljs-tag, +.hljs-name, +.hljs-attribute { + color: #000080; + font-weight: normal; +} + +.hljs-regexp, +.hljs-link { + color: #009926; +} + +.hljs-symbol, +.hljs-bullet { + color: #990073; +} + +.hljs-built_in, +.hljs-builtin-name { + color: #0086b3; +} + +.hljs-meta { + color: #999; + font-weight: bold; +} + +.hljs-deletion { + background: #fdd; +} + +.hljs-addition { + background: #dfd; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/app/assets/frontends/beaker-module/vendor/highlight.pack.js b/app/assets/frontends/beaker-module/vendor/highlight.pack.js new file mode 100644 index 0000000000..3fdaf3e24a --- /dev/null +++ b/app/assets/frontends/beaker-module/vendor/highlight.pack.js @@ -0,0 +1,2 @@ +/*! highlight.js v9.18.0 | BSD3 License | git.io/hljslicense */ +!function(e){var n="object"==typeof window&&window||"object"==typeof self&&self;"undefined"==typeof exports||exports.nodeType?n&&(n.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return n.hljs})):e(exports)}(function(a){var f=[],o=Object.keys,_={},g={},C=!0,n=/^(no-?highlight|plain|text)$/i,E=/\blang(?:uage)?-([\w-]+)\b/i,t=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,r={case_insensitive:"cI",lexemes:"l",contains:"c",keywords:"k",subLanguage:"sL",className:"cN",begin:"b",beginKeywords:"bK",end:"e",endsWithParent:"eW",illegal:"i",excludeBegin:"eB",excludeEnd:"eE",returnBegin:"rB",returnEnd:"rE",variants:"v",IDENT_RE:"IR",UNDERSCORE_IDENT_RE:"UIR",NUMBER_RE:"NR",C_NUMBER_RE:"CNR",BINARY_NUMBER_RE:"BNR",RE_STARTERS_RE:"RSR",BACKSLASH_ESCAPE:"BE",APOS_STRING_MODE:"ASM",QUOTE_STRING_MODE:"QSM",PHRASAL_WORDS_MODE:"PWM",C_LINE_COMMENT_MODE:"CLCM",C_BLOCK_COMMENT_MODE:"CBCM",HASH_COMMENT_MODE:"HCM",NUMBER_MODE:"NM",C_NUMBER_MODE:"CNM",BINARY_NUMBER_MODE:"BNM",CSS_NUMBER_MODE:"CSSNM",REGEXP_MODE:"RM",TITLE_MODE:"TM",UNDERSCORE_TITLE_MODE:"UTM",COMMENT:"C",beginRe:"bR",endRe:"eR",illegalRe:"iR",lexemesRe:"lR",terminators:"t",terminator_end:"tE"},m="",O="Could not find the language '{}', did you forget to load/include a language module?",B={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0},c="of and for in not or if then".split(" ");function x(e){return e.replace(/&/g,"&").replace(//g,">")}function d(e){return e.nodeName.toLowerCase()}function R(e){return n.test(e)}function i(e){var n,t={},r=Array.prototype.slice.call(arguments,1);for(n in e)t[n]=e[n];return r.forEach(function(e){for(n in e)t[n]=e[n]}),t}function p(e){var a=[];return function e(n,t){for(var r=n.firstChild;r;r=r.nextSibling)3===r.nodeType?t+=r.nodeValue.length:1===r.nodeType&&(a.push({event:"start",offset:t,node:r}),t=e(r,t),d(r).match(/br|hr|img|input/)||a.push({event:"stop",offset:t,node:r}));return t}(e,0),a}function v(e,n,t){var r=0,a="",i=[];function o(){return e.length&&n.length?e[0].offset!==n[0].offset?e[0].offset"}function l(e){a+=""}function u(e){("start"===e.event?c:l)(e.node)}for(;e.length||n.length;){var s=o();if(a+=x(t.substring(r,s[0].offset)),r=s[0].offset,s===e){for(i.reverse().forEach(l);u(s.splice(0,1)[0]),(s=o())===e&&s.length&&s[0].offset===r;);i.reverse().forEach(c)}else"start"===s[0].event?i.push(s[0].node):i.pop(),u(s.splice(0,1)[0])}return a+x(t.substr(r))}function l(n){return n.v&&!n.cached_variants&&(n.cached_variants=n.v.map(function(e){return i(n,{v:null},e)})),n.cached_variants?n.cached_variants:function e(n){return!!n&&(n.eW||e(n.starts))}(n)?[i(n,{starts:n.starts?i(n.starts):null})]:Object.isFrozen(n)?[i(n)]:[n]}function u(e){if(r&&!e.langApiRestored){for(var n in e.langApiRestored=!0,r)e[n]&&(e[r[n]]=e[n]);(e.c||[]).concat(e.v||[]).forEach(u)}}function M(n,t){var i={};return"string"==typeof n?r("keyword",n):o(n).forEach(function(e){r(e,n[e])}),i;function r(a,e){t&&(e=e.toLowerCase()),e.split(" ").forEach(function(e){var n,t,r=e.split("|");i[r[0]]=[a,(n=r[0],(t=r[1])?Number(t):function(e){return-1!=c.indexOf(e.toLowerCase())}(n)?0:1)]})}}function S(r){function s(e){return e&&e.source||e}function f(e,n){return new RegExp(s(e),"m"+(r.cI?"i":"")+(n?"g":""))}function a(a){var i,e,o={},c=[],l={},t=1;function n(e,n){o[t]=e,c.push([e,n]),t+=new RegExp(n.toString()+"|").exec("").length-1+1}for(var r=0;r')+n+(t?"":m)}function o(){p+=(null!=d.sL?function(){var e="string"==typeof d.sL;if(e&&!_[d.sL])return x(v);var n=e?T(d.sL,v,!0,R[d.sL]):w(v,d.sL.length?d.sL:void 0);return 0")+'"');if("end"===n.type){var r=s(n);if(null!=r)return r}return v+=t,t.length}var g=D(n);if(!g)throw console.error(O.replace("{}",n)),new Error('Unknown language: "'+n+'"');S(g);var E,d=t||g,R={},p="";for(E=d;E!==g;E=E.parent)E.cN&&(p=c(E.cN,"",!0)+p);var v="",M=0;try{for(var b,h,N=0;d.t.lastIndex=N,b=d.t.exec(i);)h=r(i.substring(N,b.index),b),N=b.index+h;for(r(i.substr(N)),E=d;E.parent;E=E.parent)E.cN&&(p+=m);return{relevance:M,value:p,i:!1,language:n,top:d}}catch(e){if(e.message&&-1!==e.message.indexOf("Illegal"))return{i:!0,relevance:0,value:x(i)};if(C)return{relevance:0,value:x(i),language:n,top:d,errorRaised:e};throw e}}function w(t,e){e=e||B.languages||o(_);var r={relevance:0,value:x(t)},a=r;return e.filter(D).filter(L).forEach(function(e){var n=T(e,t,!1);n.language=e,n.relevance>a.relevance&&(a=n),n.relevance>r.relevance&&(a=r,r=n)}),a.language&&(r.second_best=a),r}function b(e){return B.tabReplace||B.useBR?e.replace(t,function(e,n){return B.useBR&&"\n"===e?"
":B.tabReplace?n.replace(/\t/g,B.tabReplace):""}):e}function s(e){var n,t,r,a,i,o,c,l,u,s,f=function(e){var n,t,r,a,i=e.className+" ";if(i+=e.parentNode?e.parentNode.className:"",t=E.exec(i)){var o=D(t[1]);return o||(console.warn(O.replace("{}",t[1])),console.warn("Falling back to no-highlight mode for this block.",e)),o?t[1]:"no-highlight"}for(n=0,r=(i=i.split(/\s+/)).length;n/g,"\n"):n=e,i=n.textContent,r=f?T(f,i,!0):w(i),(t=p(n)).length&&((a=document.createElement("div")).innerHTML=r.value,r.value=v(t,p(a),i)),r.value=b(r.value),e.innerHTML=r.value,e.className=(o=e.className,c=f,l=r.language,u=c?g[c]:l,s=[o.trim()],o.match(/\bhljs\b/)||s.push("hljs"),-1===o.indexOf(u)&&s.push(u),s.join(" ").trim()),e.result={language:r.language,re:r.relevance},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.relevance}))}function h(){if(!h.called){h.called=!0;var e=document.querySelectorAll("pre code");f.forEach.call(e,s)}}var N={disableAutodetect:!0};function D(e){return e=(e||"").toLowerCase(),_[e]||_[g[e]]}function L(e){var n=D(e);return n&&!n.disableAutodetect}return a.highlight=T,a.highlightAuto=w,a.fixMarkup=b,a.highlightBlock=s,a.configure=function(e){B=i(B,e)},a.initHighlighting=h,a.initHighlightingOnLoad=function(){window.addEventListener("DOMContentLoaded",h,!1),window.addEventListener("load",h,!1)},a.registerLanguage=function(n,e){var t;try{t=e(a)}catch(e){if(console.error("Language definition for '{}' could not be registered.".replace("{}",n)),!C)throw e;console.error(e),t=N}u(_[n]=t),t.rawDefinition=e.bind(null,a),t.aliases&&t.aliases.forEach(function(e){g[e]=n})},a.listLanguages=function(){return o(_)},a.getLanguage=D,a.requireLanguage=function(e){var n=D(e);if(n)return n;throw new Error("The '{}' language is required, but not loaded.".replace("{}",e))},a.autoDetection=L,a.inherit=i,a.debugMode=function(){C=!1},a.IR=a.IDENT_RE="[a-zA-Z]\\w*",a.UIR=a.UNDERSCORE_IDENT_RE="[a-zA-Z_]\\w*",a.NR=a.NUMBER_RE="\\b\\d+(\\.\\d+)?",a.CNR=a.C_NUMBER_RE="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",a.BNR=a.BINARY_NUMBER_RE="\\b(0b[01]+)",a.RSR=a.RE_STARTERS_RE="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",a.BE=a.BACKSLASH_ESCAPE={b:"\\\\[\\s\\S]",relevance:0},a.ASM=a.APOS_STRING_MODE={cN:"string",b:"'",e:"'",i:"\\n",c:[a.BE]},a.QSM=a.QUOTE_STRING_MODE={cN:"string",b:'"',e:'"',i:"\\n",c:[a.BE]},a.PWM=a.PHRASAL_WORDS_MODE={b:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},a.C=a.COMMENT=function(e,n,t){var r=a.inherit({cN:"comment",b:e,e:n,c:[]},t||{});return r.c.push(a.PWM),r.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",relevance:0}),r},a.CLCM=a.C_LINE_COMMENT_MODE=a.C("//","$"),a.CBCM=a.C_BLOCK_COMMENT_MODE=a.C("/\\*","\\*/"),a.HCM=a.HASH_COMMENT_MODE=a.C("#","$"),a.NM=a.NUMBER_MODE={cN:"number",b:a.NR,relevance:0},a.CNM=a.C_NUMBER_MODE={cN:"number",b:a.CNR,relevance:0},a.BNM=a.BINARY_NUMBER_MODE={cN:"number",b:a.BNR,relevance:0},a.CSSNM=a.CSS_NUMBER_MODE={cN:"number",b:a.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},a.RM=a.REGEXP_MODE={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[a.BE,{b:/\[/,e:/\]/,relevance:0,c:[a.BE]}]},a.TM=a.TITLE_MODE={cN:"title",b:a.IR,relevance:0},a.UTM=a.UNDERSCORE_TITLE_MODE={cN:"title",b:a.UIR,relevance:0},a.METHOD_GUARD={b:"\\.\\s*"+a.UIR,relevance:0},[a.BE,a.ASM,a.QSM,a.PWM,a.C,a.CLCM,a.CBCM,a.HCM,a.NM,a.CNM,a.BNM,a.CSSNM,a.RM,a.TM,a.UTM,a.METHOD_GUARD].forEach(function(e){!function n(t){Object.freeze(t);var r="function"==typeof t;Object.getOwnPropertyNames(t).forEach(function(e){!t.hasOwnProperty(e)||null===t[e]||"object"!=typeof t[e]&&"function"!=typeof t[e]||r&&("caller"===e||"callee"===e||"arguments"===e)||Object.isFrozen(t[e])||n(t[e])});return t}(e)}),a});hljs.registerLanguage("xml",function(e){var c={cN:"symbol",b:"&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;"},s={b:"\\s",c:[{cN:"meta-keyword",b:"#?[a-z_][a-z1-9_-]+",i:"\\n"}]},a=e.inherit(s,{b:"\\(",e:"\\)"}),t=e.inherit(e.ASM,{cN:"meta-string"}),l=e.inherit(e.QSM,{cN:"meta-string"}),r={eW:!0,i:/`]+/}]}]}]};return{aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],cI:!0,c:[{cN:"meta",b:"",relevance:10,c:[s,l,t,a,{b:"\\[",e:"\\]",c:[{cN:"meta",b:"",c:[s,a,l,t]}]}]},e.C("\x3c!--","--\x3e",{relevance:10}),{b:"<\\!\\[CDATA\\[",e:"\\]\\]>",relevance:10},c,{cN:"meta",b:/<\?xml/,e:/\?>/,relevance:10},{b:/<\?(php)?/,e:/\?>/,sL:"php",c:[{b:"/\\*",e:"\\*/",skip:!0},{b:'b"',e:'"',skip:!0},{b:"b'",e:"'",skip:!0},e.inherit(e.ASM,{i:null,cN:null,c:null,skip:!0}),e.inherit(e.QSM,{i:null,cN:null,c:null,skip:!0})]},{cN:"tag",b:")",e:">",k:{name:"style"},c:[r],starts:{e:"",rE:!0,sL:["css","xml"]}},{cN:"tag",b:")",e:">",k:{name:"script"},c:[r],starts:{e:"<\/script>",rE:!0,sL:["actionscript","javascript","handlebars","xml"]}},{cN:"tag",b:"",c:[{cN:"name",b:/[^\/><\s]+/,relevance:0},r]}]}});hljs.registerLanguage("typescript",function(e){var r="[A-Za-z$_][0-9A-Za-z$_]*",t={keyword:"in if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const class public private protected get set super static implements enum export import declare type namespace abstract as from extends async await",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document any number boolean string void Promise"},n={cN:"meta",b:"@"+r},a={b:"\\(",e:/\)/,k:t,c:["self",e.QSM,e.ASM,e.NM]},c={cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,k:t,c:[e.CLCM,e.CBCM,n,a]},s={cN:"number",v:[{b:"\\b(0[bB][01]+)n?"},{b:"\\b(0[oO][0-7]+)n?"},{b:e.CNR+"n?"}],relevance:0},o={cN:"subst",b:"\\$\\{",e:"\\}",k:t,c:[]},i={b:"html`",e:"",starts:{e:"`",rE:!1,c:[e.BE,o],sL:"xml"}},l={b:"css`",e:"",starts:{e:"`",rE:!1,c:[e.BE,o],sL:"css"}},b={cN:"string",b:"`",e:"`",c:[e.BE,o]};return o.c=[e.ASM,e.QSM,i,l,b,s,e.RM],{aliases:["ts"],k:t,c:[{cN:"meta",b:/^\s*['"]use strict['"]/},e.ASM,e.QSM,i,l,b,e.CLCM,e.CBCM,s,{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{cN:"function",b:"(\\(.*?\\)|"+e.IR+")\\s*=>",rB:!0,e:"\\s*=>",c:[{cN:"params",v:[{b:e.IR},{b:/\(\s*\)/},{b:/\(/,e:/\)/,eB:!0,eE:!0,k:t,c:["self",e.CLCM,e.CBCM]}]}]}],relevance:0},{cN:"function",bK:"function",e:/[\{;]/,eE:!0,k:t,c:["self",e.inherit(e.TM,{b:r}),c],i:/%/,relevance:0},{bK:"constructor",e:/[\{;]/,eE:!0,c:["self",c]},{b:/module\./,k:{built_in:"module"},relevance:0},{bK:"module",e:/\{/,eE:!0},{bK:"interface",e:/\{/,eE:!0,k:"interface extends"},{b:/\$[(.]/},{b:"\\."+e.IR,relevance:0},n,a]}});hljs.registerLanguage("json",function(e){var i={literal:"true false null"},n=[e.CLCM,e.CBCM],c=[e.QSM,e.CNM],r={e:",",eW:!0,eE:!0,c:c,k:i},t={b:"{",e:"}",c:[{cN:"attr",b:/"/,e:/"/,c:[e.BE],i:"\\n"},e.inherit(r,{b:/:/})].concat(n),i:"\\S"},a={b:"\\[",e:"\\]",c:[e.inherit(r)],i:"\\S"};return c.push(t,a),n.forEach(function(e){c.push(e)}),{c:c,k:i,i:"\\S"}});hljs.registerLanguage("css",function(e){var c={b:/(?:[A-Z\_\.\-]+|--[a-zA-Z0-9_-]+)\s*:/,rB:!0,e:";",eW:!0,c:[{cN:"attribute",b:/\S/,e:":",eE:!0,starts:{eW:!0,eE:!0,c:[{b:/[\w-]+\(/,rB:!0,c:[{cN:"built_in",b:/[\w-]+/},{b:/\(/,e:/\)/,c:[e.ASM,e.QSM,e.CSSNM]}]},e.CSSNM,e.QSM,e.ASM,e.CBCM,{cN:"number",b:"#[0-9A-Fa-f]+"},{cN:"meta",b:"!important"}]}}]};return{cI:!0,i:/[=\/|'\$]/,c:[e.CBCM,{cN:"selector-id",b:/#[A-Za-z0-9_-]+/},{cN:"selector-class",b:/\.[A-Za-z0-9_-]+/},{cN:"selector-attr",b:/\[/,e:/\]/,i:"$",c:[e.ASM,e.QSM]},{cN:"selector-pseudo",b:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{b:"@(page|font-face)",l:"@[a-z-]+",k:"@page @font-face"},{b:"@",e:"[{;]",i:/:/,rB:!0,c:[{cN:"keyword",b:/@\-?\w[\w]*(\-\w+)*/},{b:/\s/,eW:!0,eE:!0,relevance:0,k:"and or not only",c:[{b:/[a-z-]+:/,cN:"attribute"},e.ASM,e.QSM,e.CSSNM]}]},{cN:"selector-tag",b:"[a-zA-Z-][a-zA-Z0-9_-]*",relevance:0},{b:"{",e:"}",i:/\S/,c:[e.CBCM,c]}]}});hljs.registerLanguage("javascript",function(e){var r="<>",a="",t={b:/<[A-Za-z0-9\\._:-]+/,e:/\/[A-Za-z0-9\\._:-]+>|\/>/},c="[A-Za-z$_][0-9A-Za-z$_]*",n={keyword:"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await static import from as",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise"},s={cN:"number",v:[{b:"\\b(0[bB][01]+)n?"},{b:"\\b(0[oO][0-7]+)n?"},{b:e.CNR+"n?"}],relevance:0},o={cN:"subst",b:"\\$\\{",e:"\\}",k:n,c:[]},i={b:"html`",e:"",starts:{e:"`",rE:!1,c:[e.BE,o],sL:"xml"}},b={b:"css`",e:"",starts:{e:"`",rE:!1,c:[e.BE,o],sL:"css"}},l={cN:"string",b:"`",e:"`",c:[e.BE,o]};o.c=[e.ASM,e.QSM,i,b,l,s,e.RM];var u=o.c.concat([e.CBCM,e.CLCM]);return{aliases:["js","jsx","mjs","cjs"],k:n,c:[{cN:"meta",relevance:10,b:/^\s*['"]use (strict|asm)['"]/},{cN:"meta",b:/^#!/,e:/$/},e.ASM,e.QSM,i,b,l,e.CLCM,e.C("/\\*\\*","\\*/",{relevance:0,c:[{cN:"doctag",b:"@[A-Za-z]+",c:[{cN:"type",b:"\\{",e:"\\}",relevance:0},{cN:"variable",b:c+"(?=\\s*(-)|$)",endsParent:!0,relevance:0},{b:/(?=[^\n])\s/,relevance:0}]}]}),e.CBCM,s,{b:/[{,\n]\s*/,relevance:0,c:[{b:c+"\\s*:",rB:!0,relevance:0,c:[{cN:"attr",b:c,relevance:0}]}]},{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{cN:"function",b:"(\\(.*?\\)|"+c+")\\s*=>",rB:!0,e:"\\s*=>",c:[{cN:"params",v:[{b:c},{b:/\(\s*\)/},{b:/\(/,e:/\)/,eB:!0,eE:!0,k:n,c:u}]}]},{cN:"",b:/\s/,e:/\s*/,skip:!0},{v:[{b:r,e:a},{b:t.b,e:t.e}],sL:"xml",c:[{b:t.b,e:t.e,skip:!0,c:["self"]}]}],relevance:0},{cN:"function",bK:"function",e:/\{/,eE:!0,c:[e.inherit(e.TM,{b:c}),{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,c:u}],i:/\[|%/},{b:/\$[(.]/},e.METHOD_GUARD,{cN:"class",bK:"class",e:/[{;=]/,eE:!0,i:/[:"\[\]]/,c:[{bK:"extends"},e.UTM]},{bK:"constructor get set",e:/\{/,eE:!0}],i:/#(?!!)/}}); \ No newline at end of file diff --git a/app/assets/frontends/beaker-module/vendor/markdown-it.js b/app/assets/frontends/beaker-module/vendor/markdown-it.js new file mode 100644 index 0000000000..ca121322d8 --- /dev/null +++ b/app/assets/frontends/beaker-module/vendor/markdown-it.js @@ -0,0 +1,8157 @@ +/*! markdown-it 10.0.0 https://github.com//markdown-it/markdown-it @license MIT */ +const define = (function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i utf16string } +// +'use strict'; + +/*eslint quotes:0*/ +module.exports = require('entities/lib/maps/entities.json'); + +},{"entities/lib/maps/entities.json":52}],2:[function(require,module,exports){ +// List of valid html blocks names, accorting to commonmark spec +// http://jgm.github.io/CommonMark/spec.html#html-blocks + +'use strict'; + + +module.exports = [ + 'address', + 'article', + 'aside', + 'base', + 'basefont', + 'blockquote', + 'body', + 'caption', + 'center', + 'col', + 'colgroup', + 'dd', + 'details', + 'dialog', + 'dir', + 'div', + 'dl', + 'dt', + 'fieldset', + 'figcaption', + 'figure', + 'footer', + 'form', + 'frame', + 'frameset', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'head', + 'header', + 'hr', + 'html', + 'iframe', + 'legend', + 'li', + 'link', + 'main', + 'menu', + 'menuitem', + 'meta', + 'nav', + 'noframes', + 'ol', + 'optgroup', + 'option', + 'p', + 'param', + 'section', + 'source', + 'summary', + 'table', + 'tbody', + 'td', + 'tfoot', + 'th', + 'thead', + 'title', + 'tr', + 'track', + 'ul' +]; + +},{}],3:[function(require,module,exports){ +// Regexps to match html elements + +'use strict'; + +var attr_name = '[a-zA-Z_:][a-zA-Z0-9:._-]*'; + +var unquoted = '[^"\'=<>`\\x00-\\x20]+'; +var single_quoted = "'[^']*'"; +var double_quoted = '"[^"]*"'; + +var attr_value = '(?:' + unquoted + '|' + single_quoted + '|' + double_quoted + ')'; + +var attribute = '(?:\\s+' + attr_name + '(?:\\s*=\\s*' + attr_value + ')?)'; + +var open_tag = '<[A-Za-z][A-Za-z0-9\\-]*' + attribute + '*\\s*\\/?>'; + +var close_tag = '<\\/[A-Za-z][A-Za-z0-9\\-]*\\s*>'; +var comment = '|'; +var processing = '<[?].*?[?]>'; +var declaration = ']*>'; +var cdata = ''; + +var HTML_TAG_RE = new RegExp('^(?:' + open_tag + '|' + close_tag + '|' + comment + + '|' + processing + '|' + declaration + '|' + cdata + ')'); +var HTML_OPEN_CLOSE_TAG_RE = new RegExp('^(?:' + open_tag + '|' + close_tag + ')'); + +module.exports.HTML_TAG_RE = HTML_TAG_RE; +module.exports.HTML_OPEN_CLOSE_TAG_RE = HTML_OPEN_CLOSE_TAG_RE; + +},{}],4:[function(require,module,exports){ +// Utilities +// +'use strict'; + + +function _class(obj) { return Object.prototype.toString.call(obj); } + +function isString(obj) { return _class(obj) === '[object String]'; } + +var _hasOwnProperty = Object.prototype.hasOwnProperty; + +function has(object, key) { + return _hasOwnProperty.call(object, key); +} + +// Merge objects +// +function assign(obj /*from1, from2, from3, ...*/) { + var sources = Array.prototype.slice.call(arguments, 1); + + sources.forEach(function (source) { + if (!source) { return; } + + if (typeof source !== 'object') { + throw new TypeError(source + 'must be object'); + } + + Object.keys(source).forEach(function (key) { + obj[key] = source[key]; + }); + }); + + return obj; +} + +// Remove element from array and put another array at those position. +// Useful for some operations with tokens +function arrayReplaceAt(src, pos, newElements) { + return [].concat(src.slice(0, pos), newElements, src.slice(pos + 1)); +} + +//////////////////////////////////////////////////////////////////////////////// + +function isValidEntityCode(c) { + /*eslint no-bitwise:0*/ + // broken sequence + if (c >= 0xD800 && c <= 0xDFFF) { return false; } + // never used + if (c >= 0xFDD0 && c <= 0xFDEF) { return false; } + if ((c & 0xFFFF) === 0xFFFF || (c & 0xFFFF) === 0xFFFE) { return false; } + // control codes + if (c >= 0x00 && c <= 0x08) { return false; } + if (c === 0x0B) { return false; } + if (c >= 0x0E && c <= 0x1F) { return false; } + if (c >= 0x7F && c <= 0x9F) { return false; } + // out of range + if (c > 0x10FFFF) { return false; } + return true; +} + +function fromCodePoint(c) { + /*eslint no-bitwise:0*/ + if (c > 0xffff) { + c -= 0x10000; + var surrogate1 = 0xd800 + (c >> 10), + surrogate2 = 0xdc00 + (c & 0x3ff); + + return String.fromCharCode(surrogate1, surrogate2); + } + return String.fromCharCode(c); +} + + +var UNESCAPE_MD_RE = /\\([!"#$%&'()*+,\-.\/:;<=>?@[\\\]^_`{|}~])/g; +var ENTITY_RE = /&([a-z#][a-z0-9]{1,31});/gi; +var UNESCAPE_ALL_RE = new RegExp(UNESCAPE_MD_RE.source + '|' + ENTITY_RE.source, 'gi'); + +var DIGITAL_ENTITY_TEST_RE = /^#((?:x[a-f0-9]{1,8}|[0-9]{1,8}))/i; + +var entities = require('./entities'); + +function replaceEntityPattern(match, name) { + var code = 0; + + if (has(entities, name)) { + return entities[name]; + } + + if (name.charCodeAt(0) === 0x23/* # */ && DIGITAL_ENTITY_TEST_RE.test(name)) { + code = name[1].toLowerCase() === 'x' ? + parseInt(name.slice(2), 16) : parseInt(name.slice(1), 10); + + if (isValidEntityCode(code)) { + return fromCodePoint(code); + } + } + + return match; +} + +/*function replaceEntities(str) { + if (str.indexOf('&') < 0) { return str; } + + return str.replace(ENTITY_RE, replaceEntityPattern); +}*/ + +function unescapeMd(str) { + if (str.indexOf('\\') < 0) { return str; } + return str.replace(UNESCAPE_MD_RE, '$1'); +} + +function unescapeAll(str) { + if (str.indexOf('\\') < 0 && str.indexOf('&') < 0) { return str; } + + return str.replace(UNESCAPE_ALL_RE, function (match, escaped, entity) { + if (escaped) { return escaped; } + return replaceEntityPattern(match, entity); + }); +} + +//////////////////////////////////////////////////////////////////////////////// + +var HTML_ESCAPE_TEST_RE = /[&<>"]/; +var HTML_ESCAPE_REPLACE_RE = /[&<>"]/g; +var HTML_REPLACEMENTS = { + '&': '&', + '<': '<', + '>': '>', + '"': '"' +}; + +function replaceUnsafeChar(ch) { + return HTML_REPLACEMENTS[ch]; +} + +function escapeHtml(str) { + if (HTML_ESCAPE_TEST_RE.test(str)) { + return str.replace(HTML_ESCAPE_REPLACE_RE, replaceUnsafeChar); + } + return str; +} + +//////////////////////////////////////////////////////////////////////////////// + +var REGEXP_ESCAPE_RE = /[.?*+^$[\]\\(){}|-]/g; + +function escapeRE(str) { + return str.replace(REGEXP_ESCAPE_RE, '\\$&'); +} + +//////////////////////////////////////////////////////////////////////////////// + +function isSpace(code) { + switch (code) { + case 0x09: + case 0x20: + return true; + } + return false; +} + +// Zs (unicode class) || [\t\f\v\r\n] +function isWhiteSpace(code) { + if (code >= 0x2000 && code <= 0x200A) { return true; } + switch (code) { + case 0x09: // \t + case 0x0A: // \n + case 0x0B: // \v + case 0x0C: // \f + case 0x0D: // \r + case 0x20: + case 0xA0: + case 0x1680: + case 0x202F: + case 0x205F: + case 0x3000: + return true; + } + return false; +} + +//////////////////////////////////////////////////////////////////////////////// + +/*eslint-disable max-len*/ +var UNICODE_PUNCT_RE = require('uc.micro/categories/P/regex'); + +// Currently without astral characters support. +function isPunctChar(ch) { + return UNICODE_PUNCT_RE.test(ch); +} + + +// Markdown ASCII punctuation characters. +// +// !, ", #, $, %, &, ', (, ), *, +, ,, -, ., /, :, ;, <, =, >, ?, @, [, \, ], ^, _, `, {, |, }, or ~ +// http://spec.commonmark.org/0.15/#ascii-punctuation-character +// +// Don't confuse with unicode punctuation !!! It lacks some chars in ascii range. +// +function isMdAsciiPunct(ch) { + switch (ch) { + case 0x21/* ! */: + case 0x22/* " */: + case 0x23/* # */: + case 0x24/* $ */: + case 0x25/* % */: + case 0x26/* & */: + case 0x27/* ' */: + case 0x28/* ( */: + case 0x29/* ) */: + case 0x2A/* * */: + case 0x2B/* + */: + case 0x2C/* , */: + case 0x2D/* - */: + case 0x2E/* . */: + case 0x2F/* / */: + case 0x3A/* : */: + case 0x3B/* ; */: + case 0x3C/* < */: + case 0x3D/* = */: + case 0x3E/* > */: + case 0x3F/* ? */: + case 0x40/* @ */: + case 0x5B/* [ */: + case 0x5C/* \ */: + case 0x5D/* ] */: + case 0x5E/* ^ */: + case 0x5F/* _ */: + case 0x60/* ` */: + case 0x7B/* { */: + case 0x7C/* | */: + case 0x7D/* } */: + case 0x7E/* ~ */: + return true; + default: + return false; + } +} + +// Hepler to unify [reference labels]. +// +function normalizeReference(str) { + // Trim and collapse whitespace + // + str = str.trim().replace(/\s+/g, ' '); + + // In node v10 'ẞ'.toLowerCase() === 'Ṿ', which is presumed to be a bug + // fixed in v12 (couldn't find any details). + // + // So treat this one as a special case + // (remove this when node v10 is no longer supported). + // + if ('ẞ'.toLowerCase() === 'Ṿ') { + str = str.replace(/ẞ/g, 'ß'); + } + + // .toLowerCase().toUpperCase() should get rid of all differences + // between letter variants. + // + // Simple .toLowerCase() doesn't normalize 125 code points correctly, + // and .toUpperCase doesn't normalize 6 of them (list of exceptions: + // İ, ϴ, ẞ, Ω, K, Å - those are already uppercased, but have differently + // uppercased versions). + // + // Here's an example showing how it happens. Lets take greek letter omega: + // uppercase U+0398 (Θ), U+03f4 (ϴ) and lowercase U+03b8 (θ), U+03d1 (ϑ) + // + // Unicode entries: + // 0398;GREEK CAPITAL LETTER THETA;Lu;0;L;;;;;N;;;;03B8; + // 03B8;GREEK SMALL LETTER THETA;Ll;0;L;;;;;N;;;0398;;0398 + // 03D1;GREEK THETA SYMBOL;Ll;0;L; 03B8;;;;N;GREEK SMALL LETTER SCRIPT THETA;;0398;;0398 + // 03F4;GREEK CAPITAL THETA SYMBOL;Lu;0;L; 0398;;;;N;;;;03B8; + // + // Case-insensitive comparison should treat all of them as equivalent. + // + // But .toLowerCase() doesn't change ϑ (it's already lowercase), + // and .toUpperCase() doesn't change ϴ (already uppercase). + // + // Applying first lower then upper case normalizes any character: + // '\u0398\u03f4\u03b8\u03d1'.toLowerCase().toUpperCase() === '\u0398\u0398\u0398\u0398' + // + // Note: this is equivalent to unicode case folding; unicode normalization + // is a different step that is not required here. + // + // Final result should be uppercased, because it's later stored in an object + // (this avoid a conflict with Object.prototype members, + // most notably, `__proto__`) + // + return str.toLowerCase().toUpperCase(); +} + +//////////////////////////////////////////////////////////////////////////////// + +// Re-export libraries commonly used in both markdown-it and its plugins, +// so plugins won't have to depend on them explicitly, which reduces their +// bundled size (e.g. a browser build). +// +exports.lib = {}; +exports.lib.mdurl = require('mdurl'); +exports.lib.ucmicro = require('uc.micro'); + +exports.assign = assign; +exports.isString = isString; +exports.has = has; +exports.unescapeMd = unescapeMd; +exports.unescapeAll = unescapeAll; +exports.isValidEntityCode = isValidEntityCode; +exports.fromCodePoint = fromCodePoint; +// exports.replaceEntities = replaceEntities; +exports.escapeHtml = escapeHtml; +exports.arrayReplaceAt = arrayReplaceAt; +exports.isSpace = isSpace; +exports.isWhiteSpace = isWhiteSpace; +exports.isMdAsciiPunct = isMdAsciiPunct; +exports.isPunctChar = isPunctChar; +exports.escapeRE = escapeRE; +exports.normalizeReference = normalizeReference; + +},{"./entities":1,"mdurl":58,"uc.micro":65,"uc.micro/categories/P/regex":63}],5:[function(require,module,exports){ +// Just a shortcut for bulk export +'use strict'; + + +exports.parseLinkLabel = require('./parse_link_label'); +exports.parseLinkDestination = require('./parse_link_destination'); +exports.parseLinkTitle = require('./parse_link_title'); + +},{"./parse_link_destination":6,"./parse_link_label":7,"./parse_link_title":8}],6:[function(require,module,exports){ +// Parse link destination +// +'use strict'; + + +var unescapeAll = require('../common/utils').unescapeAll; + + +module.exports = function parseLinkDestination(str, pos, max) { + var code, level, + lines = 0, + start = pos, + result = { + ok: false, + pos: 0, + lines: 0, + str: '' + }; + + if (str.charCodeAt(pos) === 0x3C /* < */) { + pos++; + while (pos < max) { + code = str.charCodeAt(pos); + if (code === 0x0A /* \n */) { return result; } + if (code === 0x3E /* > */) { + result.pos = pos + 1; + result.str = unescapeAll(str.slice(start + 1, pos)); + result.ok = true; + return result; + } + if (code === 0x5C /* \ */ && pos + 1 < max) { + pos += 2; + continue; + } + + pos++; + } + + // no closing '>' + return result; + } + + // this should be ... } else { ... branch + + level = 0; + while (pos < max) { + code = str.charCodeAt(pos); + + if (code === 0x20) { break; } + + // ascii control characters + if (code < 0x20 || code === 0x7F) { break; } + + if (code === 0x5C /* \ */ && pos + 1 < max) { + pos += 2; + continue; + } + + if (code === 0x28 /* ( */) { + level++; + } + + if (code === 0x29 /* ) */) { + if (level === 0) { break; } + level--; + } + + pos++; + } + + if (start === pos) { return result; } + if (level !== 0) { return result; } + + result.str = unescapeAll(str.slice(start, pos)); + result.lines = lines; + result.pos = pos; + result.ok = true; + return result; +}; + +},{"../common/utils":4}],7:[function(require,module,exports){ +// Parse link label +// +// this function assumes that first character ("[") already matches; +// returns the end of the label +// +'use strict'; + +module.exports = function parseLinkLabel(state, start, disableNested) { + var level, found, marker, prevPos, + labelEnd = -1, + max = state.posMax, + oldPos = state.pos; + + state.pos = start + 1; + level = 1; + + while (state.pos < max) { + marker = state.src.charCodeAt(state.pos); + if (marker === 0x5D /* ] */) { + level--; + if (level === 0) { + found = true; + break; + } + } + + prevPos = state.pos; + state.md.inline.skipToken(state); + if (marker === 0x5B /* [ */) { + if (prevPos === state.pos - 1) { + // increase level if we find text `[`, which is not a part of any token + level++; + } else if (disableNested) { + state.pos = oldPos; + return -1; + } + } + } + + if (found) { + labelEnd = state.pos; + } + + // restore old state + state.pos = oldPos; + + return labelEnd; +}; + +},{}],8:[function(require,module,exports){ +// Parse link title +// +'use strict'; + + +var unescapeAll = require('../common/utils').unescapeAll; + + +module.exports = function parseLinkTitle(str, pos, max) { + var code, + marker, + lines = 0, + start = pos, + result = { + ok: false, + pos: 0, + lines: 0, + str: '' + }; + + if (pos >= max) { return result; } + + marker = str.charCodeAt(pos); + + if (marker !== 0x22 /* " */ && marker !== 0x27 /* ' */ && marker !== 0x28 /* ( */) { return result; } + + pos++; + + // if opening marker is "(", switch it to closing marker ")" + if (marker === 0x28) { marker = 0x29; } + + while (pos < max) { + code = str.charCodeAt(pos); + if (code === marker) { + result.pos = pos + 1; + result.lines = lines; + result.str = unescapeAll(str.slice(start + 1, pos)); + result.ok = true; + return result; + } else if (code === 0x0A) { + lines++; + } else if (code === 0x5C /* \ */ && pos + 1 < max) { + pos++; + if (str.charCodeAt(pos) === 0x0A) { + lines++; + } + } + + pos++; + } + + return result; +}; + +},{"../common/utils":4}],9:[function(require,module,exports){ +// Main parser class + +'use strict'; + + +var utils = require('./common/utils'); +var helpers = require('./helpers'); +var Renderer = require('./renderer'); +var ParserCore = require('./parser_core'); +var ParserBlock = require('./parser_block'); +var ParserInline = require('./parser_inline'); +var LinkifyIt = require('linkify-it'); +var mdurl = require('mdurl'); +var punycode = require('punycode'); + + +var config = { + 'default': require('./presets/default'), + zero: require('./presets/zero'), + commonmark: require('./presets/commonmark') +}; + +//////////////////////////////////////////////////////////////////////////////// +// +// This validator can prohibit more than really needed to prevent XSS. It's a +// tradeoff to keep code simple and to be secure by default. +// +// If you need different setup - override validator method as you wish. Or +// replace it with dummy function and use external sanitizer. +// + +var BAD_PROTO_RE = /^(vbscript|javascript|file|data):/; +var GOOD_DATA_RE = /^data:image\/(gif|png|jpeg|webp);/; + +function validateLink(url) { + // url should be normalized at this point, and existing entities are decoded + var str = url.trim().toLowerCase(); + + return BAD_PROTO_RE.test(str) ? (GOOD_DATA_RE.test(str) ? true : false) : true; +} + +//////////////////////////////////////////////////////////////////////////////// + + +var RECODE_HOSTNAME_FOR = [ 'http:', 'https:', 'mailto:' ]; + +function normalizeLink(url) { + var parsed = mdurl.parse(url, true); + + if (parsed.hostname) { + // Encode hostnames in urls like: + // `http://host/`, `https://host/`, `mailto:user@host`, `//host/` + // + // We don't encode unknown schemas, because it's likely that we encode + // something we shouldn't (e.g. `skype:name` treated as `skype:host`) + // + if (!parsed.protocol || RECODE_HOSTNAME_FOR.indexOf(parsed.protocol) >= 0) { + try { + parsed.hostname = punycode.toASCII(parsed.hostname); + } catch (er) { /**/ } + } + } + + return mdurl.encode(mdurl.format(parsed)); +} + +function normalizeLinkText(url) { + var parsed = mdurl.parse(url, true); + + if (parsed.hostname) { + // Encode hostnames in urls like: + // `http://host/`, `https://host/`, `mailto:user@host`, `//host/` + // + // We don't encode unknown schemas, because it's likely that we encode + // something we shouldn't (e.g. `skype:name` treated as `skype:host`) + // + if (!parsed.protocol || RECODE_HOSTNAME_FOR.indexOf(parsed.protocol) >= 0) { + try { + parsed.hostname = punycode.toUnicode(parsed.hostname); + } catch (er) { /**/ } + } + } + + return mdurl.decode(mdurl.format(parsed)); +} + + +/** + * class MarkdownIt + * + * Main parser/renderer class. + * + * ##### Usage + * + * ```javascript + * // node.js, "classic" way: + * var MarkdownIt = require('markdown-it'), + * md = new MarkdownIt(); + * var result = md.render('# markdown-it rulezz!'); + * + * // node.js, the same, but with sugar: + * var md = require('markdown-it')(); + * var result = md.render('# markdown-it rulezz!'); + * + * // browser without AMD, added to "window" on script load + * // Note, there are no dash. + * var md = window.markdownit(); + * var result = md.render('# markdown-it rulezz!'); + * ``` + * + * Single line rendering, without paragraph wrap: + * + * ```javascript + * var md = require('markdown-it')(); + * var result = md.renderInline('__markdown-it__ rulezz!'); + * ``` + **/ + +/** + * new MarkdownIt([presetName, options]) + * - presetName (String): optional, `commonmark` / `zero` + * - options (Object) + * + * Creates parser instanse with given config. Can be called without `new`. + * + * ##### presetName + * + * MarkdownIt provides named presets as a convenience to quickly + * enable/disable active syntax rules and options for common use cases. + * + * - ["commonmark"](https://github.com/markdown-it/markdown-it/blob/master/lib/presets/commonmark.js) - + * configures parser to strict [CommonMark](http://commonmark.org/) mode. + * - [default](https://github.com/markdown-it/markdown-it/blob/master/lib/presets/default.js) - + * similar to GFM, used when no preset name given. Enables all available rules, + * but still without html, typographer & autolinker. + * - ["zero"](https://github.com/markdown-it/markdown-it/blob/master/lib/presets/zero.js) - + * all rules disabled. Useful to quickly setup your config via `.enable()`. + * For example, when you need only `bold` and `italic` markup and nothing else. + * + * ##### options: + * + * - __html__ - `false`. Set `true` to enable HTML tags in source. Be careful! + * That's not safe! You may need external sanitizer to protect output from XSS. + * It's better to extend features via plugins, instead of enabling HTML. + * - __xhtmlOut__ - `false`. Set `true` to add '/' when closing single tags + * (`
`). This is needed only for full CommonMark compatibility. In real + * world you will need HTML output. + * - __breaks__ - `false`. Set `true` to convert `\n` in paragraphs into `
`. + * - __langPrefix__ - `language-`. CSS language class prefix for fenced blocks. + * Can be useful for external highlighters. + * - __linkify__ - `false`. Set `true` to autoconvert URL-like text to links. + * - __typographer__ - `false`. Set `true` to enable [some language-neutral + * replacement](https://github.com/markdown-it/markdown-it/blob/master/lib/rules_core/replacements.js) + + * quotes beautification (smartquotes). + * - __quotes__ - `“”‘’`, String or Array. Double + single quotes replacement + * pairs, when typographer enabled and smartquotes on. For example, you can + * use `'«»„“'` for Russian, `'„“‚‘'` for German, and + * `['«\xA0', '\xA0»', '‹\xA0', '\xA0›']` for French (including nbsp). + * - __highlight__ - `null`. Highlighter function for fenced code blocks. + * Highlighter `function (str, lang)` should return escaped HTML. It can also + * return empty string if the source was not changed and should be escaped + * externaly. If result starts with `): + * + * ```javascript + * var hljs = require('highlight.js') // https://highlightjs.org/ + * + * // Actual default values + * var md = require('markdown-it')({ + * highlight: function (str, lang) { + * if (lang && hljs.getLanguage(lang)) { + * try { + * return '
' +
+ *                hljs.highlight(lang, str, true).value +
+ *                '
'; + * } catch (__) {} + * } + * + * return '
' + md.utils.escapeHtml(str) + '
'; + * } + * }); + * ``` + * + **/ +function MarkdownIt(presetName, options) { + if (!(this instanceof MarkdownIt)) { + return new MarkdownIt(presetName, options); + } + + if (!options) { + if (!utils.isString(presetName)) { + options = presetName || {}; + presetName = 'default'; + } + } + + /** + * MarkdownIt#inline -> ParserInline + * + * Instance of [[ParserInline]]. You may need it to add new rules when + * writing plugins. For simple rules control use [[MarkdownIt.disable]] and + * [[MarkdownIt.enable]]. + **/ + this.inline = new ParserInline(); + + /** + * MarkdownIt#block -> ParserBlock + * + * Instance of [[ParserBlock]]. You may need it to add new rules when + * writing plugins. For simple rules control use [[MarkdownIt.disable]] and + * [[MarkdownIt.enable]]. + **/ + this.block = new ParserBlock(); + + /** + * MarkdownIt#core -> Core + * + * Instance of [[Core]] chain executor. You may need it to add new rules when + * writing plugins. For simple rules control use [[MarkdownIt.disable]] and + * [[MarkdownIt.enable]]. + **/ + this.core = new ParserCore(); + + /** + * MarkdownIt#renderer -> Renderer + * + * Instance of [[Renderer]]. Use it to modify output look. Or to add rendering + * rules for new token types, generated by plugins. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')(); + * + * function myToken(tokens, idx, options, env, self) { + * //... + * return result; + * }; + * + * md.renderer.rules['my_token'] = myToken + * ``` + * + * See [[Renderer]] docs and [source code](https://github.com/markdown-it/markdown-it/blob/master/lib/renderer.js). + **/ + this.renderer = new Renderer(); + + /** + * MarkdownIt#linkify -> LinkifyIt + * + * [linkify-it](https://github.com/markdown-it/linkify-it) instance. + * Used by [linkify](https://github.com/markdown-it/markdown-it/blob/master/lib/rules_core/linkify.js) + * rule. + **/ + this.linkify = new LinkifyIt(); + + /** + * MarkdownIt#validateLink(url) -> Boolean + * + * Link validation function. CommonMark allows too much in links. By default + * we disable `javascript:`, `vbscript:`, `file:` schemas, and almost all `data:...` schemas + * except some embedded image types. + * + * You can change this behaviour: + * + * ```javascript + * var md = require('markdown-it')(); + * // enable everything + * md.validateLink = function () { return true; } + * ``` + **/ + this.validateLink = validateLink; + + /** + * MarkdownIt#normalizeLink(url) -> String + * + * Function used to encode link url to a machine-readable format, + * which includes url-encoding, punycode, etc. + **/ + this.normalizeLink = normalizeLink; + + /** + * MarkdownIt#normalizeLinkText(url) -> String + * + * Function used to decode link url to a human-readable format` + **/ + this.normalizeLinkText = normalizeLinkText; + + + // Expose utils & helpers for easy acces from plugins + + /** + * MarkdownIt#utils -> utils + * + * Assorted utility functions, useful to write plugins. See details + * [here](https://github.com/markdown-it/markdown-it/blob/master/lib/common/utils.js). + **/ + this.utils = utils; + + /** + * MarkdownIt#helpers -> helpers + * + * Link components parser functions, useful to write plugins. See details + * [here](https://github.com/markdown-it/markdown-it/blob/master/lib/helpers). + **/ + this.helpers = utils.assign({}, helpers); + + + this.options = {}; + this.configure(presetName); + + if (options) { this.set(options); } +} + + +/** chainable + * MarkdownIt.set(options) + * + * Set parser options (in the same format as in constructor). Probably, you + * will never need it, but you can change options after constructor call. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')() + * .set({ html: true, breaks: true }) + * .set({ typographer, true }); + * ``` + * + * __Note:__ To achieve the best possible performance, don't modify a + * `markdown-it` instance options on the fly. If you need multiple configurations + * it's best to create multiple instances and initialize each with separate + * config. + **/ +MarkdownIt.prototype.set = function (options) { + utils.assign(this.options, options); + return this; +}; + + +/** chainable, internal + * MarkdownIt.configure(presets) + * + * Batch load of all options and compenent settings. This is internal method, + * and you probably will not need it. But if you with - see available presets + * and data structure [here](https://github.com/markdown-it/markdown-it/tree/master/lib/presets) + * + * We strongly recommend to use presets instead of direct config loads. That + * will give better compatibility with next versions. + **/ +MarkdownIt.prototype.configure = function (presets) { + var self = this, presetName; + + if (utils.isString(presets)) { + presetName = presets; + presets = config[presetName]; + if (!presets) { throw new Error('Wrong `markdown-it` preset "' + presetName + '", check name'); } + } + + if (!presets) { throw new Error('Wrong `markdown-it` preset, can\'t be empty'); } + + if (presets.options) { self.set(presets.options); } + + if (presets.components) { + Object.keys(presets.components).forEach(function (name) { + if (presets.components[name].rules) { + self[name].ruler.enableOnly(presets.components[name].rules); + } + if (presets.components[name].rules2) { + self[name].ruler2.enableOnly(presets.components[name].rules2); + } + }); + } + return this; +}; + + +/** chainable + * MarkdownIt.enable(list, ignoreInvalid) + * - list (String|Array): rule name or list of rule names to enable + * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. + * + * Enable list or rules. It will automatically find appropriate components, + * containing rules with given names. If rule not found, and `ignoreInvalid` + * not set - throws exception. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')() + * .enable(['sub', 'sup']) + * .disable('smartquotes'); + * ``` + **/ +MarkdownIt.prototype.enable = function (list, ignoreInvalid) { + var result = []; + + if (!Array.isArray(list)) { list = [ list ]; } + + [ 'core', 'block', 'inline' ].forEach(function (chain) { + result = result.concat(this[chain].ruler.enable(list, true)); + }, this); + + result = result.concat(this.inline.ruler2.enable(list, true)); + + var missed = list.filter(function (name) { return result.indexOf(name) < 0; }); + + if (missed.length && !ignoreInvalid) { + throw new Error('MarkdownIt. Failed to enable unknown rule(s): ' + missed); + } + + return this; +}; + + +/** chainable + * MarkdownIt.disable(list, ignoreInvalid) + * - list (String|Array): rule name or list of rule names to disable. + * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. + * + * The same as [[MarkdownIt.enable]], but turn specified rules off. + **/ +MarkdownIt.prototype.disable = function (list, ignoreInvalid) { + var result = []; + + if (!Array.isArray(list)) { list = [ list ]; } + + [ 'core', 'block', 'inline' ].forEach(function (chain) { + result = result.concat(this[chain].ruler.disable(list, true)); + }, this); + + result = result.concat(this.inline.ruler2.disable(list, true)); + + var missed = list.filter(function (name) { return result.indexOf(name) < 0; }); + + if (missed.length && !ignoreInvalid) { + throw new Error('MarkdownIt. Failed to disable unknown rule(s): ' + missed); + } + return this; +}; + + +/** chainable + * MarkdownIt.use(plugin, params) + * + * Load specified plugin with given params into current parser instance. + * It's just a sugar to call `plugin(md, params)` with curring. + * + * ##### Example + * + * ```javascript + * var iterator = require('markdown-it-for-inline'); + * var md = require('markdown-it')() + * .use(iterator, 'foo_replace', 'text', function (tokens, idx) { + * tokens[idx].content = tokens[idx].content.replace(/foo/g, 'bar'); + * }); + * ``` + **/ +MarkdownIt.prototype.use = function (plugin /*, params, ... */) { + var args = [ this ].concat(Array.prototype.slice.call(arguments, 1)); + plugin.apply(plugin, args); + return this; +}; + + +/** internal + * MarkdownIt.parse(src, env) -> Array + * - src (String): source string + * - env (Object): environment sandbox + * + * Parse input string and returns list of block tokens (special token type + * "inline" will contain list of inline tokens). You should not call this + * method directly, until you write custom renderer (for example, to produce + * AST). + * + * `env` is used to pass data between "distributed" rules and return additional + * metadata like reference info, needed for the renderer. It also can be used to + * inject data in specific cases. Usually, you will be ok to pass `{}`, + * and then pass updated object to renderer. + **/ +MarkdownIt.prototype.parse = function (src, env) { + if (typeof src !== 'string') { + throw new Error('Input data should be a String'); + } + + var state = new this.core.State(src, this, env); + + this.core.process(state); + + return state.tokens; +}; + + +/** + * MarkdownIt.render(src [, env]) -> String + * - src (String): source string + * - env (Object): environment sandbox + * + * Render markdown string into html. It does all magic for you :). + * + * `env` can be used to inject additional metadata (`{}` by default). + * But you will not need it with high probability. See also comment + * in [[MarkdownIt.parse]]. + **/ +MarkdownIt.prototype.render = function (src, env) { + env = env || {}; + + return this.renderer.render(this.parse(src, env), this.options, env); +}; + + +/** internal + * MarkdownIt.parseInline(src, env) -> Array + * - src (String): source string + * - env (Object): environment sandbox + * + * The same as [[MarkdownIt.parse]] but skip all block rules. It returns the + * block tokens list with the single `inline` element, containing parsed inline + * tokens in `children` property. Also updates `env` object. + **/ +MarkdownIt.prototype.parseInline = function (src, env) { + var state = new this.core.State(src, this, env); + + state.inlineMode = true; + this.core.process(state); + + return state.tokens; +}; + + +/** + * MarkdownIt.renderInline(src [, env]) -> String + * - src (String): source string + * - env (Object): environment sandbox + * + * Similar to [[MarkdownIt.render]] but for single paragraph content. Result + * will NOT be wrapped into `

` tags. + **/ +MarkdownIt.prototype.renderInline = function (src, env) { + env = env || {}; + + return this.renderer.render(this.parseInline(src, env), this.options, env); +}; + + +module.exports = MarkdownIt; + +},{"./common/utils":4,"./helpers":5,"./parser_block":10,"./parser_core":11,"./parser_inline":12,"./presets/commonmark":13,"./presets/default":14,"./presets/zero":15,"./renderer":16,"linkify-it":53,"mdurl":58,"punycode":60}],10:[function(require,module,exports){ +/** internal + * class ParserBlock + * + * Block-level tokenizer. + **/ +'use strict'; + + +var Ruler = require('./ruler'); + + +var _rules = [ + // First 2 params - rule name & source. Secondary array - list of rules, + // which can be terminated by this one. + [ 'table', require('./rules_block/table'), [ 'paragraph', 'reference' ] ], + [ 'code', require('./rules_block/code') ], + [ 'fence', require('./rules_block/fence'), [ 'paragraph', 'reference', 'blockquote', 'list' ] ], + [ 'blockquote', require('./rules_block/blockquote'), [ 'paragraph', 'reference', 'blockquote', 'list' ] ], + [ 'hr', require('./rules_block/hr'), [ 'paragraph', 'reference', 'blockquote', 'list' ] ], + [ 'list', require('./rules_block/list'), [ 'paragraph', 'reference', 'blockquote' ] ], + [ 'reference', require('./rules_block/reference') ], + [ 'heading', require('./rules_block/heading'), [ 'paragraph', 'reference', 'blockquote' ] ], + [ 'lheading', require('./rules_block/lheading') ], + [ 'html_block', require('./rules_block/html_block'), [ 'paragraph', 'reference', 'blockquote' ] ], + [ 'paragraph', require('./rules_block/paragraph') ] +]; + + +/** + * new ParserBlock() + **/ +function ParserBlock() { + /** + * ParserBlock#ruler -> Ruler + * + * [[Ruler]] instance. Keep configuration of block rules. + **/ + this.ruler = new Ruler(); + + for (var i = 0; i < _rules.length; i++) { + this.ruler.push(_rules[i][0], _rules[i][1], { alt: (_rules[i][2] || []).slice() }); + } +} + + +// Generate tokens for input range +// +ParserBlock.prototype.tokenize = function (state, startLine, endLine) { + var ok, i, + rules = this.ruler.getRules(''), + len = rules.length, + line = startLine, + hasEmptyLines = false, + maxNesting = state.md.options.maxNesting; + + while (line < endLine) { + state.line = line = state.skipEmptyLines(line); + if (line >= endLine) { break; } + + // Termination condition for nested calls. + // Nested calls currently used for blockquotes & lists + if (state.sCount[line] < state.blkIndent) { break; } + + // If nesting level exceeded - skip tail to the end. That's not ordinary + // situation and we should not care about content. + if (state.level >= maxNesting) { + state.line = endLine; + break; + } + + // Try all possible rules. + // On success, rule should: + // + // - update `state.line` + // - update `state.tokens` + // - return true + + for (i = 0; i < len; i++) { + ok = rules[i](state, line, endLine, false); + if (ok) { break; } + } + + // set state.tight if we had an empty line before current tag + // i.e. latest empty line should not count + state.tight = !hasEmptyLines; + + // paragraph might "eat" one newline after it in nested lists + if (state.isEmpty(state.line - 1)) { + hasEmptyLines = true; + } + + line = state.line; + + if (line < endLine && state.isEmpty(line)) { + hasEmptyLines = true; + line++; + state.line = line; + } + } +}; + + +/** + * ParserBlock.parse(str, md, env, outTokens) + * + * Process input string and push block tokens into `outTokens` + **/ +ParserBlock.prototype.parse = function (src, md, env, outTokens) { + var state; + + if (!src) { return; } + + state = new this.State(src, md, env, outTokens); + + this.tokenize(state, state.line, state.lineMax); +}; + + +ParserBlock.prototype.State = require('./rules_block/state_block'); + + +module.exports = ParserBlock; + +},{"./ruler":17,"./rules_block/blockquote":18,"./rules_block/code":19,"./rules_block/fence":20,"./rules_block/heading":21,"./rules_block/hr":22,"./rules_block/html_block":23,"./rules_block/lheading":24,"./rules_block/list":25,"./rules_block/paragraph":26,"./rules_block/reference":27,"./rules_block/state_block":28,"./rules_block/table":29}],11:[function(require,module,exports){ +/** internal + * class Core + * + * Top-level rules executor. Glues block/inline parsers and does intermediate + * transformations. + **/ +'use strict'; + + +var Ruler = require('./ruler'); + + +var _rules = [ + [ 'normalize', require('./rules_core/normalize') ], + [ 'block', require('./rules_core/block') ], + [ 'inline', require('./rules_core/inline') ], + [ 'linkify', require('./rules_core/linkify') ], + [ 'replacements', require('./rules_core/replacements') ], + [ 'smartquotes', require('./rules_core/smartquotes') ] +]; + + +/** + * new Core() + **/ +function Core() { + /** + * Core#ruler -> Ruler + * + * [[Ruler]] instance. Keep configuration of core rules. + **/ + this.ruler = new Ruler(); + + for (var i = 0; i < _rules.length; i++) { + this.ruler.push(_rules[i][0], _rules[i][1]); + } +} + + +/** + * Core.process(state) + * + * Executes core chain rules. + **/ +Core.prototype.process = function (state) { + var i, l, rules; + + rules = this.ruler.getRules(''); + + for (i = 0, l = rules.length; i < l; i++) { + rules[i](state); + } +}; + +Core.prototype.State = require('./rules_core/state_core'); + + +module.exports = Core; + +},{"./ruler":17,"./rules_core/block":30,"./rules_core/inline":31,"./rules_core/linkify":32,"./rules_core/normalize":33,"./rules_core/replacements":34,"./rules_core/smartquotes":35,"./rules_core/state_core":36}],12:[function(require,module,exports){ +/** internal + * class ParserInline + * + * Tokenizes paragraph content. + **/ +'use strict'; + + +var Ruler = require('./ruler'); + + +//////////////////////////////////////////////////////////////////////////////// +// Parser rules + +var _rules = [ + [ 'text', require('./rules_inline/text') ], + [ 'newline', require('./rules_inline/newline') ], + [ 'escape', require('./rules_inline/escape') ], + [ 'backticks', require('./rules_inline/backticks') ], + [ 'strikethrough', require('./rules_inline/strikethrough').tokenize ], + [ 'emphasis', require('./rules_inline/emphasis').tokenize ], + [ 'link', require('./rules_inline/link') ], + [ 'image', require('./rules_inline/image') ], + [ 'autolink', require('./rules_inline/autolink') ], + [ 'html_inline', require('./rules_inline/html_inline') ], + [ 'entity', require('./rules_inline/entity') ] +]; + +var _rules2 = [ + [ 'balance_pairs', require('./rules_inline/balance_pairs') ], + [ 'strikethrough', require('./rules_inline/strikethrough').postProcess ], + [ 'emphasis', require('./rules_inline/emphasis').postProcess ], + [ 'text_collapse', require('./rules_inline/text_collapse') ] +]; + + +/** + * new ParserInline() + **/ +function ParserInline() { + var i; + + /** + * ParserInline#ruler -> Ruler + * + * [[Ruler]] instance. Keep configuration of inline rules. + **/ + this.ruler = new Ruler(); + + for (i = 0; i < _rules.length; i++) { + this.ruler.push(_rules[i][0], _rules[i][1]); + } + + /** + * ParserInline#ruler2 -> Ruler + * + * [[Ruler]] instance. Second ruler used for post-processing + * (e.g. in emphasis-like rules). + **/ + this.ruler2 = new Ruler(); + + for (i = 0; i < _rules2.length; i++) { + this.ruler2.push(_rules2[i][0], _rules2[i][1]); + } +} + + +// Skip single token by running all rules in validation mode; +// returns `true` if any rule reported success +// +ParserInline.prototype.skipToken = function (state) { + var ok, i, pos = state.pos, + rules = this.ruler.getRules(''), + len = rules.length, + maxNesting = state.md.options.maxNesting, + cache = state.cache; + + + if (typeof cache[pos] !== 'undefined') { + state.pos = cache[pos]; + return; + } + + if (state.level < maxNesting) { + for (i = 0; i < len; i++) { + // Increment state.level and decrement it later to limit recursion. + // It's harmless to do here, because no tokens are created. But ideally, + // we'd need a separate private state variable for this purpose. + // + state.level++; + ok = rules[i](state, true); + state.level--; + + if (ok) { break; } + } + } else { + // Too much nesting, just skip until the end of the paragraph. + // + // NOTE: this will cause links to behave incorrectly in the following case, + // when an amount of `[` is exactly equal to `maxNesting + 1`: + // + // [[[[[[[[[[[[[[[[[[[[[foo]() + // + // TODO: remove this workaround when CM standard will allow nested links + // (we can replace it by preventing links from being parsed in + // validation mode) + // + state.pos = state.posMax; + } + + if (!ok) { state.pos++; } + cache[pos] = state.pos; +}; + + +// Generate tokens for input range +// +ParserInline.prototype.tokenize = function (state) { + var ok, i, + rules = this.ruler.getRules(''), + len = rules.length, + end = state.posMax, + maxNesting = state.md.options.maxNesting; + + while (state.pos < end) { + // Try all possible rules. + // On success, rule should: + // + // - update `state.pos` + // - update `state.tokens` + // - return true + + if (state.level < maxNesting) { + for (i = 0; i < len; i++) { + ok = rules[i](state, false); + if (ok) { break; } + } + } + + if (ok) { + if (state.pos >= end) { break; } + continue; + } + + state.pending += state.src[state.pos++]; + } + + if (state.pending) { + state.pushPending(); + } +}; + + +/** + * ParserInline.parse(str, md, env, outTokens) + * + * Process input string and push inline tokens into `outTokens` + **/ +ParserInline.prototype.parse = function (str, md, env, outTokens) { + var i, rules, len; + var state = new this.State(str, md, env, outTokens); + + this.tokenize(state); + + rules = this.ruler2.getRules(''); + len = rules.length; + + for (i = 0; i < len; i++) { + rules[i](state); + } +}; + + +ParserInline.prototype.State = require('./rules_inline/state_inline'); + + +module.exports = ParserInline; + +},{"./ruler":17,"./rules_inline/autolink":37,"./rules_inline/backticks":38,"./rules_inline/balance_pairs":39,"./rules_inline/emphasis":40,"./rules_inline/entity":41,"./rules_inline/escape":42,"./rules_inline/html_inline":43,"./rules_inline/image":44,"./rules_inline/link":45,"./rules_inline/newline":46,"./rules_inline/state_inline":47,"./rules_inline/strikethrough":48,"./rules_inline/text":49,"./rules_inline/text_collapse":50}],13:[function(require,module,exports){ +// Commonmark default options + +'use strict'; + + +module.exports = { + options: { + html: true, // Enable HTML tags in source + xhtmlOut: true, // Use '/' to close single tags (
) + breaks: false, // Convert '\n' in paragraphs into
+ langPrefix: 'language-', // CSS language prefix for fenced blocks + linkify: false, // autoconvert URL-like texts to links + + // Enable some language-neutral replacements + quotes beautification + typographer: false, + + // Double + single quotes replacement pairs, when typographer enabled, + // and smartquotes on. Could be either a String or an Array. + // + // For example, you can use '«»„“' for Russian, '„“‚‘' for German, + // and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp). + quotes: '\u201c\u201d\u2018\u2019', /* “”‘’ */ + + // Highlighter function. Should return escaped HTML, + // or '' if the source string is not changed and should be escaped externaly. + // If result starts with ) + breaks: false, // Convert '\n' in paragraphs into
+ langPrefix: 'language-', // CSS language prefix for fenced blocks + linkify: false, // autoconvert URL-like texts to links + + // Enable some language-neutral replacements + quotes beautification + typographer: false, + + // Double + single quotes replacement pairs, when typographer enabled, + // and smartquotes on. Could be either a String or an Array. + // + // For example, you can use '«»„“' for Russian, '„“‚‘' for German, + // and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp). + quotes: '\u201c\u201d\u2018\u2019', /* “”‘’ */ + + // Highlighter function. Should return escaped HTML, + // or '' if the source string is not changed and should be escaped externaly. + // If result starts with ) + breaks: false, // Convert '\n' in paragraphs into
+ langPrefix: 'language-', // CSS language prefix for fenced blocks + linkify: false, // autoconvert URL-like texts to links + + // Enable some language-neutral replacements + quotes beautification + typographer: false, + + // Double + single quotes replacement pairs, when typographer enabled, + // and smartquotes on. Could be either a String or an Array. + // + // For example, you can use '«»„“' for Russian, '„“‚‘' for German, + // and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp). + quotes: '\u201c\u201d\u2018\u2019', /* “”‘’ */ + + // Highlighter function. Should return escaped HTML, + // or '' if the source string is not changed and should be escaped externaly. + // If result starts with ' + + escapeHtml(tokens[idx].content) + + ''; +}; + + +default_rules.code_block = function (tokens, idx, options, env, slf) { + var token = tokens[idx]; + + return '' + + escapeHtml(tokens[idx].content) + + '\n'; +}; + + +default_rules.fence = function (tokens, idx, options, env, slf) { + var token = tokens[idx], + info = token.info ? unescapeAll(token.info).trim() : '', + langName = '', + highlighted, i, tmpAttrs, tmpToken; + + if (info) { + langName = info.split(/\s+/g)[0]; + } + + if (options.highlight) { + highlighted = options.highlight(token.content, langName) || escapeHtml(token.content); + } else { + highlighted = escapeHtml(token.content); + } + + if (highlighted.indexOf('' + + highlighted + + '\n'; + } + + + return '

'
+        + highlighted
+        + '
\n'; +}; + + +default_rules.image = function (tokens, idx, options, env, slf) { + var token = tokens[idx]; + + // "alt" attr MUST be set, even if empty. Because it's mandatory and + // should be placed on proper position for tests. + // + // Replace content with actual value + + token.attrs[token.attrIndex('alt')][1] = + slf.renderInlineAsText(token.children, options, env); + + return slf.renderToken(tokens, idx, options); +}; + + +default_rules.hardbreak = function (tokens, idx, options /*, env */) { + return options.xhtmlOut ? '
\n' : '
\n'; +}; +default_rules.softbreak = function (tokens, idx, options /*, env */) { + return options.breaks ? (options.xhtmlOut ? '
\n' : '
\n') : '\n'; +}; + + +default_rules.text = function (tokens, idx /*, options, env */) { + return escapeHtml(tokens[idx].content); +}; + + +default_rules.html_block = function (tokens, idx /*, options, env */) { + return tokens[idx].content; +}; +default_rules.html_inline = function (tokens, idx /*, options, env */) { + return tokens[idx].content; +}; + + +/** + * new Renderer() + * + * Creates new [[Renderer]] instance and fill [[Renderer#rules]] with defaults. + **/ +function Renderer() { + + /** + * Renderer#rules -> Object + * + * Contains render rules for tokens. Can be updated and extended. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')(); + * + * md.renderer.rules.strong_open = function () { return ''; }; + * md.renderer.rules.strong_close = function () { return ''; }; + * + * var result = md.renderInline(...); + * ``` + * + * Each rule is called as independent static function with fixed signature: + * + * ```javascript + * function my_token_render(tokens, idx, options, env, renderer) { + * // ... + * return renderedHTML; + * } + * ``` + * + * See [source code](https://github.com/markdown-it/markdown-it/blob/master/lib/renderer.js) + * for more details and examples. + **/ + this.rules = assign({}, default_rules); +} + + +/** + * Renderer.renderAttrs(token) -> String + * + * Render token attributes to string. + **/ +Renderer.prototype.renderAttrs = function renderAttrs(token) { + var i, l, result; + + if (!token.attrs) { return ''; } + + result = ''; + + for (i = 0, l = token.attrs.length; i < l; i++) { + result += ' ' + escapeHtml(token.attrs[i][0]) + '="' + escapeHtml(token.attrs[i][1]) + '"'; + } + + return result; +}; + + +/** + * Renderer.renderToken(tokens, idx, options) -> String + * - tokens (Array): list of tokens + * - idx (Numbed): token index to render + * - options (Object): params of parser instance + * + * Default token renderer. Can be overriden by custom function + * in [[Renderer#rules]]. + **/ +Renderer.prototype.renderToken = function renderToken(tokens, idx, options) { + var nextToken, + result = '', + needLf = false, + token = tokens[idx]; + + // Tight list paragraphs + if (token.hidden) { + return ''; + } + + // Insert a newline between hidden paragraph and subsequent opening + // block-level tag. + // + // For example, here we should insert a newline before blockquote: + // - a + // > + // + if (token.block && token.nesting !== -1 && idx && tokens[idx - 1].hidden) { + result += '\n'; + } + + // Add token name, e.g. ``. + // + needLf = false; + } + } + } + } + + result += needLf ? '>\n' : '>'; + + return result; +}; + + +/** + * Renderer.renderInline(tokens, options, env) -> String + * - tokens (Array): list on block tokens to renter + * - options (Object): params of parser instance + * - env (Object): additional data from parsed input (references, for example) + * + * The same as [[Renderer.render]], but for single token of `inline` type. + **/ +Renderer.prototype.renderInline = function (tokens, options, env) { + var type, + result = '', + rules = this.rules; + + for (var i = 0, len = tokens.length; i < len; i++) { + type = tokens[i].type; + + if (typeof rules[type] !== 'undefined') { + result += rules[type](tokens, i, options, env, this); + } else { + result += this.renderToken(tokens, i, options); + } + } + + return result; +}; + + +/** internal + * Renderer.renderInlineAsText(tokens, options, env) -> String + * - tokens (Array): list on block tokens to renter + * - options (Object): params of parser instance + * - env (Object): additional data from parsed input (references, for example) + * + * Special kludge for image `alt` attributes to conform CommonMark spec. + * Don't try to use it! Spec requires to show `alt` content with stripped markup, + * instead of simple escaping. + **/ +Renderer.prototype.renderInlineAsText = function (tokens, options, env) { + var result = ''; + + for (var i = 0, len = tokens.length; i < len; i++) { + if (tokens[i].type === 'text') { + result += tokens[i].content; + } else if (tokens[i].type === 'image') { + result += this.renderInlineAsText(tokens[i].children, options, env); + } + } + + return result; +}; + + +/** + * Renderer.render(tokens, options, env) -> String + * - tokens (Array): list on block tokens to renter + * - options (Object): params of parser instance + * - env (Object): additional data from parsed input (references, for example) + * + * Takes token stream and generates HTML. Probably, you will never need to call + * this method directly. + **/ +Renderer.prototype.render = function (tokens, options, env) { + var i, len, type, + result = '', + rules = this.rules; + + for (i = 0, len = tokens.length; i < len; i++) { + type = tokens[i].type; + + if (type === 'inline') { + result += this.renderInline(tokens[i].children, options, env); + } else if (typeof rules[type] !== 'undefined') { + result += rules[tokens[i].type](tokens, i, options, env, this); + } else { + result += this.renderToken(tokens, i, options, env); + } + } + + return result; +}; + +module.exports = Renderer; + +},{"./common/utils":4}],17:[function(require,module,exports){ +/** + * class Ruler + * + * Helper class, used by [[MarkdownIt#core]], [[MarkdownIt#block]] and + * [[MarkdownIt#inline]] to manage sequences of functions (rules): + * + * - keep rules in defined order + * - assign the name to each rule + * - enable/disable rules + * - add/replace rules + * - allow assign rules to additional named chains (in the same) + * - cacheing lists of active rules + * + * You will not need use this class directly until write plugins. For simple + * rules control use [[MarkdownIt.disable]], [[MarkdownIt.enable]] and + * [[MarkdownIt.use]]. + **/ +'use strict'; + + +/** + * new Ruler() + **/ +function Ruler() { + // List of added rules. Each element is: + // + // { + // name: XXX, + // enabled: Boolean, + // fn: Function(), + // alt: [ name2, name3 ] + // } + // + this.__rules__ = []; + + // Cached rule chains. + // + // First level - chain name, '' for default. + // Second level - diginal anchor for fast filtering by charcodes. + // + this.__cache__ = null; +} + +//////////////////////////////////////////////////////////////////////////////// +// Helper methods, should not be used directly + + +// Find rule index by name +// +Ruler.prototype.__find__ = function (name) { + for (var i = 0; i < this.__rules__.length; i++) { + if (this.__rules__[i].name === name) { + return i; + } + } + return -1; +}; + + +// Build rules lookup cache +// +Ruler.prototype.__compile__ = function () { + var self = this; + var chains = [ '' ]; + + // collect unique names + self.__rules__.forEach(function (rule) { + if (!rule.enabled) { return; } + + rule.alt.forEach(function (altName) { + if (chains.indexOf(altName) < 0) { + chains.push(altName); + } + }); + }); + + self.__cache__ = {}; + + chains.forEach(function (chain) { + self.__cache__[chain] = []; + self.__rules__.forEach(function (rule) { + if (!rule.enabled) { return; } + + if (chain && rule.alt.indexOf(chain) < 0) { return; } + + self.__cache__[chain].push(rule.fn); + }); + }); +}; + + +/** + * Ruler.at(name, fn [, options]) + * - name (String): rule name to replace. + * - fn (Function): new rule function. + * - options (Object): new rule options (not mandatory). + * + * Replace rule by name with new function & options. Throws error if name not + * found. + * + * ##### Options: + * + * - __alt__ - array with names of "alternate" chains. + * + * ##### Example + * + * Replace existing typographer replacement rule with new one: + * + * ```javascript + * var md = require('markdown-it')(); + * + * md.core.ruler.at('replacements', function replace(state) { + * //... + * }); + * ``` + **/ +Ruler.prototype.at = function (name, fn, options) { + var index = this.__find__(name); + var opt = options || {}; + + if (index === -1) { throw new Error('Parser rule not found: ' + name); } + + this.__rules__[index].fn = fn; + this.__rules__[index].alt = opt.alt || []; + this.__cache__ = null; +}; + + +/** + * Ruler.before(beforeName, ruleName, fn [, options]) + * - beforeName (String): new rule will be added before this one. + * - ruleName (String): name of added rule. + * - fn (Function): rule function. + * - options (Object): rule options (not mandatory). + * + * Add new rule to chain before one with given name. See also + * [[Ruler.after]], [[Ruler.push]]. + * + * ##### Options: + * + * - __alt__ - array with names of "alternate" chains. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')(); + * + * md.block.ruler.before('paragraph', 'my_rule', function replace(state) { + * //... + * }); + * ``` + **/ +Ruler.prototype.before = function (beforeName, ruleName, fn, options) { + var index = this.__find__(beforeName); + var opt = options || {}; + + if (index === -1) { throw new Error('Parser rule not found: ' + beforeName); } + + this.__rules__.splice(index, 0, { + name: ruleName, + enabled: true, + fn: fn, + alt: opt.alt || [] + }); + + this.__cache__ = null; +}; + + +/** + * Ruler.after(afterName, ruleName, fn [, options]) + * - afterName (String): new rule will be added after this one. + * - ruleName (String): name of added rule. + * - fn (Function): rule function. + * - options (Object): rule options (not mandatory). + * + * Add new rule to chain after one with given name. See also + * [[Ruler.before]], [[Ruler.push]]. + * + * ##### Options: + * + * - __alt__ - array with names of "alternate" chains. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')(); + * + * md.inline.ruler.after('text', 'my_rule', function replace(state) { + * //... + * }); + * ``` + **/ +Ruler.prototype.after = function (afterName, ruleName, fn, options) { + var index = this.__find__(afterName); + var opt = options || {}; + + if (index === -1) { throw new Error('Parser rule not found: ' + afterName); } + + this.__rules__.splice(index + 1, 0, { + name: ruleName, + enabled: true, + fn: fn, + alt: opt.alt || [] + }); + + this.__cache__ = null; +}; + +/** + * Ruler.push(ruleName, fn [, options]) + * - ruleName (String): name of added rule. + * - fn (Function): rule function. + * - options (Object): rule options (not mandatory). + * + * Push new rule to the end of chain. See also + * [[Ruler.before]], [[Ruler.after]]. + * + * ##### Options: + * + * - __alt__ - array with names of "alternate" chains. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')(); + * + * md.core.ruler.push('my_rule', function replace(state) { + * //... + * }); + * ``` + **/ +Ruler.prototype.push = function (ruleName, fn, options) { + var opt = options || {}; + + this.__rules__.push({ + name: ruleName, + enabled: true, + fn: fn, + alt: opt.alt || [] + }); + + this.__cache__ = null; +}; + + +/** + * Ruler.enable(list [, ignoreInvalid]) -> Array + * - list (String|Array): list of rule names to enable. + * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. + * + * Enable rules with given names. If any rule name not found - throw Error. + * Errors can be disabled by second param. + * + * Returns list of found rule names (if no exception happened). + * + * See also [[Ruler.disable]], [[Ruler.enableOnly]]. + **/ +Ruler.prototype.enable = function (list, ignoreInvalid) { + if (!Array.isArray(list)) { list = [ list ]; } + + var result = []; + + // Search by name and enable + list.forEach(function (name) { + var idx = this.__find__(name); + + if (idx < 0) { + if (ignoreInvalid) { return; } + throw new Error('Rules manager: invalid rule name ' + name); + } + this.__rules__[idx].enabled = true; + result.push(name); + }, this); + + this.__cache__ = null; + return result; +}; + + +/** + * Ruler.enableOnly(list [, ignoreInvalid]) + * - list (String|Array): list of rule names to enable (whitelist). + * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. + * + * Enable rules with given names, and disable everything else. If any rule name + * not found - throw Error. Errors can be disabled by second param. + * + * See also [[Ruler.disable]], [[Ruler.enable]]. + **/ +Ruler.prototype.enableOnly = function (list, ignoreInvalid) { + if (!Array.isArray(list)) { list = [ list ]; } + + this.__rules__.forEach(function (rule) { rule.enabled = false; }); + + this.enable(list, ignoreInvalid); +}; + + +/** + * Ruler.disable(list [, ignoreInvalid]) -> Array + * - list (String|Array): list of rule names to disable. + * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. + * + * Disable rules with given names. If any rule name not found - throw Error. + * Errors can be disabled by second param. + * + * Returns list of found rule names (if no exception happened). + * + * See also [[Ruler.enable]], [[Ruler.enableOnly]]. + **/ +Ruler.prototype.disable = function (list, ignoreInvalid) { + if (!Array.isArray(list)) { list = [ list ]; } + + var result = []; + + // Search by name and disable + list.forEach(function (name) { + var idx = this.__find__(name); + + if (idx < 0) { + if (ignoreInvalid) { return; } + throw new Error('Rules manager: invalid rule name ' + name); + } + this.__rules__[idx].enabled = false; + result.push(name); + }, this); + + this.__cache__ = null; + return result; +}; + + +/** + * Ruler.getRules(chainName) -> Array + * + * Return array of active functions (rules) for given chain name. It analyzes + * rules configuration, compiles caches if not exists and returns result. + * + * Default chain name is `''` (empty string). It can't be skipped. That's + * done intentionally, to keep signature monomorphic for high speed. + **/ +Ruler.prototype.getRules = function (chainName) { + if (this.__cache__ === null) { + this.__compile__(); + } + + // Chain can be empty, if rules disabled. But we still have to return Array. + return this.__cache__[chainName] || []; +}; + +module.exports = Ruler; + +},{}],18:[function(require,module,exports){ +// Block quotes + +'use strict'; + +var isSpace = require('../common/utils').isSpace; + + +module.exports = function blockquote(state, startLine, endLine, silent) { + var adjustTab, + ch, + i, + initial, + l, + lastLineEmpty, + lines, + nextLine, + offset, + oldBMarks, + oldBSCount, + oldIndent, + oldParentType, + oldSCount, + oldTShift, + spaceAfterMarker, + terminate, + terminatorRules, + token, + wasOutdented, + oldLineMax = state.lineMax, + pos = state.bMarks[startLine] + state.tShift[startLine], + max = state.eMarks[startLine]; + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + + // check the block quote marker + if (state.src.charCodeAt(pos++) !== 0x3E/* > */) { return false; } + + // we know that it's going to be a valid blockquote, + // so no point trying to find the end of it in silent mode + if (silent) { return true; } + + // skip spaces after ">" and re-calculate offset + initial = offset = state.sCount[startLine] + pos - (state.bMarks[startLine] + state.tShift[startLine]); + + // skip one optional space after '>' + if (state.src.charCodeAt(pos) === 0x20 /* space */) { + // ' > test ' + // ^ -- position start of line here: + pos++; + initial++; + offset++; + adjustTab = false; + spaceAfterMarker = true; + } else if (state.src.charCodeAt(pos) === 0x09 /* tab */) { + spaceAfterMarker = true; + + if ((state.bsCount[startLine] + offset) % 4 === 3) { + // ' >\t test ' + // ^ -- position start of line here (tab has width===1) + pos++; + initial++; + offset++; + adjustTab = false; + } else { + // ' >\t test ' + // ^ -- position start of line here + shift bsCount slightly + // to make extra space appear + adjustTab = true; + } + } else { + spaceAfterMarker = false; + } + + oldBMarks = [ state.bMarks[startLine] ]; + state.bMarks[startLine] = pos; + + while (pos < max) { + ch = state.src.charCodeAt(pos); + + if (isSpace(ch)) { + if (ch === 0x09) { + offset += 4 - (offset + state.bsCount[startLine] + (adjustTab ? 1 : 0)) % 4; + } else { + offset++; + } + } else { + break; + } + + pos++; + } + + oldBSCount = [ state.bsCount[startLine] ]; + state.bsCount[startLine] = state.sCount[startLine] + 1 + (spaceAfterMarker ? 1 : 0); + + lastLineEmpty = pos >= max; + + oldSCount = [ state.sCount[startLine] ]; + state.sCount[startLine] = offset - initial; + + oldTShift = [ state.tShift[startLine] ]; + state.tShift[startLine] = pos - state.bMarks[startLine]; + + terminatorRules = state.md.block.ruler.getRules('blockquote'); + + oldParentType = state.parentType; + state.parentType = 'blockquote'; + wasOutdented = false; + + // Search the end of the block + // + // Block ends with either: + // 1. an empty line outside: + // ``` + // > test + // + // ``` + // 2. an empty line inside: + // ``` + // > + // test + // ``` + // 3. another tag: + // ``` + // > test + // - - - + // ``` + for (nextLine = startLine + 1; nextLine < endLine; nextLine++) { + // check if it's outdented, i.e. it's inside list item and indented + // less than said list item: + // + // ``` + // 1. anything + // > current blockquote + // 2. checking this line + // ``` + if (state.sCount[nextLine] < state.blkIndent) wasOutdented = true; + + pos = state.bMarks[nextLine] + state.tShift[nextLine]; + max = state.eMarks[nextLine]; + + if (pos >= max) { + // Case 1: line is not inside the blockquote, and this line is empty. + break; + } + + if (state.src.charCodeAt(pos++) === 0x3E/* > */ && !wasOutdented) { + // This line is inside the blockquote. + + // skip spaces after ">" and re-calculate offset + initial = offset = state.sCount[nextLine] + pos - (state.bMarks[nextLine] + state.tShift[nextLine]); + + // skip one optional space after '>' + if (state.src.charCodeAt(pos) === 0x20 /* space */) { + // ' > test ' + // ^ -- position start of line here: + pos++; + initial++; + offset++; + adjustTab = false; + spaceAfterMarker = true; + } else if (state.src.charCodeAt(pos) === 0x09 /* tab */) { + spaceAfterMarker = true; + + if ((state.bsCount[nextLine] + offset) % 4 === 3) { + // ' >\t test ' + // ^ -- position start of line here (tab has width===1) + pos++; + initial++; + offset++; + adjustTab = false; + } else { + // ' >\t test ' + // ^ -- position start of line here + shift bsCount slightly + // to make extra space appear + adjustTab = true; + } + } else { + spaceAfterMarker = false; + } + + oldBMarks.push(state.bMarks[nextLine]); + state.bMarks[nextLine] = pos; + + while (pos < max) { + ch = state.src.charCodeAt(pos); + + if (isSpace(ch)) { + if (ch === 0x09) { + offset += 4 - (offset + state.bsCount[nextLine] + (adjustTab ? 1 : 0)) % 4; + } else { + offset++; + } + } else { + break; + } + + pos++; + } + + lastLineEmpty = pos >= max; + + oldBSCount.push(state.bsCount[nextLine]); + state.bsCount[nextLine] = state.sCount[nextLine] + 1 + (spaceAfterMarker ? 1 : 0); + + oldSCount.push(state.sCount[nextLine]); + state.sCount[nextLine] = offset - initial; + + oldTShift.push(state.tShift[nextLine]); + state.tShift[nextLine] = pos - state.bMarks[nextLine]; + continue; + } + + // Case 2: line is not inside the blockquote, and the last line was empty. + if (lastLineEmpty) { break; } + + // Case 3: another tag found. + terminate = false; + for (i = 0, l = terminatorRules.length; i < l; i++) { + if (terminatorRules[i](state, nextLine, endLine, true)) { + terminate = true; + break; + } + } + + if (terminate) { + // Quirk to enforce "hard termination mode" for paragraphs; + // normally if you call `tokenize(state, startLine, nextLine)`, + // paragraphs will look below nextLine for paragraph continuation, + // but if blockquote is terminated by another tag, they shouldn't + state.lineMax = nextLine; + + if (state.blkIndent !== 0) { + // state.blkIndent was non-zero, we now set it to zero, + // so we need to re-calculate all offsets to appear as + // if indent wasn't changed + oldBMarks.push(state.bMarks[nextLine]); + oldBSCount.push(state.bsCount[nextLine]); + oldTShift.push(state.tShift[nextLine]); + oldSCount.push(state.sCount[nextLine]); + state.sCount[nextLine] -= state.blkIndent; + } + + break; + } + + oldBMarks.push(state.bMarks[nextLine]); + oldBSCount.push(state.bsCount[nextLine]); + oldTShift.push(state.tShift[nextLine]); + oldSCount.push(state.sCount[nextLine]); + + // A negative indentation means that this is a paragraph continuation + // + state.sCount[nextLine] = -1; + } + + oldIndent = state.blkIndent; + state.blkIndent = 0; + + token = state.push('blockquote_open', 'blockquote', 1); + token.markup = '>'; + token.map = lines = [ startLine, 0 ]; + + state.md.block.tokenize(state, startLine, nextLine); + + token = state.push('blockquote_close', 'blockquote', -1); + token.markup = '>'; + + state.lineMax = oldLineMax; + state.parentType = oldParentType; + lines[1] = state.line; + + // Restore original tShift; this might not be necessary since the parser + // has already been here, but just to make sure we can do that. + for (i = 0; i < oldTShift.length; i++) { + state.bMarks[i + startLine] = oldBMarks[i]; + state.tShift[i + startLine] = oldTShift[i]; + state.sCount[i + startLine] = oldSCount[i]; + state.bsCount[i + startLine] = oldBSCount[i]; + } + state.blkIndent = oldIndent; + + return true; +}; + +},{"../common/utils":4}],19:[function(require,module,exports){ +// Code block (4 spaces padded) + +'use strict'; + + +module.exports = function code(state, startLine, endLine/*, silent*/) { + var nextLine, last, token; + + if (state.sCount[startLine] - state.blkIndent < 4) { return false; } + + last = nextLine = startLine + 1; + + while (nextLine < endLine) { + if (state.isEmpty(nextLine)) { + nextLine++; + continue; + } + + if (state.sCount[nextLine] - state.blkIndent >= 4) { + nextLine++; + last = nextLine; + continue; + } + break; + } + + state.line = last; + + token = state.push('code_block', 'code', 0); + token.content = state.getLines(startLine, last, 4 + state.blkIndent, true); + token.map = [ startLine, state.line ]; + + return true; +}; + +},{}],20:[function(require,module,exports){ +// fences (``` lang, ~~~ lang) + +'use strict'; + + +module.exports = function fence(state, startLine, endLine, silent) { + var marker, len, params, nextLine, mem, token, markup, + haveEndMarker = false, + pos = state.bMarks[startLine] + state.tShift[startLine], + max = state.eMarks[startLine]; + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + + if (pos + 3 > max) { return false; } + + marker = state.src.charCodeAt(pos); + + if (marker !== 0x7E/* ~ */ && marker !== 0x60 /* ` */) { + return false; + } + + // scan marker length + mem = pos; + pos = state.skipChars(pos, marker); + + len = pos - mem; + + if (len < 3) { return false; } + + markup = state.src.slice(mem, pos); + params = state.src.slice(pos, max); + + if (marker === 0x60 /* ` */) { + if (params.indexOf(String.fromCharCode(marker)) >= 0) { + return false; + } + } + + // Since start is found, we can report success here in validation mode + if (silent) { return true; } + + // search end of block + nextLine = startLine; + + for (;;) { + nextLine++; + if (nextLine >= endLine) { + // unclosed block should be autoclosed by end of document. + // also block seems to be autoclosed by end of parent + break; + } + + pos = mem = state.bMarks[nextLine] + state.tShift[nextLine]; + max = state.eMarks[nextLine]; + + if (pos < max && state.sCount[nextLine] < state.blkIndent) { + // non-empty line with negative indent should stop the list: + // - ``` + // test + break; + } + + if (state.src.charCodeAt(pos) !== marker) { continue; } + + if (state.sCount[nextLine] - state.blkIndent >= 4) { + // closing fence should be indented less than 4 spaces + continue; + } + + pos = state.skipChars(pos, marker); + + // closing code fence must be at least as long as the opening one + if (pos - mem < len) { continue; } + + // make sure tail has spaces only + pos = state.skipSpaces(pos); + + if (pos < max) { continue; } + + haveEndMarker = true; + // found! + break; + } + + // If a fence has heading spaces, they should be removed from its inner block + len = state.sCount[startLine]; + + state.line = nextLine + (haveEndMarker ? 1 : 0); + + token = state.push('fence', 'code', 0); + token.info = params; + token.content = state.getLines(startLine + 1, nextLine, len, true); + token.markup = markup; + token.map = [ startLine, state.line ]; + + return true; +}; + +},{}],21:[function(require,module,exports){ +// heading (#, ##, ...) + +'use strict'; + +var isSpace = require('../common/utils').isSpace; + + +module.exports = function heading(state, startLine, endLine, silent) { + var ch, level, tmp, token, + pos = state.bMarks[startLine] + state.tShift[startLine], + max = state.eMarks[startLine]; + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + + ch = state.src.charCodeAt(pos); + + if (ch !== 0x23/* # */ || pos >= max) { return false; } + + // count heading level + level = 1; + ch = state.src.charCodeAt(++pos); + while (ch === 0x23/* # */ && pos < max && level <= 6) { + level++; + ch = state.src.charCodeAt(++pos); + } + + if (level > 6 || (pos < max && !isSpace(ch))) { return false; } + + if (silent) { return true; } + + // Let's cut tails like ' ### ' from the end of string + + max = state.skipSpacesBack(max, pos); + tmp = state.skipCharsBack(max, 0x23, pos); // # + if (tmp > pos && isSpace(state.src.charCodeAt(tmp - 1))) { + max = tmp; + } + + state.line = startLine + 1; + + token = state.push('heading_open', 'h' + String(level), 1); + token.markup = '########'.slice(0, level); + token.map = [ startLine, state.line ]; + + token = state.push('inline', '', 0); + token.content = state.src.slice(pos, max).trim(); + token.map = [ startLine, state.line ]; + token.children = []; + + token = state.push('heading_close', 'h' + String(level), -1); + token.markup = '########'.slice(0, level); + + return true; +}; + +},{"../common/utils":4}],22:[function(require,module,exports){ +// Horizontal rule + +'use strict'; + +var isSpace = require('../common/utils').isSpace; + + +module.exports = function hr(state, startLine, endLine, silent) { + var marker, cnt, ch, token, + pos = state.bMarks[startLine] + state.tShift[startLine], + max = state.eMarks[startLine]; + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + + marker = state.src.charCodeAt(pos++); + + // Check hr marker + if (marker !== 0x2A/* * */ && + marker !== 0x2D/* - */ && + marker !== 0x5F/* _ */) { + return false; + } + + // markers can be mixed with spaces, but there should be at least 3 of them + + cnt = 1; + while (pos < max) { + ch = state.src.charCodeAt(pos++); + if (ch !== marker && !isSpace(ch)) { return false; } + if (ch === marker) { cnt++; } + } + + if (cnt < 3) { return false; } + + if (silent) { return true; } + + state.line = startLine + 1; + + token = state.push('hr', 'hr', 0); + token.map = [ startLine, state.line ]; + token.markup = Array(cnt + 1).join(String.fromCharCode(marker)); + + return true; +}; + +},{"../common/utils":4}],23:[function(require,module,exports){ +// HTML block + +'use strict'; + + +var block_names = require('../common/html_blocks'); +var HTML_OPEN_CLOSE_TAG_RE = require('../common/html_re').HTML_OPEN_CLOSE_TAG_RE; + +// An array of opening and corresponding closing sequences for html tags, +// last argument defines whether it can terminate a paragraph or not +// +var HTML_SEQUENCES = [ + [ /^<(script|pre|style)(?=(\s|>|$))/i, /<\/(script|pre|style)>/i, true ], + [ /^/, true ], + [ /^<\?/, /\?>/, true ], + [ /^/, true ], + [ /^/, true ], + [ new RegExp('^|$))', 'i'), /^$/, true ], + [ new RegExp(HTML_OPEN_CLOSE_TAG_RE.source + '\\s*$'), /^$/, false ] +]; + + +module.exports = function html_block(state, startLine, endLine, silent) { + var i, nextLine, token, lineText, + pos = state.bMarks[startLine] + state.tShift[startLine], + max = state.eMarks[startLine]; + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + + if (!state.md.options.html) { return false; } + + if (state.src.charCodeAt(pos) !== 0x3C/* < */) { return false; } + + lineText = state.src.slice(pos, max); + + for (i = 0; i < HTML_SEQUENCES.length; i++) { + if (HTML_SEQUENCES[i][0].test(lineText)) { break; } + } + + if (i === HTML_SEQUENCES.length) { return false; } + + if (silent) { + // true if this sequence can be a terminator, false otherwise + return HTML_SEQUENCES[i][2]; + } + + nextLine = startLine + 1; + + // If we are here - we detected HTML block. + // Let's roll down till block end. + if (!HTML_SEQUENCES[i][1].test(lineText)) { + for (; nextLine < endLine; nextLine++) { + if (state.sCount[nextLine] < state.blkIndent) { break; } + + pos = state.bMarks[nextLine] + state.tShift[nextLine]; + max = state.eMarks[nextLine]; + lineText = state.src.slice(pos, max); + + if (HTML_SEQUENCES[i][1].test(lineText)) { + if (lineText.length !== 0) { nextLine++; } + break; + } + } + } + + state.line = nextLine; + + token = state.push('html_block', '', 0); + token.map = [ startLine, nextLine ]; + token.content = state.getLines(startLine, nextLine, state.blkIndent, true); + + return true; +}; + +},{"../common/html_blocks":2,"../common/html_re":3}],24:[function(require,module,exports){ +// lheading (---, ===) + +'use strict'; + + +module.exports = function lheading(state, startLine, endLine/*, silent*/) { + var content, terminate, i, l, token, pos, max, level, marker, + nextLine = startLine + 1, oldParentType, + terminatorRules = state.md.block.ruler.getRules('paragraph'); + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + + oldParentType = state.parentType; + state.parentType = 'paragraph'; // use paragraph to match terminatorRules + + // jump line-by-line until empty one or EOF + for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) { + // this would be a code block normally, but after paragraph + // it's considered a lazy continuation regardless of what's there + if (state.sCount[nextLine] - state.blkIndent > 3) { continue; } + + // + // Check for underline in setext header + // + if (state.sCount[nextLine] >= state.blkIndent) { + pos = state.bMarks[nextLine] + state.tShift[nextLine]; + max = state.eMarks[nextLine]; + + if (pos < max) { + marker = state.src.charCodeAt(pos); + + if (marker === 0x2D/* - */ || marker === 0x3D/* = */) { + pos = state.skipChars(pos, marker); + pos = state.skipSpaces(pos); + + if (pos >= max) { + level = (marker === 0x3D/* = */ ? 1 : 2); + break; + } + } + } + } + + // quirk for blockquotes, this line should already be checked by that rule + if (state.sCount[nextLine] < 0) { continue; } + + // Some tags can terminate paragraph without empty line. + terminate = false; + for (i = 0, l = terminatorRules.length; i < l; i++) { + if (terminatorRules[i](state, nextLine, endLine, true)) { + terminate = true; + break; + } + } + if (terminate) { break; } + } + + if (!level) { + // Didn't find valid underline + return false; + } + + content = state.getLines(startLine, nextLine, state.blkIndent, false).trim(); + + state.line = nextLine + 1; + + token = state.push('heading_open', 'h' + String(level), 1); + token.markup = String.fromCharCode(marker); + token.map = [ startLine, state.line ]; + + token = state.push('inline', '', 0); + token.content = content; + token.map = [ startLine, state.line - 1 ]; + token.children = []; + + token = state.push('heading_close', 'h' + String(level), -1); + token.markup = String.fromCharCode(marker); + + state.parentType = oldParentType; + + return true; +}; + +},{}],25:[function(require,module,exports){ +// Lists + +'use strict'; + +var isSpace = require('../common/utils').isSpace; + + +// Search `[-+*][\n ]`, returns next pos after marker on success +// or -1 on fail. +function skipBulletListMarker(state, startLine) { + var marker, pos, max, ch; + + pos = state.bMarks[startLine] + state.tShift[startLine]; + max = state.eMarks[startLine]; + + marker = state.src.charCodeAt(pos++); + // Check bullet + if (marker !== 0x2A/* * */ && + marker !== 0x2D/* - */ && + marker !== 0x2B/* + */) { + return -1; + } + + if (pos < max) { + ch = state.src.charCodeAt(pos); + + if (!isSpace(ch)) { + // " -test " - is not a list item + return -1; + } + } + + return pos; +} + +// Search `\d+[.)][\n ]`, returns next pos after marker on success +// or -1 on fail. +function skipOrderedListMarker(state, startLine) { + var ch, + start = state.bMarks[startLine] + state.tShift[startLine], + pos = start, + max = state.eMarks[startLine]; + + // List marker should have at least 2 chars (digit + dot) + if (pos + 1 >= max) { return -1; } + + ch = state.src.charCodeAt(pos++); + + if (ch < 0x30/* 0 */ || ch > 0x39/* 9 */) { return -1; } + + for (;;) { + // EOL -> fail + if (pos >= max) { return -1; } + + ch = state.src.charCodeAt(pos++); + + if (ch >= 0x30/* 0 */ && ch <= 0x39/* 9 */) { + + // List marker should have no more than 9 digits + // (prevents integer overflow in browsers) + if (pos - start >= 10) { return -1; } + + continue; + } + + // found valid marker + if (ch === 0x29/* ) */ || ch === 0x2e/* . */) { + break; + } + + return -1; + } + + + if (pos < max) { + ch = state.src.charCodeAt(pos); + + if (!isSpace(ch)) { + // " 1.test " - is not a list item + return -1; + } + } + return pos; +} + +function markTightParagraphs(state, idx) { + var i, l, + level = state.level + 2; + + for (i = idx + 2, l = state.tokens.length - 2; i < l; i++) { + if (state.tokens[i].level === level && state.tokens[i].type === 'paragraph_open') { + state.tokens[i + 2].hidden = true; + state.tokens[i].hidden = true; + i += 2; + } + } +} + + +module.exports = function list(state, startLine, endLine, silent) { + var ch, + contentStart, + i, + indent, + indentAfterMarker, + initial, + isOrdered, + itemLines, + l, + listLines, + listTokIdx, + markerCharCode, + markerValue, + max, + nextLine, + offset, + oldListIndent, + oldParentType, + oldSCount, + oldTShift, + oldTight, + pos, + posAfterMarker, + prevEmptyEnd, + start, + terminate, + terminatorRules, + token, + isTerminatingParagraph = false, + tight = true; + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + + // Special case: + // - item 1 + // - item 2 + // - item 3 + // - item 4 + // - this one is a paragraph continuation + if (state.listIndent >= 0 && + state.sCount[startLine] - state.listIndent >= 4 && + state.sCount[startLine] < state.blkIndent) { + return false; + } + + // limit conditions when list can interrupt + // a paragraph (validation mode only) + if (silent && state.parentType === 'paragraph') { + // Next list item should still terminate previous list item; + // + // This code can fail if plugins use blkIndent as well as lists, + // but I hope the spec gets fixed long before that happens. + // + if (state.tShift[startLine] >= state.blkIndent) { + isTerminatingParagraph = true; + } + } + + // Detect list type and position after marker + if ((posAfterMarker = skipOrderedListMarker(state, startLine)) >= 0) { + isOrdered = true; + start = state.bMarks[startLine] + state.tShift[startLine]; + markerValue = Number(state.src.substr(start, posAfterMarker - start - 1)); + + // If we're starting a new ordered list right after + // a paragraph, it should start with 1. + if (isTerminatingParagraph && markerValue !== 1) return false; + + } else if ((posAfterMarker = skipBulletListMarker(state, startLine)) >= 0) { + isOrdered = false; + + } else { + return false; + } + + // If we're starting a new unordered list right after + // a paragraph, first line should not be empty. + if (isTerminatingParagraph) { + if (state.skipSpaces(posAfterMarker) >= state.eMarks[startLine]) return false; + } + + // We should terminate list on style change. Remember first one to compare. + markerCharCode = state.src.charCodeAt(posAfterMarker - 1); + + // For validation mode we can terminate immediately + if (silent) { return true; } + + // Start list + listTokIdx = state.tokens.length; + + if (isOrdered) { + token = state.push('ordered_list_open', 'ol', 1); + if (markerValue !== 1) { + token.attrs = [ [ 'start', markerValue ] ]; + } + + } else { + token = state.push('bullet_list_open', 'ul', 1); + } + + token.map = listLines = [ startLine, 0 ]; + token.markup = String.fromCharCode(markerCharCode); + + // + // Iterate list items + // + + nextLine = startLine; + prevEmptyEnd = false; + terminatorRules = state.md.block.ruler.getRules('list'); + + oldParentType = state.parentType; + state.parentType = 'list'; + + while (nextLine < endLine) { + pos = posAfterMarker; + max = state.eMarks[nextLine]; + + initial = offset = state.sCount[nextLine] + posAfterMarker - (state.bMarks[startLine] + state.tShift[startLine]); + + while (pos < max) { + ch = state.src.charCodeAt(pos); + + if (ch === 0x09) { + offset += 4 - (offset + state.bsCount[nextLine]) % 4; + } else if (ch === 0x20) { + offset++; + } else { + break; + } + + pos++; + } + + contentStart = pos; + + if (contentStart >= max) { + // trimming space in "- \n 3" case, indent is 1 here + indentAfterMarker = 1; + } else { + indentAfterMarker = offset - initial; + } + + // If we have more than 4 spaces, the indent is 1 + // (the rest is just indented code block) + if (indentAfterMarker > 4) { indentAfterMarker = 1; } + + // " - test" + // ^^^^^ - calculating total length of this thing + indent = initial + indentAfterMarker; + + // Run subparser & write tokens + token = state.push('list_item_open', 'li', 1); + token.markup = String.fromCharCode(markerCharCode); + token.map = itemLines = [ startLine, 0 ]; + + // change current state, then restore it after parser subcall + oldTight = state.tight; + oldTShift = state.tShift[startLine]; + oldSCount = state.sCount[startLine]; + + // - example list + // ^ listIndent position will be here + // ^ blkIndent position will be here + // + oldListIndent = state.listIndent; + state.listIndent = state.blkIndent; + state.blkIndent = indent; + + state.tight = true; + state.tShift[startLine] = contentStart - state.bMarks[startLine]; + state.sCount[startLine] = offset; + + if (contentStart >= max && state.isEmpty(startLine + 1)) { + // workaround for this case + // (list item is empty, list terminates before "foo"): + // ~~~~~~~~ + // - + // + // foo + // ~~~~~~~~ + state.line = Math.min(state.line + 2, endLine); + } else { + state.md.block.tokenize(state, startLine, endLine, true); + } + + // If any of list item is tight, mark list as tight + if (!state.tight || prevEmptyEnd) { + tight = false; + } + // Item become loose if finish with empty line, + // but we should filter last element, because it means list finish + prevEmptyEnd = (state.line - startLine) > 1 && state.isEmpty(state.line - 1); + + state.blkIndent = state.listIndent; + state.listIndent = oldListIndent; + state.tShift[startLine] = oldTShift; + state.sCount[startLine] = oldSCount; + state.tight = oldTight; + + token = state.push('list_item_close', 'li', -1); + token.markup = String.fromCharCode(markerCharCode); + + nextLine = startLine = state.line; + itemLines[1] = nextLine; + contentStart = state.bMarks[startLine]; + + if (nextLine >= endLine) { break; } + + // + // Try to check if list is terminated or continued. + // + if (state.sCount[nextLine] < state.blkIndent) { break; } + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { break; } + + // fail if terminating block found + terminate = false; + for (i = 0, l = terminatorRules.length; i < l; i++) { + if (terminatorRules[i](state, nextLine, endLine, true)) { + terminate = true; + break; + } + } + if (terminate) { break; } + + // fail if list has another type + if (isOrdered) { + posAfterMarker = skipOrderedListMarker(state, nextLine); + if (posAfterMarker < 0) { break; } + } else { + posAfterMarker = skipBulletListMarker(state, nextLine); + if (posAfterMarker < 0) { break; } + } + + if (markerCharCode !== state.src.charCodeAt(posAfterMarker - 1)) { break; } + } + + // Finalize list + if (isOrdered) { + token = state.push('ordered_list_close', 'ol', -1); + } else { + token = state.push('bullet_list_close', 'ul', -1); + } + token.markup = String.fromCharCode(markerCharCode); + + listLines[1] = nextLine; + state.line = nextLine; + + state.parentType = oldParentType; + + // mark paragraphs tight if needed + if (tight) { + markTightParagraphs(state, listTokIdx); + } + + return true; +}; + +},{"../common/utils":4}],26:[function(require,module,exports){ +// Paragraph + +'use strict'; + + +module.exports = function paragraph(state, startLine/*, endLine*/) { + var content, terminate, i, l, token, oldParentType, + nextLine = startLine + 1, + terminatorRules = state.md.block.ruler.getRules('paragraph'), + endLine = state.lineMax; + + oldParentType = state.parentType; + state.parentType = 'paragraph'; + + // jump line-by-line until empty one or EOF + for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) { + // this would be a code block normally, but after paragraph + // it's considered a lazy continuation regardless of what's there + if (state.sCount[nextLine] - state.blkIndent > 3) { continue; } + + // quirk for blockquotes, this line should already be checked by that rule + if (state.sCount[nextLine] < 0) { continue; } + + // Some tags can terminate paragraph without empty line. + terminate = false; + for (i = 0, l = terminatorRules.length; i < l; i++) { + if (terminatorRules[i](state, nextLine, endLine, true)) { + terminate = true; + break; + } + } + if (terminate) { break; } + } + + content = state.getLines(startLine, nextLine, state.blkIndent, false).trim(); + + state.line = nextLine; + + token = state.push('paragraph_open', 'p', 1); + token.map = [ startLine, state.line ]; + + token = state.push('inline', '', 0); + token.content = content; + token.map = [ startLine, state.line ]; + token.children = []; + + token = state.push('paragraph_close', 'p', -1); + + state.parentType = oldParentType; + + return true; +}; + +},{}],27:[function(require,module,exports){ +'use strict'; + + +var normalizeReference = require('../common/utils').normalizeReference; +var isSpace = require('../common/utils').isSpace; + + +module.exports = function reference(state, startLine, _endLine, silent) { + var ch, + destEndPos, + destEndLineNo, + endLine, + href, + i, + l, + label, + labelEnd, + oldParentType, + res, + start, + str, + terminate, + terminatorRules, + title, + lines = 0, + pos = state.bMarks[startLine] + state.tShift[startLine], + max = state.eMarks[startLine], + nextLine = startLine + 1; + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + + if (state.src.charCodeAt(pos) !== 0x5B/* [ */) { return false; } + + // Simple check to quickly interrupt scan on [link](url) at the start of line. + // Can be useful on practice: https://github.com/markdown-it/markdown-it/issues/54 + while (++pos < max) { + if (state.src.charCodeAt(pos) === 0x5D /* ] */ && + state.src.charCodeAt(pos - 1) !== 0x5C/* \ */) { + if (pos + 1 === max) { return false; } + if (state.src.charCodeAt(pos + 1) !== 0x3A/* : */) { return false; } + break; + } + } + + endLine = state.lineMax; + + // jump line-by-line until empty one or EOF + terminatorRules = state.md.block.ruler.getRules('reference'); + + oldParentType = state.parentType; + state.parentType = 'reference'; + + for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) { + // this would be a code block normally, but after paragraph + // it's considered a lazy continuation regardless of what's there + if (state.sCount[nextLine] - state.blkIndent > 3) { continue; } + + // quirk for blockquotes, this line should already be checked by that rule + if (state.sCount[nextLine] < 0) { continue; } + + // Some tags can terminate paragraph without empty line. + terminate = false; + for (i = 0, l = terminatorRules.length; i < l; i++) { + if (terminatorRules[i](state, nextLine, endLine, true)) { + terminate = true; + break; + } + } + if (terminate) { break; } + } + + str = state.getLines(startLine, nextLine, state.blkIndent, false).trim(); + max = str.length; + + for (pos = 1; pos < max; pos++) { + ch = str.charCodeAt(pos); + if (ch === 0x5B /* [ */) { + return false; + } else if (ch === 0x5D /* ] */) { + labelEnd = pos; + break; + } else if (ch === 0x0A /* \n */) { + lines++; + } else if (ch === 0x5C /* \ */) { + pos++; + if (pos < max && str.charCodeAt(pos) === 0x0A) { + lines++; + } + } + } + + if (labelEnd < 0 || str.charCodeAt(labelEnd + 1) !== 0x3A/* : */) { return false; } + + // [label]: destination 'title' + // ^^^ skip optional whitespace here + for (pos = labelEnd + 2; pos < max; pos++) { + ch = str.charCodeAt(pos); + if (ch === 0x0A) { + lines++; + } else if (isSpace(ch)) { + /*eslint no-empty:0*/ + } else { + break; + } + } + + // [label]: destination 'title' + // ^^^^^^^^^^^ parse this + res = state.md.helpers.parseLinkDestination(str, pos, max); + if (!res.ok) { return false; } + + href = state.md.normalizeLink(res.str); + if (!state.md.validateLink(href)) { return false; } + + pos = res.pos; + lines += res.lines; + + // save cursor state, we could require to rollback later + destEndPos = pos; + destEndLineNo = lines; + + // [label]: destination 'title' + // ^^^ skipping those spaces + start = pos; + for (; pos < max; pos++) { + ch = str.charCodeAt(pos); + if (ch === 0x0A) { + lines++; + } else if (isSpace(ch)) { + /*eslint no-empty:0*/ + } else { + break; + } + } + + // [label]: destination 'title' + // ^^^^^^^ parse this + res = state.md.helpers.parseLinkTitle(str, pos, max); + if (pos < max && start !== pos && res.ok) { + title = res.str; + pos = res.pos; + lines += res.lines; + } else { + title = ''; + pos = destEndPos; + lines = destEndLineNo; + } + + // skip trailing spaces until the rest of the line + while (pos < max) { + ch = str.charCodeAt(pos); + if (!isSpace(ch)) { break; } + pos++; + } + + if (pos < max && str.charCodeAt(pos) !== 0x0A) { + if (title) { + // garbage at the end of the line after title, + // but it could still be a valid reference if we roll back + title = ''; + pos = destEndPos; + lines = destEndLineNo; + while (pos < max) { + ch = str.charCodeAt(pos); + if (!isSpace(ch)) { break; } + pos++; + } + } + } + + if (pos < max && str.charCodeAt(pos) !== 0x0A) { + // garbage at the end of the line + return false; + } + + label = normalizeReference(str.slice(1, labelEnd)); + if (!label) { + // CommonMark 0.20 disallows empty labels + return false; + } + + // Reference can not terminate anything. This check is for safety only. + /*istanbul ignore if*/ + if (silent) { return true; } + + if (typeof state.env.references === 'undefined') { + state.env.references = {}; + } + if (typeof state.env.references[label] === 'undefined') { + state.env.references[label] = { title: title, href: href }; + } + + state.parentType = oldParentType; + + state.line = startLine + lines + 1; + return true; +}; + +},{"../common/utils":4}],28:[function(require,module,exports){ +// Parser state class + +'use strict'; + +var Token = require('../token'); +var isSpace = require('../common/utils').isSpace; + + +function StateBlock(src, md, env, tokens) { + var ch, s, start, pos, len, indent, offset, indent_found; + + this.src = src; + + // link to parser instance + this.md = md; + + this.env = env; + + // + // Internal state vartiables + // + + this.tokens = tokens; + + this.bMarks = []; // line begin offsets for fast jumps + this.eMarks = []; // line end offsets for fast jumps + this.tShift = []; // offsets of the first non-space characters (tabs not expanded) + this.sCount = []; // indents for each line (tabs expanded) + + // An amount of virtual spaces (tabs expanded) between beginning + // of each line (bMarks) and real beginning of that line. + // + // It exists only as a hack because blockquotes override bMarks + // losing information in the process. + // + // It's used only when expanding tabs, you can think about it as + // an initial tab length, e.g. bsCount=21 applied to string `\t123` + // means first tab should be expanded to 4-21%4 === 3 spaces. + // + this.bsCount = []; + + // block parser variables + this.blkIndent = 0; // required block content indent (for example, if we are + // inside a list, it would be positioned after list marker) + this.line = 0; // line index in src + this.lineMax = 0; // lines count + this.tight = false; // loose/tight mode for lists + this.ddIndent = -1; // indent of the current dd block (-1 if there isn't any) + this.listIndent = -1; // indent of the current list block (-1 if there isn't any) + + // can be 'blockquote', 'list', 'root', 'paragraph' or 'reference' + // used in lists to determine if they interrupt a paragraph + this.parentType = 'root'; + + this.level = 0; + + // renderer + this.result = ''; + + // Create caches + // Generate markers. + s = this.src; + indent_found = false; + + for (start = pos = indent = offset = 0, len = s.length; pos < len; pos++) { + ch = s.charCodeAt(pos); + + if (!indent_found) { + if (isSpace(ch)) { + indent++; + + if (ch === 0x09) { + offset += 4 - offset % 4; + } else { + offset++; + } + continue; + } else { + indent_found = true; + } + } + + if (ch === 0x0A || pos === len - 1) { + if (ch !== 0x0A) { pos++; } + this.bMarks.push(start); + this.eMarks.push(pos); + this.tShift.push(indent); + this.sCount.push(offset); + this.bsCount.push(0); + + indent_found = false; + indent = 0; + offset = 0; + start = pos + 1; + } + } + + // Push fake entry to simplify cache bounds checks + this.bMarks.push(s.length); + this.eMarks.push(s.length); + this.tShift.push(0); + this.sCount.push(0); + this.bsCount.push(0); + + this.lineMax = this.bMarks.length - 1; // don't count last fake line +} + +// Push new token to "stream". +// +StateBlock.prototype.push = function (type, tag, nesting) { + var token = new Token(type, tag, nesting); + token.block = true; + + if (nesting < 0) this.level--; // closing tag + token.level = this.level; + if (nesting > 0) this.level++; // opening tag + + this.tokens.push(token); + return token; +}; + +StateBlock.prototype.isEmpty = function isEmpty(line) { + return this.bMarks[line] + this.tShift[line] >= this.eMarks[line]; +}; + +StateBlock.prototype.skipEmptyLines = function skipEmptyLines(from) { + for (var max = this.lineMax; from < max; from++) { + if (this.bMarks[from] + this.tShift[from] < this.eMarks[from]) { + break; + } + } + return from; +}; + +// Skip spaces from given position. +StateBlock.prototype.skipSpaces = function skipSpaces(pos) { + var ch; + + for (var max = this.src.length; pos < max; pos++) { + ch = this.src.charCodeAt(pos); + if (!isSpace(ch)) { break; } + } + return pos; +}; + +// Skip spaces from given position in reverse. +StateBlock.prototype.skipSpacesBack = function skipSpacesBack(pos, min) { + if (pos <= min) { return pos; } + + while (pos > min) { + if (!isSpace(this.src.charCodeAt(--pos))) { return pos + 1; } + } + return pos; +}; + +// Skip char codes from given position +StateBlock.prototype.skipChars = function skipChars(pos, code) { + for (var max = this.src.length; pos < max; pos++) { + if (this.src.charCodeAt(pos) !== code) { break; } + } + return pos; +}; + +// Skip char codes reverse from given position - 1 +StateBlock.prototype.skipCharsBack = function skipCharsBack(pos, code, min) { + if (pos <= min) { return pos; } + + while (pos > min) { + if (code !== this.src.charCodeAt(--pos)) { return pos + 1; } + } + return pos; +}; + +// cut lines range from source. +StateBlock.prototype.getLines = function getLines(begin, end, indent, keepLastLF) { + var i, lineIndent, ch, first, last, queue, lineStart, + line = begin; + + if (begin >= end) { + return ''; + } + + queue = new Array(end - begin); + + for (i = 0; line < end; line++, i++) { + lineIndent = 0; + lineStart = first = this.bMarks[line]; + + if (line + 1 < end || keepLastLF) { + // No need for bounds check because we have fake entry on tail. + last = this.eMarks[line] + 1; + } else { + last = this.eMarks[line]; + } + + while (first < last && lineIndent < indent) { + ch = this.src.charCodeAt(first); + + if (isSpace(ch)) { + if (ch === 0x09) { + lineIndent += 4 - (lineIndent + this.bsCount[line]) % 4; + } else { + lineIndent++; + } + } else if (first - lineStart < this.tShift[line]) { + // patched tShift masked characters to look like spaces (blockquotes, list markers) + lineIndent++; + } else { + break; + } + + first++; + } + + if (lineIndent > indent) { + // partially expanding tabs in code blocks, e.g '\t\tfoobar' + // with indent=2 becomes ' \tfoobar' + queue[i] = new Array(lineIndent - indent + 1).join(' ') + this.src.slice(first, last); + } else { + queue[i] = this.src.slice(first, last); + } + } + + return queue.join(''); +}; + +// re-export Token class to use in block rules +StateBlock.prototype.Token = Token; + + +module.exports = StateBlock; + +},{"../common/utils":4,"../token":51}],29:[function(require,module,exports){ +// GFM table, non-standard + +'use strict'; + +var isSpace = require('../common/utils').isSpace; + + +function getLine(state, line) { + var pos = state.bMarks[line] + state.blkIndent, + max = state.eMarks[line]; + + return state.src.substr(pos, max - pos); +} + +function escapedSplit(str) { + var result = [], + pos = 0, + max = str.length, + ch, + escapes = 0, + lastPos = 0, + backTicked = false, + lastBackTick = 0; + + ch = str.charCodeAt(pos); + + while (pos < max) { + if (ch === 0x60/* ` */) { + if (backTicked) { + // make \` close code sequence, but not open it; + // the reason is: `\` is correct code block + backTicked = false; + lastBackTick = pos; + } else if (escapes % 2 === 0) { + backTicked = true; + lastBackTick = pos; + } + } else if (ch === 0x7c/* | */ && (escapes % 2 === 0) && !backTicked) { + result.push(str.substring(lastPos, pos)); + lastPos = pos + 1; + } + + if (ch === 0x5c/* \ */) { + escapes++; + } else { + escapes = 0; + } + + pos++; + + // If there was an un-closed backtick, go back to just after + // the last backtick, but as if it was a normal character + if (pos === max && backTicked) { + backTicked = false; + pos = lastBackTick + 1; + } + + ch = str.charCodeAt(pos); + } + + result.push(str.substring(lastPos)); + + return result; +} + + +module.exports = function table(state, startLine, endLine, silent) { + var ch, lineText, pos, i, nextLine, columns, columnCount, token, + aligns, t, tableLines, tbodyLines; + + // should have at least two lines + if (startLine + 2 > endLine) { return false; } + + nextLine = startLine + 1; + + if (state.sCount[nextLine] < state.blkIndent) { return false; } + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[nextLine] - state.blkIndent >= 4) { return false; } + + // first character of the second line should be '|', '-', ':', + // and no other characters are allowed but spaces; + // basically, this is the equivalent of /^[-:|][-:|\s]*$/ regexp + + pos = state.bMarks[nextLine] + state.tShift[nextLine]; + if (pos >= state.eMarks[nextLine]) { return false; } + + ch = state.src.charCodeAt(pos++); + if (ch !== 0x7C/* | */ && ch !== 0x2D/* - */ && ch !== 0x3A/* : */) { return false; } + + while (pos < state.eMarks[nextLine]) { + ch = state.src.charCodeAt(pos); + + if (ch !== 0x7C/* | */ && ch !== 0x2D/* - */ && ch !== 0x3A/* : */ && !isSpace(ch)) { return false; } + + pos++; + } + + lineText = getLine(state, startLine + 1); + + columns = lineText.split('|'); + aligns = []; + for (i = 0; i < columns.length; i++) { + t = columns[i].trim(); + if (!t) { + // allow empty columns before and after table, but not in between columns; + // e.g. allow ` |---| `, disallow ` ---||--- ` + if (i === 0 || i === columns.length - 1) { + continue; + } else { + return false; + } + } + + if (!/^:?-+:?$/.test(t)) { return false; } + if (t.charCodeAt(t.length - 1) === 0x3A/* : */) { + aligns.push(t.charCodeAt(0) === 0x3A/* : */ ? 'center' : 'right'); + } else if (t.charCodeAt(0) === 0x3A/* : */) { + aligns.push('left'); + } else { + aligns.push(''); + } + } + + lineText = getLine(state, startLine).trim(); + if (lineText.indexOf('|') === -1) { return false; } + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + columns = escapedSplit(lineText.replace(/^\||\|$/g, '')); + + // header row will define an amount of columns in the entire table, + // and align row shouldn't be smaller than that (the rest of the rows can) + columnCount = columns.length; + if (columnCount > aligns.length) { return false; } + + if (silent) { return true; } + + token = state.push('table_open', 'table', 1); + token.map = tableLines = [ startLine, 0 ]; + + token = state.push('thead_open', 'thead', 1); + token.map = [ startLine, startLine + 1 ]; + + token = state.push('tr_open', 'tr', 1); + token.map = [ startLine, startLine + 1 ]; + + for (i = 0; i < columns.length; i++) { + token = state.push('th_open', 'th', 1); + token.map = [ startLine, startLine + 1 ]; + if (aligns[i]) { + token.attrs = [ [ 'style', 'text-align:' + aligns[i] ] ]; + } + + token = state.push('inline', '', 0); + token.content = columns[i].trim(); + token.map = [ startLine, startLine + 1 ]; + token.children = []; + + token = state.push('th_close', 'th', -1); + } + + token = state.push('tr_close', 'tr', -1); + token = state.push('thead_close', 'thead', -1); + + token = state.push('tbody_open', 'tbody', 1); + token.map = tbodyLines = [ startLine + 2, 0 ]; + + for (nextLine = startLine + 2; nextLine < endLine; nextLine++) { + if (state.sCount[nextLine] < state.blkIndent) { break; } + + lineText = getLine(state, nextLine).trim(); + if (lineText.indexOf('|') === -1) { break; } + if (state.sCount[nextLine] - state.blkIndent >= 4) { break; } + columns = escapedSplit(lineText.replace(/^\||\|$/g, '')); + + token = state.push('tr_open', 'tr', 1); + for (i = 0; i < columnCount; i++) { + token = state.push('td_open', 'td', 1); + if (aligns[i]) { + token.attrs = [ [ 'style', 'text-align:' + aligns[i] ] ]; + } + + token = state.push('inline', '', 0); + token.content = columns[i] ? columns[i].trim() : ''; + token.children = []; + + token = state.push('td_close', 'td', -1); + } + token = state.push('tr_close', 'tr', -1); + } + token = state.push('tbody_close', 'tbody', -1); + token = state.push('table_close', 'table', -1); + + tableLines[1] = tbodyLines[1] = nextLine; + state.line = nextLine; + return true; +}; + +},{"../common/utils":4}],30:[function(require,module,exports){ +'use strict'; + + +module.exports = function block(state) { + var token; + + if (state.inlineMode) { + token = new state.Token('inline', '', 0); + token.content = state.src; + token.map = [ 0, 1 ]; + token.children = []; + state.tokens.push(token); + } else { + state.md.block.parse(state.src, state.md, state.env, state.tokens); + } +}; + +},{}],31:[function(require,module,exports){ +'use strict'; + +module.exports = function inline(state) { + var tokens = state.tokens, tok, i, l; + + // Parse inlines + for (i = 0, l = tokens.length; i < l; i++) { + tok = tokens[i]; + if (tok.type === 'inline') { + state.md.inline.parse(tok.content, state.md, state.env, tok.children); + } + } +}; + +},{}],32:[function(require,module,exports){ +// Replace link-like texts with link nodes. +// +// Currently restricted by `md.validateLink()` to http/https/ftp +// +'use strict'; + + +var arrayReplaceAt = require('../common/utils').arrayReplaceAt; + + +function isLinkOpen(str) { + return /^\s]/i.test(str); +} +function isLinkClose(str) { + return /^<\/a\s*>/i.test(str); +} + + +module.exports = function linkify(state) { + var i, j, l, tokens, token, currentToken, nodes, ln, text, pos, lastPos, + level, htmlLinkLevel, url, fullUrl, urlText, + blockTokens = state.tokens, + links; + + if (!state.md.options.linkify) { return; } + + for (j = 0, l = blockTokens.length; j < l; j++) { + if (blockTokens[j].type !== 'inline' || + !state.md.linkify.pretest(blockTokens[j].content)) { + continue; + } + + tokens = blockTokens[j].children; + + htmlLinkLevel = 0; + + // We scan from the end, to keep position when new tags added. + // Use reversed logic in links start/end match + for (i = tokens.length - 1; i >= 0; i--) { + currentToken = tokens[i]; + + // Skip content of markdown links + if (currentToken.type === 'link_close') { + i--; + while (tokens[i].level !== currentToken.level && tokens[i].type !== 'link_open') { + i--; + } + continue; + } + + // Skip content of html tag links + if (currentToken.type === 'html_inline') { + if (isLinkOpen(currentToken.content) && htmlLinkLevel > 0) { + htmlLinkLevel--; + } + if (isLinkClose(currentToken.content)) { + htmlLinkLevel++; + } + } + if (htmlLinkLevel > 0) { continue; } + + if (currentToken.type === 'text' && state.md.linkify.test(currentToken.content)) { + + text = currentToken.content; + links = state.md.linkify.match(text); + + // Now split string to nodes + nodes = []; + level = currentToken.level; + lastPos = 0; + + for (ln = 0; ln < links.length; ln++) { + + url = links[ln].url; + fullUrl = state.md.normalizeLink(url); + if (!state.md.validateLink(fullUrl)) { continue; } + + urlText = links[ln].text; + + // Linkifier might send raw hostnames like "example.com", where url + // starts with domain name. So we prepend http:// in those cases, + // and remove it afterwards. + // + if (!links[ln].schema) { + urlText = state.md.normalizeLinkText('http://' + urlText).replace(/^http:\/\//, ''); + } else if (links[ln].schema === 'mailto:' && !/^mailto:/i.test(urlText)) { + urlText = state.md.normalizeLinkText('mailto:' + urlText).replace(/^mailto:/, ''); + } else { + urlText = state.md.normalizeLinkText(urlText); + } + + pos = links[ln].index; + + if (pos > lastPos) { + token = new state.Token('text', '', 0); + token.content = text.slice(lastPos, pos); + token.level = level; + nodes.push(token); + } + + token = new state.Token('link_open', 'a', 1); + token.attrs = [ [ 'href', fullUrl ] ]; + token.level = level++; + token.markup = 'linkify'; + token.info = 'auto'; + nodes.push(token); + + token = new state.Token('text', '', 0); + token.content = urlText; + token.level = level; + nodes.push(token); + + token = new state.Token('link_close', 'a', -1); + token.level = --level; + token.markup = 'linkify'; + token.info = 'auto'; + nodes.push(token); + + lastPos = links[ln].lastIndex; + } + if (lastPos < text.length) { + token = new state.Token('text', '', 0); + token.content = text.slice(lastPos); + token.level = level; + nodes.push(token); + } + + // replace current node + blockTokens[j].children = tokens = arrayReplaceAt(tokens, i, nodes); + } + } + } +}; + +},{"../common/utils":4}],33:[function(require,module,exports){ +// Normalize input string + +'use strict'; + + +// https://spec.commonmark.org/0.29/#line-ending +var NEWLINES_RE = /\r\n?|\n/g; +var NULL_RE = /\0/g; + + +module.exports = function normalize(state) { + var str; + + // Normalize newlines + str = state.src.replace(NEWLINES_RE, '\n'); + + // Replace NULL characters + str = str.replace(NULL_RE, '\uFFFD'); + + state.src = str; +}; + +},{}],34:[function(require,module,exports){ +// Simple typographic replacements +// +// (c) (C) → © +// (tm) (TM) → ™ +// (r) (R) → ® +// +- → ± +// (p) (P) -> § +// ... → … (also ?.... → ?.., !.... → !..) +// ???????? → ???, !!!!! → !!!, `,,` → `,` +// -- → –, --- → — +// +'use strict'; + +// TODO: +// - fractionals 1/2, 1/4, 3/4 -> ½, ¼, ¾ +// - miltiplication 2 x 4 -> 2 × 4 + +var RARE_RE = /\+-|\.\.|\?\?\?\?|!!!!|,,|--/; + +// Workaround for phantomjs - need regex without /g flag, +// or root check will fail every second time +var SCOPED_ABBR_TEST_RE = /\((c|tm|r|p)\)/i; + +var SCOPED_ABBR_RE = /\((c|tm|r|p)\)/ig; +var SCOPED_ABBR = { + c: '©', + r: '®', + p: '§', + tm: '™' +}; + +function replaceFn(match, name) { + return SCOPED_ABBR[name.toLowerCase()]; +} + +function replace_scoped(inlineTokens) { + var i, token, inside_autolink = 0; + + for (i = inlineTokens.length - 1; i >= 0; i--) { + token = inlineTokens[i]; + + if (token.type === 'text' && !inside_autolink) { + token.content = token.content.replace(SCOPED_ABBR_RE, replaceFn); + } + + if (token.type === 'link_open' && token.info === 'auto') { + inside_autolink--; + } + + if (token.type === 'link_close' && token.info === 'auto') { + inside_autolink++; + } + } +} + +function replace_rare(inlineTokens) { + var i, token, inside_autolink = 0; + + for (i = inlineTokens.length - 1; i >= 0; i--) { + token = inlineTokens[i]; + + if (token.type === 'text' && !inside_autolink) { + if (RARE_RE.test(token.content)) { + token.content = token.content + .replace(/\+-/g, '±') + // .., ..., ....... -> … + // but ?..... & !..... -> ?.. & !.. + .replace(/\.{2,}/g, '…').replace(/([?!])…/g, '$1..') + .replace(/([?!]){4,}/g, '$1$1$1').replace(/,{2,}/g, ',') + // em-dash + .replace(/(^|[^-])---([^-]|$)/mg, '$1\u2014$2') + // en-dash + .replace(/(^|\s)--(\s|$)/mg, '$1\u2013$2') + .replace(/(^|[^-\s])--([^-\s]|$)/mg, '$1\u2013$2'); + } + } + + if (token.type === 'link_open' && token.info === 'auto') { + inside_autolink--; + } + + if (token.type === 'link_close' && token.info === 'auto') { + inside_autolink++; + } + } +} + + +module.exports = function replace(state) { + var blkIdx; + + if (!state.md.options.typographer) { return; } + + for (blkIdx = state.tokens.length - 1; blkIdx >= 0; blkIdx--) { + + if (state.tokens[blkIdx].type !== 'inline') { continue; } + + if (SCOPED_ABBR_TEST_RE.test(state.tokens[blkIdx].content)) { + replace_scoped(state.tokens[blkIdx].children); + } + + if (RARE_RE.test(state.tokens[blkIdx].content)) { + replace_rare(state.tokens[blkIdx].children); + } + + } +}; + +},{}],35:[function(require,module,exports){ +// Convert straight quotation marks to typographic ones +// +'use strict'; + + +var isWhiteSpace = require('../common/utils').isWhiteSpace; +var isPunctChar = require('../common/utils').isPunctChar; +var isMdAsciiPunct = require('../common/utils').isMdAsciiPunct; + +var QUOTE_TEST_RE = /['"]/; +var QUOTE_RE = /['"]/g; +var APOSTROPHE = '\u2019'; /* ’ */ + + +function replaceAt(str, index, ch) { + return str.substr(0, index) + ch + str.substr(index + 1); +} + +function process_inlines(tokens, state) { + var i, token, text, t, pos, max, thisLevel, item, lastChar, nextChar, + isLastPunctChar, isNextPunctChar, isLastWhiteSpace, isNextWhiteSpace, + canOpen, canClose, j, isSingle, stack, openQuote, closeQuote; + + stack = []; + + for (i = 0; i < tokens.length; i++) { + token = tokens[i]; + + thisLevel = tokens[i].level; + + for (j = stack.length - 1; j >= 0; j--) { + if (stack[j].level <= thisLevel) { break; } + } + stack.length = j + 1; + + if (token.type !== 'text') { continue; } + + text = token.content; + pos = 0; + max = text.length; + + /*eslint no-labels:0,block-scoped-var:0*/ + OUTER: + while (pos < max) { + QUOTE_RE.lastIndex = pos; + t = QUOTE_RE.exec(text); + if (!t) { break; } + + canOpen = canClose = true; + pos = t.index + 1; + isSingle = (t[0] === "'"); + + // Find previous character, + // default to space if it's the beginning of the line + // + lastChar = 0x20; + + if (t.index - 1 >= 0) { + lastChar = text.charCodeAt(t.index - 1); + } else { + for (j = i - 1; j >= 0; j--) { + if (tokens[j].type === 'softbreak' || tokens[j].type === 'hardbreak') break; // lastChar defaults to 0x20 + if (tokens[j].type !== 'text') continue; + + lastChar = tokens[j].content.charCodeAt(tokens[j].content.length - 1); + break; + } + } + + // Find next character, + // default to space if it's the end of the line + // + nextChar = 0x20; + + if (pos < max) { + nextChar = text.charCodeAt(pos); + } else { + for (j = i + 1; j < tokens.length; j++) { + if (tokens[j].type === 'softbreak' || tokens[j].type === 'hardbreak') break; // nextChar defaults to 0x20 + if (tokens[j].type !== 'text') continue; + + nextChar = tokens[j].content.charCodeAt(0); + break; + } + } + + isLastPunctChar = isMdAsciiPunct(lastChar) || isPunctChar(String.fromCharCode(lastChar)); + isNextPunctChar = isMdAsciiPunct(nextChar) || isPunctChar(String.fromCharCode(nextChar)); + + isLastWhiteSpace = isWhiteSpace(lastChar); + isNextWhiteSpace = isWhiteSpace(nextChar); + + if (isNextWhiteSpace) { + canOpen = false; + } else if (isNextPunctChar) { + if (!(isLastWhiteSpace || isLastPunctChar)) { + canOpen = false; + } + } + + if (isLastWhiteSpace) { + canClose = false; + } else if (isLastPunctChar) { + if (!(isNextWhiteSpace || isNextPunctChar)) { + canClose = false; + } + } + + if (nextChar === 0x22 /* " */ && t[0] === '"') { + if (lastChar >= 0x30 /* 0 */ && lastChar <= 0x39 /* 9 */) { + // special case: 1"" - count first quote as an inch + canClose = canOpen = false; + } + } + + if (canOpen && canClose) { + // treat this as the middle of the word + canOpen = false; + canClose = isNextPunctChar; + } + + if (!canOpen && !canClose) { + // middle of word + if (isSingle) { + token.content = replaceAt(token.content, t.index, APOSTROPHE); + } + continue; + } + + if (canClose) { + // this could be a closing quote, rewind the stack to get a match + for (j = stack.length - 1; j >= 0; j--) { + item = stack[j]; + if (stack[j].level < thisLevel) { break; } + if (item.single === isSingle && stack[j].level === thisLevel) { + item = stack[j]; + + if (isSingle) { + openQuote = state.md.options.quotes[2]; + closeQuote = state.md.options.quotes[3]; + } else { + openQuote = state.md.options.quotes[0]; + closeQuote = state.md.options.quotes[1]; + } + + // replace token.content *before* tokens[item.token].content, + // because, if they are pointing at the same token, replaceAt + // could mess up indices when quote length != 1 + token.content = replaceAt(token.content, t.index, closeQuote); + tokens[item.token].content = replaceAt( + tokens[item.token].content, item.pos, openQuote); + + pos += closeQuote.length - 1; + if (item.token === i) { pos += openQuote.length - 1; } + + text = token.content; + max = text.length; + + stack.length = j; + continue OUTER; + } + } + } + + if (canOpen) { + stack.push({ + token: i, + pos: t.index, + single: isSingle, + level: thisLevel + }); + } else if (canClose && isSingle) { + token.content = replaceAt(token.content, t.index, APOSTROPHE); + } + } + } +} + + +module.exports = function smartquotes(state) { + /*eslint max-depth:0*/ + var blkIdx; + + if (!state.md.options.typographer) { return; } + + for (blkIdx = state.tokens.length - 1; blkIdx >= 0; blkIdx--) { + + if (state.tokens[blkIdx].type !== 'inline' || + !QUOTE_TEST_RE.test(state.tokens[blkIdx].content)) { + continue; + } + + process_inlines(state.tokens[blkIdx].children, state); + } +}; + +},{"../common/utils":4}],36:[function(require,module,exports){ +// Core state object +// +'use strict'; + +var Token = require('../token'); + + +function StateCore(src, md, env) { + this.src = src; + this.env = env; + this.tokens = []; + this.inlineMode = false; + this.md = md; // link to parser instance +} + +// re-export Token class to use in core rules +StateCore.prototype.Token = Token; + + +module.exports = StateCore; + +},{"../token":51}],37:[function(require,module,exports){ +// Process autolinks '' + +'use strict'; + + +/*eslint max-len:0*/ +var EMAIL_RE = /^<([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])?)*)>/; +var AUTOLINK_RE = /^<([a-zA-Z][a-zA-Z0-9+.\-]{1,31}):([^<>\x00-\x20]*)>/; + + +module.exports = function autolink(state, silent) { + var tail, linkMatch, emailMatch, url, fullUrl, token, + pos = state.pos; + + if (state.src.charCodeAt(pos) !== 0x3C/* < */) { return false; } + + tail = state.src.slice(pos); + + if (tail.indexOf('>') < 0) { return false; } + + if (AUTOLINK_RE.test(tail)) { + linkMatch = tail.match(AUTOLINK_RE); + + url = linkMatch[0].slice(1, -1); + fullUrl = state.md.normalizeLink(url); + if (!state.md.validateLink(fullUrl)) { return false; } + + if (!silent) { + token = state.push('link_open', 'a', 1); + token.attrs = [ [ 'href', fullUrl ] ]; + token.markup = 'autolink'; + token.info = 'auto'; + + token = state.push('text', '', 0); + token.content = state.md.normalizeLinkText(url); + + token = state.push('link_close', 'a', -1); + token.markup = 'autolink'; + token.info = 'auto'; + } + + state.pos += linkMatch[0].length; + return true; + } + + if (EMAIL_RE.test(tail)) { + emailMatch = tail.match(EMAIL_RE); + + url = emailMatch[0].slice(1, -1); + fullUrl = state.md.normalizeLink('mailto:' + url); + if (!state.md.validateLink(fullUrl)) { return false; } + + if (!silent) { + token = state.push('link_open', 'a', 1); + token.attrs = [ [ 'href', fullUrl ] ]; + token.markup = 'autolink'; + token.info = 'auto'; + + token = state.push('text', '', 0); + token.content = state.md.normalizeLinkText(url); + + token = state.push('link_close', 'a', -1); + token.markup = 'autolink'; + token.info = 'auto'; + } + + state.pos += emailMatch[0].length; + return true; + } + + return false; +}; + +},{}],38:[function(require,module,exports){ +// Parse backticks + +'use strict'; + +module.exports = function backtick(state, silent) { + var start, max, marker, matchStart, matchEnd, token, + pos = state.pos, + ch = state.src.charCodeAt(pos); + + if (ch !== 0x60/* ` */) { return false; } + + start = pos; + pos++; + max = state.posMax; + + while (pos < max && state.src.charCodeAt(pos) === 0x60/* ` */) { pos++; } + + marker = state.src.slice(start, pos); + + matchStart = matchEnd = pos; + + while ((matchStart = state.src.indexOf('`', matchEnd)) !== -1) { + matchEnd = matchStart + 1; + + while (matchEnd < max && state.src.charCodeAt(matchEnd) === 0x60/* ` */) { matchEnd++; } + + if (matchEnd - matchStart === marker.length) { + if (!silent) { + token = state.push('code_inline', 'code', 0); + token.markup = marker; + token.content = state.src.slice(pos, matchStart) + .replace(/\n/g, ' ') + .replace(/^ (.+) $/, '$1'); + } + state.pos = matchEnd; + return true; + } + } + + if (!silent) { state.pending += marker; } + state.pos += marker.length; + return true; +}; + +},{}],39:[function(require,module,exports){ +// For each opening emphasis-like marker find a matching closing one +// +'use strict'; + + +function processDelimiters(state, delimiters) { + var closerIdx, openerIdx, closer, opener, minOpenerIdx, newMinOpenerIdx, + isOddMatch, lastJump, + openersBottom = {}, + max = delimiters.length; + + for (closerIdx = 0; closerIdx < max; closerIdx++) { + closer = delimiters[closerIdx]; + + // Length is only used for emphasis-specific "rule of 3", + // if it's not defined (in strikethrough or 3rd party plugins), + // we can default it to 0 to disable those checks. + // + closer.length = closer.length || 0; + + if (!closer.close) continue; + + // Previously calculated lower bounds (previous fails) + // for each marker and each delimiter length modulo 3. + if (!openersBottom.hasOwnProperty(closer.marker)) { + openersBottom[closer.marker] = [ -1, -1, -1 ]; + } + + minOpenerIdx = openersBottom[closer.marker][closer.length % 3]; + newMinOpenerIdx = -1; + + openerIdx = closerIdx - closer.jump - 1; + + for (; openerIdx > minOpenerIdx; openerIdx -= opener.jump + 1) { + opener = delimiters[openerIdx]; + + if (opener.marker !== closer.marker) continue; + + if (newMinOpenerIdx === -1) newMinOpenerIdx = openerIdx; + + if (opener.open && + opener.end < 0 && + opener.level === closer.level) { + + isOddMatch = false; + + // from spec: + // + // If one of the delimiters can both open and close emphasis, then the + // sum of the lengths of the delimiter runs containing the opening and + // closing delimiters must not be a multiple of 3 unless both lengths + // are multiples of 3. + // + if (opener.close || closer.open) { + if ((opener.length + closer.length) % 3 === 0) { + if (opener.length % 3 !== 0 || closer.length % 3 !== 0) { + isOddMatch = true; + } + } + } + + if (!isOddMatch) { + // If previous delimiter cannot be an opener, we can safely skip + // the entire sequence in future checks. This is required to make + // sure algorithm has linear complexity (see *_*_*_*_*_... case). + // + lastJump = openerIdx > 0 && !delimiters[openerIdx - 1].open ? + delimiters[openerIdx - 1].jump + 1 : + 0; + + closer.jump = closerIdx - openerIdx + lastJump; + closer.open = false; + opener.end = closerIdx; + opener.jump = lastJump; + opener.close = false; + newMinOpenerIdx = -1; + break; + } + } + } + + if (newMinOpenerIdx !== -1) { + // If match for this delimiter run failed, we want to set lower bound for + // future lookups. This is required to make sure algorithm has linear + // complexity. + // + // See details here: + // https://github.com/commonmark/cmark/issues/178#issuecomment-270417442 + // + openersBottom[closer.marker][(closer.length || 0) % 3] = newMinOpenerIdx; + } + } +} + + +module.exports = function link_pairs(state) { + var curr, + tokens_meta = state.tokens_meta, + max = state.tokens_meta.length; + + processDelimiters(state, state.delimiters); + + for (curr = 0; curr < max; curr++) { + if (tokens_meta[curr] && tokens_meta[curr].delimiters) { + processDelimiters(state, tokens_meta[curr].delimiters); + } + } +}; + +},{}],40:[function(require,module,exports){ +// Process *this* and _that_ +// +'use strict'; + + +// Insert each marker as a separate text token, and add it to delimiter list +// +module.exports.tokenize = function emphasis(state, silent) { + var i, scanned, token, + start = state.pos, + marker = state.src.charCodeAt(start); + + if (silent) { return false; } + + if (marker !== 0x5F /* _ */ && marker !== 0x2A /* * */) { return false; } + + scanned = state.scanDelims(state.pos, marker === 0x2A); + + for (i = 0; i < scanned.length; i++) { + token = state.push('text', '', 0); + token.content = String.fromCharCode(marker); + + state.delimiters.push({ + // Char code of the starting marker (number). + // + marker: marker, + + // Total length of these series of delimiters. + // + length: scanned.length, + + // An amount of characters before this one that's equivalent to + // current one. In plain English: if this delimiter does not open + // an emphasis, neither do previous `jump` characters. + // + // Used to skip sequences like "*****" in one step, for 1st asterisk + // value will be 0, for 2nd it's 1 and so on. + // + jump: i, + + // A position of the token this delimiter corresponds to. + // + token: state.tokens.length - 1, + + // If this delimiter is matched as a valid opener, `end` will be + // equal to its position, otherwise it's `-1`. + // + end: -1, + + // Boolean flags that determine if this delimiter could open or close + // an emphasis. + // + open: scanned.can_open, + close: scanned.can_close + }); + } + + state.pos += scanned.length; + + return true; +}; + + +function postProcess(state, delimiters) { + var i, + startDelim, + endDelim, + token, + ch, + isStrong, + max = delimiters.length; + + for (i = max - 1; i >= 0; i--) { + startDelim = delimiters[i]; + + if (startDelim.marker !== 0x5F/* _ */ && startDelim.marker !== 0x2A/* * */) { + continue; + } + + // Process only opening markers + if (startDelim.end === -1) { + continue; + } + + endDelim = delimiters[startDelim.end]; + + // If the previous delimiter has the same marker and is adjacent to this one, + // merge those into one strong delimiter. + // + // `whatever` -> `whatever` + // + isStrong = i > 0 && + delimiters[i - 1].end === startDelim.end + 1 && + delimiters[i - 1].token === startDelim.token - 1 && + delimiters[startDelim.end + 1].token === endDelim.token + 1 && + delimiters[i - 1].marker === startDelim.marker; + + ch = String.fromCharCode(startDelim.marker); + + token = state.tokens[startDelim.token]; + token.type = isStrong ? 'strong_open' : 'em_open'; + token.tag = isStrong ? 'strong' : 'em'; + token.nesting = 1; + token.markup = isStrong ? ch + ch : ch; + token.content = ''; + + token = state.tokens[endDelim.token]; + token.type = isStrong ? 'strong_close' : 'em_close'; + token.tag = isStrong ? 'strong' : 'em'; + token.nesting = -1; + token.markup = isStrong ? ch + ch : ch; + token.content = ''; + + if (isStrong) { + state.tokens[delimiters[i - 1].token].content = ''; + state.tokens[delimiters[startDelim.end + 1].token].content = ''; + i--; + } + } +} + + +// Walk through delimiter list and replace text tokens with tags +// +module.exports.postProcess = function emphasis(state) { + var curr, + tokens_meta = state.tokens_meta, + max = state.tokens_meta.length; + + postProcess(state, state.delimiters); + + for (curr = 0; curr < max; curr++) { + if (tokens_meta[curr] && tokens_meta[curr].delimiters) { + postProcess(state, tokens_meta[curr].delimiters); + } + } +}; + +},{}],41:[function(require,module,exports){ +// Process html entity - {, ¯, ", ... + +'use strict'; + +var entities = require('../common/entities'); +var has = require('../common/utils').has; +var isValidEntityCode = require('../common/utils').isValidEntityCode; +var fromCodePoint = require('../common/utils').fromCodePoint; + + +var DIGITAL_RE = /^&#((?:x[a-f0-9]{1,6}|[0-9]{1,7}));/i; +var NAMED_RE = /^&([a-z][a-z0-9]{1,31});/i; + + +module.exports = function entity(state, silent) { + var ch, code, match, pos = state.pos, max = state.posMax; + + if (state.src.charCodeAt(pos) !== 0x26/* & */) { return false; } + + if (pos + 1 < max) { + ch = state.src.charCodeAt(pos + 1); + + if (ch === 0x23 /* # */) { + match = state.src.slice(pos).match(DIGITAL_RE); + if (match) { + if (!silent) { + code = match[1][0].toLowerCase() === 'x' ? parseInt(match[1].slice(1), 16) : parseInt(match[1], 10); + state.pending += isValidEntityCode(code) ? fromCodePoint(code) : fromCodePoint(0xFFFD); + } + state.pos += match[0].length; + return true; + } + } else { + match = state.src.slice(pos).match(NAMED_RE); + if (match) { + if (has(entities, match[1])) { + if (!silent) { state.pending += entities[match[1]]; } + state.pos += match[0].length; + return true; + } + } + } + } + + if (!silent) { state.pending += '&'; } + state.pos++; + return true; +}; + +},{"../common/entities":1,"../common/utils":4}],42:[function(require,module,exports){ +// Process escaped chars and hardbreaks + +'use strict'; + +var isSpace = require('../common/utils').isSpace; + +var ESCAPED = []; + +for (var i = 0; i < 256; i++) { ESCAPED.push(0); } + +'\\!"#$%&\'()*+,./:;<=>?@[]^_`{|}~-' + .split('').forEach(function (ch) { ESCAPED[ch.charCodeAt(0)] = 1; }); + + +module.exports = function escape(state, silent) { + var ch, pos = state.pos, max = state.posMax; + + if (state.src.charCodeAt(pos) !== 0x5C/* \ */) { return false; } + + pos++; + + if (pos < max) { + ch = state.src.charCodeAt(pos); + + if (ch < 256 && ESCAPED[ch] !== 0) { + if (!silent) { state.pending += state.src[pos]; } + state.pos += 2; + return true; + } + + if (ch === 0x0A) { + if (!silent) { + state.push('hardbreak', 'br', 0); + } + + pos++; + // skip leading whitespaces from next line + while (pos < max) { + ch = state.src.charCodeAt(pos); + if (!isSpace(ch)) { break; } + pos++; + } + + state.pos = pos; + return true; + } + } + + if (!silent) { state.pending += '\\'; } + state.pos++; + return true; +}; + +},{"../common/utils":4}],43:[function(require,module,exports){ +// Process html tags + +'use strict'; + + +var HTML_TAG_RE = require('../common/html_re').HTML_TAG_RE; + + +function isLetter(ch) { + /*eslint no-bitwise:0*/ + var lc = ch | 0x20; // to lower case + return (lc >= 0x61/* a */) && (lc <= 0x7a/* z */); +} + + +module.exports = function html_inline(state, silent) { + var ch, match, max, token, + pos = state.pos; + + if (!state.md.options.html) { return false; } + + // Check start + max = state.posMax; + if (state.src.charCodeAt(pos) !== 0x3C/* < */ || + pos + 2 >= max) { + return false; + } + + // Quick fail on second char + ch = state.src.charCodeAt(pos + 1); + if (ch !== 0x21/* ! */ && + ch !== 0x3F/* ? */ && + ch !== 0x2F/* / */ && + !isLetter(ch)) { + return false; + } + + match = state.src.slice(pos).match(HTML_TAG_RE); + if (!match) { return false; } + + if (!silent) { + token = state.push('html_inline', '', 0); + token.content = state.src.slice(pos, pos + match[0].length); + } + state.pos += match[0].length; + return true; +}; + +},{"../common/html_re":3}],44:[function(require,module,exports){ +// Process ![image]( "title") + +'use strict'; + +var normalizeReference = require('../common/utils').normalizeReference; +var isSpace = require('../common/utils').isSpace; + + +module.exports = function image(state, silent) { + var attrs, + code, + content, + label, + labelEnd, + labelStart, + pos, + ref, + res, + title, + token, + tokens, + start, + href = '', + oldPos = state.pos, + max = state.posMax; + + if (state.src.charCodeAt(state.pos) !== 0x21/* ! */) { return false; } + if (state.src.charCodeAt(state.pos + 1) !== 0x5B/* [ */) { return false; } + + labelStart = state.pos + 2; + labelEnd = state.md.helpers.parseLinkLabel(state, state.pos + 1, false); + + // parser failed to find ']', so it's not a valid link + if (labelEnd < 0) { return false; } + + pos = labelEnd + 1; + if (pos < max && state.src.charCodeAt(pos) === 0x28/* ( */) { + // + // Inline link + // + + // [link]( "title" ) + // ^^ skipping these spaces + pos++; + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (!isSpace(code) && code !== 0x0A) { break; } + } + if (pos >= max) { return false; } + + // [link]( "title" ) + // ^^^^^^ parsing link destination + start = pos; + res = state.md.helpers.parseLinkDestination(state.src, pos, state.posMax); + if (res.ok) { + href = state.md.normalizeLink(res.str); + if (state.md.validateLink(href)) { + pos = res.pos; + } else { + href = ''; + } + } + + // [link]( "title" ) + // ^^ skipping these spaces + start = pos; + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (!isSpace(code) && code !== 0x0A) { break; } + } + + // [link]( "title" ) + // ^^^^^^^ parsing link title + res = state.md.helpers.parseLinkTitle(state.src, pos, state.posMax); + if (pos < max && start !== pos && res.ok) { + title = res.str; + pos = res.pos; + + // [link]( "title" ) + // ^^ skipping these spaces + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (!isSpace(code) && code !== 0x0A) { break; } + } + } else { + title = ''; + } + + if (pos >= max || state.src.charCodeAt(pos) !== 0x29/* ) */) { + state.pos = oldPos; + return false; + } + pos++; + } else { + // + // Link reference + // + if (typeof state.env.references === 'undefined') { return false; } + + if (pos < max && state.src.charCodeAt(pos) === 0x5B/* [ */) { + start = pos + 1; + pos = state.md.helpers.parseLinkLabel(state, pos); + if (pos >= 0) { + label = state.src.slice(start, pos++); + } else { + pos = labelEnd + 1; + } + } else { + pos = labelEnd + 1; + } + + // covers label === '' and label === undefined + // (collapsed reference link and shortcut reference link respectively) + if (!label) { label = state.src.slice(labelStart, labelEnd); } + + ref = state.env.references[normalizeReference(label)]; + if (!ref) { + state.pos = oldPos; + return false; + } + href = ref.href; + title = ref.title; + } + + // + // We found the end of the link, and know for a fact it's a valid link; + // so all that's left to do is to call tokenizer. + // + if (!silent) { + content = state.src.slice(labelStart, labelEnd); + + state.md.inline.parse( + content, + state.md, + state.env, + tokens = [] + ); + + token = state.push('image', 'img', 0); + token.attrs = attrs = [ [ 'src', href ], [ 'alt', '' ] ]; + token.children = tokens; + token.content = content; + + if (title) { + attrs.push([ 'title', title ]); + } + } + + state.pos = pos; + state.posMax = max; + return true; +}; + +},{"../common/utils":4}],45:[function(require,module,exports){ +// Process [link]( "stuff") + +'use strict'; + +var normalizeReference = require('../common/utils').normalizeReference; +var isSpace = require('../common/utils').isSpace; + + +module.exports = function link(state, silent) { + var attrs, + code, + label, + labelEnd, + labelStart, + pos, + res, + ref, + title, + token, + href = '', + oldPos = state.pos, + max = state.posMax, + start = state.pos, + parseReference = true; + + if (state.src.charCodeAt(state.pos) !== 0x5B/* [ */) { return false; } + + labelStart = state.pos + 1; + labelEnd = state.md.helpers.parseLinkLabel(state, state.pos, true); + + // parser failed to find ']', so it's not a valid link + if (labelEnd < 0) { return false; } + + pos = labelEnd + 1; + if (pos < max && state.src.charCodeAt(pos) === 0x28/* ( */) { + // + // Inline link + // + + // might have found a valid shortcut link, disable reference parsing + parseReference = false; + + // [link]( "title" ) + // ^^ skipping these spaces + pos++; + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (!isSpace(code) && code !== 0x0A) { break; } + } + if (pos >= max) { return false; } + + // [link]( "title" ) + // ^^^^^^ parsing link destination + start = pos; + res = state.md.helpers.parseLinkDestination(state.src, pos, state.posMax); + if (res.ok) { + href = state.md.normalizeLink(res.str); + if (state.md.validateLink(href)) { + pos = res.pos; + } else { + href = ''; + } + } + + // [link]( "title" ) + // ^^ skipping these spaces + start = pos; + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (!isSpace(code) && code !== 0x0A) { break; } + } + + // [link]( "title" ) + // ^^^^^^^ parsing link title + res = state.md.helpers.parseLinkTitle(state.src, pos, state.posMax); + if (pos < max && start !== pos && res.ok) { + title = res.str; + pos = res.pos; + + // [link]( "title" ) + // ^^ skipping these spaces + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (!isSpace(code) && code !== 0x0A) { break; } + } + } else { + title = ''; + } + + if (pos >= max || state.src.charCodeAt(pos) !== 0x29/* ) */) { + // parsing a valid shortcut link failed, fallback to reference + parseReference = true; + } + pos++; + } + + if (parseReference) { + // + // Link reference + // + if (typeof state.env.references === 'undefined') { return false; } + + if (pos < max && state.src.charCodeAt(pos) === 0x5B/* [ */) { + start = pos + 1; + pos = state.md.helpers.parseLinkLabel(state, pos); + if (pos >= 0) { + label = state.src.slice(start, pos++); + } else { + pos = labelEnd + 1; + } + } else { + pos = labelEnd + 1; + } + + // covers label === '' and label === undefined + // (collapsed reference link and shortcut reference link respectively) + if (!label) { label = state.src.slice(labelStart, labelEnd); } + + ref = state.env.references[normalizeReference(label)]; + if (!ref) { + state.pos = oldPos; + return false; + } + href = ref.href; + title = ref.title; + } + + // + // We found the end of the link, and know for a fact it's a valid link; + // so all that's left to do is to call tokenizer. + // + if (!silent) { + state.pos = labelStart; + state.posMax = labelEnd; + + token = state.push('link_open', 'a', 1); + token.attrs = attrs = [ [ 'href', href ] ]; + if (title) { + attrs.push([ 'title', title ]); + } + + state.md.inline.tokenize(state); + + token = state.push('link_close', 'a', -1); + } + + state.pos = pos; + state.posMax = max; + return true; +}; + +},{"../common/utils":4}],46:[function(require,module,exports){ +// Proceess '\n' + +'use strict'; + +var isSpace = require('../common/utils').isSpace; + + +module.exports = function newline(state, silent) { + var pmax, max, pos = state.pos; + + if (state.src.charCodeAt(pos) !== 0x0A/* \n */) { return false; } + + pmax = state.pending.length - 1; + max = state.posMax; + + // ' \n' -> hardbreak + // Lookup in pending chars is bad practice! Don't copy to other rules! + // Pending string is stored in concat mode, indexed lookups will cause + // convertion to flat mode. + if (!silent) { + if (pmax >= 0 && state.pending.charCodeAt(pmax) === 0x20) { + if (pmax >= 1 && state.pending.charCodeAt(pmax - 1) === 0x20) { + state.pending = state.pending.replace(/ +$/, ''); + state.push('hardbreak', 'br', 0); + } else { + state.pending = state.pending.slice(0, -1); + state.push('softbreak', 'br', 0); + } + + } else { + state.push('softbreak', 'br', 0); + } + } + + pos++; + + // skip heading spaces for next line + while (pos < max && isSpace(state.src.charCodeAt(pos))) { pos++; } + + state.pos = pos; + return true; +}; + +},{"../common/utils":4}],47:[function(require,module,exports){ +// Inline parser state + +'use strict'; + + +var Token = require('../token'); +var isWhiteSpace = require('../common/utils').isWhiteSpace; +var isPunctChar = require('../common/utils').isPunctChar; +var isMdAsciiPunct = require('../common/utils').isMdAsciiPunct; + + +function StateInline(src, md, env, outTokens) { + this.src = src; + this.env = env; + this.md = md; + this.tokens = outTokens; + this.tokens_meta = Array(outTokens.length); + + this.pos = 0; + this.posMax = this.src.length; + this.level = 0; + this.pending = ''; + this.pendingLevel = 0; + + // Stores { start: end } pairs. Useful for backtrack + // optimization of pairs parse (emphasis, strikes). + this.cache = {}; + + // List of emphasis-like delimiters for current tag + this.delimiters = []; + + // Stack of delimiter lists for upper level tags + this._prev_delimiters = []; +} + + +// Flush pending text +// +StateInline.prototype.pushPending = function () { + var token = new Token('text', '', 0); + token.content = this.pending; + token.level = this.pendingLevel; + this.tokens.push(token); + this.pending = ''; + return token; +}; + + +// Push new token to "stream". +// If pending text exists - flush it as text token +// +StateInline.prototype.push = function (type, tag, nesting) { + if (this.pending) { + this.pushPending(); + } + + var token = new Token(type, tag, nesting); + var token_meta = null; + + if (nesting < 0) { + // closing tag + this.level--; + this.delimiters = this._prev_delimiters.pop(); + } + + token.level = this.level; + + if (nesting > 0) { + // opening tag + this.level++; + this._prev_delimiters.push(this.delimiters); + this.delimiters = []; + token_meta = { delimiters: this.delimiters }; + } + + this.pendingLevel = this.level; + this.tokens.push(token); + this.tokens_meta.push(token_meta); + return token; +}; + + +// Scan a sequence of emphasis-like markers, and determine whether +// it can start an emphasis sequence or end an emphasis sequence. +// +// - start - position to scan from (it should point at a valid marker); +// - canSplitWord - determine if these markers can be found inside a word +// +StateInline.prototype.scanDelims = function (start, canSplitWord) { + var pos = start, lastChar, nextChar, count, can_open, can_close, + isLastWhiteSpace, isLastPunctChar, + isNextWhiteSpace, isNextPunctChar, + left_flanking = true, + right_flanking = true, + max = this.posMax, + marker = this.src.charCodeAt(start); + + // treat beginning of the line as a whitespace + lastChar = start > 0 ? this.src.charCodeAt(start - 1) : 0x20; + + while (pos < max && this.src.charCodeAt(pos) === marker) { pos++; } + + count = pos - start; + + // treat end of the line as a whitespace + nextChar = pos < max ? this.src.charCodeAt(pos) : 0x20; + + isLastPunctChar = isMdAsciiPunct(lastChar) || isPunctChar(String.fromCharCode(lastChar)); + isNextPunctChar = isMdAsciiPunct(nextChar) || isPunctChar(String.fromCharCode(nextChar)); + + isLastWhiteSpace = isWhiteSpace(lastChar); + isNextWhiteSpace = isWhiteSpace(nextChar); + + if (isNextWhiteSpace) { + left_flanking = false; + } else if (isNextPunctChar) { + if (!(isLastWhiteSpace || isLastPunctChar)) { + left_flanking = false; + } + } + + if (isLastWhiteSpace) { + right_flanking = false; + } else if (isLastPunctChar) { + if (!(isNextWhiteSpace || isNextPunctChar)) { + right_flanking = false; + } + } + + if (!canSplitWord) { + can_open = left_flanking && (!right_flanking || isLastPunctChar); + can_close = right_flanking && (!left_flanking || isNextPunctChar); + } else { + can_open = left_flanking; + can_close = right_flanking; + } + + return { + can_open: can_open, + can_close: can_close, + length: count + }; +}; + + +// re-export Token class to use in block rules +StateInline.prototype.Token = Token; + + +module.exports = StateInline; + +},{"../common/utils":4,"../token":51}],48:[function(require,module,exports){ +// ~~strike through~~ +// +'use strict'; + + +// Insert each marker as a separate text token, and add it to delimiter list +// +module.exports.tokenize = function strikethrough(state, silent) { + var i, scanned, token, len, ch, + start = state.pos, + marker = state.src.charCodeAt(start); + + if (silent) { return false; } + + if (marker !== 0x7E/* ~ */) { return false; } + + scanned = state.scanDelims(state.pos, true); + len = scanned.length; + ch = String.fromCharCode(marker); + + if (len < 2) { return false; } + + if (len % 2) { + token = state.push('text', '', 0); + token.content = ch; + len--; + } + + for (i = 0; i < len; i += 2) { + token = state.push('text', '', 0); + token.content = ch + ch; + + state.delimiters.push({ + marker: marker, + length: 0, // disable "rule of 3" length checks meant for emphasis + jump: i, + token: state.tokens.length - 1, + end: -1, + open: scanned.can_open, + close: scanned.can_close + }); + } + + state.pos += scanned.length; + + return true; +}; + + +function postProcess(state, delimiters) { + var i, j, + startDelim, + endDelim, + token, + loneMarkers = [], + max = delimiters.length; + + for (i = 0; i < max; i++) { + startDelim = delimiters[i]; + + if (startDelim.marker !== 0x7E/* ~ */) { + continue; + } + + if (startDelim.end === -1) { + continue; + } + + endDelim = delimiters[startDelim.end]; + + token = state.tokens[startDelim.token]; + token.type = 's_open'; + token.tag = 's'; + token.nesting = 1; + token.markup = '~~'; + token.content = ''; + + token = state.tokens[endDelim.token]; + token.type = 's_close'; + token.tag = 's'; + token.nesting = -1; + token.markup = '~~'; + token.content = ''; + + if (state.tokens[endDelim.token - 1].type === 'text' && + state.tokens[endDelim.token - 1].content === '~') { + + loneMarkers.push(endDelim.token - 1); + } + } + + // If a marker sequence has an odd number of characters, it's splitted + // like this: `~~~~~` -> `~` + `~~` + `~~`, leaving one marker at the + // start of the sequence. + // + // So, we have to move all those markers after subsequent s_close tags. + // + while (loneMarkers.length) { + i = loneMarkers.pop(); + j = i + 1; + + while (j < state.tokens.length && state.tokens[j].type === 's_close') { + j++; + } + + j--; + + if (i !== j) { + token = state.tokens[j]; + state.tokens[j] = state.tokens[i]; + state.tokens[i] = token; + } + } +} + + +// Walk through delimiter list and replace text tokens with tags +// +module.exports.postProcess = function strikethrough(state) { + var curr, + tokens_meta = state.tokens_meta, + max = state.tokens_meta.length; + + postProcess(state, state.delimiters); + + for (curr = 0; curr < max; curr++) { + if (tokens_meta[curr] && tokens_meta[curr].delimiters) { + postProcess(state, tokens_meta[curr].delimiters); + } + } +}; + +},{}],49:[function(require,module,exports){ +// Skip text characters for text token, place those to pending buffer +// and increment current pos + +'use strict'; + + +// Rule to skip pure text +// '{}$%@~+=:' reserved for extentions + +// !, ", #, $, %, &, ', (, ), *, +, ,, -, ., /, :, ;, <, =, >, ?, @, [, \, ], ^, _, `, {, |, }, or ~ + +// !!!! Don't confuse with "Markdown ASCII Punctuation" chars +// http://spec.commonmark.org/0.15/#ascii-punctuation-character +function isTerminatorChar(ch) { + switch (ch) { + case 0x0A/* \n */: + case 0x21/* ! */: + case 0x23/* # */: + case 0x24/* $ */: + case 0x25/* % */: + case 0x26/* & */: + case 0x2A/* * */: + case 0x2B/* + */: + case 0x2D/* - */: + case 0x3A/* : */: + case 0x3C/* < */: + case 0x3D/* = */: + case 0x3E/* > */: + case 0x40/* @ */: + case 0x5B/* [ */: + case 0x5C/* \ */: + case 0x5D/* ] */: + case 0x5E/* ^ */: + case 0x5F/* _ */: + case 0x60/* ` */: + case 0x7B/* { */: + case 0x7D/* } */: + case 0x7E/* ~ */: + return true; + default: + return false; + } +} + +module.exports = function text(state, silent) { + var pos = state.pos; + + while (pos < state.posMax && !isTerminatorChar(state.src.charCodeAt(pos))) { + pos++; + } + + if (pos === state.pos) { return false; } + + if (!silent) { state.pending += state.src.slice(state.pos, pos); } + + state.pos = pos; + + return true; +}; + +// Alternative implementation, for memory. +// +// It costs 10% of performance, but allows extend terminators list, if place it +// to `ParcerInline` property. Probably, will switch to it sometime, such +// flexibility required. + +/* +var TERMINATOR_RE = /[\n!#$%&*+\-:<=>@[\\\]^_`{}~]/; + +module.exports = function text(state, silent) { + var pos = state.pos, + idx = state.src.slice(pos).search(TERMINATOR_RE); + + // first char is terminator -> empty text + if (idx === 0) { return false; } + + // no terminator -> text till end of string + if (idx < 0) { + if (!silent) { state.pending += state.src.slice(pos); } + state.pos = state.src.length; + return true; + } + + if (!silent) { state.pending += state.src.slice(pos, pos + idx); } + + state.pos += idx; + + return true; +};*/ + +},{}],50:[function(require,module,exports){ +// Clean up tokens after emphasis and strikethrough postprocessing: +// merge adjacent text nodes into one and re-calculate all token levels +// +// This is necessary because initially emphasis delimiter markers (*, _, ~) +// are treated as their own separate text tokens. Then emphasis rule either +// leaves them as text (needed to merge with adjacent text) or turns them +// into opening/closing tags (which messes up levels inside). +// +'use strict'; + + +module.exports = function text_collapse(state) { + var curr, last, + level = 0, + tokens = state.tokens, + max = state.tokens.length; + + for (curr = last = 0; curr < max; curr++) { + // re-calculate levels after emphasis/strikethrough turns some text nodes + // into opening/closing tags + if (tokens[curr].nesting < 0) level--; // closing tag + tokens[curr].level = level; + if (tokens[curr].nesting > 0) level++; // opening tag + + if (tokens[curr].type === 'text' && + curr + 1 < max && + tokens[curr + 1].type === 'text') { + + // collapse two adjacent text nodes + tokens[curr + 1].content = tokens[curr].content + tokens[curr + 1].content; + } else { + if (curr !== last) { tokens[last] = tokens[curr]; } + + last++; + } + } + + if (curr !== last) { + tokens.length = last; + } +}; + +},{}],51:[function(require,module,exports){ +// Token class + +'use strict'; + + +/** + * class Token + **/ + +/** + * new Token(type, tag, nesting) + * + * Create new token and fill passed properties. + **/ +function Token(type, tag, nesting) { + /** + * Token#type -> String + * + * Type of the token (string, e.g. "paragraph_open") + **/ + this.type = type; + + /** + * Token#tag -> String + * + * html tag name, e.g. "p" + **/ + this.tag = tag; + + /** + * Token#attrs -> Array + * + * Html attributes. Format: `[ [ name1, value1 ], [ name2, value2 ] ]` + **/ + this.attrs = null; + + /** + * Token#map -> Array + * + * Source map info. Format: `[ line_begin, line_end ]` + **/ + this.map = null; + + /** + * Token#nesting -> Number + * + * Level change (number in {-1, 0, 1} set), where: + * + * - `1` means the tag is opening + * - `0` means the tag is self-closing + * - `-1` means the tag is closing + **/ + this.nesting = nesting; + + /** + * Token#level -> Number + * + * nesting level, the same as `state.level` + **/ + this.level = 0; + + /** + * Token#children -> Array + * + * An array of child nodes (inline and img tokens) + **/ + this.children = null; + + /** + * Token#content -> String + * + * In a case of self-closing tag (code, html, fence, etc.), + * it has contents of this tag. + **/ + this.content = ''; + + /** + * Token#markup -> String + * + * '*' or '_' for emphasis, fence string for fence, etc. + **/ + this.markup = ''; + + /** + * Token#info -> String + * + * fence infostring + **/ + this.info = ''; + + /** + * Token#meta -> Object + * + * A place for plugins to store an arbitrary data + **/ + this.meta = null; + + /** + * Token#block -> Boolean + * + * True for block-level tokens, false for inline tokens. + * Used in renderer to calculate line breaks + **/ + this.block = false; + + /** + * Token#hidden -> Boolean + * + * If it's true, ignore this element when rendering. Used for tight lists + * to hide paragraphs. + **/ + this.hidden = false; +} + + +/** + * Token.attrIndex(name) -> Number + * + * Search attribute index by name. + **/ +Token.prototype.attrIndex = function attrIndex(name) { + var attrs, i, len; + + if (!this.attrs) { return -1; } + + attrs = this.attrs; + + for (i = 0, len = attrs.length; i < len; i++) { + if (attrs[i][0] === name) { return i; } + } + return -1; +}; + + +/** + * Token.attrPush(attrData) + * + * Add `[ name, value ]` attribute to list. Init attrs if necessary + **/ +Token.prototype.attrPush = function attrPush(attrData) { + if (this.attrs) { + this.attrs.push(attrData); + } else { + this.attrs = [ attrData ]; + } +}; + + +/** + * Token.attrSet(name, value) + * + * Set `name` attribute to `value`. Override old value if exists. + **/ +Token.prototype.attrSet = function attrSet(name, value) { + var idx = this.attrIndex(name), + attrData = [ name, value ]; + + if (idx < 0) { + this.attrPush(attrData); + } else { + this.attrs[idx] = attrData; + } +}; + + +/** + * Token.attrGet(name) + * + * Get the value of attribute `name`, or null if it does not exist. + **/ +Token.prototype.attrGet = function attrGet(name) { + var idx = this.attrIndex(name), value = null; + if (idx >= 0) { + value = this.attrs[idx][1]; + } + return value; +}; + + +/** + * Token.attrJoin(name, value) + * + * Join value to existing attribute via space. Or create new attribute if not + * exists. Useful to operate with token classes. + **/ +Token.prototype.attrJoin = function attrJoin(name, value) { + var idx = this.attrIndex(name); + + if (idx < 0) { + this.attrPush([ name, value ]); + } else { + this.attrs[idx][1] = this.attrs[idx][1] + ' ' + value; + } +}; + + +module.exports = Token; + +},{}],52:[function(require,module,exports){ +module.exports={ "Aacute": "\u00C1", "aacute": "\u00E1", "Abreve": "\u0102", "abreve": "\u0103", "ac": "\u223E", "acd": "\u223F", "acE": "\u223E\u0333", "Acirc": "\u00C2", "acirc": "\u00E2", "acute": "\u00B4", "Acy": "\u0410", "acy": "\u0430", "AElig": "\u00C6", "aelig": "\u00E6", "af": "\u2061", "Afr": "\uD835\uDD04", "afr": "\uD835\uDD1E", "Agrave": "\u00C0", "agrave": "\u00E0", "alefsym": "\u2135", "aleph": "\u2135", "Alpha": "\u0391", "alpha": "\u03B1", "Amacr": "\u0100", "amacr": "\u0101", "amalg": "\u2A3F", "amp": "&", "AMP": "&", "andand": "\u2A55", "And": "\u2A53", "and": "\u2227", "andd": "\u2A5C", "andslope": "\u2A58", "andv": "\u2A5A", "ang": "\u2220", "ange": "\u29A4", "angle": "\u2220", "angmsdaa": "\u29A8", "angmsdab": "\u29A9", "angmsdac": "\u29AA", "angmsdad": "\u29AB", "angmsdae": "\u29AC", "angmsdaf": "\u29AD", "angmsdag": "\u29AE", "angmsdah": "\u29AF", "angmsd": "\u2221", "angrt": "\u221F", "angrtvb": "\u22BE", "angrtvbd": "\u299D", "angsph": "\u2222", "angst": "\u00C5", "angzarr": "\u237C", "Aogon": "\u0104", "aogon": "\u0105", "Aopf": "\uD835\uDD38", "aopf": "\uD835\uDD52", "apacir": "\u2A6F", "ap": "\u2248", "apE": "\u2A70", "ape": "\u224A", "apid": "\u224B", "apos": "'", "ApplyFunction": "\u2061", "approx": "\u2248", "approxeq": "\u224A", "Aring": "\u00C5", "aring": "\u00E5", "Ascr": "\uD835\uDC9C", "ascr": "\uD835\uDCB6", "Assign": "\u2254", "ast": "*", "asymp": "\u2248", "asympeq": "\u224D", "Atilde": "\u00C3", "atilde": "\u00E3", "Auml": "\u00C4", "auml": "\u00E4", "awconint": "\u2233", "awint": "\u2A11", "backcong": "\u224C", "backepsilon": "\u03F6", "backprime": "\u2035", "backsim": "\u223D", "backsimeq": "\u22CD", "Backslash": "\u2216", "Barv": "\u2AE7", "barvee": "\u22BD", "barwed": "\u2305", "Barwed": "\u2306", "barwedge": "\u2305", "bbrk": "\u23B5", "bbrktbrk": "\u23B6", "bcong": "\u224C", "Bcy": "\u0411", "bcy": "\u0431", "bdquo": "\u201E", "becaus": "\u2235", "because": "\u2235", "Because": "\u2235", "bemptyv": "\u29B0", "bepsi": "\u03F6", "bernou": "\u212C", "Bernoullis": "\u212C", "Beta": "\u0392", "beta": "\u03B2", "beth": "\u2136", "between": "\u226C", "Bfr": "\uD835\uDD05", "bfr": "\uD835\uDD1F", "bigcap": "\u22C2", "bigcirc": "\u25EF", "bigcup": "\u22C3", "bigodot": "\u2A00", "bigoplus": "\u2A01", "bigotimes": "\u2A02", "bigsqcup": "\u2A06", "bigstar": "\u2605", "bigtriangledown": "\u25BD", "bigtriangleup": "\u25B3", "biguplus": "\u2A04", "bigvee": "\u22C1", "bigwedge": "\u22C0", "bkarow": "\u290D", "blacklozenge": "\u29EB", "blacksquare": "\u25AA", "blacktriangle": "\u25B4", "blacktriangledown": "\u25BE", "blacktriangleleft": "\u25C2", "blacktriangleright": "\u25B8", "blank": "\u2423", "blk12": "\u2592", "blk14": "\u2591", "blk34": "\u2593", "block": "\u2588", "bne": "=\u20E5", "bnequiv": "\u2261\u20E5", "bNot": "\u2AED", "bnot": "\u2310", "Bopf": "\uD835\uDD39", "bopf": "\uD835\uDD53", "bot": "\u22A5", "bottom": "\u22A5", "bowtie": "\u22C8", "boxbox": "\u29C9", "boxdl": "\u2510", "boxdL": "\u2555", "boxDl": "\u2556", "boxDL": "\u2557", "boxdr": "\u250C", "boxdR": "\u2552", "boxDr": "\u2553", "boxDR": "\u2554", "boxh": "\u2500", "boxH": "\u2550", "boxhd": "\u252C", "boxHd": "\u2564", "boxhD": "\u2565", "boxHD": "\u2566", "boxhu": "\u2534", "boxHu": "\u2567", "boxhU": "\u2568", "boxHU": "\u2569", "boxminus": "\u229F", "boxplus": "\u229E", "boxtimes": "\u22A0", "boxul": "\u2518", "boxuL": "\u255B", "boxUl": "\u255C", "boxUL": "\u255D", "boxur": "\u2514", "boxuR": "\u2558", "boxUr": "\u2559", "boxUR": "\u255A", "boxv": "\u2502", "boxV": "\u2551", "boxvh": "\u253C", "boxvH": "\u256A", "boxVh": "\u256B", "boxVH": "\u256C", "boxvl": "\u2524", "boxvL": "\u2561", "boxVl": "\u2562", "boxVL": "\u2563", "boxvr": "\u251C", "boxvR": "\u255E", "boxVr": "\u255F", "boxVR": "\u2560", "bprime": "\u2035", "breve": "\u02D8", "Breve": "\u02D8", "brvbar": "\u00A6", "bscr": "\uD835\uDCB7", "Bscr": "\u212C", "bsemi": "\u204F", "bsim": "\u223D", "bsime": "\u22CD", "bsolb": "\u29C5", "bsol": "\\", "bsolhsub": "\u27C8", "bull": "\u2022", "bullet": "\u2022", "bump": "\u224E", "bumpE": "\u2AAE", "bumpe": "\u224F", "Bumpeq": "\u224E", "bumpeq": "\u224F", "Cacute": "\u0106", "cacute": "\u0107", "capand": "\u2A44", "capbrcup": "\u2A49", "capcap": "\u2A4B", "cap": "\u2229", "Cap": "\u22D2", "capcup": "\u2A47", "capdot": "\u2A40", "CapitalDifferentialD": "\u2145", "caps": "\u2229\uFE00", "caret": "\u2041", "caron": "\u02C7", "Cayleys": "\u212D", "ccaps": "\u2A4D", "Ccaron": "\u010C", "ccaron": "\u010D", "Ccedil": "\u00C7", "ccedil": "\u00E7", "Ccirc": "\u0108", "ccirc": "\u0109", "Cconint": "\u2230", "ccups": "\u2A4C", "ccupssm": "\u2A50", "Cdot": "\u010A", "cdot": "\u010B", "cedil": "\u00B8", "Cedilla": "\u00B8", "cemptyv": "\u29B2", "cent": "\u00A2", "centerdot": "\u00B7", "CenterDot": "\u00B7", "cfr": "\uD835\uDD20", "Cfr": "\u212D", "CHcy": "\u0427", "chcy": "\u0447", "check": "\u2713", "checkmark": "\u2713", "Chi": "\u03A7", "chi": "\u03C7", "circ": "\u02C6", "circeq": "\u2257", "circlearrowleft": "\u21BA", "circlearrowright": "\u21BB", "circledast": "\u229B", "circledcirc": "\u229A", "circleddash": "\u229D", "CircleDot": "\u2299", "circledR": "\u00AE", "circledS": "\u24C8", "CircleMinus": "\u2296", "CirclePlus": "\u2295", "CircleTimes": "\u2297", "cir": "\u25CB", "cirE": "\u29C3", "cire": "\u2257", "cirfnint": "\u2A10", "cirmid": "\u2AEF", "cirscir": "\u29C2", "ClockwiseContourIntegral": "\u2232", "CloseCurlyDoubleQuote": "\u201D", "CloseCurlyQuote": "\u2019", "clubs": "\u2663", "clubsuit": "\u2663", "colon": ":", "Colon": "\u2237", "Colone": "\u2A74", "colone": "\u2254", "coloneq": "\u2254", "comma": ",", "commat": "@", "comp": "\u2201", "compfn": "\u2218", "complement": "\u2201", "complexes": "\u2102", "cong": "\u2245", "congdot": "\u2A6D", "Congruent": "\u2261", "conint": "\u222E", "Conint": "\u222F", "ContourIntegral": "\u222E", "copf": "\uD835\uDD54", "Copf": "\u2102", "coprod": "\u2210", "Coproduct": "\u2210", "copy": "\u00A9", "COPY": "\u00A9", "copysr": "\u2117", "CounterClockwiseContourIntegral": "\u2233", "crarr": "\u21B5", "cross": "\u2717", "Cross": "\u2A2F", "Cscr": "\uD835\uDC9E", "cscr": "\uD835\uDCB8", "csub": "\u2ACF", "csube": "\u2AD1", "csup": "\u2AD0", "csupe": "\u2AD2", "ctdot": "\u22EF", "cudarrl": "\u2938", "cudarrr": "\u2935", "cuepr": "\u22DE", "cuesc": "\u22DF", "cularr": "\u21B6", "cularrp": "\u293D", "cupbrcap": "\u2A48", "cupcap": "\u2A46", "CupCap": "\u224D", "cup": "\u222A", "Cup": "\u22D3", "cupcup": "\u2A4A", "cupdot": "\u228D", "cupor": "\u2A45", "cups": "\u222A\uFE00", "curarr": "\u21B7", "curarrm": "\u293C", "curlyeqprec": "\u22DE", "curlyeqsucc": "\u22DF", "curlyvee": "\u22CE", "curlywedge": "\u22CF", "curren": "\u00A4", "curvearrowleft": "\u21B6", "curvearrowright": "\u21B7", "cuvee": "\u22CE", "cuwed": "\u22CF", "cwconint": "\u2232", "cwint": "\u2231", "cylcty": "\u232D", "dagger": "\u2020", "Dagger": "\u2021", "daleth": "\u2138", "darr": "\u2193", "Darr": "\u21A1", "dArr": "\u21D3", "dash": "\u2010", "Dashv": "\u2AE4", "dashv": "\u22A3", "dbkarow": "\u290F", "dblac": "\u02DD", "Dcaron": "\u010E", "dcaron": "\u010F", "Dcy": "\u0414", "dcy": "\u0434", "ddagger": "\u2021", "ddarr": "\u21CA", "DD": "\u2145", "dd": "\u2146", "DDotrahd": "\u2911", "ddotseq": "\u2A77", "deg": "\u00B0", "Del": "\u2207", "Delta": "\u0394", "delta": "\u03B4", "demptyv": "\u29B1", "dfisht": "\u297F", "Dfr": "\uD835\uDD07", "dfr": "\uD835\uDD21", "dHar": "\u2965", "dharl": "\u21C3", "dharr": "\u21C2", "DiacriticalAcute": "\u00B4", "DiacriticalDot": "\u02D9", "DiacriticalDoubleAcute": "\u02DD", "DiacriticalGrave": "`", "DiacriticalTilde": "\u02DC", "diam": "\u22C4", "diamond": "\u22C4", "Diamond": "\u22C4", "diamondsuit": "\u2666", "diams": "\u2666", "die": "\u00A8", "DifferentialD": "\u2146", "digamma": "\u03DD", "disin": "\u22F2", "div": "\u00F7", "divide": "\u00F7", "divideontimes": "\u22C7", "divonx": "\u22C7", "DJcy": "\u0402", "djcy": "\u0452", "dlcorn": "\u231E", "dlcrop": "\u230D", "dollar": "$", "Dopf": "\uD835\uDD3B", "dopf": "\uD835\uDD55", "Dot": "\u00A8", "dot": "\u02D9", "DotDot": "\u20DC", "doteq": "\u2250", "doteqdot": "\u2251", "DotEqual": "\u2250", "dotminus": "\u2238", "dotplus": "\u2214", "dotsquare": "\u22A1", "doublebarwedge": "\u2306", "DoubleContourIntegral": "\u222F", "DoubleDot": "\u00A8", "DoubleDownArrow": "\u21D3", "DoubleLeftArrow": "\u21D0", "DoubleLeftRightArrow": "\u21D4", "DoubleLeftTee": "\u2AE4", "DoubleLongLeftArrow": "\u27F8", "DoubleLongLeftRightArrow": "\u27FA", "DoubleLongRightArrow": "\u27F9", "DoubleRightArrow": "\u21D2", "DoubleRightTee": "\u22A8", "DoubleUpArrow": "\u21D1", "DoubleUpDownArrow": "\u21D5", "DoubleVerticalBar": "\u2225", "DownArrowBar": "\u2913", "downarrow": "\u2193", "DownArrow": "\u2193", "Downarrow": "\u21D3", "DownArrowUpArrow": "\u21F5", "DownBreve": "\u0311", "downdownarrows": "\u21CA", "downharpoonleft": "\u21C3", "downharpoonright": "\u21C2", "DownLeftRightVector": "\u2950", "DownLeftTeeVector": "\u295E", "DownLeftVectorBar": "\u2956", "DownLeftVector": "\u21BD", "DownRightTeeVector": "\u295F", "DownRightVectorBar": "\u2957", "DownRightVector": "\u21C1", "DownTeeArrow": "\u21A7", "DownTee": "\u22A4", "drbkarow": "\u2910", "drcorn": "\u231F", "drcrop": "\u230C", "Dscr": "\uD835\uDC9F", "dscr": "\uD835\uDCB9", "DScy": "\u0405", "dscy": "\u0455", "dsol": "\u29F6", "Dstrok": "\u0110", "dstrok": "\u0111", "dtdot": "\u22F1", "dtri": "\u25BF", "dtrif": "\u25BE", "duarr": "\u21F5", "duhar": "\u296F", "dwangle": "\u29A6", "DZcy": "\u040F", "dzcy": "\u045F", "dzigrarr": "\u27FF", "Eacute": "\u00C9", "eacute": "\u00E9", "easter": "\u2A6E", "Ecaron": "\u011A", "ecaron": "\u011B", "Ecirc": "\u00CA", "ecirc": "\u00EA", "ecir": "\u2256", "ecolon": "\u2255", "Ecy": "\u042D", "ecy": "\u044D", "eDDot": "\u2A77", "Edot": "\u0116", "edot": "\u0117", "eDot": "\u2251", "ee": "\u2147", "efDot": "\u2252", "Efr": "\uD835\uDD08", "efr": "\uD835\uDD22", "eg": "\u2A9A", "Egrave": "\u00C8", "egrave": "\u00E8", "egs": "\u2A96", "egsdot": "\u2A98", "el": "\u2A99", "Element": "\u2208", "elinters": "\u23E7", "ell": "\u2113", "els": "\u2A95", "elsdot": "\u2A97", "Emacr": "\u0112", "emacr": "\u0113", "empty": "\u2205", "emptyset": "\u2205", "EmptySmallSquare": "\u25FB", "emptyv": "\u2205", "EmptyVerySmallSquare": "\u25AB", "emsp13": "\u2004", "emsp14": "\u2005", "emsp": "\u2003", "ENG": "\u014A", "eng": "\u014B", "ensp": "\u2002", "Eogon": "\u0118", "eogon": "\u0119", "Eopf": "\uD835\uDD3C", "eopf": "\uD835\uDD56", "epar": "\u22D5", "eparsl": "\u29E3", "eplus": "\u2A71", "epsi": "\u03B5", "Epsilon": "\u0395", "epsilon": "\u03B5", "epsiv": "\u03F5", "eqcirc": "\u2256", "eqcolon": "\u2255", "eqsim": "\u2242", "eqslantgtr": "\u2A96", "eqslantless": "\u2A95", "Equal": "\u2A75", "equals": "=", "EqualTilde": "\u2242", "equest": "\u225F", "Equilibrium": "\u21CC", "equiv": "\u2261", "equivDD": "\u2A78", "eqvparsl": "\u29E5", "erarr": "\u2971", "erDot": "\u2253", "escr": "\u212F", "Escr": "\u2130", "esdot": "\u2250", "Esim": "\u2A73", "esim": "\u2242", "Eta": "\u0397", "eta": "\u03B7", "ETH": "\u00D0", "eth": "\u00F0", "Euml": "\u00CB", "euml": "\u00EB", "euro": "\u20AC", "excl": "!", "exist": "\u2203", "Exists": "\u2203", "expectation": "\u2130", "exponentiale": "\u2147", "ExponentialE": "\u2147", "fallingdotseq": "\u2252", "Fcy": "\u0424", "fcy": "\u0444", "female": "\u2640", "ffilig": "\uFB03", "fflig": "\uFB00", "ffllig": "\uFB04", "Ffr": "\uD835\uDD09", "ffr": "\uD835\uDD23", "filig": "\uFB01", "FilledSmallSquare": "\u25FC", "FilledVerySmallSquare": "\u25AA", "fjlig": "fj", "flat": "\u266D", "fllig": "\uFB02", "fltns": "\u25B1", "fnof": "\u0192", "Fopf": "\uD835\uDD3D", "fopf": "\uD835\uDD57", "forall": "\u2200", "ForAll": "\u2200", "fork": "\u22D4", "forkv": "\u2AD9", "Fouriertrf": "\u2131", "fpartint": "\u2A0D", "frac12": "\u00BD", "frac13": "\u2153", "frac14": "\u00BC", "frac15": "\u2155", "frac16": "\u2159", "frac18": "\u215B", "frac23": "\u2154", "frac25": "\u2156", "frac34": "\u00BE", "frac35": "\u2157", "frac38": "\u215C", "frac45": "\u2158", "frac56": "\u215A", "frac58": "\u215D", "frac78": "\u215E", "frasl": "\u2044", "frown": "\u2322", "fscr": "\uD835\uDCBB", "Fscr": "\u2131", "gacute": "\u01F5", "Gamma": "\u0393", "gamma": "\u03B3", "Gammad": "\u03DC", "gammad": "\u03DD", "gap": "\u2A86", "Gbreve": "\u011E", "gbreve": "\u011F", "Gcedil": "\u0122", "Gcirc": "\u011C", "gcirc": "\u011D", "Gcy": "\u0413", "gcy": "\u0433", "Gdot": "\u0120", "gdot": "\u0121", "ge": "\u2265", "gE": "\u2267", "gEl": "\u2A8C", "gel": "\u22DB", "geq": "\u2265", "geqq": "\u2267", "geqslant": "\u2A7E", "gescc": "\u2AA9", "ges": "\u2A7E", "gesdot": "\u2A80", "gesdoto": "\u2A82", "gesdotol": "\u2A84", "gesl": "\u22DB\uFE00", "gesles": "\u2A94", "Gfr": "\uD835\uDD0A", "gfr": "\uD835\uDD24", "gg": "\u226B", "Gg": "\u22D9", "ggg": "\u22D9", "gimel": "\u2137", "GJcy": "\u0403", "gjcy": "\u0453", "gla": "\u2AA5", "gl": "\u2277", "glE": "\u2A92", "glj": "\u2AA4", "gnap": "\u2A8A", "gnapprox": "\u2A8A", "gne": "\u2A88", "gnE": "\u2269", "gneq": "\u2A88", "gneqq": "\u2269", "gnsim": "\u22E7", "Gopf": "\uD835\uDD3E", "gopf": "\uD835\uDD58", "grave": "`", "GreaterEqual": "\u2265", "GreaterEqualLess": "\u22DB", "GreaterFullEqual": "\u2267", "GreaterGreater": "\u2AA2", "GreaterLess": "\u2277", "GreaterSlantEqual": "\u2A7E", "GreaterTilde": "\u2273", "Gscr": "\uD835\uDCA2", "gscr": "\u210A", "gsim": "\u2273", "gsime": "\u2A8E", "gsiml": "\u2A90", "gtcc": "\u2AA7", "gtcir": "\u2A7A", "gt": ">", "GT": ">", "Gt": "\u226B", "gtdot": "\u22D7", "gtlPar": "\u2995", "gtquest": "\u2A7C", "gtrapprox": "\u2A86", "gtrarr": "\u2978", "gtrdot": "\u22D7", "gtreqless": "\u22DB", "gtreqqless": "\u2A8C", "gtrless": "\u2277", "gtrsim": "\u2273", "gvertneqq": "\u2269\uFE00", "gvnE": "\u2269\uFE00", "Hacek": "\u02C7", "hairsp": "\u200A", "half": "\u00BD", "hamilt": "\u210B", "HARDcy": "\u042A", "hardcy": "\u044A", "harrcir": "\u2948", "harr": "\u2194", "hArr": "\u21D4", "harrw": "\u21AD", "Hat": "^", "hbar": "\u210F", "Hcirc": "\u0124", "hcirc": "\u0125", "hearts": "\u2665", "heartsuit": "\u2665", "hellip": "\u2026", "hercon": "\u22B9", "hfr": "\uD835\uDD25", "Hfr": "\u210C", "HilbertSpace": "\u210B", "hksearow": "\u2925", "hkswarow": "\u2926", "hoarr": "\u21FF", "homtht": "\u223B", "hookleftarrow": "\u21A9", "hookrightarrow": "\u21AA", "hopf": "\uD835\uDD59", "Hopf": "\u210D", "horbar": "\u2015", "HorizontalLine": "\u2500", "hscr": "\uD835\uDCBD", "Hscr": "\u210B", "hslash": "\u210F", "Hstrok": "\u0126", "hstrok": "\u0127", "HumpDownHump": "\u224E", "HumpEqual": "\u224F", "hybull": "\u2043", "hyphen": "\u2010", "Iacute": "\u00CD", "iacute": "\u00ED", "ic": "\u2063", "Icirc": "\u00CE", "icirc": "\u00EE", "Icy": "\u0418", "icy": "\u0438", "Idot": "\u0130", "IEcy": "\u0415", "iecy": "\u0435", "iexcl": "\u00A1", "iff": "\u21D4", "ifr": "\uD835\uDD26", "Ifr": "\u2111", "Igrave": "\u00CC", "igrave": "\u00EC", "ii": "\u2148", "iiiint": "\u2A0C", "iiint": "\u222D", "iinfin": "\u29DC", "iiota": "\u2129", "IJlig": "\u0132", "ijlig": "\u0133", "Imacr": "\u012A", "imacr": "\u012B", "image": "\u2111", "ImaginaryI": "\u2148", "imagline": "\u2110", "imagpart": "\u2111", "imath": "\u0131", "Im": "\u2111", "imof": "\u22B7", "imped": "\u01B5", "Implies": "\u21D2", "incare": "\u2105", "in": "\u2208", "infin": "\u221E", "infintie": "\u29DD", "inodot": "\u0131", "intcal": "\u22BA", "int": "\u222B", "Int": "\u222C", "integers": "\u2124", "Integral": "\u222B", "intercal": "\u22BA", "Intersection": "\u22C2", "intlarhk": "\u2A17", "intprod": "\u2A3C", "InvisibleComma": "\u2063", "InvisibleTimes": "\u2062", "IOcy": "\u0401", "iocy": "\u0451", "Iogon": "\u012E", "iogon": "\u012F", "Iopf": "\uD835\uDD40", "iopf": "\uD835\uDD5A", "Iota": "\u0399", "iota": "\u03B9", "iprod": "\u2A3C", "iquest": "\u00BF", "iscr": "\uD835\uDCBE", "Iscr": "\u2110", "isin": "\u2208", "isindot": "\u22F5", "isinE": "\u22F9", "isins": "\u22F4", "isinsv": "\u22F3", "isinv": "\u2208", "it": "\u2062", "Itilde": "\u0128", "itilde": "\u0129", "Iukcy": "\u0406", "iukcy": "\u0456", "Iuml": "\u00CF", "iuml": "\u00EF", "Jcirc": "\u0134", "jcirc": "\u0135", "Jcy": "\u0419", "jcy": "\u0439", "Jfr": "\uD835\uDD0D", "jfr": "\uD835\uDD27", "jmath": "\u0237", "Jopf": "\uD835\uDD41", "jopf": "\uD835\uDD5B", "Jscr": "\uD835\uDCA5", "jscr": "\uD835\uDCBF", "Jsercy": "\u0408", "jsercy": "\u0458", "Jukcy": "\u0404", "jukcy": "\u0454", "Kappa": "\u039A", "kappa": "\u03BA", "kappav": "\u03F0", "Kcedil": "\u0136", "kcedil": "\u0137", "Kcy": "\u041A", "kcy": "\u043A", "Kfr": "\uD835\uDD0E", "kfr": "\uD835\uDD28", "kgreen": "\u0138", "KHcy": "\u0425", "khcy": "\u0445", "KJcy": "\u040C", "kjcy": "\u045C", "Kopf": "\uD835\uDD42", "kopf": "\uD835\uDD5C", "Kscr": "\uD835\uDCA6", "kscr": "\uD835\uDCC0", "lAarr": "\u21DA", "Lacute": "\u0139", "lacute": "\u013A", "laemptyv": "\u29B4", "lagran": "\u2112", "Lambda": "\u039B", "lambda": "\u03BB", "lang": "\u27E8", "Lang": "\u27EA", "langd": "\u2991", "langle": "\u27E8", "lap": "\u2A85", "Laplacetrf": "\u2112", "laquo": "\u00AB", "larrb": "\u21E4", "larrbfs": "\u291F", "larr": "\u2190", "Larr": "\u219E", "lArr": "\u21D0", "larrfs": "\u291D", "larrhk": "\u21A9", "larrlp": "\u21AB", "larrpl": "\u2939", "larrsim": "\u2973", "larrtl": "\u21A2", "latail": "\u2919", "lAtail": "\u291B", "lat": "\u2AAB", "late": "\u2AAD", "lates": "\u2AAD\uFE00", "lbarr": "\u290C", "lBarr": "\u290E", "lbbrk": "\u2772", "lbrace": "{", "lbrack": "[", "lbrke": "\u298B", "lbrksld": "\u298F", "lbrkslu": "\u298D", "Lcaron": "\u013D", "lcaron": "\u013E", "Lcedil": "\u013B", "lcedil": "\u013C", "lceil": "\u2308", "lcub": "{", "Lcy": "\u041B", "lcy": "\u043B", "ldca": "\u2936", "ldquo": "\u201C", "ldquor": "\u201E", "ldrdhar": "\u2967", "ldrushar": "\u294B", "ldsh": "\u21B2", "le": "\u2264", "lE": "\u2266", "LeftAngleBracket": "\u27E8", "LeftArrowBar": "\u21E4", "leftarrow": "\u2190", "LeftArrow": "\u2190", "Leftarrow": "\u21D0", "LeftArrowRightArrow": "\u21C6", "leftarrowtail": "\u21A2", "LeftCeiling": "\u2308", "LeftDoubleBracket": "\u27E6", "LeftDownTeeVector": "\u2961", "LeftDownVectorBar": "\u2959", "LeftDownVector": "\u21C3", "LeftFloor": "\u230A", "leftharpoondown": "\u21BD", "leftharpoonup": "\u21BC", "leftleftarrows": "\u21C7", "leftrightarrow": "\u2194", "LeftRightArrow": "\u2194", "Leftrightarrow": "\u21D4", "leftrightarrows": "\u21C6", "leftrightharpoons": "\u21CB", "leftrightsquigarrow": "\u21AD", "LeftRightVector": "\u294E", "LeftTeeArrow": "\u21A4", "LeftTee": "\u22A3", "LeftTeeVector": "\u295A", "leftthreetimes": "\u22CB", "LeftTriangleBar": "\u29CF", "LeftTriangle": "\u22B2", "LeftTriangleEqual": "\u22B4", "LeftUpDownVector": "\u2951", "LeftUpTeeVector": "\u2960", "LeftUpVectorBar": "\u2958", "LeftUpVector": "\u21BF", "LeftVectorBar": "\u2952", "LeftVector": "\u21BC", "lEg": "\u2A8B", "leg": "\u22DA", "leq": "\u2264", "leqq": "\u2266", "leqslant": "\u2A7D", "lescc": "\u2AA8", "les": "\u2A7D", "lesdot": "\u2A7F", "lesdoto": "\u2A81", "lesdotor": "\u2A83", "lesg": "\u22DA\uFE00", "lesges": "\u2A93", "lessapprox": "\u2A85", "lessdot": "\u22D6", "lesseqgtr": "\u22DA", "lesseqqgtr": "\u2A8B", "LessEqualGreater": "\u22DA", "LessFullEqual": "\u2266", "LessGreater": "\u2276", "lessgtr": "\u2276", "LessLess": "\u2AA1", "lesssim": "\u2272", "LessSlantEqual": "\u2A7D", "LessTilde": "\u2272", "lfisht": "\u297C", "lfloor": "\u230A", "Lfr": "\uD835\uDD0F", "lfr": "\uD835\uDD29", "lg": "\u2276", "lgE": "\u2A91", "lHar": "\u2962", "lhard": "\u21BD", "lharu": "\u21BC", "lharul": "\u296A", "lhblk": "\u2584", "LJcy": "\u0409", "ljcy": "\u0459", "llarr": "\u21C7", "ll": "\u226A", "Ll": "\u22D8", "llcorner": "\u231E", "Lleftarrow": "\u21DA", "llhard": "\u296B", "lltri": "\u25FA", "Lmidot": "\u013F", "lmidot": "\u0140", "lmoustache": "\u23B0", "lmoust": "\u23B0", "lnap": "\u2A89", "lnapprox": "\u2A89", "lne": "\u2A87", "lnE": "\u2268", "lneq": "\u2A87", "lneqq": "\u2268", "lnsim": "\u22E6", "loang": "\u27EC", "loarr": "\u21FD", "lobrk": "\u27E6", "longleftarrow": "\u27F5", "LongLeftArrow": "\u27F5", "Longleftarrow": "\u27F8", "longleftrightarrow": "\u27F7", "LongLeftRightArrow": "\u27F7", "Longleftrightarrow": "\u27FA", "longmapsto": "\u27FC", "longrightarrow": "\u27F6", "LongRightArrow": "\u27F6", "Longrightarrow": "\u27F9", "looparrowleft": "\u21AB", "looparrowright": "\u21AC", "lopar": "\u2985", "Lopf": "\uD835\uDD43", "lopf": "\uD835\uDD5D", "loplus": "\u2A2D", "lotimes": "\u2A34", "lowast": "\u2217", "lowbar": "_", "LowerLeftArrow": "\u2199", "LowerRightArrow": "\u2198", "loz": "\u25CA", "lozenge": "\u25CA", "lozf": "\u29EB", "lpar": "(", "lparlt": "\u2993", "lrarr": "\u21C6", "lrcorner": "\u231F", "lrhar": "\u21CB", "lrhard": "\u296D", "lrm": "\u200E", "lrtri": "\u22BF", "lsaquo": "\u2039", "lscr": "\uD835\uDCC1", "Lscr": "\u2112", "lsh": "\u21B0", "Lsh": "\u21B0", "lsim": "\u2272", "lsime": "\u2A8D", "lsimg": "\u2A8F", "lsqb": "[", "lsquo": "\u2018", "lsquor": "\u201A", "Lstrok": "\u0141", "lstrok": "\u0142", "ltcc": "\u2AA6", "ltcir": "\u2A79", "lt": "<", "LT": "<", "Lt": "\u226A", "ltdot": "\u22D6", "lthree": "\u22CB", "ltimes": "\u22C9", "ltlarr": "\u2976", "ltquest": "\u2A7B", "ltri": "\u25C3", "ltrie": "\u22B4", "ltrif": "\u25C2", "ltrPar": "\u2996", "lurdshar": "\u294A", "luruhar": "\u2966", "lvertneqq": "\u2268\uFE00", "lvnE": "\u2268\uFE00", "macr": "\u00AF", "male": "\u2642", "malt": "\u2720", "maltese": "\u2720", "Map": "\u2905", "map": "\u21A6", "mapsto": "\u21A6", "mapstodown": "\u21A7", "mapstoleft": "\u21A4", "mapstoup": "\u21A5", "marker": "\u25AE", "mcomma": "\u2A29", "Mcy": "\u041C", "mcy": "\u043C", "mdash": "\u2014", "mDDot": "\u223A", "measuredangle": "\u2221", "MediumSpace": "\u205F", "Mellintrf": "\u2133", "Mfr": "\uD835\uDD10", "mfr": "\uD835\uDD2A", "mho": "\u2127", "micro": "\u00B5", "midast": "*", "midcir": "\u2AF0", "mid": "\u2223", "middot": "\u00B7", "minusb": "\u229F", "minus": "\u2212", "minusd": "\u2238", "minusdu": "\u2A2A", "MinusPlus": "\u2213", "mlcp": "\u2ADB", "mldr": "\u2026", "mnplus": "\u2213", "models": "\u22A7", "Mopf": "\uD835\uDD44", "mopf": "\uD835\uDD5E", "mp": "\u2213", "mscr": "\uD835\uDCC2", "Mscr": "\u2133", "mstpos": "\u223E", "Mu": "\u039C", "mu": "\u03BC", "multimap": "\u22B8", "mumap": "\u22B8", "nabla": "\u2207", "Nacute": "\u0143", "nacute": "\u0144", "nang": "\u2220\u20D2", "nap": "\u2249", "napE": "\u2A70\u0338", "napid": "\u224B\u0338", "napos": "\u0149", "napprox": "\u2249", "natural": "\u266E", "naturals": "\u2115", "natur": "\u266E", "nbsp": "\u00A0", "nbump": "\u224E\u0338", "nbumpe": "\u224F\u0338", "ncap": "\u2A43", "Ncaron": "\u0147", "ncaron": "\u0148", "Ncedil": "\u0145", "ncedil": "\u0146", "ncong": "\u2247", "ncongdot": "\u2A6D\u0338", "ncup": "\u2A42", "Ncy": "\u041D", "ncy": "\u043D", "ndash": "\u2013", "nearhk": "\u2924", "nearr": "\u2197", "neArr": "\u21D7", "nearrow": "\u2197", "ne": "\u2260", "nedot": "\u2250\u0338", "NegativeMediumSpace": "\u200B", "NegativeThickSpace": "\u200B", "NegativeThinSpace": "\u200B", "NegativeVeryThinSpace": "\u200B", "nequiv": "\u2262", "nesear": "\u2928", "nesim": "\u2242\u0338", "NestedGreaterGreater": "\u226B", "NestedLessLess": "\u226A", "NewLine": "\n", "nexist": "\u2204", "nexists": "\u2204", "Nfr": "\uD835\uDD11", "nfr": "\uD835\uDD2B", "ngE": "\u2267\u0338", "nge": "\u2271", "ngeq": "\u2271", "ngeqq": "\u2267\u0338", "ngeqslant": "\u2A7E\u0338", "nges": "\u2A7E\u0338", "nGg": "\u22D9\u0338", "ngsim": "\u2275", "nGt": "\u226B\u20D2", "ngt": "\u226F", "ngtr": "\u226F", "nGtv": "\u226B\u0338", "nharr": "\u21AE", "nhArr": "\u21CE", "nhpar": "\u2AF2", "ni": "\u220B", "nis": "\u22FC", "nisd": "\u22FA", "niv": "\u220B", "NJcy": "\u040A", "njcy": "\u045A", "nlarr": "\u219A", "nlArr": "\u21CD", "nldr": "\u2025", "nlE": "\u2266\u0338", "nle": "\u2270", "nleftarrow": "\u219A", "nLeftarrow": "\u21CD", "nleftrightarrow": "\u21AE", "nLeftrightarrow": "\u21CE", "nleq": "\u2270", "nleqq": "\u2266\u0338", "nleqslant": "\u2A7D\u0338", "nles": "\u2A7D\u0338", "nless": "\u226E", "nLl": "\u22D8\u0338", "nlsim": "\u2274", "nLt": "\u226A\u20D2", "nlt": "\u226E", "nltri": "\u22EA", "nltrie": "\u22EC", "nLtv": "\u226A\u0338", "nmid": "\u2224", "NoBreak": "\u2060", "NonBreakingSpace": "\u00A0", "nopf": "\uD835\uDD5F", "Nopf": "\u2115", "Not": "\u2AEC", "not": "\u00AC", "NotCongruent": "\u2262", "NotCupCap": "\u226D", "NotDoubleVerticalBar": "\u2226", "NotElement": "\u2209", "NotEqual": "\u2260", "NotEqualTilde": "\u2242\u0338", "NotExists": "\u2204", "NotGreater": "\u226F", "NotGreaterEqual": "\u2271", "NotGreaterFullEqual": "\u2267\u0338", "NotGreaterGreater": "\u226B\u0338", "NotGreaterLess": "\u2279", "NotGreaterSlantEqual": "\u2A7E\u0338", "NotGreaterTilde": "\u2275", "NotHumpDownHump": "\u224E\u0338", "NotHumpEqual": "\u224F\u0338", "notin": "\u2209", "notindot": "\u22F5\u0338", "notinE": "\u22F9\u0338", "notinva": "\u2209", "notinvb": "\u22F7", "notinvc": "\u22F6", "NotLeftTriangleBar": "\u29CF\u0338", "NotLeftTriangle": "\u22EA", "NotLeftTriangleEqual": "\u22EC", "NotLess": "\u226E", "NotLessEqual": "\u2270", "NotLessGreater": "\u2278", "NotLessLess": "\u226A\u0338", "NotLessSlantEqual": "\u2A7D\u0338", "NotLessTilde": "\u2274", "NotNestedGreaterGreater": "\u2AA2\u0338", "NotNestedLessLess": "\u2AA1\u0338", "notni": "\u220C", "notniva": "\u220C", "notnivb": "\u22FE", "notnivc": "\u22FD", "NotPrecedes": "\u2280", "NotPrecedesEqual": "\u2AAF\u0338", "NotPrecedesSlantEqual": "\u22E0", "NotReverseElement": "\u220C", "NotRightTriangleBar": "\u29D0\u0338", "NotRightTriangle": "\u22EB", "NotRightTriangleEqual": "\u22ED", "NotSquareSubset": "\u228F\u0338", "NotSquareSubsetEqual": "\u22E2", "NotSquareSuperset": "\u2290\u0338", "NotSquareSupersetEqual": "\u22E3", "NotSubset": "\u2282\u20D2", "NotSubsetEqual": "\u2288", "NotSucceeds": "\u2281", "NotSucceedsEqual": "\u2AB0\u0338", "NotSucceedsSlantEqual": "\u22E1", "NotSucceedsTilde": "\u227F\u0338", "NotSuperset": "\u2283\u20D2", "NotSupersetEqual": "\u2289", "NotTilde": "\u2241", "NotTildeEqual": "\u2244", "NotTildeFullEqual": "\u2247", "NotTildeTilde": "\u2249", "NotVerticalBar": "\u2224", "nparallel": "\u2226", "npar": "\u2226", "nparsl": "\u2AFD\u20E5", "npart": "\u2202\u0338", "npolint": "\u2A14", "npr": "\u2280", "nprcue": "\u22E0", "nprec": "\u2280", "npreceq": "\u2AAF\u0338", "npre": "\u2AAF\u0338", "nrarrc": "\u2933\u0338", "nrarr": "\u219B", "nrArr": "\u21CF", "nrarrw": "\u219D\u0338", "nrightarrow": "\u219B", "nRightarrow": "\u21CF", "nrtri": "\u22EB", "nrtrie": "\u22ED", "nsc": "\u2281", "nsccue": "\u22E1", "nsce": "\u2AB0\u0338", "Nscr": "\uD835\uDCA9", "nscr": "\uD835\uDCC3", "nshortmid": "\u2224", "nshortparallel": "\u2226", "nsim": "\u2241", "nsime": "\u2244", "nsimeq": "\u2244", "nsmid": "\u2224", "nspar": "\u2226", "nsqsube": "\u22E2", "nsqsupe": "\u22E3", "nsub": "\u2284", "nsubE": "\u2AC5\u0338", "nsube": "\u2288", "nsubset": "\u2282\u20D2", "nsubseteq": "\u2288", "nsubseteqq": "\u2AC5\u0338", "nsucc": "\u2281", "nsucceq": "\u2AB0\u0338", "nsup": "\u2285", "nsupE": "\u2AC6\u0338", "nsupe": "\u2289", "nsupset": "\u2283\u20D2", "nsupseteq": "\u2289", "nsupseteqq": "\u2AC6\u0338", "ntgl": "\u2279", "Ntilde": "\u00D1", "ntilde": "\u00F1", "ntlg": "\u2278", "ntriangleleft": "\u22EA", "ntrianglelefteq": "\u22EC", "ntriangleright": "\u22EB", "ntrianglerighteq": "\u22ED", "Nu": "\u039D", "nu": "\u03BD", "num": "#", "numero": "\u2116", "numsp": "\u2007", "nvap": "\u224D\u20D2", "nvdash": "\u22AC", "nvDash": "\u22AD", "nVdash": "\u22AE", "nVDash": "\u22AF", "nvge": "\u2265\u20D2", "nvgt": ">\u20D2", "nvHarr": "\u2904", "nvinfin": "\u29DE", "nvlArr": "\u2902", "nvle": "\u2264\u20D2", "nvlt": "<\u20D2", "nvltrie": "\u22B4\u20D2", "nvrArr": "\u2903", "nvrtrie": "\u22B5\u20D2", "nvsim": "\u223C\u20D2", "nwarhk": "\u2923", "nwarr": "\u2196", "nwArr": "\u21D6", "nwarrow": "\u2196", "nwnear": "\u2927", "Oacute": "\u00D3", "oacute": "\u00F3", "oast": "\u229B", "Ocirc": "\u00D4", "ocirc": "\u00F4", "ocir": "\u229A", "Ocy": "\u041E", "ocy": "\u043E", "odash": "\u229D", "Odblac": "\u0150", "odblac": "\u0151", "odiv": "\u2A38", "odot": "\u2299", "odsold": "\u29BC", "OElig": "\u0152", "oelig": "\u0153", "ofcir": "\u29BF", "Ofr": "\uD835\uDD12", "ofr": "\uD835\uDD2C", "ogon": "\u02DB", "Ograve": "\u00D2", "ograve": "\u00F2", "ogt": "\u29C1", "ohbar": "\u29B5", "ohm": "\u03A9", "oint": "\u222E", "olarr": "\u21BA", "olcir": "\u29BE", "olcross": "\u29BB", "oline": "\u203E", "olt": "\u29C0", "Omacr": "\u014C", "omacr": "\u014D", "Omega": "\u03A9", "omega": "\u03C9", "Omicron": "\u039F", "omicron": "\u03BF", "omid": "\u29B6", "ominus": "\u2296", "Oopf": "\uD835\uDD46", "oopf": "\uD835\uDD60", "opar": "\u29B7", "OpenCurlyDoubleQuote": "\u201C", "OpenCurlyQuote": "\u2018", "operp": "\u29B9", "oplus": "\u2295", "orarr": "\u21BB", "Or": "\u2A54", "or": "\u2228", "ord": "\u2A5D", "order": "\u2134", "orderof": "\u2134", "ordf": "\u00AA", "ordm": "\u00BA", "origof": "\u22B6", "oror": "\u2A56", "orslope": "\u2A57", "orv": "\u2A5B", "oS": "\u24C8", "Oscr": "\uD835\uDCAA", "oscr": "\u2134", "Oslash": "\u00D8", "oslash": "\u00F8", "osol": "\u2298", "Otilde": "\u00D5", "otilde": "\u00F5", "otimesas": "\u2A36", "Otimes": "\u2A37", "otimes": "\u2297", "Ouml": "\u00D6", "ouml": "\u00F6", "ovbar": "\u233D", "OverBar": "\u203E", "OverBrace": "\u23DE", "OverBracket": "\u23B4", "OverParenthesis": "\u23DC", "para": "\u00B6", "parallel": "\u2225", "par": "\u2225", "parsim": "\u2AF3", "parsl": "\u2AFD", "part": "\u2202", "PartialD": "\u2202", "Pcy": "\u041F", "pcy": "\u043F", "percnt": "%", "period": ".", "permil": "\u2030", "perp": "\u22A5", "pertenk": "\u2031", "Pfr": "\uD835\uDD13", "pfr": "\uD835\uDD2D", "Phi": "\u03A6", "phi": "\u03C6", "phiv": "\u03D5", "phmmat": "\u2133", "phone": "\u260E", "Pi": "\u03A0", "pi": "\u03C0", "pitchfork": "\u22D4", "piv": "\u03D6", "planck": "\u210F", "planckh": "\u210E", "plankv": "\u210F", "plusacir": "\u2A23", "plusb": "\u229E", "pluscir": "\u2A22", "plus": "+", "plusdo": "\u2214", "plusdu": "\u2A25", "pluse": "\u2A72", "PlusMinus": "\u00B1", "plusmn": "\u00B1", "plussim": "\u2A26", "plustwo": "\u2A27", "pm": "\u00B1", "Poincareplane": "\u210C", "pointint": "\u2A15", "popf": "\uD835\uDD61", "Popf": "\u2119", "pound": "\u00A3", "prap": "\u2AB7", "Pr": "\u2ABB", "pr": "\u227A", "prcue": "\u227C", "precapprox": "\u2AB7", "prec": "\u227A", "preccurlyeq": "\u227C", "Precedes": "\u227A", "PrecedesEqual": "\u2AAF", "PrecedesSlantEqual": "\u227C", "PrecedesTilde": "\u227E", "preceq": "\u2AAF", "precnapprox": "\u2AB9", "precneqq": "\u2AB5", "precnsim": "\u22E8", "pre": "\u2AAF", "prE": "\u2AB3", "precsim": "\u227E", "prime": "\u2032", "Prime": "\u2033", "primes": "\u2119", "prnap": "\u2AB9", "prnE": "\u2AB5", "prnsim": "\u22E8", "prod": "\u220F", "Product": "\u220F", "profalar": "\u232E", "profline": "\u2312", "profsurf": "\u2313", "prop": "\u221D", "Proportional": "\u221D", "Proportion": "\u2237", "propto": "\u221D", "prsim": "\u227E", "prurel": "\u22B0", "Pscr": "\uD835\uDCAB", "pscr": "\uD835\uDCC5", "Psi": "\u03A8", "psi": "\u03C8", "puncsp": "\u2008", "Qfr": "\uD835\uDD14", "qfr": "\uD835\uDD2E", "qint": "\u2A0C", "qopf": "\uD835\uDD62", "Qopf": "\u211A", "qprime": "\u2057", "Qscr": "\uD835\uDCAC", "qscr": "\uD835\uDCC6", "quaternions": "\u210D", "quatint": "\u2A16", "quest": "?", "questeq": "\u225F", "quot": "\"", "QUOT": "\"", "rAarr": "\u21DB", "race": "\u223D\u0331", "Racute": "\u0154", "racute": "\u0155", "radic": "\u221A", "raemptyv": "\u29B3", "rang": "\u27E9", "Rang": "\u27EB", "rangd": "\u2992", "range": "\u29A5", "rangle": "\u27E9", "raquo": "\u00BB", "rarrap": "\u2975", "rarrb": "\u21E5", "rarrbfs": "\u2920", "rarrc": "\u2933", "rarr": "\u2192", "Rarr": "\u21A0", "rArr": "\u21D2", "rarrfs": "\u291E", "rarrhk": "\u21AA", "rarrlp": "\u21AC", "rarrpl": "\u2945", "rarrsim": "\u2974", "Rarrtl": "\u2916", "rarrtl": "\u21A3", "rarrw": "\u219D", "ratail": "\u291A", "rAtail": "\u291C", "ratio": "\u2236", "rationals": "\u211A", "rbarr": "\u290D", "rBarr": "\u290F", "RBarr": "\u2910", "rbbrk": "\u2773", "rbrace": "}", "rbrack": "]", "rbrke": "\u298C", "rbrksld": "\u298E", "rbrkslu": "\u2990", "Rcaron": "\u0158", "rcaron": "\u0159", "Rcedil": "\u0156", "rcedil": "\u0157", "rceil": "\u2309", "rcub": "}", "Rcy": "\u0420", "rcy": "\u0440", "rdca": "\u2937", "rdldhar": "\u2969", "rdquo": "\u201D", "rdquor": "\u201D", "rdsh": "\u21B3", "real": "\u211C", "realine": "\u211B", "realpart": "\u211C", "reals": "\u211D", "Re": "\u211C", "rect": "\u25AD", "reg": "\u00AE", "REG": "\u00AE", "ReverseElement": "\u220B", "ReverseEquilibrium": "\u21CB", "ReverseUpEquilibrium": "\u296F", "rfisht": "\u297D", "rfloor": "\u230B", "rfr": "\uD835\uDD2F", "Rfr": "\u211C", "rHar": "\u2964", "rhard": "\u21C1", "rharu": "\u21C0", "rharul": "\u296C", "Rho": "\u03A1", "rho": "\u03C1", "rhov": "\u03F1", "RightAngleBracket": "\u27E9", "RightArrowBar": "\u21E5", "rightarrow": "\u2192", "RightArrow": "\u2192", "Rightarrow": "\u21D2", "RightArrowLeftArrow": "\u21C4", "rightarrowtail": "\u21A3", "RightCeiling": "\u2309", "RightDoubleBracket": "\u27E7", "RightDownTeeVector": "\u295D", "RightDownVectorBar": "\u2955", "RightDownVector": "\u21C2", "RightFloor": "\u230B", "rightharpoondown": "\u21C1", "rightharpoonup": "\u21C0", "rightleftarrows": "\u21C4", "rightleftharpoons": "\u21CC", "rightrightarrows": "\u21C9", "rightsquigarrow": "\u219D", "RightTeeArrow": "\u21A6", "RightTee": "\u22A2", "RightTeeVector": "\u295B", "rightthreetimes": "\u22CC", "RightTriangleBar": "\u29D0", "RightTriangle": "\u22B3", "RightTriangleEqual": "\u22B5", "RightUpDownVector": "\u294F", "RightUpTeeVector": "\u295C", "RightUpVectorBar": "\u2954", "RightUpVector": "\u21BE", "RightVectorBar": "\u2953", "RightVector": "\u21C0", "ring": "\u02DA", "risingdotseq": "\u2253", "rlarr": "\u21C4", "rlhar": "\u21CC", "rlm": "\u200F", "rmoustache": "\u23B1", "rmoust": "\u23B1", "rnmid": "\u2AEE", "roang": "\u27ED", "roarr": "\u21FE", "robrk": "\u27E7", "ropar": "\u2986", "ropf": "\uD835\uDD63", "Ropf": "\u211D", "roplus": "\u2A2E", "rotimes": "\u2A35", "RoundImplies": "\u2970", "rpar": ")", "rpargt": "\u2994", "rppolint": "\u2A12", "rrarr": "\u21C9", "Rrightarrow": "\u21DB", "rsaquo": "\u203A", "rscr": "\uD835\uDCC7", "Rscr": "\u211B", "rsh": "\u21B1", "Rsh": "\u21B1", "rsqb": "]", "rsquo": "\u2019", "rsquor": "\u2019", "rthree": "\u22CC", "rtimes": "\u22CA", "rtri": "\u25B9", "rtrie": "\u22B5", "rtrif": "\u25B8", "rtriltri": "\u29CE", "RuleDelayed": "\u29F4", "ruluhar": "\u2968", "rx": "\u211E", "Sacute": "\u015A", "sacute": "\u015B", "sbquo": "\u201A", "scap": "\u2AB8", "Scaron": "\u0160", "scaron": "\u0161", "Sc": "\u2ABC", "sc": "\u227B", "sccue": "\u227D", "sce": "\u2AB0", "scE": "\u2AB4", "Scedil": "\u015E", "scedil": "\u015F", "Scirc": "\u015C", "scirc": "\u015D", "scnap": "\u2ABA", "scnE": "\u2AB6", "scnsim": "\u22E9", "scpolint": "\u2A13", "scsim": "\u227F", "Scy": "\u0421", "scy": "\u0441", "sdotb": "\u22A1", "sdot": "\u22C5", "sdote": "\u2A66", "searhk": "\u2925", "searr": "\u2198", "seArr": "\u21D8", "searrow": "\u2198", "sect": "\u00A7", "semi": ";", "seswar": "\u2929", "setminus": "\u2216", "setmn": "\u2216", "sext": "\u2736", "Sfr": "\uD835\uDD16", "sfr": "\uD835\uDD30", "sfrown": "\u2322", "sharp": "\u266F", "SHCHcy": "\u0429", "shchcy": "\u0449", "SHcy": "\u0428", "shcy": "\u0448", "ShortDownArrow": "\u2193", "ShortLeftArrow": "\u2190", "shortmid": "\u2223", "shortparallel": "\u2225", "ShortRightArrow": "\u2192", "ShortUpArrow": "\u2191", "shy": "\u00AD", "Sigma": "\u03A3", "sigma": "\u03C3", "sigmaf": "\u03C2", "sigmav": "\u03C2", "sim": "\u223C", "simdot": "\u2A6A", "sime": "\u2243", "simeq": "\u2243", "simg": "\u2A9E", "simgE": "\u2AA0", "siml": "\u2A9D", "simlE": "\u2A9F", "simne": "\u2246", "simplus": "\u2A24", "simrarr": "\u2972", "slarr": "\u2190", "SmallCircle": "\u2218", "smallsetminus": "\u2216", "smashp": "\u2A33", "smeparsl": "\u29E4", "smid": "\u2223", "smile": "\u2323", "smt": "\u2AAA", "smte": "\u2AAC", "smtes": "\u2AAC\uFE00", "SOFTcy": "\u042C", "softcy": "\u044C", "solbar": "\u233F", "solb": "\u29C4", "sol": "/", "Sopf": "\uD835\uDD4A", "sopf": "\uD835\uDD64", "spades": "\u2660", "spadesuit": "\u2660", "spar": "\u2225", "sqcap": "\u2293", "sqcaps": "\u2293\uFE00", "sqcup": "\u2294", "sqcups": "\u2294\uFE00", "Sqrt": "\u221A", "sqsub": "\u228F", "sqsube": "\u2291", "sqsubset": "\u228F", "sqsubseteq": "\u2291", "sqsup": "\u2290", "sqsupe": "\u2292", "sqsupset": "\u2290", "sqsupseteq": "\u2292", "square": "\u25A1", "Square": "\u25A1", "SquareIntersection": "\u2293", "SquareSubset": "\u228F", "SquareSubsetEqual": "\u2291", "SquareSuperset": "\u2290", "SquareSupersetEqual": "\u2292", "SquareUnion": "\u2294", "squarf": "\u25AA", "squ": "\u25A1", "squf": "\u25AA", "srarr": "\u2192", "Sscr": "\uD835\uDCAE", "sscr": "\uD835\uDCC8", "ssetmn": "\u2216", "ssmile": "\u2323", "sstarf": "\u22C6", "Star": "\u22C6", "star": "\u2606", "starf": "\u2605", "straightepsilon": "\u03F5", "straightphi": "\u03D5", "strns": "\u00AF", "sub": "\u2282", "Sub": "\u22D0", "subdot": "\u2ABD", "subE": "\u2AC5", "sube": "\u2286", "subedot": "\u2AC3", "submult": "\u2AC1", "subnE": "\u2ACB", "subne": "\u228A", "subplus": "\u2ABF", "subrarr": "\u2979", "subset": "\u2282", "Subset": "\u22D0", "subseteq": "\u2286", "subseteqq": "\u2AC5", "SubsetEqual": "\u2286", "subsetneq": "\u228A", "subsetneqq": "\u2ACB", "subsim": "\u2AC7", "subsub": "\u2AD5", "subsup": "\u2AD3", "succapprox": "\u2AB8", "succ": "\u227B", "succcurlyeq": "\u227D", "Succeeds": "\u227B", "SucceedsEqual": "\u2AB0", "SucceedsSlantEqual": "\u227D", "SucceedsTilde": "\u227F", "succeq": "\u2AB0", "succnapprox": "\u2ABA", "succneqq": "\u2AB6", "succnsim": "\u22E9", "succsim": "\u227F", "SuchThat": "\u220B", "sum": "\u2211", "Sum": "\u2211", "sung": "\u266A", "sup1": "\u00B9", "sup2": "\u00B2", "sup3": "\u00B3", "sup": "\u2283", "Sup": "\u22D1", "supdot": "\u2ABE", "supdsub": "\u2AD8", "supE": "\u2AC6", "supe": "\u2287", "supedot": "\u2AC4", "Superset": "\u2283", "SupersetEqual": "\u2287", "suphsol": "\u27C9", "suphsub": "\u2AD7", "suplarr": "\u297B", "supmult": "\u2AC2", "supnE": "\u2ACC", "supne": "\u228B", "supplus": "\u2AC0", "supset": "\u2283", "Supset": "\u22D1", "supseteq": "\u2287", "supseteqq": "\u2AC6", "supsetneq": "\u228B", "supsetneqq": "\u2ACC", "supsim": "\u2AC8", "supsub": "\u2AD4", "supsup": "\u2AD6", "swarhk": "\u2926", "swarr": "\u2199", "swArr": "\u21D9", "swarrow": "\u2199", "swnwar": "\u292A", "szlig": "\u00DF", "Tab": "\t", "target": "\u2316", "Tau": "\u03A4", "tau": "\u03C4", "tbrk": "\u23B4", "Tcaron": "\u0164", "tcaron": "\u0165", "Tcedil": "\u0162", "tcedil": "\u0163", "Tcy": "\u0422", "tcy": "\u0442", "tdot": "\u20DB", "telrec": "\u2315", "Tfr": "\uD835\uDD17", "tfr": "\uD835\uDD31", "there4": "\u2234", "therefore": "\u2234", "Therefore": "\u2234", "Theta": "\u0398", "theta": "\u03B8", "thetasym": "\u03D1", "thetav": "\u03D1", "thickapprox": "\u2248", "thicksim": "\u223C", "ThickSpace": "\u205F\u200A", "ThinSpace": "\u2009", "thinsp": "\u2009", "thkap": "\u2248", "thksim": "\u223C", "THORN": "\u00DE", "thorn": "\u00FE", "tilde": "\u02DC", "Tilde": "\u223C", "TildeEqual": "\u2243", "TildeFullEqual": "\u2245", "TildeTilde": "\u2248", "timesbar": "\u2A31", "timesb": "\u22A0", "times": "\u00D7", "timesd": "\u2A30", "tint": "\u222D", "toea": "\u2928", "topbot": "\u2336", "topcir": "\u2AF1", "top": "\u22A4", "Topf": "\uD835\uDD4B", "topf": "\uD835\uDD65", "topfork": "\u2ADA", "tosa": "\u2929", "tprime": "\u2034", "trade": "\u2122", "TRADE": "\u2122", "triangle": "\u25B5", "triangledown": "\u25BF", "triangleleft": "\u25C3", "trianglelefteq": "\u22B4", "triangleq": "\u225C", "triangleright": "\u25B9", "trianglerighteq": "\u22B5", "tridot": "\u25EC", "trie": "\u225C", "triminus": "\u2A3A", "TripleDot": "\u20DB", "triplus": "\u2A39", "trisb": "\u29CD", "tritime": "\u2A3B", "trpezium": "\u23E2", "Tscr": "\uD835\uDCAF", "tscr": "\uD835\uDCC9", "TScy": "\u0426", "tscy": "\u0446", "TSHcy": "\u040B", "tshcy": "\u045B", "Tstrok": "\u0166", "tstrok": "\u0167", "twixt": "\u226C", "twoheadleftarrow": "\u219E", "twoheadrightarrow": "\u21A0", "Uacute": "\u00DA", "uacute": "\u00FA", "uarr": "\u2191", "Uarr": "\u219F", "uArr": "\u21D1", "Uarrocir": "\u2949", "Ubrcy": "\u040E", "ubrcy": "\u045E", "Ubreve": "\u016C", "ubreve": "\u016D", "Ucirc": "\u00DB", "ucirc": "\u00FB", "Ucy": "\u0423", "ucy": "\u0443", "udarr": "\u21C5", "Udblac": "\u0170", "udblac": "\u0171", "udhar": "\u296E", "ufisht": "\u297E", "Ufr": "\uD835\uDD18", "ufr": "\uD835\uDD32", "Ugrave": "\u00D9", "ugrave": "\u00F9", "uHar": "\u2963", "uharl": "\u21BF", "uharr": "\u21BE", "uhblk": "\u2580", "ulcorn": "\u231C", "ulcorner": "\u231C", "ulcrop": "\u230F", "ultri": "\u25F8", "Umacr": "\u016A", "umacr": "\u016B", "uml": "\u00A8", "UnderBar": "_", "UnderBrace": "\u23DF", "UnderBracket": "\u23B5", "UnderParenthesis": "\u23DD", "Union": "\u22C3", "UnionPlus": "\u228E", "Uogon": "\u0172", "uogon": "\u0173", "Uopf": "\uD835\uDD4C", "uopf": "\uD835\uDD66", "UpArrowBar": "\u2912", "uparrow": "\u2191", "UpArrow": "\u2191", "Uparrow": "\u21D1", "UpArrowDownArrow": "\u21C5", "updownarrow": "\u2195", "UpDownArrow": "\u2195", "Updownarrow": "\u21D5", "UpEquilibrium": "\u296E", "upharpoonleft": "\u21BF", "upharpoonright": "\u21BE", "uplus": "\u228E", "UpperLeftArrow": "\u2196", "UpperRightArrow": "\u2197", "upsi": "\u03C5", "Upsi": "\u03D2", "upsih": "\u03D2", "Upsilon": "\u03A5", "upsilon": "\u03C5", "UpTeeArrow": "\u21A5", "UpTee": "\u22A5", "upuparrows": "\u21C8", "urcorn": "\u231D", "urcorner": "\u231D", "urcrop": "\u230E", "Uring": "\u016E", "uring": "\u016F", "urtri": "\u25F9", "Uscr": "\uD835\uDCB0", "uscr": "\uD835\uDCCA", "utdot": "\u22F0", "Utilde": "\u0168", "utilde": "\u0169", "utri": "\u25B5", "utrif": "\u25B4", "uuarr": "\u21C8", "Uuml": "\u00DC", "uuml": "\u00FC", "uwangle": "\u29A7", "vangrt": "\u299C", "varepsilon": "\u03F5", "varkappa": "\u03F0", "varnothing": "\u2205", "varphi": "\u03D5", "varpi": "\u03D6", "varpropto": "\u221D", "varr": "\u2195", "vArr": "\u21D5", "varrho": "\u03F1", "varsigma": "\u03C2", "varsubsetneq": "\u228A\uFE00", "varsubsetneqq": "\u2ACB\uFE00", "varsupsetneq": "\u228B\uFE00", "varsupsetneqq": "\u2ACC\uFE00", "vartheta": "\u03D1", "vartriangleleft": "\u22B2", "vartriangleright": "\u22B3", "vBar": "\u2AE8", "Vbar": "\u2AEB", "vBarv": "\u2AE9", "Vcy": "\u0412", "vcy": "\u0432", "vdash": "\u22A2", "vDash": "\u22A8", "Vdash": "\u22A9", "VDash": "\u22AB", "Vdashl": "\u2AE6", "veebar": "\u22BB", "vee": "\u2228", "Vee": "\u22C1", "veeeq": "\u225A", "vellip": "\u22EE", "verbar": "|", "Verbar": "\u2016", "vert": "|", "Vert": "\u2016", "VerticalBar": "\u2223", "VerticalLine": "|", "VerticalSeparator": "\u2758", "VerticalTilde": "\u2240", "VeryThinSpace": "\u200A", "Vfr": "\uD835\uDD19", "vfr": "\uD835\uDD33", "vltri": "\u22B2", "vnsub": "\u2282\u20D2", "vnsup": "\u2283\u20D2", "Vopf": "\uD835\uDD4D", "vopf": "\uD835\uDD67", "vprop": "\u221D", "vrtri": "\u22B3", "Vscr": "\uD835\uDCB1", "vscr": "\uD835\uDCCB", "vsubnE": "\u2ACB\uFE00", "vsubne": "\u228A\uFE00", "vsupnE": "\u2ACC\uFE00", "vsupne": "\u228B\uFE00", "Vvdash": "\u22AA", "vzigzag": "\u299A", "Wcirc": "\u0174", "wcirc": "\u0175", "wedbar": "\u2A5F", "wedge": "\u2227", "Wedge": "\u22C0", "wedgeq": "\u2259", "weierp": "\u2118", "Wfr": "\uD835\uDD1A", "wfr": "\uD835\uDD34", "Wopf": "\uD835\uDD4E", "wopf": "\uD835\uDD68", "wp": "\u2118", "wr": "\u2240", "wreath": "\u2240", "Wscr": "\uD835\uDCB2", "wscr": "\uD835\uDCCC", "xcap": "\u22C2", "xcirc": "\u25EF", "xcup": "\u22C3", "xdtri": "\u25BD", "Xfr": "\uD835\uDD1B", "xfr": "\uD835\uDD35", "xharr": "\u27F7", "xhArr": "\u27FA", "Xi": "\u039E", "xi": "\u03BE", "xlarr": "\u27F5", "xlArr": "\u27F8", "xmap": "\u27FC", "xnis": "\u22FB", "xodot": "\u2A00", "Xopf": "\uD835\uDD4F", "xopf": "\uD835\uDD69", "xoplus": "\u2A01", "xotime": "\u2A02", "xrarr": "\u27F6", "xrArr": "\u27F9", "Xscr": "\uD835\uDCB3", "xscr": "\uD835\uDCCD", "xsqcup": "\u2A06", "xuplus": "\u2A04", "xutri": "\u25B3", "xvee": "\u22C1", "xwedge": "\u22C0", "Yacute": "\u00DD", "yacute": "\u00FD", "YAcy": "\u042F", "yacy": "\u044F", "Ycirc": "\u0176", "ycirc": "\u0177", "Ycy": "\u042B", "ycy": "\u044B", "yen": "\u00A5", "Yfr": "\uD835\uDD1C", "yfr": "\uD835\uDD36", "YIcy": "\u0407", "yicy": "\u0457", "Yopf": "\uD835\uDD50", "yopf": "\uD835\uDD6A", "Yscr": "\uD835\uDCB4", "yscr": "\uD835\uDCCE", "YUcy": "\u042E", "yucy": "\u044E", "yuml": "\u00FF", "Yuml": "\u0178", "Zacute": "\u0179", "zacute": "\u017A", "Zcaron": "\u017D", "zcaron": "\u017E", "Zcy": "\u0417", "zcy": "\u0437", "Zdot": "\u017B", "zdot": "\u017C", "zeetrf": "\u2128", "ZeroWidthSpace": "\u200B", "Zeta": "\u0396", "zeta": "\u03B6", "zfr": "\uD835\uDD37", "Zfr": "\u2128", "ZHcy": "\u0416", "zhcy": "\u0436", "zigrarr": "\u21DD", "zopf": "\uD835\uDD6B", "Zopf": "\u2124", "Zscr": "\uD835\uDCB5", "zscr": "\uD835\uDCCF", "zwj": "\u200D", "zwnj": "\u200C" } + +},{}],53:[function(require,module,exports){ +'use strict'; + + +//////////////////////////////////////////////////////////////////////////////// +// Helpers + +// Merge objects +// +function assign(obj /*from1, from2, from3, ...*/) { + var sources = Array.prototype.slice.call(arguments, 1); + + sources.forEach(function (source) { + if (!source) { return; } + + Object.keys(source).forEach(function (key) { + obj[key] = source[key]; + }); + }); + + return obj; +} + +function _class(obj) { return Object.prototype.toString.call(obj); } +function isString(obj) { return _class(obj) === '[object String]'; } +function isObject(obj) { return _class(obj) === '[object Object]'; } +function isRegExp(obj) { return _class(obj) === '[object RegExp]'; } +function isFunction(obj) { return _class(obj) === '[object Function]'; } + + +function escapeRE(str) { return str.replace(/[.?*+^$[\]\\(){}|-]/g, '\\$&'); } + +//////////////////////////////////////////////////////////////////////////////// + + +var defaultOptions = { + fuzzyLink: true, + fuzzyEmail: true, + fuzzyIP: false +}; + + +function isOptionsObj(obj) { + return Object.keys(obj || {}).reduce(function (acc, k) { + return acc || defaultOptions.hasOwnProperty(k); + }, false); +} + + +var defaultSchemas = { + 'http:': { + validate: function (text, pos, self) { + var tail = text.slice(pos); + + if (!self.re.http) { + // compile lazily, because "host"-containing variables can change on tlds update. + self.re.http = new RegExp( + '^\\/\\/' + self.re.src_auth + self.re.src_host_port_strict + self.re.src_path, 'i' + ); + } + if (self.re.http.test(tail)) { + return tail.match(self.re.http)[0].length; + } + return 0; + } + }, + 'https:': 'http:', + 'ftp:': 'http:', + '//': { + validate: function (text, pos, self) { + var tail = text.slice(pos); + + if (!self.re.no_http) { + // compile lazily, because "host"-containing variables can change on tlds update. + self.re.no_http = new RegExp( + '^' + + self.re.src_auth + + // Don't allow single-level domains, because of false positives like '//test' + // with code comments + '(?:localhost|(?:(?:' + self.re.src_domain + ')\\.)+' + self.re.src_domain_root + ')' + + self.re.src_port + + self.re.src_host_terminator + + self.re.src_path, + + 'i' + ); + } + + if (self.re.no_http.test(tail)) { + // should not be `://` & `///`, that protects from errors in protocol name + if (pos >= 3 && text[pos - 3] === ':') { return 0; } + if (pos >= 3 && text[pos - 3] === '/') { return 0; } + return tail.match(self.re.no_http)[0].length; + } + return 0; + } + }, + 'mailto:': { + validate: function (text, pos, self) { + var tail = text.slice(pos); + + if (!self.re.mailto) { + self.re.mailto = new RegExp( + '^' + self.re.src_email_name + '@' + self.re.src_host_strict, 'i' + ); + } + if (self.re.mailto.test(tail)) { + return tail.match(self.re.mailto)[0].length; + } + return 0; + } + } +}; + +/*eslint-disable max-len*/ + +// RE pattern for 2-character tlds (autogenerated by ./support/tlds_2char_gen.js) +var tlds_2ch_src_re = 'a[cdefgilmnoqrstuwxz]|b[abdefghijmnorstvwyz]|c[acdfghiklmnoruvwxyz]|d[ejkmoz]|e[cegrstu]|f[ijkmor]|g[abdefghilmnpqrstuwy]|h[kmnrtu]|i[delmnoqrst]|j[emop]|k[eghimnprwyz]|l[abcikrstuvy]|m[acdeghklmnopqrstuvwxyz]|n[acefgilopruz]|om|p[aefghklmnrstwy]|qa|r[eosuw]|s[abcdeghijklmnortuvxyz]|t[cdfghjklmnortvwz]|u[agksyz]|v[aceginu]|w[fs]|y[et]|z[amw]'; + +// DON'T try to make PRs with changes. Extend TLDs with LinkifyIt.tlds() instead +var tlds_default = 'biz|com|edu|gov|net|org|pro|web|xxx|aero|asia|coop|info|museum|name|shop|рф'.split('|'); + +/*eslint-enable max-len*/ + +//////////////////////////////////////////////////////////////////////////////// + +function resetScanCache(self) { + self.__index__ = -1; + self.__text_cache__ = ''; +} + +function createValidator(re) { + return function (text, pos) { + var tail = text.slice(pos); + + if (re.test(tail)) { + return tail.match(re)[0].length; + } + return 0; + }; +} + +function createNormalizer() { + return function (match, self) { + self.normalize(match); + }; +} + +// Schemas compiler. Build regexps. +// +function compile(self) { + + // Load & clone RE patterns. + var re = self.re = require('./lib/re')(self.__opts__); + + // Define dynamic patterns + var tlds = self.__tlds__.slice(); + + self.onCompile(); + + if (!self.__tlds_replaced__) { + tlds.push(tlds_2ch_src_re); + } + tlds.push(re.src_xn); + + re.src_tlds = tlds.join('|'); + + function untpl(tpl) { return tpl.replace('%TLDS%', re.src_tlds); } + + re.email_fuzzy = RegExp(untpl(re.tpl_email_fuzzy), 'i'); + re.link_fuzzy = RegExp(untpl(re.tpl_link_fuzzy), 'i'); + re.link_no_ip_fuzzy = RegExp(untpl(re.tpl_link_no_ip_fuzzy), 'i'); + re.host_fuzzy_test = RegExp(untpl(re.tpl_host_fuzzy_test), 'i'); + + // + // Compile each schema + // + + var aliases = []; + + self.__compiled__ = {}; // Reset compiled data + + function schemaError(name, val) { + throw new Error('(LinkifyIt) Invalid schema "' + name + '": ' + val); + } + + Object.keys(self.__schemas__).forEach(function (name) { + var val = self.__schemas__[name]; + + // skip disabled methods + if (val === null) { return; } + + var compiled = { validate: null, link: null }; + + self.__compiled__[name] = compiled; + + if (isObject(val)) { + if (isRegExp(val.validate)) { + compiled.validate = createValidator(val.validate); + } else if (isFunction(val.validate)) { + compiled.validate = val.validate; + } else { + schemaError(name, val); + } + + if (isFunction(val.normalize)) { + compiled.normalize = val.normalize; + } else if (!val.normalize) { + compiled.normalize = createNormalizer(); + } else { + schemaError(name, val); + } + + return; + } + + if (isString(val)) { + aliases.push(name); + return; + } + + schemaError(name, val); + }); + + // + // Compile postponed aliases + // + + aliases.forEach(function (alias) { + if (!self.__compiled__[self.__schemas__[alias]]) { + // Silently fail on missed schemas to avoid errons on disable. + // schemaError(alias, self.__schemas__[alias]); + return; + } + + self.__compiled__[alias].validate = + self.__compiled__[self.__schemas__[alias]].validate; + self.__compiled__[alias].normalize = + self.__compiled__[self.__schemas__[alias]].normalize; + }); + + // + // Fake record for guessed links + // + self.__compiled__[''] = { validate: null, normalize: createNormalizer() }; + + // + // Build schema condition + // + var slist = Object.keys(self.__compiled__) + .filter(function (name) { + // Filter disabled & fake schemas + return name.length > 0 && self.__compiled__[name]; + }) + .map(escapeRE) + .join('|'); + // (?!_) cause 1.5x slowdown + self.re.schema_test = RegExp('(^|(?!_)(?:[><\uff5c]|' + re.src_ZPCc + '))(' + slist + ')', 'i'); + self.re.schema_search = RegExp('(^|(?!_)(?:[><\uff5c]|' + re.src_ZPCc + '))(' + slist + ')', 'ig'); + + self.re.pretest = RegExp( + '(' + self.re.schema_test.source + ')|(' + self.re.host_fuzzy_test.source + ')|@', + 'i' + ); + + // + // Cleanup + // + + resetScanCache(self); +} + +/** + * class Match + * + * Match result. Single element of array, returned by [[LinkifyIt#match]] + **/ +function Match(self, shift) { + var start = self.__index__, + end = self.__last_index__, + text = self.__text_cache__.slice(start, end); + + /** + * Match#schema -> String + * + * Prefix (protocol) for matched string. + **/ + this.schema = self.__schema__.toLowerCase(); + /** + * Match#index -> Number + * + * First position of matched string. + **/ + this.index = start + shift; + /** + * Match#lastIndex -> Number + * + * Next position after matched string. + **/ + this.lastIndex = end + shift; + /** + * Match#raw -> String + * + * Matched string. + **/ + this.raw = text; + /** + * Match#text -> String + * + * Notmalized text of matched string. + **/ + this.text = text; + /** + * Match#url -> String + * + * Normalized url of matched string. + **/ + this.url = text; +} + +function createMatch(self, shift) { + var match = new Match(self, shift); + + self.__compiled__[match.schema].normalize(match, self); + + return match; +} + + +/** + * class LinkifyIt + **/ + +/** + * new LinkifyIt(schemas, options) + * - schemas (Object): Optional. Additional schemas to validate (prefix/validator) + * - options (Object): { fuzzyLink|fuzzyEmail|fuzzyIP: true|false } + * + * Creates new linkifier instance with optional additional schemas. + * Can be called without `new` keyword for convenience. + * + * By default understands: + * + * - `http(s)://...` , `ftp://...`, `mailto:...` & `//...` links + * - "fuzzy" links and emails (example.com, foo@bar.com). + * + * `schemas` is an object, where each key/value describes protocol/rule: + * + * - __key__ - link prefix (usually, protocol name with `:` at the end, `skype:` + * for example). `linkify-it` makes shure that prefix is not preceeded with + * alphanumeric char and symbols. Only whitespaces and punctuation allowed. + * - __value__ - rule to check tail after link prefix + * - _String_ - just alias to existing rule + * - _Object_ + * - _validate_ - validator function (should return matched length on success), + * or `RegExp`. + * - _normalize_ - optional function to normalize text & url of matched result + * (for example, for @twitter mentions). + * + * `options`: + * + * - __fuzzyLink__ - recognige URL-s without `http(s):` prefix. Default `true`. + * - __fuzzyIP__ - allow IPs in fuzzy links above. Can conflict with some texts + * like version numbers. Default `false`. + * - __fuzzyEmail__ - recognize emails without `mailto:` prefix. + * + **/ +function LinkifyIt(schemas, options) { + if (!(this instanceof LinkifyIt)) { + return new LinkifyIt(schemas, options); + } + + if (!options) { + if (isOptionsObj(schemas)) { + options = schemas; + schemas = {}; + } + } + + this.__opts__ = assign({}, defaultOptions, options); + + // Cache last tested result. Used to skip repeating steps on next `match` call. + this.__index__ = -1; + this.__last_index__ = -1; // Next scan position + this.__schema__ = ''; + this.__text_cache__ = ''; + + this.__schemas__ = assign({}, defaultSchemas, schemas); + this.__compiled__ = {}; + + this.__tlds__ = tlds_default; + this.__tlds_replaced__ = false; + + this.re = {}; + + compile(this); +} + + +/** chainable + * LinkifyIt#add(schema, definition) + * - schema (String): rule name (fixed pattern prefix) + * - definition (String|RegExp|Object): schema definition + * + * Add new rule definition. See constructor description for details. + **/ +LinkifyIt.prototype.add = function add(schema, definition) { + this.__schemas__[schema] = definition; + compile(this); + return this; +}; + + +/** chainable + * LinkifyIt#set(options) + * - options (Object): { fuzzyLink|fuzzyEmail|fuzzyIP: true|false } + * + * Set recognition options for links without schema. + **/ +LinkifyIt.prototype.set = function set(options) { + this.__opts__ = assign(this.__opts__, options); + return this; +}; + + +/** + * LinkifyIt#test(text) -> Boolean + * + * Searches linkifiable pattern and returns `true` on success or `false` on fail. + **/ +LinkifyIt.prototype.test = function test(text) { + // Reset scan cache + this.__text_cache__ = text; + this.__index__ = -1; + + if (!text.length) { return false; } + + var m, ml, me, len, shift, next, re, tld_pos, at_pos; + + // try to scan for link with schema - that's the most simple rule + if (this.re.schema_test.test(text)) { + re = this.re.schema_search; + re.lastIndex = 0; + while ((m = re.exec(text)) !== null) { + len = this.testSchemaAt(text, m[2], re.lastIndex); + if (len) { + this.__schema__ = m[2]; + this.__index__ = m.index + m[1].length; + this.__last_index__ = m.index + m[0].length + len; + break; + } + } + } + + if (this.__opts__.fuzzyLink && this.__compiled__['http:']) { + // guess schemaless links + tld_pos = text.search(this.re.host_fuzzy_test); + if (tld_pos >= 0) { + // if tld is located after found link - no need to check fuzzy pattern + if (this.__index__ < 0 || tld_pos < this.__index__) { + if ((ml = text.match(this.__opts__.fuzzyIP ? this.re.link_fuzzy : this.re.link_no_ip_fuzzy)) !== null) { + + shift = ml.index + ml[1].length; + + if (this.__index__ < 0 || shift < this.__index__) { + this.__schema__ = ''; + this.__index__ = shift; + this.__last_index__ = ml.index + ml[0].length; + } + } + } + } + } + + if (this.__opts__.fuzzyEmail && this.__compiled__['mailto:']) { + // guess schemaless emails + at_pos = text.indexOf('@'); + if (at_pos >= 0) { + // We can't skip this check, because this cases are possible: + // 192.168.1.1@gmail.com, my.in@example.com + if ((me = text.match(this.re.email_fuzzy)) !== null) { + + shift = me.index + me[1].length; + next = me.index + me[0].length; + + if (this.__index__ < 0 || shift < this.__index__ || + (shift === this.__index__ && next > this.__last_index__)) { + this.__schema__ = 'mailto:'; + this.__index__ = shift; + this.__last_index__ = next; + } + } + } + } + + return this.__index__ >= 0; +}; + + +/** + * LinkifyIt#pretest(text) -> Boolean + * + * Very quick check, that can give false positives. Returns true if link MAY BE + * can exists. Can be used for speed optimization, when you need to check that + * link NOT exists. + **/ +LinkifyIt.prototype.pretest = function pretest(text) { + return this.re.pretest.test(text); +}; + + +/** + * LinkifyIt#testSchemaAt(text, name, position) -> Number + * - text (String): text to scan + * - name (String): rule (schema) name + * - position (Number): text offset to check from + * + * Similar to [[LinkifyIt#test]] but checks only specific protocol tail exactly + * at given position. Returns length of found pattern (0 on fail). + **/ +LinkifyIt.prototype.testSchemaAt = function testSchemaAt(text, schema, pos) { + // If not supported schema check requested - terminate + if (!this.__compiled__[schema.toLowerCase()]) { + return 0; + } + return this.__compiled__[schema.toLowerCase()].validate(text, pos, this); +}; + + +/** + * LinkifyIt#match(text) -> Array|null + * + * Returns array of found link descriptions or `null` on fail. We strongly + * recommend to use [[LinkifyIt#test]] first, for best speed. + * + * ##### Result match description + * + * - __schema__ - link schema, can be empty for fuzzy links, or `//` for + * protocol-neutral links. + * - __index__ - offset of matched text + * - __lastIndex__ - index of next char after mathch end + * - __raw__ - matched text + * - __text__ - normalized text + * - __url__ - link, generated from matched text + **/ +LinkifyIt.prototype.match = function match(text) { + var shift = 0, result = []; + + // Try to take previous element from cache, if .test() called before + if (this.__index__ >= 0 && this.__text_cache__ === text) { + result.push(createMatch(this, shift)); + shift = this.__last_index__; + } + + // Cut head if cache was used + var tail = shift ? text.slice(shift) : text; + + // Scan string until end reached + while (this.test(tail)) { + result.push(createMatch(this, shift)); + + tail = tail.slice(this.__last_index__); + shift += this.__last_index__; + } + + if (result.length) { + return result; + } + + return null; +}; + + +/** chainable + * LinkifyIt#tlds(list [, keepOld]) -> this + * - list (Array): list of tlds + * - keepOld (Boolean): merge with current list if `true` (`false` by default) + * + * Load (or merge) new tlds list. Those are user for fuzzy links (without prefix) + * to avoid false positives. By default this algorythm used: + * + * - hostname with any 2-letter root zones are ok. + * - biz|com|edu|gov|net|org|pro|web|xxx|aero|asia|coop|info|museum|name|shop|рф + * are ok. + * - encoded (`xn--...`) root zones are ok. + * + * If list is replaced, then exact match for 2-chars root zones will be checked. + **/ +LinkifyIt.prototype.tlds = function tlds(list, keepOld) { + list = Array.isArray(list) ? list : [ list ]; + + if (!keepOld) { + this.__tlds__ = list.slice(); + this.__tlds_replaced__ = true; + compile(this); + return this; + } + + this.__tlds__ = this.__tlds__.concat(list) + .sort() + .filter(function (el, idx, arr) { + return el !== arr[idx - 1]; + }) + .reverse(); + + compile(this); + return this; +}; + +/** + * LinkifyIt#normalize(match) + * + * Default normalizer (if schema does not define it's own). + **/ +LinkifyIt.prototype.normalize = function normalize(match) { + + // Do minimal possible changes by default. Need to collect feedback prior + // to move forward https://github.com/markdown-it/linkify-it/issues/1 + + if (!match.schema) { match.url = 'http://' + match.url; } + + if (match.schema === 'mailto:' && !/^mailto:/i.test(match.url)) { + match.url = 'mailto:' + match.url; + } +}; + + +/** + * LinkifyIt#onCompile() + * + * Override to modify basic RegExp-s. + **/ +LinkifyIt.prototype.onCompile = function onCompile() { +}; + + +module.exports = LinkifyIt; + +},{"./lib/re":54}],54:[function(require,module,exports){ +'use strict'; + + +module.exports = function (opts) { + var re = {}; + + // Use direct extract instead of `regenerate` to reduse browserified size + re.src_Any = require('uc.micro/properties/Any/regex').source; + re.src_Cc = require('uc.micro/categories/Cc/regex').source; + re.src_Z = require('uc.micro/categories/Z/regex').source; + re.src_P = require('uc.micro/categories/P/regex').source; + + // \p{\Z\P\Cc\CF} (white spaces + control + format + punctuation) + re.src_ZPCc = [ re.src_Z, re.src_P, re.src_Cc ].join('|'); + + // \p{\Z\Cc} (white spaces + control) + re.src_ZCc = [ re.src_Z, re.src_Cc ].join('|'); + + // Experimental. List of chars, completely prohibited in links + // because can separate it from other part of text + var text_separators = '[><\uff5c]'; + + // All possible word characters (everything without punctuation, spaces & controls) + // Defined via punctuation & spaces to save space + // Should be something like \p{\L\N\S\M} (\w but without `_`) + re.src_pseudo_letter = '(?:(?!' + text_separators + '|' + re.src_ZPCc + ')' + re.src_Any + ')'; + // The same as abothe but without [0-9] + // var src_pseudo_letter_non_d = '(?:(?![0-9]|' + src_ZPCc + ')' + src_Any + ')'; + + //////////////////////////////////////////////////////////////////////////////// + + re.src_ip4 = + + '(?:(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)'; + + // Prohibit any of "@/[]()" in user/pass to avoid wrong domain fetch. + re.src_auth = '(?:(?:(?!' + re.src_ZCc + '|[@/\\[\\]()]).)+@)?'; + + re.src_port = + + '(?::(?:6(?:[0-4]\\d{3}|5(?:[0-4]\\d{2}|5(?:[0-2]\\d|3[0-5])))|[1-5]?\\d{1,4}))?'; + + re.src_host_terminator = + + '(?=$|' + text_separators + '|' + re.src_ZPCc + ')(?!-|_|:\\d|\\.-|\\.(?!$|' + re.src_ZPCc + '))'; + + re.src_path = + + '(?:' + + '[/?#]' + + '(?:' + + '(?!' + re.src_ZCc + '|' + text_separators + '|[()[\\]{}.,"\'?!\\-]).|' + + '\\[(?:(?!' + re.src_ZCc + '|\\]).)*\\]|' + + '\\((?:(?!' + re.src_ZCc + '|[)]).)*\\)|' + + '\\{(?:(?!' + re.src_ZCc + '|[}]).)*\\}|' + + '\\"(?:(?!' + re.src_ZCc + '|["]).)+\\"|' + + "\\'(?:(?!" + re.src_ZCc + "|[']).)+\\'|" + + "\\'(?=" + re.src_pseudo_letter + '|[-]).|' + // allow `I'm_king` if no pair found + '\\.{2,4}[a-zA-Z0-9%/]|' + // github has ... in commit range links, + // google has .... in links (issue #66) + // Restrict to + // - english + // - percent-encoded + // - parts of file path + // until more examples found. + '\\.(?!' + re.src_ZCc + '|[.]).|' + + (opts && opts['---'] ? + '\\-(?!--(?:[^-]|$))(?:-*)|' // `---` => long dash, terminate + : + '\\-+|' + ) + + '\\,(?!' + re.src_ZCc + ').|' + // allow `,,,` in paths + '\\!(?!' + re.src_ZCc + '|[!]).|' + + '\\?(?!' + re.src_ZCc + '|[?]).' + + ')+' + + '|\\/' + + ')?'; + + // Allow anything in markdown spec, forbid quote (") at the first position + // because emails enclosed in quotes are far more common + re.src_email_name = + + '[\\-;:&=\\+\\$,\\.a-zA-Z0-9_][\\-;:&=\\+\\$,\\"\\.a-zA-Z0-9_]*'; + + re.src_xn = + + 'xn--[a-z0-9\\-]{1,59}'; + + // More to read about domain names + // http://serverfault.com/questions/638260/ + + re.src_domain_root = + + // Allow letters & digits (http://test1) + '(?:' + + re.src_xn + + '|' + + re.src_pseudo_letter + '{1,63}' + + ')'; + + re.src_domain = + + '(?:' + + re.src_xn + + '|' + + '(?:' + re.src_pseudo_letter + ')' + + '|' + + '(?:' + re.src_pseudo_letter + '(?:-|' + re.src_pseudo_letter + '){0,61}' + re.src_pseudo_letter + ')' + + ')'; + + re.src_host = + + '(?:' + + // Don't need IP check, because digits are already allowed in normal domain names + // src_ip4 + + // '|' + + '(?:(?:(?:' + re.src_domain + ')\\.)*' + re.src_domain/*_root*/ + ')' + + ')'; + + re.tpl_host_fuzzy = + + '(?:' + + re.src_ip4 + + '|' + + '(?:(?:(?:' + re.src_domain + ')\\.)+(?:%TLDS%))' + + ')'; + + re.tpl_host_no_ip_fuzzy = + + '(?:(?:(?:' + re.src_domain + ')\\.)+(?:%TLDS%))'; + + re.src_host_strict = + + re.src_host + re.src_host_terminator; + + re.tpl_host_fuzzy_strict = + + re.tpl_host_fuzzy + re.src_host_terminator; + + re.src_host_port_strict = + + re.src_host + re.src_port + re.src_host_terminator; + + re.tpl_host_port_fuzzy_strict = + + re.tpl_host_fuzzy + re.src_port + re.src_host_terminator; + + re.tpl_host_port_no_ip_fuzzy_strict = + + re.tpl_host_no_ip_fuzzy + re.src_port + re.src_host_terminator; + + + //////////////////////////////////////////////////////////////////////////////// + // Main rules + + // Rude test fuzzy links by host, for quick deny + re.tpl_host_fuzzy_test = + + 'localhost|www\\.|\\.\\d{1,3}\\.|(?:\\.(?:%TLDS%)(?:' + re.src_ZPCc + '|>|$))'; + + re.tpl_email_fuzzy = + + '(^|' + text_separators + '|"|\\(|' + re.src_ZCc + ')' + + '(' + re.src_email_name + '@' + re.tpl_host_fuzzy_strict + ')'; + + re.tpl_link_fuzzy = + // Fuzzy link can't be prepended with .:/\- and non punctuation. + // but can start with > (markdown blockquote) + '(^|(?![.:/\\-_@])(?:[$+<=>^`|\uff5c]|' + re.src_ZPCc + '))' + + '((?![$+<=>^`|\uff5c])' + re.tpl_host_port_fuzzy_strict + re.src_path + ')'; + + re.tpl_link_no_ip_fuzzy = + // Fuzzy link can't be prepended with .:/\- and non punctuation. + // but can start with > (markdown blockquote) + '(^|(?![.:/\\-_@])(?:[$+<=>^`|\uff5c]|' + re.src_ZPCc + '))' + + '((?![$+<=>^`|\uff5c])' + re.tpl_host_port_no_ip_fuzzy_strict + re.src_path + ')'; + + return re; +}; + +},{"uc.micro/categories/Cc/regex":61,"uc.micro/categories/P/regex":63,"uc.micro/categories/Z/regex":64,"uc.micro/properties/Any/regex":66}],55:[function(require,module,exports){ + +'use strict'; + + +/* eslint-disable no-bitwise */ + +var decodeCache = {}; + +function getDecodeCache(exclude) { + var i, ch, cache = decodeCache[exclude]; + if (cache) { return cache; } + + cache = decodeCache[exclude] = []; + + for (i = 0; i < 128; i++) { + ch = String.fromCharCode(i); + cache.push(ch); + } + + for (i = 0; i < exclude.length; i++) { + ch = exclude.charCodeAt(i); + cache[ch] = '%' + ('0' + ch.toString(16).toUpperCase()).slice(-2); + } + + return cache; +} + + +// Decode percent-encoded string. +// +function decode(string, exclude) { + var cache; + + if (typeof exclude !== 'string') { + exclude = decode.defaultChars; + } + + cache = getDecodeCache(exclude); + + return string.replace(/(%[a-f0-9]{2})+/gi, function(seq) { + var i, l, b1, b2, b3, b4, chr, + result = ''; + + for (i = 0, l = seq.length; i < l; i += 3) { + b1 = parseInt(seq.slice(i + 1, i + 3), 16); + + if (b1 < 0x80) { + result += cache[b1]; + continue; + } + + if ((b1 & 0xE0) === 0xC0 && (i + 3 < l)) { + // 110xxxxx 10xxxxxx + b2 = parseInt(seq.slice(i + 4, i + 6), 16); + + if ((b2 & 0xC0) === 0x80) { + chr = ((b1 << 6) & 0x7C0) | (b2 & 0x3F); + + if (chr < 0x80) { + result += '\ufffd\ufffd'; + } else { + result += String.fromCharCode(chr); + } + + i += 3; + continue; + } + } + + if ((b1 & 0xF0) === 0xE0 && (i + 6 < l)) { + // 1110xxxx 10xxxxxx 10xxxxxx + b2 = parseInt(seq.slice(i + 4, i + 6), 16); + b3 = parseInt(seq.slice(i + 7, i + 9), 16); + + if ((b2 & 0xC0) === 0x80 && (b3 & 0xC0) === 0x80) { + chr = ((b1 << 12) & 0xF000) | ((b2 << 6) & 0xFC0) | (b3 & 0x3F); + + if (chr < 0x800 || (chr >= 0xD800 && chr <= 0xDFFF)) { + result += '\ufffd\ufffd\ufffd'; + } else { + result += String.fromCharCode(chr); + } + + i += 6; + continue; + } + } + + if ((b1 & 0xF8) === 0xF0 && (i + 9 < l)) { + // 111110xx 10xxxxxx 10xxxxxx 10xxxxxx + b2 = parseInt(seq.slice(i + 4, i + 6), 16); + b3 = parseInt(seq.slice(i + 7, i + 9), 16); + b4 = parseInt(seq.slice(i + 10, i + 12), 16); + + if ((b2 & 0xC0) === 0x80 && (b3 & 0xC0) === 0x80 && (b4 & 0xC0) === 0x80) { + chr = ((b1 << 18) & 0x1C0000) | ((b2 << 12) & 0x3F000) | ((b3 << 6) & 0xFC0) | (b4 & 0x3F); + + if (chr < 0x10000 || chr > 0x10FFFF) { + result += '\ufffd\ufffd\ufffd\ufffd'; + } else { + chr -= 0x10000; + result += String.fromCharCode(0xD800 + (chr >> 10), 0xDC00 + (chr & 0x3FF)); + } + + i += 9; + continue; + } + } + + result += '\ufffd'; + } + + return result; + }); +} + + +decode.defaultChars = ';/?:@&=+$,#'; +decode.componentChars = ''; + + +module.exports = decode; + +},{}],56:[function(require,module,exports){ + +'use strict'; + + +var encodeCache = {}; + + +// Create a lookup array where anything but characters in `chars` string +// and alphanumeric chars is percent-encoded. +// +function getEncodeCache(exclude) { + var i, ch, cache = encodeCache[exclude]; + if (cache) { return cache; } + + cache = encodeCache[exclude] = []; + + for (i = 0; i < 128; i++) { + ch = String.fromCharCode(i); + + if (/^[0-9a-z]$/i.test(ch)) { + // always allow unencoded alphanumeric characters + cache.push(ch); + } else { + cache.push('%' + ('0' + i.toString(16).toUpperCase()).slice(-2)); + } + } + + for (i = 0; i < exclude.length; i++) { + cache[exclude.charCodeAt(i)] = exclude[i]; + } + + return cache; +} + + +// Encode unsafe characters with percent-encoding, skipping already +// encoded sequences. +// +// - string - string to encode +// - exclude - list of characters to ignore (in addition to a-zA-Z0-9) +// - keepEscaped - don't encode '%' in a correct escape sequence (default: true) +// +function encode(string, exclude, keepEscaped) { + var i, l, code, nextCode, cache, + result = ''; + + if (typeof exclude !== 'string') { + // encode(string, keepEscaped) + keepEscaped = exclude; + exclude = encode.defaultChars; + } + + if (typeof keepEscaped === 'undefined') { + keepEscaped = true; + } + + cache = getEncodeCache(exclude); + + for (i = 0, l = string.length; i < l; i++) { + code = string.charCodeAt(i); + + if (keepEscaped && code === 0x25 /* % */ && i + 2 < l) { + if (/^[0-9a-f]{2}$/i.test(string.slice(i + 1, i + 3))) { + result += string.slice(i, i + 3); + i += 2; + continue; + } + } + + if (code < 128) { + result += cache[code]; + continue; + } + + if (code >= 0xD800 && code <= 0xDFFF) { + if (code >= 0xD800 && code <= 0xDBFF && i + 1 < l) { + nextCode = string.charCodeAt(i + 1); + if (nextCode >= 0xDC00 && nextCode <= 0xDFFF) { + result += encodeURIComponent(string[i] + string[i + 1]); + i++; + continue; + } + } + result += '%EF%BF%BD'; + continue; + } + + result += encodeURIComponent(string[i]); + } + + return result; +} + +encode.defaultChars = ";/?:@&=+$,-_.!~*'()#"; +encode.componentChars = "-_.!~*'()"; + + +module.exports = encode; + +},{}],57:[function(require,module,exports){ + +'use strict'; + + +module.exports = function format(url) { + var result = ''; + + result += url.protocol || ''; + result += url.slashes ? '//' : ''; + result += url.auth ? url.auth + '@' : ''; + + if (url.hostname && url.hostname.indexOf(':') !== -1) { + // ipv6 address + result += '[' + url.hostname + ']'; + } else { + result += url.hostname || ''; + } + + result += url.port ? ':' + url.port : ''; + result += url.pathname || ''; + result += url.search || ''; + result += url.hash || ''; + + return result; +}; + +},{}],58:[function(require,module,exports){ +'use strict'; + + +module.exports.encode = require('./encode'); +module.exports.decode = require('./decode'); +module.exports.format = require('./format'); +module.exports.parse = require('./parse'); + +},{"./decode":55,"./encode":56,"./format":57,"./parse":59}],59:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; + +// +// Changes from joyent/node: +// +// 1. No leading slash in paths, +// e.g. in `url.parse('http://foo?bar')` pathname is ``, not `/` +// +// 2. Backslashes are not replaced with slashes, +// so `http:\\example.org\` is treated like a relative path +// +// 3. Trailing colon is treated like a part of the path, +// i.e. in `http://example.org:foo` pathname is `:foo` +// +// 4. Nothing is URL-encoded in the resulting object, +// (in joyent/node some chars in auth and paths are encoded) +// +// 5. `url.parse()` does not have `parseQueryString` argument +// +// 6. Removed extraneous result properties: `host`, `path`, `query`, etc., +// which can be constructed using other parts of the url. +// + + +function Url() { + this.protocol = null; + this.slashes = null; + this.auth = null; + this.port = null; + this.hostname = null; + this.hash = null; + this.search = null; + this.pathname = null; +} + +// Reference: RFC 3986, RFC 1808, RFC 2396 + +// define these here so at least they only have to be +// compiled once on the first module load. +var protocolPattern = /^([a-z0-9.+-]+:)/i, + portPattern = /:[0-9]*$/, + + // Special case for a simple path URL + simplePathPattern = /^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/, + + // RFC 2396: characters reserved for delimiting URLs. + // We actually just auto-escape these. + delims = [ '<', '>', '"', '`', ' ', '\r', '\n', '\t' ], + + // RFC 2396: characters not allowed for various reasons. + unwise = [ '{', '}', '|', '\\', '^', '`' ].concat(delims), + + // Allowed by RFCs, but cause of XSS attacks. Always escape these. + autoEscape = [ '\'' ].concat(unwise), + // Characters that are never ever allowed in a hostname. + // Note that any invalid chars are also handled, but these + // are the ones that are *expected* to be seen, so we fast-path + // them. + nonHostChars = [ '%', '/', '?', ';', '#' ].concat(autoEscape), + hostEndingChars = [ '/', '?', '#' ], + hostnameMaxLen = 255, + hostnamePartPattern = /^[+a-z0-9A-Z_-]{0,63}$/, + hostnamePartStart = /^([+a-z0-9A-Z_-]{0,63})(.*)$/, + // protocols that can allow "unsafe" and "unwise" chars. + /* eslint-disable no-script-url */ + // protocols that never have a hostname. + hostlessProtocol = { + 'javascript': true, + 'javascript:': true + }, + // protocols that always contain a // bit. + slashedProtocol = { + 'http': true, + 'https': true, + 'ftp': true, + 'gopher': true, + 'file': true, + 'http:': true, + 'https:': true, + 'ftp:': true, + 'gopher:': true, + 'file:': true + }; + /* eslint-enable no-script-url */ + +function urlParse(url, slashesDenoteHost) { + if (url && url instanceof Url) { return url; } + + var u = new Url(); + u.parse(url, slashesDenoteHost); + return u; +} + +Url.prototype.parse = function(url, slashesDenoteHost) { + var i, l, lowerProto, hec, slashes, + rest = url; + + // trim before proceeding. + // This is to support parse stuff like " http://foo.com \n" + rest = rest.trim(); + + if (!slashesDenoteHost && url.split('#').length === 1) { + // Try fast path regexp + var simplePath = simplePathPattern.exec(rest); + if (simplePath) { + this.pathname = simplePath[1]; + if (simplePath[2]) { + this.search = simplePath[2]; + } + return this; + } + } + + var proto = protocolPattern.exec(rest); + if (proto) { + proto = proto[0]; + lowerProto = proto.toLowerCase(); + this.protocol = proto; + rest = rest.substr(proto.length); + } + + // figure out if it's got a host + // user@server is *always* interpreted as a hostname, and url + // resolution will treat //foo/bar as host=foo,path=bar because that's + // how the browser resolves relative URLs. + if (slashesDenoteHost || proto || rest.match(/^\/\/[^@\/]+@[^@\/]+/)) { + slashes = rest.substr(0, 2) === '//'; + if (slashes && !(proto && hostlessProtocol[proto])) { + rest = rest.substr(2); + this.slashes = true; + } + } + + if (!hostlessProtocol[proto] && + (slashes || (proto && !slashedProtocol[proto]))) { + + // there's a hostname. + // the first instance of /, ?, ;, or # ends the host. + // + // If there is an @ in the hostname, then non-host chars *are* allowed + // to the left of the last @ sign, unless some host-ending character + // comes *before* the @-sign. + // URLs are obnoxious. + // + // ex: + // http://a@b@c/ => user:a@b host:c + // http://a@b?@c => user:a host:c path:/?@c + + // v0.12 TODO(isaacs): This is not quite how Chrome does things. + // Review our test case against browsers more comprehensively. + + // find the first instance of any hostEndingChars + var hostEnd = -1; + for (i = 0; i < hostEndingChars.length; i++) { + hec = rest.indexOf(hostEndingChars[i]); + if (hec !== -1 && (hostEnd === -1 || hec < hostEnd)) { + hostEnd = hec; + } + } + + // at this point, either we have an explicit point where the + // auth portion cannot go past, or the last @ char is the decider. + var auth, atSign; + if (hostEnd === -1) { + // atSign can be anywhere. + atSign = rest.lastIndexOf('@'); + } else { + // atSign must be in auth portion. + // http://a@b/c@d => host:b auth:a path:/c@d + atSign = rest.lastIndexOf('@', hostEnd); + } + + // Now we have a portion which is definitely the auth. + // Pull that off. + if (atSign !== -1) { + auth = rest.slice(0, atSign); + rest = rest.slice(atSign + 1); + this.auth = auth; + } + + // the host is the remaining to the left of the first non-host char + hostEnd = -1; + for (i = 0; i < nonHostChars.length; i++) { + hec = rest.indexOf(nonHostChars[i]); + if (hec !== -1 && (hostEnd === -1 || hec < hostEnd)) { + hostEnd = hec; + } + } + // if we still have not hit it, then the entire thing is a host. + if (hostEnd === -1) { + hostEnd = rest.length; + } + + if (rest[hostEnd - 1] === ':') { hostEnd--; } + var host = rest.slice(0, hostEnd); + rest = rest.slice(hostEnd); + + // pull out port. + this.parseHost(host); + + // we've indicated that there is a hostname, + // so even if it's empty, it has to be present. + this.hostname = this.hostname || ''; + + // if hostname begins with [ and ends with ] + // assume that it's an IPv6 address. + var ipv6Hostname = this.hostname[0] === '[' && + this.hostname[this.hostname.length - 1] === ']'; + + // validate a little. + if (!ipv6Hostname) { + var hostparts = this.hostname.split(/\./); + for (i = 0, l = hostparts.length; i < l; i++) { + var part = hostparts[i]; + if (!part) { continue; } + if (!part.match(hostnamePartPattern)) { + var newpart = ''; + for (var j = 0, k = part.length; j < k; j++) { + if (part.charCodeAt(j) > 127) { + // we replace non-ASCII char with a temporary placeholder + // we need this to make sure size of hostname is not + // broken by replacing non-ASCII by nothing + newpart += 'x'; + } else { + newpart += part[j]; + } + } + // we test again with ASCII char only + if (!newpart.match(hostnamePartPattern)) { + var validParts = hostparts.slice(0, i); + var notHost = hostparts.slice(i + 1); + var bit = part.match(hostnamePartStart); + if (bit) { + validParts.push(bit[1]); + notHost.unshift(bit[2]); + } + if (notHost.length) { + rest = notHost.join('.') + rest; + } + this.hostname = validParts.join('.'); + break; + } + } + } + } + + if (this.hostname.length > hostnameMaxLen) { + this.hostname = ''; + } + + // strip [ and ] from the hostname + // the host field still retains them, though + if (ipv6Hostname) { + this.hostname = this.hostname.substr(1, this.hostname.length - 2); + } + } + + // chop off from the tail first. + var hash = rest.indexOf('#'); + if (hash !== -1) { + // got a fragment string. + this.hash = rest.substr(hash); + rest = rest.slice(0, hash); + } + var qm = rest.indexOf('?'); + if (qm !== -1) { + this.search = rest.substr(qm); + rest = rest.slice(0, qm); + } + if (rest) { this.pathname = rest; } + if (slashedProtocol[lowerProto] && + this.hostname && !this.pathname) { + this.pathname = ''; + } + + return this; +}; + +Url.prototype.parseHost = function(host) { + var port = portPattern.exec(host); + if (port) { + port = port[0]; + if (port !== ':') { + this.port = port.substr(1); + } + host = host.substr(0, host.length - port.length); + } + if (host) { this.hostname = host; } +}; + +module.exports = urlParse; + +},{}],60:[function(require,module,exports){ +(function (global){ +/*! https://mths.be/punycode v1.4.1 by @mathias */ +;(function(root) { + + /** Detect free variables */ + var freeExports = typeof exports == 'object' && exports && + !exports.nodeType && exports; + var freeModule = typeof module == 'object' && module && + !module.nodeType && module; + var freeGlobal = typeof global == 'object' && global; + if ( + freeGlobal.global === freeGlobal || + freeGlobal.window === freeGlobal || + freeGlobal.self === freeGlobal + ) { + root = freeGlobal; + } + + /** + * The `punycode` object. + * @name punycode + * @type Object + */ + var punycode, + + /** Highest positive signed 32-bit float value */ + maxInt = 2147483647, // aka. 0x7FFFFFFF or 2^31-1 + + /** Bootstring parameters */ + base = 36, + tMin = 1, + tMax = 26, + skew = 38, + damp = 700, + initialBias = 72, + initialN = 128, // 0x80 + delimiter = '-', // '\x2D' + + /** Regular expressions */ + regexPunycode = /^xn--/, + regexNonASCII = /[^\x20-\x7E]/, // unprintable ASCII chars + non-ASCII chars + regexSeparators = /[\x2E\u3002\uFF0E\uFF61]/g, // RFC 3490 separators + + /** Error messages */ + errors = { + 'overflow': 'Overflow: input needs wider integers to process', + 'not-basic': 'Illegal input >= 0x80 (not a basic code point)', + 'invalid-input': 'Invalid input' + }, + + /** Convenience shortcuts */ + baseMinusTMin = base - tMin, + floor = Math.floor, + stringFromCharCode = String.fromCharCode, + + /** Temporary variable */ + key; + + /*--------------------------------------------------------------------------*/ + + /** + * A generic error utility function. + * @private + * @param {String} type The error type. + * @returns {Error} Throws a `RangeError` with the applicable error message. + */ + function error(type) { + throw new RangeError(errors[type]); + } + + /** + * A generic `Array#map` utility function. + * @private + * @param {Array} array The array to iterate over. + * @param {Function} callback The function that gets called for every array + * item. + * @returns {Array} A new array of values returned by the callback function. + */ + function map(array, fn) { + var length = array.length; + var result = []; + while (length--) { + result[length] = fn(array[length]); + } + return result; + } + + /** + * A simple `Array#map`-like wrapper to work with domain name strings or email + * addresses. + * @private + * @param {String} domain The domain name or email address. + * @param {Function} callback The function that gets called for every + * character. + * @returns {Array} A new string of characters returned by the callback + * function. + */ + function mapDomain(string, fn) { + var parts = string.split('@'); + var result = ''; + if (parts.length > 1) { + // In email addresses, only the domain name should be punycoded. Leave + // the local part (i.e. everything up to `@`) intact. + result = parts[0] + '@'; + string = parts[1]; + } + // Avoid `split(regex)` for IE8 compatibility. See #17. + string = string.replace(regexSeparators, '\x2E'); + var labels = string.split('.'); + var encoded = map(labels, fn).join('.'); + return result + encoded; + } + + /** + * Creates an array containing the numeric code points of each Unicode + * character in the string. While JavaScript uses UCS-2 internally, + * this function will convert a pair of surrogate halves (each of which + * UCS-2 exposes as separate characters) into a single code point, + * matching UTF-16. + * @see `punycode.ucs2.encode` + * @see + * @memberOf punycode.ucs2 + * @name decode + * @param {String} string The Unicode input string (UCS-2). + * @returns {Array} The new array of code points. + */ + function ucs2decode(string) { + var output = [], + counter = 0, + length = string.length, + value, + extra; + while (counter < length) { + value = string.charCodeAt(counter++); + if (value >= 0xD800 && value <= 0xDBFF && counter < length) { + // high surrogate, and there is a next character + extra = string.charCodeAt(counter++); + if ((extra & 0xFC00) == 0xDC00) { // low surrogate + output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000); + } else { + // unmatched surrogate; only append this code unit, in case the next + // code unit is the high surrogate of a surrogate pair + output.push(value); + counter--; + } + } else { + output.push(value); + } + } + return output; + } + + /** + * Creates a string based on an array of numeric code points. + * @see `punycode.ucs2.decode` + * @memberOf punycode.ucs2 + * @name encode + * @param {Array} codePoints The array of numeric code points. + * @returns {String} The new Unicode string (UCS-2). + */ + function ucs2encode(array) { + return map(array, function(value) { + var output = ''; + if (value > 0xFFFF) { + value -= 0x10000; + output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800); + value = 0xDC00 | value & 0x3FF; + } + output += stringFromCharCode(value); + return output; + }).join(''); + } + + /** + * Converts a basic code point into a digit/integer. + * @see `digitToBasic()` + * @private + * @param {Number} codePoint The basic numeric code point value. + * @returns {Number} The numeric value of a basic code point (for use in + * representing integers) in the range `0` to `base - 1`, or `base` if + * the code point does not represent a value. + */ + function basicToDigit(codePoint) { + if (codePoint - 48 < 10) { + return codePoint - 22; + } + if (codePoint - 65 < 26) { + return codePoint - 65; + } + if (codePoint - 97 < 26) { + return codePoint - 97; + } + return base; + } + + /** + * Converts a digit/integer into a basic code point. + * @see `basicToDigit()` + * @private + * @param {Number} digit The numeric value of a basic code point. + * @returns {Number} The basic code point whose value (when used for + * representing integers) is `digit`, which needs to be in the range + * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is + * used; else, the lowercase form is used. The behavior is undefined + * if `flag` is non-zero and `digit` has no uppercase form. + */ + function digitToBasic(digit, flag) { + // 0..25 map to ASCII a..z or A..Z + // 26..35 map to ASCII 0..9 + return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5); + } + + /** + * Bias adaptation function as per section 3.4 of RFC 3492. + * https://tools.ietf.org/html/rfc3492#section-3.4 + * @private + */ + function adapt(delta, numPoints, firstTime) { + var k = 0; + delta = firstTime ? floor(delta / damp) : delta >> 1; + delta += floor(delta / numPoints); + for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) { + delta = floor(delta / baseMinusTMin); + } + return floor(k + (baseMinusTMin + 1) * delta / (delta + skew)); + } + + /** + * Converts a Punycode string of ASCII-only symbols to a string of Unicode + * symbols. + * @memberOf punycode + * @param {String} input The Punycode string of ASCII-only symbols. + * @returns {String} The resulting string of Unicode symbols. + */ + function decode(input) { + // Don't use UCS-2 + var output = [], + inputLength = input.length, + out, + i = 0, + n = initialN, + bias = initialBias, + basic, + j, + index, + oldi, + w, + k, + digit, + t, + /** Cached calculation results */ + baseMinusT; + + // Handle the basic code points: let `basic` be the number of input code + // points before the last delimiter, or `0` if there is none, then copy + // the first basic code points to the output. + + basic = input.lastIndexOf(delimiter); + if (basic < 0) { + basic = 0; + } + + for (j = 0; j < basic; ++j) { + // if it's not a basic code point + if (input.charCodeAt(j) >= 0x80) { + error('not-basic'); + } + output.push(input.charCodeAt(j)); + } + + // Main decoding loop: start just after the last delimiter if any basic code + // points were copied; start at the beginning otherwise. + + for (index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) { + + // `index` is the index of the next character to be consumed. + // Decode a generalized variable-length integer into `delta`, + // which gets added to `i`. The overflow checking is easier + // if we increase `i` as we go, then subtract off its starting + // value at the end to obtain `delta`. + for (oldi = i, w = 1, k = base; /* no condition */; k += base) { + + if (index >= inputLength) { + error('invalid-input'); + } + + digit = basicToDigit(input.charCodeAt(index++)); + + if (digit >= base || digit > floor((maxInt - i) / w)) { + error('overflow'); + } + + i += digit * w; + t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); + + if (digit < t) { + break; + } + + baseMinusT = base - t; + if (w > floor(maxInt / baseMinusT)) { + error('overflow'); + } + + w *= baseMinusT; + + } + + out = output.length + 1; + bias = adapt(i - oldi, out, oldi == 0); + + // `i` was supposed to wrap around from `out` to `0`, + // incrementing `n` each time, so we'll fix that now: + if (floor(i / out) > maxInt - n) { + error('overflow'); + } + + n += floor(i / out); + i %= out; + + // Insert `n` at position `i` of the output + output.splice(i++, 0, n); + + } + + return ucs2encode(output); + } + + /** + * Converts a string of Unicode symbols (e.g. a domain name label) to a + * Punycode string of ASCII-only symbols. + * @memberOf punycode + * @param {String} input The string of Unicode symbols. + * @returns {String} The resulting Punycode string of ASCII-only symbols. + */ + function encode(input) { + var n, + delta, + handledCPCount, + basicLength, + bias, + j, + m, + q, + k, + t, + currentValue, + output = [], + /** `inputLength` will hold the number of code points in `input`. */ + inputLength, + /** Cached calculation results */ + handledCPCountPlusOne, + baseMinusT, + qMinusT; + + // Convert the input in UCS-2 to Unicode + input = ucs2decode(input); + + // Cache the length + inputLength = input.length; + + // Initialize the state + n = initialN; + delta = 0; + bias = initialBias; + + // Handle the basic code points + for (j = 0; j < inputLength; ++j) { + currentValue = input[j]; + if (currentValue < 0x80) { + output.push(stringFromCharCode(currentValue)); + } + } + + handledCPCount = basicLength = output.length; + + // `handledCPCount` is the number of code points that have been handled; + // `basicLength` is the number of basic code points. + + // Finish the basic string - if it is not empty - with a delimiter + if (basicLength) { + output.push(delimiter); + } + + // Main encoding loop: + while (handledCPCount < inputLength) { + + // All non-basic code points < n have been handled already. Find the next + // larger one: + for (m = maxInt, j = 0; j < inputLength; ++j) { + currentValue = input[j]; + if (currentValue >= n && currentValue < m) { + m = currentValue; + } + } + + // Increase `delta` enough to advance the decoder's state to , + // but guard against overflow + handledCPCountPlusOne = handledCPCount + 1; + if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) { + error('overflow'); + } + + delta += (m - n) * handledCPCountPlusOne; + n = m; + + for (j = 0; j < inputLength; ++j) { + currentValue = input[j]; + + if (currentValue < n && ++delta > maxInt) { + error('overflow'); + } + + if (currentValue == n) { + // Represent delta as a generalized variable-length integer + for (q = delta, k = base; /* no condition */; k += base) { + t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); + if (q < t) { + break; + } + qMinusT = q - t; + baseMinusT = base - t; + output.push( + stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0)) + ); + q = floor(qMinusT / baseMinusT); + } + + output.push(stringFromCharCode(digitToBasic(q, 0))); + bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength); + delta = 0; + ++handledCPCount; + } + } + + ++delta; + ++n; + + } + return output.join(''); + } + + /** + * Converts a Punycode string representing a domain name or an email address + * to Unicode. Only the Punycoded parts of the input will be converted, i.e. + * it doesn't matter if you call it on a string that has already been + * converted to Unicode. + * @memberOf punycode + * @param {String} input The Punycoded domain name or email address to + * convert to Unicode. + * @returns {String} The Unicode representation of the given Punycode + * string. + */ + function toUnicode(input) { + return mapDomain(input, function(string) { + return regexPunycode.test(string) + ? decode(string.slice(4).toLowerCase()) + : string; + }); + } + + /** + * Converts a Unicode string representing a domain name or an email address to + * Punycode. Only the non-ASCII parts of the domain name will be converted, + * i.e. it doesn't matter if you call it with a domain that's already in + * ASCII. + * @memberOf punycode + * @param {String} input The domain name or email address to convert, as a + * Unicode string. + * @returns {String} The Punycode representation of the given domain name or + * email address. + */ + function toASCII(input) { + return mapDomain(input, function(string) { + return regexNonASCII.test(string) + ? 'xn--' + encode(string) + : string; + }); + } + + /*--------------------------------------------------------------------------*/ + + /** Define the public API */ + punycode = { + /** + * A string representing the current Punycode.js version number. + * @memberOf punycode + * @type String + */ + 'version': '1.4.1', + /** + * An object of methods to convert from JavaScript's internal character + * representation (UCS-2) to Unicode code points, and back. + * @see + * @memberOf punycode + * @type Object + */ + 'ucs2': { + 'decode': ucs2decode, + 'encode': ucs2encode + }, + 'decode': decode, + 'encode': encode, + 'toASCII': toASCII, + 'toUnicode': toUnicode + }; + + /** Expose `punycode` */ + // Some AMD build optimizers, like r.js, check for specific condition patterns + // like the following: + if ( + typeof define == 'function' && + typeof define.amd == 'object' && + define.amd + ) { + define('punycode', function() { + return punycode; + }); + } else if (freeExports && freeModule) { + if (module.exports == freeExports) { + // in Node.js, io.js, or RingoJS v0.8.0+ + freeModule.exports = punycode; + } else { + // in Narwhal or RingoJS v0.7.0- + for (key in punycode) { + punycode.hasOwnProperty(key) && (freeExports[key] = punycode[key]); + } + } + } else { + // in Rhino or a web browser + root.punycode = punycode; + } + +}(this)); + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],61:[function(require,module,exports){ +module.exports=/[\0-\x1F\x7F-\x9F]/ +},{}],62:[function(require,module,exports){ +module.exports=/[\xAD\u0600-\u0605\u061C\u06DD\u070F\u08E2\u180E\u200B-\u200F\u202A-\u202E\u2060-\u2064\u2066-\u206F\uFEFF\uFFF9-\uFFFB]|\uD804[\uDCBD\uDCCD]|\uD82F[\uDCA0-\uDCA3]|\uD834[\uDD73-\uDD7A]|\uDB40[\uDC01\uDC20-\uDC7F]/ +},{}],63:[function(require,module,exports){ +module.exports=/[!-#%-\*,-\/:;\?@\[-\]_\{\}\xA1\xA7\xAB\xB6\xB7\xBB\xBF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061E\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u09FD\u0A76\u0AF0\u0C84\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166D\u166E\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2010-\u2027\u2030-\u2043\u2045-\u2051\u2053-\u205E\u207D\u207E\u208D\u208E\u2308-\u230B\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E4E\u3001-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65]|\uD800[\uDD00-\uDD02\uDF9F\uDFD0]|\uD801\uDD6F|\uD802[\uDC57\uDD1F\uDD3F\uDE50-\uDE58\uDE7F\uDEF0-\uDEF6\uDF39-\uDF3F\uDF99-\uDF9C]|\uD803[\uDF55-\uDF59]|\uD804[\uDC47-\uDC4D\uDCBB\uDCBC\uDCBE-\uDCC1\uDD40-\uDD43\uDD74\uDD75\uDDC5-\uDDC8\uDDCD\uDDDB\uDDDD-\uDDDF\uDE38-\uDE3D\uDEA9]|\uD805[\uDC4B-\uDC4F\uDC5B\uDC5D\uDCC6\uDDC1-\uDDD7\uDE41-\uDE43\uDE60-\uDE6C\uDF3C-\uDF3E]|\uD806[\uDC3B\uDE3F-\uDE46\uDE9A-\uDE9C\uDE9E-\uDEA2]|\uD807[\uDC41-\uDC45\uDC70\uDC71\uDEF7\uDEF8]|\uD809[\uDC70-\uDC74]|\uD81A[\uDE6E\uDE6F\uDEF5\uDF37-\uDF3B\uDF44]|\uD81B[\uDE97-\uDE9A]|\uD82F\uDC9F|\uD836[\uDE87-\uDE8B]|\uD83A[\uDD5E\uDD5F]/ +},{}],64:[function(require,module,exports){ +module.exports=/[ \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000]/ +},{}],65:[function(require,module,exports){ +'use strict'; + +exports.Any = require('./properties/Any/regex'); +exports.Cc = require('./categories/Cc/regex'); +exports.Cf = require('./categories/Cf/regex'); +exports.P = require('./categories/P/regex'); +exports.Z = require('./categories/Z/regex'); + +},{"./categories/Cc/regex":61,"./categories/Cf/regex":62,"./categories/P/regex":63,"./categories/Z/regex":64,"./properties/Any/regex":66}],66:[function(require,module,exports){ +module.exports=/[\0-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/ +},{}],67:[function(require,module,exports){ +'use strict'; + + +module.exports = require('./lib/'); + +},{"./lib/":9}]},{},[67])(67) +}); +const markdownit = define() +export default markdownit \ No newline at end of file diff --git a/app/assets/frontends/beaker-module/vendor/mocha.css b/app/assets/frontends/beaker-module/vendor/mocha.css new file mode 100644 index 0000000000..127f0eab0d --- /dev/null +++ b/app/assets/frontends/beaker-module/vendor/mocha.css @@ -0,0 +1,332 @@ +/** + * Mocha 7.0.1 + * + * Copyright (c) 2011-2020 OpenJS Foundation and contributors, https://openjsf.org + * https://github.com/mochajs/mocha/blob/master/LICENSE + */ + +@charset "utf-8"; + +body { + margin:0; +} + +#mocha { + font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif; + margin: 60px 50px; +} + +#mocha ul, +#mocha li { + margin: 0; + padding: 0; +} + +#mocha ul { + list-style: none; +} + +#mocha h1, +#mocha h2 { + margin: 0; +} + +#mocha h1 { + margin-top: 15px; + font-size: 1em; + font-weight: 200; +} + +#mocha h1 a { + text-decoration: none; + color: inherit; +} + +#mocha h1 a:hover { + text-decoration: underline; +} + +#mocha .suite .suite h1 { + margin-top: 0; + font-size: .8em; +} + +#mocha .hidden { + display: none; +} + +#mocha h2 { + font-size: 12px; + font-weight: normal; + cursor: pointer; +} + +#mocha .suite { + margin-left: 15px; +} + +#mocha .test { + margin-left: 15px; + overflow: hidden; +} + +#mocha .test.pending:hover h2::after { + content: '(pending)'; + font-family: arial, sans-serif; +} + +#mocha .test.pass.medium .duration { + background: #c09853; +} + +#mocha .test.pass.slow .duration { + background: #b94a48; +} + +#mocha .test.pass::before { + content: '✓'; + font-size: 12px; + display: block; + float: left; + margin-right: 5px; + color: #00d6b2; +} + +#mocha .test.pass .duration { + font-size: 9px; + margin-left: 5px; + padding: 2px 5px; + color: #fff; + -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); + -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); + box-shadow: inset 0 1px 1px rgba(0,0,0,.2); + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + -ms-border-radius: 5px; + -o-border-radius: 5px; + border-radius: 5px; +} + +#mocha .test.pass.fast .duration { + display: none; +} + +#mocha .test.pending { + color: #0b97c4; +} + +#mocha .test.pending::before { + content: '◦'; + color: #0b97c4; +} + +#mocha .test.fail { + color: #c00; +} + +#mocha .test.fail pre { + color: black; +} + +#mocha .test.fail::before { + content: '✖'; + font-size: 12px; + display: block; + float: left; + margin-right: 5px; + color: #c00; +} + +#mocha .test pre.error { + color: #c00; + max-height: 300px; + overflow: auto; +} + +#mocha .test .html-error { + overflow: auto; + color: black; + display: block; + float: left; + clear: left; + font: 12px/1.5 monaco, monospace; + margin: 5px; + padding: 15px; + border: 1px solid #eee; + max-width: 85%; /*(1)*/ + max-width: -webkit-calc(100% - 42px); + max-width: -moz-calc(100% - 42px); + max-width: calc(100% - 42px); /*(2)*/ + max-height: 300px; + word-wrap: break-word; + border-bottom-color: #ddd; + -webkit-box-shadow: 0 1px 3px #eee; + -moz-box-shadow: 0 1px 3px #eee; + box-shadow: 0 1px 3px #eee; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; +} + +#mocha .test .html-error pre.error { + border: none; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; + -webkit-box-shadow: 0; + -moz-box-shadow: 0; + box-shadow: 0; + padding: 0; + margin: 0; + margin-top: 18px; + max-height: none; +} + +/** + * (1): approximate for browsers not supporting calc + * (2): 42 = 2*15 + 2*10 + 2*1 (padding + margin + border) + * ^^ seriously + */ +#mocha .test pre { + display: block; + float: left; + clear: left; + font: 12px/1.5 monaco, monospace; + margin: 5px; + padding: 15px; + border: 1px solid #eee; + max-width: 85%; /*(1)*/ + max-width: -webkit-calc(100% - 42px); + max-width: -moz-calc(100% - 42px); + max-width: calc(100% - 42px); /*(2)*/ + word-wrap: break-word; + border-bottom-color: #ddd; + -webkit-box-shadow: 0 1px 3px #eee; + -moz-box-shadow: 0 1px 3px #eee; + box-shadow: 0 1px 3px #eee; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; +} + +#mocha .test h2 { + position: relative; +} + +#mocha .test a.replay { + position: absolute; + top: 3px; + right: 0; + text-decoration: none; + vertical-align: middle; + display: block; + width: 15px; + height: 15px; + line-height: 15px; + text-align: center; + background: #eee; + font-size: 15px; + -webkit-border-radius: 15px; + -moz-border-radius: 15px; + border-radius: 15px; + -webkit-transition:opacity 200ms; + -moz-transition:opacity 200ms; + -o-transition:opacity 200ms; + transition: opacity 200ms; + opacity: 0.3; + color: #888; +} + +#mocha .test:hover a.replay { + opacity: 1; +} + +#mocha-report.pass .test.fail { + display: none; +} + +#mocha-report.fail .test.pass { + display: none; +} + +#mocha-report.pending .test.pass, +#mocha-report.pending .test.fail { + display: none; +} +#mocha-report.pending .test.pass.pending { + display: block; +} + +#mocha-error { + color: #c00; + font-size: 1.5em; + font-weight: 100; + letter-spacing: 1px; +} + +#mocha-stats { + position: fixed; + top: 15px; + right: 10px; + font-size: 12px; + margin: 0; + color: #888; + z-index: 1; +} + +#mocha-stats .progress { + float: right; + padding-top: 0; + + /** + * Set safe initial values, so mochas .progress does not inherit these + * properties from Bootstrap .progress (which causes .progress height to + * equal line height set in Bootstrap). + */ + height: auto; + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; + background-color: initial; +} + +#mocha-stats em { + color: black; +} + +#mocha-stats a { + text-decoration: none; + color: inherit; +} + +#mocha-stats a:hover { + border-bottom: 1px solid #eee; +} + +#mocha-stats li { + display: inline-block; + margin: 0 5px; + list-style: none; + padding-top: 11px; +} + +#mocha-stats canvas { + width: 40px; + height: 40px; +} + +#mocha code .comment { color: #ddd; } +#mocha code .init { color: #2f6fad; } +#mocha code .string { color: #5890ad; } +#mocha code .keyword { color: #8a6343; } +#mocha code .number { color: #2f6fad; } + +@media screen and (max-device-width: 480px) { + #mocha { + margin: 60px 0px; + } + + #mocha #stats { + position: absolute; + } +} \ No newline at end of file diff --git a/app/assets/frontends/beaker-module/vendor/mocha.js b/app/assets/frontends/beaker-module/vendor/mocha.js new file mode 100644 index 0000000000..133c6ef0d2 --- /dev/null +++ b/app/assets/frontends/beaker-module/vendor/mocha.js @@ -0,0 +1,18091 @@ +/** + * Mocha 7.0.1 + * + * Copyright (c) 2011-2020 OpenJS Foundation and contributors, https://openjsf.org + * https://github.com/mochajs/mocha/blob/master/LICENSE + */ + +(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i} promise determining if browser notification + * permissible when fulfilled. + */ +function isPermitted() { + var permitted = { + granted: function allow() { + return Promise.resolve(true); + }, + denied: function deny() { + return Promise.resolve(false); + }, + default: function ask() { + return Notification.requestPermission().then(function(permission) { + return permission === 'granted'; + }); + } + }; + + return permitted[Notification.permission](); +} + +/** + * @summary + * Determines if notification should proceed. + * + * @description + * Notification shall not proceed unless `value` is true. + * + * `value` will equal one of: + *
    + *
  • true (from `isPermitted`)
  • + *
  • false (from `isPermitted`)
  • + *
  • undefined (from `Promise.race`)
  • + *
+ * + * @private + * @param {boolean|undefined} value - Determines if notification permissible. + * @returns {Promise} Notification can proceed + */ +function canNotify(value) { + if (!value) { + var why = value === false ? 'blocked' : 'unacknowledged'; + var reason = 'not permitted by user (' + why + ')'; + return Promise.reject(new Error(reason)); + } + return Promise.resolve(); +} + +/** + * Displays the notification. + * + * @private + * @param {Runner} runner - Runner instance. + */ +function display(runner) { + var stats = runner.stats; + var symbol = { + cross: '\u274C', + tick: '\u2705' + }; + var logo = require('../../package').notifyLogo; + var _message; + var message; + var title; + + if (stats.failures) { + _message = stats.failures + ' of ' + stats.tests + ' tests failed'; + message = symbol.cross + ' ' + _message; + title = 'Failed'; + } else { + _message = stats.passes + ' tests passed in ' + stats.duration + 'ms'; + message = symbol.tick + ' ' + _message; + title = 'Passed'; + } + + // Send notification + var options = { + badge: logo, + body: message, + dir: 'ltr', + icon: logo, + lang: 'en-US', + name: 'mocha', + requireInteraction: false, + timestamp: Date.now() + }; + var notification = new Notification(title, options); + + // Autoclose after brief delay (makes various browsers act same) + var FORCE_DURATION = 4000; + setTimeout(notification.close.bind(notification), FORCE_DURATION); +} + +/** + * As notifications are tangential to our purpose, just log the error. + * + * @private + * @param {Error} err - Why notification didn't happen. + */ +function notPermitted(err) { + console.error('notification error:', err.message); +} + +}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"../../package":90,"../runner":34,"_process":69}],3:[function(require,module,exports){ +'use strict'; + +/** + * Expose `Progress`. + */ + +module.exports = Progress; + +/** + * Initialize a new `Progress` indicator. + */ +function Progress() { + this.percent = 0; + this.size(0); + this.fontSize(11); + this.font('helvetica, arial, sans-serif'); +} + +/** + * Set progress size to `size`. + * + * @public + * @param {number} size + * @return {Progress} Progress instance. + */ +Progress.prototype.size = function(size) { + this._size = size; + return this; +}; + +/** + * Set text to `text`. + * + * @public + * @param {string} text + * @return {Progress} Progress instance. + */ +Progress.prototype.text = function(text) { + this._text = text; + return this; +}; + +/** + * Set font size to `size`. + * + * @public + * @param {number} size + * @return {Progress} Progress instance. + */ +Progress.prototype.fontSize = function(size) { + this._fontSize = size; + return this; +}; + +/** + * Set font to `family`. + * + * @param {string} family + * @return {Progress} Progress instance. + */ +Progress.prototype.font = function(family) { + this._font = family; + return this; +}; + +/** + * Update percentage to `n`. + * + * @param {number} n + * @return {Progress} Progress instance. + */ +Progress.prototype.update = function(n) { + this.percent = n; + return this; +}; + +/** + * Draw on `ctx`. + * + * @param {CanvasRenderingContext2d} ctx + * @return {Progress} Progress instance. + */ +Progress.prototype.draw = function(ctx) { + try { + var percent = Math.min(this.percent, 100); + var size = this._size; + var half = size / 2; + var x = half; + var y = half; + var rad = half - 1; + var fontSize = this._fontSize; + + ctx.font = fontSize + 'px ' + this._font; + + var angle = Math.PI * 2 * (percent / 100); + ctx.clearRect(0, 0, size, size); + + // outer circle + ctx.strokeStyle = '#9f9f9f'; + ctx.beginPath(); + ctx.arc(x, y, rad, 0, angle, false); + ctx.stroke(); + + // inner circle + ctx.strokeStyle = '#eee'; + ctx.beginPath(); + ctx.arc(x, y, rad - 1, 0, angle, true); + ctx.stroke(); + + // text + var text = this._text || (percent | 0) + '%'; + var w = ctx.measureText(text).width; + + ctx.fillText(text, x - w / 2 + 1, y + fontSize / 2 - 1); + } catch (ignore) { + // don't fail if we can't render progress + } + return this; +}; + +},{}],4:[function(require,module,exports){ +(function (global){ +'use strict'; + +exports.isatty = function isatty() { + return true; +}; + +exports.getWindowSize = function getWindowSize() { + if ('innerHeight' in global) { + return [global.innerHeight, global.innerWidth]; + } + // In a Web Worker, the DOM Window is not available. + return [640, 480]; +}; + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],5:[function(require,module,exports){ +'use strict'; +/** + * @module Context + */ +/** + * Expose `Context`. + */ + +module.exports = Context; + +/** + * Initialize a new `Context`. + * + * @private + */ +function Context() {} + +/** + * Set or get the context `Runnable` to `runnable`. + * + * @private + * @param {Runnable} runnable + * @return {Context} context + */ +Context.prototype.runnable = function(runnable) { + if (!arguments.length) { + return this._runnable; + } + this.test = this._runnable = runnable; + return this; +}; + +/** + * Set or get test timeout `ms`. + * + * @private + * @param {number} ms + * @return {Context} self + */ +Context.prototype.timeout = function(ms) { + if (!arguments.length) { + return this.runnable().timeout(); + } + this.runnable().timeout(ms); + return this; +}; + +/** + * Set test timeout `enabled`. + * + * @private + * @param {boolean} enabled + * @return {Context} self + */ +Context.prototype.enableTimeouts = function(enabled) { + if (!arguments.length) { + return this.runnable().enableTimeouts(); + } + this.runnable().enableTimeouts(enabled); + return this; +}; + +/** + * Set or get test slowness threshold `ms`. + * + * @private + * @param {number} ms + * @return {Context} self + */ +Context.prototype.slow = function(ms) { + if (!arguments.length) { + return this.runnable().slow(); + } + this.runnable().slow(ms); + return this; +}; + +/** + * Mark a test as skipped. + * + * @private + * @throws Pending + */ +Context.prototype.skip = function() { + this.runnable().skip(); +}; + +/** + * Set or get a number of allowed retries on failed tests + * + * @private + * @param {number} n + * @return {Context} self + */ +Context.prototype.retries = function(n) { + if (!arguments.length) { + return this.runnable().retries(); + } + this.runnable().retries(n); + return this; +}; + +},{}],6:[function(require,module,exports){ +'use strict'; +/** + * @module Errors + */ +/** + * Factory functions to create throwable error objects + */ + +/** + * Creates an error object to be thrown when no files to be tested could be found using specified pattern. + * + * @public + * @param {string} message - Error message to be displayed. + * @param {string} pattern - User-specified argument value. + * @returns {Error} instance detailing the error condition + */ +function createNoFilesMatchPatternError(message, pattern) { + var err = new Error(message); + err.code = 'ERR_MOCHA_NO_FILES_MATCH_PATTERN'; + err.pattern = pattern; + return err; +} + +/** + * Creates an error object to be thrown when the reporter specified in the options was not found. + * + * @public + * @param {string} message - Error message to be displayed. + * @param {string} reporter - User-specified reporter value. + * @returns {Error} instance detailing the error condition + */ +function createInvalidReporterError(message, reporter) { + var err = new TypeError(message); + err.code = 'ERR_MOCHA_INVALID_REPORTER'; + err.reporter = reporter; + return err; +} + +/** + * Creates an error object to be thrown when the interface specified in the options was not found. + * + * @public + * @param {string} message - Error message to be displayed. + * @param {string} ui - User-specified interface value. + * @returns {Error} instance detailing the error condition + */ +function createInvalidInterfaceError(message, ui) { + var err = new Error(message); + err.code = 'ERR_MOCHA_INVALID_INTERFACE'; + err.interface = ui; + return err; +} + +/** + * Creates an error object to be thrown when a behavior, option, or parameter is unsupported. + * + * @public + * @param {string} message - Error message to be displayed. + * @returns {Error} instance detailing the error condition + */ +function createUnsupportedError(message) { + var err = new Error(message); + err.code = 'ERR_MOCHA_UNSUPPORTED'; + return err; +} + +/** + * Creates an error object to be thrown when an argument is missing. + * + * @public + * @param {string} message - Error message to be displayed. + * @param {string} argument - Argument name. + * @param {string} expected - Expected argument datatype. + * @returns {Error} instance detailing the error condition + */ +function createMissingArgumentError(message, argument, expected) { + return createInvalidArgumentTypeError(message, argument, expected); +} + +/** + * Creates an error object to be thrown when an argument did not use the supported type + * + * @public + * @param {string} message - Error message to be displayed. + * @param {string} argument - Argument name. + * @param {string} expected - Expected argument datatype. + * @returns {Error} instance detailing the error condition + */ +function createInvalidArgumentTypeError(message, argument, expected) { + var err = new TypeError(message); + err.code = 'ERR_MOCHA_INVALID_ARG_TYPE'; + err.argument = argument; + err.expected = expected; + err.actual = typeof argument; + return err; +} + +/** + * Creates an error object to be thrown when an argument did not use the supported value + * + * @public + * @param {string} message - Error message to be displayed. + * @param {string} argument - Argument name. + * @param {string} value - Argument value. + * @param {string} [reason] - Why value is invalid. + * @returns {Error} instance detailing the error condition + */ +function createInvalidArgumentValueError(message, argument, value, reason) { + var err = new TypeError(message); + err.code = 'ERR_MOCHA_INVALID_ARG_VALUE'; + err.argument = argument; + err.value = value; + err.reason = typeof reason !== 'undefined' ? reason : 'is invalid'; + return err; +} + +/** + * Creates an error object to be thrown when an exception was caught, but the `Error` is falsy or undefined. + * + * @public + * @param {string} message - Error message to be displayed. + * @returns {Error} instance detailing the error condition + */ +function createInvalidExceptionError(message, value) { + var err = new Error(message); + err.code = 'ERR_MOCHA_INVALID_EXCEPTION'; + err.valueType = typeof value; + err.value = value; + return err; +} + +module.exports = { + createInvalidArgumentTypeError: createInvalidArgumentTypeError, + createInvalidArgumentValueError: createInvalidArgumentValueError, + createInvalidExceptionError: createInvalidExceptionError, + createInvalidInterfaceError: createInvalidInterfaceError, + createInvalidReporterError: createInvalidReporterError, + createMissingArgumentError: createMissingArgumentError, + createNoFilesMatchPatternError: createNoFilesMatchPatternError, + createUnsupportedError: createUnsupportedError +}; + +},{}],7:[function(require,module,exports){ +'use strict'; + +var Runnable = require('./runnable'); +var inherits = require('./utils').inherits; + +/** + * Expose `Hook`. + */ + +module.exports = Hook; + +/** + * Initialize a new `Hook` with the given `title` and callback `fn` + * + * @class + * @extends Runnable + * @param {String} title + * @param {Function} fn + */ +function Hook(title, fn) { + Runnable.call(this, title, fn); + this.type = 'hook'; +} + +/** + * Inherit from `Runnable.prototype`. + */ +inherits(Hook, Runnable); + +/** + * Get or set the test `err`. + * + * @memberof Hook + * @public + * @param {Error} err + * @return {Error} + */ +Hook.prototype.error = function(err) { + if (!arguments.length) { + err = this._error; + this._error = null; + return err; + } + + this._error = err; +}; + +},{"./runnable":33,"./utils":38}],8:[function(require,module,exports){ +'use strict'; + +var Test = require('../test'); +var EVENT_FILE_PRE_REQUIRE = require('../suite').constants + .EVENT_FILE_PRE_REQUIRE; + +/** + * BDD-style interface: + * + * describe('Array', function() { + * describe('#indexOf()', function() { + * it('should return -1 when not present', function() { + * // ... + * }); + * + * it('should return the index when present', function() { + * // ... + * }); + * }); + * }); + * + * @param {Suite} suite Root suite. + */ +module.exports = function bddInterface(suite) { + var suites = [suite]; + + suite.on(EVENT_FILE_PRE_REQUIRE, function(context, file, mocha) { + var common = require('./common')(suites, context, mocha); + + context.before = common.before; + context.after = common.after; + context.beforeEach = common.beforeEach; + context.afterEach = common.afterEach; + context.run = mocha.options.delay && common.runWithSuite(suite); + /** + * Describe a "suite" with the given `title` + * and callback `fn` containing nested suites + * and/or tests. + */ + + context.describe = context.context = function(title, fn) { + return common.suite.create({ + title: title, + file: file, + fn: fn + }); + }; + + /** + * Pending describe. + */ + + context.xdescribe = context.xcontext = context.describe.skip = function( + title, + fn + ) { + return common.suite.skip({ + title: title, + file: file, + fn: fn + }); + }; + + /** + * Exclusive suite. + */ + + context.describe.only = function(title, fn) { + return common.suite.only({ + title: title, + file: file, + fn: fn + }); + }; + + /** + * Describe a specification or test-case + * with the given `title` and callback `fn` + * acting as a thunk. + */ + + context.it = context.specify = function(title, fn) { + var suite = suites[0]; + if (suite.isPending()) { + fn = null; + } + var test = new Test(title, fn); + test.file = file; + suite.addTest(test); + return test; + }; + + /** + * Exclusive test-case. + */ + + context.it.only = function(title, fn) { + return common.test.only(mocha, context.it(title, fn)); + }; + + /** + * Pending test case. + */ + + context.xit = context.xspecify = context.it.skip = function(title) { + return context.it(title); + }; + + /** + * Number of attempts to retry. + */ + context.it.retries = function(n) { + context.retries(n); + }; + }); +}; + +module.exports.description = 'BDD or RSpec style [default]'; + +},{"../suite":36,"../test":37,"./common":9}],9:[function(require,module,exports){ +'use strict'; + +var Suite = require('../suite'); +var errors = require('../errors'); +var createMissingArgumentError = errors.createMissingArgumentError; + +/** + * Functions common to more than one interface. + * + * @param {Suite[]} suites + * @param {Context} context + * @param {Mocha} mocha + * @return {Object} An object containing common functions. + */ +module.exports = function(suites, context, mocha) { + /** + * Check if the suite should be tested. + * + * @private + * @param {Suite} suite - suite to check + * @returns {boolean} + */ + function shouldBeTested(suite) { + return ( + !mocha.options.grep || + (mocha.options.grep && + mocha.options.grep.test(suite.fullTitle()) && + !mocha.options.invert) + ); + } + + return { + /** + * This is only present if flag --delay is passed into Mocha. It triggers + * root suite execution. + * + * @param {Suite} suite The root suite. + * @return {Function} A function which runs the root suite + */ + runWithSuite: function runWithSuite(suite) { + return function run() { + suite.run(); + }; + }, + + /** + * Execute before running tests. + * + * @param {string} name + * @param {Function} fn + */ + before: function(name, fn) { + suites[0].beforeAll(name, fn); + }, + + /** + * Execute after running tests. + * + * @param {string} name + * @param {Function} fn + */ + after: function(name, fn) { + suites[0].afterAll(name, fn); + }, + + /** + * Execute before each test case. + * + * @param {string} name + * @param {Function} fn + */ + beforeEach: function(name, fn) { + suites[0].beforeEach(name, fn); + }, + + /** + * Execute after each test case. + * + * @param {string} name + * @param {Function} fn + */ + afterEach: function(name, fn) { + suites[0].afterEach(name, fn); + }, + + suite: { + /** + * Create an exclusive Suite; convenience function + * See docstring for create() below. + * + * @param {Object} opts + * @returns {Suite} + */ + only: function only(opts) { + opts.isOnly = true; + return this.create(opts); + }, + + /** + * Create a Suite, but skip it; convenience function + * See docstring for create() below. + * + * @param {Object} opts + * @returns {Suite} + */ + skip: function skip(opts) { + opts.pending = true; + return this.create(opts); + }, + + /** + * Creates a suite. + * + * @param {Object} opts Options + * @param {string} opts.title Title of Suite + * @param {Function} [opts.fn] Suite Function (not always applicable) + * @param {boolean} [opts.pending] Is Suite pending? + * @param {string} [opts.file] Filepath where this Suite resides + * @param {boolean} [opts.isOnly] Is Suite exclusive? + * @returns {Suite} + */ + create: function create(opts) { + var suite = Suite.create(suites[0], opts.title); + suite.pending = Boolean(opts.pending); + suite.file = opts.file; + suites.unshift(suite); + if (opts.isOnly) { + if (mocha.options.forbidOnly && shouldBeTested(suite)) { + throw new Error('`.only` forbidden'); + } + + suite.parent.appendOnlySuite(suite); + } + if (suite.pending) { + if (mocha.options.forbidPending && shouldBeTested(suite)) { + throw new Error('Pending test forbidden'); + } + } + if (typeof opts.fn === 'function') { + opts.fn.call(suite); + suites.shift(); + } else if (typeof opts.fn === 'undefined' && !suite.pending) { + throw createMissingArgumentError( + 'Suite "' + + suite.fullTitle() + + '" was defined but no callback was supplied. ' + + 'Supply a callback or explicitly skip the suite.', + 'callback', + 'function' + ); + } else if (!opts.fn && suite.pending) { + suites.shift(); + } + + return suite; + } + }, + + test: { + /** + * Exclusive test-case. + * + * @param {Object} mocha + * @param {Function} test + * @returns {*} + */ + only: function(mocha, test) { + test.parent.appendOnlyTest(test); + return test; + }, + + /** + * Pending test case. + * + * @param {string} title + */ + skip: function(title) { + context.test(title); + }, + + /** + * Number of retry attempts + * + * @param {number} n + */ + retries: function(n) { + context.retries(n); + } + } + }; +}; + +},{"../errors":6,"../suite":36}],10:[function(require,module,exports){ +'use strict'; +var Suite = require('../suite'); +var Test = require('../test'); + +/** + * Exports-style (as Node.js module) interface: + * + * exports.Array = { + * '#indexOf()': { + * 'should return -1 when the value is not present': function() { + * + * }, + * + * 'should return the correct index when the value is present': function() { + * + * } + * } + * }; + * + * @param {Suite} suite Root suite. + */ +module.exports = function(suite) { + var suites = [suite]; + + suite.on(Suite.constants.EVENT_FILE_REQUIRE, visit); + + function visit(obj, file) { + var suite; + for (var key in obj) { + if (typeof obj[key] === 'function') { + var fn = obj[key]; + switch (key) { + case 'before': + suites[0].beforeAll(fn); + break; + case 'after': + suites[0].afterAll(fn); + break; + case 'beforeEach': + suites[0].beforeEach(fn); + break; + case 'afterEach': + suites[0].afterEach(fn); + break; + default: + var test = new Test(key, fn); + test.file = file; + suites[0].addTest(test); + } + } else { + suite = Suite.create(suites[0], key); + suites.unshift(suite); + visit(obj[key], file); + suites.shift(); + } + } + } +}; + +module.exports.description = 'Node.js module ("exports") style'; + +},{"../suite":36,"../test":37}],11:[function(require,module,exports){ +'use strict'; + +exports.bdd = require('./bdd'); +exports.tdd = require('./tdd'); +exports.qunit = require('./qunit'); +exports.exports = require('./exports'); + +},{"./bdd":8,"./exports":10,"./qunit":12,"./tdd":13}],12:[function(require,module,exports){ +'use strict'; + +var Test = require('../test'); +var EVENT_FILE_PRE_REQUIRE = require('../suite').constants + .EVENT_FILE_PRE_REQUIRE; + +/** + * QUnit-style interface: + * + * suite('Array'); + * + * test('#length', function() { + * var arr = [1,2,3]; + * ok(arr.length == 3); + * }); + * + * test('#indexOf()', function() { + * var arr = [1,2,3]; + * ok(arr.indexOf(1) == 0); + * ok(arr.indexOf(2) == 1); + * ok(arr.indexOf(3) == 2); + * }); + * + * suite('String'); + * + * test('#length', function() { + * ok('foo'.length == 3); + * }); + * + * @param {Suite} suite Root suite. + */ +module.exports = function qUnitInterface(suite) { + var suites = [suite]; + + suite.on(EVENT_FILE_PRE_REQUIRE, function(context, file, mocha) { + var common = require('./common')(suites, context, mocha); + + context.before = common.before; + context.after = common.after; + context.beforeEach = common.beforeEach; + context.afterEach = common.afterEach; + context.run = mocha.options.delay && common.runWithSuite(suite); + /** + * Describe a "suite" with the given `title`. + */ + + context.suite = function(title) { + if (suites.length > 1) { + suites.shift(); + } + return common.suite.create({ + title: title, + file: file, + fn: false + }); + }; + + /** + * Exclusive Suite. + */ + + context.suite.only = function(title) { + if (suites.length > 1) { + suites.shift(); + } + return common.suite.only({ + title: title, + file: file, + fn: false + }); + }; + + /** + * Describe a specification or test-case + * with the given `title` and callback `fn` + * acting as a thunk. + */ + + context.test = function(title, fn) { + var test = new Test(title, fn); + test.file = file; + suites[0].addTest(test); + return test; + }; + + /** + * Exclusive test-case. + */ + + context.test.only = function(title, fn) { + return common.test.only(mocha, context.test(title, fn)); + }; + + context.test.skip = common.test.skip; + context.test.retries = common.test.retries; + }); +}; + +module.exports.description = 'QUnit style'; + +},{"../suite":36,"../test":37,"./common":9}],13:[function(require,module,exports){ +'use strict'; + +var Test = require('../test'); +var EVENT_FILE_PRE_REQUIRE = require('../suite').constants + .EVENT_FILE_PRE_REQUIRE; + +/** + * TDD-style interface: + * + * suite('Array', function() { + * suite('#indexOf()', function() { + * suiteSetup(function() { + * + * }); + * + * test('should return -1 when not present', function() { + * + * }); + * + * test('should return the index when present', function() { + * + * }); + * + * suiteTeardown(function() { + * + * }); + * }); + * }); + * + * @param {Suite} suite Root suite. + */ +module.exports = function(suite) { + var suites = [suite]; + + suite.on(EVENT_FILE_PRE_REQUIRE, function(context, file, mocha) { + var common = require('./common')(suites, context, mocha); + + context.setup = common.beforeEach; + context.teardown = common.afterEach; + context.suiteSetup = common.before; + context.suiteTeardown = common.after; + context.run = mocha.options.delay && common.runWithSuite(suite); + + /** + * Describe a "suite" with the given `title` and callback `fn` containing + * nested suites and/or tests. + */ + context.suite = function(title, fn) { + return common.suite.create({ + title: title, + file: file, + fn: fn + }); + }; + + /** + * Pending suite. + */ + context.suite.skip = function(title, fn) { + return common.suite.skip({ + title: title, + file: file, + fn: fn + }); + }; + + /** + * Exclusive test-case. + */ + context.suite.only = function(title, fn) { + return common.suite.only({ + title: title, + file: file, + fn: fn + }); + }; + + /** + * Describe a specification or test-case with the given `title` and + * callback `fn` acting as a thunk. + */ + context.test = function(title, fn) { + var suite = suites[0]; + if (suite.isPending()) { + fn = null; + } + var test = new Test(title, fn); + test.file = file; + suite.addTest(test); + return test; + }; + + /** + * Exclusive test-case. + */ + + context.test.only = function(title, fn) { + return common.test.only(mocha, context.test(title, fn)); + }; + + context.test.skip = common.test.skip; + context.test.retries = common.test.retries; + }); +}; + +module.exports.description = + 'traditional "suite"/"test" instead of BDD\'s "describe"/"it"'; + +},{"../suite":36,"../test":37,"./common":9}],14:[function(require,module,exports){ +(function (process,global){ +'use strict'; + +/*! + * mocha + * Copyright(c) 2011 TJ Holowaychuk + * MIT Licensed + */ + +var escapeRe = require('escape-string-regexp'); +var path = require('path'); +var builtinReporters = require('./reporters'); +var growl = require('./growl'); +var utils = require('./utils'); +var mocharc = require('./mocharc.json'); +var errors = require('./errors'); +var Suite = require('./suite'); +var createStatsCollector = require('./stats-collector'); +var createInvalidReporterError = errors.createInvalidReporterError; +var createInvalidInterfaceError = errors.createInvalidInterfaceError; +var EVENT_FILE_PRE_REQUIRE = Suite.constants.EVENT_FILE_PRE_REQUIRE; +var EVENT_FILE_POST_REQUIRE = Suite.constants.EVENT_FILE_POST_REQUIRE; +var EVENT_FILE_REQUIRE = Suite.constants.EVENT_FILE_REQUIRE; +var sQuote = utils.sQuote; + +exports = module.exports = Mocha; + +/** + * To require local UIs and reporters when running in node. + */ + +if (!process.browser) { + var cwd = process.cwd(); + module.paths.push(cwd, path.join(cwd, 'node_modules')); +} + +/** + * Expose internals. + */ + +/** + * @public + * @class utils + * @memberof Mocha + */ +exports.utils = utils; +exports.interfaces = require('./interfaces'); +/** + * @public + * @memberof Mocha + */ +exports.reporters = builtinReporters; +exports.Runnable = require('./runnable'); +exports.Context = require('./context'); +/** + * + * @memberof Mocha + */ +exports.Runner = require('./runner'); +exports.Suite = Suite; +exports.Hook = require('./hook'); +exports.Test = require('./test'); + +/** + * Constructs a new Mocha instance with `options`. + * + * @public + * @class Mocha + * @param {Object} [options] - Settings object. + * @param {boolean} [options.allowUncaught] - Propagate uncaught errors? + * @param {boolean} [options.asyncOnly] - Force `done` callback or promise? + * @param {boolean} [options.bail] - Bail after first test failure? + * @param {boolean} [options.checkLeaks] - Check for global variable leaks? + * @param {boolean} [options.color] - Color TTY output from reporter? + * @param {boolean} [options.delay] - Delay root suite execution? + * @param {boolean} [options.diff] - Show diff on failure? + * @param {string} [options.fgrep] - Test filter given string. + * @param {boolean} [options.forbidOnly] - Tests marked `only` fail the suite? + * @param {boolean} [options.forbidPending] - Pending tests fail the suite? + * @param {boolean} [options.fullTrace] - Full stacktrace upon failure? + * @param {string[]} [options.global] - Variables expected in global scope. + * @param {RegExp|string} [options.grep] - Test filter given regular expression. + * @param {boolean} [options.growl] - Enable desktop notifications? + * @param {boolean} [options.inlineDiffs] - Display inline diffs? + * @param {boolean} [options.invert] - Invert test filter matches? + * @param {boolean} [options.noHighlighting] - Disable syntax highlighting? + * @param {string|constructor} [options.reporter] - Reporter name or constructor. + * @param {Object} [options.reporterOption] - Reporter settings object. + * @param {number} [options.retries] - Number of times to retry failed tests. + * @param {number} [options.slow] - Slow threshold value. + * @param {number|string} [options.timeout] - Timeout threshold value. + * @param {string} [options.ui] - Interface name. + */ +function Mocha(options) { + options = utils.assign({}, mocharc, options || {}); + this.files = []; + this.options = options; + // root suite + this.suite = new exports.Suite('', new exports.Context(), true); + + this.grep(options.grep) + .fgrep(options.fgrep) + .ui(options.ui) + .reporter( + options.reporter, + options.reporterOption || options.reporterOptions // reporterOptions was previously the only way to specify options to reporter + ) + .slow(options.slow) + .global(options.global); + + // this guard exists because Suite#timeout does not consider `undefined` to be valid input + if (typeof options.timeout !== 'undefined') { + this.timeout(options.timeout === false ? 0 : options.timeout); + } + + if ('retries' in options) { + this.retries(options.retries); + } + + [ + 'allowUncaught', + 'asyncOnly', + 'bail', + 'checkLeaks', + 'color', + 'delay', + 'diff', + 'forbidOnly', + 'forbidPending', + 'fullTrace', + 'growl', + 'inlineDiffs', + 'invert' + ].forEach(function(opt) { + if (options[opt]) { + this[opt](); + } + }, this); +} + +/** + * Enables or disables bailing on the first failure. + * + * @public + * @see [CLI option](../#-bail-b) + * @param {boolean} [bail=true] - Whether to bail on first error. + * @returns {Mocha} this + * @chainable + */ +Mocha.prototype.bail = function(bail) { + this.suite.bail(bail !== false); + return this; +}; + +/** + * @summary + * Adds `file` to be loaded for execution. + * + * @description + * Useful for generic setup code that must be included within test suite. + * + * @public + * @see [CLI option](../#-file-filedirectoryglob) + * @param {string} file - Pathname of file to be loaded. + * @returns {Mocha} this + * @chainable + */ +Mocha.prototype.addFile = function(file) { + this.files.push(file); + return this; +}; + +/** + * Sets reporter to `reporter`, defaults to "spec". + * + * @public + * @see [CLI option](../#-reporter-name-r-name) + * @see [Reporters](../#reporters) + * @param {String|Function} reporter - Reporter name or constructor. + * @param {Object} [reporterOptions] - Options used to configure the reporter. + * @returns {Mocha} this + * @chainable + * @throws {Error} if requested reporter cannot be loaded + * @example + * + * // Use XUnit reporter and direct its output to file + * mocha.reporter('xunit', { output: '/path/to/testspec.xunit.xml' }); + */ +Mocha.prototype.reporter = function(reporter, reporterOptions) { + if (typeof reporter === 'function') { + this._reporter = reporter; + } else { + reporter = reporter || 'spec'; + var _reporter; + // Try to load a built-in reporter. + if (builtinReporters[reporter]) { + _reporter = builtinReporters[reporter]; + } + // Try to load reporters from process.cwd() and node_modules + if (!_reporter) { + try { + _reporter = require(reporter); + } catch (err) { + if ( + err.code !== 'MODULE_NOT_FOUND' || + err.message.indexOf('Cannot find module') !== -1 + ) { + // Try to load reporters from a path (absolute or relative) + try { + _reporter = require(path.resolve(process.cwd(), reporter)); + } catch (_err) { + _err.code !== 'MODULE_NOT_FOUND' || + _err.message.indexOf('Cannot find module') !== -1 + ? console.warn(sQuote(reporter) + ' reporter not found') + : console.warn( + sQuote(reporter) + + ' reporter blew up with error:\n' + + err.stack + ); + } + } else { + console.warn( + sQuote(reporter) + ' reporter blew up with error:\n' + err.stack + ); + } + } + } + if (!_reporter) { + throw createInvalidReporterError( + 'invalid reporter ' + sQuote(reporter), + reporter + ); + } + this._reporter = _reporter; + } + this.options.reporterOption = reporterOptions; + // alias option name is used in public reporters xunit/tap/progress + this.options.reporterOptions = reporterOptions; + return this; +}; + +/** + * Sets test UI `name`, defaults to "bdd". + * + * @public + * @see [CLI option](../#-ui-name-u-name) + * @see [Interface DSLs](../#interfaces) + * @param {string|Function} [ui=bdd] - Interface name or class. + * @returns {Mocha} this + * @chainable + * @throws {Error} if requested interface cannot be loaded + */ +Mocha.prototype.ui = function(ui) { + var bindInterface; + if (typeof ui === 'function') { + bindInterface = ui; + } else { + ui = ui || 'bdd'; + bindInterface = exports.interfaces[ui]; + if (!bindInterface) { + try { + bindInterface = require(ui); + } catch (err) { + throw createInvalidInterfaceError( + 'invalid interface ' + sQuote(ui), + ui + ); + } + } + } + bindInterface(this.suite); + + this.suite.on(EVENT_FILE_PRE_REQUIRE, function(context) { + exports.afterEach = context.afterEach || context.teardown; + exports.after = context.after || context.suiteTeardown; + exports.beforeEach = context.beforeEach || context.setup; + exports.before = context.before || context.suiteSetup; + exports.describe = context.describe || context.suite; + exports.it = context.it || context.test; + exports.xit = context.xit || (context.test && context.test.skip); + exports.setup = context.setup || context.beforeEach; + exports.suiteSetup = context.suiteSetup || context.before; + exports.suiteTeardown = context.suiteTeardown || context.after; + exports.suite = context.suite || context.describe; + exports.teardown = context.teardown || context.afterEach; + exports.test = context.test || context.it; + exports.run = context.run; + }); + + return this; +}; + +/** + * Loads `files` prior to execution. + * + * @description + * The implementation relies on Node's `require` to execute + * the test interface functions and will be subject to its cache. + * + * @private + * @see {@link Mocha#addFile} + * @see {@link Mocha#run} + * @see {@link Mocha#unloadFiles} + * @param {Function} [fn] - Callback invoked upon completion. + */ +Mocha.prototype.loadFiles = function(fn) { + var self = this; + var suite = this.suite; + this.files.forEach(function(file) { + file = path.resolve(file); + suite.emit(EVENT_FILE_PRE_REQUIRE, global, file, self); + suite.emit(EVENT_FILE_REQUIRE, require(file), file, self); + suite.emit(EVENT_FILE_POST_REQUIRE, global, file, self); + }); + fn && fn(); +}; + +/** + * Removes a previously loaded file from Node's `require` cache. + * + * @private + * @static + * @see {@link Mocha#unloadFiles} + * @param {string} file - Pathname of file to be unloaded. + */ +Mocha.unloadFile = function(file) { + delete require.cache[require.resolve(file)]; +}; + +/** + * Unloads `files` from Node's `require` cache. + * + * @description + * This allows files to be "freshly" reloaded, providing the ability + * to reuse a Mocha instance programmatically. + * + * Intended for consumers — not used internally + * + * @public + * @see {@link Mocha#run} + * @returns {Mocha} this + * @chainable + */ +Mocha.prototype.unloadFiles = function() { + this.files.forEach(Mocha.unloadFile); + return this; +}; + +/** + * Sets `grep` filter after escaping RegExp special characters. + * + * @public + * @see {@link Mocha#grep} + * @param {string} str - Value to be converted to a regexp. + * @returns {Mocha} this + * @chainable + * @example + * + * // Select tests whose full title begins with `"foo"` followed by a period + * mocha.fgrep('foo.'); + */ +Mocha.prototype.fgrep = function(str) { + if (!str) { + return this; + } + return this.grep(new RegExp(escapeRe(str))); +}; + +/** + * @summary + * Sets `grep` filter used to select specific tests for execution. + * + * @description + * If `re` is a regexp-like string, it will be converted to regexp. + * The regexp is tested against the full title of each test (i.e., the + * name of the test preceded by titles of each its ancestral suites). + * As such, using an exact-match fixed pattern against the + * test name itself will not yield any matches. + *
+ * Previous filter value will be overwritten on each call! + * + * @public + * @see [CLI option](../#-grep-regexp-g-regexp) + * @see {@link Mocha#fgrep} + * @see {@link Mocha#invert} + * @param {RegExp|String} re - Regular expression used to select tests. + * @return {Mocha} this + * @chainable + * @example + * + * // Select tests whose full title contains `"match"`, ignoring case + * mocha.grep(/match/i); + * @example + * + * // Same as above but with regexp-like string argument + * mocha.grep('/match/i'); + * @example + * + * // ## Anti-example + * // Given embedded test `it('only-this-test')`... + * mocha.grep('/^only-this-test$/'); // NO! Use `.only()` to do this! + */ +Mocha.prototype.grep = function(re) { + if (utils.isString(re)) { + // extract args if it's regex-like, i.e: [string, pattern, flag] + var arg = re.match(/^\/(.*)\/(g|i|)$|.*/); + this.options.grep = new RegExp(arg[1] || arg[0], arg[2]); + } else { + this.options.grep = re; + } + return this; +}; + +/** + * Inverts `grep` matches. + * + * @public + * @see {@link Mocha#grep} + * @return {Mocha} this + * @chainable + * @example + * + * // Select tests whose full title does *not* contain `"match"`, ignoring case + * mocha.grep(/match/i).invert(); + */ +Mocha.prototype.invert = function() { + this.options.invert = true; + return this; +}; + +/** + * Enables or disables ignoring global leaks. + * + * @deprecated since v7.0.0 + * @public + * @see {@link Mocha#checkLeaks} + * @param {boolean} [ignoreLeaks=false] - Whether to ignore global leaks. + * @return {Mocha} this + * @chainable + */ +Mocha.prototype.ignoreLeaks = function(ignoreLeaks) { + utils.deprecate( + '"ignoreLeaks()" is DEPRECATED, please use "checkLeaks()" instead.' + ); + this.options.checkLeaks = !ignoreLeaks; + return this; +}; + +/** + * Enables or disables checking for global variables leaked while running tests. + * + * @public + * @see [CLI option](../#-check-leaks) + * @param {boolean} [checkLeaks=true] - Whether to check for global variable leaks. + * @return {Mocha} this + * @chainable + */ +Mocha.prototype.checkLeaks = function(checkLeaks) { + this.options.checkLeaks = checkLeaks !== false; + return this; +}; + +/** + * Displays full stack trace upon test failure. + * + * @public + * @see [CLI option](../#-full-trace) + * @param {boolean} [fullTrace=true] - Whether to print full stacktrace upon failure. + * @return {Mocha} this + * @chainable + */ +Mocha.prototype.fullTrace = function(fullTrace) { + this.options.fullTrace = fullTrace !== false; + return this; +}; + +/** + * Enables desktop notification support if prerequisite software installed. + * + * @public + * @see [CLI option](../#-growl-g) + * @return {Mocha} this + * @chainable + */ +Mocha.prototype.growl = function() { + this.options.growl = this.isGrowlCapable(); + if (!this.options.growl) { + var detail = process.browser + ? 'notification support not available in this browser...' + : 'notification support prerequisites not installed...'; + console.error(detail + ' cannot enable!'); + } + return this; +}; + +/** + * @summary + * Determines if Growl support seems likely. + * + * @description + * Not available when run in browser. + * + * @private + * @see {@link Growl#isCapable} + * @see {@link Mocha#growl} + * @return {boolean} whether Growl support can be expected + */ +Mocha.prototype.isGrowlCapable = growl.isCapable; + +/** + * Implements desktop notifications using a pseudo-reporter. + * + * @private + * @see {@link Mocha#growl} + * @see {@link Growl#notify} + * @param {Runner} runner - Runner instance. + */ +Mocha.prototype._growl = growl.notify; + +/** + * Specifies whitelist of variable names to be expected in global scope. + * + * @public + * @see [CLI option](../#-global-variable-name) + * @see {@link Mocha#checkLeaks} + * @param {String[]|String} global - Accepted global variable name(s). + * @return {Mocha} this + * @chainable + * @example + * + * // Specify variables to be expected in global scope + * mocha.global(['jQuery', 'MyLib']); + */ +Mocha.prototype.global = function(global) { + this.options.global = (this.options.global || []) + .concat(global) + .filter(Boolean) + .filter(function(elt, idx, arr) { + return arr.indexOf(elt) === idx; + }); + return this; +}; +// for backwards compability, 'globals' is an alias of 'global' +Mocha.prototype.globals = Mocha.prototype.global; + +/** + * Enables or disables TTY color output by screen-oriented reporters. + * + * @deprecated since v7.0.0 + * @public + * @see {@link Mocha#color} + * @param {boolean} colors - Whether to enable color output. + * @return {Mocha} this + * @chainable + */ +Mocha.prototype.useColors = function(colors) { + utils.deprecate('"useColors()" is DEPRECATED, please use "color()" instead.'); + if (colors !== undefined) { + this.options.color = colors; + } + return this; +}; + +/** + * Enables or disables TTY color output by screen-oriented reporters. + * + * @public + * @see [CLI option](../#-color-c-colors) + * @param {boolean} [color=true] - Whether to enable color output. + * @return {Mocha} this + * @chainable + */ +Mocha.prototype.color = function(color) { + this.options.color = color !== false; + return this; +}; + +/** + * Determines if reporter should use inline diffs (rather than +/-) + * in test failure output. + * + * @deprecated since v7.0.0 + * @public + * @see {@link Mocha#inlineDiffs} + * @param {boolean} [inlineDiffs=false] - Whether to use inline diffs. + * @return {Mocha} this + * @chainable + */ +Mocha.prototype.useInlineDiffs = function(inlineDiffs) { + utils.deprecate( + '"useInlineDiffs()" is DEPRECATED, please use "inlineDiffs()" instead.' + ); + this.options.inlineDiffs = inlineDiffs !== undefined && inlineDiffs; + return this; +}; + +/** + * Enables or disables reporter to use inline diffs (rather than +/-) + * in test failure output. + * + * @public + * @see [CLI option](../#-inline-diffs) + * @param {boolean} [inlineDiffs=true] - Whether to use inline diffs. + * @return {Mocha} this + * @chainable + */ +Mocha.prototype.inlineDiffs = function(inlineDiffs) { + this.options.inlineDiffs = inlineDiffs !== false; + return this; +}; + +/** + * Determines if reporter should include diffs in test failure output. + * + * @deprecated since v7.0.0 + * @public + * @see {@link Mocha#diff} + * @param {boolean} [hideDiff=false] - Whether to hide diffs. + * @return {Mocha} this + * @chainable + */ +Mocha.prototype.hideDiff = function(hideDiff) { + utils.deprecate('"hideDiff()" is DEPRECATED, please use "diff()" instead.'); + this.options.diff = !(hideDiff === true); + return this; +}; + +/** + * Enables or disables reporter to include diff in test failure output. + * + * @public + * @see [CLI option](../#-diff) + * @param {boolean} [diff=true] - Whether to show diff on failure. + * @return {Mocha} this + * @chainable + */ +Mocha.prototype.diff = function(diff) { + this.options.diff = diff !== false; + return this; +}; + +/** + * @summary + * Sets timeout threshold value. + * + * @description + * A string argument can use shorthand (such as "2s") and will be converted. + * If the value is `0`, timeouts will be disabled. + * + * @public + * @see [CLI option](../#-timeout-ms-t-ms) + * @see [Timeouts](../#timeouts) + * @see {@link Mocha#enableTimeouts} + * @param {number|string} msecs - Timeout threshold value. + * @return {Mocha} this + * @chainable + * @example + * + * // Sets timeout to one second + * mocha.timeout(1000); + * @example + * + * // Same as above but using string argument + * mocha.timeout('1s'); + */ +Mocha.prototype.timeout = function(msecs) { + this.suite.timeout(msecs); + return this; +}; + +/** + * Sets the number of times to retry failed tests. + * + * @public + * @see [CLI option](../#-retries-n) + * @see [Retry Tests](../#retry-tests) + * @param {number} retry - Number of times to retry failed tests. + * @return {Mocha} this + * @chainable + * @example + * + * // Allow any failed test to retry one more time + * mocha.retries(1); + */ +Mocha.prototype.retries = function(n) { + this.suite.retries(n); + return this; +}; + +/** + * Sets slowness threshold value. + * + * @public + * @see [CLI option](../#-slow-ms-s-ms) + * @param {number} msecs - Slowness threshold value. + * @return {Mocha} this + * @chainable + * @example + * + * // Sets "slow" threshold to half a second + * mocha.slow(500); + * @example + * + * // Same as above but using string argument + * mocha.slow('0.5s'); + */ +Mocha.prototype.slow = function(msecs) { + this.suite.slow(msecs); + return this; +}; + +/** + * Enables or disables timeouts. + * + * @public + * @see [CLI option](../#-timeout-ms-t-ms) + * @param {boolean} enableTimeouts - Whether to enable timeouts. + * @return {Mocha} this + * @chainable + */ +Mocha.prototype.enableTimeouts = function(enableTimeouts) { + this.suite.enableTimeouts( + arguments.length && enableTimeouts !== undefined ? enableTimeouts : true + ); + return this; +}; + +/** + * Forces all tests to either accept a `done` callback or return a promise. + * + * @public + * @see [CLI option](../#-async-only-a) + * @param {boolean} [asyncOnly=true] - Wether to force `done` callback or promise. + * @return {Mocha} this + * @chainable + */ +Mocha.prototype.asyncOnly = function(asyncOnly) { + this.options.asyncOnly = asyncOnly !== false; + return this; +}; + +/** + * Disables syntax highlighting (in browser). + * + * @public + * @return {Mocha} this + * @chainable + */ +Mocha.prototype.noHighlighting = function() { + this.options.noHighlighting = true; + return this; +}; + +/** + * Enables or disables uncaught errors to propagate. + * + * @public + * @see [CLI option](../#-allow-uncaught) + * @param {boolean} [allowUncaught=true] - Whether to propagate uncaught errors. + * @return {Mocha} this + * @chainable + */ +Mocha.prototype.allowUncaught = function(allowUncaught) { + this.options.allowUncaught = allowUncaught !== false; + return this; +}; + +/** + * @summary + * Delays root suite execution. + * + * @description + * Used to perform asynch operations before any suites are run. + * + * @public + * @see [delayed root suite](../#delayed-root-suite) + * @returns {Mocha} this + * @chainable + */ +Mocha.prototype.delay = function delay() { + this.options.delay = true; + return this; +}; + +/** + * Causes tests marked `only` to fail the suite. + * + * @public + * @see [CLI option](../#-forbid-only) + * @param {boolean} [forbidOnly=true] - Whether tests marked `only` fail the suite. + * @returns {Mocha} this + * @chainable + */ +Mocha.prototype.forbidOnly = function(forbidOnly) { + this.options.forbidOnly = forbidOnly !== false; + return this; +}; + +/** + * Causes pending tests and tests marked `skip` to fail the suite. + * + * @public + * @see [CLI option](../#-forbid-pending) + * @param {boolean} [forbidPending=true] - Whether pending tests fail the suite. + * @returns {Mocha} this + * @chainable + */ +Mocha.prototype.forbidPending = function(forbidPending) { + this.options.forbidPending = forbidPending !== false; + return this; +}; + +/** + * Mocha version as specified by "package.json". + * + * @name Mocha#version + * @type string + * @readonly + */ +Object.defineProperty(Mocha.prototype, 'version', { + value: require('../package.json').version, + configurable: false, + enumerable: true, + writable: false +}); + +/** + * Callback to be invoked when test execution is complete. + * + * @callback DoneCB + * @param {number} failures - Number of failures that occurred. + */ + +/** + * Runs root suite and invokes `fn()` when complete. + * + * @description + * To run tests multiple times (or to run tests in files that are + * already in the `require` cache), make sure to clear them from + * the cache first! + * + * @public + * @see {@link Mocha#unloadFiles} + * @see {@link Runner#run} + * @param {DoneCB} [fn] - Callback invoked when test execution completed. + * @return {Runner} runner instance + */ +Mocha.prototype.run = function(fn) { + if (this.files.length) { + this.loadFiles(); + } + var suite = this.suite; + var options = this.options; + options.files = this.files; + var runner = new exports.Runner(suite, options.delay); + createStatsCollector(runner); + var reporter = new this._reporter(runner, options); + runner.checkLeaks = options.checkLeaks === true; + runner.fullStackTrace = options.fullTrace; + runner.asyncOnly = options.asyncOnly; + runner.allowUncaught = options.allowUncaught; + runner.forbidOnly = options.forbidOnly; + runner.forbidPending = options.forbidPending; + if (options.grep) { + runner.grep(options.grep, options.invert); + } + if (options.global) { + runner.globals(options.global); + } + if (options.growl) { + this._growl(runner); + } + if (options.color !== undefined) { + exports.reporters.Base.useColors = options.color; + } + exports.reporters.Base.inlineDiffs = options.inlineDiffs; + exports.reporters.Base.hideDiff = !options.diff; + + function done(failures) { + fn = fn || utils.noop; + if (reporter.done) { + reporter.done(failures, fn); + } else { + fn(failures); + } + } + + return runner.run(done); +}; + +}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"../package.json":90,"./context":5,"./errors":6,"./growl":2,"./hook":7,"./interfaces":11,"./mocharc.json":15,"./reporters":21,"./runnable":33,"./runner":34,"./stats-collector":35,"./suite":36,"./test":37,"./utils":38,"_process":69,"escape-string-regexp":49,"path":42}],15:[function(require,module,exports){ +module.exports={ + "diff": true, + "extension": ["js"], + "opts": "./test/mocha.opts", + "package": "./package.json", + "reporter": "spec", + "slow": 75, + "timeout": 2000, + "ui": "bdd", + "watch-ignore": ["node_modules", ".git"] +} + +},{}],16:[function(require,module,exports){ +'use strict'; + +module.exports = Pending; + +/** + * Initialize a new `Pending` error with the given message. + * + * @param {string} message + */ +function Pending(message) { + this.message = message; +} + +},{}],17:[function(require,module,exports){ +(function (process){ +'use strict'; +/** + * @module Base + */ +/** + * Module dependencies. + */ + +var tty = require('tty'); +var diff = require('diff'); +var milliseconds = require('ms'); +var utils = require('../utils'); +var supportsColor = process.browser ? null : require('supports-color'); +var constants = require('../runner').constants; +var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; +var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; + +/** + * Expose `Base`. + */ + +exports = module.exports = Base; + +/** + * Check if both stdio streams are associated with a tty. + */ + +var isatty = process.stdout.isTTY && process.stderr.isTTY; + +/** + * Save log references to avoid tests interfering (see GH-3604). + */ +var consoleLog = console.log; + +/** + * Enable coloring by default, except in the browser interface. + */ + +exports.useColors = + !process.browser && + (supportsColor.stdout || process.env.MOCHA_COLORS !== undefined); + +/** + * Inline diffs instead of +/- + */ + +exports.inlineDiffs = false; + +/** + * Default color map. + */ + +exports.colors = { + pass: 90, + fail: 31, + 'bright pass': 92, + 'bright fail': 91, + 'bright yellow': 93, + pending: 36, + suite: 0, + 'error title': 0, + 'error message': 31, + 'error stack': 90, + checkmark: 32, + fast: 90, + medium: 33, + slow: 31, + green: 32, + light: 90, + 'diff gutter': 90, + 'diff added': 32, + 'diff removed': 31 +}; + +/** + * Default symbol map. + */ + +exports.symbols = { + ok: '✓', + err: '✖', + dot: '․', + comma: ',', + bang: '!' +}; + +// With node.js on Windows: use symbols available in terminal default fonts +if (process.platform === 'win32') { + exports.symbols.ok = '\u221A'; + exports.symbols.err = '\u00D7'; + exports.symbols.dot = '.'; +} + +/** + * Color `str` with the given `type`, + * allowing colors to be disabled, + * as well as user-defined color + * schemes. + * + * @private + * @param {string} type + * @param {string} str + * @return {string} + */ +var color = (exports.color = function(type, str) { + if (!exports.useColors) { + return String(str); + } + return '\u001b[' + exports.colors[type] + 'm' + str + '\u001b[0m'; +}); + +/** + * Expose term window size, with some defaults for when stderr is not a tty. + */ + +exports.window = { + width: 75 +}; + +if (isatty) { + exports.window.width = process.stdout.getWindowSize + ? process.stdout.getWindowSize(1)[0] + : tty.getWindowSize()[1]; +} + +/** + * Expose some basic cursor interactions that are common among reporters. + */ + +exports.cursor = { + hide: function() { + isatty && process.stdout.write('\u001b[?25l'); + }, + + show: function() { + isatty && process.stdout.write('\u001b[?25h'); + }, + + deleteLine: function() { + isatty && process.stdout.write('\u001b[2K'); + }, + + beginningOfLine: function() { + isatty && process.stdout.write('\u001b[0G'); + }, + + CR: function() { + if (isatty) { + exports.cursor.deleteLine(); + exports.cursor.beginningOfLine(); + } else { + process.stdout.write('\r'); + } + } +}; + +var showDiff = (exports.showDiff = function(err) { + return ( + err && + err.showDiff !== false && + sameType(err.actual, err.expected) && + err.expected !== undefined + ); +}); + +function stringifyDiffObjs(err) { + if (!utils.isString(err.actual) || !utils.isString(err.expected)) { + err.actual = utils.stringify(err.actual); + err.expected = utils.stringify(err.expected); + } +} + +/** + * Returns a diff between 2 strings with coloured ANSI output. + * + * @description + * The diff will be either inline or unified dependent on the value + * of `Base.inlineDiff`. + * + * @param {string} actual + * @param {string} expected + * @return {string} Diff + */ +var generateDiff = (exports.generateDiff = function(actual, expected) { + try { + return exports.inlineDiffs + ? inlineDiff(actual, expected) + : unifiedDiff(actual, expected); + } catch (err) { + var msg = + '\n ' + + color('diff added', '+ expected') + + ' ' + + color('diff removed', '- actual: failed to generate Mocha diff') + + '\n'; + return msg; + } +}); + +/** + * Outputs the given `failures` as a list. + * + * @public + * @memberof Mocha.reporters.Base + * @variation 1 + * @param {Object[]} failures - Each is Test instance with corresponding + * Error property + */ +exports.list = function(failures) { + var multipleErr, multipleTest; + Base.consoleLog(); + failures.forEach(function(test, i) { + // format + var fmt = + color('error title', ' %s) %s:\n') + + color('error message', ' %s') + + color('error stack', '\n%s\n'); + + // msg + var msg; + var err; + if (test.err && test.err.multiple) { + if (multipleTest !== test) { + multipleTest = test; + multipleErr = [test.err].concat(test.err.multiple); + } + err = multipleErr.shift(); + } else { + err = test.err; + } + var message; + if (err.message && typeof err.message.toString === 'function') { + message = err.message + ''; + } else if (typeof err.inspect === 'function') { + message = err.inspect() + ''; + } else { + message = ''; + } + var stack = err.stack || message; + var index = message ? stack.indexOf(message) : -1; + + if (index === -1) { + msg = message; + } else { + index += message.length; + msg = stack.slice(0, index); + // remove msg from stack + stack = stack.slice(index + 1); + } + + // uncaught + if (err.uncaught) { + msg = 'Uncaught ' + msg; + } + // explicitly show diff + if (!exports.hideDiff && showDiff(err)) { + stringifyDiffObjs(err); + fmt = + color('error title', ' %s) %s:\n%s') + color('error stack', '\n%s\n'); + var match = message.match(/^([^:]+): expected/); + msg = '\n ' + color('error message', match ? match[1] : msg); + + msg += generateDiff(err.actual, err.expected); + } + + // indent stack trace + stack = stack.replace(/^/gm, ' '); + + // indented test title + var testTitle = ''; + test.titlePath().forEach(function(str, index) { + if (index !== 0) { + testTitle += '\n '; + } + for (var i = 0; i < index; i++) { + testTitle += ' '; + } + testTitle += str; + }); + + Base.consoleLog(fmt, i + 1, testTitle, msg, stack); + }); +}; + +/** + * Constructs a new `Base` reporter instance. + * + * @description + * All other reporters generally inherit from this reporter. + * + * @public + * @class + * @memberof Mocha.reporters + * @param {Runner} runner - Instance triggers reporter actions. + * @param {Object} [options] - runner options + */ +function Base(runner, options) { + var failures = (this.failures = []); + + if (!runner) { + throw new TypeError('Missing runner argument'); + } + this.options = options || {}; + this.runner = runner; + this.stats = runner.stats; // assigned so Reporters keep a closer reference + + runner.on(EVENT_TEST_PASS, function(test) { + if (test.duration > test.slow()) { + test.speed = 'slow'; + } else if (test.duration > test.slow() / 2) { + test.speed = 'medium'; + } else { + test.speed = 'fast'; + } + }); + + runner.on(EVENT_TEST_FAIL, function(test, err) { + if (showDiff(err)) { + stringifyDiffObjs(err); + } + // more than one error per test + if (test.err && err instanceof Error) { + test.err.multiple = (test.err.multiple || []).concat(err); + } else { + test.err = err; + } + failures.push(test); + }); +} + +/** + * Outputs common epilogue used by many of the bundled reporters. + * + * @public + * @memberof Mocha.reporters + */ +Base.prototype.epilogue = function() { + var stats = this.stats; + var fmt; + + Base.consoleLog(); + + // passes + fmt = + color('bright pass', ' ') + + color('green', ' %d passing') + + color('light', ' (%s)'); + + Base.consoleLog(fmt, stats.passes || 0, milliseconds(stats.duration)); + + // pending + if (stats.pending) { + fmt = color('pending', ' ') + color('pending', ' %d pending'); + + Base.consoleLog(fmt, stats.pending); + } + + // failures + if (stats.failures) { + fmt = color('fail', ' %d failing'); + + Base.consoleLog(fmt, stats.failures); + + Base.list(this.failures); + Base.consoleLog(); + } + + Base.consoleLog(); +}; + +/** + * Pads the given `str` to `len`. + * + * @private + * @param {string} str + * @param {string} len + * @return {string} + */ +function pad(str, len) { + str = String(str); + return Array(len - str.length + 1).join(' ') + str; +} + +/** + * Returns inline diff between 2 strings with coloured ANSI output. + * + * @private + * @param {String} actual + * @param {String} expected + * @return {string} Diff + */ +function inlineDiff(actual, expected) { + var msg = errorDiff(actual, expected); + + // linenos + var lines = msg.split('\n'); + if (lines.length > 4) { + var width = String(lines.length).length; + msg = lines + .map(function(str, i) { + return pad(++i, width) + ' |' + ' ' + str; + }) + .join('\n'); + } + + // legend + msg = + '\n' + + color('diff removed', 'actual') + + ' ' + + color('diff added', 'expected') + + '\n\n' + + msg + + '\n'; + + // indent + msg = msg.replace(/^/gm, ' '); + return msg; +} + +/** + * Returns unified diff between two strings with coloured ANSI output. + * + * @private + * @param {String} actual + * @param {String} expected + * @return {string} The diff. + */ +function unifiedDiff(actual, expected) { + var indent = ' '; + function cleanUp(line) { + if (line[0] === '+') { + return indent + colorLines('diff added', line); + } + if (line[0] === '-') { + return indent + colorLines('diff removed', line); + } + if (line.match(/@@/)) { + return '--'; + } + if (line.match(/\\ No newline/)) { + return null; + } + return indent + line; + } + function notBlank(line) { + return typeof line !== 'undefined' && line !== null; + } + var msg = diff.createPatch('string', actual, expected); + var lines = msg.split('\n').splice(5); + return ( + '\n ' + + colorLines('diff added', '+ expected') + + ' ' + + colorLines('diff removed', '- actual') + + '\n\n' + + lines + .map(cleanUp) + .filter(notBlank) + .join('\n') + ); +} + +/** + * Returns character diff for `err`. + * + * @private + * @param {String} actual + * @param {String} expected + * @return {string} the diff + */ +function errorDiff(actual, expected) { + return diff + .diffWordsWithSpace(actual, expected) + .map(function(str) { + if (str.added) { + return colorLines('diff added', str.value); + } + if (str.removed) { + return colorLines('diff removed', str.value); + } + return str.value; + }) + .join(''); +} + +/** + * Colors lines for `str`, using the color `name`. + * + * @private + * @param {string} name + * @param {string} str + * @return {string} + */ +function colorLines(name, str) { + return str + .split('\n') + .map(function(str) { + return color(name, str); + }) + .join('\n'); +} + +/** + * Object#toString reference. + */ +var objToString = Object.prototype.toString; + +/** + * Checks that a / b have the same type. + * + * @private + * @param {Object} a + * @param {Object} b + * @return {boolean} + */ +function sameType(a, b) { + return objToString.call(a) === objToString.call(b); +} + +Base.consoleLog = consoleLog; + +Base.abstract = true; + +}).call(this,require('_process')) +},{"../runner":34,"../utils":38,"_process":69,"diff":48,"ms":60,"supports-color":42,"tty":4}],18:[function(require,module,exports){ +'use strict'; +/** + * @module Doc + */ +/** + * Module dependencies. + */ + +var Base = require('./base'); +var utils = require('../utils'); +var constants = require('../runner').constants; +var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; +var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; +var EVENT_SUITE_BEGIN = constants.EVENT_SUITE_BEGIN; +var EVENT_SUITE_END = constants.EVENT_SUITE_END; + +/** + * Expose `Doc`. + */ + +exports = module.exports = Doc; + +/** + * Constructs a new `Doc` reporter instance. + * + * @public + * @class + * @memberof Mocha.reporters + * @extends Mocha.reporters.Base + * @param {Runner} runner - Instance triggers reporter actions. + * @param {Object} [options] - runner options + */ +function Doc(runner, options) { + Base.call(this, runner, options); + + var indents = 2; + + function indent() { + return Array(indents).join(' '); + } + + runner.on(EVENT_SUITE_BEGIN, function(suite) { + if (suite.root) { + return; + } + ++indents; + Base.consoleLog('%s
', indent()); + ++indents; + Base.consoleLog('%s

%s

', indent(), utils.escape(suite.title)); + Base.consoleLog('%s
', indent()); + }); + + runner.on(EVENT_SUITE_END, function(suite) { + if (suite.root) { + return; + } + Base.consoleLog('%s
', indent()); + --indents; + Base.consoleLog('%s
', indent()); + --indents; + }); + + runner.on(EVENT_TEST_PASS, function(test) { + Base.consoleLog('%s
%s
', indent(), utils.escape(test.title)); + var code = utils.escape(utils.clean(test.body)); + Base.consoleLog('%s
%s
', indent(), code); + }); + + runner.on(EVENT_TEST_FAIL, function(test, err) { + Base.consoleLog( + '%s
%s
', + indent(), + utils.escape(test.title) + ); + var code = utils.escape(utils.clean(test.body)); + Base.consoleLog( + '%s
%s
', + indent(), + code + ); + Base.consoleLog( + '%s
%s
', + indent(), + utils.escape(err) + ); + }); +} + +Doc.description = 'HTML documentation'; + +},{"../runner":34,"../utils":38,"./base":17}],19:[function(require,module,exports){ +(function (process){ +'use strict'; +/** + * @module Dot + */ +/** + * Module dependencies. + */ + +var Base = require('./base'); +var inherits = require('../utils').inherits; +var constants = require('../runner').constants; +var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; +var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; +var EVENT_RUN_BEGIN = constants.EVENT_RUN_BEGIN; +var EVENT_TEST_PENDING = constants.EVENT_TEST_PENDING; +var EVENT_RUN_END = constants.EVENT_RUN_END; + +/** + * Expose `Dot`. + */ + +exports = module.exports = Dot; + +/** + * Constructs a new `Dot` reporter instance. + * + * @public + * @class + * @memberof Mocha.reporters + * @extends Mocha.reporters.Base + * @param {Runner} runner - Instance triggers reporter actions. + * @param {Object} [options] - runner options + */ +function Dot(runner, options) { + Base.call(this, runner, options); + + var self = this; + var width = (Base.window.width * 0.75) | 0; + var n = -1; + + runner.on(EVENT_RUN_BEGIN, function() { + process.stdout.write('\n'); + }); + + runner.on(EVENT_TEST_PENDING, function() { + if (++n % width === 0) { + process.stdout.write('\n '); + } + process.stdout.write(Base.color('pending', Base.symbols.comma)); + }); + + runner.on(EVENT_TEST_PASS, function(test) { + if (++n % width === 0) { + process.stdout.write('\n '); + } + if (test.speed === 'slow') { + process.stdout.write(Base.color('bright yellow', Base.symbols.dot)); + } else { + process.stdout.write(Base.color(test.speed, Base.symbols.dot)); + } + }); + + runner.on(EVENT_TEST_FAIL, function() { + if (++n % width === 0) { + process.stdout.write('\n '); + } + process.stdout.write(Base.color('fail', Base.symbols.bang)); + }); + + runner.once(EVENT_RUN_END, function() { + process.stdout.write('\n'); + self.epilogue(); + }); +} + +/** + * Inherit from `Base.prototype`. + */ +inherits(Dot, Base); + +Dot.description = 'dot matrix representation'; + +}).call(this,require('_process')) +},{"../runner":34,"../utils":38,"./base":17,"_process":69}],20:[function(require,module,exports){ +(function (global){ +'use strict'; + +/* eslint-env browser */ +/** + * @module HTML + */ +/** + * Module dependencies. + */ + +var Base = require('./base'); +var utils = require('../utils'); +var Progress = require('../browser/progress'); +var escapeRe = require('escape-string-regexp'); +var constants = require('../runner').constants; +var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; +var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; +var EVENT_SUITE_BEGIN = constants.EVENT_SUITE_BEGIN; +var EVENT_SUITE_END = constants.EVENT_SUITE_END; +var EVENT_TEST_PENDING = constants.EVENT_TEST_PENDING; +var escape = utils.escape; + +/** + * Save timer references to avoid Sinon interfering (see GH-237). + */ + +var Date = global.Date; + +/** + * Expose `HTML`. + */ + +exports = module.exports = HTML; + +/** + * Stats template. + */ + +var statsTemplate = + ''; + +var playIcon = '‣'; + +/** + * Constructs a new `HTML` reporter instance. + * + * @public + * @class + * @memberof Mocha.reporters + * @extends Mocha.reporters.Base + * @param {Runner} runner - Instance triggers reporter actions. + * @param {Object} [options] - runner options + */ +function HTML(runner, options) { + Base.call(this, runner, options); + + var self = this; + var stats = this.stats; + var stat = fragment(statsTemplate); + var items = stat.getElementsByTagName('li'); + var passes = items[1].getElementsByTagName('em')[0]; + var passesLink = items[1].getElementsByTagName('a')[0]; + var failures = items[2].getElementsByTagName('em')[0]; + var failuresLink = items[2].getElementsByTagName('a')[0]; + var duration = items[3].getElementsByTagName('em')[0]; + var canvas = stat.getElementsByTagName('canvas')[0]; + var report = fragment('
    '); + var stack = [report]; + var progress; + var ctx; + var root = document.getElementById('mocha'); + + if (canvas.getContext) { + var ratio = window.devicePixelRatio || 1; + canvas.style.width = canvas.width; + canvas.style.height = canvas.height; + canvas.width *= ratio; + canvas.height *= ratio; + ctx = canvas.getContext('2d'); + ctx.scale(ratio, ratio); + progress = new Progress(); + } + + if (!root) { + return error('#mocha div missing, add it to your document'); + } + + // pass toggle + on(passesLink, 'click', function(evt) { + evt.preventDefault(); + unhide(); + var name = /pass/.test(report.className) ? '' : ' pass'; + report.className = report.className.replace(/fail|pass/g, '') + name; + if (report.className.trim()) { + hideSuitesWithout('test pass'); + } + }); + + // failure toggle + on(failuresLink, 'click', function(evt) { + evt.preventDefault(); + unhide(); + var name = /fail/.test(report.className) ? '' : ' fail'; + report.className = report.className.replace(/fail|pass/g, '') + name; + if (report.className.trim()) { + hideSuitesWithout('test fail'); + } + }); + + root.appendChild(stat); + root.appendChild(report); + + if (progress) { + progress.size(40); + } + + runner.on(EVENT_SUITE_BEGIN, function(suite) { + if (suite.root) { + return; + } + + // suite + var url = self.suiteURL(suite); + var el = fragment( + '
  • %s

  • ', + url, + escape(suite.title) + ); + + // container + stack[0].appendChild(el); + stack.unshift(document.createElement('ul')); + el.appendChild(stack[0]); + }); + + runner.on(EVENT_SUITE_END, function(suite) { + if (suite.root) { + updateStats(); + return; + } + stack.shift(); + }); + + runner.on(EVENT_TEST_PASS, function(test) { + var url = self.testURL(test); + var markup = + '
  • %e%ems ' + + '' + + playIcon + + '

  • '; + var el = fragment(markup, test.speed, test.title, test.duration, url); + self.addCodeToggle(el, test.body); + appendToStack(el); + updateStats(); + }); + + runner.on(EVENT_TEST_FAIL, function(test) { + var el = fragment( + '
  • %e ' + + playIcon + + '

  • ', + test.title, + self.testURL(test) + ); + var stackString; // Note: Includes leading newline + var message = test.err.toString(); + + // <=IE7 stringifies to [Object Error]. Since it can be overloaded, we + // check for the result of the stringifying. + if (message === '[object Error]') { + message = test.err.message; + } + + if (test.err.stack) { + var indexOfMessage = test.err.stack.indexOf(test.err.message); + if (indexOfMessage === -1) { + stackString = test.err.stack; + } else { + stackString = test.err.stack.substr( + test.err.message.length + indexOfMessage + ); + } + } else if (test.err.sourceURL && test.err.line !== undefined) { + // Safari doesn't give you a stack. Let's at least provide a source line. + stackString = '\n(' + test.err.sourceURL + ':' + test.err.line + ')'; + } + + stackString = stackString || ''; + + if (test.err.htmlMessage && stackString) { + el.appendChild( + fragment( + '
    %s\n
    %e
    ', + test.err.htmlMessage, + stackString + ) + ); + } else if (test.err.htmlMessage) { + el.appendChild( + fragment('
    %s
    ', test.err.htmlMessage) + ); + } else { + el.appendChild( + fragment('
    %e%e
    ', message, stackString) + ); + } + + self.addCodeToggle(el, test.body); + appendToStack(el); + updateStats(); + }); + + runner.on(EVENT_TEST_PENDING, function(test) { + var el = fragment( + '
  • %e

  • ', + test.title + ); + appendToStack(el); + updateStats(); + }); + + function appendToStack(el) { + // Don't call .appendChild if #mocha-report was already .shift()'ed off the stack. + if (stack[0]) { + stack[0].appendChild(el); + } + } + + function updateStats() { + // TODO: add to stats + var percent = ((stats.tests / runner.total) * 100) | 0; + if (progress) { + progress.update(percent).draw(ctx); + } + + // update stats + var ms = new Date() - stats.start; + text(passes, stats.passes); + text(failures, stats.failures); + text(duration, (ms / 1000).toFixed(2)); + } +} + +/** + * Makes a URL, preserving querystring ("search") parameters. + * + * @param {string} s + * @return {string} A new URL. + */ +function makeUrl(s) { + var search = window.location.search; + + // Remove previous grep query parameter if present + if (search) { + search = search.replace(/[?&]grep=[^&\s]*/g, '').replace(/^&/, '?'); + } + + return ( + window.location.pathname + + (search ? search + '&' : '?') + + 'grep=' + + encodeURIComponent(escapeRe(s)) + ); +} + +/** + * Provide suite URL. + * + * @param {Object} [suite] + */ +HTML.prototype.suiteURL = function(suite) { + return makeUrl(suite.fullTitle()); +}; + +/** + * Provide test URL. + * + * @param {Object} [test] + */ +HTML.prototype.testURL = function(test) { + return makeUrl(test.fullTitle()); +}; + +/** + * Adds code toggle functionality for the provided test's list element. + * + * @param {HTMLLIElement} el + * @param {string} contents + */ +HTML.prototype.addCodeToggle = function(el, contents) { + var h2 = el.getElementsByTagName('h2')[0]; + + on(h2, 'click', function() { + pre.style.display = pre.style.display === 'none' ? 'block' : 'none'; + }); + + var pre = fragment('
    %e
    ', utils.clean(contents)); + el.appendChild(pre); + pre.style.display = 'none'; +}; + +/** + * Display error `msg`. + * + * @param {string} msg + */ +function error(msg) { + document.body.appendChild(fragment('
    %s
    ', msg)); +} + +/** + * Return a DOM fragment from `html`. + * + * @param {string} html + */ +function fragment(html) { + var args = arguments; + var div = document.createElement('div'); + var i = 1; + + div.innerHTML = html.replace(/%([se])/g, function(_, type) { + switch (type) { + case 's': + return String(args[i++]); + case 'e': + return escape(args[i++]); + // no default + } + }); + + return div.firstChild; +} + +/** + * Check for suites that do not have elements + * with `classname`, and hide them. + * + * @param {text} classname + */ +function hideSuitesWithout(classname) { + var suites = document.getElementsByClassName('suite'); + for (var i = 0; i < suites.length; i++) { + var els = suites[i].getElementsByClassName(classname); + if (!els.length) { + suites[i].className += ' hidden'; + } + } +} + +/** + * Unhide .hidden suites. + */ +function unhide() { + var els = document.getElementsByClassName('suite hidden'); + while (els.length > 0) { + els[0].className = els[0].className.replace('suite hidden', 'suite'); + } +} + +/** + * Set an element's text contents. + * + * @param {HTMLElement} el + * @param {string} contents + */ +function text(el, contents) { + if (el.textContent) { + el.textContent = contents; + } else { + el.innerText = contents; + } +} + +/** + * Listen on `event` with callback `fn`. + */ +function on(el, event, fn) { + if (el.addEventListener) { + el.addEventListener(event, fn, false); + } else { + el.attachEvent('on' + event, fn); + } +} + +HTML.browserOnly = true; + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"../browser/progress":3,"../runner":34,"../utils":38,"./base":17,"escape-string-regexp":49}],21:[function(require,module,exports){ +'use strict'; + +// Alias exports to a their normalized format Mocha#reporter to prevent a need +// for dynamic (try/catch) requires, which Browserify doesn't handle. +exports.Base = exports.base = require('./base'); +exports.Dot = exports.dot = require('./dot'); +exports.Doc = exports.doc = require('./doc'); +exports.TAP = exports.tap = require('./tap'); +exports.JSON = exports.json = require('./json'); +exports.HTML = exports.html = require('./html'); +exports.List = exports.list = require('./list'); +exports.Min = exports.min = require('./min'); +exports.Spec = exports.spec = require('./spec'); +exports.Nyan = exports.nyan = require('./nyan'); +exports.XUnit = exports.xunit = require('./xunit'); +exports.Markdown = exports.markdown = require('./markdown'); +exports.Progress = exports.progress = require('./progress'); +exports.Landing = exports.landing = require('./landing'); +exports.JSONStream = exports['json-stream'] = require('./json-stream'); + +},{"./base":17,"./doc":18,"./dot":19,"./html":20,"./json":23,"./json-stream":22,"./landing":24,"./list":25,"./markdown":26,"./min":27,"./nyan":28,"./progress":29,"./spec":30,"./tap":31,"./xunit":32}],22:[function(require,module,exports){ +(function (process){ +'use strict'; +/** + * @module JSONStream + */ +/** + * Module dependencies. + */ + +var Base = require('./base'); +var constants = require('../runner').constants; +var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; +var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; +var EVENT_RUN_BEGIN = constants.EVENT_RUN_BEGIN; +var EVENT_RUN_END = constants.EVENT_RUN_END; + +/** + * Expose `JSONStream`. + */ + +exports = module.exports = JSONStream; + +/** + * Constructs a new `JSONStream` reporter instance. + * + * @public + * @class + * @memberof Mocha.reporters + * @extends Mocha.reporters.Base + * @param {Runner} runner - Instance triggers reporter actions. + * @param {Object} [options] - runner options + */ +function JSONStream(runner, options) { + Base.call(this, runner, options); + + var self = this; + var total = runner.total; + + runner.once(EVENT_RUN_BEGIN, function() { + writeEvent(['start', {total: total}]); + }); + + runner.on(EVENT_TEST_PASS, function(test) { + writeEvent(['pass', clean(test)]); + }); + + runner.on(EVENT_TEST_FAIL, function(test, err) { + test = clean(test); + test.err = err.message; + test.stack = err.stack || null; + writeEvent(['fail', test]); + }); + + runner.once(EVENT_RUN_END, function() { + writeEvent(['end', self.stats]); + }); +} + +/** + * Mocha event to be written to the output stream. + * @typedef {Array} JSONStream~MochaEvent + */ + +/** + * Writes Mocha event to reporter output stream. + * + * @private + * @param {JSONStream~MochaEvent} event - Mocha event to be output. + */ +function writeEvent(event) { + process.stdout.write(JSON.stringify(event) + '\n'); +} + +/** + * Returns an object literal representation of `test` + * free of cyclic properties, etc. + * + * @private + * @param {Test} test - Instance used as data source. + * @return {Object} object containing pared-down test instance data + */ +function clean(test) { + return { + title: test.title, + fullTitle: test.fullTitle(), + duration: test.duration, + currentRetry: test.currentRetry() + }; +} + +JSONStream.description = 'newline delimited JSON events'; + +}).call(this,require('_process')) +},{"../runner":34,"./base":17,"_process":69}],23:[function(require,module,exports){ +(function (process){ +'use strict'; +/** + * @module JSON + */ +/** + * Module dependencies. + */ + +var Base = require('./base'); +var constants = require('../runner').constants; +var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; +var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; +var EVENT_TEST_END = constants.EVENT_TEST_END; +var EVENT_RUN_END = constants.EVENT_RUN_END; +var EVENT_TEST_PENDING = constants.EVENT_TEST_PENDING; + +/** + * Expose `JSON`. + */ + +exports = module.exports = JSONReporter; + +/** + * Constructs a new `JSON` reporter instance. + * + * @public + * @class JSON + * @memberof Mocha.reporters + * @extends Mocha.reporters.Base + * @param {Runner} runner - Instance triggers reporter actions. + * @param {Object} [options] - runner options + */ +function JSONReporter(runner, options) { + Base.call(this, runner, options); + + var self = this; + var tests = []; + var pending = []; + var failures = []; + var passes = []; + + runner.on(EVENT_TEST_END, function(test) { + tests.push(test); + }); + + runner.on(EVENT_TEST_PASS, function(test) { + passes.push(test); + }); + + runner.on(EVENT_TEST_FAIL, function(test) { + failures.push(test); + }); + + runner.on(EVENT_TEST_PENDING, function(test) { + pending.push(test); + }); + + runner.once(EVENT_RUN_END, function() { + var obj = { + stats: self.stats, + tests: tests.map(clean), + pending: pending.map(clean), + failures: failures.map(clean), + passes: passes.map(clean) + }; + + runner.testResults = obj; + + process.stdout.write(JSON.stringify(obj, null, 2)); + }); +} + +/** + * Return a plain-object representation of `test` + * free of cyclic properties etc. + * + * @private + * @param {Object} test + * @return {Object} + */ +function clean(test) { + var err = test.err || {}; + if (err instanceof Error) { + err = errorJSON(err); + } + + return { + title: test.title, + fullTitle: test.fullTitle(), + duration: test.duration, + currentRetry: test.currentRetry(), + err: cleanCycles(err) + }; +} + +/** + * Replaces any circular references inside `obj` with '[object Object]' + * + * @private + * @param {Object} obj + * @return {Object} + */ +function cleanCycles(obj) { + var cache = []; + return JSON.parse( + JSON.stringify(obj, function(key, value) { + if (typeof value === 'object' && value !== null) { + if (cache.indexOf(value) !== -1) { + // Instead of going in a circle, we'll print [object Object] + return '' + value; + } + cache.push(value); + } + + return value; + }) + ); +} + +/** + * Transform an Error object into a JSON object. + * + * @private + * @param {Error} err + * @return {Object} + */ +function errorJSON(err) { + var res = {}; + Object.getOwnPropertyNames(err).forEach(function(key) { + res[key] = err[key]; + }, err); + return res; +} + +JSONReporter.description = 'single JSON object'; + +}).call(this,require('_process')) +},{"../runner":34,"./base":17,"_process":69}],24:[function(require,module,exports){ +(function (process){ +'use strict'; +/** + * @module Landing + */ +/** + * Module dependencies. + */ + +var Base = require('./base'); +var inherits = require('../utils').inherits; +var constants = require('../runner').constants; +var EVENT_RUN_BEGIN = constants.EVENT_RUN_BEGIN; +var EVENT_RUN_END = constants.EVENT_RUN_END; +var EVENT_TEST_END = constants.EVENT_TEST_END; +var STATE_FAILED = require('../runnable').constants.STATE_FAILED; + +var cursor = Base.cursor; +var color = Base.color; + +/** + * Expose `Landing`. + */ + +exports = module.exports = Landing; + +/** + * Airplane color. + */ + +Base.colors.plane = 0; + +/** + * Airplane crash color. + */ + +Base.colors['plane crash'] = 31; + +/** + * Runway color. + */ + +Base.colors.runway = 90; + +/** + * Constructs a new `Landing` reporter instance. + * + * @public + * @class + * @memberof Mocha.reporters + * @extends Mocha.reporters.Base + * @param {Runner} runner - Instance triggers reporter actions. + * @param {Object} [options] - runner options + */ +function Landing(runner, options) { + Base.call(this, runner, options); + + var self = this; + var width = (Base.window.width * 0.75) | 0; + var total = runner.total; + var stream = process.stdout; + var plane = color('plane', '✈'); + var crashed = -1; + var n = 0; + + function runway() { + var buf = Array(width).join('-'); + return ' ' + color('runway', buf); + } + + runner.on(EVENT_RUN_BEGIN, function() { + stream.write('\n\n\n '); + cursor.hide(); + }); + + runner.on(EVENT_TEST_END, function(test) { + // check if the plane crashed + var col = crashed === -1 ? ((width * ++n) / total) | 0 : crashed; + + // show the crash + if (test.state === STATE_FAILED) { + plane = color('plane crash', '✈'); + crashed = col; + } + + // render landing strip + stream.write('\u001b[' + (width + 1) + 'D\u001b[2A'); + stream.write(runway()); + stream.write('\n '); + stream.write(color('runway', Array(col).join('⋅'))); + stream.write(plane); + stream.write(color('runway', Array(width - col).join('⋅') + '\n')); + stream.write(runway()); + stream.write('\u001b[0m'); + }); + + runner.once(EVENT_RUN_END, function() { + cursor.show(); + process.stdout.write('\n'); + self.epilogue(); + }); +} + +/** + * Inherit from `Base.prototype`. + */ +inherits(Landing, Base); + +Landing.description = 'Unicode landing strip'; + +}).call(this,require('_process')) +},{"../runnable":33,"../runner":34,"../utils":38,"./base":17,"_process":69}],25:[function(require,module,exports){ +(function (process){ +'use strict'; +/** + * @module List + */ +/** + * Module dependencies. + */ + +var Base = require('./base'); +var inherits = require('../utils').inherits; +var constants = require('../runner').constants; +var EVENT_RUN_BEGIN = constants.EVENT_RUN_BEGIN; +var EVENT_RUN_END = constants.EVENT_RUN_END; +var EVENT_TEST_BEGIN = constants.EVENT_TEST_BEGIN; +var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; +var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; +var EVENT_TEST_PENDING = constants.EVENT_TEST_PENDING; +var color = Base.color; +var cursor = Base.cursor; + +/** + * Expose `List`. + */ + +exports = module.exports = List; + +/** + * Constructs a new `List` reporter instance. + * + * @public + * @class + * @memberof Mocha.reporters + * @extends Mocha.reporters.Base + * @param {Runner} runner - Instance triggers reporter actions. + * @param {Object} [options] - runner options + */ +function List(runner, options) { + Base.call(this, runner, options); + + var self = this; + var n = 0; + + runner.on(EVENT_RUN_BEGIN, function() { + Base.consoleLog(); + }); + + runner.on(EVENT_TEST_BEGIN, function(test) { + process.stdout.write(color('pass', ' ' + test.fullTitle() + ': ')); + }); + + runner.on(EVENT_TEST_PENDING, function(test) { + var fmt = color('checkmark', ' -') + color('pending', ' %s'); + Base.consoleLog(fmt, test.fullTitle()); + }); + + runner.on(EVENT_TEST_PASS, function(test) { + var fmt = + color('checkmark', ' ' + Base.symbols.ok) + + color('pass', ' %s: ') + + color(test.speed, '%dms'); + cursor.CR(); + Base.consoleLog(fmt, test.fullTitle(), test.duration); + }); + + runner.on(EVENT_TEST_FAIL, function(test) { + cursor.CR(); + Base.consoleLog(color('fail', ' %d) %s'), ++n, test.fullTitle()); + }); + + runner.once(EVENT_RUN_END, self.epilogue.bind(self)); +} + +/** + * Inherit from `Base.prototype`. + */ +inherits(List, Base); + +List.description = 'like "spec" reporter but flat'; + +}).call(this,require('_process')) +},{"../runner":34,"../utils":38,"./base":17,"_process":69}],26:[function(require,module,exports){ +(function (process){ +'use strict'; +/** + * @module Markdown + */ +/** + * Module dependencies. + */ + +var Base = require('./base'); +var utils = require('../utils'); +var constants = require('../runner').constants; +var EVENT_RUN_END = constants.EVENT_RUN_END; +var EVENT_SUITE_BEGIN = constants.EVENT_SUITE_BEGIN; +var EVENT_SUITE_END = constants.EVENT_SUITE_END; +var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; + +/** + * Constants + */ + +var SUITE_PREFIX = '$'; + +/** + * Expose `Markdown`. + */ + +exports = module.exports = Markdown; + +/** + * Constructs a new `Markdown` reporter instance. + * + * @public + * @class + * @memberof Mocha.reporters + * @extends Mocha.reporters.Base + * @param {Runner} runner - Instance triggers reporter actions. + * @param {Object} [options] - runner options + */ +function Markdown(runner, options) { + Base.call(this, runner, options); + + var level = 0; + var buf = ''; + + function title(str) { + return Array(level).join('#') + ' ' + str; + } + + function mapTOC(suite, obj) { + var ret = obj; + var key = SUITE_PREFIX + suite.title; + + obj = obj[key] = obj[key] || {suite: suite}; + suite.suites.forEach(function(suite) { + mapTOC(suite, obj); + }); + + return ret; + } + + function stringifyTOC(obj, level) { + ++level; + var buf = ''; + var link; + for (var key in obj) { + if (key === 'suite') { + continue; + } + if (key !== SUITE_PREFIX) { + link = ' - [' + key.substring(1) + ']'; + link += '(#' + utils.slug(obj[key].suite.fullTitle()) + ')\n'; + buf += Array(level).join(' ') + link; + } + buf += stringifyTOC(obj[key], level); + } + return buf; + } + + function generateTOC(suite) { + var obj = mapTOC(suite, {}); + return stringifyTOC(obj, 0); + } + + generateTOC(runner.suite); + + runner.on(EVENT_SUITE_BEGIN, function(suite) { + ++level; + var slug = utils.slug(suite.fullTitle()); + buf += '' + '\n'; + buf += title(suite.title) + '\n'; + }); + + runner.on(EVENT_SUITE_END, function() { + --level; + }); + + runner.on(EVENT_TEST_PASS, function(test) { + var code = utils.clean(test.body); + buf += test.title + '.\n'; + buf += '\n```js\n'; + buf += code + '\n'; + buf += '```\n\n'; + }); + + runner.once(EVENT_RUN_END, function() { + process.stdout.write('# TOC\n'); + process.stdout.write(generateTOC(runner.suite)); + process.stdout.write(buf); + }); +} + +Markdown.description = 'GitHub Flavored Markdown'; + +}).call(this,require('_process')) +},{"../runner":34,"../utils":38,"./base":17,"_process":69}],27:[function(require,module,exports){ +(function (process){ +'use strict'; +/** + * @module Min + */ +/** + * Module dependencies. + */ + +var Base = require('./base'); +var inherits = require('../utils').inherits; +var constants = require('../runner').constants; +var EVENT_RUN_END = constants.EVENT_RUN_END; +var EVENT_RUN_BEGIN = constants.EVENT_RUN_BEGIN; + +/** + * Expose `Min`. + */ + +exports = module.exports = Min; + +/** + * Constructs a new `Min` reporter instance. + * + * @description + * This minimal test reporter is best used with '--watch'. + * + * @public + * @class + * @memberof Mocha.reporters + * @extends Mocha.reporters.Base + * @param {Runner} runner - Instance triggers reporter actions. + * @param {Object} [options] - runner options + */ +function Min(runner, options) { + Base.call(this, runner, options); + + runner.on(EVENT_RUN_BEGIN, function() { + // clear screen + process.stdout.write('\u001b[2J'); + // set cursor position + process.stdout.write('\u001b[1;3H'); + }); + + runner.once(EVENT_RUN_END, this.epilogue.bind(this)); +} + +/** + * Inherit from `Base.prototype`. + */ +inherits(Min, Base); + +Min.description = 'essentially just a summary'; + +}).call(this,require('_process')) +},{"../runner":34,"../utils":38,"./base":17,"_process":69}],28:[function(require,module,exports){ +(function (process){ +'use strict'; +/** + * @module Nyan + */ +/** + * Module dependencies. + */ + +var Base = require('./base'); +var constants = require('../runner').constants; +var inherits = require('../utils').inherits; +var EVENT_RUN_BEGIN = constants.EVENT_RUN_BEGIN; +var EVENT_TEST_PENDING = constants.EVENT_TEST_PENDING; +var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; +var EVENT_RUN_END = constants.EVENT_RUN_END; +var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; + +/** + * Expose `Dot`. + */ + +exports = module.exports = NyanCat; + +/** + * Constructs a new `Nyan` reporter instance. + * + * @public + * @class Nyan + * @memberof Mocha.reporters + * @extends Mocha.reporters.Base + * @param {Runner} runner - Instance triggers reporter actions. + * @param {Object} [options] - runner options + */ +function NyanCat(runner, options) { + Base.call(this, runner, options); + + var self = this; + var width = (Base.window.width * 0.75) | 0; + var nyanCatWidth = (this.nyanCatWidth = 11); + + this.colorIndex = 0; + this.numberOfLines = 4; + this.rainbowColors = self.generateColors(); + this.scoreboardWidth = 5; + this.tick = 0; + this.trajectories = [[], [], [], []]; + this.trajectoryWidthMax = width - nyanCatWidth; + + runner.on(EVENT_RUN_BEGIN, function() { + Base.cursor.hide(); + self.draw(); + }); + + runner.on(EVENT_TEST_PENDING, function() { + self.draw(); + }); + + runner.on(EVENT_TEST_PASS, function() { + self.draw(); + }); + + runner.on(EVENT_TEST_FAIL, function() { + self.draw(); + }); + + runner.once(EVENT_RUN_END, function() { + Base.cursor.show(); + for (var i = 0; i < self.numberOfLines; i++) { + write('\n'); + } + self.epilogue(); + }); +} + +/** + * Inherit from `Base.prototype`. + */ +inherits(NyanCat, Base); + +/** + * Draw the nyan cat + * + * @private + */ + +NyanCat.prototype.draw = function() { + this.appendRainbow(); + this.drawScoreboard(); + this.drawRainbow(); + this.drawNyanCat(); + this.tick = !this.tick; +}; + +/** + * Draw the "scoreboard" showing the number + * of passes, failures and pending tests. + * + * @private + */ + +NyanCat.prototype.drawScoreboard = function() { + var stats = this.stats; + + function draw(type, n) { + write(' '); + write(Base.color(type, n)); + write('\n'); + } + + draw('green', stats.passes); + draw('fail', stats.failures); + draw('pending', stats.pending); + write('\n'); + + this.cursorUp(this.numberOfLines); +}; + +/** + * Append the rainbow. + * + * @private + */ + +NyanCat.prototype.appendRainbow = function() { + var segment = this.tick ? '_' : '-'; + var rainbowified = this.rainbowify(segment); + + for (var index = 0; index < this.numberOfLines; index++) { + var trajectory = this.trajectories[index]; + if (trajectory.length >= this.trajectoryWidthMax) { + trajectory.shift(); + } + trajectory.push(rainbowified); + } +}; + +/** + * Draw the rainbow. + * + * @private + */ + +NyanCat.prototype.drawRainbow = function() { + var self = this; + + this.trajectories.forEach(function(line) { + write('\u001b[' + self.scoreboardWidth + 'C'); + write(line.join('')); + write('\n'); + }); + + this.cursorUp(this.numberOfLines); +}; + +/** + * Draw the nyan cat + * + * @private + */ +NyanCat.prototype.drawNyanCat = function() { + var self = this; + var startWidth = this.scoreboardWidth + this.trajectories[0].length; + var dist = '\u001b[' + startWidth + 'C'; + var padding = ''; + + write(dist); + write('_,------,'); + write('\n'); + + write(dist); + padding = self.tick ? ' ' : ' '; + write('_|' + padding + '/\\_/\\ '); + write('\n'); + + write(dist); + padding = self.tick ? '_' : '__'; + var tail = self.tick ? '~' : '^'; + write(tail + '|' + padding + this.face() + ' '); + write('\n'); + + write(dist); + padding = self.tick ? ' ' : ' '; + write(padding + '"" "" '); + write('\n'); + + this.cursorUp(this.numberOfLines); +}; + +/** + * Draw nyan cat face. + * + * @private + * @return {string} + */ + +NyanCat.prototype.face = function() { + var stats = this.stats; + if (stats.failures) { + return '( x .x)'; + } else if (stats.pending) { + return '( o .o)'; + } else if (stats.passes) { + return '( ^ .^)'; + } + return '( - .-)'; +}; + +/** + * Move cursor up `n`. + * + * @private + * @param {number} n + */ + +NyanCat.prototype.cursorUp = function(n) { + write('\u001b[' + n + 'A'); +}; + +/** + * Move cursor down `n`. + * + * @private + * @param {number} n + */ + +NyanCat.prototype.cursorDown = function(n) { + write('\u001b[' + n + 'B'); +}; + +/** + * Generate rainbow colors. + * + * @private + * @return {Array} + */ +NyanCat.prototype.generateColors = function() { + var colors = []; + + for (var i = 0; i < 6 * 7; i++) { + var pi3 = Math.floor(Math.PI / 3); + var n = i * (1.0 / 6); + var r = Math.floor(3 * Math.sin(n) + 3); + var g = Math.floor(3 * Math.sin(n + 2 * pi3) + 3); + var b = Math.floor(3 * Math.sin(n + 4 * pi3) + 3); + colors.push(36 * r + 6 * g + b + 16); + } + + return colors; +}; + +/** + * Apply rainbow to the given `str`. + * + * @private + * @param {string} str + * @return {string} + */ +NyanCat.prototype.rainbowify = function(str) { + if (!Base.useColors) { + return str; + } + var color = this.rainbowColors[this.colorIndex % this.rainbowColors.length]; + this.colorIndex += 1; + return '\u001b[38;5;' + color + 'm' + str + '\u001b[0m'; +}; + +/** + * Stdout helper. + * + * @param {string} string A message to write to stdout. + */ +function write(string) { + process.stdout.write(string); +} + +NyanCat.description = '"nyan cat"'; + +}).call(this,require('_process')) +},{"../runner":34,"../utils":38,"./base":17,"_process":69}],29:[function(require,module,exports){ +(function (process){ +'use strict'; +/** + * @module Progress + */ +/** + * Module dependencies. + */ + +var Base = require('./base'); +var constants = require('../runner').constants; +var EVENT_RUN_BEGIN = constants.EVENT_RUN_BEGIN; +var EVENT_TEST_END = constants.EVENT_TEST_END; +var EVENT_RUN_END = constants.EVENT_RUN_END; +var inherits = require('../utils').inherits; +var color = Base.color; +var cursor = Base.cursor; + +/** + * Expose `Progress`. + */ + +exports = module.exports = Progress; + +/** + * General progress bar color. + */ + +Base.colors.progress = 90; + +/** + * Constructs a new `Progress` reporter instance. + * + * @public + * @class + * @memberof Mocha.reporters + * @extends Mocha.reporters.Base + * @param {Runner} runner - Instance triggers reporter actions. + * @param {Object} [options] - runner options + */ +function Progress(runner, options) { + Base.call(this, runner, options); + + var self = this; + var width = (Base.window.width * 0.5) | 0; + var total = runner.total; + var complete = 0; + var lastN = -1; + + // default chars + options = options || {}; + var reporterOptions = options.reporterOptions || {}; + + options.open = reporterOptions.open || '['; + options.complete = reporterOptions.complete || '▬'; + options.incomplete = reporterOptions.incomplete || Base.symbols.dot; + options.close = reporterOptions.close || ']'; + options.verbose = reporterOptions.verbose || false; + + // tests started + runner.on(EVENT_RUN_BEGIN, function() { + process.stdout.write('\n'); + cursor.hide(); + }); + + // tests complete + runner.on(EVENT_TEST_END, function() { + complete++; + + var percent = complete / total; + var n = (width * percent) | 0; + var i = width - n; + + if (n === lastN && !options.verbose) { + // Don't re-render the line if it hasn't changed + return; + } + lastN = n; + + cursor.CR(); + process.stdout.write('\u001b[J'); + process.stdout.write(color('progress', ' ' + options.open)); + process.stdout.write(Array(n).join(options.complete)); + process.stdout.write(Array(i).join(options.incomplete)); + process.stdout.write(color('progress', options.close)); + if (options.verbose) { + process.stdout.write(color('progress', ' ' + complete + ' of ' + total)); + } + }); + + // tests are complete, output some stats + // and the failures if any + runner.once(EVENT_RUN_END, function() { + cursor.show(); + process.stdout.write('\n'); + self.epilogue(); + }); +} + +/** + * Inherit from `Base.prototype`. + */ +inherits(Progress, Base); + +Progress.description = 'a progress bar'; + +}).call(this,require('_process')) +},{"../runner":34,"../utils":38,"./base":17,"_process":69}],30:[function(require,module,exports){ +'use strict'; +/** + * @module Spec + */ +/** + * Module dependencies. + */ + +var Base = require('./base'); +var constants = require('../runner').constants; +var EVENT_RUN_BEGIN = constants.EVENT_RUN_BEGIN; +var EVENT_RUN_END = constants.EVENT_RUN_END; +var EVENT_SUITE_BEGIN = constants.EVENT_SUITE_BEGIN; +var EVENT_SUITE_END = constants.EVENT_SUITE_END; +var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; +var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; +var EVENT_TEST_PENDING = constants.EVENT_TEST_PENDING; +var inherits = require('../utils').inherits; +var color = Base.color; + +/** + * Expose `Spec`. + */ + +exports = module.exports = Spec; + +/** + * Constructs a new `Spec` reporter instance. + * + * @public + * @class + * @memberof Mocha.reporters + * @extends Mocha.reporters.Base + * @param {Runner} runner - Instance triggers reporter actions. + * @param {Object} [options] - runner options + */ +function Spec(runner, options) { + Base.call(this, runner, options); + + var self = this; + var indents = 0; + var n = 0; + + function indent() { + return Array(indents).join(' '); + } + + runner.on(EVENT_RUN_BEGIN, function() { + Base.consoleLog(); + }); + + runner.on(EVENT_SUITE_BEGIN, function(suite) { + ++indents; + Base.consoleLog(color('suite', '%s%s'), indent(), suite.title); + }); + + runner.on(EVENT_SUITE_END, function() { + --indents; + if (indents === 1) { + Base.consoleLog(); + } + }); + + runner.on(EVENT_TEST_PENDING, function(test) { + var fmt = indent() + color('pending', ' - %s'); + Base.consoleLog(fmt, test.title); + }); + + runner.on(EVENT_TEST_PASS, function(test) { + var fmt; + if (test.speed === 'fast') { + fmt = + indent() + + color('checkmark', ' ' + Base.symbols.ok) + + color('pass', ' %s'); + Base.consoleLog(fmt, test.title); + } else { + fmt = + indent() + + color('checkmark', ' ' + Base.symbols.ok) + + color('pass', ' %s') + + color(test.speed, ' (%dms)'); + Base.consoleLog(fmt, test.title, test.duration); + } + }); + + runner.on(EVENT_TEST_FAIL, function(test) { + Base.consoleLog(indent() + color('fail', ' %d) %s'), ++n, test.title); + }); + + runner.once(EVENT_RUN_END, self.epilogue.bind(self)); +} + +/** + * Inherit from `Base.prototype`. + */ +inherits(Spec, Base); + +Spec.description = 'hierarchical & verbose [default]'; + +},{"../runner":34,"../utils":38,"./base":17}],31:[function(require,module,exports){ +(function (process){ +'use strict'; +/** + * @module TAP + */ +/** + * Module dependencies. + */ + +var util = require('util'); +var Base = require('./base'); +var constants = require('../runner').constants; +var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; +var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; +var EVENT_RUN_BEGIN = constants.EVENT_RUN_BEGIN; +var EVENT_RUN_END = constants.EVENT_RUN_END; +var EVENT_TEST_PENDING = constants.EVENT_TEST_PENDING; +var EVENT_TEST_END = constants.EVENT_TEST_END; +var inherits = require('../utils').inherits; +var sprintf = util.format; + +/** + * Expose `TAP`. + */ + +exports = module.exports = TAP; + +/** + * Constructs a new `TAP` reporter instance. + * + * @public + * @class + * @memberof Mocha.reporters + * @extends Mocha.reporters.Base + * @param {Runner} runner - Instance triggers reporter actions. + * @param {Object} [options] - runner options + */ +function TAP(runner, options) { + Base.call(this, runner, options); + + var self = this; + var n = 1; + + var tapVersion = '12'; + if (options && options.reporterOptions) { + if (options.reporterOptions.tapVersion) { + tapVersion = options.reporterOptions.tapVersion.toString(); + } + } + + this._producer = createProducer(tapVersion); + + runner.once(EVENT_RUN_BEGIN, function() { + var ntests = runner.grepTotal(runner.suite); + self._producer.writeVersion(); + self._producer.writePlan(ntests); + }); + + runner.on(EVENT_TEST_END, function() { + ++n; + }); + + runner.on(EVENT_TEST_PENDING, function(test) { + self._producer.writePending(n, test); + }); + + runner.on(EVENT_TEST_PASS, function(test) { + self._producer.writePass(n, test); + }); + + runner.on(EVENT_TEST_FAIL, function(test, err) { + self._producer.writeFail(n, test, err); + }); + + runner.once(EVENT_RUN_END, function() { + self._producer.writeEpilogue(runner.stats); + }); +} + +/** + * Inherit from `Base.prototype`. + */ +inherits(TAP, Base); + +/** + * Returns a TAP-safe title of `test`. + * + * @private + * @param {Test} test - Test instance. + * @return {String} title with any hash character removed + */ +function title(test) { + return test.fullTitle().replace(/#/g, ''); +} + +/** + * Writes newline-terminated formatted string to reporter output stream. + * + * @private + * @param {string} format - `printf`-like format string + * @param {...*} [varArgs] - Format string arguments + */ +function println(format, varArgs) { + var vargs = Array.from(arguments); + vargs[0] += '\n'; + process.stdout.write(sprintf.apply(null, vargs)); +} + +/** + * Returns a `tapVersion`-appropriate TAP producer instance, if possible. + * + * @private + * @param {string} tapVersion - Version of TAP specification to produce. + * @returns {TAPProducer} specification-appropriate instance + * @throws {Error} if specification version has no associated producer. + */ +function createProducer(tapVersion) { + var producers = { + '12': new TAP12Producer(), + '13': new TAP13Producer() + }; + var producer = producers[tapVersion]; + + if (!producer) { + throw new Error( + 'invalid or unsupported TAP version: ' + JSON.stringify(tapVersion) + ); + } + + return producer; +} + +/** + * @summary + * Constructs a new TAPProducer. + * + * @description + * Only to be used as an abstract base class. + * + * @private + * @constructor + */ +function TAPProducer() {} + +/** + * Writes the TAP version to reporter output stream. + * + * @abstract + */ +TAPProducer.prototype.writeVersion = function() {}; + +/** + * Writes the plan to reporter output stream. + * + * @abstract + * @param {number} ntests - Number of tests that are planned to run. + */ +TAPProducer.prototype.writePlan = function(ntests) { + println('%d..%d', 1, ntests); +}; + +/** + * Writes that test passed to reporter output stream. + * + * @abstract + * @param {number} n - Index of test that passed. + * @param {Test} test - Instance containing test information. + */ +TAPProducer.prototype.writePass = function(n, test) { + println('ok %d %s', n, title(test)); +}; + +/** + * Writes that test was skipped to reporter output stream. + * + * @abstract + * @param {number} n - Index of test that was skipped. + * @param {Test} test - Instance containing test information. + */ +TAPProducer.prototype.writePending = function(n, test) { + println('ok %d %s # SKIP -', n, title(test)); +}; + +/** + * Writes that test failed to reporter output stream. + * + * @abstract + * @param {number} n - Index of test that failed. + * @param {Test} test - Instance containing test information. + * @param {Error} err - Reason the test failed. + */ +TAPProducer.prototype.writeFail = function(n, test, err) { + println('not ok %d %s', n, title(test)); +}; + +/** + * Writes the summary epilogue to reporter output stream. + * + * @abstract + * @param {Object} stats - Object containing run statistics. + */ +TAPProducer.prototype.writeEpilogue = function(stats) { + // :TBD: Why is this not counting pending tests? + println('# tests ' + (stats.passes + stats.failures)); + println('# pass ' + stats.passes); + // :TBD: Why are we not showing pending results? + println('# fail ' + stats.failures); +}; + +/** + * @summary + * Constructs a new TAP12Producer. + * + * @description + * Produces output conforming to the TAP12 specification. + * + * @private + * @constructor + * @extends TAPProducer + * @see {@link https://testanything.org/tap-specification.html|Specification} + */ +function TAP12Producer() { + /** + * Writes that test failed to reporter output stream, with error formatting. + * @override + */ + this.writeFail = function(n, test, err) { + TAPProducer.prototype.writeFail.call(this, n, test, err); + if (err.message) { + println(err.message.replace(/^/gm, ' ')); + } + if (err.stack) { + println(err.stack.replace(/^/gm, ' ')); + } + }; +} + +/** + * Inherit from `TAPProducer.prototype`. + */ +inherits(TAP12Producer, TAPProducer); + +/** + * @summary + * Constructs a new TAP13Producer. + * + * @description + * Produces output conforming to the TAP13 specification. + * + * @private + * @constructor + * @extends TAPProducer + * @see {@link https://testanything.org/tap-version-13-specification.html|Specification} + */ +function TAP13Producer() { + /** + * Writes the TAP version to reporter output stream. + * @override + */ + this.writeVersion = function() { + println('TAP version 13'); + }; + + /** + * Writes that test failed to reporter output stream, with error formatting. + * @override + */ + this.writeFail = function(n, test, err) { + TAPProducer.prototype.writeFail.call(this, n, test, err); + var emitYamlBlock = err.message != null || err.stack != null; + if (emitYamlBlock) { + println(indent(1) + '---'); + if (err.message) { + println(indent(2) + 'message: |-'); + println(err.message.replace(/^/gm, indent(3))); + } + if (err.stack) { + println(indent(2) + 'stack: |-'); + println(err.stack.replace(/^/gm, indent(3))); + } + println(indent(1) + '...'); + } + }; + + function indent(level) { + return Array(level + 1).join(' '); + } +} + +/** + * Inherit from `TAPProducer.prototype`. + */ +inherits(TAP13Producer, TAPProducer); + +TAP.description = 'TAP-compatible output'; + +}).call(this,require('_process')) +},{"../runner":34,"../utils":38,"./base":17,"_process":69,"util":89}],32:[function(require,module,exports){ +(function (process,global){ +'use strict'; +/** + * @module XUnit + */ +/** + * Module dependencies. + */ + +var Base = require('./base'); +var utils = require('../utils'); +var fs = require('fs'); +var mkdirp = require('mkdirp'); +var path = require('path'); +var errors = require('../errors'); +var createUnsupportedError = errors.createUnsupportedError; +var constants = require('../runner').constants; +var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; +var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; +var EVENT_RUN_END = constants.EVENT_RUN_END; +var EVENT_TEST_PENDING = constants.EVENT_TEST_PENDING; +var STATE_FAILED = require('../runnable').constants.STATE_FAILED; +var inherits = utils.inherits; +var escape = utils.escape; + +/** + * Save timer references to avoid Sinon interfering (see GH-237). + */ +var Date = global.Date; + +/** + * Expose `XUnit`. + */ + +exports = module.exports = XUnit; + +/** + * Constructs a new `XUnit` reporter instance. + * + * @public + * @class + * @memberof Mocha.reporters + * @extends Mocha.reporters.Base + * @param {Runner} runner - Instance triggers reporter actions. + * @param {Object} [options] - runner options + */ +function XUnit(runner, options) { + Base.call(this, runner, options); + + var stats = this.stats; + var tests = []; + var self = this; + + // the name of the test suite, as it will appear in the resulting XML file + var suiteName; + + // the default name of the test suite if none is provided + var DEFAULT_SUITE_NAME = 'Mocha Tests'; + + if (options && options.reporterOptions) { + if (options.reporterOptions.output) { + if (!fs.createWriteStream) { + throw createUnsupportedError('file output not supported in browser'); + } + + mkdirp.sync(path.dirname(options.reporterOptions.output)); + self.fileStream = fs.createWriteStream(options.reporterOptions.output); + } + + // get the suite name from the reporter options (if provided) + suiteName = options.reporterOptions.suiteName; + } + + // fall back to the default suite name + suiteName = suiteName || DEFAULT_SUITE_NAME; + + runner.on(EVENT_TEST_PENDING, function(test) { + tests.push(test); + }); + + runner.on(EVENT_TEST_PASS, function(test) { + tests.push(test); + }); + + runner.on(EVENT_TEST_FAIL, function(test) { + tests.push(test); + }); + + runner.once(EVENT_RUN_END, function() { + self.write( + tag( + 'testsuite', + { + name: suiteName, + tests: stats.tests, + failures: 0, + errors: stats.failures, + skipped: stats.tests - stats.failures - stats.passes, + timestamp: new Date().toUTCString(), + time: stats.duration / 1000 || 0 + }, + false + ) + ); + + tests.forEach(function(t) { + self.test(t); + }); + + self.write(''); + }); +} + +/** + * Inherit from `Base.prototype`. + */ +inherits(XUnit, Base); + +/** + * Override done to close the stream (if it's a file). + * + * @param failures + * @param {Function} fn + */ +XUnit.prototype.done = function(failures, fn) { + if (this.fileStream) { + this.fileStream.end(function() { + fn(failures); + }); + } else { + fn(failures); + } +}; + +/** + * Write out the given line. + * + * @param {string} line + */ +XUnit.prototype.write = function(line) { + if (this.fileStream) { + this.fileStream.write(line + '\n'); + } else if (typeof process === 'object' && process.stdout) { + process.stdout.write(line + '\n'); + } else { + Base.consoleLog(line); + } +}; + +/** + * Output tag for the given `test.` + * + * @param {Test} test + */ +XUnit.prototype.test = function(test) { + Base.useColors = false; + + var attrs = { + classname: test.parent.fullTitle(), + name: test.title, + time: test.duration / 1000 || 0 + }; + + if (test.state === STATE_FAILED) { + var err = test.err; + var diff = + !Base.hideDiff && Base.showDiff(err) + ? '\n' + Base.generateDiff(err.actual, err.expected) + : ''; + this.write( + tag( + 'testcase', + attrs, + false, + tag( + 'failure', + {}, + false, + escape(err.message) + escape(diff) + '\n' + escape(err.stack) + ) + ) + ); + } else if (test.isPending()) { + this.write(tag('testcase', attrs, false, tag('skipped', {}, true))); + } else { + this.write(tag('testcase', attrs, true)); + } +}; + +/** + * HTML tag helper. + * + * @param name + * @param attrs + * @param close + * @param content + * @return {string} + */ +function tag(name, attrs, close, content) { + var end = close ? '/>' : '>'; + var pairs = []; + var tag; + + for (var key in attrs) { + if (Object.prototype.hasOwnProperty.call(attrs, key)) { + pairs.push(key + '="' + escape(attrs[key]) + '"'); + } + } + + tag = '<' + name + (pairs.length ? ' ' + pairs.join(' ') : '') + end; + if (content) { + tag += content + '0, 2^31-1]. + * If clamped value matches either range endpoint, timeouts will be disabled. + * + * @private + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout#Maximum_delay_value} + * @param {number|string} ms - Timeout threshold value. + * @returns {Runnable} this + * @chainable + */ +Runnable.prototype.timeout = function(ms) { + if (!arguments.length) { + return this._timeout; + } + if (typeof ms === 'string') { + ms = milliseconds(ms); + } + + // Clamp to range + var INT_MAX = Math.pow(2, 31) - 1; + var range = [0, INT_MAX]; + ms = utils.clamp(ms, range); + + // see #1652 for reasoning + if (ms === range[0] || ms === range[1]) { + this._enableTimeouts = false; + } + debug('timeout %d', ms); + this._timeout = ms; + if (this.timer) { + this.resetTimeout(); + } + return this; +}; + +/** + * Set or get slow `ms`. + * + * @private + * @param {number|string} ms + * @return {Runnable|number} ms or Runnable instance. + */ +Runnable.prototype.slow = function(ms) { + if (!arguments.length || typeof ms === 'undefined') { + return this._slow; + } + if (typeof ms === 'string') { + ms = milliseconds(ms); + } + debug('slow %d', ms); + this._slow = ms; + return this; +}; + +/** + * Set and get whether timeout is `enabled`. + * + * @private + * @param {boolean} enabled + * @return {Runnable|boolean} enabled or Runnable instance. + */ +Runnable.prototype.enableTimeouts = function(enabled) { + if (!arguments.length) { + return this._enableTimeouts; + } + debug('enableTimeouts %s', enabled); + this._enableTimeouts = enabled; + return this; +}; + +/** + * Halt and mark as pending. + * + * @memberof Mocha.Runnable + * @public + */ +Runnable.prototype.skip = function() { + this.pending = true; + throw new Pending('sync skip; aborting execution'); +}; + +/** + * Check if this runnable or its parent suite is marked as pending. + * + * @private + */ +Runnable.prototype.isPending = function() { + return this.pending || (this.parent && this.parent.isPending()); +}; + +/** + * Return `true` if this Runnable has failed. + * @return {boolean} + * @private + */ +Runnable.prototype.isFailed = function() { + return !this.isPending() && this.state === constants.STATE_FAILED; +}; + +/** + * Return `true` if this Runnable has passed. + * @return {boolean} + * @private + */ +Runnable.prototype.isPassed = function() { + return !this.isPending() && this.state === constants.STATE_PASSED; +}; + +/** + * Set or get number of retries. + * + * @private + */ +Runnable.prototype.retries = function(n) { + if (!arguments.length) { + return this._retries; + } + this._retries = n; +}; + +/** + * Set or get current retry + * + * @private + */ +Runnable.prototype.currentRetry = function(n) { + if (!arguments.length) { + return this._currentRetry; + } + this._currentRetry = n; +}; + +/** + * Return the full title generated by recursively concatenating the parent's + * full title. + * + * @memberof Mocha.Runnable + * @public + * @return {string} + */ +Runnable.prototype.fullTitle = function() { + return this.titlePath().join(' '); +}; + +/** + * Return the title path generated by concatenating the parent's title path with the title. + * + * @memberof Mocha.Runnable + * @public + * @return {string} + */ +Runnable.prototype.titlePath = function() { + return this.parent.titlePath().concat([this.title]); +}; + +/** + * Clear the timeout. + * + * @private + */ +Runnable.prototype.clearTimeout = function() { + clearTimeout(this.timer); +}; + +/** + * Inspect the runnable void of private properties. + * + * @private + * @return {string} + */ +Runnable.prototype.inspect = function() { + return JSON.stringify( + this, + function(key, val) { + if (key[0] === '_') { + return; + } + if (key === 'parent') { + return '#'; + } + if (key === 'ctx') { + return '#'; + } + return val; + }, + 2 + ); +}; + +/** + * Reset the timeout. + * + * @private + */ +Runnable.prototype.resetTimeout = function() { + var self = this; + var ms = this.timeout() || 1e9; + + if (!this._enableTimeouts) { + return; + } + this.clearTimeout(); + this.timer = setTimeout(function() { + if (!self._enableTimeouts) { + return; + } + self.callback(self._timeoutError(ms)); + self.timedOut = true; + }, ms); +}; + +/** + * Set or get a list of whitelisted globals for this test run. + * + * @private + * @param {string[]} globals + */ +Runnable.prototype.globals = function(globals) { + if (!arguments.length) { + return this._allowedGlobals; + } + this._allowedGlobals = globals; +}; + +/** + * Run the test and invoke `fn(err)`. + * + * @param {Function} fn + * @private + */ +Runnable.prototype.run = function(fn) { + var self = this; + var start = new Date(); + var ctx = this.ctx; + var finished; + var emitted; + + // Sometimes the ctx exists, but it is not runnable + if (ctx && ctx.runnable) { + ctx.runnable(this); + } + + // called multiple times + function multiple(err) { + if (emitted) { + return; + } + emitted = true; + var msg = 'done() called multiple times'; + if (err && err.message) { + err.message += " (and Mocha's " + msg + ')'; + self.emit('error', err); + } else { + self.emit('error', new Error(msg)); + } + } + + // finished + function done(err) { + var ms = self.timeout(); + if (self.timedOut) { + return; + } + + if (finished) { + return multiple(err); + } + + self.clearTimeout(); + self.duration = new Date() - start; + finished = true; + if (!err && self.duration > ms && self._enableTimeouts) { + err = self._timeoutError(ms); + } + fn(err); + } + + // for .resetTimeout() and Runner#uncaught() + this.callback = done; + + if (this.fn && typeof this.fn.call !== 'function') { + done( + new TypeError( + 'A runnable must be passed a function as its second argument.' + ) + ); + return; + } + + // explicit async with `done` argument + if (this.async) { + this.resetTimeout(); + + // allows skip() to be used in an explicit async context + this.skip = function asyncSkip() { + this.pending = true; + done(); + // halt execution, the uncaught handler will ignore the failure. + throw new Pending('async skip; aborting execution'); + }; + + try { + callFnAsync(this.fn); + } catch (err) { + // handles async runnables which actually run synchronously + emitted = true; + if (err instanceof Pending) { + return; // done() is already called in this.skip() + } else if (this.allowUncaught) { + throw err; + } + done(Runnable.toValueOrError(err)); + } + return; + } + + // sync or promise-returning + try { + if (this.isPending()) { + done(); + } else { + callFn(this.fn); + } + } catch (err) { + emitted = true; + if (err instanceof Pending) { + return done(); + } else if (this.allowUncaught) { + throw err; + } + done(Runnable.toValueOrError(err)); + } + + function callFn(fn) { + var result = fn.call(ctx); + if (result && typeof result.then === 'function') { + self.resetTimeout(); + result.then( + function() { + done(); + // Return null so libraries like bluebird do not warn about + // subsequently constructed Promises. + return null; + }, + function(reason) { + done(reason || new Error('Promise rejected with no or falsy reason')); + } + ); + } else { + if (self.asyncOnly) { + return done( + new Error( + '--async-only option in use without declaring `done()` or returning a promise' + ) + ); + } + + done(); + } + } + + function callFnAsync(fn) { + var result = fn.call(ctx, function(err) { + if (err instanceof Error || toString.call(err) === '[object Error]') { + return done(err); + } + if (err) { + if (Object.prototype.toString.call(err) === '[object Object]') { + return done( + new Error('done() invoked with non-Error: ' + JSON.stringify(err)) + ); + } + return done(new Error('done() invoked with non-Error: ' + err)); + } + if (result && utils.isPromise(result)) { + return done( + new Error( + 'Resolution method is overspecified. Specify a callback *or* return a Promise; not both.' + ) + ); + } + + done(); + }); + } +}; + +/** + * Instantiates a "timeout" error + * + * @param {number} ms - Timeout (in milliseconds) + * @returns {Error} a "timeout" error + * @private + */ +Runnable.prototype._timeoutError = function(ms) { + var msg = + 'Timeout of ' + + ms + + 'ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.'; + if (this.file) { + msg += ' (' + this.file + ')'; + } + return new Error(msg); +}; + +var constants = utils.defineConstants( + /** + * {@link Runnable}-related constants. + * @public + * @memberof Runnable + * @readonly + * @static + * @alias constants + * @enum {string} + */ + { + /** + * Value of `state` prop when a `Runnable` has failed + */ + STATE_FAILED: 'failed', + /** + * Value of `state` prop when a `Runnable` has passed + */ + STATE_PASSED: 'passed' + } +); + +/** + * Given `value`, return identity if truthy, otherwise create an "invalid exception" error and return that. + * @param {*} [value] - Value to return, if present + * @returns {*|Error} `value`, otherwise an `Error` + * @private + */ +Runnable.toValueOrError = function(value) { + return ( + value || + createInvalidExceptionError( + 'Runnable failed with falsy or undefined exception. Please throw an Error instead.', + value + ) + ); +}; + +Runnable.constants = constants; + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./errors":6,"./pending":16,"./utils":38,"debug":45,"events":50,"ms":60}],34:[function(require,module,exports){ +(function (process,global){ +'use strict'; + +/** + * Module dependencies. + */ +var util = require('util'); +var EventEmitter = require('events').EventEmitter; +var Pending = require('./pending'); +var utils = require('./utils'); +var inherits = utils.inherits; +var debug = require('debug')('mocha:runner'); +var Runnable = require('./runnable'); +var Suite = require('./suite'); +var HOOK_TYPE_BEFORE_EACH = Suite.constants.HOOK_TYPE_BEFORE_EACH; +var HOOK_TYPE_AFTER_EACH = Suite.constants.HOOK_TYPE_AFTER_EACH; +var HOOK_TYPE_AFTER_ALL = Suite.constants.HOOK_TYPE_AFTER_ALL; +var HOOK_TYPE_BEFORE_ALL = Suite.constants.HOOK_TYPE_BEFORE_ALL; +var EVENT_ROOT_SUITE_RUN = Suite.constants.EVENT_ROOT_SUITE_RUN; +var STATE_FAILED = Runnable.constants.STATE_FAILED; +var STATE_PASSED = Runnable.constants.STATE_PASSED; +var dQuote = utils.dQuote; +var ngettext = utils.ngettext; +var sQuote = utils.sQuote; +var stackFilter = utils.stackTraceFilter(); +var stringify = utils.stringify; +var type = utils.type; +var errors = require('./errors'); +var createInvalidExceptionError = errors.createInvalidExceptionError; +var createUnsupportedError = errors.createUnsupportedError; + +/** + * Non-enumerable globals. + * @readonly + */ +var globals = [ + 'setTimeout', + 'clearTimeout', + 'setInterval', + 'clearInterval', + 'XMLHttpRequest', + 'Date', + 'setImmediate', + 'clearImmediate' +]; + +var constants = utils.defineConstants( + /** + * {@link Runner}-related constants. + * @public + * @memberof Runner + * @readonly + * @alias constants + * @static + * @enum {string} + */ + { + /** + * Emitted when {@link Hook} execution begins + */ + EVENT_HOOK_BEGIN: 'hook', + /** + * Emitted when {@link Hook} execution ends + */ + EVENT_HOOK_END: 'hook end', + /** + * Emitted when Root {@link Suite} execution begins (all files have been parsed and hooks/tests are ready for execution) + */ + EVENT_RUN_BEGIN: 'start', + /** + * Emitted when Root {@link Suite} execution has been delayed via `delay` option + */ + EVENT_DELAY_BEGIN: 'waiting', + /** + * Emitted when delayed Root {@link Suite} execution is triggered by user via `global.run()` + */ + EVENT_DELAY_END: 'ready', + /** + * Emitted when Root {@link Suite} execution ends + */ + EVENT_RUN_END: 'end', + /** + * Emitted when {@link Suite} execution begins + */ + EVENT_SUITE_BEGIN: 'suite', + /** + * Emitted when {@link Suite} execution ends + */ + EVENT_SUITE_END: 'suite end', + /** + * Emitted when {@link Test} execution begins + */ + EVENT_TEST_BEGIN: 'test', + /** + * Emitted when {@link Test} execution ends + */ + EVENT_TEST_END: 'test end', + /** + * Emitted when {@link Test} execution fails + */ + EVENT_TEST_FAIL: 'fail', + /** + * Emitted when {@link Test} execution succeeds + */ + EVENT_TEST_PASS: 'pass', + /** + * Emitted when {@link Test} becomes pending + */ + EVENT_TEST_PENDING: 'pending', + /** + * Emitted when {@link Test} execution has failed, but will retry + */ + EVENT_TEST_RETRY: 'retry' + } +); + +module.exports = Runner; + +/** + * Initialize a `Runner` at the Root {@link Suite}, which represents a hierarchy of {@link Suite|Suites} and {@link Test|Tests}. + * + * @extends external:EventEmitter + * @public + * @class + * @param {Suite} suite Root suite + * @param {boolean} [delay] Whether or not to delay execution of root suite + * until ready. + */ +function Runner(suite, delay) { + var self = this; + this._globals = []; + this._abort = false; + this._delay = delay; + this.suite = suite; + this.started = false; + this.total = suite.total(); + this.failures = 0; + this.on(constants.EVENT_TEST_END, function(test) { + self.checkGlobals(test); + }); + this.on(constants.EVENT_HOOK_END, function(hook) { + self.checkGlobals(hook); + }); + this._defaultGrep = /.*/; + this.grep(this._defaultGrep); + this.globals(this.globalProps()); +} + +/** + * Wrapper for setImmediate, process.nextTick, or browser polyfill. + * + * @param {Function} fn + * @private + */ +Runner.immediately = global.setImmediate || process.nextTick; + +/** + * Inherit from `EventEmitter.prototype`. + */ +inherits(Runner, EventEmitter); + +/** + * Run tests with full titles matching `re`. Updates runner.total + * with number of tests matched. + * + * @public + * @memberof Runner + * @param {RegExp} re + * @param {boolean} invert + * @return {Runner} Runner instance. + */ +Runner.prototype.grep = function(re, invert) { + debug('grep %s', re); + this._grep = re; + this._invert = invert; + this.total = this.grepTotal(this.suite); + return this; +}; + +/** + * Returns the number of tests matching the grep search for the + * given suite. + * + * @memberof Runner + * @public + * @param {Suite} suite + * @return {number} + */ +Runner.prototype.grepTotal = function(suite) { + var self = this; + var total = 0; + + suite.eachTest(function(test) { + var match = self._grep.test(test.fullTitle()); + if (self._invert) { + match = !match; + } + if (match) { + total++; + } + }); + + return total; +}; + +/** + * Return a list of global properties. + * + * @return {Array} + * @private + */ +Runner.prototype.globalProps = function() { + var props = Object.keys(global); + + // non-enumerables + for (var i = 0; i < globals.length; ++i) { + if (~props.indexOf(globals[i])) { + continue; + } + props.push(globals[i]); + } + + return props; +}; + +/** + * Allow the given `arr` of globals. + * + * @public + * @memberof Runner + * @param {Array} arr + * @return {Runner} Runner instance. + */ +Runner.prototype.globals = function(arr) { + if (!arguments.length) { + return this._globals; + } + debug('globals %j', arr); + this._globals = this._globals.concat(arr); + return this; +}; + +/** + * Check for global variable leaks. + * + * @private + */ +Runner.prototype.checkGlobals = function(test) { + if (!this.checkLeaks) { + return; + } + var ok = this._globals; + + var globals = this.globalProps(); + var leaks; + + if (test) { + ok = ok.concat(test._allowedGlobals || []); + } + + if (this.prevGlobalsLength === globals.length) { + return; + } + this.prevGlobalsLength = globals.length; + + leaks = filterLeaks(ok, globals); + this._globals = this._globals.concat(leaks); + + if (leaks.length) { + var format = ngettext( + leaks.length, + 'global leak detected: %s', + 'global leaks detected: %s' + ); + var error = new Error(util.format(format, leaks.map(sQuote).join(', '))); + this.fail(test, error); + } +}; + +/** + * Fail the given `test`. + * + * @private + * @param {Test} test + * @param {Error} err + */ +Runner.prototype.fail = function(test, err) { + if (test.isPending()) { + return; + } + + ++this.failures; + test.state = STATE_FAILED; + + if (!isError(err)) { + err = thrown2Error(err); + } + + try { + err.stack = + this.fullStackTrace || !err.stack ? err.stack : stackFilter(err.stack); + } catch (ignore) { + // some environments do not take kindly to monkeying with the stack + } + + this.emit(constants.EVENT_TEST_FAIL, test, err); +}; + +/** + * Fail the given `hook` with `err`. + * + * Hook failures work in the following pattern: + * - If bail, run corresponding `after each` and `after` hooks, + * then exit + * - Failed `before` hook skips all tests in a suite and subsuites, + * but jumps to corresponding `after` hook + * - Failed `before each` hook skips remaining tests in a + * suite and jumps to corresponding `after each` hook, + * which is run only once + * - Failed `after` hook does not alter execution order + * - Failed `after each` hook skips remaining tests in a + * suite and subsuites, but executes other `after each` + * hooks + * + * @private + * @param {Hook} hook + * @param {Error} err + */ +Runner.prototype.failHook = function(hook, err) { + hook.originalTitle = hook.originalTitle || hook.title; + if (hook.ctx && hook.ctx.currentTest) { + hook.title = + hook.originalTitle + ' for ' + dQuote(hook.ctx.currentTest.title); + } else { + var parentTitle; + if (hook.parent.title) { + parentTitle = hook.parent.title; + } else { + parentTitle = hook.parent.root ? '{root}' : ''; + } + hook.title = hook.originalTitle + ' in ' + dQuote(parentTitle); + } + + this.fail(hook, err); +}; + +/** + * Run hook `name` callbacks and then invoke `fn()`. + * + * @private + * @param {string} name + * @param {Function} fn + */ + +Runner.prototype.hook = function(name, fn) { + var suite = this.suite; + var hooks = suite.getHooks(name); + var self = this; + + function next(i) { + var hook = hooks[i]; + if (!hook) { + return fn(); + } + self.currentRunnable = hook; + + if (name === HOOK_TYPE_BEFORE_ALL) { + hook.ctx.currentTest = hook.parent.tests[0]; + } else if (name === HOOK_TYPE_AFTER_ALL) { + hook.ctx.currentTest = hook.parent.tests[hook.parent.tests.length - 1]; + } else { + hook.ctx.currentTest = self.test; + } + + hook.allowUncaught = self.allowUncaught; + + self.emit(constants.EVENT_HOOK_BEGIN, hook); + + if (!hook.listeners('error').length) { + hook.on('error', function(err) { + self.failHook(hook, err); + }); + } + + hook.run(function(err) { + var testError = hook.error(); + if (testError) { + self.fail(self.test, testError); + } + // conditional skip + if (hook.pending) { + if (name === HOOK_TYPE_AFTER_EACH) { + // TODO define and implement use case + if (self.test) { + self.test.pending = true; + } + } else if (name === HOOK_TYPE_BEFORE_EACH) { + if (self.test) { + self.test.pending = true; + } + self.emit(constants.EVENT_HOOK_END, hook); + hook.pending = false; // activates hook for next test + return fn(new Error('abort hookDown')); + } else if (name === HOOK_TYPE_BEFORE_ALL) { + suite.tests.forEach(function(test) { + test.pending = true; + }); + suite.suites.forEach(function(suite) { + suite.pending = true; + }); + } else { + hook.pending = false; + var errForbid = createUnsupportedError('`this.skip` forbidden'); + self.failHook(hook, errForbid); + return fn(errForbid); + } + } else if (err) { + self.failHook(hook, err); + // stop executing hooks, notify callee of hook err + return fn(err); + } + self.emit(constants.EVENT_HOOK_END, hook); + delete hook.ctx.currentTest; + next(++i); + }); + } + + Runner.immediately(function() { + next(0); + }); +}; + +/** + * Run hook `name` for the given array of `suites` + * in order, and callback `fn(err, errSuite)`. + * + * @private + * @param {string} name + * @param {Array} suites + * @param {Function} fn + */ +Runner.prototype.hooks = function(name, suites, fn) { + var self = this; + var orig = this.suite; + + function next(suite) { + self.suite = suite; + + if (!suite) { + self.suite = orig; + return fn(); + } + + self.hook(name, function(err) { + if (err) { + var errSuite = self.suite; + self.suite = orig; + return fn(err, errSuite); + } + + next(suites.pop()); + }); + } + + next(suites.pop()); +}; + +/** + * Run hooks from the top level down. + * + * @param {String} name + * @param {Function} fn + * @private + */ +Runner.prototype.hookUp = function(name, fn) { + var suites = [this.suite].concat(this.parents()).reverse(); + this.hooks(name, suites, fn); +}; + +/** + * Run hooks from the bottom up. + * + * @param {String} name + * @param {Function} fn + * @private + */ +Runner.prototype.hookDown = function(name, fn) { + var suites = [this.suite].concat(this.parents()); + this.hooks(name, suites, fn); +}; + +/** + * Return an array of parent Suites from + * closest to furthest. + * + * @return {Array} + * @private + */ +Runner.prototype.parents = function() { + var suite = this.suite; + var suites = []; + while (suite.parent) { + suite = suite.parent; + suites.push(suite); + } + return suites; +}; + +/** + * Run the current test and callback `fn(err)`. + * + * @param {Function} fn + * @private + */ +Runner.prototype.runTest = function(fn) { + var self = this; + var test = this.test; + + if (!test) { + return; + } + + var suite = this.parents().reverse()[0] || this.suite; + if (this.forbidOnly && suite.hasOnly()) { + fn(new Error('`.only` forbidden')); + return; + } + if (this.asyncOnly) { + test.asyncOnly = true; + } + test.on('error', function(err) { + if (err instanceof Pending) { + return; + } + self.fail(test, err); + }); + if (this.allowUncaught) { + test.allowUncaught = true; + return test.run(fn); + } + try { + test.run(fn); + } catch (err) { + fn(err); + } +}; + +/** + * Run tests in the given `suite` and invoke the callback `fn()` when complete. + * + * @private + * @param {Suite} suite + * @param {Function} fn + */ +Runner.prototype.runTests = function(suite, fn) { + var self = this; + var tests = suite.tests.slice(); + var test; + + function hookErr(_, errSuite, after) { + // before/after Each hook for errSuite failed: + var orig = self.suite; + + // for failed 'after each' hook start from errSuite parent, + // otherwise start from errSuite itself + self.suite = after ? errSuite.parent : errSuite; + + if (self.suite) { + // call hookUp afterEach + self.hookUp(HOOK_TYPE_AFTER_EACH, function(err2, errSuite2) { + self.suite = orig; + // some hooks may fail even now + if (err2) { + return hookErr(err2, errSuite2, true); + } + // report error suite + fn(errSuite); + }); + } else { + // there is no need calling other 'after each' hooks + self.suite = orig; + fn(errSuite); + } + } + + function next(err, errSuite) { + // if we bail after first err + if (self.failures && suite._bail) { + tests = []; + } + + if (self._abort) { + return fn(); + } + + if (err) { + return hookErr(err, errSuite, true); + } + + // next test + test = tests.shift(); + + // all done + if (!test) { + return fn(); + } + + // grep + var match = self._grep.test(test.fullTitle()); + if (self._invert) { + match = !match; + } + if (!match) { + // Run immediately only if we have defined a grep. When we + // define a grep — It can cause maximum callstack error if + // the grep is doing a large recursive loop by neglecting + // all tests. The run immediately function also comes with + // a performance cost. So we don't want to run immediately + // if we run the whole test suite, because running the whole + // test suite don't do any immediate recursive loops. Thus, + // allowing a JS runtime to breathe. + if (self._grep !== self._defaultGrep) { + Runner.immediately(next); + } else { + next(); + } + return; + } + + // static skip, no hooks are executed + if (test.isPending()) { + if (self.forbidPending) { + test.isPending = alwaysFalse; + self.fail(test, new Error('Pending test forbidden')); + delete test.isPending; + } else { + self.emit(constants.EVENT_TEST_PENDING, test); + } + self.emit(constants.EVENT_TEST_END, test); + return next(); + } + + // execute test and hook(s) + self.emit(constants.EVENT_TEST_BEGIN, (self.test = test)); + self.hookDown(HOOK_TYPE_BEFORE_EACH, function(err, errSuite) { + // conditional skip within beforeEach + if (test.isPending()) { + if (self.forbidPending) { + test.isPending = alwaysFalse; + self.fail(test, new Error('Pending test forbidden')); + delete test.isPending; + } else { + self.emit(constants.EVENT_TEST_PENDING, test); + } + self.emit(constants.EVENT_TEST_END, test); + // skip inner afterEach hooks below errSuite level + var origSuite = self.suite; + self.suite = errSuite || self.suite; + return self.hookUp(HOOK_TYPE_AFTER_EACH, function(e, eSuite) { + self.suite = origSuite; + next(e, eSuite); + }); + } + if (err) { + return hookErr(err, errSuite, false); + } + self.currentRunnable = self.test; + self.runTest(function(err) { + test = self.test; + // conditional skip within it + if (test.pending) { + if (self.forbidPending) { + test.isPending = alwaysFalse; + self.fail(test, new Error('Pending test forbidden')); + delete test.isPending; + } else { + self.emit(constants.EVENT_TEST_PENDING, test); + } + self.emit(constants.EVENT_TEST_END, test); + return self.hookUp(HOOK_TYPE_AFTER_EACH, next); + } else if (err) { + var retry = test.currentRetry(); + if (retry < test.retries()) { + var clonedTest = test.clone(); + clonedTest.currentRetry(retry + 1); + tests.unshift(clonedTest); + + self.emit(constants.EVENT_TEST_RETRY, test, err); + + // Early return + hook trigger so that it doesn't + // increment the count wrong + return self.hookUp(HOOK_TYPE_AFTER_EACH, next); + } else { + self.fail(test, err); + } + self.emit(constants.EVENT_TEST_END, test); + return self.hookUp(HOOK_TYPE_AFTER_EACH, next); + } + + test.state = STATE_PASSED; + self.emit(constants.EVENT_TEST_PASS, test); + self.emit(constants.EVENT_TEST_END, test); + self.hookUp(HOOK_TYPE_AFTER_EACH, next); + }); + }); + } + + this.next = next; + this.hookErr = hookErr; + next(); +}; + +function alwaysFalse() { + return false; +} + +/** + * Run the given `suite` and invoke the callback `fn()` when complete. + * + * @private + * @param {Suite} suite + * @param {Function} fn + */ +Runner.prototype.runSuite = function(suite, fn) { + var i = 0; + var self = this; + var total = this.grepTotal(suite); + + debug('run suite %s', suite.fullTitle()); + + if (!total || (self.failures && suite._bail)) { + return fn(); + } + + this.emit(constants.EVENT_SUITE_BEGIN, (this.suite = suite)); + + function next(errSuite) { + if (errSuite) { + // current suite failed on a hook from errSuite + if (errSuite === suite) { + // if errSuite is current suite + // continue to the next sibling suite + return done(); + } + // errSuite is among the parents of current suite + // stop execution of errSuite and all sub-suites + return done(errSuite); + } + + if (self._abort) { + return done(); + } + + var curr = suite.suites[i++]; + if (!curr) { + return done(); + } + + // Avoid grep neglecting large number of tests causing a + // huge recursive loop and thus a maximum call stack error. + // See comment in `this.runTests()` for more information. + if (self._grep !== self._defaultGrep) { + Runner.immediately(function() { + self.runSuite(curr, next); + }); + } else { + self.runSuite(curr, next); + } + } + + function done(errSuite) { + self.suite = suite; + self.nextSuite = next; + + // remove reference to test + delete self.test; + + self.hook(HOOK_TYPE_AFTER_ALL, function() { + self.emit(constants.EVENT_SUITE_END, suite); + fn(errSuite); + }); + } + + this.nextSuite = next; + + this.hook(HOOK_TYPE_BEFORE_ALL, function(err) { + if (err) { + return done(); + } + self.runTests(suite, next); + }); +}; + +/** + * Handle uncaught exceptions within runner. + * + * @param {Error} err + * @private + */ +Runner.prototype.uncaught = function(err) { + if (err instanceof Pending) { + return; + } + if (this.allowUncaught) { + throw err; + } + + if (err) { + debug('uncaught exception %O', err); + } else { + debug('uncaught undefined/falsy exception'); + err = createInvalidExceptionError( + 'Caught falsy/undefined exception which would otherwise be uncaught. No stack trace found; try a debugger', + err + ); + } + + if (!isError(err)) { + err = thrown2Error(err); + } + err.uncaught = true; + + var runnable = this.currentRunnable; + + if (!runnable) { + runnable = new Runnable('Uncaught error outside test suite'); + runnable.parent = this.suite; + + if (this.started) { + this.fail(runnable, err); + } else { + // Can't recover from this failure + this.emit(constants.EVENT_RUN_BEGIN); + this.fail(runnable, err); + this.emit(constants.EVENT_RUN_END); + } + + return; + } + + runnable.clearTimeout(); + + if (runnable.isFailed()) { + // Ignore error if already failed + return; + } else if (runnable.isPending()) { + // report 'pending test' retrospectively as failed + runnable.isPending = alwaysFalse; + this.fail(runnable, err); + delete runnable.isPending; + return; + } + + // we cannot recover gracefully if a Runnable has already passed + // then fails asynchronously + if (runnable.isPassed()) { + this.fail(runnable, err); + this.abort(); + } else { + debug(runnable); + return runnable.callback(err); + } +}; + +/** + * Handle uncaught exceptions after runner's end event. + * + * @param {Error} err + * @private + */ +Runner.prototype.uncaughtEnd = function uncaughtEnd(err) { + if (err instanceof Pending) return; + throw err; +}; + +/** + * Run the root suite and invoke `fn(failures)` + * on completion. + * + * @public + * @memberof Runner + * @param {Function} fn + * @return {Runner} Runner instance. + */ +Runner.prototype.run = function(fn) { + var self = this; + var rootSuite = this.suite; + + fn = fn || function() {}; + + function uncaught(err) { + self.uncaught(err); + } + + function start() { + // If there is an `only` filter + if (rootSuite.hasOnly()) { + rootSuite.filterOnly(); + } + self.started = true; + if (self._delay) { + self.emit(constants.EVENT_DELAY_END); + } + self.emit(constants.EVENT_RUN_BEGIN); + + self.runSuite(rootSuite, function() { + debug('finished running'); + self.emit(constants.EVENT_RUN_END); + }); + } + + debug(constants.EVENT_RUN_BEGIN); + + // references cleanup to avoid memory leaks + this.on(constants.EVENT_SUITE_END, function(suite) { + suite.cleanReferences(); + }); + + // callback + this.on(constants.EVENT_RUN_END, function() { + debug(constants.EVENT_RUN_END); + process.removeListener('uncaughtException', uncaught); + process.on('uncaughtException', self.uncaughtEnd); + fn(self.failures); + }); + + // uncaught exception + process.removeListener('uncaughtException', self.uncaughtEnd); + process.on('uncaughtException', uncaught); + + if (this._delay) { + // for reporters, I guess. + // might be nice to debounce some dots while we wait. + this.emit(constants.EVENT_DELAY_BEGIN, rootSuite); + rootSuite.once(EVENT_ROOT_SUITE_RUN, start); + } else { + start(); + } + + return this; +}; + +/** + * Cleanly abort execution. + * + * @memberof Runner + * @public + * @return {Runner} Runner instance. + */ +Runner.prototype.abort = function() { + debug('aborting'); + this._abort = true; + + return this; +}; + +/** + * Filter leaks with the given globals flagged as `ok`. + * + * @private + * @param {Array} ok + * @param {Array} globals + * @return {Array} + */ +function filterLeaks(ok, globals) { + return globals.filter(function(key) { + // Firefox and Chrome exposes iframes as index inside the window object + if (/^\d+/.test(key)) { + return false; + } + + // in firefox + // if runner runs in an iframe, this iframe's window.getInterface method + // not init at first it is assigned in some seconds + if (global.navigator && /^getInterface/.test(key)) { + return false; + } + + // an iframe could be approached by window[iframeIndex] + // in ie6,7,8 and opera, iframeIndex is enumerable, this could cause leak + if (global.navigator && /^\d+/.test(key)) { + return false; + } + + // Opera and IE expose global variables for HTML element IDs (issue #243) + if (/^mocha-/.test(key)) { + return false; + } + + var matched = ok.filter(function(ok) { + if (~ok.indexOf('*')) { + return key.indexOf(ok.split('*')[0]) === 0; + } + return key === ok; + }); + return !matched.length && (!global.navigator || key !== 'onerror'); + }); +} + +/** + * Check if argument is an instance of Error object or a duck-typed equivalent. + * + * @private + * @param {Object} err - object to check + * @param {string} err.message - error message + * @returns {boolean} + */ +function isError(err) { + return err instanceof Error || (err && typeof err.message === 'string'); +} + +/** + * + * Converts thrown non-extensible type into proper Error. + * + * @private + * @param {*} thrown - Non-extensible type thrown by code + * @return {Error} + */ +function thrown2Error(err) { + return new Error( + 'the ' + type(err) + ' ' + stringify(err) + ' was thrown, throw an Error :)' + ); +} + +Runner.constants = constants; + +/** + * Node.js' `EventEmitter` + * @external EventEmitter + * @see {@link https://nodejs.org/api/events.html#events_class_eventemitter} + */ + +}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./errors":6,"./pending":16,"./runnable":33,"./suite":36,"./utils":38,"_process":69,"debug":45,"events":50,"util":89}],35:[function(require,module,exports){ +(function (global){ +'use strict'; + +/** + * Provides a factory function for a {@link StatsCollector} object. + * @module + */ + +var constants = require('./runner').constants; +var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; +var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; +var EVENT_SUITE_BEGIN = constants.EVENT_SUITE_BEGIN; +var EVENT_RUN_BEGIN = constants.EVENT_RUN_BEGIN; +var EVENT_TEST_PENDING = constants.EVENT_TEST_PENDING; +var EVENT_RUN_END = constants.EVENT_RUN_END; +var EVENT_TEST_END = constants.EVENT_TEST_END; + +/** + * Test statistics collector. + * + * @public + * @typedef {Object} StatsCollector + * @property {number} suites - integer count of suites run. + * @property {number} tests - integer count of tests run. + * @property {number} passes - integer count of passing tests. + * @property {number} pending - integer count of pending tests. + * @property {number} failures - integer count of failed tests. + * @property {Date} start - time when testing began. + * @property {Date} end - time when testing concluded. + * @property {number} duration - number of msecs that testing took. + */ + +var Date = global.Date; + +/** + * Provides stats such as test duration, number of tests passed / failed etc., by listening for events emitted by `runner`. + * + * @private + * @param {Runner} runner - Runner instance + * @throws {TypeError} If falsy `runner` + */ +function createStatsCollector(runner) { + /** + * @type StatsCollector + */ + var stats = { + suites: 0, + tests: 0, + passes: 0, + pending: 0, + failures: 0 + }; + + if (!runner) { + throw new TypeError('Missing runner argument'); + } + + runner.stats = stats; + + runner.once(EVENT_RUN_BEGIN, function() { + stats.start = new Date(); + }); + runner.on(EVENT_SUITE_BEGIN, function(suite) { + suite.root || stats.suites++; + }); + runner.on(EVENT_TEST_PASS, function() { + stats.passes++; + }); + runner.on(EVENT_TEST_FAIL, function() { + stats.failures++; + }); + runner.on(EVENT_TEST_PENDING, function() { + stats.pending++; + }); + runner.on(EVENT_TEST_END, function() { + stats.tests++; + }); + runner.once(EVENT_RUN_END, function() { + stats.end = new Date(); + stats.duration = stats.end - stats.start; + }); +} + +module.exports = createStatsCollector; + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./runner":34}],36:[function(require,module,exports){ +'use strict'; + +/** + * Module dependencies. + */ +var EventEmitter = require('events').EventEmitter; +var Hook = require('./hook'); +var utils = require('./utils'); +var inherits = utils.inherits; +var debug = require('debug')('mocha:suite'); +var milliseconds = require('ms'); +var errors = require('./errors'); +var createInvalidArgumentTypeError = errors.createInvalidArgumentTypeError; + +/** + * Expose `Suite`. + */ + +exports = module.exports = Suite; + +/** + * Create a new `Suite` with the given `title` and parent `Suite`. + * + * @public + * @param {Suite} parent - Parent suite (required!) + * @param {string} title - Title + * @return {Suite} + */ +Suite.create = function(parent, title) { + var suite = new Suite(title, parent.ctx); + suite.parent = parent; + title = suite.fullTitle(); + parent.addSuite(suite); + return suite; +}; + +/** + * Constructs a new `Suite` instance with the given `title`, `ctx`, and `isRoot`. + * + * @public + * @class + * @extends EventEmitter + * @see {@link https://nodejs.org/api/events.html#events_class_eventemitter|EventEmitter} + * @param {string} title - Suite title. + * @param {Context} parentContext - Parent context instance. + * @param {boolean} [isRoot=false] - Whether this is the root suite. + */ +function Suite(title, parentContext, isRoot) { + if (!utils.isString(title)) { + throw createInvalidArgumentTypeError( + 'Suite argument "title" must be a string. Received type "' + + typeof title + + '"', + 'title', + 'string' + ); + } + this.title = title; + function Context() {} + Context.prototype = parentContext; + this.ctx = new Context(); + this.suites = []; + this.tests = []; + this.pending = false; + this._beforeEach = []; + this._beforeAll = []; + this._afterEach = []; + this._afterAll = []; + this.root = isRoot === true; + this._timeout = 2000; + this._enableTimeouts = true; + this._slow = 75; + this._bail = false; + this._retries = -1; + this._onlyTests = []; + this._onlySuites = []; + this.delayed = false; + + this.on('newListener', function(event) { + if (deprecatedEvents[event]) { + utils.deprecate( + 'Event "' + + event + + '" is deprecated. Please let the Mocha team know about your use case: https://git.io/v6Lwm' + ); + } + }); +} + +/** + * Inherit from `EventEmitter.prototype`. + */ +inherits(Suite, EventEmitter); + +/** + * Return a clone of this `Suite`. + * + * @private + * @return {Suite} + */ +Suite.prototype.clone = function() { + var suite = new Suite(this.title); + debug('clone'); + suite.ctx = this.ctx; + suite.root = this.root; + suite.timeout(this.timeout()); + suite.retries(this.retries()); + suite.enableTimeouts(this.enableTimeouts()); + suite.slow(this.slow()); + suite.bail(this.bail()); + return suite; +}; + +/** + * Set or get timeout `ms` or short-hand such as "2s". + * + * @private + * @todo Do not attempt to set value if `ms` is undefined + * @param {number|string} ms + * @return {Suite|number} for chaining + */ +Suite.prototype.timeout = function(ms) { + if (!arguments.length) { + return this._timeout; + } + if (ms.toString() === '0') { + this._enableTimeouts = false; + } + if (typeof ms === 'string') { + ms = milliseconds(ms); + } + debug('timeout %d', ms); + this._timeout = parseInt(ms, 10); + return this; +}; + +/** + * Set or get number of times to retry a failed test. + * + * @private + * @param {number|string} n + * @return {Suite|number} for chaining + */ +Suite.prototype.retries = function(n) { + if (!arguments.length) { + return this._retries; + } + debug('retries %d', n); + this._retries = parseInt(n, 10) || 0; + return this; +}; + +/** + * Set or get timeout to `enabled`. + * + * @private + * @param {boolean} enabled + * @return {Suite|boolean} self or enabled + */ +Suite.prototype.enableTimeouts = function(enabled) { + if (!arguments.length) { + return this._enableTimeouts; + } + debug('enableTimeouts %s', enabled); + this._enableTimeouts = enabled; + return this; +}; + +/** + * Set or get slow `ms` or short-hand such as "2s". + * + * @private + * @param {number|string} ms + * @return {Suite|number} for chaining + */ +Suite.prototype.slow = function(ms) { + if (!arguments.length) { + return this._slow; + } + if (typeof ms === 'string') { + ms = milliseconds(ms); + } + debug('slow %d', ms); + this._slow = ms; + return this; +}; + +/** + * Set or get whether to bail after first error. + * + * @private + * @param {boolean} bail + * @return {Suite|number} for chaining + */ +Suite.prototype.bail = function(bail) { + if (!arguments.length) { + return this._bail; + } + debug('bail %s', bail); + this._bail = bail; + return this; +}; + +/** + * Check if this suite or its parent suite is marked as pending. + * + * @private + */ +Suite.prototype.isPending = function() { + return this.pending || (this.parent && this.parent.isPending()); +}; + +/** + * Generic hook-creator. + * @private + * @param {string} title - Title of hook + * @param {Function} fn - Hook callback + * @returns {Hook} A new hook + */ +Suite.prototype._createHook = function(title, fn) { + var hook = new Hook(title, fn); + hook.parent = this; + hook.timeout(this.timeout()); + hook.retries(this.retries()); + hook.enableTimeouts(this.enableTimeouts()); + hook.slow(this.slow()); + hook.ctx = this.ctx; + hook.file = this.file; + return hook; +}; + +/** + * Run `fn(test[, done])` before running tests. + * + * @private + * @param {string} title + * @param {Function} fn + * @return {Suite} for chaining + */ +Suite.prototype.beforeAll = function(title, fn) { + if (this.isPending()) { + return this; + } + if (typeof title === 'function') { + fn = title; + title = fn.name; + } + title = '"before all" hook' + (title ? ': ' + title : ''); + + var hook = this._createHook(title, fn); + this._beforeAll.push(hook); + this.emit(constants.EVENT_SUITE_ADD_HOOK_BEFORE_ALL, hook); + return this; +}; + +/** + * Run `fn(test[, done])` after running tests. + * + * @private + * @param {string} title + * @param {Function} fn + * @return {Suite} for chaining + */ +Suite.prototype.afterAll = function(title, fn) { + if (this.isPending()) { + return this; + } + if (typeof title === 'function') { + fn = title; + title = fn.name; + } + title = '"after all" hook' + (title ? ': ' + title : ''); + + var hook = this._createHook(title, fn); + this._afterAll.push(hook); + this.emit(constants.EVENT_SUITE_ADD_HOOK_AFTER_ALL, hook); + return this; +}; + +/** + * Run `fn(test[, done])` before each test case. + * + * @private + * @param {string} title + * @param {Function} fn + * @return {Suite} for chaining + */ +Suite.prototype.beforeEach = function(title, fn) { + if (this.isPending()) { + return this; + } + if (typeof title === 'function') { + fn = title; + title = fn.name; + } + title = '"before each" hook' + (title ? ': ' + title : ''); + + var hook = this._createHook(title, fn); + this._beforeEach.push(hook); + this.emit(constants.EVENT_SUITE_ADD_HOOK_BEFORE_EACH, hook); + return this; +}; + +/** + * Run `fn(test[, done])` after each test case. + * + * @private + * @param {string} title + * @param {Function} fn + * @return {Suite} for chaining + */ +Suite.prototype.afterEach = function(title, fn) { + if (this.isPending()) { + return this; + } + if (typeof title === 'function') { + fn = title; + title = fn.name; + } + title = '"after each" hook' + (title ? ': ' + title : ''); + + var hook = this._createHook(title, fn); + this._afterEach.push(hook); + this.emit(constants.EVENT_SUITE_ADD_HOOK_AFTER_EACH, hook); + return this; +}; + +/** + * Add a test `suite`. + * + * @private + * @param {Suite} suite + * @return {Suite} for chaining + */ +Suite.prototype.addSuite = function(suite) { + suite.parent = this; + suite.root = false; + suite.timeout(this.timeout()); + suite.retries(this.retries()); + suite.enableTimeouts(this.enableTimeouts()); + suite.slow(this.slow()); + suite.bail(this.bail()); + this.suites.push(suite); + this.emit(constants.EVENT_SUITE_ADD_SUITE, suite); + return this; +}; + +/** + * Add a `test` to this suite. + * + * @private + * @param {Test} test + * @return {Suite} for chaining + */ +Suite.prototype.addTest = function(test) { + test.parent = this; + test.timeout(this.timeout()); + test.retries(this.retries()); + test.enableTimeouts(this.enableTimeouts()); + test.slow(this.slow()); + test.ctx = this.ctx; + this.tests.push(test); + this.emit(constants.EVENT_SUITE_ADD_TEST, test); + return this; +}; + +/** + * Return the full title generated by recursively concatenating the parent's + * full title. + * + * @memberof Suite + * @public + * @return {string} + */ +Suite.prototype.fullTitle = function() { + return this.titlePath().join(' '); +}; + +/** + * Return the title path generated by recursively concatenating the parent's + * title path. + * + * @memberof Suite + * @public + * @return {string} + */ +Suite.prototype.titlePath = function() { + var result = []; + if (this.parent) { + result = result.concat(this.parent.titlePath()); + } + if (!this.root) { + result.push(this.title); + } + return result; +}; + +/** + * Return the total number of tests. + * + * @memberof Suite + * @public + * @return {number} + */ +Suite.prototype.total = function() { + return ( + this.suites.reduce(function(sum, suite) { + return sum + suite.total(); + }, 0) + this.tests.length + ); +}; + +/** + * Iterates through each suite recursively to find all tests. Applies a + * function in the format `fn(test)`. + * + * @private + * @param {Function} fn + * @return {Suite} + */ +Suite.prototype.eachTest = function(fn) { + this.tests.forEach(fn); + this.suites.forEach(function(suite) { + suite.eachTest(fn); + }); + return this; +}; + +/** + * This will run the root suite if we happen to be running in delayed mode. + * @private + */ +Suite.prototype.run = function run() { + if (this.root) { + this.emit(constants.EVENT_ROOT_SUITE_RUN); + } +}; + +/** + * Determines whether a suite has an `only` test or suite as a descendant. + * + * @private + * @returns {Boolean} + */ +Suite.prototype.hasOnly = function hasOnly() { + return ( + this._onlyTests.length > 0 || + this._onlySuites.length > 0 || + this.suites.some(function(suite) { + return suite.hasOnly(); + }) + ); +}; + +/** + * Filter suites based on `isOnly` logic. + * + * @private + * @returns {Boolean} + */ +Suite.prototype.filterOnly = function filterOnly() { + if (this._onlyTests.length) { + // If the suite contains `only` tests, run those and ignore any nested suites. + this.tests = this._onlyTests; + this.suites = []; + } else { + // Otherwise, do not run any of the tests in this suite. + this.tests = []; + this._onlySuites.forEach(function(onlySuite) { + // If there are other `only` tests/suites nested in the current `only` suite, then filter that `only` suite. + // Otherwise, all of the tests on this `only` suite should be run, so don't filter it. + if (onlySuite.hasOnly()) { + onlySuite.filterOnly(); + } + }); + // Run the `only` suites, as well as any other suites that have `only` tests/suites as descendants. + var onlySuites = this._onlySuites; + this.suites = this.suites.filter(function(childSuite) { + return onlySuites.indexOf(childSuite) !== -1 || childSuite.filterOnly(); + }); + } + // Keep the suite only if there is something to run + return this.tests.length > 0 || this.suites.length > 0; +}; + +/** + * Adds a suite to the list of subsuites marked `only`. + * + * @private + * @param {Suite} suite + */ +Suite.prototype.appendOnlySuite = function(suite) { + this._onlySuites.push(suite); +}; + +/** + * Adds a test to the list of tests marked `only`. + * + * @private + * @param {Test} test + */ +Suite.prototype.appendOnlyTest = function(test) { + this._onlyTests.push(test); +}; + +/** + * Returns the array of hooks by hook name; see `HOOK_TYPE_*` constants. + * @private + */ +Suite.prototype.getHooks = function getHooks(name) { + return this['_' + name]; +}; + +/** + * Cleans up the references to all the deferred functions + * (before/after/beforeEach/afterEach) and tests of a Suite. + * These must be deleted otherwise a memory leak can happen, + * as those functions may reference variables from closures, + * thus those variables can never be garbage collected as long + * as the deferred functions exist. + * + * @private + */ +Suite.prototype.cleanReferences = function cleanReferences() { + function cleanArrReferences(arr) { + for (var i = 0; i < arr.length; i++) { + delete arr[i].fn; + } + } + + if (Array.isArray(this._beforeAll)) { + cleanArrReferences(this._beforeAll); + } + + if (Array.isArray(this._beforeEach)) { + cleanArrReferences(this._beforeEach); + } + + if (Array.isArray(this._afterAll)) { + cleanArrReferences(this._afterAll); + } + + if (Array.isArray(this._afterEach)) { + cleanArrReferences(this._afterEach); + } + + for (var i = 0; i < this.tests.length; i++) { + delete this.tests[i].fn; + } +}; + +var constants = utils.defineConstants( + /** + * {@link Suite}-related constants. + * @public + * @memberof Suite + * @alias constants + * @readonly + * @static + * @enum {string} + */ + { + /** + * Event emitted after a test file has been loaded Not emitted in browser. + */ + EVENT_FILE_POST_REQUIRE: 'post-require', + /** + * Event emitted before a test file has been loaded. In browser, this is emitted once an interface has been selected. + */ + EVENT_FILE_PRE_REQUIRE: 'pre-require', + /** + * Event emitted immediately after a test file has been loaded. Not emitted in browser. + */ + EVENT_FILE_REQUIRE: 'require', + /** + * Event emitted when `global.run()` is called (use with `delay` option) + */ + EVENT_ROOT_SUITE_RUN: 'run', + + /** + * Namespace for collection of a `Suite`'s "after all" hooks + */ + HOOK_TYPE_AFTER_ALL: 'afterAll', + /** + * Namespace for collection of a `Suite`'s "after each" hooks + */ + HOOK_TYPE_AFTER_EACH: 'afterEach', + /** + * Namespace for collection of a `Suite`'s "before all" hooks + */ + HOOK_TYPE_BEFORE_ALL: 'beforeAll', + /** + * Namespace for collection of a `Suite`'s "before all" hooks + */ + HOOK_TYPE_BEFORE_EACH: 'beforeEach', + + // the following events are all deprecated + + /** + * Emitted after an "after all" `Hook` has been added to a `Suite`. Deprecated + */ + EVENT_SUITE_ADD_HOOK_AFTER_ALL: 'afterAll', + /** + * Emitted after an "after each" `Hook` has been added to a `Suite` Deprecated + */ + EVENT_SUITE_ADD_HOOK_AFTER_EACH: 'afterEach', + /** + * Emitted after an "before all" `Hook` has been added to a `Suite` Deprecated + */ + EVENT_SUITE_ADD_HOOK_BEFORE_ALL: 'beforeAll', + /** + * Emitted after an "before each" `Hook` has been added to a `Suite` Deprecated + */ + EVENT_SUITE_ADD_HOOK_BEFORE_EACH: 'beforeEach', + /** + * Emitted after a child `Suite` has been added to a `Suite`. Deprecated + */ + EVENT_SUITE_ADD_SUITE: 'suite', + /** + * Emitted after a `Test` has been added to a `Suite`. Deprecated + */ + EVENT_SUITE_ADD_TEST: 'test' + } +); + +/** + * @summary There are no known use cases for these events. + * @desc This is a `Set`-like object having all keys being the constant's string value and the value being `true`. + * @todo Remove eventually + * @type {Object} + * @ignore + */ +var deprecatedEvents = Object.keys(constants) + .filter(function(constant) { + return constant.substring(0, 15) === 'EVENT_SUITE_ADD'; + }) + .reduce(function(acc, constant) { + acc[constants[constant]] = true; + return acc; + }, utils.createMap()); + +Suite.constants = constants; + +},{"./errors":6,"./hook":7,"./utils":38,"debug":45,"events":50,"ms":60}],37:[function(require,module,exports){ +'use strict'; +var Runnable = require('./runnable'); +var utils = require('./utils'); +var errors = require('./errors'); +var createInvalidArgumentTypeError = errors.createInvalidArgumentTypeError; +var isString = utils.isString; + +module.exports = Test; + +/** + * Initialize a new `Test` with the given `title` and callback `fn`. + * + * @public + * @class + * @extends Runnable + * @param {String} title - Test title (required) + * @param {Function} [fn] - Test callback. If omitted, the Test is considered "pending" + */ +function Test(title, fn) { + if (!isString(title)) { + throw createInvalidArgumentTypeError( + 'Test argument "title" should be a string. Received type "' + + typeof title + + '"', + 'title', + 'string' + ); + } + Runnable.call(this, title, fn); + this.pending = !fn; + this.type = 'test'; +} + +/** + * Inherit from `Runnable.prototype`. + */ +utils.inherits(Test, Runnable); + +Test.prototype.clone = function() { + var test = new Test(this.title, this.fn); + test.timeout(this.timeout()); + test.slow(this.slow()); + test.enableTimeouts(this.enableTimeouts()); + test.retries(this.retries()); + test.currentRetry(this.currentRetry()); + test.globals(this.globals()); + test.parent = this.parent; + test.file = this.file; + test.ctx = this.ctx; + return test; +}; + +},{"./errors":6,"./runnable":33,"./utils":38}],38:[function(require,module,exports){ +(function (process,Buffer){ +'use strict'; + +/** + * Various utility functions used throughout Mocha's codebase. + * @module utils + */ + +/** + * Module dependencies. + */ + +var fs = require('fs'); +var path = require('path'); +var util = require('util'); +var glob = require('glob'); +var he = require('he'); +var errors = require('./errors'); +var createNoFilesMatchPatternError = errors.createNoFilesMatchPatternError; +var createMissingArgumentError = errors.createMissingArgumentError; + +var assign = (exports.assign = require('object.assign').getPolyfill()); + +/** + * Inherit the prototype methods from one constructor into another. + * + * @param {function} ctor - Constructor function which needs to inherit the + * prototype. + * @param {function} superCtor - Constructor function to inherit prototype from. + * @throws {TypeError} if either constructor is null, or if super constructor + * lacks a prototype. + */ +exports.inherits = util.inherits; + +/** + * Escape special characters in the given string of html. + * + * @private + * @param {string} html + * @return {string} + */ +exports.escape = function(html) { + return he.encode(String(html), {useNamedReferences: false}); +}; + +/** + * Test if the given obj is type of string. + * + * @private + * @param {Object} obj + * @return {boolean} + */ +exports.isString = function(obj) { + return typeof obj === 'string'; +}; + +/** + * Compute a slug from the given `str`. + * + * @private + * @param {string} str + * @return {string} + */ +exports.slug = function(str) { + return str + .toLowerCase() + .replace(/ +/g, '-') + .replace(/[^-\w]/g, ''); +}; + +/** + * Strip the function definition from `str`, and re-indent for pre whitespace. + * + * @param {string} str + * @return {string} + */ +exports.clean = function(str) { + str = str + .replace(/\r\n?|[\n\u2028\u2029]/g, '\n') + .replace(/^\uFEFF/, '') + // (traditional)-> space/name parameters body (lambda)-> parameters body multi-statement/single keep body content + .replace( + /^function(?:\s*|\s+[^(]*)\([^)]*\)\s*\{((?:.|\n)*?)\s*\}$|^\([^)]*\)\s*=>\s*(?:\{((?:.|\n)*?)\s*\}|((?:.|\n)*))$/, + '$1$2$3' + ); + + var spaces = str.match(/^\n?( *)/)[1].length; + var tabs = str.match(/^\n?(\t*)/)[1].length; + var re = new RegExp( + '^\n?' + (tabs ? '\t' : ' ') + '{' + (tabs || spaces) + '}', + 'gm' + ); + + str = str.replace(re, ''); + + return str.trim(); +}; + +/** + * Parse the given `qs`. + * + * @private + * @param {string} qs + * @return {Object} + */ +exports.parseQuery = function(qs) { + return qs + .replace('?', '') + .split('&') + .reduce(function(obj, pair) { + var i = pair.indexOf('='); + var key = pair.slice(0, i); + var val = pair.slice(++i); + + // Due to how the URLSearchParams API treats spaces + obj[key] = decodeURIComponent(val.replace(/\+/g, '%20')); + + return obj; + }, {}); +}; + +/** + * Highlight the given string of `js`. + * + * @private + * @param {string} js + * @return {string} + */ +function highlight(js) { + return js + .replace(//g, '>') + .replace(/\/\/(.*)/gm, '//$1') + .replace(/('.*?')/gm, '$1') + .replace(/(\d+\.\d+)/gm, '$1') + .replace(/(\d+)/gm, '$1') + .replace( + /\bnew[ \t]+(\w+)/gm, + 'new $1' + ) + .replace( + /\b(function|new|throw|return|var|if|else)\b/gm, + '$1' + ); +} + +/** + * Highlight the contents of tag `name`. + * + * @private + * @param {string} name + */ +exports.highlightTags = function(name) { + var code = document.getElementById('mocha').getElementsByTagName(name); + for (var i = 0, len = code.length; i < len; ++i) { + code[i].innerHTML = highlight(code[i].innerHTML); + } +}; + +/** + * If a value could have properties, and has none, this function is called, + * which returns a string representation of the empty value. + * + * Functions w/ no properties return `'[Function]'` + * Arrays w/ length === 0 return `'[]'` + * Objects w/ no properties return `'{}'` + * All else: return result of `value.toString()` + * + * @private + * @param {*} value The value to inspect. + * @param {string} typeHint The type of the value + * @returns {string} + */ +function emptyRepresentation(value, typeHint) { + switch (typeHint) { + case 'function': + return '[Function]'; + case 'object': + return '{}'; + case 'array': + return '[]'; + default: + return value.toString(); + } +} + +/** + * Takes some variable and asks `Object.prototype.toString()` what it thinks it + * is. + * + * @private + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString + * @param {*} value The value to test. + * @returns {string} Computed type + * @example + * type({}) // 'object' + * type([]) // 'array' + * type(1) // 'number' + * type(false) // 'boolean' + * type(Infinity) // 'number' + * type(null) // 'null' + * type(new Date()) // 'date' + * type(/foo/) // 'regexp' + * type('type') // 'string' + * type(global) // 'global' + * type(new String('foo') // 'object' + */ +var type = (exports.type = function type(value) { + if (value === undefined) { + return 'undefined'; + } else if (value === null) { + return 'null'; + } else if (Buffer.isBuffer(value)) { + return 'buffer'; + } + return Object.prototype.toString + .call(value) + .replace(/^\[.+\s(.+?)]$/, '$1') + .toLowerCase(); +}); + +/** + * Stringify `value`. Different behavior depending on type of value: + * + * - If `value` is undefined or null, return `'[undefined]'` or `'[null]'`, respectively. + * - If `value` is not an object, function or array, return result of `value.toString()` wrapped in double-quotes. + * - If `value` is an *empty* object, function, or array, return result of function + * {@link emptyRepresentation}. + * - If `value` has properties, call {@link exports.canonicalize} on it, then return result of + * JSON.stringify(). + * + * @private + * @see exports.type + * @param {*} value + * @return {string} + */ +exports.stringify = function(value) { + var typeHint = type(value); + + if (!~['object', 'array', 'function'].indexOf(typeHint)) { + if (typeHint === 'buffer') { + var json = Buffer.prototype.toJSON.call(value); + // Based on the toJSON result + return jsonStringify( + json.data && json.type ? json.data : json, + 2 + ).replace(/,(\n|$)/g, '$1'); + } + + // IE7/IE8 has a bizarre String constructor; needs to be coerced + // into an array and back to obj. + if (typeHint === 'string' && typeof value === 'object') { + value = value.split('').reduce(function(acc, char, idx) { + acc[idx] = char; + return acc; + }, {}); + typeHint = 'object'; + } else { + return jsonStringify(value); + } + } + + for (var prop in value) { + if (Object.prototype.hasOwnProperty.call(value, prop)) { + return jsonStringify( + exports.canonicalize(value, null, typeHint), + 2 + ).replace(/,(\n|$)/g, '$1'); + } + } + + return emptyRepresentation(value, typeHint); +}; + +/** + * like JSON.stringify but more sense. + * + * @private + * @param {Object} object + * @param {number=} spaces + * @param {number=} depth + * @returns {*} + */ +function jsonStringify(object, spaces, depth) { + if (typeof spaces === 'undefined') { + // primitive types + return _stringify(object); + } + + depth = depth || 1; + var space = spaces * depth; + var str = Array.isArray(object) ? '[' : '{'; + var end = Array.isArray(object) ? ']' : '}'; + var length = + typeof object.length === 'number' + ? object.length + : Object.keys(object).length; + // `.repeat()` polyfill + function repeat(s, n) { + return new Array(n).join(s); + } + + function _stringify(val) { + switch (type(val)) { + case 'null': + case 'undefined': + val = '[' + val + ']'; + break; + case 'array': + case 'object': + val = jsonStringify(val, spaces, depth + 1); + break; + case 'boolean': + case 'regexp': + case 'symbol': + case 'number': + val = + val === 0 && 1 / val === -Infinity // `-0` + ? '-0' + : val.toString(); + break; + case 'date': + var sDate = isNaN(val.getTime()) ? val.toString() : val.toISOString(); + val = '[Date: ' + sDate + ']'; + break; + case 'buffer': + var json = val.toJSON(); + // Based on the toJSON result + json = json.data && json.type ? json.data : json; + val = '[Buffer: ' + jsonStringify(json, 2, depth + 1) + ']'; + break; + default: + val = + val === '[Function]' || val === '[Circular]' + ? val + : JSON.stringify(val); // string + } + return val; + } + + for (var i in object) { + if (!Object.prototype.hasOwnProperty.call(object, i)) { + continue; // not my business + } + --length; + str += + '\n ' + + repeat(' ', space) + + (Array.isArray(object) ? '' : '"' + i + '": ') + // key + _stringify(object[i]) + // value + (length ? ',' : ''); // comma + } + + return ( + str + + // [], {} + (str.length !== 1 ? '\n' + repeat(' ', --space) + end : end) + ); +} + +/** + * Return a new Thing that has the keys in sorted order. Recursive. + * + * If the Thing... + * - has already been seen, return string `'[Circular]'` + * - is `undefined`, return string `'[undefined]'` + * - is `null`, return value `null` + * - is some other primitive, return the value + * - is not a primitive or an `Array`, `Object`, or `Function`, return the value of the Thing's `toString()` method + * - is a non-empty `Array`, `Object`, or `Function`, return the result of calling this function again. + * - is an empty `Array`, `Object`, or `Function`, return the result of calling `emptyRepresentation()` + * + * @private + * @see {@link exports.stringify} + * @param {*} value Thing to inspect. May or may not have properties. + * @param {Array} [stack=[]] Stack of seen values + * @param {string} [typeHint] Type hint + * @return {(Object|Array|Function|string|undefined)} + */ +exports.canonicalize = function canonicalize(value, stack, typeHint) { + var canonicalizedObj; + /* eslint-disable no-unused-vars */ + var prop; + /* eslint-enable no-unused-vars */ + typeHint = typeHint || type(value); + function withStack(value, fn) { + stack.push(value); + fn(); + stack.pop(); + } + + stack = stack || []; + + if (stack.indexOf(value) !== -1) { + return '[Circular]'; + } + + switch (typeHint) { + case 'undefined': + case 'buffer': + case 'null': + canonicalizedObj = value; + break; + case 'array': + withStack(value, function() { + canonicalizedObj = value.map(function(item) { + return exports.canonicalize(item, stack); + }); + }); + break; + case 'function': + /* eslint-disable guard-for-in */ + for (prop in value) { + canonicalizedObj = {}; + break; + } + /* eslint-enable guard-for-in */ + if (!canonicalizedObj) { + canonicalizedObj = emptyRepresentation(value, typeHint); + break; + } + /* falls through */ + case 'object': + canonicalizedObj = canonicalizedObj || {}; + withStack(value, function() { + Object.keys(value) + .sort() + .forEach(function(key) { + canonicalizedObj[key] = exports.canonicalize(value[key], stack); + }); + }); + break; + case 'date': + case 'number': + case 'regexp': + case 'boolean': + case 'symbol': + canonicalizedObj = value; + break; + default: + canonicalizedObj = value + ''; + } + + return canonicalizedObj; +}; + +/** + * Determines if pathname has a matching file extension. + * + * @private + * @param {string} pathname - Pathname to check for match. + * @param {string[]} exts - List of file extensions (sans period). + * @return {boolean} whether file extension matches. + * @example + * hasMatchingExtname('foo.html', ['js', 'css']); // => false + */ +function hasMatchingExtname(pathname, exts) { + var suffix = path.extname(pathname).slice(1); + return exts.some(function(element) { + return suffix === element; + }); +} + +/** + * Determines if pathname would be a "hidden" file (or directory) on UN*X. + * + * @description + * On UN*X, pathnames beginning with a full stop (aka dot) are hidden during + * typical usage. Dotfiles, plain-text configuration files, are prime examples. + * + * @see {@link http://xahlee.info/UnixResource_dir/writ/unix_origin_of_dot_filename.html|Origin of Dot File Names} + * + * @private + * @param {string} pathname - Pathname to check for match. + * @return {boolean} whether pathname would be considered a hidden file. + * @example + * isHiddenOnUnix('.profile'); // => true + */ +function isHiddenOnUnix(pathname) { + return path.basename(pathname)[0] === '.'; +} + +/** + * Lookup file names at the given `path`. + * + * @description + * Filenames are returned in _traversal_ order by the OS/filesystem. + * **Make no assumption that the names will be sorted in any fashion.** + * + * @public + * @memberof Mocha.utils + * @param {string} filepath - Base path to start searching from. + * @param {string[]} [extensions=[]] - File extensions to look for. + * @param {boolean} [recursive=false] - Whether to recurse into subdirectories. + * @return {string[]} An array of paths. + * @throws {Error} if no files match pattern. + * @throws {TypeError} if `filepath` is directory and `extensions` not provided. + */ +exports.lookupFiles = function lookupFiles(filepath, extensions, recursive) { + extensions = extensions || []; + recursive = recursive || false; + var files = []; + var stat; + + if (!fs.existsSync(filepath)) { + var pattern; + if (glob.hasMagic(filepath)) { + // Handle glob as is without extensions + pattern = filepath; + } else { + // glob pattern e.g. 'filepath+(.js|.ts)' + var strExtensions = extensions + .map(function(v) { + return '.' + v; + }) + .join('|'); + pattern = filepath + '+(' + strExtensions + ')'; + } + files = glob.sync(pattern, {nodir: true}); + if (!files.length) { + throw createNoFilesMatchPatternError( + 'Cannot find any files matching pattern ' + exports.dQuote(filepath), + filepath + ); + } + return files; + } + + // Handle file + try { + stat = fs.statSync(filepath); + if (stat.isFile()) { + return filepath; + } + } catch (err) { + // ignore error + return; + } + + // Handle directory + fs.readdirSync(filepath).forEach(function(dirent) { + var pathname = path.join(filepath, dirent); + var stat; + + try { + stat = fs.statSync(pathname); + if (stat.isDirectory()) { + if (recursive) { + files = files.concat(lookupFiles(pathname, extensions, recursive)); + } + return; + } + } catch (err) { + // ignore error + return; + } + if (!extensions.length) { + throw createMissingArgumentError( + util.format( + 'Argument %s required when argument %s is a directory', + exports.sQuote('extensions'), + exports.sQuote('filepath') + ), + 'extensions', + 'array' + ); + } + + if ( + !stat.isFile() || + !hasMatchingExtname(pathname, extensions) || + isHiddenOnUnix(pathname) + ) { + return; + } + files.push(pathname); + }); + + return files; +}; + +/** + * process.emitWarning or a polyfill + * @see https://nodejs.org/api/process.html#process_process_emitwarning_warning_options + * @ignore + */ +function emitWarning(msg, type) { + if (process.emitWarning) { + process.emitWarning(msg, type); + } else { + process.nextTick(function() { + console.warn(type + ': ' + msg); + }); + } +} + +/** + * Show a deprecation warning. Each distinct message is only displayed once. + * Ignores empty messages. + * + * @param {string} [msg] - Warning to print + * @private + */ +exports.deprecate = function deprecate(msg) { + msg = String(msg); + if (msg && !deprecate.cache[msg]) { + deprecate.cache[msg] = true; + emitWarning(msg, 'DeprecationWarning'); + } +}; +exports.deprecate.cache = {}; + +/** + * Show a generic warning. + * Ignores empty messages. + * + * @param {string} [msg] - Warning to print + * @private + */ +exports.warn = function warn(msg) { + if (msg) { + emitWarning(msg); + } +}; + +/** + * @summary + * This Filter based on `mocha-clean` module.(see: `github.com/rstacruz/mocha-clean`) + * @description + * When invoking this function you get a filter function that get the Error.stack as an input, + * and return a prettify output. + * (i.e: strip Mocha and internal node functions from stack trace). + * @returns {Function} + */ +exports.stackTraceFilter = function() { + // TODO: Replace with `process.browser` + var is = typeof document === 'undefined' ? {node: true} : {browser: true}; + var slash = path.sep; + var cwd; + if (is.node) { + cwd = process.cwd() + slash; + } else { + cwd = (typeof location === 'undefined' + ? window.location + : location + ).href.replace(/\/[^/]*$/, '/'); + slash = '/'; + } + + function isMochaInternal(line) { + return ( + ~line.indexOf('node_modules' + slash + 'mocha' + slash) || + ~line.indexOf(slash + 'mocha.js') || + ~line.indexOf(slash + 'mocha.min.js') + ); + } + + function isNodeInternal(line) { + return ( + ~line.indexOf('(timers.js:') || + ~line.indexOf('(events.js:') || + ~line.indexOf('(node.js:') || + ~line.indexOf('(module.js:') || + ~line.indexOf('GeneratorFunctionPrototype.next (native)') || + false + ); + } + + return function(stack) { + stack = stack.split('\n'); + + stack = stack.reduce(function(list, line) { + if (isMochaInternal(line)) { + return list; + } + + if (is.node && isNodeInternal(line)) { + return list; + } + + // Clean up cwd(absolute) + if (/:\d+:\d+\)?$/.test(line)) { + line = line.replace('(' + cwd, '('); + } + + list.push(line); + return list; + }, []); + + return stack.join('\n'); + }; +}; + +/** + * Crude, but effective. + * @public + * @param {*} value + * @returns {boolean} Whether or not `value` is a Promise + */ +exports.isPromise = function isPromise(value) { + return ( + typeof value === 'object' && + value !== null && + typeof value.then === 'function' + ); +}; + +/** + * Clamps a numeric value to an inclusive range. + * + * @param {number} value - Value to be clamped. + * @param {numer[]} range - Two element array specifying [min, max] range. + * @returns {number} clamped value + */ +exports.clamp = function clamp(value, range) { + return Math.min(Math.max(value, range[0]), range[1]); +}; + +/** + * Single quote text by combining with undirectional ASCII quotation marks. + * + * @description + * Provides a simple means of markup for quoting text to be used in output. + * Use this to quote names of variables, methods, and packages. + * + * package 'foo' cannot be found + * + * @private + * @param {string} str - Value to be quoted. + * @returns {string} quoted value + * @example + * sQuote('n') // => 'n' + */ +exports.sQuote = function(str) { + return "'" + str + "'"; +}; + +/** + * Double quote text by combining with undirectional ASCII quotation marks. + * + * @description + * Provides a simple means of markup for quoting text to be used in output. + * Use this to quote names of datatypes, classes, pathnames, and strings. + * + * argument 'value' must be "string" or "number" + * + * @private + * @param {string} str - Value to be quoted. + * @returns {string} quoted value + * @example + * dQuote('number') // => "number" + */ +exports.dQuote = function(str) { + return '"' + str + '"'; +}; + +/** + * Provides simplistic message translation for dealing with plurality. + * + * @description + * Use this to create messages which need to be singular or plural. + * Some languages have several plural forms, so _complete_ message clauses + * are preferable to generating the message on the fly. + * + * @private + * @param {number} n - Non-negative integer + * @param {string} msg1 - Message to be used in English for `n = 1` + * @param {string} msg2 - Message to be used in English for `n = 0, 2, 3, ...` + * @returns {string} message corresponding to value of `n` + * @example + * var sprintf = require('util').format; + * var pkgs = ['one', 'two']; + * var msg = sprintf( + * ngettext( + * pkgs.length, + * 'cannot load package: %s', + * 'cannot load packages: %s' + * ), + * pkgs.map(sQuote).join(', ') + * ); + * console.log(msg); // => cannot load packages: 'one', 'two' + */ +exports.ngettext = function(n, msg1, msg2) { + if (typeof n === 'number' && n >= 0) { + return n === 1 ? msg1 : msg2; + } +}; + +/** + * It's a noop. + * @public + */ +exports.noop = function() {}; + +/** + * Creates a map-like object. + * + * @description + * A "map" is an object with no prototype, for our purposes. In some cases + * this would be more appropriate than a `Map`, especially if your environment + * doesn't support it. Recommended for use in Mocha's public APIs. + * + * @public + * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map|MDN:Map} + * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create#Custom_and_Null_objects|MDN:Object.create - Custom objects} + * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign|MDN:Object.assign} + * @param {...*} [obj] - Arguments to `Object.assign()`. + * @returns {Object} An object with no prototype, having `...obj` properties + */ +exports.createMap = function(obj) { + return assign.apply( + null, + [Object.create(null)].concat(Array.prototype.slice.call(arguments)) + ); +}; + +/** + * Creates a read-only map-like object. + * + * @description + * This differs from {@link module:utils.createMap createMap} only in that + * the argument must be non-empty, because the result is frozen. + * + * @see {@link module:utils.createMap createMap} + * @param {...*} [obj] - Arguments to `Object.assign()`. + * @returns {Object} A frozen object with no prototype, having `...obj` properties + * @throws {TypeError} if argument is not a non-empty object. + */ +exports.defineConstants = function(obj) { + if (type(obj) !== 'object' || !Object.keys(obj).length) { + throw new TypeError('Invalid argument; expected a non-empty object'); + } + return Object.freeze(exports.createMap(obj)); +}; + +}).call(this,require('_process'),require("buffer").Buffer) +},{"./errors":6,"_process":69,"buffer":43,"fs":42,"glob":42,"he":54,"object.assign":65,"path":42,"util":89}],39:[function(require,module,exports){ +'use strict' + +exports.byteLength = byteLength +exports.toByteArray = toByteArray +exports.fromByteArray = fromByteArray + +var lookup = [] +var revLookup = [] +var Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array + +var code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' +for (var i = 0, len = code.length; i < len; ++i) { + lookup[i] = code[i] + revLookup[code.charCodeAt(i)] = i +} + +// Support decoding URL-safe base64 strings, as Node.js does. +// See: https://en.wikipedia.org/wiki/Base64#URL_applications +revLookup['-'.charCodeAt(0)] = 62 +revLookup['_'.charCodeAt(0)] = 63 + +function getLens (b64) { + var len = b64.length + + if (len % 4 > 0) { + throw new Error('Invalid string. Length must be a multiple of 4') + } + + // Trim off extra bytes after placeholder bytes are found + // See: https://github.com/beatgammit/base64-js/issues/42 + var validLen = b64.indexOf('=') + if (validLen === -1) validLen = len + + var placeHoldersLen = validLen === len + ? 0 + : 4 - (validLen % 4) + + return [validLen, placeHoldersLen] +} + +// base64 is 4/3 + up to two characters of the original data +function byteLength (b64) { + var lens = getLens(b64) + var validLen = lens[0] + var placeHoldersLen = lens[1] + return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen +} + +function _byteLength (b64, validLen, placeHoldersLen) { + return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen +} + +function toByteArray (b64) { + var tmp + var lens = getLens(b64) + var validLen = lens[0] + var placeHoldersLen = lens[1] + + var arr = new Arr(_byteLength(b64, validLen, placeHoldersLen)) + + var curByte = 0 + + // if there are placeholders, only get up to the last complete 4 chars + var len = placeHoldersLen > 0 + ? validLen - 4 + : validLen + + for (var i = 0; i < len; i += 4) { + tmp = + (revLookup[b64.charCodeAt(i)] << 18) | + (revLookup[b64.charCodeAt(i + 1)] << 12) | + (revLookup[b64.charCodeAt(i + 2)] << 6) | + revLookup[b64.charCodeAt(i + 3)] + arr[curByte++] = (tmp >> 16) & 0xFF + arr[curByte++] = (tmp >> 8) & 0xFF + arr[curByte++] = tmp & 0xFF + } + + if (placeHoldersLen === 2) { + tmp = + (revLookup[b64.charCodeAt(i)] << 2) | + (revLookup[b64.charCodeAt(i + 1)] >> 4) + arr[curByte++] = tmp & 0xFF + } + + if (placeHoldersLen === 1) { + tmp = + (revLookup[b64.charCodeAt(i)] << 10) | + (revLookup[b64.charCodeAt(i + 1)] << 4) | + (revLookup[b64.charCodeAt(i + 2)] >> 2) + arr[curByte++] = (tmp >> 8) & 0xFF + arr[curByte++] = tmp & 0xFF + } + + return arr +} + +function tripletToBase64 (num) { + return lookup[num >> 18 & 0x3F] + + lookup[num >> 12 & 0x3F] + + lookup[num >> 6 & 0x3F] + + lookup[num & 0x3F] +} + +function encodeChunk (uint8, start, end) { + var tmp + var output = [] + for (var i = start; i < end; i += 3) { + tmp = + ((uint8[i] << 16) & 0xFF0000) + + ((uint8[i + 1] << 8) & 0xFF00) + + (uint8[i + 2] & 0xFF) + output.push(tripletToBase64(tmp)) + } + return output.join('') +} + +function fromByteArray (uint8) { + var tmp + var len = uint8.length + var extraBytes = len % 3 // if we have 1 byte left, pad 2 bytes + var parts = [] + var maxChunkLength = 16383 // must be multiple of 3 + + // go through the array every three bytes, we'll deal with trailing stuff later + for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) { + parts.push(encodeChunk( + uint8, i, (i + maxChunkLength) > len2 ? len2 : (i + maxChunkLength) + )) + } + + // pad the end with zeros, but make sure to not forget the extra bytes + if (extraBytes === 1) { + tmp = uint8[len - 1] + parts.push( + lookup[tmp >> 2] + + lookup[(tmp << 4) & 0x3F] + + '==' + ) + } else if (extraBytes === 2) { + tmp = (uint8[len - 2] << 8) + uint8[len - 1] + parts.push( + lookup[tmp >> 10] + + lookup[(tmp >> 4) & 0x3F] + + lookup[(tmp << 2) & 0x3F] + + '=' + ) + } + + return parts.join('') +} + +},{}],40:[function(require,module,exports){ + +},{}],41:[function(require,module,exports){ +(function (process){ +var WritableStream = require('stream').Writable +var inherits = require('util').inherits + +module.exports = BrowserStdout + + +inherits(BrowserStdout, WritableStream) + +function BrowserStdout(opts) { + if (!(this instanceof BrowserStdout)) return new BrowserStdout(opts) + + opts = opts || {} + WritableStream.call(this, opts) + this.label = (opts.label !== undefined) ? opts.label : 'stdout' +} + +BrowserStdout.prototype._write = function(chunks, encoding, cb) { + var output = chunks.toString ? chunks.toString() : chunks + if (this.label === false) { + console.log(output) + } else { + console.log(this.label+':', output) + } + process.nextTick(cb) +} + +}).call(this,require('_process')) +},{"_process":69,"stream":84,"util":89}],42:[function(require,module,exports){ +arguments[4][40][0].apply(exports,arguments) +},{"dup":40}],43:[function(require,module,exports){ +(function (Buffer){ +/*! + * The buffer module from node.js, for the browser. + * + * @author Feross Aboukhadijeh + * @license MIT + */ +/* eslint-disable no-proto */ + +'use strict' + +var base64 = require('base64-js') +var ieee754 = require('ieee754') + +exports.Buffer = Buffer +exports.SlowBuffer = SlowBuffer +exports.INSPECT_MAX_BYTES = 50 + +var K_MAX_LENGTH = 0x7fffffff +exports.kMaxLength = K_MAX_LENGTH + +/** + * If `Buffer.TYPED_ARRAY_SUPPORT`: + * === true Use Uint8Array implementation (fastest) + * === false Print warning and recommend using `buffer` v4.x which has an Object + * implementation (most compatible, even IE6) + * + * Browsers that support typed arrays are IE 10+, Firefox 4+, Chrome 7+, Safari 5.1+, + * Opera 11.6+, iOS 4.2+. + * + * We report that the browser does not support typed arrays if the are not subclassable + * using __proto__. Firefox 4-29 lacks support for adding new properties to `Uint8Array` + * (See: https://bugzilla.mozilla.org/show_bug.cgi?id=695438). IE 10 lacks support + * for __proto__ and has a buggy typed array implementation. + */ +Buffer.TYPED_ARRAY_SUPPORT = typedArraySupport() + +if (!Buffer.TYPED_ARRAY_SUPPORT && typeof console !== 'undefined' && + typeof console.error === 'function') { + console.error( + 'This browser lacks typed array (Uint8Array) support which is required by ' + + '`buffer` v5.x. Use `buffer` v4.x if you require old browser support.' + ) +} + +function typedArraySupport () { + // Can typed array instances can be augmented? + try { + var arr = new Uint8Array(1) + arr.__proto__ = { __proto__: Uint8Array.prototype, foo: function () { return 42 } } + return arr.foo() === 42 + } catch (e) { + return false + } +} + +Object.defineProperty(Buffer.prototype, 'parent', { + enumerable: true, + get: function () { + if (!Buffer.isBuffer(this)) return undefined + return this.buffer + } +}) + +Object.defineProperty(Buffer.prototype, 'offset', { + enumerable: true, + get: function () { + if (!Buffer.isBuffer(this)) return undefined + return this.byteOffset + } +}) + +function createBuffer (length) { + if (length > K_MAX_LENGTH) { + throw new RangeError('The value "' + length + '" is invalid for option "size"') + } + // Return an augmented `Uint8Array` instance + var buf = new Uint8Array(length) + buf.__proto__ = Buffer.prototype + return buf +} + +/** + * The Buffer constructor returns instances of `Uint8Array` that have their + * prototype changed to `Buffer.prototype`. Furthermore, `Buffer` is a subclass of + * `Uint8Array`, so the returned instances will have all the node `Buffer` methods + * and the `Uint8Array` methods. Square bracket notation works as expected -- it + * returns a single octet. + * + * The `Uint8Array` prototype remains unmodified. + */ + +function Buffer (arg, encodingOrOffset, length) { + // Common case. + if (typeof arg === 'number') { + if (typeof encodingOrOffset === 'string') { + throw new TypeError( + 'The "string" argument must be of type string. Received type number' + ) + } + return allocUnsafe(arg) + } + return from(arg, encodingOrOffset, length) +} + +// Fix subarray() in ES2016. See: https://github.com/feross/buffer/pull/97 +if (typeof Symbol !== 'undefined' && Symbol.species != null && + Buffer[Symbol.species] === Buffer) { + Object.defineProperty(Buffer, Symbol.species, { + value: null, + configurable: true, + enumerable: false, + writable: false + }) +} + +Buffer.poolSize = 8192 // not used by this implementation + +function from (value, encodingOrOffset, length) { + if (typeof value === 'string') { + return fromString(value, encodingOrOffset) + } + + if (ArrayBuffer.isView(value)) { + return fromArrayLike(value) + } + + if (value == null) { + throw TypeError( + 'The first argument must be one of type string, Buffer, ArrayBuffer, Array, ' + + 'or Array-like Object. Received type ' + (typeof value) + ) + } + + if (isInstance(value, ArrayBuffer) || + (value && isInstance(value.buffer, ArrayBuffer))) { + return fromArrayBuffer(value, encodingOrOffset, length) + } + + if (typeof value === 'number') { + throw new TypeError( + 'The "value" argument must not be of type number. Received type number' + ) + } + + var valueOf = value.valueOf && value.valueOf() + if (valueOf != null && valueOf !== value) { + return Buffer.from(valueOf, encodingOrOffset, length) + } + + var b = fromObject(value) + if (b) return b + + if (typeof Symbol !== 'undefined' && Symbol.toPrimitive != null && + typeof value[Symbol.toPrimitive] === 'function') { + return Buffer.from( + value[Symbol.toPrimitive]('string'), encodingOrOffset, length + ) + } + + throw new TypeError( + 'The first argument must be one of type string, Buffer, ArrayBuffer, Array, ' + + 'or Array-like Object. Received type ' + (typeof value) + ) +} + +/** + * Functionally equivalent to Buffer(arg, encoding) but throws a TypeError + * if value is a number. + * Buffer.from(str[, encoding]) + * Buffer.from(array) + * Buffer.from(buffer) + * Buffer.from(arrayBuffer[, byteOffset[, length]]) + **/ +Buffer.from = function (value, encodingOrOffset, length) { + return from(value, encodingOrOffset, length) +} + +// Note: Change prototype *after* Buffer.from is defined to workaround Chrome bug: +// https://github.com/feross/buffer/pull/148 +Buffer.prototype.__proto__ = Uint8Array.prototype +Buffer.__proto__ = Uint8Array + +function assertSize (size) { + if (typeof size !== 'number') { + throw new TypeError('"size" argument must be of type number') + } else if (size < 0) { + throw new RangeError('The value "' + size + '" is invalid for option "size"') + } +} + +function alloc (size, fill, encoding) { + assertSize(size) + if (size <= 0) { + return createBuffer(size) + } + if (fill !== undefined) { + // Only pay attention to encoding if it's a string. This + // prevents accidentally sending in a number that would + // be interpretted as a start offset. + return typeof encoding === 'string' + ? createBuffer(size).fill(fill, encoding) + : createBuffer(size).fill(fill) + } + return createBuffer(size) +} + +/** + * Creates a new filled Buffer instance. + * alloc(size[, fill[, encoding]]) + **/ +Buffer.alloc = function (size, fill, encoding) { + return alloc(size, fill, encoding) +} + +function allocUnsafe (size) { + assertSize(size) + return createBuffer(size < 0 ? 0 : checked(size) | 0) +} + +/** + * Equivalent to Buffer(num), by default creates a non-zero-filled Buffer instance. + * */ +Buffer.allocUnsafe = function (size) { + return allocUnsafe(size) +} +/** + * Equivalent to SlowBuffer(num), by default creates a non-zero-filled Buffer instance. + */ +Buffer.allocUnsafeSlow = function (size) { + return allocUnsafe(size) +} + +function fromString (string, encoding) { + if (typeof encoding !== 'string' || encoding === '') { + encoding = 'utf8' + } + + if (!Buffer.isEncoding(encoding)) { + throw new TypeError('Unknown encoding: ' + encoding) + } + + var length = byteLength(string, encoding) | 0 + var buf = createBuffer(length) + + var actual = buf.write(string, encoding) + + if (actual !== length) { + // Writing a hex string, for example, that contains invalid characters will + // cause everything after the first invalid character to be ignored. (e.g. + // 'abxxcd' will be treated as 'ab') + buf = buf.slice(0, actual) + } + + return buf +} + +function fromArrayLike (array) { + var length = array.length < 0 ? 0 : checked(array.length) | 0 + var buf = createBuffer(length) + for (var i = 0; i < length; i += 1) { + buf[i] = array[i] & 255 + } + return buf +} + +function fromArrayBuffer (array, byteOffset, length) { + if (byteOffset < 0 || array.byteLength < byteOffset) { + throw new RangeError('"offset" is outside of buffer bounds') + } + + if (array.byteLength < byteOffset + (length || 0)) { + throw new RangeError('"length" is outside of buffer bounds') + } + + var buf + if (byteOffset === undefined && length === undefined) { + buf = new Uint8Array(array) + } else if (length === undefined) { + buf = new Uint8Array(array, byteOffset) + } else { + buf = new Uint8Array(array, byteOffset, length) + } + + // Return an augmented `Uint8Array` instance + buf.__proto__ = Buffer.prototype + return buf +} + +function fromObject (obj) { + if (Buffer.isBuffer(obj)) { + var len = checked(obj.length) | 0 + var buf = createBuffer(len) + + if (buf.length === 0) { + return buf + } + + obj.copy(buf, 0, 0, len) + return buf + } + + if (obj.length !== undefined) { + if (typeof obj.length !== 'number' || numberIsNaN(obj.length)) { + return createBuffer(0) + } + return fromArrayLike(obj) + } + + if (obj.type === 'Buffer' && Array.isArray(obj.data)) { + return fromArrayLike(obj.data) + } +} + +function checked (length) { + // Note: cannot use `length < K_MAX_LENGTH` here because that fails when + // length is NaN (which is otherwise coerced to zero.) + if (length >= K_MAX_LENGTH) { + throw new RangeError('Attempt to allocate Buffer larger than maximum ' + + 'size: 0x' + K_MAX_LENGTH.toString(16) + ' bytes') + } + return length | 0 +} + +function SlowBuffer (length) { + if (+length != length) { // eslint-disable-line eqeqeq + length = 0 + } + return Buffer.alloc(+length) +} + +Buffer.isBuffer = function isBuffer (b) { + return b != null && b._isBuffer === true && + b !== Buffer.prototype // so Buffer.isBuffer(Buffer.prototype) will be false +} + +Buffer.compare = function compare (a, b) { + if (isInstance(a, Uint8Array)) a = Buffer.from(a, a.offset, a.byteLength) + if (isInstance(b, Uint8Array)) b = Buffer.from(b, b.offset, b.byteLength) + if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) { + throw new TypeError( + 'The "buf1", "buf2" arguments must be one of type Buffer or Uint8Array' + ) + } + + if (a === b) return 0 + + var x = a.length + var y = b.length + + for (var i = 0, len = Math.min(x, y); i < len; ++i) { + if (a[i] !== b[i]) { + x = a[i] + y = b[i] + break + } + } + + if (x < y) return -1 + if (y < x) return 1 + return 0 +} + +Buffer.isEncoding = function isEncoding (encoding) { + switch (String(encoding).toLowerCase()) { + case 'hex': + case 'utf8': + case 'utf-8': + case 'ascii': + case 'latin1': + case 'binary': + case 'base64': + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return true + default: + return false + } +} + +Buffer.concat = function concat (list, length) { + if (!Array.isArray(list)) { + throw new TypeError('"list" argument must be an Array of Buffers') + } + + if (list.length === 0) { + return Buffer.alloc(0) + } + + var i + if (length === undefined) { + length = 0 + for (i = 0; i < list.length; ++i) { + length += list[i].length + } + } + + var buffer = Buffer.allocUnsafe(length) + var pos = 0 + for (i = 0; i < list.length; ++i) { + var buf = list[i] + if (isInstance(buf, Uint8Array)) { + buf = Buffer.from(buf) + } + if (!Buffer.isBuffer(buf)) { + throw new TypeError('"list" argument must be an Array of Buffers') + } + buf.copy(buffer, pos) + pos += buf.length + } + return buffer +} + +function byteLength (string, encoding) { + if (Buffer.isBuffer(string)) { + return string.length + } + if (ArrayBuffer.isView(string) || isInstance(string, ArrayBuffer)) { + return string.byteLength + } + if (typeof string !== 'string') { + throw new TypeError( + 'The "string" argument must be one of type string, Buffer, or ArrayBuffer. ' + + 'Received type ' + typeof string + ) + } + + var len = string.length + var mustMatch = (arguments.length > 2 && arguments[2] === true) + if (!mustMatch && len === 0) return 0 + + // Use a for loop to avoid recursion + var loweredCase = false + for (;;) { + switch (encoding) { + case 'ascii': + case 'latin1': + case 'binary': + return len + case 'utf8': + case 'utf-8': + return utf8ToBytes(string).length + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return len * 2 + case 'hex': + return len >>> 1 + case 'base64': + return base64ToBytes(string).length + default: + if (loweredCase) { + return mustMatch ? -1 : utf8ToBytes(string).length // assume utf8 + } + encoding = ('' + encoding).toLowerCase() + loweredCase = true + } + } +} +Buffer.byteLength = byteLength + +function slowToString (encoding, start, end) { + var loweredCase = false + + // No need to verify that "this.length <= MAX_UINT32" since it's a read-only + // property of a typed array. + + // This behaves neither like String nor Uint8Array in that we set start/end + // to their upper/lower bounds if the value passed is out of range. + // undefined is handled specially as per ECMA-262 6th Edition, + // Section 13.3.3.7 Runtime Semantics: KeyedBindingInitialization. + if (start === undefined || start < 0) { + start = 0 + } + // Return early if start > this.length. Done here to prevent potential uint32 + // coercion fail below. + if (start > this.length) { + return '' + } + + if (end === undefined || end > this.length) { + end = this.length + } + + if (end <= 0) { + return '' + } + + // Force coersion to uint32. This will also coerce falsey/NaN values to 0. + end >>>= 0 + start >>>= 0 + + if (end <= start) { + return '' + } + + if (!encoding) encoding = 'utf8' + + while (true) { + switch (encoding) { + case 'hex': + return hexSlice(this, start, end) + + case 'utf8': + case 'utf-8': + return utf8Slice(this, start, end) + + case 'ascii': + return asciiSlice(this, start, end) + + case 'latin1': + case 'binary': + return latin1Slice(this, start, end) + + case 'base64': + return base64Slice(this, start, end) + + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return utf16leSlice(this, start, end) + + default: + if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding) + encoding = (encoding + '').toLowerCase() + loweredCase = true + } + } +} + +// This property is used by `Buffer.isBuffer` (and the `is-buffer` npm package) +// to detect a Buffer instance. It's not possible to use `instanceof Buffer` +// reliably in a browserify context because there could be multiple different +// copies of the 'buffer' package in use. This method works even for Buffer +// instances that were created from another copy of the `buffer` package. +// See: https://github.com/feross/buffer/issues/154 +Buffer.prototype._isBuffer = true + +function swap (b, n, m) { + var i = b[n] + b[n] = b[m] + b[m] = i +} + +Buffer.prototype.swap16 = function swap16 () { + var len = this.length + if (len % 2 !== 0) { + throw new RangeError('Buffer size must be a multiple of 16-bits') + } + for (var i = 0; i < len; i += 2) { + swap(this, i, i + 1) + } + return this +} + +Buffer.prototype.swap32 = function swap32 () { + var len = this.length + if (len % 4 !== 0) { + throw new RangeError('Buffer size must be a multiple of 32-bits') + } + for (var i = 0; i < len; i += 4) { + swap(this, i, i + 3) + swap(this, i + 1, i + 2) + } + return this +} + +Buffer.prototype.swap64 = function swap64 () { + var len = this.length + if (len % 8 !== 0) { + throw new RangeError('Buffer size must be a multiple of 64-bits') + } + for (var i = 0; i < len; i += 8) { + swap(this, i, i + 7) + swap(this, i + 1, i + 6) + swap(this, i + 2, i + 5) + swap(this, i + 3, i + 4) + } + return this +} + +Buffer.prototype.toString = function toString () { + var length = this.length + if (length === 0) return '' + if (arguments.length === 0) return utf8Slice(this, 0, length) + return slowToString.apply(this, arguments) +} + +Buffer.prototype.toLocaleString = Buffer.prototype.toString + +Buffer.prototype.equals = function equals (b) { + if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer') + if (this === b) return true + return Buffer.compare(this, b) === 0 +} + +Buffer.prototype.inspect = function inspect () { + var str = '' + var max = exports.INSPECT_MAX_BYTES + str = this.toString('hex', 0, max).replace(/(.{2})/g, '$1 ').trim() + if (this.length > max) str += ' ... ' + return '' +} + +Buffer.prototype.compare = function compare (target, start, end, thisStart, thisEnd) { + if (isInstance(target, Uint8Array)) { + target = Buffer.from(target, target.offset, target.byteLength) + } + if (!Buffer.isBuffer(target)) { + throw new TypeError( + 'The "target" argument must be one of type Buffer or Uint8Array. ' + + 'Received type ' + (typeof target) + ) + } + + if (start === undefined) { + start = 0 + } + if (end === undefined) { + end = target ? target.length : 0 + } + if (thisStart === undefined) { + thisStart = 0 + } + if (thisEnd === undefined) { + thisEnd = this.length + } + + if (start < 0 || end > target.length || thisStart < 0 || thisEnd > this.length) { + throw new RangeError('out of range index') + } + + if (thisStart >= thisEnd && start >= end) { + return 0 + } + if (thisStart >= thisEnd) { + return -1 + } + if (start >= end) { + return 1 + } + + start >>>= 0 + end >>>= 0 + thisStart >>>= 0 + thisEnd >>>= 0 + + if (this === target) return 0 + + var x = thisEnd - thisStart + var y = end - start + var len = Math.min(x, y) + + var thisCopy = this.slice(thisStart, thisEnd) + var targetCopy = target.slice(start, end) + + for (var i = 0; i < len; ++i) { + if (thisCopy[i] !== targetCopy[i]) { + x = thisCopy[i] + y = targetCopy[i] + break + } + } + + if (x < y) return -1 + if (y < x) return 1 + return 0 +} + +// Finds either the first index of `val` in `buffer` at offset >= `byteOffset`, +// OR the last index of `val` in `buffer` at offset <= `byteOffset`. +// +// Arguments: +// - buffer - a Buffer to search +// - val - a string, Buffer, or number +// - byteOffset - an index into `buffer`; will be clamped to an int32 +// - encoding - an optional encoding, relevant is val is a string +// - dir - true for indexOf, false for lastIndexOf +function bidirectionalIndexOf (buffer, val, byteOffset, encoding, dir) { + // Empty buffer means no match + if (buffer.length === 0) return -1 + + // Normalize byteOffset + if (typeof byteOffset === 'string') { + encoding = byteOffset + byteOffset = 0 + } else if (byteOffset > 0x7fffffff) { + byteOffset = 0x7fffffff + } else if (byteOffset < -0x80000000) { + byteOffset = -0x80000000 + } + byteOffset = +byteOffset // Coerce to Number. + if (numberIsNaN(byteOffset)) { + // byteOffset: it it's undefined, null, NaN, "foo", etc, search whole buffer + byteOffset = dir ? 0 : (buffer.length - 1) + } + + // Normalize byteOffset: negative offsets start from the end of the buffer + if (byteOffset < 0) byteOffset = buffer.length + byteOffset + if (byteOffset >= buffer.length) { + if (dir) return -1 + else byteOffset = buffer.length - 1 + } else if (byteOffset < 0) { + if (dir) byteOffset = 0 + else return -1 + } + + // Normalize val + if (typeof val === 'string') { + val = Buffer.from(val, encoding) + } + + // Finally, search either indexOf (if dir is true) or lastIndexOf + if (Buffer.isBuffer(val)) { + // Special case: looking for empty string/buffer always fails + if (val.length === 0) { + return -1 + } + return arrayIndexOf(buffer, val, byteOffset, encoding, dir) + } else if (typeof val === 'number') { + val = val & 0xFF // Search for a byte value [0-255] + if (typeof Uint8Array.prototype.indexOf === 'function') { + if (dir) { + return Uint8Array.prototype.indexOf.call(buffer, val, byteOffset) + } else { + return Uint8Array.prototype.lastIndexOf.call(buffer, val, byteOffset) + } + } + return arrayIndexOf(buffer, [ val ], byteOffset, encoding, dir) + } + + throw new TypeError('val must be string, number or Buffer') +} + +function arrayIndexOf (arr, val, byteOffset, encoding, dir) { + var indexSize = 1 + var arrLength = arr.length + var valLength = val.length + + if (encoding !== undefined) { + encoding = String(encoding).toLowerCase() + if (encoding === 'ucs2' || encoding === 'ucs-2' || + encoding === 'utf16le' || encoding === 'utf-16le') { + if (arr.length < 2 || val.length < 2) { + return -1 + } + indexSize = 2 + arrLength /= 2 + valLength /= 2 + byteOffset /= 2 + } + } + + function read (buf, i) { + if (indexSize === 1) { + return buf[i] + } else { + return buf.readUInt16BE(i * indexSize) + } + } + + var i + if (dir) { + var foundIndex = -1 + for (i = byteOffset; i < arrLength; i++) { + if (read(arr, i) === read(val, foundIndex === -1 ? 0 : i - foundIndex)) { + if (foundIndex === -1) foundIndex = i + if (i - foundIndex + 1 === valLength) return foundIndex * indexSize + } else { + if (foundIndex !== -1) i -= i - foundIndex + foundIndex = -1 + } + } + } else { + if (byteOffset + valLength > arrLength) byteOffset = arrLength - valLength + for (i = byteOffset; i >= 0; i--) { + var found = true + for (var j = 0; j < valLength; j++) { + if (read(arr, i + j) !== read(val, j)) { + found = false + break + } + } + if (found) return i + } + } + + return -1 +} + +Buffer.prototype.includes = function includes (val, byteOffset, encoding) { + return this.indexOf(val, byteOffset, encoding) !== -1 +} + +Buffer.prototype.indexOf = function indexOf (val, byteOffset, encoding) { + return bidirectionalIndexOf(this, val, byteOffset, encoding, true) +} + +Buffer.prototype.lastIndexOf = function lastIndexOf (val, byteOffset, encoding) { + return bidirectionalIndexOf(this, val, byteOffset, encoding, false) +} + +function hexWrite (buf, string, offset, length) { + offset = Number(offset) || 0 + var remaining = buf.length - offset + if (!length) { + length = remaining + } else { + length = Number(length) + if (length > remaining) { + length = remaining + } + } + + var strLen = string.length + + if (length > strLen / 2) { + length = strLen / 2 + } + for (var i = 0; i < length; ++i) { + var parsed = parseInt(string.substr(i * 2, 2), 16) + if (numberIsNaN(parsed)) return i + buf[offset + i] = parsed + } + return i +} + +function utf8Write (buf, string, offset, length) { + return blitBuffer(utf8ToBytes(string, buf.length - offset), buf, offset, length) +} + +function asciiWrite (buf, string, offset, length) { + return blitBuffer(asciiToBytes(string), buf, offset, length) +} + +function latin1Write (buf, string, offset, length) { + return asciiWrite(buf, string, offset, length) +} + +function base64Write (buf, string, offset, length) { + return blitBuffer(base64ToBytes(string), buf, offset, length) +} + +function ucs2Write (buf, string, offset, length) { + return blitBuffer(utf16leToBytes(string, buf.length - offset), buf, offset, length) +} + +Buffer.prototype.write = function write (string, offset, length, encoding) { + // Buffer#write(string) + if (offset === undefined) { + encoding = 'utf8' + length = this.length + offset = 0 + // Buffer#write(string, encoding) + } else if (length === undefined && typeof offset === 'string') { + encoding = offset + length = this.length + offset = 0 + // Buffer#write(string, offset[, length][, encoding]) + } else if (isFinite(offset)) { + offset = offset >>> 0 + if (isFinite(length)) { + length = length >>> 0 + if (encoding === undefined) encoding = 'utf8' + } else { + encoding = length + length = undefined + } + } else { + throw new Error( + 'Buffer.write(string, encoding, offset[, length]) is no longer supported' + ) + } + + var remaining = this.length - offset + if (length === undefined || length > remaining) length = remaining + + if ((string.length > 0 && (length < 0 || offset < 0)) || offset > this.length) { + throw new RangeError('Attempt to write outside buffer bounds') + } + + if (!encoding) encoding = 'utf8' + + var loweredCase = false + for (;;) { + switch (encoding) { + case 'hex': + return hexWrite(this, string, offset, length) + + case 'utf8': + case 'utf-8': + return utf8Write(this, string, offset, length) + + case 'ascii': + return asciiWrite(this, string, offset, length) + + case 'latin1': + case 'binary': + return latin1Write(this, string, offset, length) + + case 'base64': + // Warning: maxLength not taken into account in base64Write + return base64Write(this, string, offset, length) + + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return ucs2Write(this, string, offset, length) + + default: + if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding) + encoding = ('' + encoding).toLowerCase() + loweredCase = true + } + } +} + +Buffer.prototype.toJSON = function toJSON () { + return { + type: 'Buffer', + data: Array.prototype.slice.call(this._arr || this, 0) + } +} + +function base64Slice (buf, start, end) { + if (start === 0 && end === buf.length) { + return base64.fromByteArray(buf) + } else { + return base64.fromByteArray(buf.slice(start, end)) + } +} + +function utf8Slice (buf, start, end) { + end = Math.min(buf.length, end) + var res = [] + + var i = start + while (i < end) { + var firstByte = buf[i] + var codePoint = null + var bytesPerSequence = (firstByte > 0xEF) ? 4 + : (firstByte > 0xDF) ? 3 + : (firstByte > 0xBF) ? 2 + : 1 + + if (i + bytesPerSequence <= end) { + var secondByte, thirdByte, fourthByte, tempCodePoint + + switch (bytesPerSequence) { + case 1: + if (firstByte < 0x80) { + codePoint = firstByte + } + break + case 2: + secondByte = buf[i + 1] + if ((secondByte & 0xC0) === 0x80) { + tempCodePoint = (firstByte & 0x1F) << 0x6 | (secondByte & 0x3F) + if (tempCodePoint > 0x7F) { + codePoint = tempCodePoint + } + } + break + case 3: + secondByte = buf[i + 1] + thirdByte = buf[i + 2] + if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80) { + tempCodePoint = (firstByte & 0xF) << 0xC | (secondByte & 0x3F) << 0x6 | (thirdByte & 0x3F) + if (tempCodePoint > 0x7FF && (tempCodePoint < 0xD800 || tempCodePoint > 0xDFFF)) { + codePoint = tempCodePoint + } + } + break + case 4: + secondByte = buf[i + 1] + thirdByte = buf[i + 2] + fourthByte = buf[i + 3] + if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80 && (fourthByte & 0xC0) === 0x80) { + tempCodePoint = (firstByte & 0xF) << 0x12 | (secondByte & 0x3F) << 0xC | (thirdByte & 0x3F) << 0x6 | (fourthByte & 0x3F) + if (tempCodePoint > 0xFFFF && tempCodePoint < 0x110000) { + codePoint = tempCodePoint + } + } + } + } + + if (codePoint === null) { + // we did not generate a valid codePoint so insert a + // replacement char (U+FFFD) and advance only 1 byte + codePoint = 0xFFFD + bytesPerSequence = 1 + } else if (codePoint > 0xFFFF) { + // encode to utf16 (surrogate pair dance) + codePoint -= 0x10000 + res.push(codePoint >>> 10 & 0x3FF | 0xD800) + codePoint = 0xDC00 | codePoint & 0x3FF + } + + res.push(codePoint) + i += bytesPerSequence + } + + return decodeCodePointsArray(res) +} + +// Based on http://stackoverflow.com/a/22747272/680742, the browser with +// the lowest limit is Chrome, with 0x10000 args. +// We go 1 magnitude less, for safety +var MAX_ARGUMENTS_LENGTH = 0x1000 + +function decodeCodePointsArray (codePoints) { + var len = codePoints.length + if (len <= MAX_ARGUMENTS_LENGTH) { + return String.fromCharCode.apply(String, codePoints) // avoid extra slice() + } + + // Decode in chunks to avoid "call stack size exceeded". + var res = '' + var i = 0 + while (i < len) { + res += String.fromCharCode.apply( + String, + codePoints.slice(i, i += MAX_ARGUMENTS_LENGTH) + ) + } + return res +} + +function asciiSlice (buf, start, end) { + var ret = '' + end = Math.min(buf.length, end) + + for (var i = start; i < end; ++i) { + ret += String.fromCharCode(buf[i] & 0x7F) + } + return ret +} + +function latin1Slice (buf, start, end) { + var ret = '' + end = Math.min(buf.length, end) + + for (var i = start; i < end; ++i) { + ret += String.fromCharCode(buf[i]) + } + return ret +} + +function hexSlice (buf, start, end) { + var len = buf.length + + if (!start || start < 0) start = 0 + if (!end || end < 0 || end > len) end = len + + var out = '' + for (var i = start; i < end; ++i) { + out += toHex(buf[i]) + } + return out +} + +function utf16leSlice (buf, start, end) { + var bytes = buf.slice(start, end) + var res = '' + for (var i = 0; i < bytes.length; i += 2) { + res += String.fromCharCode(bytes[i] + (bytes[i + 1] * 256)) + } + return res +} + +Buffer.prototype.slice = function slice (start, end) { + var len = this.length + start = ~~start + end = end === undefined ? len : ~~end + + if (start < 0) { + start += len + if (start < 0) start = 0 + } else if (start > len) { + start = len + } + + if (end < 0) { + end += len + if (end < 0) end = 0 + } else if (end > len) { + end = len + } + + if (end < start) end = start + + var newBuf = this.subarray(start, end) + // Return an augmented `Uint8Array` instance + newBuf.__proto__ = Buffer.prototype + return newBuf +} + +/* + * Need to make sure that buffer isn't trying to write out of bounds. + */ +function checkOffset (offset, ext, length) { + if ((offset % 1) !== 0 || offset < 0) throw new RangeError('offset is not uint') + if (offset + ext > length) throw new RangeError('Trying to access beyond buffer length') +} + +Buffer.prototype.readUIntLE = function readUIntLE (offset, byteLength, noAssert) { + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) checkOffset(offset, byteLength, this.length) + + var val = this[offset] + var mul = 1 + var i = 0 + while (++i < byteLength && (mul *= 0x100)) { + val += this[offset + i] * mul + } + + return val +} + +Buffer.prototype.readUIntBE = function readUIntBE (offset, byteLength, noAssert) { + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) { + checkOffset(offset, byteLength, this.length) + } + + var val = this[offset + --byteLength] + var mul = 1 + while (byteLength > 0 && (mul *= 0x100)) { + val += this[offset + --byteLength] * mul + } + + return val +} + +Buffer.prototype.readUInt8 = function readUInt8 (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 1, this.length) + return this[offset] +} + +Buffer.prototype.readUInt16LE = function readUInt16LE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 2, this.length) + return this[offset] | (this[offset + 1] << 8) +} + +Buffer.prototype.readUInt16BE = function readUInt16BE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 2, this.length) + return (this[offset] << 8) | this[offset + 1] +} + +Buffer.prototype.readUInt32LE = function readUInt32LE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + + return ((this[offset]) | + (this[offset + 1] << 8) | + (this[offset + 2] << 16)) + + (this[offset + 3] * 0x1000000) +} + +Buffer.prototype.readUInt32BE = function readUInt32BE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + + return (this[offset] * 0x1000000) + + ((this[offset + 1] << 16) | + (this[offset + 2] << 8) | + this[offset + 3]) +} + +Buffer.prototype.readIntLE = function readIntLE (offset, byteLength, noAssert) { + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) checkOffset(offset, byteLength, this.length) + + var val = this[offset] + var mul = 1 + var i = 0 + while (++i < byteLength && (mul *= 0x100)) { + val += this[offset + i] * mul + } + mul *= 0x80 + + if (val >= mul) val -= Math.pow(2, 8 * byteLength) + + return val +} + +Buffer.prototype.readIntBE = function readIntBE (offset, byteLength, noAssert) { + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) checkOffset(offset, byteLength, this.length) + + var i = byteLength + var mul = 1 + var val = this[offset + --i] + while (i > 0 && (mul *= 0x100)) { + val += this[offset + --i] * mul + } + mul *= 0x80 + + if (val >= mul) val -= Math.pow(2, 8 * byteLength) + + return val +} + +Buffer.prototype.readInt8 = function readInt8 (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 1, this.length) + if (!(this[offset] & 0x80)) return (this[offset]) + return ((0xff - this[offset] + 1) * -1) +} + +Buffer.prototype.readInt16LE = function readInt16LE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 2, this.length) + var val = this[offset] | (this[offset + 1] << 8) + return (val & 0x8000) ? val | 0xFFFF0000 : val +} + +Buffer.prototype.readInt16BE = function readInt16BE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 2, this.length) + var val = this[offset + 1] | (this[offset] << 8) + return (val & 0x8000) ? val | 0xFFFF0000 : val +} + +Buffer.prototype.readInt32LE = function readInt32LE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + + return (this[offset]) | + (this[offset + 1] << 8) | + (this[offset + 2] << 16) | + (this[offset + 3] << 24) +} + +Buffer.prototype.readInt32BE = function readInt32BE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + + return (this[offset] << 24) | + (this[offset + 1] << 16) | + (this[offset + 2] << 8) | + (this[offset + 3]) +} + +Buffer.prototype.readFloatLE = function readFloatLE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + return ieee754.read(this, offset, true, 23, 4) +} + +Buffer.prototype.readFloatBE = function readFloatBE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + return ieee754.read(this, offset, false, 23, 4) +} + +Buffer.prototype.readDoubleLE = function readDoubleLE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 8, this.length) + return ieee754.read(this, offset, true, 52, 8) +} + +Buffer.prototype.readDoubleBE = function readDoubleBE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 8, this.length) + return ieee754.read(this, offset, false, 52, 8) +} + +function checkInt (buf, value, offset, ext, max, min) { + if (!Buffer.isBuffer(buf)) throw new TypeError('"buffer" argument must be a Buffer instance') + if (value > max || value < min) throw new RangeError('"value" argument is out of bounds') + if (offset + ext > buf.length) throw new RangeError('Index out of range') +} + +Buffer.prototype.writeUIntLE = function writeUIntLE (value, offset, byteLength, noAssert) { + value = +value + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) { + var maxBytes = Math.pow(2, 8 * byteLength) - 1 + checkInt(this, value, offset, byteLength, maxBytes, 0) + } + + var mul = 1 + var i = 0 + this[offset] = value & 0xFF + while (++i < byteLength && (mul *= 0x100)) { + this[offset + i] = (value / mul) & 0xFF + } + + return offset + byteLength +} + +Buffer.prototype.writeUIntBE = function writeUIntBE (value, offset, byteLength, noAssert) { + value = +value + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) { + var maxBytes = Math.pow(2, 8 * byteLength) - 1 + checkInt(this, value, offset, byteLength, maxBytes, 0) + } + + var i = byteLength - 1 + var mul = 1 + this[offset + i] = value & 0xFF + while (--i >= 0 && (mul *= 0x100)) { + this[offset + i] = (value / mul) & 0xFF + } + + return offset + byteLength +} + +Buffer.prototype.writeUInt8 = function writeUInt8 (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 1, 0xff, 0) + this[offset] = (value & 0xff) + return offset + 1 +} + +Buffer.prototype.writeUInt16LE = function writeUInt16LE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0) + this[offset] = (value & 0xff) + this[offset + 1] = (value >>> 8) + return offset + 2 +} + +Buffer.prototype.writeUInt16BE = function writeUInt16BE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0) + this[offset] = (value >>> 8) + this[offset + 1] = (value & 0xff) + return offset + 2 +} + +Buffer.prototype.writeUInt32LE = function writeUInt32LE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0) + this[offset + 3] = (value >>> 24) + this[offset + 2] = (value >>> 16) + this[offset + 1] = (value >>> 8) + this[offset] = (value & 0xff) + return offset + 4 +} + +Buffer.prototype.writeUInt32BE = function writeUInt32BE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0) + this[offset] = (value >>> 24) + this[offset + 1] = (value >>> 16) + this[offset + 2] = (value >>> 8) + this[offset + 3] = (value & 0xff) + return offset + 4 +} + +Buffer.prototype.writeIntLE = function writeIntLE (value, offset, byteLength, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) { + var limit = Math.pow(2, (8 * byteLength) - 1) + + checkInt(this, value, offset, byteLength, limit - 1, -limit) + } + + var i = 0 + var mul = 1 + var sub = 0 + this[offset] = value & 0xFF + while (++i < byteLength && (mul *= 0x100)) { + if (value < 0 && sub === 0 && this[offset + i - 1] !== 0) { + sub = 1 + } + this[offset + i] = ((value / mul) >> 0) - sub & 0xFF + } + + return offset + byteLength +} + +Buffer.prototype.writeIntBE = function writeIntBE (value, offset, byteLength, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) { + var limit = Math.pow(2, (8 * byteLength) - 1) + + checkInt(this, value, offset, byteLength, limit - 1, -limit) + } + + var i = byteLength - 1 + var mul = 1 + var sub = 0 + this[offset + i] = value & 0xFF + while (--i >= 0 && (mul *= 0x100)) { + if (value < 0 && sub === 0 && this[offset + i + 1] !== 0) { + sub = 1 + } + this[offset + i] = ((value / mul) >> 0) - sub & 0xFF + } + + return offset + byteLength +} + +Buffer.prototype.writeInt8 = function writeInt8 (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 1, 0x7f, -0x80) + if (value < 0) value = 0xff + value + 1 + this[offset] = (value & 0xff) + return offset + 1 +} + +Buffer.prototype.writeInt16LE = function writeInt16LE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000) + this[offset] = (value & 0xff) + this[offset + 1] = (value >>> 8) + return offset + 2 +} + +Buffer.prototype.writeInt16BE = function writeInt16BE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000) + this[offset] = (value >>> 8) + this[offset + 1] = (value & 0xff) + return offset + 2 +} + +Buffer.prototype.writeInt32LE = function writeInt32LE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000) + this[offset] = (value & 0xff) + this[offset + 1] = (value >>> 8) + this[offset + 2] = (value >>> 16) + this[offset + 3] = (value >>> 24) + return offset + 4 +} + +Buffer.prototype.writeInt32BE = function writeInt32BE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000) + if (value < 0) value = 0xffffffff + value + 1 + this[offset] = (value >>> 24) + this[offset + 1] = (value >>> 16) + this[offset + 2] = (value >>> 8) + this[offset + 3] = (value & 0xff) + return offset + 4 +} + +function checkIEEE754 (buf, value, offset, ext, max, min) { + if (offset + ext > buf.length) throw new RangeError('Index out of range') + if (offset < 0) throw new RangeError('Index out of range') +} + +function writeFloat (buf, value, offset, littleEndian, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) { + checkIEEE754(buf, value, offset, 4, 3.4028234663852886e+38, -3.4028234663852886e+38) + } + ieee754.write(buf, value, offset, littleEndian, 23, 4) + return offset + 4 +} + +Buffer.prototype.writeFloatLE = function writeFloatLE (value, offset, noAssert) { + return writeFloat(this, value, offset, true, noAssert) +} + +Buffer.prototype.writeFloatBE = function writeFloatBE (value, offset, noAssert) { + return writeFloat(this, value, offset, false, noAssert) +} + +function writeDouble (buf, value, offset, littleEndian, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) { + checkIEEE754(buf, value, offset, 8, 1.7976931348623157E+308, -1.7976931348623157E+308) + } + ieee754.write(buf, value, offset, littleEndian, 52, 8) + return offset + 8 +} + +Buffer.prototype.writeDoubleLE = function writeDoubleLE (value, offset, noAssert) { + return writeDouble(this, value, offset, true, noAssert) +} + +Buffer.prototype.writeDoubleBE = function writeDoubleBE (value, offset, noAssert) { + return writeDouble(this, value, offset, false, noAssert) +} + +// copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length) +Buffer.prototype.copy = function copy (target, targetStart, start, end) { + if (!Buffer.isBuffer(target)) throw new TypeError('argument should be a Buffer') + if (!start) start = 0 + if (!end && end !== 0) end = this.length + if (targetStart >= target.length) targetStart = target.length + if (!targetStart) targetStart = 0 + if (end > 0 && end < start) end = start + + // Copy 0 bytes; we're done + if (end === start) return 0 + if (target.length === 0 || this.length === 0) return 0 + + // Fatal error conditions + if (targetStart < 0) { + throw new RangeError('targetStart out of bounds') + } + if (start < 0 || start >= this.length) throw new RangeError('Index out of range') + if (end < 0) throw new RangeError('sourceEnd out of bounds') + + // Are we oob? + if (end > this.length) end = this.length + if (target.length - targetStart < end - start) { + end = target.length - targetStart + start + } + + var len = end - start + + if (this === target && typeof Uint8Array.prototype.copyWithin === 'function') { + // Use built-in when available, missing from IE11 + this.copyWithin(targetStart, start, end) + } else if (this === target && start < targetStart && targetStart < end) { + // descending copy from end + for (var i = len - 1; i >= 0; --i) { + target[i + targetStart] = this[i + start] + } + } else { + Uint8Array.prototype.set.call( + target, + this.subarray(start, end), + targetStart + ) + } + + return len +} + +// Usage: +// buffer.fill(number[, offset[, end]]) +// buffer.fill(buffer[, offset[, end]]) +// buffer.fill(string[, offset[, end]][, encoding]) +Buffer.prototype.fill = function fill (val, start, end, encoding) { + // Handle string cases: + if (typeof val === 'string') { + if (typeof start === 'string') { + encoding = start + start = 0 + end = this.length + } else if (typeof end === 'string') { + encoding = end + end = this.length + } + if (encoding !== undefined && typeof encoding !== 'string') { + throw new TypeError('encoding must be a string') + } + if (typeof encoding === 'string' && !Buffer.isEncoding(encoding)) { + throw new TypeError('Unknown encoding: ' + encoding) + } + if (val.length === 1) { + var code = val.charCodeAt(0) + if ((encoding === 'utf8' && code < 128) || + encoding === 'latin1') { + // Fast path: If `val` fits into a single byte, use that numeric value. + val = code + } + } + } else if (typeof val === 'number') { + val = val & 255 + } + + // Invalid ranges are not set to a default, so can range check early. + if (start < 0 || this.length < start || this.length < end) { + throw new RangeError('Out of range index') + } + + if (end <= start) { + return this + } + + start = start >>> 0 + end = end === undefined ? this.length : end >>> 0 + + if (!val) val = 0 + + var i + if (typeof val === 'number') { + for (i = start; i < end; ++i) { + this[i] = val + } + } else { + var bytes = Buffer.isBuffer(val) + ? val + : Buffer.from(val, encoding) + var len = bytes.length + if (len === 0) { + throw new TypeError('The value "' + val + + '" is invalid for argument "value"') + } + for (i = 0; i < end - start; ++i) { + this[i + start] = bytes[i % len] + } + } + + return this +} + +// HELPER FUNCTIONS +// ================ + +var INVALID_BASE64_RE = /[^+/0-9A-Za-z-_]/g + +function base64clean (str) { + // Node takes equal signs as end of the Base64 encoding + str = str.split('=')[0] + // Node strips out invalid characters like \n and \t from the string, base64-js does not + str = str.trim().replace(INVALID_BASE64_RE, '') + // Node converts strings with length < 2 to '' + if (str.length < 2) return '' + // Node allows for non-padded base64 strings (missing trailing ===), base64-js does not + while (str.length % 4 !== 0) { + str = str + '=' + } + return str +} + +function toHex (n) { + if (n < 16) return '0' + n.toString(16) + return n.toString(16) +} + +function utf8ToBytes (string, units) { + units = units || Infinity + var codePoint + var length = string.length + var leadSurrogate = null + var bytes = [] + + for (var i = 0; i < length; ++i) { + codePoint = string.charCodeAt(i) + + // is surrogate component + if (codePoint > 0xD7FF && codePoint < 0xE000) { + // last char was a lead + if (!leadSurrogate) { + // no lead yet + if (codePoint > 0xDBFF) { + // unexpected trail + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + continue + } else if (i + 1 === length) { + // unpaired lead + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + continue + } + + // valid lead + leadSurrogate = codePoint + + continue + } + + // 2 leads in a row + if (codePoint < 0xDC00) { + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + leadSurrogate = codePoint + continue + } + + // valid surrogate pair + codePoint = (leadSurrogate - 0xD800 << 10 | codePoint - 0xDC00) + 0x10000 + } else if (leadSurrogate) { + // valid bmp char, but last char was a lead + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + } + + leadSurrogate = null + + // encode utf8 + if (codePoint < 0x80) { + if ((units -= 1) < 0) break + bytes.push(codePoint) + } else if (codePoint < 0x800) { + if ((units -= 2) < 0) break + bytes.push( + codePoint >> 0x6 | 0xC0, + codePoint & 0x3F | 0x80 + ) + } else if (codePoint < 0x10000) { + if ((units -= 3) < 0) break + bytes.push( + codePoint >> 0xC | 0xE0, + codePoint >> 0x6 & 0x3F | 0x80, + codePoint & 0x3F | 0x80 + ) + } else if (codePoint < 0x110000) { + if ((units -= 4) < 0) break + bytes.push( + codePoint >> 0x12 | 0xF0, + codePoint >> 0xC & 0x3F | 0x80, + codePoint >> 0x6 & 0x3F | 0x80, + codePoint & 0x3F | 0x80 + ) + } else { + throw new Error('Invalid code point') + } + } + + return bytes +} + +function asciiToBytes (str) { + var byteArray = [] + for (var i = 0; i < str.length; ++i) { + // Node's code seems to be doing this and not & 0x7F.. + byteArray.push(str.charCodeAt(i) & 0xFF) + } + return byteArray +} + +function utf16leToBytes (str, units) { + var c, hi, lo + var byteArray = [] + for (var i = 0; i < str.length; ++i) { + if ((units -= 2) < 0) break + + c = str.charCodeAt(i) + hi = c >> 8 + lo = c % 256 + byteArray.push(lo) + byteArray.push(hi) + } + + return byteArray +} + +function base64ToBytes (str) { + return base64.toByteArray(base64clean(str)) +} + +function blitBuffer (src, dst, offset, length) { + for (var i = 0; i < length; ++i) { + if ((i + offset >= dst.length) || (i >= src.length)) break + dst[i + offset] = src[i] + } + return i +} + +// ArrayBuffer or Uint8Array objects from other contexts (i.e. iframes) do not pass +// the `instanceof` check but they should be treated as of that type. +// See: https://github.com/feross/buffer/issues/166 +function isInstance (obj, type) { + return obj instanceof type || + (obj != null && obj.constructor != null && obj.constructor.name != null && + obj.constructor.name === type.name) +} +function numberIsNaN (obj) { + // For IE11 support + return obj !== obj // eslint-disable-line no-self-compare +} + +}).call(this,require("buffer").Buffer) +},{"base64-js":39,"buffer":43,"ieee754":55}],44:[function(require,module,exports){ +(function (Buffer){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// NOTE: These type checking functions intentionally don't use `instanceof` +// because it is fragile and can be easily faked with `Object.create()`. + +function isArray(arg) { + if (Array.isArray) { + return Array.isArray(arg); + } + return objectToString(arg) === '[object Array]'; +} +exports.isArray = isArray; + +function isBoolean(arg) { + return typeof arg === 'boolean'; +} +exports.isBoolean = isBoolean; + +function isNull(arg) { + return arg === null; +} +exports.isNull = isNull; + +function isNullOrUndefined(arg) { + return arg == null; +} +exports.isNullOrUndefined = isNullOrUndefined; + +function isNumber(arg) { + return typeof arg === 'number'; +} +exports.isNumber = isNumber; + +function isString(arg) { + return typeof arg === 'string'; +} +exports.isString = isString; + +function isSymbol(arg) { + return typeof arg === 'symbol'; +} +exports.isSymbol = isSymbol; + +function isUndefined(arg) { + return arg === void 0; +} +exports.isUndefined = isUndefined; + +function isRegExp(re) { + return objectToString(re) === '[object RegExp]'; +} +exports.isRegExp = isRegExp; + +function isObject(arg) { + return typeof arg === 'object' && arg !== null; +} +exports.isObject = isObject; + +function isDate(d) { + return objectToString(d) === '[object Date]'; +} +exports.isDate = isDate; + +function isError(e) { + return (objectToString(e) === '[object Error]' || e instanceof Error); +} +exports.isError = isError; + +function isFunction(arg) { + return typeof arg === 'function'; +} +exports.isFunction = isFunction; + +function isPrimitive(arg) { + return arg === null || + typeof arg === 'boolean' || + typeof arg === 'number' || + typeof arg === 'string' || + typeof arg === 'symbol' || // ES6 symbol + typeof arg === 'undefined'; +} +exports.isPrimitive = isPrimitive; + +exports.isBuffer = Buffer.isBuffer; + +function objectToString(o) { + return Object.prototype.toString.call(o); +} + +}).call(this,{"isBuffer":require("../../is-buffer/index.js")}) +},{"../../is-buffer/index.js":57}],45:[function(require,module,exports){ +(function (process){ +"use strict"; + +function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } + +/* eslint-env browser */ + +/** + * This is the web browser implementation of `debug()`. + */ +exports.log = log; +exports.formatArgs = formatArgs; +exports.save = save; +exports.load = load; +exports.useColors = useColors; +exports.storage = localstorage(); +/** + * Colors. + */ + +exports.colors = ['#0000CC', '#0000FF', '#0033CC', '#0033FF', '#0066CC', '#0066FF', '#0099CC', '#0099FF', '#00CC00', '#00CC33', '#00CC66', '#00CC99', '#00CCCC', '#00CCFF', '#3300CC', '#3300FF', '#3333CC', '#3333FF', '#3366CC', '#3366FF', '#3399CC', '#3399FF', '#33CC00', '#33CC33', '#33CC66', '#33CC99', '#33CCCC', '#33CCFF', '#6600CC', '#6600FF', '#6633CC', '#6633FF', '#66CC00', '#66CC33', '#9900CC', '#9900FF', '#9933CC', '#9933FF', '#99CC00', '#99CC33', '#CC0000', '#CC0033', '#CC0066', '#CC0099', '#CC00CC', '#CC00FF', '#CC3300', '#CC3333', '#CC3366', '#CC3399', '#CC33CC', '#CC33FF', '#CC6600', '#CC6633', '#CC9900', '#CC9933', '#CCCC00', '#CCCC33', '#FF0000', '#FF0033', '#FF0066', '#FF0099', '#FF00CC', '#FF00FF', '#FF3300', '#FF3333', '#FF3366', '#FF3399', '#FF33CC', '#FF33FF', '#FF6600', '#FF6633', '#FF9900', '#FF9933', '#FFCC00', '#FFCC33']; +/** + * Currently only WebKit-based Web Inspectors, Firefox >= v31, + * and the Firebug extension (any Firefox version) are known + * to support "%c" CSS customizations. + * + * TODO: add a `localStorage` variable to explicitly enable/disable colors + */ +// eslint-disable-next-line complexity + +function useColors() { + // NB: In an Electron preload script, document will be defined but not fully + // initialized. Since we know we're in Chrome, we'll just detect this case + // explicitly + if (typeof window !== 'undefined' && window.process && (window.process.type === 'renderer' || window.process.__nwjs)) { + return true; + } // Internet Explorer and Edge do not support colors. + + + if (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)) { + return false; + } // Is webkit? http://stackoverflow.com/a/16459606/376773 + // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632 + + + return typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance || // Is firebug? http://stackoverflow.com/a/398120/376773 + typeof window !== 'undefined' && window.console && (window.console.firebug || window.console.exception && window.console.table) || // Is firefox >= v31? + // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages + typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31 || // Double check webkit in userAgent just in case we are in a worker + typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/); +} +/** + * Colorize log arguments if enabled. + * + * @api public + */ + + +function formatArgs(args) { + args[0] = (this.useColors ? '%c' : '') + this.namespace + (this.useColors ? ' %c' : ' ') + args[0] + (this.useColors ? '%c ' : ' ') + '+' + module.exports.humanize(this.diff); + + if (!this.useColors) { + return; + } + + var c = 'color: ' + this.color; + args.splice(1, 0, c, 'color: inherit'); // The final "%c" is somewhat tricky, because there could be other + // arguments passed either before or after the %c, so we need to + // figure out the correct index to insert the CSS into + + var index = 0; + var lastC = 0; + args[0].replace(/%[a-zA-Z%]/g, function (match) { + if (match === '%%') { + return; + } + + index++; + + if (match === '%c') { + // We only are interested in the *last* %c + // (the user may have provided their own) + lastC = index; + } + }); + args.splice(lastC, 0, c); +} +/** + * Invokes `console.log()` when available. + * No-op when `console.log` is not a "function". + * + * @api public + */ + + +function log() { + var _console; + + // This hackery is required for IE8/9, where + // the `console.log` function doesn't have 'apply' + return (typeof console === "undefined" ? "undefined" : _typeof(console)) === 'object' && console.log && (_console = console).log.apply(_console, arguments); +} +/** + * Save `namespaces`. + * + * @param {String} namespaces + * @api private + */ + + +function save(namespaces) { + try { + if (namespaces) { + exports.storage.setItem('debug', namespaces); + } else { + exports.storage.removeItem('debug'); + } + } catch (error) {// Swallow + // XXX (@Qix-) should we be logging these? + } +} +/** + * Load `namespaces`. + * + * @return {String} returns the previously persisted debug modes + * @api private + */ + + +function load() { + var r; + + try { + r = exports.storage.getItem('debug'); + } catch (error) {} // Swallow + // XXX (@Qix-) should we be logging these? + // If debug isn't set in LS, and we're in Electron, try to load $DEBUG + + + if (!r && typeof process !== 'undefined' && 'env' in process) { + r = process.env.DEBUG; + } + + return r; +} +/** + * Localstorage attempts to return the localstorage. + * + * This is necessary because safari throws + * when a user disables cookies/localstorage + * and you attempt to access it. + * + * @return {LocalStorage} + * @api private + */ + + +function localstorage() { + try { + // TVMLKit (Apple TV JS Runtime) does not have a window object, just localStorage in the global context + // The Browser also has localStorage in the global context. + return localStorage; + } catch (error) {// Swallow + // XXX (@Qix-) should we be logging these? + } +} + +module.exports = require('./common')(exports); +var formatters = module.exports.formatters; +/** + * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default. + */ + +formatters.j = function (v) { + try { + return JSON.stringify(v); + } catch (error) { + return '[UnexpectedJSONParseError]: ' + error.message; + } +}; + + +}).call(this,require('_process')) +},{"./common":46,"_process":69}],46:[function(require,module,exports){ +"use strict"; + +/** + * This is the common logic for both the Node.js and web browser + * implementations of `debug()`. + */ +function setup(env) { + createDebug.debug = createDebug; + createDebug.default = createDebug; + createDebug.coerce = coerce; + createDebug.disable = disable; + createDebug.enable = enable; + createDebug.enabled = enabled; + createDebug.humanize = require('ms'); + Object.keys(env).forEach(function (key) { + createDebug[key] = env[key]; + }); + /** + * Active `debug` instances. + */ + + createDebug.instances = []; + /** + * The currently active debug mode names, and names to skip. + */ + + createDebug.names = []; + createDebug.skips = []; + /** + * Map of special "%n" handling functions, for the debug "format" argument. + * + * Valid key names are a single, lower or upper-case letter, i.e. "n" and "N". + */ + + createDebug.formatters = {}; + /** + * Selects a color for a debug namespace + * @param {String} namespace The namespace string for the for the debug instance to be colored + * @return {Number|String} An ANSI color code for the given namespace + * @api private + */ + + function selectColor(namespace) { + var hash = 0; + + for (var i = 0; i < namespace.length; i++) { + hash = (hash << 5) - hash + namespace.charCodeAt(i); + hash |= 0; // Convert to 32bit integer + } + + return createDebug.colors[Math.abs(hash) % createDebug.colors.length]; + } + + createDebug.selectColor = selectColor; + /** + * Create a debugger with the given `namespace`. + * + * @param {String} namespace + * @return {Function} + * @api public + */ + + function createDebug(namespace) { + var prevTime; + + function debug() { + // Disabled? + if (!debug.enabled) { + return; + } + + for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + + var self = debug; // Set `diff` timestamp + + var curr = Number(new Date()); + var ms = curr - (prevTime || curr); + self.diff = ms; + self.prev = prevTime; + self.curr = curr; + prevTime = curr; + args[0] = createDebug.coerce(args[0]); + + if (typeof args[0] !== 'string') { + // Anything else let's inspect with %O + args.unshift('%O'); + } // Apply any `formatters` transformations + + + var index = 0; + args[0] = args[0].replace(/%([a-zA-Z%])/g, function (match, format) { + // If we encounter an escaped % then don't increase the array index + if (match === '%%') { + return match; + } + + index++; + var formatter = createDebug.formatters[format]; + + if (typeof formatter === 'function') { + var val = args[index]; + match = formatter.call(self, val); // Now we need to remove `args[index]` since it's inlined in the `format` + + args.splice(index, 1); + index--; + } + + return match; + }); // Apply env-specific formatting (colors, etc.) + + createDebug.formatArgs.call(self, args); + var logFn = self.log || createDebug.log; + logFn.apply(self, args); + } + + debug.namespace = namespace; + debug.enabled = createDebug.enabled(namespace); + debug.useColors = createDebug.useColors(); + debug.color = selectColor(namespace); + debug.destroy = destroy; + debug.extend = extend; // Debug.formatArgs = formatArgs; + // debug.rawLog = rawLog; + // env-specific initialization logic for debug instances + + if (typeof createDebug.init === 'function') { + createDebug.init(debug); + } + + createDebug.instances.push(debug); + return debug; + } + + function destroy() { + var index = createDebug.instances.indexOf(this); + + if (index !== -1) { + createDebug.instances.splice(index, 1); + return true; + } + + return false; + } + + function extend(namespace, delimiter) { + return createDebug(this.namespace + (typeof delimiter === 'undefined' ? ':' : delimiter) + namespace); + } + /** + * Enables a debug mode by namespaces. This can include modes + * separated by a colon and wildcards. + * + * @param {String} namespaces + * @api public + */ + + + function enable(namespaces) { + createDebug.save(namespaces); + createDebug.names = []; + createDebug.skips = []; + var i; + var split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/); + var len = split.length; + + for (i = 0; i < len; i++) { + if (!split[i]) { + // ignore empty strings + continue; + } + + namespaces = split[i].replace(/\*/g, '.*?'); + + if (namespaces[0] === '-') { + createDebug.skips.push(new RegExp('^' + namespaces.substr(1) + '$')); + } else { + createDebug.names.push(new RegExp('^' + namespaces + '$')); + } + } + + for (i = 0; i < createDebug.instances.length; i++) { + var instance = createDebug.instances[i]; + instance.enabled = createDebug.enabled(instance.namespace); + } + } + /** + * Disable debug output. + * + * @api public + */ + + + function disable() { + createDebug.enable(''); + } + /** + * Returns true if the given mode name is enabled, false otherwise. + * + * @param {String} name + * @return {Boolean} + * @api public + */ + + + function enabled(name) { + if (name[name.length - 1] === '*') { + return true; + } + + var i; + var len; + + for (i = 0, len = createDebug.skips.length; i < len; i++) { + if (createDebug.skips[i].test(name)) { + return false; + } + } + + for (i = 0, len = createDebug.names.length; i < len; i++) { + if (createDebug.names[i].test(name)) { + return true; + } + } + + return false; + } + /** + * Coerce `val`. + * + * @param {Mixed} val + * @return {Mixed} + * @api private + */ + + + function coerce(val) { + if (val instanceof Error) { + return val.stack || val.message; + } + + return val; + } + + createDebug.enable(createDebug.load()); + return createDebug; +} + +module.exports = setup; + + +},{"ms":60}],47:[function(require,module,exports){ +'use strict'; + +var keys = require('object-keys'); +var hasSymbols = typeof Symbol === 'function' && typeof Symbol('foo') === 'symbol'; + +var toStr = Object.prototype.toString; +var concat = Array.prototype.concat; +var origDefineProperty = Object.defineProperty; + +var isFunction = function (fn) { + return typeof fn === 'function' && toStr.call(fn) === '[object Function]'; +}; + +var arePropertyDescriptorsSupported = function () { + var obj = {}; + try { + origDefineProperty(obj, 'x', { enumerable: false, value: obj }); + // eslint-disable-next-line no-unused-vars, no-restricted-syntax + for (var _ in obj) { // jscs:ignore disallowUnusedVariables + return false; + } + return obj.x === obj; + } catch (e) { /* this is IE 8. */ + return false; + } +}; +var supportsDescriptors = origDefineProperty && arePropertyDescriptorsSupported(); + +var defineProperty = function (object, name, value, predicate) { + if (name in object && (!isFunction(predicate) || !predicate())) { + return; + } + if (supportsDescriptors) { + origDefineProperty(object, name, { + configurable: true, + enumerable: false, + value: value, + writable: true + }); + } else { + object[name] = value; + } +}; + +var defineProperties = function (object, map) { + var predicates = arguments.length > 2 ? arguments[2] : {}; + var props = keys(map); + if (hasSymbols) { + props = concat.call(props, Object.getOwnPropertySymbols(map)); + } + for (var i = 0; i < props.length; i += 1) { + defineProperty(object, props[i], map[props[i]], predicates[props[i]]); + } +}; + +defineProperties.supportsDescriptors = !!supportsDescriptors; + +module.exports = defineProperties; + +},{"object-keys":62}],48:[function(require,module,exports){ +/*! + + diff v3.5.0 + +Software License Agreement (BSD License) + +Copyright (c) 2009-2015, Kevin Decker + +All rights reserved. + +Redistribution and use of this software in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of Kevin Decker nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +@license +*/ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(); + else if(false) + define([], factory); + else if(typeof exports === 'object') + exports["JsDiff"] = factory(); + else + root["JsDiff"] = factory(); +})(this, function() { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; + +/******/ // The require function +/******/ function __webpack_require__(moduleId) { + +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) +/******/ return installedModules[moduleId].exports; + +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ exports: {}, +/******/ id: moduleId, +/******/ loaded: false +/******/ }; + +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); + +/******/ // Flag the module as loaded +/******/ module.loaded = true; + +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } + + +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; + +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; + +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; + +/******/ // Load entry module and return exports +/******/ return __webpack_require__(0); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ (function(module, exports, __webpack_require__) { + + /*istanbul ignore start*/'use strict'; + + exports.__esModule = true; + exports.canonicalize = exports.convertChangesToXML = exports.convertChangesToDMP = exports.merge = exports.parsePatch = exports.applyPatches = exports.applyPatch = exports.createPatch = exports.createTwoFilesPatch = exports.structuredPatch = exports.diffArrays = exports.diffJson = exports.diffCss = exports.diffSentences = exports.diffTrimmedLines = exports.diffLines = exports.diffWordsWithSpace = exports.diffWords = exports.diffChars = exports.Diff = undefined; + + /*istanbul ignore end*/var /*istanbul ignore start*/_base = __webpack_require__(1) /*istanbul ignore end*/; + + /*istanbul ignore start*/var _base2 = _interopRequireDefault(_base); + + /*istanbul ignore end*/var /*istanbul ignore start*/_character = __webpack_require__(2) /*istanbul ignore end*/; + + var /*istanbul ignore start*/_word = __webpack_require__(3) /*istanbul ignore end*/; + + var /*istanbul ignore start*/_line = __webpack_require__(5) /*istanbul ignore end*/; + + var /*istanbul ignore start*/_sentence = __webpack_require__(6) /*istanbul ignore end*/; + + var /*istanbul ignore start*/_css = __webpack_require__(7) /*istanbul ignore end*/; + + var /*istanbul ignore start*/_json = __webpack_require__(8) /*istanbul ignore end*/; + + var /*istanbul ignore start*/_array = __webpack_require__(9) /*istanbul ignore end*/; + + var /*istanbul ignore start*/_apply = __webpack_require__(10) /*istanbul ignore end*/; + + var /*istanbul ignore start*/_parse = __webpack_require__(11) /*istanbul ignore end*/; + + var /*istanbul ignore start*/_merge = __webpack_require__(13) /*istanbul ignore end*/; + + var /*istanbul ignore start*/_create = __webpack_require__(14) /*istanbul ignore end*/; + + var /*istanbul ignore start*/_dmp = __webpack_require__(16) /*istanbul ignore end*/; + + var /*istanbul ignore start*/_xml = __webpack_require__(17) /*istanbul ignore end*/; + + /*istanbul ignore start*/function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + + /* See LICENSE file for terms of use */ + + /* + * Text diff implementation. + * + * This library supports the following APIS: + * JsDiff.diffChars: Character by character diff + * JsDiff.diffWords: Word (as defined by \b regex) diff which ignores whitespace + * JsDiff.diffLines: Line based diff + * + * JsDiff.diffCss: Diff targeted at CSS content + * + * These methods are based on the implementation proposed in + * "An O(ND) Difference Algorithm and its Variations" (Myers, 1986). + * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.4.6927 + */ + exports. /*istanbul ignore end*/Diff = _base2['default']; + /*istanbul ignore start*/exports. /*istanbul ignore end*/diffChars = _character.diffChars; + /*istanbul ignore start*/exports. /*istanbul ignore end*/diffWords = _word.diffWords; + /*istanbul ignore start*/exports. /*istanbul ignore end*/diffWordsWithSpace = _word.diffWordsWithSpace; + /*istanbul ignore start*/exports. /*istanbul ignore end*/diffLines = _line.diffLines; + /*istanbul ignore start*/exports. /*istanbul ignore end*/diffTrimmedLines = _line.diffTrimmedLines; + /*istanbul ignore start*/exports. /*istanbul ignore end*/diffSentences = _sentence.diffSentences; + /*istanbul ignore start*/exports. /*istanbul ignore end*/diffCss = _css.diffCss; + /*istanbul ignore start*/exports. /*istanbul ignore end*/diffJson = _json.diffJson; + /*istanbul ignore start*/exports. /*istanbul ignore end*/diffArrays = _array.diffArrays; + /*istanbul ignore start*/exports. /*istanbul ignore end*/structuredPatch = _create.structuredPatch; + /*istanbul ignore start*/exports. /*istanbul ignore end*/createTwoFilesPatch = _create.createTwoFilesPatch; + /*istanbul ignore start*/exports. /*istanbul ignore end*/createPatch = _create.createPatch; + /*istanbul ignore start*/exports. /*istanbul ignore end*/applyPatch = _apply.applyPatch; + /*istanbul ignore start*/exports. /*istanbul ignore end*/applyPatches = _apply.applyPatches; + /*istanbul ignore start*/exports. /*istanbul ignore end*/parsePatch = _parse.parsePatch; + /*istanbul ignore start*/exports. /*istanbul ignore end*/merge = _merge.merge; + /*istanbul ignore start*/exports. /*istanbul ignore end*/convertChangesToDMP = _dmp.convertChangesToDMP; + /*istanbul ignore start*/exports. /*istanbul ignore end*/convertChangesToXML = _xml.convertChangesToXML; + /*istanbul ignore start*/exports. /*istanbul ignore end*/canonicalize = _json.canonicalize; + + + +/***/ }), +/* 1 */ +/***/ (function(module, exports) { + + /*istanbul ignore start*/'use strict'; + + exports.__esModule = true; + exports['default'] = /*istanbul ignore end*/Diff; + function Diff() {} + + Diff.prototype = { + /*istanbul ignore start*/ /*istanbul ignore end*/diff: function diff(oldString, newString) { + /*istanbul ignore start*/var /*istanbul ignore end*/options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; + + var callback = options.callback; + if (typeof options === 'function') { + callback = options; + options = {}; + } + this.options = options; + + var self = this; + + function done(value) { + if (callback) { + setTimeout(function () { + callback(undefined, value); + }, 0); + return true; + } else { + return value; + } + } + + // Allow subclasses to massage the input prior to running + oldString = this.castInput(oldString); + newString = this.castInput(newString); + + oldString = this.removeEmpty(this.tokenize(oldString)); + newString = this.removeEmpty(this.tokenize(newString)); + + var newLen = newString.length, + oldLen = oldString.length; + var editLength = 1; + var maxEditLength = newLen + oldLen; + var bestPath = [{ newPos: -1, components: [] }]; + + // Seed editLength = 0, i.e. the content starts with the same values + var oldPos = this.extractCommon(bestPath[0], newString, oldString, 0); + if (bestPath[0].newPos + 1 >= newLen && oldPos + 1 >= oldLen) { + // Identity per the equality and tokenizer + return done([{ value: this.join(newString), count: newString.length }]); + } + + // Main worker method. checks all permutations of a given edit length for acceptance. + function execEditLength() { + for (var diagonalPath = -1 * editLength; diagonalPath <= editLength; diagonalPath += 2) { + var basePath = /*istanbul ignore start*/void 0 /*istanbul ignore end*/; + var addPath = bestPath[diagonalPath - 1], + removePath = bestPath[diagonalPath + 1], + _oldPos = (removePath ? removePath.newPos : 0) - diagonalPath; + if (addPath) { + // No one else is going to attempt to use this value, clear it + bestPath[diagonalPath - 1] = undefined; + } + + var canAdd = addPath && addPath.newPos + 1 < newLen, + canRemove = removePath && 0 <= _oldPos && _oldPos < oldLen; + if (!canAdd && !canRemove) { + // If this path is a terminal then prune + bestPath[diagonalPath] = undefined; + continue; + } + + // Select the diagonal that we want to branch from. We select the prior + // path whose position in the new string is the farthest from the origin + // and does not pass the bounds of the diff graph + if (!canAdd || canRemove && addPath.newPos < removePath.newPos) { + basePath = clonePath(removePath); + self.pushComponent(basePath.components, undefined, true); + } else { + basePath = addPath; // No need to clone, we've pulled it from the list + basePath.newPos++; + self.pushComponent(basePath.components, true, undefined); + } + + _oldPos = self.extractCommon(basePath, newString, oldString, diagonalPath); + + // If we have hit the end of both strings, then we are done + if (basePath.newPos + 1 >= newLen && _oldPos + 1 >= oldLen) { + return done(buildValues(self, basePath.components, newString, oldString, self.useLongestToken)); + } else { + // Otherwise track this path as a potential candidate and continue. + bestPath[diagonalPath] = basePath; + } + } + + editLength++; + } + + // Performs the length of edit iteration. Is a bit fugly as this has to support the + // sync and async mode which is never fun. Loops over execEditLength until a value + // is produced. + if (callback) { + (function exec() { + setTimeout(function () { + // This should not happen, but we want to be safe. + /* istanbul ignore next */ + if (editLength > maxEditLength) { + return callback(); + } + + if (!execEditLength()) { + exec(); + } + }, 0); + })(); + } else { + while (editLength <= maxEditLength) { + var ret = execEditLength(); + if (ret) { + return ret; + } + } + } + }, + /*istanbul ignore start*/ /*istanbul ignore end*/pushComponent: function pushComponent(components, added, removed) { + var last = components[components.length - 1]; + if (last && last.added === added && last.removed === removed) { + // We need to clone here as the component clone operation is just + // as shallow array clone + components[components.length - 1] = { count: last.count + 1, added: added, removed: removed }; + } else { + components.push({ count: 1, added: added, removed: removed }); + } + }, + /*istanbul ignore start*/ /*istanbul ignore end*/extractCommon: function extractCommon(basePath, newString, oldString, diagonalPath) { + var newLen = newString.length, + oldLen = oldString.length, + newPos = basePath.newPos, + oldPos = newPos - diagonalPath, + commonCount = 0; + while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(newString[newPos + 1], oldString[oldPos + 1])) { + newPos++; + oldPos++; + commonCount++; + } + + if (commonCount) { + basePath.components.push({ count: commonCount }); + } + + basePath.newPos = newPos; + return oldPos; + }, + /*istanbul ignore start*/ /*istanbul ignore end*/equals: function equals(left, right) { + if (this.options.comparator) { + return this.options.comparator(left, right); + } else { + return left === right || this.options.ignoreCase && left.toLowerCase() === right.toLowerCase(); + } + }, + /*istanbul ignore start*/ /*istanbul ignore end*/removeEmpty: function removeEmpty(array) { + var ret = []; + for (var i = 0; i < array.length; i++) { + if (array[i]) { + ret.push(array[i]); + } + } + return ret; + }, + /*istanbul ignore start*/ /*istanbul ignore end*/castInput: function castInput(value) { + return value; + }, + /*istanbul ignore start*/ /*istanbul ignore end*/tokenize: function tokenize(value) { + return value.split(''); + }, + /*istanbul ignore start*/ /*istanbul ignore end*/join: function join(chars) { + return chars.join(''); + } + }; + + function buildValues(diff, components, newString, oldString, useLongestToken) { + var componentPos = 0, + componentLen = components.length, + newPos = 0, + oldPos = 0; + + for (; componentPos < componentLen; componentPos++) { + var component = components[componentPos]; + if (!component.removed) { + if (!component.added && useLongestToken) { + var value = newString.slice(newPos, newPos + component.count); + value = value.map(function (value, i) { + var oldValue = oldString[oldPos + i]; + return oldValue.length > value.length ? oldValue : value; + }); + + component.value = diff.join(value); + } else { + component.value = diff.join(newString.slice(newPos, newPos + component.count)); + } + newPos += component.count; + + // Common case + if (!component.added) { + oldPos += component.count; + } + } else { + component.value = diff.join(oldString.slice(oldPos, oldPos + component.count)); + oldPos += component.count; + + // Reverse add and remove so removes are output first to match common convention + // The diffing algorithm is tied to add then remove output and this is the simplest + // route to get the desired output with minimal overhead. + if (componentPos && components[componentPos - 1].added) { + var tmp = components[componentPos - 1]; + components[componentPos - 1] = components[componentPos]; + components[componentPos] = tmp; + } + } + } + + // Special case handle for when one terminal is ignored (i.e. whitespace). + // For this case we merge the terminal into the prior string and drop the change. + // This is only available for string mode. + var lastComponent = components[componentLen - 1]; + if (componentLen > 1 && typeof lastComponent.value === 'string' && (lastComponent.added || lastComponent.removed) && diff.equals('', lastComponent.value)) { + components[componentLen - 2].value += lastComponent.value; + components.pop(); + } + + return components; + } + + function clonePath(path) { + return { newPos: path.newPos, components: path.components.slice(0) }; + } + + + +/***/ }), +/* 2 */ +/***/ (function(module, exports, __webpack_require__) { + + /*istanbul ignore start*/'use strict'; + + exports.__esModule = true; + exports.characterDiff = undefined; + exports. /*istanbul ignore end*/diffChars = diffChars; + + var /*istanbul ignore start*/_base = __webpack_require__(1) /*istanbul ignore end*/; + + /*istanbul ignore start*/var _base2 = _interopRequireDefault(_base); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + + /*istanbul ignore end*/var characterDiff = /*istanbul ignore start*/exports. /*istanbul ignore end*/characterDiff = new /*istanbul ignore start*/_base2['default'] /*istanbul ignore end*/(); + function diffChars(oldStr, newStr, options) { + return characterDiff.diff(oldStr, newStr, options); + } + + + +/***/ }), +/* 3 */ +/***/ (function(module, exports, __webpack_require__) { + + /*istanbul ignore start*/'use strict'; + + exports.__esModule = true; + exports.wordDiff = undefined; + exports. /*istanbul ignore end*/diffWords = diffWords; + /*istanbul ignore start*/exports. /*istanbul ignore end*/diffWordsWithSpace = diffWordsWithSpace; + + var /*istanbul ignore start*/_base = __webpack_require__(1) /*istanbul ignore end*/; + + /*istanbul ignore start*/var _base2 = _interopRequireDefault(_base); + + /*istanbul ignore end*/var /*istanbul ignore start*/_params = __webpack_require__(4) /*istanbul ignore end*/; + + /*istanbul ignore start*/function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + + /*istanbul ignore end*/ // Based on https://en.wikipedia.org/wiki/Latin_script_in_Unicode + // + // Ranges and exceptions: + // Latin-1 Supplement, 0080–00FF + // - U+00D7 × Multiplication sign + // - U+00F7 ÷ Division sign + // Latin Extended-A, 0100–017F + // Latin Extended-B, 0180–024F + // IPA Extensions, 0250–02AF + // Spacing Modifier Letters, 02B0–02FF + // - U+02C7 ˇ ˇ Caron + // - U+02D8 ˘ ˘ Breve + // - U+02D9 ˙ ˙ Dot Above + // - U+02DA ˚ ˚ Ring Above + // - U+02DB ˛ ˛ Ogonek + // - U+02DC ˜ ˜ Small Tilde + // - U+02DD ˝ ˝ Double Acute Accent + // Latin Extended Additional, 1E00–1EFF + var extendedWordChars = /^[A-Za-z\xC0-\u02C6\u02C8-\u02D7\u02DE-\u02FF\u1E00-\u1EFF]+$/; + + var reWhitespace = /\S/; + + var wordDiff = /*istanbul ignore start*/exports. /*istanbul ignore end*/wordDiff = new /*istanbul ignore start*/_base2['default'] /*istanbul ignore end*/(); + wordDiff.equals = function (left, right) { + if (this.options.ignoreCase) { + left = left.toLowerCase(); + right = right.toLowerCase(); + } + return left === right || this.options.ignoreWhitespace && !reWhitespace.test(left) && !reWhitespace.test(right); + }; + wordDiff.tokenize = function (value) { + var tokens = value.split(/(\s+|\b)/); + + // Join the boundary splits that we do not consider to be boundaries. This is primarily the extended Latin character set. + for (var i = 0; i < tokens.length - 1; i++) { + // If we have an empty string in the next field and we have only word chars before and after, merge + if (!tokens[i + 1] && tokens[i + 2] && extendedWordChars.test(tokens[i]) && extendedWordChars.test(tokens[i + 2])) { + tokens[i] += tokens[i + 2]; + tokens.splice(i + 1, 2); + i--; + } + } + + return tokens; + }; + + function diffWords(oldStr, newStr, options) { + options = /*istanbul ignore start*/(0, _params.generateOptions) /*istanbul ignore end*/(options, { ignoreWhitespace: true }); + return wordDiff.diff(oldStr, newStr, options); + } + + function diffWordsWithSpace(oldStr, newStr, options) { + return wordDiff.diff(oldStr, newStr, options); + } + + + +/***/ }), +/* 4 */ +/***/ (function(module, exports) { + + /*istanbul ignore start*/'use strict'; + + exports.__esModule = true; + exports. /*istanbul ignore end*/generateOptions = generateOptions; + function generateOptions(options, defaults) { + if (typeof options === 'function') { + defaults.callback = options; + } else if (options) { + for (var name in options) { + /* istanbul ignore else */ + if (options.hasOwnProperty(name)) { + defaults[name] = options[name]; + } + } + } + return defaults; + } + + + +/***/ }), +/* 5 */ +/***/ (function(module, exports, __webpack_require__) { + + /*istanbul ignore start*/'use strict'; + + exports.__esModule = true; + exports.lineDiff = undefined; + exports. /*istanbul ignore end*/diffLines = diffLines; + /*istanbul ignore start*/exports. /*istanbul ignore end*/diffTrimmedLines = diffTrimmedLines; + + var /*istanbul ignore start*/_base = __webpack_require__(1) /*istanbul ignore end*/; + + /*istanbul ignore start*/var _base2 = _interopRequireDefault(_base); + + /*istanbul ignore end*/var /*istanbul ignore start*/_params = __webpack_require__(4) /*istanbul ignore end*/; + + /*istanbul ignore start*/function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + + /*istanbul ignore end*/var lineDiff = /*istanbul ignore start*/exports. /*istanbul ignore end*/lineDiff = new /*istanbul ignore start*/_base2['default'] /*istanbul ignore end*/(); + lineDiff.tokenize = function (value) { + var retLines = [], + linesAndNewlines = value.split(/(\n|\r\n)/); + + // Ignore the final empty token that occurs if the string ends with a new line + if (!linesAndNewlines[linesAndNewlines.length - 1]) { + linesAndNewlines.pop(); + } + + // Merge the content and line separators into single tokens + for (var i = 0; i < linesAndNewlines.length; i++) { + var line = linesAndNewlines[i]; + + if (i % 2 && !this.options.newlineIsToken) { + retLines[retLines.length - 1] += line; + } else { + if (this.options.ignoreWhitespace) { + line = line.trim(); + } + retLines.push(line); + } + } + + return retLines; + }; + + function diffLines(oldStr, newStr, callback) { + return lineDiff.diff(oldStr, newStr, callback); + } + function diffTrimmedLines(oldStr, newStr, callback) { + var options = /*istanbul ignore start*/(0, _params.generateOptions) /*istanbul ignore end*/(callback, { ignoreWhitespace: true }); + return lineDiff.diff(oldStr, newStr, options); + } + + + +/***/ }), +/* 6 */ +/***/ (function(module, exports, __webpack_require__) { + + /*istanbul ignore start*/'use strict'; + + exports.__esModule = true; + exports.sentenceDiff = undefined; + exports. /*istanbul ignore end*/diffSentences = diffSentences; + + var /*istanbul ignore start*/_base = __webpack_require__(1) /*istanbul ignore end*/; + + /*istanbul ignore start*/var _base2 = _interopRequireDefault(_base); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + + /*istanbul ignore end*/var sentenceDiff = /*istanbul ignore start*/exports. /*istanbul ignore end*/sentenceDiff = new /*istanbul ignore start*/_base2['default'] /*istanbul ignore end*/(); + sentenceDiff.tokenize = function (value) { + return value.split(/(\S.+?[.!?])(?=\s+|$)/); + }; + + function diffSentences(oldStr, newStr, callback) { + return sentenceDiff.diff(oldStr, newStr, callback); + } + + + +/***/ }), +/* 7 */ +/***/ (function(module, exports, __webpack_require__) { + + /*istanbul ignore start*/'use strict'; + + exports.__esModule = true; + exports.cssDiff = undefined; + exports. /*istanbul ignore end*/diffCss = diffCss; + + var /*istanbul ignore start*/_base = __webpack_require__(1) /*istanbul ignore end*/; + + /*istanbul ignore start*/var _base2 = _interopRequireDefault(_base); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + + /*istanbul ignore end*/var cssDiff = /*istanbul ignore start*/exports. /*istanbul ignore end*/cssDiff = new /*istanbul ignore start*/_base2['default'] /*istanbul ignore end*/(); + cssDiff.tokenize = function (value) { + return value.split(/([{}:;,]|\s+)/); + }; + + function diffCss(oldStr, newStr, callback) { + return cssDiff.diff(oldStr, newStr, callback); + } + + + +/***/ }), +/* 8 */ +/***/ (function(module, exports, __webpack_require__) { + + /*istanbul ignore start*/'use strict'; + + exports.__esModule = true; + exports.jsonDiff = undefined; + + var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + + exports. /*istanbul ignore end*/diffJson = diffJson; + /*istanbul ignore start*/exports. /*istanbul ignore end*/canonicalize = canonicalize; + + var /*istanbul ignore start*/_base = __webpack_require__(1) /*istanbul ignore end*/; + + /*istanbul ignore start*/var _base2 = _interopRequireDefault(_base); + + /*istanbul ignore end*/var /*istanbul ignore start*/_line = __webpack_require__(5) /*istanbul ignore end*/; + + /*istanbul ignore start*/function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + + /*istanbul ignore end*/var objectPrototypeToString = Object.prototype.toString; + + var jsonDiff = /*istanbul ignore start*/exports. /*istanbul ignore end*/jsonDiff = new /*istanbul ignore start*/_base2['default'] /*istanbul ignore end*/(); + // Discriminate between two lines of pretty-printed, serialized JSON where one of them has a + // dangling comma and the other doesn't. Turns out including the dangling comma yields the nicest output: + jsonDiff.useLongestToken = true; + + jsonDiff.tokenize = /*istanbul ignore start*/_line.lineDiff /*istanbul ignore end*/.tokenize; + jsonDiff.castInput = function (value) { + /*istanbul ignore start*/var _options = /*istanbul ignore end*/this.options, + undefinedReplacement = _options.undefinedReplacement, + _options$stringifyRep = _options.stringifyReplacer, + stringifyReplacer = _options$stringifyRep === undefined ? function (k, v) /*istanbul ignore start*/{ + return (/*istanbul ignore end*/typeof v === 'undefined' ? undefinedReplacement : v + ); + } : _options$stringifyRep; + + + return typeof value === 'string' ? value : JSON.stringify(canonicalize(value, null, null, stringifyReplacer), stringifyReplacer, ' '); + }; + jsonDiff.equals = function (left, right) { + return (/*istanbul ignore start*/_base2['default'] /*istanbul ignore end*/.prototype.equals.call(jsonDiff, left.replace(/,([\r\n])/g, '$1'), right.replace(/,([\r\n])/g, '$1')) + ); + }; + + function diffJson(oldObj, newObj, options) { + return jsonDiff.diff(oldObj, newObj, options); + } + + // This function handles the presence of circular references by bailing out when encountering an + // object that is already on the "stack" of items being processed. Accepts an optional replacer + function canonicalize(obj, stack, replacementStack, replacer, key) { + stack = stack || []; + replacementStack = replacementStack || []; + + if (replacer) { + obj = replacer(key, obj); + } + + var i = /*istanbul ignore start*/void 0 /*istanbul ignore end*/; + + for (i = 0; i < stack.length; i += 1) { + if (stack[i] === obj) { + return replacementStack[i]; + } + } + + var canonicalizedObj = /*istanbul ignore start*/void 0 /*istanbul ignore end*/; + + if ('[object Array]' === objectPrototypeToString.call(obj)) { + stack.push(obj); + canonicalizedObj = new Array(obj.length); + replacementStack.push(canonicalizedObj); + for (i = 0; i < obj.length; i += 1) { + canonicalizedObj[i] = canonicalize(obj[i], stack, replacementStack, replacer, key); + } + stack.pop(); + replacementStack.pop(); + return canonicalizedObj; + } + + if (obj && obj.toJSON) { + obj = obj.toJSON(); + } + + if ( /*istanbul ignore start*/(typeof /*istanbul ignore end*/obj === 'undefined' ? 'undefined' : _typeof(obj)) === 'object' && obj !== null) { + stack.push(obj); + canonicalizedObj = {}; + replacementStack.push(canonicalizedObj); + var sortedKeys = [], + _key = /*istanbul ignore start*/void 0 /*istanbul ignore end*/; + for (_key in obj) { + /* istanbul ignore else */ + if (obj.hasOwnProperty(_key)) { + sortedKeys.push(_key); + } + } + sortedKeys.sort(); + for (i = 0; i < sortedKeys.length; i += 1) { + _key = sortedKeys[i]; + canonicalizedObj[_key] = canonicalize(obj[_key], stack, replacementStack, replacer, _key); + } + stack.pop(); + replacementStack.pop(); + } else { + canonicalizedObj = obj; + } + return canonicalizedObj; + } + + + +/***/ }), +/* 9 */ +/***/ (function(module, exports, __webpack_require__) { + + /*istanbul ignore start*/'use strict'; + + exports.__esModule = true; + exports.arrayDiff = undefined; + exports. /*istanbul ignore end*/diffArrays = diffArrays; + + var /*istanbul ignore start*/_base = __webpack_require__(1) /*istanbul ignore end*/; + + /*istanbul ignore start*/var _base2 = _interopRequireDefault(_base); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + + /*istanbul ignore end*/var arrayDiff = /*istanbul ignore start*/exports. /*istanbul ignore end*/arrayDiff = new /*istanbul ignore start*/_base2['default'] /*istanbul ignore end*/(); + arrayDiff.tokenize = function (value) { + return value.slice(); + }; + arrayDiff.join = arrayDiff.removeEmpty = function (value) { + return value; + }; + + function diffArrays(oldArr, newArr, callback) { + return arrayDiff.diff(oldArr, newArr, callback); + } + + + +/***/ }), +/* 10 */ +/***/ (function(module, exports, __webpack_require__) { + + /*istanbul ignore start*/'use strict'; + + exports.__esModule = true; + exports. /*istanbul ignore end*/applyPatch = applyPatch; + /*istanbul ignore start*/exports. /*istanbul ignore end*/applyPatches = applyPatches; + + var /*istanbul ignore start*/_parse = __webpack_require__(11) /*istanbul ignore end*/; + + var /*istanbul ignore start*/_distanceIterator = __webpack_require__(12) /*istanbul ignore end*/; + + /*istanbul ignore start*/var _distanceIterator2 = _interopRequireDefault(_distanceIterator); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + + /*istanbul ignore end*/function applyPatch(source, uniDiff) { + /*istanbul ignore start*/var /*istanbul ignore end*/options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; + + if (typeof uniDiff === 'string') { + uniDiff = /*istanbul ignore start*/(0, _parse.parsePatch) /*istanbul ignore end*/(uniDiff); + } + + if (Array.isArray(uniDiff)) { + if (uniDiff.length > 1) { + throw new Error('applyPatch only works with a single input.'); + } + + uniDiff = uniDiff[0]; + } + + // Apply the diff to the input + var lines = source.split(/\r\n|[\n\v\f\r\x85]/), + delimiters = source.match(/\r\n|[\n\v\f\r\x85]/g) || [], + hunks = uniDiff.hunks, + compareLine = options.compareLine || function (lineNumber, line, operation, patchContent) /*istanbul ignore start*/{ + return (/*istanbul ignore end*/line === patchContent + ); + }, + errorCount = 0, + fuzzFactor = options.fuzzFactor || 0, + minLine = 0, + offset = 0, + removeEOFNL = /*istanbul ignore start*/void 0 /*istanbul ignore end*/, + addEOFNL = /*istanbul ignore start*/void 0 /*istanbul ignore end*/; + + /** + * Checks if the hunk exactly fits on the provided location + */ + function hunkFits(hunk, toPos) { + for (var j = 0; j < hunk.lines.length; j++) { + var line = hunk.lines[j], + operation = line.length > 0 ? line[0] : ' ', + content = line.length > 0 ? line.substr(1) : line; + + if (operation === ' ' || operation === '-') { + // Context sanity check + if (!compareLine(toPos + 1, lines[toPos], operation, content)) { + errorCount++; + + if (errorCount > fuzzFactor) { + return false; + } + } + toPos++; + } + } + + return true; + } + + // Search best fit offsets for each hunk based on the previous ones + for (var i = 0; i < hunks.length; i++) { + var hunk = hunks[i], + maxLine = lines.length - hunk.oldLines, + localOffset = 0, + toPos = offset + hunk.oldStart - 1; + + var iterator = /*istanbul ignore start*/(0, _distanceIterator2['default']) /*istanbul ignore end*/(toPos, minLine, maxLine); + + for (; localOffset !== undefined; localOffset = iterator()) { + if (hunkFits(hunk, toPos + localOffset)) { + hunk.offset = offset += localOffset; + break; + } + } + + if (localOffset === undefined) { + return false; + } + + // Set lower text limit to end of the current hunk, so next ones don't try + // to fit over already patched text + minLine = hunk.offset + hunk.oldStart + hunk.oldLines; + } + + // Apply patch hunks + var diffOffset = 0; + for (var _i = 0; _i < hunks.length; _i++) { + var _hunk = hunks[_i], + _toPos = _hunk.oldStart + _hunk.offset + diffOffset - 1; + diffOffset += _hunk.newLines - _hunk.oldLines; + + if (_toPos < 0) { + // Creating a new file + _toPos = 0; + } + + for (var j = 0; j < _hunk.lines.length; j++) { + var line = _hunk.lines[j], + operation = line.length > 0 ? line[0] : ' ', + content = line.length > 0 ? line.substr(1) : line, + delimiter = _hunk.linedelimiters[j]; + + if (operation === ' ') { + _toPos++; + } else if (operation === '-') { + lines.splice(_toPos, 1); + delimiters.splice(_toPos, 1); + /* istanbul ignore else */ + } else if (operation === '+') { + lines.splice(_toPos, 0, content); + delimiters.splice(_toPos, 0, delimiter); + _toPos++; + } else if (operation === '\\') { + var previousOperation = _hunk.lines[j - 1] ? _hunk.lines[j - 1][0] : null; + if (previousOperation === '+') { + removeEOFNL = true; + } else if (previousOperation === '-') { + addEOFNL = true; + } + } + } + } + + // Handle EOFNL insertion/removal + if (removeEOFNL) { + while (!lines[lines.length - 1]) { + lines.pop(); + delimiters.pop(); + } + } else if (addEOFNL) { + lines.push(''); + delimiters.push('\n'); + } + for (var _k = 0; _k < lines.length - 1; _k++) { + lines[_k] = lines[_k] + delimiters[_k]; + } + return lines.join(''); + } + + // Wrapper that supports multiple file patches via callbacks. + function applyPatches(uniDiff, options) { + if (typeof uniDiff === 'string') { + uniDiff = /*istanbul ignore start*/(0, _parse.parsePatch) /*istanbul ignore end*/(uniDiff); + } + + var currentIndex = 0; + function processIndex() { + var index = uniDiff[currentIndex++]; + if (!index) { + return options.complete(); + } + + options.loadFile(index, function (err, data) { + if (err) { + return options.complete(err); + } + + var updatedContent = applyPatch(data, index, options); + options.patched(index, updatedContent, function (err) { + if (err) { + return options.complete(err); + } + + processIndex(); + }); + }); + } + processIndex(); + } + + + +/***/ }), +/* 11 */ +/***/ (function(module, exports) { + + /*istanbul ignore start*/'use strict'; + + exports.__esModule = true; + exports. /*istanbul ignore end*/parsePatch = parsePatch; + function parsePatch(uniDiff) { + /*istanbul ignore start*/var /*istanbul ignore end*/options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + + var diffstr = uniDiff.split(/\r\n|[\n\v\f\r\x85]/), + delimiters = uniDiff.match(/\r\n|[\n\v\f\r\x85]/g) || [], + list = [], + i = 0; + + function parseIndex() { + var index = {}; + list.push(index); + + // Parse diff metadata + while (i < diffstr.length) { + var line = diffstr[i]; + + // File header found, end parsing diff metadata + if (/^(\-\-\-|\+\+\+|@@)\s/.test(line)) { + break; + } + + // Diff index + var header = /^(?:Index:|diff(?: -r \w+)+)\s+(.+?)\s*$/.exec(line); + if (header) { + index.index = header[1]; + } + + i++; + } + + // Parse file headers if they are defined. Unified diff requires them, but + // there's no technical issues to have an isolated hunk without file header + parseFileHeader(index); + parseFileHeader(index); + + // Parse hunks + index.hunks = []; + + while (i < diffstr.length) { + var _line = diffstr[i]; + + if (/^(Index:|diff|\-\-\-|\+\+\+)\s/.test(_line)) { + break; + } else if (/^@@/.test(_line)) { + index.hunks.push(parseHunk()); + } else if (_line && options.strict) { + // Ignore unexpected content unless in strict mode + throw new Error('Unknown line ' + (i + 1) + ' ' + JSON.stringify(_line)); + } else { + i++; + } + } + } + + // Parses the --- and +++ headers, if none are found, no lines + // are consumed. + function parseFileHeader(index) { + var fileHeader = /^(---|\+\+\+)\s+(.*)$/.exec(diffstr[i]); + if (fileHeader) { + var keyPrefix = fileHeader[1] === '---' ? 'old' : 'new'; + var data = fileHeader[2].split('\t', 2); + var fileName = data[0].replace(/\\\\/g, '\\'); + if (/^".*"$/.test(fileName)) { + fileName = fileName.substr(1, fileName.length - 2); + } + index[keyPrefix + 'FileName'] = fileName; + index[keyPrefix + 'Header'] = (data[1] || '').trim(); + + i++; + } + } + + // Parses a hunk + // This assumes that we are at the start of a hunk. + function parseHunk() { + var chunkHeaderIndex = i, + chunkHeaderLine = diffstr[i++], + chunkHeader = chunkHeaderLine.split(/@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/); + + var hunk = { + oldStart: +chunkHeader[1], + oldLines: +chunkHeader[2] || 1, + newStart: +chunkHeader[3], + newLines: +chunkHeader[4] || 1, + lines: [], + linedelimiters: [] + }; + + var addCount = 0, + removeCount = 0; + for (; i < diffstr.length; i++) { + // Lines starting with '---' could be mistaken for the "remove line" operation + // But they could be the header for the next file. Therefore prune such cases out. + if (diffstr[i].indexOf('--- ') === 0 && i + 2 < diffstr.length && diffstr[i + 1].indexOf('+++ ') === 0 && diffstr[i + 2].indexOf('@@') === 0) { + break; + } + var operation = diffstr[i].length == 0 && i != diffstr.length - 1 ? ' ' : diffstr[i][0]; + + if (operation === '+' || operation === '-' || operation === ' ' || operation === '\\') { + hunk.lines.push(diffstr[i]); + hunk.linedelimiters.push(delimiters[i] || '\n'); + + if (operation === '+') { + addCount++; + } else if (operation === '-') { + removeCount++; + } else if (operation === ' ') { + addCount++; + removeCount++; + } + } else { + break; + } + } + + // Handle the empty block count case + if (!addCount && hunk.newLines === 1) { + hunk.newLines = 0; + } + if (!removeCount && hunk.oldLines === 1) { + hunk.oldLines = 0; + } + + // Perform optional sanity checking + if (options.strict) { + if (addCount !== hunk.newLines) { + throw new Error('Added line count did not match for hunk at line ' + (chunkHeaderIndex + 1)); + } + if (removeCount !== hunk.oldLines) { + throw new Error('Removed line count did not match for hunk at line ' + (chunkHeaderIndex + 1)); + } + } + + return hunk; + } + + while (i < diffstr.length) { + parseIndex(); + } + + return list; + } + + + +/***/ }), +/* 12 */ +/***/ (function(module, exports) { + + /*istanbul ignore start*/"use strict"; + + exports.__esModule = true; + + exports["default"] = /*istanbul ignore end*/function (start, minLine, maxLine) { + var wantForward = true, + backwardExhausted = false, + forwardExhausted = false, + localOffset = 1; + + return function iterator() { + if (wantForward && !forwardExhausted) { + if (backwardExhausted) { + localOffset++; + } else { + wantForward = false; + } + + // Check if trying to fit beyond text length, and if not, check it fits + // after offset location (or desired location on first iteration) + if (start + localOffset <= maxLine) { + return localOffset; + } + + forwardExhausted = true; + } + + if (!backwardExhausted) { + if (!forwardExhausted) { + wantForward = true; + } + + // Check if trying to fit before text beginning, and if not, check it fits + // before offset location + if (minLine <= start - localOffset) { + return -localOffset++; + } + + backwardExhausted = true; + return iterator(); + } + + // We tried to fit hunk before text beginning and beyond text length, then + // hunk can't fit on the text. Return undefined + }; + }; + + + +/***/ }), +/* 13 */ +/***/ (function(module, exports, __webpack_require__) { + + /*istanbul ignore start*/'use strict'; + + exports.__esModule = true; + exports. /*istanbul ignore end*/calcLineCount = calcLineCount; + /*istanbul ignore start*/exports. /*istanbul ignore end*/merge = merge; + + var /*istanbul ignore start*/_create = __webpack_require__(14) /*istanbul ignore end*/; + + var /*istanbul ignore start*/_parse = __webpack_require__(11) /*istanbul ignore end*/; + + var /*istanbul ignore start*/_array = __webpack_require__(15) /*istanbul ignore end*/; + + /*istanbul ignore start*/function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } + + /*istanbul ignore end*/function calcLineCount(hunk) { + /*istanbul ignore start*/var _calcOldNewLineCount = /*istanbul ignore end*/calcOldNewLineCount(hunk.lines), + oldLines = _calcOldNewLineCount.oldLines, + newLines = _calcOldNewLineCount.newLines; + + if (oldLines !== undefined) { + hunk.oldLines = oldLines; + } else { + delete hunk.oldLines; + } + + if (newLines !== undefined) { + hunk.newLines = newLines; + } else { + delete hunk.newLines; + } + } + + function merge(mine, theirs, base) { + mine = loadPatch(mine, base); + theirs = loadPatch(theirs, base); + + var ret = {}; + + // For index we just let it pass through as it doesn't have any necessary meaning. + // Leaving sanity checks on this to the API consumer that may know more about the + // meaning in their own context. + if (mine.index || theirs.index) { + ret.index = mine.index || theirs.index; + } + + if (mine.newFileName || theirs.newFileName) { + if (!fileNameChanged(mine)) { + // No header or no change in ours, use theirs (and ours if theirs does not exist) + ret.oldFileName = theirs.oldFileName || mine.oldFileName; + ret.newFileName = theirs.newFileName || mine.newFileName; + ret.oldHeader = theirs.oldHeader || mine.oldHeader; + ret.newHeader = theirs.newHeader || mine.newHeader; + } else if (!fileNameChanged(theirs)) { + // No header or no change in theirs, use ours + ret.oldFileName = mine.oldFileName; + ret.newFileName = mine.newFileName; + ret.oldHeader = mine.oldHeader; + ret.newHeader = mine.newHeader; + } else { + // Both changed... figure it out + ret.oldFileName = selectField(ret, mine.oldFileName, theirs.oldFileName); + ret.newFileName = selectField(ret, mine.newFileName, theirs.newFileName); + ret.oldHeader = selectField(ret, mine.oldHeader, theirs.oldHeader); + ret.newHeader = selectField(ret, mine.newHeader, theirs.newHeader); + } + } + + ret.hunks = []; + + var mineIndex = 0, + theirsIndex = 0, + mineOffset = 0, + theirsOffset = 0; + + while (mineIndex < mine.hunks.length || theirsIndex < theirs.hunks.length) { + var mineCurrent = mine.hunks[mineIndex] || { oldStart: Infinity }, + theirsCurrent = theirs.hunks[theirsIndex] || { oldStart: Infinity }; + + if (hunkBefore(mineCurrent, theirsCurrent)) { + // This patch does not overlap with any of the others, yay. + ret.hunks.push(cloneHunk(mineCurrent, mineOffset)); + mineIndex++; + theirsOffset += mineCurrent.newLines - mineCurrent.oldLines; + } else if (hunkBefore(theirsCurrent, mineCurrent)) { + // This patch does not overlap with any of the others, yay. + ret.hunks.push(cloneHunk(theirsCurrent, theirsOffset)); + theirsIndex++; + mineOffset += theirsCurrent.newLines - theirsCurrent.oldLines; + } else { + // Overlap, merge as best we can + var mergedHunk = { + oldStart: Math.min(mineCurrent.oldStart, theirsCurrent.oldStart), + oldLines: 0, + newStart: Math.min(mineCurrent.newStart + mineOffset, theirsCurrent.oldStart + theirsOffset), + newLines: 0, + lines: [] + }; + mergeLines(mergedHunk, mineCurrent.oldStart, mineCurrent.lines, theirsCurrent.oldStart, theirsCurrent.lines); + theirsIndex++; + mineIndex++; + + ret.hunks.push(mergedHunk); + } + } + + return ret; + } + + function loadPatch(param, base) { + if (typeof param === 'string') { + if (/^@@/m.test(param) || /^Index:/m.test(param)) { + return (/*istanbul ignore start*/(0, _parse.parsePatch) /*istanbul ignore end*/(param)[0] + ); + } + + if (!base) { + throw new Error('Must provide a base reference or pass in a patch'); + } + return (/*istanbul ignore start*/(0, _create.structuredPatch) /*istanbul ignore end*/(undefined, undefined, base, param) + ); + } + + return param; + } + + function fileNameChanged(patch) { + return patch.newFileName && patch.newFileName !== patch.oldFileName; + } + + function selectField(index, mine, theirs) { + if (mine === theirs) { + return mine; + } else { + index.conflict = true; + return { mine: mine, theirs: theirs }; + } + } + + function hunkBefore(test, check) { + return test.oldStart < check.oldStart && test.oldStart + test.oldLines < check.oldStart; + } + + function cloneHunk(hunk, offset) { + return { + oldStart: hunk.oldStart, oldLines: hunk.oldLines, + newStart: hunk.newStart + offset, newLines: hunk.newLines, + lines: hunk.lines + }; + } + + function mergeLines(hunk, mineOffset, mineLines, theirOffset, theirLines) { + // This will generally result in a conflicted hunk, but there are cases where the context + // is the only overlap where we can successfully merge the content here. + var mine = { offset: mineOffset, lines: mineLines, index: 0 }, + their = { offset: theirOffset, lines: theirLines, index: 0 }; + + // Handle any leading content + insertLeading(hunk, mine, their); + insertLeading(hunk, their, mine); + + // Now in the overlap content. Scan through and select the best changes from each. + while (mine.index < mine.lines.length && their.index < their.lines.length) { + var mineCurrent = mine.lines[mine.index], + theirCurrent = their.lines[their.index]; + + if ((mineCurrent[0] === '-' || mineCurrent[0] === '+') && (theirCurrent[0] === '-' || theirCurrent[0] === '+')) { + // Both modified ... + mutualChange(hunk, mine, their); + } else if (mineCurrent[0] === '+' && theirCurrent[0] === ' ') { + /*istanbul ignore start*/var _hunk$lines; + + /*istanbul ignore end*/ // Mine inserted + /*istanbul ignore start*/(_hunk$lines = /*istanbul ignore end*/hunk.lines).push. /*istanbul ignore start*/apply /*istanbul ignore end*/( /*istanbul ignore start*/_hunk$lines /*istanbul ignore end*/, /*istanbul ignore start*/_toConsumableArray( /*istanbul ignore end*/collectChange(mine))); + } else if (theirCurrent[0] === '+' && mineCurrent[0] === ' ') { + /*istanbul ignore start*/var _hunk$lines2; + + /*istanbul ignore end*/ // Theirs inserted + /*istanbul ignore start*/(_hunk$lines2 = /*istanbul ignore end*/hunk.lines).push. /*istanbul ignore start*/apply /*istanbul ignore end*/( /*istanbul ignore start*/_hunk$lines2 /*istanbul ignore end*/, /*istanbul ignore start*/_toConsumableArray( /*istanbul ignore end*/collectChange(their))); + } else if (mineCurrent[0] === '-' && theirCurrent[0] === ' ') { + // Mine removed or edited + removal(hunk, mine, their); + } else if (theirCurrent[0] === '-' && mineCurrent[0] === ' ') { + // Their removed or edited + removal(hunk, their, mine, true); + } else if (mineCurrent === theirCurrent) { + // Context identity + hunk.lines.push(mineCurrent); + mine.index++; + their.index++; + } else { + // Context mismatch + conflict(hunk, collectChange(mine), collectChange(their)); + } + } + + // Now push anything that may be remaining + insertTrailing(hunk, mine); + insertTrailing(hunk, their); + + calcLineCount(hunk); + } + + function mutualChange(hunk, mine, their) { + var myChanges = collectChange(mine), + theirChanges = collectChange(their); + + if (allRemoves(myChanges) && allRemoves(theirChanges)) { + // Special case for remove changes that are supersets of one another + if ( /*istanbul ignore start*/(0, _array.arrayStartsWith) /*istanbul ignore end*/(myChanges, theirChanges) && skipRemoveSuperset(their, myChanges, myChanges.length - theirChanges.length)) { + /*istanbul ignore start*/var _hunk$lines3; + + /*istanbul ignore end*/ /*istanbul ignore start*/(_hunk$lines3 = /*istanbul ignore end*/hunk.lines).push. /*istanbul ignore start*/apply /*istanbul ignore end*/( /*istanbul ignore start*/_hunk$lines3 /*istanbul ignore end*/, /*istanbul ignore start*/_toConsumableArray( /*istanbul ignore end*/myChanges)); + return; + } else if ( /*istanbul ignore start*/(0, _array.arrayStartsWith) /*istanbul ignore end*/(theirChanges, myChanges) && skipRemoveSuperset(mine, theirChanges, theirChanges.length - myChanges.length)) { + /*istanbul ignore start*/var _hunk$lines4; + + /*istanbul ignore end*/ /*istanbul ignore start*/(_hunk$lines4 = /*istanbul ignore end*/hunk.lines).push. /*istanbul ignore start*/apply /*istanbul ignore end*/( /*istanbul ignore start*/_hunk$lines4 /*istanbul ignore end*/, /*istanbul ignore start*/_toConsumableArray( /*istanbul ignore end*/theirChanges)); + return; + } + } else if ( /*istanbul ignore start*/(0, _array.arrayEqual) /*istanbul ignore end*/(myChanges, theirChanges)) { + /*istanbul ignore start*/var _hunk$lines5; + + /*istanbul ignore end*/ /*istanbul ignore start*/(_hunk$lines5 = /*istanbul ignore end*/hunk.lines).push. /*istanbul ignore start*/apply /*istanbul ignore end*/( /*istanbul ignore start*/_hunk$lines5 /*istanbul ignore end*/, /*istanbul ignore start*/_toConsumableArray( /*istanbul ignore end*/myChanges)); + return; + } + + conflict(hunk, myChanges, theirChanges); + } + + function removal(hunk, mine, their, swap) { + var myChanges = collectChange(mine), + theirChanges = collectContext(their, myChanges); + if (theirChanges.merged) { + /*istanbul ignore start*/var _hunk$lines6; + + /*istanbul ignore end*/ /*istanbul ignore start*/(_hunk$lines6 = /*istanbul ignore end*/hunk.lines).push. /*istanbul ignore start*/apply /*istanbul ignore end*/( /*istanbul ignore start*/_hunk$lines6 /*istanbul ignore end*/, /*istanbul ignore start*/_toConsumableArray( /*istanbul ignore end*/theirChanges.merged)); + } else { + conflict(hunk, swap ? theirChanges : myChanges, swap ? myChanges : theirChanges); + } + } + + function conflict(hunk, mine, their) { + hunk.conflict = true; + hunk.lines.push({ + conflict: true, + mine: mine, + theirs: their + }); + } + + function insertLeading(hunk, insert, their) { + while (insert.offset < their.offset && insert.index < insert.lines.length) { + var line = insert.lines[insert.index++]; + hunk.lines.push(line); + insert.offset++; + } + } + function insertTrailing(hunk, insert) { + while (insert.index < insert.lines.length) { + var line = insert.lines[insert.index++]; + hunk.lines.push(line); + } + } + + function collectChange(state) { + var ret = [], + operation = state.lines[state.index][0]; + while (state.index < state.lines.length) { + var line = state.lines[state.index]; + + // Group additions that are immediately after subtractions and treat them as one "atomic" modify change. + if (operation === '-' && line[0] === '+') { + operation = '+'; + } + + if (operation === line[0]) { + ret.push(line); + state.index++; + } else { + break; + } + } + + return ret; + } + function collectContext(state, matchChanges) { + var changes = [], + merged = [], + matchIndex = 0, + contextChanges = false, + conflicted = false; + while (matchIndex < matchChanges.length && state.index < state.lines.length) { + var change = state.lines[state.index], + match = matchChanges[matchIndex]; + + // Once we've hit our add, then we are done + if (match[0] === '+') { + break; + } + + contextChanges = contextChanges || change[0] !== ' '; + + merged.push(match); + matchIndex++; + + // Consume any additions in the other block as a conflict to attempt + // to pull in the remaining context after this + if (change[0] === '+') { + conflicted = true; + + while (change[0] === '+') { + changes.push(change); + change = state.lines[++state.index]; + } + } + + if (match.substr(1) === change.substr(1)) { + changes.push(change); + state.index++; + } else { + conflicted = true; + } + } + + if ((matchChanges[matchIndex] || '')[0] === '+' && contextChanges) { + conflicted = true; + } + + if (conflicted) { + return changes; + } + + while (matchIndex < matchChanges.length) { + merged.push(matchChanges[matchIndex++]); + } + + return { + merged: merged, + changes: changes + }; + } + + function allRemoves(changes) { + return changes.reduce(function (prev, change) { + return prev && change[0] === '-'; + }, true); + } + function skipRemoveSuperset(state, removeChanges, delta) { + for (var i = 0; i < delta; i++) { + var changeContent = removeChanges[removeChanges.length - delta + i].substr(1); + if (state.lines[state.index + i] !== ' ' + changeContent) { + return false; + } + } + + state.index += delta; + return true; + } + + function calcOldNewLineCount(lines) { + var oldLines = 0; + var newLines = 0; + + lines.forEach(function (line) { + if (typeof line !== 'string') { + var myCount = calcOldNewLineCount(line.mine); + var theirCount = calcOldNewLineCount(line.theirs); + + if (oldLines !== undefined) { + if (myCount.oldLines === theirCount.oldLines) { + oldLines += myCount.oldLines; + } else { + oldLines = undefined; + } + } + + if (newLines !== undefined) { + if (myCount.newLines === theirCount.newLines) { + newLines += myCount.newLines; + } else { + newLines = undefined; + } + } + } else { + if (newLines !== undefined && (line[0] === '+' || line[0] === ' ')) { + newLines++; + } + if (oldLines !== undefined && (line[0] === '-' || line[0] === ' ')) { + oldLines++; + } + } + }); + + return { oldLines: oldLines, newLines: newLines }; + } + + + +/***/ }), +/* 14 */ +/***/ (function(module, exports, __webpack_require__) { + + /*istanbul ignore start*/'use strict'; + + exports.__esModule = true; + exports. /*istanbul ignore end*/structuredPatch = structuredPatch; + /*istanbul ignore start*/exports. /*istanbul ignore end*/createTwoFilesPatch = createTwoFilesPatch; + /*istanbul ignore start*/exports. /*istanbul ignore end*/createPatch = createPatch; + + var /*istanbul ignore start*/_line = __webpack_require__(5) /*istanbul ignore end*/; + + /*istanbul ignore start*/function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } + + /*istanbul ignore end*/function structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) { + if (!options) { + options = {}; + } + if (typeof options.context === 'undefined') { + options.context = 4; + } + + var diff = /*istanbul ignore start*/(0, _line.diffLines) /*istanbul ignore end*/(oldStr, newStr, options); + diff.push({ value: '', lines: [] }); // Append an empty value to make cleanup easier + + function contextLines(lines) { + return lines.map(function (entry) { + return ' ' + entry; + }); + } + + var hunks = []; + var oldRangeStart = 0, + newRangeStart = 0, + curRange = [], + oldLine = 1, + newLine = 1; + + /*istanbul ignore start*/var _loop = function _loop( /*istanbul ignore end*/i) { + var current = diff[i], + lines = current.lines || current.value.replace(/\n$/, '').split('\n'); + current.lines = lines; + + if (current.added || current.removed) { + /*istanbul ignore start*/var _curRange; + + /*istanbul ignore end*/ // If we have previous context, start with that + if (!oldRangeStart) { + var prev = diff[i - 1]; + oldRangeStart = oldLine; + newRangeStart = newLine; + + if (prev) { + curRange = options.context > 0 ? contextLines(prev.lines.slice(-options.context)) : []; + oldRangeStart -= curRange.length; + newRangeStart -= curRange.length; + } + } + + // Output our changes + /*istanbul ignore start*/(_curRange = /*istanbul ignore end*/curRange).push. /*istanbul ignore start*/apply /*istanbul ignore end*/( /*istanbul ignore start*/_curRange /*istanbul ignore end*/, /*istanbul ignore start*/_toConsumableArray( /*istanbul ignore end*/lines.map(function (entry) { + return (current.added ? '+' : '-') + entry; + }))); + + // Track the updated file position + if (current.added) { + newLine += lines.length; + } else { + oldLine += lines.length; + } + } else { + // Identical context lines. Track line changes + if (oldRangeStart) { + // Close out any changes that have been output (or join overlapping) + if (lines.length <= options.context * 2 && i < diff.length - 2) { + /*istanbul ignore start*/var _curRange2; + + /*istanbul ignore end*/ // Overlapping + /*istanbul ignore start*/(_curRange2 = /*istanbul ignore end*/curRange).push. /*istanbul ignore start*/apply /*istanbul ignore end*/( /*istanbul ignore start*/_curRange2 /*istanbul ignore end*/, /*istanbul ignore start*/_toConsumableArray( /*istanbul ignore end*/contextLines(lines))); + } else { + /*istanbul ignore start*/var _curRange3; + + /*istanbul ignore end*/ // end the range and output + var contextSize = Math.min(lines.length, options.context); + /*istanbul ignore start*/(_curRange3 = /*istanbul ignore end*/curRange).push. /*istanbul ignore start*/apply /*istanbul ignore end*/( /*istanbul ignore start*/_curRange3 /*istanbul ignore end*/, /*istanbul ignore start*/_toConsumableArray( /*istanbul ignore end*/contextLines(lines.slice(0, contextSize)))); + + var hunk = { + oldStart: oldRangeStart, + oldLines: oldLine - oldRangeStart + contextSize, + newStart: newRangeStart, + newLines: newLine - newRangeStart + contextSize, + lines: curRange + }; + if (i >= diff.length - 2 && lines.length <= options.context) { + // EOF is inside this hunk + var oldEOFNewline = /\n$/.test(oldStr); + var newEOFNewline = /\n$/.test(newStr); + if (lines.length == 0 && !oldEOFNewline) { + // special case: old has no eol and no trailing context; no-nl can end up before adds + curRange.splice(hunk.oldLines, 0, '\\ No newline at end of file'); + } else if (!oldEOFNewline || !newEOFNewline) { + curRange.push('\\ No newline at end of file'); + } + } + hunks.push(hunk); + + oldRangeStart = 0; + newRangeStart = 0; + curRange = []; + } + } + oldLine += lines.length; + newLine += lines.length; + } + }; + + for (var i = 0; i < diff.length; i++) { + /*istanbul ignore start*/_loop( /*istanbul ignore end*/i); + } + + return { + oldFileName: oldFileName, newFileName: newFileName, + oldHeader: oldHeader, newHeader: newHeader, + hunks: hunks + }; + } + + function createTwoFilesPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) { + var diff = structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options); + + var ret = []; + if (oldFileName == newFileName) { + ret.push('Index: ' + oldFileName); + } + ret.push('==================================================================='); + ret.push('--- ' + diff.oldFileName + (typeof diff.oldHeader === 'undefined' ? '' : '\t' + diff.oldHeader)); + ret.push('+++ ' + diff.newFileName + (typeof diff.newHeader === 'undefined' ? '' : '\t' + diff.newHeader)); + + for (var i = 0; i < diff.hunks.length; i++) { + var hunk = diff.hunks[i]; + ret.push('@@ -' + hunk.oldStart + ',' + hunk.oldLines + ' +' + hunk.newStart + ',' + hunk.newLines + ' @@'); + ret.push.apply(ret, hunk.lines); + } + + return ret.join('\n') + '\n'; + } + + function createPatch(fileName, oldStr, newStr, oldHeader, newHeader, options) { + return createTwoFilesPatch(fileName, fileName, oldStr, newStr, oldHeader, newHeader, options); + } + + + +/***/ }), +/* 15 */ +/***/ (function(module, exports) { + + /*istanbul ignore start*/"use strict"; + + exports.__esModule = true; + exports. /*istanbul ignore end*/arrayEqual = arrayEqual; + /*istanbul ignore start*/exports. /*istanbul ignore end*/arrayStartsWith = arrayStartsWith; + function arrayEqual(a, b) { + if (a.length !== b.length) { + return false; + } + + return arrayStartsWith(a, b); + } + + function arrayStartsWith(array, start) { + if (start.length > array.length) { + return false; + } + + for (var i = 0; i < start.length; i++) { + if (start[i] !== array[i]) { + return false; + } + } + + return true; + } + + + +/***/ }), +/* 16 */ +/***/ (function(module, exports) { + + /*istanbul ignore start*/"use strict"; + + exports.__esModule = true; + exports. /*istanbul ignore end*/convertChangesToDMP = convertChangesToDMP; + // See: http://code.google.com/p/google-diff-match-patch/wiki/API + function convertChangesToDMP(changes) { + var ret = [], + change = /*istanbul ignore start*/void 0 /*istanbul ignore end*/, + operation = /*istanbul ignore start*/void 0 /*istanbul ignore end*/; + for (var i = 0; i < changes.length; i++) { + change = changes[i]; + if (change.added) { + operation = 1; + } else if (change.removed) { + operation = -1; + } else { + operation = 0; + } + + ret.push([operation, change.value]); + } + return ret; + } + + + +/***/ }), +/* 17 */ +/***/ (function(module, exports) { + + /*istanbul ignore start*/'use strict'; + + exports.__esModule = true; + exports. /*istanbul ignore end*/convertChangesToXML = convertChangesToXML; + function convertChangesToXML(changes) { + var ret = []; + for (var i = 0; i < changes.length; i++) { + var change = changes[i]; + if (change.added) { + ret.push(''); + } else if (change.removed) { + ret.push(''); + } + + ret.push(escapeHTML(change.value)); + + if (change.added) { + ret.push(''); + } else if (change.removed) { + ret.push(''); + } + } + return ret.join(''); + } + + function escapeHTML(s) { + var n = s; + n = n.replace(/&/g, '&'); + n = n.replace(//g, '>'); + n = n.replace(/"/g, '"'); + + return n; + } + + + +/***/ }) +/******/ ]) +}); +; +},{}],49:[function(require,module,exports){ +'use strict'; + +var matchOperatorsRe = /[|\\{}()[\]^$+*?.]/g; + +module.exports = function (str) { + if (typeof str !== 'string') { + throw new TypeError('Expected a string'); + } + + return str.replace(matchOperatorsRe, '\\$&'); +}; + +},{}],50:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var objectCreate = Object.create || objectCreatePolyfill +var objectKeys = Object.keys || objectKeysPolyfill +var bind = Function.prototype.bind || functionBindPolyfill + +function EventEmitter() { + if (!this._events || !Object.prototype.hasOwnProperty.call(this, '_events')) { + this._events = objectCreate(null); + this._eventsCount = 0; + } + + this._maxListeners = this._maxListeners || undefined; +} +module.exports = EventEmitter; + +// Backwards-compat with node 0.10.x +EventEmitter.EventEmitter = EventEmitter; + +EventEmitter.prototype._events = undefined; +EventEmitter.prototype._maxListeners = undefined; + +// By default EventEmitters will print a warning if more than 10 listeners are +// added to it. This is a useful default which helps finding memory leaks. +var defaultMaxListeners = 10; + +var hasDefineProperty; +try { + var o = {}; + if (Object.defineProperty) Object.defineProperty(o, 'x', { value: 0 }); + hasDefineProperty = o.x === 0; +} catch (err) { hasDefineProperty = false } +if (hasDefineProperty) { + Object.defineProperty(EventEmitter, 'defaultMaxListeners', { + enumerable: true, + get: function() { + return defaultMaxListeners; + }, + set: function(arg) { + // check whether the input is a positive number (whose value is zero or + // greater and not a NaN). + if (typeof arg !== 'number' || arg < 0 || arg !== arg) + throw new TypeError('"defaultMaxListeners" must be a positive number'); + defaultMaxListeners = arg; + } + }); +} else { + EventEmitter.defaultMaxListeners = defaultMaxListeners; +} + +// Obviously not all Emitters should be limited to 10. This function allows +// that to be increased. Set to zero for unlimited. +EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) { + if (typeof n !== 'number' || n < 0 || isNaN(n)) + throw new TypeError('"n" argument must be a positive number'); + this._maxListeners = n; + return this; +}; + +function $getMaxListeners(that) { + if (that._maxListeners === undefined) + return EventEmitter.defaultMaxListeners; + return that._maxListeners; +} + +EventEmitter.prototype.getMaxListeners = function getMaxListeners() { + return $getMaxListeners(this); +}; + +// These standalone emit* functions are used to optimize calling of event +// handlers for fast cases because emit() itself often has a variable number of +// arguments and can be deoptimized because of that. These functions always have +// the same number of arguments and thus do not get deoptimized, so the code +// inside them can execute faster. +function emitNone(handler, isFn, self) { + if (isFn) + handler.call(self); + else { + var len = handler.length; + var listeners = arrayClone(handler, len); + for (var i = 0; i < len; ++i) + listeners[i].call(self); + } +} +function emitOne(handler, isFn, self, arg1) { + if (isFn) + handler.call(self, arg1); + else { + var len = handler.length; + var listeners = arrayClone(handler, len); + for (var i = 0; i < len; ++i) + listeners[i].call(self, arg1); + } +} +function emitTwo(handler, isFn, self, arg1, arg2) { + if (isFn) + handler.call(self, arg1, arg2); + else { + var len = handler.length; + var listeners = arrayClone(handler, len); + for (var i = 0; i < len; ++i) + listeners[i].call(self, arg1, arg2); + } +} +function emitThree(handler, isFn, self, arg1, arg2, arg3) { + if (isFn) + handler.call(self, arg1, arg2, arg3); + else { + var len = handler.length; + var listeners = arrayClone(handler, len); + for (var i = 0; i < len; ++i) + listeners[i].call(self, arg1, arg2, arg3); + } +} + +function emitMany(handler, isFn, self, args) { + if (isFn) + handler.apply(self, args); + else { + var len = handler.length; + var listeners = arrayClone(handler, len); + for (var i = 0; i < len; ++i) + listeners[i].apply(self, args); + } +} + +EventEmitter.prototype.emit = function emit(type) { + var er, handler, len, args, i, events; + var doError = (type === 'error'); + + events = this._events; + if (events) + doError = (doError && events.error == null); + else if (!doError) + return false; + + // If there is no 'error' event listener then throw. + if (doError) { + if (arguments.length > 1) + er = arguments[1]; + if (er instanceof Error) { + throw er; // Unhandled 'error' event + } else { + // At least give some kind of context to the user + var err = new Error('Unhandled "error" event. (' + er + ')'); + err.context = er; + throw err; + } + return false; + } + + handler = events[type]; + + if (!handler) + return false; + + var isFn = typeof handler === 'function'; + len = arguments.length; + switch (len) { + // fast cases + case 1: + emitNone(handler, isFn, this); + break; + case 2: + emitOne(handler, isFn, this, arguments[1]); + break; + case 3: + emitTwo(handler, isFn, this, arguments[1], arguments[2]); + break; + case 4: + emitThree(handler, isFn, this, arguments[1], arguments[2], arguments[3]); + break; + // slower + default: + args = new Array(len - 1); + for (i = 1; i < len; i++) + args[i - 1] = arguments[i]; + emitMany(handler, isFn, this, args); + } + + return true; +}; + +function _addListener(target, type, listener, prepend) { + var m; + var events; + var existing; + + if (typeof listener !== 'function') + throw new TypeError('"listener" argument must be a function'); + + events = target._events; + if (!events) { + events = target._events = objectCreate(null); + target._eventsCount = 0; + } else { + // To avoid recursion in the case that type === "newListener"! Before + // adding it to the listeners, first emit "newListener". + if (events.newListener) { + target.emit('newListener', type, + listener.listener ? listener.listener : listener); + + // Re-assign `events` because a newListener handler could have caused the + // this._events to be assigned to a new object + events = target._events; + } + existing = events[type]; + } + + if (!existing) { + // Optimize the case of one listener. Don't need the extra array object. + existing = events[type] = listener; + ++target._eventsCount; + } else { + if (typeof existing === 'function') { + // Adding the second element, need to change to array. + existing = events[type] = + prepend ? [listener, existing] : [existing, listener]; + } else { + // If we've already got an array, just append. + if (prepend) { + existing.unshift(listener); + } else { + existing.push(listener); + } + } + + // Check for listener leak + if (!existing.warned) { + m = $getMaxListeners(target); + if (m && m > 0 && existing.length > m) { + existing.warned = true; + var w = new Error('Possible EventEmitter memory leak detected. ' + + existing.length + ' "' + String(type) + '" listeners ' + + 'added. Use emitter.setMaxListeners() to ' + + 'increase limit.'); + w.name = 'MaxListenersExceededWarning'; + w.emitter = target; + w.type = type; + w.count = existing.length; + if (typeof console === 'object' && console.warn) { + console.warn('%s: %s', w.name, w.message); + } + } + } + } + + return target; +} + +EventEmitter.prototype.addListener = function addListener(type, listener) { + return _addListener(this, type, listener, false); +}; + +EventEmitter.prototype.on = EventEmitter.prototype.addListener; + +EventEmitter.prototype.prependListener = + function prependListener(type, listener) { + return _addListener(this, type, listener, true); + }; + +function onceWrapper() { + if (!this.fired) { + this.target.removeListener(this.type, this.wrapFn); + this.fired = true; + switch (arguments.length) { + case 0: + return this.listener.call(this.target); + case 1: + return this.listener.call(this.target, arguments[0]); + case 2: + return this.listener.call(this.target, arguments[0], arguments[1]); + case 3: + return this.listener.call(this.target, arguments[0], arguments[1], + arguments[2]); + default: + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) + args[i] = arguments[i]; + this.listener.apply(this.target, args); + } + } +} + +function _onceWrap(target, type, listener) { + var state = { fired: false, wrapFn: undefined, target: target, type: type, listener: listener }; + var wrapped = bind.call(onceWrapper, state); + wrapped.listener = listener; + state.wrapFn = wrapped; + return wrapped; +} + +EventEmitter.prototype.once = function once(type, listener) { + if (typeof listener !== 'function') + throw new TypeError('"listener" argument must be a function'); + this.on(type, _onceWrap(this, type, listener)); + return this; +}; + +EventEmitter.prototype.prependOnceListener = + function prependOnceListener(type, listener) { + if (typeof listener !== 'function') + throw new TypeError('"listener" argument must be a function'); + this.prependListener(type, _onceWrap(this, type, listener)); + return this; + }; + +// Emits a 'removeListener' event if and only if the listener was removed. +EventEmitter.prototype.removeListener = + function removeListener(type, listener) { + var list, events, position, i, originalListener; + + if (typeof listener !== 'function') + throw new TypeError('"listener" argument must be a function'); + + events = this._events; + if (!events) + return this; + + list = events[type]; + if (!list) + return this; + + if (list === listener || list.listener === listener) { + if (--this._eventsCount === 0) + this._events = objectCreate(null); + else { + delete events[type]; + if (events.removeListener) + this.emit('removeListener', type, list.listener || listener); + } + } else if (typeof list !== 'function') { + position = -1; + + for (i = list.length - 1; i >= 0; i--) { + if (list[i] === listener || list[i].listener === listener) { + originalListener = list[i].listener; + position = i; + break; + } + } + + if (position < 0) + return this; + + if (position === 0) + list.shift(); + else + spliceOne(list, position); + + if (list.length === 1) + events[type] = list[0]; + + if (events.removeListener) + this.emit('removeListener', type, originalListener || listener); + } + + return this; + }; + +EventEmitter.prototype.removeAllListeners = + function removeAllListeners(type) { + var listeners, events, i; + + events = this._events; + if (!events) + return this; + + // not listening for removeListener, no need to emit + if (!events.removeListener) { + if (arguments.length === 0) { + this._events = objectCreate(null); + this._eventsCount = 0; + } else if (events[type]) { + if (--this._eventsCount === 0) + this._events = objectCreate(null); + else + delete events[type]; + } + return this; + } + + // emit removeListener for all listeners on all events + if (arguments.length === 0) { + var keys = objectKeys(events); + var key; + for (i = 0; i < keys.length; ++i) { + key = keys[i]; + if (key === 'removeListener') continue; + this.removeAllListeners(key); + } + this.removeAllListeners('removeListener'); + this._events = objectCreate(null); + this._eventsCount = 0; + return this; + } + + listeners = events[type]; + + if (typeof listeners === 'function') { + this.removeListener(type, listeners); + } else if (listeners) { + // LIFO order + for (i = listeners.length - 1; i >= 0; i--) { + this.removeListener(type, listeners[i]); + } + } + + return this; + }; + +function _listeners(target, type, unwrap) { + var events = target._events; + + if (!events) + return []; + + var evlistener = events[type]; + if (!evlistener) + return []; + + if (typeof evlistener === 'function') + return unwrap ? [evlistener.listener || evlistener] : [evlistener]; + + return unwrap ? unwrapListeners(evlistener) : arrayClone(evlistener, evlistener.length); +} + +EventEmitter.prototype.listeners = function listeners(type) { + return _listeners(this, type, true); +}; + +EventEmitter.prototype.rawListeners = function rawListeners(type) { + return _listeners(this, type, false); +}; + +EventEmitter.listenerCount = function(emitter, type) { + if (typeof emitter.listenerCount === 'function') { + return emitter.listenerCount(type); + } else { + return listenerCount.call(emitter, type); + } +}; + +EventEmitter.prototype.listenerCount = listenerCount; +function listenerCount(type) { + var events = this._events; + + if (events) { + var evlistener = events[type]; + + if (typeof evlistener === 'function') { + return 1; + } else if (evlistener) { + return evlistener.length; + } + } + + return 0; +} + +EventEmitter.prototype.eventNames = function eventNames() { + return this._eventsCount > 0 ? Reflect.ownKeys(this._events) : []; +}; + +// About 1.5x faster than the two-arg version of Array#splice(). +function spliceOne(list, index) { + for (var i = index, k = i + 1, n = list.length; k < n; i += 1, k += 1) + list[i] = list[k]; + list.pop(); +} + +function arrayClone(arr, n) { + var copy = new Array(n); + for (var i = 0; i < n; ++i) + copy[i] = arr[i]; + return copy; +} + +function unwrapListeners(arr) { + var ret = new Array(arr.length); + for (var i = 0; i < ret.length; ++i) { + ret[i] = arr[i].listener || arr[i]; + } + return ret; +} + +function objectCreatePolyfill(proto) { + var F = function() {}; + F.prototype = proto; + return new F; +} +function objectKeysPolyfill(obj) { + var keys = []; + for (var k in obj) if (Object.prototype.hasOwnProperty.call(obj, k)) { + keys.push(k); + } + return k; +} +function functionBindPolyfill(context) { + var fn = this; + return function () { + return fn.apply(context, arguments); + }; +} + +},{}],51:[function(require,module,exports){ +'use strict'; + +/* eslint no-invalid-this: 1 */ + +var ERROR_MESSAGE = 'Function.prototype.bind called on incompatible '; +var slice = Array.prototype.slice; +var toStr = Object.prototype.toString; +var funcType = '[object Function]'; + +module.exports = function bind(that) { + var target = this; + if (typeof target !== 'function' || toStr.call(target) !== funcType) { + throw new TypeError(ERROR_MESSAGE + target); + } + var args = slice.call(arguments, 1); + + var bound; + var binder = function () { + if (this instanceof bound) { + var result = target.apply( + this, + args.concat(slice.call(arguments)) + ); + if (Object(result) === result) { + return result; + } + return this; + } else { + return target.apply( + that, + args.concat(slice.call(arguments)) + ); + } + }; + + var boundLength = Math.max(0, target.length - args.length); + var boundArgs = []; + for (var i = 0; i < boundLength; i++) { + boundArgs.push('$' + i); + } + + bound = Function('binder', 'return function (' + boundArgs.join(',') + '){ return binder.apply(this,arguments); }')(binder); + + if (target.prototype) { + var Empty = function Empty() {}; + Empty.prototype = target.prototype; + bound.prototype = new Empty(); + Empty.prototype = null; + } + + return bound; +}; + +},{}],52:[function(require,module,exports){ +'use strict'; + +var implementation = require('./implementation'); + +module.exports = Function.prototype.bind || implementation; + +},{"./implementation":51}],53:[function(require,module,exports){ +'use strict'; + +/* eslint complexity: [2, 17], max-statements: [2, 33] */ +module.exports = function hasSymbols() { + if (typeof Symbol !== 'function' || typeof Object.getOwnPropertySymbols !== 'function') { return false; } + if (typeof Symbol.iterator === 'symbol') { return true; } + + var obj = {}; + var sym = Symbol('test'); + var symObj = Object(sym); + if (typeof sym === 'string') { return false; } + + if (Object.prototype.toString.call(sym) !== '[object Symbol]') { return false; } + if (Object.prototype.toString.call(symObj) !== '[object Symbol]') { return false; } + + // temp disabled per https://github.com/ljharb/object.assign/issues/17 + // if (sym instanceof Symbol) { return false; } + // temp disabled per https://github.com/WebReflection/get-own-property-symbols/issues/4 + // if (!(symObj instanceof Symbol)) { return false; } + + // if (typeof Symbol.prototype.toString !== 'function') { return false; } + // if (String(sym) !== Symbol.prototype.toString.call(sym)) { return false; } + + var symVal = 42; + obj[sym] = symVal; + for (sym in obj) { return false; } // eslint-disable-line no-restricted-syntax + if (typeof Object.keys === 'function' && Object.keys(obj).length !== 0) { return false; } + + if (typeof Object.getOwnPropertyNames === 'function' && Object.getOwnPropertyNames(obj).length !== 0) { return false; } + + var syms = Object.getOwnPropertySymbols(obj); + if (syms.length !== 1 || syms[0] !== sym) { return false; } + + if (!Object.prototype.propertyIsEnumerable.call(obj, sym)) { return false; } + + if (typeof Object.getOwnPropertyDescriptor === 'function') { + var descriptor = Object.getOwnPropertyDescriptor(obj, sym); + if (descriptor.value !== symVal || descriptor.enumerable !== true) { return false; } + } + + return true; +}; + +},{}],54:[function(require,module,exports){ +(function (global){ +/*! https://mths.be/he v1.2.0 by @mathias | MIT license */ +;(function(root) { + + // Detect free variables `exports`. + var freeExports = typeof exports == 'object' && exports; + + // Detect free variable `module`. + var freeModule = typeof module == 'object' && module && + module.exports == freeExports && module; + + // Detect free variable `global`, from Node.js or Browserified code, + // and use it as `root`. + var freeGlobal = typeof global == 'object' && global; + if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) { + root = freeGlobal; + } + + /*--------------------------------------------------------------------------*/ + + // All astral symbols. + var regexAstralSymbols = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g; + // All ASCII symbols (not just printable ASCII) except those listed in the + // first column of the overrides table. + // https://html.spec.whatwg.org/multipage/syntax.html#table-charref-overrides + var regexAsciiWhitelist = /[\x01-\x7F]/g; + // All BMP symbols that are not ASCII newlines, printable ASCII symbols, or + // code points listed in the first column of the overrides table on + // https://html.spec.whatwg.org/multipage/syntax.html#table-charref-overrides. + var regexBmpWhitelist = /[\x01-\t\x0B\f\x0E-\x1F\x7F\x81\x8D\x8F\x90\x9D\xA0-\uFFFF]/g; + + var regexEncodeNonAscii = /<\u20D2|=\u20E5|>\u20D2|\u205F\u200A|\u219D\u0338|\u2202\u0338|\u2220\u20D2|\u2229\uFE00|\u222A\uFE00|\u223C\u20D2|\u223D\u0331|\u223E\u0333|\u2242\u0338|\u224B\u0338|\u224D\u20D2|\u224E\u0338|\u224F\u0338|\u2250\u0338|\u2261\u20E5|\u2264\u20D2|\u2265\u20D2|\u2266\u0338|\u2267\u0338|\u2268\uFE00|\u2269\uFE00|\u226A\u0338|\u226A\u20D2|\u226B\u0338|\u226B\u20D2|\u227F\u0338|\u2282\u20D2|\u2283\u20D2|\u228A\uFE00|\u228B\uFE00|\u228F\u0338|\u2290\u0338|\u2293\uFE00|\u2294\uFE00|\u22B4\u20D2|\u22B5\u20D2|\u22D8\u0338|\u22D9\u0338|\u22DA\uFE00|\u22DB\uFE00|\u22F5\u0338|\u22F9\u0338|\u2933\u0338|\u29CF\u0338|\u29D0\u0338|\u2A6D\u0338|\u2A70\u0338|\u2A7D\u0338|\u2A7E\u0338|\u2AA1\u0338|\u2AA2\u0338|\u2AAC\uFE00|\u2AAD\uFE00|\u2AAF\u0338|\u2AB0\u0338|\u2AC5\u0338|\u2AC6\u0338|\u2ACB\uFE00|\u2ACC\uFE00|\u2AFD\u20E5|[\xA0-\u0113\u0116-\u0122\u0124-\u012B\u012E-\u014D\u0150-\u017E\u0192\u01B5\u01F5\u0237\u02C6\u02C7\u02D8-\u02DD\u0311\u0391-\u03A1\u03A3-\u03A9\u03B1-\u03C9\u03D1\u03D2\u03D5\u03D6\u03DC\u03DD\u03F0\u03F1\u03F5\u03F6\u0401-\u040C\u040E-\u044F\u0451-\u045C\u045E\u045F\u2002-\u2005\u2007-\u2010\u2013-\u2016\u2018-\u201A\u201C-\u201E\u2020-\u2022\u2025\u2026\u2030-\u2035\u2039\u203A\u203E\u2041\u2043\u2044\u204F\u2057\u205F-\u2063\u20AC\u20DB\u20DC\u2102\u2105\u210A-\u2113\u2115-\u211E\u2122\u2124\u2127-\u2129\u212C\u212D\u212F-\u2131\u2133-\u2138\u2145-\u2148\u2153-\u215E\u2190-\u219B\u219D-\u21A7\u21A9-\u21AE\u21B0-\u21B3\u21B5-\u21B7\u21BA-\u21DB\u21DD\u21E4\u21E5\u21F5\u21FD-\u2205\u2207-\u2209\u220B\u220C\u220F-\u2214\u2216-\u2218\u221A\u221D-\u2238\u223A-\u2257\u2259\u225A\u225C\u225F-\u2262\u2264-\u228B\u228D-\u229B\u229D-\u22A5\u22A7-\u22B0\u22B2-\u22BB\u22BD-\u22DB\u22DE-\u22E3\u22E6-\u22F7\u22F9-\u22FE\u2305\u2306\u2308-\u2310\u2312\u2313\u2315\u2316\u231C-\u231F\u2322\u2323\u232D\u232E\u2336\u233D\u233F\u237C\u23B0\u23B1\u23B4-\u23B6\u23DC-\u23DF\u23E2\u23E7\u2423\u24C8\u2500\u2502\u250C\u2510\u2514\u2518\u251C\u2524\u252C\u2534\u253C\u2550-\u256C\u2580\u2584\u2588\u2591-\u2593\u25A1\u25AA\u25AB\u25AD\u25AE\u25B1\u25B3-\u25B5\u25B8\u25B9\u25BD-\u25BF\u25C2\u25C3\u25CA\u25CB\u25EC\u25EF\u25F8-\u25FC\u2605\u2606\u260E\u2640\u2642\u2660\u2663\u2665\u2666\u266A\u266D-\u266F\u2713\u2717\u2720\u2736\u2758\u2772\u2773\u27C8\u27C9\u27E6-\u27ED\u27F5-\u27FA\u27FC\u27FF\u2902-\u2905\u290C-\u2913\u2916\u2919-\u2920\u2923-\u292A\u2933\u2935-\u2939\u293C\u293D\u2945\u2948-\u294B\u294E-\u2976\u2978\u2979\u297B-\u297F\u2985\u2986\u298B-\u2996\u299A\u299C\u299D\u29A4-\u29B7\u29B9\u29BB\u29BC\u29BE-\u29C5\u29C9\u29CD-\u29D0\u29DC-\u29DE\u29E3-\u29E5\u29EB\u29F4\u29F6\u2A00-\u2A02\u2A04\u2A06\u2A0C\u2A0D\u2A10-\u2A17\u2A22-\u2A27\u2A29\u2A2A\u2A2D-\u2A31\u2A33-\u2A3C\u2A3F\u2A40\u2A42-\u2A4D\u2A50\u2A53-\u2A58\u2A5A-\u2A5D\u2A5F\u2A66\u2A6A\u2A6D-\u2A75\u2A77-\u2A9A\u2A9D-\u2AA2\u2AA4-\u2AB0\u2AB3-\u2AC8\u2ACB\u2ACC\u2ACF-\u2ADB\u2AE4\u2AE6-\u2AE9\u2AEB-\u2AF3\u2AFD\uFB00-\uFB04]|\uD835[\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDCCF\uDD04\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDD6B]/g; + var encodeMap = {'\xAD':'shy','\u200C':'zwnj','\u200D':'zwj','\u200E':'lrm','\u2063':'ic','\u2062':'it','\u2061':'af','\u200F':'rlm','\u200B':'ZeroWidthSpace','\u2060':'NoBreak','\u0311':'DownBreve','\u20DB':'tdot','\u20DC':'DotDot','\t':'Tab','\n':'NewLine','\u2008':'puncsp','\u205F':'MediumSpace','\u2009':'thinsp','\u200A':'hairsp','\u2004':'emsp13','\u2002':'ensp','\u2005':'emsp14','\u2003':'emsp','\u2007':'numsp','\xA0':'nbsp','\u205F\u200A':'ThickSpace','\u203E':'oline','_':'lowbar','\u2010':'dash','\u2013':'ndash','\u2014':'mdash','\u2015':'horbar',',':'comma',';':'semi','\u204F':'bsemi',':':'colon','\u2A74':'Colone','!':'excl','\xA1':'iexcl','?':'quest','\xBF':'iquest','.':'period','\u2025':'nldr','\u2026':'mldr','\xB7':'middot','\'':'apos','\u2018':'lsquo','\u2019':'rsquo','\u201A':'sbquo','\u2039':'lsaquo','\u203A':'rsaquo','"':'quot','\u201C':'ldquo','\u201D':'rdquo','\u201E':'bdquo','\xAB':'laquo','\xBB':'raquo','(':'lpar',')':'rpar','[':'lsqb',']':'rsqb','{':'lcub','}':'rcub','\u2308':'lceil','\u2309':'rceil','\u230A':'lfloor','\u230B':'rfloor','\u2985':'lopar','\u2986':'ropar','\u298B':'lbrke','\u298C':'rbrke','\u298D':'lbrkslu','\u298E':'rbrksld','\u298F':'lbrksld','\u2990':'rbrkslu','\u2991':'langd','\u2992':'rangd','\u2993':'lparlt','\u2994':'rpargt','\u2995':'gtlPar','\u2996':'ltrPar','\u27E6':'lobrk','\u27E7':'robrk','\u27E8':'lang','\u27E9':'rang','\u27EA':'Lang','\u27EB':'Rang','\u27EC':'loang','\u27ED':'roang','\u2772':'lbbrk','\u2773':'rbbrk','\u2016':'Vert','\xA7':'sect','\xB6':'para','@':'commat','*':'ast','/':'sol','undefined':null,'&':'amp','#':'num','%':'percnt','\u2030':'permil','\u2031':'pertenk','\u2020':'dagger','\u2021':'Dagger','\u2022':'bull','\u2043':'hybull','\u2032':'prime','\u2033':'Prime','\u2034':'tprime','\u2057':'qprime','\u2035':'bprime','\u2041':'caret','`':'grave','\xB4':'acute','\u02DC':'tilde','^':'Hat','\xAF':'macr','\u02D8':'breve','\u02D9':'dot','\xA8':'die','\u02DA':'ring','\u02DD':'dblac','\xB8':'cedil','\u02DB':'ogon','\u02C6':'circ','\u02C7':'caron','\xB0':'deg','\xA9':'copy','\xAE':'reg','\u2117':'copysr','\u2118':'wp','\u211E':'rx','\u2127':'mho','\u2129':'iiota','\u2190':'larr','\u219A':'nlarr','\u2192':'rarr','\u219B':'nrarr','\u2191':'uarr','\u2193':'darr','\u2194':'harr','\u21AE':'nharr','\u2195':'varr','\u2196':'nwarr','\u2197':'nearr','\u2198':'searr','\u2199':'swarr','\u219D':'rarrw','\u219D\u0338':'nrarrw','\u219E':'Larr','\u219F':'Uarr','\u21A0':'Rarr','\u21A1':'Darr','\u21A2':'larrtl','\u21A3':'rarrtl','\u21A4':'mapstoleft','\u21A5':'mapstoup','\u21A6':'map','\u21A7':'mapstodown','\u21A9':'larrhk','\u21AA':'rarrhk','\u21AB':'larrlp','\u21AC':'rarrlp','\u21AD':'harrw','\u21B0':'lsh','\u21B1':'rsh','\u21B2':'ldsh','\u21B3':'rdsh','\u21B5':'crarr','\u21B6':'cularr','\u21B7':'curarr','\u21BA':'olarr','\u21BB':'orarr','\u21BC':'lharu','\u21BD':'lhard','\u21BE':'uharr','\u21BF':'uharl','\u21C0':'rharu','\u21C1':'rhard','\u21C2':'dharr','\u21C3':'dharl','\u21C4':'rlarr','\u21C5':'udarr','\u21C6':'lrarr','\u21C7':'llarr','\u21C8':'uuarr','\u21C9':'rrarr','\u21CA':'ddarr','\u21CB':'lrhar','\u21CC':'rlhar','\u21D0':'lArr','\u21CD':'nlArr','\u21D1':'uArr','\u21D2':'rArr','\u21CF':'nrArr','\u21D3':'dArr','\u21D4':'iff','\u21CE':'nhArr','\u21D5':'vArr','\u21D6':'nwArr','\u21D7':'neArr','\u21D8':'seArr','\u21D9':'swArr','\u21DA':'lAarr','\u21DB':'rAarr','\u21DD':'zigrarr','\u21E4':'larrb','\u21E5':'rarrb','\u21F5':'duarr','\u21FD':'loarr','\u21FE':'roarr','\u21FF':'hoarr','\u2200':'forall','\u2201':'comp','\u2202':'part','\u2202\u0338':'npart','\u2203':'exist','\u2204':'nexist','\u2205':'empty','\u2207':'Del','\u2208':'in','\u2209':'notin','\u220B':'ni','\u220C':'notni','\u03F6':'bepsi','\u220F':'prod','\u2210':'coprod','\u2211':'sum','+':'plus','\xB1':'pm','\xF7':'div','\xD7':'times','<':'lt','\u226E':'nlt','<\u20D2':'nvlt','=':'equals','\u2260':'ne','=\u20E5':'bne','\u2A75':'Equal','>':'gt','\u226F':'ngt','>\u20D2':'nvgt','\xAC':'not','|':'vert','\xA6':'brvbar','\u2212':'minus','\u2213':'mp','\u2214':'plusdo','\u2044':'frasl','\u2216':'setmn','\u2217':'lowast','\u2218':'compfn','\u221A':'Sqrt','\u221D':'prop','\u221E':'infin','\u221F':'angrt','\u2220':'ang','\u2220\u20D2':'nang','\u2221':'angmsd','\u2222':'angsph','\u2223':'mid','\u2224':'nmid','\u2225':'par','\u2226':'npar','\u2227':'and','\u2228':'or','\u2229':'cap','\u2229\uFE00':'caps','\u222A':'cup','\u222A\uFE00':'cups','\u222B':'int','\u222C':'Int','\u222D':'tint','\u2A0C':'qint','\u222E':'oint','\u222F':'Conint','\u2230':'Cconint','\u2231':'cwint','\u2232':'cwconint','\u2233':'awconint','\u2234':'there4','\u2235':'becaus','\u2236':'ratio','\u2237':'Colon','\u2238':'minusd','\u223A':'mDDot','\u223B':'homtht','\u223C':'sim','\u2241':'nsim','\u223C\u20D2':'nvsim','\u223D':'bsim','\u223D\u0331':'race','\u223E':'ac','\u223E\u0333':'acE','\u223F':'acd','\u2240':'wr','\u2242':'esim','\u2242\u0338':'nesim','\u2243':'sime','\u2244':'nsime','\u2245':'cong','\u2247':'ncong','\u2246':'simne','\u2248':'ap','\u2249':'nap','\u224A':'ape','\u224B':'apid','\u224B\u0338':'napid','\u224C':'bcong','\u224D':'CupCap','\u226D':'NotCupCap','\u224D\u20D2':'nvap','\u224E':'bump','\u224E\u0338':'nbump','\u224F':'bumpe','\u224F\u0338':'nbumpe','\u2250':'doteq','\u2250\u0338':'nedot','\u2251':'eDot','\u2252':'efDot','\u2253':'erDot','\u2254':'colone','\u2255':'ecolon','\u2256':'ecir','\u2257':'cire','\u2259':'wedgeq','\u225A':'veeeq','\u225C':'trie','\u225F':'equest','\u2261':'equiv','\u2262':'nequiv','\u2261\u20E5':'bnequiv','\u2264':'le','\u2270':'nle','\u2264\u20D2':'nvle','\u2265':'ge','\u2271':'nge','\u2265\u20D2':'nvge','\u2266':'lE','\u2266\u0338':'nlE','\u2267':'gE','\u2267\u0338':'ngE','\u2268\uFE00':'lvnE','\u2268':'lnE','\u2269':'gnE','\u2269\uFE00':'gvnE','\u226A':'ll','\u226A\u0338':'nLtv','\u226A\u20D2':'nLt','\u226B':'gg','\u226B\u0338':'nGtv','\u226B\u20D2':'nGt','\u226C':'twixt','\u2272':'lsim','\u2274':'nlsim','\u2273':'gsim','\u2275':'ngsim','\u2276':'lg','\u2278':'ntlg','\u2277':'gl','\u2279':'ntgl','\u227A':'pr','\u2280':'npr','\u227B':'sc','\u2281':'nsc','\u227C':'prcue','\u22E0':'nprcue','\u227D':'sccue','\u22E1':'nsccue','\u227E':'prsim','\u227F':'scsim','\u227F\u0338':'NotSucceedsTilde','\u2282':'sub','\u2284':'nsub','\u2282\u20D2':'vnsub','\u2283':'sup','\u2285':'nsup','\u2283\u20D2':'vnsup','\u2286':'sube','\u2288':'nsube','\u2287':'supe','\u2289':'nsupe','\u228A\uFE00':'vsubne','\u228A':'subne','\u228B\uFE00':'vsupne','\u228B':'supne','\u228D':'cupdot','\u228E':'uplus','\u228F':'sqsub','\u228F\u0338':'NotSquareSubset','\u2290':'sqsup','\u2290\u0338':'NotSquareSuperset','\u2291':'sqsube','\u22E2':'nsqsube','\u2292':'sqsupe','\u22E3':'nsqsupe','\u2293':'sqcap','\u2293\uFE00':'sqcaps','\u2294':'sqcup','\u2294\uFE00':'sqcups','\u2295':'oplus','\u2296':'ominus','\u2297':'otimes','\u2298':'osol','\u2299':'odot','\u229A':'ocir','\u229B':'oast','\u229D':'odash','\u229E':'plusb','\u229F':'minusb','\u22A0':'timesb','\u22A1':'sdotb','\u22A2':'vdash','\u22AC':'nvdash','\u22A3':'dashv','\u22A4':'top','\u22A5':'bot','\u22A7':'models','\u22A8':'vDash','\u22AD':'nvDash','\u22A9':'Vdash','\u22AE':'nVdash','\u22AA':'Vvdash','\u22AB':'VDash','\u22AF':'nVDash','\u22B0':'prurel','\u22B2':'vltri','\u22EA':'nltri','\u22B3':'vrtri','\u22EB':'nrtri','\u22B4':'ltrie','\u22EC':'nltrie','\u22B4\u20D2':'nvltrie','\u22B5':'rtrie','\u22ED':'nrtrie','\u22B5\u20D2':'nvrtrie','\u22B6':'origof','\u22B7':'imof','\u22B8':'mumap','\u22B9':'hercon','\u22BA':'intcal','\u22BB':'veebar','\u22BD':'barvee','\u22BE':'angrtvb','\u22BF':'lrtri','\u22C0':'Wedge','\u22C1':'Vee','\u22C2':'xcap','\u22C3':'xcup','\u22C4':'diam','\u22C5':'sdot','\u22C6':'Star','\u22C7':'divonx','\u22C8':'bowtie','\u22C9':'ltimes','\u22CA':'rtimes','\u22CB':'lthree','\u22CC':'rthree','\u22CD':'bsime','\u22CE':'cuvee','\u22CF':'cuwed','\u22D0':'Sub','\u22D1':'Sup','\u22D2':'Cap','\u22D3':'Cup','\u22D4':'fork','\u22D5':'epar','\u22D6':'ltdot','\u22D7':'gtdot','\u22D8':'Ll','\u22D8\u0338':'nLl','\u22D9':'Gg','\u22D9\u0338':'nGg','\u22DA\uFE00':'lesg','\u22DA':'leg','\u22DB':'gel','\u22DB\uFE00':'gesl','\u22DE':'cuepr','\u22DF':'cuesc','\u22E6':'lnsim','\u22E7':'gnsim','\u22E8':'prnsim','\u22E9':'scnsim','\u22EE':'vellip','\u22EF':'ctdot','\u22F0':'utdot','\u22F1':'dtdot','\u22F2':'disin','\u22F3':'isinsv','\u22F4':'isins','\u22F5':'isindot','\u22F5\u0338':'notindot','\u22F6':'notinvc','\u22F7':'notinvb','\u22F9':'isinE','\u22F9\u0338':'notinE','\u22FA':'nisd','\u22FB':'xnis','\u22FC':'nis','\u22FD':'notnivc','\u22FE':'notnivb','\u2305':'barwed','\u2306':'Barwed','\u230C':'drcrop','\u230D':'dlcrop','\u230E':'urcrop','\u230F':'ulcrop','\u2310':'bnot','\u2312':'profline','\u2313':'profsurf','\u2315':'telrec','\u2316':'target','\u231C':'ulcorn','\u231D':'urcorn','\u231E':'dlcorn','\u231F':'drcorn','\u2322':'frown','\u2323':'smile','\u232D':'cylcty','\u232E':'profalar','\u2336':'topbot','\u233D':'ovbar','\u233F':'solbar','\u237C':'angzarr','\u23B0':'lmoust','\u23B1':'rmoust','\u23B4':'tbrk','\u23B5':'bbrk','\u23B6':'bbrktbrk','\u23DC':'OverParenthesis','\u23DD':'UnderParenthesis','\u23DE':'OverBrace','\u23DF':'UnderBrace','\u23E2':'trpezium','\u23E7':'elinters','\u2423':'blank','\u2500':'boxh','\u2502':'boxv','\u250C':'boxdr','\u2510':'boxdl','\u2514':'boxur','\u2518':'boxul','\u251C':'boxvr','\u2524':'boxvl','\u252C':'boxhd','\u2534':'boxhu','\u253C':'boxvh','\u2550':'boxH','\u2551':'boxV','\u2552':'boxdR','\u2553':'boxDr','\u2554':'boxDR','\u2555':'boxdL','\u2556':'boxDl','\u2557':'boxDL','\u2558':'boxuR','\u2559':'boxUr','\u255A':'boxUR','\u255B':'boxuL','\u255C':'boxUl','\u255D':'boxUL','\u255E':'boxvR','\u255F':'boxVr','\u2560':'boxVR','\u2561':'boxvL','\u2562':'boxVl','\u2563':'boxVL','\u2564':'boxHd','\u2565':'boxhD','\u2566':'boxHD','\u2567':'boxHu','\u2568':'boxhU','\u2569':'boxHU','\u256A':'boxvH','\u256B':'boxVh','\u256C':'boxVH','\u2580':'uhblk','\u2584':'lhblk','\u2588':'block','\u2591':'blk14','\u2592':'blk12','\u2593':'blk34','\u25A1':'squ','\u25AA':'squf','\u25AB':'EmptyVerySmallSquare','\u25AD':'rect','\u25AE':'marker','\u25B1':'fltns','\u25B3':'xutri','\u25B4':'utrif','\u25B5':'utri','\u25B8':'rtrif','\u25B9':'rtri','\u25BD':'xdtri','\u25BE':'dtrif','\u25BF':'dtri','\u25C2':'ltrif','\u25C3':'ltri','\u25CA':'loz','\u25CB':'cir','\u25EC':'tridot','\u25EF':'xcirc','\u25F8':'ultri','\u25F9':'urtri','\u25FA':'lltri','\u25FB':'EmptySmallSquare','\u25FC':'FilledSmallSquare','\u2605':'starf','\u2606':'star','\u260E':'phone','\u2640':'female','\u2642':'male','\u2660':'spades','\u2663':'clubs','\u2665':'hearts','\u2666':'diams','\u266A':'sung','\u2713':'check','\u2717':'cross','\u2720':'malt','\u2736':'sext','\u2758':'VerticalSeparator','\u27C8':'bsolhsub','\u27C9':'suphsol','\u27F5':'xlarr','\u27F6':'xrarr','\u27F7':'xharr','\u27F8':'xlArr','\u27F9':'xrArr','\u27FA':'xhArr','\u27FC':'xmap','\u27FF':'dzigrarr','\u2902':'nvlArr','\u2903':'nvrArr','\u2904':'nvHarr','\u2905':'Map','\u290C':'lbarr','\u290D':'rbarr','\u290E':'lBarr','\u290F':'rBarr','\u2910':'RBarr','\u2911':'DDotrahd','\u2912':'UpArrowBar','\u2913':'DownArrowBar','\u2916':'Rarrtl','\u2919':'latail','\u291A':'ratail','\u291B':'lAtail','\u291C':'rAtail','\u291D':'larrfs','\u291E':'rarrfs','\u291F':'larrbfs','\u2920':'rarrbfs','\u2923':'nwarhk','\u2924':'nearhk','\u2925':'searhk','\u2926':'swarhk','\u2927':'nwnear','\u2928':'toea','\u2929':'tosa','\u292A':'swnwar','\u2933':'rarrc','\u2933\u0338':'nrarrc','\u2935':'cudarrr','\u2936':'ldca','\u2937':'rdca','\u2938':'cudarrl','\u2939':'larrpl','\u293C':'curarrm','\u293D':'cularrp','\u2945':'rarrpl','\u2948':'harrcir','\u2949':'Uarrocir','\u294A':'lurdshar','\u294B':'ldrushar','\u294E':'LeftRightVector','\u294F':'RightUpDownVector','\u2950':'DownLeftRightVector','\u2951':'LeftUpDownVector','\u2952':'LeftVectorBar','\u2953':'RightVectorBar','\u2954':'RightUpVectorBar','\u2955':'RightDownVectorBar','\u2956':'DownLeftVectorBar','\u2957':'DownRightVectorBar','\u2958':'LeftUpVectorBar','\u2959':'LeftDownVectorBar','\u295A':'LeftTeeVector','\u295B':'RightTeeVector','\u295C':'RightUpTeeVector','\u295D':'RightDownTeeVector','\u295E':'DownLeftTeeVector','\u295F':'DownRightTeeVector','\u2960':'LeftUpTeeVector','\u2961':'LeftDownTeeVector','\u2962':'lHar','\u2963':'uHar','\u2964':'rHar','\u2965':'dHar','\u2966':'luruhar','\u2967':'ldrdhar','\u2968':'ruluhar','\u2969':'rdldhar','\u296A':'lharul','\u296B':'llhard','\u296C':'rharul','\u296D':'lrhard','\u296E':'udhar','\u296F':'duhar','\u2970':'RoundImplies','\u2971':'erarr','\u2972':'simrarr','\u2973':'larrsim','\u2974':'rarrsim','\u2975':'rarrap','\u2976':'ltlarr','\u2978':'gtrarr','\u2979':'subrarr','\u297B':'suplarr','\u297C':'lfisht','\u297D':'rfisht','\u297E':'ufisht','\u297F':'dfisht','\u299A':'vzigzag','\u299C':'vangrt','\u299D':'angrtvbd','\u29A4':'ange','\u29A5':'range','\u29A6':'dwangle','\u29A7':'uwangle','\u29A8':'angmsdaa','\u29A9':'angmsdab','\u29AA':'angmsdac','\u29AB':'angmsdad','\u29AC':'angmsdae','\u29AD':'angmsdaf','\u29AE':'angmsdag','\u29AF':'angmsdah','\u29B0':'bemptyv','\u29B1':'demptyv','\u29B2':'cemptyv','\u29B3':'raemptyv','\u29B4':'laemptyv','\u29B5':'ohbar','\u29B6':'omid','\u29B7':'opar','\u29B9':'operp','\u29BB':'olcross','\u29BC':'odsold','\u29BE':'olcir','\u29BF':'ofcir','\u29C0':'olt','\u29C1':'ogt','\u29C2':'cirscir','\u29C3':'cirE','\u29C4':'solb','\u29C5':'bsolb','\u29C9':'boxbox','\u29CD':'trisb','\u29CE':'rtriltri','\u29CF':'LeftTriangleBar','\u29CF\u0338':'NotLeftTriangleBar','\u29D0':'RightTriangleBar','\u29D0\u0338':'NotRightTriangleBar','\u29DC':'iinfin','\u29DD':'infintie','\u29DE':'nvinfin','\u29E3':'eparsl','\u29E4':'smeparsl','\u29E5':'eqvparsl','\u29EB':'lozf','\u29F4':'RuleDelayed','\u29F6':'dsol','\u2A00':'xodot','\u2A01':'xoplus','\u2A02':'xotime','\u2A04':'xuplus','\u2A06':'xsqcup','\u2A0D':'fpartint','\u2A10':'cirfnint','\u2A11':'awint','\u2A12':'rppolint','\u2A13':'scpolint','\u2A14':'npolint','\u2A15':'pointint','\u2A16':'quatint','\u2A17':'intlarhk','\u2A22':'pluscir','\u2A23':'plusacir','\u2A24':'simplus','\u2A25':'plusdu','\u2A26':'plussim','\u2A27':'plustwo','\u2A29':'mcomma','\u2A2A':'minusdu','\u2A2D':'loplus','\u2A2E':'roplus','\u2A2F':'Cross','\u2A30':'timesd','\u2A31':'timesbar','\u2A33':'smashp','\u2A34':'lotimes','\u2A35':'rotimes','\u2A36':'otimesas','\u2A37':'Otimes','\u2A38':'odiv','\u2A39':'triplus','\u2A3A':'triminus','\u2A3B':'tritime','\u2A3C':'iprod','\u2A3F':'amalg','\u2A40':'capdot','\u2A42':'ncup','\u2A43':'ncap','\u2A44':'capand','\u2A45':'cupor','\u2A46':'cupcap','\u2A47':'capcup','\u2A48':'cupbrcap','\u2A49':'capbrcup','\u2A4A':'cupcup','\u2A4B':'capcap','\u2A4C':'ccups','\u2A4D':'ccaps','\u2A50':'ccupssm','\u2A53':'And','\u2A54':'Or','\u2A55':'andand','\u2A56':'oror','\u2A57':'orslope','\u2A58':'andslope','\u2A5A':'andv','\u2A5B':'orv','\u2A5C':'andd','\u2A5D':'ord','\u2A5F':'wedbar','\u2A66':'sdote','\u2A6A':'simdot','\u2A6D':'congdot','\u2A6D\u0338':'ncongdot','\u2A6E':'easter','\u2A6F':'apacir','\u2A70':'apE','\u2A70\u0338':'napE','\u2A71':'eplus','\u2A72':'pluse','\u2A73':'Esim','\u2A77':'eDDot','\u2A78':'equivDD','\u2A79':'ltcir','\u2A7A':'gtcir','\u2A7B':'ltquest','\u2A7C':'gtquest','\u2A7D':'les','\u2A7D\u0338':'nles','\u2A7E':'ges','\u2A7E\u0338':'nges','\u2A7F':'lesdot','\u2A80':'gesdot','\u2A81':'lesdoto','\u2A82':'gesdoto','\u2A83':'lesdotor','\u2A84':'gesdotol','\u2A85':'lap','\u2A86':'gap','\u2A87':'lne','\u2A88':'gne','\u2A89':'lnap','\u2A8A':'gnap','\u2A8B':'lEg','\u2A8C':'gEl','\u2A8D':'lsime','\u2A8E':'gsime','\u2A8F':'lsimg','\u2A90':'gsiml','\u2A91':'lgE','\u2A92':'glE','\u2A93':'lesges','\u2A94':'gesles','\u2A95':'els','\u2A96':'egs','\u2A97':'elsdot','\u2A98':'egsdot','\u2A99':'el','\u2A9A':'eg','\u2A9D':'siml','\u2A9E':'simg','\u2A9F':'simlE','\u2AA0':'simgE','\u2AA1':'LessLess','\u2AA1\u0338':'NotNestedLessLess','\u2AA2':'GreaterGreater','\u2AA2\u0338':'NotNestedGreaterGreater','\u2AA4':'glj','\u2AA5':'gla','\u2AA6':'ltcc','\u2AA7':'gtcc','\u2AA8':'lescc','\u2AA9':'gescc','\u2AAA':'smt','\u2AAB':'lat','\u2AAC':'smte','\u2AAC\uFE00':'smtes','\u2AAD':'late','\u2AAD\uFE00':'lates','\u2AAE':'bumpE','\u2AAF':'pre','\u2AAF\u0338':'npre','\u2AB0':'sce','\u2AB0\u0338':'nsce','\u2AB3':'prE','\u2AB4':'scE','\u2AB5':'prnE','\u2AB6':'scnE','\u2AB7':'prap','\u2AB8':'scap','\u2AB9':'prnap','\u2ABA':'scnap','\u2ABB':'Pr','\u2ABC':'Sc','\u2ABD':'subdot','\u2ABE':'supdot','\u2ABF':'subplus','\u2AC0':'supplus','\u2AC1':'submult','\u2AC2':'supmult','\u2AC3':'subedot','\u2AC4':'supedot','\u2AC5':'subE','\u2AC5\u0338':'nsubE','\u2AC6':'supE','\u2AC6\u0338':'nsupE','\u2AC7':'subsim','\u2AC8':'supsim','\u2ACB\uFE00':'vsubnE','\u2ACB':'subnE','\u2ACC\uFE00':'vsupnE','\u2ACC':'supnE','\u2ACF':'csub','\u2AD0':'csup','\u2AD1':'csube','\u2AD2':'csupe','\u2AD3':'subsup','\u2AD4':'supsub','\u2AD5':'subsub','\u2AD6':'supsup','\u2AD7':'suphsub','\u2AD8':'supdsub','\u2AD9':'forkv','\u2ADA':'topfork','\u2ADB':'mlcp','\u2AE4':'Dashv','\u2AE6':'Vdashl','\u2AE7':'Barv','\u2AE8':'vBar','\u2AE9':'vBarv','\u2AEB':'Vbar','\u2AEC':'Not','\u2AED':'bNot','\u2AEE':'rnmid','\u2AEF':'cirmid','\u2AF0':'midcir','\u2AF1':'topcir','\u2AF2':'nhpar','\u2AF3':'parsim','\u2AFD':'parsl','\u2AFD\u20E5':'nparsl','\u266D':'flat','\u266E':'natur','\u266F':'sharp','\xA4':'curren','\xA2':'cent','$':'dollar','\xA3':'pound','\xA5':'yen','\u20AC':'euro','\xB9':'sup1','\xBD':'half','\u2153':'frac13','\xBC':'frac14','\u2155':'frac15','\u2159':'frac16','\u215B':'frac18','\xB2':'sup2','\u2154':'frac23','\u2156':'frac25','\xB3':'sup3','\xBE':'frac34','\u2157':'frac35','\u215C':'frac38','\u2158':'frac45','\u215A':'frac56','\u215D':'frac58','\u215E':'frac78','\uD835\uDCB6':'ascr','\uD835\uDD52':'aopf','\uD835\uDD1E':'afr','\uD835\uDD38':'Aopf','\uD835\uDD04':'Afr','\uD835\uDC9C':'Ascr','\xAA':'ordf','\xE1':'aacute','\xC1':'Aacute','\xE0':'agrave','\xC0':'Agrave','\u0103':'abreve','\u0102':'Abreve','\xE2':'acirc','\xC2':'Acirc','\xE5':'aring','\xC5':'angst','\xE4':'auml','\xC4':'Auml','\xE3':'atilde','\xC3':'Atilde','\u0105':'aogon','\u0104':'Aogon','\u0101':'amacr','\u0100':'Amacr','\xE6':'aelig','\xC6':'AElig','\uD835\uDCB7':'bscr','\uD835\uDD53':'bopf','\uD835\uDD1F':'bfr','\uD835\uDD39':'Bopf','\u212C':'Bscr','\uD835\uDD05':'Bfr','\uD835\uDD20':'cfr','\uD835\uDCB8':'cscr','\uD835\uDD54':'copf','\u212D':'Cfr','\uD835\uDC9E':'Cscr','\u2102':'Copf','\u0107':'cacute','\u0106':'Cacute','\u0109':'ccirc','\u0108':'Ccirc','\u010D':'ccaron','\u010C':'Ccaron','\u010B':'cdot','\u010A':'Cdot','\xE7':'ccedil','\xC7':'Ccedil','\u2105':'incare','\uD835\uDD21':'dfr','\u2146':'dd','\uD835\uDD55':'dopf','\uD835\uDCB9':'dscr','\uD835\uDC9F':'Dscr','\uD835\uDD07':'Dfr','\u2145':'DD','\uD835\uDD3B':'Dopf','\u010F':'dcaron','\u010E':'Dcaron','\u0111':'dstrok','\u0110':'Dstrok','\xF0':'eth','\xD0':'ETH','\u2147':'ee','\u212F':'escr','\uD835\uDD22':'efr','\uD835\uDD56':'eopf','\u2130':'Escr','\uD835\uDD08':'Efr','\uD835\uDD3C':'Eopf','\xE9':'eacute','\xC9':'Eacute','\xE8':'egrave','\xC8':'Egrave','\xEA':'ecirc','\xCA':'Ecirc','\u011B':'ecaron','\u011A':'Ecaron','\xEB':'euml','\xCB':'Euml','\u0117':'edot','\u0116':'Edot','\u0119':'eogon','\u0118':'Eogon','\u0113':'emacr','\u0112':'Emacr','\uD835\uDD23':'ffr','\uD835\uDD57':'fopf','\uD835\uDCBB':'fscr','\uD835\uDD09':'Ffr','\uD835\uDD3D':'Fopf','\u2131':'Fscr','\uFB00':'fflig','\uFB03':'ffilig','\uFB04':'ffllig','\uFB01':'filig','fj':'fjlig','\uFB02':'fllig','\u0192':'fnof','\u210A':'gscr','\uD835\uDD58':'gopf','\uD835\uDD24':'gfr','\uD835\uDCA2':'Gscr','\uD835\uDD3E':'Gopf','\uD835\uDD0A':'Gfr','\u01F5':'gacute','\u011F':'gbreve','\u011E':'Gbreve','\u011D':'gcirc','\u011C':'Gcirc','\u0121':'gdot','\u0120':'Gdot','\u0122':'Gcedil','\uD835\uDD25':'hfr','\u210E':'planckh','\uD835\uDCBD':'hscr','\uD835\uDD59':'hopf','\u210B':'Hscr','\u210C':'Hfr','\u210D':'Hopf','\u0125':'hcirc','\u0124':'Hcirc','\u210F':'hbar','\u0127':'hstrok','\u0126':'Hstrok','\uD835\uDD5A':'iopf','\uD835\uDD26':'ifr','\uD835\uDCBE':'iscr','\u2148':'ii','\uD835\uDD40':'Iopf','\u2110':'Iscr','\u2111':'Im','\xED':'iacute','\xCD':'Iacute','\xEC':'igrave','\xCC':'Igrave','\xEE':'icirc','\xCE':'Icirc','\xEF':'iuml','\xCF':'Iuml','\u0129':'itilde','\u0128':'Itilde','\u0130':'Idot','\u012F':'iogon','\u012E':'Iogon','\u012B':'imacr','\u012A':'Imacr','\u0133':'ijlig','\u0132':'IJlig','\u0131':'imath','\uD835\uDCBF':'jscr','\uD835\uDD5B':'jopf','\uD835\uDD27':'jfr','\uD835\uDCA5':'Jscr','\uD835\uDD0D':'Jfr','\uD835\uDD41':'Jopf','\u0135':'jcirc','\u0134':'Jcirc','\u0237':'jmath','\uD835\uDD5C':'kopf','\uD835\uDCC0':'kscr','\uD835\uDD28':'kfr','\uD835\uDCA6':'Kscr','\uD835\uDD42':'Kopf','\uD835\uDD0E':'Kfr','\u0137':'kcedil','\u0136':'Kcedil','\uD835\uDD29':'lfr','\uD835\uDCC1':'lscr','\u2113':'ell','\uD835\uDD5D':'lopf','\u2112':'Lscr','\uD835\uDD0F':'Lfr','\uD835\uDD43':'Lopf','\u013A':'lacute','\u0139':'Lacute','\u013E':'lcaron','\u013D':'Lcaron','\u013C':'lcedil','\u013B':'Lcedil','\u0142':'lstrok','\u0141':'Lstrok','\u0140':'lmidot','\u013F':'Lmidot','\uD835\uDD2A':'mfr','\uD835\uDD5E':'mopf','\uD835\uDCC2':'mscr','\uD835\uDD10':'Mfr','\uD835\uDD44':'Mopf','\u2133':'Mscr','\uD835\uDD2B':'nfr','\uD835\uDD5F':'nopf','\uD835\uDCC3':'nscr','\u2115':'Nopf','\uD835\uDCA9':'Nscr','\uD835\uDD11':'Nfr','\u0144':'nacute','\u0143':'Nacute','\u0148':'ncaron','\u0147':'Ncaron','\xF1':'ntilde','\xD1':'Ntilde','\u0146':'ncedil','\u0145':'Ncedil','\u2116':'numero','\u014B':'eng','\u014A':'ENG','\uD835\uDD60':'oopf','\uD835\uDD2C':'ofr','\u2134':'oscr','\uD835\uDCAA':'Oscr','\uD835\uDD12':'Ofr','\uD835\uDD46':'Oopf','\xBA':'ordm','\xF3':'oacute','\xD3':'Oacute','\xF2':'ograve','\xD2':'Ograve','\xF4':'ocirc','\xD4':'Ocirc','\xF6':'ouml','\xD6':'Ouml','\u0151':'odblac','\u0150':'Odblac','\xF5':'otilde','\xD5':'Otilde','\xF8':'oslash','\xD8':'Oslash','\u014D':'omacr','\u014C':'Omacr','\u0153':'oelig','\u0152':'OElig','\uD835\uDD2D':'pfr','\uD835\uDCC5':'pscr','\uD835\uDD61':'popf','\u2119':'Popf','\uD835\uDD13':'Pfr','\uD835\uDCAB':'Pscr','\uD835\uDD62':'qopf','\uD835\uDD2E':'qfr','\uD835\uDCC6':'qscr','\uD835\uDCAC':'Qscr','\uD835\uDD14':'Qfr','\u211A':'Qopf','\u0138':'kgreen','\uD835\uDD2F':'rfr','\uD835\uDD63':'ropf','\uD835\uDCC7':'rscr','\u211B':'Rscr','\u211C':'Re','\u211D':'Ropf','\u0155':'racute','\u0154':'Racute','\u0159':'rcaron','\u0158':'Rcaron','\u0157':'rcedil','\u0156':'Rcedil','\uD835\uDD64':'sopf','\uD835\uDCC8':'sscr','\uD835\uDD30':'sfr','\uD835\uDD4A':'Sopf','\uD835\uDD16':'Sfr','\uD835\uDCAE':'Sscr','\u24C8':'oS','\u015B':'sacute','\u015A':'Sacute','\u015D':'scirc','\u015C':'Scirc','\u0161':'scaron','\u0160':'Scaron','\u015F':'scedil','\u015E':'Scedil','\xDF':'szlig','\uD835\uDD31':'tfr','\uD835\uDCC9':'tscr','\uD835\uDD65':'topf','\uD835\uDCAF':'Tscr','\uD835\uDD17':'Tfr','\uD835\uDD4B':'Topf','\u0165':'tcaron','\u0164':'Tcaron','\u0163':'tcedil','\u0162':'Tcedil','\u2122':'trade','\u0167':'tstrok','\u0166':'Tstrok','\uD835\uDCCA':'uscr','\uD835\uDD66':'uopf','\uD835\uDD32':'ufr','\uD835\uDD4C':'Uopf','\uD835\uDD18':'Ufr','\uD835\uDCB0':'Uscr','\xFA':'uacute','\xDA':'Uacute','\xF9':'ugrave','\xD9':'Ugrave','\u016D':'ubreve','\u016C':'Ubreve','\xFB':'ucirc','\xDB':'Ucirc','\u016F':'uring','\u016E':'Uring','\xFC':'uuml','\xDC':'Uuml','\u0171':'udblac','\u0170':'Udblac','\u0169':'utilde','\u0168':'Utilde','\u0173':'uogon','\u0172':'Uogon','\u016B':'umacr','\u016A':'Umacr','\uD835\uDD33':'vfr','\uD835\uDD67':'vopf','\uD835\uDCCB':'vscr','\uD835\uDD19':'Vfr','\uD835\uDD4D':'Vopf','\uD835\uDCB1':'Vscr','\uD835\uDD68':'wopf','\uD835\uDCCC':'wscr','\uD835\uDD34':'wfr','\uD835\uDCB2':'Wscr','\uD835\uDD4E':'Wopf','\uD835\uDD1A':'Wfr','\u0175':'wcirc','\u0174':'Wcirc','\uD835\uDD35':'xfr','\uD835\uDCCD':'xscr','\uD835\uDD69':'xopf','\uD835\uDD4F':'Xopf','\uD835\uDD1B':'Xfr','\uD835\uDCB3':'Xscr','\uD835\uDD36':'yfr','\uD835\uDCCE':'yscr','\uD835\uDD6A':'yopf','\uD835\uDCB4':'Yscr','\uD835\uDD1C':'Yfr','\uD835\uDD50':'Yopf','\xFD':'yacute','\xDD':'Yacute','\u0177':'ycirc','\u0176':'Ycirc','\xFF':'yuml','\u0178':'Yuml','\uD835\uDCCF':'zscr','\uD835\uDD37':'zfr','\uD835\uDD6B':'zopf','\u2128':'Zfr','\u2124':'Zopf','\uD835\uDCB5':'Zscr','\u017A':'zacute','\u0179':'Zacute','\u017E':'zcaron','\u017D':'Zcaron','\u017C':'zdot','\u017B':'Zdot','\u01B5':'imped','\xFE':'thorn','\xDE':'THORN','\u0149':'napos','\u03B1':'alpha','\u0391':'Alpha','\u03B2':'beta','\u0392':'Beta','\u03B3':'gamma','\u0393':'Gamma','\u03B4':'delta','\u0394':'Delta','\u03B5':'epsi','\u03F5':'epsiv','\u0395':'Epsilon','\u03DD':'gammad','\u03DC':'Gammad','\u03B6':'zeta','\u0396':'Zeta','\u03B7':'eta','\u0397':'Eta','\u03B8':'theta','\u03D1':'thetav','\u0398':'Theta','\u03B9':'iota','\u0399':'Iota','\u03BA':'kappa','\u03F0':'kappav','\u039A':'Kappa','\u03BB':'lambda','\u039B':'Lambda','\u03BC':'mu','\xB5':'micro','\u039C':'Mu','\u03BD':'nu','\u039D':'Nu','\u03BE':'xi','\u039E':'Xi','\u03BF':'omicron','\u039F':'Omicron','\u03C0':'pi','\u03D6':'piv','\u03A0':'Pi','\u03C1':'rho','\u03F1':'rhov','\u03A1':'Rho','\u03C3':'sigma','\u03A3':'Sigma','\u03C2':'sigmaf','\u03C4':'tau','\u03A4':'Tau','\u03C5':'upsi','\u03A5':'Upsilon','\u03D2':'Upsi','\u03C6':'phi','\u03D5':'phiv','\u03A6':'Phi','\u03C7':'chi','\u03A7':'Chi','\u03C8':'psi','\u03A8':'Psi','\u03C9':'omega','\u03A9':'ohm','\u0430':'acy','\u0410':'Acy','\u0431':'bcy','\u0411':'Bcy','\u0432':'vcy','\u0412':'Vcy','\u0433':'gcy','\u0413':'Gcy','\u0453':'gjcy','\u0403':'GJcy','\u0434':'dcy','\u0414':'Dcy','\u0452':'djcy','\u0402':'DJcy','\u0435':'iecy','\u0415':'IEcy','\u0451':'iocy','\u0401':'IOcy','\u0454':'jukcy','\u0404':'Jukcy','\u0436':'zhcy','\u0416':'ZHcy','\u0437':'zcy','\u0417':'Zcy','\u0455':'dscy','\u0405':'DScy','\u0438':'icy','\u0418':'Icy','\u0456':'iukcy','\u0406':'Iukcy','\u0457':'yicy','\u0407':'YIcy','\u0439':'jcy','\u0419':'Jcy','\u0458':'jsercy','\u0408':'Jsercy','\u043A':'kcy','\u041A':'Kcy','\u045C':'kjcy','\u040C':'KJcy','\u043B':'lcy','\u041B':'Lcy','\u0459':'ljcy','\u0409':'LJcy','\u043C':'mcy','\u041C':'Mcy','\u043D':'ncy','\u041D':'Ncy','\u045A':'njcy','\u040A':'NJcy','\u043E':'ocy','\u041E':'Ocy','\u043F':'pcy','\u041F':'Pcy','\u0440':'rcy','\u0420':'Rcy','\u0441':'scy','\u0421':'Scy','\u0442':'tcy','\u0422':'Tcy','\u045B':'tshcy','\u040B':'TSHcy','\u0443':'ucy','\u0423':'Ucy','\u045E':'ubrcy','\u040E':'Ubrcy','\u0444':'fcy','\u0424':'Fcy','\u0445':'khcy','\u0425':'KHcy','\u0446':'tscy','\u0426':'TScy','\u0447':'chcy','\u0427':'CHcy','\u045F':'dzcy','\u040F':'DZcy','\u0448':'shcy','\u0428':'SHcy','\u0449':'shchcy','\u0429':'SHCHcy','\u044A':'hardcy','\u042A':'HARDcy','\u044B':'ycy','\u042B':'Ycy','\u044C':'softcy','\u042C':'SOFTcy','\u044D':'ecy','\u042D':'Ecy','\u044E':'yucy','\u042E':'YUcy','\u044F':'yacy','\u042F':'YAcy','\u2135':'aleph','\u2136':'beth','\u2137':'gimel','\u2138':'daleth'}; + + var regexEscape = /["&'<>`]/g; + var escapeMap = { + '"': '"', + '&': '&', + '\'': ''', + '<': '<', + // See https://mathiasbynens.be/notes/ambiguous-ampersands: in HTML, the + // following is not strictly necessary unless it’s part of a tag or an + // unquoted attribute value. We’re only escaping it to support those + // situations, and for XML support. + '>': '>', + // In Internet Explorer ≤ 8, the backtick character can be used + // to break out of (un)quoted attribute values or HTML comments. + // See http://html5sec.org/#102, http://html5sec.org/#108, and + // http://html5sec.org/#133. + '`': '`' + }; + + var regexInvalidEntity = /&#(?:[xX][^a-fA-F0-9]|[^0-9xX])/; + var regexInvalidRawCodePoint = /[\0-\x08\x0B\x0E-\x1F\x7F-\x9F\uFDD0-\uFDEF\uFFFE\uFFFF]|[\uD83F\uD87F\uD8BF\uD8FF\uD93F\uD97F\uD9BF\uD9FF\uDA3F\uDA7F\uDABF\uDAFF\uDB3F\uDB7F\uDBBF\uDBFF][\uDFFE\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/; + var regexDecode = /&(CounterClockwiseContourIntegral|DoubleLongLeftRightArrow|ClockwiseContourIntegral|NotNestedGreaterGreater|NotSquareSupersetEqual|DiacriticalDoubleAcute|NotRightTriangleEqual|NotSucceedsSlantEqual|NotPrecedesSlantEqual|CloseCurlyDoubleQuote|NegativeVeryThinSpace|DoubleContourIntegral|FilledVerySmallSquare|CapitalDifferentialD|OpenCurlyDoubleQuote|EmptyVerySmallSquare|NestedGreaterGreater|DoubleLongRightArrow|NotLeftTriangleEqual|NotGreaterSlantEqual|ReverseUpEquilibrium|DoubleLeftRightArrow|NotSquareSubsetEqual|NotDoubleVerticalBar|RightArrowLeftArrow|NotGreaterFullEqual|NotRightTriangleBar|SquareSupersetEqual|DownLeftRightVector|DoubleLongLeftArrow|leftrightsquigarrow|LeftArrowRightArrow|NegativeMediumSpace|blacktriangleright|RightDownVectorBar|PrecedesSlantEqual|RightDoubleBracket|SucceedsSlantEqual|NotLeftTriangleBar|RightTriangleEqual|SquareIntersection|RightDownTeeVector|ReverseEquilibrium|NegativeThickSpace|longleftrightarrow|Longleftrightarrow|LongLeftRightArrow|DownRightTeeVector|DownRightVectorBar|GreaterSlantEqual|SquareSubsetEqual|LeftDownVectorBar|LeftDoubleBracket|VerticalSeparator|rightleftharpoons|NotGreaterGreater|NotSquareSuperset|blacktriangleleft|blacktriangledown|NegativeThinSpace|LeftDownTeeVector|NotLessSlantEqual|leftrightharpoons|DoubleUpDownArrow|DoubleVerticalBar|LeftTriangleEqual|FilledSmallSquare|twoheadrightarrow|NotNestedLessLess|DownLeftTeeVector|DownLeftVectorBar|RightAngleBracket|NotTildeFullEqual|NotReverseElement|RightUpDownVector|DiacriticalTilde|NotSucceedsTilde|circlearrowright|NotPrecedesEqual|rightharpoondown|DoubleRightArrow|NotSucceedsEqual|NonBreakingSpace|NotRightTriangle|LessEqualGreater|RightUpTeeVector|LeftAngleBracket|GreaterFullEqual|DownArrowUpArrow|RightUpVectorBar|twoheadleftarrow|GreaterEqualLess|downharpoonright|RightTriangleBar|ntrianglerighteq|NotSupersetEqual|LeftUpDownVector|DiacriticalAcute|rightrightarrows|vartriangleright|UpArrowDownArrow|DiacriticalGrave|UnderParenthesis|EmptySmallSquare|LeftUpVectorBar|leftrightarrows|DownRightVector|downharpoonleft|trianglerighteq|ShortRightArrow|OverParenthesis|DoubleLeftArrow|DoubleDownArrow|NotSquareSubset|bigtriangledown|ntrianglelefteq|UpperRightArrow|curvearrowright|vartriangleleft|NotLeftTriangle|nleftrightarrow|LowerRightArrow|NotHumpDownHump|NotGreaterTilde|rightthreetimes|LeftUpTeeVector|NotGreaterEqual|straightepsilon|LeftTriangleBar|rightsquigarrow|ContourIntegral|rightleftarrows|CloseCurlyQuote|RightDownVector|LeftRightVector|nLeftrightarrow|leftharpoondown|circlearrowleft|SquareSuperset|OpenCurlyQuote|hookrightarrow|HorizontalLine|DiacriticalDot|NotLessGreater|ntriangleright|DoubleRightTee|InvisibleComma|InvisibleTimes|LowerLeftArrow|DownLeftVector|NotSubsetEqual|curvearrowleft|trianglelefteq|NotVerticalBar|TildeFullEqual|downdownarrows|NotGreaterLess|RightTeeVector|ZeroWidthSpace|looparrowright|LongRightArrow|doublebarwedge|ShortLeftArrow|ShortDownArrow|RightVectorBar|GreaterGreater|ReverseElement|rightharpoonup|LessSlantEqual|leftthreetimes|upharpoonright|rightarrowtail|LeftDownVector|Longrightarrow|NestedLessLess|UpperLeftArrow|nshortparallel|leftleftarrows|leftrightarrow|Leftrightarrow|LeftRightArrow|longrightarrow|upharpoonleft|RightArrowBar|ApplyFunction|LeftTeeVector|leftarrowtail|NotEqualTilde|varsubsetneqq|varsupsetneqq|RightTeeArrow|SucceedsEqual|SucceedsTilde|LeftVectorBar|SupersetEqual|hookleftarrow|DifferentialD|VerticalTilde|VeryThinSpace|blacktriangle|bigtriangleup|LessFullEqual|divideontimes|leftharpoonup|UpEquilibrium|ntriangleleft|RightTriangle|measuredangle|shortparallel|longleftarrow|Longleftarrow|LongLeftArrow|DoubleLeftTee|Poincareplane|PrecedesEqual|triangleright|DoubleUpArrow|RightUpVector|fallingdotseq|looparrowleft|PrecedesTilde|NotTildeEqual|NotTildeTilde|smallsetminus|Proportional|triangleleft|triangledown|UnderBracket|NotHumpEqual|exponentiale|ExponentialE|NotLessTilde|HilbertSpace|RightCeiling|blacklozenge|varsupsetneq|HumpDownHump|GreaterEqual|VerticalLine|LeftTeeArrow|NotLessEqual|DownTeeArrow|LeftTriangle|varsubsetneq|Intersection|NotCongruent|DownArrowBar|LeftUpVector|LeftArrowBar|risingdotseq|GreaterTilde|RoundImplies|SquareSubset|ShortUpArrow|NotSuperset|quaternions|precnapprox|backepsilon|preccurlyeq|OverBracket|blacksquare|MediumSpace|VerticalBar|circledcirc|circleddash|CircleMinus|CircleTimes|LessGreater|curlyeqprec|curlyeqsucc|diamondsuit|UpDownArrow|Updownarrow|RuleDelayed|Rrightarrow|updownarrow|RightVector|nRightarrow|nrightarrow|eqslantless|LeftCeiling|Equilibrium|SmallCircle|expectation|NotSucceeds|thickapprox|GreaterLess|SquareUnion|NotPrecedes|NotLessLess|straightphi|succnapprox|succcurlyeq|SubsetEqual|sqsupseteq|Proportion|Laplacetrf|ImaginaryI|supsetneqq|NotGreater|gtreqqless|NotElement|ThickSpace|TildeEqual|TildeTilde|Fouriertrf|rmoustache|EqualTilde|eqslantgtr|UnderBrace|LeftVector|UpArrowBar|nLeftarrow|nsubseteqq|subsetneqq|nsupseteqq|nleftarrow|succapprox|lessapprox|UpTeeArrow|upuparrows|curlywedge|lesseqqgtr|varepsilon|varnothing|RightFloor|complement|CirclePlus|sqsubseteq|Lleftarrow|circledast|RightArrow|Rightarrow|rightarrow|lmoustache|Bernoullis|precapprox|mapstoleft|mapstodown|longmapsto|dotsquare|downarrow|DoubleDot|nsubseteq|supsetneq|leftarrow|nsupseteq|subsetneq|ThinSpace|ngeqslant|subseteqq|HumpEqual|NotSubset|triangleq|NotCupCap|lesseqgtr|heartsuit|TripleDot|Leftarrow|Coproduct|Congruent|varpropto|complexes|gvertneqq|LeftArrow|LessTilde|supseteqq|MinusPlus|CircleDot|nleqslant|NotExists|gtreqless|nparallel|UnionPlus|LeftFloor|checkmark|CenterDot|centerdot|Mellintrf|gtrapprox|bigotimes|OverBrace|spadesuit|therefore|pitchfork|rationals|PlusMinus|Backslash|Therefore|DownBreve|backsimeq|backprime|DownArrow|nshortmid|Downarrow|lvertneqq|eqvparsl|imagline|imagpart|infintie|integers|Integral|intercal|LessLess|Uarrocir|intlarhk|sqsupset|angmsdaf|sqsubset|llcorner|vartheta|cupbrcap|lnapprox|Superset|SuchThat|succnsim|succneqq|angmsdag|biguplus|curlyvee|trpezium|Succeeds|NotTilde|bigwedge|angmsdah|angrtvbd|triminus|cwconint|fpartint|lrcorner|smeparsl|subseteq|urcorner|lurdshar|laemptyv|DDotrahd|approxeq|ldrushar|awconint|mapstoup|backcong|shortmid|triangle|geqslant|gesdotol|timesbar|circledR|circledS|setminus|multimap|naturals|scpolint|ncongdot|RightTee|boxminus|gnapprox|boxtimes|andslope|thicksim|angmsdaa|varsigma|cirfnint|rtriltri|angmsdab|rppolint|angmsdac|barwedge|drbkarow|clubsuit|thetasym|bsolhsub|capbrcup|dzigrarr|doteqdot|DotEqual|dotminus|UnderBar|NotEqual|realpart|otimesas|ulcorner|hksearow|hkswarow|parallel|PartialD|elinters|emptyset|plusacir|bbrktbrk|angmsdad|pointint|bigoplus|angmsdae|Precedes|bigsqcup|varkappa|notindot|supseteq|precneqq|precnsim|profalar|profline|profsurf|leqslant|lesdotor|raemptyv|subplus|notnivb|notnivc|subrarr|zigrarr|vzigzag|submult|subedot|Element|between|cirscir|larrbfs|larrsim|lotimes|lbrksld|lbrkslu|lozenge|ldrdhar|dbkarow|bigcirc|epsilon|simrarr|simplus|ltquest|Epsilon|luruhar|gtquest|maltese|npolint|eqcolon|npreceq|bigodot|ddagger|gtrless|bnequiv|harrcir|ddotseq|equivDD|backsim|demptyv|nsqsube|nsqsupe|Upsilon|nsubset|upsilon|minusdu|nsucceq|swarrow|nsupset|coloneq|searrow|boxplus|napprox|natural|asympeq|alefsym|congdot|nearrow|bigstar|diamond|supplus|tritime|LeftTee|nvinfin|triplus|NewLine|nvltrie|nvrtrie|nwarrow|nexists|Diamond|ruluhar|Implies|supmult|angzarr|suplarr|suphsub|questeq|because|digamma|Because|olcross|bemptyv|omicron|Omicron|rotimes|NoBreak|intprod|angrtvb|orderof|uwangle|suphsol|lesdoto|orslope|DownTee|realine|cudarrl|rdldhar|OverBar|supedot|lessdot|supdsub|topfork|succsim|rbrkslu|rbrksld|pertenk|cudarrr|isindot|planckh|lessgtr|pluscir|gesdoto|plussim|plustwo|lesssim|cularrp|rarrsim|Cayleys|notinva|notinvb|notinvc|UpArrow|Uparrow|uparrow|NotLess|dwangle|precsim|Product|curarrm|Cconint|dotplus|rarrbfs|ccupssm|Cedilla|cemptyv|notniva|quatint|frac35|frac38|frac45|frac56|frac58|frac78|tridot|xoplus|gacute|gammad|Gammad|lfisht|lfloor|bigcup|sqsupe|gbreve|Gbreve|lharul|sqsube|sqcups|Gcedil|apacir|llhard|lmidot|Lmidot|lmoust|andand|sqcaps|approx|Abreve|spades|circeq|tprime|divide|topcir|Assign|topbot|gesdot|divonx|xuplus|timesd|gesles|atilde|solbar|SOFTcy|loplus|timesb|lowast|lowbar|dlcorn|dlcrop|softcy|dollar|lparlt|thksim|lrhard|Atilde|lsaquo|smashp|bigvee|thinsp|wreath|bkarow|lsquor|lstrok|Lstrok|lthree|ltimes|ltlarr|DotDot|simdot|ltrPar|weierp|xsqcup|angmsd|sigmav|sigmaf|zeetrf|Zcaron|zcaron|mapsto|vsupne|thetav|cirmid|marker|mcomma|Zacute|vsubnE|there4|gtlPar|vsubne|bottom|gtrarr|SHCHcy|shchcy|midast|midcir|middot|minusb|minusd|gtrdot|bowtie|sfrown|mnplus|models|colone|seswar|Colone|mstpos|searhk|gtrsim|nacute|Nacute|boxbox|telrec|hairsp|Tcedil|nbumpe|scnsim|ncaron|Ncaron|ncedil|Ncedil|hamilt|Scedil|nearhk|hardcy|HARDcy|tcedil|Tcaron|commat|nequiv|nesear|tcaron|target|hearts|nexist|varrho|scedil|Scaron|scaron|hellip|Sacute|sacute|hercon|swnwar|compfn|rtimes|rthree|rsquor|rsaquo|zacute|wedgeq|homtht|barvee|barwed|Barwed|rpargt|horbar|conint|swarhk|roplus|nltrie|hslash|hstrok|Hstrok|rmoust|Conint|bprime|hybull|hyphen|iacute|Iacute|supsup|supsub|supsim|varphi|coprod|brvbar|agrave|Supset|supset|igrave|Igrave|notinE|Agrave|iiiint|iinfin|copysr|wedbar|Verbar|vangrt|becaus|incare|verbar|inodot|bullet|drcorn|intcal|drcrop|cularr|vellip|Utilde|bumpeq|cupcap|dstrok|Dstrok|CupCap|cupcup|cupdot|eacute|Eacute|supdot|iquest|easter|ecaron|Ecaron|ecolon|isinsv|utilde|itilde|Itilde|curarr|succeq|Bumpeq|cacute|ulcrop|nparsl|Cacute|nprcue|egrave|Egrave|nrarrc|nrarrw|subsup|subsub|nrtrie|jsercy|nsccue|Jsercy|kappav|kcedil|Kcedil|subsim|ulcorn|nsimeq|egsdot|veebar|kgreen|capand|elsdot|Subset|subset|curren|aacute|lacute|Lacute|emptyv|ntilde|Ntilde|lagran|lambda|Lambda|capcap|Ugrave|langle|subdot|emsp13|numero|emsp14|nvdash|nvDash|nVdash|nVDash|ugrave|ufisht|nvHarr|larrfs|nvlArr|larrhk|larrlp|larrpl|nvrArr|Udblac|nwarhk|larrtl|nwnear|oacute|Oacute|latail|lAtail|sstarf|lbrace|odblac|Odblac|lbrack|udblac|odsold|eparsl|lcaron|Lcaron|ograve|Ograve|lcedil|Lcedil|Aacute|ssmile|ssetmn|squarf|ldquor|capcup|ominus|cylcty|rharul|eqcirc|dagger|rfloor|rfisht|Dagger|daleth|equals|origof|capdot|equest|dcaron|Dcaron|rdquor|oslash|Oslash|otilde|Otilde|otimes|Otimes|urcrop|Ubreve|ubreve|Yacute|Uacute|uacute|Rcedil|rcedil|urcorn|parsim|Rcaron|Vdashl|rcaron|Tstrok|percnt|period|permil|Exists|yacute|rbrack|rbrace|phmmat|ccaron|Ccaron|planck|ccedil|plankv|tstrok|female|plusdo|plusdu|ffilig|plusmn|ffllig|Ccedil|rAtail|dfisht|bernou|ratail|Rarrtl|rarrtl|angsph|rarrpl|rarrlp|rarrhk|xwedge|xotime|forall|ForAll|Vvdash|vsupnE|preceq|bigcap|frac12|frac13|frac14|primes|rarrfs|prnsim|frac15|Square|frac16|square|lesdot|frac18|frac23|propto|prurel|rarrap|rangle|puncsp|frac25|Racute|qprime|racute|lesges|frac34|abreve|AElig|eqsim|utdot|setmn|urtri|Equal|Uring|seArr|uring|searr|dashv|Dashv|mumap|nabla|iogon|Iogon|sdote|sdotb|scsim|napid|napos|equiv|natur|Acirc|dblac|erarr|nbump|iprod|erDot|ucirc|awint|esdot|angrt|ncong|isinE|scnap|Scirc|scirc|ndash|isins|Ubrcy|nearr|neArr|isinv|nedot|ubrcy|acute|Ycirc|iukcy|Iukcy|xutri|nesim|caret|jcirc|Jcirc|caron|twixt|ddarr|sccue|exist|jmath|sbquo|ngeqq|angst|ccaps|lceil|ngsim|UpTee|delta|Delta|rtrif|nharr|nhArr|nhpar|rtrie|jukcy|Jukcy|kappa|rsquo|Kappa|nlarr|nlArr|TSHcy|rrarr|aogon|Aogon|fflig|xrarr|tshcy|ccirc|nleqq|filig|upsih|nless|dharl|nlsim|fjlig|ropar|nltri|dharr|robrk|roarr|fllig|fltns|roang|rnmid|subnE|subne|lAarr|trisb|Ccirc|acirc|ccups|blank|VDash|forkv|Vdash|langd|cedil|blk12|blk14|laquo|strns|diams|notin|vDash|larrb|blk34|block|disin|uplus|vdash|vBarv|aelig|starf|Wedge|check|xrArr|lates|lbarr|lBarr|notni|lbbrk|bcong|frasl|lbrke|frown|vrtri|vprop|vnsup|gamma|Gamma|wedge|xodot|bdquo|srarr|doteq|ldquo|boxdl|boxdL|gcirc|Gcirc|boxDl|boxDL|boxdr|boxdR|boxDr|TRADE|trade|rlhar|boxDR|vnsub|npart|vltri|rlarr|boxhd|boxhD|nprec|gescc|nrarr|nrArr|boxHd|boxHD|boxhu|boxhU|nrtri|boxHu|clubs|boxHU|times|colon|Colon|gimel|xlArr|Tilde|nsime|tilde|nsmid|nspar|THORN|thorn|xlarr|nsube|nsubE|thkap|xhArr|comma|nsucc|boxul|boxuL|nsupe|nsupE|gneqq|gnsim|boxUl|boxUL|grave|boxur|boxuR|boxUr|boxUR|lescc|angle|bepsi|boxvh|varpi|boxvH|numsp|Theta|gsime|gsiml|theta|boxVh|boxVH|boxvl|gtcir|gtdot|boxvL|boxVl|boxVL|crarr|cross|Cross|nvsim|boxvr|nwarr|nwArr|sqsup|dtdot|Uogon|lhard|lharu|dtrif|ocirc|Ocirc|lhblk|duarr|odash|sqsub|Hacek|sqcup|llarr|duhar|oelig|OElig|ofcir|boxvR|uogon|lltri|boxVr|csube|uuarr|ohbar|csupe|ctdot|olarr|olcir|harrw|oline|sqcap|omacr|Omacr|omega|Omega|boxVR|aleph|lneqq|lnsim|loang|loarr|rharu|lobrk|hcirc|operp|oplus|rhard|Hcirc|orarr|Union|order|ecirc|Ecirc|cuepr|szlig|cuesc|breve|reals|eDDot|Breve|hoarr|lopar|utrif|rdquo|Umacr|umacr|efDot|swArr|ultri|alpha|rceil|ovbar|swarr|Wcirc|wcirc|smtes|smile|bsemi|lrarr|aring|parsl|lrhar|bsime|uhblk|lrtri|cupor|Aring|uharr|uharl|slarr|rbrke|bsolb|lsime|rbbrk|RBarr|lsimg|phone|rBarr|rbarr|icirc|lsquo|Icirc|emacr|Emacr|ratio|simne|plusb|simlE|simgE|simeq|pluse|ltcir|ltdot|empty|xharr|xdtri|iexcl|Alpha|ltrie|rarrw|pound|ltrif|xcirc|bumpe|prcue|bumpE|asymp|amacr|cuvee|Sigma|sigma|iiint|udhar|iiota|ijlig|IJlig|supnE|imacr|Imacr|prime|Prime|image|prnap|eogon|Eogon|rarrc|mdash|mDDot|cuwed|imath|supne|imped|Amacr|udarr|prsim|micro|rarrb|cwint|raquo|infin|eplus|range|rangd|Ucirc|radic|minus|amalg|veeeq|rAarr|epsiv|ycirc|quest|sharp|quot|zwnj|Qscr|race|qscr|Qopf|qopf|qint|rang|Rang|Zscr|zscr|Zopf|zopf|rarr|rArr|Rarr|Pscr|pscr|prop|prod|prnE|prec|ZHcy|zhcy|prap|Zeta|zeta|Popf|popf|Zdot|plus|zdot|Yuml|yuml|phiv|YUcy|yucy|Yscr|yscr|perp|Yopf|yopf|part|para|YIcy|Ouml|rcub|yicy|YAcy|rdca|ouml|osol|Oscr|rdsh|yacy|real|oscr|xvee|andd|rect|andv|Xscr|oror|ordm|ordf|xscr|ange|aopf|Aopf|rHar|Xopf|opar|Oopf|xopf|xnis|rhov|oopf|omid|xmap|oint|apid|apos|ogon|ascr|Ascr|odot|odiv|xcup|xcap|ocir|oast|nvlt|nvle|nvgt|nvge|nvap|Wscr|wscr|auml|ntlg|ntgl|nsup|nsub|nsim|Nscr|nscr|nsce|Wopf|ring|npre|wopf|npar|Auml|Barv|bbrk|Nopf|nopf|nmid|nLtv|beta|ropf|Ropf|Beta|beth|nles|rpar|nleq|bnot|bNot|nldr|NJcy|rscr|Rscr|Vscr|vscr|rsqb|njcy|bopf|nisd|Bopf|rtri|Vopf|nGtv|ngtr|vopf|boxh|boxH|boxv|nges|ngeq|boxV|bscr|scap|Bscr|bsim|Vert|vert|bsol|bull|bump|caps|cdot|ncup|scnE|ncap|nbsp|napE|Cdot|cent|sdot|Vbar|nang|vBar|chcy|Mscr|mscr|sect|semi|CHcy|Mopf|mopf|sext|circ|cire|mldr|mlcp|cirE|comp|shcy|SHcy|vArr|varr|cong|copf|Copf|copy|COPY|malt|male|macr|lvnE|cscr|ltri|sime|ltcc|simg|Cscr|siml|csub|Uuml|lsqb|lsim|uuml|csup|Lscr|lscr|utri|smid|lpar|cups|smte|lozf|darr|Lopf|Uscr|solb|lopf|sopf|Sopf|lneq|uscr|spar|dArr|lnap|Darr|dash|Sqrt|LJcy|ljcy|lHar|dHar|Upsi|upsi|diam|lesg|djcy|DJcy|leqq|dopf|Dopf|dscr|Dscr|dscy|ldsh|ldca|squf|DScy|sscr|Sscr|dsol|lcub|late|star|Star|Uopf|Larr|lArr|larr|uopf|dtri|dzcy|sube|subE|Lang|lang|Kscr|kscr|Kopf|kopf|KJcy|kjcy|KHcy|khcy|DZcy|ecir|edot|eDot|Jscr|jscr|succ|Jopf|jopf|Edot|uHar|emsp|ensp|Iuml|iuml|eopf|isin|Iscr|iscr|Eopf|epar|sung|epsi|escr|sup1|sup2|sup3|Iota|iota|supe|supE|Iopf|iopf|IOcy|iocy|Escr|esim|Esim|imof|Uarr|QUOT|uArr|uarr|euml|IEcy|iecy|Idot|Euml|euro|excl|Hscr|hscr|Hopf|hopf|TScy|tscy|Tscr|hbar|tscr|flat|tbrk|fnof|hArr|harr|half|fopf|Fopf|tdot|gvnE|fork|trie|gtcc|fscr|Fscr|gdot|gsim|Gscr|gscr|Gopf|gopf|gneq|Gdot|tosa|gnap|Topf|topf|geqq|toea|GJcy|gjcy|tint|gesl|mid|Sfr|ggg|top|ges|gla|glE|glj|geq|gne|gEl|gel|gnE|Gcy|gcy|gap|Tfr|tfr|Tcy|tcy|Hat|Tau|Ffr|tau|Tab|hfr|Hfr|ffr|Fcy|fcy|icy|Icy|iff|ETH|eth|ifr|Ifr|Eta|eta|int|Int|Sup|sup|ucy|Ucy|Sum|sum|jcy|ENG|ufr|Ufr|eng|Jcy|jfr|els|ell|egs|Efr|efr|Jfr|uml|kcy|Kcy|Ecy|ecy|kfr|Kfr|lap|Sub|sub|lat|lcy|Lcy|leg|Dot|dot|lEg|leq|les|squ|div|die|lfr|Lfr|lgE|Dfr|dfr|Del|deg|Dcy|dcy|lne|lnE|sol|loz|smt|Cup|lrm|cup|lsh|Lsh|sim|shy|map|Map|mcy|Mcy|mfr|Mfr|mho|gfr|Gfr|sfr|cir|Chi|chi|nap|Cfr|vcy|Vcy|cfr|Scy|scy|ncy|Ncy|vee|Vee|Cap|cap|nfr|scE|sce|Nfr|nge|ngE|nGg|vfr|Vfr|ngt|bot|nGt|nis|niv|Rsh|rsh|nle|nlE|bne|Bfr|bfr|nLl|nlt|nLt|Bcy|bcy|not|Not|rlm|wfr|Wfr|npr|nsc|num|ocy|ast|Ocy|ofr|xfr|Xfr|Ofr|ogt|ohm|apE|olt|Rho|ape|rho|Rfr|rfr|ord|REG|ang|reg|orv|And|and|AMP|Rcy|amp|Afr|ycy|Ycy|yen|yfr|Yfr|rcy|par|pcy|Pcy|pfr|Pfr|phi|Phi|afr|Acy|acy|zcy|Zcy|piv|acE|acd|zfr|Zfr|pre|prE|psi|Psi|qfr|Qfr|zwj|Or|ge|Gg|gt|gg|el|oS|lt|Lt|LT|Re|lg|gl|eg|ne|Im|it|le|DD|wp|wr|nu|Nu|dd|lE|Sc|sc|pi|Pi|ee|af|ll|Ll|rx|gE|xi|pm|Xi|ic|pr|Pr|in|ni|mp|mu|ac|Mu|or|ap|Gt|GT|ii);|&(Aacute|Agrave|Atilde|Ccedil|Eacute|Egrave|Iacute|Igrave|Ntilde|Oacute|Ograve|Oslash|Otilde|Uacute|Ugrave|Yacute|aacute|agrave|atilde|brvbar|ccedil|curren|divide|eacute|egrave|frac12|frac14|frac34|iacute|igrave|iquest|middot|ntilde|oacute|ograve|oslash|otilde|plusmn|uacute|ugrave|yacute|AElig|Acirc|Aring|Ecirc|Icirc|Ocirc|THORN|Ucirc|acirc|acute|aelig|aring|cedil|ecirc|icirc|iexcl|laquo|micro|ocirc|pound|raquo|szlig|thorn|times|ucirc|Auml|COPY|Euml|Iuml|Ouml|QUOT|Uuml|auml|cent|copy|euml|iuml|macr|nbsp|ordf|ordm|ouml|para|quot|sect|sup1|sup2|sup3|uuml|yuml|AMP|ETH|REG|amp|deg|eth|not|reg|shy|uml|yen|GT|LT|gt|lt)(?!;)([=a-zA-Z0-9]?)|&#([0-9]+)(;?)|&#[xX]([a-fA-F0-9]+)(;?)|&([0-9a-zA-Z]+)/g; + var decodeMap = {'aacute':'\xE1','Aacute':'\xC1','abreve':'\u0103','Abreve':'\u0102','ac':'\u223E','acd':'\u223F','acE':'\u223E\u0333','acirc':'\xE2','Acirc':'\xC2','acute':'\xB4','acy':'\u0430','Acy':'\u0410','aelig':'\xE6','AElig':'\xC6','af':'\u2061','afr':'\uD835\uDD1E','Afr':'\uD835\uDD04','agrave':'\xE0','Agrave':'\xC0','alefsym':'\u2135','aleph':'\u2135','alpha':'\u03B1','Alpha':'\u0391','amacr':'\u0101','Amacr':'\u0100','amalg':'\u2A3F','amp':'&','AMP':'&','and':'\u2227','And':'\u2A53','andand':'\u2A55','andd':'\u2A5C','andslope':'\u2A58','andv':'\u2A5A','ang':'\u2220','ange':'\u29A4','angle':'\u2220','angmsd':'\u2221','angmsdaa':'\u29A8','angmsdab':'\u29A9','angmsdac':'\u29AA','angmsdad':'\u29AB','angmsdae':'\u29AC','angmsdaf':'\u29AD','angmsdag':'\u29AE','angmsdah':'\u29AF','angrt':'\u221F','angrtvb':'\u22BE','angrtvbd':'\u299D','angsph':'\u2222','angst':'\xC5','angzarr':'\u237C','aogon':'\u0105','Aogon':'\u0104','aopf':'\uD835\uDD52','Aopf':'\uD835\uDD38','ap':'\u2248','apacir':'\u2A6F','ape':'\u224A','apE':'\u2A70','apid':'\u224B','apos':'\'','ApplyFunction':'\u2061','approx':'\u2248','approxeq':'\u224A','aring':'\xE5','Aring':'\xC5','ascr':'\uD835\uDCB6','Ascr':'\uD835\uDC9C','Assign':'\u2254','ast':'*','asymp':'\u2248','asympeq':'\u224D','atilde':'\xE3','Atilde':'\xC3','auml':'\xE4','Auml':'\xC4','awconint':'\u2233','awint':'\u2A11','backcong':'\u224C','backepsilon':'\u03F6','backprime':'\u2035','backsim':'\u223D','backsimeq':'\u22CD','Backslash':'\u2216','Barv':'\u2AE7','barvee':'\u22BD','barwed':'\u2305','Barwed':'\u2306','barwedge':'\u2305','bbrk':'\u23B5','bbrktbrk':'\u23B6','bcong':'\u224C','bcy':'\u0431','Bcy':'\u0411','bdquo':'\u201E','becaus':'\u2235','because':'\u2235','Because':'\u2235','bemptyv':'\u29B0','bepsi':'\u03F6','bernou':'\u212C','Bernoullis':'\u212C','beta':'\u03B2','Beta':'\u0392','beth':'\u2136','between':'\u226C','bfr':'\uD835\uDD1F','Bfr':'\uD835\uDD05','bigcap':'\u22C2','bigcirc':'\u25EF','bigcup':'\u22C3','bigodot':'\u2A00','bigoplus':'\u2A01','bigotimes':'\u2A02','bigsqcup':'\u2A06','bigstar':'\u2605','bigtriangledown':'\u25BD','bigtriangleup':'\u25B3','biguplus':'\u2A04','bigvee':'\u22C1','bigwedge':'\u22C0','bkarow':'\u290D','blacklozenge':'\u29EB','blacksquare':'\u25AA','blacktriangle':'\u25B4','blacktriangledown':'\u25BE','blacktriangleleft':'\u25C2','blacktriangleright':'\u25B8','blank':'\u2423','blk12':'\u2592','blk14':'\u2591','blk34':'\u2593','block':'\u2588','bne':'=\u20E5','bnequiv':'\u2261\u20E5','bnot':'\u2310','bNot':'\u2AED','bopf':'\uD835\uDD53','Bopf':'\uD835\uDD39','bot':'\u22A5','bottom':'\u22A5','bowtie':'\u22C8','boxbox':'\u29C9','boxdl':'\u2510','boxdL':'\u2555','boxDl':'\u2556','boxDL':'\u2557','boxdr':'\u250C','boxdR':'\u2552','boxDr':'\u2553','boxDR':'\u2554','boxh':'\u2500','boxH':'\u2550','boxhd':'\u252C','boxhD':'\u2565','boxHd':'\u2564','boxHD':'\u2566','boxhu':'\u2534','boxhU':'\u2568','boxHu':'\u2567','boxHU':'\u2569','boxminus':'\u229F','boxplus':'\u229E','boxtimes':'\u22A0','boxul':'\u2518','boxuL':'\u255B','boxUl':'\u255C','boxUL':'\u255D','boxur':'\u2514','boxuR':'\u2558','boxUr':'\u2559','boxUR':'\u255A','boxv':'\u2502','boxV':'\u2551','boxvh':'\u253C','boxvH':'\u256A','boxVh':'\u256B','boxVH':'\u256C','boxvl':'\u2524','boxvL':'\u2561','boxVl':'\u2562','boxVL':'\u2563','boxvr':'\u251C','boxvR':'\u255E','boxVr':'\u255F','boxVR':'\u2560','bprime':'\u2035','breve':'\u02D8','Breve':'\u02D8','brvbar':'\xA6','bscr':'\uD835\uDCB7','Bscr':'\u212C','bsemi':'\u204F','bsim':'\u223D','bsime':'\u22CD','bsol':'\\','bsolb':'\u29C5','bsolhsub':'\u27C8','bull':'\u2022','bullet':'\u2022','bump':'\u224E','bumpe':'\u224F','bumpE':'\u2AAE','bumpeq':'\u224F','Bumpeq':'\u224E','cacute':'\u0107','Cacute':'\u0106','cap':'\u2229','Cap':'\u22D2','capand':'\u2A44','capbrcup':'\u2A49','capcap':'\u2A4B','capcup':'\u2A47','capdot':'\u2A40','CapitalDifferentialD':'\u2145','caps':'\u2229\uFE00','caret':'\u2041','caron':'\u02C7','Cayleys':'\u212D','ccaps':'\u2A4D','ccaron':'\u010D','Ccaron':'\u010C','ccedil':'\xE7','Ccedil':'\xC7','ccirc':'\u0109','Ccirc':'\u0108','Cconint':'\u2230','ccups':'\u2A4C','ccupssm':'\u2A50','cdot':'\u010B','Cdot':'\u010A','cedil':'\xB8','Cedilla':'\xB8','cemptyv':'\u29B2','cent':'\xA2','centerdot':'\xB7','CenterDot':'\xB7','cfr':'\uD835\uDD20','Cfr':'\u212D','chcy':'\u0447','CHcy':'\u0427','check':'\u2713','checkmark':'\u2713','chi':'\u03C7','Chi':'\u03A7','cir':'\u25CB','circ':'\u02C6','circeq':'\u2257','circlearrowleft':'\u21BA','circlearrowright':'\u21BB','circledast':'\u229B','circledcirc':'\u229A','circleddash':'\u229D','CircleDot':'\u2299','circledR':'\xAE','circledS':'\u24C8','CircleMinus':'\u2296','CirclePlus':'\u2295','CircleTimes':'\u2297','cire':'\u2257','cirE':'\u29C3','cirfnint':'\u2A10','cirmid':'\u2AEF','cirscir':'\u29C2','ClockwiseContourIntegral':'\u2232','CloseCurlyDoubleQuote':'\u201D','CloseCurlyQuote':'\u2019','clubs':'\u2663','clubsuit':'\u2663','colon':':','Colon':'\u2237','colone':'\u2254','Colone':'\u2A74','coloneq':'\u2254','comma':',','commat':'@','comp':'\u2201','compfn':'\u2218','complement':'\u2201','complexes':'\u2102','cong':'\u2245','congdot':'\u2A6D','Congruent':'\u2261','conint':'\u222E','Conint':'\u222F','ContourIntegral':'\u222E','copf':'\uD835\uDD54','Copf':'\u2102','coprod':'\u2210','Coproduct':'\u2210','copy':'\xA9','COPY':'\xA9','copysr':'\u2117','CounterClockwiseContourIntegral':'\u2233','crarr':'\u21B5','cross':'\u2717','Cross':'\u2A2F','cscr':'\uD835\uDCB8','Cscr':'\uD835\uDC9E','csub':'\u2ACF','csube':'\u2AD1','csup':'\u2AD0','csupe':'\u2AD2','ctdot':'\u22EF','cudarrl':'\u2938','cudarrr':'\u2935','cuepr':'\u22DE','cuesc':'\u22DF','cularr':'\u21B6','cularrp':'\u293D','cup':'\u222A','Cup':'\u22D3','cupbrcap':'\u2A48','cupcap':'\u2A46','CupCap':'\u224D','cupcup':'\u2A4A','cupdot':'\u228D','cupor':'\u2A45','cups':'\u222A\uFE00','curarr':'\u21B7','curarrm':'\u293C','curlyeqprec':'\u22DE','curlyeqsucc':'\u22DF','curlyvee':'\u22CE','curlywedge':'\u22CF','curren':'\xA4','curvearrowleft':'\u21B6','curvearrowright':'\u21B7','cuvee':'\u22CE','cuwed':'\u22CF','cwconint':'\u2232','cwint':'\u2231','cylcty':'\u232D','dagger':'\u2020','Dagger':'\u2021','daleth':'\u2138','darr':'\u2193','dArr':'\u21D3','Darr':'\u21A1','dash':'\u2010','dashv':'\u22A3','Dashv':'\u2AE4','dbkarow':'\u290F','dblac':'\u02DD','dcaron':'\u010F','Dcaron':'\u010E','dcy':'\u0434','Dcy':'\u0414','dd':'\u2146','DD':'\u2145','ddagger':'\u2021','ddarr':'\u21CA','DDotrahd':'\u2911','ddotseq':'\u2A77','deg':'\xB0','Del':'\u2207','delta':'\u03B4','Delta':'\u0394','demptyv':'\u29B1','dfisht':'\u297F','dfr':'\uD835\uDD21','Dfr':'\uD835\uDD07','dHar':'\u2965','dharl':'\u21C3','dharr':'\u21C2','DiacriticalAcute':'\xB4','DiacriticalDot':'\u02D9','DiacriticalDoubleAcute':'\u02DD','DiacriticalGrave':'`','DiacriticalTilde':'\u02DC','diam':'\u22C4','diamond':'\u22C4','Diamond':'\u22C4','diamondsuit':'\u2666','diams':'\u2666','die':'\xA8','DifferentialD':'\u2146','digamma':'\u03DD','disin':'\u22F2','div':'\xF7','divide':'\xF7','divideontimes':'\u22C7','divonx':'\u22C7','djcy':'\u0452','DJcy':'\u0402','dlcorn':'\u231E','dlcrop':'\u230D','dollar':'$','dopf':'\uD835\uDD55','Dopf':'\uD835\uDD3B','dot':'\u02D9','Dot':'\xA8','DotDot':'\u20DC','doteq':'\u2250','doteqdot':'\u2251','DotEqual':'\u2250','dotminus':'\u2238','dotplus':'\u2214','dotsquare':'\u22A1','doublebarwedge':'\u2306','DoubleContourIntegral':'\u222F','DoubleDot':'\xA8','DoubleDownArrow':'\u21D3','DoubleLeftArrow':'\u21D0','DoubleLeftRightArrow':'\u21D4','DoubleLeftTee':'\u2AE4','DoubleLongLeftArrow':'\u27F8','DoubleLongLeftRightArrow':'\u27FA','DoubleLongRightArrow':'\u27F9','DoubleRightArrow':'\u21D2','DoubleRightTee':'\u22A8','DoubleUpArrow':'\u21D1','DoubleUpDownArrow':'\u21D5','DoubleVerticalBar':'\u2225','downarrow':'\u2193','Downarrow':'\u21D3','DownArrow':'\u2193','DownArrowBar':'\u2913','DownArrowUpArrow':'\u21F5','DownBreve':'\u0311','downdownarrows':'\u21CA','downharpoonleft':'\u21C3','downharpoonright':'\u21C2','DownLeftRightVector':'\u2950','DownLeftTeeVector':'\u295E','DownLeftVector':'\u21BD','DownLeftVectorBar':'\u2956','DownRightTeeVector':'\u295F','DownRightVector':'\u21C1','DownRightVectorBar':'\u2957','DownTee':'\u22A4','DownTeeArrow':'\u21A7','drbkarow':'\u2910','drcorn':'\u231F','drcrop':'\u230C','dscr':'\uD835\uDCB9','Dscr':'\uD835\uDC9F','dscy':'\u0455','DScy':'\u0405','dsol':'\u29F6','dstrok':'\u0111','Dstrok':'\u0110','dtdot':'\u22F1','dtri':'\u25BF','dtrif':'\u25BE','duarr':'\u21F5','duhar':'\u296F','dwangle':'\u29A6','dzcy':'\u045F','DZcy':'\u040F','dzigrarr':'\u27FF','eacute':'\xE9','Eacute':'\xC9','easter':'\u2A6E','ecaron':'\u011B','Ecaron':'\u011A','ecir':'\u2256','ecirc':'\xEA','Ecirc':'\xCA','ecolon':'\u2255','ecy':'\u044D','Ecy':'\u042D','eDDot':'\u2A77','edot':'\u0117','eDot':'\u2251','Edot':'\u0116','ee':'\u2147','efDot':'\u2252','efr':'\uD835\uDD22','Efr':'\uD835\uDD08','eg':'\u2A9A','egrave':'\xE8','Egrave':'\xC8','egs':'\u2A96','egsdot':'\u2A98','el':'\u2A99','Element':'\u2208','elinters':'\u23E7','ell':'\u2113','els':'\u2A95','elsdot':'\u2A97','emacr':'\u0113','Emacr':'\u0112','empty':'\u2205','emptyset':'\u2205','EmptySmallSquare':'\u25FB','emptyv':'\u2205','EmptyVerySmallSquare':'\u25AB','emsp':'\u2003','emsp13':'\u2004','emsp14':'\u2005','eng':'\u014B','ENG':'\u014A','ensp':'\u2002','eogon':'\u0119','Eogon':'\u0118','eopf':'\uD835\uDD56','Eopf':'\uD835\uDD3C','epar':'\u22D5','eparsl':'\u29E3','eplus':'\u2A71','epsi':'\u03B5','epsilon':'\u03B5','Epsilon':'\u0395','epsiv':'\u03F5','eqcirc':'\u2256','eqcolon':'\u2255','eqsim':'\u2242','eqslantgtr':'\u2A96','eqslantless':'\u2A95','Equal':'\u2A75','equals':'=','EqualTilde':'\u2242','equest':'\u225F','Equilibrium':'\u21CC','equiv':'\u2261','equivDD':'\u2A78','eqvparsl':'\u29E5','erarr':'\u2971','erDot':'\u2253','escr':'\u212F','Escr':'\u2130','esdot':'\u2250','esim':'\u2242','Esim':'\u2A73','eta':'\u03B7','Eta':'\u0397','eth':'\xF0','ETH':'\xD0','euml':'\xEB','Euml':'\xCB','euro':'\u20AC','excl':'!','exist':'\u2203','Exists':'\u2203','expectation':'\u2130','exponentiale':'\u2147','ExponentialE':'\u2147','fallingdotseq':'\u2252','fcy':'\u0444','Fcy':'\u0424','female':'\u2640','ffilig':'\uFB03','fflig':'\uFB00','ffllig':'\uFB04','ffr':'\uD835\uDD23','Ffr':'\uD835\uDD09','filig':'\uFB01','FilledSmallSquare':'\u25FC','FilledVerySmallSquare':'\u25AA','fjlig':'fj','flat':'\u266D','fllig':'\uFB02','fltns':'\u25B1','fnof':'\u0192','fopf':'\uD835\uDD57','Fopf':'\uD835\uDD3D','forall':'\u2200','ForAll':'\u2200','fork':'\u22D4','forkv':'\u2AD9','Fouriertrf':'\u2131','fpartint':'\u2A0D','frac12':'\xBD','frac13':'\u2153','frac14':'\xBC','frac15':'\u2155','frac16':'\u2159','frac18':'\u215B','frac23':'\u2154','frac25':'\u2156','frac34':'\xBE','frac35':'\u2157','frac38':'\u215C','frac45':'\u2158','frac56':'\u215A','frac58':'\u215D','frac78':'\u215E','frasl':'\u2044','frown':'\u2322','fscr':'\uD835\uDCBB','Fscr':'\u2131','gacute':'\u01F5','gamma':'\u03B3','Gamma':'\u0393','gammad':'\u03DD','Gammad':'\u03DC','gap':'\u2A86','gbreve':'\u011F','Gbreve':'\u011E','Gcedil':'\u0122','gcirc':'\u011D','Gcirc':'\u011C','gcy':'\u0433','Gcy':'\u0413','gdot':'\u0121','Gdot':'\u0120','ge':'\u2265','gE':'\u2267','gel':'\u22DB','gEl':'\u2A8C','geq':'\u2265','geqq':'\u2267','geqslant':'\u2A7E','ges':'\u2A7E','gescc':'\u2AA9','gesdot':'\u2A80','gesdoto':'\u2A82','gesdotol':'\u2A84','gesl':'\u22DB\uFE00','gesles':'\u2A94','gfr':'\uD835\uDD24','Gfr':'\uD835\uDD0A','gg':'\u226B','Gg':'\u22D9','ggg':'\u22D9','gimel':'\u2137','gjcy':'\u0453','GJcy':'\u0403','gl':'\u2277','gla':'\u2AA5','glE':'\u2A92','glj':'\u2AA4','gnap':'\u2A8A','gnapprox':'\u2A8A','gne':'\u2A88','gnE':'\u2269','gneq':'\u2A88','gneqq':'\u2269','gnsim':'\u22E7','gopf':'\uD835\uDD58','Gopf':'\uD835\uDD3E','grave':'`','GreaterEqual':'\u2265','GreaterEqualLess':'\u22DB','GreaterFullEqual':'\u2267','GreaterGreater':'\u2AA2','GreaterLess':'\u2277','GreaterSlantEqual':'\u2A7E','GreaterTilde':'\u2273','gscr':'\u210A','Gscr':'\uD835\uDCA2','gsim':'\u2273','gsime':'\u2A8E','gsiml':'\u2A90','gt':'>','Gt':'\u226B','GT':'>','gtcc':'\u2AA7','gtcir':'\u2A7A','gtdot':'\u22D7','gtlPar':'\u2995','gtquest':'\u2A7C','gtrapprox':'\u2A86','gtrarr':'\u2978','gtrdot':'\u22D7','gtreqless':'\u22DB','gtreqqless':'\u2A8C','gtrless':'\u2277','gtrsim':'\u2273','gvertneqq':'\u2269\uFE00','gvnE':'\u2269\uFE00','Hacek':'\u02C7','hairsp':'\u200A','half':'\xBD','hamilt':'\u210B','hardcy':'\u044A','HARDcy':'\u042A','harr':'\u2194','hArr':'\u21D4','harrcir':'\u2948','harrw':'\u21AD','Hat':'^','hbar':'\u210F','hcirc':'\u0125','Hcirc':'\u0124','hearts':'\u2665','heartsuit':'\u2665','hellip':'\u2026','hercon':'\u22B9','hfr':'\uD835\uDD25','Hfr':'\u210C','HilbertSpace':'\u210B','hksearow':'\u2925','hkswarow':'\u2926','hoarr':'\u21FF','homtht':'\u223B','hookleftarrow':'\u21A9','hookrightarrow':'\u21AA','hopf':'\uD835\uDD59','Hopf':'\u210D','horbar':'\u2015','HorizontalLine':'\u2500','hscr':'\uD835\uDCBD','Hscr':'\u210B','hslash':'\u210F','hstrok':'\u0127','Hstrok':'\u0126','HumpDownHump':'\u224E','HumpEqual':'\u224F','hybull':'\u2043','hyphen':'\u2010','iacute':'\xED','Iacute':'\xCD','ic':'\u2063','icirc':'\xEE','Icirc':'\xCE','icy':'\u0438','Icy':'\u0418','Idot':'\u0130','iecy':'\u0435','IEcy':'\u0415','iexcl':'\xA1','iff':'\u21D4','ifr':'\uD835\uDD26','Ifr':'\u2111','igrave':'\xEC','Igrave':'\xCC','ii':'\u2148','iiiint':'\u2A0C','iiint':'\u222D','iinfin':'\u29DC','iiota':'\u2129','ijlig':'\u0133','IJlig':'\u0132','Im':'\u2111','imacr':'\u012B','Imacr':'\u012A','image':'\u2111','ImaginaryI':'\u2148','imagline':'\u2110','imagpart':'\u2111','imath':'\u0131','imof':'\u22B7','imped':'\u01B5','Implies':'\u21D2','in':'\u2208','incare':'\u2105','infin':'\u221E','infintie':'\u29DD','inodot':'\u0131','int':'\u222B','Int':'\u222C','intcal':'\u22BA','integers':'\u2124','Integral':'\u222B','intercal':'\u22BA','Intersection':'\u22C2','intlarhk':'\u2A17','intprod':'\u2A3C','InvisibleComma':'\u2063','InvisibleTimes':'\u2062','iocy':'\u0451','IOcy':'\u0401','iogon':'\u012F','Iogon':'\u012E','iopf':'\uD835\uDD5A','Iopf':'\uD835\uDD40','iota':'\u03B9','Iota':'\u0399','iprod':'\u2A3C','iquest':'\xBF','iscr':'\uD835\uDCBE','Iscr':'\u2110','isin':'\u2208','isindot':'\u22F5','isinE':'\u22F9','isins':'\u22F4','isinsv':'\u22F3','isinv':'\u2208','it':'\u2062','itilde':'\u0129','Itilde':'\u0128','iukcy':'\u0456','Iukcy':'\u0406','iuml':'\xEF','Iuml':'\xCF','jcirc':'\u0135','Jcirc':'\u0134','jcy':'\u0439','Jcy':'\u0419','jfr':'\uD835\uDD27','Jfr':'\uD835\uDD0D','jmath':'\u0237','jopf':'\uD835\uDD5B','Jopf':'\uD835\uDD41','jscr':'\uD835\uDCBF','Jscr':'\uD835\uDCA5','jsercy':'\u0458','Jsercy':'\u0408','jukcy':'\u0454','Jukcy':'\u0404','kappa':'\u03BA','Kappa':'\u039A','kappav':'\u03F0','kcedil':'\u0137','Kcedil':'\u0136','kcy':'\u043A','Kcy':'\u041A','kfr':'\uD835\uDD28','Kfr':'\uD835\uDD0E','kgreen':'\u0138','khcy':'\u0445','KHcy':'\u0425','kjcy':'\u045C','KJcy':'\u040C','kopf':'\uD835\uDD5C','Kopf':'\uD835\uDD42','kscr':'\uD835\uDCC0','Kscr':'\uD835\uDCA6','lAarr':'\u21DA','lacute':'\u013A','Lacute':'\u0139','laemptyv':'\u29B4','lagran':'\u2112','lambda':'\u03BB','Lambda':'\u039B','lang':'\u27E8','Lang':'\u27EA','langd':'\u2991','langle':'\u27E8','lap':'\u2A85','Laplacetrf':'\u2112','laquo':'\xAB','larr':'\u2190','lArr':'\u21D0','Larr':'\u219E','larrb':'\u21E4','larrbfs':'\u291F','larrfs':'\u291D','larrhk':'\u21A9','larrlp':'\u21AB','larrpl':'\u2939','larrsim':'\u2973','larrtl':'\u21A2','lat':'\u2AAB','latail':'\u2919','lAtail':'\u291B','late':'\u2AAD','lates':'\u2AAD\uFE00','lbarr':'\u290C','lBarr':'\u290E','lbbrk':'\u2772','lbrace':'{','lbrack':'[','lbrke':'\u298B','lbrksld':'\u298F','lbrkslu':'\u298D','lcaron':'\u013E','Lcaron':'\u013D','lcedil':'\u013C','Lcedil':'\u013B','lceil':'\u2308','lcub':'{','lcy':'\u043B','Lcy':'\u041B','ldca':'\u2936','ldquo':'\u201C','ldquor':'\u201E','ldrdhar':'\u2967','ldrushar':'\u294B','ldsh':'\u21B2','le':'\u2264','lE':'\u2266','LeftAngleBracket':'\u27E8','leftarrow':'\u2190','Leftarrow':'\u21D0','LeftArrow':'\u2190','LeftArrowBar':'\u21E4','LeftArrowRightArrow':'\u21C6','leftarrowtail':'\u21A2','LeftCeiling':'\u2308','LeftDoubleBracket':'\u27E6','LeftDownTeeVector':'\u2961','LeftDownVector':'\u21C3','LeftDownVectorBar':'\u2959','LeftFloor':'\u230A','leftharpoondown':'\u21BD','leftharpoonup':'\u21BC','leftleftarrows':'\u21C7','leftrightarrow':'\u2194','Leftrightarrow':'\u21D4','LeftRightArrow':'\u2194','leftrightarrows':'\u21C6','leftrightharpoons':'\u21CB','leftrightsquigarrow':'\u21AD','LeftRightVector':'\u294E','LeftTee':'\u22A3','LeftTeeArrow':'\u21A4','LeftTeeVector':'\u295A','leftthreetimes':'\u22CB','LeftTriangle':'\u22B2','LeftTriangleBar':'\u29CF','LeftTriangleEqual':'\u22B4','LeftUpDownVector':'\u2951','LeftUpTeeVector':'\u2960','LeftUpVector':'\u21BF','LeftUpVectorBar':'\u2958','LeftVector':'\u21BC','LeftVectorBar':'\u2952','leg':'\u22DA','lEg':'\u2A8B','leq':'\u2264','leqq':'\u2266','leqslant':'\u2A7D','les':'\u2A7D','lescc':'\u2AA8','lesdot':'\u2A7F','lesdoto':'\u2A81','lesdotor':'\u2A83','lesg':'\u22DA\uFE00','lesges':'\u2A93','lessapprox':'\u2A85','lessdot':'\u22D6','lesseqgtr':'\u22DA','lesseqqgtr':'\u2A8B','LessEqualGreater':'\u22DA','LessFullEqual':'\u2266','LessGreater':'\u2276','lessgtr':'\u2276','LessLess':'\u2AA1','lesssim':'\u2272','LessSlantEqual':'\u2A7D','LessTilde':'\u2272','lfisht':'\u297C','lfloor':'\u230A','lfr':'\uD835\uDD29','Lfr':'\uD835\uDD0F','lg':'\u2276','lgE':'\u2A91','lHar':'\u2962','lhard':'\u21BD','lharu':'\u21BC','lharul':'\u296A','lhblk':'\u2584','ljcy':'\u0459','LJcy':'\u0409','ll':'\u226A','Ll':'\u22D8','llarr':'\u21C7','llcorner':'\u231E','Lleftarrow':'\u21DA','llhard':'\u296B','lltri':'\u25FA','lmidot':'\u0140','Lmidot':'\u013F','lmoust':'\u23B0','lmoustache':'\u23B0','lnap':'\u2A89','lnapprox':'\u2A89','lne':'\u2A87','lnE':'\u2268','lneq':'\u2A87','lneqq':'\u2268','lnsim':'\u22E6','loang':'\u27EC','loarr':'\u21FD','lobrk':'\u27E6','longleftarrow':'\u27F5','Longleftarrow':'\u27F8','LongLeftArrow':'\u27F5','longleftrightarrow':'\u27F7','Longleftrightarrow':'\u27FA','LongLeftRightArrow':'\u27F7','longmapsto':'\u27FC','longrightarrow':'\u27F6','Longrightarrow':'\u27F9','LongRightArrow':'\u27F6','looparrowleft':'\u21AB','looparrowright':'\u21AC','lopar':'\u2985','lopf':'\uD835\uDD5D','Lopf':'\uD835\uDD43','loplus':'\u2A2D','lotimes':'\u2A34','lowast':'\u2217','lowbar':'_','LowerLeftArrow':'\u2199','LowerRightArrow':'\u2198','loz':'\u25CA','lozenge':'\u25CA','lozf':'\u29EB','lpar':'(','lparlt':'\u2993','lrarr':'\u21C6','lrcorner':'\u231F','lrhar':'\u21CB','lrhard':'\u296D','lrm':'\u200E','lrtri':'\u22BF','lsaquo':'\u2039','lscr':'\uD835\uDCC1','Lscr':'\u2112','lsh':'\u21B0','Lsh':'\u21B0','lsim':'\u2272','lsime':'\u2A8D','lsimg':'\u2A8F','lsqb':'[','lsquo':'\u2018','lsquor':'\u201A','lstrok':'\u0142','Lstrok':'\u0141','lt':'<','Lt':'\u226A','LT':'<','ltcc':'\u2AA6','ltcir':'\u2A79','ltdot':'\u22D6','lthree':'\u22CB','ltimes':'\u22C9','ltlarr':'\u2976','ltquest':'\u2A7B','ltri':'\u25C3','ltrie':'\u22B4','ltrif':'\u25C2','ltrPar':'\u2996','lurdshar':'\u294A','luruhar':'\u2966','lvertneqq':'\u2268\uFE00','lvnE':'\u2268\uFE00','macr':'\xAF','male':'\u2642','malt':'\u2720','maltese':'\u2720','map':'\u21A6','Map':'\u2905','mapsto':'\u21A6','mapstodown':'\u21A7','mapstoleft':'\u21A4','mapstoup':'\u21A5','marker':'\u25AE','mcomma':'\u2A29','mcy':'\u043C','Mcy':'\u041C','mdash':'\u2014','mDDot':'\u223A','measuredangle':'\u2221','MediumSpace':'\u205F','Mellintrf':'\u2133','mfr':'\uD835\uDD2A','Mfr':'\uD835\uDD10','mho':'\u2127','micro':'\xB5','mid':'\u2223','midast':'*','midcir':'\u2AF0','middot':'\xB7','minus':'\u2212','minusb':'\u229F','minusd':'\u2238','minusdu':'\u2A2A','MinusPlus':'\u2213','mlcp':'\u2ADB','mldr':'\u2026','mnplus':'\u2213','models':'\u22A7','mopf':'\uD835\uDD5E','Mopf':'\uD835\uDD44','mp':'\u2213','mscr':'\uD835\uDCC2','Mscr':'\u2133','mstpos':'\u223E','mu':'\u03BC','Mu':'\u039C','multimap':'\u22B8','mumap':'\u22B8','nabla':'\u2207','nacute':'\u0144','Nacute':'\u0143','nang':'\u2220\u20D2','nap':'\u2249','napE':'\u2A70\u0338','napid':'\u224B\u0338','napos':'\u0149','napprox':'\u2249','natur':'\u266E','natural':'\u266E','naturals':'\u2115','nbsp':'\xA0','nbump':'\u224E\u0338','nbumpe':'\u224F\u0338','ncap':'\u2A43','ncaron':'\u0148','Ncaron':'\u0147','ncedil':'\u0146','Ncedil':'\u0145','ncong':'\u2247','ncongdot':'\u2A6D\u0338','ncup':'\u2A42','ncy':'\u043D','Ncy':'\u041D','ndash':'\u2013','ne':'\u2260','nearhk':'\u2924','nearr':'\u2197','neArr':'\u21D7','nearrow':'\u2197','nedot':'\u2250\u0338','NegativeMediumSpace':'\u200B','NegativeThickSpace':'\u200B','NegativeThinSpace':'\u200B','NegativeVeryThinSpace':'\u200B','nequiv':'\u2262','nesear':'\u2928','nesim':'\u2242\u0338','NestedGreaterGreater':'\u226B','NestedLessLess':'\u226A','NewLine':'\n','nexist':'\u2204','nexists':'\u2204','nfr':'\uD835\uDD2B','Nfr':'\uD835\uDD11','nge':'\u2271','ngE':'\u2267\u0338','ngeq':'\u2271','ngeqq':'\u2267\u0338','ngeqslant':'\u2A7E\u0338','nges':'\u2A7E\u0338','nGg':'\u22D9\u0338','ngsim':'\u2275','ngt':'\u226F','nGt':'\u226B\u20D2','ngtr':'\u226F','nGtv':'\u226B\u0338','nharr':'\u21AE','nhArr':'\u21CE','nhpar':'\u2AF2','ni':'\u220B','nis':'\u22FC','nisd':'\u22FA','niv':'\u220B','njcy':'\u045A','NJcy':'\u040A','nlarr':'\u219A','nlArr':'\u21CD','nldr':'\u2025','nle':'\u2270','nlE':'\u2266\u0338','nleftarrow':'\u219A','nLeftarrow':'\u21CD','nleftrightarrow':'\u21AE','nLeftrightarrow':'\u21CE','nleq':'\u2270','nleqq':'\u2266\u0338','nleqslant':'\u2A7D\u0338','nles':'\u2A7D\u0338','nless':'\u226E','nLl':'\u22D8\u0338','nlsim':'\u2274','nlt':'\u226E','nLt':'\u226A\u20D2','nltri':'\u22EA','nltrie':'\u22EC','nLtv':'\u226A\u0338','nmid':'\u2224','NoBreak':'\u2060','NonBreakingSpace':'\xA0','nopf':'\uD835\uDD5F','Nopf':'\u2115','not':'\xAC','Not':'\u2AEC','NotCongruent':'\u2262','NotCupCap':'\u226D','NotDoubleVerticalBar':'\u2226','NotElement':'\u2209','NotEqual':'\u2260','NotEqualTilde':'\u2242\u0338','NotExists':'\u2204','NotGreater':'\u226F','NotGreaterEqual':'\u2271','NotGreaterFullEqual':'\u2267\u0338','NotGreaterGreater':'\u226B\u0338','NotGreaterLess':'\u2279','NotGreaterSlantEqual':'\u2A7E\u0338','NotGreaterTilde':'\u2275','NotHumpDownHump':'\u224E\u0338','NotHumpEqual':'\u224F\u0338','notin':'\u2209','notindot':'\u22F5\u0338','notinE':'\u22F9\u0338','notinva':'\u2209','notinvb':'\u22F7','notinvc':'\u22F6','NotLeftTriangle':'\u22EA','NotLeftTriangleBar':'\u29CF\u0338','NotLeftTriangleEqual':'\u22EC','NotLess':'\u226E','NotLessEqual':'\u2270','NotLessGreater':'\u2278','NotLessLess':'\u226A\u0338','NotLessSlantEqual':'\u2A7D\u0338','NotLessTilde':'\u2274','NotNestedGreaterGreater':'\u2AA2\u0338','NotNestedLessLess':'\u2AA1\u0338','notni':'\u220C','notniva':'\u220C','notnivb':'\u22FE','notnivc':'\u22FD','NotPrecedes':'\u2280','NotPrecedesEqual':'\u2AAF\u0338','NotPrecedesSlantEqual':'\u22E0','NotReverseElement':'\u220C','NotRightTriangle':'\u22EB','NotRightTriangleBar':'\u29D0\u0338','NotRightTriangleEqual':'\u22ED','NotSquareSubset':'\u228F\u0338','NotSquareSubsetEqual':'\u22E2','NotSquareSuperset':'\u2290\u0338','NotSquareSupersetEqual':'\u22E3','NotSubset':'\u2282\u20D2','NotSubsetEqual':'\u2288','NotSucceeds':'\u2281','NotSucceedsEqual':'\u2AB0\u0338','NotSucceedsSlantEqual':'\u22E1','NotSucceedsTilde':'\u227F\u0338','NotSuperset':'\u2283\u20D2','NotSupersetEqual':'\u2289','NotTilde':'\u2241','NotTildeEqual':'\u2244','NotTildeFullEqual':'\u2247','NotTildeTilde':'\u2249','NotVerticalBar':'\u2224','npar':'\u2226','nparallel':'\u2226','nparsl':'\u2AFD\u20E5','npart':'\u2202\u0338','npolint':'\u2A14','npr':'\u2280','nprcue':'\u22E0','npre':'\u2AAF\u0338','nprec':'\u2280','npreceq':'\u2AAF\u0338','nrarr':'\u219B','nrArr':'\u21CF','nrarrc':'\u2933\u0338','nrarrw':'\u219D\u0338','nrightarrow':'\u219B','nRightarrow':'\u21CF','nrtri':'\u22EB','nrtrie':'\u22ED','nsc':'\u2281','nsccue':'\u22E1','nsce':'\u2AB0\u0338','nscr':'\uD835\uDCC3','Nscr':'\uD835\uDCA9','nshortmid':'\u2224','nshortparallel':'\u2226','nsim':'\u2241','nsime':'\u2244','nsimeq':'\u2244','nsmid':'\u2224','nspar':'\u2226','nsqsube':'\u22E2','nsqsupe':'\u22E3','nsub':'\u2284','nsube':'\u2288','nsubE':'\u2AC5\u0338','nsubset':'\u2282\u20D2','nsubseteq':'\u2288','nsubseteqq':'\u2AC5\u0338','nsucc':'\u2281','nsucceq':'\u2AB0\u0338','nsup':'\u2285','nsupe':'\u2289','nsupE':'\u2AC6\u0338','nsupset':'\u2283\u20D2','nsupseteq':'\u2289','nsupseteqq':'\u2AC6\u0338','ntgl':'\u2279','ntilde':'\xF1','Ntilde':'\xD1','ntlg':'\u2278','ntriangleleft':'\u22EA','ntrianglelefteq':'\u22EC','ntriangleright':'\u22EB','ntrianglerighteq':'\u22ED','nu':'\u03BD','Nu':'\u039D','num':'#','numero':'\u2116','numsp':'\u2007','nvap':'\u224D\u20D2','nvdash':'\u22AC','nvDash':'\u22AD','nVdash':'\u22AE','nVDash':'\u22AF','nvge':'\u2265\u20D2','nvgt':'>\u20D2','nvHarr':'\u2904','nvinfin':'\u29DE','nvlArr':'\u2902','nvle':'\u2264\u20D2','nvlt':'<\u20D2','nvltrie':'\u22B4\u20D2','nvrArr':'\u2903','nvrtrie':'\u22B5\u20D2','nvsim':'\u223C\u20D2','nwarhk':'\u2923','nwarr':'\u2196','nwArr':'\u21D6','nwarrow':'\u2196','nwnear':'\u2927','oacute':'\xF3','Oacute':'\xD3','oast':'\u229B','ocir':'\u229A','ocirc':'\xF4','Ocirc':'\xD4','ocy':'\u043E','Ocy':'\u041E','odash':'\u229D','odblac':'\u0151','Odblac':'\u0150','odiv':'\u2A38','odot':'\u2299','odsold':'\u29BC','oelig':'\u0153','OElig':'\u0152','ofcir':'\u29BF','ofr':'\uD835\uDD2C','Ofr':'\uD835\uDD12','ogon':'\u02DB','ograve':'\xF2','Ograve':'\xD2','ogt':'\u29C1','ohbar':'\u29B5','ohm':'\u03A9','oint':'\u222E','olarr':'\u21BA','olcir':'\u29BE','olcross':'\u29BB','oline':'\u203E','olt':'\u29C0','omacr':'\u014D','Omacr':'\u014C','omega':'\u03C9','Omega':'\u03A9','omicron':'\u03BF','Omicron':'\u039F','omid':'\u29B6','ominus':'\u2296','oopf':'\uD835\uDD60','Oopf':'\uD835\uDD46','opar':'\u29B7','OpenCurlyDoubleQuote':'\u201C','OpenCurlyQuote':'\u2018','operp':'\u29B9','oplus':'\u2295','or':'\u2228','Or':'\u2A54','orarr':'\u21BB','ord':'\u2A5D','order':'\u2134','orderof':'\u2134','ordf':'\xAA','ordm':'\xBA','origof':'\u22B6','oror':'\u2A56','orslope':'\u2A57','orv':'\u2A5B','oS':'\u24C8','oscr':'\u2134','Oscr':'\uD835\uDCAA','oslash':'\xF8','Oslash':'\xD8','osol':'\u2298','otilde':'\xF5','Otilde':'\xD5','otimes':'\u2297','Otimes':'\u2A37','otimesas':'\u2A36','ouml':'\xF6','Ouml':'\xD6','ovbar':'\u233D','OverBar':'\u203E','OverBrace':'\u23DE','OverBracket':'\u23B4','OverParenthesis':'\u23DC','par':'\u2225','para':'\xB6','parallel':'\u2225','parsim':'\u2AF3','parsl':'\u2AFD','part':'\u2202','PartialD':'\u2202','pcy':'\u043F','Pcy':'\u041F','percnt':'%','period':'.','permil':'\u2030','perp':'\u22A5','pertenk':'\u2031','pfr':'\uD835\uDD2D','Pfr':'\uD835\uDD13','phi':'\u03C6','Phi':'\u03A6','phiv':'\u03D5','phmmat':'\u2133','phone':'\u260E','pi':'\u03C0','Pi':'\u03A0','pitchfork':'\u22D4','piv':'\u03D6','planck':'\u210F','planckh':'\u210E','plankv':'\u210F','plus':'+','plusacir':'\u2A23','plusb':'\u229E','pluscir':'\u2A22','plusdo':'\u2214','plusdu':'\u2A25','pluse':'\u2A72','PlusMinus':'\xB1','plusmn':'\xB1','plussim':'\u2A26','plustwo':'\u2A27','pm':'\xB1','Poincareplane':'\u210C','pointint':'\u2A15','popf':'\uD835\uDD61','Popf':'\u2119','pound':'\xA3','pr':'\u227A','Pr':'\u2ABB','prap':'\u2AB7','prcue':'\u227C','pre':'\u2AAF','prE':'\u2AB3','prec':'\u227A','precapprox':'\u2AB7','preccurlyeq':'\u227C','Precedes':'\u227A','PrecedesEqual':'\u2AAF','PrecedesSlantEqual':'\u227C','PrecedesTilde':'\u227E','preceq':'\u2AAF','precnapprox':'\u2AB9','precneqq':'\u2AB5','precnsim':'\u22E8','precsim':'\u227E','prime':'\u2032','Prime':'\u2033','primes':'\u2119','prnap':'\u2AB9','prnE':'\u2AB5','prnsim':'\u22E8','prod':'\u220F','Product':'\u220F','profalar':'\u232E','profline':'\u2312','profsurf':'\u2313','prop':'\u221D','Proportion':'\u2237','Proportional':'\u221D','propto':'\u221D','prsim':'\u227E','prurel':'\u22B0','pscr':'\uD835\uDCC5','Pscr':'\uD835\uDCAB','psi':'\u03C8','Psi':'\u03A8','puncsp':'\u2008','qfr':'\uD835\uDD2E','Qfr':'\uD835\uDD14','qint':'\u2A0C','qopf':'\uD835\uDD62','Qopf':'\u211A','qprime':'\u2057','qscr':'\uD835\uDCC6','Qscr':'\uD835\uDCAC','quaternions':'\u210D','quatint':'\u2A16','quest':'?','questeq':'\u225F','quot':'"','QUOT':'"','rAarr':'\u21DB','race':'\u223D\u0331','racute':'\u0155','Racute':'\u0154','radic':'\u221A','raemptyv':'\u29B3','rang':'\u27E9','Rang':'\u27EB','rangd':'\u2992','range':'\u29A5','rangle':'\u27E9','raquo':'\xBB','rarr':'\u2192','rArr':'\u21D2','Rarr':'\u21A0','rarrap':'\u2975','rarrb':'\u21E5','rarrbfs':'\u2920','rarrc':'\u2933','rarrfs':'\u291E','rarrhk':'\u21AA','rarrlp':'\u21AC','rarrpl':'\u2945','rarrsim':'\u2974','rarrtl':'\u21A3','Rarrtl':'\u2916','rarrw':'\u219D','ratail':'\u291A','rAtail':'\u291C','ratio':'\u2236','rationals':'\u211A','rbarr':'\u290D','rBarr':'\u290F','RBarr':'\u2910','rbbrk':'\u2773','rbrace':'}','rbrack':']','rbrke':'\u298C','rbrksld':'\u298E','rbrkslu':'\u2990','rcaron':'\u0159','Rcaron':'\u0158','rcedil':'\u0157','Rcedil':'\u0156','rceil':'\u2309','rcub':'}','rcy':'\u0440','Rcy':'\u0420','rdca':'\u2937','rdldhar':'\u2969','rdquo':'\u201D','rdquor':'\u201D','rdsh':'\u21B3','Re':'\u211C','real':'\u211C','realine':'\u211B','realpart':'\u211C','reals':'\u211D','rect':'\u25AD','reg':'\xAE','REG':'\xAE','ReverseElement':'\u220B','ReverseEquilibrium':'\u21CB','ReverseUpEquilibrium':'\u296F','rfisht':'\u297D','rfloor':'\u230B','rfr':'\uD835\uDD2F','Rfr':'\u211C','rHar':'\u2964','rhard':'\u21C1','rharu':'\u21C0','rharul':'\u296C','rho':'\u03C1','Rho':'\u03A1','rhov':'\u03F1','RightAngleBracket':'\u27E9','rightarrow':'\u2192','Rightarrow':'\u21D2','RightArrow':'\u2192','RightArrowBar':'\u21E5','RightArrowLeftArrow':'\u21C4','rightarrowtail':'\u21A3','RightCeiling':'\u2309','RightDoubleBracket':'\u27E7','RightDownTeeVector':'\u295D','RightDownVector':'\u21C2','RightDownVectorBar':'\u2955','RightFloor':'\u230B','rightharpoondown':'\u21C1','rightharpoonup':'\u21C0','rightleftarrows':'\u21C4','rightleftharpoons':'\u21CC','rightrightarrows':'\u21C9','rightsquigarrow':'\u219D','RightTee':'\u22A2','RightTeeArrow':'\u21A6','RightTeeVector':'\u295B','rightthreetimes':'\u22CC','RightTriangle':'\u22B3','RightTriangleBar':'\u29D0','RightTriangleEqual':'\u22B5','RightUpDownVector':'\u294F','RightUpTeeVector':'\u295C','RightUpVector':'\u21BE','RightUpVectorBar':'\u2954','RightVector':'\u21C0','RightVectorBar':'\u2953','ring':'\u02DA','risingdotseq':'\u2253','rlarr':'\u21C4','rlhar':'\u21CC','rlm':'\u200F','rmoust':'\u23B1','rmoustache':'\u23B1','rnmid':'\u2AEE','roang':'\u27ED','roarr':'\u21FE','robrk':'\u27E7','ropar':'\u2986','ropf':'\uD835\uDD63','Ropf':'\u211D','roplus':'\u2A2E','rotimes':'\u2A35','RoundImplies':'\u2970','rpar':')','rpargt':'\u2994','rppolint':'\u2A12','rrarr':'\u21C9','Rrightarrow':'\u21DB','rsaquo':'\u203A','rscr':'\uD835\uDCC7','Rscr':'\u211B','rsh':'\u21B1','Rsh':'\u21B1','rsqb':']','rsquo':'\u2019','rsquor':'\u2019','rthree':'\u22CC','rtimes':'\u22CA','rtri':'\u25B9','rtrie':'\u22B5','rtrif':'\u25B8','rtriltri':'\u29CE','RuleDelayed':'\u29F4','ruluhar':'\u2968','rx':'\u211E','sacute':'\u015B','Sacute':'\u015A','sbquo':'\u201A','sc':'\u227B','Sc':'\u2ABC','scap':'\u2AB8','scaron':'\u0161','Scaron':'\u0160','sccue':'\u227D','sce':'\u2AB0','scE':'\u2AB4','scedil':'\u015F','Scedil':'\u015E','scirc':'\u015D','Scirc':'\u015C','scnap':'\u2ABA','scnE':'\u2AB6','scnsim':'\u22E9','scpolint':'\u2A13','scsim':'\u227F','scy':'\u0441','Scy':'\u0421','sdot':'\u22C5','sdotb':'\u22A1','sdote':'\u2A66','searhk':'\u2925','searr':'\u2198','seArr':'\u21D8','searrow':'\u2198','sect':'\xA7','semi':';','seswar':'\u2929','setminus':'\u2216','setmn':'\u2216','sext':'\u2736','sfr':'\uD835\uDD30','Sfr':'\uD835\uDD16','sfrown':'\u2322','sharp':'\u266F','shchcy':'\u0449','SHCHcy':'\u0429','shcy':'\u0448','SHcy':'\u0428','ShortDownArrow':'\u2193','ShortLeftArrow':'\u2190','shortmid':'\u2223','shortparallel':'\u2225','ShortRightArrow':'\u2192','ShortUpArrow':'\u2191','shy':'\xAD','sigma':'\u03C3','Sigma':'\u03A3','sigmaf':'\u03C2','sigmav':'\u03C2','sim':'\u223C','simdot':'\u2A6A','sime':'\u2243','simeq':'\u2243','simg':'\u2A9E','simgE':'\u2AA0','siml':'\u2A9D','simlE':'\u2A9F','simne':'\u2246','simplus':'\u2A24','simrarr':'\u2972','slarr':'\u2190','SmallCircle':'\u2218','smallsetminus':'\u2216','smashp':'\u2A33','smeparsl':'\u29E4','smid':'\u2223','smile':'\u2323','smt':'\u2AAA','smte':'\u2AAC','smtes':'\u2AAC\uFE00','softcy':'\u044C','SOFTcy':'\u042C','sol':'/','solb':'\u29C4','solbar':'\u233F','sopf':'\uD835\uDD64','Sopf':'\uD835\uDD4A','spades':'\u2660','spadesuit':'\u2660','spar':'\u2225','sqcap':'\u2293','sqcaps':'\u2293\uFE00','sqcup':'\u2294','sqcups':'\u2294\uFE00','Sqrt':'\u221A','sqsub':'\u228F','sqsube':'\u2291','sqsubset':'\u228F','sqsubseteq':'\u2291','sqsup':'\u2290','sqsupe':'\u2292','sqsupset':'\u2290','sqsupseteq':'\u2292','squ':'\u25A1','square':'\u25A1','Square':'\u25A1','SquareIntersection':'\u2293','SquareSubset':'\u228F','SquareSubsetEqual':'\u2291','SquareSuperset':'\u2290','SquareSupersetEqual':'\u2292','SquareUnion':'\u2294','squarf':'\u25AA','squf':'\u25AA','srarr':'\u2192','sscr':'\uD835\uDCC8','Sscr':'\uD835\uDCAE','ssetmn':'\u2216','ssmile':'\u2323','sstarf':'\u22C6','star':'\u2606','Star':'\u22C6','starf':'\u2605','straightepsilon':'\u03F5','straightphi':'\u03D5','strns':'\xAF','sub':'\u2282','Sub':'\u22D0','subdot':'\u2ABD','sube':'\u2286','subE':'\u2AC5','subedot':'\u2AC3','submult':'\u2AC1','subne':'\u228A','subnE':'\u2ACB','subplus':'\u2ABF','subrarr':'\u2979','subset':'\u2282','Subset':'\u22D0','subseteq':'\u2286','subseteqq':'\u2AC5','SubsetEqual':'\u2286','subsetneq':'\u228A','subsetneqq':'\u2ACB','subsim':'\u2AC7','subsub':'\u2AD5','subsup':'\u2AD3','succ':'\u227B','succapprox':'\u2AB8','succcurlyeq':'\u227D','Succeeds':'\u227B','SucceedsEqual':'\u2AB0','SucceedsSlantEqual':'\u227D','SucceedsTilde':'\u227F','succeq':'\u2AB0','succnapprox':'\u2ABA','succneqq':'\u2AB6','succnsim':'\u22E9','succsim':'\u227F','SuchThat':'\u220B','sum':'\u2211','Sum':'\u2211','sung':'\u266A','sup':'\u2283','Sup':'\u22D1','sup1':'\xB9','sup2':'\xB2','sup3':'\xB3','supdot':'\u2ABE','supdsub':'\u2AD8','supe':'\u2287','supE':'\u2AC6','supedot':'\u2AC4','Superset':'\u2283','SupersetEqual':'\u2287','suphsol':'\u27C9','suphsub':'\u2AD7','suplarr':'\u297B','supmult':'\u2AC2','supne':'\u228B','supnE':'\u2ACC','supplus':'\u2AC0','supset':'\u2283','Supset':'\u22D1','supseteq':'\u2287','supseteqq':'\u2AC6','supsetneq':'\u228B','supsetneqq':'\u2ACC','supsim':'\u2AC8','supsub':'\u2AD4','supsup':'\u2AD6','swarhk':'\u2926','swarr':'\u2199','swArr':'\u21D9','swarrow':'\u2199','swnwar':'\u292A','szlig':'\xDF','Tab':'\t','target':'\u2316','tau':'\u03C4','Tau':'\u03A4','tbrk':'\u23B4','tcaron':'\u0165','Tcaron':'\u0164','tcedil':'\u0163','Tcedil':'\u0162','tcy':'\u0442','Tcy':'\u0422','tdot':'\u20DB','telrec':'\u2315','tfr':'\uD835\uDD31','Tfr':'\uD835\uDD17','there4':'\u2234','therefore':'\u2234','Therefore':'\u2234','theta':'\u03B8','Theta':'\u0398','thetasym':'\u03D1','thetav':'\u03D1','thickapprox':'\u2248','thicksim':'\u223C','ThickSpace':'\u205F\u200A','thinsp':'\u2009','ThinSpace':'\u2009','thkap':'\u2248','thksim':'\u223C','thorn':'\xFE','THORN':'\xDE','tilde':'\u02DC','Tilde':'\u223C','TildeEqual':'\u2243','TildeFullEqual':'\u2245','TildeTilde':'\u2248','times':'\xD7','timesb':'\u22A0','timesbar':'\u2A31','timesd':'\u2A30','tint':'\u222D','toea':'\u2928','top':'\u22A4','topbot':'\u2336','topcir':'\u2AF1','topf':'\uD835\uDD65','Topf':'\uD835\uDD4B','topfork':'\u2ADA','tosa':'\u2929','tprime':'\u2034','trade':'\u2122','TRADE':'\u2122','triangle':'\u25B5','triangledown':'\u25BF','triangleleft':'\u25C3','trianglelefteq':'\u22B4','triangleq':'\u225C','triangleright':'\u25B9','trianglerighteq':'\u22B5','tridot':'\u25EC','trie':'\u225C','triminus':'\u2A3A','TripleDot':'\u20DB','triplus':'\u2A39','trisb':'\u29CD','tritime':'\u2A3B','trpezium':'\u23E2','tscr':'\uD835\uDCC9','Tscr':'\uD835\uDCAF','tscy':'\u0446','TScy':'\u0426','tshcy':'\u045B','TSHcy':'\u040B','tstrok':'\u0167','Tstrok':'\u0166','twixt':'\u226C','twoheadleftarrow':'\u219E','twoheadrightarrow':'\u21A0','uacute':'\xFA','Uacute':'\xDA','uarr':'\u2191','uArr':'\u21D1','Uarr':'\u219F','Uarrocir':'\u2949','ubrcy':'\u045E','Ubrcy':'\u040E','ubreve':'\u016D','Ubreve':'\u016C','ucirc':'\xFB','Ucirc':'\xDB','ucy':'\u0443','Ucy':'\u0423','udarr':'\u21C5','udblac':'\u0171','Udblac':'\u0170','udhar':'\u296E','ufisht':'\u297E','ufr':'\uD835\uDD32','Ufr':'\uD835\uDD18','ugrave':'\xF9','Ugrave':'\xD9','uHar':'\u2963','uharl':'\u21BF','uharr':'\u21BE','uhblk':'\u2580','ulcorn':'\u231C','ulcorner':'\u231C','ulcrop':'\u230F','ultri':'\u25F8','umacr':'\u016B','Umacr':'\u016A','uml':'\xA8','UnderBar':'_','UnderBrace':'\u23DF','UnderBracket':'\u23B5','UnderParenthesis':'\u23DD','Union':'\u22C3','UnionPlus':'\u228E','uogon':'\u0173','Uogon':'\u0172','uopf':'\uD835\uDD66','Uopf':'\uD835\uDD4C','uparrow':'\u2191','Uparrow':'\u21D1','UpArrow':'\u2191','UpArrowBar':'\u2912','UpArrowDownArrow':'\u21C5','updownarrow':'\u2195','Updownarrow':'\u21D5','UpDownArrow':'\u2195','UpEquilibrium':'\u296E','upharpoonleft':'\u21BF','upharpoonright':'\u21BE','uplus':'\u228E','UpperLeftArrow':'\u2196','UpperRightArrow':'\u2197','upsi':'\u03C5','Upsi':'\u03D2','upsih':'\u03D2','upsilon':'\u03C5','Upsilon':'\u03A5','UpTee':'\u22A5','UpTeeArrow':'\u21A5','upuparrows':'\u21C8','urcorn':'\u231D','urcorner':'\u231D','urcrop':'\u230E','uring':'\u016F','Uring':'\u016E','urtri':'\u25F9','uscr':'\uD835\uDCCA','Uscr':'\uD835\uDCB0','utdot':'\u22F0','utilde':'\u0169','Utilde':'\u0168','utri':'\u25B5','utrif':'\u25B4','uuarr':'\u21C8','uuml':'\xFC','Uuml':'\xDC','uwangle':'\u29A7','vangrt':'\u299C','varepsilon':'\u03F5','varkappa':'\u03F0','varnothing':'\u2205','varphi':'\u03D5','varpi':'\u03D6','varpropto':'\u221D','varr':'\u2195','vArr':'\u21D5','varrho':'\u03F1','varsigma':'\u03C2','varsubsetneq':'\u228A\uFE00','varsubsetneqq':'\u2ACB\uFE00','varsupsetneq':'\u228B\uFE00','varsupsetneqq':'\u2ACC\uFE00','vartheta':'\u03D1','vartriangleleft':'\u22B2','vartriangleright':'\u22B3','vBar':'\u2AE8','Vbar':'\u2AEB','vBarv':'\u2AE9','vcy':'\u0432','Vcy':'\u0412','vdash':'\u22A2','vDash':'\u22A8','Vdash':'\u22A9','VDash':'\u22AB','Vdashl':'\u2AE6','vee':'\u2228','Vee':'\u22C1','veebar':'\u22BB','veeeq':'\u225A','vellip':'\u22EE','verbar':'|','Verbar':'\u2016','vert':'|','Vert':'\u2016','VerticalBar':'\u2223','VerticalLine':'|','VerticalSeparator':'\u2758','VerticalTilde':'\u2240','VeryThinSpace':'\u200A','vfr':'\uD835\uDD33','Vfr':'\uD835\uDD19','vltri':'\u22B2','vnsub':'\u2282\u20D2','vnsup':'\u2283\u20D2','vopf':'\uD835\uDD67','Vopf':'\uD835\uDD4D','vprop':'\u221D','vrtri':'\u22B3','vscr':'\uD835\uDCCB','Vscr':'\uD835\uDCB1','vsubne':'\u228A\uFE00','vsubnE':'\u2ACB\uFE00','vsupne':'\u228B\uFE00','vsupnE':'\u2ACC\uFE00','Vvdash':'\u22AA','vzigzag':'\u299A','wcirc':'\u0175','Wcirc':'\u0174','wedbar':'\u2A5F','wedge':'\u2227','Wedge':'\u22C0','wedgeq':'\u2259','weierp':'\u2118','wfr':'\uD835\uDD34','Wfr':'\uD835\uDD1A','wopf':'\uD835\uDD68','Wopf':'\uD835\uDD4E','wp':'\u2118','wr':'\u2240','wreath':'\u2240','wscr':'\uD835\uDCCC','Wscr':'\uD835\uDCB2','xcap':'\u22C2','xcirc':'\u25EF','xcup':'\u22C3','xdtri':'\u25BD','xfr':'\uD835\uDD35','Xfr':'\uD835\uDD1B','xharr':'\u27F7','xhArr':'\u27FA','xi':'\u03BE','Xi':'\u039E','xlarr':'\u27F5','xlArr':'\u27F8','xmap':'\u27FC','xnis':'\u22FB','xodot':'\u2A00','xopf':'\uD835\uDD69','Xopf':'\uD835\uDD4F','xoplus':'\u2A01','xotime':'\u2A02','xrarr':'\u27F6','xrArr':'\u27F9','xscr':'\uD835\uDCCD','Xscr':'\uD835\uDCB3','xsqcup':'\u2A06','xuplus':'\u2A04','xutri':'\u25B3','xvee':'\u22C1','xwedge':'\u22C0','yacute':'\xFD','Yacute':'\xDD','yacy':'\u044F','YAcy':'\u042F','ycirc':'\u0177','Ycirc':'\u0176','ycy':'\u044B','Ycy':'\u042B','yen':'\xA5','yfr':'\uD835\uDD36','Yfr':'\uD835\uDD1C','yicy':'\u0457','YIcy':'\u0407','yopf':'\uD835\uDD6A','Yopf':'\uD835\uDD50','yscr':'\uD835\uDCCE','Yscr':'\uD835\uDCB4','yucy':'\u044E','YUcy':'\u042E','yuml':'\xFF','Yuml':'\u0178','zacute':'\u017A','Zacute':'\u0179','zcaron':'\u017E','Zcaron':'\u017D','zcy':'\u0437','Zcy':'\u0417','zdot':'\u017C','Zdot':'\u017B','zeetrf':'\u2128','ZeroWidthSpace':'\u200B','zeta':'\u03B6','Zeta':'\u0396','zfr':'\uD835\uDD37','Zfr':'\u2128','zhcy':'\u0436','ZHcy':'\u0416','zigrarr':'\u21DD','zopf':'\uD835\uDD6B','Zopf':'\u2124','zscr':'\uD835\uDCCF','Zscr':'\uD835\uDCB5','zwj':'\u200D','zwnj':'\u200C'}; + var decodeMapLegacy = {'aacute':'\xE1','Aacute':'\xC1','acirc':'\xE2','Acirc':'\xC2','acute':'\xB4','aelig':'\xE6','AElig':'\xC6','agrave':'\xE0','Agrave':'\xC0','amp':'&','AMP':'&','aring':'\xE5','Aring':'\xC5','atilde':'\xE3','Atilde':'\xC3','auml':'\xE4','Auml':'\xC4','brvbar':'\xA6','ccedil':'\xE7','Ccedil':'\xC7','cedil':'\xB8','cent':'\xA2','copy':'\xA9','COPY':'\xA9','curren':'\xA4','deg':'\xB0','divide':'\xF7','eacute':'\xE9','Eacute':'\xC9','ecirc':'\xEA','Ecirc':'\xCA','egrave':'\xE8','Egrave':'\xC8','eth':'\xF0','ETH':'\xD0','euml':'\xEB','Euml':'\xCB','frac12':'\xBD','frac14':'\xBC','frac34':'\xBE','gt':'>','GT':'>','iacute':'\xED','Iacute':'\xCD','icirc':'\xEE','Icirc':'\xCE','iexcl':'\xA1','igrave':'\xEC','Igrave':'\xCC','iquest':'\xBF','iuml':'\xEF','Iuml':'\xCF','laquo':'\xAB','lt':'<','LT':'<','macr':'\xAF','micro':'\xB5','middot':'\xB7','nbsp':'\xA0','not':'\xAC','ntilde':'\xF1','Ntilde':'\xD1','oacute':'\xF3','Oacute':'\xD3','ocirc':'\xF4','Ocirc':'\xD4','ograve':'\xF2','Ograve':'\xD2','ordf':'\xAA','ordm':'\xBA','oslash':'\xF8','Oslash':'\xD8','otilde':'\xF5','Otilde':'\xD5','ouml':'\xF6','Ouml':'\xD6','para':'\xB6','plusmn':'\xB1','pound':'\xA3','quot':'"','QUOT':'"','raquo':'\xBB','reg':'\xAE','REG':'\xAE','sect':'\xA7','shy':'\xAD','sup1':'\xB9','sup2':'\xB2','sup3':'\xB3','szlig':'\xDF','thorn':'\xFE','THORN':'\xDE','times':'\xD7','uacute':'\xFA','Uacute':'\xDA','ucirc':'\xFB','Ucirc':'\xDB','ugrave':'\xF9','Ugrave':'\xD9','uml':'\xA8','uuml':'\xFC','Uuml':'\xDC','yacute':'\xFD','Yacute':'\xDD','yen':'\xA5','yuml':'\xFF'}; + var decodeMapNumeric = {'0':'\uFFFD','128':'\u20AC','130':'\u201A','131':'\u0192','132':'\u201E','133':'\u2026','134':'\u2020','135':'\u2021','136':'\u02C6','137':'\u2030','138':'\u0160','139':'\u2039','140':'\u0152','142':'\u017D','145':'\u2018','146':'\u2019','147':'\u201C','148':'\u201D','149':'\u2022','150':'\u2013','151':'\u2014','152':'\u02DC','153':'\u2122','154':'\u0161','155':'\u203A','156':'\u0153','158':'\u017E','159':'\u0178'}; + var invalidReferenceCodePoints = [1,2,3,4,5,6,7,8,11,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,64976,64977,64978,64979,64980,64981,64982,64983,64984,64985,64986,64987,64988,64989,64990,64991,64992,64993,64994,64995,64996,64997,64998,64999,65000,65001,65002,65003,65004,65005,65006,65007,65534,65535,131070,131071,196606,196607,262142,262143,327678,327679,393214,393215,458750,458751,524286,524287,589822,589823,655358,655359,720894,720895,786430,786431,851966,851967,917502,917503,983038,983039,1048574,1048575,1114110,1114111]; + + /*--------------------------------------------------------------------------*/ + + var stringFromCharCode = String.fromCharCode; + + var object = {}; + var hasOwnProperty = object.hasOwnProperty; + var has = function(object, propertyName) { + return hasOwnProperty.call(object, propertyName); + }; + + var contains = function(array, value) { + var index = -1; + var length = array.length; + while (++index < length) { + if (array[index] == value) { + return true; + } + } + return false; + }; + + var merge = function(options, defaults) { + if (!options) { + return defaults; + } + var result = {}; + var key; + for (key in defaults) { + // A `hasOwnProperty` check is not needed here, since only recognized + // option names are used anyway. Any others are ignored. + result[key] = has(options, key) ? options[key] : defaults[key]; + } + return result; + }; + + // Modified version of `ucs2encode`; see https://mths.be/punycode. + var codePointToSymbol = function(codePoint, strict) { + var output = ''; + if ((codePoint >= 0xD800 && codePoint <= 0xDFFF) || codePoint > 0x10FFFF) { + // See issue #4: + // “Otherwise, if the number is in the range 0xD800 to 0xDFFF or is + // greater than 0x10FFFF, then this is a parse error. Return a U+FFFD + // REPLACEMENT CHARACTER.” + if (strict) { + parseError('character reference outside the permissible Unicode range'); + } + return '\uFFFD'; + } + if (has(decodeMapNumeric, codePoint)) { + if (strict) { + parseError('disallowed character reference'); + } + return decodeMapNumeric[codePoint]; + } + if (strict && contains(invalidReferenceCodePoints, codePoint)) { + parseError('disallowed character reference'); + } + if (codePoint > 0xFFFF) { + codePoint -= 0x10000; + output += stringFromCharCode(codePoint >>> 10 & 0x3FF | 0xD800); + codePoint = 0xDC00 | codePoint & 0x3FF; + } + output += stringFromCharCode(codePoint); + return output; + }; + + var hexEscape = function(codePoint) { + return '&#x' + codePoint.toString(16).toUpperCase() + ';'; + }; + + var decEscape = function(codePoint) { + return '&#' + codePoint + ';'; + }; + + var parseError = function(message) { + throw Error('Parse error: ' + message); + }; + + /*--------------------------------------------------------------------------*/ + + var encode = function(string, options) { + options = merge(options, encode.options); + var strict = options.strict; + if (strict && regexInvalidRawCodePoint.test(string)) { + parseError('forbidden code point'); + } + var encodeEverything = options.encodeEverything; + var useNamedReferences = options.useNamedReferences; + var allowUnsafeSymbols = options.allowUnsafeSymbols; + var escapeCodePoint = options.decimal ? decEscape : hexEscape; + + var escapeBmpSymbol = function(symbol) { + return escapeCodePoint(symbol.charCodeAt(0)); + }; + + if (encodeEverything) { + // Encode ASCII symbols. + string = string.replace(regexAsciiWhitelist, function(symbol) { + // Use named references if requested & possible. + if (useNamedReferences && has(encodeMap, symbol)) { + return '&' + encodeMap[symbol] + ';'; + } + return escapeBmpSymbol(symbol); + }); + // Shorten a few escapes that represent two symbols, of which at least one + // is within the ASCII range. + if (useNamedReferences) { + string = string + .replace(/>\u20D2/g, '>⃒') + .replace(/<\u20D2/g, '<⃒') + .replace(/fj/g, 'fj'); + } + // Encode non-ASCII symbols. + if (useNamedReferences) { + // Encode non-ASCII symbols that can be replaced with a named reference. + string = string.replace(regexEncodeNonAscii, function(string) { + // Note: there is no need to check `has(encodeMap, string)` here. + return '&' + encodeMap[string] + ';'; + }); + } + // Note: any remaining non-ASCII symbols are handled outside of the `if`. + } else if (useNamedReferences) { + // Apply named character references. + // Encode `<>"'&` using named character references. + if (!allowUnsafeSymbols) { + string = string.replace(regexEscape, function(string) { + return '&' + encodeMap[string] + ';'; // no need to check `has()` here + }); + } + // Shorten escapes that represent two symbols, of which at least one is + // `<>"'&`. + string = string + .replace(/>\u20D2/g, '>⃒') + .replace(/<\u20D2/g, '<⃒'); + // Encode non-ASCII symbols that can be replaced with a named reference. + string = string.replace(regexEncodeNonAscii, function(string) { + // Note: there is no need to check `has(encodeMap, string)` here. + return '&' + encodeMap[string] + ';'; + }); + } else if (!allowUnsafeSymbols) { + // Encode `<>"'&` using hexadecimal escapes, now that they’re not handled + // using named character references. + string = string.replace(regexEscape, escapeBmpSymbol); + } + return string + // Encode astral symbols. + .replace(regexAstralSymbols, function($0) { + // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae + var high = $0.charCodeAt(0); + var low = $0.charCodeAt(1); + var codePoint = (high - 0xD800) * 0x400 + low - 0xDC00 + 0x10000; + return escapeCodePoint(codePoint); + }) + // Encode any remaining BMP symbols that are not printable ASCII symbols + // using a hexadecimal escape. + .replace(regexBmpWhitelist, escapeBmpSymbol); + }; + // Expose default options (so they can be overridden globally). + encode.options = { + 'allowUnsafeSymbols': false, + 'encodeEverything': false, + 'strict': false, + 'useNamedReferences': false, + 'decimal' : false + }; + + var decode = function(html, options) { + options = merge(options, decode.options); + var strict = options.strict; + if (strict && regexInvalidEntity.test(html)) { + parseError('malformed character reference'); + } + return html.replace(regexDecode, function($0, $1, $2, $3, $4, $5, $6, $7, $8) { + var codePoint; + var semicolon; + var decDigits; + var hexDigits; + var reference; + var next; + + if ($1) { + reference = $1; + // Note: there is no need to check `has(decodeMap, reference)`. + return decodeMap[reference]; + } + + if ($2) { + // Decode named character references without trailing `;`, e.g. `&`. + // This is only a parse error if it gets converted to `&`, or if it is + // followed by `=` in an attribute context. + reference = $2; + next = $3; + if (next && options.isAttributeValue) { + if (strict && next == '=') { + parseError('`&` did not start a character reference'); + } + return $0; + } else { + if (strict) { + parseError( + 'named character reference was not terminated by a semicolon' + ); + } + // Note: there is no need to check `has(decodeMapLegacy, reference)`. + return decodeMapLegacy[reference] + (next || ''); + } + } + + if ($4) { + // Decode decimal escapes, e.g. `𝌆`. + decDigits = $4; + semicolon = $5; + if (strict && !semicolon) { + parseError('character reference was not terminated by a semicolon'); + } + codePoint = parseInt(decDigits, 10); + return codePointToSymbol(codePoint, strict); + } + + if ($6) { + // Decode hexadecimal escapes, e.g. `𝌆`. + hexDigits = $6; + semicolon = $7; + if (strict && !semicolon) { + parseError('character reference was not terminated by a semicolon'); + } + codePoint = parseInt(hexDigits, 16); + return codePointToSymbol(codePoint, strict); + } + + // If we’re still here, `if ($7)` is implied; it’s an ambiguous + // ampersand for sure. https://mths.be/notes/ambiguous-ampersands + if (strict) { + parseError( + 'named character reference was not terminated by a semicolon' + ); + } + return $0; + }); + }; + // Expose default options (so they can be overridden globally). + decode.options = { + 'isAttributeValue': false, + 'strict': false + }; + + var escape = function(string) { + return string.replace(regexEscape, function($0) { + // Note: there is no need to check `has(escapeMap, $0)` here. + return escapeMap[$0]; + }); + }; + + /*--------------------------------------------------------------------------*/ + + var he = { + 'version': '1.2.0', + 'encode': encode, + 'decode': decode, + 'escape': escape, + 'unescape': decode + }; + + // Some AMD build optimizers, like r.js, check for specific condition patterns + // like the following: + if ( + false + ) { + define(function() { + return he; + }); + } else if (freeExports && !freeExports.nodeType) { + if (freeModule) { // in Node.js, io.js, or RingoJS v0.8.0+ + freeModule.exports = he; + } else { // in Narwhal or RingoJS v0.7.0- + for (var key in he) { + has(he, key) && (freeExports[key] = he[key]); + } + } + } else { // in Rhino or a web browser + root.he = he; + } + +}(this)); + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],55:[function(require,module,exports){ +exports.read = function (buffer, offset, isLE, mLen, nBytes) { + var e, m + var eLen = (nBytes * 8) - mLen - 1 + var eMax = (1 << eLen) - 1 + var eBias = eMax >> 1 + var nBits = -7 + var i = isLE ? (nBytes - 1) : 0 + var d = isLE ? -1 : 1 + var s = buffer[offset + i] + + i += d + + e = s & ((1 << (-nBits)) - 1) + s >>= (-nBits) + nBits += eLen + for (; nBits > 0; e = (e * 256) + buffer[offset + i], i += d, nBits -= 8) {} + + m = e & ((1 << (-nBits)) - 1) + e >>= (-nBits) + nBits += mLen + for (; nBits > 0; m = (m * 256) + buffer[offset + i], i += d, nBits -= 8) {} + + if (e === 0) { + e = 1 - eBias + } else if (e === eMax) { + return m ? NaN : ((s ? -1 : 1) * Infinity) + } else { + m = m + Math.pow(2, mLen) + e = e - eBias + } + return (s ? -1 : 1) * m * Math.pow(2, e - mLen) +} + +exports.write = function (buffer, value, offset, isLE, mLen, nBytes) { + var e, m, c + var eLen = (nBytes * 8) - mLen - 1 + var eMax = (1 << eLen) - 1 + var eBias = eMax >> 1 + var rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0) + var i = isLE ? 0 : (nBytes - 1) + var d = isLE ? 1 : -1 + var s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0 + + value = Math.abs(value) + + if (isNaN(value) || value === Infinity) { + m = isNaN(value) ? 1 : 0 + e = eMax + } else { + e = Math.floor(Math.log(value) / Math.LN2) + if (value * (c = Math.pow(2, -e)) < 1) { + e-- + c *= 2 + } + if (e + eBias >= 1) { + value += rt / c + } else { + value += rt * Math.pow(2, 1 - eBias) + } + if (value * c >= 2) { + e++ + c /= 2 + } + + if (e + eBias >= eMax) { + m = 0 + e = eMax + } else if (e + eBias >= 1) { + m = ((value * c) - 1) * Math.pow(2, mLen) + e = e + eBias + } else { + m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen) + e = 0 + } + } + + for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {} + + e = (e << mLen) | m + eLen += mLen + for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {} + + buffer[offset + i - d] |= s * 128 +} + +},{}],56:[function(require,module,exports){ +if (typeof Object.create === 'function') { + // implementation from standard node.js 'util' module + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor + ctor.prototype = Object.create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true + } + }); + }; +} else { + // old school shim for old browsers + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor + var TempCtor = function () {} + TempCtor.prototype = superCtor.prototype + ctor.prototype = new TempCtor() + ctor.prototype.constructor = ctor + } +} + +},{}],57:[function(require,module,exports){ +/*! + * Determine if an object is a Buffer + * + * @author Feross Aboukhadijeh + * @license MIT + */ + +// The _isBuffer check is for Safari 5-7 support, because it's missing +// Object.prototype.constructor. Remove this eventually +module.exports = function (obj) { + return obj != null && (isBuffer(obj) || isSlowBuffer(obj) || !!obj._isBuffer) +} + +function isBuffer (obj) { + return !!obj.constructor && typeof obj.constructor.isBuffer === 'function' && obj.constructor.isBuffer(obj) +} + +// For Node v0.10 support. Remove this eventually. +function isSlowBuffer (obj) { + return typeof obj.readFloatLE === 'function' && typeof obj.slice === 'function' && isBuffer(obj.slice(0, 0)) +} + +},{}],58:[function(require,module,exports){ +var toString = {}.toString; + +module.exports = Array.isArray || function (arr) { + return toString.call(arr) == '[object Array]'; +}; + +},{}],59:[function(require,module,exports){ +(function (process){ +var path = require('path'); +var fs = require('fs'); +var _0777 = parseInt('0777', 8); + +module.exports = mkdirP.mkdirp = mkdirP.mkdirP = mkdirP; + +function mkdirP (p, opts, f, made) { + if (typeof opts === 'function') { + f = opts; + opts = {}; + } + else if (!opts || typeof opts !== 'object') { + opts = { mode: opts }; + } + + var mode = opts.mode; + var xfs = opts.fs || fs; + + if (mode === undefined) { + mode = _0777 & (~process.umask()); + } + if (!made) made = null; + + var cb = f || function () {}; + p = path.resolve(p); + + xfs.mkdir(p, mode, function (er) { + if (!er) { + made = made || p; + return cb(null, made); + } + switch (er.code) { + case 'ENOENT': + mkdirP(path.dirname(p), opts, function (er, made) { + if (er) cb(er, made); + else mkdirP(p, opts, cb, made); + }); + break; + + // In the case of any other error, just see if there's a dir + // there already. If so, then hooray! If not, then something + // is borked. + default: + xfs.stat(p, function (er2, stat) { + // if the stat fails, then that's super weird. + // let the original error be the failure reason. + if (er2 || !stat.isDirectory()) cb(er, made) + else cb(null, made); + }); + break; + } + }); +} + +mkdirP.sync = function sync (p, opts, made) { + if (!opts || typeof opts !== 'object') { + opts = { mode: opts }; + } + + var mode = opts.mode; + var xfs = opts.fs || fs; + + if (mode === undefined) { + mode = _0777 & (~process.umask()); + } + if (!made) made = null; + + p = path.resolve(p); + + try { + xfs.mkdirSync(p, mode); + made = made || p; + } + catch (err0) { + switch (err0.code) { + case 'ENOENT' : + made = sync(path.dirname(p), opts, made); + sync(p, opts, made); + break; + + // In the case of any other error, just see if there's a dir + // there already. If so, then hooray! If not, then something + // is borked. + default: + var stat; + try { + stat = xfs.statSync(p); + } + catch (err1) { + throw err0; + } + if (!stat.isDirectory()) throw err0; + break; + } + } + + return made; +}; + +}).call(this,require('_process')) +},{"_process":69,"fs":42,"path":42}],60:[function(require,module,exports){ +/** + * Helpers. + */ + +var s = 1000; +var m = s * 60; +var h = m * 60; +var d = h * 24; +var w = d * 7; +var y = d * 365.25; + +/** + * Parse or format the given `val`. + * + * Options: + * + * - `long` verbose formatting [false] + * + * @param {String|Number} val + * @param {Object} [options] + * @throws {Error} throw an error if val is not a non-empty string or a number + * @return {String|Number} + * @api public + */ + +module.exports = function(val, options) { + options = options || {}; + var type = typeof val; + if (type === 'string' && val.length > 0) { + return parse(val); + } else if (type === 'number' && isNaN(val) === false) { + return options.long ? fmtLong(val) : fmtShort(val); + } + throw new Error( + 'val is not a non-empty string or a valid number. val=' + + JSON.stringify(val) + ); +}; + +/** + * Parse the given `str` and return milliseconds. + * + * @param {String} str + * @return {Number} + * @api private + */ + +function parse(str) { + str = String(str); + if (str.length > 100) { + return; + } + var match = /^((?:\d+)?\-?\d?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec( + str + ); + if (!match) { + return; + } + var n = parseFloat(match[1]); + var type = (match[2] || 'ms').toLowerCase(); + switch (type) { + case 'years': + case 'year': + case 'yrs': + case 'yr': + case 'y': + return n * y; + case 'weeks': + case 'week': + case 'w': + return n * w; + case 'days': + case 'day': + case 'd': + return n * d; + case 'hours': + case 'hour': + case 'hrs': + case 'hr': + case 'h': + return n * h; + case 'minutes': + case 'minute': + case 'mins': + case 'min': + case 'm': + return n * m; + case 'seconds': + case 'second': + case 'secs': + case 'sec': + case 's': + return n * s; + case 'milliseconds': + case 'millisecond': + case 'msecs': + case 'msec': + case 'ms': + return n; + default: + return undefined; + } +} + +/** + * Short format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ + +function fmtShort(ms) { + var msAbs = Math.abs(ms); + if (msAbs >= d) { + return Math.round(ms / d) + 'd'; + } + if (msAbs >= h) { + return Math.round(ms / h) + 'h'; + } + if (msAbs >= m) { + return Math.round(ms / m) + 'm'; + } + if (msAbs >= s) { + return Math.round(ms / s) + 's'; + } + return ms + 'ms'; +} + +/** + * Long format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ + +function fmtLong(ms) { + var msAbs = Math.abs(ms); + if (msAbs >= d) { + return plural(ms, msAbs, d, 'day'); + } + if (msAbs >= h) { + return plural(ms, msAbs, h, 'hour'); + } + if (msAbs >= m) { + return plural(ms, msAbs, m, 'minute'); + } + if (msAbs >= s) { + return plural(ms, msAbs, s, 'second'); + } + return ms + ' ms'; +} + +/** + * Pluralization helper. + */ + +function plural(ms, msAbs, n, name) { + var isPlural = msAbs >= n * 1.5; + return Math.round(ms / n) + ' ' + name + (isPlural ? 's' : ''); +} + +},{}],61:[function(require,module,exports){ +'use strict'; + +var keysShim; +if (!Object.keys) { + // modified from https://github.com/es-shims/es5-shim + var has = Object.prototype.hasOwnProperty; + var toStr = Object.prototype.toString; + var isArgs = require('./isArguments'); // eslint-disable-line global-require + var isEnumerable = Object.prototype.propertyIsEnumerable; + var hasDontEnumBug = !isEnumerable.call({ toString: null }, 'toString'); + var hasProtoEnumBug = isEnumerable.call(function () {}, 'prototype'); + var dontEnums = [ + 'toString', + 'toLocaleString', + 'valueOf', + 'hasOwnProperty', + 'isPrototypeOf', + 'propertyIsEnumerable', + 'constructor' + ]; + var equalsConstructorPrototype = function (o) { + var ctor = o.constructor; + return ctor && ctor.prototype === o; + }; + var excludedKeys = { + $applicationCache: true, + $console: true, + $external: true, + $frame: true, + $frameElement: true, + $frames: true, + $innerHeight: true, + $innerWidth: true, + $outerHeight: true, + $outerWidth: true, + $pageXOffset: true, + $pageYOffset: true, + $parent: true, + $scrollLeft: true, + $scrollTop: true, + $scrollX: true, + $scrollY: true, + $self: true, + $webkitIndexedDB: true, + $webkitStorageInfo: true, + $window: true + }; + var hasAutomationEqualityBug = (function () { + /* global window */ + if (typeof window === 'undefined') { return false; } + for (var k in window) { + try { + if (!excludedKeys['$' + k] && has.call(window, k) && window[k] !== null && typeof window[k] === 'object') { + try { + equalsConstructorPrototype(window[k]); + } catch (e) { + return true; + } + } + } catch (e) { + return true; + } + } + return false; + }()); + var equalsConstructorPrototypeIfNotBuggy = function (o) { + /* global window */ + if (typeof window === 'undefined' || !hasAutomationEqualityBug) { + return equalsConstructorPrototype(o); + } + try { + return equalsConstructorPrototype(o); + } catch (e) { + return false; + } + }; + + keysShim = function keys(object) { + var isObject = object !== null && typeof object === 'object'; + var isFunction = toStr.call(object) === '[object Function]'; + var isArguments = isArgs(object); + var isString = isObject && toStr.call(object) === '[object String]'; + var theKeys = []; + + if (!isObject && !isFunction && !isArguments) { + throw new TypeError('Object.keys called on a non-object'); + } + + var skipProto = hasProtoEnumBug && isFunction; + if (isString && object.length > 0 && !has.call(object, 0)) { + for (var i = 0; i < object.length; ++i) { + theKeys.push(String(i)); + } + } + + if (isArguments && object.length > 0) { + for (var j = 0; j < object.length; ++j) { + theKeys.push(String(j)); + } + } else { + for (var name in object) { + if (!(skipProto && name === 'prototype') && has.call(object, name)) { + theKeys.push(String(name)); + } + } + } + + if (hasDontEnumBug) { + var skipConstructor = equalsConstructorPrototypeIfNotBuggy(object); + + for (var k = 0; k < dontEnums.length; ++k) { + if (!(skipConstructor && dontEnums[k] === 'constructor') && has.call(object, dontEnums[k])) { + theKeys.push(dontEnums[k]); + } + } + } + return theKeys; + }; +} +module.exports = keysShim; + +},{"./isArguments":63}],62:[function(require,module,exports){ +'use strict'; + +var slice = Array.prototype.slice; +var isArgs = require('./isArguments'); + +var origKeys = Object.keys; +var keysShim = origKeys ? function keys(o) { return origKeys(o); } : require('./implementation'); + +var originalKeys = Object.keys; + +keysShim.shim = function shimObjectKeys() { + if (Object.keys) { + var keysWorksWithArguments = (function () { + // Safari 5.0 bug + var args = Object.keys(arguments); + return args && args.length === arguments.length; + }(1, 2)); + if (!keysWorksWithArguments) { + Object.keys = function keys(object) { // eslint-disable-line func-name-matching + if (isArgs(object)) { + return originalKeys(slice.call(object)); + } + return originalKeys(object); + }; + } + } else { + Object.keys = keysShim; + } + return Object.keys || keysShim; +}; + +module.exports = keysShim; + +},{"./implementation":61,"./isArguments":63}],63:[function(require,module,exports){ +'use strict'; + +var toStr = Object.prototype.toString; + +module.exports = function isArguments(value) { + var str = toStr.call(value); + var isArgs = str === '[object Arguments]'; + if (!isArgs) { + isArgs = str !== '[object Array]' && + value !== null && + typeof value === 'object' && + typeof value.length === 'number' && + value.length >= 0 && + toStr.call(value.callee) === '[object Function]'; + } + return isArgs; +}; + +},{}],64:[function(require,module,exports){ +'use strict'; + +// modified from https://github.com/es-shims/es6-shim +var keys = require('object-keys'); +var bind = require('function-bind'); +var canBeObject = function (obj) { + return typeof obj !== 'undefined' && obj !== null; +}; +var hasSymbols = require('has-symbols/shams')(); +var toObject = Object; +var push = bind.call(Function.call, Array.prototype.push); +var propIsEnumerable = bind.call(Function.call, Object.prototype.propertyIsEnumerable); +var originalGetSymbols = hasSymbols ? Object.getOwnPropertySymbols : null; + +module.exports = function assign(target, source1) { + if (!canBeObject(target)) { throw new TypeError('target must be an object'); } + var objTarget = toObject(target); + var s, source, i, props, syms, value, key; + for (s = 1; s < arguments.length; ++s) { + source = toObject(arguments[s]); + props = keys(source); + var getSymbols = hasSymbols && (Object.getOwnPropertySymbols || originalGetSymbols); + if (getSymbols) { + syms = getSymbols(source); + for (i = 0; i < syms.length; ++i) { + key = syms[i]; + if (propIsEnumerable(source, key)) { + push(props, key); + } + } + } + for (i = 0; i < props.length; ++i) { + key = props[i]; + value = source[key]; + if (propIsEnumerable(source, key)) { + objTarget[key] = value; + } + } + } + return objTarget; +}; + +},{"function-bind":52,"has-symbols/shams":53,"object-keys":62}],65:[function(require,module,exports){ +'use strict'; + +var defineProperties = require('define-properties'); + +var implementation = require('./implementation'); +var getPolyfill = require('./polyfill'); +var shim = require('./shim'); + +var polyfill = getPolyfill(); + +defineProperties(polyfill, { + getPolyfill: getPolyfill, + implementation: implementation, + shim: shim +}); + +module.exports = polyfill; + +},{"./implementation":64,"./polyfill":66,"./shim":67,"define-properties":47}],66:[function(require,module,exports){ +'use strict'; + +var implementation = require('./implementation'); + +var lacksProperEnumerationOrder = function () { + if (!Object.assign) { + return false; + } + // v8, specifically in node 4.x, has a bug with incorrect property enumeration order + // note: this does not detect the bug unless there's 20 characters + var str = 'abcdefghijklmnopqrst'; + var letters = str.split(''); + var map = {}; + for (var i = 0; i < letters.length; ++i) { + map[letters[i]] = letters[i]; + } + var obj = Object.assign({}, map); + var actual = ''; + for (var k in obj) { + actual += k; + } + return str !== actual; +}; + +var assignHasPendingExceptions = function () { + if (!Object.assign || !Object.preventExtensions) { + return false; + } + // Firefox 37 still has "pending exception" logic in its Object.assign implementation, + // which is 72% slower than our shim, and Firefox 40's native implementation. + var thrower = Object.preventExtensions({ 1: 2 }); + try { + Object.assign(thrower, 'xy'); + } catch (e) { + return thrower[1] === 'y'; + } + return false; +}; + +module.exports = function getPolyfill() { + if (!Object.assign) { + return implementation; + } + if (lacksProperEnumerationOrder()) { + return implementation; + } + if (assignHasPendingExceptions()) { + return implementation; + } + return Object.assign; +}; + +},{"./implementation":64}],67:[function(require,module,exports){ +'use strict'; + +var define = require('define-properties'); +var getPolyfill = require('./polyfill'); + +module.exports = function shimAssign() { + var polyfill = getPolyfill(); + define( + Object, + { assign: polyfill }, + { assign: function () { return Object.assign !== polyfill; } } + ); + return polyfill; +}; + +},{"./polyfill":66,"define-properties":47}],68:[function(require,module,exports){ +(function (process){ +'use strict'; + +if (!process.version || + process.version.indexOf('v0.') === 0 || + process.version.indexOf('v1.') === 0 && process.version.indexOf('v1.8.') !== 0) { + module.exports = { nextTick: nextTick }; +} else { + module.exports = process +} + +function nextTick(fn, arg1, arg2, arg3) { + if (typeof fn !== 'function') { + throw new TypeError('"callback" argument must be a function'); + } + var len = arguments.length; + var args, i; + switch (len) { + case 0: + case 1: + return process.nextTick(fn); + case 2: + return process.nextTick(function afterTickOne() { + fn.call(null, arg1); + }); + case 3: + return process.nextTick(function afterTickTwo() { + fn.call(null, arg1, arg2); + }); + case 4: + return process.nextTick(function afterTickThree() { + fn.call(null, arg1, arg2, arg3); + }); + default: + args = new Array(len - 1); + i = 0; + while (i < args.length) { + args[i++] = arguments[i]; + } + return process.nextTick(function afterTick() { + fn.apply(null, args); + }); + } +} + + +}).call(this,require('_process')) +},{"_process":69}],69:[function(require,module,exports){ +// shim for using process in browser +var process = module.exports = {}; + +// cached from whatever global is present so that test runners that stub it +// don't break things. But we need to wrap it in a try catch in case it is +// wrapped in strict mode code which doesn't define any globals. It's inside a +// function because try/catches deoptimize in certain engines. + +var cachedSetTimeout; +var cachedClearTimeout; + +function defaultSetTimout() { + throw new Error('setTimeout has not been defined'); +} +function defaultClearTimeout () { + throw new Error('clearTimeout has not been defined'); +} +(function () { + try { + if (typeof setTimeout === 'function') { + cachedSetTimeout = setTimeout; + } else { + cachedSetTimeout = defaultSetTimout; + } + } catch (e) { + cachedSetTimeout = defaultSetTimout; + } + try { + if (typeof clearTimeout === 'function') { + cachedClearTimeout = clearTimeout; + } else { + cachedClearTimeout = defaultClearTimeout; + } + } catch (e) { + cachedClearTimeout = defaultClearTimeout; + } +} ()) +function runTimeout(fun) { + if (cachedSetTimeout === setTimeout) { + //normal enviroments in sane situations + return setTimeout(fun, 0); + } + // if setTimeout wasn't available but was latter defined + if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) { + cachedSetTimeout = setTimeout; + return setTimeout(fun, 0); + } + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedSetTimeout(fun, 0); + } catch(e){ + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedSetTimeout.call(null, fun, 0); + } catch(e){ + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error + return cachedSetTimeout.call(this, fun, 0); + } + } + + +} +function runClearTimeout(marker) { + if (cachedClearTimeout === clearTimeout) { + //normal enviroments in sane situations + return clearTimeout(marker); + } + // if clearTimeout wasn't available but was latter defined + if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) { + cachedClearTimeout = clearTimeout; + return clearTimeout(marker); + } + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedClearTimeout(marker); + } catch (e){ + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedClearTimeout.call(null, marker); + } catch (e){ + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error. + // Some versions of I.E. have different rules for clearTimeout vs setTimeout + return cachedClearTimeout.call(this, marker); + } + } + + + +} +var queue = []; +var draining = false; +var currentQueue; +var queueIndex = -1; + +function cleanUpNextTick() { + if (!draining || !currentQueue) { + return; + } + draining = false; + if (currentQueue.length) { + queue = currentQueue.concat(queue); + } else { + queueIndex = -1; + } + if (queue.length) { + drainQueue(); + } +} + +function drainQueue() { + if (draining) { + return; + } + var timeout = runTimeout(cleanUpNextTick); + draining = true; + + var len = queue.length; + while(len) { + currentQueue = queue; + queue = []; + while (++queueIndex < len) { + if (currentQueue) { + currentQueue[queueIndex].run(); + } + } + queueIndex = -1; + len = queue.length; + } + currentQueue = null; + draining = false; + runClearTimeout(timeout); +} + +process.nextTick = function (fun) { + var args = new Array(arguments.length - 1); + if (arguments.length > 1) { + for (var i = 1; i < arguments.length; i++) { + args[i - 1] = arguments[i]; + } + } + queue.push(new Item(fun, args)); + if (queue.length === 1 && !draining) { + runTimeout(drainQueue); + } +}; + +// v8 likes predictible objects +function Item(fun, array) { + this.fun = fun; + this.array = array; +} +Item.prototype.run = function () { + this.fun.apply(null, this.array); +}; +process.title = 'browser'; +process.browser = true; +process.env = {}; +process.argv = []; +process.version = ''; // empty string to avoid regexp issues +process.versions = {}; + +function noop() {} + +process.on = noop; +process.addListener = noop; +process.once = noop; +process.off = noop; +process.removeListener = noop; +process.removeAllListeners = noop; +process.emit = noop; +process.prependListener = noop; +process.prependOnceListener = noop; + +process.listeners = function (name) { return [] } + +process.binding = function (name) { + throw new Error('process.binding is not supported'); +}; + +process.cwd = function () { return '/' }; +process.chdir = function (dir) { + throw new Error('process.chdir is not supported'); +}; +process.umask = function() { return 0; }; + +},{}],70:[function(require,module,exports){ +module.exports = require('./lib/_stream_duplex.js'); + +},{"./lib/_stream_duplex.js":71}],71:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// a duplex stream is just a stream that is both readable and writable. +// Since JS doesn't have multiple prototypal inheritance, this class +// prototypally inherits from Readable, and then parasitically from +// Writable. + +'use strict'; + +/**/ + +var pna = require('process-nextick-args'); +/**/ + +/**/ +var objectKeys = Object.keys || function (obj) { + var keys = []; + for (var key in obj) { + keys.push(key); + }return keys; +}; +/**/ + +module.exports = Duplex; + +/**/ +var util = require('core-util-is'); +util.inherits = require('inherits'); +/**/ + +var Readable = require('./_stream_readable'); +var Writable = require('./_stream_writable'); + +util.inherits(Duplex, Readable); + +{ + // avoid scope creep, the keys array can then be collected + var keys = objectKeys(Writable.prototype); + for (var v = 0; v < keys.length; v++) { + var method = keys[v]; + if (!Duplex.prototype[method]) Duplex.prototype[method] = Writable.prototype[method]; + } +} + +function Duplex(options) { + if (!(this instanceof Duplex)) return new Duplex(options); + + Readable.call(this, options); + Writable.call(this, options); + + if (options && options.readable === false) this.readable = false; + + if (options && options.writable === false) this.writable = false; + + this.allowHalfOpen = true; + if (options && options.allowHalfOpen === false) this.allowHalfOpen = false; + + this.once('end', onend); +} + +Object.defineProperty(Duplex.prototype, 'writableHighWaterMark', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function () { + return this._writableState.highWaterMark; + } +}); + +// the no-half-open enforcer +function onend() { + // if we allow half-open state, or if the writable side ended, + // then we're ok. + if (this.allowHalfOpen || this._writableState.ended) return; + + // no more data can be written. + // But allow more writes to happen in this tick. + pna.nextTick(onEndNT, this); +} + +function onEndNT(self) { + self.end(); +} + +Object.defineProperty(Duplex.prototype, 'destroyed', { + get: function () { + if (this._readableState === undefined || this._writableState === undefined) { + return false; + } + return this._readableState.destroyed && this._writableState.destroyed; + }, + set: function (value) { + // we ignore the value if the stream + // has not been initialized yet + if (this._readableState === undefined || this._writableState === undefined) { + return; + } + + // backward compatibility, the user is explicitly + // managing destroyed + this._readableState.destroyed = value; + this._writableState.destroyed = value; + } +}); + +Duplex.prototype._destroy = function (err, cb) { + this.push(null); + this.end(); + + pna.nextTick(cb, err); +}; +},{"./_stream_readable":73,"./_stream_writable":75,"core-util-is":44,"inherits":56,"process-nextick-args":68}],72:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// a passthrough stream. +// basically just the most minimal sort of Transform stream. +// Every written chunk gets output as-is. + +'use strict'; + +module.exports = PassThrough; + +var Transform = require('./_stream_transform'); + +/**/ +var util = require('core-util-is'); +util.inherits = require('inherits'); +/**/ + +util.inherits(PassThrough, Transform); + +function PassThrough(options) { + if (!(this instanceof PassThrough)) return new PassThrough(options); + + Transform.call(this, options); +} + +PassThrough.prototype._transform = function (chunk, encoding, cb) { + cb(null, chunk); +}; +},{"./_stream_transform":74,"core-util-is":44,"inherits":56}],73:[function(require,module,exports){ +(function (process,global){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; + +/**/ + +var pna = require('process-nextick-args'); +/**/ + +module.exports = Readable; + +/**/ +var isArray = require('isarray'); +/**/ + +/**/ +var Duplex; +/**/ + +Readable.ReadableState = ReadableState; + +/**/ +var EE = require('events').EventEmitter; + +var EElistenerCount = function (emitter, type) { + return emitter.listeners(type).length; +}; +/**/ + +/**/ +var Stream = require('./internal/streams/stream'); +/**/ + +/**/ + +var Buffer = require('safe-buffer').Buffer; +var OurUint8Array = global.Uint8Array || function () {}; +function _uint8ArrayToBuffer(chunk) { + return Buffer.from(chunk); +} +function _isUint8Array(obj) { + return Buffer.isBuffer(obj) || obj instanceof OurUint8Array; +} + +/**/ + +/**/ +var util = require('core-util-is'); +util.inherits = require('inherits'); +/**/ + +/**/ +var debugUtil = require('util'); +var debug = void 0; +if (debugUtil && debugUtil.debuglog) { + debug = debugUtil.debuglog('stream'); +} else { + debug = function () {}; +} +/**/ + +var BufferList = require('./internal/streams/BufferList'); +var destroyImpl = require('./internal/streams/destroy'); +var StringDecoder; + +util.inherits(Readable, Stream); + +var kProxyEvents = ['error', 'close', 'destroy', 'pause', 'resume']; + +function prependListener(emitter, event, fn) { + // Sadly this is not cacheable as some libraries bundle their own + // event emitter implementation with them. + if (typeof emitter.prependListener === 'function') return emitter.prependListener(event, fn); + + // This is a hack to make sure that our error handler is attached before any + // userland ones. NEVER DO THIS. This is here only because this code needs + // to continue to work with older versions of Node.js that do not include + // the prependListener() method. The goal is to eventually remove this hack. + if (!emitter._events || !emitter._events[event]) emitter.on(event, fn);else if (isArray(emitter._events[event])) emitter._events[event].unshift(fn);else emitter._events[event] = [fn, emitter._events[event]]; +} + +function ReadableState(options, stream) { + Duplex = Duplex || require('./_stream_duplex'); + + options = options || {}; + + // Duplex streams are both readable and writable, but share + // the same options object. + // However, some cases require setting options to different + // values for the readable and the writable sides of the duplex stream. + // These options can be provided separately as readableXXX and writableXXX. + var isDuplex = stream instanceof Duplex; + + // object stream flag. Used to make read(n) ignore n and to + // make all the buffer merging and length checks go away + this.objectMode = !!options.objectMode; + + if (isDuplex) this.objectMode = this.objectMode || !!options.readableObjectMode; + + // the point at which it stops calling _read() to fill the buffer + // Note: 0 is a valid value, means "don't call _read preemptively ever" + var hwm = options.highWaterMark; + var readableHwm = options.readableHighWaterMark; + var defaultHwm = this.objectMode ? 16 : 16 * 1024; + + if (hwm || hwm === 0) this.highWaterMark = hwm;else if (isDuplex && (readableHwm || readableHwm === 0)) this.highWaterMark = readableHwm;else this.highWaterMark = defaultHwm; + + // cast to ints. + this.highWaterMark = Math.floor(this.highWaterMark); + + // A linked list is used to store data chunks instead of an array because the + // linked list can remove elements from the beginning faster than + // array.shift() + this.buffer = new BufferList(); + this.length = 0; + this.pipes = null; + this.pipesCount = 0; + this.flowing = null; + this.ended = false; + this.endEmitted = false; + this.reading = false; + + // a flag to be able to tell if the event 'readable'/'data' is emitted + // immediately, or on a later tick. We set this to true at first, because + // any actions that shouldn't happen until "later" should generally also + // not happen before the first read call. + this.sync = true; + + // whenever we return null, then we set a flag to say + // that we're awaiting a 'readable' event emission. + this.needReadable = false; + this.emittedReadable = false; + this.readableListening = false; + this.resumeScheduled = false; + + // has it been destroyed + this.destroyed = false; + + // Crypto is kind of old and crusty. Historically, its default string + // encoding is 'binary' so we have to make this configurable. + // Everything else in the universe uses 'utf8', though. + this.defaultEncoding = options.defaultEncoding || 'utf8'; + + // the number of writers that are awaiting a drain event in .pipe()s + this.awaitDrain = 0; + + // if true, a maybeReadMore has been scheduled + this.readingMore = false; + + this.decoder = null; + this.encoding = null; + if (options.encoding) { + if (!StringDecoder) StringDecoder = require('string_decoder/').StringDecoder; + this.decoder = new StringDecoder(options.encoding); + this.encoding = options.encoding; + } +} + +function Readable(options) { + Duplex = Duplex || require('./_stream_duplex'); + + if (!(this instanceof Readable)) return new Readable(options); + + this._readableState = new ReadableState(options, this); + + // legacy + this.readable = true; + + if (options) { + if (typeof options.read === 'function') this._read = options.read; + + if (typeof options.destroy === 'function') this._destroy = options.destroy; + } + + Stream.call(this); +} + +Object.defineProperty(Readable.prototype, 'destroyed', { + get: function () { + if (this._readableState === undefined) { + return false; + } + return this._readableState.destroyed; + }, + set: function (value) { + // we ignore the value if the stream + // has not been initialized yet + if (!this._readableState) { + return; + } + + // backward compatibility, the user is explicitly + // managing destroyed + this._readableState.destroyed = value; + } +}); + +Readable.prototype.destroy = destroyImpl.destroy; +Readable.prototype._undestroy = destroyImpl.undestroy; +Readable.prototype._destroy = function (err, cb) { + this.push(null); + cb(err); +}; + +// Manually shove something into the read() buffer. +// This returns true if the highWaterMark has not been hit yet, +// similar to how Writable.write() returns true if you should +// write() some more. +Readable.prototype.push = function (chunk, encoding) { + var state = this._readableState; + var skipChunkCheck; + + if (!state.objectMode) { + if (typeof chunk === 'string') { + encoding = encoding || state.defaultEncoding; + if (encoding !== state.encoding) { + chunk = Buffer.from(chunk, encoding); + encoding = ''; + } + skipChunkCheck = true; + } + } else { + skipChunkCheck = true; + } + + return readableAddChunk(this, chunk, encoding, false, skipChunkCheck); +}; + +// Unshift should *always* be something directly out of read() +Readable.prototype.unshift = function (chunk) { + return readableAddChunk(this, chunk, null, true, false); +}; + +function readableAddChunk(stream, chunk, encoding, addToFront, skipChunkCheck) { + var state = stream._readableState; + if (chunk === null) { + state.reading = false; + onEofChunk(stream, state); + } else { + var er; + if (!skipChunkCheck) er = chunkInvalid(state, chunk); + if (er) { + stream.emit('error', er); + } else if (state.objectMode || chunk && chunk.length > 0) { + if (typeof chunk !== 'string' && !state.objectMode && Object.getPrototypeOf(chunk) !== Buffer.prototype) { + chunk = _uint8ArrayToBuffer(chunk); + } + + if (addToFront) { + if (state.endEmitted) stream.emit('error', new Error('stream.unshift() after end event'));else addChunk(stream, state, chunk, true); + } else if (state.ended) { + stream.emit('error', new Error('stream.push() after EOF')); + } else { + state.reading = false; + if (state.decoder && !encoding) { + chunk = state.decoder.write(chunk); + if (state.objectMode || chunk.length !== 0) addChunk(stream, state, chunk, false);else maybeReadMore(stream, state); + } else { + addChunk(stream, state, chunk, false); + } + } + } else if (!addToFront) { + state.reading = false; + } + } + + return needMoreData(state); +} + +function addChunk(stream, state, chunk, addToFront) { + if (state.flowing && state.length === 0 && !state.sync) { + stream.emit('data', chunk); + stream.read(0); + } else { + // update the buffer info. + state.length += state.objectMode ? 1 : chunk.length; + if (addToFront) state.buffer.unshift(chunk);else state.buffer.push(chunk); + + if (state.needReadable) emitReadable(stream); + } + maybeReadMore(stream, state); +} + +function chunkInvalid(state, chunk) { + var er; + if (!_isUint8Array(chunk) && typeof chunk !== 'string' && chunk !== undefined && !state.objectMode) { + er = new TypeError('Invalid non-string/buffer chunk'); + } + return er; +} + +// if it's past the high water mark, we can push in some more. +// Also, if we have no data yet, we can stand some +// more bytes. This is to work around cases where hwm=0, +// such as the repl. Also, if the push() triggered a +// readable event, and the user called read(largeNumber) such that +// needReadable was set, then we ought to push more, so that another +// 'readable' event will be triggered. +function needMoreData(state) { + return !state.ended && (state.needReadable || state.length < state.highWaterMark || state.length === 0); +} + +Readable.prototype.isPaused = function () { + return this._readableState.flowing === false; +}; + +// backwards compatibility. +Readable.prototype.setEncoding = function (enc) { + if (!StringDecoder) StringDecoder = require('string_decoder/').StringDecoder; + this._readableState.decoder = new StringDecoder(enc); + this._readableState.encoding = enc; + return this; +}; + +// Don't raise the hwm > 8MB +var MAX_HWM = 0x800000; +function computeNewHighWaterMark(n) { + if (n >= MAX_HWM) { + n = MAX_HWM; + } else { + // Get the next highest power of 2 to prevent increasing hwm excessively in + // tiny amounts + n--; + n |= n >>> 1; + n |= n >>> 2; + n |= n >>> 4; + n |= n >>> 8; + n |= n >>> 16; + n++; + } + return n; +} + +// This function is designed to be inlinable, so please take care when making +// changes to the function body. +function howMuchToRead(n, state) { + if (n <= 0 || state.length === 0 && state.ended) return 0; + if (state.objectMode) return 1; + if (n !== n) { + // Only flow one buffer at a time + if (state.flowing && state.length) return state.buffer.head.data.length;else return state.length; + } + // If we're asking for more than the current hwm, then raise the hwm. + if (n > state.highWaterMark) state.highWaterMark = computeNewHighWaterMark(n); + if (n <= state.length) return n; + // Don't have enough + if (!state.ended) { + state.needReadable = true; + return 0; + } + return state.length; +} + +// you can override either this method, or the async _read(n) below. +Readable.prototype.read = function (n) { + debug('read', n); + n = parseInt(n, 10); + var state = this._readableState; + var nOrig = n; + + if (n !== 0) state.emittedReadable = false; + + // if we're doing read(0) to trigger a readable event, but we + // already have a bunch of data in the buffer, then just trigger + // the 'readable' event and move on. + if (n === 0 && state.needReadable && (state.length >= state.highWaterMark || state.ended)) { + debug('read: emitReadable', state.length, state.ended); + if (state.length === 0 && state.ended) endReadable(this);else emitReadable(this); + return null; + } + + n = howMuchToRead(n, state); + + // if we've ended, and we're now clear, then finish it up. + if (n === 0 && state.ended) { + if (state.length === 0) endReadable(this); + return null; + } + + // All the actual chunk generation logic needs to be + // *below* the call to _read. The reason is that in certain + // synthetic stream cases, such as passthrough streams, _read + // may be a completely synchronous operation which may change + // the state of the read buffer, providing enough data when + // before there was *not* enough. + // + // So, the steps are: + // 1. Figure out what the state of things will be after we do + // a read from the buffer. + // + // 2. If that resulting state will trigger a _read, then call _read. + // Note that this may be asynchronous, or synchronous. Yes, it is + // deeply ugly to write APIs this way, but that still doesn't mean + // that the Readable class should behave improperly, as streams are + // designed to be sync/async agnostic. + // Take note if the _read call is sync or async (ie, if the read call + // has returned yet), so that we know whether or not it's safe to emit + // 'readable' etc. + // + // 3. Actually pull the requested chunks out of the buffer and return. + + // if we need a readable event, then we need to do some reading. + var doRead = state.needReadable; + debug('need readable', doRead); + + // if we currently have less than the highWaterMark, then also read some + if (state.length === 0 || state.length - n < state.highWaterMark) { + doRead = true; + debug('length less than watermark', doRead); + } + + // however, if we've ended, then there's no point, and if we're already + // reading, then it's unnecessary. + if (state.ended || state.reading) { + doRead = false; + debug('reading or ended', doRead); + } else if (doRead) { + debug('do read'); + state.reading = true; + state.sync = true; + // if the length is currently zero, then we *need* a readable event. + if (state.length === 0) state.needReadable = true; + // call internal read method + this._read(state.highWaterMark); + state.sync = false; + // If _read pushed data synchronously, then `reading` will be false, + // and we need to re-evaluate how much data we can return to the user. + if (!state.reading) n = howMuchToRead(nOrig, state); + } + + var ret; + if (n > 0) ret = fromList(n, state);else ret = null; + + if (ret === null) { + state.needReadable = true; + n = 0; + } else { + state.length -= n; + } + + if (state.length === 0) { + // If we have nothing in the buffer, then we want to know + // as soon as we *do* get something into the buffer. + if (!state.ended) state.needReadable = true; + + // If we tried to read() past the EOF, then emit end on the next tick. + if (nOrig !== n && state.ended) endReadable(this); + } + + if (ret !== null) this.emit('data', ret); + + return ret; +}; + +function onEofChunk(stream, state) { + if (state.ended) return; + if (state.decoder) { + var chunk = state.decoder.end(); + if (chunk && chunk.length) { + state.buffer.push(chunk); + state.length += state.objectMode ? 1 : chunk.length; + } + } + state.ended = true; + + // emit 'readable' now to make sure it gets picked up. + emitReadable(stream); +} + +// Don't emit readable right away in sync mode, because this can trigger +// another read() call => stack overflow. This way, it might trigger +// a nextTick recursion warning, but that's not so bad. +function emitReadable(stream) { + var state = stream._readableState; + state.needReadable = false; + if (!state.emittedReadable) { + debug('emitReadable', state.flowing); + state.emittedReadable = true; + if (state.sync) pna.nextTick(emitReadable_, stream);else emitReadable_(stream); + } +} + +function emitReadable_(stream) { + debug('emit readable'); + stream.emit('readable'); + flow(stream); +} + +// at this point, the user has presumably seen the 'readable' event, +// and called read() to consume some data. that may have triggered +// in turn another _read(n) call, in which case reading = true if +// it's in progress. +// However, if we're not ended, or reading, and the length < hwm, +// then go ahead and try to read some more preemptively. +function maybeReadMore(stream, state) { + if (!state.readingMore) { + state.readingMore = true; + pna.nextTick(maybeReadMore_, stream, state); + } +} + +function maybeReadMore_(stream, state) { + var len = state.length; + while (!state.reading && !state.flowing && !state.ended && state.length < state.highWaterMark) { + debug('maybeReadMore read 0'); + stream.read(0); + if (len === state.length) + // didn't get any data, stop spinning. + break;else len = state.length; + } + state.readingMore = false; +} + +// abstract method. to be overridden in specific implementation classes. +// call cb(er, data) where data is <= n in length. +// for virtual (non-string, non-buffer) streams, "length" is somewhat +// arbitrary, and perhaps not very meaningful. +Readable.prototype._read = function (n) { + this.emit('error', new Error('_read() is not implemented')); +}; + +Readable.prototype.pipe = function (dest, pipeOpts) { + var src = this; + var state = this._readableState; + + switch (state.pipesCount) { + case 0: + state.pipes = dest; + break; + case 1: + state.pipes = [state.pipes, dest]; + break; + default: + state.pipes.push(dest); + break; + } + state.pipesCount += 1; + debug('pipe count=%d opts=%j', state.pipesCount, pipeOpts); + + var doEnd = (!pipeOpts || pipeOpts.end !== false) && dest !== process.stdout && dest !== process.stderr; + + var endFn = doEnd ? onend : unpipe; + if (state.endEmitted) pna.nextTick(endFn);else src.once('end', endFn); + + dest.on('unpipe', onunpipe); + function onunpipe(readable, unpipeInfo) { + debug('onunpipe'); + if (readable === src) { + if (unpipeInfo && unpipeInfo.hasUnpiped === false) { + unpipeInfo.hasUnpiped = true; + cleanup(); + } + } + } + + function onend() { + debug('onend'); + dest.end(); + } + + // when the dest drains, it reduces the awaitDrain counter + // on the source. This would be more elegant with a .once() + // handler in flow(), but adding and removing repeatedly is + // too slow. + var ondrain = pipeOnDrain(src); + dest.on('drain', ondrain); + + var cleanedUp = false; + function cleanup() { + debug('cleanup'); + // cleanup event handlers once the pipe is broken + dest.removeListener('close', onclose); + dest.removeListener('finish', onfinish); + dest.removeListener('drain', ondrain); + dest.removeListener('error', onerror); + dest.removeListener('unpipe', onunpipe); + src.removeListener('end', onend); + src.removeListener('end', unpipe); + src.removeListener('data', ondata); + + cleanedUp = true; + + // if the reader is waiting for a drain event from this + // specific writer, then it would cause it to never start + // flowing again. + // So, if this is awaiting a drain, then we just call it now. + // If we don't know, then assume that we are waiting for one. + if (state.awaitDrain && (!dest._writableState || dest._writableState.needDrain)) ondrain(); + } + + // If the user pushes more data while we're writing to dest then we'll end up + // in ondata again. However, we only want to increase awaitDrain once because + // dest will only emit one 'drain' event for the multiple writes. + // => Introduce a guard on increasing awaitDrain. + var increasedAwaitDrain = false; + src.on('data', ondata); + function ondata(chunk) { + debug('ondata'); + increasedAwaitDrain = false; + var ret = dest.write(chunk); + if (false === ret && !increasedAwaitDrain) { + // If the user unpiped during `dest.write()`, it is possible + // to get stuck in a permanently paused state if that write + // also returned false. + // => Check whether `dest` is still a piping destination. + if ((state.pipesCount === 1 && state.pipes === dest || state.pipesCount > 1 && indexOf(state.pipes, dest) !== -1) && !cleanedUp) { + debug('false write response, pause', src._readableState.awaitDrain); + src._readableState.awaitDrain++; + increasedAwaitDrain = true; + } + src.pause(); + } + } + + // if the dest has an error, then stop piping into it. + // however, don't suppress the throwing behavior for this. + function onerror(er) { + debug('onerror', er); + unpipe(); + dest.removeListener('error', onerror); + if (EElistenerCount(dest, 'error') === 0) dest.emit('error', er); + } + + // Make sure our error handler is attached before userland ones. + prependListener(dest, 'error', onerror); + + // Both close and finish should trigger unpipe, but only once. + function onclose() { + dest.removeListener('finish', onfinish); + unpipe(); + } + dest.once('close', onclose); + function onfinish() { + debug('onfinish'); + dest.removeListener('close', onclose); + unpipe(); + } + dest.once('finish', onfinish); + + function unpipe() { + debug('unpipe'); + src.unpipe(dest); + } + + // tell the dest that it's being piped to + dest.emit('pipe', src); + + // start the flow if it hasn't been started already. + if (!state.flowing) { + debug('pipe resume'); + src.resume(); + } + + return dest; +}; + +function pipeOnDrain(src) { + return function () { + var state = src._readableState; + debug('pipeOnDrain', state.awaitDrain); + if (state.awaitDrain) state.awaitDrain--; + if (state.awaitDrain === 0 && EElistenerCount(src, 'data')) { + state.flowing = true; + flow(src); + } + }; +} + +Readable.prototype.unpipe = function (dest) { + var state = this._readableState; + var unpipeInfo = { hasUnpiped: false }; + + // if we're not piping anywhere, then do nothing. + if (state.pipesCount === 0) return this; + + // just one destination. most common case. + if (state.pipesCount === 1) { + // passed in one, but it's not the right one. + if (dest && dest !== state.pipes) return this; + + if (!dest) dest = state.pipes; + + // got a match. + state.pipes = null; + state.pipesCount = 0; + state.flowing = false; + if (dest) dest.emit('unpipe', this, unpipeInfo); + return this; + } + + // slow case. multiple pipe destinations. + + if (!dest) { + // remove all. + var dests = state.pipes; + var len = state.pipesCount; + state.pipes = null; + state.pipesCount = 0; + state.flowing = false; + + for (var i = 0; i < len; i++) { + dests[i].emit('unpipe', this, unpipeInfo); + }return this; + } + + // try to find the right one. + var index = indexOf(state.pipes, dest); + if (index === -1) return this; + + state.pipes.splice(index, 1); + state.pipesCount -= 1; + if (state.pipesCount === 1) state.pipes = state.pipes[0]; + + dest.emit('unpipe', this, unpipeInfo); + + return this; +}; + +// set up data events if they are asked for +// Ensure readable listeners eventually get something +Readable.prototype.on = function (ev, fn) { + var res = Stream.prototype.on.call(this, ev, fn); + + if (ev === 'data') { + // Start flowing on next tick if stream isn't explicitly paused + if (this._readableState.flowing !== false) this.resume(); + } else if (ev === 'readable') { + var state = this._readableState; + if (!state.endEmitted && !state.readableListening) { + state.readableListening = state.needReadable = true; + state.emittedReadable = false; + if (!state.reading) { + pna.nextTick(nReadingNextTick, this); + } else if (state.length) { + emitReadable(this); + } + } + } + + return res; +}; +Readable.prototype.addListener = Readable.prototype.on; + +function nReadingNextTick(self) { + debug('readable nexttick read 0'); + self.read(0); +} + +// pause() and resume() are remnants of the legacy readable stream API +// If the user uses them, then switch into old mode. +Readable.prototype.resume = function () { + var state = this._readableState; + if (!state.flowing) { + debug('resume'); + state.flowing = true; + resume(this, state); + } + return this; +}; + +function resume(stream, state) { + if (!state.resumeScheduled) { + state.resumeScheduled = true; + pna.nextTick(resume_, stream, state); + } +} + +function resume_(stream, state) { + if (!state.reading) { + debug('resume read 0'); + stream.read(0); + } + + state.resumeScheduled = false; + state.awaitDrain = 0; + stream.emit('resume'); + flow(stream); + if (state.flowing && !state.reading) stream.read(0); +} + +Readable.prototype.pause = function () { + debug('call pause flowing=%j', this._readableState.flowing); + if (false !== this._readableState.flowing) { + debug('pause'); + this._readableState.flowing = false; + this.emit('pause'); + } + return this; +}; + +function flow(stream) { + var state = stream._readableState; + debug('flow', state.flowing); + while (state.flowing && stream.read() !== null) {} +} + +// wrap an old-style stream as the async data source. +// This is *not* part of the readable stream interface. +// It is an ugly unfortunate mess of history. +Readable.prototype.wrap = function (stream) { + var _this = this; + + var state = this._readableState; + var paused = false; + + stream.on('end', function () { + debug('wrapped end'); + if (state.decoder && !state.ended) { + var chunk = state.decoder.end(); + if (chunk && chunk.length) _this.push(chunk); + } + + _this.push(null); + }); + + stream.on('data', function (chunk) { + debug('wrapped data'); + if (state.decoder) chunk = state.decoder.write(chunk); + + // don't skip over falsy values in objectMode + if (state.objectMode && (chunk === null || chunk === undefined)) return;else if (!state.objectMode && (!chunk || !chunk.length)) return; + + var ret = _this.push(chunk); + if (!ret) { + paused = true; + stream.pause(); + } + }); + + // proxy all the other methods. + // important when wrapping filters and duplexes. + for (var i in stream) { + if (this[i] === undefined && typeof stream[i] === 'function') { + this[i] = function (method) { + return function () { + return stream[method].apply(stream, arguments); + }; + }(i); + } + } + + // proxy certain important events. + for (var n = 0; n < kProxyEvents.length; n++) { + stream.on(kProxyEvents[n], this.emit.bind(this, kProxyEvents[n])); + } + + // when we try to consume some more bytes, simply unpause the + // underlying stream. + this._read = function (n) { + debug('wrapped _read', n); + if (paused) { + paused = false; + stream.resume(); + } + }; + + return this; +}; + +Object.defineProperty(Readable.prototype, 'readableHighWaterMark', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function () { + return this._readableState.highWaterMark; + } +}); + +// exposed for testing purposes only. +Readable._fromList = fromList; + +// Pluck off n bytes from an array of buffers. +// Length is the combined lengths of all the buffers in the list. +// This function is designed to be inlinable, so please take care when making +// changes to the function body. +function fromList(n, state) { + // nothing buffered + if (state.length === 0) return null; + + var ret; + if (state.objectMode) ret = state.buffer.shift();else if (!n || n >= state.length) { + // read it all, truncate the list + if (state.decoder) ret = state.buffer.join('');else if (state.buffer.length === 1) ret = state.buffer.head.data;else ret = state.buffer.concat(state.length); + state.buffer.clear(); + } else { + // read part of list + ret = fromListPartial(n, state.buffer, state.decoder); + } + + return ret; +} + +// Extracts only enough buffered data to satisfy the amount requested. +// This function is designed to be inlinable, so please take care when making +// changes to the function body. +function fromListPartial(n, list, hasStrings) { + var ret; + if (n < list.head.data.length) { + // slice is the same for buffers and strings + ret = list.head.data.slice(0, n); + list.head.data = list.head.data.slice(n); + } else if (n === list.head.data.length) { + // first chunk is a perfect match + ret = list.shift(); + } else { + // result spans more than one buffer + ret = hasStrings ? copyFromBufferString(n, list) : copyFromBuffer(n, list); + } + return ret; +} + +// Copies a specified amount of characters from the list of buffered data +// chunks. +// This function is designed to be inlinable, so please take care when making +// changes to the function body. +function copyFromBufferString(n, list) { + var p = list.head; + var c = 1; + var ret = p.data; + n -= ret.length; + while (p = p.next) { + var str = p.data; + var nb = n > str.length ? str.length : n; + if (nb === str.length) ret += str;else ret += str.slice(0, n); + n -= nb; + if (n === 0) { + if (nb === str.length) { + ++c; + if (p.next) list.head = p.next;else list.head = list.tail = null; + } else { + list.head = p; + p.data = str.slice(nb); + } + break; + } + ++c; + } + list.length -= c; + return ret; +} + +// Copies a specified amount of bytes from the list of buffered data chunks. +// This function is designed to be inlinable, so please take care when making +// changes to the function body. +function copyFromBuffer(n, list) { + var ret = Buffer.allocUnsafe(n); + var p = list.head; + var c = 1; + p.data.copy(ret); + n -= p.data.length; + while (p = p.next) { + var buf = p.data; + var nb = n > buf.length ? buf.length : n; + buf.copy(ret, ret.length - n, 0, nb); + n -= nb; + if (n === 0) { + if (nb === buf.length) { + ++c; + if (p.next) list.head = p.next;else list.head = list.tail = null; + } else { + list.head = p; + p.data = buf.slice(nb); + } + break; + } + ++c; + } + list.length -= c; + return ret; +} + +function endReadable(stream) { + var state = stream._readableState; + + // If we get here before consuming all the bytes, then that is a + // bug in node. Should never happen. + if (state.length > 0) throw new Error('"endReadable()" called on non-empty stream'); + + if (!state.endEmitted) { + state.ended = true; + pna.nextTick(endReadableNT, state, stream); + } +} + +function endReadableNT(state, stream) { + // Check that we didn't get one last unshift. + if (!state.endEmitted && state.length === 0) { + state.endEmitted = true; + stream.readable = false; + stream.emit('end'); + } +} + +function indexOf(xs, x) { + for (var i = 0, l = xs.length; i < l; i++) { + if (xs[i] === x) return i; + } + return -1; +} +}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./_stream_duplex":71,"./internal/streams/BufferList":76,"./internal/streams/destroy":77,"./internal/streams/stream":78,"_process":69,"core-util-is":44,"events":50,"inherits":56,"isarray":58,"process-nextick-args":68,"safe-buffer":83,"string_decoder/":85,"util":40}],74:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// a transform stream is a readable/writable stream where you do +// something with the data. Sometimes it's called a "filter", +// but that's not a great name for it, since that implies a thing where +// some bits pass through, and others are simply ignored. (That would +// be a valid example of a transform, of course.) +// +// While the output is causally related to the input, it's not a +// necessarily symmetric or synchronous transformation. For example, +// a zlib stream might take multiple plain-text writes(), and then +// emit a single compressed chunk some time in the future. +// +// Here's how this works: +// +// The Transform stream has all the aspects of the readable and writable +// stream classes. When you write(chunk), that calls _write(chunk,cb) +// internally, and returns false if there's a lot of pending writes +// buffered up. When you call read(), that calls _read(n) until +// there's enough pending readable data buffered up. +// +// In a transform stream, the written data is placed in a buffer. When +// _read(n) is called, it transforms the queued up data, calling the +// buffered _write cb's as it consumes chunks. If consuming a single +// written chunk would result in multiple output chunks, then the first +// outputted bit calls the readcb, and subsequent chunks just go into +// the read buffer, and will cause it to emit 'readable' if necessary. +// +// This way, back-pressure is actually determined by the reading side, +// since _read has to be called to start processing a new chunk. However, +// a pathological inflate type of transform can cause excessive buffering +// here. For example, imagine a stream where every byte of input is +// interpreted as an integer from 0-255, and then results in that many +// bytes of output. Writing the 4 bytes {ff,ff,ff,ff} would result in +// 1kb of data being output. In this case, you could write a very small +// amount of input, and end up with a very large amount of output. In +// such a pathological inflating mechanism, there'd be no way to tell +// the system to stop doing the transform. A single 4MB write could +// cause the system to run out of memory. +// +// However, even in such a pathological case, only a single written chunk +// would be consumed, and then the rest would wait (un-transformed) until +// the results of the previous transformed chunk were consumed. + +'use strict'; + +module.exports = Transform; + +var Duplex = require('./_stream_duplex'); + +/**/ +var util = require('core-util-is'); +util.inherits = require('inherits'); +/**/ + +util.inherits(Transform, Duplex); + +function afterTransform(er, data) { + var ts = this._transformState; + ts.transforming = false; + + var cb = ts.writecb; + + if (!cb) { + return this.emit('error', new Error('write callback called multiple times')); + } + + ts.writechunk = null; + ts.writecb = null; + + if (data != null) // single equals check for both `null` and `undefined` + this.push(data); + + cb(er); + + var rs = this._readableState; + rs.reading = false; + if (rs.needReadable || rs.length < rs.highWaterMark) { + this._read(rs.highWaterMark); + } +} + +function Transform(options) { + if (!(this instanceof Transform)) return new Transform(options); + + Duplex.call(this, options); + + this._transformState = { + afterTransform: afterTransform.bind(this), + needTransform: false, + transforming: false, + writecb: null, + writechunk: null, + writeencoding: null + }; + + // start out asking for a readable event once data is transformed. + this._readableState.needReadable = true; + + // we have implemented the _read method, and done the other things + // that Readable wants before the first _read call, so unset the + // sync guard flag. + this._readableState.sync = false; + + if (options) { + if (typeof options.transform === 'function') this._transform = options.transform; + + if (typeof options.flush === 'function') this._flush = options.flush; + } + + // When the writable side finishes, then flush out anything remaining. + this.on('prefinish', prefinish); +} + +function prefinish() { + var _this = this; + + if (typeof this._flush === 'function') { + this._flush(function (er, data) { + done(_this, er, data); + }); + } else { + done(this, null, null); + } +} + +Transform.prototype.push = function (chunk, encoding) { + this._transformState.needTransform = false; + return Duplex.prototype.push.call(this, chunk, encoding); +}; + +// This is the part where you do stuff! +// override this function in implementation classes. +// 'chunk' is an input chunk. +// +// Call `push(newChunk)` to pass along transformed output +// to the readable side. You may call 'push' zero or more times. +// +// Call `cb(err)` when you are done with this chunk. If you pass +// an error, then that'll put the hurt on the whole operation. If you +// never call cb(), then you'll never get another chunk. +Transform.prototype._transform = function (chunk, encoding, cb) { + throw new Error('_transform() is not implemented'); +}; + +Transform.prototype._write = function (chunk, encoding, cb) { + var ts = this._transformState; + ts.writecb = cb; + ts.writechunk = chunk; + ts.writeencoding = encoding; + if (!ts.transforming) { + var rs = this._readableState; + if (ts.needTransform || rs.needReadable || rs.length < rs.highWaterMark) this._read(rs.highWaterMark); + } +}; + +// Doesn't matter what the args are here. +// _transform does all the work. +// That we got here means that the readable side wants more data. +Transform.prototype._read = function (n) { + var ts = this._transformState; + + if (ts.writechunk !== null && ts.writecb && !ts.transforming) { + ts.transforming = true; + this._transform(ts.writechunk, ts.writeencoding, ts.afterTransform); + } else { + // mark that we need a transform, so that any data that comes in + // will get processed, now that we've asked for it. + ts.needTransform = true; + } +}; + +Transform.prototype._destroy = function (err, cb) { + var _this2 = this; + + Duplex.prototype._destroy.call(this, err, function (err2) { + cb(err2); + _this2.emit('close'); + }); +}; + +function done(stream, er, data) { + if (er) return stream.emit('error', er); + + if (data != null) // single equals check for both `null` and `undefined` + stream.push(data); + + // if there's nothing in the write buffer, then that means + // that nothing more will ever be provided + if (stream._writableState.length) throw new Error('Calling transform done when ws.length != 0'); + + if (stream._transformState.transforming) throw new Error('Calling transform done when still transforming'); + + return stream.push(null); +} +},{"./_stream_duplex":71,"core-util-is":44,"inherits":56}],75:[function(require,module,exports){ +(function (process,global,setImmediate){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// A bit simpler than readable streams. +// Implement an async ._write(chunk, encoding, cb), and it'll handle all +// the drain event emission and buffering. + +'use strict'; + +/**/ + +var pna = require('process-nextick-args'); +/**/ + +module.exports = Writable; + +/* */ +function WriteReq(chunk, encoding, cb) { + this.chunk = chunk; + this.encoding = encoding; + this.callback = cb; + this.next = null; +} + +// It seems a linked list but it is not +// there will be only 2 of these for each stream +function CorkedRequest(state) { + var _this = this; + + this.next = null; + this.entry = null; + this.finish = function () { + onCorkedFinish(_this, state); + }; +} +/* */ + +/**/ +var asyncWrite = !process.browser && ['v0.10', 'v0.9.'].indexOf(process.version.slice(0, 5)) > -1 ? setImmediate : pna.nextTick; +/**/ + +/**/ +var Duplex; +/**/ + +Writable.WritableState = WritableState; + +/**/ +var util = require('core-util-is'); +util.inherits = require('inherits'); +/**/ + +/**/ +var internalUtil = { + deprecate: require('util-deprecate') +}; +/**/ + +/**/ +var Stream = require('./internal/streams/stream'); +/**/ + +/**/ + +var Buffer = require('safe-buffer').Buffer; +var OurUint8Array = global.Uint8Array || function () {}; +function _uint8ArrayToBuffer(chunk) { + return Buffer.from(chunk); +} +function _isUint8Array(obj) { + return Buffer.isBuffer(obj) || obj instanceof OurUint8Array; +} + +/**/ + +var destroyImpl = require('./internal/streams/destroy'); + +util.inherits(Writable, Stream); + +function nop() {} + +function WritableState(options, stream) { + Duplex = Duplex || require('./_stream_duplex'); + + options = options || {}; + + // Duplex streams are both readable and writable, but share + // the same options object. + // However, some cases require setting options to different + // values for the readable and the writable sides of the duplex stream. + // These options can be provided separately as readableXXX and writableXXX. + var isDuplex = stream instanceof Duplex; + + // object stream flag to indicate whether or not this stream + // contains buffers or objects. + this.objectMode = !!options.objectMode; + + if (isDuplex) this.objectMode = this.objectMode || !!options.writableObjectMode; + + // the point at which write() starts returning false + // Note: 0 is a valid value, means that we always return false if + // the entire buffer is not flushed immediately on write() + var hwm = options.highWaterMark; + var writableHwm = options.writableHighWaterMark; + var defaultHwm = this.objectMode ? 16 : 16 * 1024; + + if (hwm || hwm === 0) this.highWaterMark = hwm;else if (isDuplex && (writableHwm || writableHwm === 0)) this.highWaterMark = writableHwm;else this.highWaterMark = defaultHwm; + + // cast to ints. + this.highWaterMark = Math.floor(this.highWaterMark); + + // if _final has been called + this.finalCalled = false; + + // drain event flag. + this.needDrain = false; + // at the start of calling end() + this.ending = false; + // when end() has been called, and returned + this.ended = false; + // when 'finish' is emitted + this.finished = false; + + // has it been destroyed + this.destroyed = false; + + // should we decode strings into buffers before passing to _write? + // this is here so that some node-core streams can optimize string + // handling at a lower level. + var noDecode = options.decodeStrings === false; + this.decodeStrings = !noDecode; + + // Crypto is kind of old and crusty. Historically, its default string + // encoding is 'binary' so we have to make this configurable. + // Everything else in the universe uses 'utf8', though. + this.defaultEncoding = options.defaultEncoding || 'utf8'; + + // not an actual buffer we keep track of, but a measurement + // of how much we're waiting to get pushed to some underlying + // socket or file. + this.length = 0; + + // a flag to see when we're in the middle of a write. + this.writing = false; + + // when true all writes will be buffered until .uncork() call + this.corked = 0; + + // a flag to be able to tell if the onwrite cb is called immediately, + // or on a later tick. We set this to true at first, because any + // actions that shouldn't happen until "later" should generally also + // not happen before the first write call. + this.sync = true; + + // a flag to know if we're processing previously buffered items, which + // may call the _write() callback in the same tick, so that we don't + // end up in an overlapped onwrite situation. + this.bufferProcessing = false; + + // the callback that's passed to _write(chunk,cb) + this.onwrite = function (er) { + onwrite(stream, er); + }; + + // the callback that the user supplies to write(chunk,encoding,cb) + this.writecb = null; + + // the amount that is being written when _write is called. + this.writelen = 0; + + this.bufferedRequest = null; + this.lastBufferedRequest = null; + + // number of pending user-supplied write callbacks + // this must be 0 before 'finish' can be emitted + this.pendingcb = 0; + + // emit prefinish if the only thing we're waiting for is _write cbs + // This is relevant for synchronous Transform streams + this.prefinished = false; + + // True if the error was already emitted and should not be thrown again + this.errorEmitted = false; + + // count buffered requests + this.bufferedRequestCount = 0; + + // allocate the first CorkedRequest, there is always + // one allocated and free to use, and we maintain at most two + this.corkedRequestsFree = new CorkedRequest(this); +} + +WritableState.prototype.getBuffer = function getBuffer() { + var current = this.bufferedRequest; + var out = []; + while (current) { + out.push(current); + current = current.next; + } + return out; +}; + +(function () { + try { + Object.defineProperty(WritableState.prototype, 'buffer', { + get: internalUtil.deprecate(function () { + return this.getBuffer(); + }, '_writableState.buffer is deprecated. Use _writableState.getBuffer ' + 'instead.', 'DEP0003') + }); + } catch (_) {} +})(); + +// Test _writableState for inheritance to account for Duplex streams, +// whose prototype chain only points to Readable. +var realHasInstance; +if (typeof Symbol === 'function' && Symbol.hasInstance && typeof Function.prototype[Symbol.hasInstance] === 'function') { + realHasInstance = Function.prototype[Symbol.hasInstance]; + Object.defineProperty(Writable, Symbol.hasInstance, { + value: function (object) { + if (realHasInstance.call(this, object)) return true; + if (this !== Writable) return false; + + return object && object._writableState instanceof WritableState; + } + }); +} else { + realHasInstance = function (object) { + return object instanceof this; + }; +} + +function Writable(options) { + Duplex = Duplex || require('./_stream_duplex'); + + // Writable ctor is applied to Duplexes, too. + // `realHasInstance` is necessary because using plain `instanceof` + // would return false, as no `_writableState` property is attached. + + // Trying to use the custom `instanceof` for Writable here will also break the + // Node.js LazyTransform implementation, which has a non-trivial getter for + // `_writableState` that would lead to infinite recursion. + if (!realHasInstance.call(Writable, this) && !(this instanceof Duplex)) { + return new Writable(options); + } + + this._writableState = new WritableState(options, this); + + // legacy. + this.writable = true; + + if (options) { + if (typeof options.write === 'function') this._write = options.write; + + if (typeof options.writev === 'function') this._writev = options.writev; + + if (typeof options.destroy === 'function') this._destroy = options.destroy; + + if (typeof options.final === 'function') this._final = options.final; + } + + Stream.call(this); +} + +// Otherwise people can pipe Writable streams, which is just wrong. +Writable.prototype.pipe = function () { + this.emit('error', new Error('Cannot pipe, not readable')); +}; + +function writeAfterEnd(stream, cb) { + var er = new Error('write after end'); + // TODO: defer error events consistently everywhere, not just the cb + stream.emit('error', er); + pna.nextTick(cb, er); +} + +// Checks that a user-supplied chunk is valid, especially for the particular +// mode the stream is in. Currently this means that `null` is never accepted +// and undefined/non-string values are only allowed in object mode. +function validChunk(stream, state, chunk, cb) { + var valid = true; + var er = false; + + if (chunk === null) { + er = new TypeError('May not write null values to stream'); + } else if (typeof chunk !== 'string' && chunk !== undefined && !state.objectMode) { + er = new TypeError('Invalid non-string/buffer chunk'); + } + if (er) { + stream.emit('error', er); + pna.nextTick(cb, er); + valid = false; + } + return valid; +} + +Writable.prototype.write = function (chunk, encoding, cb) { + var state = this._writableState; + var ret = false; + var isBuf = !state.objectMode && _isUint8Array(chunk); + + if (isBuf && !Buffer.isBuffer(chunk)) { + chunk = _uint8ArrayToBuffer(chunk); + } + + if (typeof encoding === 'function') { + cb = encoding; + encoding = null; + } + + if (isBuf) encoding = 'buffer';else if (!encoding) encoding = state.defaultEncoding; + + if (typeof cb !== 'function') cb = nop; + + if (state.ended) writeAfterEnd(this, cb);else if (isBuf || validChunk(this, state, chunk, cb)) { + state.pendingcb++; + ret = writeOrBuffer(this, state, isBuf, chunk, encoding, cb); + } + + return ret; +}; + +Writable.prototype.cork = function () { + var state = this._writableState; + + state.corked++; +}; + +Writable.prototype.uncork = function () { + var state = this._writableState; + + if (state.corked) { + state.corked--; + + if (!state.writing && !state.corked && !state.finished && !state.bufferProcessing && state.bufferedRequest) clearBuffer(this, state); + } +}; + +Writable.prototype.setDefaultEncoding = function setDefaultEncoding(encoding) { + // node::ParseEncoding() requires lower case. + if (typeof encoding === 'string') encoding = encoding.toLowerCase(); + if (!(['hex', 'utf8', 'utf-8', 'ascii', 'binary', 'base64', 'ucs2', 'ucs-2', 'utf16le', 'utf-16le', 'raw'].indexOf((encoding + '').toLowerCase()) > -1)) throw new TypeError('Unknown encoding: ' + encoding); + this._writableState.defaultEncoding = encoding; + return this; +}; + +function decodeChunk(state, chunk, encoding) { + if (!state.objectMode && state.decodeStrings !== false && typeof chunk === 'string') { + chunk = Buffer.from(chunk, encoding); + } + return chunk; +} + +Object.defineProperty(Writable.prototype, 'writableHighWaterMark', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function () { + return this._writableState.highWaterMark; + } +}); + +// if we're already writing something, then just put this +// in the queue, and wait our turn. Otherwise, call _write +// If we return false, then we need a drain event, so set that flag. +function writeOrBuffer(stream, state, isBuf, chunk, encoding, cb) { + if (!isBuf) { + var newChunk = decodeChunk(state, chunk, encoding); + if (chunk !== newChunk) { + isBuf = true; + encoding = 'buffer'; + chunk = newChunk; + } + } + var len = state.objectMode ? 1 : chunk.length; + + state.length += len; + + var ret = state.length < state.highWaterMark; + // we must ensure that previous needDrain will not be reset to false. + if (!ret) state.needDrain = true; + + if (state.writing || state.corked) { + var last = state.lastBufferedRequest; + state.lastBufferedRequest = { + chunk: chunk, + encoding: encoding, + isBuf: isBuf, + callback: cb, + next: null + }; + if (last) { + last.next = state.lastBufferedRequest; + } else { + state.bufferedRequest = state.lastBufferedRequest; + } + state.bufferedRequestCount += 1; + } else { + doWrite(stream, state, false, len, chunk, encoding, cb); + } + + return ret; +} + +function doWrite(stream, state, writev, len, chunk, encoding, cb) { + state.writelen = len; + state.writecb = cb; + state.writing = true; + state.sync = true; + if (writev) stream._writev(chunk, state.onwrite);else stream._write(chunk, encoding, state.onwrite); + state.sync = false; +} + +function onwriteError(stream, state, sync, er, cb) { + --state.pendingcb; + + if (sync) { + // defer the callback if we are being called synchronously + // to avoid piling up things on the stack + pna.nextTick(cb, er); + // this can emit finish, and it will always happen + // after error + pna.nextTick(finishMaybe, stream, state); + stream._writableState.errorEmitted = true; + stream.emit('error', er); + } else { + // the caller expect this to happen before if + // it is async + cb(er); + stream._writableState.errorEmitted = true; + stream.emit('error', er); + // this can emit finish, but finish must + // always follow error + finishMaybe(stream, state); + } +} + +function onwriteStateUpdate(state) { + state.writing = false; + state.writecb = null; + state.length -= state.writelen; + state.writelen = 0; +} + +function onwrite(stream, er) { + var state = stream._writableState; + var sync = state.sync; + var cb = state.writecb; + + onwriteStateUpdate(state); + + if (er) onwriteError(stream, state, sync, er, cb);else { + // Check if we're actually ready to finish, but don't emit yet + var finished = needFinish(state); + + if (!finished && !state.corked && !state.bufferProcessing && state.bufferedRequest) { + clearBuffer(stream, state); + } + + if (sync) { + /**/ + asyncWrite(afterWrite, stream, state, finished, cb); + /**/ + } else { + afterWrite(stream, state, finished, cb); + } + } +} + +function afterWrite(stream, state, finished, cb) { + if (!finished) onwriteDrain(stream, state); + state.pendingcb--; + cb(); + finishMaybe(stream, state); +} + +// Must force callback to be called on nextTick, so that we don't +// emit 'drain' before the write() consumer gets the 'false' return +// value, and has a chance to attach a 'drain' listener. +function onwriteDrain(stream, state) { + if (state.length === 0 && state.needDrain) { + state.needDrain = false; + stream.emit('drain'); + } +} + +// if there's something in the buffer waiting, then process it +function clearBuffer(stream, state) { + state.bufferProcessing = true; + var entry = state.bufferedRequest; + + if (stream._writev && entry && entry.next) { + // Fast case, write everything using _writev() + var l = state.bufferedRequestCount; + var buffer = new Array(l); + var holder = state.corkedRequestsFree; + holder.entry = entry; + + var count = 0; + var allBuffers = true; + while (entry) { + buffer[count] = entry; + if (!entry.isBuf) allBuffers = false; + entry = entry.next; + count += 1; + } + buffer.allBuffers = allBuffers; + + doWrite(stream, state, true, state.length, buffer, '', holder.finish); + + // doWrite is almost always async, defer these to save a bit of time + // as the hot path ends with doWrite + state.pendingcb++; + state.lastBufferedRequest = null; + if (holder.next) { + state.corkedRequestsFree = holder.next; + holder.next = null; + } else { + state.corkedRequestsFree = new CorkedRequest(state); + } + state.bufferedRequestCount = 0; + } else { + // Slow case, write chunks one-by-one + while (entry) { + var chunk = entry.chunk; + var encoding = entry.encoding; + var cb = entry.callback; + var len = state.objectMode ? 1 : chunk.length; + + doWrite(stream, state, false, len, chunk, encoding, cb); + entry = entry.next; + state.bufferedRequestCount--; + // if we didn't call the onwrite immediately, then + // it means that we need to wait until it does. + // also, that means that the chunk and cb are currently + // being processed, so move the buffer counter past them. + if (state.writing) { + break; + } + } + + if (entry === null) state.lastBufferedRequest = null; + } + + state.bufferedRequest = entry; + state.bufferProcessing = false; +} + +Writable.prototype._write = function (chunk, encoding, cb) { + cb(new Error('_write() is not implemented')); +}; + +Writable.prototype._writev = null; + +Writable.prototype.end = function (chunk, encoding, cb) { + var state = this._writableState; + + if (typeof chunk === 'function') { + cb = chunk; + chunk = null; + encoding = null; + } else if (typeof encoding === 'function') { + cb = encoding; + encoding = null; + } + + if (chunk !== null && chunk !== undefined) this.write(chunk, encoding); + + // .end() fully uncorks + if (state.corked) { + state.corked = 1; + this.uncork(); + } + + // ignore unnecessary end() calls. + if (!state.ending && !state.finished) endWritable(this, state, cb); +}; + +function needFinish(state) { + return state.ending && state.length === 0 && state.bufferedRequest === null && !state.finished && !state.writing; +} +function callFinal(stream, state) { + stream._final(function (err) { + state.pendingcb--; + if (err) { + stream.emit('error', err); + } + state.prefinished = true; + stream.emit('prefinish'); + finishMaybe(stream, state); + }); +} +function prefinish(stream, state) { + if (!state.prefinished && !state.finalCalled) { + if (typeof stream._final === 'function') { + state.pendingcb++; + state.finalCalled = true; + pna.nextTick(callFinal, stream, state); + } else { + state.prefinished = true; + stream.emit('prefinish'); + } + } +} + +function finishMaybe(stream, state) { + var need = needFinish(state); + if (need) { + prefinish(stream, state); + if (state.pendingcb === 0) { + state.finished = true; + stream.emit('finish'); + } + } + return need; +} + +function endWritable(stream, state, cb) { + state.ending = true; + finishMaybe(stream, state); + if (cb) { + if (state.finished) pna.nextTick(cb);else stream.once('finish', cb); + } + state.ended = true; + stream.writable = false; +} + +function onCorkedFinish(corkReq, state, err) { + var entry = corkReq.entry; + corkReq.entry = null; + while (entry) { + var cb = entry.callback; + state.pendingcb--; + cb(err); + entry = entry.next; + } + if (state.corkedRequestsFree) { + state.corkedRequestsFree.next = corkReq; + } else { + state.corkedRequestsFree = corkReq; + } +} + +Object.defineProperty(Writable.prototype, 'destroyed', { + get: function () { + if (this._writableState === undefined) { + return false; + } + return this._writableState.destroyed; + }, + set: function (value) { + // we ignore the value if the stream + // has not been initialized yet + if (!this._writableState) { + return; + } + + // backward compatibility, the user is explicitly + // managing destroyed + this._writableState.destroyed = value; + } +}); + +Writable.prototype.destroy = destroyImpl.destroy; +Writable.prototype._undestroy = destroyImpl.undestroy; +Writable.prototype._destroy = function (err, cb) { + this.end(); + cb(err); +}; +}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {},require("timers").setImmediate) +},{"./_stream_duplex":71,"./internal/streams/destroy":77,"./internal/streams/stream":78,"_process":69,"core-util-is":44,"inherits":56,"process-nextick-args":68,"safe-buffer":83,"timers":86,"util-deprecate":87}],76:[function(require,module,exports){ +'use strict'; + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var Buffer = require('safe-buffer').Buffer; +var util = require('util'); + +function copyBuffer(src, target, offset) { + src.copy(target, offset); +} + +module.exports = function () { + function BufferList() { + _classCallCheck(this, BufferList); + + this.head = null; + this.tail = null; + this.length = 0; + } + + BufferList.prototype.push = function push(v) { + var entry = { data: v, next: null }; + if (this.length > 0) this.tail.next = entry;else this.head = entry; + this.tail = entry; + ++this.length; + }; + + BufferList.prototype.unshift = function unshift(v) { + var entry = { data: v, next: this.head }; + if (this.length === 0) this.tail = entry; + this.head = entry; + ++this.length; + }; + + BufferList.prototype.shift = function shift() { + if (this.length === 0) return; + var ret = this.head.data; + if (this.length === 1) this.head = this.tail = null;else this.head = this.head.next; + --this.length; + return ret; + }; + + BufferList.prototype.clear = function clear() { + this.head = this.tail = null; + this.length = 0; + }; + + BufferList.prototype.join = function join(s) { + if (this.length === 0) return ''; + var p = this.head; + var ret = '' + p.data; + while (p = p.next) { + ret += s + p.data; + }return ret; + }; + + BufferList.prototype.concat = function concat(n) { + if (this.length === 0) return Buffer.alloc(0); + if (this.length === 1) return this.head.data; + var ret = Buffer.allocUnsafe(n >>> 0); + var p = this.head; + var i = 0; + while (p) { + copyBuffer(p.data, ret, i); + i += p.data.length; + p = p.next; + } + return ret; + }; + + return BufferList; +}(); + +if (util && util.inspect && util.inspect.custom) { + module.exports.prototype[util.inspect.custom] = function () { + var obj = util.inspect({ length: this.length }); + return this.constructor.name + ' ' + obj; + }; +} +},{"safe-buffer":83,"util":40}],77:[function(require,module,exports){ +'use strict'; + +/**/ + +var pna = require('process-nextick-args'); +/**/ + +// undocumented cb() API, needed for core, not for public API +function destroy(err, cb) { + var _this = this; + + var readableDestroyed = this._readableState && this._readableState.destroyed; + var writableDestroyed = this._writableState && this._writableState.destroyed; + + if (readableDestroyed || writableDestroyed) { + if (cb) { + cb(err); + } else if (err && (!this._writableState || !this._writableState.errorEmitted)) { + pna.nextTick(emitErrorNT, this, err); + } + return this; + } + + // we set destroyed to true before firing error callbacks in order + // to make it re-entrance safe in case destroy() is called within callbacks + + if (this._readableState) { + this._readableState.destroyed = true; + } + + // if this is a duplex stream mark the writable part as destroyed as well + if (this._writableState) { + this._writableState.destroyed = true; + } + + this._destroy(err || null, function (err) { + if (!cb && err) { + pna.nextTick(emitErrorNT, _this, err); + if (_this._writableState) { + _this._writableState.errorEmitted = true; + } + } else if (cb) { + cb(err); + } + }); + + return this; +} + +function undestroy() { + if (this._readableState) { + this._readableState.destroyed = false; + this._readableState.reading = false; + this._readableState.ended = false; + this._readableState.endEmitted = false; + } + + if (this._writableState) { + this._writableState.destroyed = false; + this._writableState.ended = false; + this._writableState.ending = false; + this._writableState.finished = false; + this._writableState.errorEmitted = false; + } +} + +function emitErrorNT(self, err) { + self.emit('error', err); +} + +module.exports = { + destroy: destroy, + undestroy: undestroy +}; +},{"process-nextick-args":68}],78:[function(require,module,exports){ +module.exports = require('events').EventEmitter; + +},{"events":50}],79:[function(require,module,exports){ +module.exports = require('./readable').PassThrough + +},{"./readable":80}],80:[function(require,module,exports){ +exports = module.exports = require('./lib/_stream_readable.js'); +exports.Stream = exports; +exports.Readable = exports; +exports.Writable = require('./lib/_stream_writable.js'); +exports.Duplex = require('./lib/_stream_duplex.js'); +exports.Transform = require('./lib/_stream_transform.js'); +exports.PassThrough = require('./lib/_stream_passthrough.js'); + +},{"./lib/_stream_duplex.js":71,"./lib/_stream_passthrough.js":72,"./lib/_stream_readable.js":73,"./lib/_stream_transform.js":74,"./lib/_stream_writable.js":75}],81:[function(require,module,exports){ +module.exports = require('./readable').Transform + +},{"./readable":80}],82:[function(require,module,exports){ +module.exports = require('./lib/_stream_writable.js'); + +},{"./lib/_stream_writable.js":75}],83:[function(require,module,exports){ +/* eslint-disable node/no-deprecated-api */ +var buffer = require('buffer') +var Buffer = buffer.Buffer + +// alternative to using Object.keys for old browsers +function copyProps (src, dst) { + for (var key in src) { + dst[key] = src[key] + } +} +if (Buffer.from && Buffer.alloc && Buffer.allocUnsafe && Buffer.allocUnsafeSlow) { + module.exports = buffer +} else { + // Copy properties from require('buffer') + copyProps(buffer, exports) + exports.Buffer = SafeBuffer +} + +function SafeBuffer (arg, encodingOrOffset, length) { + return Buffer(arg, encodingOrOffset, length) +} + +// Copy static methods from Buffer +copyProps(Buffer, SafeBuffer) + +SafeBuffer.from = function (arg, encodingOrOffset, length) { + if (typeof arg === 'number') { + throw new TypeError('Argument must not be a number') + } + return Buffer(arg, encodingOrOffset, length) +} + +SafeBuffer.alloc = function (size, fill, encoding) { + if (typeof size !== 'number') { + throw new TypeError('Argument must be a number') + } + var buf = Buffer(size) + if (fill !== undefined) { + if (typeof encoding === 'string') { + buf.fill(fill, encoding) + } else { + buf.fill(fill) + } + } else { + buf.fill(0) + } + return buf +} + +SafeBuffer.allocUnsafe = function (size) { + if (typeof size !== 'number') { + throw new TypeError('Argument must be a number') + } + return Buffer(size) +} + +SafeBuffer.allocUnsafeSlow = function (size) { + if (typeof size !== 'number') { + throw new TypeError('Argument must be a number') + } + return buffer.SlowBuffer(size) +} + +},{"buffer":43}],84:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +module.exports = Stream; + +var EE = require('events').EventEmitter; +var inherits = require('inherits'); + +inherits(Stream, EE); +Stream.Readable = require('readable-stream/readable.js'); +Stream.Writable = require('readable-stream/writable.js'); +Stream.Duplex = require('readable-stream/duplex.js'); +Stream.Transform = require('readable-stream/transform.js'); +Stream.PassThrough = require('readable-stream/passthrough.js'); + +// Backwards-compat with node 0.4.x +Stream.Stream = Stream; + + + +// old-style streams. Note that the pipe method (the only relevant +// part of this class) is overridden in the Readable class. + +function Stream() { + EE.call(this); +} + +Stream.prototype.pipe = function(dest, options) { + var source = this; + + function ondata(chunk) { + if (dest.writable) { + if (false === dest.write(chunk) && source.pause) { + source.pause(); + } + } + } + + source.on('data', ondata); + + function ondrain() { + if (source.readable && source.resume) { + source.resume(); + } + } + + dest.on('drain', ondrain); + + // If the 'end' option is not supplied, dest.end() will be called when + // source gets the 'end' or 'close' events. Only dest.end() once. + if (!dest._isStdio && (!options || options.end !== false)) { + source.on('end', onend); + source.on('close', onclose); + } + + var didOnEnd = false; + function onend() { + if (didOnEnd) return; + didOnEnd = true; + + dest.end(); + } + + + function onclose() { + if (didOnEnd) return; + didOnEnd = true; + + if (typeof dest.destroy === 'function') dest.destroy(); + } + + // don't leave dangling pipes when there are errors. + function onerror(er) { + cleanup(); + if (EE.listenerCount(this, 'error') === 0) { + throw er; // Unhandled stream error in pipe. + } + } + + source.on('error', onerror); + dest.on('error', onerror); + + // remove all the event listeners that were added. + function cleanup() { + source.removeListener('data', ondata); + dest.removeListener('drain', ondrain); + + source.removeListener('end', onend); + source.removeListener('close', onclose); + + source.removeListener('error', onerror); + dest.removeListener('error', onerror); + + source.removeListener('end', cleanup); + source.removeListener('close', cleanup); + + dest.removeListener('close', cleanup); + } + + source.on('end', cleanup); + source.on('close', cleanup); + + dest.on('close', cleanup); + + dest.emit('pipe', source); + + // Allow for unix-like usage: A.pipe(B).pipe(C) + return dest; +}; + +},{"events":50,"inherits":56,"readable-stream/duplex.js":70,"readable-stream/passthrough.js":79,"readable-stream/readable.js":80,"readable-stream/transform.js":81,"readable-stream/writable.js":82}],85:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; + +/**/ + +var Buffer = require('safe-buffer').Buffer; +/**/ + +var isEncoding = Buffer.isEncoding || function (encoding) { + encoding = '' + encoding; + switch (encoding && encoding.toLowerCase()) { + case 'hex':case 'utf8':case 'utf-8':case 'ascii':case 'binary':case 'base64':case 'ucs2':case 'ucs-2':case 'utf16le':case 'utf-16le':case 'raw': + return true; + default: + return false; + } +}; + +function _normalizeEncoding(enc) { + if (!enc) return 'utf8'; + var retried; + while (true) { + switch (enc) { + case 'utf8': + case 'utf-8': + return 'utf8'; + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return 'utf16le'; + case 'latin1': + case 'binary': + return 'latin1'; + case 'base64': + case 'ascii': + case 'hex': + return enc; + default: + if (retried) return; // undefined + enc = ('' + enc).toLowerCase(); + retried = true; + } + } +}; + +// Do not cache `Buffer.isEncoding` when checking encoding names as some +// modules monkey-patch it to support additional encodings +function normalizeEncoding(enc) { + var nenc = _normalizeEncoding(enc); + if (typeof nenc !== 'string' && (Buffer.isEncoding === isEncoding || !isEncoding(enc))) throw new Error('Unknown encoding: ' + enc); + return nenc || enc; +} + +// StringDecoder provides an interface for efficiently splitting a series of +// buffers into a series of JS strings without breaking apart multi-byte +// characters. +exports.StringDecoder = StringDecoder; +function StringDecoder(encoding) { + this.encoding = normalizeEncoding(encoding); + var nb; + switch (this.encoding) { + case 'utf16le': + this.text = utf16Text; + this.end = utf16End; + nb = 4; + break; + case 'utf8': + this.fillLast = utf8FillLast; + nb = 4; + break; + case 'base64': + this.text = base64Text; + this.end = base64End; + nb = 3; + break; + default: + this.write = simpleWrite; + this.end = simpleEnd; + return; + } + this.lastNeed = 0; + this.lastTotal = 0; + this.lastChar = Buffer.allocUnsafe(nb); +} + +StringDecoder.prototype.write = function (buf) { + if (buf.length === 0) return ''; + var r; + var i; + if (this.lastNeed) { + r = this.fillLast(buf); + if (r === undefined) return ''; + i = this.lastNeed; + this.lastNeed = 0; + } else { + i = 0; + } + if (i < buf.length) return r ? r + this.text(buf, i) : this.text(buf, i); + return r || ''; +}; + +StringDecoder.prototype.end = utf8End; + +// Returns only complete characters in a Buffer +StringDecoder.prototype.text = utf8Text; + +// Attempts to complete a partial non-UTF-8 character using bytes from a Buffer +StringDecoder.prototype.fillLast = function (buf) { + if (this.lastNeed <= buf.length) { + buf.copy(this.lastChar, this.lastTotal - this.lastNeed, 0, this.lastNeed); + return this.lastChar.toString(this.encoding, 0, this.lastTotal); + } + buf.copy(this.lastChar, this.lastTotal - this.lastNeed, 0, buf.length); + this.lastNeed -= buf.length; +}; + +// Checks the type of a UTF-8 byte, whether it's ASCII, a leading byte, or a +// continuation byte. If an invalid byte is detected, -2 is returned. +function utf8CheckByte(byte) { + if (byte <= 0x7F) return 0;else if (byte >> 5 === 0x06) return 2;else if (byte >> 4 === 0x0E) return 3;else if (byte >> 3 === 0x1E) return 4; + return byte >> 6 === 0x02 ? -1 : -2; +} + +// Checks at most 3 bytes at the end of a Buffer in order to detect an +// incomplete multi-byte UTF-8 character. The total number of bytes (2, 3, or 4) +// needed to complete the UTF-8 character (if applicable) are returned. +function utf8CheckIncomplete(self, buf, i) { + var j = buf.length - 1; + if (j < i) return 0; + var nb = utf8CheckByte(buf[j]); + if (nb >= 0) { + if (nb > 0) self.lastNeed = nb - 1; + return nb; + } + if (--j < i || nb === -2) return 0; + nb = utf8CheckByte(buf[j]); + if (nb >= 0) { + if (nb > 0) self.lastNeed = nb - 2; + return nb; + } + if (--j < i || nb === -2) return 0; + nb = utf8CheckByte(buf[j]); + if (nb >= 0) { + if (nb > 0) { + if (nb === 2) nb = 0;else self.lastNeed = nb - 3; + } + return nb; + } + return 0; +} + +// Validates as many continuation bytes for a multi-byte UTF-8 character as +// needed or are available. If we see a non-continuation byte where we expect +// one, we "replace" the validated continuation bytes we've seen so far with +// a single UTF-8 replacement character ('\ufffd'), to match v8's UTF-8 decoding +// behavior. The continuation byte check is included three times in the case +// where all of the continuation bytes for a character exist in the same buffer. +// It is also done this way as a slight performance increase instead of using a +// loop. +function utf8CheckExtraBytes(self, buf, p) { + if ((buf[0] & 0xC0) !== 0x80) { + self.lastNeed = 0; + return '\ufffd'; + } + if (self.lastNeed > 1 && buf.length > 1) { + if ((buf[1] & 0xC0) !== 0x80) { + self.lastNeed = 1; + return '\ufffd'; + } + if (self.lastNeed > 2 && buf.length > 2) { + if ((buf[2] & 0xC0) !== 0x80) { + self.lastNeed = 2; + return '\ufffd'; + } + } + } +} + +// Attempts to complete a multi-byte UTF-8 character using bytes from a Buffer. +function utf8FillLast(buf) { + var p = this.lastTotal - this.lastNeed; + var r = utf8CheckExtraBytes(this, buf, p); + if (r !== undefined) return r; + if (this.lastNeed <= buf.length) { + buf.copy(this.lastChar, p, 0, this.lastNeed); + return this.lastChar.toString(this.encoding, 0, this.lastTotal); + } + buf.copy(this.lastChar, p, 0, buf.length); + this.lastNeed -= buf.length; +} + +// Returns all complete UTF-8 characters in a Buffer. If the Buffer ended on a +// partial character, the character's bytes are buffered until the required +// number of bytes are available. +function utf8Text(buf, i) { + var total = utf8CheckIncomplete(this, buf, i); + if (!this.lastNeed) return buf.toString('utf8', i); + this.lastTotal = total; + var end = buf.length - (total - this.lastNeed); + buf.copy(this.lastChar, 0, end); + return buf.toString('utf8', i, end); +} + +// For UTF-8, a replacement character is added when ending on a partial +// character. +function utf8End(buf) { + var r = buf && buf.length ? this.write(buf) : ''; + if (this.lastNeed) return r + '\ufffd'; + return r; +} + +// UTF-16LE typically needs two bytes per character, but even if we have an even +// number of bytes available, we need to check if we end on a leading/high +// surrogate. In that case, we need to wait for the next two bytes in order to +// decode the last character properly. +function utf16Text(buf, i) { + if ((buf.length - i) % 2 === 0) { + var r = buf.toString('utf16le', i); + if (r) { + var c = r.charCodeAt(r.length - 1); + if (c >= 0xD800 && c <= 0xDBFF) { + this.lastNeed = 2; + this.lastTotal = 4; + this.lastChar[0] = buf[buf.length - 2]; + this.lastChar[1] = buf[buf.length - 1]; + return r.slice(0, -1); + } + } + return r; + } + this.lastNeed = 1; + this.lastTotal = 2; + this.lastChar[0] = buf[buf.length - 1]; + return buf.toString('utf16le', i, buf.length - 1); +} + +// For UTF-16LE we do not explicitly append special replacement characters if we +// end on a partial character, we simply let v8 handle that. +function utf16End(buf) { + var r = buf && buf.length ? this.write(buf) : ''; + if (this.lastNeed) { + var end = this.lastTotal - this.lastNeed; + return r + this.lastChar.toString('utf16le', 0, end); + } + return r; +} + +function base64Text(buf, i) { + var n = (buf.length - i) % 3; + if (n === 0) return buf.toString('base64', i); + this.lastNeed = 3 - n; + this.lastTotal = 3; + if (n === 1) { + this.lastChar[0] = buf[buf.length - 1]; + } else { + this.lastChar[0] = buf[buf.length - 2]; + this.lastChar[1] = buf[buf.length - 1]; + } + return buf.toString('base64', i, buf.length - n); +} + +function base64End(buf) { + var r = buf && buf.length ? this.write(buf) : ''; + if (this.lastNeed) return r + this.lastChar.toString('base64', 0, 3 - this.lastNeed); + return r; +} + +// Pass bytes on through for single-byte encodings (e.g. ascii, latin1, hex) +function simpleWrite(buf) { + return buf.toString(this.encoding); +} + +function simpleEnd(buf) { + return buf && buf.length ? this.write(buf) : ''; +} +},{"safe-buffer":83}],86:[function(require,module,exports){ +(function (setImmediate,clearImmediate){ +var nextTick = require('process/browser.js').nextTick; +var apply = Function.prototype.apply; +var slice = Array.prototype.slice; +var immediateIds = {}; +var nextImmediateId = 0; + +// DOM APIs, for completeness + +exports.setTimeout = function() { + return new Timeout(apply.call(setTimeout, window, arguments), clearTimeout); +}; +exports.setInterval = function() { + return new Timeout(apply.call(setInterval, window, arguments), clearInterval); +}; +exports.clearTimeout = +exports.clearInterval = function(timeout) { timeout.close(); }; + +function Timeout(id, clearFn) { + this._id = id; + this._clearFn = clearFn; +} +Timeout.prototype.unref = Timeout.prototype.ref = function() {}; +Timeout.prototype.close = function() { + this._clearFn.call(window, this._id); +}; + +// Does not start the time, just sets up the members needed. +exports.enroll = function(item, msecs) { + clearTimeout(item._idleTimeoutId); + item._idleTimeout = msecs; +}; + +exports.unenroll = function(item) { + clearTimeout(item._idleTimeoutId); + item._idleTimeout = -1; +}; + +exports._unrefActive = exports.active = function(item) { + clearTimeout(item._idleTimeoutId); + + var msecs = item._idleTimeout; + if (msecs >= 0) { + item._idleTimeoutId = setTimeout(function onTimeout() { + if (item._onTimeout) + item._onTimeout(); + }, msecs); + } +}; + +// That's not how node.js implements it but the exposed api is the same. +exports.setImmediate = typeof setImmediate === "function" ? setImmediate : function(fn) { + var id = nextImmediateId++; + var args = arguments.length < 2 ? false : slice.call(arguments, 1); + + immediateIds[id] = true; + + nextTick(function onNextTick() { + if (immediateIds[id]) { + // fn.call() is faster so we optimize for the common use-case + // @see http://jsperf.com/call-apply-segu + if (args) { + fn.apply(null, args); + } else { + fn.call(null); + } + // Prevent ids from leaking + exports.clearImmediate(id); + } + }); + + return id; +}; + +exports.clearImmediate = typeof clearImmediate === "function" ? clearImmediate : function(id) { + delete immediateIds[id]; +}; +}).call(this,require("timers").setImmediate,require("timers").clearImmediate) +},{"process/browser.js":69,"timers":86}],87:[function(require,module,exports){ +(function (global){ + +/** + * Module exports. + */ + +module.exports = deprecate; + +/** + * Mark that a method should not be used. + * Returns a modified function which warns once by default. + * + * If `localStorage.noDeprecation = true` is set, then it is a no-op. + * + * If `localStorage.throwDeprecation = true` is set, then deprecated functions + * will throw an Error when invoked. + * + * If `localStorage.traceDeprecation = true` is set, then deprecated functions + * will invoke `console.trace()` instead of `console.error()`. + * + * @param {Function} fn - the function to deprecate + * @param {String} msg - the string to print to the console when `fn` is invoked + * @returns {Function} a new "deprecated" version of `fn` + * @api public + */ + +function deprecate (fn, msg) { + if (config('noDeprecation')) { + return fn; + } + + var warned = false; + function deprecated() { + if (!warned) { + if (config('throwDeprecation')) { + throw new Error(msg); + } else if (config('traceDeprecation')) { + console.trace(msg); + } else { + console.warn(msg); + } + warned = true; + } + return fn.apply(this, arguments); + } + + return deprecated; +} + +/** + * Checks `localStorage` for boolean values for the given `name`. + * + * @param {String} name + * @returns {Boolean} + * @api private + */ + +function config (name) { + // accessing global.localStorage can trigger a DOMException in sandboxed iframes + try { + if (!global.localStorage) return false; + } catch (_) { + return false; + } + var val = global.localStorage[name]; + if (null == val) return false; + return String(val).toLowerCase() === 'true'; +} + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],88:[function(require,module,exports){ +module.exports = function isBuffer(arg) { + return arg && typeof arg === 'object' + && typeof arg.copy === 'function' + && typeof arg.fill === 'function' + && typeof arg.readUInt8 === 'function'; +} +},{}],89:[function(require,module,exports){ +(function (process,global){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var formatRegExp = /%[sdj%]/g; +exports.format = function(f) { + if (!isString(f)) { + var objects = []; + for (var i = 0; i < arguments.length; i++) { + objects.push(inspect(arguments[i])); + } + return objects.join(' '); + } + + var i = 1; + var args = arguments; + var len = args.length; + var str = String(f).replace(formatRegExp, function(x) { + if (x === '%%') return '%'; + if (i >= len) return x; + switch (x) { + case '%s': return String(args[i++]); + case '%d': return Number(args[i++]); + case '%j': + try { + return JSON.stringify(args[i++]); + } catch (_) { + return '[Circular]'; + } + default: + return x; + } + }); + for (var x = args[i]; i < len; x = args[++i]) { + if (isNull(x) || !isObject(x)) { + str += ' ' + x; + } else { + str += ' ' + inspect(x); + } + } + return str; +}; + + +// Mark that a method should not be used. +// Returns a modified function which warns once by default. +// If --no-deprecation is set, then it is a no-op. +exports.deprecate = function(fn, msg) { + // Allow for deprecating things in the process of starting up. + if (isUndefined(global.process)) { + return function() { + return exports.deprecate(fn, msg).apply(this, arguments); + }; + } + + if (process.noDeprecation === true) { + return fn; + } + + var warned = false; + function deprecated() { + if (!warned) { + if (process.throwDeprecation) { + throw new Error(msg); + } else if (process.traceDeprecation) { + console.trace(msg); + } else { + console.error(msg); + } + warned = true; + } + return fn.apply(this, arguments); + } + + return deprecated; +}; + + +var debugs = {}; +var debugEnviron; +exports.debuglog = function(set) { + if (isUndefined(debugEnviron)) + debugEnviron = process.env.NODE_DEBUG || ''; + set = set.toUpperCase(); + if (!debugs[set]) { + if (new RegExp('\\b' + set + '\\b', 'i').test(debugEnviron)) { + var pid = process.pid; + debugs[set] = function() { + var msg = exports.format.apply(exports, arguments); + console.error('%s %d: %s', set, pid, msg); + }; + } else { + debugs[set] = function() {}; + } + } + return debugs[set]; +}; + + +/** + * Echos the value of a value. Trys to print the value out + * in the best way possible given the different types. + * + * @param {Object} obj The object to print out. + * @param {Object} opts Optional options object that alters the output. + */ +/* legacy: obj, showHidden, depth, colors*/ +function inspect(obj, opts) { + // default options + var ctx = { + seen: [], + stylize: stylizeNoColor + }; + // legacy... + if (arguments.length >= 3) ctx.depth = arguments[2]; + if (arguments.length >= 4) ctx.colors = arguments[3]; + if (isBoolean(opts)) { + // legacy... + ctx.showHidden = opts; + } else if (opts) { + // got an "options" object + exports._extend(ctx, opts); + } + // set default options + if (isUndefined(ctx.showHidden)) ctx.showHidden = false; + if (isUndefined(ctx.depth)) ctx.depth = 2; + if (isUndefined(ctx.colors)) ctx.colors = false; + if (isUndefined(ctx.customInspect)) ctx.customInspect = true; + if (ctx.colors) ctx.stylize = stylizeWithColor; + return formatValue(ctx, obj, ctx.depth); +} +exports.inspect = inspect; + + +// http://en.wikipedia.org/wiki/ANSI_escape_code#graphics +inspect.colors = { + 'bold' : [1, 22], + 'italic' : [3, 23], + 'underline' : [4, 24], + 'inverse' : [7, 27], + 'white' : [37, 39], + 'grey' : [90, 39], + 'black' : [30, 39], + 'blue' : [34, 39], + 'cyan' : [36, 39], + 'green' : [32, 39], + 'magenta' : [35, 39], + 'red' : [31, 39], + 'yellow' : [33, 39] +}; + +// Don't use 'blue' not visible on cmd.exe +inspect.styles = { + 'special': 'cyan', + 'number': 'yellow', + 'boolean': 'yellow', + 'undefined': 'grey', + 'null': 'bold', + 'string': 'green', + 'date': 'magenta', + // "name": intentionally not styling + 'regexp': 'red' +}; + + +function stylizeWithColor(str, styleType) { + var style = inspect.styles[styleType]; + + if (style) { + return '\u001b[' + inspect.colors[style][0] + 'm' + str + + '\u001b[' + inspect.colors[style][1] + 'm'; + } else { + return str; + } +} + + +function stylizeNoColor(str, styleType) { + return str; +} + + +function arrayToHash(array) { + var hash = {}; + + array.forEach(function(val, idx) { + hash[val] = true; + }); + + return hash; +} + + +function formatValue(ctx, value, recurseTimes) { + // Provide a hook for user-specified inspect functions. + // Check that value is an object with an inspect function on it + if (ctx.customInspect && + value && + isFunction(value.inspect) && + // Filter out the util module, it's inspect function is special + value.inspect !== exports.inspect && + // Also filter out any prototype objects using the circular check. + !(value.constructor && value.constructor.prototype === value)) { + var ret = value.inspect(recurseTimes, ctx); + if (!isString(ret)) { + ret = formatValue(ctx, ret, recurseTimes); + } + return ret; + } + + // Primitive types cannot have properties + var primitive = formatPrimitive(ctx, value); + if (primitive) { + return primitive; + } + + // Look up the keys of the object. + var keys = Object.keys(value); + var visibleKeys = arrayToHash(keys); + + if (ctx.showHidden) { + keys = Object.getOwnPropertyNames(value); + } + + // IE doesn't make error fields non-enumerable + // http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx + if (isError(value) + && (keys.indexOf('message') >= 0 || keys.indexOf('description') >= 0)) { + return formatError(value); + } + + // Some type of object without properties can be shortcutted. + if (keys.length === 0) { + if (isFunction(value)) { + var name = value.name ? ': ' + value.name : ''; + return ctx.stylize('[Function' + name + ']', 'special'); + } + if (isRegExp(value)) { + return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); + } + if (isDate(value)) { + return ctx.stylize(Date.prototype.toString.call(value), 'date'); + } + if (isError(value)) { + return formatError(value); + } + } + + var base = '', array = false, braces = ['{', '}']; + + // Make Array say that they are Array + if (isArray(value)) { + array = true; + braces = ['[', ']']; + } + + // Make functions say that they are functions + if (isFunction(value)) { + var n = value.name ? ': ' + value.name : ''; + base = ' [Function' + n + ']'; + } + + // Make RegExps say that they are RegExps + if (isRegExp(value)) { + base = ' ' + RegExp.prototype.toString.call(value); + } + + // Make dates with properties first say the date + if (isDate(value)) { + base = ' ' + Date.prototype.toUTCString.call(value); + } + + // Make error with message first say the error + if (isError(value)) { + base = ' ' + formatError(value); + } + + if (keys.length === 0 && (!array || value.length == 0)) { + return braces[0] + base + braces[1]; + } + + if (recurseTimes < 0) { + if (isRegExp(value)) { + return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); + } else { + return ctx.stylize('[Object]', 'special'); + } + } + + ctx.seen.push(value); + + var output; + if (array) { + output = formatArray(ctx, value, recurseTimes, visibleKeys, keys); + } else { + output = keys.map(function(key) { + return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array); + }); + } + + ctx.seen.pop(); + + return reduceToSingleString(output, base, braces); +} + + +function formatPrimitive(ctx, value) { + if (isUndefined(value)) + return ctx.stylize('undefined', 'undefined'); + if (isString(value)) { + var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '') + .replace(/'/g, "\\'") + .replace(/\\"/g, '"') + '\''; + return ctx.stylize(simple, 'string'); + } + if (isNumber(value)) + return ctx.stylize('' + value, 'number'); + if (isBoolean(value)) + return ctx.stylize('' + value, 'boolean'); + // For some reason typeof null is "object", so special case here. + if (isNull(value)) + return ctx.stylize('null', 'null'); +} + + +function formatError(value) { + return '[' + Error.prototype.toString.call(value) + ']'; +} + + +function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { + var output = []; + for (var i = 0, l = value.length; i < l; ++i) { + if (hasOwnProperty(value, String(i))) { + output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, + String(i), true)); + } else { + output.push(''); + } + } + keys.forEach(function(key) { + if (!key.match(/^\d+$/)) { + output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, + key, true)); + } + }); + return output; +} + + +function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) { + var name, str, desc; + desc = Object.getOwnPropertyDescriptor(value, key) || { value: value[key] }; + if (desc.get) { + if (desc.set) { + str = ctx.stylize('[Getter/Setter]', 'special'); + } else { + str = ctx.stylize('[Getter]', 'special'); + } + } else { + if (desc.set) { + str = ctx.stylize('[Setter]', 'special'); + } + } + if (!hasOwnProperty(visibleKeys, key)) { + name = '[' + key + ']'; + } + if (!str) { + if (ctx.seen.indexOf(desc.value) < 0) { + if (isNull(recurseTimes)) { + str = formatValue(ctx, desc.value, null); + } else { + str = formatValue(ctx, desc.value, recurseTimes - 1); + } + if (str.indexOf('\n') > -1) { + if (array) { + str = str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n').substr(2); + } else { + str = '\n' + str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n'); + } + } + } else { + str = ctx.stylize('[Circular]', 'special'); + } + } + if (isUndefined(name)) { + if (array && key.match(/^\d+$/)) { + return str; + } + name = JSON.stringify('' + key); + if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { + name = name.substr(1, name.length - 2); + name = ctx.stylize(name, 'name'); + } else { + name = name.replace(/'/g, "\\'") + .replace(/\\"/g, '"') + .replace(/(^"|"$)/g, "'"); + name = ctx.stylize(name, 'string'); + } + } + + return name + ': ' + str; +} + + +function reduceToSingleString(output, base, braces) { + var numLinesEst = 0; + var length = output.reduce(function(prev, cur) { + numLinesEst++; + if (cur.indexOf('\n') >= 0) numLinesEst++; + return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1; + }, 0); + + if (length > 60) { + return braces[0] + + (base === '' ? '' : base + '\n ') + + ' ' + + output.join(',\n ') + + ' ' + + braces[1]; + } + + return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; +} + + +// NOTE: These type checking functions intentionally don't use `instanceof` +// because it is fragile and can be easily faked with `Object.create()`. +function isArray(ar) { + return Array.isArray(ar); +} +exports.isArray = isArray; + +function isBoolean(arg) { + return typeof arg === 'boolean'; +} +exports.isBoolean = isBoolean; + +function isNull(arg) { + return arg === null; +} +exports.isNull = isNull; + +function isNullOrUndefined(arg) { + return arg == null; +} +exports.isNullOrUndefined = isNullOrUndefined; + +function isNumber(arg) { + return typeof arg === 'number'; +} +exports.isNumber = isNumber; + +function isString(arg) { + return typeof arg === 'string'; +} +exports.isString = isString; + +function isSymbol(arg) { + return typeof arg === 'symbol'; +} +exports.isSymbol = isSymbol; + +function isUndefined(arg) { + return arg === void 0; +} +exports.isUndefined = isUndefined; + +function isRegExp(re) { + return isObject(re) && objectToString(re) === '[object RegExp]'; +} +exports.isRegExp = isRegExp; + +function isObject(arg) { + return typeof arg === 'object' && arg !== null; +} +exports.isObject = isObject; + +function isDate(d) { + return isObject(d) && objectToString(d) === '[object Date]'; +} +exports.isDate = isDate; + +function isError(e) { + return isObject(e) && + (objectToString(e) === '[object Error]' || e instanceof Error); +} +exports.isError = isError; + +function isFunction(arg) { + return typeof arg === 'function'; +} +exports.isFunction = isFunction; + +function isPrimitive(arg) { + return arg === null || + typeof arg === 'boolean' || + typeof arg === 'number' || + typeof arg === 'string' || + typeof arg === 'symbol' || // ES6 symbol + typeof arg === 'undefined'; +} +exports.isPrimitive = isPrimitive; + +exports.isBuffer = require('./support/isBuffer'); + +function objectToString(o) { + return Object.prototype.toString.call(o); +} + + +function pad(n) { + return n < 10 ? '0' + n.toString(10) : n.toString(10); +} + + +var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', + 'Oct', 'Nov', 'Dec']; + +// 26 Feb 16:19:34 +function timestamp() { + var d = new Date(); + var time = [pad(d.getHours()), + pad(d.getMinutes()), + pad(d.getSeconds())].join(':'); + return [d.getDate(), months[d.getMonth()], time].join(' '); +} + + +// log is just a thin wrapper to console.log that prepends a timestamp +exports.log = function() { + console.log('%s - %s', timestamp(), exports.format.apply(exports, arguments)); +}; + + +/** + * Inherit the prototype methods from one constructor into another. + * + * The Function.prototype.inherits from lang.js rewritten as a standalone + * function (not on Function.prototype). NOTE: If this file is to be loaded + * during bootstrapping this function needs to be rewritten using some native + * functions as prototype setup using normal JavaScript does not work as + * expected during bootstrapping (see mirror.js in r114903). + * + * @param {function} ctor Constructor function which needs to inherit the + * prototype. + * @param {function} superCtor Constructor function to inherit prototype from. + */ +exports.inherits = require('inherits'); + +exports._extend = function(origin, add) { + // Don't do anything if add isn't an object + if (!add || !isObject(add)) return origin; + + var keys = Object.keys(add); + var i = keys.length; + while (i--) { + origin[keys[i]] = add[keys[i]]; + } + return origin; +}; + +function hasOwnProperty(obj, prop) { + return Object.prototype.hasOwnProperty.call(obj, prop); +} + +}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./support/isBuffer":88,"_process":69,"inherits":56}],90:[function(require,module,exports){ +module.exports={ + "name": "mocha", + "version": "7.0.1", + "homepage": "https://mochajs.org/", + "notifyLogo": "https://ibin.co/4QuRuGjXvl36.png" +} +},{}]},{},[1]); \ No newline at end of file diff --git a/app/assets/frontends/beaker-photo-album/.beaker-ui b/app/assets/frontends/beaker-photo-album/.beaker-ui new file mode 100644 index 0000000000..12f79438a9 --- /dev/null +++ b/app/assets/frontends/beaker-photo-album/.beaker-ui @@ -0,0 +1 @@ +builtin:beaker-photo-album diff --git a/app/assets/frontends/beaker-photo-album/ui.css b/app/assets/frontends/beaker-photo-album/ui.css new file mode 100644 index 0000000000..79beb616bb --- /dev/null +++ b/app/assets/frontends/beaker-photo-album/ui.css @@ -0,0 +1,158 @@ +body { + margin: 0 10px; + font-family: 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; +} + +button { + border: 1px solid #6899fb; + border-radius: 4px; + padding: 5px 10px; + color: #2864dc; + outline: 0; + background: #fff; +} + +button:hover { + background: #fafafd; + cursor: pointer; +} + +button.red { + color: red; + border-color: red; +} + +button.noborder { + border: 0; +} + +photo-album-app { + display: block; + max-width: 770px; + margin: 0 auto; +} + +photo-album-app header { + position: relative; + padding: 16px 0 20px; +} + +photo-album-app header h1, +photo-album-app header p { + line-height: 1; + margin: 0; + letter-spacing: 0.5px +} + +photo-album-app header h1 { + font-weight: 500; +} + +photo-album-app header h1 small a { + font-size: 15px; + text-decoration: none; +} + +photo-album-app header h1 small a:hover { + text-decoration: underline; +} + +photo-album-app header p { + margin-top: 5px; +} + +photo-album-app header button { + position: absolute; + bottom: 20px; + right: 0; +} + +photo-album-app input[type="file"] { + display: none; +} + +photo-album-app .photos { + display: grid; + grid-template-columns: repeat(auto-fill, 380px); + grid-gap: 10px; +} + +photo-album-app .photos img { + width: 100%; + height: 200px; + object-fit: cover; + border-radius: 4px; + cursor: pointer; +} + +photo-album-app .photos .empty { + grid-column-end: 3; + grid-column-start: 1; + padding: 130px 0; + text-align: center; + background: #f3f3f8; + color: #667; +} + +photo-album-app dialog[open] { + border: 0; + padding: 0; +} + +photo-album-app dialog > div { + display: grid; + grid-template-columns: auto 300px; +} + +photo-album-app dialog img { + max-width: 60vw; + height: 80vh; + object-fit: contain; + background: #f3f3f8; +} + +photo-album-app dialog .ctrls { + padding: 15px; + background: #fafafd; + text-align: right; + position: absolute; + bottom: 0; + width: 270px; +} + +photo-album-app dialog .description { + margin: 15px; + letter-spacing: 0.5px; + max-height: 70vh; + overflow-y: auto; + font-size: 13px; + white-space: pre-line; +} + +photo-album-app dialog .edit-description { + padding: 15px; + display: none; +} + +photo-album-app dialog .edit-description textarea { + width: 100%; + height: 50vh; + margin-bottom: 10px; + font-size: 13px; + letter-spacing: 0.5px; + resize: none; + outline: 0; +} + +photo-album-app dialog .edit-description .form-actions { + display: flex; + justify-content: space-between; +} + +photo-album-app dialog.editing-description .description { + display: none; +} + +photo-album-app dialog.editing-description .edit-description { + display: block; +} \ No newline at end of file diff --git a/app/assets/frontends/beaker-photo-album/ui.html b/app/assets/frontends/beaker-photo-album/ui.html new file mode 100644 index 0000000000..dfccc2c679 --- /dev/null +++ b/app/assets/frontends/beaker-photo-album/ui.html @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/assets/frontends/beaker-photo-album/ui.js b/app/assets/frontends/beaker-photo-album/ui.js new file mode 100644 index 0000000000..a16624d204 --- /dev/null +++ b/app/assets/frontends/beaker-photo-album/ui.js @@ -0,0 +1,176 @@ +var self = hyperdrive.self + +function h (tag, attrs, ...children) { + var el = document.createElement(tag) + for (let k in attrs) { + if (typeof attrs[k] === 'function') { + el.addEventListener(k, attrs[k]) + } else { + el.setAttribute(k, attrs[k]) + } + } + for (let child of children) el.append(child) + return el +} + +customElements.define('photo-album-app', class extends HTMLElement { + constructor () { + super() + this.siteInfo = undefined + this.photos = [] + } + + connectedCallback () { + this.load() + } + + async load () { + this.siteInfo = await self.getInfo() + this.photos = await self.readdir('/photos').catch(e => ([])) + + this.append(h('header', {}, + h('h1', {}, + this.siteInfo.title || 'Untitled Photo Album', + ' ', + this.siteInfo.writable + ? h('small', {}, h('a', {href: '#', click: this.onEditInfo.bind(this)}, 'edit')) + : '' + ), + (this.siteInfo.description) + ? h('p', {}, this.siteInfo.description) + : '', + this.siteInfo.writable + ? h('button', {click: this.onAdd.bind(this)}, '+ Add Photo') + : '', + h('input', {type: 'file', accept: '.jpg,.jpeg,.png', change: this.onSelectAdded.bind(this)}) + )) + this.append(h('div', {class: 'photos'})) + this.renderPhotos() + } + + renderPhotos () { + var container = this.querySelector('.photos') + container.innerHTML = '' + for (let photo of this.photos) { + container.append( + h('div', {class: 'photo', click: e => this.doViewModal(e, photo)}, + h('img', {src: `/photos/${photo}`, alt: photo}) + ) + ) + } + if (this.photos.length === 0) { + container.append(h('div', {class: 'empty'}, 'This album has no photos')) + } + } + + onAdd () { + this.querySelector('input[type="file"]').click() + } + + onSelectAdded (e) { + var file = e.currentTarget.files[0] + if (!file) return + var fr = new FileReader() + fr.onload = async () => { + var ext = file.name.split('.').pop() + var name = `${Date.now()}.${ext}` + await self.mkdir('/photos').catch(e => undefined) + await self.writeFile(`/photos/${name}`, fr.result, 'binary') + this.photos.push(name) + this.renderPhotos() + + } + fr.readAsArrayBuffer(file) + } + + onRemove (photo) { + if (!confirm('Remove this photo?')) { + return + } + + this.photos.splice(this.photos.indexOf(photo), 1) + // TODO save + this.renderPhotos() + } + + async onEditInfo (e) { + e.preventDefault() + await navigator.drivePropertiesDialog(self.url) + location.reload() + } + + async doViewModal (e, photo) { + e.stopPropagation() + + var existingDialog = this.querySelector('dialog') + if (existingDialog) existingDialog.remove() + + console.log(await self.stat(`/photos/${photo}`)) + var description = (await self.stat(`/photos/${photo}`).catch(e => {}))?.metadata?.description + + var dialog = h('dialog', {}, + h('div', {}, + h('img', {src: `/photos/${photo}`}), + h('div', {}, + this.siteInfo.writable + ? h('div', {class: 'ctrls'}, + h('button', {class: 'red', click: onDelete}, 'Delete Photo') + ) + : '', + h('div', {class: 'description'}, + description ? description : h('em', {}, 'No description'), + ), + this.siteInfo.writable + ? h('div', {class: 'description'}, h('a', {href: '#', click: onShowEditDescription}, 'Edit')) + : '', + h('form', {class: 'edit-description'}, + h('textarea', {}, description || ''), + h('div', {class: 'form-actions'}, + h('button', {class: 'noborder', click: onHideEditDescription}, 'Cancel'), + h('button', {click: onSaveEditDescription}, 'Save') + ) + ) + ) + ) + ) + + function onShowEditDescription (e) { + e.preventDefault() + dialog.classList.add('editing-description') + dialog.querySelector('.edit-description textarea').focus() + } + + function onHideEditDescription (e) { + e.preventDefault() + dialog.classList.remove('editing-description') + } + + async function onSaveEditDescription (e) { + e.preventDefault() + dialog.classList.remove('editing-description') + + description = dialog.querySelector('.edit-description textarea').value + dialog.querySelector('.description').textContent = description + console.log('writing', `/photos/${photo}`, {description}) + await self.updateMetadata(`/photos/${photo}`, {description}) + } + + async function onDelete (e) { + if (!confirm('Delete this photo?')) { + return + } + await self.unlink(`/photos/${photo}`) + location.reload() + } + + this.append(dialog) + dialog.showModal() + } +}) + +document.body.addEventListener('click', e => { + var existingDialog = document.querySelector('dialog') + if (existingDialog && e.path[0] === existingDialog) { + existingDialog.remove() + } +}) \ No newline at end of file diff --git a/app/assets/frontends/beaker-wiki/.beaker-ui b/app/assets/frontends/beaker-wiki/.beaker-ui new file mode 100644 index 0000000000..bb425b8e7b --- /dev/null +++ b/app/assets/frontends/beaker-wiki/.beaker-ui @@ -0,0 +1 @@ +builtin:beaker-wiki \ No newline at end of file diff --git a/app/assets/frontends/beaker-wiki/markdown-it.js b/app/assets/frontends/beaker-wiki/markdown-it.js new file mode 100644 index 0000000000..ca121322d8 --- /dev/null +++ b/app/assets/frontends/beaker-wiki/markdown-it.js @@ -0,0 +1,8157 @@ +/*! markdown-it 10.0.0 https://github.com//markdown-it/markdown-it @license MIT */ +const define = (function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i utf16string } +// +'use strict'; + +/*eslint quotes:0*/ +module.exports = require('entities/lib/maps/entities.json'); + +},{"entities/lib/maps/entities.json":52}],2:[function(require,module,exports){ +// List of valid html blocks names, accorting to commonmark spec +// http://jgm.github.io/CommonMark/spec.html#html-blocks + +'use strict'; + + +module.exports = [ + 'address', + 'article', + 'aside', + 'base', + 'basefont', + 'blockquote', + 'body', + 'caption', + 'center', + 'col', + 'colgroup', + 'dd', + 'details', + 'dialog', + 'dir', + 'div', + 'dl', + 'dt', + 'fieldset', + 'figcaption', + 'figure', + 'footer', + 'form', + 'frame', + 'frameset', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'head', + 'header', + 'hr', + 'html', + 'iframe', + 'legend', + 'li', + 'link', + 'main', + 'menu', + 'menuitem', + 'meta', + 'nav', + 'noframes', + 'ol', + 'optgroup', + 'option', + 'p', + 'param', + 'section', + 'source', + 'summary', + 'table', + 'tbody', + 'td', + 'tfoot', + 'th', + 'thead', + 'title', + 'tr', + 'track', + 'ul' +]; + +},{}],3:[function(require,module,exports){ +// Regexps to match html elements + +'use strict'; + +var attr_name = '[a-zA-Z_:][a-zA-Z0-9:._-]*'; + +var unquoted = '[^"\'=<>`\\x00-\\x20]+'; +var single_quoted = "'[^']*'"; +var double_quoted = '"[^"]*"'; + +var attr_value = '(?:' + unquoted + '|' + single_quoted + '|' + double_quoted + ')'; + +var attribute = '(?:\\s+' + attr_name + '(?:\\s*=\\s*' + attr_value + ')?)'; + +var open_tag = '<[A-Za-z][A-Za-z0-9\\-]*' + attribute + '*\\s*\\/?>'; + +var close_tag = '<\\/[A-Za-z][A-Za-z0-9\\-]*\\s*>'; +var comment = '|'; +var processing = '<[?].*?[?]>'; +var declaration = ']*>'; +var cdata = ''; + +var HTML_TAG_RE = new RegExp('^(?:' + open_tag + '|' + close_tag + '|' + comment + + '|' + processing + '|' + declaration + '|' + cdata + ')'); +var HTML_OPEN_CLOSE_TAG_RE = new RegExp('^(?:' + open_tag + '|' + close_tag + ')'); + +module.exports.HTML_TAG_RE = HTML_TAG_RE; +module.exports.HTML_OPEN_CLOSE_TAG_RE = HTML_OPEN_CLOSE_TAG_RE; + +},{}],4:[function(require,module,exports){ +// Utilities +// +'use strict'; + + +function _class(obj) { return Object.prototype.toString.call(obj); } + +function isString(obj) { return _class(obj) === '[object String]'; } + +var _hasOwnProperty = Object.prototype.hasOwnProperty; + +function has(object, key) { + return _hasOwnProperty.call(object, key); +} + +// Merge objects +// +function assign(obj /*from1, from2, from3, ...*/) { + var sources = Array.prototype.slice.call(arguments, 1); + + sources.forEach(function (source) { + if (!source) { return; } + + if (typeof source !== 'object') { + throw new TypeError(source + 'must be object'); + } + + Object.keys(source).forEach(function (key) { + obj[key] = source[key]; + }); + }); + + return obj; +} + +// Remove element from array and put another array at those position. +// Useful for some operations with tokens +function arrayReplaceAt(src, pos, newElements) { + return [].concat(src.slice(0, pos), newElements, src.slice(pos + 1)); +} + +//////////////////////////////////////////////////////////////////////////////// + +function isValidEntityCode(c) { + /*eslint no-bitwise:0*/ + // broken sequence + if (c >= 0xD800 && c <= 0xDFFF) { return false; } + // never used + if (c >= 0xFDD0 && c <= 0xFDEF) { return false; } + if ((c & 0xFFFF) === 0xFFFF || (c & 0xFFFF) === 0xFFFE) { return false; } + // control codes + if (c >= 0x00 && c <= 0x08) { return false; } + if (c === 0x0B) { return false; } + if (c >= 0x0E && c <= 0x1F) { return false; } + if (c >= 0x7F && c <= 0x9F) { return false; } + // out of range + if (c > 0x10FFFF) { return false; } + return true; +} + +function fromCodePoint(c) { + /*eslint no-bitwise:0*/ + if (c > 0xffff) { + c -= 0x10000; + var surrogate1 = 0xd800 + (c >> 10), + surrogate2 = 0xdc00 + (c & 0x3ff); + + return String.fromCharCode(surrogate1, surrogate2); + } + return String.fromCharCode(c); +} + + +var UNESCAPE_MD_RE = /\\([!"#$%&'()*+,\-.\/:;<=>?@[\\\]^_`{|}~])/g; +var ENTITY_RE = /&([a-z#][a-z0-9]{1,31});/gi; +var UNESCAPE_ALL_RE = new RegExp(UNESCAPE_MD_RE.source + '|' + ENTITY_RE.source, 'gi'); + +var DIGITAL_ENTITY_TEST_RE = /^#((?:x[a-f0-9]{1,8}|[0-9]{1,8}))/i; + +var entities = require('./entities'); + +function replaceEntityPattern(match, name) { + var code = 0; + + if (has(entities, name)) { + return entities[name]; + } + + if (name.charCodeAt(0) === 0x23/* # */ && DIGITAL_ENTITY_TEST_RE.test(name)) { + code = name[1].toLowerCase() === 'x' ? + parseInt(name.slice(2), 16) : parseInt(name.slice(1), 10); + + if (isValidEntityCode(code)) { + return fromCodePoint(code); + } + } + + return match; +} + +/*function replaceEntities(str) { + if (str.indexOf('&') < 0) { return str; } + + return str.replace(ENTITY_RE, replaceEntityPattern); +}*/ + +function unescapeMd(str) { + if (str.indexOf('\\') < 0) { return str; } + return str.replace(UNESCAPE_MD_RE, '$1'); +} + +function unescapeAll(str) { + if (str.indexOf('\\') < 0 && str.indexOf('&') < 0) { return str; } + + return str.replace(UNESCAPE_ALL_RE, function (match, escaped, entity) { + if (escaped) { return escaped; } + return replaceEntityPattern(match, entity); + }); +} + +//////////////////////////////////////////////////////////////////////////////// + +var HTML_ESCAPE_TEST_RE = /[&<>"]/; +var HTML_ESCAPE_REPLACE_RE = /[&<>"]/g; +var HTML_REPLACEMENTS = { + '&': '&', + '<': '<', + '>': '>', + '"': '"' +}; + +function replaceUnsafeChar(ch) { + return HTML_REPLACEMENTS[ch]; +} + +function escapeHtml(str) { + if (HTML_ESCAPE_TEST_RE.test(str)) { + return str.replace(HTML_ESCAPE_REPLACE_RE, replaceUnsafeChar); + } + return str; +} + +//////////////////////////////////////////////////////////////////////////////// + +var REGEXP_ESCAPE_RE = /[.?*+^$[\]\\(){}|-]/g; + +function escapeRE(str) { + return str.replace(REGEXP_ESCAPE_RE, '\\$&'); +} + +//////////////////////////////////////////////////////////////////////////////// + +function isSpace(code) { + switch (code) { + case 0x09: + case 0x20: + return true; + } + return false; +} + +// Zs (unicode class) || [\t\f\v\r\n] +function isWhiteSpace(code) { + if (code >= 0x2000 && code <= 0x200A) { return true; } + switch (code) { + case 0x09: // \t + case 0x0A: // \n + case 0x0B: // \v + case 0x0C: // \f + case 0x0D: // \r + case 0x20: + case 0xA0: + case 0x1680: + case 0x202F: + case 0x205F: + case 0x3000: + return true; + } + return false; +} + +//////////////////////////////////////////////////////////////////////////////// + +/*eslint-disable max-len*/ +var UNICODE_PUNCT_RE = require('uc.micro/categories/P/regex'); + +// Currently without astral characters support. +function isPunctChar(ch) { + return UNICODE_PUNCT_RE.test(ch); +} + + +// Markdown ASCII punctuation characters. +// +// !, ", #, $, %, &, ', (, ), *, +, ,, -, ., /, :, ;, <, =, >, ?, @, [, \, ], ^, _, `, {, |, }, or ~ +// http://spec.commonmark.org/0.15/#ascii-punctuation-character +// +// Don't confuse with unicode punctuation !!! It lacks some chars in ascii range. +// +function isMdAsciiPunct(ch) { + switch (ch) { + case 0x21/* ! */: + case 0x22/* " */: + case 0x23/* # */: + case 0x24/* $ */: + case 0x25/* % */: + case 0x26/* & */: + case 0x27/* ' */: + case 0x28/* ( */: + case 0x29/* ) */: + case 0x2A/* * */: + case 0x2B/* + */: + case 0x2C/* , */: + case 0x2D/* - */: + case 0x2E/* . */: + case 0x2F/* / */: + case 0x3A/* : */: + case 0x3B/* ; */: + case 0x3C/* < */: + case 0x3D/* = */: + case 0x3E/* > */: + case 0x3F/* ? */: + case 0x40/* @ */: + case 0x5B/* [ */: + case 0x5C/* \ */: + case 0x5D/* ] */: + case 0x5E/* ^ */: + case 0x5F/* _ */: + case 0x60/* ` */: + case 0x7B/* { */: + case 0x7C/* | */: + case 0x7D/* } */: + case 0x7E/* ~ */: + return true; + default: + return false; + } +} + +// Hepler to unify [reference labels]. +// +function normalizeReference(str) { + // Trim and collapse whitespace + // + str = str.trim().replace(/\s+/g, ' '); + + // In node v10 'ẞ'.toLowerCase() === 'Ṿ', which is presumed to be a bug + // fixed in v12 (couldn't find any details). + // + // So treat this one as a special case + // (remove this when node v10 is no longer supported). + // + if ('ẞ'.toLowerCase() === 'Ṿ') { + str = str.replace(/ẞ/g, 'ß'); + } + + // .toLowerCase().toUpperCase() should get rid of all differences + // between letter variants. + // + // Simple .toLowerCase() doesn't normalize 125 code points correctly, + // and .toUpperCase doesn't normalize 6 of them (list of exceptions: + // İ, ϴ, ẞ, Ω, K, Å - those are already uppercased, but have differently + // uppercased versions). + // + // Here's an example showing how it happens. Lets take greek letter omega: + // uppercase U+0398 (Θ), U+03f4 (ϴ) and lowercase U+03b8 (θ), U+03d1 (ϑ) + // + // Unicode entries: + // 0398;GREEK CAPITAL LETTER THETA;Lu;0;L;;;;;N;;;;03B8; + // 03B8;GREEK SMALL LETTER THETA;Ll;0;L;;;;;N;;;0398;;0398 + // 03D1;GREEK THETA SYMBOL;Ll;0;L; 03B8;;;;N;GREEK SMALL LETTER SCRIPT THETA;;0398;;0398 + // 03F4;GREEK CAPITAL THETA SYMBOL;Lu;0;L; 0398;;;;N;;;;03B8; + // + // Case-insensitive comparison should treat all of them as equivalent. + // + // But .toLowerCase() doesn't change ϑ (it's already lowercase), + // and .toUpperCase() doesn't change ϴ (already uppercase). + // + // Applying first lower then upper case normalizes any character: + // '\u0398\u03f4\u03b8\u03d1'.toLowerCase().toUpperCase() === '\u0398\u0398\u0398\u0398' + // + // Note: this is equivalent to unicode case folding; unicode normalization + // is a different step that is not required here. + // + // Final result should be uppercased, because it's later stored in an object + // (this avoid a conflict with Object.prototype members, + // most notably, `__proto__`) + // + return str.toLowerCase().toUpperCase(); +} + +//////////////////////////////////////////////////////////////////////////////// + +// Re-export libraries commonly used in both markdown-it and its plugins, +// so plugins won't have to depend on them explicitly, which reduces their +// bundled size (e.g. a browser build). +// +exports.lib = {}; +exports.lib.mdurl = require('mdurl'); +exports.lib.ucmicro = require('uc.micro'); + +exports.assign = assign; +exports.isString = isString; +exports.has = has; +exports.unescapeMd = unescapeMd; +exports.unescapeAll = unescapeAll; +exports.isValidEntityCode = isValidEntityCode; +exports.fromCodePoint = fromCodePoint; +// exports.replaceEntities = replaceEntities; +exports.escapeHtml = escapeHtml; +exports.arrayReplaceAt = arrayReplaceAt; +exports.isSpace = isSpace; +exports.isWhiteSpace = isWhiteSpace; +exports.isMdAsciiPunct = isMdAsciiPunct; +exports.isPunctChar = isPunctChar; +exports.escapeRE = escapeRE; +exports.normalizeReference = normalizeReference; + +},{"./entities":1,"mdurl":58,"uc.micro":65,"uc.micro/categories/P/regex":63}],5:[function(require,module,exports){ +// Just a shortcut for bulk export +'use strict'; + + +exports.parseLinkLabel = require('./parse_link_label'); +exports.parseLinkDestination = require('./parse_link_destination'); +exports.parseLinkTitle = require('./parse_link_title'); + +},{"./parse_link_destination":6,"./parse_link_label":7,"./parse_link_title":8}],6:[function(require,module,exports){ +// Parse link destination +// +'use strict'; + + +var unescapeAll = require('../common/utils').unescapeAll; + + +module.exports = function parseLinkDestination(str, pos, max) { + var code, level, + lines = 0, + start = pos, + result = { + ok: false, + pos: 0, + lines: 0, + str: '' + }; + + if (str.charCodeAt(pos) === 0x3C /* < */) { + pos++; + while (pos < max) { + code = str.charCodeAt(pos); + if (code === 0x0A /* \n */) { return result; } + if (code === 0x3E /* > */) { + result.pos = pos + 1; + result.str = unescapeAll(str.slice(start + 1, pos)); + result.ok = true; + return result; + } + if (code === 0x5C /* \ */ && pos + 1 < max) { + pos += 2; + continue; + } + + pos++; + } + + // no closing '>' + return result; + } + + // this should be ... } else { ... branch + + level = 0; + while (pos < max) { + code = str.charCodeAt(pos); + + if (code === 0x20) { break; } + + // ascii control characters + if (code < 0x20 || code === 0x7F) { break; } + + if (code === 0x5C /* \ */ && pos + 1 < max) { + pos += 2; + continue; + } + + if (code === 0x28 /* ( */) { + level++; + } + + if (code === 0x29 /* ) */) { + if (level === 0) { break; } + level--; + } + + pos++; + } + + if (start === pos) { return result; } + if (level !== 0) { return result; } + + result.str = unescapeAll(str.slice(start, pos)); + result.lines = lines; + result.pos = pos; + result.ok = true; + return result; +}; + +},{"../common/utils":4}],7:[function(require,module,exports){ +// Parse link label +// +// this function assumes that first character ("[") already matches; +// returns the end of the label +// +'use strict'; + +module.exports = function parseLinkLabel(state, start, disableNested) { + var level, found, marker, prevPos, + labelEnd = -1, + max = state.posMax, + oldPos = state.pos; + + state.pos = start + 1; + level = 1; + + while (state.pos < max) { + marker = state.src.charCodeAt(state.pos); + if (marker === 0x5D /* ] */) { + level--; + if (level === 0) { + found = true; + break; + } + } + + prevPos = state.pos; + state.md.inline.skipToken(state); + if (marker === 0x5B /* [ */) { + if (prevPos === state.pos - 1) { + // increase level if we find text `[`, which is not a part of any token + level++; + } else if (disableNested) { + state.pos = oldPos; + return -1; + } + } + } + + if (found) { + labelEnd = state.pos; + } + + // restore old state + state.pos = oldPos; + + return labelEnd; +}; + +},{}],8:[function(require,module,exports){ +// Parse link title +// +'use strict'; + + +var unescapeAll = require('../common/utils').unescapeAll; + + +module.exports = function parseLinkTitle(str, pos, max) { + var code, + marker, + lines = 0, + start = pos, + result = { + ok: false, + pos: 0, + lines: 0, + str: '' + }; + + if (pos >= max) { return result; } + + marker = str.charCodeAt(pos); + + if (marker !== 0x22 /* " */ && marker !== 0x27 /* ' */ && marker !== 0x28 /* ( */) { return result; } + + pos++; + + // if opening marker is "(", switch it to closing marker ")" + if (marker === 0x28) { marker = 0x29; } + + while (pos < max) { + code = str.charCodeAt(pos); + if (code === marker) { + result.pos = pos + 1; + result.lines = lines; + result.str = unescapeAll(str.slice(start + 1, pos)); + result.ok = true; + return result; + } else if (code === 0x0A) { + lines++; + } else if (code === 0x5C /* \ */ && pos + 1 < max) { + pos++; + if (str.charCodeAt(pos) === 0x0A) { + lines++; + } + } + + pos++; + } + + return result; +}; + +},{"../common/utils":4}],9:[function(require,module,exports){ +// Main parser class + +'use strict'; + + +var utils = require('./common/utils'); +var helpers = require('./helpers'); +var Renderer = require('./renderer'); +var ParserCore = require('./parser_core'); +var ParserBlock = require('./parser_block'); +var ParserInline = require('./parser_inline'); +var LinkifyIt = require('linkify-it'); +var mdurl = require('mdurl'); +var punycode = require('punycode'); + + +var config = { + 'default': require('./presets/default'), + zero: require('./presets/zero'), + commonmark: require('./presets/commonmark') +}; + +//////////////////////////////////////////////////////////////////////////////// +// +// This validator can prohibit more than really needed to prevent XSS. It's a +// tradeoff to keep code simple and to be secure by default. +// +// If you need different setup - override validator method as you wish. Or +// replace it with dummy function and use external sanitizer. +// + +var BAD_PROTO_RE = /^(vbscript|javascript|file|data):/; +var GOOD_DATA_RE = /^data:image\/(gif|png|jpeg|webp);/; + +function validateLink(url) { + // url should be normalized at this point, and existing entities are decoded + var str = url.trim().toLowerCase(); + + return BAD_PROTO_RE.test(str) ? (GOOD_DATA_RE.test(str) ? true : false) : true; +} + +//////////////////////////////////////////////////////////////////////////////// + + +var RECODE_HOSTNAME_FOR = [ 'http:', 'https:', 'mailto:' ]; + +function normalizeLink(url) { + var parsed = mdurl.parse(url, true); + + if (parsed.hostname) { + // Encode hostnames in urls like: + // `http://host/`, `https://host/`, `mailto:user@host`, `//host/` + // + // We don't encode unknown schemas, because it's likely that we encode + // something we shouldn't (e.g. `skype:name` treated as `skype:host`) + // + if (!parsed.protocol || RECODE_HOSTNAME_FOR.indexOf(parsed.protocol) >= 0) { + try { + parsed.hostname = punycode.toASCII(parsed.hostname); + } catch (er) { /**/ } + } + } + + return mdurl.encode(mdurl.format(parsed)); +} + +function normalizeLinkText(url) { + var parsed = mdurl.parse(url, true); + + if (parsed.hostname) { + // Encode hostnames in urls like: + // `http://host/`, `https://host/`, `mailto:user@host`, `//host/` + // + // We don't encode unknown schemas, because it's likely that we encode + // something we shouldn't (e.g. `skype:name` treated as `skype:host`) + // + if (!parsed.protocol || RECODE_HOSTNAME_FOR.indexOf(parsed.protocol) >= 0) { + try { + parsed.hostname = punycode.toUnicode(parsed.hostname); + } catch (er) { /**/ } + } + } + + return mdurl.decode(mdurl.format(parsed)); +} + + +/** + * class MarkdownIt + * + * Main parser/renderer class. + * + * ##### Usage + * + * ```javascript + * // node.js, "classic" way: + * var MarkdownIt = require('markdown-it'), + * md = new MarkdownIt(); + * var result = md.render('# markdown-it rulezz!'); + * + * // node.js, the same, but with sugar: + * var md = require('markdown-it')(); + * var result = md.render('# markdown-it rulezz!'); + * + * // browser without AMD, added to "window" on script load + * // Note, there are no dash. + * var md = window.markdownit(); + * var result = md.render('# markdown-it rulezz!'); + * ``` + * + * Single line rendering, without paragraph wrap: + * + * ```javascript + * var md = require('markdown-it')(); + * var result = md.renderInline('__markdown-it__ rulezz!'); + * ``` + **/ + +/** + * new MarkdownIt([presetName, options]) + * - presetName (String): optional, `commonmark` / `zero` + * - options (Object) + * + * Creates parser instanse with given config. Can be called without `new`. + * + * ##### presetName + * + * MarkdownIt provides named presets as a convenience to quickly + * enable/disable active syntax rules and options for common use cases. + * + * - ["commonmark"](https://github.com/markdown-it/markdown-it/blob/master/lib/presets/commonmark.js) - + * configures parser to strict [CommonMark](http://commonmark.org/) mode. + * - [default](https://github.com/markdown-it/markdown-it/blob/master/lib/presets/default.js) - + * similar to GFM, used when no preset name given. Enables all available rules, + * but still without html, typographer & autolinker. + * - ["zero"](https://github.com/markdown-it/markdown-it/blob/master/lib/presets/zero.js) - + * all rules disabled. Useful to quickly setup your config via `.enable()`. + * For example, when you need only `bold` and `italic` markup and nothing else. + * + * ##### options: + * + * - __html__ - `false`. Set `true` to enable HTML tags in source. Be careful! + * That's not safe! You may need external sanitizer to protect output from XSS. + * It's better to extend features via plugins, instead of enabling HTML. + * - __xhtmlOut__ - `false`. Set `true` to add '/' when closing single tags + * (`
    `). This is needed only for full CommonMark compatibility. In real + * world you will need HTML output. + * - __breaks__ - `false`. Set `true` to convert `\n` in paragraphs into `
    `. + * - __langPrefix__ - `language-`. CSS language class prefix for fenced blocks. + * Can be useful for external highlighters. + * - __linkify__ - `false`. Set `true` to autoconvert URL-like text to links. + * - __typographer__ - `false`. Set `true` to enable [some language-neutral + * replacement](https://github.com/markdown-it/markdown-it/blob/master/lib/rules_core/replacements.js) + + * quotes beautification (smartquotes). + * - __quotes__ - `“”‘’`, String or Array. Double + single quotes replacement + * pairs, when typographer enabled and smartquotes on. For example, you can + * use `'«»„“'` for Russian, `'„“‚‘'` for German, and + * `['«\xA0', '\xA0»', '‹\xA0', '\xA0›']` for French (including nbsp). + * - __highlight__ - `null`. Highlighter function for fenced code blocks. + * Highlighter `function (str, lang)` should return escaped HTML. It can also + * return empty string if the source was not changed and should be escaped + * externaly. If result starts with `): + * + * ```javascript + * var hljs = require('highlight.js') // https://highlightjs.org/ + * + * // Actual default values + * var md = require('markdown-it')({ + * highlight: function (str, lang) { + * if (lang && hljs.getLanguage(lang)) { + * try { + * return '
    ' +
    + *                hljs.highlight(lang, str, true).value +
    + *                '
    '; + * } catch (__) {} + * } + * + * return '
    ' + md.utils.escapeHtml(str) + '
    '; + * } + * }); + * ``` + * + **/ +function MarkdownIt(presetName, options) { + if (!(this instanceof MarkdownIt)) { + return new MarkdownIt(presetName, options); + } + + if (!options) { + if (!utils.isString(presetName)) { + options = presetName || {}; + presetName = 'default'; + } + } + + /** + * MarkdownIt#inline -> ParserInline + * + * Instance of [[ParserInline]]. You may need it to add new rules when + * writing plugins. For simple rules control use [[MarkdownIt.disable]] and + * [[MarkdownIt.enable]]. + **/ + this.inline = new ParserInline(); + + /** + * MarkdownIt#block -> ParserBlock + * + * Instance of [[ParserBlock]]. You may need it to add new rules when + * writing plugins. For simple rules control use [[MarkdownIt.disable]] and + * [[MarkdownIt.enable]]. + **/ + this.block = new ParserBlock(); + + /** + * MarkdownIt#core -> Core + * + * Instance of [[Core]] chain executor. You may need it to add new rules when + * writing plugins. For simple rules control use [[MarkdownIt.disable]] and + * [[MarkdownIt.enable]]. + **/ + this.core = new ParserCore(); + + /** + * MarkdownIt#renderer -> Renderer + * + * Instance of [[Renderer]]. Use it to modify output look. Or to add rendering + * rules for new token types, generated by plugins. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')(); + * + * function myToken(tokens, idx, options, env, self) { + * //... + * return result; + * }; + * + * md.renderer.rules['my_token'] = myToken + * ``` + * + * See [[Renderer]] docs and [source code](https://github.com/markdown-it/markdown-it/blob/master/lib/renderer.js). + **/ + this.renderer = new Renderer(); + + /** + * MarkdownIt#linkify -> LinkifyIt + * + * [linkify-it](https://github.com/markdown-it/linkify-it) instance. + * Used by [linkify](https://github.com/markdown-it/markdown-it/blob/master/lib/rules_core/linkify.js) + * rule. + **/ + this.linkify = new LinkifyIt(); + + /** + * MarkdownIt#validateLink(url) -> Boolean + * + * Link validation function. CommonMark allows too much in links. By default + * we disable `javascript:`, `vbscript:`, `file:` schemas, and almost all `data:...` schemas + * except some embedded image types. + * + * You can change this behaviour: + * + * ```javascript + * var md = require('markdown-it')(); + * // enable everything + * md.validateLink = function () { return true; } + * ``` + **/ + this.validateLink = validateLink; + + /** + * MarkdownIt#normalizeLink(url) -> String + * + * Function used to encode link url to a machine-readable format, + * which includes url-encoding, punycode, etc. + **/ + this.normalizeLink = normalizeLink; + + /** + * MarkdownIt#normalizeLinkText(url) -> String + * + * Function used to decode link url to a human-readable format` + **/ + this.normalizeLinkText = normalizeLinkText; + + + // Expose utils & helpers for easy acces from plugins + + /** + * MarkdownIt#utils -> utils + * + * Assorted utility functions, useful to write plugins. See details + * [here](https://github.com/markdown-it/markdown-it/blob/master/lib/common/utils.js). + **/ + this.utils = utils; + + /** + * MarkdownIt#helpers -> helpers + * + * Link components parser functions, useful to write plugins. See details + * [here](https://github.com/markdown-it/markdown-it/blob/master/lib/helpers). + **/ + this.helpers = utils.assign({}, helpers); + + + this.options = {}; + this.configure(presetName); + + if (options) { this.set(options); } +} + + +/** chainable + * MarkdownIt.set(options) + * + * Set parser options (in the same format as in constructor). Probably, you + * will never need it, but you can change options after constructor call. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')() + * .set({ html: true, breaks: true }) + * .set({ typographer, true }); + * ``` + * + * __Note:__ To achieve the best possible performance, don't modify a + * `markdown-it` instance options on the fly. If you need multiple configurations + * it's best to create multiple instances and initialize each with separate + * config. + **/ +MarkdownIt.prototype.set = function (options) { + utils.assign(this.options, options); + return this; +}; + + +/** chainable, internal + * MarkdownIt.configure(presets) + * + * Batch load of all options and compenent settings. This is internal method, + * and you probably will not need it. But if you with - see available presets + * and data structure [here](https://github.com/markdown-it/markdown-it/tree/master/lib/presets) + * + * We strongly recommend to use presets instead of direct config loads. That + * will give better compatibility with next versions. + **/ +MarkdownIt.prototype.configure = function (presets) { + var self = this, presetName; + + if (utils.isString(presets)) { + presetName = presets; + presets = config[presetName]; + if (!presets) { throw new Error('Wrong `markdown-it` preset "' + presetName + '", check name'); } + } + + if (!presets) { throw new Error('Wrong `markdown-it` preset, can\'t be empty'); } + + if (presets.options) { self.set(presets.options); } + + if (presets.components) { + Object.keys(presets.components).forEach(function (name) { + if (presets.components[name].rules) { + self[name].ruler.enableOnly(presets.components[name].rules); + } + if (presets.components[name].rules2) { + self[name].ruler2.enableOnly(presets.components[name].rules2); + } + }); + } + return this; +}; + + +/** chainable + * MarkdownIt.enable(list, ignoreInvalid) + * - list (String|Array): rule name or list of rule names to enable + * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. + * + * Enable list or rules. It will automatically find appropriate components, + * containing rules with given names. If rule not found, and `ignoreInvalid` + * not set - throws exception. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')() + * .enable(['sub', 'sup']) + * .disable('smartquotes'); + * ``` + **/ +MarkdownIt.prototype.enable = function (list, ignoreInvalid) { + var result = []; + + if (!Array.isArray(list)) { list = [ list ]; } + + [ 'core', 'block', 'inline' ].forEach(function (chain) { + result = result.concat(this[chain].ruler.enable(list, true)); + }, this); + + result = result.concat(this.inline.ruler2.enable(list, true)); + + var missed = list.filter(function (name) { return result.indexOf(name) < 0; }); + + if (missed.length && !ignoreInvalid) { + throw new Error('MarkdownIt. Failed to enable unknown rule(s): ' + missed); + } + + return this; +}; + + +/** chainable + * MarkdownIt.disable(list, ignoreInvalid) + * - list (String|Array): rule name or list of rule names to disable. + * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. + * + * The same as [[MarkdownIt.enable]], but turn specified rules off. + **/ +MarkdownIt.prototype.disable = function (list, ignoreInvalid) { + var result = []; + + if (!Array.isArray(list)) { list = [ list ]; } + + [ 'core', 'block', 'inline' ].forEach(function (chain) { + result = result.concat(this[chain].ruler.disable(list, true)); + }, this); + + result = result.concat(this.inline.ruler2.disable(list, true)); + + var missed = list.filter(function (name) { return result.indexOf(name) < 0; }); + + if (missed.length && !ignoreInvalid) { + throw new Error('MarkdownIt. Failed to disable unknown rule(s): ' + missed); + } + return this; +}; + + +/** chainable + * MarkdownIt.use(plugin, params) + * + * Load specified plugin with given params into current parser instance. + * It's just a sugar to call `plugin(md, params)` with curring. + * + * ##### Example + * + * ```javascript + * var iterator = require('markdown-it-for-inline'); + * var md = require('markdown-it')() + * .use(iterator, 'foo_replace', 'text', function (tokens, idx) { + * tokens[idx].content = tokens[idx].content.replace(/foo/g, 'bar'); + * }); + * ``` + **/ +MarkdownIt.prototype.use = function (plugin /*, params, ... */) { + var args = [ this ].concat(Array.prototype.slice.call(arguments, 1)); + plugin.apply(plugin, args); + return this; +}; + + +/** internal + * MarkdownIt.parse(src, env) -> Array + * - src (String): source string + * - env (Object): environment sandbox + * + * Parse input string and returns list of block tokens (special token type + * "inline" will contain list of inline tokens). You should not call this + * method directly, until you write custom renderer (for example, to produce + * AST). + * + * `env` is used to pass data between "distributed" rules and return additional + * metadata like reference info, needed for the renderer. It also can be used to + * inject data in specific cases. Usually, you will be ok to pass `{}`, + * and then pass updated object to renderer. + **/ +MarkdownIt.prototype.parse = function (src, env) { + if (typeof src !== 'string') { + throw new Error('Input data should be a String'); + } + + var state = new this.core.State(src, this, env); + + this.core.process(state); + + return state.tokens; +}; + + +/** + * MarkdownIt.render(src [, env]) -> String + * - src (String): source string + * - env (Object): environment sandbox + * + * Render markdown string into html. It does all magic for you :). + * + * `env` can be used to inject additional metadata (`{}` by default). + * But you will not need it with high probability. See also comment + * in [[MarkdownIt.parse]]. + **/ +MarkdownIt.prototype.render = function (src, env) { + env = env || {}; + + return this.renderer.render(this.parse(src, env), this.options, env); +}; + + +/** internal + * MarkdownIt.parseInline(src, env) -> Array + * - src (String): source string + * - env (Object): environment sandbox + * + * The same as [[MarkdownIt.parse]] but skip all block rules. It returns the + * block tokens list with the single `inline` element, containing parsed inline + * tokens in `children` property. Also updates `env` object. + **/ +MarkdownIt.prototype.parseInline = function (src, env) { + var state = new this.core.State(src, this, env); + + state.inlineMode = true; + this.core.process(state); + + return state.tokens; +}; + + +/** + * MarkdownIt.renderInline(src [, env]) -> String + * - src (String): source string + * - env (Object): environment sandbox + * + * Similar to [[MarkdownIt.render]] but for single paragraph content. Result + * will NOT be wrapped into `

    ` tags. + **/ +MarkdownIt.prototype.renderInline = function (src, env) { + env = env || {}; + + return this.renderer.render(this.parseInline(src, env), this.options, env); +}; + + +module.exports = MarkdownIt; + +},{"./common/utils":4,"./helpers":5,"./parser_block":10,"./parser_core":11,"./parser_inline":12,"./presets/commonmark":13,"./presets/default":14,"./presets/zero":15,"./renderer":16,"linkify-it":53,"mdurl":58,"punycode":60}],10:[function(require,module,exports){ +/** internal + * class ParserBlock + * + * Block-level tokenizer. + **/ +'use strict'; + + +var Ruler = require('./ruler'); + + +var _rules = [ + // First 2 params - rule name & source. Secondary array - list of rules, + // which can be terminated by this one. + [ 'table', require('./rules_block/table'), [ 'paragraph', 'reference' ] ], + [ 'code', require('./rules_block/code') ], + [ 'fence', require('./rules_block/fence'), [ 'paragraph', 'reference', 'blockquote', 'list' ] ], + [ 'blockquote', require('./rules_block/blockquote'), [ 'paragraph', 'reference', 'blockquote', 'list' ] ], + [ 'hr', require('./rules_block/hr'), [ 'paragraph', 'reference', 'blockquote', 'list' ] ], + [ 'list', require('./rules_block/list'), [ 'paragraph', 'reference', 'blockquote' ] ], + [ 'reference', require('./rules_block/reference') ], + [ 'heading', require('./rules_block/heading'), [ 'paragraph', 'reference', 'blockquote' ] ], + [ 'lheading', require('./rules_block/lheading') ], + [ 'html_block', require('./rules_block/html_block'), [ 'paragraph', 'reference', 'blockquote' ] ], + [ 'paragraph', require('./rules_block/paragraph') ] +]; + + +/** + * new ParserBlock() + **/ +function ParserBlock() { + /** + * ParserBlock#ruler -> Ruler + * + * [[Ruler]] instance. Keep configuration of block rules. + **/ + this.ruler = new Ruler(); + + for (var i = 0; i < _rules.length; i++) { + this.ruler.push(_rules[i][0], _rules[i][1], { alt: (_rules[i][2] || []).slice() }); + } +} + + +// Generate tokens for input range +// +ParserBlock.prototype.tokenize = function (state, startLine, endLine) { + var ok, i, + rules = this.ruler.getRules(''), + len = rules.length, + line = startLine, + hasEmptyLines = false, + maxNesting = state.md.options.maxNesting; + + while (line < endLine) { + state.line = line = state.skipEmptyLines(line); + if (line >= endLine) { break; } + + // Termination condition for nested calls. + // Nested calls currently used for blockquotes & lists + if (state.sCount[line] < state.blkIndent) { break; } + + // If nesting level exceeded - skip tail to the end. That's not ordinary + // situation and we should not care about content. + if (state.level >= maxNesting) { + state.line = endLine; + break; + } + + // Try all possible rules. + // On success, rule should: + // + // - update `state.line` + // - update `state.tokens` + // - return true + + for (i = 0; i < len; i++) { + ok = rules[i](state, line, endLine, false); + if (ok) { break; } + } + + // set state.tight if we had an empty line before current tag + // i.e. latest empty line should not count + state.tight = !hasEmptyLines; + + // paragraph might "eat" one newline after it in nested lists + if (state.isEmpty(state.line - 1)) { + hasEmptyLines = true; + } + + line = state.line; + + if (line < endLine && state.isEmpty(line)) { + hasEmptyLines = true; + line++; + state.line = line; + } + } +}; + + +/** + * ParserBlock.parse(str, md, env, outTokens) + * + * Process input string and push block tokens into `outTokens` + **/ +ParserBlock.prototype.parse = function (src, md, env, outTokens) { + var state; + + if (!src) { return; } + + state = new this.State(src, md, env, outTokens); + + this.tokenize(state, state.line, state.lineMax); +}; + + +ParserBlock.prototype.State = require('./rules_block/state_block'); + + +module.exports = ParserBlock; + +},{"./ruler":17,"./rules_block/blockquote":18,"./rules_block/code":19,"./rules_block/fence":20,"./rules_block/heading":21,"./rules_block/hr":22,"./rules_block/html_block":23,"./rules_block/lheading":24,"./rules_block/list":25,"./rules_block/paragraph":26,"./rules_block/reference":27,"./rules_block/state_block":28,"./rules_block/table":29}],11:[function(require,module,exports){ +/** internal + * class Core + * + * Top-level rules executor. Glues block/inline parsers and does intermediate + * transformations. + **/ +'use strict'; + + +var Ruler = require('./ruler'); + + +var _rules = [ + [ 'normalize', require('./rules_core/normalize') ], + [ 'block', require('./rules_core/block') ], + [ 'inline', require('./rules_core/inline') ], + [ 'linkify', require('./rules_core/linkify') ], + [ 'replacements', require('./rules_core/replacements') ], + [ 'smartquotes', require('./rules_core/smartquotes') ] +]; + + +/** + * new Core() + **/ +function Core() { + /** + * Core#ruler -> Ruler + * + * [[Ruler]] instance. Keep configuration of core rules. + **/ + this.ruler = new Ruler(); + + for (var i = 0; i < _rules.length; i++) { + this.ruler.push(_rules[i][0], _rules[i][1]); + } +} + + +/** + * Core.process(state) + * + * Executes core chain rules. + **/ +Core.prototype.process = function (state) { + var i, l, rules; + + rules = this.ruler.getRules(''); + + for (i = 0, l = rules.length; i < l; i++) { + rules[i](state); + } +}; + +Core.prototype.State = require('./rules_core/state_core'); + + +module.exports = Core; + +},{"./ruler":17,"./rules_core/block":30,"./rules_core/inline":31,"./rules_core/linkify":32,"./rules_core/normalize":33,"./rules_core/replacements":34,"./rules_core/smartquotes":35,"./rules_core/state_core":36}],12:[function(require,module,exports){ +/** internal + * class ParserInline + * + * Tokenizes paragraph content. + **/ +'use strict'; + + +var Ruler = require('./ruler'); + + +//////////////////////////////////////////////////////////////////////////////// +// Parser rules + +var _rules = [ + [ 'text', require('./rules_inline/text') ], + [ 'newline', require('./rules_inline/newline') ], + [ 'escape', require('./rules_inline/escape') ], + [ 'backticks', require('./rules_inline/backticks') ], + [ 'strikethrough', require('./rules_inline/strikethrough').tokenize ], + [ 'emphasis', require('./rules_inline/emphasis').tokenize ], + [ 'link', require('./rules_inline/link') ], + [ 'image', require('./rules_inline/image') ], + [ 'autolink', require('./rules_inline/autolink') ], + [ 'html_inline', require('./rules_inline/html_inline') ], + [ 'entity', require('./rules_inline/entity') ] +]; + +var _rules2 = [ + [ 'balance_pairs', require('./rules_inline/balance_pairs') ], + [ 'strikethrough', require('./rules_inline/strikethrough').postProcess ], + [ 'emphasis', require('./rules_inline/emphasis').postProcess ], + [ 'text_collapse', require('./rules_inline/text_collapse') ] +]; + + +/** + * new ParserInline() + **/ +function ParserInline() { + var i; + + /** + * ParserInline#ruler -> Ruler + * + * [[Ruler]] instance. Keep configuration of inline rules. + **/ + this.ruler = new Ruler(); + + for (i = 0; i < _rules.length; i++) { + this.ruler.push(_rules[i][0], _rules[i][1]); + } + + /** + * ParserInline#ruler2 -> Ruler + * + * [[Ruler]] instance. Second ruler used for post-processing + * (e.g. in emphasis-like rules). + **/ + this.ruler2 = new Ruler(); + + for (i = 0; i < _rules2.length; i++) { + this.ruler2.push(_rules2[i][0], _rules2[i][1]); + } +} + + +// Skip single token by running all rules in validation mode; +// returns `true` if any rule reported success +// +ParserInline.prototype.skipToken = function (state) { + var ok, i, pos = state.pos, + rules = this.ruler.getRules(''), + len = rules.length, + maxNesting = state.md.options.maxNesting, + cache = state.cache; + + + if (typeof cache[pos] !== 'undefined') { + state.pos = cache[pos]; + return; + } + + if (state.level < maxNesting) { + for (i = 0; i < len; i++) { + // Increment state.level and decrement it later to limit recursion. + // It's harmless to do here, because no tokens are created. But ideally, + // we'd need a separate private state variable for this purpose. + // + state.level++; + ok = rules[i](state, true); + state.level--; + + if (ok) { break; } + } + } else { + // Too much nesting, just skip until the end of the paragraph. + // + // NOTE: this will cause links to behave incorrectly in the following case, + // when an amount of `[` is exactly equal to `maxNesting + 1`: + // + // [[[[[[[[[[[[[[[[[[[[[foo]() + // + // TODO: remove this workaround when CM standard will allow nested links + // (we can replace it by preventing links from being parsed in + // validation mode) + // + state.pos = state.posMax; + } + + if (!ok) { state.pos++; } + cache[pos] = state.pos; +}; + + +// Generate tokens for input range +// +ParserInline.prototype.tokenize = function (state) { + var ok, i, + rules = this.ruler.getRules(''), + len = rules.length, + end = state.posMax, + maxNesting = state.md.options.maxNesting; + + while (state.pos < end) { + // Try all possible rules. + // On success, rule should: + // + // - update `state.pos` + // - update `state.tokens` + // - return true + + if (state.level < maxNesting) { + for (i = 0; i < len; i++) { + ok = rules[i](state, false); + if (ok) { break; } + } + } + + if (ok) { + if (state.pos >= end) { break; } + continue; + } + + state.pending += state.src[state.pos++]; + } + + if (state.pending) { + state.pushPending(); + } +}; + + +/** + * ParserInline.parse(str, md, env, outTokens) + * + * Process input string and push inline tokens into `outTokens` + **/ +ParserInline.prototype.parse = function (str, md, env, outTokens) { + var i, rules, len; + var state = new this.State(str, md, env, outTokens); + + this.tokenize(state); + + rules = this.ruler2.getRules(''); + len = rules.length; + + for (i = 0; i < len; i++) { + rules[i](state); + } +}; + + +ParserInline.prototype.State = require('./rules_inline/state_inline'); + + +module.exports = ParserInline; + +},{"./ruler":17,"./rules_inline/autolink":37,"./rules_inline/backticks":38,"./rules_inline/balance_pairs":39,"./rules_inline/emphasis":40,"./rules_inline/entity":41,"./rules_inline/escape":42,"./rules_inline/html_inline":43,"./rules_inline/image":44,"./rules_inline/link":45,"./rules_inline/newline":46,"./rules_inline/state_inline":47,"./rules_inline/strikethrough":48,"./rules_inline/text":49,"./rules_inline/text_collapse":50}],13:[function(require,module,exports){ +// Commonmark default options + +'use strict'; + + +module.exports = { + options: { + html: true, // Enable HTML tags in source + xhtmlOut: true, // Use '/' to close single tags (
    ) + breaks: false, // Convert '\n' in paragraphs into
    + langPrefix: 'language-', // CSS language prefix for fenced blocks + linkify: false, // autoconvert URL-like texts to links + + // Enable some language-neutral replacements + quotes beautification + typographer: false, + + // Double + single quotes replacement pairs, when typographer enabled, + // and smartquotes on. Could be either a String or an Array. + // + // For example, you can use '«»„“' for Russian, '„“‚‘' for German, + // and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp). + quotes: '\u201c\u201d\u2018\u2019', /* “”‘’ */ + + // Highlighter function. Should return escaped HTML, + // or '' if the source string is not changed and should be escaped externaly. + // If result starts with ) + breaks: false, // Convert '\n' in paragraphs into
    + langPrefix: 'language-', // CSS language prefix for fenced blocks + linkify: false, // autoconvert URL-like texts to links + + // Enable some language-neutral replacements + quotes beautification + typographer: false, + + // Double + single quotes replacement pairs, when typographer enabled, + // and smartquotes on. Could be either a String or an Array. + // + // For example, you can use '«»„“' for Russian, '„“‚‘' for German, + // and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp). + quotes: '\u201c\u201d\u2018\u2019', /* “”‘’ */ + + // Highlighter function. Should return escaped HTML, + // or '' if the source string is not changed and should be escaped externaly. + // If result starts with ) + breaks: false, // Convert '\n' in paragraphs into
    + langPrefix: 'language-', // CSS language prefix for fenced blocks + linkify: false, // autoconvert URL-like texts to links + + // Enable some language-neutral replacements + quotes beautification + typographer: false, + + // Double + single quotes replacement pairs, when typographer enabled, + // and smartquotes on. Could be either a String or an Array. + // + // For example, you can use '«»„“' for Russian, '„“‚‘' for German, + // and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp). + quotes: '\u201c\u201d\u2018\u2019', /* “”‘’ */ + + // Highlighter function. Should return escaped HTML, + // or '' if the source string is not changed and should be escaped externaly. + // If result starts with ' + + escapeHtml(tokens[idx].content) + + ''; +}; + + +default_rules.code_block = function (tokens, idx, options, env, slf) { + var token = tokens[idx]; + + return '' + + escapeHtml(tokens[idx].content) + + '\n'; +}; + + +default_rules.fence = function (tokens, idx, options, env, slf) { + var token = tokens[idx], + info = token.info ? unescapeAll(token.info).trim() : '', + langName = '', + highlighted, i, tmpAttrs, tmpToken; + + if (info) { + langName = info.split(/\s+/g)[0]; + } + + if (options.highlight) { + highlighted = options.highlight(token.content, langName) || escapeHtml(token.content); + } else { + highlighted = escapeHtml(token.content); + } + + if (highlighted.indexOf('' + + highlighted + + '\n'; + } + + + return '

    '
    +        + highlighted
    +        + '
    \n'; +}; + + +default_rules.image = function (tokens, idx, options, env, slf) { + var token = tokens[idx]; + + // "alt" attr MUST be set, even if empty. Because it's mandatory and + // should be placed on proper position for tests. + // + // Replace content with actual value + + token.attrs[token.attrIndex('alt')][1] = + slf.renderInlineAsText(token.children, options, env); + + return slf.renderToken(tokens, idx, options); +}; + + +default_rules.hardbreak = function (tokens, idx, options /*, env */) { + return options.xhtmlOut ? '
    \n' : '
    \n'; +}; +default_rules.softbreak = function (tokens, idx, options /*, env */) { + return options.breaks ? (options.xhtmlOut ? '
    \n' : '
    \n') : '\n'; +}; + + +default_rules.text = function (tokens, idx /*, options, env */) { + return escapeHtml(tokens[idx].content); +}; + + +default_rules.html_block = function (tokens, idx /*, options, env */) { + return tokens[idx].content; +}; +default_rules.html_inline = function (tokens, idx /*, options, env */) { + return tokens[idx].content; +}; + + +/** + * new Renderer() + * + * Creates new [[Renderer]] instance and fill [[Renderer#rules]] with defaults. + **/ +function Renderer() { + + /** + * Renderer#rules -> Object + * + * Contains render rules for tokens. Can be updated and extended. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')(); + * + * md.renderer.rules.strong_open = function () { return ''; }; + * md.renderer.rules.strong_close = function () { return ''; }; + * + * var result = md.renderInline(...); + * ``` + * + * Each rule is called as independent static function with fixed signature: + * + * ```javascript + * function my_token_render(tokens, idx, options, env, renderer) { + * // ... + * return renderedHTML; + * } + * ``` + * + * See [source code](https://github.com/markdown-it/markdown-it/blob/master/lib/renderer.js) + * for more details and examples. + **/ + this.rules = assign({}, default_rules); +} + + +/** + * Renderer.renderAttrs(token) -> String + * + * Render token attributes to string. + **/ +Renderer.prototype.renderAttrs = function renderAttrs(token) { + var i, l, result; + + if (!token.attrs) { return ''; } + + result = ''; + + for (i = 0, l = token.attrs.length; i < l; i++) { + result += ' ' + escapeHtml(token.attrs[i][0]) + '="' + escapeHtml(token.attrs[i][1]) + '"'; + } + + return result; +}; + + +/** + * Renderer.renderToken(tokens, idx, options) -> String + * - tokens (Array): list of tokens + * - idx (Numbed): token index to render + * - options (Object): params of parser instance + * + * Default token renderer. Can be overriden by custom function + * in [[Renderer#rules]]. + **/ +Renderer.prototype.renderToken = function renderToken(tokens, idx, options) { + var nextToken, + result = '', + needLf = false, + token = tokens[idx]; + + // Tight list paragraphs + if (token.hidden) { + return ''; + } + + // Insert a newline between hidden paragraph and subsequent opening + // block-level tag. + // + // For example, here we should insert a newline before blockquote: + // - a + // > + // + if (token.block && token.nesting !== -1 && idx && tokens[idx - 1].hidden) { + result += '\n'; + } + + // Add token name, e.g. ``. + // + needLf = false; + } + } + } + } + + result += needLf ? '>\n' : '>'; + + return result; +}; + + +/** + * Renderer.renderInline(tokens, options, env) -> String + * - tokens (Array): list on block tokens to renter + * - options (Object): params of parser instance + * - env (Object): additional data from parsed input (references, for example) + * + * The same as [[Renderer.render]], but for single token of `inline` type. + **/ +Renderer.prototype.renderInline = function (tokens, options, env) { + var type, + result = '', + rules = this.rules; + + for (var i = 0, len = tokens.length; i < len; i++) { + type = tokens[i].type; + + if (typeof rules[type] !== 'undefined') { + result += rules[type](tokens, i, options, env, this); + } else { + result += this.renderToken(tokens, i, options); + } + } + + return result; +}; + + +/** internal + * Renderer.renderInlineAsText(tokens, options, env) -> String + * - tokens (Array): list on block tokens to renter + * - options (Object): params of parser instance + * - env (Object): additional data from parsed input (references, for example) + * + * Special kludge for image `alt` attributes to conform CommonMark spec. + * Don't try to use it! Spec requires to show `alt` content with stripped markup, + * instead of simple escaping. + **/ +Renderer.prototype.renderInlineAsText = function (tokens, options, env) { + var result = ''; + + for (var i = 0, len = tokens.length; i < len; i++) { + if (tokens[i].type === 'text') { + result += tokens[i].content; + } else if (tokens[i].type === 'image') { + result += this.renderInlineAsText(tokens[i].children, options, env); + } + } + + return result; +}; + + +/** + * Renderer.render(tokens, options, env) -> String + * - tokens (Array): list on block tokens to renter + * - options (Object): params of parser instance + * - env (Object): additional data from parsed input (references, for example) + * + * Takes token stream and generates HTML. Probably, you will never need to call + * this method directly. + **/ +Renderer.prototype.render = function (tokens, options, env) { + var i, len, type, + result = '', + rules = this.rules; + + for (i = 0, len = tokens.length; i < len; i++) { + type = tokens[i].type; + + if (type === 'inline') { + result += this.renderInline(tokens[i].children, options, env); + } else if (typeof rules[type] !== 'undefined') { + result += rules[tokens[i].type](tokens, i, options, env, this); + } else { + result += this.renderToken(tokens, i, options, env); + } + } + + return result; +}; + +module.exports = Renderer; + +},{"./common/utils":4}],17:[function(require,module,exports){ +/** + * class Ruler + * + * Helper class, used by [[MarkdownIt#core]], [[MarkdownIt#block]] and + * [[MarkdownIt#inline]] to manage sequences of functions (rules): + * + * - keep rules in defined order + * - assign the name to each rule + * - enable/disable rules + * - add/replace rules + * - allow assign rules to additional named chains (in the same) + * - cacheing lists of active rules + * + * You will not need use this class directly until write plugins. For simple + * rules control use [[MarkdownIt.disable]], [[MarkdownIt.enable]] and + * [[MarkdownIt.use]]. + **/ +'use strict'; + + +/** + * new Ruler() + **/ +function Ruler() { + // List of added rules. Each element is: + // + // { + // name: XXX, + // enabled: Boolean, + // fn: Function(), + // alt: [ name2, name3 ] + // } + // + this.__rules__ = []; + + // Cached rule chains. + // + // First level - chain name, '' for default. + // Second level - diginal anchor for fast filtering by charcodes. + // + this.__cache__ = null; +} + +//////////////////////////////////////////////////////////////////////////////// +// Helper methods, should not be used directly + + +// Find rule index by name +// +Ruler.prototype.__find__ = function (name) { + for (var i = 0; i < this.__rules__.length; i++) { + if (this.__rules__[i].name === name) { + return i; + } + } + return -1; +}; + + +// Build rules lookup cache +// +Ruler.prototype.__compile__ = function () { + var self = this; + var chains = [ '' ]; + + // collect unique names + self.__rules__.forEach(function (rule) { + if (!rule.enabled) { return; } + + rule.alt.forEach(function (altName) { + if (chains.indexOf(altName) < 0) { + chains.push(altName); + } + }); + }); + + self.__cache__ = {}; + + chains.forEach(function (chain) { + self.__cache__[chain] = []; + self.__rules__.forEach(function (rule) { + if (!rule.enabled) { return; } + + if (chain && rule.alt.indexOf(chain) < 0) { return; } + + self.__cache__[chain].push(rule.fn); + }); + }); +}; + + +/** + * Ruler.at(name, fn [, options]) + * - name (String): rule name to replace. + * - fn (Function): new rule function. + * - options (Object): new rule options (not mandatory). + * + * Replace rule by name with new function & options. Throws error if name not + * found. + * + * ##### Options: + * + * - __alt__ - array with names of "alternate" chains. + * + * ##### Example + * + * Replace existing typographer replacement rule with new one: + * + * ```javascript + * var md = require('markdown-it')(); + * + * md.core.ruler.at('replacements', function replace(state) { + * //... + * }); + * ``` + **/ +Ruler.prototype.at = function (name, fn, options) { + var index = this.__find__(name); + var opt = options || {}; + + if (index === -1) { throw new Error('Parser rule not found: ' + name); } + + this.__rules__[index].fn = fn; + this.__rules__[index].alt = opt.alt || []; + this.__cache__ = null; +}; + + +/** + * Ruler.before(beforeName, ruleName, fn [, options]) + * - beforeName (String): new rule will be added before this one. + * - ruleName (String): name of added rule. + * - fn (Function): rule function. + * - options (Object): rule options (not mandatory). + * + * Add new rule to chain before one with given name. See also + * [[Ruler.after]], [[Ruler.push]]. + * + * ##### Options: + * + * - __alt__ - array with names of "alternate" chains. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')(); + * + * md.block.ruler.before('paragraph', 'my_rule', function replace(state) { + * //... + * }); + * ``` + **/ +Ruler.prototype.before = function (beforeName, ruleName, fn, options) { + var index = this.__find__(beforeName); + var opt = options || {}; + + if (index === -1) { throw new Error('Parser rule not found: ' + beforeName); } + + this.__rules__.splice(index, 0, { + name: ruleName, + enabled: true, + fn: fn, + alt: opt.alt || [] + }); + + this.__cache__ = null; +}; + + +/** + * Ruler.after(afterName, ruleName, fn [, options]) + * - afterName (String): new rule will be added after this one. + * - ruleName (String): name of added rule. + * - fn (Function): rule function. + * - options (Object): rule options (not mandatory). + * + * Add new rule to chain after one with given name. See also + * [[Ruler.before]], [[Ruler.push]]. + * + * ##### Options: + * + * - __alt__ - array with names of "alternate" chains. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')(); + * + * md.inline.ruler.after('text', 'my_rule', function replace(state) { + * //... + * }); + * ``` + **/ +Ruler.prototype.after = function (afterName, ruleName, fn, options) { + var index = this.__find__(afterName); + var opt = options || {}; + + if (index === -1) { throw new Error('Parser rule not found: ' + afterName); } + + this.__rules__.splice(index + 1, 0, { + name: ruleName, + enabled: true, + fn: fn, + alt: opt.alt || [] + }); + + this.__cache__ = null; +}; + +/** + * Ruler.push(ruleName, fn [, options]) + * - ruleName (String): name of added rule. + * - fn (Function): rule function. + * - options (Object): rule options (not mandatory). + * + * Push new rule to the end of chain. See also + * [[Ruler.before]], [[Ruler.after]]. + * + * ##### Options: + * + * - __alt__ - array with names of "alternate" chains. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')(); + * + * md.core.ruler.push('my_rule', function replace(state) { + * //... + * }); + * ``` + **/ +Ruler.prototype.push = function (ruleName, fn, options) { + var opt = options || {}; + + this.__rules__.push({ + name: ruleName, + enabled: true, + fn: fn, + alt: opt.alt || [] + }); + + this.__cache__ = null; +}; + + +/** + * Ruler.enable(list [, ignoreInvalid]) -> Array + * - list (String|Array): list of rule names to enable. + * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. + * + * Enable rules with given names. If any rule name not found - throw Error. + * Errors can be disabled by second param. + * + * Returns list of found rule names (if no exception happened). + * + * See also [[Ruler.disable]], [[Ruler.enableOnly]]. + **/ +Ruler.prototype.enable = function (list, ignoreInvalid) { + if (!Array.isArray(list)) { list = [ list ]; } + + var result = []; + + // Search by name and enable + list.forEach(function (name) { + var idx = this.__find__(name); + + if (idx < 0) { + if (ignoreInvalid) { return; } + throw new Error('Rules manager: invalid rule name ' + name); + } + this.__rules__[idx].enabled = true; + result.push(name); + }, this); + + this.__cache__ = null; + return result; +}; + + +/** + * Ruler.enableOnly(list [, ignoreInvalid]) + * - list (String|Array): list of rule names to enable (whitelist). + * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. + * + * Enable rules with given names, and disable everything else. If any rule name + * not found - throw Error. Errors can be disabled by second param. + * + * See also [[Ruler.disable]], [[Ruler.enable]]. + **/ +Ruler.prototype.enableOnly = function (list, ignoreInvalid) { + if (!Array.isArray(list)) { list = [ list ]; } + + this.__rules__.forEach(function (rule) { rule.enabled = false; }); + + this.enable(list, ignoreInvalid); +}; + + +/** + * Ruler.disable(list [, ignoreInvalid]) -> Array + * - list (String|Array): list of rule names to disable. + * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. + * + * Disable rules with given names. If any rule name not found - throw Error. + * Errors can be disabled by second param. + * + * Returns list of found rule names (if no exception happened). + * + * See also [[Ruler.enable]], [[Ruler.enableOnly]]. + **/ +Ruler.prototype.disable = function (list, ignoreInvalid) { + if (!Array.isArray(list)) { list = [ list ]; } + + var result = []; + + // Search by name and disable + list.forEach(function (name) { + var idx = this.__find__(name); + + if (idx < 0) { + if (ignoreInvalid) { return; } + throw new Error('Rules manager: invalid rule name ' + name); + } + this.__rules__[idx].enabled = false; + result.push(name); + }, this); + + this.__cache__ = null; + return result; +}; + + +/** + * Ruler.getRules(chainName) -> Array + * + * Return array of active functions (rules) for given chain name. It analyzes + * rules configuration, compiles caches if not exists and returns result. + * + * Default chain name is `''` (empty string). It can't be skipped. That's + * done intentionally, to keep signature monomorphic for high speed. + **/ +Ruler.prototype.getRules = function (chainName) { + if (this.__cache__ === null) { + this.__compile__(); + } + + // Chain can be empty, if rules disabled. But we still have to return Array. + return this.__cache__[chainName] || []; +}; + +module.exports = Ruler; + +},{}],18:[function(require,module,exports){ +// Block quotes + +'use strict'; + +var isSpace = require('../common/utils').isSpace; + + +module.exports = function blockquote(state, startLine, endLine, silent) { + var adjustTab, + ch, + i, + initial, + l, + lastLineEmpty, + lines, + nextLine, + offset, + oldBMarks, + oldBSCount, + oldIndent, + oldParentType, + oldSCount, + oldTShift, + spaceAfterMarker, + terminate, + terminatorRules, + token, + wasOutdented, + oldLineMax = state.lineMax, + pos = state.bMarks[startLine] + state.tShift[startLine], + max = state.eMarks[startLine]; + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + + // check the block quote marker + if (state.src.charCodeAt(pos++) !== 0x3E/* > */) { return false; } + + // we know that it's going to be a valid blockquote, + // so no point trying to find the end of it in silent mode + if (silent) { return true; } + + // skip spaces after ">" and re-calculate offset + initial = offset = state.sCount[startLine] + pos - (state.bMarks[startLine] + state.tShift[startLine]); + + // skip one optional space after '>' + if (state.src.charCodeAt(pos) === 0x20 /* space */) { + // ' > test ' + // ^ -- position start of line here: + pos++; + initial++; + offset++; + adjustTab = false; + spaceAfterMarker = true; + } else if (state.src.charCodeAt(pos) === 0x09 /* tab */) { + spaceAfterMarker = true; + + if ((state.bsCount[startLine] + offset) % 4 === 3) { + // ' >\t test ' + // ^ -- position start of line here (tab has width===1) + pos++; + initial++; + offset++; + adjustTab = false; + } else { + // ' >\t test ' + // ^ -- position start of line here + shift bsCount slightly + // to make extra space appear + adjustTab = true; + } + } else { + spaceAfterMarker = false; + } + + oldBMarks = [ state.bMarks[startLine] ]; + state.bMarks[startLine] = pos; + + while (pos < max) { + ch = state.src.charCodeAt(pos); + + if (isSpace(ch)) { + if (ch === 0x09) { + offset += 4 - (offset + state.bsCount[startLine] + (adjustTab ? 1 : 0)) % 4; + } else { + offset++; + } + } else { + break; + } + + pos++; + } + + oldBSCount = [ state.bsCount[startLine] ]; + state.bsCount[startLine] = state.sCount[startLine] + 1 + (spaceAfterMarker ? 1 : 0); + + lastLineEmpty = pos >= max; + + oldSCount = [ state.sCount[startLine] ]; + state.sCount[startLine] = offset - initial; + + oldTShift = [ state.tShift[startLine] ]; + state.tShift[startLine] = pos - state.bMarks[startLine]; + + terminatorRules = state.md.block.ruler.getRules('blockquote'); + + oldParentType = state.parentType; + state.parentType = 'blockquote'; + wasOutdented = false; + + // Search the end of the block + // + // Block ends with either: + // 1. an empty line outside: + // ``` + // > test + // + // ``` + // 2. an empty line inside: + // ``` + // > + // test + // ``` + // 3. another tag: + // ``` + // > test + // - - - + // ``` + for (nextLine = startLine + 1; nextLine < endLine; nextLine++) { + // check if it's outdented, i.e. it's inside list item and indented + // less than said list item: + // + // ``` + // 1. anything + // > current blockquote + // 2. checking this line + // ``` + if (state.sCount[nextLine] < state.blkIndent) wasOutdented = true; + + pos = state.bMarks[nextLine] + state.tShift[nextLine]; + max = state.eMarks[nextLine]; + + if (pos >= max) { + // Case 1: line is not inside the blockquote, and this line is empty. + break; + } + + if (state.src.charCodeAt(pos++) === 0x3E/* > */ && !wasOutdented) { + // This line is inside the blockquote. + + // skip spaces after ">" and re-calculate offset + initial = offset = state.sCount[nextLine] + pos - (state.bMarks[nextLine] + state.tShift[nextLine]); + + // skip one optional space after '>' + if (state.src.charCodeAt(pos) === 0x20 /* space */) { + // ' > test ' + // ^ -- position start of line here: + pos++; + initial++; + offset++; + adjustTab = false; + spaceAfterMarker = true; + } else if (state.src.charCodeAt(pos) === 0x09 /* tab */) { + spaceAfterMarker = true; + + if ((state.bsCount[nextLine] + offset) % 4 === 3) { + // ' >\t test ' + // ^ -- position start of line here (tab has width===1) + pos++; + initial++; + offset++; + adjustTab = false; + } else { + // ' >\t test ' + // ^ -- position start of line here + shift bsCount slightly + // to make extra space appear + adjustTab = true; + } + } else { + spaceAfterMarker = false; + } + + oldBMarks.push(state.bMarks[nextLine]); + state.bMarks[nextLine] = pos; + + while (pos < max) { + ch = state.src.charCodeAt(pos); + + if (isSpace(ch)) { + if (ch === 0x09) { + offset += 4 - (offset + state.bsCount[nextLine] + (adjustTab ? 1 : 0)) % 4; + } else { + offset++; + } + } else { + break; + } + + pos++; + } + + lastLineEmpty = pos >= max; + + oldBSCount.push(state.bsCount[nextLine]); + state.bsCount[nextLine] = state.sCount[nextLine] + 1 + (spaceAfterMarker ? 1 : 0); + + oldSCount.push(state.sCount[nextLine]); + state.sCount[nextLine] = offset - initial; + + oldTShift.push(state.tShift[nextLine]); + state.tShift[nextLine] = pos - state.bMarks[nextLine]; + continue; + } + + // Case 2: line is not inside the blockquote, and the last line was empty. + if (lastLineEmpty) { break; } + + // Case 3: another tag found. + terminate = false; + for (i = 0, l = terminatorRules.length; i < l; i++) { + if (terminatorRules[i](state, nextLine, endLine, true)) { + terminate = true; + break; + } + } + + if (terminate) { + // Quirk to enforce "hard termination mode" for paragraphs; + // normally if you call `tokenize(state, startLine, nextLine)`, + // paragraphs will look below nextLine for paragraph continuation, + // but if blockquote is terminated by another tag, they shouldn't + state.lineMax = nextLine; + + if (state.blkIndent !== 0) { + // state.blkIndent was non-zero, we now set it to zero, + // so we need to re-calculate all offsets to appear as + // if indent wasn't changed + oldBMarks.push(state.bMarks[nextLine]); + oldBSCount.push(state.bsCount[nextLine]); + oldTShift.push(state.tShift[nextLine]); + oldSCount.push(state.sCount[nextLine]); + state.sCount[nextLine] -= state.blkIndent; + } + + break; + } + + oldBMarks.push(state.bMarks[nextLine]); + oldBSCount.push(state.bsCount[nextLine]); + oldTShift.push(state.tShift[nextLine]); + oldSCount.push(state.sCount[nextLine]); + + // A negative indentation means that this is a paragraph continuation + // + state.sCount[nextLine] = -1; + } + + oldIndent = state.blkIndent; + state.blkIndent = 0; + + token = state.push('blockquote_open', 'blockquote', 1); + token.markup = '>'; + token.map = lines = [ startLine, 0 ]; + + state.md.block.tokenize(state, startLine, nextLine); + + token = state.push('blockquote_close', 'blockquote', -1); + token.markup = '>'; + + state.lineMax = oldLineMax; + state.parentType = oldParentType; + lines[1] = state.line; + + // Restore original tShift; this might not be necessary since the parser + // has already been here, but just to make sure we can do that. + for (i = 0; i < oldTShift.length; i++) { + state.bMarks[i + startLine] = oldBMarks[i]; + state.tShift[i + startLine] = oldTShift[i]; + state.sCount[i + startLine] = oldSCount[i]; + state.bsCount[i + startLine] = oldBSCount[i]; + } + state.blkIndent = oldIndent; + + return true; +}; + +},{"../common/utils":4}],19:[function(require,module,exports){ +// Code block (4 spaces padded) + +'use strict'; + + +module.exports = function code(state, startLine, endLine/*, silent*/) { + var nextLine, last, token; + + if (state.sCount[startLine] - state.blkIndent < 4) { return false; } + + last = nextLine = startLine + 1; + + while (nextLine < endLine) { + if (state.isEmpty(nextLine)) { + nextLine++; + continue; + } + + if (state.sCount[nextLine] - state.blkIndent >= 4) { + nextLine++; + last = nextLine; + continue; + } + break; + } + + state.line = last; + + token = state.push('code_block', 'code', 0); + token.content = state.getLines(startLine, last, 4 + state.blkIndent, true); + token.map = [ startLine, state.line ]; + + return true; +}; + +},{}],20:[function(require,module,exports){ +// fences (``` lang, ~~~ lang) + +'use strict'; + + +module.exports = function fence(state, startLine, endLine, silent) { + var marker, len, params, nextLine, mem, token, markup, + haveEndMarker = false, + pos = state.bMarks[startLine] + state.tShift[startLine], + max = state.eMarks[startLine]; + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + + if (pos + 3 > max) { return false; } + + marker = state.src.charCodeAt(pos); + + if (marker !== 0x7E/* ~ */ && marker !== 0x60 /* ` */) { + return false; + } + + // scan marker length + mem = pos; + pos = state.skipChars(pos, marker); + + len = pos - mem; + + if (len < 3) { return false; } + + markup = state.src.slice(mem, pos); + params = state.src.slice(pos, max); + + if (marker === 0x60 /* ` */) { + if (params.indexOf(String.fromCharCode(marker)) >= 0) { + return false; + } + } + + // Since start is found, we can report success here in validation mode + if (silent) { return true; } + + // search end of block + nextLine = startLine; + + for (;;) { + nextLine++; + if (nextLine >= endLine) { + // unclosed block should be autoclosed by end of document. + // also block seems to be autoclosed by end of parent + break; + } + + pos = mem = state.bMarks[nextLine] + state.tShift[nextLine]; + max = state.eMarks[nextLine]; + + if (pos < max && state.sCount[nextLine] < state.blkIndent) { + // non-empty line with negative indent should stop the list: + // - ``` + // test + break; + } + + if (state.src.charCodeAt(pos) !== marker) { continue; } + + if (state.sCount[nextLine] - state.blkIndent >= 4) { + // closing fence should be indented less than 4 spaces + continue; + } + + pos = state.skipChars(pos, marker); + + // closing code fence must be at least as long as the opening one + if (pos - mem < len) { continue; } + + // make sure tail has spaces only + pos = state.skipSpaces(pos); + + if (pos < max) { continue; } + + haveEndMarker = true; + // found! + break; + } + + // If a fence has heading spaces, they should be removed from its inner block + len = state.sCount[startLine]; + + state.line = nextLine + (haveEndMarker ? 1 : 0); + + token = state.push('fence', 'code', 0); + token.info = params; + token.content = state.getLines(startLine + 1, nextLine, len, true); + token.markup = markup; + token.map = [ startLine, state.line ]; + + return true; +}; + +},{}],21:[function(require,module,exports){ +// heading (#, ##, ...) + +'use strict'; + +var isSpace = require('../common/utils').isSpace; + + +module.exports = function heading(state, startLine, endLine, silent) { + var ch, level, tmp, token, + pos = state.bMarks[startLine] + state.tShift[startLine], + max = state.eMarks[startLine]; + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + + ch = state.src.charCodeAt(pos); + + if (ch !== 0x23/* # */ || pos >= max) { return false; } + + // count heading level + level = 1; + ch = state.src.charCodeAt(++pos); + while (ch === 0x23/* # */ && pos < max && level <= 6) { + level++; + ch = state.src.charCodeAt(++pos); + } + + if (level > 6 || (pos < max && !isSpace(ch))) { return false; } + + if (silent) { return true; } + + // Let's cut tails like ' ### ' from the end of string + + max = state.skipSpacesBack(max, pos); + tmp = state.skipCharsBack(max, 0x23, pos); // # + if (tmp > pos && isSpace(state.src.charCodeAt(tmp - 1))) { + max = tmp; + } + + state.line = startLine + 1; + + token = state.push('heading_open', 'h' + String(level), 1); + token.markup = '########'.slice(0, level); + token.map = [ startLine, state.line ]; + + token = state.push('inline', '', 0); + token.content = state.src.slice(pos, max).trim(); + token.map = [ startLine, state.line ]; + token.children = []; + + token = state.push('heading_close', 'h' + String(level), -1); + token.markup = '########'.slice(0, level); + + return true; +}; + +},{"../common/utils":4}],22:[function(require,module,exports){ +// Horizontal rule + +'use strict'; + +var isSpace = require('../common/utils').isSpace; + + +module.exports = function hr(state, startLine, endLine, silent) { + var marker, cnt, ch, token, + pos = state.bMarks[startLine] + state.tShift[startLine], + max = state.eMarks[startLine]; + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + + marker = state.src.charCodeAt(pos++); + + // Check hr marker + if (marker !== 0x2A/* * */ && + marker !== 0x2D/* - */ && + marker !== 0x5F/* _ */) { + return false; + } + + // markers can be mixed with spaces, but there should be at least 3 of them + + cnt = 1; + while (pos < max) { + ch = state.src.charCodeAt(pos++); + if (ch !== marker && !isSpace(ch)) { return false; } + if (ch === marker) { cnt++; } + } + + if (cnt < 3) { return false; } + + if (silent) { return true; } + + state.line = startLine + 1; + + token = state.push('hr', 'hr', 0); + token.map = [ startLine, state.line ]; + token.markup = Array(cnt + 1).join(String.fromCharCode(marker)); + + return true; +}; + +},{"../common/utils":4}],23:[function(require,module,exports){ +// HTML block + +'use strict'; + + +var block_names = require('../common/html_blocks'); +var HTML_OPEN_CLOSE_TAG_RE = require('../common/html_re').HTML_OPEN_CLOSE_TAG_RE; + +// An array of opening and corresponding closing sequences for html tags, +// last argument defines whether it can terminate a paragraph or not +// +var HTML_SEQUENCES = [ + [ /^<(script|pre|style)(?=(\s|>|$))/i, /<\/(script|pre|style)>/i, true ], + [ /^/, true ], + [ /^<\?/, /\?>/, true ], + [ /^/, true ], + [ /^/, true ], + [ new RegExp('^|$))', 'i'), /^$/, true ], + [ new RegExp(HTML_OPEN_CLOSE_TAG_RE.source + '\\s*$'), /^$/, false ] +]; + + +module.exports = function html_block(state, startLine, endLine, silent) { + var i, nextLine, token, lineText, + pos = state.bMarks[startLine] + state.tShift[startLine], + max = state.eMarks[startLine]; + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + + if (!state.md.options.html) { return false; } + + if (state.src.charCodeAt(pos) !== 0x3C/* < */) { return false; } + + lineText = state.src.slice(pos, max); + + for (i = 0; i < HTML_SEQUENCES.length; i++) { + if (HTML_SEQUENCES[i][0].test(lineText)) { break; } + } + + if (i === HTML_SEQUENCES.length) { return false; } + + if (silent) { + // true if this sequence can be a terminator, false otherwise + return HTML_SEQUENCES[i][2]; + } + + nextLine = startLine + 1; + + // If we are here - we detected HTML block. + // Let's roll down till block end. + if (!HTML_SEQUENCES[i][1].test(lineText)) { + for (; nextLine < endLine; nextLine++) { + if (state.sCount[nextLine] < state.blkIndent) { break; } + + pos = state.bMarks[nextLine] + state.tShift[nextLine]; + max = state.eMarks[nextLine]; + lineText = state.src.slice(pos, max); + + if (HTML_SEQUENCES[i][1].test(lineText)) { + if (lineText.length !== 0) { nextLine++; } + break; + } + } + } + + state.line = nextLine; + + token = state.push('html_block', '', 0); + token.map = [ startLine, nextLine ]; + token.content = state.getLines(startLine, nextLine, state.blkIndent, true); + + return true; +}; + +},{"../common/html_blocks":2,"../common/html_re":3}],24:[function(require,module,exports){ +// lheading (---, ===) + +'use strict'; + + +module.exports = function lheading(state, startLine, endLine/*, silent*/) { + var content, terminate, i, l, token, pos, max, level, marker, + nextLine = startLine + 1, oldParentType, + terminatorRules = state.md.block.ruler.getRules('paragraph'); + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + + oldParentType = state.parentType; + state.parentType = 'paragraph'; // use paragraph to match terminatorRules + + // jump line-by-line until empty one or EOF + for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) { + // this would be a code block normally, but after paragraph + // it's considered a lazy continuation regardless of what's there + if (state.sCount[nextLine] - state.blkIndent > 3) { continue; } + + // + // Check for underline in setext header + // + if (state.sCount[nextLine] >= state.blkIndent) { + pos = state.bMarks[nextLine] + state.tShift[nextLine]; + max = state.eMarks[nextLine]; + + if (pos < max) { + marker = state.src.charCodeAt(pos); + + if (marker === 0x2D/* - */ || marker === 0x3D/* = */) { + pos = state.skipChars(pos, marker); + pos = state.skipSpaces(pos); + + if (pos >= max) { + level = (marker === 0x3D/* = */ ? 1 : 2); + break; + } + } + } + } + + // quirk for blockquotes, this line should already be checked by that rule + if (state.sCount[nextLine] < 0) { continue; } + + // Some tags can terminate paragraph without empty line. + terminate = false; + for (i = 0, l = terminatorRules.length; i < l; i++) { + if (terminatorRules[i](state, nextLine, endLine, true)) { + terminate = true; + break; + } + } + if (terminate) { break; } + } + + if (!level) { + // Didn't find valid underline + return false; + } + + content = state.getLines(startLine, nextLine, state.blkIndent, false).trim(); + + state.line = nextLine + 1; + + token = state.push('heading_open', 'h' + String(level), 1); + token.markup = String.fromCharCode(marker); + token.map = [ startLine, state.line ]; + + token = state.push('inline', '', 0); + token.content = content; + token.map = [ startLine, state.line - 1 ]; + token.children = []; + + token = state.push('heading_close', 'h' + String(level), -1); + token.markup = String.fromCharCode(marker); + + state.parentType = oldParentType; + + return true; +}; + +},{}],25:[function(require,module,exports){ +// Lists + +'use strict'; + +var isSpace = require('../common/utils').isSpace; + + +// Search `[-+*][\n ]`, returns next pos after marker on success +// or -1 on fail. +function skipBulletListMarker(state, startLine) { + var marker, pos, max, ch; + + pos = state.bMarks[startLine] + state.tShift[startLine]; + max = state.eMarks[startLine]; + + marker = state.src.charCodeAt(pos++); + // Check bullet + if (marker !== 0x2A/* * */ && + marker !== 0x2D/* - */ && + marker !== 0x2B/* + */) { + return -1; + } + + if (pos < max) { + ch = state.src.charCodeAt(pos); + + if (!isSpace(ch)) { + // " -test " - is not a list item + return -1; + } + } + + return pos; +} + +// Search `\d+[.)][\n ]`, returns next pos after marker on success +// or -1 on fail. +function skipOrderedListMarker(state, startLine) { + var ch, + start = state.bMarks[startLine] + state.tShift[startLine], + pos = start, + max = state.eMarks[startLine]; + + // List marker should have at least 2 chars (digit + dot) + if (pos + 1 >= max) { return -1; } + + ch = state.src.charCodeAt(pos++); + + if (ch < 0x30/* 0 */ || ch > 0x39/* 9 */) { return -1; } + + for (;;) { + // EOL -> fail + if (pos >= max) { return -1; } + + ch = state.src.charCodeAt(pos++); + + if (ch >= 0x30/* 0 */ && ch <= 0x39/* 9 */) { + + // List marker should have no more than 9 digits + // (prevents integer overflow in browsers) + if (pos - start >= 10) { return -1; } + + continue; + } + + // found valid marker + if (ch === 0x29/* ) */ || ch === 0x2e/* . */) { + break; + } + + return -1; + } + + + if (pos < max) { + ch = state.src.charCodeAt(pos); + + if (!isSpace(ch)) { + // " 1.test " - is not a list item + return -1; + } + } + return pos; +} + +function markTightParagraphs(state, idx) { + var i, l, + level = state.level + 2; + + for (i = idx + 2, l = state.tokens.length - 2; i < l; i++) { + if (state.tokens[i].level === level && state.tokens[i].type === 'paragraph_open') { + state.tokens[i + 2].hidden = true; + state.tokens[i].hidden = true; + i += 2; + } + } +} + + +module.exports = function list(state, startLine, endLine, silent) { + var ch, + contentStart, + i, + indent, + indentAfterMarker, + initial, + isOrdered, + itemLines, + l, + listLines, + listTokIdx, + markerCharCode, + markerValue, + max, + nextLine, + offset, + oldListIndent, + oldParentType, + oldSCount, + oldTShift, + oldTight, + pos, + posAfterMarker, + prevEmptyEnd, + start, + terminate, + terminatorRules, + token, + isTerminatingParagraph = false, + tight = true; + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + + // Special case: + // - item 1 + // - item 2 + // - item 3 + // - item 4 + // - this one is a paragraph continuation + if (state.listIndent >= 0 && + state.sCount[startLine] - state.listIndent >= 4 && + state.sCount[startLine] < state.blkIndent) { + return false; + } + + // limit conditions when list can interrupt + // a paragraph (validation mode only) + if (silent && state.parentType === 'paragraph') { + // Next list item should still terminate previous list item; + // + // This code can fail if plugins use blkIndent as well as lists, + // but I hope the spec gets fixed long before that happens. + // + if (state.tShift[startLine] >= state.blkIndent) { + isTerminatingParagraph = true; + } + } + + // Detect list type and position after marker + if ((posAfterMarker = skipOrderedListMarker(state, startLine)) >= 0) { + isOrdered = true; + start = state.bMarks[startLine] + state.tShift[startLine]; + markerValue = Number(state.src.substr(start, posAfterMarker - start - 1)); + + // If we're starting a new ordered list right after + // a paragraph, it should start with 1. + if (isTerminatingParagraph && markerValue !== 1) return false; + + } else if ((posAfterMarker = skipBulletListMarker(state, startLine)) >= 0) { + isOrdered = false; + + } else { + return false; + } + + // If we're starting a new unordered list right after + // a paragraph, first line should not be empty. + if (isTerminatingParagraph) { + if (state.skipSpaces(posAfterMarker) >= state.eMarks[startLine]) return false; + } + + // We should terminate list on style change. Remember first one to compare. + markerCharCode = state.src.charCodeAt(posAfterMarker - 1); + + // For validation mode we can terminate immediately + if (silent) { return true; } + + // Start list + listTokIdx = state.tokens.length; + + if (isOrdered) { + token = state.push('ordered_list_open', 'ol', 1); + if (markerValue !== 1) { + token.attrs = [ [ 'start', markerValue ] ]; + } + + } else { + token = state.push('bullet_list_open', 'ul', 1); + } + + token.map = listLines = [ startLine, 0 ]; + token.markup = String.fromCharCode(markerCharCode); + + // + // Iterate list items + // + + nextLine = startLine; + prevEmptyEnd = false; + terminatorRules = state.md.block.ruler.getRules('list'); + + oldParentType = state.parentType; + state.parentType = 'list'; + + while (nextLine < endLine) { + pos = posAfterMarker; + max = state.eMarks[nextLine]; + + initial = offset = state.sCount[nextLine] + posAfterMarker - (state.bMarks[startLine] + state.tShift[startLine]); + + while (pos < max) { + ch = state.src.charCodeAt(pos); + + if (ch === 0x09) { + offset += 4 - (offset + state.bsCount[nextLine]) % 4; + } else if (ch === 0x20) { + offset++; + } else { + break; + } + + pos++; + } + + contentStart = pos; + + if (contentStart >= max) { + // trimming space in "- \n 3" case, indent is 1 here + indentAfterMarker = 1; + } else { + indentAfterMarker = offset - initial; + } + + // If we have more than 4 spaces, the indent is 1 + // (the rest is just indented code block) + if (indentAfterMarker > 4) { indentAfterMarker = 1; } + + // " - test" + // ^^^^^ - calculating total length of this thing + indent = initial + indentAfterMarker; + + // Run subparser & write tokens + token = state.push('list_item_open', 'li', 1); + token.markup = String.fromCharCode(markerCharCode); + token.map = itemLines = [ startLine, 0 ]; + + // change current state, then restore it after parser subcall + oldTight = state.tight; + oldTShift = state.tShift[startLine]; + oldSCount = state.sCount[startLine]; + + // - example list + // ^ listIndent position will be here + // ^ blkIndent position will be here + // + oldListIndent = state.listIndent; + state.listIndent = state.blkIndent; + state.blkIndent = indent; + + state.tight = true; + state.tShift[startLine] = contentStart - state.bMarks[startLine]; + state.sCount[startLine] = offset; + + if (contentStart >= max && state.isEmpty(startLine + 1)) { + // workaround for this case + // (list item is empty, list terminates before "foo"): + // ~~~~~~~~ + // - + // + // foo + // ~~~~~~~~ + state.line = Math.min(state.line + 2, endLine); + } else { + state.md.block.tokenize(state, startLine, endLine, true); + } + + // If any of list item is tight, mark list as tight + if (!state.tight || prevEmptyEnd) { + tight = false; + } + // Item become loose if finish with empty line, + // but we should filter last element, because it means list finish + prevEmptyEnd = (state.line - startLine) > 1 && state.isEmpty(state.line - 1); + + state.blkIndent = state.listIndent; + state.listIndent = oldListIndent; + state.tShift[startLine] = oldTShift; + state.sCount[startLine] = oldSCount; + state.tight = oldTight; + + token = state.push('list_item_close', 'li', -1); + token.markup = String.fromCharCode(markerCharCode); + + nextLine = startLine = state.line; + itemLines[1] = nextLine; + contentStart = state.bMarks[startLine]; + + if (nextLine >= endLine) { break; } + + // + // Try to check if list is terminated or continued. + // + if (state.sCount[nextLine] < state.blkIndent) { break; } + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { break; } + + // fail if terminating block found + terminate = false; + for (i = 0, l = terminatorRules.length; i < l; i++) { + if (terminatorRules[i](state, nextLine, endLine, true)) { + terminate = true; + break; + } + } + if (terminate) { break; } + + // fail if list has another type + if (isOrdered) { + posAfterMarker = skipOrderedListMarker(state, nextLine); + if (posAfterMarker < 0) { break; } + } else { + posAfterMarker = skipBulletListMarker(state, nextLine); + if (posAfterMarker < 0) { break; } + } + + if (markerCharCode !== state.src.charCodeAt(posAfterMarker - 1)) { break; } + } + + // Finalize list + if (isOrdered) { + token = state.push('ordered_list_close', 'ol', -1); + } else { + token = state.push('bullet_list_close', 'ul', -1); + } + token.markup = String.fromCharCode(markerCharCode); + + listLines[1] = nextLine; + state.line = nextLine; + + state.parentType = oldParentType; + + // mark paragraphs tight if needed + if (tight) { + markTightParagraphs(state, listTokIdx); + } + + return true; +}; + +},{"../common/utils":4}],26:[function(require,module,exports){ +// Paragraph + +'use strict'; + + +module.exports = function paragraph(state, startLine/*, endLine*/) { + var content, terminate, i, l, token, oldParentType, + nextLine = startLine + 1, + terminatorRules = state.md.block.ruler.getRules('paragraph'), + endLine = state.lineMax; + + oldParentType = state.parentType; + state.parentType = 'paragraph'; + + // jump line-by-line until empty one or EOF + for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) { + // this would be a code block normally, but after paragraph + // it's considered a lazy continuation regardless of what's there + if (state.sCount[nextLine] - state.blkIndent > 3) { continue; } + + // quirk for blockquotes, this line should already be checked by that rule + if (state.sCount[nextLine] < 0) { continue; } + + // Some tags can terminate paragraph without empty line. + terminate = false; + for (i = 0, l = terminatorRules.length; i < l; i++) { + if (terminatorRules[i](state, nextLine, endLine, true)) { + terminate = true; + break; + } + } + if (terminate) { break; } + } + + content = state.getLines(startLine, nextLine, state.blkIndent, false).trim(); + + state.line = nextLine; + + token = state.push('paragraph_open', 'p', 1); + token.map = [ startLine, state.line ]; + + token = state.push('inline', '', 0); + token.content = content; + token.map = [ startLine, state.line ]; + token.children = []; + + token = state.push('paragraph_close', 'p', -1); + + state.parentType = oldParentType; + + return true; +}; + +},{}],27:[function(require,module,exports){ +'use strict'; + + +var normalizeReference = require('../common/utils').normalizeReference; +var isSpace = require('../common/utils').isSpace; + + +module.exports = function reference(state, startLine, _endLine, silent) { + var ch, + destEndPos, + destEndLineNo, + endLine, + href, + i, + l, + label, + labelEnd, + oldParentType, + res, + start, + str, + terminate, + terminatorRules, + title, + lines = 0, + pos = state.bMarks[startLine] + state.tShift[startLine], + max = state.eMarks[startLine], + nextLine = startLine + 1; + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + + if (state.src.charCodeAt(pos) !== 0x5B/* [ */) { return false; } + + // Simple check to quickly interrupt scan on [link](url) at the start of line. + // Can be useful on practice: https://github.com/markdown-it/markdown-it/issues/54 + while (++pos < max) { + if (state.src.charCodeAt(pos) === 0x5D /* ] */ && + state.src.charCodeAt(pos - 1) !== 0x5C/* \ */) { + if (pos + 1 === max) { return false; } + if (state.src.charCodeAt(pos + 1) !== 0x3A/* : */) { return false; } + break; + } + } + + endLine = state.lineMax; + + // jump line-by-line until empty one or EOF + terminatorRules = state.md.block.ruler.getRules('reference'); + + oldParentType = state.parentType; + state.parentType = 'reference'; + + for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) { + // this would be a code block normally, but after paragraph + // it's considered a lazy continuation regardless of what's there + if (state.sCount[nextLine] - state.blkIndent > 3) { continue; } + + // quirk for blockquotes, this line should already be checked by that rule + if (state.sCount[nextLine] < 0) { continue; } + + // Some tags can terminate paragraph without empty line. + terminate = false; + for (i = 0, l = terminatorRules.length; i < l; i++) { + if (terminatorRules[i](state, nextLine, endLine, true)) { + terminate = true; + break; + } + } + if (terminate) { break; } + } + + str = state.getLines(startLine, nextLine, state.blkIndent, false).trim(); + max = str.length; + + for (pos = 1; pos < max; pos++) { + ch = str.charCodeAt(pos); + if (ch === 0x5B /* [ */) { + return false; + } else if (ch === 0x5D /* ] */) { + labelEnd = pos; + break; + } else if (ch === 0x0A /* \n */) { + lines++; + } else if (ch === 0x5C /* \ */) { + pos++; + if (pos < max && str.charCodeAt(pos) === 0x0A) { + lines++; + } + } + } + + if (labelEnd < 0 || str.charCodeAt(labelEnd + 1) !== 0x3A/* : */) { return false; } + + // [label]: destination 'title' + // ^^^ skip optional whitespace here + for (pos = labelEnd + 2; pos < max; pos++) { + ch = str.charCodeAt(pos); + if (ch === 0x0A) { + lines++; + } else if (isSpace(ch)) { + /*eslint no-empty:0*/ + } else { + break; + } + } + + // [label]: destination 'title' + // ^^^^^^^^^^^ parse this + res = state.md.helpers.parseLinkDestination(str, pos, max); + if (!res.ok) { return false; } + + href = state.md.normalizeLink(res.str); + if (!state.md.validateLink(href)) { return false; } + + pos = res.pos; + lines += res.lines; + + // save cursor state, we could require to rollback later + destEndPos = pos; + destEndLineNo = lines; + + // [label]: destination 'title' + // ^^^ skipping those spaces + start = pos; + for (; pos < max; pos++) { + ch = str.charCodeAt(pos); + if (ch === 0x0A) { + lines++; + } else if (isSpace(ch)) { + /*eslint no-empty:0*/ + } else { + break; + } + } + + // [label]: destination 'title' + // ^^^^^^^ parse this + res = state.md.helpers.parseLinkTitle(str, pos, max); + if (pos < max && start !== pos && res.ok) { + title = res.str; + pos = res.pos; + lines += res.lines; + } else { + title = ''; + pos = destEndPos; + lines = destEndLineNo; + } + + // skip trailing spaces until the rest of the line + while (pos < max) { + ch = str.charCodeAt(pos); + if (!isSpace(ch)) { break; } + pos++; + } + + if (pos < max && str.charCodeAt(pos) !== 0x0A) { + if (title) { + // garbage at the end of the line after title, + // but it could still be a valid reference if we roll back + title = ''; + pos = destEndPos; + lines = destEndLineNo; + while (pos < max) { + ch = str.charCodeAt(pos); + if (!isSpace(ch)) { break; } + pos++; + } + } + } + + if (pos < max && str.charCodeAt(pos) !== 0x0A) { + // garbage at the end of the line + return false; + } + + label = normalizeReference(str.slice(1, labelEnd)); + if (!label) { + // CommonMark 0.20 disallows empty labels + return false; + } + + // Reference can not terminate anything. This check is for safety only. + /*istanbul ignore if*/ + if (silent) { return true; } + + if (typeof state.env.references === 'undefined') { + state.env.references = {}; + } + if (typeof state.env.references[label] === 'undefined') { + state.env.references[label] = { title: title, href: href }; + } + + state.parentType = oldParentType; + + state.line = startLine + lines + 1; + return true; +}; + +},{"../common/utils":4}],28:[function(require,module,exports){ +// Parser state class + +'use strict'; + +var Token = require('../token'); +var isSpace = require('../common/utils').isSpace; + + +function StateBlock(src, md, env, tokens) { + var ch, s, start, pos, len, indent, offset, indent_found; + + this.src = src; + + // link to parser instance + this.md = md; + + this.env = env; + + // + // Internal state vartiables + // + + this.tokens = tokens; + + this.bMarks = []; // line begin offsets for fast jumps + this.eMarks = []; // line end offsets for fast jumps + this.tShift = []; // offsets of the first non-space characters (tabs not expanded) + this.sCount = []; // indents for each line (tabs expanded) + + // An amount of virtual spaces (tabs expanded) between beginning + // of each line (bMarks) and real beginning of that line. + // + // It exists only as a hack because blockquotes override bMarks + // losing information in the process. + // + // It's used only when expanding tabs, you can think about it as + // an initial tab length, e.g. bsCount=21 applied to string `\t123` + // means first tab should be expanded to 4-21%4 === 3 spaces. + // + this.bsCount = []; + + // block parser variables + this.blkIndent = 0; // required block content indent (for example, if we are + // inside a list, it would be positioned after list marker) + this.line = 0; // line index in src + this.lineMax = 0; // lines count + this.tight = false; // loose/tight mode for lists + this.ddIndent = -1; // indent of the current dd block (-1 if there isn't any) + this.listIndent = -1; // indent of the current list block (-1 if there isn't any) + + // can be 'blockquote', 'list', 'root', 'paragraph' or 'reference' + // used in lists to determine if they interrupt a paragraph + this.parentType = 'root'; + + this.level = 0; + + // renderer + this.result = ''; + + // Create caches + // Generate markers. + s = this.src; + indent_found = false; + + for (start = pos = indent = offset = 0, len = s.length; pos < len; pos++) { + ch = s.charCodeAt(pos); + + if (!indent_found) { + if (isSpace(ch)) { + indent++; + + if (ch === 0x09) { + offset += 4 - offset % 4; + } else { + offset++; + } + continue; + } else { + indent_found = true; + } + } + + if (ch === 0x0A || pos === len - 1) { + if (ch !== 0x0A) { pos++; } + this.bMarks.push(start); + this.eMarks.push(pos); + this.tShift.push(indent); + this.sCount.push(offset); + this.bsCount.push(0); + + indent_found = false; + indent = 0; + offset = 0; + start = pos + 1; + } + } + + // Push fake entry to simplify cache bounds checks + this.bMarks.push(s.length); + this.eMarks.push(s.length); + this.tShift.push(0); + this.sCount.push(0); + this.bsCount.push(0); + + this.lineMax = this.bMarks.length - 1; // don't count last fake line +} + +// Push new token to "stream". +// +StateBlock.prototype.push = function (type, tag, nesting) { + var token = new Token(type, tag, nesting); + token.block = true; + + if (nesting < 0) this.level--; // closing tag + token.level = this.level; + if (nesting > 0) this.level++; // opening tag + + this.tokens.push(token); + return token; +}; + +StateBlock.prototype.isEmpty = function isEmpty(line) { + return this.bMarks[line] + this.tShift[line] >= this.eMarks[line]; +}; + +StateBlock.prototype.skipEmptyLines = function skipEmptyLines(from) { + for (var max = this.lineMax; from < max; from++) { + if (this.bMarks[from] + this.tShift[from] < this.eMarks[from]) { + break; + } + } + return from; +}; + +// Skip spaces from given position. +StateBlock.prototype.skipSpaces = function skipSpaces(pos) { + var ch; + + for (var max = this.src.length; pos < max; pos++) { + ch = this.src.charCodeAt(pos); + if (!isSpace(ch)) { break; } + } + return pos; +}; + +// Skip spaces from given position in reverse. +StateBlock.prototype.skipSpacesBack = function skipSpacesBack(pos, min) { + if (pos <= min) { return pos; } + + while (pos > min) { + if (!isSpace(this.src.charCodeAt(--pos))) { return pos + 1; } + } + return pos; +}; + +// Skip char codes from given position +StateBlock.prototype.skipChars = function skipChars(pos, code) { + for (var max = this.src.length; pos < max; pos++) { + if (this.src.charCodeAt(pos) !== code) { break; } + } + return pos; +}; + +// Skip char codes reverse from given position - 1 +StateBlock.prototype.skipCharsBack = function skipCharsBack(pos, code, min) { + if (pos <= min) { return pos; } + + while (pos > min) { + if (code !== this.src.charCodeAt(--pos)) { return pos + 1; } + } + return pos; +}; + +// cut lines range from source. +StateBlock.prototype.getLines = function getLines(begin, end, indent, keepLastLF) { + var i, lineIndent, ch, first, last, queue, lineStart, + line = begin; + + if (begin >= end) { + return ''; + } + + queue = new Array(end - begin); + + for (i = 0; line < end; line++, i++) { + lineIndent = 0; + lineStart = first = this.bMarks[line]; + + if (line + 1 < end || keepLastLF) { + // No need for bounds check because we have fake entry on tail. + last = this.eMarks[line] + 1; + } else { + last = this.eMarks[line]; + } + + while (first < last && lineIndent < indent) { + ch = this.src.charCodeAt(first); + + if (isSpace(ch)) { + if (ch === 0x09) { + lineIndent += 4 - (lineIndent + this.bsCount[line]) % 4; + } else { + lineIndent++; + } + } else if (first - lineStart < this.tShift[line]) { + // patched tShift masked characters to look like spaces (blockquotes, list markers) + lineIndent++; + } else { + break; + } + + first++; + } + + if (lineIndent > indent) { + // partially expanding tabs in code blocks, e.g '\t\tfoobar' + // with indent=2 becomes ' \tfoobar' + queue[i] = new Array(lineIndent - indent + 1).join(' ') + this.src.slice(first, last); + } else { + queue[i] = this.src.slice(first, last); + } + } + + return queue.join(''); +}; + +// re-export Token class to use in block rules +StateBlock.prototype.Token = Token; + + +module.exports = StateBlock; + +},{"../common/utils":4,"../token":51}],29:[function(require,module,exports){ +// GFM table, non-standard + +'use strict'; + +var isSpace = require('../common/utils').isSpace; + + +function getLine(state, line) { + var pos = state.bMarks[line] + state.blkIndent, + max = state.eMarks[line]; + + return state.src.substr(pos, max - pos); +} + +function escapedSplit(str) { + var result = [], + pos = 0, + max = str.length, + ch, + escapes = 0, + lastPos = 0, + backTicked = false, + lastBackTick = 0; + + ch = str.charCodeAt(pos); + + while (pos < max) { + if (ch === 0x60/* ` */) { + if (backTicked) { + // make \` close code sequence, but not open it; + // the reason is: `\` is correct code block + backTicked = false; + lastBackTick = pos; + } else if (escapes % 2 === 0) { + backTicked = true; + lastBackTick = pos; + } + } else if (ch === 0x7c/* | */ && (escapes % 2 === 0) && !backTicked) { + result.push(str.substring(lastPos, pos)); + lastPos = pos + 1; + } + + if (ch === 0x5c/* \ */) { + escapes++; + } else { + escapes = 0; + } + + pos++; + + // If there was an un-closed backtick, go back to just after + // the last backtick, but as if it was a normal character + if (pos === max && backTicked) { + backTicked = false; + pos = lastBackTick + 1; + } + + ch = str.charCodeAt(pos); + } + + result.push(str.substring(lastPos)); + + return result; +} + + +module.exports = function table(state, startLine, endLine, silent) { + var ch, lineText, pos, i, nextLine, columns, columnCount, token, + aligns, t, tableLines, tbodyLines; + + // should have at least two lines + if (startLine + 2 > endLine) { return false; } + + nextLine = startLine + 1; + + if (state.sCount[nextLine] < state.blkIndent) { return false; } + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[nextLine] - state.blkIndent >= 4) { return false; } + + // first character of the second line should be '|', '-', ':', + // and no other characters are allowed but spaces; + // basically, this is the equivalent of /^[-:|][-:|\s]*$/ regexp + + pos = state.bMarks[nextLine] + state.tShift[nextLine]; + if (pos >= state.eMarks[nextLine]) { return false; } + + ch = state.src.charCodeAt(pos++); + if (ch !== 0x7C/* | */ && ch !== 0x2D/* - */ && ch !== 0x3A/* : */) { return false; } + + while (pos < state.eMarks[nextLine]) { + ch = state.src.charCodeAt(pos); + + if (ch !== 0x7C/* | */ && ch !== 0x2D/* - */ && ch !== 0x3A/* : */ && !isSpace(ch)) { return false; } + + pos++; + } + + lineText = getLine(state, startLine + 1); + + columns = lineText.split('|'); + aligns = []; + for (i = 0; i < columns.length; i++) { + t = columns[i].trim(); + if (!t) { + // allow empty columns before and after table, but not in between columns; + // e.g. allow ` |---| `, disallow ` ---||--- ` + if (i === 0 || i === columns.length - 1) { + continue; + } else { + return false; + } + } + + if (!/^:?-+:?$/.test(t)) { return false; } + if (t.charCodeAt(t.length - 1) === 0x3A/* : */) { + aligns.push(t.charCodeAt(0) === 0x3A/* : */ ? 'center' : 'right'); + } else if (t.charCodeAt(0) === 0x3A/* : */) { + aligns.push('left'); + } else { + aligns.push(''); + } + } + + lineText = getLine(state, startLine).trim(); + if (lineText.indexOf('|') === -1) { return false; } + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + columns = escapedSplit(lineText.replace(/^\||\|$/g, '')); + + // header row will define an amount of columns in the entire table, + // and align row shouldn't be smaller than that (the rest of the rows can) + columnCount = columns.length; + if (columnCount > aligns.length) { return false; } + + if (silent) { return true; } + + token = state.push('table_open', 'table', 1); + token.map = tableLines = [ startLine, 0 ]; + + token = state.push('thead_open', 'thead', 1); + token.map = [ startLine, startLine + 1 ]; + + token = state.push('tr_open', 'tr', 1); + token.map = [ startLine, startLine + 1 ]; + + for (i = 0; i < columns.length; i++) { + token = state.push('th_open', 'th', 1); + token.map = [ startLine, startLine + 1 ]; + if (aligns[i]) { + token.attrs = [ [ 'style', 'text-align:' + aligns[i] ] ]; + } + + token = state.push('inline', '', 0); + token.content = columns[i].trim(); + token.map = [ startLine, startLine + 1 ]; + token.children = []; + + token = state.push('th_close', 'th', -1); + } + + token = state.push('tr_close', 'tr', -1); + token = state.push('thead_close', 'thead', -1); + + token = state.push('tbody_open', 'tbody', 1); + token.map = tbodyLines = [ startLine + 2, 0 ]; + + for (nextLine = startLine + 2; nextLine < endLine; nextLine++) { + if (state.sCount[nextLine] < state.blkIndent) { break; } + + lineText = getLine(state, nextLine).trim(); + if (lineText.indexOf('|') === -1) { break; } + if (state.sCount[nextLine] - state.blkIndent >= 4) { break; } + columns = escapedSplit(lineText.replace(/^\||\|$/g, '')); + + token = state.push('tr_open', 'tr', 1); + for (i = 0; i < columnCount; i++) { + token = state.push('td_open', 'td', 1); + if (aligns[i]) { + token.attrs = [ [ 'style', 'text-align:' + aligns[i] ] ]; + } + + token = state.push('inline', '', 0); + token.content = columns[i] ? columns[i].trim() : ''; + token.children = []; + + token = state.push('td_close', 'td', -1); + } + token = state.push('tr_close', 'tr', -1); + } + token = state.push('tbody_close', 'tbody', -1); + token = state.push('table_close', 'table', -1); + + tableLines[1] = tbodyLines[1] = nextLine; + state.line = nextLine; + return true; +}; + +},{"../common/utils":4}],30:[function(require,module,exports){ +'use strict'; + + +module.exports = function block(state) { + var token; + + if (state.inlineMode) { + token = new state.Token('inline', '', 0); + token.content = state.src; + token.map = [ 0, 1 ]; + token.children = []; + state.tokens.push(token); + } else { + state.md.block.parse(state.src, state.md, state.env, state.tokens); + } +}; + +},{}],31:[function(require,module,exports){ +'use strict'; + +module.exports = function inline(state) { + var tokens = state.tokens, tok, i, l; + + // Parse inlines + for (i = 0, l = tokens.length; i < l; i++) { + tok = tokens[i]; + if (tok.type === 'inline') { + state.md.inline.parse(tok.content, state.md, state.env, tok.children); + } + } +}; + +},{}],32:[function(require,module,exports){ +// Replace link-like texts with link nodes. +// +// Currently restricted by `md.validateLink()` to http/https/ftp +// +'use strict'; + + +var arrayReplaceAt = require('../common/utils').arrayReplaceAt; + + +function isLinkOpen(str) { + return /^\s]/i.test(str); +} +function isLinkClose(str) { + return /^<\/a\s*>/i.test(str); +} + + +module.exports = function linkify(state) { + var i, j, l, tokens, token, currentToken, nodes, ln, text, pos, lastPos, + level, htmlLinkLevel, url, fullUrl, urlText, + blockTokens = state.tokens, + links; + + if (!state.md.options.linkify) { return; } + + for (j = 0, l = blockTokens.length; j < l; j++) { + if (blockTokens[j].type !== 'inline' || + !state.md.linkify.pretest(blockTokens[j].content)) { + continue; + } + + tokens = blockTokens[j].children; + + htmlLinkLevel = 0; + + // We scan from the end, to keep position when new tags added. + // Use reversed logic in links start/end match + for (i = tokens.length - 1; i >= 0; i--) { + currentToken = tokens[i]; + + // Skip content of markdown links + if (currentToken.type === 'link_close') { + i--; + while (tokens[i].level !== currentToken.level && tokens[i].type !== 'link_open') { + i--; + } + continue; + } + + // Skip content of html tag links + if (currentToken.type === 'html_inline') { + if (isLinkOpen(currentToken.content) && htmlLinkLevel > 0) { + htmlLinkLevel--; + } + if (isLinkClose(currentToken.content)) { + htmlLinkLevel++; + } + } + if (htmlLinkLevel > 0) { continue; } + + if (currentToken.type === 'text' && state.md.linkify.test(currentToken.content)) { + + text = currentToken.content; + links = state.md.linkify.match(text); + + // Now split string to nodes + nodes = []; + level = currentToken.level; + lastPos = 0; + + for (ln = 0; ln < links.length; ln++) { + + url = links[ln].url; + fullUrl = state.md.normalizeLink(url); + if (!state.md.validateLink(fullUrl)) { continue; } + + urlText = links[ln].text; + + // Linkifier might send raw hostnames like "example.com", where url + // starts with domain name. So we prepend http:// in those cases, + // and remove it afterwards. + // + if (!links[ln].schema) { + urlText = state.md.normalizeLinkText('http://' + urlText).replace(/^http:\/\//, ''); + } else if (links[ln].schema === 'mailto:' && !/^mailto:/i.test(urlText)) { + urlText = state.md.normalizeLinkText('mailto:' + urlText).replace(/^mailto:/, ''); + } else { + urlText = state.md.normalizeLinkText(urlText); + } + + pos = links[ln].index; + + if (pos > lastPos) { + token = new state.Token('text', '', 0); + token.content = text.slice(lastPos, pos); + token.level = level; + nodes.push(token); + } + + token = new state.Token('link_open', 'a', 1); + token.attrs = [ [ 'href', fullUrl ] ]; + token.level = level++; + token.markup = 'linkify'; + token.info = 'auto'; + nodes.push(token); + + token = new state.Token('text', '', 0); + token.content = urlText; + token.level = level; + nodes.push(token); + + token = new state.Token('link_close', 'a', -1); + token.level = --level; + token.markup = 'linkify'; + token.info = 'auto'; + nodes.push(token); + + lastPos = links[ln].lastIndex; + } + if (lastPos < text.length) { + token = new state.Token('text', '', 0); + token.content = text.slice(lastPos); + token.level = level; + nodes.push(token); + } + + // replace current node + blockTokens[j].children = tokens = arrayReplaceAt(tokens, i, nodes); + } + } + } +}; + +},{"../common/utils":4}],33:[function(require,module,exports){ +// Normalize input string + +'use strict'; + + +// https://spec.commonmark.org/0.29/#line-ending +var NEWLINES_RE = /\r\n?|\n/g; +var NULL_RE = /\0/g; + + +module.exports = function normalize(state) { + var str; + + // Normalize newlines + str = state.src.replace(NEWLINES_RE, '\n'); + + // Replace NULL characters + str = str.replace(NULL_RE, '\uFFFD'); + + state.src = str; +}; + +},{}],34:[function(require,module,exports){ +// Simple typographic replacements +// +// (c) (C) → © +// (tm) (TM) → ™ +// (r) (R) → ® +// +- → ± +// (p) (P) -> § +// ... → … (also ?.... → ?.., !.... → !..) +// ???????? → ???, !!!!! → !!!, `,,` → `,` +// -- → –, --- → — +// +'use strict'; + +// TODO: +// - fractionals 1/2, 1/4, 3/4 -> ½, ¼, ¾ +// - miltiplication 2 x 4 -> 2 × 4 + +var RARE_RE = /\+-|\.\.|\?\?\?\?|!!!!|,,|--/; + +// Workaround for phantomjs - need regex without /g flag, +// or root check will fail every second time +var SCOPED_ABBR_TEST_RE = /\((c|tm|r|p)\)/i; + +var SCOPED_ABBR_RE = /\((c|tm|r|p)\)/ig; +var SCOPED_ABBR = { + c: '©', + r: '®', + p: '§', + tm: '™' +}; + +function replaceFn(match, name) { + return SCOPED_ABBR[name.toLowerCase()]; +} + +function replace_scoped(inlineTokens) { + var i, token, inside_autolink = 0; + + for (i = inlineTokens.length - 1; i >= 0; i--) { + token = inlineTokens[i]; + + if (token.type === 'text' && !inside_autolink) { + token.content = token.content.replace(SCOPED_ABBR_RE, replaceFn); + } + + if (token.type === 'link_open' && token.info === 'auto') { + inside_autolink--; + } + + if (token.type === 'link_close' && token.info === 'auto') { + inside_autolink++; + } + } +} + +function replace_rare(inlineTokens) { + var i, token, inside_autolink = 0; + + for (i = inlineTokens.length - 1; i >= 0; i--) { + token = inlineTokens[i]; + + if (token.type === 'text' && !inside_autolink) { + if (RARE_RE.test(token.content)) { + token.content = token.content + .replace(/\+-/g, '±') + // .., ..., ....... -> … + // but ?..... & !..... -> ?.. & !.. + .replace(/\.{2,}/g, '…').replace(/([?!])…/g, '$1..') + .replace(/([?!]){4,}/g, '$1$1$1').replace(/,{2,}/g, ',') + // em-dash + .replace(/(^|[^-])---([^-]|$)/mg, '$1\u2014$2') + // en-dash + .replace(/(^|\s)--(\s|$)/mg, '$1\u2013$2') + .replace(/(^|[^-\s])--([^-\s]|$)/mg, '$1\u2013$2'); + } + } + + if (token.type === 'link_open' && token.info === 'auto') { + inside_autolink--; + } + + if (token.type === 'link_close' && token.info === 'auto') { + inside_autolink++; + } + } +} + + +module.exports = function replace(state) { + var blkIdx; + + if (!state.md.options.typographer) { return; } + + for (blkIdx = state.tokens.length - 1; blkIdx >= 0; blkIdx--) { + + if (state.tokens[blkIdx].type !== 'inline') { continue; } + + if (SCOPED_ABBR_TEST_RE.test(state.tokens[blkIdx].content)) { + replace_scoped(state.tokens[blkIdx].children); + } + + if (RARE_RE.test(state.tokens[blkIdx].content)) { + replace_rare(state.tokens[blkIdx].children); + } + + } +}; + +},{}],35:[function(require,module,exports){ +// Convert straight quotation marks to typographic ones +// +'use strict'; + + +var isWhiteSpace = require('../common/utils').isWhiteSpace; +var isPunctChar = require('../common/utils').isPunctChar; +var isMdAsciiPunct = require('../common/utils').isMdAsciiPunct; + +var QUOTE_TEST_RE = /['"]/; +var QUOTE_RE = /['"]/g; +var APOSTROPHE = '\u2019'; /* ’ */ + + +function replaceAt(str, index, ch) { + return str.substr(0, index) + ch + str.substr(index + 1); +} + +function process_inlines(tokens, state) { + var i, token, text, t, pos, max, thisLevel, item, lastChar, nextChar, + isLastPunctChar, isNextPunctChar, isLastWhiteSpace, isNextWhiteSpace, + canOpen, canClose, j, isSingle, stack, openQuote, closeQuote; + + stack = []; + + for (i = 0; i < tokens.length; i++) { + token = tokens[i]; + + thisLevel = tokens[i].level; + + for (j = stack.length - 1; j >= 0; j--) { + if (stack[j].level <= thisLevel) { break; } + } + stack.length = j + 1; + + if (token.type !== 'text') { continue; } + + text = token.content; + pos = 0; + max = text.length; + + /*eslint no-labels:0,block-scoped-var:0*/ + OUTER: + while (pos < max) { + QUOTE_RE.lastIndex = pos; + t = QUOTE_RE.exec(text); + if (!t) { break; } + + canOpen = canClose = true; + pos = t.index + 1; + isSingle = (t[0] === "'"); + + // Find previous character, + // default to space if it's the beginning of the line + // + lastChar = 0x20; + + if (t.index - 1 >= 0) { + lastChar = text.charCodeAt(t.index - 1); + } else { + for (j = i - 1; j >= 0; j--) { + if (tokens[j].type === 'softbreak' || tokens[j].type === 'hardbreak') break; // lastChar defaults to 0x20 + if (tokens[j].type !== 'text') continue; + + lastChar = tokens[j].content.charCodeAt(tokens[j].content.length - 1); + break; + } + } + + // Find next character, + // default to space if it's the end of the line + // + nextChar = 0x20; + + if (pos < max) { + nextChar = text.charCodeAt(pos); + } else { + for (j = i + 1; j < tokens.length; j++) { + if (tokens[j].type === 'softbreak' || tokens[j].type === 'hardbreak') break; // nextChar defaults to 0x20 + if (tokens[j].type !== 'text') continue; + + nextChar = tokens[j].content.charCodeAt(0); + break; + } + } + + isLastPunctChar = isMdAsciiPunct(lastChar) || isPunctChar(String.fromCharCode(lastChar)); + isNextPunctChar = isMdAsciiPunct(nextChar) || isPunctChar(String.fromCharCode(nextChar)); + + isLastWhiteSpace = isWhiteSpace(lastChar); + isNextWhiteSpace = isWhiteSpace(nextChar); + + if (isNextWhiteSpace) { + canOpen = false; + } else if (isNextPunctChar) { + if (!(isLastWhiteSpace || isLastPunctChar)) { + canOpen = false; + } + } + + if (isLastWhiteSpace) { + canClose = false; + } else if (isLastPunctChar) { + if (!(isNextWhiteSpace || isNextPunctChar)) { + canClose = false; + } + } + + if (nextChar === 0x22 /* " */ && t[0] === '"') { + if (lastChar >= 0x30 /* 0 */ && lastChar <= 0x39 /* 9 */) { + // special case: 1"" - count first quote as an inch + canClose = canOpen = false; + } + } + + if (canOpen && canClose) { + // treat this as the middle of the word + canOpen = false; + canClose = isNextPunctChar; + } + + if (!canOpen && !canClose) { + // middle of word + if (isSingle) { + token.content = replaceAt(token.content, t.index, APOSTROPHE); + } + continue; + } + + if (canClose) { + // this could be a closing quote, rewind the stack to get a match + for (j = stack.length - 1; j >= 0; j--) { + item = stack[j]; + if (stack[j].level < thisLevel) { break; } + if (item.single === isSingle && stack[j].level === thisLevel) { + item = stack[j]; + + if (isSingle) { + openQuote = state.md.options.quotes[2]; + closeQuote = state.md.options.quotes[3]; + } else { + openQuote = state.md.options.quotes[0]; + closeQuote = state.md.options.quotes[1]; + } + + // replace token.content *before* tokens[item.token].content, + // because, if they are pointing at the same token, replaceAt + // could mess up indices when quote length != 1 + token.content = replaceAt(token.content, t.index, closeQuote); + tokens[item.token].content = replaceAt( + tokens[item.token].content, item.pos, openQuote); + + pos += closeQuote.length - 1; + if (item.token === i) { pos += openQuote.length - 1; } + + text = token.content; + max = text.length; + + stack.length = j; + continue OUTER; + } + } + } + + if (canOpen) { + stack.push({ + token: i, + pos: t.index, + single: isSingle, + level: thisLevel + }); + } else if (canClose && isSingle) { + token.content = replaceAt(token.content, t.index, APOSTROPHE); + } + } + } +} + + +module.exports = function smartquotes(state) { + /*eslint max-depth:0*/ + var blkIdx; + + if (!state.md.options.typographer) { return; } + + for (blkIdx = state.tokens.length - 1; blkIdx >= 0; blkIdx--) { + + if (state.tokens[blkIdx].type !== 'inline' || + !QUOTE_TEST_RE.test(state.tokens[blkIdx].content)) { + continue; + } + + process_inlines(state.tokens[blkIdx].children, state); + } +}; + +},{"../common/utils":4}],36:[function(require,module,exports){ +// Core state object +// +'use strict'; + +var Token = require('../token'); + + +function StateCore(src, md, env) { + this.src = src; + this.env = env; + this.tokens = []; + this.inlineMode = false; + this.md = md; // link to parser instance +} + +// re-export Token class to use in core rules +StateCore.prototype.Token = Token; + + +module.exports = StateCore; + +},{"../token":51}],37:[function(require,module,exports){ +// Process autolinks '' + +'use strict'; + + +/*eslint max-len:0*/ +var EMAIL_RE = /^<([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])?)*)>/; +var AUTOLINK_RE = /^<([a-zA-Z][a-zA-Z0-9+.\-]{1,31}):([^<>\x00-\x20]*)>/; + + +module.exports = function autolink(state, silent) { + var tail, linkMatch, emailMatch, url, fullUrl, token, + pos = state.pos; + + if (state.src.charCodeAt(pos) !== 0x3C/* < */) { return false; } + + tail = state.src.slice(pos); + + if (tail.indexOf('>') < 0) { return false; } + + if (AUTOLINK_RE.test(tail)) { + linkMatch = tail.match(AUTOLINK_RE); + + url = linkMatch[0].slice(1, -1); + fullUrl = state.md.normalizeLink(url); + if (!state.md.validateLink(fullUrl)) { return false; } + + if (!silent) { + token = state.push('link_open', 'a', 1); + token.attrs = [ [ 'href', fullUrl ] ]; + token.markup = 'autolink'; + token.info = 'auto'; + + token = state.push('text', '', 0); + token.content = state.md.normalizeLinkText(url); + + token = state.push('link_close', 'a', -1); + token.markup = 'autolink'; + token.info = 'auto'; + } + + state.pos += linkMatch[0].length; + return true; + } + + if (EMAIL_RE.test(tail)) { + emailMatch = tail.match(EMAIL_RE); + + url = emailMatch[0].slice(1, -1); + fullUrl = state.md.normalizeLink('mailto:' + url); + if (!state.md.validateLink(fullUrl)) { return false; } + + if (!silent) { + token = state.push('link_open', 'a', 1); + token.attrs = [ [ 'href', fullUrl ] ]; + token.markup = 'autolink'; + token.info = 'auto'; + + token = state.push('text', '', 0); + token.content = state.md.normalizeLinkText(url); + + token = state.push('link_close', 'a', -1); + token.markup = 'autolink'; + token.info = 'auto'; + } + + state.pos += emailMatch[0].length; + return true; + } + + return false; +}; + +},{}],38:[function(require,module,exports){ +// Parse backticks + +'use strict'; + +module.exports = function backtick(state, silent) { + var start, max, marker, matchStart, matchEnd, token, + pos = state.pos, + ch = state.src.charCodeAt(pos); + + if (ch !== 0x60/* ` */) { return false; } + + start = pos; + pos++; + max = state.posMax; + + while (pos < max && state.src.charCodeAt(pos) === 0x60/* ` */) { pos++; } + + marker = state.src.slice(start, pos); + + matchStart = matchEnd = pos; + + while ((matchStart = state.src.indexOf('`', matchEnd)) !== -1) { + matchEnd = matchStart + 1; + + while (matchEnd < max && state.src.charCodeAt(matchEnd) === 0x60/* ` */) { matchEnd++; } + + if (matchEnd - matchStart === marker.length) { + if (!silent) { + token = state.push('code_inline', 'code', 0); + token.markup = marker; + token.content = state.src.slice(pos, matchStart) + .replace(/\n/g, ' ') + .replace(/^ (.+) $/, '$1'); + } + state.pos = matchEnd; + return true; + } + } + + if (!silent) { state.pending += marker; } + state.pos += marker.length; + return true; +}; + +},{}],39:[function(require,module,exports){ +// For each opening emphasis-like marker find a matching closing one +// +'use strict'; + + +function processDelimiters(state, delimiters) { + var closerIdx, openerIdx, closer, opener, minOpenerIdx, newMinOpenerIdx, + isOddMatch, lastJump, + openersBottom = {}, + max = delimiters.length; + + for (closerIdx = 0; closerIdx < max; closerIdx++) { + closer = delimiters[closerIdx]; + + // Length is only used for emphasis-specific "rule of 3", + // if it's not defined (in strikethrough or 3rd party plugins), + // we can default it to 0 to disable those checks. + // + closer.length = closer.length || 0; + + if (!closer.close) continue; + + // Previously calculated lower bounds (previous fails) + // for each marker and each delimiter length modulo 3. + if (!openersBottom.hasOwnProperty(closer.marker)) { + openersBottom[closer.marker] = [ -1, -1, -1 ]; + } + + minOpenerIdx = openersBottom[closer.marker][closer.length % 3]; + newMinOpenerIdx = -1; + + openerIdx = closerIdx - closer.jump - 1; + + for (; openerIdx > minOpenerIdx; openerIdx -= opener.jump + 1) { + opener = delimiters[openerIdx]; + + if (opener.marker !== closer.marker) continue; + + if (newMinOpenerIdx === -1) newMinOpenerIdx = openerIdx; + + if (opener.open && + opener.end < 0 && + opener.level === closer.level) { + + isOddMatch = false; + + // from spec: + // + // If one of the delimiters can both open and close emphasis, then the + // sum of the lengths of the delimiter runs containing the opening and + // closing delimiters must not be a multiple of 3 unless both lengths + // are multiples of 3. + // + if (opener.close || closer.open) { + if ((opener.length + closer.length) % 3 === 0) { + if (opener.length % 3 !== 0 || closer.length % 3 !== 0) { + isOddMatch = true; + } + } + } + + if (!isOddMatch) { + // If previous delimiter cannot be an opener, we can safely skip + // the entire sequence in future checks. This is required to make + // sure algorithm has linear complexity (see *_*_*_*_*_... case). + // + lastJump = openerIdx > 0 && !delimiters[openerIdx - 1].open ? + delimiters[openerIdx - 1].jump + 1 : + 0; + + closer.jump = closerIdx - openerIdx + lastJump; + closer.open = false; + opener.end = closerIdx; + opener.jump = lastJump; + opener.close = false; + newMinOpenerIdx = -1; + break; + } + } + } + + if (newMinOpenerIdx !== -1) { + // If match for this delimiter run failed, we want to set lower bound for + // future lookups. This is required to make sure algorithm has linear + // complexity. + // + // See details here: + // https://github.com/commonmark/cmark/issues/178#issuecomment-270417442 + // + openersBottom[closer.marker][(closer.length || 0) % 3] = newMinOpenerIdx; + } + } +} + + +module.exports = function link_pairs(state) { + var curr, + tokens_meta = state.tokens_meta, + max = state.tokens_meta.length; + + processDelimiters(state, state.delimiters); + + for (curr = 0; curr < max; curr++) { + if (tokens_meta[curr] && tokens_meta[curr].delimiters) { + processDelimiters(state, tokens_meta[curr].delimiters); + } + } +}; + +},{}],40:[function(require,module,exports){ +// Process *this* and _that_ +// +'use strict'; + + +// Insert each marker as a separate text token, and add it to delimiter list +// +module.exports.tokenize = function emphasis(state, silent) { + var i, scanned, token, + start = state.pos, + marker = state.src.charCodeAt(start); + + if (silent) { return false; } + + if (marker !== 0x5F /* _ */ && marker !== 0x2A /* * */) { return false; } + + scanned = state.scanDelims(state.pos, marker === 0x2A); + + for (i = 0; i < scanned.length; i++) { + token = state.push('text', '', 0); + token.content = String.fromCharCode(marker); + + state.delimiters.push({ + // Char code of the starting marker (number). + // + marker: marker, + + // Total length of these series of delimiters. + // + length: scanned.length, + + // An amount of characters before this one that's equivalent to + // current one. In plain English: if this delimiter does not open + // an emphasis, neither do previous `jump` characters. + // + // Used to skip sequences like "*****" in one step, for 1st asterisk + // value will be 0, for 2nd it's 1 and so on. + // + jump: i, + + // A position of the token this delimiter corresponds to. + // + token: state.tokens.length - 1, + + // If this delimiter is matched as a valid opener, `end` will be + // equal to its position, otherwise it's `-1`. + // + end: -1, + + // Boolean flags that determine if this delimiter could open or close + // an emphasis. + // + open: scanned.can_open, + close: scanned.can_close + }); + } + + state.pos += scanned.length; + + return true; +}; + + +function postProcess(state, delimiters) { + var i, + startDelim, + endDelim, + token, + ch, + isStrong, + max = delimiters.length; + + for (i = max - 1; i >= 0; i--) { + startDelim = delimiters[i]; + + if (startDelim.marker !== 0x5F/* _ */ && startDelim.marker !== 0x2A/* * */) { + continue; + } + + // Process only opening markers + if (startDelim.end === -1) { + continue; + } + + endDelim = delimiters[startDelim.end]; + + // If the previous delimiter has the same marker and is adjacent to this one, + // merge those into one strong delimiter. + // + // `whatever` -> `whatever` + // + isStrong = i > 0 && + delimiters[i - 1].end === startDelim.end + 1 && + delimiters[i - 1].token === startDelim.token - 1 && + delimiters[startDelim.end + 1].token === endDelim.token + 1 && + delimiters[i - 1].marker === startDelim.marker; + + ch = String.fromCharCode(startDelim.marker); + + token = state.tokens[startDelim.token]; + token.type = isStrong ? 'strong_open' : 'em_open'; + token.tag = isStrong ? 'strong' : 'em'; + token.nesting = 1; + token.markup = isStrong ? ch + ch : ch; + token.content = ''; + + token = state.tokens[endDelim.token]; + token.type = isStrong ? 'strong_close' : 'em_close'; + token.tag = isStrong ? 'strong' : 'em'; + token.nesting = -1; + token.markup = isStrong ? ch + ch : ch; + token.content = ''; + + if (isStrong) { + state.tokens[delimiters[i - 1].token].content = ''; + state.tokens[delimiters[startDelim.end + 1].token].content = ''; + i--; + } + } +} + + +// Walk through delimiter list and replace text tokens with tags +// +module.exports.postProcess = function emphasis(state) { + var curr, + tokens_meta = state.tokens_meta, + max = state.tokens_meta.length; + + postProcess(state, state.delimiters); + + for (curr = 0; curr < max; curr++) { + if (tokens_meta[curr] && tokens_meta[curr].delimiters) { + postProcess(state, tokens_meta[curr].delimiters); + } + } +}; + +},{}],41:[function(require,module,exports){ +// Process html entity - {, ¯, ", ... + +'use strict'; + +var entities = require('../common/entities'); +var has = require('../common/utils').has; +var isValidEntityCode = require('../common/utils').isValidEntityCode; +var fromCodePoint = require('../common/utils').fromCodePoint; + + +var DIGITAL_RE = /^&#((?:x[a-f0-9]{1,6}|[0-9]{1,7}));/i; +var NAMED_RE = /^&([a-z][a-z0-9]{1,31});/i; + + +module.exports = function entity(state, silent) { + var ch, code, match, pos = state.pos, max = state.posMax; + + if (state.src.charCodeAt(pos) !== 0x26/* & */) { return false; } + + if (pos + 1 < max) { + ch = state.src.charCodeAt(pos + 1); + + if (ch === 0x23 /* # */) { + match = state.src.slice(pos).match(DIGITAL_RE); + if (match) { + if (!silent) { + code = match[1][0].toLowerCase() === 'x' ? parseInt(match[1].slice(1), 16) : parseInt(match[1], 10); + state.pending += isValidEntityCode(code) ? fromCodePoint(code) : fromCodePoint(0xFFFD); + } + state.pos += match[0].length; + return true; + } + } else { + match = state.src.slice(pos).match(NAMED_RE); + if (match) { + if (has(entities, match[1])) { + if (!silent) { state.pending += entities[match[1]]; } + state.pos += match[0].length; + return true; + } + } + } + } + + if (!silent) { state.pending += '&'; } + state.pos++; + return true; +}; + +},{"../common/entities":1,"../common/utils":4}],42:[function(require,module,exports){ +// Process escaped chars and hardbreaks + +'use strict'; + +var isSpace = require('../common/utils').isSpace; + +var ESCAPED = []; + +for (var i = 0; i < 256; i++) { ESCAPED.push(0); } + +'\\!"#$%&\'()*+,./:;<=>?@[]^_`{|}~-' + .split('').forEach(function (ch) { ESCAPED[ch.charCodeAt(0)] = 1; }); + + +module.exports = function escape(state, silent) { + var ch, pos = state.pos, max = state.posMax; + + if (state.src.charCodeAt(pos) !== 0x5C/* \ */) { return false; } + + pos++; + + if (pos < max) { + ch = state.src.charCodeAt(pos); + + if (ch < 256 && ESCAPED[ch] !== 0) { + if (!silent) { state.pending += state.src[pos]; } + state.pos += 2; + return true; + } + + if (ch === 0x0A) { + if (!silent) { + state.push('hardbreak', 'br', 0); + } + + pos++; + // skip leading whitespaces from next line + while (pos < max) { + ch = state.src.charCodeAt(pos); + if (!isSpace(ch)) { break; } + pos++; + } + + state.pos = pos; + return true; + } + } + + if (!silent) { state.pending += '\\'; } + state.pos++; + return true; +}; + +},{"../common/utils":4}],43:[function(require,module,exports){ +// Process html tags + +'use strict'; + + +var HTML_TAG_RE = require('../common/html_re').HTML_TAG_RE; + + +function isLetter(ch) { + /*eslint no-bitwise:0*/ + var lc = ch | 0x20; // to lower case + return (lc >= 0x61/* a */) && (lc <= 0x7a/* z */); +} + + +module.exports = function html_inline(state, silent) { + var ch, match, max, token, + pos = state.pos; + + if (!state.md.options.html) { return false; } + + // Check start + max = state.posMax; + if (state.src.charCodeAt(pos) !== 0x3C/* < */ || + pos + 2 >= max) { + return false; + } + + // Quick fail on second char + ch = state.src.charCodeAt(pos + 1); + if (ch !== 0x21/* ! */ && + ch !== 0x3F/* ? */ && + ch !== 0x2F/* / */ && + !isLetter(ch)) { + return false; + } + + match = state.src.slice(pos).match(HTML_TAG_RE); + if (!match) { return false; } + + if (!silent) { + token = state.push('html_inline', '', 0); + token.content = state.src.slice(pos, pos + match[0].length); + } + state.pos += match[0].length; + return true; +}; + +},{"../common/html_re":3}],44:[function(require,module,exports){ +// Process ![image]( "title") + +'use strict'; + +var normalizeReference = require('../common/utils').normalizeReference; +var isSpace = require('../common/utils').isSpace; + + +module.exports = function image(state, silent) { + var attrs, + code, + content, + label, + labelEnd, + labelStart, + pos, + ref, + res, + title, + token, + tokens, + start, + href = '', + oldPos = state.pos, + max = state.posMax; + + if (state.src.charCodeAt(state.pos) !== 0x21/* ! */) { return false; } + if (state.src.charCodeAt(state.pos + 1) !== 0x5B/* [ */) { return false; } + + labelStart = state.pos + 2; + labelEnd = state.md.helpers.parseLinkLabel(state, state.pos + 1, false); + + // parser failed to find ']', so it's not a valid link + if (labelEnd < 0) { return false; } + + pos = labelEnd + 1; + if (pos < max && state.src.charCodeAt(pos) === 0x28/* ( */) { + // + // Inline link + // + + // [link]( "title" ) + // ^^ skipping these spaces + pos++; + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (!isSpace(code) && code !== 0x0A) { break; } + } + if (pos >= max) { return false; } + + // [link]( "title" ) + // ^^^^^^ parsing link destination + start = pos; + res = state.md.helpers.parseLinkDestination(state.src, pos, state.posMax); + if (res.ok) { + href = state.md.normalizeLink(res.str); + if (state.md.validateLink(href)) { + pos = res.pos; + } else { + href = ''; + } + } + + // [link]( "title" ) + // ^^ skipping these spaces + start = pos; + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (!isSpace(code) && code !== 0x0A) { break; } + } + + // [link]( "title" ) + // ^^^^^^^ parsing link title + res = state.md.helpers.parseLinkTitle(state.src, pos, state.posMax); + if (pos < max && start !== pos && res.ok) { + title = res.str; + pos = res.pos; + + // [link]( "title" ) + // ^^ skipping these spaces + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (!isSpace(code) && code !== 0x0A) { break; } + } + } else { + title = ''; + } + + if (pos >= max || state.src.charCodeAt(pos) !== 0x29/* ) */) { + state.pos = oldPos; + return false; + } + pos++; + } else { + // + // Link reference + // + if (typeof state.env.references === 'undefined') { return false; } + + if (pos < max && state.src.charCodeAt(pos) === 0x5B/* [ */) { + start = pos + 1; + pos = state.md.helpers.parseLinkLabel(state, pos); + if (pos >= 0) { + label = state.src.slice(start, pos++); + } else { + pos = labelEnd + 1; + } + } else { + pos = labelEnd + 1; + } + + // covers label === '' and label === undefined + // (collapsed reference link and shortcut reference link respectively) + if (!label) { label = state.src.slice(labelStart, labelEnd); } + + ref = state.env.references[normalizeReference(label)]; + if (!ref) { + state.pos = oldPos; + return false; + } + href = ref.href; + title = ref.title; + } + + // + // We found the end of the link, and know for a fact it's a valid link; + // so all that's left to do is to call tokenizer. + // + if (!silent) { + content = state.src.slice(labelStart, labelEnd); + + state.md.inline.parse( + content, + state.md, + state.env, + tokens = [] + ); + + token = state.push('image', 'img', 0); + token.attrs = attrs = [ [ 'src', href ], [ 'alt', '' ] ]; + token.children = tokens; + token.content = content; + + if (title) { + attrs.push([ 'title', title ]); + } + } + + state.pos = pos; + state.posMax = max; + return true; +}; + +},{"../common/utils":4}],45:[function(require,module,exports){ +// Process [link]( "stuff") + +'use strict'; + +var normalizeReference = require('../common/utils').normalizeReference; +var isSpace = require('../common/utils').isSpace; + + +module.exports = function link(state, silent) { + var attrs, + code, + label, + labelEnd, + labelStart, + pos, + res, + ref, + title, + token, + href = '', + oldPos = state.pos, + max = state.posMax, + start = state.pos, + parseReference = true; + + if (state.src.charCodeAt(state.pos) !== 0x5B/* [ */) { return false; } + + labelStart = state.pos + 1; + labelEnd = state.md.helpers.parseLinkLabel(state, state.pos, true); + + // parser failed to find ']', so it's not a valid link + if (labelEnd < 0) { return false; } + + pos = labelEnd + 1; + if (pos < max && state.src.charCodeAt(pos) === 0x28/* ( */) { + // + // Inline link + // + + // might have found a valid shortcut link, disable reference parsing + parseReference = false; + + // [link]( "title" ) + // ^^ skipping these spaces + pos++; + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (!isSpace(code) && code !== 0x0A) { break; } + } + if (pos >= max) { return false; } + + // [link]( "title" ) + // ^^^^^^ parsing link destination + start = pos; + res = state.md.helpers.parseLinkDestination(state.src, pos, state.posMax); + if (res.ok) { + href = state.md.normalizeLink(res.str); + if (state.md.validateLink(href)) { + pos = res.pos; + } else { + href = ''; + } + } + + // [link]( "title" ) + // ^^ skipping these spaces + start = pos; + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (!isSpace(code) && code !== 0x0A) { break; } + } + + // [link]( "title" ) + // ^^^^^^^ parsing link title + res = state.md.helpers.parseLinkTitle(state.src, pos, state.posMax); + if (pos < max && start !== pos && res.ok) { + title = res.str; + pos = res.pos; + + // [link]( "title" ) + // ^^ skipping these spaces + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (!isSpace(code) && code !== 0x0A) { break; } + } + } else { + title = ''; + } + + if (pos >= max || state.src.charCodeAt(pos) !== 0x29/* ) */) { + // parsing a valid shortcut link failed, fallback to reference + parseReference = true; + } + pos++; + } + + if (parseReference) { + // + // Link reference + // + if (typeof state.env.references === 'undefined') { return false; } + + if (pos < max && state.src.charCodeAt(pos) === 0x5B/* [ */) { + start = pos + 1; + pos = state.md.helpers.parseLinkLabel(state, pos); + if (pos >= 0) { + label = state.src.slice(start, pos++); + } else { + pos = labelEnd + 1; + } + } else { + pos = labelEnd + 1; + } + + // covers label === '' and label === undefined + // (collapsed reference link and shortcut reference link respectively) + if (!label) { label = state.src.slice(labelStart, labelEnd); } + + ref = state.env.references[normalizeReference(label)]; + if (!ref) { + state.pos = oldPos; + return false; + } + href = ref.href; + title = ref.title; + } + + // + // We found the end of the link, and know for a fact it's a valid link; + // so all that's left to do is to call tokenizer. + // + if (!silent) { + state.pos = labelStart; + state.posMax = labelEnd; + + token = state.push('link_open', 'a', 1); + token.attrs = attrs = [ [ 'href', href ] ]; + if (title) { + attrs.push([ 'title', title ]); + } + + state.md.inline.tokenize(state); + + token = state.push('link_close', 'a', -1); + } + + state.pos = pos; + state.posMax = max; + return true; +}; + +},{"../common/utils":4}],46:[function(require,module,exports){ +// Proceess '\n' + +'use strict'; + +var isSpace = require('../common/utils').isSpace; + + +module.exports = function newline(state, silent) { + var pmax, max, pos = state.pos; + + if (state.src.charCodeAt(pos) !== 0x0A/* \n */) { return false; } + + pmax = state.pending.length - 1; + max = state.posMax; + + // ' \n' -> hardbreak + // Lookup in pending chars is bad practice! Don't copy to other rules! + // Pending string is stored in concat mode, indexed lookups will cause + // convertion to flat mode. + if (!silent) { + if (pmax >= 0 && state.pending.charCodeAt(pmax) === 0x20) { + if (pmax >= 1 && state.pending.charCodeAt(pmax - 1) === 0x20) { + state.pending = state.pending.replace(/ +$/, ''); + state.push('hardbreak', 'br', 0); + } else { + state.pending = state.pending.slice(0, -1); + state.push('softbreak', 'br', 0); + } + + } else { + state.push('softbreak', 'br', 0); + } + } + + pos++; + + // skip heading spaces for next line + while (pos < max && isSpace(state.src.charCodeAt(pos))) { pos++; } + + state.pos = pos; + return true; +}; + +},{"../common/utils":4}],47:[function(require,module,exports){ +// Inline parser state + +'use strict'; + + +var Token = require('../token'); +var isWhiteSpace = require('../common/utils').isWhiteSpace; +var isPunctChar = require('../common/utils').isPunctChar; +var isMdAsciiPunct = require('../common/utils').isMdAsciiPunct; + + +function StateInline(src, md, env, outTokens) { + this.src = src; + this.env = env; + this.md = md; + this.tokens = outTokens; + this.tokens_meta = Array(outTokens.length); + + this.pos = 0; + this.posMax = this.src.length; + this.level = 0; + this.pending = ''; + this.pendingLevel = 0; + + // Stores { start: end } pairs. Useful for backtrack + // optimization of pairs parse (emphasis, strikes). + this.cache = {}; + + // List of emphasis-like delimiters for current tag + this.delimiters = []; + + // Stack of delimiter lists for upper level tags + this._prev_delimiters = []; +} + + +// Flush pending text +// +StateInline.prototype.pushPending = function () { + var token = new Token('text', '', 0); + token.content = this.pending; + token.level = this.pendingLevel; + this.tokens.push(token); + this.pending = ''; + return token; +}; + + +// Push new token to "stream". +// If pending text exists - flush it as text token +// +StateInline.prototype.push = function (type, tag, nesting) { + if (this.pending) { + this.pushPending(); + } + + var token = new Token(type, tag, nesting); + var token_meta = null; + + if (nesting < 0) { + // closing tag + this.level--; + this.delimiters = this._prev_delimiters.pop(); + } + + token.level = this.level; + + if (nesting > 0) { + // opening tag + this.level++; + this._prev_delimiters.push(this.delimiters); + this.delimiters = []; + token_meta = { delimiters: this.delimiters }; + } + + this.pendingLevel = this.level; + this.tokens.push(token); + this.tokens_meta.push(token_meta); + return token; +}; + + +// Scan a sequence of emphasis-like markers, and determine whether +// it can start an emphasis sequence or end an emphasis sequence. +// +// - start - position to scan from (it should point at a valid marker); +// - canSplitWord - determine if these markers can be found inside a word +// +StateInline.prototype.scanDelims = function (start, canSplitWord) { + var pos = start, lastChar, nextChar, count, can_open, can_close, + isLastWhiteSpace, isLastPunctChar, + isNextWhiteSpace, isNextPunctChar, + left_flanking = true, + right_flanking = true, + max = this.posMax, + marker = this.src.charCodeAt(start); + + // treat beginning of the line as a whitespace + lastChar = start > 0 ? this.src.charCodeAt(start - 1) : 0x20; + + while (pos < max && this.src.charCodeAt(pos) === marker) { pos++; } + + count = pos - start; + + // treat end of the line as a whitespace + nextChar = pos < max ? this.src.charCodeAt(pos) : 0x20; + + isLastPunctChar = isMdAsciiPunct(lastChar) || isPunctChar(String.fromCharCode(lastChar)); + isNextPunctChar = isMdAsciiPunct(nextChar) || isPunctChar(String.fromCharCode(nextChar)); + + isLastWhiteSpace = isWhiteSpace(lastChar); + isNextWhiteSpace = isWhiteSpace(nextChar); + + if (isNextWhiteSpace) { + left_flanking = false; + } else if (isNextPunctChar) { + if (!(isLastWhiteSpace || isLastPunctChar)) { + left_flanking = false; + } + } + + if (isLastWhiteSpace) { + right_flanking = false; + } else if (isLastPunctChar) { + if (!(isNextWhiteSpace || isNextPunctChar)) { + right_flanking = false; + } + } + + if (!canSplitWord) { + can_open = left_flanking && (!right_flanking || isLastPunctChar); + can_close = right_flanking && (!left_flanking || isNextPunctChar); + } else { + can_open = left_flanking; + can_close = right_flanking; + } + + return { + can_open: can_open, + can_close: can_close, + length: count + }; +}; + + +// re-export Token class to use in block rules +StateInline.prototype.Token = Token; + + +module.exports = StateInline; + +},{"../common/utils":4,"../token":51}],48:[function(require,module,exports){ +// ~~strike through~~ +// +'use strict'; + + +// Insert each marker as a separate text token, and add it to delimiter list +// +module.exports.tokenize = function strikethrough(state, silent) { + var i, scanned, token, len, ch, + start = state.pos, + marker = state.src.charCodeAt(start); + + if (silent) { return false; } + + if (marker !== 0x7E/* ~ */) { return false; } + + scanned = state.scanDelims(state.pos, true); + len = scanned.length; + ch = String.fromCharCode(marker); + + if (len < 2) { return false; } + + if (len % 2) { + token = state.push('text', '', 0); + token.content = ch; + len--; + } + + for (i = 0; i < len; i += 2) { + token = state.push('text', '', 0); + token.content = ch + ch; + + state.delimiters.push({ + marker: marker, + length: 0, // disable "rule of 3" length checks meant for emphasis + jump: i, + token: state.tokens.length - 1, + end: -1, + open: scanned.can_open, + close: scanned.can_close + }); + } + + state.pos += scanned.length; + + return true; +}; + + +function postProcess(state, delimiters) { + var i, j, + startDelim, + endDelim, + token, + loneMarkers = [], + max = delimiters.length; + + for (i = 0; i < max; i++) { + startDelim = delimiters[i]; + + if (startDelim.marker !== 0x7E/* ~ */) { + continue; + } + + if (startDelim.end === -1) { + continue; + } + + endDelim = delimiters[startDelim.end]; + + token = state.tokens[startDelim.token]; + token.type = 's_open'; + token.tag = 's'; + token.nesting = 1; + token.markup = '~~'; + token.content = ''; + + token = state.tokens[endDelim.token]; + token.type = 's_close'; + token.tag = 's'; + token.nesting = -1; + token.markup = '~~'; + token.content = ''; + + if (state.tokens[endDelim.token - 1].type === 'text' && + state.tokens[endDelim.token - 1].content === '~') { + + loneMarkers.push(endDelim.token - 1); + } + } + + // If a marker sequence has an odd number of characters, it's splitted + // like this: `~~~~~` -> `~` + `~~` + `~~`, leaving one marker at the + // start of the sequence. + // + // So, we have to move all those markers after subsequent s_close tags. + // + while (loneMarkers.length) { + i = loneMarkers.pop(); + j = i + 1; + + while (j < state.tokens.length && state.tokens[j].type === 's_close') { + j++; + } + + j--; + + if (i !== j) { + token = state.tokens[j]; + state.tokens[j] = state.tokens[i]; + state.tokens[i] = token; + } + } +} + + +// Walk through delimiter list and replace text tokens with tags +// +module.exports.postProcess = function strikethrough(state) { + var curr, + tokens_meta = state.tokens_meta, + max = state.tokens_meta.length; + + postProcess(state, state.delimiters); + + for (curr = 0; curr < max; curr++) { + if (tokens_meta[curr] && tokens_meta[curr].delimiters) { + postProcess(state, tokens_meta[curr].delimiters); + } + } +}; + +},{}],49:[function(require,module,exports){ +// Skip text characters for text token, place those to pending buffer +// and increment current pos + +'use strict'; + + +// Rule to skip pure text +// '{}$%@~+=:' reserved for extentions + +// !, ", #, $, %, &, ', (, ), *, +, ,, -, ., /, :, ;, <, =, >, ?, @, [, \, ], ^, _, `, {, |, }, or ~ + +// !!!! Don't confuse with "Markdown ASCII Punctuation" chars +// http://spec.commonmark.org/0.15/#ascii-punctuation-character +function isTerminatorChar(ch) { + switch (ch) { + case 0x0A/* \n */: + case 0x21/* ! */: + case 0x23/* # */: + case 0x24/* $ */: + case 0x25/* % */: + case 0x26/* & */: + case 0x2A/* * */: + case 0x2B/* + */: + case 0x2D/* - */: + case 0x3A/* : */: + case 0x3C/* < */: + case 0x3D/* = */: + case 0x3E/* > */: + case 0x40/* @ */: + case 0x5B/* [ */: + case 0x5C/* \ */: + case 0x5D/* ] */: + case 0x5E/* ^ */: + case 0x5F/* _ */: + case 0x60/* ` */: + case 0x7B/* { */: + case 0x7D/* } */: + case 0x7E/* ~ */: + return true; + default: + return false; + } +} + +module.exports = function text(state, silent) { + var pos = state.pos; + + while (pos < state.posMax && !isTerminatorChar(state.src.charCodeAt(pos))) { + pos++; + } + + if (pos === state.pos) { return false; } + + if (!silent) { state.pending += state.src.slice(state.pos, pos); } + + state.pos = pos; + + return true; +}; + +// Alternative implementation, for memory. +// +// It costs 10% of performance, but allows extend terminators list, if place it +// to `ParcerInline` property. Probably, will switch to it sometime, such +// flexibility required. + +/* +var TERMINATOR_RE = /[\n!#$%&*+\-:<=>@[\\\]^_`{}~]/; + +module.exports = function text(state, silent) { + var pos = state.pos, + idx = state.src.slice(pos).search(TERMINATOR_RE); + + // first char is terminator -> empty text + if (idx === 0) { return false; } + + // no terminator -> text till end of string + if (idx < 0) { + if (!silent) { state.pending += state.src.slice(pos); } + state.pos = state.src.length; + return true; + } + + if (!silent) { state.pending += state.src.slice(pos, pos + idx); } + + state.pos += idx; + + return true; +};*/ + +},{}],50:[function(require,module,exports){ +// Clean up tokens after emphasis and strikethrough postprocessing: +// merge adjacent text nodes into one and re-calculate all token levels +// +// This is necessary because initially emphasis delimiter markers (*, _, ~) +// are treated as their own separate text tokens. Then emphasis rule either +// leaves them as text (needed to merge with adjacent text) or turns them +// into opening/closing tags (which messes up levels inside). +// +'use strict'; + + +module.exports = function text_collapse(state) { + var curr, last, + level = 0, + tokens = state.tokens, + max = state.tokens.length; + + for (curr = last = 0; curr < max; curr++) { + // re-calculate levels after emphasis/strikethrough turns some text nodes + // into opening/closing tags + if (tokens[curr].nesting < 0) level--; // closing tag + tokens[curr].level = level; + if (tokens[curr].nesting > 0) level++; // opening tag + + if (tokens[curr].type === 'text' && + curr + 1 < max && + tokens[curr + 1].type === 'text') { + + // collapse two adjacent text nodes + tokens[curr + 1].content = tokens[curr].content + tokens[curr + 1].content; + } else { + if (curr !== last) { tokens[last] = tokens[curr]; } + + last++; + } + } + + if (curr !== last) { + tokens.length = last; + } +}; + +},{}],51:[function(require,module,exports){ +// Token class + +'use strict'; + + +/** + * class Token + **/ + +/** + * new Token(type, tag, nesting) + * + * Create new token and fill passed properties. + **/ +function Token(type, tag, nesting) { + /** + * Token#type -> String + * + * Type of the token (string, e.g. "paragraph_open") + **/ + this.type = type; + + /** + * Token#tag -> String + * + * html tag name, e.g. "p" + **/ + this.tag = tag; + + /** + * Token#attrs -> Array + * + * Html attributes. Format: `[ [ name1, value1 ], [ name2, value2 ] ]` + **/ + this.attrs = null; + + /** + * Token#map -> Array + * + * Source map info. Format: `[ line_begin, line_end ]` + **/ + this.map = null; + + /** + * Token#nesting -> Number + * + * Level change (number in {-1, 0, 1} set), where: + * + * - `1` means the tag is opening + * - `0` means the tag is self-closing + * - `-1` means the tag is closing + **/ + this.nesting = nesting; + + /** + * Token#level -> Number + * + * nesting level, the same as `state.level` + **/ + this.level = 0; + + /** + * Token#children -> Array + * + * An array of child nodes (inline and img tokens) + **/ + this.children = null; + + /** + * Token#content -> String + * + * In a case of self-closing tag (code, html, fence, etc.), + * it has contents of this tag. + **/ + this.content = ''; + + /** + * Token#markup -> String + * + * '*' or '_' for emphasis, fence string for fence, etc. + **/ + this.markup = ''; + + /** + * Token#info -> String + * + * fence infostring + **/ + this.info = ''; + + /** + * Token#meta -> Object + * + * A place for plugins to store an arbitrary data + **/ + this.meta = null; + + /** + * Token#block -> Boolean + * + * True for block-level tokens, false for inline tokens. + * Used in renderer to calculate line breaks + **/ + this.block = false; + + /** + * Token#hidden -> Boolean + * + * If it's true, ignore this element when rendering. Used for tight lists + * to hide paragraphs. + **/ + this.hidden = false; +} + + +/** + * Token.attrIndex(name) -> Number + * + * Search attribute index by name. + **/ +Token.prototype.attrIndex = function attrIndex(name) { + var attrs, i, len; + + if (!this.attrs) { return -1; } + + attrs = this.attrs; + + for (i = 0, len = attrs.length; i < len; i++) { + if (attrs[i][0] === name) { return i; } + } + return -1; +}; + + +/** + * Token.attrPush(attrData) + * + * Add `[ name, value ]` attribute to list. Init attrs if necessary + **/ +Token.prototype.attrPush = function attrPush(attrData) { + if (this.attrs) { + this.attrs.push(attrData); + } else { + this.attrs = [ attrData ]; + } +}; + + +/** + * Token.attrSet(name, value) + * + * Set `name` attribute to `value`. Override old value if exists. + **/ +Token.prototype.attrSet = function attrSet(name, value) { + var idx = this.attrIndex(name), + attrData = [ name, value ]; + + if (idx < 0) { + this.attrPush(attrData); + } else { + this.attrs[idx] = attrData; + } +}; + + +/** + * Token.attrGet(name) + * + * Get the value of attribute `name`, or null if it does not exist. + **/ +Token.prototype.attrGet = function attrGet(name) { + var idx = this.attrIndex(name), value = null; + if (idx >= 0) { + value = this.attrs[idx][1]; + } + return value; +}; + + +/** + * Token.attrJoin(name, value) + * + * Join value to existing attribute via space. Or create new attribute if not + * exists. Useful to operate with token classes. + **/ +Token.prototype.attrJoin = function attrJoin(name, value) { + var idx = this.attrIndex(name); + + if (idx < 0) { + this.attrPush([ name, value ]); + } else { + this.attrs[idx][1] = this.attrs[idx][1] + ' ' + value; + } +}; + + +module.exports = Token; + +},{}],52:[function(require,module,exports){ +module.exports={ "Aacute": "\u00C1", "aacute": "\u00E1", "Abreve": "\u0102", "abreve": "\u0103", "ac": "\u223E", "acd": "\u223F", "acE": "\u223E\u0333", "Acirc": "\u00C2", "acirc": "\u00E2", "acute": "\u00B4", "Acy": "\u0410", "acy": "\u0430", "AElig": "\u00C6", "aelig": "\u00E6", "af": "\u2061", "Afr": "\uD835\uDD04", "afr": "\uD835\uDD1E", "Agrave": "\u00C0", "agrave": "\u00E0", "alefsym": "\u2135", "aleph": "\u2135", "Alpha": "\u0391", "alpha": "\u03B1", "Amacr": "\u0100", "amacr": "\u0101", "amalg": "\u2A3F", "amp": "&", "AMP": "&", "andand": "\u2A55", "And": "\u2A53", "and": "\u2227", "andd": "\u2A5C", "andslope": "\u2A58", "andv": "\u2A5A", "ang": "\u2220", "ange": "\u29A4", "angle": "\u2220", "angmsdaa": "\u29A8", "angmsdab": "\u29A9", "angmsdac": "\u29AA", "angmsdad": "\u29AB", "angmsdae": "\u29AC", "angmsdaf": "\u29AD", "angmsdag": "\u29AE", "angmsdah": "\u29AF", "angmsd": "\u2221", "angrt": "\u221F", "angrtvb": "\u22BE", "angrtvbd": "\u299D", "angsph": "\u2222", "angst": "\u00C5", "angzarr": "\u237C", "Aogon": "\u0104", "aogon": "\u0105", "Aopf": "\uD835\uDD38", "aopf": "\uD835\uDD52", "apacir": "\u2A6F", "ap": "\u2248", "apE": "\u2A70", "ape": "\u224A", "apid": "\u224B", "apos": "'", "ApplyFunction": "\u2061", "approx": "\u2248", "approxeq": "\u224A", "Aring": "\u00C5", "aring": "\u00E5", "Ascr": "\uD835\uDC9C", "ascr": "\uD835\uDCB6", "Assign": "\u2254", "ast": "*", "asymp": "\u2248", "asympeq": "\u224D", "Atilde": "\u00C3", "atilde": "\u00E3", "Auml": "\u00C4", "auml": "\u00E4", "awconint": "\u2233", "awint": "\u2A11", "backcong": "\u224C", "backepsilon": "\u03F6", "backprime": "\u2035", "backsim": "\u223D", "backsimeq": "\u22CD", "Backslash": "\u2216", "Barv": "\u2AE7", "barvee": "\u22BD", "barwed": "\u2305", "Barwed": "\u2306", "barwedge": "\u2305", "bbrk": "\u23B5", "bbrktbrk": "\u23B6", "bcong": "\u224C", "Bcy": "\u0411", "bcy": "\u0431", "bdquo": "\u201E", "becaus": "\u2235", "because": "\u2235", "Because": "\u2235", "bemptyv": "\u29B0", "bepsi": "\u03F6", "bernou": "\u212C", "Bernoullis": "\u212C", "Beta": "\u0392", "beta": "\u03B2", "beth": "\u2136", "between": "\u226C", "Bfr": "\uD835\uDD05", "bfr": "\uD835\uDD1F", "bigcap": "\u22C2", "bigcirc": "\u25EF", "bigcup": "\u22C3", "bigodot": "\u2A00", "bigoplus": "\u2A01", "bigotimes": "\u2A02", "bigsqcup": "\u2A06", "bigstar": "\u2605", "bigtriangledown": "\u25BD", "bigtriangleup": "\u25B3", "biguplus": "\u2A04", "bigvee": "\u22C1", "bigwedge": "\u22C0", "bkarow": "\u290D", "blacklozenge": "\u29EB", "blacksquare": "\u25AA", "blacktriangle": "\u25B4", "blacktriangledown": "\u25BE", "blacktriangleleft": "\u25C2", "blacktriangleright": "\u25B8", "blank": "\u2423", "blk12": "\u2592", "blk14": "\u2591", "blk34": "\u2593", "block": "\u2588", "bne": "=\u20E5", "bnequiv": "\u2261\u20E5", "bNot": "\u2AED", "bnot": "\u2310", "Bopf": "\uD835\uDD39", "bopf": "\uD835\uDD53", "bot": "\u22A5", "bottom": "\u22A5", "bowtie": "\u22C8", "boxbox": "\u29C9", "boxdl": "\u2510", "boxdL": "\u2555", "boxDl": "\u2556", "boxDL": "\u2557", "boxdr": "\u250C", "boxdR": "\u2552", "boxDr": "\u2553", "boxDR": "\u2554", "boxh": "\u2500", "boxH": "\u2550", "boxhd": "\u252C", "boxHd": "\u2564", "boxhD": "\u2565", "boxHD": "\u2566", "boxhu": "\u2534", "boxHu": "\u2567", "boxhU": "\u2568", "boxHU": "\u2569", "boxminus": "\u229F", "boxplus": "\u229E", "boxtimes": "\u22A0", "boxul": "\u2518", "boxuL": "\u255B", "boxUl": "\u255C", "boxUL": "\u255D", "boxur": "\u2514", "boxuR": "\u2558", "boxUr": "\u2559", "boxUR": "\u255A", "boxv": "\u2502", "boxV": "\u2551", "boxvh": "\u253C", "boxvH": "\u256A", "boxVh": "\u256B", "boxVH": "\u256C", "boxvl": "\u2524", "boxvL": "\u2561", "boxVl": "\u2562", "boxVL": "\u2563", "boxvr": "\u251C", "boxvR": "\u255E", "boxVr": "\u255F", "boxVR": "\u2560", "bprime": "\u2035", "breve": "\u02D8", "Breve": "\u02D8", "brvbar": "\u00A6", "bscr": "\uD835\uDCB7", "Bscr": "\u212C", "bsemi": "\u204F", "bsim": "\u223D", "bsime": "\u22CD", "bsolb": "\u29C5", "bsol": "\\", "bsolhsub": "\u27C8", "bull": "\u2022", "bullet": "\u2022", "bump": "\u224E", "bumpE": "\u2AAE", "bumpe": "\u224F", "Bumpeq": "\u224E", "bumpeq": "\u224F", "Cacute": "\u0106", "cacute": "\u0107", "capand": "\u2A44", "capbrcup": "\u2A49", "capcap": "\u2A4B", "cap": "\u2229", "Cap": "\u22D2", "capcup": "\u2A47", "capdot": "\u2A40", "CapitalDifferentialD": "\u2145", "caps": "\u2229\uFE00", "caret": "\u2041", "caron": "\u02C7", "Cayleys": "\u212D", "ccaps": "\u2A4D", "Ccaron": "\u010C", "ccaron": "\u010D", "Ccedil": "\u00C7", "ccedil": "\u00E7", "Ccirc": "\u0108", "ccirc": "\u0109", "Cconint": "\u2230", "ccups": "\u2A4C", "ccupssm": "\u2A50", "Cdot": "\u010A", "cdot": "\u010B", "cedil": "\u00B8", "Cedilla": "\u00B8", "cemptyv": "\u29B2", "cent": "\u00A2", "centerdot": "\u00B7", "CenterDot": "\u00B7", "cfr": "\uD835\uDD20", "Cfr": "\u212D", "CHcy": "\u0427", "chcy": "\u0447", "check": "\u2713", "checkmark": "\u2713", "Chi": "\u03A7", "chi": "\u03C7", "circ": "\u02C6", "circeq": "\u2257", "circlearrowleft": "\u21BA", "circlearrowright": "\u21BB", "circledast": "\u229B", "circledcirc": "\u229A", "circleddash": "\u229D", "CircleDot": "\u2299", "circledR": "\u00AE", "circledS": "\u24C8", "CircleMinus": "\u2296", "CirclePlus": "\u2295", "CircleTimes": "\u2297", "cir": "\u25CB", "cirE": "\u29C3", "cire": "\u2257", "cirfnint": "\u2A10", "cirmid": "\u2AEF", "cirscir": "\u29C2", "ClockwiseContourIntegral": "\u2232", "CloseCurlyDoubleQuote": "\u201D", "CloseCurlyQuote": "\u2019", "clubs": "\u2663", "clubsuit": "\u2663", "colon": ":", "Colon": "\u2237", "Colone": "\u2A74", "colone": "\u2254", "coloneq": "\u2254", "comma": ",", "commat": "@", "comp": "\u2201", "compfn": "\u2218", "complement": "\u2201", "complexes": "\u2102", "cong": "\u2245", "congdot": "\u2A6D", "Congruent": "\u2261", "conint": "\u222E", "Conint": "\u222F", "ContourIntegral": "\u222E", "copf": "\uD835\uDD54", "Copf": "\u2102", "coprod": "\u2210", "Coproduct": "\u2210", "copy": "\u00A9", "COPY": "\u00A9", "copysr": "\u2117", "CounterClockwiseContourIntegral": "\u2233", "crarr": "\u21B5", "cross": "\u2717", "Cross": "\u2A2F", "Cscr": "\uD835\uDC9E", "cscr": "\uD835\uDCB8", "csub": "\u2ACF", "csube": "\u2AD1", "csup": "\u2AD0", "csupe": "\u2AD2", "ctdot": "\u22EF", "cudarrl": "\u2938", "cudarrr": "\u2935", "cuepr": "\u22DE", "cuesc": "\u22DF", "cularr": "\u21B6", "cularrp": "\u293D", "cupbrcap": "\u2A48", "cupcap": "\u2A46", "CupCap": "\u224D", "cup": "\u222A", "Cup": "\u22D3", "cupcup": "\u2A4A", "cupdot": "\u228D", "cupor": "\u2A45", "cups": "\u222A\uFE00", "curarr": "\u21B7", "curarrm": "\u293C", "curlyeqprec": "\u22DE", "curlyeqsucc": "\u22DF", "curlyvee": "\u22CE", "curlywedge": "\u22CF", "curren": "\u00A4", "curvearrowleft": "\u21B6", "curvearrowright": "\u21B7", "cuvee": "\u22CE", "cuwed": "\u22CF", "cwconint": "\u2232", "cwint": "\u2231", "cylcty": "\u232D", "dagger": "\u2020", "Dagger": "\u2021", "daleth": "\u2138", "darr": "\u2193", "Darr": "\u21A1", "dArr": "\u21D3", "dash": "\u2010", "Dashv": "\u2AE4", "dashv": "\u22A3", "dbkarow": "\u290F", "dblac": "\u02DD", "Dcaron": "\u010E", "dcaron": "\u010F", "Dcy": "\u0414", "dcy": "\u0434", "ddagger": "\u2021", "ddarr": "\u21CA", "DD": "\u2145", "dd": "\u2146", "DDotrahd": "\u2911", "ddotseq": "\u2A77", "deg": "\u00B0", "Del": "\u2207", "Delta": "\u0394", "delta": "\u03B4", "demptyv": "\u29B1", "dfisht": "\u297F", "Dfr": "\uD835\uDD07", "dfr": "\uD835\uDD21", "dHar": "\u2965", "dharl": "\u21C3", "dharr": "\u21C2", "DiacriticalAcute": "\u00B4", "DiacriticalDot": "\u02D9", "DiacriticalDoubleAcute": "\u02DD", "DiacriticalGrave": "`", "DiacriticalTilde": "\u02DC", "diam": "\u22C4", "diamond": "\u22C4", "Diamond": "\u22C4", "diamondsuit": "\u2666", "diams": "\u2666", "die": "\u00A8", "DifferentialD": "\u2146", "digamma": "\u03DD", "disin": "\u22F2", "div": "\u00F7", "divide": "\u00F7", "divideontimes": "\u22C7", "divonx": "\u22C7", "DJcy": "\u0402", "djcy": "\u0452", "dlcorn": "\u231E", "dlcrop": "\u230D", "dollar": "$", "Dopf": "\uD835\uDD3B", "dopf": "\uD835\uDD55", "Dot": "\u00A8", "dot": "\u02D9", "DotDot": "\u20DC", "doteq": "\u2250", "doteqdot": "\u2251", "DotEqual": "\u2250", "dotminus": "\u2238", "dotplus": "\u2214", "dotsquare": "\u22A1", "doublebarwedge": "\u2306", "DoubleContourIntegral": "\u222F", "DoubleDot": "\u00A8", "DoubleDownArrow": "\u21D3", "DoubleLeftArrow": "\u21D0", "DoubleLeftRightArrow": "\u21D4", "DoubleLeftTee": "\u2AE4", "DoubleLongLeftArrow": "\u27F8", "DoubleLongLeftRightArrow": "\u27FA", "DoubleLongRightArrow": "\u27F9", "DoubleRightArrow": "\u21D2", "DoubleRightTee": "\u22A8", "DoubleUpArrow": "\u21D1", "DoubleUpDownArrow": "\u21D5", "DoubleVerticalBar": "\u2225", "DownArrowBar": "\u2913", "downarrow": "\u2193", "DownArrow": "\u2193", "Downarrow": "\u21D3", "DownArrowUpArrow": "\u21F5", "DownBreve": "\u0311", "downdownarrows": "\u21CA", "downharpoonleft": "\u21C3", "downharpoonright": "\u21C2", "DownLeftRightVector": "\u2950", "DownLeftTeeVector": "\u295E", "DownLeftVectorBar": "\u2956", "DownLeftVector": "\u21BD", "DownRightTeeVector": "\u295F", "DownRightVectorBar": "\u2957", "DownRightVector": "\u21C1", "DownTeeArrow": "\u21A7", "DownTee": "\u22A4", "drbkarow": "\u2910", "drcorn": "\u231F", "drcrop": "\u230C", "Dscr": "\uD835\uDC9F", "dscr": "\uD835\uDCB9", "DScy": "\u0405", "dscy": "\u0455", "dsol": "\u29F6", "Dstrok": "\u0110", "dstrok": "\u0111", "dtdot": "\u22F1", "dtri": "\u25BF", "dtrif": "\u25BE", "duarr": "\u21F5", "duhar": "\u296F", "dwangle": "\u29A6", "DZcy": "\u040F", "dzcy": "\u045F", "dzigrarr": "\u27FF", "Eacute": "\u00C9", "eacute": "\u00E9", "easter": "\u2A6E", "Ecaron": "\u011A", "ecaron": "\u011B", "Ecirc": "\u00CA", "ecirc": "\u00EA", "ecir": "\u2256", "ecolon": "\u2255", "Ecy": "\u042D", "ecy": "\u044D", "eDDot": "\u2A77", "Edot": "\u0116", "edot": "\u0117", "eDot": "\u2251", "ee": "\u2147", "efDot": "\u2252", "Efr": "\uD835\uDD08", "efr": "\uD835\uDD22", "eg": "\u2A9A", "Egrave": "\u00C8", "egrave": "\u00E8", "egs": "\u2A96", "egsdot": "\u2A98", "el": "\u2A99", "Element": "\u2208", "elinters": "\u23E7", "ell": "\u2113", "els": "\u2A95", "elsdot": "\u2A97", "Emacr": "\u0112", "emacr": "\u0113", "empty": "\u2205", "emptyset": "\u2205", "EmptySmallSquare": "\u25FB", "emptyv": "\u2205", "EmptyVerySmallSquare": "\u25AB", "emsp13": "\u2004", "emsp14": "\u2005", "emsp": "\u2003", "ENG": "\u014A", "eng": "\u014B", "ensp": "\u2002", "Eogon": "\u0118", "eogon": "\u0119", "Eopf": "\uD835\uDD3C", "eopf": "\uD835\uDD56", "epar": "\u22D5", "eparsl": "\u29E3", "eplus": "\u2A71", "epsi": "\u03B5", "Epsilon": "\u0395", "epsilon": "\u03B5", "epsiv": "\u03F5", "eqcirc": "\u2256", "eqcolon": "\u2255", "eqsim": "\u2242", "eqslantgtr": "\u2A96", "eqslantless": "\u2A95", "Equal": "\u2A75", "equals": "=", "EqualTilde": "\u2242", "equest": "\u225F", "Equilibrium": "\u21CC", "equiv": "\u2261", "equivDD": "\u2A78", "eqvparsl": "\u29E5", "erarr": "\u2971", "erDot": "\u2253", "escr": "\u212F", "Escr": "\u2130", "esdot": "\u2250", "Esim": "\u2A73", "esim": "\u2242", "Eta": "\u0397", "eta": "\u03B7", "ETH": "\u00D0", "eth": "\u00F0", "Euml": "\u00CB", "euml": "\u00EB", "euro": "\u20AC", "excl": "!", "exist": "\u2203", "Exists": "\u2203", "expectation": "\u2130", "exponentiale": "\u2147", "ExponentialE": "\u2147", "fallingdotseq": "\u2252", "Fcy": "\u0424", "fcy": "\u0444", "female": "\u2640", "ffilig": "\uFB03", "fflig": "\uFB00", "ffllig": "\uFB04", "Ffr": "\uD835\uDD09", "ffr": "\uD835\uDD23", "filig": "\uFB01", "FilledSmallSquare": "\u25FC", "FilledVerySmallSquare": "\u25AA", "fjlig": "fj", "flat": "\u266D", "fllig": "\uFB02", "fltns": "\u25B1", "fnof": "\u0192", "Fopf": "\uD835\uDD3D", "fopf": "\uD835\uDD57", "forall": "\u2200", "ForAll": "\u2200", "fork": "\u22D4", "forkv": "\u2AD9", "Fouriertrf": "\u2131", "fpartint": "\u2A0D", "frac12": "\u00BD", "frac13": "\u2153", "frac14": "\u00BC", "frac15": "\u2155", "frac16": "\u2159", "frac18": "\u215B", "frac23": "\u2154", "frac25": "\u2156", "frac34": "\u00BE", "frac35": "\u2157", "frac38": "\u215C", "frac45": "\u2158", "frac56": "\u215A", "frac58": "\u215D", "frac78": "\u215E", "frasl": "\u2044", "frown": "\u2322", "fscr": "\uD835\uDCBB", "Fscr": "\u2131", "gacute": "\u01F5", "Gamma": "\u0393", "gamma": "\u03B3", "Gammad": "\u03DC", "gammad": "\u03DD", "gap": "\u2A86", "Gbreve": "\u011E", "gbreve": "\u011F", "Gcedil": "\u0122", "Gcirc": "\u011C", "gcirc": "\u011D", "Gcy": "\u0413", "gcy": "\u0433", "Gdot": "\u0120", "gdot": "\u0121", "ge": "\u2265", "gE": "\u2267", "gEl": "\u2A8C", "gel": "\u22DB", "geq": "\u2265", "geqq": "\u2267", "geqslant": "\u2A7E", "gescc": "\u2AA9", "ges": "\u2A7E", "gesdot": "\u2A80", "gesdoto": "\u2A82", "gesdotol": "\u2A84", "gesl": "\u22DB\uFE00", "gesles": "\u2A94", "Gfr": "\uD835\uDD0A", "gfr": "\uD835\uDD24", "gg": "\u226B", "Gg": "\u22D9", "ggg": "\u22D9", "gimel": "\u2137", "GJcy": "\u0403", "gjcy": "\u0453", "gla": "\u2AA5", "gl": "\u2277", "glE": "\u2A92", "glj": "\u2AA4", "gnap": "\u2A8A", "gnapprox": "\u2A8A", "gne": "\u2A88", "gnE": "\u2269", "gneq": "\u2A88", "gneqq": "\u2269", "gnsim": "\u22E7", "Gopf": "\uD835\uDD3E", "gopf": "\uD835\uDD58", "grave": "`", "GreaterEqual": "\u2265", "GreaterEqualLess": "\u22DB", "GreaterFullEqual": "\u2267", "GreaterGreater": "\u2AA2", "GreaterLess": "\u2277", "GreaterSlantEqual": "\u2A7E", "GreaterTilde": "\u2273", "Gscr": "\uD835\uDCA2", "gscr": "\u210A", "gsim": "\u2273", "gsime": "\u2A8E", "gsiml": "\u2A90", "gtcc": "\u2AA7", "gtcir": "\u2A7A", "gt": ">", "GT": ">", "Gt": "\u226B", "gtdot": "\u22D7", "gtlPar": "\u2995", "gtquest": "\u2A7C", "gtrapprox": "\u2A86", "gtrarr": "\u2978", "gtrdot": "\u22D7", "gtreqless": "\u22DB", "gtreqqless": "\u2A8C", "gtrless": "\u2277", "gtrsim": "\u2273", "gvertneqq": "\u2269\uFE00", "gvnE": "\u2269\uFE00", "Hacek": "\u02C7", "hairsp": "\u200A", "half": "\u00BD", "hamilt": "\u210B", "HARDcy": "\u042A", "hardcy": "\u044A", "harrcir": "\u2948", "harr": "\u2194", "hArr": "\u21D4", "harrw": "\u21AD", "Hat": "^", "hbar": "\u210F", "Hcirc": "\u0124", "hcirc": "\u0125", "hearts": "\u2665", "heartsuit": "\u2665", "hellip": "\u2026", "hercon": "\u22B9", "hfr": "\uD835\uDD25", "Hfr": "\u210C", "HilbertSpace": "\u210B", "hksearow": "\u2925", "hkswarow": "\u2926", "hoarr": "\u21FF", "homtht": "\u223B", "hookleftarrow": "\u21A9", "hookrightarrow": "\u21AA", "hopf": "\uD835\uDD59", "Hopf": "\u210D", "horbar": "\u2015", "HorizontalLine": "\u2500", "hscr": "\uD835\uDCBD", "Hscr": "\u210B", "hslash": "\u210F", "Hstrok": "\u0126", "hstrok": "\u0127", "HumpDownHump": "\u224E", "HumpEqual": "\u224F", "hybull": "\u2043", "hyphen": "\u2010", "Iacute": "\u00CD", "iacute": "\u00ED", "ic": "\u2063", "Icirc": "\u00CE", "icirc": "\u00EE", "Icy": "\u0418", "icy": "\u0438", "Idot": "\u0130", "IEcy": "\u0415", "iecy": "\u0435", "iexcl": "\u00A1", "iff": "\u21D4", "ifr": "\uD835\uDD26", "Ifr": "\u2111", "Igrave": "\u00CC", "igrave": "\u00EC", "ii": "\u2148", "iiiint": "\u2A0C", "iiint": "\u222D", "iinfin": "\u29DC", "iiota": "\u2129", "IJlig": "\u0132", "ijlig": "\u0133", "Imacr": "\u012A", "imacr": "\u012B", "image": "\u2111", "ImaginaryI": "\u2148", "imagline": "\u2110", "imagpart": "\u2111", "imath": "\u0131", "Im": "\u2111", "imof": "\u22B7", "imped": "\u01B5", "Implies": "\u21D2", "incare": "\u2105", "in": "\u2208", "infin": "\u221E", "infintie": "\u29DD", "inodot": "\u0131", "intcal": "\u22BA", "int": "\u222B", "Int": "\u222C", "integers": "\u2124", "Integral": "\u222B", "intercal": "\u22BA", "Intersection": "\u22C2", "intlarhk": "\u2A17", "intprod": "\u2A3C", "InvisibleComma": "\u2063", "InvisibleTimes": "\u2062", "IOcy": "\u0401", "iocy": "\u0451", "Iogon": "\u012E", "iogon": "\u012F", "Iopf": "\uD835\uDD40", "iopf": "\uD835\uDD5A", "Iota": "\u0399", "iota": "\u03B9", "iprod": "\u2A3C", "iquest": "\u00BF", "iscr": "\uD835\uDCBE", "Iscr": "\u2110", "isin": "\u2208", "isindot": "\u22F5", "isinE": "\u22F9", "isins": "\u22F4", "isinsv": "\u22F3", "isinv": "\u2208", "it": "\u2062", "Itilde": "\u0128", "itilde": "\u0129", "Iukcy": "\u0406", "iukcy": "\u0456", "Iuml": "\u00CF", "iuml": "\u00EF", "Jcirc": "\u0134", "jcirc": "\u0135", "Jcy": "\u0419", "jcy": "\u0439", "Jfr": "\uD835\uDD0D", "jfr": "\uD835\uDD27", "jmath": "\u0237", "Jopf": "\uD835\uDD41", "jopf": "\uD835\uDD5B", "Jscr": "\uD835\uDCA5", "jscr": "\uD835\uDCBF", "Jsercy": "\u0408", "jsercy": "\u0458", "Jukcy": "\u0404", "jukcy": "\u0454", "Kappa": "\u039A", "kappa": "\u03BA", "kappav": "\u03F0", "Kcedil": "\u0136", "kcedil": "\u0137", "Kcy": "\u041A", "kcy": "\u043A", "Kfr": "\uD835\uDD0E", "kfr": "\uD835\uDD28", "kgreen": "\u0138", "KHcy": "\u0425", "khcy": "\u0445", "KJcy": "\u040C", "kjcy": "\u045C", "Kopf": "\uD835\uDD42", "kopf": "\uD835\uDD5C", "Kscr": "\uD835\uDCA6", "kscr": "\uD835\uDCC0", "lAarr": "\u21DA", "Lacute": "\u0139", "lacute": "\u013A", "laemptyv": "\u29B4", "lagran": "\u2112", "Lambda": "\u039B", "lambda": "\u03BB", "lang": "\u27E8", "Lang": "\u27EA", "langd": "\u2991", "langle": "\u27E8", "lap": "\u2A85", "Laplacetrf": "\u2112", "laquo": "\u00AB", "larrb": "\u21E4", "larrbfs": "\u291F", "larr": "\u2190", "Larr": "\u219E", "lArr": "\u21D0", "larrfs": "\u291D", "larrhk": "\u21A9", "larrlp": "\u21AB", "larrpl": "\u2939", "larrsim": "\u2973", "larrtl": "\u21A2", "latail": "\u2919", "lAtail": "\u291B", "lat": "\u2AAB", "late": "\u2AAD", "lates": "\u2AAD\uFE00", "lbarr": "\u290C", "lBarr": "\u290E", "lbbrk": "\u2772", "lbrace": "{", "lbrack": "[", "lbrke": "\u298B", "lbrksld": "\u298F", "lbrkslu": "\u298D", "Lcaron": "\u013D", "lcaron": "\u013E", "Lcedil": "\u013B", "lcedil": "\u013C", "lceil": "\u2308", "lcub": "{", "Lcy": "\u041B", "lcy": "\u043B", "ldca": "\u2936", "ldquo": "\u201C", "ldquor": "\u201E", "ldrdhar": "\u2967", "ldrushar": "\u294B", "ldsh": "\u21B2", "le": "\u2264", "lE": "\u2266", "LeftAngleBracket": "\u27E8", "LeftArrowBar": "\u21E4", "leftarrow": "\u2190", "LeftArrow": "\u2190", "Leftarrow": "\u21D0", "LeftArrowRightArrow": "\u21C6", "leftarrowtail": "\u21A2", "LeftCeiling": "\u2308", "LeftDoubleBracket": "\u27E6", "LeftDownTeeVector": "\u2961", "LeftDownVectorBar": "\u2959", "LeftDownVector": "\u21C3", "LeftFloor": "\u230A", "leftharpoondown": "\u21BD", "leftharpoonup": "\u21BC", "leftleftarrows": "\u21C7", "leftrightarrow": "\u2194", "LeftRightArrow": "\u2194", "Leftrightarrow": "\u21D4", "leftrightarrows": "\u21C6", "leftrightharpoons": "\u21CB", "leftrightsquigarrow": "\u21AD", "LeftRightVector": "\u294E", "LeftTeeArrow": "\u21A4", "LeftTee": "\u22A3", "LeftTeeVector": "\u295A", "leftthreetimes": "\u22CB", "LeftTriangleBar": "\u29CF", "LeftTriangle": "\u22B2", "LeftTriangleEqual": "\u22B4", "LeftUpDownVector": "\u2951", "LeftUpTeeVector": "\u2960", "LeftUpVectorBar": "\u2958", "LeftUpVector": "\u21BF", "LeftVectorBar": "\u2952", "LeftVector": "\u21BC", "lEg": "\u2A8B", "leg": "\u22DA", "leq": "\u2264", "leqq": "\u2266", "leqslant": "\u2A7D", "lescc": "\u2AA8", "les": "\u2A7D", "lesdot": "\u2A7F", "lesdoto": "\u2A81", "lesdotor": "\u2A83", "lesg": "\u22DA\uFE00", "lesges": "\u2A93", "lessapprox": "\u2A85", "lessdot": "\u22D6", "lesseqgtr": "\u22DA", "lesseqqgtr": "\u2A8B", "LessEqualGreater": "\u22DA", "LessFullEqual": "\u2266", "LessGreater": "\u2276", "lessgtr": "\u2276", "LessLess": "\u2AA1", "lesssim": "\u2272", "LessSlantEqual": "\u2A7D", "LessTilde": "\u2272", "lfisht": "\u297C", "lfloor": "\u230A", "Lfr": "\uD835\uDD0F", "lfr": "\uD835\uDD29", "lg": "\u2276", "lgE": "\u2A91", "lHar": "\u2962", "lhard": "\u21BD", "lharu": "\u21BC", "lharul": "\u296A", "lhblk": "\u2584", "LJcy": "\u0409", "ljcy": "\u0459", "llarr": "\u21C7", "ll": "\u226A", "Ll": "\u22D8", "llcorner": "\u231E", "Lleftarrow": "\u21DA", "llhard": "\u296B", "lltri": "\u25FA", "Lmidot": "\u013F", "lmidot": "\u0140", "lmoustache": "\u23B0", "lmoust": "\u23B0", "lnap": "\u2A89", "lnapprox": "\u2A89", "lne": "\u2A87", "lnE": "\u2268", "lneq": "\u2A87", "lneqq": "\u2268", "lnsim": "\u22E6", "loang": "\u27EC", "loarr": "\u21FD", "lobrk": "\u27E6", "longleftarrow": "\u27F5", "LongLeftArrow": "\u27F5", "Longleftarrow": "\u27F8", "longleftrightarrow": "\u27F7", "LongLeftRightArrow": "\u27F7", "Longleftrightarrow": "\u27FA", "longmapsto": "\u27FC", "longrightarrow": "\u27F6", "LongRightArrow": "\u27F6", "Longrightarrow": "\u27F9", "looparrowleft": "\u21AB", "looparrowright": "\u21AC", "lopar": "\u2985", "Lopf": "\uD835\uDD43", "lopf": "\uD835\uDD5D", "loplus": "\u2A2D", "lotimes": "\u2A34", "lowast": "\u2217", "lowbar": "_", "LowerLeftArrow": "\u2199", "LowerRightArrow": "\u2198", "loz": "\u25CA", "lozenge": "\u25CA", "lozf": "\u29EB", "lpar": "(", "lparlt": "\u2993", "lrarr": "\u21C6", "lrcorner": "\u231F", "lrhar": "\u21CB", "lrhard": "\u296D", "lrm": "\u200E", "lrtri": "\u22BF", "lsaquo": "\u2039", "lscr": "\uD835\uDCC1", "Lscr": "\u2112", "lsh": "\u21B0", "Lsh": "\u21B0", "lsim": "\u2272", "lsime": "\u2A8D", "lsimg": "\u2A8F", "lsqb": "[", "lsquo": "\u2018", "lsquor": "\u201A", "Lstrok": "\u0141", "lstrok": "\u0142", "ltcc": "\u2AA6", "ltcir": "\u2A79", "lt": "<", "LT": "<", "Lt": "\u226A", "ltdot": "\u22D6", "lthree": "\u22CB", "ltimes": "\u22C9", "ltlarr": "\u2976", "ltquest": "\u2A7B", "ltri": "\u25C3", "ltrie": "\u22B4", "ltrif": "\u25C2", "ltrPar": "\u2996", "lurdshar": "\u294A", "luruhar": "\u2966", "lvertneqq": "\u2268\uFE00", "lvnE": "\u2268\uFE00", "macr": "\u00AF", "male": "\u2642", "malt": "\u2720", "maltese": "\u2720", "Map": "\u2905", "map": "\u21A6", "mapsto": "\u21A6", "mapstodown": "\u21A7", "mapstoleft": "\u21A4", "mapstoup": "\u21A5", "marker": "\u25AE", "mcomma": "\u2A29", "Mcy": "\u041C", "mcy": "\u043C", "mdash": "\u2014", "mDDot": "\u223A", "measuredangle": "\u2221", "MediumSpace": "\u205F", "Mellintrf": "\u2133", "Mfr": "\uD835\uDD10", "mfr": "\uD835\uDD2A", "mho": "\u2127", "micro": "\u00B5", "midast": "*", "midcir": "\u2AF0", "mid": "\u2223", "middot": "\u00B7", "minusb": "\u229F", "minus": "\u2212", "minusd": "\u2238", "minusdu": "\u2A2A", "MinusPlus": "\u2213", "mlcp": "\u2ADB", "mldr": "\u2026", "mnplus": "\u2213", "models": "\u22A7", "Mopf": "\uD835\uDD44", "mopf": "\uD835\uDD5E", "mp": "\u2213", "mscr": "\uD835\uDCC2", "Mscr": "\u2133", "mstpos": "\u223E", "Mu": "\u039C", "mu": "\u03BC", "multimap": "\u22B8", "mumap": "\u22B8", "nabla": "\u2207", "Nacute": "\u0143", "nacute": "\u0144", "nang": "\u2220\u20D2", "nap": "\u2249", "napE": "\u2A70\u0338", "napid": "\u224B\u0338", "napos": "\u0149", "napprox": "\u2249", "natural": "\u266E", "naturals": "\u2115", "natur": "\u266E", "nbsp": "\u00A0", "nbump": "\u224E\u0338", "nbumpe": "\u224F\u0338", "ncap": "\u2A43", "Ncaron": "\u0147", "ncaron": "\u0148", "Ncedil": "\u0145", "ncedil": "\u0146", "ncong": "\u2247", "ncongdot": "\u2A6D\u0338", "ncup": "\u2A42", "Ncy": "\u041D", "ncy": "\u043D", "ndash": "\u2013", "nearhk": "\u2924", "nearr": "\u2197", "neArr": "\u21D7", "nearrow": "\u2197", "ne": "\u2260", "nedot": "\u2250\u0338", "NegativeMediumSpace": "\u200B", "NegativeThickSpace": "\u200B", "NegativeThinSpace": "\u200B", "NegativeVeryThinSpace": "\u200B", "nequiv": "\u2262", "nesear": "\u2928", "nesim": "\u2242\u0338", "NestedGreaterGreater": "\u226B", "NestedLessLess": "\u226A", "NewLine": "\n", "nexist": "\u2204", "nexists": "\u2204", "Nfr": "\uD835\uDD11", "nfr": "\uD835\uDD2B", "ngE": "\u2267\u0338", "nge": "\u2271", "ngeq": "\u2271", "ngeqq": "\u2267\u0338", "ngeqslant": "\u2A7E\u0338", "nges": "\u2A7E\u0338", "nGg": "\u22D9\u0338", "ngsim": "\u2275", "nGt": "\u226B\u20D2", "ngt": "\u226F", "ngtr": "\u226F", "nGtv": "\u226B\u0338", "nharr": "\u21AE", "nhArr": "\u21CE", "nhpar": "\u2AF2", "ni": "\u220B", "nis": "\u22FC", "nisd": "\u22FA", "niv": "\u220B", "NJcy": "\u040A", "njcy": "\u045A", "nlarr": "\u219A", "nlArr": "\u21CD", "nldr": "\u2025", "nlE": "\u2266\u0338", "nle": "\u2270", "nleftarrow": "\u219A", "nLeftarrow": "\u21CD", "nleftrightarrow": "\u21AE", "nLeftrightarrow": "\u21CE", "nleq": "\u2270", "nleqq": "\u2266\u0338", "nleqslant": "\u2A7D\u0338", "nles": "\u2A7D\u0338", "nless": "\u226E", "nLl": "\u22D8\u0338", "nlsim": "\u2274", "nLt": "\u226A\u20D2", "nlt": "\u226E", "nltri": "\u22EA", "nltrie": "\u22EC", "nLtv": "\u226A\u0338", "nmid": "\u2224", "NoBreak": "\u2060", "NonBreakingSpace": "\u00A0", "nopf": "\uD835\uDD5F", "Nopf": "\u2115", "Not": "\u2AEC", "not": "\u00AC", "NotCongruent": "\u2262", "NotCupCap": "\u226D", "NotDoubleVerticalBar": "\u2226", "NotElement": "\u2209", "NotEqual": "\u2260", "NotEqualTilde": "\u2242\u0338", "NotExists": "\u2204", "NotGreater": "\u226F", "NotGreaterEqual": "\u2271", "NotGreaterFullEqual": "\u2267\u0338", "NotGreaterGreater": "\u226B\u0338", "NotGreaterLess": "\u2279", "NotGreaterSlantEqual": "\u2A7E\u0338", "NotGreaterTilde": "\u2275", "NotHumpDownHump": "\u224E\u0338", "NotHumpEqual": "\u224F\u0338", "notin": "\u2209", "notindot": "\u22F5\u0338", "notinE": "\u22F9\u0338", "notinva": "\u2209", "notinvb": "\u22F7", "notinvc": "\u22F6", "NotLeftTriangleBar": "\u29CF\u0338", "NotLeftTriangle": "\u22EA", "NotLeftTriangleEqual": "\u22EC", "NotLess": "\u226E", "NotLessEqual": "\u2270", "NotLessGreater": "\u2278", "NotLessLess": "\u226A\u0338", "NotLessSlantEqual": "\u2A7D\u0338", "NotLessTilde": "\u2274", "NotNestedGreaterGreater": "\u2AA2\u0338", "NotNestedLessLess": "\u2AA1\u0338", "notni": "\u220C", "notniva": "\u220C", "notnivb": "\u22FE", "notnivc": "\u22FD", "NotPrecedes": "\u2280", "NotPrecedesEqual": "\u2AAF\u0338", "NotPrecedesSlantEqual": "\u22E0", "NotReverseElement": "\u220C", "NotRightTriangleBar": "\u29D0\u0338", "NotRightTriangle": "\u22EB", "NotRightTriangleEqual": "\u22ED", "NotSquareSubset": "\u228F\u0338", "NotSquareSubsetEqual": "\u22E2", "NotSquareSuperset": "\u2290\u0338", "NotSquareSupersetEqual": "\u22E3", "NotSubset": "\u2282\u20D2", "NotSubsetEqual": "\u2288", "NotSucceeds": "\u2281", "NotSucceedsEqual": "\u2AB0\u0338", "NotSucceedsSlantEqual": "\u22E1", "NotSucceedsTilde": "\u227F\u0338", "NotSuperset": "\u2283\u20D2", "NotSupersetEqual": "\u2289", "NotTilde": "\u2241", "NotTildeEqual": "\u2244", "NotTildeFullEqual": "\u2247", "NotTildeTilde": "\u2249", "NotVerticalBar": "\u2224", "nparallel": "\u2226", "npar": "\u2226", "nparsl": "\u2AFD\u20E5", "npart": "\u2202\u0338", "npolint": "\u2A14", "npr": "\u2280", "nprcue": "\u22E0", "nprec": "\u2280", "npreceq": "\u2AAF\u0338", "npre": "\u2AAF\u0338", "nrarrc": "\u2933\u0338", "nrarr": "\u219B", "nrArr": "\u21CF", "nrarrw": "\u219D\u0338", "nrightarrow": "\u219B", "nRightarrow": "\u21CF", "nrtri": "\u22EB", "nrtrie": "\u22ED", "nsc": "\u2281", "nsccue": "\u22E1", "nsce": "\u2AB0\u0338", "Nscr": "\uD835\uDCA9", "nscr": "\uD835\uDCC3", "nshortmid": "\u2224", "nshortparallel": "\u2226", "nsim": "\u2241", "nsime": "\u2244", "nsimeq": "\u2244", "nsmid": "\u2224", "nspar": "\u2226", "nsqsube": "\u22E2", "nsqsupe": "\u22E3", "nsub": "\u2284", "nsubE": "\u2AC5\u0338", "nsube": "\u2288", "nsubset": "\u2282\u20D2", "nsubseteq": "\u2288", "nsubseteqq": "\u2AC5\u0338", "nsucc": "\u2281", "nsucceq": "\u2AB0\u0338", "nsup": "\u2285", "nsupE": "\u2AC6\u0338", "nsupe": "\u2289", "nsupset": "\u2283\u20D2", "nsupseteq": "\u2289", "nsupseteqq": "\u2AC6\u0338", "ntgl": "\u2279", "Ntilde": "\u00D1", "ntilde": "\u00F1", "ntlg": "\u2278", "ntriangleleft": "\u22EA", "ntrianglelefteq": "\u22EC", "ntriangleright": "\u22EB", "ntrianglerighteq": "\u22ED", "Nu": "\u039D", "nu": "\u03BD", "num": "#", "numero": "\u2116", "numsp": "\u2007", "nvap": "\u224D\u20D2", "nvdash": "\u22AC", "nvDash": "\u22AD", "nVdash": "\u22AE", "nVDash": "\u22AF", "nvge": "\u2265\u20D2", "nvgt": ">\u20D2", "nvHarr": "\u2904", "nvinfin": "\u29DE", "nvlArr": "\u2902", "nvle": "\u2264\u20D2", "nvlt": "<\u20D2", "nvltrie": "\u22B4\u20D2", "nvrArr": "\u2903", "nvrtrie": "\u22B5\u20D2", "nvsim": "\u223C\u20D2", "nwarhk": "\u2923", "nwarr": "\u2196", "nwArr": "\u21D6", "nwarrow": "\u2196", "nwnear": "\u2927", "Oacute": "\u00D3", "oacute": "\u00F3", "oast": "\u229B", "Ocirc": "\u00D4", "ocirc": "\u00F4", "ocir": "\u229A", "Ocy": "\u041E", "ocy": "\u043E", "odash": "\u229D", "Odblac": "\u0150", "odblac": "\u0151", "odiv": "\u2A38", "odot": "\u2299", "odsold": "\u29BC", "OElig": "\u0152", "oelig": "\u0153", "ofcir": "\u29BF", "Ofr": "\uD835\uDD12", "ofr": "\uD835\uDD2C", "ogon": "\u02DB", "Ograve": "\u00D2", "ograve": "\u00F2", "ogt": "\u29C1", "ohbar": "\u29B5", "ohm": "\u03A9", "oint": "\u222E", "olarr": "\u21BA", "olcir": "\u29BE", "olcross": "\u29BB", "oline": "\u203E", "olt": "\u29C0", "Omacr": "\u014C", "omacr": "\u014D", "Omega": "\u03A9", "omega": "\u03C9", "Omicron": "\u039F", "omicron": "\u03BF", "omid": "\u29B6", "ominus": "\u2296", "Oopf": "\uD835\uDD46", "oopf": "\uD835\uDD60", "opar": "\u29B7", "OpenCurlyDoubleQuote": "\u201C", "OpenCurlyQuote": "\u2018", "operp": "\u29B9", "oplus": "\u2295", "orarr": "\u21BB", "Or": "\u2A54", "or": "\u2228", "ord": "\u2A5D", "order": "\u2134", "orderof": "\u2134", "ordf": "\u00AA", "ordm": "\u00BA", "origof": "\u22B6", "oror": "\u2A56", "orslope": "\u2A57", "orv": "\u2A5B", "oS": "\u24C8", "Oscr": "\uD835\uDCAA", "oscr": "\u2134", "Oslash": "\u00D8", "oslash": "\u00F8", "osol": "\u2298", "Otilde": "\u00D5", "otilde": "\u00F5", "otimesas": "\u2A36", "Otimes": "\u2A37", "otimes": "\u2297", "Ouml": "\u00D6", "ouml": "\u00F6", "ovbar": "\u233D", "OverBar": "\u203E", "OverBrace": "\u23DE", "OverBracket": "\u23B4", "OverParenthesis": "\u23DC", "para": "\u00B6", "parallel": "\u2225", "par": "\u2225", "parsim": "\u2AF3", "parsl": "\u2AFD", "part": "\u2202", "PartialD": "\u2202", "Pcy": "\u041F", "pcy": "\u043F", "percnt": "%", "period": ".", "permil": "\u2030", "perp": "\u22A5", "pertenk": "\u2031", "Pfr": "\uD835\uDD13", "pfr": "\uD835\uDD2D", "Phi": "\u03A6", "phi": "\u03C6", "phiv": "\u03D5", "phmmat": "\u2133", "phone": "\u260E", "Pi": "\u03A0", "pi": "\u03C0", "pitchfork": "\u22D4", "piv": "\u03D6", "planck": "\u210F", "planckh": "\u210E", "plankv": "\u210F", "plusacir": "\u2A23", "plusb": "\u229E", "pluscir": "\u2A22", "plus": "+", "plusdo": "\u2214", "plusdu": "\u2A25", "pluse": "\u2A72", "PlusMinus": "\u00B1", "plusmn": "\u00B1", "plussim": "\u2A26", "plustwo": "\u2A27", "pm": "\u00B1", "Poincareplane": "\u210C", "pointint": "\u2A15", "popf": "\uD835\uDD61", "Popf": "\u2119", "pound": "\u00A3", "prap": "\u2AB7", "Pr": "\u2ABB", "pr": "\u227A", "prcue": "\u227C", "precapprox": "\u2AB7", "prec": "\u227A", "preccurlyeq": "\u227C", "Precedes": "\u227A", "PrecedesEqual": "\u2AAF", "PrecedesSlantEqual": "\u227C", "PrecedesTilde": "\u227E", "preceq": "\u2AAF", "precnapprox": "\u2AB9", "precneqq": "\u2AB5", "precnsim": "\u22E8", "pre": "\u2AAF", "prE": "\u2AB3", "precsim": "\u227E", "prime": "\u2032", "Prime": "\u2033", "primes": "\u2119", "prnap": "\u2AB9", "prnE": "\u2AB5", "prnsim": "\u22E8", "prod": "\u220F", "Product": "\u220F", "profalar": "\u232E", "profline": "\u2312", "profsurf": "\u2313", "prop": "\u221D", "Proportional": "\u221D", "Proportion": "\u2237", "propto": "\u221D", "prsim": "\u227E", "prurel": "\u22B0", "Pscr": "\uD835\uDCAB", "pscr": "\uD835\uDCC5", "Psi": "\u03A8", "psi": "\u03C8", "puncsp": "\u2008", "Qfr": "\uD835\uDD14", "qfr": "\uD835\uDD2E", "qint": "\u2A0C", "qopf": "\uD835\uDD62", "Qopf": "\u211A", "qprime": "\u2057", "Qscr": "\uD835\uDCAC", "qscr": "\uD835\uDCC6", "quaternions": "\u210D", "quatint": "\u2A16", "quest": "?", "questeq": "\u225F", "quot": "\"", "QUOT": "\"", "rAarr": "\u21DB", "race": "\u223D\u0331", "Racute": "\u0154", "racute": "\u0155", "radic": "\u221A", "raemptyv": "\u29B3", "rang": "\u27E9", "Rang": "\u27EB", "rangd": "\u2992", "range": "\u29A5", "rangle": "\u27E9", "raquo": "\u00BB", "rarrap": "\u2975", "rarrb": "\u21E5", "rarrbfs": "\u2920", "rarrc": "\u2933", "rarr": "\u2192", "Rarr": "\u21A0", "rArr": "\u21D2", "rarrfs": "\u291E", "rarrhk": "\u21AA", "rarrlp": "\u21AC", "rarrpl": "\u2945", "rarrsim": "\u2974", "Rarrtl": "\u2916", "rarrtl": "\u21A3", "rarrw": "\u219D", "ratail": "\u291A", "rAtail": "\u291C", "ratio": "\u2236", "rationals": "\u211A", "rbarr": "\u290D", "rBarr": "\u290F", "RBarr": "\u2910", "rbbrk": "\u2773", "rbrace": "}", "rbrack": "]", "rbrke": "\u298C", "rbrksld": "\u298E", "rbrkslu": "\u2990", "Rcaron": "\u0158", "rcaron": "\u0159", "Rcedil": "\u0156", "rcedil": "\u0157", "rceil": "\u2309", "rcub": "}", "Rcy": "\u0420", "rcy": "\u0440", "rdca": "\u2937", "rdldhar": "\u2969", "rdquo": "\u201D", "rdquor": "\u201D", "rdsh": "\u21B3", "real": "\u211C", "realine": "\u211B", "realpart": "\u211C", "reals": "\u211D", "Re": "\u211C", "rect": "\u25AD", "reg": "\u00AE", "REG": "\u00AE", "ReverseElement": "\u220B", "ReverseEquilibrium": "\u21CB", "ReverseUpEquilibrium": "\u296F", "rfisht": "\u297D", "rfloor": "\u230B", "rfr": "\uD835\uDD2F", "Rfr": "\u211C", "rHar": "\u2964", "rhard": "\u21C1", "rharu": "\u21C0", "rharul": "\u296C", "Rho": "\u03A1", "rho": "\u03C1", "rhov": "\u03F1", "RightAngleBracket": "\u27E9", "RightArrowBar": "\u21E5", "rightarrow": "\u2192", "RightArrow": "\u2192", "Rightarrow": "\u21D2", "RightArrowLeftArrow": "\u21C4", "rightarrowtail": "\u21A3", "RightCeiling": "\u2309", "RightDoubleBracket": "\u27E7", "RightDownTeeVector": "\u295D", "RightDownVectorBar": "\u2955", "RightDownVector": "\u21C2", "RightFloor": "\u230B", "rightharpoondown": "\u21C1", "rightharpoonup": "\u21C0", "rightleftarrows": "\u21C4", "rightleftharpoons": "\u21CC", "rightrightarrows": "\u21C9", "rightsquigarrow": "\u219D", "RightTeeArrow": "\u21A6", "RightTee": "\u22A2", "RightTeeVector": "\u295B", "rightthreetimes": "\u22CC", "RightTriangleBar": "\u29D0", "RightTriangle": "\u22B3", "RightTriangleEqual": "\u22B5", "RightUpDownVector": "\u294F", "RightUpTeeVector": "\u295C", "RightUpVectorBar": "\u2954", "RightUpVector": "\u21BE", "RightVectorBar": "\u2953", "RightVector": "\u21C0", "ring": "\u02DA", "risingdotseq": "\u2253", "rlarr": "\u21C4", "rlhar": "\u21CC", "rlm": "\u200F", "rmoustache": "\u23B1", "rmoust": "\u23B1", "rnmid": "\u2AEE", "roang": "\u27ED", "roarr": "\u21FE", "robrk": "\u27E7", "ropar": "\u2986", "ropf": "\uD835\uDD63", "Ropf": "\u211D", "roplus": "\u2A2E", "rotimes": "\u2A35", "RoundImplies": "\u2970", "rpar": ")", "rpargt": "\u2994", "rppolint": "\u2A12", "rrarr": "\u21C9", "Rrightarrow": "\u21DB", "rsaquo": "\u203A", "rscr": "\uD835\uDCC7", "Rscr": "\u211B", "rsh": "\u21B1", "Rsh": "\u21B1", "rsqb": "]", "rsquo": "\u2019", "rsquor": "\u2019", "rthree": "\u22CC", "rtimes": "\u22CA", "rtri": "\u25B9", "rtrie": "\u22B5", "rtrif": "\u25B8", "rtriltri": "\u29CE", "RuleDelayed": "\u29F4", "ruluhar": "\u2968", "rx": "\u211E", "Sacute": "\u015A", "sacute": "\u015B", "sbquo": "\u201A", "scap": "\u2AB8", "Scaron": "\u0160", "scaron": "\u0161", "Sc": "\u2ABC", "sc": "\u227B", "sccue": "\u227D", "sce": "\u2AB0", "scE": "\u2AB4", "Scedil": "\u015E", "scedil": "\u015F", "Scirc": "\u015C", "scirc": "\u015D", "scnap": "\u2ABA", "scnE": "\u2AB6", "scnsim": "\u22E9", "scpolint": "\u2A13", "scsim": "\u227F", "Scy": "\u0421", "scy": "\u0441", "sdotb": "\u22A1", "sdot": "\u22C5", "sdote": "\u2A66", "searhk": "\u2925", "searr": "\u2198", "seArr": "\u21D8", "searrow": "\u2198", "sect": "\u00A7", "semi": ";", "seswar": "\u2929", "setminus": "\u2216", "setmn": "\u2216", "sext": "\u2736", "Sfr": "\uD835\uDD16", "sfr": "\uD835\uDD30", "sfrown": "\u2322", "sharp": "\u266F", "SHCHcy": "\u0429", "shchcy": "\u0449", "SHcy": "\u0428", "shcy": "\u0448", "ShortDownArrow": "\u2193", "ShortLeftArrow": "\u2190", "shortmid": "\u2223", "shortparallel": "\u2225", "ShortRightArrow": "\u2192", "ShortUpArrow": "\u2191", "shy": "\u00AD", "Sigma": "\u03A3", "sigma": "\u03C3", "sigmaf": "\u03C2", "sigmav": "\u03C2", "sim": "\u223C", "simdot": "\u2A6A", "sime": "\u2243", "simeq": "\u2243", "simg": "\u2A9E", "simgE": "\u2AA0", "siml": "\u2A9D", "simlE": "\u2A9F", "simne": "\u2246", "simplus": "\u2A24", "simrarr": "\u2972", "slarr": "\u2190", "SmallCircle": "\u2218", "smallsetminus": "\u2216", "smashp": "\u2A33", "smeparsl": "\u29E4", "smid": "\u2223", "smile": "\u2323", "smt": "\u2AAA", "smte": "\u2AAC", "smtes": "\u2AAC\uFE00", "SOFTcy": "\u042C", "softcy": "\u044C", "solbar": "\u233F", "solb": "\u29C4", "sol": "/", "Sopf": "\uD835\uDD4A", "sopf": "\uD835\uDD64", "spades": "\u2660", "spadesuit": "\u2660", "spar": "\u2225", "sqcap": "\u2293", "sqcaps": "\u2293\uFE00", "sqcup": "\u2294", "sqcups": "\u2294\uFE00", "Sqrt": "\u221A", "sqsub": "\u228F", "sqsube": "\u2291", "sqsubset": "\u228F", "sqsubseteq": "\u2291", "sqsup": "\u2290", "sqsupe": "\u2292", "sqsupset": "\u2290", "sqsupseteq": "\u2292", "square": "\u25A1", "Square": "\u25A1", "SquareIntersection": "\u2293", "SquareSubset": "\u228F", "SquareSubsetEqual": "\u2291", "SquareSuperset": "\u2290", "SquareSupersetEqual": "\u2292", "SquareUnion": "\u2294", "squarf": "\u25AA", "squ": "\u25A1", "squf": "\u25AA", "srarr": "\u2192", "Sscr": "\uD835\uDCAE", "sscr": "\uD835\uDCC8", "ssetmn": "\u2216", "ssmile": "\u2323", "sstarf": "\u22C6", "Star": "\u22C6", "star": "\u2606", "starf": "\u2605", "straightepsilon": "\u03F5", "straightphi": "\u03D5", "strns": "\u00AF", "sub": "\u2282", "Sub": "\u22D0", "subdot": "\u2ABD", "subE": "\u2AC5", "sube": "\u2286", "subedot": "\u2AC3", "submult": "\u2AC1", "subnE": "\u2ACB", "subne": "\u228A", "subplus": "\u2ABF", "subrarr": "\u2979", "subset": "\u2282", "Subset": "\u22D0", "subseteq": "\u2286", "subseteqq": "\u2AC5", "SubsetEqual": "\u2286", "subsetneq": "\u228A", "subsetneqq": "\u2ACB", "subsim": "\u2AC7", "subsub": "\u2AD5", "subsup": "\u2AD3", "succapprox": "\u2AB8", "succ": "\u227B", "succcurlyeq": "\u227D", "Succeeds": "\u227B", "SucceedsEqual": "\u2AB0", "SucceedsSlantEqual": "\u227D", "SucceedsTilde": "\u227F", "succeq": "\u2AB0", "succnapprox": "\u2ABA", "succneqq": "\u2AB6", "succnsim": "\u22E9", "succsim": "\u227F", "SuchThat": "\u220B", "sum": "\u2211", "Sum": "\u2211", "sung": "\u266A", "sup1": "\u00B9", "sup2": "\u00B2", "sup3": "\u00B3", "sup": "\u2283", "Sup": "\u22D1", "supdot": "\u2ABE", "supdsub": "\u2AD8", "supE": "\u2AC6", "supe": "\u2287", "supedot": "\u2AC4", "Superset": "\u2283", "SupersetEqual": "\u2287", "suphsol": "\u27C9", "suphsub": "\u2AD7", "suplarr": "\u297B", "supmult": "\u2AC2", "supnE": "\u2ACC", "supne": "\u228B", "supplus": "\u2AC0", "supset": "\u2283", "Supset": "\u22D1", "supseteq": "\u2287", "supseteqq": "\u2AC6", "supsetneq": "\u228B", "supsetneqq": "\u2ACC", "supsim": "\u2AC8", "supsub": "\u2AD4", "supsup": "\u2AD6", "swarhk": "\u2926", "swarr": "\u2199", "swArr": "\u21D9", "swarrow": "\u2199", "swnwar": "\u292A", "szlig": "\u00DF", "Tab": "\t", "target": "\u2316", "Tau": "\u03A4", "tau": "\u03C4", "tbrk": "\u23B4", "Tcaron": "\u0164", "tcaron": "\u0165", "Tcedil": "\u0162", "tcedil": "\u0163", "Tcy": "\u0422", "tcy": "\u0442", "tdot": "\u20DB", "telrec": "\u2315", "Tfr": "\uD835\uDD17", "tfr": "\uD835\uDD31", "there4": "\u2234", "therefore": "\u2234", "Therefore": "\u2234", "Theta": "\u0398", "theta": "\u03B8", "thetasym": "\u03D1", "thetav": "\u03D1", "thickapprox": "\u2248", "thicksim": "\u223C", "ThickSpace": "\u205F\u200A", "ThinSpace": "\u2009", "thinsp": "\u2009", "thkap": "\u2248", "thksim": "\u223C", "THORN": "\u00DE", "thorn": "\u00FE", "tilde": "\u02DC", "Tilde": "\u223C", "TildeEqual": "\u2243", "TildeFullEqual": "\u2245", "TildeTilde": "\u2248", "timesbar": "\u2A31", "timesb": "\u22A0", "times": "\u00D7", "timesd": "\u2A30", "tint": "\u222D", "toea": "\u2928", "topbot": "\u2336", "topcir": "\u2AF1", "top": "\u22A4", "Topf": "\uD835\uDD4B", "topf": "\uD835\uDD65", "topfork": "\u2ADA", "tosa": "\u2929", "tprime": "\u2034", "trade": "\u2122", "TRADE": "\u2122", "triangle": "\u25B5", "triangledown": "\u25BF", "triangleleft": "\u25C3", "trianglelefteq": "\u22B4", "triangleq": "\u225C", "triangleright": "\u25B9", "trianglerighteq": "\u22B5", "tridot": "\u25EC", "trie": "\u225C", "triminus": "\u2A3A", "TripleDot": "\u20DB", "triplus": "\u2A39", "trisb": "\u29CD", "tritime": "\u2A3B", "trpezium": "\u23E2", "Tscr": "\uD835\uDCAF", "tscr": "\uD835\uDCC9", "TScy": "\u0426", "tscy": "\u0446", "TSHcy": "\u040B", "tshcy": "\u045B", "Tstrok": "\u0166", "tstrok": "\u0167", "twixt": "\u226C", "twoheadleftarrow": "\u219E", "twoheadrightarrow": "\u21A0", "Uacute": "\u00DA", "uacute": "\u00FA", "uarr": "\u2191", "Uarr": "\u219F", "uArr": "\u21D1", "Uarrocir": "\u2949", "Ubrcy": "\u040E", "ubrcy": "\u045E", "Ubreve": "\u016C", "ubreve": "\u016D", "Ucirc": "\u00DB", "ucirc": "\u00FB", "Ucy": "\u0423", "ucy": "\u0443", "udarr": "\u21C5", "Udblac": "\u0170", "udblac": "\u0171", "udhar": "\u296E", "ufisht": "\u297E", "Ufr": "\uD835\uDD18", "ufr": "\uD835\uDD32", "Ugrave": "\u00D9", "ugrave": "\u00F9", "uHar": "\u2963", "uharl": "\u21BF", "uharr": "\u21BE", "uhblk": "\u2580", "ulcorn": "\u231C", "ulcorner": "\u231C", "ulcrop": "\u230F", "ultri": "\u25F8", "Umacr": "\u016A", "umacr": "\u016B", "uml": "\u00A8", "UnderBar": "_", "UnderBrace": "\u23DF", "UnderBracket": "\u23B5", "UnderParenthesis": "\u23DD", "Union": "\u22C3", "UnionPlus": "\u228E", "Uogon": "\u0172", "uogon": "\u0173", "Uopf": "\uD835\uDD4C", "uopf": "\uD835\uDD66", "UpArrowBar": "\u2912", "uparrow": "\u2191", "UpArrow": "\u2191", "Uparrow": "\u21D1", "UpArrowDownArrow": "\u21C5", "updownarrow": "\u2195", "UpDownArrow": "\u2195", "Updownarrow": "\u21D5", "UpEquilibrium": "\u296E", "upharpoonleft": "\u21BF", "upharpoonright": "\u21BE", "uplus": "\u228E", "UpperLeftArrow": "\u2196", "UpperRightArrow": "\u2197", "upsi": "\u03C5", "Upsi": "\u03D2", "upsih": "\u03D2", "Upsilon": "\u03A5", "upsilon": "\u03C5", "UpTeeArrow": "\u21A5", "UpTee": "\u22A5", "upuparrows": "\u21C8", "urcorn": "\u231D", "urcorner": "\u231D", "urcrop": "\u230E", "Uring": "\u016E", "uring": "\u016F", "urtri": "\u25F9", "Uscr": "\uD835\uDCB0", "uscr": "\uD835\uDCCA", "utdot": "\u22F0", "Utilde": "\u0168", "utilde": "\u0169", "utri": "\u25B5", "utrif": "\u25B4", "uuarr": "\u21C8", "Uuml": "\u00DC", "uuml": "\u00FC", "uwangle": "\u29A7", "vangrt": "\u299C", "varepsilon": "\u03F5", "varkappa": "\u03F0", "varnothing": "\u2205", "varphi": "\u03D5", "varpi": "\u03D6", "varpropto": "\u221D", "varr": "\u2195", "vArr": "\u21D5", "varrho": "\u03F1", "varsigma": "\u03C2", "varsubsetneq": "\u228A\uFE00", "varsubsetneqq": "\u2ACB\uFE00", "varsupsetneq": "\u228B\uFE00", "varsupsetneqq": "\u2ACC\uFE00", "vartheta": "\u03D1", "vartriangleleft": "\u22B2", "vartriangleright": "\u22B3", "vBar": "\u2AE8", "Vbar": "\u2AEB", "vBarv": "\u2AE9", "Vcy": "\u0412", "vcy": "\u0432", "vdash": "\u22A2", "vDash": "\u22A8", "Vdash": "\u22A9", "VDash": "\u22AB", "Vdashl": "\u2AE6", "veebar": "\u22BB", "vee": "\u2228", "Vee": "\u22C1", "veeeq": "\u225A", "vellip": "\u22EE", "verbar": "|", "Verbar": "\u2016", "vert": "|", "Vert": "\u2016", "VerticalBar": "\u2223", "VerticalLine": "|", "VerticalSeparator": "\u2758", "VerticalTilde": "\u2240", "VeryThinSpace": "\u200A", "Vfr": "\uD835\uDD19", "vfr": "\uD835\uDD33", "vltri": "\u22B2", "vnsub": "\u2282\u20D2", "vnsup": "\u2283\u20D2", "Vopf": "\uD835\uDD4D", "vopf": "\uD835\uDD67", "vprop": "\u221D", "vrtri": "\u22B3", "Vscr": "\uD835\uDCB1", "vscr": "\uD835\uDCCB", "vsubnE": "\u2ACB\uFE00", "vsubne": "\u228A\uFE00", "vsupnE": "\u2ACC\uFE00", "vsupne": "\u228B\uFE00", "Vvdash": "\u22AA", "vzigzag": "\u299A", "Wcirc": "\u0174", "wcirc": "\u0175", "wedbar": "\u2A5F", "wedge": "\u2227", "Wedge": "\u22C0", "wedgeq": "\u2259", "weierp": "\u2118", "Wfr": "\uD835\uDD1A", "wfr": "\uD835\uDD34", "Wopf": "\uD835\uDD4E", "wopf": "\uD835\uDD68", "wp": "\u2118", "wr": "\u2240", "wreath": "\u2240", "Wscr": "\uD835\uDCB2", "wscr": "\uD835\uDCCC", "xcap": "\u22C2", "xcirc": "\u25EF", "xcup": "\u22C3", "xdtri": "\u25BD", "Xfr": "\uD835\uDD1B", "xfr": "\uD835\uDD35", "xharr": "\u27F7", "xhArr": "\u27FA", "Xi": "\u039E", "xi": "\u03BE", "xlarr": "\u27F5", "xlArr": "\u27F8", "xmap": "\u27FC", "xnis": "\u22FB", "xodot": "\u2A00", "Xopf": "\uD835\uDD4F", "xopf": "\uD835\uDD69", "xoplus": "\u2A01", "xotime": "\u2A02", "xrarr": "\u27F6", "xrArr": "\u27F9", "Xscr": "\uD835\uDCB3", "xscr": "\uD835\uDCCD", "xsqcup": "\u2A06", "xuplus": "\u2A04", "xutri": "\u25B3", "xvee": "\u22C1", "xwedge": "\u22C0", "Yacute": "\u00DD", "yacute": "\u00FD", "YAcy": "\u042F", "yacy": "\u044F", "Ycirc": "\u0176", "ycirc": "\u0177", "Ycy": "\u042B", "ycy": "\u044B", "yen": "\u00A5", "Yfr": "\uD835\uDD1C", "yfr": "\uD835\uDD36", "YIcy": "\u0407", "yicy": "\u0457", "Yopf": "\uD835\uDD50", "yopf": "\uD835\uDD6A", "Yscr": "\uD835\uDCB4", "yscr": "\uD835\uDCCE", "YUcy": "\u042E", "yucy": "\u044E", "yuml": "\u00FF", "Yuml": "\u0178", "Zacute": "\u0179", "zacute": "\u017A", "Zcaron": "\u017D", "zcaron": "\u017E", "Zcy": "\u0417", "zcy": "\u0437", "Zdot": "\u017B", "zdot": "\u017C", "zeetrf": "\u2128", "ZeroWidthSpace": "\u200B", "Zeta": "\u0396", "zeta": "\u03B6", "zfr": "\uD835\uDD37", "Zfr": "\u2128", "ZHcy": "\u0416", "zhcy": "\u0436", "zigrarr": "\u21DD", "zopf": "\uD835\uDD6B", "Zopf": "\u2124", "Zscr": "\uD835\uDCB5", "zscr": "\uD835\uDCCF", "zwj": "\u200D", "zwnj": "\u200C" } + +},{}],53:[function(require,module,exports){ +'use strict'; + + +//////////////////////////////////////////////////////////////////////////////// +// Helpers + +// Merge objects +// +function assign(obj /*from1, from2, from3, ...*/) { + var sources = Array.prototype.slice.call(arguments, 1); + + sources.forEach(function (source) { + if (!source) { return; } + + Object.keys(source).forEach(function (key) { + obj[key] = source[key]; + }); + }); + + return obj; +} + +function _class(obj) { return Object.prototype.toString.call(obj); } +function isString(obj) { return _class(obj) === '[object String]'; } +function isObject(obj) { return _class(obj) === '[object Object]'; } +function isRegExp(obj) { return _class(obj) === '[object RegExp]'; } +function isFunction(obj) { return _class(obj) === '[object Function]'; } + + +function escapeRE(str) { return str.replace(/[.?*+^$[\]\\(){}|-]/g, '\\$&'); } + +//////////////////////////////////////////////////////////////////////////////// + + +var defaultOptions = { + fuzzyLink: true, + fuzzyEmail: true, + fuzzyIP: false +}; + + +function isOptionsObj(obj) { + return Object.keys(obj || {}).reduce(function (acc, k) { + return acc || defaultOptions.hasOwnProperty(k); + }, false); +} + + +var defaultSchemas = { + 'http:': { + validate: function (text, pos, self) { + var tail = text.slice(pos); + + if (!self.re.http) { + // compile lazily, because "host"-containing variables can change on tlds update. + self.re.http = new RegExp( + '^\\/\\/' + self.re.src_auth + self.re.src_host_port_strict + self.re.src_path, 'i' + ); + } + if (self.re.http.test(tail)) { + return tail.match(self.re.http)[0].length; + } + return 0; + } + }, + 'https:': 'http:', + 'ftp:': 'http:', + '//': { + validate: function (text, pos, self) { + var tail = text.slice(pos); + + if (!self.re.no_http) { + // compile lazily, because "host"-containing variables can change on tlds update. + self.re.no_http = new RegExp( + '^' + + self.re.src_auth + + // Don't allow single-level domains, because of false positives like '//test' + // with code comments + '(?:localhost|(?:(?:' + self.re.src_domain + ')\\.)+' + self.re.src_domain_root + ')' + + self.re.src_port + + self.re.src_host_terminator + + self.re.src_path, + + 'i' + ); + } + + if (self.re.no_http.test(tail)) { + // should not be `://` & `///`, that protects from errors in protocol name + if (pos >= 3 && text[pos - 3] === ':') { return 0; } + if (pos >= 3 && text[pos - 3] === '/') { return 0; } + return tail.match(self.re.no_http)[0].length; + } + return 0; + } + }, + 'mailto:': { + validate: function (text, pos, self) { + var tail = text.slice(pos); + + if (!self.re.mailto) { + self.re.mailto = new RegExp( + '^' + self.re.src_email_name + '@' + self.re.src_host_strict, 'i' + ); + } + if (self.re.mailto.test(tail)) { + return tail.match(self.re.mailto)[0].length; + } + return 0; + } + } +}; + +/*eslint-disable max-len*/ + +// RE pattern for 2-character tlds (autogenerated by ./support/tlds_2char_gen.js) +var tlds_2ch_src_re = 'a[cdefgilmnoqrstuwxz]|b[abdefghijmnorstvwyz]|c[acdfghiklmnoruvwxyz]|d[ejkmoz]|e[cegrstu]|f[ijkmor]|g[abdefghilmnpqrstuwy]|h[kmnrtu]|i[delmnoqrst]|j[emop]|k[eghimnprwyz]|l[abcikrstuvy]|m[acdeghklmnopqrstuvwxyz]|n[acefgilopruz]|om|p[aefghklmnrstwy]|qa|r[eosuw]|s[abcdeghijklmnortuvxyz]|t[cdfghjklmnortvwz]|u[agksyz]|v[aceginu]|w[fs]|y[et]|z[amw]'; + +// DON'T try to make PRs with changes. Extend TLDs with LinkifyIt.tlds() instead +var tlds_default = 'biz|com|edu|gov|net|org|pro|web|xxx|aero|asia|coop|info|museum|name|shop|рф'.split('|'); + +/*eslint-enable max-len*/ + +//////////////////////////////////////////////////////////////////////////////// + +function resetScanCache(self) { + self.__index__ = -1; + self.__text_cache__ = ''; +} + +function createValidator(re) { + return function (text, pos) { + var tail = text.slice(pos); + + if (re.test(tail)) { + return tail.match(re)[0].length; + } + return 0; + }; +} + +function createNormalizer() { + return function (match, self) { + self.normalize(match); + }; +} + +// Schemas compiler. Build regexps. +// +function compile(self) { + + // Load & clone RE patterns. + var re = self.re = require('./lib/re')(self.__opts__); + + // Define dynamic patterns + var tlds = self.__tlds__.slice(); + + self.onCompile(); + + if (!self.__tlds_replaced__) { + tlds.push(tlds_2ch_src_re); + } + tlds.push(re.src_xn); + + re.src_tlds = tlds.join('|'); + + function untpl(tpl) { return tpl.replace('%TLDS%', re.src_tlds); } + + re.email_fuzzy = RegExp(untpl(re.tpl_email_fuzzy), 'i'); + re.link_fuzzy = RegExp(untpl(re.tpl_link_fuzzy), 'i'); + re.link_no_ip_fuzzy = RegExp(untpl(re.tpl_link_no_ip_fuzzy), 'i'); + re.host_fuzzy_test = RegExp(untpl(re.tpl_host_fuzzy_test), 'i'); + + // + // Compile each schema + // + + var aliases = []; + + self.__compiled__ = {}; // Reset compiled data + + function schemaError(name, val) { + throw new Error('(LinkifyIt) Invalid schema "' + name + '": ' + val); + } + + Object.keys(self.__schemas__).forEach(function (name) { + var val = self.__schemas__[name]; + + // skip disabled methods + if (val === null) { return; } + + var compiled = { validate: null, link: null }; + + self.__compiled__[name] = compiled; + + if (isObject(val)) { + if (isRegExp(val.validate)) { + compiled.validate = createValidator(val.validate); + } else if (isFunction(val.validate)) { + compiled.validate = val.validate; + } else { + schemaError(name, val); + } + + if (isFunction(val.normalize)) { + compiled.normalize = val.normalize; + } else if (!val.normalize) { + compiled.normalize = createNormalizer(); + } else { + schemaError(name, val); + } + + return; + } + + if (isString(val)) { + aliases.push(name); + return; + } + + schemaError(name, val); + }); + + // + // Compile postponed aliases + // + + aliases.forEach(function (alias) { + if (!self.__compiled__[self.__schemas__[alias]]) { + // Silently fail on missed schemas to avoid errons on disable. + // schemaError(alias, self.__schemas__[alias]); + return; + } + + self.__compiled__[alias].validate = + self.__compiled__[self.__schemas__[alias]].validate; + self.__compiled__[alias].normalize = + self.__compiled__[self.__schemas__[alias]].normalize; + }); + + // + // Fake record for guessed links + // + self.__compiled__[''] = { validate: null, normalize: createNormalizer() }; + + // + // Build schema condition + // + var slist = Object.keys(self.__compiled__) + .filter(function (name) { + // Filter disabled & fake schemas + return name.length > 0 && self.__compiled__[name]; + }) + .map(escapeRE) + .join('|'); + // (?!_) cause 1.5x slowdown + self.re.schema_test = RegExp('(^|(?!_)(?:[><\uff5c]|' + re.src_ZPCc + '))(' + slist + ')', 'i'); + self.re.schema_search = RegExp('(^|(?!_)(?:[><\uff5c]|' + re.src_ZPCc + '))(' + slist + ')', 'ig'); + + self.re.pretest = RegExp( + '(' + self.re.schema_test.source + ')|(' + self.re.host_fuzzy_test.source + ')|@', + 'i' + ); + + // + // Cleanup + // + + resetScanCache(self); +} + +/** + * class Match + * + * Match result. Single element of array, returned by [[LinkifyIt#match]] + **/ +function Match(self, shift) { + var start = self.__index__, + end = self.__last_index__, + text = self.__text_cache__.slice(start, end); + + /** + * Match#schema -> String + * + * Prefix (protocol) for matched string. + **/ + this.schema = self.__schema__.toLowerCase(); + /** + * Match#index -> Number + * + * First position of matched string. + **/ + this.index = start + shift; + /** + * Match#lastIndex -> Number + * + * Next position after matched string. + **/ + this.lastIndex = end + shift; + /** + * Match#raw -> String + * + * Matched string. + **/ + this.raw = text; + /** + * Match#text -> String + * + * Notmalized text of matched string. + **/ + this.text = text; + /** + * Match#url -> String + * + * Normalized url of matched string. + **/ + this.url = text; +} + +function createMatch(self, shift) { + var match = new Match(self, shift); + + self.__compiled__[match.schema].normalize(match, self); + + return match; +} + + +/** + * class LinkifyIt + **/ + +/** + * new LinkifyIt(schemas, options) + * - schemas (Object): Optional. Additional schemas to validate (prefix/validator) + * - options (Object): { fuzzyLink|fuzzyEmail|fuzzyIP: true|false } + * + * Creates new linkifier instance with optional additional schemas. + * Can be called without `new` keyword for convenience. + * + * By default understands: + * + * - `http(s)://...` , `ftp://...`, `mailto:...` & `//...` links + * - "fuzzy" links and emails (example.com, foo@bar.com). + * + * `schemas` is an object, where each key/value describes protocol/rule: + * + * - __key__ - link prefix (usually, protocol name with `:` at the end, `skype:` + * for example). `linkify-it` makes shure that prefix is not preceeded with + * alphanumeric char and symbols. Only whitespaces and punctuation allowed. + * - __value__ - rule to check tail after link prefix + * - _String_ - just alias to existing rule + * - _Object_ + * - _validate_ - validator function (should return matched length on success), + * or `RegExp`. + * - _normalize_ - optional function to normalize text & url of matched result + * (for example, for @twitter mentions). + * + * `options`: + * + * - __fuzzyLink__ - recognige URL-s without `http(s):` prefix. Default `true`. + * - __fuzzyIP__ - allow IPs in fuzzy links above. Can conflict with some texts + * like version numbers. Default `false`. + * - __fuzzyEmail__ - recognize emails without `mailto:` prefix. + * + **/ +function LinkifyIt(schemas, options) { + if (!(this instanceof LinkifyIt)) { + return new LinkifyIt(schemas, options); + } + + if (!options) { + if (isOptionsObj(schemas)) { + options = schemas; + schemas = {}; + } + } + + this.__opts__ = assign({}, defaultOptions, options); + + // Cache last tested result. Used to skip repeating steps on next `match` call. + this.__index__ = -1; + this.__last_index__ = -1; // Next scan position + this.__schema__ = ''; + this.__text_cache__ = ''; + + this.__schemas__ = assign({}, defaultSchemas, schemas); + this.__compiled__ = {}; + + this.__tlds__ = tlds_default; + this.__tlds_replaced__ = false; + + this.re = {}; + + compile(this); +} + + +/** chainable + * LinkifyIt#add(schema, definition) + * - schema (String): rule name (fixed pattern prefix) + * - definition (String|RegExp|Object): schema definition + * + * Add new rule definition. See constructor description for details. + **/ +LinkifyIt.prototype.add = function add(schema, definition) { + this.__schemas__[schema] = definition; + compile(this); + return this; +}; + + +/** chainable + * LinkifyIt#set(options) + * - options (Object): { fuzzyLink|fuzzyEmail|fuzzyIP: true|false } + * + * Set recognition options for links without schema. + **/ +LinkifyIt.prototype.set = function set(options) { + this.__opts__ = assign(this.__opts__, options); + return this; +}; + + +/** + * LinkifyIt#test(text) -> Boolean + * + * Searches linkifiable pattern and returns `true` on success or `false` on fail. + **/ +LinkifyIt.prototype.test = function test(text) { + // Reset scan cache + this.__text_cache__ = text; + this.__index__ = -1; + + if (!text.length) { return false; } + + var m, ml, me, len, shift, next, re, tld_pos, at_pos; + + // try to scan for link with schema - that's the most simple rule + if (this.re.schema_test.test(text)) { + re = this.re.schema_search; + re.lastIndex = 0; + while ((m = re.exec(text)) !== null) { + len = this.testSchemaAt(text, m[2], re.lastIndex); + if (len) { + this.__schema__ = m[2]; + this.__index__ = m.index + m[1].length; + this.__last_index__ = m.index + m[0].length + len; + break; + } + } + } + + if (this.__opts__.fuzzyLink && this.__compiled__['http:']) { + // guess schemaless links + tld_pos = text.search(this.re.host_fuzzy_test); + if (tld_pos >= 0) { + // if tld is located after found link - no need to check fuzzy pattern + if (this.__index__ < 0 || tld_pos < this.__index__) { + if ((ml = text.match(this.__opts__.fuzzyIP ? this.re.link_fuzzy : this.re.link_no_ip_fuzzy)) !== null) { + + shift = ml.index + ml[1].length; + + if (this.__index__ < 0 || shift < this.__index__) { + this.__schema__ = ''; + this.__index__ = shift; + this.__last_index__ = ml.index + ml[0].length; + } + } + } + } + } + + if (this.__opts__.fuzzyEmail && this.__compiled__['mailto:']) { + // guess schemaless emails + at_pos = text.indexOf('@'); + if (at_pos >= 0) { + // We can't skip this check, because this cases are possible: + // 192.168.1.1@gmail.com, my.in@example.com + if ((me = text.match(this.re.email_fuzzy)) !== null) { + + shift = me.index + me[1].length; + next = me.index + me[0].length; + + if (this.__index__ < 0 || shift < this.__index__ || + (shift === this.__index__ && next > this.__last_index__)) { + this.__schema__ = 'mailto:'; + this.__index__ = shift; + this.__last_index__ = next; + } + } + } + } + + return this.__index__ >= 0; +}; + + +/** + * LinkifyIt#pretest(text) -> Boolean + * + * Very quick check, that can give false positives. Returns true if link MAY BE + * can exists. Can be used for speed optimization, when you need to check that + * link NOT exists. + **/ +LinkifyIt.prototype.pretest = function pretest(text) { + return this.re.pretest.test(text); +}; + + +/** + * LinkifyIt#testSchemaAt(text, name, position) -> Number + * - text (String): text to scan + * - name (String): rule (schema) name + * - position (Number): text offset to check from + * + * Similar to [[LinkifyIt#test]] but checks only specific protocol tail exactly + * at given position. Returns length of found pattern (0 on fail). + **/ +LinkifyIt.prototype.testSchemaAt = function testSchemaAt(text, schema, pos) { + // If not supported schema check requested - terminate + if (!this.__compiled__[schema.toLowerCase()]) { + return 0; + } + return this.__compiled__[schema.toLowerCase()].validate(text, pos, this); +}; + + +/** + * LinkifyIt#match(text) -> Array|null + * + * Returns array of found link descriptions or `null` on fail. We strongly + * recommend to use [[LinkifyIt#test]] first, for best speed. + * + * ##### Result match description + * + * - __schema__ - link schema, can be empty for fuzzy links, or `//` for + * protocol-neutral links. + * - __index__ - offset of matched text + * - __lastIndex__ - index of next char after mathch end + * - __raw__ - matched text + * - __text__ - normalized text + * - __url__ - link, generated from matched text + **/ +LinkifyIt.prototype.match = function match(text) { + var shift = 0, result = []; + + // Try to take previous element from cache, if .test() called before + if (this.__index__ >= 0 && this.__text_cache__ === text) { + result.push(createMatch(this, shift)); + shift = this.__last_index__; + } + + // Cut head if cache was used + var tail = shift ? text.slice(shift) : text; + + // Scan string until end reached + while (this.test(tail)) { + result.push(createMatch(this, shift)); + + tail = tail.slice(this.__last_index__); + shift += this.__last_index__; + } + + if (result.length) { + return result; + } + + return null; +}; + + +/** chainable + * LinkifyIt#tlds(list [, keepOld]) -> this + * - list (Array): list of tlds + * - keepOld (Boolean): merge with current list if `true` (`false` by default) + * + * Load (or merge) new tlds list. Those are user for fuzzy links (without prefix) + * to avoid false positives. By default this algorythm used: + * + * - hostname with any 2-letter root zones are ok. + * - biz|com|edu|gov|net|org|pro|web|xxx|aero|asia|coop|info|museum|name|shop|рф + * are ok. + * - encoded (`xn--...`) root zones are ok. + * + * If list is replaced, then exact match for 2-chars root zones will be checked. + **/ +LinkifyIt.prototype.tlds = function tlds(list, keepOld) { + list = Array.isArray(list) ? list : [ list ]; + + if (!keepOld) { + this.__tlds__ = list.slice(); + this.__tlds_replaced__ = true; + compile(this); + return this; + } + + this.__tlds__ = this.__tlds__.concat(list) + .sort() + .filter(function (el, idx, arr) { + return el !== arr[idx - 1]; + }) + .reverse(); + + compile(this); + return this; +}; + +/** + * LinkifyIt#normalize(match) + * + * Default normalizer (if schema does not define it's own). + **/ +LinkifyIt.prototype.normalize = function normalize(match) { + + // Do minimal possible changes by default. Need to collect feedback prior + // to move forward https://github.com/markdown-it/linkify-it/issues/1 + + if (!match.schema) { match.url = 'http://' + match.url; } + + if (match.schema === 'mailto:' && !/^mailto:/i.test(match.url)) { + match.url = 'mailto:' + match.url; + } +}; + + +/** + * LinkifyIt#onCompile() + * + * Override to modify basic RegExp-s. + **/ +LinkifyIt.prototype.onCompile = function onCompile() { +}; + + +module.exports = LinkifyIt; + +},{"./lib/re":54}],54:[function(require,module,exports){ +'use strict'; + + +module.exports = function (opts) { + var re = {}; + + // Use direct extract instead of `regenerate` to reduse browserified size + re.src_Any = require('uc.micro/properties/Any/regex').source; + re.src_Cc = require('uc.micro/categories/Cc/regex').source; + re.src_Z = require('uc.micro/categories/Z/regex').source; + re.src_P = require('uc.micro/categories/P/regex').source; + + // \p{\Z\P\Cc\CF} (white spaces + control + format + punctuation) + re.src_ZPCc = [ re.src_Z, re.src_P, re.src_Cc ].join('|'); + + // \p{\Z\Cc} (white spaces + control) + re.src_ZCc = [ re.src_Z, re.src_Cc ].join('|'); + + // Experimental. List of chars, completely prohibited in links + // because can separate it from other part of text + var text_separators = '[><\uff5c]'; + + // All possible word characters (everything without punctuation, spaces & controls) + // Defined via punctuation & spaces to save space + // Should be something like \p{\L\N\S\M} (\w but without `_`) + re.src_pseudo_letter = '(?:(?!' + text_separators + '|' + re.src_ZPCc + ')' + re.src_Any + ')'; + // The same as abothe but without [0-9] + // var src_pseudo_letter_non_d = '(?:(?![0-9]|' + src_ZPCc + ')' + src_Any + ')'; + + //////////////////////////////////////////////////////////////////////////////// + + re.src_ip4 = + + '(?:(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)'; + + // Prohibit any of "@/[]()" in user/pass to avoid wrong domain fetch. + re.src_auth = '(?:(?:(?!' + re.src_ZCc + '|[@/\\[\\]()]).)+@)?'; + + re.src_port = + + '(?::(?:6(?:[0-4]\\d{3}|5(?:[0-4]\\d{2}|5(?:[0-2]\\d|3[0-5])))|[1-5]?\\d{1,4}))?'; + + re.src_host_terminator = + + '(?=$|' + text_separators + '|' + re.src_ZPCc + ')(?!-|_|:\\d|\\.-|\\.(?!$|' + re.src_ZPCc + '))'; + + re.src_path = + + '(?:' + + '[/?#]' + + '(?:' + + '(?!' + re.src_ZCc + '|' + text_separators + '|[()[\\]{}.,"\'?!\\-]).|' + + '\\[(?:(?!' + re.src_ZCc + '|\\]).)*\\]|' + + '\\((?:(?!' + re.src_ZCc + '|[)]).)*\\)|' + + '\\{(?:(?!' + re.src_ZCc + '|[}]).)*\\}|' + + '\\"(?:(?!' + re.src_ZCc + '|["]).)+\\"|' + + "\\'(?:(?!" + re.src_ZCc + "|[']).)+\\'|" + + "\\'(?=" + re.src_pseudo_letter + '|[-]).|' + // allow `I'm_king` if no pair found + '\\.{2,4}[a-zA-Z0-9%/]|' + // github has ... in commit range links, + // google has .... in links (issue #66) + // Restrict to + // - english + // - percent-encoded + // - parts of file path + // until more examples found. + '\\.(?!' + re.src_ZCc + '|[.]).|' + + (opts && opts['---'] ? + '\\-(?!--(?:[^-]|$))(?:-*)|' // `---` => long dash, terminate + : + '\\-+|' + ) + + '\\,(?!' + re.src_ZCc + ').|' + // allow `,,,` in paths + '\\!(?!' + re.src_ZCc + '|[!]).|' + + '\\?(?!' + re.src_ZCc + '|[?]).' + + ')+' + + '|\\/' + + ')?'; + + // Allow anything in markdown spec, forbid quote (") at the first position + // because emails enclosed in quotes are far more common + re.src_email_name = + + '[\\-;:&=\\+\\$,\\.a-zA-Z0-9_][\\-;:&=\\+\\$,\\"\\.a-zA-Z0-9_]*'; + + re.src_xn = + + 'xn--[a-z0-9\\-]{1,59}'; + + // More to read about domain names + // http://serverfault.com/questions/638260/ + + re.src_domain_root = + + // Allow letters & digits (http://test1) + '(?:' + + re.src_xn + + '|' + + re.src_pseudo_letter + '{1,63}' + + ')'; + + re.src_domain = + + '(?:' + + re.src_xn + + '|' + + '(?:' + re.src_pseudo_letter + ')' + + '|' + + '(?:' + re.src_pseudo_letter + '(?:-|' + re.src_pseudo_letter + '){0,61}' + re.src_pseudo_letter + ')' + + ')'; + + re.src_host = + + '(?:' + + // Don't need IP check, because digits are already allowed in normal domain names + // src_ip4 + + // '|' + + '(?:(?:(?:' + re.src_domain + ')\\.)*' + re.src_domain/*_root*/ + ')' + + ')'; + + re.tpl_host_fuzzy = + + '(?:' + + re.src_ip4 + + '|' + + '(?:(?:(?:' + re.src_domain + ')\\.)+(?:%TLDS%))' + + ')'; + + re.tpl_host_no_ip_fuzzy = + + '(?:(?:(?:' + re.src_domain + ')\\.)+(?:%TLDS%))'; + + re.src_host_strict = + + re.src_host + re.src_host_terminator; + + re.tpl_host_fuzzy_strict = + + re.tpl_host_fuzzy + re.src_host_terminator; + + re.src_host_port_strict = + + re.src_host + re.src_port + re.src_host_terminator; + + re.tpl_host_port_fuzzy_strict = + + re.tpl_host_fuzzy + re.src_port + re.src_host_terminator; + + re.tpl_host_port_no_ip_fuzzy_strict = + + re.tpl_host_no_ip_fuzzy + re.src_port + re.src_host_terminator; + + + //////////////////////////////////////////////////////////////////////////////// + // Main rules + + // Rude test fuzzy links by host, for quick deny + re.tpl_host_fuzzy_test = + + 'localhost|www\\.|\\.\\d{1,3}\\.|(?:\\.(?:%TLDS%)(?:' + re.src_ZPCc + '|>|$))'; + + re.tpl_email_fuzzy = + + '(^|' + text_separators + '|"|\\(|' + re.src_ZCc + ')' + + '(' + re.src_email_name + '@' + re.tpl_host_fuzzy_strict + ')'; + + re.tpl_link_fuzzy = + // Fuzzy link can't be prepended with .:/\- and non punctuation. + // but can start with > (markdown blockquote) + '(^|(?![.:/\\-_@])(?:[$+<=>^`|\uff5c]|' + re.src_ZPCc + '))' + + '((?![$+<=>^`|\uff5c])' + re.tpl_host_port_fuzzy_strict + re.src_path + ')'; + + re.tpl_link_no_ip_fuzzy = + // Fuzzy link can't be prepended with .:/\- and non punctuation. + // but can start with > (markdown blockquote) + '(^|(?![.:/\\-_@])(?:[$+<=>^`|\uff5c]|' + re.src_ZPCc + '))' + + '((?![$+<=>^`|\uff5c])' + re.tpl_host_port_no_ip_fuzzy_strict + re.src_path + ')'; + + return re; +}; + +},{"uc.micro/categories/Cc/regex":61,"uc.micro/categories/P/regex":63,"uc.micro/categories/Z/regex":64,"uc.micro/properties/Any/regex":66}],55:[function(require,module,exports){ + +'use strict'; + + +/* eslint-disable no-bitwise */ + +var decodeCache = {}; + +function getDecodeCache(exclude) { + var i, ch, cache = decodeCache[exclude]; + if (cache) { return cache; } + + cache = decodeCache[exclude] = []; + + for (i = 0; i < 128; i++) { + ch = String.fromCharCode(i); + cache.push(ch); + } + + for (i = 0; i < exclude.length; i++) { + ch = exclude.charCodeAt(i); + cache[ch] = '%' + ('0' + ch.toString(16).toUpperCase()).slice(-2); + } + + return cache; +} + + +// Decode percent-encoded string. +// +function decode(string, exclude) { + var cache; + + if (typeof exclude !== 'string') { + exclude = decode.defaultChars; + } + + cache = getDecodeCache(exclude); + + return string.replace(/(%[a-f0-9]{2})+/gi, function(seq) { + var i, l, b1, b2, b3, b4, chr, + result = ''; + + for (i = 0, l = seq.length; i < l; i += 3) { + b1 = parseInt(seq.slice(i + 1, i + 3), 16); + + if (b1 < 0x80) { + result += cache[b1]; + continue; + } + + if ((b1 & 0xE0) === 0xC0 && (i + 3 < l)) { + // 110xxxxx 10xxxxxx + b2 = parseInt(seq.slice(i + 4, i + 6), 16); + + if ((b2 & 0xC0) === 0x80) { + chr = ((b1 << 6) & 0x7C0) | (b2 & 0x3F); + + if (chr < 0x80) { + result += '\ufffd\ufffd'; + } else { + result += String.fromCharCode(chr); + } + + i += 3; + continue; + } + } + + if ((b1 & 0xF0) === 0xE0 && (i + 6 < l)) { + // 1110xxxx 10xxxxxx 10xxxxxx + b2 = parseInt(seq.slice(i + 4, i + 6), 16); + b3 = parseInt(seq.slice(i + 7, i + 9), 16); + + if ((b2 & 0xC0) === 0x80 && (b3 & 0xC0) === 0x80) { + chr = ((b1 << 12) & 0xF000) | ((b2 << 6) & 0xFC0) | (b3 & 0x3F); + + if (chr < 0x800 || (chr >= 0xD800 && chr <= 0xDFFF)) { + result += '\ufffd\ufffd\ufffd'; + } else { + result += String.fromCharCode(chr); + } + + i += 6; + continue; + } + } + + if ((b1 & 0xF8) === 0xF0 && (i + 9 < l)) { + // 111110xx 10xxxxxx 10xxxxxx 10xxxxxx + b2 = parseInt(seq.slice(i + 4, i + 6), 16); + b3 = parseInt(seq.slice(i + 7, i + 9), 16); + b4 = parseInt(seq.slice(i + 10, i + 12), 16); + + if ((b2 & 0xC0) === 0x80 && (b3 & 0xC0) === 0x80 && (b4 & 0xC0) === 0x80) { + chr = ((b1 << 18) & 0x1C0000) | ((b2 << 12) & 0x3F000) | ((b3 << 6) & 0xFC0) | (b4 & 0x3F); + + if (chr < 0x10000 || chr > 0x10FFFF) { + result += '\ufffd\ufffd\ufffd\ufffd'; + } else { + chr -= 0x10000; + result += String.fromCharCode(0xD800 + (chr >> 10), 0xDC00 + (chr & 0x3FF)); + } + + i += 9; + continue; + } + } + + result += '\ufffd'; + } + + return result; + }); +} + + +decode.defaultChars = ';/?:@&=+$,#'; +decode.componentChars = ''; + + +module.exports = decode; + +},{}],56:[function(require,module,exports){ + +'use strict'; + + +var encodeCache = {}; + + +// Create a lookup array where anything but characters in `chars` string +// and alphanumeric chars is percent-encoded. +// +function getEncodeCache(exclude) { + var i, ch, cache = encodeCache[exclude]; + if (cache) { return cache; } + + cache = encodeCache[exclude] = []; + + for (i = 0; i < 128; i++) { + ch = String.fromCharCode(i); + + if (/^[0-9a-z]$/i.test(ch)) { + // always allow unencoded alphanumeric characters + cache.push(ch); + } else { + cache.push('%' + ('0' + i.toString(16).toUpperCase()).slice(-2)); + } + } + + for (i = 0; i < exclude.length; i++) { + cache[exclude.charCodeAt(i)] = exclude[i]; + } + + return cache; +} + + +// Encode unsafe characters with percent-encoding, skipping already +// encoded sequences. +// +// - string - string to encode +// - exclude - list of characters to ignore (in addition to a-zA-Z0-9) +// - keepEscaped - don't encode '%' in a correct escape sequence (default: true) +// +function encode(string, exclude, keepEscaped) { + var i, l, code, nextCode, cache, + result = ''; + + if (typeof exclude !== 'string') { + // encode(string, keepEscaped) + keepEscaped = exclude; + exclude = encode.defaultChars; + } + + if (typeof keepEscaped === 'undefined') { + keepEscaped = true; + } + + cache = getEncodeCache(exclude); + + for (i = 0, l = string.length; i < l; i++) { + code = string.charCodeAt(i); + + if (keepEscaped && code === 0x25 /* % */ && i + 2 < l) { + if (/^[0-9a-f]{2}$/i.test(string.slice(i + 1, i + 3))) { + result += string.slice(i, i + 3); + i += 2; + continue; + } + } + + if (code < 128) { + result += cache[code]; + continue; + } + + if (code >= 0xD800 && code <= 0xDFFF) { + if (code >= 0xD800 && code <= 0xDBFF && i + 1 < l) { + nextCode = string.charCodeAt(i + 1); + if (nextCode >= 0xDC00 && nextCode <= 0xDFFF) { + result += encodeURIComponent(string[i] + string[i + 1]); + i++; + continue; + } + } + result += '%EF%BF%BD'; + continue; + } + + result += encodeURIComponent(string[i]); + } + + return result; +} + +encode.defaultChars = ";/?:@&=+$,-_.!~*'()#"; +encode.componentChars = "-_.!~*'()"; + + +module.exports = encode; + +},{}],57:[function(require,module,exports){ + +'use strict'; + + +module.exports = function format(url) { + var result = ''; + + result += url.protocol || ''; + result += url.slashes ? '//' : ''; + result += url.auth ? url.auth + '@' : ''; + + if (url.hostname && url.hostname.indexOf(':') !== -1) { + // ipv6 address + result += '[' + url.hostname + ']'; + } else { + result += url.hostname || ''; + } + + result += url.port ? ':' + url.port : ''; + result += url.pathname || ''; + result += url.search || ''; + result += url.hash || ''; + + return result; +}; + +},{}],58:[function(require,module,exports){ +'use strict'; + + +module.exports.encode = require('./encode'); +module.exports.decode = require('./decode'); +module.exports.format = require('./format'); +module.exports.parse = require('./parse'); + +},{"./decode":55,"./encode":56,"./format":57,"./parse":59}],59:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; + +// +// Changes from joyent/node: +// +// 1. No leading slash in paths, +// e.g. in `url.parse('http://foo?bar')` pathname is ``, not `/` +// +// 2. Backslashes are not replaced with slashes, +// so `http:\\example.org\` is treated like a relative path +// +// 3. Trailing colon is treated like a part of the path, +// i.e. in `http://example.org:foo` pathname is `:foo` +// +// 4. Nothing is URL-encoded in the resulting object, +// (in joyent/node some chars in auth and paths are encoded) +// +// 5. `url.parse()` does not have `parseQueryString` argument +// +// 6. Removed extraneous result properties: `host`, `path`, `query`, etc., +// which can be constructed using other parts of the url. +// + + +function Url() { + this.protocol = null; + this.slashes = null; + this.auth = null; + this.port = null; + this.hostname = null; + this.hash = null; + this.search = null; + this.pathname = null; +} + +// Reference: RFC 3986, RFC 1808, RFC 2396 + +// define these here so at least they only have to be +// compiled once on the first module load. +var protocolPattern = /^([a-z0-9.+-]+:)/i, + portPattern = /:[0-9]*$/, + + // Special case for a simple path URL + simplePathPattern = /^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/, + + // RFC 2396: characters reserved for delimiting URLs. + // We actually just auto-escape these. + delims = [ '<', '>', '"', '`', ' ', '\r', '\n', '\t' ], + + // RFC 2396: characters not allowed for various reasons. + unwise = [ '{', '}', '|', '\\', '^', '`' ].concat(delims), + + // Allowed by RFCs, but cause of XSS attacks. Always escape these. + autoEscape = [ '\'' ].concat(unwise), + // Characters that are never ever allowed in a hostname. + // Note that any invalid chars are also handled, but these + // are the ones that are *expected* to be seen, so we fast-path + // them. + nonHostChars = [ '%', '/', '?', ';', '#' ].concat(autoEscape), + hostEndingChars = [ '/', '?', '#' ], + hostnameMaxLen = 255, + hostnamePartPattern = /^[+a-z0-9A-Z_-]{0,63}$/, + hostnamePartStart = /^([+a-z0-9A-Z_-]{0,63})(.*)$/, + // protocols that can allow "unsafe" and "unwise" chars. + /* eslint-disable no-script-url */ + // protocols that never have a hostname. + hostlessProtocol = { + 'javascript': true, + 'javascript:': true + }, + // protocols that always contain a // bit. + slashedProtocol = { + 'http': true, + 'https': true, + 'ftp': true, + 'gopher': true, + 'file': true, + 'http:': true, + 'https:': true, + 'ftp:': true, + 'gopher:': true, + 'file:': true + }; + /* eslint-enable no-script-url */ + +function urlParse(url, slashesDenoteHost) { + if (url && url instanceof Url) { return url; } + + var u = new Url(); + u.parse(url, slashesDenoteHost); + return u; +} + +Url.prototype.parse = function(url, slashesDenoteHost) { + var i, l, lowerProto, hec, slashes, + rest = url; + + // trim before proceeding. + // This is to support parse stuff like " http://foo.com \n" + rest = rest.trim(); + + if (!slashesDenoteHost && url.split('#').length === 1) { + // Try fast path regexp + var simplePath = simplePathPattern.exec(rest); + if (simplePath) { + this.pathname = simplePath[1]; + if (simplePath[2]) { + this.search = simplePath[2]; + } + return this; + } + } + + var proto = protocolPattern.exec(rest); + if (proto) { + proto = proto[0]; + lowerProto = proto.toLowerCase(); + this.protocol = proto; + rest = rest.substr(proto.length); + } + + // figure out if it's got a host + // user@server is *always* interpreted as a hostname, and url + // resolution will treat //foo/bar as host=foo,path=bar because that's + // how the browser resolves relative URLs. + if (slashesDenoteHost || proto || rest.match(/^\/\/[^@\/]+@[^@\/]+/)) { + slashes = rest.substr(0, 2) === '//'; + if (slashes && !(proto && hostlessProtocol[proto])) { + rest = rest.substr(2); + this.slashes = true; + } + } + + if (!hostlessProtocol[proto] && + (slashes || (proto && !slashedProtocol[proto]))) { + + // there's a hostname. + // the first instance of /, ?, ;, or # ends the host. + // + // If there is an @ in the hostname, then non-host chars *are* allowed + // to the left of the last @ sign, unless some host-ending character + // comes *before* the @-sign. + // URLs are obnoxious. + // + // ex: + // http://a@b@c/ => user:a@b host:c + // http://a@b?@c => user:a host:c path:/?@c + + // v0.12 TODO(isaacs): This is not quite how Chrome does things. + // Review our test case against browsers more comprehensively. + + // find the first instance of any hostEndingChars + var hostEnd = -1; + for (i = 0; i < hostEndingChars.length; i++) { + hec = rest.indexOf(hostEndingChars[i]); + if (hec !== -1 && (hostEnd === -1 || hec < hostEnd)) { + hostEnd = hec; + } + } + + // at this point, either we have an explicit point where the + // auth portion cannot go past, or the last @ char is the decider. + var auth, atSign; + if (hostEnd === -1) { + // atSign can be anywhere. + atSign = rest.lastIndexOf('@'); + } else { + // atSign must be in auth portion. + // http://a@b/c@d => host:b auth:a path:/c@d + atSign = rest.lastIndexOf('@', hostEnd); + } + + // Now we have a portion which is definitely the auth. + // Pull that off. + if (atSign !== -1) { + auth = rest.slice(0, atSign); + rest = rest.slice(atSign + 1); + this.auth = auth; + } + + // the host is the remaining to the left of the first non-host char + hostEnd = -1; + for (i = 0; i < nonHostChars.length; i++) { + hec = rest.indexOf(nonHostChars[i]); + if (hec !== -1 && (hostEnd === -1 || hec < hostEnd)) { + hostEnd = hec; + } + } + // if we still have not hit it, then the entire thing is a host. + if (hostEnd === -1) { + hostEnd = rest.length; + } + + if (rest[hostEnd - 1] === ':') { hostEnd--; } + var host = rest.slice(0, hostEnd); + rest = rest.slice(hostEnd); + + // pull out port. + this.parseHost(host); + + // we've indicated that there is a hostname, + // so even if it's empty, it has to be present. + this.hostname = this.hostname || ''; + + // if hostname begins with [ and ends with ] + // assume that it's an IPv6 address. + var ipv6Hostname = this.hostname[0] === '[' && + this.hostname[this.hostname.length - 1] === ']'; + + // validate a little. + if (!ipv6Hostname) { + var hostparts = this.hostname.split(/\./); + for (i = 0, l = hostparts.length; i < l; i++) { + var part = hostparts[i]; + if (!part) { continue; } + if (!part.match(hostnamePartPattern)) { + var newpart = ''; + for (var j = 0, k = part.length; j < k; j++) { + if (part.charCodeAt(j) > 127) { + // we replace non-ASCII char with a temporary placeholder + // we need this to make sure size of hostname is not + // broken by replacing non-ASCII by nothing + newpart += 'x'; + } else { + newpart += part[j]; + } + } + // we test again with ASCII char only + if (!newpart.match(hostnamePartPattern)) { + var validParts = hostparts.slice(0, i); + var notHost = hostparts.slice(i + 1); + var bit = part.match(hostnamePartStart); + if (bit) { + validParts.push(bit[1]); + notHost.unshift(bit[2]); + } + if (notHost.length) { + rest = notHost.join('.') + rest; + } + this.hostname = validParts.join('.'); + break; + } + } + } + } + + if (this.hostname.length > hostnameMaxLen) { + this.hostname = ''; + } + + // strip [ and ] from the hostname + // the host field still retains them, though + if (ipv6Hostname) { + this.hostname = this.hostname.substr(1, this.hostname.length - 2); + } + } + + // chop off from the tail first. + var hash = rest.indexOf('#'); + if (hash !== -1) { + // got a fragment string. + this.hash = rest.substr(hash); + rest = rest.slice(0, hash); + } + var qm = rest.indexOf('?'); + if (qm !== -1) { + this.search = rest.substr(qm); + rest = rest.slice(0, qm); + } + if (rest) { this.pathname = rest; } + if (slashedProtocol[lowerProto] && + this.hostname && !this.pathname) { + this.pathname = ''; + } + + return this; +}; + +Url.prototype.parseHost = function(host) { + var port = portPattern.exec(host); + if (port) { + port = port[0]; + if (port !== ':') { + this.port = port.substr(1); + } + host = host.substr(0, host.length - port.length); + } + if (host) { this.hostname = host; } +}; + +module.exports = urlParse; + +},{}],60:[function(require,module,exports){ +(function (global){ +/*! https://mths.be/punycode v1.4.1 by @mathias */ +;(function(root) { + + /** Detect free variables */ + var freeExports = typeof exports == 'object' && exports && + !exports.nodeType && exports; + var freeModule = typeof module == 'object' && module && + !module.nodeType && module; + var freeGlobal = typeof global == 'object' && global; + if ( + freeGlobal.global === freeGlobal || + freeGlobal.window === freeGlobal || + freeGlobal.self === freeGlobal + ) { + root = freeGlobal; + } + + /** + * The `punycode` object. + * @name punycode + * @type Object + */ + var punycode, + + /** Highest positive signed 32-bit float value */ + maxInt = 2147483647, // aka. 0x7FFFFFFF or 2^31-1 + + /** Bootstring parameters */ + base = 36, + tMin = 1, + tMax = 26, + skew = 38, + damp = 700, + initialBias = 72, + initialN = 128, // 0x80 + delimiter = '-', // '\x2D' + + /** Regular expressions */ + regexPunycode = /^xn--/, + regexNonASCII = /[^\x20-\x7E]/, // unprintable ASCII chars + non-ASCII chars + regexSeparators = /[\x2E\u3002\uFF0E\uFF61]/g, // RFC 3490 separators + + /** Error messages */ + errors = { + 'overflow': 'Overflow: input needs wider integers to process', + 'not-basic': 'Illegal input >= 0x80 (not a basic code point)', + 'invalid-input': 'Invalid input' + }, + + /** Convenience shortcuts */ + baseMinusTMin = base - tMin, + floor = Math.floor, + stringFromCharCode = String.fromCharCode, + + /** Temporary variable */ + key; + + /*--------------------------------------------------------------------------*/ + + /** + * A generic error utility function. + * @private + * @param {String} type The error type. + * @returns {Error} Throws a `RangeError` with the applicable error message. + */ + function error(type) { + throw new RangeError(errors[type]); + } + + /** + * A generic `Array#map` utility function. + * @private + * @param {Array} array The array to iterate over. + * @param {Function} callback The function that gets called for every array + * item. + * @returns {Array} A new array of values returned by the callback function. + */ + function map(array, fn) { + var length = array.length; + var result = []; + while (length--) { + result[length] = fn(array[length]); + } + return result; + } + + /** + * A simple `Array#map`-like wrapper to work with domain name strings or email + * addresses. + * @private + * @param {String} domain The domain name or email address. + * @param {Function} callback The function that gets called for every + * character. + * @returns {Array} A new string of characters returned by the callback + * function. + */ + function mapDomain(string, fn) { + var parts = string.split('@'); + var result = ''; + if (parts.length > 1) { + // In email addresses, only the domain name should be punycoded. Leave + // the local part (i.e. everything up to `@`) intact. + result = parts[0] + '@'; + string = parts[1]; + } + // Avoid `split(regex)` for IE8 compatibility. See #17. + string = string.replace(regexSeparators, '\x2E'); + var labels = string.split('.'); + var encoded = map(labels, fn).join('.'); + return result + encoded; + } + + /** + * Creates an array containing the numeric code points of each Unicode + * character in the string. While JavaScript uses UCS-2 internally, + * this function will convert a pair of surrogate halves (each of which + * UCS-2 exposes as separate characters) into a single code point, + * matching UTF-16. + * @see `punycode.ucs2.encode` + * @see + * @memberOf punycode.ucs2 + * @name decode + * @param {String} string The Unicode input string (UCS-2). + * @returns {Array} The new array of code points. + */ + function ucs2decode(string) { + var output = [], + counter = 0, + length = string.length, + value, + extra; + while (counter < length) { + value = string.charCodeAt(counter++); + if (value >= 0xD800 && value <= 0xDBFF && counter < length) { + // high surrogate, and there is a next character + extra = string.charCodeAt(counter++); + if ((extra & 0xFC00) == 0xDC00) { // low surrogate + output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000); + } else { + // unmatched surrogate; only append this code unit, in case the next + // code unit is the high surrogate of a surrogate pair + output.push(value); + counter--; + } + } else { + output.push(value); + } + } + return output; + } + + /** + * Creates a string based on an array of numeric code points. + * @see `punycode.ucs2.decode` + * @memberOf punycode.ucs2 + * @name encode + * @param {Array} codePoints The array of numeric code points. + * @returns {String} The new Unicode string (UCS-2). + */ + function ucs2encode(array) { + return map(array, function(value) { + var output = ''; + if (value > 0xFFFF) { + value -= 0x10000; + output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800); + value = 0xDC00 | value & 0x3FF; + } + output += stringFromCharCode(value); + return output; + }).join(''); + } + + /** + * Converts a basic code point into a digit/integer. + * @see `digitToBasic()` + * @private + * @param {Number} codePoint The basic numeric code point value. + * @returns {Number} The numeric value of a basic code point (for use in + * representing integers) in the range `0` to `base - 1`, or `base` if + * the code point does not represent a value. + */ + function basicToDigit(codePoint) { + if (codePoint - 48 < 10) { + return codePoint - 22; + } + if (codePoint - 65 < 26) { + return codePoint - 65; + } + if (codePoint - 97 < 26) { + return codePoint - 97; + } + return base; + } + + /** + * Converts a digit/integer into a basic code point. + * @see `basicToDigit()` + * @private + * @param {Number} digit The numeric value of a basic code point. + * @returns {Number} The basic code point whose value (when used for + * representing integers) is `digit`, which needs to be in the range + * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is + * used; else, the lowercase form is used. The behavior is undefined + * if `flag` is non-zero and `digit` has no uppercase form. + */ + function digitToBasic(digit, flag) { + // 0..25 map to ASCII a..z or A..Z + // 26..35 map to ASCII 0..9 + return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5); + } + + /** + * Bias adaptation function as per section 3.4 of RFC 3492. + * https://tools.ietf.org/html/rfc3492#section-3.4 + * @private + */ + function adapt(delta, numPoints, firstTime) { + var k = 0; + delta = firstTime ? floor(delta / damp) : delta >> 1; + delta += floor(delta / numPoints); + for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) { + delta = floor(delta / baseMinusTMin); + } + return floor(k + (baseMinusTMin + 1) * delta / (delta + skew)); + } + + /** + * Converts a Punycode string of ASCII-only symbols to a string of Unicode + * symbols. + * @memberOf punycode + * @param {String} input The Punycode string of ASCII-only symbols. + * @returns {String} The resulting string of Unicode symbols. + */ + function decode(input) { + // Don't use UCS-2 + var output = [], + inputLength = input.length, + out, + i = 0, + n = initialN, + bias = initialBias, + basic, + j, + index, + oldi, + w, + k, + digit, + t, + /** Cached calculation results */ + baseMinusT; + + // Handle the basic code points: let `basic` be the number of input code + // points before the last delimiter, or `0` if there is none, then copy + // the first basic code points to the output. + + basic = input.lastIndexOf(delimiter); + if (basic < 0) { + basic = 0; + } + + for (j = 0; j < basic; ++j) { + // if it's not a basic code point + if (input.charCodeAt(j) >= 0x80) { + error('not-basic'); + } + output.push(input.charCodeAt(j)); + } + + // Main decoding loop: start just after the last delimiter if any basic code + // points were copied; start at the beginning otherwise. + + for (index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) { + + // `index` is the index of the next character to be consumed. + // Decode a generalized variable-length integer into `delta`, + // which gets added to `i`. The overflow checking is easier + // if we increase `i` as we go, then subtract off its starting + // value at the end to obtain `delta`. + for (oldi = i, w = 1, k = base; /* no condition */; k += base) { + + if (index >= inputLength) { + error('invalid-input'); + } + + digit = basicToDigit(input.charCodeAt(index++)); + + if (digit >= base || digit > floor((maxInt - i) / w)) { + error('overflow'); + } + + i += digit * w; + t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); + + if (digit < t) { + break; + } + + baseMinusT = base - t; + if (w > floor(maxInt / baseMinusT)) { + error('overflow'); + } + + w *= baseMinusT; + + } + + out = output.length + 1; + bias = adapt(i - oldi, out, oldi == 0); + + // `i` was supposed to wrap around from `out` to `0`, + // incrementing `n` each time, so we'll fix that now: + if (floor(i / out) > maxInt - n) { + error('overflow'); + } + + n += floor(i / out); + i %= out; + + // Insert `n` at position `i` of the output + output.splice(i++, 0, n); + + } + + return ucs2encode(output); + } + + /** + * Converts a string of Unicode symbols (e.g. a domain name label) to a + * Punycode string of ASCII-only symbols. + * @memberOf punycode + * @param {String} input The string of Unicode symbols. + * @returns {String} The resulting Punycode string of ASCII-only symbols. + */ + function encode(input) { + var n, + delta, + handledCPCount, + basicLength, + bias, + j, + m, + q, + k, + t, + currentValue, + output = [], + /** `inputLength` will hold the number of code points in `input`. */ + inputLength, + /** Cached calculation results */ + handledCPCountPlusOne, + baseMinusT, + qMinusT; + + // Convert the input in UCS-2 to Unicode + input = ucs2decode(input); + + // Cache the length + inputLength = input.length; + + // Initialize the state + n = initialN; + delta = 0; + bias = initialBias; + + // Handle the basic code points + for (j = 0; j < inputLength; ++j) { + currentValue = input[j]; + if (currentValue < 0x80) { + output.push(stringFromCharCode(currentValue)); + } + } + + handledCPCount = basicLength = output.length; + + // `handledCPCount` is the number of code points that have been handled; + // `basicLength` is the number of basic code points. + + // Finish the basic string - if it is not empty - with a delimiter + if (basicLength) { + output.push(delimiter); + } + + // Main encoding loop: + while (handledCPCount < inputLength) { + + // All non-basic code points < n have been handled already. Find the next + // larger one: + for (m = maxInt, j = 0; j < inputLength; ++j) { + currentValue = input[j]; + if (currentValue >= n && currentValue < m) { + m = currentValue; + } + } + + // Increase `delta` enough to advance the decoder's state to , + // but guard against overflow + handledCPCountPlusOne = handledCPCount + 1; + if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) { + error('overflow'); + } + + delta += (m - n) * handledCPCountPlusOne; + n = m; + + for (j = 0; j < inputLength; ++j) { + currentValue = input[j]; + + if (currentValue < n && ++delta > maxInt) { + error('overflow'); + } + + if (currentValue == n) { + // Represent delta as a generalized variable-length integer + for (q = delta, k = base; /* no condition */; k += base) { + t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); + if (q < t) { + break; + } + qMinusT = q - t; + baseMinusT = base - t; + output.push( + stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0)) + ); + q = floor(qMinusT / baseMinusT); + } + + output.push(stringFromCharCode(digitToBasic(q, 0))); + bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength); + delta = 0; + ++handledCPCount; + } + } + + ++delta; + ++n; + + } + return output.join(''); + } + + /** + * Converts a Punycode string representing a domain name or an email address + * to Unicode. Only the Punycoded parts of the input will be converted, i.e. + * it doesn't matter if you call it on a string that has already been + * converted to Unicode. + * @memberOf punycode + * @param {String} input The Punycoded domain name or email address to + * convert to Unicode. + * @returns {String} The Unicode representation of the given Punycode + * string. + */ + function toUnicode(input) { + return mapDomain(input, function(string) { + return regexPunycode.test(string) + ? decode(string.slice(4).toLowerCase()) + : string; + }); + } + + /** + * Converts a Unicode string representing a domain name or an email address to + * Punycode. Only the non-ASCII parts of the domain name will be converted, + * i.e. it doesn't matter if you call it with a domain that's already in + * ASCII. + * @memberOf punycode + * @param {String} input The domain name or email address to convert, as a + * Unicode string. + * @returns {String} The Punycode representation of the given domain name or + * email address. + */ + function toASCII(input) { + return mapDomain(input, function(string) { + return regexNonASCII.test(string) + ? 'xn--' + encode(string) + : string; + }); + } + + /*--------------------------------------------------------------------------*/ + + /** Define the public API */ + punycode = { + /** + * A string representing the current Punycode.js version number. + * @memberOf punycode + * @type String + */ + 'version': '1.4.1', + /** + * An object of methods to convert from JavaScript's internal character + * representation (UCS-2) to Unicode code points, and back. + * @see + * @memberOf punycode + * @type Object + */ + 'ucs2': { + 'decode': ucs2decode, + 'encode': ucs2encode + }, + 'decode': decode, + 'encode': encode, + 'toASCII': toASCII, + 'toUnicode': toUnicode + }; + + /** Expose `punycode` */ + // Some AMD build optimizers, like r.js, check for specific condition patterns + // like the following: + if ( + typeof define == 'function' && + typeof define.amd == 'object' && + define.amd + ) { + define('punycode', function() { + return punycode; + }); + } else if (freeExports && freeModule) { + if (module.exports == freeExports) { + // in Node.js, io.js, or RingoJS v0.8.0+ + freeModule.exports = punycode; + } else { + // in Narwhal or RingoJS v0.7.0- + for (key in punycode) { + punycode.hasOwnProperty(key) && (freeExports[key] = punycode[key]); + } + } + } else { + // in Rhino or a web browser + root.punycode = punycode; + } + +}(this)); + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],61:[function(require,module,exports){ +module.exports=/[\0-\x1F\x7F-\x9F]/ +},{}],62:[function(require,module,exports){ +module.exports=/[\xAD\u0600-\u0605\u061C\u06DD\u070F\u08E2\u180E\u200B-\u200F\u202A-\u202E\u2060-\u2064\u2066-\u206F\uFEFF\uFFF9-\uFFFB]|\uD804[\uDCBD\uDCCD]|\uD82F[\uDCA0-\uDCA3]|\uD834[\uDD73-\uDD7A]|\uDB40[\uDC01\uDC20-\uDC7F]/ +},{}],63:[function(require,module,exports){ +module.exports=/[!-#%-\*,-\/:;\?@\[-\]_\{\}\xA1\xA7\xAB\xB6\xB7\xBB\xBF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061E\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u09FD\u0A76\u0AF0\u0C84\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166D\u166E\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2010-\u2027\u2030-\u2043\u2045-\u2051\u2053-\u205E\u207D\u207E\u208D\u208E\u2308-\u230B\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E4E\u3001-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65]|\uD800[\uDD00-\uDD02\uDF9F\uDFD0]|\uD801\uDD6F|\uD802[\uDC57\uDD1F\uDD3F\uDE50-\uDE58\uDE7F\uDEF0-\uDEF6\uDF39-\uDF3F\uDF99-\uDF9C]|\uD803[\uDF55-\uDF59]|\uD804[\uDC47-\uDC4D\uDCBB\uDCBC\uDCBE-\uDCC1\uDD40-\uDD43\uDD74\uDD75\uDDC5-\uDDC8\uDDCD\uDDDB\uDDDD-\uDDDF\uDE38-\uDE3D\uDEA9]|\uD805[\uDC4B-\uDC4F\uDC5B\uDC5D\uDCC6\uDDC1-\uDDD7\uDE41-\uDE43\uDE60-\uDE6C\uDF3C-\uDF3E]|\uD806[\uDC3B\uDE3F-\uDE46\uDE9A-\uDE9C\uDE9E-\uDEA2]|\uD807[\uDC41-\uDC45\uDC70\uDC71\uDEF7\uDEF8]|\uD809[\uDC70-\uDC74]|\uD81A[\uDE6E\uDE6F\uDEF5\uDF37-\uDF3B\uDF44]|\uD81B[\uDE97-\uDE9A]|\uD82F\uDC9F|\uD836[\uDE87-\uDE8B]|\uD83A[\uDD5E\uDD5F]/ +},{}],64:[function(require,module,exports){ +module.exports=/[ \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000]/ +},{}],65:[function(require,module,exports){ +'use strict'; + +exports.Any = require('./properties/Any/regex'); +exports.Cc = require('./categories/Cc/regex'); +exports.Cf = require('./categories/Cf/regex'); +exports.P = require('./categories/P/regex'); +exports.Z = require('./categories/Z/regex'); + +},{"./categories/Cc/regex":61,"./categories/Cf/regex":62,"./categories/P/regex":63,"./categories/Z/regex":64,"./properties/Any/regex":66}],66:[function(require,module,exports){ +module.exports=/[\0-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/ +},{}],67:[function(require,module,exports){ +'use strict'; + + +module.exports = require('./lib/'); + +},{"./lib/":9}]},{},[67])(67) +}); +const markdownit = define() +export default markdownit \ No newline at end of file diff --git a/app/assets/frontends/beaker-wiki/pencil.svg b/app/assets/frontends/beaker-wiki/pencil.svg new file mode 100644 index 0000000000..00f05cb84d --- /dev/null +++ b/app/assets/frontends/beaker-wiki/pencil.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/assets/frontends/beaker-wiki/trash.svg b/app/assets/frontends/beaker-wiki/trash.svg new file mode 100644 index 0000000000..5f93a640c9 --- /dev/null +++ b/app/assets/frontends/beaker-wiki/trash.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/assets/frontends/beaker-wiki/ui.css b/app/assets/frontends/beaker-wiki/ui.css new file mode 100644 index 0000000000..89ab333033 --- /dev/null +++ b/app/assets/frontends/beaker-wiki/ui.css @@ -0,0 +1,170 @@ +body { + --light-gray: #f7f7fc; + + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + margin: 0; + display: grid; + grid-template-columns: 300px 1fr; + min-height: 100vh; + min-width: 100vh; +} + +a { + text-decoration: none; + color: #2864dc; +} + +a:hover { + text-decoration: underline; +} + +button { + padding: 0.5rem 1rem; + background: #fff; + border: 1px solid #ccd; + border-radius: 4px; + outline: 0; + font-size: 12px; + cursor: pointer; +} + +button:active { + background: var(--light-gray); +} + +button.primary { + color: #1073ca; + border: 1px solid #1E88E5; +} + +header { + grid-column-start: 1; + grid-column-end: 3; + background: var(--light-gray); +} + +nav { + border-right: 1px solid #ccd; +} + +main { +} + +wiki-nav { + display: block; + overflow: hidden; +} + +wiki-nav a { + display: flex; + align-items: center; + justify-content: space-between; + color: #445; + padding: 0.5rem 1rem; + margin: 0; +} + +wiki-nav a:hover { + text-decoration: none; + background: var(--light-gray); +} + +wiki-nav a.active { + background: var(--light-gray); +} + +wiki-nav a img { + display: inline-block; + width: 12px; + height: 12px; + margin-left: 8px; + opacity: 0.35; + visibility: hidden; +} + +wiki-nav a:hover img { + visibility: visible; +} + +wiki-nav a img:hover { + opacity: 1; +} + +wiki-nav .empty { + padding: 0.6rem 1rem; + color: #667; +} + +.content { + padding: 20px; +} + +.content > :first-child { + margin-top: 0; +} +.content hr { + border: 0; + border-top: 1px solid #ccd; +} +.content h1, +.content h2, +.content h3, +.content h4, +.content h5 { margin: 1.5rem 0; } +.content h1 { font-size: 2em; } +.content h2 { font-size: 1.7em; } +.content h3 { font-size: 1.4em; } +.content h4 { font-size: 1.3em; } +.content h5 { font-size: 1.1em; } +.content pre { + background: var(--light-gray); + padding: 1em; + overflow: auto; +} +.content p, +.content ul, +.content ol { + line-height: 1.5; +} +.content table { + margin: 1em 0; +} +.content blockquote { + border-left: 10px solid var(--light-gray); + margin: 1em 0; + padding: 1px 1.5em; + color: #667; +} + +wiki-page .empty { + padding: 20vh 5vw 40vh 0; + text-align: center; + font-size: 23px; + color: #667; +} + +wiki-page .empty button { + font-size: 18px; +} + +wiki-page textarea.editor { + width: 100%; + height: calc(100vh - 66px); + padding: 20px; + box-sizing: border-box; + border: 0; + font-size: 17px; + letter-spacing: 0.75px; + line-height: 1.4; + outline: 0; + resize: none; +} + +wiki-page .buttons { + padding: 16px; + border-bottom: 1px solid #ccd; +} + +wiki-page .buttons button { + margin-right: 5px; +} \ No newline at end of file diff --git a/app/assets/frontends/beaker-wiki/ui.html b/app/assets/frontends/beaker-wiki/ui.html new file mode 100644 index 0000000000..dca8846551 --- /dev/null +++ b/app/assets/frontends/beaker-wiki/ui.html @@ -0,0 +1,16 @@ + + + + + + + + + +
    + +
    + + \ No newline at end of file diff --git a/app/assets/frontends/beaker-wiki/ui.js b/app/assets/frontends/beaker-wiki/ui.js new file mode 100644 index 0000000000..56bbb29558 --- /dev/null +++ b/app/assets/frontends/beaker-wiki/ui.js @@ -0,0 +1,175 @@ +import MarkdownIt from './markdown-it.js' + +var self = hyperdrive.self +var pathname = location.pathname.endsWith('/') ? location.pathname + 'index.md' : location.pathname +var isEditing = location.search === '?edit' + +function h (tag, attrs, ...children) { + var el = document.createElement(tag) + for (let k in attrs) el.setAttribute(k, attrs[k]) + for (let child of children) el.append(child) + return el +} + +async function ensureParentDir (p) { + let parts = p.split('/').slice(0, -1) + let acc = [] + for (let part of parts) { + acc.push(part) + await self.mkdir(acc.join('/')).catch(e => undefined) + } +} + +customElements.define('wiki-nav', class extends HTMLElement { + constructor () { + super() + this.load() + } + + async load () { + this.innerHTML = '' + this.info = await self.getInfo() + this.files = await self.readdir('/', {recursive: true}) + this.files = this.files.filter(file => file.endsWith('.md')) + this.files.sort() + this.render() + } + + render () { + for (let file of this.files) { + let href = `/${file}` + let cls = pathname === href ? 'active' : '' + let buttons = [] + + if (this.info.writable) { + if (/\.(png|jpe?g|gif|mp4|mp3|ogg|webm|mov)$/.test(file) === false) { + let editPage = h('img', {src: '/.ui/pencil.svg', alt: 'Edit', title: 'Edit'}) + editPage.addEventListener('click', async (e) => { + e.preventDefault() + location = `${href}?edit` + }) + buttons.push(editPage) + } + let deletePage = h('img', {src: '/.ui/trash.svg', alt: 'Delete', title: 'Delete'}) + deletePage.addEventListener('click', async (e) => { + e.preventDefault() + if (!confirm('Delete this page?')) return + await self.unlink(href) + if (href === pathname) location.reload() + else this.load() + }) + buttons.push(deletePage) + } + + this.append(h('a', {href, class: cls}, h('span', {}, file.slice(0, -3)), h('span', {class: 'buttons'}, ...buttons))) + } + if (this.files.length === 0) { + this.append(h('div', {class: 'empty'}, 'This Wiki has no pages')) + } + + if (this.info.writable) { + let newPage = h('a', {href: '#'}, '+ New Page') + newPage.addEventListener('click', async (e) => { + e.preventDefault() + var newPathname = prompt('Enter the path of the new page') + if (!newPathname) return + if (!newPathname.endsWith('.md')) newPathname += '.md' + await ensureParentDir(newPathname) + if ((await self.stat(newPathname).catch(e => undefined)) === undefined) { + await self.writeFile(newPathname, `# ${newPathname}`) + } + this.load() + }) + this.append(newPage) + } + } +}) + +customElements.define('wiki-page', class extends HTMLElement { + constructor () { + super() + this.render() + } + + async render () { + // check existence + let stat = await self.stat(pathname).catch(e => undefined) + if (!stat) { + // 404 + let canEdit = (await self.getInfo()).writable + if (canEdit) { + let btn = h('button', {class: 'primary'}, 'Create Page') + btn.addEventListener('click', async (e) => { + await ensureParentDir(pathname) + await self.writeFile(pathname, `# ${pathname}`) + location.search = '?edit' + }) + this.append(h('div', {class: 'empty'}, h('h2', {}, 'This Page Does Not Exist'), btn)) + } else { + this.append(h('div', {class: 'empty'}, h('h2', {}, 'This Page Does Not Exist'))) + } + return + } + + // embed content + if (/\.(png|jpe?g|gif)$/i.test(pathname)) { + this.append(h('img', {src: pathname})) + } else if (/\.(mp4|webm|mov)/i.test(pathname)) { + this.append(h('video', {controls: true}, h('source', {src: pathname}))) + } else if (/\.(mp3|ogg)/i.test(pathname)) { + this.append(h('audio', {controls: true}, h('source', {src: pathname}))) + } else { + let content = await self.readFile(pathname) + if (isEditing) { + // render editor + let savePage = h('button', {}, 'Save Changes') + savePage.addEventListener('click', async (e) => { + let value = document.body.querySelector('textarea.editor').value + await self.writeFile(pathname, value) + location.search = '' + }) + let discardChanges = h('button', {}, 'Discard Changes') + discardChanges.addEventListener('click', async (e) => { + if (!confirm('Discard changes?')) return + location.search = '' + }) + this.append(h('div', {class: 'buttons'}, savePage, discardChanges)) + + let textarea = h('textarea', {class: 'editor', autofocus: true}, content) + this.append(textarea) + } else { + // render content + if (/\.(md|html)$/i.test(pathname)) { + if (pathname.endsWith('.md')) { + let md = new MarkdownIt({html: true}) + content = md.render(content) + } + let contentEl = h('div', {class: 'content'}) + contentEl.innerHTML = content + this.append(contentEl) + executeJs(this) + } else { + this.append(h('pre', {class: 'content'}, content)) + } + } + } + } +}) + + +function executeJs (container) { + const scripts = container.getElementsByTagName('script') + const scriptsInitialLength = scripts.length + for (let i = 0; i < scriptsInitialLength; i++) { + const script = scripts[i] + const scriptCopy = document.createElement('script') + scriptCopy.type = script.type ? script.type : 'text/javascript' + if (script.innerHTML) { + scriptCopy.innerHTML = script.innerHTML + } else if (script.src) { + scriptCopy.src = script.src + } + scriptCopy.async = false + script.parentNode.replaceChild(scriptCopy, script) + } +} \ No newline at end of file diff --git a/app/assets/frontends/blogger/.beaker-theme b/app/assets/frontends/blogger/.beaker-theme new file mode 100644 index 0000000000..74010f11b4 --- /dev/null +++ b/app/assets/frontends/blogger/.beaker-theme @@ -0,0 +1 @@ +builtin:blogger \ No newline at end of file diff --git a/app/assets/frontends/blogger/js/admin.js b/app/assets/frontends/blogger/js/admin.js new file mode 100644 index 0000000000..514875a38a --- /dev/null +++ b/app/assets/frontends/blogger/js/admin.js @@ -0,0 +1,35 @@ +import { h } from './util.js' + +export class DriveAdmin extends HTMLElement { + constructor () { + super() + this.load() + } + + async load () { + this.info = await hyperdrive.self.getInfo() + this.render() + } + + render () { + var links = h('div', {className: 'links'}) + var key = (/[0-9a-f]{64}/i).exec(hyperdrive.self.url)[0] + links.append(h('a', {className: 'btn primary', href: `https://beaker.network/${key}`}, 'View on Beaker.Network')) + if (this.info.writable) { + let editProfile = h('a', {className: 'btn'}, 'Edit Profile') + editProfile.addEventListener('click', this.onClickEditProfile.bind(this)) + links.prepend(editProfile) + + let newPost = h('a', {className: 'btn', href: `https://beaker.network/compose`}, 'New Post') + links.prepend(newPost) + } + this.append(links) + } + + async onClickEditProfile (e) { + e.preventDefault() + await navigator.drivePropertiesDialog(location.origin) + location.reload() + } +} +customElements.define('drive-admin', DriveAdmin) \ No newline at end of file diff --git a/app/assets/frontends/blogger/js/breadcrumbs.js b/app/assets/frontends/blogger/js/breadcrumbs.js new file mode 100644 index 0000000000..fbbdb7bc67 --- /dev/null +++ b/app/assets/frontends/blogger/js/breadcrumbs.js @@ -0,0 +1,28 @@ +import { h } from './util.js' + +export class DriveBreadcrumbs extends HTMLElement { + constructor () { + super() + this.render() + } + + render () { + var pathname = location.pathname + var parts = pathname.split('/') + if (parts.length > 1 && !parts[parts.length - 1]) { + parts.pop() + } + + let acc = [] + for (let part of parts) { + let href = acc.concat([part]).join('/') || '/' + this.append(h('a', {href}, part || 'Home')) + acc.push(part) + if (acc.length !== parts.length) { + this.append(h('span', {}, '❯')) + } + } + } + +} +customElements.define('drive-breadcrumbs', DriveBreadcrumbs) \ No newline at end of file diff --git a/app/assets/frontends/blogger/js/files.js b/app/assets/frontends/blogger/js/files.js new file mode 100644 index 0000000000..8cbd811b70 --- /dev/null +++ b/app/assets/frontends/blogger/js/files.js @@ -0,0 +1,27 @@ +import { h } from './util.js' + +export class DriveFiles extends HTMLElement { + constructor () { + super() + this.load() + } + + async load () { + this.info = await hyperdrive.self.getInfo() + this.entries = await hyperdrive.self.readdir(location.pathname, {includeStats: true}) + this.entries.sort((a, b) => a.name.localeCompare(b.name)) + this.render() + } + + render () { + this.append(h('h2', {}, 'Files')) + var grid = h('div', {className: 'grid'}) + for (let entry of this.entries) { + let href = entry.stat.mount ? `hyper://${entry.stat.mount.key}` : `./${entry.name}${entry.stat.isDirectory() ? '/' : ''}` + grid.append(h('div', {className: 'entry'}, h('a', {href}, entry.name))) + } + this.append(grid) + } + +} +customElements.define('drive-files', DriveFiles) \ No newline at end of file diff --git a/app/assets/frontends/blogger/js/header.js b/app/assets/frontends/blogger/js/header.js new file mode 100644 index 0000000000..451df01c70 --- /dev/null +++ b/app/assets/frontends/blogger/js/header.js @@ -0,0 +1,33 @@ +import { h } from './util.js' + +export class DriveHeader extends HTMLElement { + constructor () { + super() + this.load() + } + + async load () { + this.info = await hyperdrive.self.getInfo() + this.render() + } + + render () { + var details = h('div', {className: 'details'}) + details.append(h('h1', {}, this.info.title)) + if (this.info.description) { + details.append(h('p', {className: 'description'}, this.info.description)) + } + this.append(details) + + var img = h('img', {src: '/thumb'}) + img.addEventListener('error', e => img.style.display = 'none') + this.append(img) + } + + async onClickEditProfile (e) { + e.preventDefault() + await navigator.drivePropertiesDialog(location.origin) + location.reload() + } +} +customElements.define('drive-header', DriveHeader) \ No newline at end of file diff --git a/app/assets/frontends/blogger/js/markdown-it.js b/app/assets/frontends/blogger/js/markdown-it.js new file mode 100644 index 0000000000..ca121322d8 --- /dev/null +++ b/app/assets/frontends/blogger/js/markdown-it.js @@ -0,0 +1,8157 @@ +/*! markdown-it 10.0.0 https://github.com//markdown-it/markdown-it @license MIT */ +const define = (function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i utf16string } +// +'use strict'; + +/*eslint quotes:0*/ +module.exports = require('entities/lib/maps/entities.json'); + +},{"entities/lib/maps/entities.json":52}],2:[function(require,module,exports){ +// List of valid html blocks names, accorting to commonmark spec +// http://jgm.github.io/CommonMark/spec.html#html-blocks + +'use strict'; + + +module.exports = [ + 'address', + 'article', + 'aside', + 'base', + 'basefont', + 'blockquote', + 'body', + 'caption', + 'center', + 'col', + 'colgroup', + 'dd', + 'details', + 'dialog', + 'dir', + 'div', + 'dl', + 'dt', + 'fieldset', + 'figcaption', + 'figure', + 'footer', + 'form', + 'frame', + 'frameset', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'head', + 'header', + 'hr', + 'html', + 'iframe', + 'legend', + 'li', + 'link', + 'main', + 'menu', + 'menuitem', + 'meta', + 'nav', + 'noframes', + 'ol', + 'optgroup', + 'option', + 'p', + 'param', + 'section', + 'source', + 'summary', + 'table', + 'tbody', + 'td', + 'tfoot', + 'th', + 'thead', + 'title', + 'tr', + 'track', + 'ul' +]; + +},{}],3:[function(require,module,exports){ +// Regexps to match html elements + +'use strict'; + +var attr_name = '[a-zA-Z_:][a-zA-Z0-9:._-]*'; + +var unquoted = '[^"\'=<>`\\x00-\\x20]+'; +var single_quoted = "'[^']*'"; +var double_quoted = '"[^"]*"'; + +var attr_value = '(?:' + unquoted + '|' + single_quoted + '|' + double_quoted + ')'; + +var attribute = '(?:\\s+' + attr_name + '(?:\\s*=\\s*' + attr_value + ')?)'; + +var open_tag = '<[A-Za-z][A-Za-z0-9\\-]*' + attribute + '*\\s*\\/?>'; + +var close_tag = '<\\/[A-Za-z][A-Za-z0-9\\-]*\\s*>'; +var comment = '|'; +var processing = '<[?].*?[?]>'; +var declaration = ']*>'; +var cdata = ''; + +var HTML_TAG_RE = new RegExp('^(?:' + open_tag + '|' + close_tag + '|' + comment + + '|' + processing + '|' + declaration + '|' + cdata + ')'); +var HTML_OPEN_CLOSE_TAG_RE = new RegExp('^(?:' + open_tag + '|' + close_tag + ')'); + +module.exports.HTML_TAG_RE = HTML_TAG_RE; +module.exports.HTML_OPEN_CLOSE_TAG_RE = HTML_OPEN_CLOSE_TAG_RE; + +},{}],4:[function(require,module,exports){ +// Utilities +// +'use strict'; + + +function _class(obj) { return Object.prototype.toString.call(obj); } + +function isString(obj) { return _class(obj) === '[object String]'; } + +var _hasOwnProperty = Object.prototype.hasOwnProperty; + +function has(object, key) { + return _hasOwnProperty.call(object, key); +} + +// Merge objects +// +function assign(obj /*from1, from2, from3, ...*/) { + var sources = Array.prototype.slice.call(arguments, 1); + + sources.forEach(function (source) { + if (!source) { return; } + + if (typeof source !== 'object') { + throw new TypeError(source + 'must be object'); + } + + Object.keys(source).forEach(function (key) { + obj[key] = source[key]; + }); + }); + + return obj; +} + +// Remove element from array and put another array at those position. +// Useful for some operations with tokens +function arrayReplaceAt(src, pos, newElements) { + return [].concat(src.slice(0, pos), newElements, src.slice(pos + 1)); +} + +//////////////////////////////////////////////////////////////////////////////// + +function isValidEntityCode(c) { + /*eslint no-bitwise:0*/ + // broken sequence + if (c >= 0xD800 && c <= 0xDFFF) { return false; } + // never used + if (c >= 0xFDD0 && c <= 0xFDEF) { return false; } + if ((c & 0xFFFF) === 0xFFFF || (c & 0xFFFF) === 0xFFFE) { return false; } + // control codes + if (c >= 0x00 && c <= 0x08) { return false; } + if (c === 0x0B) { return false; } + if (c >= 0x0E && c <= 0x1F) { return false; } + if (c >= 0x7F && c <= 0x9F) { return false; } + // out of range + if (c > 0x10FFFF) { return false; } + return true; +} + +function fromCodePoint(c) { + /*eslint no-bitwise:0*/ + if (c > 0xffff) { + c -= 0x10000; + var surrogate1 = 0xd800 + (c >> 10), + surrogate2 = 0xdc00 + (c & 0x3ff); + + return String.fromCharCode(surrogate1, surrogate2); + } + return String.fromCharCode(c); +} + + +var UNESCAPE_MD_RE = /\\([!"#$%&'()*+,\-.\/:;<=>?@[\\\]^_`{|}~])/g; +var ENTITY_RE = /&([a-z#][a-z0-9]{1,31});/gi; +var UNESCAPE_ALL_RE = new RegExp(UNESCAPE_MD_RE.source + '|' + ENTITY_RE.source, 'gi'); + +var DIGITAL_ENTITY_TEST_RE = /^#((?:x[a-f0-9]{1,8}|[0-9]{1,8}))/i; + +var entities = require('./entities'); + +function replaceEntityPattern(match, name) { + var code = 0; + + if (has(entities, name)) { + return entities[name]; + } + + if (name.charCodeAt(0) === 0x23/* # */ && DIGITAL_ENTITY_TEST_RE.test(name)) { + code = name[1].toLowerCase() === 'x' ? + parseInt(name.slice(2), 16) : parseInt(name.slice(1), 10); + + if (isValidEntityCode(code)) { + return fromCodePoint(code); + } + } + + return match; +} + +/*function replaceEntities(str) { + if (str.indexOf('&') < 0) { return str; } + + return str.replace(ENTITY_RE, replaceEntityPattern); +}*/ + +function unescapeMd(str) { + if (str.indexOf('\\') < 0) { return str; } + return str.replace(UNESCAPE_MD_RE, '$1'); +} + +function unescapeAll(str) { + if (str.indexOf('\\') < 0 && str.indexOf('&') < 0) { return str; } + + return str.replace(UNESCAPE_ALL_RE, function (match, escaped, entity) { + if (escaped) { return escaped; } + return replaceEntityPattern(match, entity); + }); +} + +//////////////////////////////////////////////////////////////////////////////// + +var HTML_ESCAPE_TEST_RE = /[&<>"]/; +var HTML_ESCAPE_REPLACE_RE = /[&<>"]/g; +var HTML_REPLACEMENTS = { + '&': '&', + '<': '<', + '>': '>', + '"': '"' +}; + +function replaceUnsafeChar(ch) { + return HTML_REPLACEMENTS[ch]; +} + +function escapeHtml(str) { + if (HTML_ESCAPE_TEST_RE.test(str)) { + return str.replace(HTML_ESCAPE_REPLACE_RE, replaceUnsafeChar); + } + return str; +} + +//////////////////////////////////////////////////////////////////////////////// + +var REGEXP_ESCAPE_RE = /[.?*+^$[\]\\(){}|-]/g; + +function escapeRE(str) { + return str.replace(REGEXP_ESCAPE_RE, '\\$&'); +} + +//////////////////////////////////////////////////////////////////////////////// + +function isSpace(code) { + switch (code) { + case 0x09: + case 0x20: + return true; + } + return false; +} + +// Zs (unicode class) || [\t\f\v\r\n] +function isWhiteSpace(code) { + if (code >= 0x2000 && code <= 0x200A) { return true; } + switch (code) { + case 0x09: // \t + case 0x0A: // \n + case 0x0B: // \v + case 0x0C: // \f + case 0x0D: // \r + case 0x20: + case 0xA0: + case 0x1680: + case 0x202F: + case 0x205F: + case 0x3000: + return true; + } + return false; +} + +//////////////////////////////////////////////////////////////////////////////// + +/*eslint-disable max-len*/ +var UNICODE_PUNCT_RE = require('uc.micro/categories/P/regex'); + +// Currently without astral characters support. +function isPunctChar(ch) { + return UNICODE_PUNCT_RE.test(ch); +} + + +// Markdown ASCII punctuation characters. +// +// !, ", #, $, %, &, ', (, ), *, +, ,, -, ., /, :, ;, <, =, >, ?, @, [, \, ], ^, _, `, {, |, }, or ~ +// http://spec.commonmark.org/0.15/#ascii-punctuation-character +// +// Don't confuse with unicode punctuation !!! It lacks some chars in ascii range. +// +function isMdAsciiPunct(ch) { + switch (ch) { + case 0x21/* ! */: + case 0x22/* " */: + case 0x23/* # */: + case 0x24/* $ */: + case 0x25/* % */: + case 0x26/* & */: + case 0x27/* ' */: + case 0x28/* ( */: + case 0x29/* ) */: + case 0x2A/* * */: + case 0x2B/* + */: + case 0x2C/* , */: + case 0x2D/* - */: + case 0x2E/* . */: + case 0x2F/* / */: + case 0x3A/* : */: + case 0x3B/* ; */: + case 0x3C/* < */: + case 0x3D/* = */: + case 0x3E/* > */: + case 0x3F/* ? */: + case 0x40/* @ */: + case 0x5B/* [ */: + case 0x5C/* \ */: + case 0x5D/* ] */: + case 0x5E/* ^ */: + case 0x5F/* _ */: + case 0x60/* ` */: + case 0x7B/* { */: + case 0x7C/* | */: + case 0x7D/* } */: + case 0x7E/* ~ */: + return true; + default: + return false; + } +} + +// Hepler to unify [reference labels]. +// +function normalizeReference(str) { + // Trim and collapse whitespace + // + str = str.trim().replace(/\s+/g, ' '); + + // In node v10 'ẞ'.toLowerCase() === 'Ṿ', which is presumed to be a bug + // fixed in v12 (couldn't find any details). + // + // So treat this one as a special case + // (remove this when node v10 is no longer supported). + // + if ('ẞ'.toLowerCase() === 'Ṿ') { + str = str.replace(/ẞ/g, 'ß'); + } + + // .toLowerCase().toUpperCase() should get rid of all differences + // between letter variants. + // + // Simple .toLowerCase() doesn't normalize 125 code points correctly, + // and .toUpperCase doesn't normalize 6 of them (list of exceptions: + // İ, ϴ, ẞ, Ω, K, Å - those are already uppercased, but have differently + // uppercased versions). + // + // Here's an example showing how it happens. Lets take greek letter omega: + // uppercase U+0398 (Θ), U+03f4 (ϴ) and lowercase U+03b8 (θ), U+03d1 (ϑ) + // + // Unicode entries: + // 0398;GREEK CAPITAL LETTER THETA;Lu;0;L;;;;;N;;;;03B8; + // 03B8;GREEK SMALL LETTER THETA;Ll;0;L;;;;;N;;;0398;;0398 + // 03D1;GREEK THETA SYMBOL;Ll;0;L; 03B8;;;;N;GREEK SMALL LETTER SCRIPT THETA;;0398;;0398 + // 03F4;GREEK CAPITAL THETA SYMBOL;Lu;0;L; 0398;;;;N;;;;03B8; + // + // Case-insensitive comparison should treat all of them as equivalent. + // + // But .toLowerCase() doesn't change ϑ (it's already lowercase), + // and .toUpperCase() doesn't change ϴ (already uppercase). + // + // Applying first lower then upper case normalizes any character: + // '\u0398\u03f4\u03b8\u03d1'.toLowerCase().toUpperCase() === '\u0398\u0398\u0398\u0398' + // + // Note: this is equivalent to unicode case folding; unicode normalization + // is a different step that is not required here. + // + // Final result should be uppercased, because it's later stored in an object + // (this avoid a conflict with Object.prototype members, + // most notably, `__proto__`) + // + return str.toLowerCase().toUpperCase(); +} + +//////////////////////////////////////////////////////////////////////////////// + +// Re-export libraries commonly used in both markdown-it and its plugins, +// so plugins won't have to depend on them explicitly, which reduces their +// bundled size (e.g. a browser build). +// +exports.lib = {}; +exports.lib.mdurl = require('mdurl'); +exports.lib.ucmicro = require('uc.micro'); + +exports.assign = assign; +exports.isString = isString; +exports.has = has; +exports.unescapeMd = unescapeMd; +exports.unescapeAll = unescapeAll; +exports.isValidEntityCode = isValidEntityCode; +exports.fromCodePoint = fromCodePoint; +// exports.replaceEntities = replaceEntities; +exports.escapeHtml = escapeHtml; +exports.arrayReplaceAt = arrayReplaceAt; +exports.isSpace = isSpace; +exports.isWhiteSpace = isWhiteSpace; +exports.isMdAsciiPunct = isMdAsciiPunct; +exports.isPunctChar = isPunctChar; +exports.escapeRE = escapeRE; +exports.normalizeReference = normalizeReference; + +},{"./entities":1,"mdurl":58,"uc.micro":65,"uc.micro/categories/P/regex":63}],5:[function(require,module,exports){ +// Just a shortcut for bulk export +'use strict'; + + +exports.parseLinkLabel = require('./parse_link_label'); +exports.parseLinkDestination = require('./parse_link_destination'); +exports.parseLinkTitle = require('./parse_link_title'); + +},{"./parse_link_destination":6,"./parse_link_label":7,"./parse_link_title":8}],6:[function(require,module,exports){ +// Parse link destination +// +'use strict'; + + +var unescapeAll = require('../common/utils').unescapeAll; + + +module.exports = function parseLinkDestination(str, pos, max) { + var code, level, + lines = 0, + start = pos, + result = { + ok: false, + pos: 0, + lines: 0, + str: '' + }; + + if (str.charCodeAt(pos) === 0x3C /* < */) { + pos++; + while (pos < max) { + code = str.charCodeAt(pos); + if (code === 0x0A /* \n */) { return result; } + if (code === 0x3E /* > */) { + result.pos = pos + 1; + result.str = unescapeAll(str.slice(start + 1, pos)); + result.ok = true; + return result; + } + if (code === 0x5C /* \ */ && pos + 1 < max) { + pos += 2; + continue; + } + + pos++; + } + + // no closing '>' + return result; + } + + // this should be ... } else { ... branch + + level = 0; + while (pos < max) { + code = str.charCodeAt(pos); + + if (code === 0x20) { break; } + + // ascii control characters + if (code < 0x20 || code === 0x7F) { break; } + + if (code === 0x5C /* \ */ && pos + 1 < max) { + pos += 2; + continue; + } + + if (code === 0x28 /* ( */) { + level++; + } + + if (code === 0x29 /* ) */) { + if (level === 0) { break; } + level--; + } + + pos++; + } + + if (start === pos) { return result; } + if (level !== 0) { return result; } + + result.str = unescapeAll(str.slice(start, pos)); + result.lines = lines; + result.pos = pos; + result.ok = true; + return result; +}; + +},{"../common/utils":4}],7:[function(require,module,exports){ +// Parse link label +// +// this function assumes that first character ("[") already matches; +// returns the end of the label +// +'use strict'; + +module.exports = function parseLinkLabel(state, start, disableNested) { + var level, found, marker, prevPos, + labelEnd = -1, + max = state.posMax, + oldPos = state.pos; + + state.pos = start + 1; + level = 1; + + while (state.pos < max) { + marker = state.src.charCodeAt(state.pos); + if (marker === 0x5D /* ] */) { + level--; + if (level === 0) { + found = true; + break; + } + } + + prevPos = state.pos; + state.md.inline.skipToken(state); + if (marker === 0x5B /* [ */) { + if (prevPos === state.pos - 1) { + // increase level if we find text `[`, which is not a part of any token + level++; + } else if (disableNested) { + state.pos = oldPos; + return -1; + } + } + } + + if (found) { + labelEnd = state.pos; + } + + // restore old state + state.pos = oldPos; + + return labelEnd; +}; + +},{}],8:[function(require,module,exports){ +// Parse link title +// +'use strict'; + + +var unescapeAll = require('../common/utils').unescapeAll; + + +module.exports = function parseLinkTitle(str, pos, max) { + var code, + marker, + lines = 0, + start = pos, + result = { + ok: false, + pos: 0, + lines: 0, + str: '' + }; + + if (pos >= max) { return result; } + + marker = str.charCodeAt(pos); + + if (marker !== 0x22 /* " */ && marker !== 0x27 /* ' */ && marker !== 0x28 /* ( */) { return result; } + + pos++; + + // if opening marker is "(", switch it to closing marker ")" + if (marker === 0x28) { marker = 0x29; } + + while (pos < max) { + code = str.charCodeAt(pos); + if (code === marker) { + result.pos = pos + 1; + result.lines = lines; + result.str = unescapeAll(str.slice(start + 1, pos)); + result.ok = true; + return result; + } else if (code === 0x0A) { + lines++; + } else if (code === 0x5C /* \ */ && pos + 1 < max) { + pos++; + if (str.charCodeAt(pos) === 0x0A) { + lines++; + } + } + + pos++; + } + + return result; +}; + +},{"../common/utils":4}],9:[function(require,module,exports){ +// Main parser class + +'use strict'; + + +var utils = require('./common/utils'); +var helpers = require('./helpers'); +var Renderer = require('./renderer'); +var ParserCore = require('./parser_core'); +var ParserBlock = require('./parser_block'); +var ParserInline = require('./parser_inline'); +var LinkifyIt = require('linkify-it'); +var mdurl = require('mdurl'); +var punycode = require('punycode'); + + +var config = { + 'default': require('./presets/default'), + zero: require('./presets/zero'), + commonmark: require('./presets/commonmark') +}; + +//////////////////////////////////////////////////////////////////////////////// +// +// This validator can prohibit more than really needed to prevent XSS. It's a +// tradeoff to keep code simple and to be secure by default. +// +// If you need different setup - override validator method as you wish. Or +// replace it with dummy function and use external sanitizer. +// + +var BAD_PROTO_RE = /^(vbscript|javascript|file|data):/; +var GOOD_DATA_RE = /^data:image\/(gif|png|jpeg|webp);/; + +function validateLink(url) { + // url should be normalized at this point, and existing entities are decoded + var str = url.trim().toLowerCase(); + + return BAD_PROTO_RE.test(str) ? (GOOD_DATA_RE.test(str) ? true : false) : true; +} + +//////////////////////////////////////////////////////////////////////////////// + + +var RECODE_HOSTNAME_FOR = [ 'http:', 'https:', 'mailto:' ]; + +function normalizeLink(url) { + var parsed = mdurl.parse(url, true); + + if (parsed.hostname) { + // Encode hostnames in urls like: + // `http://host/`, `https://host/`, `mailto:user@host`, `//host/` + // + // We don't encode unknown schemas, because it's likely that we encode + // something we shouldn't (e.g. `skype:name` treated as `skype:host`) + // + if (!parsed.protocol || RECODE_HOSTNAME_FOR.indexOf(parsed.protocol) >= 0) { + try { + parsed.hostname = punycode.toASCII(parsed.hostname); + } catch (er) { /**/ } + } + } + + return mdurl.encode(mdurl.format(parsed)); +} + +function normalizeLinkText(url) { + var parsed = mdurl.parse(url, true); + + if (parsed.hostname) { + // Encode hostnames in urls like: + // `http://host/`, `https://host/`, `mailto:user@host`, `//host/` + // + // We don't encode unknown schemas, because it's likely that we encode + // something we shouldn't (e.g. `skype:name` treated as `skype:host`) + // + if (!parsed.protocol || RECODE_HOSTNAME_FOR.indexOf(parsed.protocol) >= 0) { + try { + parsed.hostname = punycode.toUnicode(parsed.hostname); + } catch (er) { /**/ } + } + } + + return mdurl.decode(mdurl.format(parsed)); +} + + +/** + * class MarkdownIt + * + * Main parser/renderer class. + * + * ##### Usage + * + * ```javascript + * // node.js, "classic" way: + * var MarkdownIt = require('markdown-it'), + * md = new MarkdownIt(); + * var result = md.render('# markdown-it rulezz!'); + * + * // node.js, the same, but with sugar: + * var md = require('markdown-it')(); + * var result = md.render('# markdown-it rulezz!'); + * + * // browser without AMD, added to "window" on script load + * // Note, there are no dash. + * var md = window.markdownit(); + * var result = md.render('# markdown-it rulezz!'); + * ``` + * + * Single line rendering, without paragraph wrap: + * + * ```javascript + * var md = require('markdown-it')(); + * var result = md.renderInline('__markdown-it__ rulezz!'); + * ``` + **/ + +/** + * new MarkdownIt([presetName, options]) + * - presetName (String): optional, `commonmark` / `zero` + * - options (Object) + * + * Creates parser instanse with given config. Can be called without `new`. + * + * ##### presetName + * + * MarkdownIt provides named presets as a convenience to quickly + * enable/disable active syntax rules and options for common use cases. + * + * - ["commonmark"](https://github.com/markdown-it/markdown-it/blob/master/lib/presets/commonmark.js) - + * configures parser to strict [CommonMark](http://commonmark.org/) mode. + * - [default](https://github.com/markdown-it/markdown-it/blob/master/lib/presets/default.js) - + * similar to GFM, used when no preset name given. Enables all available rules, + * but still without html, typographer & autolinker. + * - ["zero"](https://github.com/markdown-it/markdown-it/blob/master/lib/presets/zero.js) - + * all rules disabled. Useful to quickly setup your config via `.enable()`. + * For example, when you need only `bold` and `italic` markup and nothing else. + * + * ##### options: + * + * - __html__ - `false`. Set `true` to enable HTML tags in source. Be careful! + * That's not safe! You may need external sanitizer to protect output from XSS. + * It's better to extend features via plugins, instead of enabling HTML. + * - __xhtmlOut__ - `false`. Set `true` to add '/' when closing single tags + * (`
    `). This is needed only for full CommonMark compatibility. In real + * world you will need HTML output. + * - __breaks__ - `false`. Set `true` to convert `\n` in paragraphs into `
    `. + * - __langPrefix__ - `language-`. CSS language class prefix for fenced blocks. + * Can be useful for external highlighters. + * - __linkify__ - `false`. Set `true` to autoconvert URL-like text to links. + * - __typographer__ - `false`. Set `true` to enable [some language-neutral + * replacement](https://github.com/markdown-it/markdown-it/blob/master/lib/rules_core/replacements.js) + + * quotes beautification (smartquotes). + * - __quotes__ - `“”‘’`, String or Array. Double + single quotes replacement + * pairs, when typographer enabled and smartquotes on. For example, you can + * use `'«»„“'` for Russian, `'„“‚‘'` for German, and + * `['«\xA0', '\xA0»', '‹\xA0', '\xA0›']` for French (including nbsp). + * - __highlight__ - `null`. Highlighter function for fenced code blocks. + * Highlighter `function (str, lang)` should return escaped HTML. It can also + * return empty string if the source was not changed and should be escaped + * externaly. If result starts with `): + * + * ```javascript + * var hljs = require('highlight.js') // https://highlightjs.org/ + * + * // Actual default values + * var md = require('markdown-it')({ + * highlight: function (str, lang) { + * if (lang && hljs.getLanguage(lang)) { + * try { + * return '
    ' +
    + *                hljs.highlight(lang, str, true).value +
    + *                '
    '; + * } catch (__) {} + * } + * + * return '
    ' + md.utils.escapeHtml(str) + '
    '; + * } + * }); + * ``` + * + **/ +function MarkdownIt(presetName, options) { + if (!(this instanceof MarkdownIt)) { + return new MarkdownIt(presetName, options); + } + + if (!options) { + if (!utils.isString(presetName)) { + options = presetName || {}; + presetName = 'default'; + } + } + + /** + * MarkdownIt#inline -> ParserInline + * + * Instance of [[ParserInline]]. You may need it to add new rules when + * writing plugins. For simple rules control use [[MarkdownIt.disable]] and + * [[MarkdownIt.enable]]. + **/ + this.inline = new ParserInline(); + + /** + * MarkdownIt#block -> ParserBlock + * + * Instance of [[ParserBlock]]. You may need it to add new rules when + * writing plugins. For simple rules control use [[MarkdownIt.disable]] and + * [[MarkdownIt.enable]]. + **/ + this.block = new ParserBlock(); + + /** + * MarkdownIt#core -> Core + * + * Instance of [[Core]] chain executor. You may need it to add new rules when + * writing plugins. For simple rules control use [[MarkdownIt.disable]] and + * [[MarkdownIt.enable]]. + **/ + this.core = new ParserCore(); + + /** + * MarkdownIt#renderer -> Renderer + * + * Instance of [[Renderer]]. Use it to modify output look. Or to add rendering + * rules for new token types, generated by plugins. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')(); + * + * function myToken(tokens, idx, options, env, self) { + * //... + * return result; + * }; + * + * md.renderer.rules['my_token'] = myToken + * ``` + * + * See [[Renderer]] docs and [source code](https://github.com/markdown-it/markdown-it/blob/master/lib/renderer.js). + **/ + this.renderer = new Renderer(); + + /** + * MarkdownIt#linkify -> LinkifyIt + * + * [linkify-it](https://github.com/markdown-it/linkify-it) instance. + * Used by [linkify](https://github.com/markdown-it/markdown-it/blob/master/lib/rules_core/linkify.js) + * rule. + **/ + this.linkify = new LinkifyIt(); + + /** + * MarkdownIt#validateLink(url) -> Boolean + * + * Link validation function. CommonMark allows too much in links. By default + * we disable `javascript:`, `vbscript:`, `file:` schemas, and almost all `data:...` schemas + * except some embedded image types. + * + * You can change this behaviour: + * + * ```javascript + * var md = require('markdown-it')(); + * // enable everything + * md.validateLink = function () { return true; } + * ``` + **/ + this.validateLink = validateLink; + + /** + * MarkdownIt#normalizeLink(url) -> String + * + * Function used to encode link url to a machine-readable format, + * which includes url-encoding, punycode, etc. + **/ + this.normalizeLink = normalizeLink; + + /** + * MarkdownIt#normalizeLinkText(url) -> String + * + * Function used to decode link url to a human-readable format` + **/ + this.normalizeLinkText = normalizeLinkText; + + + // Expose utils & helpers for easy acces from plugins + + /** + * MarkdownIt#utils -> utils + * + * Assorted utility functions, useful to write plugins. See details + * [here](https://github.com/markdown-it/markdown-it/blob/master/lib/common/utils.js). + **/ + this.utils = utils; + + /** + * MarkdownIt#helpers -> helpers + * + * Link components parser functions, useful to write plugins. See details + * [here](https://github.com/markdown-it/markdown-it/blob/master/lib/helpers). + **/ + this.helpers = utils.assign({}, helpers); + + + this.options = {}; + this.configure(presetName); + + if (options) { this.set(options); } +} + + +/** chainable + * MarkdownIt.set(options) + * + * Set parser options (in the same format as in constructor). Probably, you + * will never need it, but you can change options after constructor call. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')() + * .set({ html: true, breaks: true }) + * .set({ typographer, true }); + * ``` + * + * __Note:__ To achieve the best possible performance, don't modify a + * `markdown-it` instance options on the fly. If you need multiple configurations + * it's best to create multiple instances and initialize each with separate + * config. + **/ +MarkdownIt.prototype.set = function (options) { + utils.assign(this.options, options); + return this; +}; + + +/** chainable, internal + * MarkdownIt.configure(presets) + * + * Batch load of all options and compenent settings. This is internal method, + * and you probably will not need it. But if you with - see available presets + * and data structure [here](https://github.com/markdown-it/markdown-it/tree/master/lib/presets) + * + * We strongly recommend to use presets instead of direct config loads. That + * will give better compatibility with next versions. + **/ +MarkdownIt.prototype.configure = function (presets) { + var self = this, presetName; + + if (utils.isString(presets)) { + presetName = presets; + presets = config[presetName]; + if (!presets) { throw new Error('Wrong `markdown-it` preset "' + presetName + '", check name'); } + } + + if (!presets) { throw new Error('Wrong `markdown-it` preset, can\'t be empty'); } + + if (presets.options) { self.set(presets.options); } + + if (presets.components) { + Object.keys(presets.components).forEach(function (name) { + if (presets.components[name].rules) { + self[name].ruler.enableOnly(presets.components[name].rules); + } + if (presets.components[name].rules2) { + self[name].ruler2.enableOnly(presets.components[name].rules2); + } + }); + } + return this; +}; + + +/** chainable + * MarkdownIt.enable(list, ignoreInvalid) + * - list (String|Array): rule name or list of rule names to enable + * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. + * + * Enable list or rules. It will automatically find appropriate components, + * containing rules with given names. If rule not found, and `ignoreInvalid` + * not set - throws exception. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')() + * .enable(['sub', 'sup']) + * .disable('smartquotes'); + * ``` + **/ +MarkdownIt.prototype.enable = function (list, ignoreInvalid) { + var result = []; + + if (!Array.isArray(list)) { list = [ list ]; } + + [ 'core', 'block', 'inline' ].forEach(function (chain) { + result = result.concat(this[chain].ruler.enable(list, true)); + }, this); + + result = result.concat(this.inline.ruler2.enable(list, true)); + + var missed = list.filter(function (name) { return result.indexOf(name) < 0; }); + + if (missed.length && !ignoreInvalid) { + throw new Error('MarkdownIt. Failed to enable unknown rule(s): ' + missed); + } + + return this; +}; + + +/** chainable + * MarkdownIt.disable(list, ignoreInvalid) + * - list (String|Array): rule name or list of rule names to disable. + * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. + * + * The same as [[MarkdownIt.enable]], but turn specified rules off. + **/ +MarkdownIt.prototype.disable = function (list, ignoreInvalid) { + var result = []; + + if (!Array.isArray(list)) { list = [ list ]; } + + [ 'core', 'block', 'inline' ].forEach(function (chain) { + result = result.concat(this[chain].ruler.disable(list, true)); + }, this); + + result = result.concat(this.inline.ruler2.disable(list, true)); + + var missed = list.filter(function (name) { return result.indexOf(name) < 0; }); + + if (missed.length && !ignoreInvalid) { + throw new Error('MarkdownIt. Failed to disable unknown rule(s): ' + missed); + } + return this; +}; + + +/** chainable + * MarkdownIt.use(plugin, params) + * + * Load specified plugin with given params into current parser instance. + * It's just a sugar to call `plugin(md, params)` with curring. + * + * ##### Example + * + * ```javascript + * var iterator = require('markdown-it-for-inline'); + * var md = require('markdown-it')() + * .use(iterator, 'foo_replace', 'text', function (tokens, idx) { + * tokens[idx].content = tokens[idx].content.replace(/foo/g, 'bar'); + * }); + * ``` + **/ +MarkdownIt.prototype.use = function (plugin /*, params, ... */) { + var args = [ this ].concat(Array.prototype.slice.call(arguments, 1)); + plugin.apply(plugin, args); + return this; +}; + + +/** internal + * MarkdownIt.parse(src, env) -> Array + * - src (String): source string + * - env (Object): environment sandbox + * + * Parse input string and returns list of block tokens (special token type + * "inline" will contain list of inline tokens). You should not call this + * method directly, until you write custom renderer (for example, to produce + * AST). + * + * `env` is used to pass data between "distributed" rules and return additional + * metadata like reference info, needed for the renderer. It also can be used to + * inject data in specific cases. Usually, you will be ok to pass `{}`, + * and then pass updated object to renderer. + **/ +MarkdownIt.prototype.parse = function (src, env) { + if (typeof src !== 'string') { + throw new Error('Input data should be a String'); + } + + var state = new this.core.State(src, this, env); + + this.core.process(state); + + return state.tokens; +}; + + +/** + * MarkdownIt.render(src [, env]) -> String + * - src (String): source string + * - env (Object): environment sandbox + * + * Render markdown string into html. It does all magic for you :). + * + * `env` can be used to inject additional metadata (`{}` by default). + * But you will not need it with high probability. See also comment + * in [[MarkdownIt.parse]]. + **/ +MarkdownIt.prototype.render = function (src, env) { + env = env || {}; + + return this.renderer.render(this.parse(src, env), this.options, env); +}; + + +/** internal + * MarkdownIt.parseInline(src, env) -> Array + * - src (String): source string + * - env (Object): environment sandbox + * + * The same as [[MarkdownIt.parse]] but skip all block rules. It returns the + * block tokens list with the single `inline` element, containing parsed inline + * tokens in `children` property. Also updates `env` object. + **/ +MarkdownIt.prototype.parseInline = function (src, env) { + var state = new this.core.State(src, this, env); + + state.inlineMode = true; + this.core.process(state); + + return state.tokens; +}; + + +/** + * MarkdownIt.renderInline(src [, env]) -> String + * - src (String): source string + * - env (Object): environment sandbox + * + * Similar to [[MarkdownIt.render]] but for single paragraph content. Result + * will NOT be wrapped into `

    ` tags. + **/ +MarkdownIt.prototype.renderInline = function (src, env) { + env = env || {}; + + return this.renderer.render(this.parseInline(src, env), this.options, env); +}; + + +module.exports = MarkdownIt; + +},{"./common/utils":4,"./helpers":5,"./parser_block":10,"./parser_core":11,"./parser_inline":12,"./presets/commonmark":13,"./presets/default":14,"./presets/zero":15,"./renderer":16,"linkify-it":53,"mdurl":58,"punycode":60}],10:[function(require,module,exports){ +/** internal + * class ParserBlock + * + * Block-level tokenizer. + **/ +'use strict'; + + +var Ruler = require('./ruler'); + + +var _rules = [ + // First 2 params - rule name & source. Secondary array - list of rules, + // which can be terminated by this one. + [ 'table', require('./rules_block/table'), [ 'paragraph', 'reference' ] ], + [ 'code', require('./rules_block/code') ], + [ 'fence', require('./rules_block/fence'), [ 'paragraph', 'reference', 'blockquote', 'list' ] ], + [ 'blockquote', require('./rules_block/blockquote'), [ 'paragraph', 'reference', 'blockquote', 'list' ] ], + [ 'hr', require('./rules_block/hr'), [ 'paragraph', 'reference', 'blockquote', 'list' ] ], + [ 'list', require('./rules_block/list'), [ 'paragraph', 'reference', 'blockquote' ] ], + [ 'reference', require('./rules_block/reference') ], + [ 'heading', require('./rules_block/heading'), [ 'paragraph', 'reference', 'blockquote' ] ], + [ 'lheading', require('./rules_block/lheading') ], + [ 'html_block', require('./rules_block/html_block'), [ 'paragraph', 'reference', 'blockquote' ] ], + [ 'paragraph', require('./rules_block/paragraph') ] +]; + + +/** + * new ParserBlock() + **/ +function ParserBlock() { + /** + * ParserBlock#ruler -> Ruler + * + * [[Ruler]] instance. Keep configuration of block rules. + **/ + this.ruler = new Ruler(); + + for (var i = 0; i < _rules.length; i++) { + this.ruler.push(_rules[i][0], _rules[i][1], { alt: (_rules[i][2] || []).slice() }); + } +} + + +// Generate tokens for input range +// +ParserBlock.prototype.tokenize = function (state, startLine, endLine) { + var ok, i, + rules = this.ruler.getRules(''), + len = rules.length, + line = startLine, + hasEmptyLines = false, + maxNesting = state.md.options.maxNesting; + + while (line < endLine) { + state.line = line = state.skipEmptyLines(line); + if (line >= endLine) { break; } + + // Termination condition for nested calls. + // Nested calls currently used for blockquotes & lists + if (state.sCount[line] < state.blkIndent) { break; } + + // If nesting level exceeded - skip tail to the end. That's not ordinary + // situation and we should not care about content. + if (state.level >= maxNesting) { + state.line = endLine; + break; + } + + // Try all possible rules. + // On success, rule should: + // + // - update `state.line` + // - update `state.tokens` + // - return true + + for (i = 0; i < len; i++) { + ok = rules[i](state, line, endLine, false); + if (ok) { break; } + } + + // set state.tight if we had an empty line before current tag + // i.e. latest empty line should not count + state.tight = !hasEmptyLines; + + // paragraph might "eat" one newline after it in nested lists + if (state.isEmpty(state.line - 1)) { + hasEmptyLines = true; + } + + line = state.line; + + if (line < endLine && state.isEmpty(line)) { + hasEmptyLines = true; + line++; + state.line = line; + } + } +}; + + +/** + * ParserBlock.parse(str, md, env, outTokens) + * + * Process input string and push block tokens into `outTokens` + **/ +ParserBlock.prototype.parse = function (src, md, env, outTokens) { + var state; + + if (!src) { return; } + + state = new this.State(src, md, env, outTokens); + + this.tokenize(state, state.line, state.lineMax); +}; + + +ParserBlock.prototype.State = require('./rules_block/state_block'); + + +module.exports = ParserBlock; + +},{"./ruler":17,"./rules_block/blockquote":18,"./rules_block/code":19,"./rules_block/fence":20,"./rules_block/heading":21,"./rules_block/hr":22,"./rules_block/html_block":23,"./rules_block/lheading":24,"./rules_block/list":25,"./rules_block/paragraph":26,"./rules_block/reference":27,"./rules_block/state_block":28,"./rules_block/table":29}],11:[function(require,module,exports){ +/** internal + * class Core + * + * Top-level rules executor. Glues block/inline parsers and does intermediate + * transformations. + **/ +'use strict'; + + +var Ruler = require('./ruler'); + + +var _rules = [ + [ 'normalize', require('./rules_core/normalize') ], + [ 'block', require('./rules_core/block') ], + [ 'inline', require('./rules_core/inline') ], + [ 'linkify', require('./rules_core/linkify') ], + [ 'replacements', require('./rules_core/replacements') ], + [ 'smartquotes', require('./rules_core/smartquotes') ] +]; + + +/** + * new Core() + **/ +function Core() { + /** + * Core#ruler -> Ruler + * + * [[Ruler]] instance. Keep configuration of core rules. + **/ + this.ruler = new Ruler(); + + for (var i = 0; i < _rules.length; i++) { + this.ruler.push(_rules[i][0], _rules[i][1]); + } +} + + +/** + * Core.process(state) + * + * Executes core chain rules. + **/ +Core.prototype.process = function (state) { + var i, l, rules; + + rules = this.ruler.getRules(''); + + for (i = 0, l = rules.length; i < l; i++) { + rules[i](state); + } +}; + +Core.prototype.State = require('./rules_core/state_core'); + + +module.exports = Core; + +},{"./ruler":17,"./rules_core/block":30,"./rules_core/inline":31,"./rules_core/linkify":32,"./rules_core/normalize":33,"./rules_core/replacements":34,"./rules_core/smartquotes":35,"./rules_core/state_core":36}],12:[function(require,module,exports){ +/** internal + * class ParserInline + * + * Tokenizes paragraph content. + **/ +'use strict'; + + +var Ruler = require('./ruler'); + + +//////////////////////////////////////////////////////////////////////////////// +// Parser rules + +var _rules = [ + [ 'text', require('./rules_inline/text') ], + [ 'newline', require('./rules_inline/newline') ], + [ 'escape', require('./rules_inline/escape') ], + [ 'backticks', require('./rules_inline/backticks') ], + [ 'strikethrough', require('./rules_inline/strikethrough').tokenize ], + [ 'emphasis', require('./rules_inline/emphasis').tokenize ], + [ 'link', require('./rules_inline/link') ], + [ 'image', require('./rules_inline/image') ], + [ 'autolink', require('./rules_inline/autolink') ], + [ 'html_inline', require('./rules_inline/html_inline') ], + [ 'entity', require('./rules_inline/entity') ] +]; + +var _rules2 = [ + [ 'balance_pairs', require('./rules_inline/balance_pairs') ], + [ 'strikethrough', require('./rules_inline/strikethrough').postProcess ], + [ 'emphasis', require('./rules_inline/emphasis').postProcess ], + [ 'text_collapse', require('./rules_inline/text_collapse') ] +]; + + +/** + * new ParserInline() + **/ +function ParserInline() { + var i; + + /** + * ParserInline#ruler -> Ruler + * + * [[Ruler]] instance. Keep configuration of inline rules. + **/ + this.ruler = new Ruler(); + + for (i = 0; i < _rules.length; i++) { + this.ruler.push(_rules[i][0], _rules[i][1]); + } + + /** + * ParserInline#ruler2 -> Ruler + * + * [[Ruler]] instance. Second ruler used for post-processing + * (e.g. in emphasis-like rules). + **/ + this.ruler2 = new Ruler(); + + for (i = 0; i < _rules2.length; i++) { + this.ruler2.push(_rules2[i][0], _rules2[i][1]); + } +} + + +// Skip single token by running all rules in validation mode; +// returns `true` if any rule reported success +// +ParserInline.prototype.skipToken = function (state) { + var ok, i, pos = state.pos, + rules = this.ruler.getRules(''), + len = rules.length, + maxNesting = state.md.options.maxNesting, + cache = state.cache; + + + if (typeof cache[pos] !== 'undefined') { + state.pos = cache[pos]; + return; + } + + if (state.level < maxNesting) { + for (i = 0; i < len; i++) { + // Increment state.level and decrement it later to limit recursion. + // It's harmless to do here, because no tokens are created. But ideally, + // we'd need a separate private state variable for this purpose. + // + state.level++; + ok = rules[i](state, true); + state.level--; + + if (ok) { break; } + } + } else { + // Too much nesting, just skip until the end of the paragraph. + // + // NOTE: this will cause links to behave incorrectly in the following case, + // when an amount of `[` is exactly equal to `maxNesting + 1`: + // + // [[[[[[[[[[[[[[[[[[[[[foo]() + // + // TODO: remove this workaround when CM standard will allow nested links + // (we can replace it by preventing links from being parsed in + // validation mode) + // + state.pos = state.posMax; + } + + if (!ok) { state.pos++; } + cache[pos] = state.pos; +}; + + +// Generate tokens for input range +// +ParserInline.prototype.tokenize = function (state) { + var ok, i, + rules = this.ruler.getRules(''), + len = rules.length, + end = state.posMax, + maxNesting = state.md.options.maxNesting; + + while (state.pos < end) { + // Try all possible rules. + // On success, rule should: + // + // - update `state.pos` + // - update `state.tokens` + // - return true + + if (state.level < maxNesting) { + for (i = 0; i < len; i++) { + ok = rules[i](state, false); + if (ok) { break; } + } + } + + if (ok) { + if (state.pos >= end) { break; } + continue; + } + + state.pending += state.src[state.pos++]; + } + + if (state.pending) { + state.pushPending(); + } +}; + + +/** + * ParserInline.parse(str, md, env, outTokens) + * + * Process input string and push inline tokens into `outTokens` + **/ +ParserInline.prototype.parse = function (str, md, env, outTokens) { + var i, rules, len; + var state = new this.State(str, md, env, outTokens); + + this.tokenize(state); + + rules = this.ruler2.getRules(''); + len = rules.length; + + for (i = 0; i < len; i++) { + rules[i](state); + } +}; + + +ParserInline.prototype.State = require('./rules_inline/state_inline'); + + +module.exports = ParserInline; + +},{"./ruler":17,"./rules_inline/autolink":37,"./rules_inline/backticks":38,"./rules_inline/balance_pairs":39,"./rules_inline/emphasis":40,"./rules_inline/entity":41,"./rules_inline/escape":42,"./rules_inline/html_inline":43,"./rules_inline/image":44,"./rules_inline/link":45,"./rules_inline/newline":46,"./rules_inline/state_inline":47,"./rules_inline/strikethrough":48,"./rules_inline/text":49,"./rules_inline/text_collapse":50}],13:[function(require,module,exports){ +// Commonmark default options + +'use strict'; + + +module.exports = { + options: { + html: true, // Enable HTML tags in source + xhtmlOut: true, // Use '/' to close single tags (
    ) + breaks: false, // Convert '\n' in paragraphs into
    + langPrefix: 'language-', // CSS language prefix for fenced blocks + linkify: false, // autoconvert URL-like texts to links + + // Enable some language-neutral replacements + quotes beautification + typographer: false, + + // Double + single quotes replacement pairs, when typographer enabled, + // and smartquotes on. Could be either a String or an Array. + // + // For example, you can use '«»„“' for Russian, '„“‚‘' for German, + // and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp). + quotes: '\u201c\u201d\u2018\u2019', /* “”‘’ */ + + // Highlighter function. Should return escaped HTML, + // or '' if the source string is not changed and should be escaped externaly. + // If result starts with ) + breaks: false, // Convert '\n' in paragraphs into
    + langPrefix: 'language-', // CSS language prefix for fenced blocks + linkify: false, // autoconvert URL-like texts to links + + // Enable some language-neutral replacements + quotes beautification + typographer: false, + + // Double + single quotes replacement pairs, when typographer enabled, + // and smartquotes on. Could be either a String or an Array. + // + // For example, you can use '«»„“' for Russian, '„“‚‘' for German, + // and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp). + quotes: '\u201c\u201d\u2018\u2019', /* “”‘’ */ + + // Highlighter function. Should return escaped HTML, + // or '' if the source string is not changed and should be escaped externaly. + // If result starts with ) + breaks: false, // Convert '\n' in paragraphs into
    + langPrefix: 'language-', // CSS language prefix for fenced blocks + linkify: false, // autoconvert URL-like texts to links + + // Enable some language-neutral replacements + quotes beautification + typographer: false, + + // Double + single quotes replacement pairs, when typographer enabled, + // and smartquotes on. Could be either a String or an Array. + // + // For example, you can use '«»„“' for Russian, '„“‚‘' for German, + // and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp). + quotes: '\u201c\u201d\u2018\u2019', /* “”‘’ */ + + // Highlighter function. Should return escaped HTML, + // or '' if the source string is not changed and should be escaped externaly. + // If result starts with ' + + escapeHtml(tokens[idx].content) + + ''; +}; + + +default_rules.code_block = function (tokens, idx, options, env, slf) { + var token = tokens[idx]; + + return '' + + escapeHtml(tokens[idx].content) + + '\n'; +}; + + +default_rules.fence = function (tokens, idx, options, env, slf) { + var token = tokens[idx], + info = token.info ? unescapeAll(token.info).trim() : '', + langName = '', + highlighted, i, tmpAttrs, tmpToken; + + if (info) { + langName = info.split(/\s+/g)[0]; + } + + if (options.highlight) { + highlighted = options.highlight(token.content, langName) || escapeHtml(token.content); + } else { + highlighted = escapeHtml(token.content); + } + + if (highlighted.indexOf('' + + highlighted + + '\n'; + } + + + return '

    '
    +        + highlighted
    +        + '
    \n'; +}; + + +default_rules.image = function (tokens, idx, options, env, slf) { + var token = tokens[idx]; + + // "alt" attr MUST be set, even if empty. Because it's mandatory and + // should be placed on proper position for tests. + // + // Replace content with actual value + + token.attrs[token.attrIndex('alt')][1] = + slf.renderInlineAsText(token.children, options, env); + + return slf.renderToken(tokens, idx, options); +}; + + +default_rules.hardbreak = function (tokens, idx, options /*, env */) { + return options.xhtmlOut ? '
    \n' : '
    \n'; +}; +default_rules.softbreak = function (tokens, idx, options /*, env */) { + return options.breaks ? (options.xhtmlOut ? '
    \n' : '
    \n') : '\n'; +}; + + +default_rules.text = function (tokens, idx /*, options, env */) { + return escapeHtml(tokens[idx].content); +}; + + +default_rules.html_block = function (tokens, idx /*, options, env */) { + return tokens[idx].content; +}; +default_rules.html_inline = function (tokens, idx /*, options, env */) { + return tokens[idx].content; +}; + + +/** + * new Renderer() + * + * Creates new [[Renderer]] instance and fill [[Renderer#rules]] with defaults. + **/ +function Renderer() { + + /** + * Renderer#rules -> Object + * + * Contains render rules for tokens. Can be updated and extended. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')(); + * + * md.renderer.rules.strong_open = function () { return ''; }; + * md.renderer.rules.strong_close = function () { return ''; }; + * + * var result = md.renderInline(...); + * ``` + * + * Each rule is called as independent static function with fixed signature: + * + * ```javascript + * function my_token_render(tokens, idx, options, env, renderer) { + * // ... + * return renderedHTML; + * } + * ``` + * + * See [source code](https://github.com/markdown-it/markdown-it/blob/master/lib/renderer.js) + * for more details and examples. + **/ + this.rules = assign({}, default_rules); +} + + +/** + * Renderer.renderAttrs(token) -> String + * + * Render token attributes to string. + **/ +Renderer.prototype.renderAttrs = function renderAttrs(token) { + var i, l, result; + + if (!token.attrs) { return ''; } + + result = ''; + + for (i = 0, l = token.attrs.length; i < l; i++) { + result += ' ' + escapeHtml(token.attrs[i][0]) + '="' + escapeHtml(token.attrs[i][1]) + '"'; + } + + return result; +}; + + +/** + * Renderer.renderToken(tokens, idx, options) -> String + * - tokens (Array): list of tokens + * - idx (Numbed): token index to render + * - options (Object): params of parser instance + * + * Default token renderer. Can be overriden by custom function + * in [[Renderer#rules]]. + **/ +Renderer.prototype.renderToken = function renderToken(tokens, idx, options) { + var nextToken, + result = '', + needLf = false, + token = tokens[idx]; + + // Tight list paragraphs + if (token.hidden) { + return ''; + } + + // Insert a newline between hidden paragraph and subsequent opening + // block-level tag. + // + // For example, here we should insert a newline before blockquote: + // - a + // > + // + if (token.block && token.nesting !== -1 && idx && tokens[idx - 1].hidden) { + result += '\n'; + } + + // Add token name, e.g. ``. + // + needLf = false; + } + } + } + } + + result += needLf ? '>\n' : '>'; + + return result; +}; + + +/** + * Renderer.renderInline(tokens, options, env) -> String + * - tokens (Array): list on block tokens to renter + * - options (Object): params of parser instance + * - env (Object): additional data from parsed input (references, for example) + * + * The same as [[Renderer.render]], but for single token of `inline` type. + **/ +Renderer.prototype.renderInline = function (tokens, options, env) { + var type, + result = '', + rules = this.rules; + + for (var i = 0, len = tokens.length; i < len; i++) { + type = tokens[i].type; + + if (typeof rules[type] !== 'undefined') { + result += rules[type](tokens, i, options, env, this); + } else { + result += this.renderToken(tokens, i, options); + } + } + + return result; +}; + + +/** internal + * Renderer.renderInlineAsText(tokens, options, env) -> String + * - tokens (Array): list on block tokens to renter + * - options (Object): params of parser instance + * - env (Object): additional data from parsed input (references, for example) + * + * Special kludge for image `alt` attributes to conform CommonMark spec. + * Don't try to use it! Spec requires to show `alt` content with stripped markup, + * instead of simple escaping. + **/ +Renderer.prototype.renderInlineAsText = function (tokens, options, env) { + var result = ''; + + for (var i = 0, len = tokens.length; i < len; i++) { + if (tokens[i].type === 'text') { + result += tokens[i].content; + } else if (tokens[i].type === 'image') { + result += this.renderInlineAsText(tokens[i].children, options, env); + } + } + + return result; +}; + + +/** + * Renderer.render(tokens, options, env) -> String + * - tokens (Array): list on block tokens to renter + * - options (Object): params of parser instance + * - env (Object): additional data from parsed input (references, for example) + * + * Takes token stream and generates HTML. Probably, you will never need to call + * this method directly. + **/ +Renderer.prototype.render = function (tokens, options, env) { + var i, len, type, + result = '', + rules = this.rules; + + for (i = 0, len = tokens.length; i < len; i++) { + type = tokens[i].type; + + if (type === 'inline') { + result += this.renderInline(tokens[i].children, options, env); + } else if (typeof rules[type] !== 'undefined') { + result += rules[tokens[i].type](tokens, i, options, env, this); + } else { + result += this.renderToken(tokens, i, options, env); + } + } + + return result; +}; + +module.exports = Renderer; + +},{"./common/utils":4}],17:[function(require,module,exports){ +/** + * class Ruler + * + * Helper class, used by [[MarkdownIt#core]], [[MarkdownIt#block]] and + * [[MarkdownIt#inline]] to manage sequences of functions (rules): + * + * - keep rules in defined order + * - assign the name to each rule + * - enable/disable rules + * - add/replace rules + * - allow assign rules to additional named chains (in the same) + * - cacheing lists of active rules + * + * You will not need use this class directly until write plugins. For simple + * rules control use [[MarkdownIt.disable]], [[MarkdownIt.enable]] and + * [[MarkdownIt.use]]. + **/ +'use strict'; + + +/** + * new Ruler() + **/ +function Ruler() { + // List of added rules. Each element is: + // + // { + // name: XXX, + // enabled: Boolean, + // fn: Function(), + // alt: [ name2, name3 ] + // } + // + this.__rules__ = []; + + // Cached rule chains. + // + // First level - chain name, '' for default. + // Second level - diginal anchor for fast filtering by charcodes. + // + this.__cache__ = null; +} + +//////////////////////////////////////////////////////////////////////////////// +// Helper methods, should not be used directly + + +// Find rule index by name +// +Ruler.prototype.__find__ = function (name) { + for (var i = 0; i < this.__rules__.length; i++) { + if (this.__rules__[i].name === name) { + return i; + } + } + return -1; +}; + + +// Build rules lookup cache +// +Ruler.prototype.__compile__ = function () { + var self = this; + var chains = [ '' ]; + + // collect unique names + self.__rules__.forEach(function (rule) { + if (!rule.enabled) { return; } + + rule.alt.forEach(function (altName) { + if (chains.indexOf(altName) < 0) { + chains.push(altName); + } + }); + }); + + self.__cache__ = {}; + + chains.forEach(function (chain) { + self.__cache__[chain] = []; + self.__rules__.forEach(function (rule) { + if (!rule.enabled) { return; } + + if (chain && rule.alt.indexOf(chain) < 0) { return; } + + self.__cache__[chain].push(rule.fn); + }); + }); +}; + + +/** + * Ruler.at(name, fn [, options]) + * - name (String): rule name to replace. + * - fn (Function): new rule function. + * - options (Object): new rule options (not mandatory). + * + * Replace rule by name with new function & options. Throws error if name not + * found. + * + * ##### Options: + * + * - __alt__ - array with names of "alternate" chains. + * + * ##### Example + * + * Replace existing typographer replacement rule with new one: + * + * ```javascript + * var md = require('markdown-it')(); + * + * md.core.ruler.at('replacements', function replace(state) { + * //... + * }); + * ``` + **/ +Ruler.prototype.at = function (name, fn, options) { + var index = this.__find__(name); + var opt = options || {}; + + if (index === -1) { throw new Error('Parser rule not found: ' + name); } + + this.__rules__[index].fn = fn; + this.__rules__[index].alt = opt.alt || []; + this.__cache__ = null; +}; + + +/** + * Ruler.before(beforeName, ruleName, fn [, options]) + * - beforeName (String): new rule will be added before this one. + * - ruleName (String): name of added rule. + * - fn (Function): rule function. + * - options (Object): rule options (not mandatory). + * + * Add new rule to chain before one with given name. See also + * [[Ruler.after]], [[Ruler.push]]. + * + * ##### Options: + * + * - __alt__ - array with names of "alternate" chains. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')(); + * + * md.block.ruler.before('paragraph', 'my_rule', function replace(state) { + * //... + * }); + * ``` + **/ +Ruler.prototype.before = function (beforeName, ruleName, fn, options) { + var index = this.__find__(beforeName); + var opt = options || {}; + + if (index === -1) { throw new Error('Parser rule not found: ' + beforeName); } + + this.__rules__.splice(index, 0, { + name: ruleName, + enabled: true, + fn: fn, + alt: opt.alt || [] + }); + + this.__cache__ = null; +}; + + +/** + * Ruler.after(afterName, ruleName, fn [, options]) + * - afterName (String): new rule will be added after this one. + * - ruleName (String): name of added rule. + * - fn (Function): rule function. + * - options (Object): rule options (not mandatory). + * + * Add new rule to chain after one with given name. See also + * [[Ruler.before]], [[Ruler.push]]. + * + * ##### Options: + * + * - __alt__ - array with names of "alternate" chains. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')(); + * + * md.inline.ruler.after('text', 'my_rule', function replace(state) { + * //... + * }); + * ``` + **/ +Ruler.prototype.after = function (afterName, ruleName, fn, options) { + var index = this.__find__(afterName); + var opt = options || {}; + + if (index === -1) { throw new Error('Parser rule not found: ' + afterName); } + + this.__rules__.splice(index + 1, 0, { + name: ruleName, + enabled: true, + fn: fn, + alt: opt.alt || [] + }); + + this.__cache__ = null; +}; + +/** + * Ruler.push(ruleName, fn [, options]) + * - ruleName (String): name of added rule. + * - fn (Function): rule function. + * - options (Object): rule options (not mandatory). + * + * Push new rule to the end of chain. See also + * [[Ruler.before]], [[Ruler.after]]. + * + * ##### Options: + * + * - __alt__ - array with names of "alternate" chains. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')(); + * + * md.core.ruler.push('my_rule', function replace(state) { + * //... + * }); + * ``` + **/ +Ruler.prototype.push = function (ruleName, fn, options) { + var opt = options || {}; + + this.__rules__.push({ + name: ruleName, + enabled: true, + fn: fn, + alt: opt.alt || [] + }); + + this.__cache__ = null; +}; + + +/** + * Ruler.enable(list [, ignoreInvalid]) -> Array + * - list (String|Array): list of rule names to enable. + * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. + * + * Enable rules with given names. If any rule name not found - throw Error. + * Errors can be disabled by second param. + * + * Returns list of found rule names (if no exception happened). + * + * See also [[Ruler.disable]], [[Ruler.enableOnly]]. + **/ +Ruler.prototype.enable = function (list, ignoreInvalid) { + if (!Array.isArray(list)) { list = [ list ]; } + + var result = []; + + // Search by name and enable + list.forEach(function (name) { + var idx = this.__find__(name); + + if (idx < 0) { + if (ignoreInvalid) { return; } + throw new Error('Rules manager: invalid rule name ' + name); + } + this.__rules__[idx].enabled = true; + result.push(name); + }, this); + + this.__cache__ = null; + return result; +}; + + +/** + * Ruler.enableOnly(list [, ignoreInvalid]) + * - list (String|Array): list of rule names to enable (whitelist). + * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. + * + * Enable rules with given names, and disable everything else. If any rule name + * not found - throw Error. Errors can be disabled by second param. + * + * See also [[Ruler.disable]], [[Ruler.enable]]. + **/ +Ruler.prototype.enableOnly = function (list, ignoreInvalid) { + if (!Array.isArray(list)) { list = [ list ]; } + + this.__rules__.forEach(function (rule) { rule.enabled = false; }); + + this.enable(list, ignoreInvalid); +}; + + +/** + * Ruler.disable(list [, ignoreInvalid]) -> Array + * - list (String|Array): list of rule names to disable. + * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. + * + * Disable rules with given names. If any rule name not found - throw Error. + * Errors can be disabled by second param. + * + * Returns list of found rule names (if no exception happened). + * + * See also [[Ruler.enable]], [[Ruler.enableOnly]]. + **/ +Ruler.prototype.disable = function (list, ignoreInvalid) { + if (!Array.isArray(list)) { list = [ list ]; } + + var result = []; + + // Search by name and disable + list.forEach(function (name) { + var idx = this.__find__(name); + + if (idx < 0) { + if (ignoreInvalid) { return; } + throw new Error('Rules manager: invalid rule name ' + name); + } + this.__rules__[idx].enabled = false; + result.push(name); + }, this); + + this.__cache__ = null; + return result; +}; + + +/** + * Ruler.getRules(chainName) -> Array + * + * Return array of active functions (rules) for given chain name. It analyzes + * rules configuration, compiles caches if not exists and returns result. + * + * Default chain name is `''` (empty string). It can't be skipped. That's + * done intentionally, to keep signature monomorphic for high speed. + **/ +Ruler.prototype.getRules = function (chainName) { + if (this.__cache__ === null) { + this.__compile__(); + } + + // Chain can be empty, if rules disabled. But we still have to return Array. + return this.__cache__[chainName] || []; +}; + +module.exports = Ruler; + +},{}],18:[function(require,module,exports){ +// Block quotes + +'use strict'; + +var isSpace = require('../common/utils').isSpace; + + +module.exports = function blockquote(state, startLine, endLine, silent) { + var adjustTab, + ch, + i, + initial, + l, + lastLineEmpty, + lines, + nextLine, + offset, + oldBMarks, + oldBSCount, + oldIndent, + oldParentType, + oldSCount, + oldTShift, + spaceAfterMarker, + terminate, + terminatorRules, + token, + wasOutdented, + oldLineMax = state.lineMax, + pos = state.bMarks[startLine] + state.tShift[startLine], + max = state.eMarks[startLine]; + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + + // check the block quote marker + if (state.src.charCodeAt(pos++) !== 0x3E/* > */) { return false; } + + // we know that it's going to be a valid blockquote, + // so no point trying to find the end of it in silent mode + if (silent) { return true; } + + // skip spaces after ">" and re-calculate offset + initial = offset = state.sCount[startLine] + pos - (state.bMarks[startLine] + state.tShift[startLine]); + + // skip one optional space after '>' + if (state.src.charCodeAt(pos) === 0x20 /* space */) { + // ' > test ' + // ^ -- position start of line here: + pos++; + initial++; + offset++; + adjustTab = false; + spaceAfterMarker = true; + } else if (state.src.charCodeAt(pos) === 0x09 /* tab */) { + spaceAfterMarker = true; + + if ((state.bsCount[startLine] + offset) % 4 === 3) { + // ' >\t test ' + // ^ -- position start of line here (tab has width===1) + pos++; + initial++; + offset++; + adjustTab = false; + } else { + // ' >\t test ' + // ^ -- position start of line here + shift bsCount slightly + // to make extra space appear + adjustTab = true; + } + } else { + spaceAfterMarker = false; + } + + oldBMarks = [ state.bMarks[startLine] ]; + state.bMarks[startLine] = pos; + + while (pos < max) { + ch = state.src.charCodeAt(pos); + + if (isSpace(ch)) { + if (ch === 0x09) { + offset += 4 - (offset + state.bsCount[startLine] + (adjustTab ? 1 : 0)) % 4; + } else { + offset++; + } + } else { + break; + } + + pos++; + } + + oldBSCount = [ state.bsCount[startLine] ]; + state.bsCount[startLine] = state.sCount[startLine] + 1 + (spaceAfterMarker ? 1 : 0); + + lastLineEmpty = pos >= max; + + oldSCount = [ state.sCount[startLine] ]; + state.sCount[startLine] = offset - initial; + + oldTShift = [ state.tShift[startLine] ]; + state.tShift[startLine] = pos - state.bMarks[startLine]; + + terminatorRules = state.md.block.ruler.getRules('blockquote'); + + oldParentType = state.parentType; + state.parentType = 'blockquote'; + wasOutdented = false; + + // Search the end of the block + // + // Block ends with either: + // 1. an empty line outside: + // ``` + // > test + // + // ``` + // 2. an empty line inside: + // ``` + // > + // test + // ``` + // 3. another tag: + // ``` + // > test + // - - - + // ``` + for (nextLine = startLine + 1; nextLine < endLine; nextLine++) { + // check if it's outdented, i.e. it's inside list item and indented + // less than said list item: + // + // ``` + // 1. anything + // > current blockquote + // 2. checking this line + // ``` + if (state.sCount[nextLine] < state.blkIndent) wasOutdented = true; + + pos = state.bMarks[nextLine] + state.tShift[nextLine]; + max = state.eMarks[nextLine]; + + if (pos >= max) { + // Case 1: line is not inside the blockquote, and this line is empty. + break; + } + + if (state.src.charCodeAt(pos++) === 0x3E/* > */ && !wasOutdented) { + // This line is inside the blockquote. + + // skip spaces after ">" and re-calculate offset + initial = offset = state.sCount[nextLine] + pos - (state.bMarks[nextLine] + state.tShift[nextLine]); + + // skip one optional space after '>' + if (state.src.charCodeAt(pos) === 0x20 /* space */) { + // ' > test ' + // ^ -- position start of line here: + pos++; + initial++; + offset++; + adjustTab = false; + spaceAfterMarker = true; + } else if (state.src.charCodeAt(pos) === 0x09 /* tab */) { + spaceAfterMarker = true; + + if ((state.bsCount[nextLine] + offset) % 4 === 3) { + // ' >\t test ' + // ^ -- position start of line here (tab has width===1) + pos++; + initial++; + offset++; + adjustTab = false; + } else { + // ' >\t test ' + // ^ -- position start of line here + shift bsCount slightly + // to make extra space appear + adjustTab = true; + } + } else { + spaceAfterMarker = false; + } + + oldBMarks.push(state.bMarks[nextLine]); + state.bMarks[nextLine] = pos; + + while (pos < max) { + ch = state.src.charCodeAt(pos); + + if (isSpace(ch)) { + if (ch === 0x09) { + offset += 4 - (offset + state.bsCount[nextLine] + (adjustTab ? 1 : 0)) % 4; + } else { + offset++; + } + } else { + break; + } + + pos++; + } + + lastLineEmpty = pos >= max; + + oldBSCount.push(state.bsCount[nextLine]); + state.bsCount[nextLine] = state.sCount[nextLine] + 1 + (spaceAfterMarker ? 1 : 0); + + oldSCount.push(state.sCount[nextLine]); + state.sCount[nextLine] = offset - initial; + + oldTShift.push(state.tShift[nextLine]); + state.tShift[nextLine] = pos - state.bMarks[nextLine]; + continue; + } + + // Case 2: line is not inside the blockquote, and the last line was empty. + if (lastLineEmpty) { break; } + + // Case 3: another tag found. + terminate = false; + for (i = 0, l = terminatorRules.length; i < l; i++) { + if (terminatorRules[i](state, nextLine, endLine, true)) { + terminate = true; + break; + } + } + + if (terminate) { + // Quirk to enforce "hard termination mode" for paragraphs; + // normally if you call `tokenize(state, startLine, nextLine)`, + // paragraphs will look below nextLine for paragraph continuation, + // but if blockquote is terminated by another tag, they shouldn't + state.lineMax = nextLine; + + if (state.blkIndent !== 0) { + // state.blkIndent was non-zero, we now set it to zero, + // so we need to re-calculate all offsets to appear as + // if indent wasn't changed + oldBMarks.push(state.bMarks[nextLine]); + oldBSCount.push(state.bsCount[nextLine]); + oldTShift.push(state.tShift[nextLine]); + oldSCount.push(state.sCount[nextLine]); + state.sCount[nextLine] -= state.blkIndent; + } + + break; + } + + oldBMarks.push(state.bMarks[nextLine]); + oldBSCount.push(state.bsCount[nextLine]); + oldTShift.push(state.tShift[nextLine]); + oldSCount.push(state.sCount[nextLine]); + + // A negative indentation means that this is a paragraph continuation + // + state.sCount[nextLine] = -1; + } + + oldIndent = state.blkIndent; + state.blkIndent = 0; + + token = state.push('blockquote_open', 'blockquote', 1); + token.markup = '>'; + token.map = lines = [ startLine, 0 ]; + + state.md.block.tokenize(state, startLine, nextLine); + + token = state.push('blockquote_close', 'blockquote', -1); + token.markup = '>'; + + state.lineMax = oldLineMax; + state.parentType = oldParentType; + lines[1] = state.line; + + // Restore original tShift; this might not be necessary since the parser + // has already been here, but just to make sure we can do that. + for (i = 0; i < oldTShift.length; i++) { + state.bMarks[i + startLine] = oldBMarks[i]; + state.tShift[i + startLine] = oldTShift[i]; + state.sCount[i + startLine] = oldSCount[i]; + state.bsCount[i + startLine] = oldBSCount[i]; + } + state.blkIndent = oldIndent; + + return true; +}; + +},{"../common/utils":4}],19:[function(require,module,exports){ +// Code block (4 spaces padded) + +'use strict'; + + +module.exports = function code(state, startLine, endLine/*, silent*/) { + var nextLine, last, token; + + if (state.sCount[startLine] - state.blkIndent < 4) { return false; } + + last = nextLine = startLine + 1; + + while (nextLine < endLine) { + if (state.isEmpty(nextLine)) { + nextLine++; + continue; + } + + if (state.sCount[nextLine] - state.blkIndent >= 4) { + nextLine++; + last = nextLine; + continue; + } + break; + } + + state.line = last; + + token = state.push('code_block', 'code', 0); + token.content = state.getLines(startLine, last, 4 + state.blkIndent, true); + token.map = [ startLine, state.line ]; + + return true; +}; + +},{}],20:[function(require,module,exports){ +// fences (``` lang, ~~~ lang) + +'use strict'; + + +module.exports = function fence(state, startLine, endLine, silent) { + var marker, len, params, nextLine, mem, token, markup, + haveEndMarker = false, + pos = state.bMarks[startLine] + state.tShift[startLine], + max = state.eMarks[startLine]; + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + + if (pos + 3 > max) { return false; } + + marker = state.src.charCodeAt(pos); + + if (marker !== 0x7E/* ~ */ && marker !== 0x60 /* ` */) { + return false; + } + + // scan marker length + mem = pos; + pos = state.skipChars(pos, marker); + + len = pos - mem; + + if (len < 3) { return false; } + + markup = state.src.slice(mem, pos); + params = state.src.slice(pos, max); + + if (marker === 0x60 /* ` */) { + if (params.indexOf(String.fromCharCode(marker)) >= 0) { + return false; + } + } + + // Since start is found, we can report success here in validation mode + if (silent) { return true; } + + // search end of block + nextLine = startLine; + + for (;;) { + nextLine++; + if (nextLine >= endLine) { + // unclosed block should be autoclosed by end of document. + // also block seems to be autoclosed by end of parent + break; + } + + pos = mem = state.bMarks[nextLine] + state.tShift[nextLine]; + max = state.eMarks[nextLine]; + + if (pos < max && state.sCount[nextLine] < state.blkIndent) { + // non-empty line with negative indent should stop the list: + // - ``` + // test + break; + } + + if (state.src.charCodeAt(pos) !== marker) { continue; } + + if (state.sCount[nextLine] - state.blkIndent >= 4) { + // closing fence should be indented less than 4 spaces + continue; + } + + pos = state.skipChars(pos, marker); + + // closing code fence must be at least as long as the opening one + if (pos - mem < len) { continue; } + + // make sure tail has spaces only + pos = state.skipSpaces(pos); + + if (pos < max) { continue; } + + haveEndMarker = true; + // found! + break; + } + + // If a fence has heading spaces, they should be removed from its inner block + len = state.sCount[startLine]; + + state.line = nextLine + (haveEndMarker ? 1 : 0); + + token = state.push('fence', 'code', 0); + token.info = params; + token.content = state.getLines(startLine + 1, nextLine, len, true); + token.markup = markup; + token.map = [ startLine, state.line ]; + + return true; +}; + +},{}],21:[function(require,module,exports){ +// heading (#, ##, ...) + +'use strict'; + +var isSpace = require('../common/utils').isSpace; + + +module.exports = function heading(state, startLine, endLine, silent) { + var ch, level, tmp, token, + pos = state.bMarks[startLine] + state.tShift[startLine], + max = state.eMarks[startLine]; + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + + ch = state.src.charCodeAt(pos); + + if (ch !== 0x23/* # */ || pos >= max) { return false; } + + // count heading level + level = 1; + ch = state.src.charCodeAt(++pos); + while (ch === 0x23/* # */ && pos < max && level <= 6) { + level++; + ch = state.src.charCodeAt(++pos); + } + + if (level > 6 || (pos < max && !isSpace(ch))) { return false; } + + if (silent) { return true; } + + // Let's cut tails like ' ### ' from the end of string + + max = state.skipSpacesBack(max, pos); + tmp = state.skipCharsBack(max, 0x23, pos); // # + if (tmp > pos && isSpace(state.src.charCodeAt(tmp - 1))) { + max = tmp; + } + + state.line = startLine + 1; + + token = state.push('heading_open', 'h' + String(level), 1); + token.markup = '########'.slice(0, level); + token.map = [ startLine, state.line ]; + + token = state.push('inline', '', 0); + token.content = state.src.slice(pos, max).trim(); + token.map = [ startLine, state.line ]; + token.children = []; + + token = state.push('heading_close', 'h' + String(level), -1); + token.markup = '########'.slice(0, level); + + return true; +}; + +},{"../common/utils":4}],22:[function(require,module,exports){ +// Horizontal rule + +'use strict'; + +var isSpace = require('../common/utils').isSpace; + + +module.exports = function hr(state, startLine, endLine, silent) { + var marker, cnt, ch, token, + pos = state.bMarks[startLine] + state.tShift[startLine], + max = state.eMarks[startLine]; + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + + marker = state.src.charCodeAt(pos++); + + // Check hr marker + if (marker !== 0x2A/* * */ && + marker !== 0x2D/* - */ && + marker !== 0x5F/* _ */) { + return false; + } + + // markers can be mixed with spaces, but there should be at least 3 of them + + cnt = 1; + while (pos < max) { + ch = state.src.charCodeAt(pos++); + if (ch !== marker && !isSpace(ch)) { return false; } + if (ch === marker) { cnt++; } + } + + if (cnt < 3) { return false; } + + if (silent) { return true; } + + state.line = startLine + 1; + + token = state.push('hr', 'hr', 0); + token.map = [ startLine, state.line ]; + token.markup = Array(cnt + 1).join(String.fromCharCode(marker)); + + return true; +}; + +},{"../common/utils":4}],23:[function(require,module,exports){ +// HTML block + +'use strict'; + + +var block_names = require('../common/html_blocks'); +var HTML_OPEN_CLOSE_TAG_RE = require('../common/html_re').HTML_OPEN_CLOSE_TAG_RE; + +// An array of opening and corresponding closing sequences for html tags, +// last argument defines whether it can terminate a paragraph or not +// +var HTML_SEQUENCES = [ + [ /^<(script|pre|style)(?=(\s|>|$))/i, /<\/(script|pre|style)>/i, true ], + [ /^/, true ], + [ /^<\?/, /\?>/, true ], + [ /^/, true ], + [ /^/, true ], + [ new RegExp('^|$))', 'i'), /^$/, true ], + [ new RegExp(HTML_OPEN_CLOSE_TAG_RE.source + '\\s*$'), /^$/, false ] +]; + + +module.exports = function html_block(state, startLine, endLine, silent) { + var i, nextLine, token, lineText, + pos = state.bMarks[startLine] + state.tShift[startLine], + max = state.eMarks[startLine]; + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + + if (!state.md.options.html) { return false; } + + if (state.src.charCodeAt(pos) !== 0x3C/* < */) { return false; } + + lineText = state.src.slice(pos, max); + + for (i = 0; i < HTML_SEQUENCES.length; i++) { + if (HTML_SEQUENCES[i][0].test(lineText)) { break; } + } + + if (i === HTML_SEQUENCES.length) { return false; } + + if (silent) { + // true if this sequence can be a terminator, false otherwise + return HTML_SEQUENCES[i][2]; + } + + nextLine = startLine + 1; + + // If we are here - we detected HTML block. + // Let's roll down till block end. + if (!HTML_SEQUENCES[i][1].test(lineText)) { + for (; nextLine < endLine; nextLine++) { + if (state.sCount[nextLine] < state.blkIndent) { break; } + + pos = state.bMarks[nextLine] + state.tShift[nextLine]; + max = state.eMarks[nextLine]; + lineText = state.src.slice(pos, max); + + if (HTML_SEQUENCES[i][1].test(lineText)) { + if (lineText.length !== 0) { nextLine++; } + break; + } + } + } + + state.line = nextLine; + + token = state.push('html_block', '', 0); + token.map = [ startLine, nextLine ]; + token.content = state.getLines(startLine, nextLine, state.blkIndent, true); + + return true; +}; + +},{"../common/html_blocks":2,"../common/html_re":3}],24:[function(require,module,exports){ +// lheading (---, ===) + +'use strict'; + + +module.exports = function lheading(state, startLine, endLine/*, silent*/) { + var content, terminate, i, l, token, pos, max, level, marker, + nextLine = startLine + 1, oldParentType, + terminatorRules = state.md.block.ruler.getRules('paragraph'); + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + + oldParentType = state.parentType; + state.parentType = 'paragraph'; // use paragraph to match terminatorRules + + // jump line-by-line until empty one or EOF + for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) { + // this would be a code block normally, but after paragraph + // it's considered a lazy continuation regardless of what's there + if (state.sCount[nextLine] - state.blkIndent > 3) { continue; } + + // + // Check for underline in setext header + // + if (state.sCount[nextLine] >= state.blkIndent) { + pos = state.bMarks[nextLine] + state.tShift[nextLine]; + max = state.eMarks[nextLine]; + + if (pos < max) { + marker = state.src.charCodeAt(pos); + + if (marker === 0x2D/* - */ || marker === 0x3D/* = */) { + pos = state.skipChars(pos, marker); + pos = state.skipSpaces(pos); + + if (pos >= max) { + level = (marker === 0x3D/* = */ ? 1 : 2); + break; + } + } + } + } + + // quirk for blockquotes, this line should already be checked by that rule + if (state.sCount[nextLine] < 0) { continue; } + + // Some tags can terminate paragraph without empty line. + terminate = false; + for (i = 0, l = terminatorRules.length; i < l; i++) { + if (terminatorRules[i](state, nextLine, endLine, true)) { + terminate = true; + break; + } + } + if (terminate) { break; } + } + + if (!level) { + // Didn't find valid underline + return false; + } + + content = state.getLines(startLine, nextLine, state.blkIndent, false).trim(); + + state.line = nextLine + 1; + + token = state.push('heading_open', 'h' + String(level), 1); + token.markup = String.fromCharCode(marker); + token.map = [ startLine, state.line ]; + + token = state.push('inline', '', 0); + token.content = content; + token.map = [ startLine, state.line - 1 ]; + token.children = []; + + token = state.push('heading_close', 'h' + String(level), -1); + token.markup = String.fromCharCode(marker); + + state.parentType = oldParentType; + + return true; +}; + +},{}],25:[function(require,module,exports){ +// Lists + +'use strict'; + +var isSpace = require('../common/utils').isSpace; + + +// Search `[-+*][\n ]`, returns next pos after marker on success +// or -1 on fail. +function skipBulletListMarker(state, startLine) { + var marker, pos, max, ch; + + pos = state.bMarks[startLine] + state.tShift[startLine]; + max = state.eMarks[startLine]; + + marker = state.src.charCodeAt(pos++); + // Check bullet + if (marker !== 0x2A/* * */ && + marker !== 0x2D/* - */ && + marker !== 0x2B/* + */) { + return -1; + } + + if (pos < max) { + ch = state.src.charCodeAt(pos); + + if (!isSpace(ch)) { + // " -test " - is not a list item + return -1; + } + } + + return pos; +} + +// Search `\d+[.)][\n ]`, returns next pos after marker on success +// or -1 on fail. +function skipOrderedListMarker(state, startLine) { + var ch, + start = state.bMarks[startLine] + state.tShift[startLine], + pos = start, + max = state.eMarks[startLine]; + + // List marker should have at least 2 chars (digit + dot) + if (pos + 1 >= max) { return -1; } + + ch = state.src.charCodeAt(pos++); + + if (ch < 0x30/* 0 */ || ch > 0x39/* 9 */) { return -1; } + + for (;;) { + // EOL -> fail + if (pos >= max) { return -1; } + + ch = state.src.charCodeAt(pos++); + + if (ch >= 0x30/* 0 */ && ch <= 0x39/* 9 */) { + + // List marker should have no more than 9 digits + // (prevents integer overflow in browsers) + if (pos - start >= 10) { return -1; } + + continue; + } + + // found valid marker + if (ch === 0x29/* ) */ || ch === 0x2e/* . */) { + break; + } + + return -1; + } + + + if (pos < max) { + ch = state.src.charCodeAt(pos); + + if (!isSpace(ch)) { + // " 1.test " - is not a list item + return -1; + } + } + return pos; +} + +function markTightParagraphs(state, idx) { + var i, l, + level = state.level + 2; + + for (i = idx + 2, l = state.tokens.length - 2; i < l; i++) { + if (state.tokens[i].level === level && state.tokens[i].type === 'paragraph_open') { + state.tokens[i + 2].hidden = true; + state.tokens[i].hidden = true; + i += 2; + } + } +} + + +module.exports = function list(state, startLine, endLine, silent) { + var ch, + contentStart, + i, + indent, + indentAfterMarker, + initial, + isOrdered, + itemLines, + l, + listLines, + listTokIdx, + markerCharCode, + markerValue, + max, + nextLine, + offset, + oldListIndent, + oldParentType, + oldSCount, + oldTShift, + oldTight, + pos, + posAfterMarker, + prevEmptyEnd, + start, + terminate, + terminatorRules, + token, + isTerminatingParagraph = false, + tight = true; + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + + // Special case: + // - item 1 + // - item 2 + // - item 3 + // - item 4 + // - this one is a paragraph continuation + if (state.listIndent >= 0 && + state.sCount[startLine] - state.listIndent >= 4 && + state.sCount[startLine] < state.blkIndent) { + return false; + } + + // limit conditions when list can interrupt + // a paragraph (validation mode only) + if (silent && state.parentType === 'paragraph') { + // Next list item should still terminate previous list item; + // + // This code can fail if plugins use blkIndent as well as lists, + // but I hope the spec gets fixed long before that happens. + // + if (state.tShift[startLine] >= state.blkIndent) { + isTerminatingParagraph = true; + } + } + + // Detect list type and position after marker + if ((posAfterMarker = skipOrderedListMarker(state, startLine)) >= 0) { + isOrdered = true; + start = state.bMarks[startLine] + state.tShift[startLine]; + markerValue = Number(state.src.substr(start, posAfterMarker - start - 1)); + + // If we're starting a new ordered list right after + // a paragraph, it should start with 1. + if (isTerminatingParagraph && markerValue !== 1) return false; + + } else if ((posAfterMarker = skipBulletListMarker(state, startLine)) >= 0) { + isOrdered = false; + + } else { + return false; + } + + // If we're starting a new unordered list right after + // a paragraph, first line should not be empty. + if (isTerminatingParagraph) { + if (state.skipSpaces(posAfterMarker) >= state.eMarks[startLine]) return false; + } + + // We should terminate list on style change. Remember first one to compare. + markerCharCode = state.src.charCodeAt(posAfterMarker - 1); + + // For validation mode we can terminate immediately + if (silent) { return true; } + + // Start list + listTokIdx = state.tokens.length; + + if (isOrdered) { + token = state.push('ordered_list_open', 'ol', 1); + if (markerValue !== 1) { + token.attrs = [ [ 'start', markerValue ] ]; + } + + } else { + token = state.push('bullet_list_open', 'ul', 1); + } + + token.map = listLines = [ startLine, 0 ]; + token.markup = String.fromCharCode(markerCharCode); + + // + // Iterate list items + // + + nextLine = startLine; + prevEmptyEnd = false; + terminatorRules = state.md.block.ruler.getRules('list'); + + oldParentType = state.parentType; + state.parentType = 'list'; + + while (nextLine < endLine) { + pos = posAfterMarker; + max = state.eMarks[nextLine]; + + initial = offset = state.sCount[nextLine] + posAfterMarker - (state.bMarks[startLine] + state.tShift[startLine]); + + while (pos < max) { + ch = state.src.charCodeAt(pos); + + if (ch === 0x09) { + offset += 4 - (offset + state.bsCount[nextLine]) % 4; + } else if (ch === 0x20) { + offset++; + } else { + break; + } + + pos++; + } + + contentStart = pos; + + if (contentStart >= max) { + // trimming space in "- \n 3" case, indent is 1 here + indentAfterMarker = 1; + } else { + indentAfterMarker = offset - initial; + } + + // If we have more than 4 spaces, the indent is 1 + // (the rest is just indented code block) + if (indentAfterMarker > 4) { indentAfterMarker = 1; } + + // " - test" + // ^^^^^ - calculating total length of this thing + indent = initial + indentAfterMarker; + + // Run subparser & write tokens + token = state.push('list_item_open', 'li', 1); + token.markup = String.fromCharCode(markerCharCode); + token.map = itemLines = [ startLine, 0 ]; + + // change current state, then restore it after parser subcall + oldTight = state.tight; + oldTShift = state.tShift[startLine]; + oldSCount = state.sCount[startLine]; + + // - example list + // ^ listIndent position will be here + // ^ blkIndent position will be here + // + oldListIndent = state.listIndent; + state.listIndent = state.blkIndent; + state.blkIndent = indent; + + state.tight = true; + state.tShift[startLine] = contentStart - state.bMarks[startLine]; + state.sCount[startLine] = offset; + + if (contentStart >= max && state.isEmpty(startLine + 1)) { + // workaround for this case + // (list item is empty, list terminates before "foo"): + // ~~~~~~~~ + // - + // + // foo + // ~~~~~~~~ + state.line = Math.min(state.line + 2, endLine); + } else { + state.md.block.tokenize(state, startLine, endLine, true); + } + + // If any of list item is tight, mark list as tight + if (!state.tight || prevEmptyEnd) { + tight = false; + } + // Item become loose if finish with empty line, + // but we should filter last element, because it means list finish + prevEmptyEnd = (state.line - startLine) > 1 && state.isEmpty(state.line - 1); + + state.blkIndent = state.listIndent; + state.listIndent = oldListIndent; + state.tShift[startLine] = oldTShift; + state.sCount[startLine] = oldSCount; + state.tight = oldTight; + + token = state.push('list_item_close', 'li', -1); + token.markup = String.fromCharCode(markerCharCode); + + nextLine = startLine = state.line; + itemLines[1] = nextLine; + contentStart = state.bMarks[startLine]; + + if (nextLine >= endLine) { break; } + + // + // Try to check if list is terminated or continued. + // + if (state.sCount[nextLine] < state.blkIndent) { break; } + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { break; } + + // fail if terminating block found + terminate = false; + for (i = 0, l = terminatorRules.length; i < l; i++) { + if (terminatorRules[i](state, nextLine, endLine, true)) { + terminate = true; + break; + } + } + if (terminate) { break; } + + // fail if list has another type + if (isOrdered) { + posAfterMarker = skipOrderedListMarker(state, nextLine); + if (posAfterMarker < 0) { break; } + } else { + posAfterMarker = skipBulletListMarker(state, nextLine); + if (posAfterMarker < 0) { break; } + } + + if (markerCharCode !== state.src.charCodeAt(posAfterMarker - 1)) { break; } + } + + // Finalize list + if (isOrdered) { + token = state.push('ordered_list_close', 'ol', -1); + } else { + token = state.push('bullet_list_close', 'ul', -1); + } + token.markup = String.fromCharCode(markerCharCode); + + listLines[1] = nextLine; + state.line = nextLine; + + state.parentType = oldParentType; + + // mark paragraphs tight if needed + if (tight) { + markTightParagraphs(state, listTokIdx); + } + + return true; +}; + +},{"../common/utils":4}],26:[function(require,module,exports){ +// Paragraph + +'use strict'; + + +module.exports = function paragraph(state, startLine/*, endLine*/) { + var content, terminate, i, l, token, oldParentType, + nextLine = startLine + 1, + terminatorRules = state.md.block.ruler.getRules('paragraph'), + endLine = state.lineMax; + + oldParentType = state.parentType; + state.parentType = 'paragraph'; + + // jump line-by-line until empty one or EOF + for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) { + // this would be a code block normally, but after paragraph + // it's considered a lazy continuation regardless of what's there + if (state.sCount[nextLine] - state.blkIndent > 3) { continue; } + + // quirk for blockquotes, this line should already be checked by that rule + if (state.sCount[nextLine] < 0) { continue; } + + // Some tags can terminate paragraph without empty line. + terminate = false; + for (i = 0, l = terminatorRules.length; i < l; i++) { + if (terminatorRules[i](state, nextLine, endLine, true)) { + terminate = true; + break; + } + } + if (terminate) { break; } + } + + content = state.getLines(startLine, nextLine, state.blkIndent, false).trim(); + + state.line = nextLine; + + token = state.push('paragraph_open', 'p', 1); + token.map = [ startLine, state.line ]; + + token = state.push('inline', '', 0); + token.content = content; + token.map = [ startLine, state.line ]; + token.children = []; + + token = state.push('paragraph_close', 'p', -1); + + state.parentType = oldParentType; + + return true; +}; + +},{}],27:[function(require,module,exports){ +'use strict'; + + +var normalizeReference = require('../common/utils').normalizeReference; +var isSpace = require('../common/utils').isSpace; + + +module.exports = function reference(state, startLine, _endLine, silent) { + var ch, + destEndPos, + destEndLineNo, + endLine, + href, + i, + l, + label, + labelEnd, + oldParentType, + res, + start, + str, + terminate, + terminatorRules, + title, + lines = 0, + pos = state.bMarks[startLine] + state.tShift[startLine], + max = state.eMarks[startLine], + nextLine = startLine + 1; + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + + if (state.src.charCodeAt(pos) !== 0x5B/* [ */) { return false; } + + // Simple check to quickly interrupt scan on [link](url) at the start of line. + // Can be useful on practice: https://github.com/markdown-it/markdown-it/issues/54 + while (++pos < max) { + if (state.src.charCodeAt(pos) === 0x5D /* ] */ && + state.src.charCodeAt(pos - 1) !== 0x5C/* \ */) { + if (pos + 1 === max) { return false; } + if (state.src.charCodeAt(pos + 1) !== 0x3A/* : */) { return false; } + break; + } + } + + endLine = state.lineMax; + + // jump line-by-line until empty one or EOF + terminatorRules = state.md.block.ruler.getRules('reference'); + + oldParentType = state.parentType; + state.parentType = 'reference'; + + for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) { + // this would be a code block normally, but after paragraph + // it's considered a lazy continuation regardless of what's there + if (state.sCount[nextLine] - state.blkIndent > 3) { continue; } + + // quirk for blockquotes, this line should already be checked by that rule + if (state.sCount[nextLine] < 0) { continue; } + + // Some tags can terminate paragraph without empty line. + terminate = false; + for (i = 0, l = terminatorRules.length; i < l; i++) { + if (terminatorRules[i](state, nextLine, endLine, true)) { + terminate = true; + break; + } + } + if (terminate) { break; } + } + + str = state.getLines(startLine, nextLine, state.blkIndent, false).trim(); + max = str.length; + + for (pos = 1; pos < max; pos++) { + ch = str.charCodeAt(pos); + if (ch === 0x5B /* [ */) { + return false; + } else if (ch === 0x5D /* ] */) { + labelEnd = pos; + break; + } else if (ch === 0x0A /* \n */) { + lines++; + } else if (ch === 0x5C /* \ */) { + pos++; + if (pos < max && str.charCodeAt(pos) === 0x0A) { + lines++; + } + } + } + + if (labelEnd < 0 || str.charCodeAt(labelEnd + 1) !== 0x3A/* : */) { return false; } + + // [label]: destination 'title' + // ^^^ skip optional whitespace here + for (pos = labelEnd + 2; pos < max; pos++) { + ch = str.charCodeAt(pos); + if (ch === 0x0A) { + lines++; + } else if (isSpace(ch)) { + /*eslint no-empty:0*/ + } else { + break; + } + } + + // [label]: destination 'title' + // ^^^^^^^^^^^ parse this + res = state.md.helpers.parseLinkDestination(str, pos, max); + if (!res.ok) { return false; } + + href = state.md.normalizeLink(res.str); + if (!state.md.validateLink(href)) { return false; } + + pos = res.pos; + lines += res.lines; + + // save cursor state, we could require to rollback later + destEndPos = pos; + destEndLineNo = lines; + + // [label]: destination 'title' + // ^^^ skipping those spaces + start = pos; + for (; pos < max; pos++) { + ch = str.charCodeAt(pos); + if (ch === 0x0A) { + lines++; + } else if (isSpace(ch)) { + /*eslint no-empty:0*/ + } else { + break; + } + } + + // [label]: destination 'title' + // ^^^^^^^ parse this + res = state.md.helpers.parseLinkTitle(str, pos, max); + if (pos < max && start !== pos && res.ok) { + title = res.str; + pos = res.pos; + lines += res.lines; + } else { + title = ''; + pos = destEndPos; + lines = destEndLineNo; + } + + // skip trailing spaces until the rest of the line + while (pos < max) { + ch = str.charCodeAt(pos); + if (!isSpace(ch)) { break; } + pos++; + } + + if (pos < max && str.charCodeAt(pos) !== 0x0A) { + if (title) { + // garbage at the end of the line after title, + // but it could still be a valid reference if we roll back + title = ''; + pos = destEndPos; + lines = destEndLineNo; + while (pos < max) { + ch = str.charCodeAt(pos); + if (!isSpace(ch)) { break; } + pos++; + } + } + } + + if (pos < max && str.charCodeAt(pos) !== 0x0A) { + // garbage at the end of the line + return false; + } + + label = normalizeReference(str.slice(1, labelEnd)); + if (!label) { + // CommonMark 0.20 disallows empty labels + return false; + } + + // Reference can not terminate anything. This check is for safety only. + /*istanbul ignore if*/ + if (silent) { return true; } + + if (typeof state.env.references === 'undefined') { + state.env.references = {}; + } + if (typeof state.env.references[label] === 'undefined') { + state.env.references[label] = { title: title, href: href }; + } + + state.parentType = oldParentType; + + state.line = startLine + lines + 1; + return true; +}; + +},{"../common/utils":4}],28:[function(require,module,exports){ +// Parser state class + +'use strict'; + +var Token = require('../token'); +var isSpace = require('../common/utils').isSpace; + + +function StateBlock(src, md, env, tokens) { + var ch, s, start, pos, len, indent, offset, indent_found; + + this.src = src; + + // link to parser instance + this.md = md; + + this.env = env; + + // + // Internal state vartiables + // + + this.tokens = tokens; + + this.bMarks = []; // line begin offsets for fast jumps + this.eMarks = []; // line end offsets for fast jumps + this.tShift = []; // offsets of the first non-space characters (tabs not expanded) + this.sCount = []; // indents for each line (tabs expanded) + + // An amount of virtual spaces (tabs expanded) between beginning + // of each line (bMarks) and real beginning of that line. + // + // It exists only as a hack because blockquotes override bMarks + // losing information in the process. + // + // It's used only when expanding tabs, you can think about it as + // an initial tab length, e.g. bsCount=21 applied to string `\t123` + // means first tab should be expanded to 4-21%4 === 3 spaces. + // + this.bsCount = []; + + // block parser variables + this.blkIndent = 0; // required block content indent (for example, if we are + // inside a list, it would be positioned after list marker) + this.line = 0; // line index in src + this.lineMax = 0; // lines count + this.tight = false; // loose/tight mode for lists + this.ddIndent = -1; // indent of the current dd block (-1 if there isn't any) + this.listIndent = -1; // indent of the current list block (-1 if there isn't any) + + // can be 'blockquote', 'list', 'root', 'paragraph' or 'reference' + // used in lists to determine if they interrupt a paragraph + this.parentType = 'root'; + + this.level = 0; + + // renderer + this.result = ''; + + // Create caches + // Generate markers. + s = this.src; + indent_found = false; + + for (start = pos = indent = offset = 0, len = s.length; pos < len; pos++) { + ch = s.charCodeAt(pos); + + if (!indent_found) { + if (isSpace(ch)) { + indent++; + + if (ch === 0x09) { + offset += 4 - offset % 4; + } else { + offset++; + } + continue; + } else { + indent_found = true; + } + } + + if (ch === 0x0A || pos === len - 1) { + if (ch !== 0x0A) { pos++; } + this.bMarks.push(start); + this.eMarks.push(pos); + this.tShift.push(indent); + this.sCount.push(offset); + this.bsCount.push(0); + + indent_found = false; + indent = 0; + offset = 0; + start = pos + 1; + } + } + + // Push fake entry to simplify cache bounds checks + this.bMarks.push(s.length); + this.eMarks.push(s.length); + this.tShift.push(0); + this.sCount.push(0); + this.bsCount.push(0); + + this.lineMax = this.bMarks.length - 1; // don't count last fake line +} + +// Push new token to "stream". +// +StateBlock.prototype.push = function (type, tag, nesting) { + var token = new Token(type, tag, nesting); + token.block = true; + + if (nesting < 0) this.level--; // closing tag + token.level = this.level; + if (nesting > 0) this.level++; // opening tag + + this.tokens.push(token); + return token; +}; + +StateBlock.prototype.isEmpty = function isEmpty(line) { + return this.bMarks[line] + this.tShift[line] >= this.eMarks[line]; +}; + +StateBlock.prototype.skipEmptyLines = function skipEmptyLines(from) { + for (var max = this.lineMax; from < max; from++) { + if (this.bMarks[from] + this.tShift[from] < this.eMarks[from]) { + break; + } + } + return from; +}; + +// Skip spaces from given position. +StateBlock.prototype.skipSpaces = function skipSpaces(pos) { + var ch; + + for (var max = this.src.length; pos < max; pos++) { + ch = this.src.charCodeAt(pos); + if (!isSpace(ch)) { break; } + } + return pos; +}; + +// Skip spaces from given position in reverse. +StateBlock.prototype.skipSpacesBack = function skipSpacesBack(pos, min) { + if (pos <= min) { return pos; } + + while (pos > min) { + if (!isSpace(this.src.charCodeAt(--pos))) { return pos + 1; } + } + return pos; +}; + +// Skip char codes from given position +StateBlock.prototype.skipChars = function skipChars(pos, code) { + for (var max = this.src.length; pos < max; pos++) { + if (this.src.charCodeAt(pos) !== code) { break; } + } + return pos; +}; + +// Skip char codes reverse from given position - 1 +StateBlock.prototype.skipCharsBack = function skipCharsBack(pos, code, min) { + if (pos <= min) { return pos; } + + while (pos > min) { + if (code !== this.src.charCodeAt(--pos)) { return pos + 1; } + } + return pos; +}; + +// cut lines range from source. +StateBlock.prototype.getLines = function getLines(begin, end, indent, keepLastLF) { + var i, lineIndent, ch, first, last, queue, lineStart, + line = begin; + + if (begin >= end) { + return ''; + } + + queue = new Array(end - begin); + + for (i = 0; line < end; line++, i++) { + lineIndent = 0; + lineStart = first = this.bMarks[line]; + + if (line + 1 < end || keepLastLF) { + // No need for bounds check because we have fake entry on tail. + last = this.eMarks[line] + 1; + } else { + last = this.eMarks[line]; + } + + while (first < last && lineIndent < indent) { + ch = this.src.charCodeAt(first); + + if (isSpace(ch)) { + if (ch === 0x09) { + lineIndent += 4 - (lineIndent + this.bsCount[line]) % 4; + } else { + lineIndent++; + } + } else if (first - lineStart < this.tShift[line]) { + // patched tShift masked characters to look like spaces (blockquotes, list markers) + lineIndent++; + } else { + break; + } + + first++; + } + + if (lineIndent > indent) { + // partially expanding tabs in code blocks, e.g '\t\tfoobar' + // with indent=2 becomes ' \tfoobar' + queue[i] = new Array(lineIndent - indent + 1).join(' ') + this.src.slice(first, last); + } else { + queue[i] = this.src.slice(first, last); + } + } + + return queue.join(''); +}; + +// re-export Token class to use in block rules +StateBlock.prototype.Token = Token; + + +module.exports = StateBlock; + +},{"../common/utils":4,"../token":51}],29:[function(require,module,exports){ +// GFM table, non-standard + +'use strict'; + +var isSpace = require('../common/utils').isSpace; + + +function getLine(state, line) { + var pos = state.bMarks[line] + state.blkIndent, + max = state.eMarks[line]; + + return state.src.substr(pos, max - pos); +} + +function escapedSplit(str) { + var result = [], + pos = 0, + max = str.length, + ch, + escapes = 0, + lastPos = 0, + backTicked = false, + lastBackTick = 0; + + ch = str.charCodeAt(pos); + + while (pos < max) { + if (ch === 0x60/* ` */) { + if (backTicked) { + // make \` close code sequence, but not open it; + // the reason is: `\` is correct code block + backTicked = false; + lastBackTick = pos; + } else if (escapes % 2 === 0) { + backTicked = true; + lastBackTick = pos; + } + } else if (ch === 0x7c/* | */ && (escapes % 2 === 0) && !backTicked) { + result.push(str.substring(lastPos, pos)); + lastPos = pos + 1; + } + + if (ch === 0x5c/* \ */) { + escapes++; + } else { + escapes = 0; + } + + pos++; + + // If there was an un-closed backtick, go back to just after + // the last backtick, but as if it was a normal character + if (pos === max && backTicked) { + backTicked = false; + pos = lastBackTick + 1; + } + + ch = str.charCodeAt(pos); + } + + result.push(str.substring(lastPos)); + + return result; +} + + +module.exports = function table(state, startLine, endLine, silent) { + var ch, lineText, pos, i, nextLine, columns, columnCount, token, + aligns, t, tableLines, tbodyLines; + + // should have at least two lines + if (startLine + 2 > endLine) { return false; } + + nextLine = startLine + 1; + + if (state.sCount[nextLine] < state.blkIndent) { return false; } + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[nextLine] - state.blkIndent >= 4) { return false; } + + // first character of the second line should be '|', '-', ':', + // and no other characters are allowed but spaces; + // basically, this is the equivalent of /^[-:|][-:|\s]*$/ regexp + + pos = state.bMarks[nextLine] + state.tShift[nextLine]; + if (pos >= state.eMarks[nextLine]) { return false; } + + ch = state.src.charCodeAt(pos++); + if (ch !== 0x7C/* | */ && ch !== 0x2D/* - */ && ch !== 0x3A/* : */) { return false; } + + while (pos < state.eMarks[nextLine]) { + ch = state.src.charCodeAt(pos); + + if (ch !== 0x7C/* | */ && ch !== 0x2D/* - */ && ch !== 0x3A/* : */ && !isSpace(ch)) { return false; } + + pos++; + } + + lineText = getLine(state, startLine + 1); + + columns = lineText.split('|'); + aligns = []; + for (i = 0; i < columns.length; i++) { + t = columns[i].trim(); + if (!t) { + // allow empty columns before and after table, but not in between columns; + // e.g. allow ` |---| `, disallow ` ---||--- ` + if (i === 0 || i === columns.length - 1) { + continue; + } else { + return false; + } + } + + if (!/^:?-+:?$/.test(t)) { return false; } + if (t.charCodeAt(t.length - 1) === 0x3A/* : */) { + aligns.push(t.charCodeAt(0) === 0x3A/* : */ ? 'center' : 'right'); + } else if (t.charCodeAt(0) === 0x3A/* : */) { + aligns.push('left'); + } else { + aligns.push(''); + } + } + + lineText = getLine(state, startLine).trim(); + if (lineText.indexOf('|') === -1) { return false; } + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + columns = escapedSplit(lineText.replace(/^\||\|$/g, '')); + + // header row will define an amount of columns in the entire table, + // and align row shouldn't be smaller than that (the rest of the rows can) + columnCount = columns.length; + if (columnCount > aligns.length) { return false; } + + if (silent) { return true; } + + token = state.push('table_open', 'table', 1); + token.map = tableLines = [ startLine, 0 ]; + + token = state.push('thead_open', 'thead', 1); + token.map = [ startLine, startLine + 1 ]; + + token = state.push('tr_open', 'tr', 1); + token.map = [ startLine, startLine + 1 ]; + + for (i = 0; i < columns.length; i++) { + token = state.push('th_open', 'th', 1); + token.map = [ startLine, startLine + 1 ]; + if (aligns[i]) { + token.attrs = [ [ 'style', 'text-align:' + aligns[i] ] ]; + } + + token = state.push('inline', '', 0); + token.content = columns[i].trim(); + token.map = [ startLine, startLine + 1 ]; + token.children = []; + + token = state.push('th_close', 'th', -1); + } + + token = state.push('tr_close', 'tr', -1); + token = state.push('thead_close', 'thead', -1); + + token = state.push('tbody_open', 'tbody', 1); + token.map = tbodyLines = [ startLine + 2, 0 ]; + + for (nextLine = startLine + 2; nextLine < endLine; nextLine++) { + if (state.sCount[nextLine] < state.blkIndent) { break; } + + lineText = getLine(state, nextLine).trim(); + if (lineText.indexOf('|') === -1) { break; } + if (state.sCount[nextLine] - state.blkIndent >= 4) { break; } + columns = escapedSplit(lineText.replace(/^\||\|$/g, '')); + + token = state.push('tr_open', 'tr', 1); + for (i = 0; i < columnCount; i++) { + token = state.push('td_open', 'td', 1); + if (aligns[i]) { + token.attrs = [ [ 'style', 'text-align:' + aligns[i] ] ]; + } + + token = state.push('inline', '', 0); + token.content = columns[i] ? columns[i].trim() : ''; + token.children = []; + + token = state.push('td_close', 'td', -1); + } + token = state.push('tr_close', 'tr', -1); + } + token = state.push('tbody_close', 'tbody', -1); + token = state.push('table_close', 'table', -1); + + tableLines[1] = tbodyLines[1] = nextLine; + state.line = nextLine; + return true; +}; + +},{"../common/utils":4}],30:[function(require,module,exports){ +'use strict'; + + +module.exports = function block(state) { + var token; + + if (state.inlineMode) { + token = new state.Token('inline', '', 0); + token.content = state.src; + token.map = [ 0, 1 ]; + token.children = []; + state.tokens.push(token); + } else { + state.md.block.parse(state.src, state.md, state.env, state.tokens); + } +}; + +},{}],31:[function(require,module,exports){ +'use strict'; + +module.exports = function inline(state) { + var tokens = state.tokens, tok, i, l; + + // Parse inlines + for (i = 0, l = tokens.length; i < l; i++) { + tok = tokens[i]; + if (tok.type === 'inline') { + state.md.inline.parse(tok.content, state.md, state.env, tok.children); + } + } +}; + +},{}],32:[function(require,module,exports){ +// Replace link-like texts with link nodes. +// +// Currently restricted by `md.validateLink()` to http/https/ftp +// +'use strict'; + + +var arrayReplaceAt = require('../common/utils').arrayReplaceAt; + + +function isLinkOpen(str) { + return /^\s]/i.test(str); +} +function isLinkClose(str) { + return /^<\/a\s*>/i.test(str); +} + + +module.exports = function linkify(state) { + var i, j, l, tokens, token, currentToken, nodes, ln, text, pos, lastPos, + level, htmlLinkLevel, url, fullUrl, urlText, + blockTokens = state.tokens, + links; + + if (!state.md.options.linkify) { return; } + + for (j = 0, l = blockTokens.length; j < l; j++) { + if (blockTokens[j].type !== 'inline' || + !state.md.linkify.pretest(blockTokens[j].content)) { + continue; + } + + tokens = blockTokens[j].children; + + htmlLinkLevel = 0; + + // We scan from the end, to keep position when new tags added. + // Use reversed logic in links start/end match + for (i = tokens.length - 1; i >= 0; i--) { + currentToken = tokens[i]; + + // Skip content of markdown links + if (currentToken.type === 'link_close') { + i--; + while (tokens[i].level !== currentToken.level && tokens[i].type !== 'link_open') { + i--; + } + continue; + } + + // Skip content of html tag links + if (currentToken.type === 'html_inline') { + if (isLinkOpen(currentToken.content) && htmlLinkLevel > 0) { + htmlLinkLevel--; + } + if (isLinkClose(currentToken.content)) { + htmlLinkLevel++; + } + } + if (htmlLinkLevel > 0) { continue; } + + if (currentToken.type === 'text' && state.md.linkify.test(currentToken.content)) { + + text = currentToken.content; + links = state.md.linkify.match(text); + + // Now split string to nodes + nodes = []; + level = currentToken.level; + lastPos = 0; + + for (ln = 0; ln < links.length; ln++) { + + url = links[ln].url; + fullUrl = state.md.normalizeLink(url); + if (!state.md.validateLink(fullUrl)) { continue; } + + urlText = links[ln].text; + + // Linkifier might send raw hostnames like "example.com", where url + // starts with domain name. So we prepend http:// in those cases, + // and remove it afterwards. + // + if (!links[ln].schema) { + urlText = state.md.normalizeLinkText('http://' + urlText).replace(/^http:\/\//, ''); + } else if (links[ln].schema === 'mailto:' && !/^mailto:/i.test(urlText)) { + urlText = state.md.normalizeLinkText('mailto:' + urlText).replace(/^mailto:/, ''); + } else { + urlText = state.md.normalizeLinkText(urlText); + } + + pos = links[ln].index; + + if (pos > lastPos) { + token = new state.Token('text', '', 0); + token.content = text.slice(lastPos, pos); + token.level = level; + nodes.push(token); + } + + token = new state.Token('link_open', 'a', 1); + token.attrs = [ [ 'href', fullUrl ] ]; + token.level = level++; + token.markup = 'linkify'; + token.info = 'auto'; + nodes.push(token); + + token = new state.Token('text', '', 0); + token.content = urlText; + token.level = level; + nodes.push(token); + + token = new state.Token('link_close', 'a', -1); + token.level = --level; + token.markup = 'linkify'; + token.info = 'auto'; + nodes.push(token); + + lastPos = links[ln].lastIndex; + } + if (lastPos < text.length) { + token = new state.Token('text', '', 0); + token.content = text.slice(lastPos); + token.level = level; + nodes.push(token); + } + + // replace current node + blockTokens[j].children = tokens = arrayReplaceAt(tokens, i, nodes); + } + } + } +}; + +},{"../common/utils":4}],33:[function(require,module,exports){ +// Normalize input string + +'use strict'; + + +// https://spec.commonmark.org/0.29/#line-ending +var NEWLINES_RE = /\r\n?|\n/g; +var NULL_RE = /\0/g; + + +module.exports = function normalize(state) { + var str; + + // Normalize newlines + str = state.src.replace(NEWLINES_RE, '\n'); + + // Replace NULL characters + str = str.replace(NULL_RE, '\uFFFD'); + + state.src = str; +}; + +},{}],34:[function(require,module,exports){ +// Simple typographic replacements +// +// (c) (C) → © +// (tm) (TM) → ™ +// (r) (R) → ® +// +- → ± +// (p) (P) -> § +// ... → … (also ?.... → ?.., !.... → !..) +// ???????? → ???, !!!!! → !!!, `,,` → `,` +// -- → –, --- → — +// +'use strict'; + +// TODO: +// - fractionals 1/2, 1/4, 3/4 -> ½, ¼, ¾ +// - miltiplication 2 x 4 -> 2 × 4 + +var RARE_RE = /\+-|\.\.|\?\?\?\?|!!!!|,,|--/; + +// Workaround for phantomjs - need regex without /g flag, +// or root check will fail every second time +var SCOPED_ABBR_TEST_RE = /\((c|tm|r|p)\)/i; + +var SCOPED_ABBR_RE = /\((c|tm|r|p)\)/ig; +var SCOPED_ABBR = { + c: '©', + r: '®', + p: '§', + tm: '™' +}; + +function replaceFn(match, name) { + return SCOPED_ABBR[name.toLowerCase()]; +} + +function replace_scoped(inlineTokens) { + var i, token, inside_autolink = 0; + + for (i = inlineTokens.length - 1; i >= 0; i--) { + token = inlineTokens[i]; + + if (token.type === 'text' && !inside_autolink) { + token.content = token.content.replace(SCOPED_ABBR_RE, replaceFn); + } + + if (token.type === 'link_open' && token.info === 'auto') { + inside_autolink--; + } + + if (token.type === 'link_close' && token.info === 'auto') { + inside_autolink++; + } + } +} + +function replace_rare(inlineTokens) { + var i, token, inside_autolink = 0; + + for (i = inlineTokens.length - 1; i >= 0; i--) { + token = inlineTokens[i]; + + if (token.type === 'text' && !inside_autolink) { + if (RARE_RE.test(token.content)) { + token.content = token.content + .replace(/\+-/g, '±') + // .., ..., ....... -> … + // but ?..... & !..... -> ?.. & !.. + .replace(/\.{2,}/g, '…').replace(/([?!])…/g, '$1..') + .replace(/([?!]){4,}/g, '$1$1$1').replace(/,{2,}/g, ',') + // em-dash + .replace(/(^|[^-])---([^-]|$)/mg, '$1\u2014$2') + // en-dash + .replace(/(^|\s)--(\s|$)/mg, '$1\u2013$2') + .replace(/(^|[^-\s])--([^-\s]|$)/mg, '$1\u2013$2'); + } + } + + if (token.type === 'link_open' && token.info === 'auto') { + inside_autolink--; + } + + if (token.type === 'link_close' && token.info === 'auto') { + inside_autolink++; + } + } +} + + +module.exports = function replace(state) { + var blkIdx; + + if (!state.md.options.typographer) { return; } + + for (blkIdx = state.tokens.length - 1; blkIdx >= 0; blkIdx--) { + + if (state.tokens[blkIdx].type !== 'inline') { continue; } + + if (SCOPED_ABBR_TEST_RE.test(state.tokens[blkIdx].content)) { + replace_scoped(state.tokens[blkIdx].children); + } + + if (RARE_RE.test(state.tokens[blkIdx].content)) { + replace_rare(state.tokens[blkIdx].children); + } + + } +}; + +},{}],35:[function(require,module,exports){ +// Convert straight quotation marks to typographic ones +// +'use strict'; + + +var isWhiteSpace = require('../common/utils').isWhiteSpace; +var isPunctChar = require('../common/utils').isPunctChar; +var isMdAsciiPunct = require('../common/utils').isMdAsciiPunct; + +var QUOTE_TEST_RE = /['"]/; +var QUOTE_RE = /['"]/g; +var APOSTROPHE = '\u2019'; /* ’ */ + + +function replaceAt(str, index, ch) { + return str.substr(0, index) + ch + str.substr(index + 1); +} + +function process_inlines(tokens, state) { + var i, token, text, t, pos, max, thisLevel, item, lastChar, nextChar, + isLastPunctChar, isNextPunctChar, isLastWhiteSpace, isNextWhiteSpace, + canOpen, canClose, j, isSingle, stack, openQuote, closeQuote; + + stack = []; + + for (i = 0; i < tokens.length; i++) { + token = tokens[i]; + + thisLevel = tokens[i].level; + + for (j = stack.length - 1; j >= 0; j--) { + if (stack[j].level <= thisLevel) { break; } + } + stack.length = j + 1; + + if (token.type !== 'text') { continue; } + + text = token.content; + pos = 0; + max = text.length; + + /*eslint no-labels:0,block-scoped-var:0*/ + OUTER: + while (pos < max) { + QUOTE_RE.lastIndex = pos; + t = QUOTE_RE.exec(text); + if (!t) { break; } + + canOpen = canClose = true; + pos = t.index + 1; + isSingle = (t[0] === "'"); + + // Find previous character, + // default to space if it's the beginning of the line + // + lastChar = 0x20; + + if (t.index - 1 >= 0) { + lastChar = text.charCodeAt(t.index - 1); + } else { + for (j = i - 1; j >= 0; j--) { + if (tokens[j].type === 'softbreak' || tokens[j].type === 'hardbreak') break; // lastChar defaults to 0x20 + if (tokens[j].type !== 'text') continue; + + lastChar = tokens[j].content.charCodeAt(tokens[j].content.length - 1); + break; + } + } + + // Find next character, + // default to space if it's the end of the line + // + nextChar = 0x20; + + if (pos < max) { + nextChar = text.charCodeAt(pos); + } else { + for (j = i + 1; j < tokens.length; j++) { + if (tokens[j].type === 'softbreak' || tokens[j].type === 'hardbreak') break; // nextChar defaults to 0x20 + if (tokens[j].type !== 'text') continue; + + nextChar = tokens[j].content.charCodeAt(0); + break; + } + } + + isLastPunctChar = isMdAsciiPunct(lastChar) || isPunctChar(String.fromCharCode(lastChar)); + isNextPunctChar = isMdAsciiPunct(nextChar) || isPunctChar(String.fromCharCode(nextChar)); + + isLastWhiteSpace = isWhiteSpace(lastChar); + isNextWhiteSpace = isWhiteSpace(nextChar); + + if (isNextWhiteSpace) { + canOpen = false; + } else if (isNextPunctChar) { + if (!(isLastWhiteSpace || isLastPunctChar)) { + canOpen = false; + } + } + + if (isLastWhiteSpace) { + canClose = false; + } else if (isLastPunctChar) { + if (!(isNextWhiteSpace || isNextPunctChar)) { + canClose = false; + } + } + + if (nextChar === 0x22 /* " */ && t[0] === '"') { + if (lastChar >= 0x30 /* 0 */ && lastChar <= 0x39 /* 9 */) { + // special case: 1"" - count first quote as an inch + canClose = canOpen = false; + } + } + + if (canOpen && canClose) { + // treat this as the middle of the word + canOpen = false; + canClose = isNextPunctChar; + } + + if (!canOpen && !canClose) { + // middle of word + if (isSingle) { + token.content = replaceAt(token.content, t.index, APOSTROPHE); + } + continue; + } + + if (canClose) { + // this could be a closing quote, rewind the stack to get a match + for (j = stack.length - 1; j >= 0; j--) { + item = stack[j]; + if (stack[j].level < thisLevel) { break; } + if (item.single === isSingle && stack[j].level === thisLevel) { + item = stack[j]; + + if (isSingle) { + openQuote = state.md.options.quotes[2]; + closeQuote = state.md.options.quotes[3]; + } else { + openQuote = state.md.options.quotes[0]; + closeQuote = state.md.options.quotes[1]; + } + + // replace token.content *before* tokens[item.token].content, + // because, if they are pointing at the same token, replaceAt + // could mess up indices when quote length != 1 + token.content = replaceAt(token.content, t.index, closeQuote); + tokens[item.token].content = replaceAt( + tokens[item.token].content, item.pos, openQuote); + + pos += closeQuote.length - 1; + if (item.token === i) { pos += openQuote.length - 1; } + + text = token.content; + max = text.length; + + stack.length = j; + continue OUTER; + } + } + } + + if (canOpen) { + stack.push({ + token: i, + pos: t.index, + single: isSingle, + level: thisLevel + }); + } else if (canClose && isSingle) { + token.content = replaceAt(token.content, t.index, APOSTROPHE); + } + } + } +} + + +module.exports = function smartquotes(state) { + /*eslint max-depth:0*/ + var blkIdx; + + if (!state.md.options.typographer) { return; } + + for (blkIdx = state.tokens.length - 1; blkIdx >= 0; blkIdx--) { + + if (state.tokens[blkIdx].type !== 'inline' || + !QUOTE_TEST_RE.test(state.tokens[blkIdx].content)) { + continue; + } + + process_inlines(state.tokens[blkIdx].children, state); + } +}; + +},{"../common/utils":4}],36:[function(require,module,exports){ +// Core state object +// +'use strict'; + +var Token = require('../token'); + + +function StateCore(src, md, env) { + this.src = src; + this.env = env; + this.tokens = []; + this.inlineMode = false; + this.md = md; // link to parser instance +} + +// re-export Token class to use in core rules +StateCore.prototype.Token = Token; + + +module.exports = StateCore; + +},{"../token":51}],37:[function(require,module,exports){ +// Process autolinks '' + +'use strict'; + + +/*eslint max-len:0*/ +var EMAIL_RE = /^<([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])?)*)>/; +var AUTOLINK_RE = /^<([a-zA-Z][a-zA-Z0-9+.\-]{1,31}):([^<>\x00-\x20]*)>/; + + +module.exports = function autolink(state, silent) { + var tail, linkMatch, emailMatch, url, fullUrl, token, + pos = state.pos; + + if (state.src.charCodeAt(pos) !== 0x3C/* < */) { return false; } + + tail = state.src.slice(pos); + + if (tail.indexOf('>') < 0) { return false; } + + if (AUTOLINK_RE.test(tail)) { + linkMatch = tail.match(AUTOLINK_RE); + + url = linkMatch[0].slice(1, -1); + fullUrl = state.md.normalizeLink(url); + if (!state.md.validateLink(fullUrl)) { return false; } + + if (!silent) { + token = state.push('link_open', 'a', 1); + token.attrs = [ [ 'href', fullUrl ] ]; + token.markup = 'autolink'; + token.info = 'auto'; + + token = state.push('text', '', 0); + token.content = state.md.normalizeLinkText(url); + + token = state.push('link_close', 'a', -1); + token.markup = 'autolink'; + token.info = 'auto'; + } + + state.pos += linkMatch[0].length; + return true; + } + + if (EMAIL_RE.test(tail)) { + emailMatch = tail.match(EMAIL_RE); + + url = emailMatch[0].slice(1, -1); + fullUrl = state.md.normalizeLink('mailto:' + url); + if (!state.md.validateLink(fullUrl)) { return false; } + + if (!silent) { + token = state.push('link_open', 'a', 1); + token.attrs = [ [ 'href', fullUrl ] ]; + token.markup = 'autolink'; + token.info = 'auto'; + + token = state.push('text', '', 0); + token.content = state.md.normalizeLinkText(url); + + token = state.push('link_close', 'a', -1); + token.markup = 'autolink'; + token.info = 'auto'; + } + + state.pos += emailMatch[0].length; + return true; + } + + return false; +}; + +},{}],38:[function(require,module,exports){ +// Parse backticks + +'use strict'; + +module.exports = function backtick(state, silent) { + var start, max, marker, matchStart, matchEnd, token, + pos = state.pos, + ch = state.src.charCodeAt(pos); + + if (ch !== 0x60/* ` */) { return false; } + + start = pos; + pos++; + max = state.posMax; + + while (pos < max && state.src.charCodeAt(pos) === 0x60/* ` */) { pos++; } + + marker = state.src.slice(start, pos); + + matchStart = matchEnd = pos; + + while ((matchStart = state.src.indexOf('`', matchEnd)) !== -1) { + matchEnd = matchStart + 1; + + while (matchEnd < max && state.src.charCodeAt(matchEnd) === 0x60/* ` */) { matchEnd++; } + + if (matchEnd - matchStart === marker.length) { + if (!silent) { + token = state.push('code_inline', 'code', 0); + token.markup = marker; + token.content = state.src.slice(pos, matchStart) + .replace(/\n/g, ' ') + .replace(/^ (.+) $/, '$1'); + } + state.pos = matchEnd; + return true; + } + } + + if (!silent) { state.pending += marker; } + state.pos += marker.length; + return true; +}; + +},{}],39:[function(require,module,exports){ +// For each opening emphasis-like marker find a matching closing one +// +'use strict'; + + +function processDelimiters(state, delimiters) { + var closerIdx, openerIdx, closer, opener, minOpenerIdx, newMinOpenerIdx, + isOddMatch, lastJump, + openersBottom = {}, + max = delimiters.length; + + for (closerIdx = 0; closerIdx < max; closerIdx++) { + closer = delimiters[closerIdx]; + + // Length is only used for emphasis-specific "rule of 3", + // if it's not defined (in strikethrough or 3rd party plugins), + // we can default it to 0 to disable those checks. + // + closer.length = closer.length || 0; + + if (!closer.close) continue; + + // Previously calculated lower bounds (previous fails) + // for each marker and each delimiter length modulo 3. + if (!openersBottom.hasOwnProperty(closer.marker)) { + openersBottom[closer.marker] = [ -1, -1, -1 ]; + } + + minOpenerIdx = openersBottom[closer.marker][closer.length % 3]; + newMinOpenerIdx = -1; + + openerIdx = closerIdx - closer.jump - 1; + + for (; openerIdx > minOpenerIdx; openerIdx -= opener.jump + 1) { + opener = delimiters[openerIdx]; + + if (opener.marker !== closer.marker) continue; + + if (newMinOpenerIdx === -1) newMinOpenerIdx = openerIdx; + + if (opener.open && + opener.end < 0 && + opener.level === closer.level) { + + isOddMatch = false; + + // from spec: + // + // If one of the delimiters can both open and close emphasis, then the + // sum of the lengths of the delimiter runs containing the opening and + // closing delimiters must not be a multiple of 3 unless both lengths + // are multiples of 3. + // + if (opener.close || closer.open) { + if ((opener.length + closer.length) % 3 === 0) { + if (opener.length % 3 !== 0 || closer.length % 3 !== 0) { + isOddMatch = true; + } + } + } + + if (!isOddMatch) { + // If previous delimiter cannot be an opener, we can safely skip + // the entire sequence in future checks. This is required to make + // sure algorithm has linear complexity (see *_*_*_*_*_... case). + // + lastJump = openerIdx > 0 && !delimiters[openerIdx - 1].open ? + delimiters[openerIdx - 1].jump + 1 : + 0; + + closer.jump = closerIdx - openerIdx + lastJump; + closer.open = false; + opener.end = closerIdx; + opener.jump = lastJump; + opener.close = false; + newMinOpenerIdx = -1; + break; + } + } + } + + if (newMinOpenerIdx !== -1) { + // If match for this delimiter run failed, we want to set lower bound for + // future lookups. This is required to make sure algorithm has linear + // complexity. + // + // See details here: + // https://github.com/commonmark/cmark/issues/178#issuecomment-270417442 + // + openersBottom[closer.marker][(closer.length || 0) % 3] = newMinOpenerIdx; + } + } +} + + +module.exports = function link_pairs(state) { + var curr, + tokens_meta = state.tokens_meta, + max = state.tokens_meta.length; + + processDelimiters(state, state.delimiters); + + for (curr = 0; curr < max; curr++) { + if (tokens_meta[curr] && tokens_meta[curr].delimiters) { + processDelimiters(state, tokens_meta[curr].delimiters); + } + } +}; + +},{}],40:[function(require,module,exports){ +// Process *this* and _that_ +// +'use strict'; + + +// Insert each marker as a separate text token, and add it to delimiter list +// +module.exports.tokenize = function emphasis(state, silent) { + var i, scanned, token, + start = state.pos, + marker = state.src.charCodeAt(start); + + if (silent) { return false; } + + if (marker !== 0x5F /* _ */ && marker !== 0x2A /* * */) { return false; } + + scanned = state.scanDelims(state.pos, marker === 0x2A); + + for (i = 0; i < scanned.length; i++) { + token = state.push('text', '', 0); + token.content = String.fromCharCode(marker); + + state.delimiters.push({ + // Char code of the starting marker (number). + // + marker: marker, + + // Total length of these series of delimiters. + // + length: scanned.length, + + // An amount of characters before this one that's equivalent to + // current one. In plain English: if this delimiter does not open + // an emphasis, neither do previous `jump` characters. + // + // Used to skip sequences like "*****" in one step, for 1st asterisk + // value will be 0, for 2nd it's 1 and so on. + // + jump: i, + + // A position of the token this delimiter corresponds to. + // + token: state.tokens.length - 1, + + // If this delimiter is matched as a valid opener, `end` will be + // equal to its position, otherwise it's `-1`. + // + end: -1, + + // Boolean flags that determine if this delimiter could open or close + // an emphasis. + // + open: scanned.can_open, + close: scanned.can_close + }); + } + + state.pos += scanned.length; + + return true; +}; + + +function postProcess(state, delimiters) { + var i, + startDelim, + endDelim, + token, + ch, + isStrong, + max = delimiters.length; + + for (i = max - 1; i >= 0; i--) { + startDelim = delimiters[i]; + + if (startDelim.marker !== 0x5F/* _ */ && startDelim.marker !== 0x2A/* * */) { + continue; + } + + // Process only opening markers + if (startDelim.end === -1) { + continue; + } + + endDelim = delimiters[startDelim.end]; + + // If the previous delimiter has the same marker and is adjacent to this one, + // merge those into one strong delimiter. + // + // `whatever` -> `whatever` + // + isStrong = i > 0 && + delimiters[i - 1].end === startDelim.end + 1 && + delimiters[i - 1].token === startDelim.token - 1 && + delimiters[startDelim.end + 1].token === endDelim.token + 1 && + delimiters[i - 1].marker === startDelim.marker; + + ch = String.fromCharCode(startDelim.marker); + + token = state.tokens[startDelim.token]; + token.type = isStrong ? 'strong_open' : 'em_open'; + token.tag = isStrong ? 'strong' : 'em'; + token.nesting = 1; + token.markup = isStrong ? ch + ch : ch; + token.content = ''; + + token = state.tokens[endDelim.token]; + token.type = isStrong ? 'strong_close' : 'em_close'; + token.tag = isStrong ? 'strong' : 'em'; + token.nesting = -1; + token.markup = isStrong ? ch + ch : ch; + token.content = ''; + + if (isStrong) { + state.tokens[delimiters[i - 1].token].content = ''; + state.tokens[delimiters[startDelim.end + 1].token].content = ''; + i--; + } + } +} + + +// Walk through delimiter list and replace text tokens with tags +// +module.exports.postProcess = function emphasis(state) { + var curr, + tokens_meta = state.tokens_meta, + max = state.tokens_meta.length; + + postProcess(state, state.delimiters); + + for (curr = 0; curr < max; curr++) { + if (tokens_meta[curr] && tokens_meta[curr].delimiters) { + postProcess(state, tokens_meta[curr].delimiters); + } + } +}; + +},{}],41:[function(require,module,exports){ +// Process html entity - {, ¯, ", ... + +'use strict'; + +var entities = require('../common/entities'); +var has = require('../common/utils').has; +var isValidEntityCode = require('../common/utils').isValidEntityCode; +var fromCodePoint = require('../common/utils').fromCodePoint; + + +var DIGITAL_RE = /^&#((?:x[a-f0-9]{1,6}|[0-9]{1,7}));/i; +var NAMED_RE = /^&([a-z][a-z0-9]{1,31});/i; + + +module.exports = function entity(state, silent) { + var ch, code, match, pos = state.pos, max = state.posMax; + + if (state.src.charCodeAt(pos) !== 0x26/* & */) { return false; } + + if (pos + 1 < max) { + ch = state.src.charCodeAt(pos + 1); + + if (ch === 0x23 /* # */) { + match = state.src.slice(pos).match(DIGITAL_RE); + if (match) { + if (!silent) { + code = match[1][0].toLowerCase() === 'x' ? parseInt(match[1].slice(1), 16) : parseInt(match[1], 10); + state.pending += isValidEntityCode(code) ? fromCodePoint(code) : fromCodePoint(0xFFFD); + } + state.pos += match[0].length; + return true; + } + } else { + match = state.src.slice(pos).match(NAMED_RE); + if (match) { + if (has(entities, match[1])) { + if (!silent) { state.pending += entities[match[1]]; } + state.pos += match[0].length; + return true; + } + } + } + } + + if (!silent) { state.pending += '&'; } + state.pos++; + return true; +}; + +},{"../common/entities":1,"../common/utils":4}],42:[function(require,module,exports){ +// Process escaped chars and hardbreaks + +'use strict'; + +var isSpace = require('../common/utils').isSpace; + +var ESCAPED = []; + +for (var i = 0; i < 256; i++) { ESCAPED.push(0); } + +'\\!"#$%&\'()*+,./:;<=>?@[]^_`{|}~-' + .split('').forEach(function (ch) { ESCAPED[ch.charCodeAt(0)] = 1; }); + + +module.exports = function escape(state, silent) { + var ch, pos = state.pos, max = state.posMax; + + if (state.src.charCodeAt(pos) !== 0x5C/* \ */) { return false; } + + pos++; + + if (pos < max) { + ch = state.src.charCodeAt(pos); + + if (ch < 256 && ESCAPED[ch] !== 0) { + if (!silent) { state.pending += state.src[pos]; } + state.pos += 2; + return true; + } + + if (ch === 0x0A) { + if (!silent) { + state.push('hardbreak', 'br', 0); + } + + pos++; + // skip leading whitespaces from next line + while (pos < max) { + ch = state.src.charCodeAt(pos); + if (!isSpace(ch)) { break; } + pos++; + } + + state.pos = pos; + return true; + } + } + + if (!silent) { state.pending += '\\'; } + state.pos++; + return true; +}; + +},{"../common/utils":4}],43:[function(require,module,exports){ +// Process html tags + +'use strict'; + + +var HTML_TAG_RE = require('../common/html_re').HTML_TAG_RE; + + +function isLetter(ch) { + /*eslint no-bitwise:0*/ + var lc = ch | 0x20; // to lower case + return (lc >= 0x61/* a */) && (lc <= 0x7a/* z */); +} + + +module.exports = function html_inline(state, silent) { + var ch, match, max, token, + pos = state.pos; + + if (!state.md.options.html) { return false; } + + // Check start + max = state.posMax; + if (state.src.charCodeAt(pos) !== 0x3C/* < */ || + pos + 2 >= max) { + return false; + } + + // Quick fail on second char + ch = state.src.charCodeAt(pos + 1); + if (ch !== 0x21/* ! */ && + ch !== 0x3F/* ? */ && + ch !== 0x2F/* / */ && + !isLetter(ch)) { + return false; + } + + match = state.src.slice(pos).match(HTML_TAG_RE); + if (!match) { return false; } + + if (!silent) { + token = state.push('html_inline', '', 0); + token.content = state.src.slice(pos, pos + match[0].length); + } + state.pos += match[0].length; + return true; +}; + +},{"../common/html_re":3}],44:[function(require,module,exports){ +// Process ![image]( "title") + +'use strict'; + +var normalizeReference = require('../common/utils').normalizeReference; +var isSpace = require('../common/utils').isSpace; + + +module.exports = function image(state, silent) { + var attrs, + code, + content, + label, + labelEnd, + labelStart, + pos, + ref, + res, + title, + token, + tokens, + start, + href = '', + oldPos = state.pos, + max = state.posMax; + + if (state.src.charCodeAt(state.pos) !== 0x21/* ! */) { return false; } + if (state.src.charCodeAt(state.pos + 1) !== 0x5B/* [ */) { return false; } + + labelStart = state.pos + 2; + labelEnd = state.md.helpers.parseLinkLabel(state, state.pos + 1, false); + + // parser failed to find ']', so it's not a valid link + if (labelEnd < 0) { return false; } + + pos = labelEnd + 1; + if (pos < max && state.src.charCodeAt(pos) === 0x28/* ( */) { + // + // Inline link + // + + // [link]( "title" ) + // ^^ skipping these spaces + pos++; + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (!isSpace(code) && code !== 0x0A) { break; } + } + if (pos >= max) { return false; } + + // [link]( "title" ) + // ^^^^^^ parsing link destination + start = pos; + res = state.md.helpers.parseLinkDestination(state.src, pos, state.posMax); + if (res.ok) { + href = state.md.normalizeLink(res.str); + if (state.md.validateLink(href)) { + pos = res.pos; + } else { + href = ''; + } + } + + // [link]( "title" ) + // ^^ skipping these spaces + start = pos; + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (!isSpace(code) && code !== 0x0A) { break; } + } + + // [link]( "title" ) + // ^^^^^^^ parsing link title + res = state.md.helpers.parseLinkTitle(state.src, pos, state.posMax); + if (pos < max && start !== pos && res.ok) { + title = res.str; + pos = res.pos; + + // [link]( "title" ) + // ^^ skipping these spaces + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (!isSpace(code) && code !== 0x0A) { break; } + } + } else { + title = ''; + } + + if (pos >= max || state.src.charCodeAt(pos) !== 0x29/* ) */) { + state.pos = oldPos; + return false; + } + pos++; + } else { + // + // Link reference + // + if (typeof state.env.references === 'undefined') { return false; } + + if (pos < max && state.src.charCodeAt(pos) === 0x5B/* [ */) { + start = pos + 1; + pos = state.md.helpers.parseLinkLabel(state, pos); + if (pos >= 0) { + label = state.src.slice(start, pos++); + } else { + pos = labelEnd + 1; + } + } else { + pos = labelEnd + 1; + } + + // covers label === '' and label === undefined + // (collapsed reference link and shortcut reference link respectively) + if (!label) { label = state.src.slice(labelStart, labelEnd); } + + ref = state.env.references[normalizeReference(label)]; + if (!ref) { + state.pos = oldPos; + return false; + } + href = ref.href; + title = ref.title; + } + + // + // We found the end of the link, and know for a fact it's a valid link; + // so all that's left to do is to call tokenizer. + // + if (!silent) { + content = state.src.slice(labelStart, labelEnd); + + state.md.inline.parse( + content, + state.md, + state.env, + tokens = [] + ); + + token = state.push('image', 'img', 0); + token.attrs = attrs = [ [ 'src', href ], [ 'alt', '' ] ]; + token.children = tokens; + token.content = content; + + if (title) { + attrs.push([ 'title', title ]); + } + } + + state.pos = pos; + state.posMax = max; + return true; +}; + +},{"../common/utils":4}],45:[function(require,module,exports){ +// Process [link]( "stuff") + +'use strict'; + +var normalizeReference = require('../common/utils').normalizeReference; +var isSpace = require('../common/utils').isSpace; + + +module.exports = function link(state, silent) { + var attrs, + code, + label, + labelEnd, + labelStart, + pos, + res, + ref, + title, + token, + href = '', + oldPos = state.pos, + max = state.posMax, + start = state.pos, + parseReference = true; + + if (state.src.charCodeAt(state.pos) !== 0x5B/* [ */) { return false; } + + labelStart = state.pos + 1; + labelEnd = state.md.helpers.parseLinkLabel(state, state.pos, true); + + // parser failed to find ']', so it's not a valid link + if (labelEnd < 0) { return false; } + + pos = labelEnd + 1; + if (pos < max && state.src.charCodeAt(pos) === 0x28/* ( */) { + // + // Inline link + // + + // might have found a valid shortcut link, disable reference parsing + parseReference = false; + + // [link]( "title" ) + // ^^ skipping these spaces + pos++; + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (!isSpace(code) && code !== 0x0A) { break; } + } + if (pos >= max) { return false; } + + // [link]( "title" ) + // ^^^^^^ parsing link destination + start = pos; + res = state.md.helpers.parseLinkDestination(state.src, pos, state.posMax); + if (res.ok) { + href = state.md.normalizeLink(res.str); + if (state.md.validateLink(href)) { + pos = res.pos; + } else { + href = ''; + } + } + + // [link]( "title" ) + // ^^ skipping these spaces + start = pos; + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (!isSpace(code) && code !== 0x0A) { break; } + } + + // [link]( "title" ) + // ^^^^^^^ parsing link title + res = state.md.helpers.parseLinkTitle(state.src, pos, state.posMax); + if (pos < max && start !== pos && res.ok) { + title = res.str; + pos = res.pos; + + // [link]( "title" ) + // ^^ skipping these spaces + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (!isSpace(code) && code !== 0x0A) { break; } + } + } else { + title = ''; + } + + if (pos >= max || state.src.charCodeAt(pos) !== 0x29/* ) */) { + // parsing a valid shortcut link failed, fallback to reference + parseReference = true; + } + pos++; + } + + if (parseReference) { + // + // Link reference + // + if (typeof state.env.references === 'undefined') { return false; } + + if (pos < max && state.src.charCodeAt(pos) === 0x5B/* [ */) { + start = pos + 1; + pos = state.md.helpers.parseLinkLabel(state, pos); + if (pos >= 0) { + label = state.src.slice(start, pos++); + } else { + pos = labelEnd + 1; + } + } else { + pos = labelEnd + 1; + } + + // covers label === '' and label === undefined + // (collapsed reference link and shortcut reference link respectively) + if (!label) { label = state.src.slice(labelStart, labelEnd); } + + ref = state.env.references[normalizeReference(label)]; + if (!ref) { + state.pos = oldPos; + return false; + } + href = ref.href; + title = ref.title; + } + + // + // We found the end of the link, and know for a fact it's a valid link; + // so all that's left to do is to call tokenizer. + // + if (!silent) { + state.pos = labelStart; + state.posMax = labelEnd; + + token = state.push('link_open', 'a', 1); + token.attrs = attrs = [ [ 'href', href ] ]; + if (title) { + attrs.push([ 'title', title ]); + } + + state.md.inline.tokenize(state); + + token = state.push('link_close', 'a', -1); + } + + state.pos = pos; + state.posMax = max; + return true; +}; + +},{"../common/utils":4}],46:[function(require,module,exports){ +// Proceess '\n' + +'use strict'; + +var isSpace = require('../common/utils').isSpace; + + +module.exports = function newline(state, silent) { + var pmax, max, pos = state.pos; + + if (state.src.charCodeAt(pos) !== 0x0A/* \n */) { return false; } + + pmax = state.pending.length - 1; + max = state.posMax; + + // ' \n' -> hardbreak + // Lookup in pending chars is bad practice! Don't copy to other rules! + // Pending string is stored in concat mode, indexed lookups will cause + // convertion to flat mode. + if (!silent) { + if (pmax >= 0 && state.pending.charCodeAt(pmax) === 0x20) { + if (pmax >= 1 && state.pending.charCodeAt(pmax - 1) === 0x20) { + state.pending = state.pending.replace(/ +$/, ''); + state.push('hardbreak', 'br', 0); + } else { + state.pending = state.pending.slice(0, -1); + state.push('softbreak', 'br', 0); + } + + } else { + state.push('softbreak', 'br', 0); + } + } + + pos++; + + // skip heading spaces for next line + while (pos < max && isSpace(state.src.charCodeAt(pos))) { pos++; } + + state.pos = pos; + return true; +}; + +},{"../common/utils":4}],47:[function(require,module,exports){ +// Inline parser state + +'use strict'; + + +var Token = require('../token'); +var isWhiteSpace = require('../common/utils').isWhiteSpace; +var isPunctChar = require('../common/utils').isPunctChar; +var isMdAsciiPunct = require('../common/utils').isMdAsciiPunct; + + +function StateInline(src, md, env, outTokens) { + this.src = src; + this.env = env; + this.md = md; + this.tokens = outTokens; + this.tokens_meta = Array(outTokens.length); + + this.pos = 0; + this.posMax = this.src.length; + this.level = 0; + this.pending = ''; + this.pendingLevel = 0; + + // Stores { start: end } pairs. Useful for backtrack + // optimization of pairs parse (emphasis, strikes). + this.cache = {}; + + // List of emphasis-like delimiters for current tag + this.delimiters = []; + + // Stack of delimiter lists for upper level tags + this._prev_delimiters = []; +} + + +// Flush pending text +// +StateInline.prototype.pushPending = function () { + var token = new Token('text', '', 0); + token.content = this.pending; + token.level = this.pendingLevel; + this.tokens.push(token); + this.pending = ''; + return token; +}; + + +// Push new token to "stream". +// If pending text exists - flush it as text token +// +StateInline.prototype.push = function (type, tag, nesting) { + if (this.pending) { + this.pushPending(); + } + + var token = new Token(type, tag, nesting); + var token_meta = null; + + if (nesting < 0) { + // closing tag + this.level--; + this.delimiters = this._prev_delimiters.pop(); + } + + token.level = this.level; + + if (nesting > 0) { + // opening tag + this.level++; + this._prev_delimiters.push(this.delimiters); + this.delimiters = []; + token_meta = { delimiters: this.delimiters }; + } + + this.pendingLevel = this.level; + this.tokens.push(token); + this.tokens_meta.push(token_meta); + return token; +}; + + +// Scan a sequence of emphasis-like markers, and determine whether +// it can start an emphasis sequence or end an emphasis sequence. +// +// - start - position to scan from (it should point at a valid marker); +// - canSplitWord - determine if these markers can be found inside a word +// +StateInline.prototype.scanDelims = function (start, canSplitWord) { + var pos = start, lastChar, nextChar, count, can_open, can_close, + isLastWhiteSpace, isLastPunctChar, + isNextWhiteSpace, isNextPunctChar, + left_flanking = true, + right_flanking = true, + max = this.posMax, + marker = this.src.charCodeAt(start); + + // treat beginning of the line as a whitespace + lastChar = start > 0 ? this.src.charCodeAt(start - 1) : 0x20; + + while (pos < max && this.src.charCodeAt(pos) === marker) { pos++; } + + count = pos - start; + + // treat end of the line as a whitespace + nextChar = pos < max ? this.src.charCodeAt(pos) : 0x20; + + isLastPunctChar = isMdAsciiPunct(lastChar) || isPunctChar(String.fromCharCode(lastChar)); + isNextPunctChar = isMdAsciiPunct(nextChar) || isPunctChar(String.fromCharCode(nextChar)); + + isLastWhiteSpace = isWhiteSpace(lastChar); + isNextWhiteSpace = isWhiteSpace(nextChar); + + if (isNextWhiteSpace) { + left_flanking = false; + } else if (isNextPunctChar) { + if (!(isLastWhiteSpace || isLastPunctChar)) { + left_flanking = false; + } + } + + if (isLastWhiteSpace) { + right_flanking = false; + } else if (isLastPunctChar) { + if (!(isNextWhiteSpace || isNextPunctChar)) { + right_flanking = false; + } + } + + if (!canSplitWord) { + can_open = left_flanking && (!right_flanking || isLastPunctChar); + can_close = right_flanking && (!left_flanking || isNextPunctChar); + } else { + can_open = left_flanking; + can_close = right_flanking; + } + + return { + can_open: can_open, + can_close: can_close, + length: count + }; +}; + + +// re-export Token class to use in block rules +StateInline.prototype.Token = Token; + + +module.exports = StateInline; + +},{"../common/utils":4,"../token":51}],48:[function(require,module,exports){ +// ~~strike through~~ +// +'use strict'; + + +// Insert each marker as a separate text token, and add it to delimiter list +// +module.exports.tokenize = function strikethrough(state, silent) { + var i, scanned, token, len, ch, + start = state.pos, + marker = state.src.charCodeAt(start); + + if (silent) { return false; } + + if (marker !== 0x7E/* ~ */) { return false; } + + scanned = state.scanDelims(state.pos, true); + len = scanned.length; + ch = String.fromCharCode(marker); + + if (len < 2) { return false; } + + if (len % 2) { + token = state.push('text', '', 0); + token.content = ch; + len--; + } + + for (i = 0; i < len; i += 2) { + token = state.push('text', '', 0); + token.content = ch + ch; + + state.delimiters.push({ + marker: marker, + length: 0, // disable "rule of 3" length checks meant for emphasis + jump: i, + token: state.tokens.length - 1, + end: -1, + open: scanned.can_open, + close: scanned.can_close + }); + } + + state.pos += scanned.length; + + return true; +}; + + +function postProcess(state, delimiters) { + var i, j, + startDelim, + endDelim, + token, + loneMarkers = [], + max = delimiters.length; + + for (i = 0; i < max; i++) { + startDelim = delimiters[i]; + + if (startDelim.marker !== 0x7E/* ~ */) { + continue; + } + + if (startDelim.end === -1) { + continue; + } + + endDelim = delimiters[startDelim.end]; + + token = state.tokens[startDelim.token]; + token.type = 's_open'; + token.tag = 's'; + token.nesting = 1; + token.markup = '~~'; + token.content = ''; + + token = state.tokens[endDelim.token]; + token.type = 's_close'; + token.tag = 's'; + token.nesting = -1; + token.markup = '~~'; + token.content = ''; + + if (state.tokens[endDelim.token - 1].type === 'text' && + state.tokens[endDelim.token - 1].content === '~') { + + loneMarkers.push(endDelim.token - 1); + } + } + + // If a marker sequence has an odd number of characters, it's splitted + // like this: `~~~~~` -> `~` + `~~` + `~~`, leaving one marker at the + // start of the sequence. + // + // So, we have to move all those markers after subsequent s_close tags. + // + while (loneMarkers.length) { + i = loneMarkers.pop(); + j = i + 1; + + while (j < state.tokens.length && state.tokens[j].type === 's_close') { + j++; + } + + j--; + + if (i !== j) { + token = state.tokens[j]; + state.tokens[j] = state.tokens[i]; + state.tokens[i] = token; + } + } +} + + +// Walk through delimiter list and replace text tokens with tags +// +module.exports.postProcess = function strikethrough(state) { + var curr, + tokens_meta = state.tokens_meta, + max = state.tokens_meta.length; + + postProcess(state, state.delimiters); + + for (curr = 0; curr < max; curr++) { + if (tokens_meta[curr] && tokens_meta[curr].delimiters) { + postProcess(state, tokens_meta[curr].delimiters); + } + } +}; + +},{}],49:[function(require,module,exports){ +// Skip text characters for text token, place those to pending buffer +// and increment current pos + +'use strict'; + + +// Rule to skip pure text +// '{}$%@~+=:' reserved for extentions + +// !, ", #, $, %, &, ', (, ), *, +, ,, -, ., /, :, ;, <, =, >, ?, @, [, \, ], ^, _, `, {, |, }, or ~ + +// !!!! Don't confuse with "Markdown ASCII Punctuation" chars +// http://spec.commonmark.org/0.15/#ascii-punctuation-character +function isTerminatorChar(ch) { + switch (ch) { + case 0x0A/* \n */: + case 0x21/* ! */: + case 0x23/* # */: + case 0x24/* $ */: + case 0x25/* % */: + case 0x26/* & */: + case 0x2A/* * */: + case 0x2B/* + */: + case 0x2D/* - */: + case 0x3A/* : */: + case 0x3C/* < */: + case 0x3D/* = */: + case 0x3E/* > */: + case 0x40/* @ */: + case 0x5B/* [ */: + case 0x5C/* \ */: + case 0x5D/* ] */: + case 0x5E/* ^ */: + case 0x5F/* _ */: + case 0x60/* ` */: + case 0x7B/* { */: + case 0x7D/* } */: + case 0x7E/* ~ */: + return true; + default: + return false; + } +} + +module.exports = function text(state, silent) { + var pos = state.pos; + + while (pos < state.posMax && !isTerminatorChar(state.src.charCodeAt(pos))) { + pos++; + } + + if (pos === state.pos) { return false; } + + if (!silent) { state.pending += state.src.slice(state.pos, pos); } + + state.pos = pos; + + return true; +}; + +// Alternative implementation, for memory. +// +// It costs 10% of performance, but allows extend terminators list, if place it +// to `ParcerInline` property. Probably, will switch to it sometime, such +// flexibility required. + +/* +var TERMINATOR_RE = /[\n!#$%&*+\-:<=>@[\\\]^_`{}~]/; + +module.exports = function text(state, silent) { + var pos = state.pos, + idx = state.src.slice(pos).search(TERMINATOR_RE); + + // first char is terminator -> empty text + if (idx === 0) { return false; } + + // no terminator -> text till end of string + if (idx < 0) { + if (!silent) { state.pending += state.src.slice(pos); } + state.pos = state.src.length; + return true; + } + + if (!silent) { state.pending += state.src.slice(pos, pos + idx); } + + state.pos += idx; + + return true; +};*/ + +},{}],50:[function(require,module,exports){ +// Clean up tokens after emphasis and strikethrough postprocessing: +// merge adjacent text nodes into one and re-calculate all token levels +// +// This is necessary because initially emphasis delimiter markers (*, _, ~) +// are treated as their own separate text tokens. Then emphasis rule either +// leaves them as text (needed to merge with adjacent text) or turns them +// into opening/closing tags (which messes up levels inside). +// +'use strict'; + + +module.exports = function text_collapse(state) { + var curr, last, + level = 0, + tokens = state.tokens, + max = state.tokens.length; + + for (curr = last = 0; curr < max; curr++) { + // re-calculate levels after emphasis/strikethrough turns some text nodes + // into opening/closing tags + if (tokens[curr].nesting < 0) level--; // closing tag + tokens[curr].level = level; + if (tokens[curr].nesting > 0) level++; // opening tag + + if (tokens[curr].type === 'text' && + curr + 1 < max && + tokens[curr + 1].type === 'text') { + + // collapse two adjacent text nodes + tokens[curr + 1].content = tokens[curr].content + tokens[curr + 1].content; + } else { + if (curr !== last) { tokens[last] = tokens[curr]; } + + last++; + } + } + + if (curr !== last) { + tokens.length = last; + } +}; + +},{}],51:[function(require,module,exports){ +// Token class + +'use strict'; + + +/** + * class Token + **/ + +/** + * new Token(type, tag, nesting) + * + * Create new token and fill passed properties. + **/ +function Token(type, tag, nesting) { + /** + * Token#type -> String + * + * Type of the token (string, e.g. "paragraph_open") + **/ + this.type = type; + + /** + * Token#tag -> String + * + * html tag name, e.g. "p" + **/ + this.tag = tag; + + /** + * Token#attrs -> Array + * + * Html attributes. Format: `[ [ name1, value1 ], [ name2, value2 ] ]` + **/ + this.attrs = null; + + /** + * Token#map -> Array + * + * Source map info. Format: `[ line_begin, line_end ]` + **/ + this.map = null; + + /** + * Token#nesting -> Number + * + * Level change (number in {-1, 0, 1} set), where: + * + * - `1` means the tag is opening + * - `0` means the tag is self-closing + * - `-1` means the tag is closing + **/ + this.nesting = nesting; + + /** + * Token#level -> Number + * + * nesting level, the same as `state.level` + **/ + this.level = 0; + + /** + * Token#children -> Array + * + * An array of child nodes (inline and img tokens) + **/ + this.children = null; + + /** + * Token#content -> String + * + * In a case of self-closing tag (code, html, fence, etc.), + * it has contents of this tag. + **/ + this.content = ''; + + /** + * Token#markup -> String + * + * '*' or '_' for emphasis, fence string for fence, etc. + **/ + this.markup = ''; + + /** + * Token#info -> String + * + * fence infostring + **/ + this.info = ''; + + /** + * Token#meta -> Object + * + * A place for plugins to store an arbitrary data + **/ + this.meta = null; + + /** + * Token#block -> Boolean + * + * True for block-level tokens, false for inline tokens. + * Used in renderer to calculate line breaks + **/ + this.block = false; + + /** + * Token#hidden -> Boolean + * + * If it's true, ignore this element when rendering. Used for tight lists + * to hide paragraphs. + **/ + this.hidden = false; +} + + +/** + * Token.attrIndex(name) -> Number + * + * Search attribute index by name. + **/ +Token.prototype.attrIndex = function attrIndex(name) { + var attrs, i, len; + + if (!this.attrs) { return -1; } + + attrs = this.attrs; + + for (i = 0, len = attrs.length; i < len; i++) { + if (attrs[i][0] === name) { return i; } + } + return -1; +}; + + +/** + * Token.attrPush(attrData) + * + * Add `[ name, value ]` attribute to list. Init attrs if necessary + **/ +Token.prototype.attrPush = function attrPush(attrData) { + if (this.attrs) { + this.attrs.push(attrData); + } else { + this.attrs = [ attrData ]; + } +}; + + +/** + * Token.attrSet(name, value) + * + * Set `name` attribute to `value`. Override old value if exists. + **/ +Token.prototype.attrSet = function attrSet(name, value) { + var idx = this.attrIndex(name), + attrData = [ name, value ]; + + if (idx < 0) { + this.attrPush(attrData); + } else { + this.attrs[idx] = attrData; + } +}; + + +/** + * Token.attrGet(name) + * + * Get the value of attribute `name`, or null if it does not exist. + **/ +Token.prototype.attrGet = function attrGet(name) { + var idx = this.attrIndex(name), value = null; + if (idx >= 0) { + value = this.attrs[idx][1]; + } + return value; +}; + + +/** + * Token.attrJoin(name, value) + * + * Join value to existing attribute via space. Or create new attribute if not + * exists. Useful to operate with token classes. + **/ +Token.prototype.attrJoin = function attrJoin(name, value) { + var idx = this.attrIndex(name); + + if (idx < 0) { + this.attrPush([ name, value ]); + } else { + this.attrs[idx][1] = this.attrs[idx][1] + ' ' + value; + } +}; + + +module.exports = Token; + +},{}],52:[function(require,module,exports){ +module.exports={ "Aacute": "\u00C1", "aacute": "\u00E1", "Abreve": "\u0102", "abreve": "\u0103", "ac": "\u223E", "acd": "\u223F", "acE": "\u223E\u0333", "Acirc": "\u00C2", "acirc": "\u00E2", "acute": "\u00B4", "Acy": "\u0410", "acy": "\u0430", "AElig": "\u00C6", "aelig": "\u00E6", "af": "\u2061", "Afr": "\uD835\uDD04", "afr": "\uD835\uDD1E", "Agrave": "\u00C0", "agrave": "\u00E0", "alefsym": "\u2135", "aleph": "\u2135", "Alpha": "\u0391", "alpha": "\u03B1", "Amacr": "\u0100", "amacr": "\u0101", "amalg": "\u2A3F", "amp": "&", "AMP": "&", "andand": "\u2A55", "And": "\u2A53", "and": "\u2227", "andd": "\u2A5C", "andslope": "\u2A58", "andv": "\u2A5A", "ang": "\u2220", "ange": "\u29A4", "angle": "\u2220", "angmsdaa": "\u29A8", "angmsdab": "\u29A9", "angmsdac": "\u29AA", "angmsdad": "\u29AB", "angmsdae": "\u29AC", "angmsdaf": "\u29AD", "angmsdag": "\u29AE", "angmsdah": "\u29AF", "angmsd": "\u2221", "angrt": "\u221F", "angrtvb": "\u22BE", "angrtvbd": "\u299D", "angsph": "\u2222", "angst": "\u00C5", "angzarr": "\u237C", "Aogon": "\u0104", "aogon": "\u0105", "Aopf": "\uD835\uDD38", "aopf": "\uD835\uDD52", "apacir": "\u2A6F", "ap": "\u2248", "apE": "\u2A70", "ape": "\u224A", "apid": "\u224B", "apos": "'", "ApplyFunction": "\u2061", "approx": "\u2248", "approxeq": "\u224A", "Aring": "\u00C5", "aring": "\u00E5", "Ascr": "\uD835\uDC9C", "ascr": "\uD835\uDCB6", "Assign": "\u2254", "ast": "*", "asymp": "\u2248", "asympeq": "\u224D", "Atilde": "\u00C3", "atilde": "\u00E3", "Auml": "\u00C4", "auml": "\u00E4", "awconint": "\u2233", "awint": "\u2A11", "backcong": "\u224C", "backepsilon": "\u03F6", "backprime": "\u2035", "backsim": "\u223D", "backsimeq": "\u22CD", "Backslash": "\u2216", "Barv": "\u2AE7", "barvee": "\u22BD", "barwed": "\u2305", "Barwed": "\u2306", "barwedge": "\u2305", "bbrk": "\u23B5", "bbrktbrk": "\u23B6", "bcong": "\u224C", "Bcy": "\u0411", "bcy": "\u0431", "bdquo": "\u201E", "becaus": "\u2235", "because": "\u2235", "Because": "\u2235", "bemptyv": "\u29B0", "bepsi": "\u03F6", "bernou": "\u212C", "Bernoullis": "\u212C", "Beta": "\u0392", "beta": "\u03B2", "beth": "\u2136", "between": "\u226C", "Bfr": "\uD835\uDD05", "bfr": "\uD835\uDD1F", "bigcap": "\u22C2", "bigcirc": "\u25EF", "bigcup": "\u22C3", "bigodot": "\u2A00", "bigoplus": "\u2A01", "bigotimes": "\u2A02", "bigsqcup": "\u2A06", "bigstar": "\u2605", "bigtriangledown": "\u25BD", "bigtriangleup": "\u25B3", "biguplus": "\u2A04", "bigvee": "\u22C1", "bigwedge": "\u22C0", "bkarow": "\u290D", "blacklozenge": "\u29EB", "blacksquare": "\u25AA", "blacktriangle": "\u25B4", "blacktriangledown": "\u25BE", "blacktriangleleft": "\u25C2", "blacktriangleright": "\u25B8", "blank": "\u2423", "blk12": "\u2592", "blk14": "\u2591", "blk34": "\u2593", "block": "\u2588", "bne": "=\u20E5", "bnequiv": "\u2261\u20E5", "bNot": "\u2AED", "bnot": "\u2310", "Bopf": "\uD835\uDD39", "bopf": "\uD835\uDD53", "bot": "\u22A5", "bottom": "\u22A5", "bowtie": "\u22C8", "boxbox": "\u29C9", "boxdl": "\u2510", "boxdL": "\u2555", "boxDl": "\u2556", "boxDL": "\u2557", "boxdr": "\u250C", "boxdR": "\u2552", "boxDr": "\u2553", "boxDR": "\u2554", "boxh": "\u2500", "boxH": "\u2550", "boxhd": "\u252C", "boxHd": "\u2564", "boxhD": "\u2565", "boxHD": "\u2566", "boxhu": "\u2534", "boxHu": "\u2567", "boxhU": "\u2568", "boxHU": "\u2569", "boxminus": "\u229F", "boxplus": "\u229E", "boxtimes": "\u22A0", "boxul": "\u2518", "boxuL": "\u255B", "boxUl": "\u255C", "boxUL": "\u255D", "boxur": "\u2514", "boxuR": "\u2558", "boxUr": "\u2559", "boxUR": "\u255A", "boxv": "\u2502", "boxV": "\u2551", "boxvh": "\u253C", "boxvH": "\u256A", "boxVh": "\u256B", "boxVH": "\u256C", "boxvl": "\u2524", "boxvL": "\u2561", "boxVl": "\u2562", "boxVL": "\u2563", "boxvr": "\u251C", "boxvR": "\u255E", "boxVr": "\u255F", "boxVR": "\u2560", "bprime": "\u2035", "breve": "\u02D8", "Breve": "\u02D8", "brvbar": "\u00A6", "bscr": "\uD835\uDCB7", "Bscr": "\u212C", "bsemi": "\u204F", "bsim": "\u223D", "bsime": "\u22CD", "bsolb": "\u29C5", "bsol": "\\", "bsolhsub": "\u27C8", "bull": "\u2022", "bullet": "\u2022", "bump": "\u224E", "bumpE": "\u2AAE", "bumpe": "\u224F", "Bumpeq": "\u224E", "bumpeq": "\u224F", "Cacute": "\u0106", "cacute": "\u0107", "capand": "\u2A44", "capbrcup": "\u2A49", "capcap": "\u2A4B", "cap": "\u2229", "Cap": "\u22D2", "capcup": "\u2A47", "capdot": "\u2A40", "CapitalDifferentialD": "\u2145", "caps": "\u2229\uFE00", "caret": "\u2041", "caron": "\u02C7", "Cayleys": "\u212D", "ccaps": "\u2A4D", "Ccaron": "\u010C", "ccaron": "\u010D", "Ccedil": "\u00C7", "ccedil": "\u00E7", "Ccirc": "\u0108", "ccirc": "\u0109", "Cconint": "\u2230", "ccups": "\u2A4C", "ccupssm": "\u2A50", "Cdot": "\u010A", "cdot": "\u010B", "cedil": "\u00B8", "Cedilla": "\u00B8", "cemptyv": "\u29B2", "cent": "\u00A2", "centerdot": "\u00B7", "CenterDot": "\u00B7", "cfr": "\uD835\uDD20", "Cfr": "\u212D", "CHcy": "\u0427", "chcy": "\u0447", "check": "\u2713", "checkmark": "\u2713", "Chi": "\u03A7", "chi": "\u03C7", "circ": "\u02C6", "circeq": "\u2257", "circlearrowleft": "\u21BA", "circlearrowright": "\u21BB", "circledast": "\u229B", "circledcirc": "\u229A", "circleddash": "\u229D", "CircleDot": "\u2299", "circledR": "\u00AE", "circledS": "\u24C8", "CircleMinus": "\u2296", "CirclePlus": "\u2295", "CircleTimes": "\u2297", "cir": "\u25CB", "cirE": "\u29C3", "cire": "\u2257", "cirfnint": "\u2A10", "cirmid": "\u2AEF", "cirscir": "\u29C2", "ClockwiseContourIntegral": "\u2232", "CloseCurlyDoubleQuote": "\u201D", "CloseCurlyQuote": "\u2019", "clubs": "\u2663", "clubsuit": "\u2663", "colon": ":", "Colon": "\u2237", "Colone": "\u2A74", "colone": "\u2254", "coloneq": "\u2254", "comma": ",", "commat": "@", "comp": "\u2201", "compfn": "\u2218", "complement": "\u2201", "complexes": "\u2102", "cong": "\u2245", "congdot": "\u2A6D", "Congruent": "\u2261", "conint": "\u222E", "Conint": "\u222F", "ContourIntegral": "\u222E", "copf": "\uD835\uDD54", "Copf": "\u2102", "coprod": "\u2210", "Coproduct": "\u2210", "copy": "\u00A9", "COPY": "\u00A9", "copysr": "\u2117", "CounterClockwiseContourIntegral": "\u2233", "crarr": "\u21B5", "cross": "\u2717", "Cross": "\u2A2F", "Cscr": "\uD835\uDC9E", "cscr": "\uD835\uDCB8", "csub": "\u2ACF", "csube": "\u2AD1", "csup": "\u2AD0", "csupe": "\u2AD2", "ctdot": "\u22EF", "cudarrl": "\u2938", "cudarrr": "\u2935", "cuepr": "\u22DE", "cuesc": "\u22DF", "cularr": "\u21B6", "cularrp": "\u293D", "cupbrcap": "\u2A48", "cupcap": "\u2A46", "CupCap": "\u224D", "cup": "\u222A", "Cup": "\u22D3", "cupcup": "\u2A4A", "cupdot": "\u228D", "cupor": "\u2A45", "cups": "\u222A\uFE00", "curarr": "\u21B7", "curarrm": "\u293C", "curlyeqprec": "\u22DE", "curlyeqsucc": "\u22DF", "curlyvee": "\u22CE", "curlywedge": "\u22CF", "curren": "\u00A4", "curvearrowleft": "\u21B6", "curvearrowright": "\u21B7", "cuvee": "\u22CE", "cuwed": "\u22CF", "cwconint": "\u2232", "cwint": "\u2231", "cylcty": "\u232D", "dagger": "\u2020", "Dagger": "\u2021", "daleth": "\u2138", "darr": "\u2193", "Darr": "\u21A1", "dArr": "\u21D3", "dash": "\u2010", "Dashv": "\u2AE4", "dashv": "\u22A3", "dbkarow": "\u290F", "dblac": "\u02DD", "Dcaron": "\u010E", "dcaron": "\u010F", "Dcy": "\u0414", "dcy": "\u0434", "ddagger": "\u2021", "ddarr": "\u21CA", "DD": "\u2145", "dd": "\u2146", "DDotrahd": "\u2911", "ddotseq": "\u2A77", "deg": "\u00B0", "Del": "\u2207", "Delta": "\u0394", "delta": "\u03B4", "demptyv": "\u29B1", "dfisht": "\u297F", "Dfr": "\uD835\uDD07", "dfr": "\uD835\uDD21", "dHar": "\u2965", "dharl": "\u21C3", "dharr": "\u21C2", "DiacriticalAcute": "\u00B4", "DiacriticalDot": "\u02D9", "DiacriticalDoubleAcute": "\u02DD", "DiacriticalGrave": "`", "DiacriticalTilde": "\u02DC", "diam": "\u22C4", "diamond": "\u22C4", "Diamond": "\u22C4", "diamondsuit": "\u2666", "diams": "\u2666", "die": "\u00A8", "DifferentialD": "\u2146", "digamma": "\u03DD", "disin": "\u22F2", "div": "\u00F7", "divide": "\u00F7", "divideontimes": "\u22C7", "divonx": "\u22C7", "DJcy": "\u0402", "djcy": "\u0452", "dlcorn": "\u231E", "dlcrop": "\u230D", "dollar": "$", "Dopf": "\uD835\uDD3B", "dopf": "\uD835\uDD55", "Dot": "\u00A8", "dot": "\u02D9", "DotDot": "\u20DC", "doteq": "\u2250", "doteqdot": "\u2251", "DotEqual": "\u2250", "dotminus": "\u2238", "dotplus": "\u2214", "dotsquare": "\u22A1", "doublebarwedge": "\u2306", "DoubleContourIntegral": "\u222F", "DoubleDot": "\u00A8", "DoubleDownArrow": "\u21D3", "DoubleLeftArrow": "\u21D0", "DoubleLeftRightArrow": "\u21D4", "DoubleLeftTee": "\u2AE4", "DoubleLongLeftArrow": "\u27F8", "DoubleLongLeftRightArrow": "\u27FA", "DoubleLongRightArrow": "\u27F9", "DoubleRightArrow": "\u21D2", "DoubleRightTee": "\u22A8", "DoubleUpArrow": "\u21D1", "DoubleUpDownArrow": "\u21D5", "DoubleVerticalBar": "\u2225", "DownArrowBar": "\u2913", "downarrow": "\u2193", "DownArrow": "\u2193", "Downarrow": "\u21D3", "DownArrowUpArrow": "\u21F5", "DownBreve": "\u0311", "downdownarrows": "\u21CA", "downharpoonleft": "\u21C3", "downharpoonright": "\u21C2", "DownLeftRightVector": "\u2950", "DownLeftTeeVector": "\u295E", "DownLeftVectorBar": "\u2956", "DownLeftVector": "\u21BD", "DownRightTeeVector": "\u295F", "DownRightVectorBar": "\u2957", "DownRightVector": "\u21C1", "DownTeeArrow": "\u21A7", "DownTee": "\u22A4", "drbkarow": "\u2910", "drcorn": "\u231F", "drcrop": "\u230C", "Dscr": "\uD835\uDC9F", "dscr": "\uD835\uDCB9", "DScy": "\u0405", "dscy": "\u0455", "dsol": "\u29F6", "Dstrok": "\u0110", "dstrok": "\u0111", "dtdot": "\u22F1", "dtri": "\u25BF", "dtrif": "\u25BE", "duarr": "\u21F5", "duhar": "\u296F", "dwangle": "\u29A6", "DZcy": "\u040F", "dzcy": "\u045F", "dzigrarr": "\u27FF", "Eacute": "\u00C9", "eacute": "\u00E9", "easter": "\u2A6E", "Ecaron": "\u011A", "ecaron": "\u011B", "Ecirc": "\u00CA", "ecirc": "\u00EA", "ecir": "\u2256", "ecolon": "\u2255", "Ecy": "\u042D", "ecy": "\u044D", "eDDot": "\u2A77", "Edot": "\u0116", "edot": "\u0117", "eDot": "\u2251", "ee": "\u2147", "efDot": "\u2252", "Efr": "\uD835\uDD08", "efr": "\uD835\uDD22", "eg": "\u2A9A", "Egrave": "\u00C8", "egrave": "\u00E8", "egs": "\u2A96", "egsdot": "\u2A98", "el": "\u2A99", "Element": "\u2208", "elinters": "\u23E7", "ell": "\u2113", "els": "\u2A95", "elsdot": "\u2A97", "Emacr": "\u0112", "emacr": "\u0113", "empty": "\u2205", "emptyset": "\u2205", "EmptySmallSquare": "\u25FB", "emptyv": "\u2205", "EmptyVerySmallSquare": "\u25AB", "emsp13": "\u2004", "emsp14": "\u2005", "emsp": "\u2003", "ENG": "\u014A", "eng": "\u014B", "ensp": "\u2002", "Eogon": "\u0118", "eogon": "\u0119", "Eopf": "\uD835\uDD3C", "eopf": "\uD835\uDD56", "epar": "\u22D5", "eparsl": "\u29E3", "eplus": "\u2A71", "epsi": "\u03B5", "Epsilon": "\u0395", "epsilon": "\u03B5", "epsiv": "\u03F5", "eqcirc": "\u2256", "eqcolon": "\u2255", "eqsim": "\u2242", "eqslantgtr": "\u2A96", "eqslantless": "\u2A95", "Equal": "\u2A75", "equals": "=", "EqualTilde": "\u2242", "equest": "\u225F", "Equilibrium": "\u21CC", "equiv": "\u2261", "equivDD": "\u2A78", "eqvparsl": "\u29E5", "erarr": "\u2971", "erDot": "\u2253", "escr": "\u212F", "Escr": "\u2130", "esdot": "\u2250", "Esim": "\u2A73", "esim": "\u2242", "Eta": "\u0397", "eta": "\u03B7", "ETH": "\u00D0", "eth": "\u00F0", "Euml": "\u00CB", "euml": "\u00EB", "euro": "\u20AC", "excl": "!", "exist": "\u2203", "Exists": "\u2203", "expectation": "\u2130", "exponentiale": "\u2147", "ExponentialE": "\u2147", "fallingdotseq": "\u2252", "Fcy": "\u0424", "fcy": "\u0444", "female": "\u2640", "ffilig": "\uFB03", "fflig": "\uFB00", "ffllig": "\uFB04", "Ffr": "\uD835\uDD09", "ffr": "\uD835\uDD23", "filig": "\uFB01", "FilledSmallSquare": "\u25FC", "FilledVerySmallSquare": "\u25AA", "fjlig": "fj", "flat": "\u266D", "fllig": "\uFB02", "fltns": "\u25B1", "fnof": "\u0192", "Fopf": "\uD835\uDD3D", "fopf": "\uD835\uDD57", "forall": "\u2200", "ForAll": "\u2200", "fork": "\u22D4", "forkv": "\u2AD9", "Fouriertrf": "\u2131", "fpartint": "\u2A0D", "frac12": "\u00BD", "frac13": "\u2153", "frac14": "\u00BC", "frac15": "\u2155", "frac16": "\u2159", "frac18": "\u215B", "frac23": "\u2154", "frac25": "\u2156", "frac34": "\u00BE", "frac35": "\u2157", "frac38": "\u215C", "frac45": "\u2158", "frac56": "\u215A", "frac58": "\u215D", "frac78": "\u215E", "frasl": "\u2044", "frown": "\u2322", "fscr": "\uD835\uDCBB", "Fscr": "\u2131", "gacute": "\u01F5", "Gamma": "\u0393", "gamma": "\u03B3", "Gammad": "\u03DC", "gammad": "\u03DD", "gap": "\u2A86", "Gbreve": "\u011E", "gbreve": "\u011F", "Gcedil": "\u0122", "Gcirc": "\u011C", "gcirc": "\u011D", "Gcy": "\u0413", "gcy": "\u0433", "Gdot": "\u0120", "gdot": "\u0121", "ge": "\u2265", "gE": "\u2267", "gEl": "\u2A8C", "gel": "\u22DB", "geq": "\u2265", "geqq": "\u2267", "geqslant": "\u2A7E", "gescc": "\u2AA9", "ges": "\u2A7E", "gesdot": "\u2A80", "gesdoto": "\u2A82", "gesdotol": "\u2A84", "gesl": "\u22DB\uFE00", "gesles": "\u2A94", "Gfr": "\uD835\uDD0A", "gfr": "\uD835\uDD24", "gg": "\u226B", "Gg": "\u22D9", "ggg": "\u22D9", "gimel": "\u2137", "GJcy": "\u0403", "gjcy": "\u0453", "gla": "\u2AA5", "gl": "\u2277", "glE": "\u2A92", "glj": "\u2AA4", "gnap": "\u2A8A", "gnapprox": "\u2A8A", "gne": "\u2A88", "gnE": "\u2269", "gneq": "\u2A88", "gneqq": "\u2269", "gnsim": "\u22E7", "Gopf": "\uD835\uDD3E", "gopf": "\uD835\uDD58", "grave": "`", "GreaterEqual": "\u2265", "GreaterEqualLess": "\u22DB", "GreaterFullEqual": "\u2267", "GreaterGreater": "\u2AA2", "GreaterLess": "\u2277", "GreaterSlantEqual": "\u2A7E", "GreaterTilde": "\u2273", "Gscr": "\uD835\uDCA2", "gscr": "\u210A", "gsim": "\u2273", "gsime": "\u2A8E", "gsiml": "\u2A90", "gtcc": "\u2AA7", "gtcir": "\u2A7A", "gt": ">", "GT": ">", "Gt": "\u226B", "gtdot": "\u22D7", "gtlPar": "\u2995", "gtquest": "\u2A7C", "gtrapprox": "\u2A86", "gtrarr": "\u2978", "gtrdot": "\u22D7", "gtreqless": "\u22DB", "gtreqqless": "\u2A8C", "gtrless": "\u2277", "gtrsim": "\u2273", "gvertneqq": "\u2269\uFE00", "gvnE": "\u2269\uFE00", "Hacek": "\u02C7", "hairsp": "\u200A", "half": "\u00BD", "hamilt": "\u210B", "HARDcy": "\u042A", "hardcy": "\u044A", "harrcir": "\u2948", "harr": "\u2194", "hArr": "\u21D4", "harrw": "\u21AD", "Hat": "^", "hbar": "\u210F", "Hcirc": "\u0124", "hcirc": "\u0125", "hearts": "\u2665", "heartsuit": "\u2665", "hellip": "\u2026", "hercon": "\u22B9", "hfr": "\uD835\uDD25", "Hfr": "\u210C", "HilbertSpace": "\u210B", "hksearow": "\u2925", "hkswarow": "\u2926", "hoarr": "\u21FF", "homtht": "\u223B", "hookleftarrow": "\u21A9", "hookrightarrow": "\u21AA", "hopf": "\uD835\uDD59", "Hopf": "\u210D", "horbar": "\u2015", "HorizontalLine": "\u2500", "hscr": "\uD835\uDCBD", "Hscr": "\u210B", "hslash": "\u210F", "Hstrok": "\u0126", "hstrok": "\u0127", "HumpDownHump": "\u224E", "HumpEqual": "\u224F", "hybull": "\u2043", "hyphen": "\u2010", "Iacute": "\u00CD", "iacute": "\u00ED", "ic": "\u2063", "Icirc": "\u00CE", "icirc": "\u00EE", "Icy": "\u0418", "icy": "\u0438", "Idot": "\u0130", "IEcy": "\u0415", "iecy": "\u0435", "iexcl": "\u00A1", "iff": "\u21D4", "ifr": "\uD835\uDD26", "Ifr": "\u2111", "Igrave": "\u00CC", "igrave": "\u00EC", "ii": "\u2148", "iiiint": "\u2A0C", "iiint": "\u222D", "iinfin": "\u29DC", "iiota": "\u2129", "IJlig": "\u0132", "ijlig": "\u0133", "Imacr": "\u012A", "imacr": "\u012B", "image": "\u2111", "ImaginaryI": "\u2148", "imagline": "\u2110", "imagpart": "\u2111", "imath": "\u0131", "Im": "\u2111", "imof": "\u22B7", "imped": "\u01B5", "Implies": "\u21D2", "incare": "\u2105", "in": "\u2208", "infin": "\u221E", "infintie": "\u29DD", "inodot": "\u0131", "intcal": "\u22BA", "int": "\u222B", "Int": "\u222C", "integers": "\u2124", "Integral": "\u222B", "intercal": "\u22BA", "Intersection": "\u22C2", "intlarhk": "\u2A17", "intprod": "\u2A3C", "InvisibleComma": "\u2063", "InvisibleTimes": "\u2062", "IOcy": "\u0401", "iocy": "\u0451", "Iogon": "\u012E", "iogon": "\u012F", "Iopf": "\uD835\uDD40", "iopf": "\uD835\uDD5A", "Iota": "\u0399", "iota": "\u03B9", "iprod": "\u2A3C", "iquest": "\u00BF", "iscr": "\uD835\uDCBE", "Iscr": "\u2110", "isin": "\u2208", "isindot": "\u22F5", "isinE": "\u22F9", "isins": "\u22F4", "isinsv": "\u22F3", "isinv": "\u2208", "it": "\u2062", "Itilde": "\u0128", "itilde": "\u0129", "Iukcy": "\u0406", "iukcy": "\u0456", "Iuml": "\u00CF", "iuml": "\u00EF", "Jcirc": "\u0134", "jcirc": "\u0135", "Jcy": "\u0419", "jcy": "\u0439", "Jfr": "\uD835\uDD0D", "jfr": "\uD835\uDD27", "jmath": "\u0237", "Jopf": "\uD835\uDD41", "jopf": "\uD835\uDD5B", "Jscr": "\uD835\uDCA5", "jscr": "\uD835\uDCBF", "Jsercy": "\u0408", "jsercy": "\u0458", "Jukcy": "\u0404", "jukcy": "\u0454", "Kappa": "\u039A", "kappa": "\u03BA", "kappav": "\u03F0", "Kcedil": "\u0136", "kcedil": "\u0137", "Kcy": "\u041A", "kcy": "\u043A", "Kfr": "\uD835\uDD0E", "kfr": "\uD835\uDD28", "kgreen": "\u0138", "KHcy": "\u0425", "khcy": "\u0445", "KJcy": "\u040C", "kjcy": "\u045C", "Kopf": "\uD835\uDD42", "kopf": "\uD835\uDD5C", "Kscr": "\uD835\uDCA6", "kscr": "\uD835\uDCC0", "lAarr": "\u21DA", "Lacute": "\u0139", "lacute": "\u013A", "laemptyv": "\u29B4", "lagran": "\u2112", "Lambda": "\u039B", "lambda": "\u03BB", "lang": "\u27E8", "Lang": "\u27EA", "langd": "\u2991", "langle": "\u27E8", "lap": "\u2A85", "Laplacetrf": "\u2112", "laquo": "\u00AB", "larrb": "\u21E4", "larrbfs": "\u291F", "larr": "\u2190", "Larr": "\u219E", "lArr": "\u21D0", "larrfs": "\u291D", "larrhk": "\u21A9", "larrlp": "\u21AB", "larrpl": "\u2939", "larrsim": "\u2973", "larrtl": "\u21A2", "latail": "\u2919", "lAtail": "\u291B", "lat": "\u2AAB", "late": "\u2AAD", "lates": "\u2AAD\uFE00", "lbarr": "\u290C", "lBarr": "\u290E", "lbbrk": "\u2772", "lbrace": "{", "lbrack": "[", "lbrke": "\u298B", "lbrksld": "\u298F", "lbrkslu": "\u298D", "Lcaron": "\u013D", "lcaron": "\u013E", "Lcedil": "\u013B", "lcedil": "\u013C", "lceil": "\u2308", "lcub": "{", "Lcy": "\u041B", "lcy": "\u043B", "ldca": "\u2936", "ldquo": "\u201C", "ldquor": "\u201E", "ldrdhar": "\u2967", "ldrushar": "\u294B", "ldsh": "\u21B2", "le": "\u2264", "lE": "\u2266", "LeftAngleBracket": "\u27E8", "LeftArrowBar": "\u21E4", "leftarrow": "\u2190", "LeftArrow": "\u2190", "Leftarrow": "\u21D0", "LeftArrowRightArrow": "\u21C6", "leftarrowtail": "\u21A2", "LeftCeiling": "\u2308", "LeftDoubleBracket": "\u27E6", "LeftDownTeeVector": "\u2961", "LeftDownVectorBar": "\u2959", "LeftDownVector": "\u21C3", "LeftFloor": "\u230A", "leftharpoondown": "\u21BD", "leftharpoonup": "\u21BC", "leftleftarrows": "\u21C7", "leftrightarrow": "\u2194", "LeftRightArrow": "\u2194", "Leftrightarrow": "\u21D4", "leftrightarrows": "\u21C6", "leftrightharpoons": "\u21CB", "leftrightsquigarrow": "\u21AD", "LeftRightVector": "\u294E", "LeftTeeArrow": "\u21A4", "LeftTee": "\u22A3", "LeftTeeVector": "\u295A", "leftthreetimes": "\u22CB", "LeftTriangleBar": "\u29CF", "LeftTriangle": "\u22B2", "LeftTriangleEqual": "\u22B4", "LeftUpDownVector": "\u2951", "LeftUpTeeVector": "\u2960", "LeftUpVectorBar": "\u2958", "LeftUpVector": "\u21BF", "LeftVectorBar": "\u2952", "LeftVector": "\u21BC", "lEg": "\u2A8B", "leg": "\u22DA", "leq": "\u2264", "leqq": "\u2266", "leqslant": "\u2A7D", "lescc": "\u2AA8", "les": "\u2A7D", "lesdot": "\u2A7F", "lesdoto": "\u2A81", "lesdotor": "\u2A83", "lesg": "\u22DA\uFE00", "lesges": "\u2A93", "lessapprox": "\u2A85", "lessdot": "\u22D6", "lesseqgtr": "\u22DA", "lesseqqgtr": "\u2A8B", "LessEqualGreater": "\u22DA", "LessFullEqual": "\u2266", "LessGreater": "\u2276", "lessgtr": "\u2276", "LessLess": "\u2AA1", "lesssim": "\u2272", "LessSlantEqual": "\u2A7D", "LessTilde": "\u2272", "lfisht": "\u297C", "lfloor": "\u230A", "Lfr": "\uD835\uDD0F", "lfr": "\uD835\uDD29", "lg": "\u2276", "lgE": "\u2A91", "lHar": "\u2962", "lhard": "\u21BD", "lharu": "\u21BC", "lharul": "\u296A", "lhblk": "\u2584", "LJcy": "\u0409", "ljcy": "\u0459", "llarr": "\u21C7", "ll": "\u226A", "Ll": "\u22D8", "llcorner": "\u231E", "Lleftarrow": "\u21DA", "llhard": "\u296B", "lltri": "\u25FA", "Lmidot": "\u013F", "lmidot": "\u0140", "lmoustache": "\u23B0", "lmoust": "\u23B0", "lnap": "\u2A89", "lnapprox": "\u2A89", "lne": "\u2A87", "lnE": "\u2268", "lneq": "\u2A87", "lneqq": "\u2268", "lnsim": "\u22E6", "loang": "\u27EC", "loarr": "\u21FD", "lobrk": "\u27E6", "longleftarrow": "\u27F5", "LongLeftArrow": "\u27F5", "Longleftarrow": "\u27F8", "longleftrightarrow": "\u27F7", "LongLeftRightArrow": "\u27F7", "Longleftrightarrow": "\u27FA", "longmapsto": "\u27FC", "longrightarrow": "\u27F6", "LongRightArrow": "\u27F6", "Longrightarrow": "\u27F9", "looparrowleft": "\u21AB", "looparrowright": "\u21AC", "lopar": "\u2985", "Lopf": "\uD835\uDD43", "lopf": "\uD835\uDD5D", "loplus": "\u2A2D", "lotimes": "\u2A34", "lowast": "\u2217", "lowbar": "_", "LowerLeftArrow": "\u2199", "LowerRightArrow": "\u2198", "loz": "\u25CA", "lozenge": "\u25CA", "lozf": "\u29EB", "lpar": "(", "lparlt": "\u2993", "lrarr": "\u21C6", "lrcorner": "\u231F", "lrhar": "\u21CB", "lrhard": "\u296D", "lrm": "\u200E", "lrtri": "\u22BF", "lsaquo": "\u2039", "lscr": "\uD835\uDCC1", "Lscr": "\u2112", "lsh": "\u21B0", "Lsh": "\u21B0", "lsim": "\u2272", "lsime": "\u2A8D", "lsimg": "\u2A8F", "lsqb": "[", "lsquo": "\u2018", "lsquor": "\u201A", "Lstrok": "\u0141", "lstrok": "\u0142", "ltcc": "\u2AA6", "ltcir": "\u2A79", "lt": "<", "LT": "<", "Lt": "\u226A", "ltdot": "\u22D6", "lthree": "\u22CB", "ltimes": "\u22C9", "ltlarr": "\u2976", "ltquest": "\u2A7B", "ltri": "\u25C3", "ltrie": "\u22B4", "ltrif": "\u25C2", "ltrPar": "\u2996", "lurdshar": "\u294A", "luruhar": "\u2966", "lvertneqq": "\u2268\uFE00", "lvnE": "\u2268\uFE00", "macr": "\u00AF", "male": "\u2642", "malt": "\u2720", "maltese": "\u2720", "Map": "\u2905", "map": "\u21A6", "mapsto": "\u21A6", "mapstodown": "\u21A7", "mapstoleft": "\u21A4", "mapstoup": "\u21A5", "marker": "\u25AE", "mcomma": "\u2A29", "Mcy": "\u041C", "mcy": "\u043C", "mdash": "\u2014", "mDDot": "\u223A", "measuredangle": "\u2221", "MediumSpace": "\u205F", "Mellintrf": "\u2133", "Mfr": "\uD835\uDD10", "mfr": "\uD835\uDD2A", "mho": "\u2127", "micro": "\u00B5", "midast": "*", "midcir": "\u2AF0", "mid": "\u2223", "middot": "\u00B7", "minusb": "\u229F", "minus": "\u2212", "minusd": "\u2238", "minusdu": "\u2A2A", "MinusPlus": "\u2213", "mlcp": "\u2ADB", "mldr": "\u2026", "mnplus": "\u2213", "models": "\u22A7", "Mopf": "\uD835\uDD44", "mopf": "\uD835\uDD5E", "mp": "\u2213", "mscr": "\uD835\uDCC2", "Mscr": "\u2133", "mstpos": "\u223E", "Mu": "\u039C", "mu": "\u03BC", "multimap": "\u22B8", "mumap": "\u22B8", "nabla": "\u2207", "Nacute": "\u0143", "nacute": "\u0144", "nang": "\u2220\u20D2", "nap": "\u2249", "napE": "\u2A70\u0338", "napid": "\u224B\u0338", "napos": "\u0149", "napprox": "\u2249", "natural": "\u266E", "naturals": "\u2115", "natur": "\u266E", "nbsp": "\u00A0", "nbump": "\u224E\u0338", "nbumpe": "\u224F\u0338", "ncap": "\u2A43", "Ncaron": "\u0147", "ncaron": "\u0148", "Ncedil": "\u0145", "ncedil": "\u0146", "ncong": "\u2247", "ncongdot": "\u2A6D\u0338", "ncup": "\u2A42", "Ncy": "\u041D", "ncy": "\u043D", "ndash": "\u2013", "nearhk": "\u2924", "nearr": "\u2197", "neArr": "\u21D7", "nearrow": "\u2197", "ne": "\u2260", "nedot": "\u2250\u0338", "NegativeMediumSpace": "\u200B", "NegativeThickSpace": "\u200B", "NegativeThinSpace": "\u200B", "NegativeVeryThinSpace": "\u200B", "nequiv": "\u2262", "nesear": "\u2928", "nesim": "\u2242\u0338", "NestedGreaterGreater": "\u226B", "NestedLessLess": "\u226A", "NewLine": "\n", "nexist": "\u2204", "nexists": "\u2204", "Nfr": "\uD835\uDD11", "nfr": "\uD835\uDD2B", "ngE": "\u2267\u0338", "nge": "\u2271", "ngeq": "\u2271", "ngeqq": "\u2267\u0338", "ngeqslant": "\u2A7E\u0338", "nges": "\u2A7E\u0338", "nGg": "\u22D9\u0338", "ngsim": "\u2275", "nGt": "\u226B\u20D2", "ngt": "\u226F", "ngtr": "\u226F", "nGtv": "\u226B\u0338", "nharr": "\u21AE", "nhArr": "\u21CE", "nhpar": "\u2AF2", "ni": "\u220B", "nis": "\u22FC", "nisd": "\u22FA", "niv": "\u220B", "NJcy": "\u040A", "njcy": "\u045A", "nlarr": "\u219A", "nlArr": "\u21CD", "nldr": "\u2025", "nlE": "\u2266\u0338", "nle": "\u2270", "nleftarrow": "\u219A", "nLeftarrow": "\u21CD", "nleftrightarrow": "\u21AE", "nLeftrightarrow": "\u21CE", "nleq": "\u2270", "nleqq": "\u2266\u0338", "nleqslant": "\u2A7D\u0338", "nles": "\u2A7D\u0338", "nless": "\u226E", "nLl": "\u22D8\u0338", "nlsim": "\u2274", "nLt": "\u226A\u20D2", "nlt": "\u226E", "nltri": "\u22EA", "nltrie": "\u22EC", "nLtv": "\u226A\u0338", "nmid": "\u2224", "NoBreak": "\u2060", "NonBreakingSpace": "\u00A0", "nopf": "\uD835\uDD5F", "Nopf": "\u2115", "Not": "\u2AEC", "not": "\u00AC", "NotCongruent": "\u2262", "NotCupCap": "\u226D", "NotDoubleVerticalBar": "\u2226", "NotElement": "\u2209", "NotEqual": "\u2260", "NotEqualTilde": "\u2242\u0338", "NotExists": "\u2204", "NotGreater": "\u226F", "NotGreaterEqual": "\u2271", "NotGreaterFullEqual": "\u2267\u0338", "NotGreaterGreater": "\u226B\u0338", "NotGreaterLess": "\u2279", "NotGreaterSlantEqual": "\u2A7E\u0338", "NotGreaterTilde": "\u2275", "NotHumpDownHump": "\u224E\u0338", "NotHumpEqual": "\u224F\u0338", "notin": "\u2209", "notindot": "\u22F5\u0338", "notinE": "\u22F9\u0338", "notinva": "\u2209", "notinvb": "\u22F7", "notinvc": "\u22F6", "NotLeftTriangleBar": "\u29CF\u0338", "NotLeftTriangle": "\u22EA", "NotLeftTriangleEqual": "\u22EC", "NotLess": "\u226E", "NotLessEqual": "\u2270", "NotLessGreater": "\u2278", "NotLessLess": "\u226A\u0338", "NotLessSlantEqual": "\u2A7D\u0338", "NotLessTilde": "\u2274", "NotNestedGreaterGreater": "\u2AA2\u0338", "NotNestedLessLess": "\u2AA1\u0338", "notni": "\u220C", "notniva": "\u220C", "notnivb": "\u22FE", "notnivc": "\u22FD", "NotPrecedes": "\u2280", "NotPrecedesEqual": "\u2AAF\u0338", "NotPrecedesSlantEqual": "\u22E0", "NotReverseElement": "\u220C", "NotRightTriangleBar": "\u29D0\u0338", "NotRightTriangle": "\u22EB", "NotRightTriangleEqual": "\u22ED", "NotSquareSubset": "\u228F\u0338", "NotSquareSubsetEqual": "\u22E2", "NotSquareSuperset": "\u2290\u0338", "NotSquareSupersetEqual": "\u22E3", "NotSubset": "\u2282\u20D2", "NotSubsetEqual": "\u2288", "NotSucceeds": "\u2281", "NotSucceedsEqual": "\u2AB0\u0338", "NotSucceedsSlantEqual": "\u22E1", "NotSucceedsTilde": "\u227F\u0338", "NotSuperset": "\u2283\u20D2", "NotSupersetEqual": "\u2289", "NotTilde": "\u2241", "NotTildeEqual": "\u2244", "NotTildeFullEqual": "\u2247", "NotTildeTilde": "\u2249", "NotVerticalBar": "\u2224", "nparallel": "\u2226", "npar": "\u2226", "nparsl": "\u2AFD\u20E5", "npart": "\u2202\u0338", "npolint": "\u2A14", "npr": "\u2280", "nprcue": "\u22E0", "nprec": "\u2280", "npreceq": "\u2AAF\u0338", "npre": "\u2AAF\u0338", "nrarrc": "\u2933\u0338", "nrarr": "\u219B", "nrArr": "\u21CF", "nrarrw": "\u219D\u0338", "nrightarrow": "\u219B", "nRightarrow": "\u21CF", "nrtri": "\u22EB", "nrtrie": "\u22ED", "nsc": "\u2281", "nsccue": "\u22E1", "nsce": "\u2AB0\u0338", "Nscr": "\uD835\uDCA9", "nscr": "\uD835\uDCC3", "nshortmid": "\u2224", "nshortparallel": "\u2226", "nsim": "\u2241", "nsime": "\u2244", "nsimeq": "\u2244", "nsmid": "\u2224", "nspar": "\u2226", "nsqsube": "\u22E2", "nsqsupe": "\u22E3", "nsub": "\u2284", "nsubE": "\u2AC5\u0338", "nsube": "\u2288", "nsubset": "\u2282\u20D2", "nsubseteq": "\u2288", "nsubseteqq": "\u2AC5\u0338", "nsucc": "\u2281", "nsucceq": "\u2AB0\u0338", "nsup": "\u2285", "nsupE": "\u2AC6\u0338", "nsupe": "\u2289", "nsupset": "\u2283\u20D2", "nsupseteq": "\u2289", "nsupseteqq": "\u2AC6\u0338", "ntgl": "\u2279", "Ntilde": "\u00D1", "ntilde": "\u00F1", "ntlg": "\u2278", "ntriangleleft": "\u22EA", "ntrianglelefteq": "\u22EC", "ntriangleright": "\u22EB", "ntrianglerighteq": "\u22ED", "Nu": "\u039D", "nu": "\u03BD", "num": "#", "numero": "\u2116", "numsp": "\u2007", "nvap": "\u224D\u20D2", "nvdash": "\u22AC", "nvDash": "\u22AD", "nVdash": "\u22AE", "nVDash": "\u22AF", "nvge": "\u2265\u20D2", "nvgt": ">\u20D2", "nvHarr": "\u2904", "nvinfin": "\u29DE", "nvlArr": "\u2902", "nvle": "\u2264\u20D2", "nvlt": "<\u20D2", "nvltrie": "\u22B4\u20D2", "nvrArr": "\u2903", "nvrtrie": "\u22B5\u20D2", "nvsim": "\u223C\u20D2", "nwarhk": "\u2923", "nwarr": "\u2196", "nwArr": "\u21D6", "nwarrow": "\u2196", "nwnear": "\u2927", "Oacute": "\u00D3", "oacute": "\u00F3", "oast": "\u229B", "Ocirc": "\u00D4", "ocirc": "\u00F4", "ocir": "\u229A", "Ocy": "\u041E", "ocy": "\u043E", "odash": "\u229D", "Odblac": "\u0150", "odblac": "\u0151", "odiv": "\u2A38", "odot": "\u2299", "odsold": "\u29BC", "OElig": "\u0152", "oelig": "\u0153", "ofcir": "\u29BF", "Ofr": "\uD835\uDD12", "ofr": "\uD835\uDD2C", "ogon": "\u02DB", "Ograve": "\u00D2", "ograve": "\u00F2", "ogt": "\u29C1", "ohbar": "\u29B5", "ohm": "\u03A9", "oint": "\u222E", "olarr": "\u21BA", "olcir": "\u29BE", "olcross": "\u29BB", "oline": "\u203E", "olt": "\u29C0", "Omacr": "\u014C", "omacr": "\u014D", "Omega": "\u03A9", "omega": "\u03C9", "Omicron": "\u039F", "omicron": "\u03BF", "omid": "\u29B6", "ominus": "\u2296", "Oopf": "\uD835\uDD46", "oopf": "\uD835\uDD60", "opar": "\u29B7", "OpenCurlyDoubleQuote": "\u201C", "OpenCurlyQuote": "\u2018", "operp": "\u29B9", "oplus": "\u2295", "orarr": "\u21BB", "Or": "\u2A54", "or": "\u2228", "ord": "\u2A5D", "order": "\u2134", "orderof": "\u2134", "ordf": "\u00AA", "ordm": "\u00BA", "origof": "\u22B6", "oror": "\u2A56", "orslope": "\u2A57", "orv": "\u2A5B", "oS": "\u24C8", "Oscr": "\uD835\uDCAA", "oscr": "\u2134", "Oslash": "\u00D8", "oslash": "\u00F8", "osol": "\u2298", "Otilde": "\u00D5", "otilde": "\u00F5", "otimesas": "\u2A36", "Otimes": "\u2A37", "otimes": "\u2297", "Ouml": "\u00D6", "ouml": "\u00F6", "ovbar": "\u233D", "OverBar": "\u203E", "OverBrace": "\u23DE", "OverBracket": "\u23B4", "OverParenthesis": "\u23DC", "para": "\u00B6", "parallel": "\u2225", "par": "\u2225", "parsim": "\u2AF3", "parsl": "\u2AFD", "part": "\u2202", "PartialD": "\u2202", "Pcy": "\u041F", "pcy": "\u043F", "percnt": "%", "period": ".", "permil": "\u2030", "perp": "\u22A5", "pertenk": "\u2031", "Pfr": "\uD835\uDD13", "pfr": "\uD835\uDD2D", "Phi": "\u03A6", "phi": "\u03C6", "phiv": "\u03D5", "phmmat": "\u2133", "phone": "\u260E", "Pi": "\u03A0", "pi": "\u03C0", "pitchfork": "\u22D4", "piv": "\u03D6", "planck": "\u210F", "planckh": "\u210E", "plankv": "\u210F", "plusacir": "\u2A23", "plusb": "\u229E", "pluscir": "\u2A22", "plus": "+", "plusdo": "\u2214", "plusdu": "\u2A25", "pluse": "\u2A72", "PlusMinus": "\u00B1", "plusmn": "\u00B1", "plussim": "\u2A26", "plustwo": "\u2A27", "pm": "\u00B1", "Poincareplane": "\u210C", "pointint": "\u2A15", "popf": "\uD835\uDD61", "Popf": "\u2119", "pound": "\u00A3", "prap": "\u2AB7", "Pr": "\u2ABB", "pr": "\u227A", "prcue": "\u227C", "precapprox": "\u2AB7", "prec": "\u227A", "preccurlyeq": "\u227C", "Precedes": "\u227A", "PrecedesEqual": "\u2AAF", "PrecedesSlantEqual": "\u227C", "PrecedesTilde": "\u227E", "preceq": "\u2AAF", "precnapprox": "\u2AB9", "precneqq": "\u2AB5", "precnsim": "\u22E8", "pre": "\u2AAF", "prE": "\u2AB3", "precsim": "\u227E", "prime": "\u2032", "Prime": "\u2033", "primes": "\u2119", "prnap": "\u2AB9", "prnE": "\u2AB5", "prnsim": "\u22E8", "prod": "\u220F", "Product": "\u220F", "profalar": "\u232E", "profline": "\u2312", "profsurf": "\u2313", "prop": "\u221D", "Proportional": "\u221D", "Proportion": "\u2237", "propto": "\u221D", "prsim": "\u227E", "prurel": "\u22B0", "Pscr": "\uD835\uDCAB", "pscr": "\uD835\uDCC5", "Psi": "\u03A8", "psi": "\u03C8", "puncsp": "\u2008", "Qfr": "\uD835\uDD14", "qfr": "\uD835\uDD2E", "qint": "\u2A0C", "qopf": "\uD835\uDD62", "Qopf": "\u211A", "qprime": "\u2057", "Qscr": "\uD835\uDCAC", "qscr": "\uD835\uDCC6", "quaternions": "\u210D", "quatint": "\u2A16", "quest": "?", "questeq": "\u225F", "quot": "\"", "QUOT": "\"", "rAarr": "\u21DB", "race": "\u223D\u0331", "Racute": "\u0154", "racute": "\u0155", "radic": "\u221A", "raemptyv": "\u29B3", "rang": "\u27E9", "Rang": "\u27EB", "rangd": "\u2992", "range": "\u29A5", "rangle": "\u27E9", "raquo": "\u00BB", "rarrap": "\u2975", "rarrb": "\u21E5", "rarrbfs": "\u2920", "rarrc": "\u2933", "rarr": "\u2192", "Rarr": "\u21A0", "rArr": "\u21D2", "rarrfs": "\u291E", "rarrhk": "\u21AA", "rarrlp": "\u21AC", "rarrpl": "\u2945", "rarrsim": "\u2974", "Rarrtl": "\u2916", "rarrtl": "\u21A3", "rarrw": "\u219D", "ratail": "\u291A", "rAtail": "\u291C", "ratio": "\u2236", "rationals": "\u211A", "rbarr": "\u290D", "rBarr": "\u290F", "RBarr": "\u2910", "rbbrk": "\u2773", "rbrace": "}", "rbrack": "]", "rbrke": "\u298C", "rbrksld": "\u298E", "rbrkslu": "\u2990", "Rcaron": "\u0158", "rcaron": "\u0159", "Rcedil": "\u0156", "rcedil": "\u0157", "rceil": "\u2309", "rcub": "}", "Rcy": "\u0420", "rcy": "\u0440", "rdca": "\u2937", "rdldhar": "\u2969", "rdquo": "\u201D", "rdquor": "\u201D", "rdsh": "\u21B3", "real": "\u211C", "realine": "\u211B", "realpart": "\u211C", "reals": "\u211D", "Re": "\u211C", "rect": "\u25AD", "reg": "\u00AE", "REG": "\u00AE", "ReverseElement": "\u220B", "ReverseEquilibrium": "\u21CB", "ReverseUpEquilibrium": "\u296F", "rfisht": "\u297D", "rfloor": "\u230B", "rfr": "\uD835\uDD2F", "Rfr": "\u211C", "rHar": "\u2964", "rhard": "\u21C1", "rharu": "\u21C0", "rharul": "\u296C", "Rho": "\u03A1", "rho": "\u03C1", "rhov": "\u03F1", "RightAngleBracket": "\u27E9", "RightArrowBar": "\u21E5", "rightarrow": "\u2192", "RightArrow": "\u2192", "Rightarrow": "\u21D2", "RightArrowLeftArrow": "\u21C4", "rightarrowtail": "\u21A3", "RightCeiling": "\u2309", "RightDoubleBracket": "\u27E7", "RightDownTeeVector": "\u295D", "RightDownVectorBar": "\u2955", "RightDownVector": "\u21C2", "RightFloor": "\u230B", "rightharpoondown": "\u21C1", "rightharpoonup": "\u21C0", "rightleftarrows": "\u21C4", "rightleftharpoons": "\u21CC", "rightrightarrows": "\u21C9", "rightsquigarrow": "\u219D", "RightTeeArrow": "\u21A6", "RightTee": "\u22A2", "RightTeeVector": "\u295B", "rightthreetimes": "\u22CC", "RightTriangleBar": "\u29D0", "RightTriangle": "\u22B3", "RightTriangleEqual": "\u22B5", "RightUpDownVector": "\u294F", "RightUpTeeVector": "\u295C", "RightUpVectorBar": "\u2954", "RightUpVector": "\u21BE", "RightVectorBar": "\u2953", "RightVector": "\u21C0", "ring": "\u02DA", "risingdotseq": "\u2253", "rlarr": "\u21C4", "rlhar": "\u21CC", "rlm": "\u200F", "rmoustache": "\u23B1", "rmoust": "\u23B1", "rnmid": "\u2AEE", "roang": "\u27ED", "roarr": "\u21FE", "robrk": "\u27E7", "ropar": "\u2986", "ropf": "\uD835\uDD63", "Ropf": "\u211D", "roplus": "\u2A2E", "rotimes": "\u2A35", "RoundImplies": "\u2970", "rpar": ")", "rpargt": "\u2994", "rppolint": "\u2A12", "rrarr": "\u21C9", "Rrightarrow": "\u21DB", "rsaquo": "\u203A", "rscr": "\uD835\uDCC7", "Rscr": "\u211B", "rsh": "\u21B1", "Rsh": "\u21B1", "rsqb": "]", "rsquo": "\u2019", "rsquor": "\u2019", "rthree": "\u22CC", "rtimes": "\u22CA", "rtri": "\u25B9", "rtrie": "\u22B5", "rtrif": "\u25B8", "rtriltri": "\u29CE", "RuleDelayed": "\u29F4", "ruluhar": "\u2968", "rx": "\u211E", "Sacute": "\u015A", "sacute": "\u015B", "sbquo": "\u201A", "scap": "\u2AB8", "Scaron": "\u0160", "scaron": "\u0161", "Sc": "\u2ABC", "sc": "\u227B", "sccue": "\u227D", "sce": "\u2AB0", "scE": "\u2AB4", "Scedil": "\u015E", "scedil": "\u015F", "Scirc": "\u015C", "scirc": "\u015D", "scnap": "\u2ABA", "scnE": "\u2AB6", "scnsim": "\u22E9", "scpolint": "\u2A13", "scsim": "\u227F", "Scy": "\u0421", "scy": "\u0441", "sdotb": "\u22A1", "sdot": "\u22C5", "sdote": "\u2A66", "searhk": "\u2925", "searr": "\u2198", "seArr": "\u21D8", "searrow": "\u2198", "sect": "\u00A7", "semi": ";", "seswar": "\u2929", "setminus": "\u2216", "setmn": "\u2216", "sext": "\u2736", "Sfr": "\uD835\uDD16", "sfr": "\uD835\uDD30", "sfrown": "\u2322", "sharp": "\u266F", "SHCHcy": "\u0429", "shchcy": "\u0449", "SHcy": "\u0428", "shcy": "\u0448", "ShortDownArrow": "\u2193", "ShortLeftArrow": "\u2190", "shortmid": "\u2223", "shortparallel": "\u2225", "ShortRightArrow": "\u2192", "ShortUpArrow": "\u2191", "shy": "\u00AD", "Sigma": "\u03A3", "sigma": "\u03C3", "sigmaf": "\u03C2", "sigmav": "\u03C2", "sim": "\u223C", "simdot": "\u2A6A", "sime": "\u2243", "simeq": "\u2243", "simg": "\u2A9E", "simgE": "\u2AA0", "siml": "\u2A9D", "simlE": "\u2A9F", "simne": "\u2246", "simplus": "\u2A24", "simrarr": "\u2972", "slarr": "\u2190", "SmallCircle": "\u2218", "smallsetminus": "\u2216", "smashp": "\u2A33", "smeparsl": "\u29E4", "smid": "\u2223", "smile": "\u2323", "smt": "\u2AAA", "smte": "\u2AAC", "smtes": "\u2AAC\uFE00", "SOFTcy": "\u042C", "softcy": "\u044C", "solbar": "\u233F", "solb": "\u29C4", "sol": "/", "Sopf": "\uD835\uDD4A", "sopf": "\uD835\uDD64", "spades": "\u2660", "spadesuit": "\u2660", "spar": "\u2225", "sqcap": "\u2293", "sqcaps": "\u2293\uFE00", "sqcup": "\u2294", "sqcups": "\u2294\uFE00", "Sqrt": "\u221A", "sqsub": "\u228F", "sqsube": "\u2291", "sqsubset": "\u228F", "sqsubseteq": "\u2291", "sqsup": "\u2290", "sqsupe": "\u2292", "sqsupset": "\u2290", "sqsupseteq": "\u2292", "square": "\u25A1", "Square": "\u25A1", "SquareIntersection": "\u2293", "SquareSubset": "\u228F", "SquareSubsetEqual": "\u2291", "SquareSuperset": "\u2290", "SquareSupersetEqual": "\u2292", "SquareUnion": "\u2294", "squarf": "\u25AA", "squ": "\u25A1", "squf": "\u25AA", "srarr": "\u2192", "Sscr": "\uD835\uDCAE", "sscr": "\uD835\uDCC8", "ssetmn": "\u2216", "ssmile": "\u2323", "sstarf": "\u22C6", "Star": "\u22C6", "star": "\u2606", "starf": "\u2605", "straightepsilon": "\u03F5", "straightphi": "\u03D5", "strns": "\u00AF", "sub": "\u2282", "Sub": "\u22D0", "subdot": "\u2ABD", "subE": "\u2AC5", "sube": "\u2286", "subedot": "\u2AC3", "submult": "\u2AC1", "subnE": "\u2ACB", "subne": "\u228A", "subplus": "\u2ABF", "subrarr": "\u2979", "subset": "\u2282", "Subset": "\u22D0", "subseteq": "\u2286", "subseteqq": "\u2AC5", "SubsetEqual": "\u2286", "subsetneq": "\u228A", "subsetneqq": "\u2ACB", "subsim": "\u2AC7", "subsub": "\u2AD5", "subsup": "\u2AD3", "succapprox": "\u2AB8", "succ": "\u227B", "succcurlyeq": "\u227D", "Succeeds": "\u227B", "SucceedsEqual": "\u2AB0", "SucceedsSlantEqual": "\u227D", "SucceedsTilde": "\u227F", "succeq": "\u2AB0", "succnapprox": "\u2ABA", "succneqq": "\u2AB6", "succnsim": "\u22E9", "succsim": "\u227F", "SuchThat": "\u220B", "sum": "\u2211", "Sum": "\u2211", "sung": "\u266A", "sup1": "\u00B9", "sup2": "\u00B2", "sup3": "\u00B3", "sup": "\u2283", "Sup": "\u22D1", "supdot": "\u2ABE", "supdsub": "\u2AD8", "supE": "\u2AC6", "supe": "\u2287", "supedot": "\u2AC4", "Superset": "\u2283", "SupersetEqual": "\u2287", "suphsol": "\u27C9", "suphsub": "\u2AD7", "suplarr": "\u297B", "supmult": "\u2AC2", "supnE": "\u2ACC", "supne": "\u228B", "supplus": "\u2AC0", "supset": "\u2283", "Supset": "\u22D1", "supseteq": "\u2287", "supseteqq": "\u2AC6", "supsetneq": "\u228B", "supsetneqq": "\u2ACC", "supsim": "\u2AC8", "supsub": "\u2AD4", "supsup": "\u2AD6", "swarhk": "\u2926", "swarr": "\u2199", "swArr": "\u21D9", "swarrow": "\u2199", "swnwar": "\u292A", "szlig": "\u00DF", "Tab": "\t", "target": "\u2316", "Tau": "\u03A4", "tau": "\u03C4", "tbrk": "\u23B4", "Tcaron": "\u0164", "tcaron": "\u0165", "Tcedil": "\u0162", "tcedil": "\u0163", "Tcy": "\u0422", "tcy": "\u0442", "tdot": "\u20DB", "telrec": "\u2315", "Tfr": "\uD835\uDD17", "tfr": "\uD835\uDD31", "there4": "\u2234", "therefore": "\u2234", "Therefore": "\u2234", "Theta": "\u0398", "theta": "\u03B8", "thetasym": "\u03D1", "thetav": "\u03D1", "thickapprox": "\u2248", "thicksim": "\u223C", "ThickSpace": "\u205F\u200A", "ThinSpace": "\u2009", "thinsp": "\u2009", "thkap": "\u2248", "thksim": "\u223C", "THORN": "\u00DE", "thorn": "\u00FE", "tilde": "\u02DC", "Tilde": "\u223C", "TildeEqual": "\u2243", "TildeFullEqual": "\u2245", "TildeTilde": "\u2248", "timesbar": "\u2A31", "timesb": "\u22A0", "times": "\u00D7", "timesd": "\u2A30", "tint": "\u222D", "toea": "\u2928", "topbot": "\u2336", "topcir": "\u2AF1", "top": "\u22A4", "Topf": "\uD835\uDD4B", "topf": "\uD835\uDD65", "topfork": "\u2ADA", "tosa": "\u2929", "tprime": "\u2034", "trade": "\u2122", "TRADE": "\u2122", "triangle": "\u25B5", "triangledown": "\u25BF", "triangleleft": "\u25C3", "trianglelefteq": "\u22B4", "triangleq": "\u225C", "triangleright": "\u25B9", "trianglerighteq": "\u22B5", "tridot": "\u25EC", "trie": "\u225C", "triminus": "\u2A3A", "TripleDot": "\u20DB", "triplus": "\u2A39", "trisb": "\u29CD", "tritime": "\u2A3B", "trpezium": "\u23E2", "Tscr": "\uD835\uDCAF", "tscr": "\uD835\uDCC9", "TScy": "\u0426", "tscy": "\u0446", "TSHcy": "\u040B", "tshcy": "\u045B", "Tstrok": "\u0166", "tstrok": "\u0167", "twixt": "\u226C", "twoheadleftarrow": "\u219E", "twoheadrightarrow": "\u21A0", "Uacute": "\u00DA", "uacute": "\u00FA", "uarr": "\u2191", "Uarr": "\u219F", "uArr": "\u21D1", "Uarrocir": "\u2949", "Ubrcy": "\u040E", "ubrcy": "\u045E", "Ubreve": "\u016C", "ubreve": "\u016D", "Ucirc": "\u00DB", "ucirc": "\u00FB", "Ucy": "\u0423", "ucy": "\u0443", "udarr": "\u21C5", "Udblac": "\u0170", "udblac": "\u0171", "udhar": "\u296E", "ufisht": "\u297E", "Ufr": "\uD835\uDD18", "ufr": "\uD835\uDD32", "Ugrave": "\u00D9", "ugrave": "\u00F9", "uHar": "\u2963", "uharl": "\u21BF", "uharr": "\u21BE", "uhblk": "\u2580", "ulcorn": "\u231C", "ulcorner": "\u231C", "ulcrop": "\u230F", "ultri": "\u25F8", "Umacr": "\u016A", "umacr": "\u016B", "uml": "\u00A8", "UnderBar": "_", "UnderBrace": "\u23DF", "UnderBracket": "\u23B5", "UnderParenthesis": "\u23DD", "Union": "\u22C3", "UnionPlus": "\u228E", "Uogon": "\u0172", "uogon": "\u0173", "Uopf": "\uD835\uDD4C", "uopf": "\uD835\uDD66", "UpArrowBar": "\u2912", "uparrow": "\u2191", "UpArrow": "\u2191", "Uparrow": "\u21D1", "UpArrowDownArrow": "\u21C5", "updownarrow": "\u2195", "UpDownArrow": "\u2195", "Updownarrow": "\u21D5", "UpEquilibrium": "\u296E", "upharpoonleft": "\u21BF", "upharpoonright": "\u21BE", "uplus": "\u228E", "UpperLeftArrow": "\u2196", "UpperRightArrow": "\u2197", "upsi": "\u03C5", "Upsi": "\u03D2", "upsih": "\u03D2", "Upsilon": "\u03A5", "upsilon": "\u03C5", "UpTeeArrow": "\u21A5", "UpTee": "\u22A5", "upuparrows": "\u21C8", "urcorn": "\u231D", "urcorner": "\u231D", "urcrop": "\u230E", "Uring": "\u016E", "uring": "\u016F", "urtri": "\u25F9", "Uscr": "\uD835\uDCB0", "uscr": "\uD835\uDCCA", "utdot": "\u22F0", "Utilde": "\u0168", "utilde": "\u0169", "utri": "\u25B5", "utrif": "\u25B4", "uuarr": "\u21C8", "Uuml": "\u00DC", "uuml": "\u00FC", "uwangle": "\u29A7", "vangrt": "\u299C", "varepsilon": "\u03F5", "varkappa": "\u03F0", "varnothing": "\u2205", "varphi": "\u03D5", "varpi": "\u03D6", "varpropto": "\u221D", "varr": "\u2195", "vArr": "\u21D5", "varrho": "\u03F1", "varsigma": "\u03C2", "varsubsetneq": "\u228A\uFE00", "varsubsetneqq": "\u2ACB\uFE00", "varsupsetneq": "\u228B\uFE00", "varsupsetneqq": "\u2ACC\uFE00", "vartheta": "\u03D1", "vartriangleleft": "\u22B2", "vartriangleright": "\u22B3", "vBar": "\u2AE8", "Vbar": "\u2AEB", "vBarv": "\u2AE9", "Vcy": "\u0412", "vcy": "\u0432", "vdash": "\u22A2", "vDash": "\u22A8", "Vdash": "\u22A9", "VDash": "\u22AB", "Vdashl": "\u2AE6", "veebar": "\u22BB", "vee": "\u2228", "Vee": "\u22C1", "veeeq": "\u225A", "vellip": "\u22EE", "verbar": "|", "Verbar": "\u2016", "vert": "|", "Vert": "\u2016", "VerticalBar": "\u2223", "VerticalLine": "|", "VerticalSeparator": "\u2758", "VerticalTilde": "\u2240", "VeryThinSpace": "\u200A", "Vfr": "\uD835\uDD19", "vfr": "\uD835\uDD33", "vltri": "\u22B2", "vnsub": "\u2282\u20D2", "vnsup": "\u2283\u20D2", "Vopf": "\uD835\uDD4D", "vopf": "\uD835\uDD67", "vprop": "\u221D", "vrtri": "\u22B3", "Vscr": "\uD835\uDCB1", "vscr": "\uD835\uDCCB", "vsubnE": "\u2ACB\uFE00", "vsubne": "\u228A\uFE00", "vsupnE": "\u2ACC\uFE00", "vsupne": "\u228B\uFE00", "Vvdash": "\u22AA", "vzigzag": "\u299A", "Wcirc": "\u0174", "wcirc": "\u0175", "wedbar": "\u2A5F", "wedge": "\u2227", "Wedge": "\u22C0", "wedgeq": "\u2259", "weierp": "\u2118", "Wfr": "\uD835\uDD1A", "wfr": "\uD835\uDD34", "Wopf": "\uD835\uDD4E", "wopf": "\uD835\uDD68", "wp": "\u2118", "wr": "\u2240", "wreath": "\u2240", "Wscr": "\uD835\uDCB2", "wscr": "\uD835\uDCCC", "xcap": "\u22C2", "xcirc": "\u25EF", "xcup": "\u22C3", "xdtri": "\u25BD", "Xfr": "\uD835\uDD1B", "xfr": "\uD835\uDD35", "xharr": "\u27F7", "xhArr": "\u27FA", "Xi": "\u039E", "xi": "\u03BE", "xlarr": "\u27F5", "xlArr": "\u27F8", "xmap": "\u27FC", "xnis": "\u22FB", "xodot": "\u2A00", "Xopf": "\uD835\uDD4F", "xopf": "\uD835\uDD69", "xoplus": "\u2A01", "xotime": "\u2A02", "xrarr": "\u27F6", "xrArr": "\u27F9", "Xscr": "\uD835\uDCB3", "xscr": "\uD835\uDCCD", "xsqcup": "\u2A06", "xuplus": "\u2A04", "xutri": "\u25B3", "xvee": "\u22C1", "xwedge": "\u22C0", "Yacute": "\u00DD", "yacute": "\u00FD", "YAcy": "\u042F", "yacy": "\u044F", "Ycirc": "\u0176", "ycirc": "\u0177", "Ycy": "\u042B", "ycy": "\u044B", "yen": "\u00A5", "Yfr": "\uD835\uDD1C", "yfr": "\uD835\uDD36", "YIcy": "\u0407", "yicy": "\u0457", "Yopf": "\uD835\uDD50", "yopf": "\uD835\uDD6A", "Yscr": "\uD835\uDCB4", "yscr": "\uD835\uDCCE", "YUcy": "\u042E", "yucy": "\u044E", "yuml": "\u00FF", "Yuml": "\u0178", "Zacute": "\u0179", "zacute": "\u017A", "Zcaron": "\u017D", "zcaron": "\u017E", "Zcy": "\u0417", "zcy": "\u0437", "Zdot": "\u017B", "zdot": "\u017C", "zeetrf": "\u2128", "ZeroWidthSpace": "\u200B", "Zeta": "\u0396", "zeta": "\u03B6", "zfr": "\uD835\uDD37", "Zfr": "\u2128", "ZHcy": "\u0416", "zhcy": "\u0436", "zigrarr": "\u21DD", "zopf": "\uD835\uDD6B", "Zopf": "\u2124", "Zscr": "\uD835\uDCB5", "zscr": "\uD835\uDCCF", "zwj": "\u200D", "zwnj": "\u200C" } + +},{}],53:[function(require,module,exports){ +'use strict'; + + +//////////////////////////////////////////////////////////////////////////////// +// Helpers + +// Merge objects +// +function assign(obj /*from1, from2, from3, ...*/) { + var sources = Array.prototype.slice.call(arguments, 1); + + sources.forEach(function (source) { + if (!source) { return; } + + Object.keys(source).forEach(function (key) { + obj[key] = source[key]; + }); + }); + + return obj; +} + +function _class(obj) { return Object.prototype.toString.call(obj); } +function isString(obj) { return _class(obj) === '[object String]'; } +function isObject(obj) { return _class(obj) === '[object Object]'; } +function isRegExp(obj) { return _class(obj) === '[object RegExp]'; } +function isFunction(obj) { return _class(obj) === '[object Function]'; } + + +function escapeRE(str) { return str.replace(/[.?*+^$[\]\\(){}|-]/g, '\\$&'); } + +//////////////////////////////////////////////////////////////////////////////// + + +var defaultOptions = { + fuzzyLink: true, + fuzzyEmail: true, + fuzzyIP: false +}; + + +function isOptionsObj(obj) { + return Object.keys(obj || {}).reduce(function (acc, k) { + return acc || defaultOptions.hasOwnProperty(k); + }, false); +} + + +var defaultSchemas = { + 'http:': { + validate: function (text, pos, self) { + var tail = text.slice(pos); + + if (!self.re.http) { + // compile lazily, because "host"-containing variables can change on tlds update. + self.re.http = new RegExp( + '^\\/\\/' + self.re.src_auth + self.re.src_host_port_strict + self.re.src_path, 'i' + ); + } + if (self.re.http.test(tail)) { + return tail.match(self.re.http)[0].length; + } + return 0; + } + }, + 'https:': 'http:', + 'ftp:': 'http:', + '//': { + validate: function (text, pos, self) { + var tail = text.slice(pos); + + if (!self.re.no_http) { + // compile lazily, because "host"-containing variables can change on tlds update. + self.re.no_http = new RegExp( + '^' + + self.re.src_auth + + // Don't allow single-level domains, because of false positives like '//test' + // with code comments + '(?:localhost|(?:(?:' + self.re.src_domain + ')\\.)+' + self.re.src_domain_root + ')' + + self.re.src_port + + self.re.src_host_terminator + + self.re.src_path, + + 'i' + ); + } + + if (self.re.no_http.test(tail)) { + // should not be `://` & `///`, that protects from errors in protocol name + if (pos >= 3 && text[pos - 3] === ':') { return 0; } + if (pos >= 3 && text[pos - 3] === '/') { return 0; } + return tail.match(self.re.no_http)[0].length; + } + return 0; + } + }, + 'mailto:': { + validate: function (text, pos, self) { + var tail = text.slice(pos); + + if (!self.re.mailto) { + self.re.mailto = new RegExp( + '^' + self.re.src_email_name + '@' + self.re.src_host_strict, 'i' + ); + } + if (self.re.mailto.test(tail)) { + return tail.match(self.re.mailto)[0].length; + } + return 0; + } + } +}; + +/*eslint-disable max-len*/ + +// RE pattern for 2-character tlds (autogenerated by ./support/tlds_2char_gen.js) +var tlds_2ch_src_re = 'a[cdefgilmnoqrstuwxz]|b[abdefghijmnorstvwyz]|c[acdfghiklmnoruvwxyz]|d[ejkmoz]|e[cegrstu]|f[ijkmor]|g[abdefghilmnpqrstuwy]|h[kmnrtu]|i[delmnoqrst]|j[emop]|k[eghimnprwyz]|l[abcikrstuvy]|m[acdeghklmnopqrstuvwxyz]|n[acefgilopruz]|om|p[aefghklmnrstwy]|qa|r[eosuw]|s[abcdeghijklmnortuvxyz]|t[cdfghjklmnortvwz]|u[agksyz]|v[aceginu]|w[fs]|y[et]|z[amw]'; + +// DON'T try to make PRs with changes. Extend TLDs with LinkifyIt.tlds() instead +var tlds_default = 'biz|com|edu|gov|net|org|pro|web|xxx|aero|asia|coop|info|museum|name|shop|рф'.split('|'); + +/*eslint-enable max-len*/ + +//////////////////////////////////////////////////////////////////////////////// + +function resetScanCache(self) { + self.__index__ = -1; + self.__text_cache__ = ''; +} + +function createValidator(re) { + return function (text, pos) { + var tail = text.slice(pos); + + if (re.test(tail)) { + return tail.match(re)[0].length; + } + return 0; + }; +} + +function createNormalizer() { + return function (match, self) { + self.normalize(match); + }; +} + +// Schemas compiler. Build regexps. +// +function compile(self) { + + // Load & clone RE patterns. + var re = self.re = require('./lib/re')(self.__opts__); + + // Define dynamic patterns + var tlds = self.__tlds__.slice(); + + self.onCompile(); + + if (!self.__tlds_replaced__) { + tlds.push(tlds_2ch_src_re); + } + tlds.push(re.src_xn); + + re.src_tlds = tlds.join('|'); + + function untpl(tpl) { return tpl.replace('%TLDS%', re.src_tlds); } + + re.email_fuzzy = RegExp(untpl(re.tpl_email_fuzzy), 'i'); + re.link_fuzzy = RegExp(untpl(re.tpl_link_fuzzy), 'i'); + re.link_no_ip_fuzzy = RegExp(untpl(re.tpl_link_no_ip_fuzzy), 'i'); + re.host_fuzzy_test = RegExp(untpl(re.tpl_host_fuzzy_test), 'i'); + + // + // Compile each schema + // + + var aliases = []; + + self.__compiled__ = {}; // Reset compiled data + + function schemaError(name, val) { + throw new Error('(LinkifyIt) Invalid schema "' + name + '": ' + val); + } + + Object.keys(self.__schemas__).forEach(function (name) { + var val = self.__schemas__[name]; + + // skip disabled methods + if (val === null) { return; } + + var compiled = { validate: null, link: null }; + + self.__compiled__[name] = compiled; + + if (isObject(val)) { + if (isRegExp(val.validate)) { + compiled.validate = createValidator(val.validate); + } else if (isFunction(val.validate)) { + compiled.validate = val.validate; + } else { + schemaError(name, val); + } + + if (isFunction(val.normalize)) { + compiled.normalize = val.normalize; + } else if (!val.normalize) { + compiled.normalize = createNormalizer(); + } else { + schemaError(name, val); + } + + return; + } + + if (isString(val)) { + aliases.push(name); + return; + } + + schemaError(name, val); + }); + + // + // Compile postponed aliases + // + + aliases.forEach(function (alias) { + if (!self.__compiled__[self.__schemas__[alias]]) { + // Silently fail on missed schemas to avoid errons on disable. + // schemaError(alias, self.__schemas__[alias]); + return; + } + + self.__compiled__[alias].validate = + self.__compiled__[self.__schemas__[alias]].validate; + self.__compiled__[alias].normalize = + self.__compiled__[self.__schemas__[alias]].normalize; + }); + + // + // Fake record for guessed links + // + self.__compiled__[''] = { validate: null, normalize: createNormalizer() }; + + // + // Build schema condition + // + var slist = Object.keys(self.__compiled__) + .filter(function (name) { + // Filter disabled & fake schemas + return name.length > 0 && self.__compiled__[name]; + }) + .map(escapeRE) + .join('|'); + // (?!_) cause 1.5x slowdown + self.re.schema_test = RegExp('(^|(?!_)(?:[><\uff5c]|' + re.src_ZPCc + '))(' + slist + ')', 'i'); + self.re.schema_search = RegExp('(^|(?!_)(?:[><\uff5c]|' + re.src_ZPCc + '))(' + slist + ')', 'ig'); + + self.re.pretest = RegExp( + '(' + self.re.schema_test.source + ')|(' + self.re.host_fuzzy_test.source + ')|@', + 'i' + ); + + // + // Cleanup + // + + resetScanCache(self); +} + +/** + * class Match + * + * Match result. Single element of array, returned by [[LinkifyIt#match]] + **/ +function Match(self, shift) { + var start = self.__index__, + end = self.__last_index__, + text = self.__text_cache__.slice(start, end); + + /** + * Match#schema -> String + * + * Prefix (protocol) for matched string. + **/ + this.schema = self.__schema__.toLowerCase(); + /** + * Match#index -> Number + * + * First position of matched string. + **/ + this.index = start + shift; + /** + * Match#lastIndex -> Number + * + * Next position after matched string. + **/ + this.lastIndex = end + shift; + /** + * Match#raw -> String + * + * Matched string. + **/ + this.raw = text; + /** + * Match#text -> String + * + * Notmalized text of matched string. + **/ + this.text = text; + /** + * Match#url -> String + * + * Normalized url of matched string. + **/ + this.url = text; +} + +function createMatch(self, shift) { + var match = new Match(self, shift); + + self.__compiled__[match.schema].normalize(match, self); + + return match; +} + + +/** + * class LinkifyIt + **/ + +/** + * new LinkifyIt(schemas, options) + * - schemas (Object): Optional. Additional schemas to validate (prefix/validator) + * - options (Object): { fuzzyLink|fuzzyEmail|fuzzyIP: true|false } + * + * Creates new linkifier instance with optional additional schemas. + * Can be called without `new` keyword for convenience. + * + * By default understands: + * + * - `http(s)://...` , `ftp://...`, `mailto:...` & `//...` links + * - "fuzzy" links and emails (example.com, foo@bar.com). + * + * `schemas` is an object, where each key/value describes protocol/rule: + * + * - __key__ - link prefix (usually, protocol name with `:` at the end, `skype:` + * for example). `linkify-it` makes shure that prefix is not preceeded with + * alphanumeric char and symbols. Only whitespaces and punctuation allowed. + * - __value__ - rule to check tail after link prefix + * - _String_ - just alias to existing rule + * - _Object_ + * - _validate_ - validator function (should return matched length on success), + * or `RegExp`. + * - _normalize_ - optional function to normalize text & url of matched result + * (for example, for @twitter mentions). + * + * `options`: + * + * - __fuzzyLink__ - recognige URL-s without `http(s):` prefix. Default `true`. + * - __fuzzyIP__ - allow IPs in fuzzy links above. Can conflict with some texts + * like version numbers. Default `false`. + * - __fuzzyEmail__ - recognize emails without `mailto:` prefix. + * + **/ +function LinkifyIt(schemas, options) { + if (!(this instanceof LinkifyIt)) { + return new LinkifyIt(schemas, options); + } + + if (!options) { + if (isOptionsObj(schemas)) { + options = schemas; + schemas = {}; + } + } + + this.__opts__ = assign({}, defaultOptions, options); + + // Cache last tested result. Used to skip repeating steps on next `match` call. + this.__index__ = -1; + this.__last_index__ = -1; // Next scan position + this.__schema__ = ''; + this.__text_cache__ = ''; + + this.__schemas__ = assign({}, defaultSchemas, schemas); + this.__compiled__ = {}; + + this.__tlds__ = tlds_default; + this.__tlds_replaced__ = false; + + this.re = {}; + + compile(this); +} + + +/** chainable + * LinkifyIt#add(schema, definition) + * - schema (String): rule name (fixed pattern prefix) + * - definition (String|RegExp|Object): schema definition + * + * Add new rule definition. See constructor description for details. + **/ +LinkifyIt.prototype.add = function add(schema, definition) { + this.__schemas__[schema] = definition; + compile(this); + return this; +}; + + +/** chainable + * LinkifyIt#set(options) + * - options (Object): { fuzzyLink|fuzzyEmail|fuzzyIP: true|false } + * + * Set recognition options for links without schema. + **/ +LinkifyIt.prototype.set = function set(options) { + this.__opts__ = assign(this.__opts__, options); + return this; +}; + + +/** + * LinkifyIt#test(text) -> Boolean + * + * Searches linkifiable pattern and returns `true` on success or `false` on fail. + **/ +LinkifyIt.prototype.test = function test(text) { + // Reset scan cache + this.__text_cache__ = text; + this.__index__ = -1; + + if (!text.length) { return false; } + + var m, ml, me, len, shift, next, re, tld_pos, at_pos; + + // try to scan for link with schema - that's the most simple rule + if (this.re.schema_test.test(text)) { + re = this.re.schema_search; + re.lastIndex = 0; + while ((m = re.exec(text)) !== null) { + len = this.testSchemaAt(text, m[2], re.lastIndex); + if (len) { + this.__schema__ = m[2]; + this.__index__ = m.index + m[1].length; + this.__last_index__ = m.index + m[0].length + len; + break; + } + } + } + + if (this.__opts__.fuzzyLink && this.__compiled__['http:']) { + // guess schemaless links + tld_pos = text.search(this.re.host_fuzzy_test); + if (tld_pos >= 0) { + // if tld is located after found link - no need to check fuzzy pattern + if (this.__index__ < 0 || tld_pos < this.__index__) { + if ((ml = text.match(this.__opts__.fuzzyIP ? this.re.link_fuzzy : this.re.link_no_ip_fuzzy)) !== null) { + + shift = ml.index + ml[1].length; + + if (this.__index__ < 0 || shift < this.__index__) { + this.__schema__ = ''; + this.__index__ = shift; + this.__last_index__ = ml.index + ml[0].length; + } + } + } + } + } + + if (this.__opts__.fuzzyEmail && this.__compiled__['mailto:']) { + // guess schemaless emails + at_pos = text.indexOf('@'); + if (at_pos >= 0) { + // We can't skip this check, because this cases are possible: + // 192.168.1.1@gmail.com, my.in@example.com + if ((me = text.match(this.re.email_fuzzy)) !== null) { + + shift = me.index + me[1].length; + next = me.index + me[0].length; + + if (this.__index__ < 0 || shift < this.__index__ || + (shift === this.__index__ && next > this.__last_index__)) { + this.__schema__ = 'mailto:'; + this.__index__ = shift; + this.__last_index__ = next; + } + } + } + } + + return this.__index__ >= 0; +}; + + +/** + * LinkifyIt#pretest(text) -> Boolean + * + * Very quick check, that can give false positives. Returns true if link MAY BE + * can exists. Can be used for speed optimization, when you need to check that + * link NOT exists. + **/ +LinkifyIt.prototype.pretest = function pretest(text) { + return this.re.pretest.test(text); +}; + + +/** + * LinkifyIt#testSchemaAt(text, name, position) -> Number + * - text (String): text to scan + * - name (String): rule (schema) name + * - position (Number): text offset to check from + * + * Similar to [[LinkifyIt#test]] but checks only specific protocol tail exactly + * at given position. Returns length of found pattern (0 on fail). + **/ +LinkifyIt.prototype.testSchemaAt = function testSchemaAt(text, schema, pos) { + // If not supported schema check requested - terminate + if (!this.__compiled__[schema.toLowerCase()]) { + return 0; + } + return this.__compiled__[schema.toLowerCase()].validate(text, pos, this); +}; + + +/** + * LinkifyIt#match(text) -> Array|null + * + * Returns array of found link descriptions or `null` on fail. We strongly + * recommend to use [[LinkifyIt#test]] first, for best speed. + * + * ##### Result match description + * + * - __schema__ - link schema, can be empty for fuzzy links, or `//` for + * protocol-neutral links. + * - __index__ - offset of matched text + * - __lastIndex__ - index of next char after mathch end + * - __raw__ - matched text + * - __text__ - normalized text + * - __url__ - link, generated from matched text + **/ +LinkifyIt.prototype.match = function match(text) { + var shift = 0, result = []; + + // Try to take previous element from cache, if .test() called before + if (this.__index__ >= 0 && this.__text_cache__ === text) { + result.push(createMatch(this, shift)); + shift = this.__last_index__; + } + + // Cut head if cache was used + var tail = shift ? text.slice(shift) : text; + + // Scan string until end reached + while (this.test(tail)) { + result.push(createMatch(this, shift)); + + tail = tail.slice(this.__last_index__); + shift += this.__last_index__; + } + + if (result.length) { + return result; + } + + return null; +}; + + +/** chainable + * LinkifyIt#tlds(list [, keepOld]) -> this + * - list (Array): list of tlds + * - keepOld (Boolean): merge with current list if `true` (`false` by default) + * + * Load (or merge) new tlds list. Those are user for fuzzy links (without prefix) + * to avoid false positives. By default this algorythm used: + * + * - hostname with any 2-letter root zones are ok. + * - biz|com|edu|gov|net|org|pro|web|xxx|aero|asia|coop|info|museum|name|shop|рф + * are ok. + * - encoded (`xn--...`) root zones are ok. + * + * If list is replaced, then exact match for 2-chars root zones will be checked. + **/ +LinkifyIt.prototype.tlds = function tlds(list, keepOld) { + list = Array.isArray(list) ? list : [ list ]; + + if (!keepOld) { + this.__tlds__ = list.slice(); + this.__tlds_replaced__ = true; + compile(this); + return this; + } + + this.__tlds__ = this.__tlds__.concat(list) + .sort() + .filter(function (el, idx, arr) { + return el !== arr[idx - 1]; + }) + .reverse(); + + compile(this); + return this; +}; + +/** + * LinkifyIt#normalize(match) + * + * Default normalizer (if schema does not define it's own). + **/ +LinkifyIt.prototype.normalize = function normalize(match) { + + // Do minimal possible changes by default. Need to collect feedback prior + // to move forward https://github.com/markdown-it/linkify-it/issues/1 + + if (!match.schema) { match.url = 'http://' + match.url; } + + if (match.schema === 'mailto:' && !/^mailto:/i.test(match.url)) { + match.url = 'mailto:' + match.url; + } +}; + + +/** + * LinkifyIt#onCompile() + * + * Override to modify basic RegExp-s. + **/ +LinkifyIt.prototype.onCompile = function onCompile() { +}; + + +module.exports = LinkifyIt; + +},{"./lib/re":54}],54:[function(require,module,exports){ +'use strict'; + + +module.exports = function (opts) { + var re = {}; + + // Use direct extract instead of `regenerate` to reduse browserified size + re.src_Any = require('uc.micro/properties/Any/regex').source; + re.src_Cc = require('uc.micro/categories/Cc/regex').source; + re.src_Z = require('uc.micro/categories/Z/regex').source; + re.src_P = require('uc.micro/categories/P/regex').source; + + // \p{\Z\P\Cc\CF} (white spaces + control + format + punctuation) + re.src_ZPCc = [ re.src_Z, re.src_P, re.src_Cc ].join('|'); + + // \p{\Z\Cc} (white spaces + control) + re.src_ZCc = [ re.src_Z, re.src_Cc ].join('|'); + + // Experimental. List of chars, completely prohibited in links + // because can separate it from other part of text + var text_separators = '[><\uff5c]'; + + // All possible word characters (everything without punctuation, spaces & controls) + // Defined via punctuation & spaces to save space + // Should be something like \p{\L\N\S\M} (\w but without `_`) + re.src_pseudo_letter = '(?:(?!' + text_separators + '|' + re.src_ZPCc + ')' + re.src_Any + ')'; + // The same as abothe but without [0-9] + // var src_pseudo_letter_non_d = '(?:(?![0-9]|' + src_ZPCc + ')' + src_Any + ')'; + + //////////////////////////////////////////////////////////////////////////////// + + re.src_ip4 = + + '(?:(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)'; + + // Prohibit any of "@/[]()" in user/pass to avoid wrong domain fetch. + re.src_auth = '(?:(?:(?!' + re.src_ZCc + '|[@/\\[\\]()]).)+@)?'; + + re.src_port = + + '(?::(?:6(?:[0-4]\\d{3}|5(?:[0-4]\\d{2}|5(?:[0-2]\\d|3[0-5])))|[1-5]?\\d{1,4}))?'; + + re.src_host_terminator = + + '(?=$|' + text_separators + '|' + re.src_ZPCc + ')(?!-|_|:\\d|\\.-|\\.(?!$|' + re.src_ZPCc + '))'; + + re.src_path = + + '(?:' + + '[/?#]' + + '(?:' + + '(?!' + re.src_ZCc + '|' + text_separators + '|[()[\\]{}.,"\'?!\\-]).|' + + '\\[(?:(?!' + re.src_ZCc + '|\\]).)*\\]|' + + '\\((?:(?!' + re.src_ZCc + '|[)]).)*\\)|' + + '\\{(?:(?!' + re.src_ZCc + '|[}]).)*\\}|' + + '\\"(?:(?!' + re.src_ZCc + '|["]).)+\\"|' + + "\\'(?:(?!" + re.src_ZCc + "|[']).)+\\'|" + + "\\'(?=" + re.src_pseudo_letter + '|[-]).|' + // allow `I'm_king` if no pair found + '\\.{2,4}[a-zA-Z0-9%/]|' + // github has ... in commit range links, + // google has .... in links (issue #66) + // Restrict to + // - english + // - percent-encoded + // - parts of file path + // until more examples found. + '\\.(?!' + re.src_ZCc + '|[.]).|' + + (opts && opts['---'] ? + '\\-(?!--(?:[^-]|$))(?:-*)|' // `---` => long dash, terminate + : + '\\-+|' + ) + + '\\,(?!' + re.src_ZCc + ').|' + // allow `,,,` in paths + '\\!(?!' + re.src_ZCc + '|[!]).|' + + '\\?(?!' + re.src_ZCc + '|[?]).' + + ')+' + + '|\\/' + + ')?'; + + // Allow anything in markdown spec, forbid quote (") at the first position + // because emails enclosed in quotes are far more common + re.src_email_name = + + '[\\-;:&=\\+\\$,\\.a-zA-Z0-9_][\\-;:&=\\+\\$,\\"\\.a-zA-Z0-9_]*'; + + re.src_xn = + + 'xn--[a-z0-9\\-]{1,59}'; + + // More to read about domain names + // http://serverfault.com/questions/638260/ + + re.src_domain_root = + + // Allow letters & digits (http://test1) + '(?:' + + re.src_xn + + '|' + + re.src_pseudo_letter + '{1,63}' + + ')'; + + re.src_domain = + + '(?:' + + re.src_xn + + '|' + + '(?:' + re.src_pseudo_letter + ')' + + '|' + + '(?:' + re.src_pseudo_letter + '(?:-|' + re.src_pseudo_letter + '){0,61}' + re.src_pseudo_letter + ')' + + ')'; + + re.src_host = + + '(?:' + + // Don't need IP check, because digits are already allowed in normal domain names + // src_ip4 + + // '|' + + '(?:(?:(?:' + re.src_domain + ')\\.)*' + re.src_domain/*_root*/ + ')' + + ')'; + + re.tpl_host_fuzzy = + + '(?:' + + re.src_ip4 + + '|' + + '(?:(?:(?:' + re.src_domain + ')\\.)+(?:%TLDS%))' + + ')'; + + re.tpl_host_no_ip_fuzzy = + + '(?:(?:(?:' + re.src_domain + ')\\.)+(?:%TLDS%))'; + + re.src_host_strict = + + re.src_host + re.src_host_terminator; + + re.tpl_host_fuzzy_strict = + + re.tpl_host_fuzzy + re.src_host_terminator; + + re.src_host_port_strict = + + re.src_host + re.src_port + re.src_host_terminator; + + re.tpl_host_port_fuzzy_strict = + + re.tpl_host_fuzzy + re.src_port + re.src_host_terminator; + + re.tpl_host_port_no_ip_fuzzy_strict = + + re.tpl_host_no_ip_fuzzy + re.src_port + re.src_host_terminator; + + + //////////////////////////////////////////////////////////////////////////////// + // Main rules + + // Rude test fuzzy links by host, for quick deny + re.tpl_host_fuzzy_test = + + 'localhost|www\\.|\\.\\d{1,3}\\.|(?:\\.(?:%TLDS%)(?:' + re.src_ZPCc + '|>|$))'; + + re.tpl_email_fuzzy = + + '(^|' + text_separators + '|"|\\(|' + re.src_ZCc + ')' + + '(' + re.src_email_name + '@' + re.tpl_host_fuzzy_strict + ')'; + + re.tpl_link_fuzzy = + // Fuzzy link can't be prepended with .:/\- and non punctuation. + // but can start with > (markdown blockquote) + '(^|(?![.:/\\-_@])(?:[$+<=>^`|\uff5c]|' + re.src_ZPCc + '))' + + '((?![$+<=>^`|\uff5c])' + re.tpl_host_port_fuzzy_strict + re.src_path + ')'; + + re.tpl_link_no_ip_fuzzy = + // Fuzzy link can't be prepended with .:/\- and non punctuation. + // but can start with > (markdown blockquote) + '(^|(?![.:/\\-_@])(?:[$+<=>^`|\uff5c]|' + re.src_ZPCc + '))' + + '((?![$+<=>^`|\uff5c])' + re.tpl_host_port_no_ip_fuzzy_strict + re.src_path + ')'; + + return re; +}; + +},{"uc.micro/categories/Cc/regex":61,"uc.micro/categories/P/regex":63,"uc.micro/categories/Z/regex":64,"uc.micro/properties/Any/regex":66}],55:[function(require,module,exports){ + +'use strict'; + + +/* eslint-disable no-bitwise */ + +var decodeCache = {}; + +function getDecodeCache(exclude) { + var i, ch, cache = decodeCache[exclude]; + if (cache) { return cache; } + + cache = decodeCache[exclude] = []; + + for (i = 0; i < 128; i++) { + ch = String.fromCharCode(i); + cache.push(ch); + } + + for (i = 0; i < exclude.length; i++) { + ch = exclude.charCodeAt(i); + cache[ch] = '%' + ('0' + ch.toString(16).toUpperCase()).slice(-2); + } + + return cache; +} + + +// Decode percent-encoded string. +// +function decode(string, exclude) { + var cache; + + if (typeof exclude !== 'string') { + exclude = decode.defaultChars; + } + + cache = getDecodeCache(exclude); + + return string.replace(/(%[a-f0-9]{2})+/gi, function(seq) { + var i, l, b1, b2, b3, b4, chr, + result = ''; + + for (i = 0, l = seq.length; i < l; i += 3) { + b1 = parseInt(seq.slice(i + 1, i + 3), 16); + + if (b1 < 0x80) { + result += cache[b1]; + continue; + } + + if ((b1 & 0xE0) === 0xC0 && (i + 3 < l)) { + // 110xxxxx 10xxxxxx + b2 = parseInt(seq.slice(i + 4, i + 6), 16); + + if ((b2 & 0xC0) === 0x80) { + chr = ((b1 << 6) & 0x7C0) | (b2 & 0x3F); + + if (chr < 0x80) { + result += '\ufffd\ufffd'; + } else { + result += String.fromCharCode(chr); + } + + i += 3; + continue; + } + } + + if ((b1 & 0xF0) === 0xE0 && (i + 6 < l)) { + // 1110xxxx 10xxxxxx 10xxxxxx + b2 = parseInt(seq.slice(i + 4, i + 6), 16); + b3 = parseInt(seq.slice(i + 7, i + 9), 16); + + if ((b2 & 0xC0) === 0x80 && (b3 & 0xC0) === 0x80) { + chr = ((b1 << 12) & 0xF000) | ((b2 << 6) & 0xFC0) | (b3 & 0x3F); + + if (chr < 0x800 || (chr >= 0xD800 && chr <= 0xDFFF)) { + result += '\ufffd\ufffd\ufffd'; + } else { + result += String.fromCharCode(chr); + } + + i += 6; + continue; + } + } + + if ((b1 & 0xF8) === 0xF0 && (i + 9 < l)) { + // 111110xx 10xxxxxx 10xxxxxx 10xxxxxx + b2 = parseInt(seq.slice(i + 4, i + 6), 16); + b3 = parseInt(seq.slice(i + 7, i + 9), 16); + b4 = parseInt(seq.slice(i + 10, i + 12), 16); + + if ((b2 & 0xC0) === 0x80 && (b3 & 0xC0) === 0x80 && (b4 & 0xC0) === 0x80) { + chr = ((b1 << 18) & 0x1C0000) | ((b2 << 12) & 0x3F000) | ((b3 << 6) & 0xFC0) | (b4 & 0x3F); + + if (chr < 0x10000 || chr > 0x10FFFF) { + result += '\ufffd\ufffd\ufffd\ufffd'; + } else { + chr -= 0x10000; + result += String.fromCharCode(0xD800 + (chr >> 10), 0xDC00 + (chr & 0x3FF)); + } + + i += 9; + continue; + } + } + + result += '\ufffd'; + } + + return result; + }); +} + + +decode.defaultChars = ';/?:@&=+$,#'; +decode.componentChars = ''; + + +module.exports = decode; + +},{}],56:[function(require,module,exports){ + +'use strict'; + + +var encodeCache = {}; + + +// Create a lookup array where anything but characters in `chars` string +// and alphanumeric chars is percent-encoded. +// +function getEncodeCache(exclude) { + var i, ch, cache = encodeCache[exclude]; + if (cache) { return cache; } + + cache = encodeCache[exclude] = []; + + for (i = 0; i < 128; i++) { + ch = String.fromCharCode(i); + + if (/^[0-9a-z]$/i.test(ch)) { + // always allow unencoded alphanumeric characters + cache.push(ch); + } else { + cache.push('%' + ('0' + i.toString(16).toUpperCase()).slice(-2)); + } + } + + for (i = 0; i < exclude.length; i++) { + cache[exclude.charCodeAt(i)] = exclude[i]; + } + + return cache; +} + + +// Encode unsafe characters with percent-encoding, skipping already +// encoded sequences. +// +// - string - string to encode +// - exclude - list of characters to ignore (in addition to a-zA-Z0-9) +// - keepEscaped - don't encode '%' in a correct escape sequence (default: true) +// +function encode(string, exclude, keepEscaped) { + var i, l, code, nextCode, cache, + result = ''; + + if (typeof exclude !== 'string') { + // encode(string, keepEscaped) + keepEscaped = exclude; + exclude = encode.defaultChars; + } + + if (typeof keepEscaped === 'undefined') { + keepEscaped = true; + } + + cache = getEncodeCache(exclude); + + for (i = 0, l = string.length; i < l; i++) { + code = string.charCodeAt(i); + + if (keepEscaped && code === 0x25 /* % */ && i + 2 < l) { + if (/^[0-9a-f]{2}$/i.test(string.slice(i + 1, i + 3))) { + result += string.slice(i, i + 3); + i += 2; + continue; + } + } + + if (code < 128) { + result += cache[code]; + continue; + } + + if (code >= 0xD800 && code <= 0xDFFF) { + if (code >= 0xD800 && code <= 0xDBFF && i + 1 < l) { + nextCode = string.charCodeAt(i + 1); + if (nextCode >= 0xDC00 && nextCode <= 0xDFFF) { + result += encodeURIComponent(string[i] + string[i + 1]); + i++; + continue; + } + } + result += '%EF%BF%BD'; + continue; + } + + result += encodeURIComponent(string[i]); + } + + return result; +} + +encode.defaultChars = ";/?:@&=+$,-_.!~*'()#"; +encode.componentChars = "-_.!~*'()"; + + +module.exports = encode; + +},{}],57:[function(require,module,exports){ + +'use strict'; + + +module.exports = function format(url) { + var result = ''; + + result += url.protocol || ''; + result += url.slashes ? '//' : ''; + result += url.auth ? url.auth + '@' : ''; + + if (url.hostname && url.hostname.indexOf(':') !== -1) { + // ipv6 address + result += '[' + url.hostname + ']'; + } else { + result += url.hostname || ''; + } + + result += url.port ? ':' + url.port : ''; + result += url.pathname || ''; + result += url.search || ''; + result += url.hash || ''; + + return result; +}; + +},{}],58:[function(require,module,exports){ +'use strict'; + + +module.exports.encode = require('./encode'); +module.exports.decode = require('./decode'); +module.exports.format = require('./format'); +module.exports.parse = require('./parse'); + +},{"./decode":55,"./encode":56,"./format":57,"./parse":59}],59:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; + +// +// Changes from joyent/node: +// +// 1. No leading slash in paths, +// e.g. in `url.parse('http://foo?bar')` pathname is ``, not `/` +// +// 2. Backslashes are not replaced with slashes, +// so `http:\\example.org\` is treated like a relative path +// +// 3. Trailing colon is treated like a part of the path, +// i.e. in `http://example.org:foo` pathname is `:foo` +// +// 4. Nothing is URL-encoded in the resulting object, +// (in joyent/node some chars in auth and paths are encoded) +// +// 5. `url.parse()` does not have `parseQueryString` argument +// +// 6. Removed extraneous result properties: `host`, `path`, `query`, etc., +// which can be constructed using other parts of the url. +// + + +function Url() { + this.protocol = null; + this.slashes = null; + this.auth = null; + this.port = null; + this.hostname = null; + this.hash = null; + this.search = null; + this.pathname = null; +} + +// Reference: RFC 3986, RFC 1808, RFC 2396 + +// define these here so at least they only have to be +// compiled once on the first module load. +var protocolPattern = /^([a-z0-9.+-]+:)/i, + portPattern = /:[0-9]*$/, + + // Special case for a simple path URL + simplePathPattern = /^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/, + + // RFC 2396: characters reserved for delimiting URLs. + // We actually just auto-escape these. + delims = [ '<', '>', '"', '`', ' ', '\r', '\n', '\t' ], + + // RFC 2396: characters not allowed for various reasons. + unwise = [ '{', '}', '|', '\\', '^', '`' ].concat(delims), + + // Allowed by RFCs, but cause of XSS attacks. Always escape these. + autoEscape = [ '\'' ].concat(unwise), + // Characters that are never ever allowed in a hostname. + // Note that any invalid chars are also handled, but these + // are the ones that are *expected* to be seen, so we fast-path + // them. + nonHostChars = [ '%', '/', '?', ';', '#' ].concat(autoEscape), + hostEndingChars = [ '/', '?', '#' ], + hostnameMaxLen = 255, + hostnamePartPattern = /^[+a-z0-9A-Z_-]{0,63}$/, + hostnamePartStart = /^([+a-z0-9A-Z_-]{0,63})(.*)$/, + // protocols that can allow "unsafe" and "unwise" chars. + /* eslint-disable no-script-url */ + // protocols that never have a hostname. + hostlessProtocol = { + 'javascript': true, + 'javascript:': true + }, + // protocols that always contain a // bit. + slashedProtocol = { + 'http': true, + 'https': true, + 'ftp': true, + 'gopher': true, + 'file': true, + 'http:': true, + 'https:': true, + 'ftp:': true, + 'gopher:': true, + 'file:': true + }; + /* eslint-enable no-script-url */ + +function urlParse(url, slashesDenoteHost) { + if (url && url instanceof Url) { return url; } + + var u = new Url(); + u.parse(url, slashesDenoteHost); + return u; +} + +Url.prototype.parse = function(url, slashesDenoteHost) { + var i, l, lowerProto, hec, slashes, + rest = url; + + // trim before proceeding. + // This is to support parse stuff like " http://foo.com \n" + rest = rest.trim(); + + if (!slashesDenoteHost && url.split('#').length === 1) { + // Try fast path regexp + var simplePath = simplePathPattern.exec(rest); + if (simplePath) { + this.pathname = simplePath[1]; + if (simplePath[2]) { + this.search = simplePath[2]; + } + return this; + } + } + + var proto = protocolPattern.exec(rest); + if (proto) { + proto = proto[0]; + lowerProto = proto.toLowerCase(); + this.protocol = proto; + rest = rest.substr(proto.length); + } + + // figure out if it's got a host + // user@server is *always* interpreted as a hostname, and url + // resolution will treat //foo/bar as host=foo,path=bar because that's + // how the browser resolves relative URLs. + if (slashesDenoteHost || proto || rest.match(/^\/\/[^@\/]+@[^@\/]+/)) { + slashes = rest.substr(0, 2) === '//'; + if (slashes && !(proto && hostlessProtocol[proto])) { + rest = rest.substr(2); + this.slashes = true; + } + } + + if (!hostlessProtocol[proto] && + (slashes || (proto && !slashedProtocol[proto]))) { + + // there's a hostname. + // the first instance of /, ?, ;, or # ends the host. + // + // If there is an @ in the hostname, then non-host chars *are* allowed + // to the left of the last @ sign, unless some host-ending character + // comes *before* the @-sign. + // URLs are obnoxious. + // + // ex: + // http://a@b@c/ => user:a@b host:c + // http://a@b?@c => user:a host:c path:/?@c + + // v0.12 TODO(isaacs): This is not quite how Chrome does things. + // Review our test case against browsers more comprehensively. + + // find the first instance of any hostEndingChars + var hostEnd = -1; + for (i = 0; i < hostEndingChars.length; i++) { + hec = rest.indexOf(hostEndingChars[i]); + if (hec !== -1 && (hostEnd === -1 || hec < hostEnd)) { + hostEnd = hec; + } + } + + // at this point, either we have an explicit point where the + // auth portion cannot go past, or the last @ char is the decider. + var auth, atSign; + if (hostEnd === -1) { + // atSign can be anywhere. + atSign = rest.lastIndexOf('@'); + } else { + // atSign must be in auth portion. + // http://a@b/c@d => host:b auth:a path:/c@d + atSign = rest.lastIndexOf('@', hostEnd); + } + + // Now we have a portion which is definitely the auth. + // Pull that off. + if (atSign !== -1) { + auth = rest.slice(0, atSign); + rest = rest.slice(atSign + 1); + this.auth = auth; + } + + // the host is the remaining to the left of the first non-host char + hostEnd = -1; + for (i = 0; i < nonHostChars.length; i++) { + hec = rest.indexOf(nonHostChars[i]); + if (hec !== -1 && (hostEnd === -1 || hec < hostEnd)) { + hostEnd = hec; + } + } + // if we still have not hit it, then the entire thing is a host. + if (hostEnd === -1) { + hostEnd = rest.length; + } + + if (rest[hostEnd - 1] === ':') { hostEnd--; } + var host = rest.slice(0, hostEnd); + rest = rest.slice(hostEnd); + + // pull out port. + this.parseHost(host); + + // we've indicated that there is a hostname, + // so even if it's empty, it has to be present. + this.hostname = this.hostname || ''; + + // if hostname begins with [ and ends with ] + // assume that it's an IPv6 address. + var ipv6Hostname = this.hostname[0] === '[' && + this.hostname[this.hostname.length - 1] === ']'; + + // validate a little. + if (!ipv6Hostname) { + var hostparts = this.hostname.split(/\./); + for (i = 0, l = hostparts.length; i < l; i++) { + var part = hostparts[i]; + if (!part) { continue; } + if (!part.match(hostnamePartPattern)) { + var newpart = ''; + for (var j = 0, k = part.length; j < k; j++) { + if (part.charCodeAt(j) > 127) { + // we replace non-ASCII char with a temporary placeholder + // we need this to make sure size of hostname is not + // broken by replacing non-ASCII by nothing + newpart += 'x'; + } else { + newpart += part[j]; + } + } + // we test again with ASCII char only + if (!newpart.match(hostnamePartPattern)) { + var validParts = hostparts.slice(0, i); + var notHost = hostparts.slice(i + 1); + var bit = part.match(hostnamePartStart); + if (bit) { + validParts.push(bit[1]); + notHost.unshift(bit[2]); + } + if (notHost.length) { + rest = notHost.join('.') + rest; + } + this.hostname = validParts.join('.'); + break; + } + } + } + } + + if (this.hostname.length > hostnameMaxLen) { + this.hostname = ''; + } + + // strip [ and ] from the hostname + // the host field still retains them, though + if (ipv6Hostname) { + this.hostname = this.hostname.substr(1, this.hostname.length - 2); + } + } + + // chop off from the tail first. + var hash = rest.indexOf('#'); + if (hash !== -1) { + // got a fragment string. + this.hash = rest.substr(hash); + rest = rest.slice(0, hash); + } + var qm = rest.indexOf('?'); + if (qm !== -1) { + this.search = rest.substr(qm); + rest = rest.slice(0, qm); + } + if (rest) { this.pathname = rest; } + if (slashedProtocol[lowerProto] && + this.hostname && !this.pathname) { + this.pathname = ''; + } + + return this; +}; + +Url.prototype.parseHost = function(host) { + var port = portPattern.exec(host); + if (port) { + port = port[0]; + if (port !== ':') { + this.port = port.substr(1); + } + host = host.substr(0, host.length - port.length); + } + if (host) { this.hostname = host; } +}; + +module.exports = urlParse; + +},{}],60:[function(require,module,exports){ +(function (global){ +/*! https://mths.be/punycode v1.4.1 by @mathias */ +;(function(root) { + + /** Detect free variables */ + var freeExports = typeof exports == 'object' && exports && + !exports.nodeType && exports; + var freeModule = typeof module == 'object' && module && + !module.nodeType && module; + var freeGlobal = typeof global == 'object' && global; + if ( + freeGlobal.global === freeGlobal || + freeGlobal.window === freeGlobal || + freeGlobal.self === freeGlobal + ) { + root = freeGlobal; + } + + /** + * The `punycode` object. + * @name punycode + * @type Object + */ + var punycode, + + /** Highest positive signed 32-bit float value */ + maxInt = 2147483647, // aka. 0x7FFFFFFF or 2^31-1 + + /** Bootstring parameters */ + base = 36, + tMin = 1, + tMax = 26, + skew = 38, + damp = 700, + initialBias = 72, + initialN = 128, // 0x80 + delimiter = '-', // '\x2D' + + /** Regular expressions */ + regexPunycode = /^xn--/, + regexNonASCII = /[^\x20-\x7E]/, // unprintable ASCII chars + non-ASCII chars + regexSeparators = /[\x2E\u3002\uFF0E\uFF61]/g, // RFC 3490 separators + + /** Error messages */ + errors = { + 'overflow': 'Overflow: input needs wider integers to process', + 'not-basic': 'Illegal input >= 0x80 (not a basic code point)', + 'invalid-input': 'Invalid input' + }, + + /** Convenience shortcuts */ + baseMinusTMin = base - tMin, + floor = Math.floor, + stringFromCharCode = String.fromCharCode, + + /** Temporary variable */ + key; + + /*--------------------------------------------------------------------------*/ + + /** + * A generic error utility function. + * @private + * @param {String} type The error type. + * @returns {Error} Throws a `RangeError` with the applicable error message. + */ + function error(type) { + throw new RangeError(errors[type]); + } + + /** + * A generic `Array#map` utility function. + * @private + * @param {Array} array The array to iterate over. + * @param {Function} callback The function that gets called for every array + * item. + * @returns {Array} A new array of values returned by the callback function. + */ + function map(array, fn) { + var length = array.length; + var result = []; + while (length--) { + result[length] = fn(array[length]); + } + return result; + } + + /** + * A simple `Array#map`-like wrapper to work with domain name strings or email + * addresses. + * @private + * @param {String} domain The domain name or email address. + * @param {Function} callback The function that gets called for every + * character. + * @returns {Array} A new string of characters returned by the callback + * function. + */ + function mapDomain(string, fn) { + var parts = string.split('@'); + var result = ''; + if (parts.length > 1) { + // In email addresses, only the domain name should be punycoded. Leave + // the local part (i.e. everything up to `@`) intact. + result = parts[0] + '@'; + string = parts[1]; + } + // Avoid `split(regex)` for IE8 compatibility. See #17. + string = string.replace(regexSeparators, '\x2E'); + var labels = string.split('.'); + var encoded = map(labels, fn).join('.'); + return result + encoded; + } + + /** + * Creates an array containing the numeric code points of each Unicode + * character in the string. While JavaScript uses UCS-2 internally, + * this function will convert a pair of surrogate halves (each of which + * UCS-2 exposes as separate characters) into a single code point, + * matching UTF-16. + * @see `punycode.ucs2.encode` + * @see + * @memberOf punycode.ucs2 + * @name decode + * @param {String} string The Unicode input string (UCS-2). + * @returns {Array} The new array of code points. + */ + function ucs2decode(string) { + var output = [], + counter = 0, + length = string.length, + value, + extra; + while (counter < length) { + value = string.charCodeAt(counter++); + if (value >= 0xD800 && value <= 0xDBFF && counter < length) { + // high surrogate, and there is a next character + extra = string.charCodeAt(counter++); + if ((extra & 0xFC00) == 0xDC00) { // low surrogate + output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000); + } else { + // unmatched surrogate; only append this code unit, in case the next + // code unit is the high surrogate of a surrogate pair + output.push(value); + counter--; + } + } else { + output.push(value); + } + } + return output; + } + + /** + * Creates a string based on an array of numeric code points. + * @see `punycode.ucs2.decode` + * @memberOf punycode.ucs2 + * @name encode + * @param {Array} codePoints The array of numeric code points. + * @returns {String} The new Unicode string (UCS-2). + */ + function ucs2encode(array) { + return map(array, function(value) { + var output = ''; + if (value > 0xFFFF) { + value -= 0x10000; + output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800); + value = 0xDC00 | value & 0x3FF; + } + output += stringFromCharCode(value); + return output; + }).join(''); + } + + /** + * Converts a basic code point into a digit/integer. + * @see `digitToBasic()` + * @private + * @param {Number} codePoint The basic numeric code point value. + * @returns {Number} The numeric value of a basic code point (for use in + * representing integers) in the range `0` to `base - 1`, or `base` if + * the code point does not represent a value. + */ + function basicToDigit(codePoint) { + if (codePoint - 48 < 10) { + return codePoint - 22; + } + if (codePoint - 65 < 26) { + return codePoint - 65; + } + if (codePoint - 97 < 26) { + return codePoint - 97; + } + return base; + } + + /** + * Converts a digit/integer into a basic code point. + * @see `basicToDigit()` + * @private + * @param {Number} digit The numeric value of a basic code point. + * @returns {Number} The basic code point whose value (when used for + * representing integers) is `digit`, which needs to be in the range + * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is + * used; else, the lowercase form is used. The behavior is undefined + * if `flag` is non-zero and `digit` has no uppercase form. + */ + function digitToBasic(digit, flag) { + // 0..25 map to ASCII a..z or A..Z + // 26..35 map to ASCII 0..9 + return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5); + } + + /** + * Bias adaptation function as per section 3.4 of RFC 3492. + * https://tools.ietf.org/html/rfc3492#section-3.4 + * @private + */ + function adapt(delta, numPoints, firstTime) { + var k = 0; + delta = firstTime ? floor(delta / damp) : delta >> 1; + delta += floor(delta / numPoints); + for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) { + delta = floor(delta / baseMinusTMin); + } + return floor(k + (baseMinusTMin + 1) * delta / (delta + skew)); + } + + /** + * Converts a Punycode string of ASCII-only symbols to a string of Unicode + * symbols. + * @memberOf punycode + * @param {String} input The Punycode string of ASCII-only symbols. + * @returns {String} The resulting string of Unicode symbols. + */ + function decode(input) { + // Don't use UCS-2 + var output = [], + inputLength = input.length, + out, + i = 0, + n = initialN, + bias = initialBias, + basic, + j, + index, + oldi, + w, + k, + digit, + t, + /** Cached calculation results */ + baseMinusT; + + // Handle the basic code points: let `basic` be the number of input code + // points before the last delimiter, or `0` if there is none, then copy + // the first basic code points to the output. + + basic = input.lastIndexOf(delimiter); + if (basic < 0) { + basic = 0; + } + + for (j = 0; j < basic; ++j) { + // if it's not a basic code point + if (input.charCodeAt(j) >= 0x80) { + error('not-basic'); + } + output.push(input.charCodeAt(j)); + } + + // Main decoding loop: start just after the last delimiter if any basic code + // points were copied; start at the beginning otherwise. + + for (index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) { + + // `index` is the index of the next character to be consumed. + // Decode a generalized variable-length integer into `delta`, + // which gets added to `i`. The overflow checking is easier + // if we increase `i` as we go, then subtract off its starting + // value at the end to obtain `delta`. + for (oldi = i, w = 1, k = base; /* no condition */; k += base) { + + if (index >= inputLength) { + error('invalid-input'); + } + + digit = basicToDigit(input.charCodeAt(index++)); + + if (digit >= base || digit > floor((maxInt - i) / w)) { + error('overflow'); + } + + i += digit * w; + t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); + + if (digit < t) { + break; + } + + baseMinusT = base - t; + if (w > floor(maxInt / baseMinusT)) { + error('overflow'); + } + + w *= baseMinusT; + + } + + out = output.length + 1; + bias = adapt(i - oldi, out, oldi == 0); + + // `i` was supposed to wrap around from `out` to `0`, + // incrementing `n` each time, so we'll fix that now: + if (floor(i / out) > maxInt - n) { + error('overflow'); + } + + n += floor(i / out); + i %= out; + + // Insert `n` at position `i` of the output + output.splice(i++, 0, n); + + } + + return ucs2encode(output); + } + + /** + * Converts a string of Unicode symbols (e.g. a domain name label) to a + * Punycode string of ASCII-only symbols. + * @memberOf punycode + * @param {String} input The string of Unicode symbols. + * @returns {String} The resulting Punycode string of ASCII-only symbols. + */ + function encode(input) { + var n, + delta, + handledCPCount, + basicLength, + bias, + j, + m, + q, + k, + t, + currentValue, + output = [], + /** `inputLength` will hold the number of code points in `input`. */ + inputLength, + /** Cached calculation results */ + handledCPCountPlusOne, + baseMinusT, + qMinusT; + + // Convert the input in UCS-2 to Unicode + input = ucs2decode(input); + + // Cache the length + inputLength = input.length; + + // Initialize the state + n = initialN; + delta = 0; + bias = initialBias; + + // Handle the basic code points + for (j = 0; j < inputLength; ++j) { + currentValue = input[j]; + if (currentValue < 0x80) { + output.push(stringFromCharCode(currentValue)); + } + } + + handledCPCount = basicLength = output.length; + + // `handledCPCount` is the number of code points that have been handled; + // `basicLength` is the number of basic code points. + + // Finish the basic string - if it is not empty - with a delimiter + if (basicLength) { + output.push(delimiter); + } + + // Main encoding loop: + while (handledCPCount < inputLength) { + + // All non-basic code points < n have been handled already. Find the next + // larger one: + for (m = maxInt, j = 0; j < inputLength; ++j) { + currentValue = input[j]; + if (currentValue >= n && currentValue < m) { + m = currentValue; + } + } + + // Increase `delta` enough to advance the decoder's state to , + // but guard against overflow + handledCPCountPlusOne = handledCPCount + 1; + if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) { + error('overflow'); + } + + delta += (m - n) * handledCPCountPlusOne; + n = m; + + for (j = 0; j < inputLength; ++j) { + currentValue = input[j]; + + if (currentValue < n && ++delta > maxInt) { + error('overflow'); + } + + if (currentValue == n) { + // Represent delta as a generalized variable-length integer + for (q = delta, k = base; /* no condition */; k += base) { + t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); + if (q < t) { + break; + } + qMinusT = q - t; + baseMinusT = base - t; + output.push( + stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0)) + ); + q = floor(qMinusT / baseMinusT); + } + + output.push(stringFromCharCode(digitToBasic(q, 0))); + bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength); + delta = 0; + ++handledCPCount; + } + } + + ++delta; + ++n; + + } + return output.join(''); + } + + /** + * Converts a Punycode string representing a domain name or an email address + * to Unicode. Only the Punycoded parts of the input will be converted, i.e. + * it doesn't matter if you call it on a string that has already been + * converted to Unicode. + * @memberOf punycode + * @param {String} input The Punycoded domain name or email address to + * convert to Unicode. + * @returns {String} The Unicode representation of the given Punycode + * string. + */ + function toUnicode(input) { + return mapDomain(input, function(string) { + return regexPunycode.test(string) + ? decode(string.slice(4).toLowerCase()) + : string; + }); + } + + /** + * Converts a Unicode string representing a domain name or an email address to + * Punycode. Only the non-ASCII parts of the domain name will be converted, + * i.e. it doesn't matter if you call it with a domain that's already in + * ASCII. + * @memberOf punycode + * @param {String} input The domain name or email address to convert, as a + * Unicode string. + * @returns {String} The Punycode representation of the given domain name or + * email address. + */ + function toASCII(input) { + return mapDomain(input, function(string) { + return regexNonASCII.test(string) + ? 'xn--' + encode(string) + : string; + }); + } + + /*--------------------------------------------------------------------------*/ + + /** Define the public API */ + punycode = { + /** + * A string representing the current Punycode.js version number. + * @memberOf punycode + * @type String + */ + 'version': '1.4.1', + /** + * An object of methods to convert from JavaScript's internal character + * representation (UCS-2) to Unicode code points, and back. + * @see + * @memberOf punycode + * @type Object + */ + 'ucs2': { + 'decode': ucs2decode, + 'encode': ucs2encode + }, + 'decode': decode, + 'encode': encode, + 'toASCII': toASCII, + 'toUnicode': toUnicode + }; + + /** Expose `punycode` */ + // Some AMD build optimizers, like r.js, check for specific condition patterns + // like the following: + if ( + typeof define == 'function' && + typeof define.amd == 'object' && + define.amd + ) { + define('punycode', function() { + return punycode; + }); + } else if (freeExports && freeModule) { + if (module.exports == freeExports) { + // in Node.js, io.js, or RingoJS v0.8.0+ + freeModule.exports = punycode; + } else { + // in Narwhal or RingoJS v0.7.0- + for (key in punycode) { + punycode.hasOwnProperty(key) && (freeExports[key] = punycode[key]); + } + } + } else { + // in Rhino or a web browser + root.punycode = punycode; + } + +}(this)); + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],61:[function(require,module,exports){ +module.exports=/[\0-\x1F\x7F-\x9F]/ +},{}],62:[function(require,module,exports){ +module.exports=/[\xAD\u0600-\u0605\u061C\u06DD\u070F\u08E2\u180E\u200B-\u200F\u202A-\u202E\u2060-\u2064\u2066-\u206F\uFEFF\uFFF9-\uFFFB]|\uD804[\uDCBD\uDCCD]|\uD82F[\uDCA0-\uDCA3]|\uD834[\uDD73-\uDD7A]|\uDB40[\uDC01\uDC20-\uDC7F]/ +},{}],63:[function(require,module,exports){ +module.exports=/[!-#%-\*,-\/:;\?@\[-\]_\{\}\xA1\xA7\xAB\xB6\xB7\xBB\xBF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061E\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u09FD\u0A76\u0AF0\u0C84\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166D\u166E\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2010-\u2027\u2030-\u2043\u2045-\u2051\u2053-\u205E\u207D\u207E\u208D\u208E\u2308-\u230B\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E4E\u3001-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65]|\uD800[\uDD00-\uDD02\uDF9F\uDFD0]|\uD801\uDD6F|\uD802[\uDC57\uDD1F\uDD3F\uDE50-\uDE58\uDE7F\uDEF0-\uDEF6\uDF39-\uDF3F\uDF99-\uDF9C]|\uD803[\uDF55-\uDF59]|\uD804[\uDC47-\uDC4D\uDCBB\uDCBC\uDCBE-\uDCC1\uDD40-\uDD43\uDD74\uDD75\uDDC5-\uDDC8\uDDCD\uDDDB\uDDDD-\uDDDF\uDE38-\uDE3D\uDEA9]|\uD805[\uDC4B-\uDC4F\uDC5B\uDC5D\uDCC6\uDDC1-\uDDD7\uDE41-\uDE43\uDE60-\uDE6C\uDF3C-\uDF3E]|\uD806[\uDC3B\uDE3F-\uDE46\uDE9A-\uDE9C\uDE9E-\uDEA2]|\uD807[\uDC41-\uDC45\uDC70\uDC71\uDEF7\uDEF8]|\uD809[\uDC70-\uDC74]|\uD81A[\uDE6E\uDE6F\uDEF5\uDF37-\uDF3B\uDF44]|\uD81B[\uDE97-\uDE9A]|\uD82F\uDC9F|\uD836[\uDE87-\uDE8B]|\uD83A[\uDD5E\uDD5F]/ +},{}],64:[function(require,module,exports){ +module.exports=/[ \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000]/ +},{}],65:[function(require,module,exports){ +'use strict'; + +exports.Any = require('./properties/Any/regex'); +exports.Cc = require('./categories/Cc/regex'); +exports.Cf = require('./categories/Cf/regex'); +exports.P = require('./categories/P/regex'); +exports.Z = require('./categories/Z/regex'); + +},{"./categories/Cc/regex":61,"./categories/Cf/regex":62,"./categories/P/regex":63,"./categories/Z/regex":64,"./properties/Any/regex":66}],66:[function(require,module,exports){ +module.exports=/[\0-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/ +},{}],67:[function(require,module,exports){ +'use strict'; + + +module.exports = require('./lib/'); + +},{"./lib/":9}]},{},[67])(67) +}); +const markdownit = define() +export default markdownit \ No newline at end of file diff --git a/app/assets/frontends/blogger/js/page.js b/app/assets/frontends/blogger/js/page.js new file mode 100644 index 0000000000..e528228e5f --- /dev/null +++ b/app/assets/frontends/blogger/js/page.js @@ -0,0 +1,58 @@ +import { DrivePosts } from './posts.js' +import { DriveFiles } from './files.js' +import { h } from './util.js' +import MarkdownIt from './markdown-it.js' + +const md = MarkdownIt({ + html: false, + xhtmlOut: false, + breaks: true, + langPrefix: 'language-', + linkify: false, + typographer: true, + quotes: '“”‘’', + highlight: undefined +}) + +export class DrivePage extends HTMLElement { + constructor () { + super() + this.load() + } + + async load () { + var {pathname} = location + this.stat = await hyperdrive.self.stat(pathname).catch(e => undefined) + + if (this.stat && this.stat.isFile()) { + if (this.stat.metadata.title) { + this.append(h('h2', {}, this.stat.metadata.title)) + } + if (/\.(png|jpe?g|gif)/i.test(pathname)) { + this.append(h('img', {className: 'content', src: pathname, title: pathname.split('/').pop()})) + } else if (/\.(mp4|webm|mov)/i.test(pathname)) { + this.append(h('video', {className: 'content', controls: true}, h('source', {src: pathname}))) + } else if (/\.(mp3|ogg)/i.test(pathname)) { + this.append(h('audio', {className: 'content', controls: true}, h('source', {src: pathname}))) + } else { + let text = await hyperdrive.self.readFile(pathname) + if (pathname.endsWith('.md')) { + let content = h('div', {className: 'content'}) + content.innerHTML = md.render(text) + this.append(content) + } else { + this.append(h('pre', {className: 'content'}, text)) + } + } + } else if (!this.stat && pathname !== '/') { + // 404 todo + } else { + if (pathname === '/' || pathname.startsWith('/posts/')) { + this.append(new DrivePosts()) + } else { + this.append(new DriveFiles()) + } + } + } +} +customElements.define('drive-page', DrivePage) \ No newline at end of file diff --git a/app/assets/frontends/blogger/js/posts.js b/app/assets/frontends/blogger/js/posts.js new file mode 100644 index 0000000000..bb2dde61fa --- /dev/null +++ b/app/assets/frontends/blogger/js/posts.js @@ -0,0 +1,52 @@ +import { h } from './util.js' + +export class DrivePosts extends HTMLElement { + constructor () { + super() + this.load() + } + + get mode () { + var {pathname} = location + if (pathname === '/') return 'recent' + if (pathname === '/posts/') return 'all' + if (pathname.startsWith('/posts/')) return 'topic' + } + + get topic () { + var {pathname} = location + return pathname.split('/').filter(Boolean)[1] + } + + async load () { + this.info = await hyperdrive.self.getInfo() + var path = this.mode === 'topic' ? `/posts/${this.topic}/*` : '/posts/*/*' + this.posts = await hyperdrive.self.query({ + type: 'file', + path, + sort: 'ctime', + reverse: true, + limit: this.mode === 'recent' ? 10 : undefined + }) + this.render() + } + + render () { + for (let post of this.posts) { + let href = post.stat.metadata.href || post.url + let link = h('h3', {}, h('a', {href}, post.stat.metadata.title)) + + let topic = post.path.split('/').slice(-2, -1)[0] + let topicA = h('a', {className:' topic', href: `/posts/${topic}/`}, topic) + let details = h('div', {className: 'details'}, [ + '[', topicA, `] Posted ${post.stat.ctime.toLocaleString()}` + ]) + + this.append(h('div', {className: 'post'}, [ + link, + details + ])) + } + } +} +customElements.define('drive-posts', DrivePosts) \ No newline at end of file diff --git a/app/assets/frontends/blogger/js/util.js b/app/assets/frontends/blogger/js/util.js new file mode 100644 index 0000000000..ac8950d4d6 --- /dev/null +++ b/app/assets/frontends/blogger/js/util.js @@ -0,0 +1,24 @@ +export function h (tag, attributes = {}, children) { + var el = document.createElement(tag) + for (let k in attributes) { + if (k === 'className') { + el.className = attributes[k] + } else if (attributes[k] === true) { + el.setAttribute(k, '') + } else { + el.setAttribute(k, attributes[k]) + } + } + if (children) { + for (let child of (Array.isArray(children) ? children : [children])) { + el.append(child) + } + } + return el +} + +export function moveChildren ({src, dst}) { + while (src.childNodes.length > 0) { + dst.appendChild(src.childNodes[0]) + } +} \ No newline at end of file diff --git a/app/assets/frontends/blogger/ui.css b/app/assets/frontends/blogger/ui.css new file mode 100644 index 0000000000..35f8896d52 --- /dev/null +++ b/app/assets/frontends/blogger/ui.css @@ -0,0 +1,203 @@ +body { + --blue: #2864dc; + + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + margin: 0; +} + +a { + color: var(--blue); + text-decoration: none; + cursor: pointer; +} + +a:hover { + text-decoration: underline; +} + +h1, h2, h3, h4, h5 { + margin: 0.83rem 0; +} + +h1 { + font-size: 3em; +} + +h2 { + font-size: 2.4em; +} + +h3 { + font-size: 1.7em; +} + +hr { + border: 0; + border-top: 1px solid #ccd; +} + +.btn { + display: inline-block; + border-radius: 4px; + padding: 4px 8px; + text-decoration: none; + font-size: 11px; + letter-spacing: 0.25px; + font-weight: 400; + transition: background 0.1s ease; + margin-left: 4px; + background: #fff; + border: 1px solid #ccd; +} + +.btn.pressed { + box-shadow: inset 0px 0 5px rgba(0, 0, 0, 0.25); +} + +.btn:hover { + background: #f3f3f7; + text-decoration: none; +} + +.btn.primary { + background: #2864dc; + color: #fff; + border-color: #2864dc; +} + +.btn.primary:hover { + background: #2357bf; +} + +main { + margin: 0 auto; + padding: 0 10px; + max-width: 800px; +} + +drive-admin { + display: block; + background: #f3f3f7; + padding: 1em; + margin-bottom: 1.5em; + text-align: right; +} + +drive-header { + display: flex; + align-items: center; + margin: 1em 0; +} + +drive-header img { + display: block; + object-fit: cover; + width: 100px; + height: 100px; + border-radius: 50%; +} + +drive-header .details { + flex: 1; +} + +drive-header .links { + align-self: baseline; + padding: 14px 0; +} + +drive-header h1, +drive-header p { + margin: 0.5rem 0; + line-height: 1; +} + +drive-breadcrumbs { + display: flex; + font-size: 12px; + padding: 14px 0; +} + +drive-breadcrumbs span, +drive-breadcrumbs a { + margin-right: 6px; + color: #778; +} + +drive-page .content { + padding: 14px 0; + max-width: 100%; +} + +drive-page pre.content { + margin: 0; + white-space: pre-wrap; + font-size: 15px; +} + +drive-posts { + display: block; + padding-bottom: 14px; +} + +drive-posts .post { + padding: 0.7rem 0; + letter-spacing: 0.5px; +} + +drive-posts h3 { + margin: 0; +} + +drive-posts .post .details { + color: #778; + font-size: 12px; +} + +drive-posts .post .details a.topic { + color: #778; +} + +drive-files { + display: block; +} + +drive-files .entry { + line-height: 30px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.content > :first-child { + margin-top: 0; +} +.content h1, +.content h2, +.content h3, +.content h4, +.content h5 { margin: 1.5rem 0; } +.content h1 { font-size: 2em; } +.content h2 { font-size: 1.7em; } +.content h3 { font-size: 1.4em; } +.content h4 { font-size: 1.3em; } +.content h5 { font-size: 1.1em; } +.content pre { + background: #f3f3f7; + padding: 1em; + overflow: auto; +} +.content p, +.content ul, +.content ol { + line-height: 1.5; +} +.content table { + margin: 1em 0; +} +.content blockquote { + border-left: 10px solid #f3f3f7; + margin: 1em 0; + padding: 1px 1.5em; + color: #667; +} \ No newline at end of file diff --git a/app/assets/frontends/blogger/ui.html b/app/assets/frontends/blogger/ui.html new file mode 100644 index 0000000000..d0e77eb944 --- /dev/null +++ b/app/assets/frontends/blogger/ui.html @@ -0,0 +1,16 @@ + + + + + + + + +
    + + + +
    + + + \ No newline at end of file diff --git a/app/assets/frontends/blogger/ui.js b/app/assets/frontends/blogger/ui.js new file mode 100644 index 0000000000..366d39510c --- /dev/null +++ b/app/assets/frontends/blogger/ui.js @@ -0,0 +1,4 @@ +import './js/admin.js' +import './js/header.js' +import './js/breadcrumbs.js' +import './js/page.js' \ No newline at end of file diff --git a/app/assets/img/default-favicon.png b/app/assets/img/default-favicon.png deleted file mode 100755 index 7f1997a76c..0000000000 Binary files a/app/assets/img/default-favicon.png and /dev/null differ diff --git a/app/assets/img/default-screenshot.jpg b/app/assets/img/default-screenshot.jpg new file mode 100644 index 0000000000..fbddac5583 Binary files /dev/null and b/app/assets/img/default-screenshot.jpg differ diff --git a/app/assets/img/default-theme-thumb.jpg b/app/assets/img/default-theme-thumb.jpg new file mode 100644 index 0000000000..9cdc7566da Binary files /dev/null and b/app/assets/img/default-theme-thumb.jpg differ diff --git a/app/assets/img/drive-types/files-64.png b/app/assets/img/drive-types/files-64.png new file mode 100644 index 0000000000..6457c96f48 Binary files /dev/null and b/app/assets/img/drive-types/files-64.png differ diff --git a/app/assets/img/drive-types/files.png b/app/assets/img/drive-types/files.png new file mode 100644 index 0000000000..508bf96920 Binary files /dev/null and b/app/assets/img/drive-types/files.png differ diff --git a/app/assets/img/drive-types/group-64.png b/app/assets/img/drive-types/group-64.png new file mode 100644 index 0000000000..b96352b320 Binary files /dev/null and b/app/assets/img/drive-types/group-64.png differ diff --git a/app/assets/img/drive-types/group.png b/app/assets/img/drive-types/group.png new file mode 100644 index 0000000000..d782c47b7e Binary files /dev/null and b/app/assets/img/drive-types/group.png differ diff --git a/app/assets/img/drive-types/module-64.png b/app/assets/img/drive-types/module-64.png new file mode 100644 index 0000000000..c29262f991 Binary files /dev/null and b/app/assets/img/drive-types/module-64.png differ diff --git a/app/assets/img/drive-types/module.png b/app/assets/img/drive-types/module.png new file mode 100644 index 0000000000..0d50b8ae22 Binary files /dev/null and b/app/assets/img/drive-types/module.png differ diff --git a/app/assets/img/drive-types/other-64.png b/app/assets/img/drive-types/other-64.png new file mode 100644 index 0000000000..4701251781 Binary files /dev/null and b/app/assets/img/drive-types/other-64.png differ diff --git a/app/assets/img/drive-types/other.png b/app/assets/img/drive-types/other.png new file mode 100644 index 0000000000..3a8edc6e23 Binary files /dev/null and b/app/assets/img/drive-types/other.png differ diff --git a/app/assets/img/drive-types/user-64.png b/app/assets/img/drive-types/user-64.png new file mode 100644 index 0000000000..9f4e36277a Binary files /dev/null and b/app/assets/img/drive-types/user-64.png differ diff --git a/app/assets/img/drive-types/user.png b/app/assets/img/drive-types/user.png new file mode 100644 index 0000000000..efc1b0b8ac Binary files /dev/null and b/app/assets/img/drive-types/user.png differ diff --git a/app/assets/img/drive-types/website-64.png b/app/assets/img/drive-types/website-64.png new file mode 100644 index 0000000000..13b0952fe5 Binary files /dev/null and b/app/assets/img/drive-types/website-64.png differ diff --git a/app/assets/img/drive-types/website.png b/app/assets/img/drive-types/website.png new file mode 100644 index 0000000000..42f1ac0bf3 Binary files /dev/null and b/app/assets/img/drive-types/website.png differ diff --git a/app/assets/img/drive-types/webterm-sh-cmd-pkg-64.png b/app/assets/img/drive-types/webterm-sh-cmd-pkg-64.png new file mode 100644 index 0000000000..8af1424aa8 Binary files /dev/null and b/app/assets/img/drive-types/webterm-sh-cmd-pkg-64.png differ diff --git a/app/assets/img/drive-types/webterm-sh-cmd-pkg.png b/app/assets/img/drive-types/webterm-sh-cmd-pkg.png new file mode 100644 index 0000000000..5efa13cde8 Binary files /dev/null and b/app/assets/img/drive-types/webterm-sh-cmd-pkg.png differ diff --git a/app/assets/img/favicons/addressbook.png b/app/assets/img/favicons/addressbook.png deleted file mode 100755 index 42b0a8903a..0000000000 Binary files a/app/assets/img/favicons/addressbook.png and /dev/null differ diff --git a/app/assets/img/favicons/bookmarks.png b/app/assets/img/favicons/bookmarks.png index 108e6592a8..95de6964ce 100644 Binary files a/app/assets/img/favicons/bookmarks.png and b/app/assets/img/favicons/bookmarks.png differ diff --git a/app/assets/img/favicons/cloud-peers.png b/app/assets/img/favicons/cloud-peers.png new file mode 100644 index 0000000000..e996ec0fce Binary files /dev/null and b/app/assets/img/favicons/cloud-peers.png differ diff --git a/app/assets/img/favicons/default.png b/app/assets/img/favicons/default.png new file mode 100644 index 0000000000..7dc6d7d729 Binary files /dev/null and b/app/assets/img/favicons/default.png differ diff --git a/app/assets/img/favicons/start.png b/app/assets/img/favicons/desktop.png similarity index 100% rename from app/assets/img/favicons/start.png rename to app/assets/img/favicons/desktop.png diff --git a/app/assets/img/favicons/drive.png b/app/assets/img/favicons/drive.png new file mode 100644 index 0000000000..0b67679da8 Binary files /dev/null and b/app/assets/img/favicons/drive.png differ diff --git a/app/assets/img/favicons/explorer.png b/app/assets/img/favicons/explorer.png new file mode 100644 index 0000000000..fd9537db4b Binary files /dev/null and b/app/assets/img/favicons/explorer.png differ diff --git a/app/assets/img/favicons/folder.png b/app/assets/img/favicons/folder.png new file mode 100644 index 0000000000..381123228a Binary files /dev/null and b/app/assets/img/favicons/folder.png differ diff --git a/app/assets/img/favicons/home.png b/app/assets/img/favicons/home.png new file mode 100644 index 0000000000..a5993b7c50 Binary files /dev/null and b/app/assets/img/favicons/home.png differ diff --git a/app/assets/img/favicons/library.png b/app/assets/img/favicons/library.png index b317285ea6..8cdcf774e4 100644 Binary files a/app/assets/img/favicons/library.png and b/app/assets/img/favicons/library.png differ diff --git a/app/assets/img/favicons/news-feed.png b/app/assets/img/favicons/news-feed.png new file mode 100644 index 0000000000..9d75d5d85d Binary files /dev/null and b/app/assets/img/favicons/news-feed.png differ diff --git a/app/assets/img/favicons/people.png b/app/assets/img/favicons/people.png new file mode 100644 index 0000000000..a905fd193f Binary files /dev/null and b/app/assets/img/favicons/people.png differ diff --git a/app/assets/img/favicons/search.png b/app/assets/img/favicons/search.png index 525ee64323..12cf6ee21f 100644 Binary files a/app/assets/img/favicons/search.png and b/app/assets/img/favicons/search.png differ diff --git a/app/assets/img/favicons/services.png b/app/assets/img/favicons/services.png deleted file mode 100644 index f7341f8c3e..0000000000 Binary files a/app/assets/img/favicons/services.png and /dev/null differ diff --git a/app/assets/img/favicons/trash.png b/app/assets/img/favicons/trash.png new file mode 100644 index 0000000000..8b77e8629f Binary files /dev/null and b/app/assets/img/favicons/trash.png differ diff --git a/app/assets/img/favicons/watchlist.png b/app/assets/img/favicons/watchlist.png deleted file mode 100644 index 044ccee522..0000000000 Binary files a/app/assets/img/favicons/watchlist.png and /dev/null differ diff --git a/app/assets/img/favicons/websites.png b/app/assets/img/favicons/websites.png old mode 100755 new mode 100644 index 53d8dcc13f..91c9ee7a0f Binary files a/app/assets/img/favicons/websites.png and b/app/assets/img/favicons/websites.png differ diff --git a/app/assets/img/favicons/webterm.png b/app/assets/img/favicons/webterm.png new file mode 100644 index 0000000000..9eb7e01bfd Binary files /dev/null and b/app/assets/img/favicons/webterm.png differ diff --git a/app/assets/img/frontends/beaker-code-snippet.png b/app/assets/img/frontends/beaker-code-snippet.png new file mode 100644 index 0000000000..38177fb488 Binary files /dev/null and b/app/assets/img/frontends/beaker-code-snippet.png differ diff --git a/app/assets/img/frontends/beaker-forum.png b/app/assets/img/frontends/beaker-forum.png new file mode 100644 index 0000000000..422148b44a Binary files /dev/null and b/app/assets/img/frontends/beaker-forum.png differ diff --git a/app/assets/img/frontends/beaker-module.png b/app/assets/img/frontends/beaker-module.png new file mode 100644 index 0000000000..c479a0aed5 Binary files /dev/null and b/app/assets/img/frontends/beaker-module.png differ diff --git a/app/assets/img/frontends/beaker-photo-album.png b/app/assets/img/frontends/beaker-photo-album.png new file mode 100644 index 0000000000..7d112ead46 Binary files /dev/null and b/app/assets/img/frontends/beaker-photo-album.png differ diff --git a/app/assets/img/frontends/beaker-wiki.png b/app/assets/img/frontends/beaker-wiki.png new file mode 100644 index 0000000000..4084f4e93d Binary files /dev/null and b/app/assets/img/frontends/beaker-wiki.png differ diff --git a/app/assets/img/frontends/blogger.png b/app/assets/img/frontends/blogger.png new file mode 100644 index 0000000000..b6c8423b83 Binary files /dev/null and b/app/assets/img/frontends/blogger.png differ diff --git a/app/assets/img/frontends/files-drive.png b/app/assets/img/frontends/files-drive.png new file mode 100644 index 0000000000..03a589ecf9 Binary files /dev/null and b/app/assets/img/frontends/files-drive.png differ diff --git a/app/assets/img/frontends/none-actually.png b/app/assets/img/frontends/none-actually.png new file mode 100644 index 0000000000..050b66b409 Binary files /dev/null and b/app/assets/img/frontends/none-actually.png differ diff --git a/app/assets/img/frontends/none.png b/app/assets/img/frontends/none.png new file mode 100644 index 0000000000..6fc8701ba6 Binary files /dev/null and b/app/assets/img/frontends/none.png differ diff --git a/app/assets/img/frontends/website.png b/app/assets/img/frontends/website.png new file mode 100644 index 0000000000..ed0cf9e793 Binary files /dev/null and b/app/assets/img/frontends/website.png differ diff --git a/app/assets/img/logo-black.svg b/app/assets/img/logo-black.svg new file mode 100644 index 0000000000..99bbf02723 --- /dev/null +++ b/app/assets/img/logo-black.svg @@ -0,0 +1,62 @@ + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/app/assets/img/onboarding/setup-tray-icon.png b/app/assets/img/onboarding/setup-tray-icon.png new file mode 100644 index 0000000000..246c87537d Binary files /dev/null and b/app/assets/img/onboarding/setup-tray-icon.png differ diff --git a/app/assets/img/templates/litelement.png b/app/assets/img/templates/litelement.png deleted file mode 100644 index 8d0cd89c01..0000000000 Binary files a/app/assets/img/templates/litelement.png and /dev/null differ diff --git a/app/assets/img/templates/module.png b/app/assets/img/templates/module.png deleted file mode 100644 index efbc2e701a..0000000000 Binary files a/app/assets/img/templates/module.png and /dev/null differ diff --git a/app/assets/img/templates/simple.png b/app/assets/img/templates/simple.png deleted file mode 100644 index ce2c9fddb5..0000000000 Binary files a/app/assets/img/templates/simple.png and /dev/null differ diff --git a/app/assets/img/templates/website.png b/app/assets/img/templates/website.png deleted file mode 100644 index ce1d3888d7..0000000000 Binary files a/app/assets/img/templates/website.png and /dev/null differ diff --git a/app/assets/img/tray-icon-white.png b/app/assets/img/tray-icon-white.png new file mode 100644 index 0000000000..dca5280372 Binary files /dev/null and b/app/assets/img/tray-icon-white.png differ diff --git a/app/assets/img/tray-icon.png b/app/assets/img/tray-icon.png new file mode 100644 index 0000000000..0034f96094 Binary files /dev/null and b/app/assets/img/tray-icon.png differ diff --git a/app/assets/img/tray-icon@2x.png b/app/assets/img/tray-icon@2x.png new file mode 100644 index 0000000000..2b469c92ac Binary files /dev/null and b/app/assets/img/tray-icon@2x.png differ diff --git a/app/assets/templates/blank/index.html b/app/assets/templates/blank/index.html deleted file mode 100644 index aee5b21bf0..0000000000 --- a/app/assets/templates/blank/index.html +++ /dev/null @@ -1,3 +0,0 @@ -

    New website

    - - \ No newline at end of file diff --git a/app/assets/templates/file-share/app/img/file-40.png b/app/assets/templates/file-share/app/img/file-40.png deleted file mode 100644 index f78b87bcc6..0000000000 Binary files a/app/assets/templates/file-share/app/img/file-40.png and /dev/null differ diff --git a/app/assets/templates/file-share/app/img/file-archive-40.png b/app/assets/templates/file-share/app/img/file-archive-40.png deleted file mode 100644 index e7ab338ff0..0000000000 Binary files a/app/assets/templates/file-share/app/img/file-archive-40.png and /dev/null differ diff --git a/app/assets/templates/file-share/app/img/file-audio-40.png b/app/assets/templates/file-share/app/img/file-audio-40.png deleted file mode 100644 index d262d1dc58..0000000000 Binary files a/app/assets/templates/file-share/app/img/file-audio-40.png and /dev/null differ diff --git a/app/assets/templates/file-share/app/img/file-code-40.png b/app/assets/templates/file-share/app/img/file-code-40.png deleted file mode 100644 index b8614c62f8..0000000000 Binary files a/app/assets/templates/file-share/app/img/file-code-40.png and /dev/null differ diff --git a/app/assets/templates/file-share/app/img/file-document-40.png b/app/assets/templates/file-share/app/img/file-document-40.png deleted file mode 100644 index d408f64dd9..0000000000 Binary files a/app/assets/templates/file-share/app/img/file-document-40.png and /dev/null differ diff --git a/app/assets/templates/file-share/app/img/file-image-40.png b/app/assets/templates/file-share/app/img/file-image-40.png deleted file mode 100644 index 556f8fe314..0000000000 Binary files a/app/assets/templates/file-share/app/img/file-image-40.png and /dev/null differ diff --git a/app/assets/templates/file-share/app/img/file-media-40.png b/app/assets/templates/file-share/app/img/file-media-40.png deleted file mode 100644 index 5fbd22f0d3..0000000000 Binary files a/app/assets/templates/file-share/app/img/file-media-40.png and /dev/null differ diff --git a/app/assets/templates/file-share/app/img/file-pdf-40.png b/app/assets/templates/file-share/app/img/file-pdf-40.png deleted file mode 100644 index 07974145ab..0000000000 Binary files a/app/assets/templates/file-share/app/img/file-pdf-40.png and /dev/null differ diff --git a/app/assets/templates/file-share/app/img/folder-40.png b/app/assets/templates/file-share/app/img/folder-40.png deleted file mode 100644 index 9896baf43d..0000000000 Binary files a/app/assets/templates/file-share/app/img/folder-40.png and /dev/null differ diff --git a/app/assets/templates/file-share/app/main.js b/app/assets/templates/file-share/app/main.js deleted file mode 100644 index 929314c678..0000000000 --- a/app/assets/templates/file-share/app/main.js +++ /dev/null @@ -1,347 +0,0 @@ -var site = new DatArchive(window.location) -var siteInfo = {} -var isOwner = false -const $ = (sel, el=document.body) => el.querySelector(sel) -const on = (el, evt, fn) => el.addEventListener(evt, fn) -const emit = (evt) => document.body.dispatchEvent(new Event(evt)) - -class BaseAppElement extends HTMLElement { - $ (sel) { - return this.shadowRoot.querySelector(sel) - } - - emit (evt) { - this.shadowRoot.dispatchEvent(new Event(evt)) - } - - on (sel, evt, fn) { - try { - this.$(sel).addEventListener(evt, fn.bind(this)) - } catch (e) { - // ignore, el probably doesnt exist - } - } -} - -class AppSiteInfo extends BaseAppElement { - constructor () { - super() - this.attachShadow({mode: 'open'}) - this.isEditing = { - title: false, - description: false - } - this.render() - } - - render () { - this.shadowRoot.innerHTML = ` - - ${this.titleCtrl} - ${this.descriptionCtrl} - ` - this.on('h1', 'click', e => { - this.isEditing.title = true - this.render() - this.$('input.title').focus() - }) - this.on('p', 'click', e => { - this.isEditing.description = true - this.render() - this.$('input.description').focus() - }) - this.on('input', 'blur', e => { - var key = e.currentTarget.dataset.key - this.isEditing[key] = false - this.render() - }) - this.on('input', 'keydown', async (e) => { - var key = e.currentTarget.dataset.key - if (e.key === 'Enter') { - var value = e.currentTarget.value - await site.configure({[key]: value}) - window.location.reload() - } - if (e.key === 'Escape') { - this.isEditing[key] = false - this.render() - } - }) - } - - get titleCtrl () { - if (this.isEditing.title) { - return ` - - ` - } - return `

    ${siteInfo.title || 'Untitled fileshare'}

    ` - } - - get descriptionCtrl () { - if (this.isEditing.description) { - return ` - - ` - } - return `

    ${siteInfo.description || `No description`}

    ` - } -} -customElements.define('app-site-info', AppSiteInfo) - -class AppControls extends BaseAppElement { - constructor () { - super() - this.attachShadow({mode: 'open'}) - this.render() - } - - render () { - this.shadowRoot.innerHTML = ` - - ${isOwner ? `` : ''} - Download as .zip - - ` - - // attach events - this.on('.add-btn', 'click', this.onClickAddFile) - this.on('.file-input', 'change', this.onFileChange) - } - - onClickAddFile (e) { - e.preventDefault() - this.$('.file-input').click() - } - - onFileChange (e) { - for (let f of e.target.files) { - var r = new FileReader() - r.onload = async (e) => { - await site.writeFile(`/files/${f.name}`, e.target.result) - emit('files-changed') - } - r.readAsArrayBuffer(f) - } - } -} -customElements.define('app-controls', AppControls) - -class AppFilesGridItem extends BaseAppElement { - constructor () { - super() - this.attachShadow({mode: 'open'}) - this.render() - } - - get fileType () { - var path = this.getAttribute('path') - if (/(zip|tar|tar.gz|rar)$/.test(path)) { - return 'archive' - } - if (/(jpe?g|bmp|gif|png|tiff)$/.test(path)) { - return 'image' - } - if (/(mp4|avi|mpeg)$/.test(path)) { - return 'media' - } - if (/(mpa|mp3|ogg|wav)$/.test(path)) { - return 'media' - } - if (/(txt|doc|docx|ppt|xls|md)$/.test(path)) { - return 'document' - } - if (/(pdf)$/.test(path)) { - return 'pdf' - } - if (/(html?|css|less|sass|js|jsx|wasm|mjs)$/.test(path)) { - return 'code' - } - } - - get imgName () { - if (this.hasAttribute('folder')) { - return 'folder' - } - var type = this.fileType - return type ? `file-${type}` : 'file' - } - - render () { - var path = this.getAttribute('path') - var filename = path.split('/').pop() - this.shadowRoot.innerHTML = ` - - - - ${filename} - - - download - ${isOwner ? `delete` : ''} - - ` - this.on('.remove-btn', 'click', async (e) => { - e.preventDefault() - if (!confirm('Delete this file?')) return - await site.unlink(path) - emit('files-changed') - }) - } -} -customElements.define('files-grid-item', AppFilesGridItem) - -class AppFilesGrid extends BaseAppElement { - constructor () { - super() - this.attachShadow({mode: 'open'}) - - this.files = [] - this.readFiles() - document.body.addEventListener('files-changed', () => this.readFiles()) - } - - async readFiles () { - this.files = await site.readdir('/files', {stat: true}) - this.render() - } - - render () { - this.shadowRoot.innerHTML = ` - - - ${this.files.map(f => ``).join('')} - ${this.files.length === 0 - ? `
    ${isOwner ? 'Add a file' : 'No files'}
    ` - : ''} - ` - if (isOwner && this.files.length === 0) { - this.$('.empty').classList.add('clickable') - this.on('.empty', 'click', () => { - $('app-main').shadowRoot.querySelector('app-controls').shadowRoot.querySelector('.file-input').click() - }) - } - } -} -customElements.define('files-grid', AppFilesGrid) - -class AppMain extends BaseAppElement { - constructor () { - super() - this.attachShadow({mode: 'open'}) - this.setup() - } - - async setup () { - siteInfo = await site.getInfo() - isOwner = siteInfo.isOwner - this.render() - } - - render () { - this.shadowRoot.innerHTML = ` - - - - - ` - } -} -customElements.define('app-main', AppMain) - -function escapeQuotes (str) { - return (str || '').replace(/"/g, '"') -} \ No newline at end of file diff --git a/app/assets/templates/file-share/app/styles.css b/app/assets/templates/file-share/app/styles.css deleted file mode 100644 index 75ad679db0..0000000000 --- a/app/assets/templates/file-share/app/styles.css +++ /dev/null @@ -1 +0,0 @@ -/* needed? */ \ No newline at end of file diff --git a/app/assets/templates/file-share/dat.json b/app/assets/templates/file-share/dat.json deleted file mode 100644 index 53fd679405..0000000000 --- a/app/assets/templates/file-share/dat.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "type": ["file-share", "unwalled.garden/file-share"] -} \ No newline at end of file diff --git a/app/assets/templates/file-share/index.html b/app/assets/templates/file-share/index.html deleted file mode 100644 index 8f830c11ed..0000000000 --- a/app/assets/templates/file-share/index.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - -

    Icons by Icons8

    - - \ No newline at end of file diff --git a/app/assets/templates/image-collection/app/img/file-40.png b/app/assets/templates/image-collection/app/img/file-40.png deleted file mode 100644 index f78b87bcc6..0000000000 Binary files a/app/assets/templates/image-collection/app/img/file-40.png and /dev/null differ diff --git a/app/assets/templates/image-collection/app/img/file-archive-40.png b/app/assets/templates/image-collection/app/img/file-archive-40.png deleted file mode 100644 index e7ab338ff0..0000000000 Binary files a/app/assets/templates/image-collection/app/img/file-archive-40.png and /dev/null differ diff --git a/app/assets/templates/image-collection/app/img/file-audio-40.png b/app/assets/templates/image-collection/app/img/file-audio-40.png deleted file mode 100644 index d262d1dc58..0000000000 Binary files a/app/assets/templates/image-collection/app/img/file-audio-40.png and /dev/null differ diff --git a/app/assets/templates/image-collection/app/img/file-code-40.png b/app/assets/templates/image-collection/app/img/file-code-40.png deleted file mode 100644 index b8614c62f8..0000000000 Binary files a/app/assets/templates/image-collection/app/img/file-code-40.png and /dev/null differ diff --git a/app/assets/templates/image-collection/app/img/file-document-40.png b/app/assets/templates/image-collection/app/img/file-document-40.png deleted file mode 100644 index d408f64dd9..0000000000 Binary files a/app/assets/templates/image-collection/app/img/file-document-40.png and /dev/null differ diff --git a/app/assets/templates/image-collection/app/img/file-image-40.png b/app/assets/templates/image-collection/app/img/file-image-40.png deleted file mode 100644 index 556f8fe314..0000000000 Binary files a/app/assets/templates/image-collection/app/img/file-image-40.png and /dev/null differ diff --git a/app/assets/templates/image-collection/app/img/file-media-40.png b/app/assets/templates/image-collection/app/img/file-media-40.png deleted file mode 100644 index 5fbd22f0d3..0000000000 Binary files a/app/assets/templates/image-collection/app/img/file-media-40.png and /dev/null differ diff --git a/app/assets/templates/image-collection/app/img/file-pdf-40.png b/app/assets/templates/image-collection/app/img/file-pdf-40.png deleted file mode 100644 index 07974145ab..0000000000 Binary files a/app/assets/templates/image-collection/app/img/file-pdf-40.png and /dev/null differ diff --git a/app/assets/templates/image-collection/app/img/folder-40.png b/app/assets/templates/image-collection/app/img/folder-40.png deleted file mode 100644 index 9896baf43d..0000000000 Binary files a/app/assets/templates/image-collection/app/img/folder-40.png and /dev/null differ diff --git a/app/assets/templates/image-collection/app/main.js b/app/assets/templates/image-collection/app/main.js deleted file mode 100644 index 0e948f48b9..0000000000 --- a/app/assets/templates/image-collection/app/main.js +++ /dev/null @@ -1,319 +0,0 @@ -var site = new DatArchive(window.location) -var siteInfo = {} -var isOwner = false -const $ = (sel, el=document.body) => el.querySelector(sel) -const on = (el, evt, fn) => el.addEventListener(evt, fn) -const emit = (evt) => document.body.dispatchEvent(new Event(evt)) - -class BaseAppElement extends HTMLElement { - $ (sel) { - return this.shadowRoot.querySelector(sel) - } - - emit (evt) { - this.shadowRoot.dispatchEvent(new Event(evt)) - } - - on (sel, evt, fn) { - try { - this.$(sel).addEventListener(evt, fn.bind(this)) - } catch (e) { - // ignore, el probably doesnt exist - } - } -} - -class AppSiteInfo extends BaseAppElement { - constructor () { - super() - this.attachShadow({mode: 'open'}) - this.isEditing = { - title: false, - description: false - } - this.render() - } - - render () { - this.shadowRoot.innerHTML = ` - - ${this.titleCtrl} - ${this.descriptionCtrl} - ` - this.on('h1', 'click', e => { - this.isEditing.title = true - this.render() - this.$('input.title').focus() - }) - this.on('p', 'click', e => { - this.isEditing.description = true - this.render() - this.$('input.description').focus() - }) - this.on('input', 'blur', e => { - var key = e.currentTarget.dataset.key - this.isEditing[key] = false - this.render() - }) - this.on('input', 'keydown', async (e) => { - var key = e.currentTarget.dataset.key - if (e.key === 'Enter') { - var value = e.currentTarget.value - await site.configure({[key]: value}) - window.location.reload() - } - if (e.key === 'Escape') { - this.isEditing[key] = false - this.render() - } - }) - } - - get titleCtrl () { - if (this.isEditing.title) { - return ` - - ` - } - return `

    ${siteInfo.title || 'Untitled images collection'}

    ` - } - - get descriptionCtrl () { - if (this.isEditing.description) { - return ` - - ` - } - return `

    ${siteInfo.description || `No description`}

    ` - } -} -customElements.define('app-site-info', AppSiteInfo) - -class AppControls extends BaseAppElement { - constructor () { - super() - this.attachShadow({mode: 'open'}) - this.render() - } - - render () { - this.shadowRoot.innerHTML = ` - - ${isOwner ? `` : ''} - Download as .zip - - ` - - // attach events - this.on('.add-btn', 'click', this.onClickAddImage) - this.on('.file-input', 'change', this.onFileChange) - } - - onClickAddImage (e) { - e.preventDefault() - this.$('.file-input').click() - } - - onFileChange (e) { - for (let f of e.target.files) { - var r = new FileReader() - r.onload = async (e) => { - await site.writeFile(`/images/${f.name}`, e.target.result) - emit('images-changed') - } - r.readAsArrayBuffer(f) - } - } -} -customElements.define('app-controls', AppControls) - -class AppImgGridItem extends BaseAppElement { - constructor () { - super() - this.attachShadow({mode: 'open'}) - this.render() - } - - render () { - var path = this.getAttribute('path') - var filename = path.split('/').pop() - this.shadowRoot.innerHTML = ` - -
    - - - - ${filename} - - - download - ${isOwner ? `delete` : ''} - -
    - ` - this.on('.remove-btn', 'click', async (e) => { - e.preventDefault() - if (!confirm('Delete this image?')) return - await site.unlink(path) - emit('images-changed') - }) - } -} -customElements.define('img-grid-item', AppImgGridItem) - -class AppImgGrid extends BaseAppElement { - constructor () { - super() - this.attachShadow({mode: 'open'}) - - this.files = [] - this.readFiles() - document.body.addEventListener('images-changed', () => this.readFiles()) - } - - async readFiles () { - this.files = await site.readdir('/images', {stat: true}) - this.files = this.files.filter(f => /(png|jpe?g|gif|svg)$/i.test(f.name)) // images only - this.render() - } - - render () { - this.shadowRoot.innerHTML = ` - - - ${this.files.map(f => ``).join('')} - ${this.files.length === 0 - ? `
    ${isOwner ? 'Add an image' : 'No images'}
    ` - : ''} - ` - if (isOwner && this.files.length === 0) { - this.$('.empty').classList.add('clickable') - this.on('.empty', 'click', () => { - $('app-main').shadowRoot.querySelector('app-controls').shadowRoot.querySelector('.file-input').click() - }) - } - } -} -customElements.define('img-grid', AppImgGrid) - -class AppMain extends BaseAppElement { - constructor () { - super() - this.attachShadow({mode: 'open'}) - this.setup() - } - - async setup () { - siteInfo = await site.getInfo() - isOwner = siteInfo.isOwner - this.render() - } - - render () { - this.shadowRoot.innerHTML = ` - - - - - ` - } -} -customElements.define('app-main', AppMain) - -function escapeQuotes (str) { - return (str || '').replace(/"/g, '"') -} \ No newline at end of file diff --git a/app/assets/templates/image-collection/app/styles.css b/app/assets/templates/image-collection/app/styles.css deleted file mode 100644 index 75ad679db0..0000000000 --- a/app/assets/templates/image-collection/app/styles.css +++ /dev/null @@ -1 +0,0 @@ -/* needed? */ \ No newline at end of file diff --git a/app/assets/templates/image-collection/dat.json b/app/assets/templates/image-collection/dat.json deleted file mode 100644 index 7f660461c1..0000000000 --- a/app/assets/templates/image-collection/dat.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "type": ["image-collection", "unwalled.garden/image-collection"] -} \ No newline at end of file diff --git a/app/assets/templates/image-collection/index.html b/app/assets/templates/image-collection/index.html deleted file mode 100644 index 8b4bc277b8..0000000000 --- a/app/assets/templates/image-collection/index.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/app/assets/templates/litelement/.datignore b/app/assets/templates/litelement/.datignore deleted file mode 100644 index 2a4e8b1e93..0000000000 --- a/app/assets/templates/litelement/.datignore +++ /dev/null @@ -1,6 +0,0 @@ -.git -.dat -node_modules -*.log -**/.DS_Store -Thumbs.db diff --git a/app/assets/templates/litelement/index.html b/app/assets/templates/litelement/index.html deleted file mode 100644 index bae958f71e..0000000000 --- a/app/assets/templates/litelement/index.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/app/assets/templates/litelement/js/app.js b/app/assets/templates/litelement/js/app.js deleted file mode 100644 index ca75f07d39..0000000000 --- a/app/assets/templates/litelement/js/app.js +++ /dev/null @@ -1,59 +0,0 @@ -import { LitElement, html, css } from '/vendor/lit-element/lit-element.js'; - -class MyApp extends LitElement { - static get properties() { - return { - title: { type: String }, - }; - } - - constructor() { - super(); - this.title = 'My LitElement App'; - } - - static get styles() { - return [ - css` - :host { - text-align: center; - font-size: calc(10px + 2vmin); - color: #1a2b42; - } - a { - color: #217ff9; - } - - .app-footer { - color: #a8a8a8; - font-size: calc(10px + 0.5vmin); - position: fixed; - bottom: 0; - left: 10px; - } - `, - ]; - } - - render() { - return html` -
    -

    ${this.title}

    - - Code examples - -
    - - `; - } -} - -customElements.define('my-app', MyApp); diff --git a/app/assets/templates/markdown-page/app.js b/app/assets/templates/markdown-page/app.js deleted file mode 100644 index d6f129633b..0000000000 --- a/app/assets/templates/markdown-page/app.js +++ /dev/null @@ -1,173 +0,0 @@ -var site = new DatArchive(window.location) -var siteInfo -var indexMd -var scriptLine -setup() - -async function setup () { - siteInfo = await site.getInfo() - - var h1 = document.createElement('h1') - h1.textContent = siteInfo.title - document.body.querySelector('.markdown').prepend(h1) - - if (siteInfo.isOwner) { - // read the markdown, strip the app script - indexMd = await site.readFile('/index.md') - let parts = indexMd.split('\n') - scriptLine = parts.shift() - indexMd = parts.join('\n') - - // add controls - document.body.prepend(new OwnerControls()) - } -} - -class OwnerControls extends HTMLElement { - constructor () { - super() - this.isEditing = !siteInfo.title // go straight to editor when there is no title - this.attachShadow({mode: 'open'}) - this.render() - } - - $ (sel) { - return this.shadowRoot.querySelector(sel) - } - - render () { - // render controls - this.shadowRoot.innerHTML = ` - -

    - -

    - ${this.isEditing - ? ` -
    -

    - - -

    -

    - -

    -

    - -

    -
    -
    -

    Markdown Cheat Sheet

    - -

    Headings

    - -
    # H1
    -## H2
    -### H3
    - -

    Text formatting

    - -
    **bold text**
    -*italicized text*
    -> blockquote
    -${'`'}code${'`'}
    - -

    Links

    - -
    [title](https://www.example.com)
    - -

    Images

    - -
    ![alt text](image.jpg)
    - -

    Lists

    - -
    1. First item
    -2. Second item
    -3. Third item
    - -
    - First item
    -- Second item
    -- Third item
    - -

    Tables

    - -
    | Syntax | Description |
    -| ----------- | ----------- |
    -| Header | Title |
    -| Paragraph | Text |
    - -

    Horizontal lines

    - -
    ---
    - -
    - ` - : ''} - ` - - // attach event handlers - if (this.isEditing) { - this.$('.save-btn').addEventListener('click', e => this.onClickSave(e)) - this.$('.cancel-btn').addEventListener('click', e => this.onClickCancel(e)) - } else { - this.$('.edit-btn').addEventListener('click', e => this.onClickEdit(e)) - } - } - - onClickEdit (e) { - this.isEditing = true - this.render() - } - - async onClickSave (e) { - var title = this.$('.title-input').value - var content = this.$('.content-textarea').value - - // validate - if (!title) { - this.$('.title-input').style.borderColor = '#f00' - return - } - - // update site title - await site.configure({title}) - - // write the content - await site.writeFile('index.md', scriptLine + '\n' + content) - window.location.reload() - } - - onClickCancel (e) { - if (!siteInfo.title) return // dont cancel until a title is set - this.isEditing = false - this.render() - } -} - -customElements.define('app-owner-controls', OwnerControls) - -function escapeQuotes (str) { - return (str || '').replace(/"/g, '"') -} \ No newline at end of file diff --git a/app/assets/templates/markdown-page/dat.json b/app/assets/templates/markdown-page/dat.json deleted file mode 100644 index 0596798ade..0000000000 --- a/app/assets/templates/markdown-page/dat.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "type": ["web-page"] -} \ No newline at end of file diff --git a/app/assets/templates/markdown-page/index.md b/app/assets/templates/markdown-page/index.md deleted file mode 100644 index 9014b4eb67..0000000000 --- a/app/assets/templates/markdown-page/index.md +++ /dev/null @@ -1,6 +0,0 @@ - -Welcome to your new page! - -You can edit the content by clicking the "Edit" button. (Only you can edit the page.) - -As you might have guessed, this editor uses [**Markdown**](https://en.wikipedia.org/wiki/Markdown) diff --git a/app/assets/templates/simple/.datignore b/app/assets/templates/simple/.datignore deleted file mode 100644 index 2a4e8b1e93..0000000000 --- a/app/assets/templates/simple/.datignore +++ /dev/null @@ -1,6 +0,0 @@ -.git -.dat -node_modules -*.log -**/.DS_Store -Thumbs.db diff --git a/app/assets/templates/simple/css/index.css b/app/assets/templates/simple/css/index.css deleted file mode 100644 index 74e939e029..0000000000 --- a/app/assets/templates/simple/css/index.css +++ /dev/null @@ -1,15 +0,0 @@ -/* insert your custom CSS here */ - -body { - background: #fafafa; -} - -main { - width: 400px; - height: 140px; - border: 2px dashed gray; - margin: 30px auto; - text-align: center; - padding: 40px; - background: #fff; -} \ No newline at end of file diff --git a/app/assets/templates/simple/index.html b/app/assets/templates/simple/index.html deleted file mode 100644 index 4ea1671745..0000000000 --- a/app/assets/templates/simple/index.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - -
    -

    My New Website

    -

    Created with the Beaker Browser!

    -
    - - - \ No newline at end of file diff --git a/app/assets/templates/simple/js/index.js b/app/assets/templates/simple/js/index.js deleted file mode 100644 index c63c8e504e..0000000000 --- a/app/assets/templates/simple/js/index.js +++ /dev/null @@ -1,2 +0,0 @@ -// insert your custom JS here! -console.log('Hello from index.js') \ No newline at end of file diff --git a/app/assets/templates/theme/index.css b/app/assets/templates/theme/index.css deleted file mode 100644 index c47ebd604f..0000000000 --- a/app/assets/templates/theme/index.css +++ /dev/null @@ -1 +0,0 @@ -/* Place your theme styles here */ \ No newline at end of file diff --git a/app/assets/templates/theme/index.html b/app/assets/templates/theme/index.html deleted file mode 100644 index 3657743064..0000000000 --- a/app/assets/templates/theme/index.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - -

    Untitled Theme

    - - \ No newline at end of file diff --git a/app/assets/templates/theme/index.js b/app/assets/templates/theme/index.js deleted file mode 100644 index bd4ad8225d..0000000000 --- a/app/assets/templates/theme/index.js +++ /dev/null @@ -1 +0,0 @@ -// Place your theme Javascript here \ No newline at end of file diff --git a/app/background-process.js b/app/background-process.js deleted file mode 100644 index a7cba9d900..0000000000 --- a/app/background-process.js +++ /dev/null @@ -1,173 +0,0 @@ -Error.stackTraceLimit = Infinity -require('tls').DEFAULT_ECDH_CURVE = 'auto' // HACK (prf) fix Node 8.9.x TLS issues, see https://github.com/nodejs/node/issues/19359 - -// This is main process of Electron, started as first thing when your -// app starts. This script is running through entire life of your application. -// It doesn't have any windows which you can see on screen, but we can open -// window from here. - -import {app, protocol} from 'electron' -import {join} from 'path' -import * as beakerCore from '@beaker/core' -import * as rpc from 'pauls-electron-rpc' - -import * as beakerBrowser from './background-process/browser' -import * as adblocker from './background-process/adblocker' -import * as analytics from './background-process/analytics' -import * as portForwarder from './background-process/nat-port-forwarder' - -import * as windows from './background-process/ui/windows' -import * as modals from './background-process/ui/subwindows/modals' -import * as windowMenu from './background-process/ui/window-menu' -import registerContextMenu from './background-process/ui/context-menu' -import * as downloads from './background-process/ui/downloads' -import * as permissions from './background-process/ui/permissions' -import * as childProcesses from './background-process/child-processes' - -import * as beakerProtocol from './background-process/protocols/beaker' -import * as beakerFaviconProtocol from './background-process/protocols/beaker-favicon' -import * as assetProtocol from './background-process/protocols/asset' -import * as intentProtocol from './background-process/protocols/intent' - -import * as testDriver from './background-process/test-driver' -import * as openURL from './background-process/open-url' - -const DISALLOWED_SAVE_PATH_NAMES = [ - 'home', - 'desktop', - 'documents', - 'downloads', - 'music', - 'pictures', - 'videos' -] - -// setup -// = - -// read config from env vars -if (beakerCore.getEnvVar('BEAKER_USER_DATA_PATH')) { - console.log('User data path set by environment variables') - console.log('userData:', beakerCore.getEnvVar('BEAKER_USER_DATA_PATH')) - app.setPath('userData', beakerCore.getEnvVar('BEAKER_USER_DATA_PATH')) -} -if (beakerCore.getEnvVar('BEAKER_TEST_DRIVER')) { - testDriver.setup() -} - -// enable the sandbox -app.enableSandbox() - -// enable process reuse to speed up navigations -// see https://github.com/electron/electron/issues/18397 -app.allowRendererProcessReuse = true - -// configure the protocols -protocol.registerSchemesAsPrivileged([ - {scheme: 'dat', privileges: {standard: true, secure: true, allowServiceWorkers: true, supportFetchAPI: true, corsEnabled: true}}, - {scheme: 'beaker', privileges: {standard: true, secure: true, allowServiceWorkers: true, supportFetchAPI: true, corsEnabled: true}} -]) - -// handle OS event to open URLs -app.on('open-url', (e, url) => { - e.preventDefault() // we are handling it - // wait for ready (not waiting can trigger errors) - if (app.isReady()) openURL.open(url) - else app.on('ready', () => openURL.open(url)) -}) - -// handle OS event to open files -app.on('open-file', (e, filepath) => { - e.preventDefault() // we are handling it - // wait for ready (not waiting can trigger errors) - if (app.isReady()) openURL.open(`file://${filepath}`) - else app.on('ready', () => openURL.open(`file://${filepath}`)) -}) - -app.on('ready', async function () { - // start the daemon process - var datDaemonProcess = await childProcesses.spawn('dat-daemon', './dat-daemon.js') - - portForwarder.setup() - - // setup core - await beakerCore.setup({ - // paths - userDataPath: app.getPath('userData'), - homePath: app.getPath('home'), - templatesPath: join(__dirname, 'assets', 'templates'), - disallowedSavePaths: DISALLOWED_SAVE_PATH_NAMES.map(path => app.getPath(path)), - - // APIs - permsAPI: permissions, - uiAPI: { - showModal: modals.create, - capturePage: beakerBrowser.capturePage - }, - userSessionAPI: { - getFor: windows.getUserSessionFor - }, - rpcAPI: rpc, - downloadsWebAPI: downloads.WEBAPI, - browserWebAPI: beakerBrowser.WEBAPI, - datDaemonProcess - }) - - // base - await beakerBrowser.setup() - adblocker.setup() - analytics.setup() - - // ui - windowMenu.setup() - registerContextMenu() - windows.setup() - downloads.setup() - permissions.setup() - - // protocols - beakerProtocol.setup() - beakerFaviconProtocol.setup() // TODO deprecateme - assetProtocol.setup() - intentProtocol.setup() - protocol.registerStreamProtocol('dat', beakerCore.dat.protocol.electronHandler, err => { - if (err) { - console.error(err) - throw new Error('Failed to create protocol: dat') - } - }) -}) - -app.on('quit', () => { - portForwarder.closePort() - childProcesses.closeAll() -}) - -app.on('custom-ready-to-show', () => { - // our first window is ready to show, do any additional setup - beakerCore.dat.library.loadSavedArchives() -}) - -// only run one instance -const isFirstInstance = app.requestSingleInstanceLock() -if (!isFirstInstance) { - app.exit() -} else { - handleArgv(process.argv) - app.on('second-instance', (event, argv, workingDirectory) => { - handleArgv(argv) - - // focus/create a window - windows.ensureOneWindowExists() - windows.getActiveWindow().focus() - }) -} -function handleArgv (argv) { - if (process.platform !== 'darwin') { - // look for URLs, windows & linux use argv instead of open-url - let url = argv.find(v => v.indexOf('://') !== -1) - if (url) { - openURL.open(url) - } - } -} diff --git a/app/background-process/protocols/asset.js b/app/background-process/protocols/asset.js deleted file mode 100644 index 200fa762ec..0000000000 --- a/app/background-process/protocols/asset.js +++ /dev/null @@ -1,108 +0,0 @@ -/** - * asset:{type}{-dimension?}:{url} - * - * Helper protocol to serve site favicons and avatars from the cache. - * Examples: - * - * - asset:favicon:dat://beakerbrowser.com - * - asset:favicon-32:dat://beakerbrowser.com - * - asset:thumb:dat://beakerbrowser.com - * - asset:cover:dat://beakerbrowser.com - **/ - -import {protocol, screen} from 'electron' -import * as beakerCore from '@beaker/core' -const {sitedata} = beakerCore.dbs -import fs from 'fs' -import path from 'path' - -const NOT_FOUND = -6 // TODO I dont think this is the right code -prf - -export function setup () { - var DEFAULTS = { - favicon: {type: 'image/png', data: NOT_FOUND}, - thumb: {type: 'image/jpeg', data: NOT_FOUND}, - cover: {type: 'image/jpeg', data: NOT_FOUND} - } - - // load defaults - fs.readFile(path.join(__dirname, './assets/img/default-favicon.png'), (err, buf) => { - if (err) { console.error('Failed to load default favicon', path.join(__dirname, './assets/img/default-favicon.png'), err) } - if (buf) { DEFAULTS.favicon.data = buf } - }) - fs.readFile(path.join(__dirname, './assets/img/default-thumb.jpg'), (err, buf) => { - if (err) { console.error('Failed to load default thumb', path.join(__dirname, './assets/img/default-thumb.jpg'), err) } - if (buf) { DEFAULTS.thumb.data = buf } - }) - fs.readFile(path.join(__dirname, './assets/img/default-cover.jpg'), (err, buf) => { - if (err) { console.error('Failed to load default cover', path.join(__dirname, './assets/img/default-cover.jpg'), err) } - if (buf) { DEFAULTS.cover.data = buf } - }) - - // detect if is retina - let display = screen.getPrimaryDisplay() - const isRetina = display.scaleFactor >= 2 - - // register favicon protocol - protocol.registerBufferProtocol('asset', async (request, cb) => { - // parse the URL - let {asset, url, size} = parseAssetUrl(request.url) - if (isRetina) { - size *= 2 - } - - // validate - if (asset !== 'favicon' && asset !== 'thumb' && asset !== 'cover') { - return cb({data: NOT_FOUND}) - } - - // if beaker://, pull from hard-coded assets - if (url.startsWith('beaker://')) { - let name = /beaker:\/\/([^\/]+)/.exec(url)[1] - return fs.readFile(path.join(__dirname, `./assets/img/favicons/${name}.png`), (err, buf) => { - if (buf) cb({mimeType: 'image/png', data: buf}) - else cb(DEFAULTS[asset]) - }) - } - - try { - // look up in db - let data = await sitedata.get(url, asset) - if (!data && asset === 'thumb') { - // try fallback to favicon - data = await sitedata.get(url, 'favicon') - if (!data) { - // try fallback to screenshot - data = await sitedata.get(url, 'screenshot') - } - } - if (data) { - // `data` is a data url ('data:image/png;base64,...') - // so, skip the beginning and pull out the data - let parts = data.split(',') - let mimeType = /data:([^;]+);base64/.exec(parts[0])[1] - data = parts[1] - if (data) { - return cb({ mimeType, data: Buffer.from(data, 'base64') }) - } - } - } catch (e) { - // ignore - console.log(e) - } - - cb(DEFAULTS[asset]) - }, e => { - if (e) { console.error('Failed to register asset protocol', e) } - }) -} - -const ASSET_URL_RE = /^asset:([a-z]+)(-\d+)?:(.*)/ -function parseAssetUrl (str) { - const match = ASSET_URL_RE.exec(str) - return { - asset: match[1], - size: (+match[2]) || 16, - url: match[3] - } -} diff --git a/app/background-process/protocols/beaker-favicon.js b/app/background-process/protocols/beaker-favicon.js deleted file mode 100644 index 7418cea8e3..0000000000 --- a/app/background-process/protocols/beaker-favicon.js +++ /dev/null @@ -1,134 +0,0 @@ -/** - * beaker-favicon: - * - * Helper protocol to serve site favicons from the sitedata db. - **/ - -import {protocol, screen} from 'electron' -import * as beakerCore from '@beaker/core' -const {dat} = beakerCore -const {sitedata} = beakerCore.dbs -import fs from 'fs' -import path from 'path' -import ICO from 'icojs' - -export function setup () { - // load default favicon - var defaultFaviconBuffer = -6 // not found, till we load it - fs.readFile(path.join(__dirname, './assets/img/default-favicon.png'), (err, buf) => { - if (err) { console.error('Failed to load default favicon', path.join(__dirname, './assets/img/default-favicon.png'), err) } - if (buf) { defaultFaviconBuffer = buf } - }) - - // detect if is retina - let display = screen.getPrimaryDisplay() - const isRetina = display.scaleFactor >= 2 - - // register favicon protocol - protocol.registerBufferProtocol('beaker-favicon', async (request, cb) => { - // parse the URL - let {url, faviconSize} = parseBeakerFaviconURL(request.url) - if (isRetina) { - faviconSize *= 2 - } - - // if beaker://, pull from assets - if (url.startsWith('beaker://')) { - let name = /beaker:\/\/([^\/]+)/.exec(url)[1] - if (url.startsWith('beaker://library/?view=addressbook')) name = 'addressbook' - if (url.startsWith('beaker://library/?view=bookmarks')) name = 'bookmarks' - if (url.startsWith('beaker://library/?view=websites')) name = 'websites' - return fs.readFile(path.join(__dirname, `./assets/img/favicons/${name}.png`), (err, buf) => { - cb({mimeType: 'image/png', data: buf || defaultFaviconBuffer}) - }) - } - - // if a dat, see if there's a favicon.ico or .png - try { - let data, datfs - // pick the filesystem - let datResolvedUrl = url - if (url.startsWith('dat://')) { - datResolvedUrl = await dat.dns.resolveName(url) - datfs = dat.library.getArchive(datResolvedUrl) // (only try if the dat is loaded) - } - if (datfs) { - // try .ico - try { - data = await datfs.pda.readFile('/favicon.ico', 'binary') - if (data) { - // select the best-fitting size - let images = await ICO.parse(data, 'image/png') - let image = images[0] - for (let i = 1; i < images.length; i++) { - if (Math.abs(images[i].width - faviconSize) < Math.abs(image.width - faviconSize)) { - image = images[i] - } - } - let buf = Buffer.from(image.buffer) - return cb({mimeType: 'image/png', data: buf}) - } - } catch (e) { - // .ico failed, ignore - data = null - } - - try { - // try .png - data = await datfs.pda.readFile('/favicon.png', 'binary') - if (data) { - return cb({mimeType: 'image/png', data}) - } - } catch (e) { - // .png not found, ignore - } - - // see if there's a dat type we can use - // TODO restore if/when dat types get involved again - // let manifest = await datfs.pda.readManifest() - // let type = getShortenedUnwalledGardenType(manifest.type) - // if (type) { - // return fs.readFile(path.join(__dirname, `./assets/img/templates/${type}.png`), (err, buf) => { - // cb({mimeType: 'image/png', data: buf || defaultFaviconBuffer}) - // }) - // } - } - } catch (e) { - // ignore - console.error(e) - } - - try { - // look up in db - let data = await sitedata.get(url, 'favicon') - if (data) { - // `data` is a data url ('data:image/png;base64,...') - // so, skip the beginning and pull out the data - data = data.split(',')[1] - if (data) { - return cb({ mimeType: 'image/png', data: Buffer.from(data, 'base64') }) - } - } - } catch (e) { - // ignore - } - - cb({ mimeType: 'image/png', data: defaultFaviconBuffer }) - }, e => { - if (e) { console.error('Failed to register beaker-favicon protocol', e) } - }) -} - -const BEAKER_FAVICON_URL_RE = /^beaker-favicon:(\d*),?(.*)/ -function parseBeakerFaviconURL (str) { - const match = BEAKER_FAVICON_URL_RE.exec(str) - let res = { - faviconSize: (+match[1]) || 16, - url: match[2] - } - // special case: in beaker://library, use the dat being viewed - if (res.url.startsWith('beaker://library/dat://')) { - res.url = res.url.slice('beaker://library/'.length) - } - return res -} diff --git a/app/background-process/ui/subwindows/sidebars.js b/app/background-process/ui/subwindows/sidebars.js deleted file mode 100644 index 7b511e5505..0000000000 --- a/app/background-process/ui/subwindows/sidebars.js +++ /dev/null @@ -1,135 +0,0 @@ -/** - * Sidebar - * - * NOTES - * - Sidebar views are created as-needed, and desroyed when not in use - * - Sidebar views are attached to individual BrowserView instances - * - Sidebar views are shown and hidden based on whether its owning BrowserView is visible - */ - -import path from 'path' -import { app, BrowserWindow, BrowserView } from 'electron' -import * as rpc from 'pauls-electron-rpc' -import * as viewManager from '../view-manager' -// import modalsRPCManifest from '../../rpc-manifests/modals' -import { findWebContentsParentWindow } from '../../../lib/electron' - -// globals -// = - -export const SIDEBAR_Y = 76 -var views = {} // map of {[parentView.id] => BrowserView} - -// exported api -// = - -export function setup (parentWindow) { -} - -export function destroy (parentWindow) { - // destroy all under this window - for (let view of viewManager.getAll(parentWindow)) { - if (view.id in views) { - views[view.id].destroy() - delete views[view.id] - } - } -} - -export function reposition (parentWindow) { - // reposition all under this window - for (let view of viewManager.getAll(parentWindow)) { - if (view.id in views) { - setBounds(views[view.id], parentWindow) - } - } -} - -export function create (parentView) { - // make sure a sidebar doesnt already exist - if (parentView.id in views) { - return - } - var win = viewManager.findContainingWindow(parentView) - if (!win) win = findWebContentsParentWindow(views[parentView.id].webContents) - - // create the view - var view = views[parentView.id] = new BrowserView({ - webPreferences: { - defaultEncoding: 'utf-8', - preload: path.join(__dirname, 'webview-preload.build.js') - } - }) - view.webContents.on('console-message', (e, level, message) => { - console.log('Sidebar window says:', message) - }) - view.webContents.loadURL('beaker://sidebar/') - return view -} - -export function get (parentView) { - return views[parentView.id] -} - -export function findContainingWindow (sidebarView) { - return findWebContentsParentWindow(sidebarView.webContents) -} - -export function show (parentView) { - if (parentView.id in views) { - var win = viewManager.findContainingWindow(parentView) - if (!win) win = findWebContentsParentWindow(views[parentView.id].webContents) - if (win) { - win.addBrowserView(views[parentView.id]) - setBounds(views[parentView.id], win) - views[parentView.id].webContents.executeJavaScript(`window.sidebarShow()`) - } - } -} - -export function hide (parentView) { - if (parentView.id in views) { - var win = viewManager.findContainingWindow(parentView) - if (!win) win = findWebContentsParentWindow(views[parentView.id].webContents) - if (win) win.removeBrowserView(views[parentView.id]) - } -} - -export function close (parentView) { - if (parentView.id in views) { - var win = viewManager.findContainingWindow(parentView) - if (!win) win = findWebContentsParentWindow(views[parentView.id].webContents) - win.removeBrowserView(views[parentView.id]) - views[parentView.id].destroy() - delete views[parentView.id] - } -} - -// rpc api -// = -/* -rpc.exportAPI('background-process-modals', modalsRPCManifest, { - async createTab (url) { - var win = findWebContentsParentWindow(this.sender) - viewManager.create(win, url, {setActive: true}) - }, - - async resizeSelf (dimensions) { - var view = BrowserView.fromWebContents(this.sender) - var parentWindow = findWebContentsParentWindow(this.sender) - setBounds(view, parentWindow, dimensions) - } -})*/ - -// internal methods -// = - -function setBounds (sidebarView, parentWindow) { - var parentBounds = parentWindow.getContentBounds() - sidebarView.setBounds({ - x: Math.floor(parentBounds.width / 2), - y: SIDEBAR_Y, - width: Math.floor(parentBounds.width / 2), - height: parentBounds.height - SIDEBAR_Y - }) -} diff --git a/app/background-process/ui/view-manager.js b/app/background-process/ui/view-manager.js deleted file mode 100644 index 05c88dd512..0000000000 --- a/app/background-process/ui/view-manager.js +++ /dev/null @@ -1,1522 +0,0 @@ -import { app, dialog, BrowserView, BrowserWindow, Menu, clipboard, ipcMain } from 'electron' -import * as beakerCore from '@beaker/core' -import errorPage from '@beaker/core/lib/error-page' -import path from 'path' -import { promises as fs } from 'fs' -import Events from 'events' -import _throttle from 'lodash.throttle' -import parseDatURL from 'parse-dat-url' -import emitStream from 'emit-stream' -import prettyHash from 'pretty-hash' -import _get from 'lodash.get' -import _pick from 'lodash.pick' -import * as rpc from 'pauls-electron-rpc' -import normalizeURL from 'normalize-url' -import viewsRPCManifest from '../rpc-manifests/views' -import * as zoom from './views/zoom' -import * as shellMenus from './subwindows/shell-menus' -import * as locationBar from './subwindows/location-bar' -import * as statusBar from './subwindows/status-bar' -import * as prompts from './subwindows/prompts' -import * as permPrompt from './subwindows/perm-prompt' -import * as modals from './subwindows/modals' -import * as sidebars from './subwindows/sidebars' -import * as windowMenu from './window-menu' -import { getUserSessionFor } from './windows' -import { getResourceContentType } from '../browser' -import { examineLocationInput } from '../../lib/urls' -import { findWebContentsParentWindow } from '../../lib/electron' -import { findImageBounds } from '../../lib/bg/image' -import { ucfirst } from '../../lib/strings' -const sitedataDb = beakerCore.dbs.sitedata -const settingsDb = beakerCore.dbs.settings -const historyDb = beakerCore.dbs.history -const bookmarksDb = beakerCore.dbs.bookmarks - -const ERR_ABORTED = -3 -const ERR_CONNECTION_REFUSED = -102 -const ERR_INSECURE_RESPONSE = -501 -const TLS_ERROR_CODES = Object.values({ - ERR_NO_SSL_VERSIONS_ENABLED: -112, - ERR_SSL_VERSION_OR_CIPHER_MISMATCH: -113, - ERR_SSL_RENEGOTIATION_REQUESTED: -114, - ERR_PROXY_AUTH_UNSUPPORTED: -115, - ERR_CERT_ERROR_IN_SSL_RENEGOTIATION: -116, - ERR_BAD_SSL_CLIENT_AUTH_CERT: -117, - ERR_SSL_NO_RENEGOTIATION: -123, - ERR_SSL_WEAK_SERVER_EPHEMERAL_DH_KEY: -129, - ERR_PROXY_CERTIFICATE_INVALID: -136, - ERR_SSL_HANDSHAKE_NOT_COMPLETED: -148, - ERR_SSL_BAD_PEER_PUBLIC_KEY: -149, - ERR_SSL_PINNED_KEY_NOT_IN_CERT_CHAIN: -150, - ERR_CLIENT_AUTH_CERT_TYPE_UNSUPPORTED: -151, - ERR_SSL_DECRYPT_ERROR_ALERT: -153, - ERR_SSL_SERVER_CERT_CHANGED: -156, - ERR_SSL_UNRECOGNIZED_NAME_ALERT: -159, - ERR_SSL_SERVER_CERT_BAD_FORMAT: -167, - ERR_CT_STH_PARSING_FAILED: -168, - ERR_CT_STH_INCOMPLETE: -169, - ERR_CT_CONSISTENCY_PROOF_PARSING_FAILED: -171, - ERR_SSL_OBSOLETE_CIPHER: -172, - ERR_SSL_VERSION_INTERFERENCE: -175, - ERR_EARLY_DATA_REJECTED: -178, - ERR_WRONG_VERSION_ON_EARLY_DATA: -179, - ERR_TLS13_DOWNGRADE_DETECTED: -180 -}) -const IS_CODE_INSECURE_RESPONSE = x => x === ERR_CONNECTION_REFUSED || x === ERR_INSECURE_RESPONSE || (x <= -200 && x > -300) || TLS_ERROR_CODES.includes(x) - -const Y_POSITION = 76 -const DEFAULT_URL = 'beaker://start' -const TRIGGER_LIVE_RELOAD_DEBOUNCE = 500 // throttle live-reload triggers by this amount - -// the variables which are automatically sent to the shell-window for rendering -const STATE_VARS = [ - 'url', - 'title', - 'siteTitle', - 'datDomain', - 'isOwner', - 'numFollowers', - 'numComments', - 'peers', - 'favicons', - 'zoom', - 'loadError', - 'isActive', - 'isPinned', - 'isBookmarked', - 'isLoading', - 'isReceivingAssets', - 'canGoBack', - 'canGoForward', - 'isAudioMuted', - 'isCurrentlyAudible', - 'isSidebarActive', - 'isInpageFindActive', - 'currentInpageFindString', - 'currentInpageFindResults', - 'availableAlternative', - 'donateLinkHref', - 'isLiveReloading' -] - -// globals -// = - -var activeViews = {} // map of {[win.id]: Array} -var preloadedNewTabViews = {} // map of {[win.id]: View} -var closedURLs = {} // map of {[win.id]: Array} -var windowEvents = {} // mapof {[win.id]: Events} -var noRedirectHostnames = new Set() // set of hostnames which have dat-redirection disabled -var nextViewIsScriptCloseable = false // will the next view created be "script closable"? - -// classes -// = - -class View { - constructor (win, opts = {isPinned: false, isHidden: false}) { - this.browserWindow = win - this.browserView = new BrowserView({ - webPreferences: { - preload: path.join(__dirname, 'webview-preload.build.js'), - contextIsolation: false, - webviewTag: false, - sandbox: true, - defaultEncoding: 'utf-8', - nativeWindowOpen: true, - nodeIntegration: false, - scrollBounce: true, - navigateOnDragDrop: true - } - }) - this.browserView.setBackgroundColor('#fff') - - // webview state - this.loadingURL = null // URL being loaded, if any - this.isLoading = false // is the tab loading? - this.isReceivingAssets = false // has the webview started receiving assets in the current load-cycle? - this.favicons = null // array of favicon URLs - this.zoom = 0 // what's the current zoom level? - this.loadError = null // page error state, if any - - // browser state - this.isHidden = opts.isHidden // is this tab hidden from the user? used for the preloaded tab - this.isActive = false // is this the active page in the window? - this.isPinned = Boolean(opts.isPinned) // is this page pinned? - this.isSidebarActive = false // is the sidebar open? - this.liveReloadEvents = null // live-reload event stream - this.isInpageFindActive = false // is the inpage-finder UI active? - this.currentInpageFindString = undefined // what's the current inpage-finder query string? - this.currentInpageFindResults = undefined // what's the current inpage-finder query results? - this.isScriptClosable = takeIsScriptClosable() // can this view be closed by `window.close` ? - - // helper state - this.peers = 0 // how many peers does the site have? - this.isBookmarked = false // is the active page bookmarked? - this.datInfo = null // metadata about the site if viewing a dat - this.numFollowers = 0 // how many sites are following this site? (unwalled garden) - this.numComments = 0 // how many comments are on the current page? (unwalled garden) - this.donateLinkHref = null // the URL of the donate site, if set by the dat.json - this.availableAlternative = '' // tracks if there's alternative protocol available for the site - this.wasDatTimeout = false // did the last navigation result in a timed-out dat? - - // wire up events - this.webContents.on('did-start-loading', this.onDidStartLoading.bind(this)) - this.webContents.on('did-start-navigation', this.onDidStartNavigation.bind(this)) - this.webContents.on('did-navigate', this.onDidNavigate.bind(this)) - this.webContents.on('did-navigate-in-page', this.onDidNavigateInPage.bind(this)) - this.webContents.on('did-stop-loading', this.onDidStopLoading.bind(this)) - this.webContents.on('dom-ready', this.onDomReady.bind(this)) - this.webContents.on('did-fail-load', this.onDidFailLoad.bind(this)) - this.webContents.on('update-target-url', this.onUpdateTargetUrl.bind(this)) - this.webContents.on('page-title-updated', this.onPageTitleUpdated.bind(this)) // NOTE page-title-updated isn't documented on webContents but it is supported - this.webContents.on('page-favicon-updated', this.onPageFaviconUpdated.bind(this)) - this.webContents.on('new-window', this.onNewWindow.bind(this)) - this.webContents.on('media-started-playing', this.onMediaChange.bind(this)) - this.webContents.on('media-paused', this.onMediaChange.bind(this)) - this.webContents.on('found-in-page', this.onFoundInPage.bind(this)) - - // security - deny these events - const deny = e => e.preventDefault() - this.webContents.on('remote-require', deny) - this.webContents.on('remote-get-global', deny) - this.webContents.on('remote-get-builtin', deny) - this.webContents.on('remote-get-current-window', deny) - this.webContents.on('remote-get-current-web-contents', deny) - this.webContents.on('remote-get-guest-web-contents', deny) - } - - get id () { - return this.browserView.id - } - - get webContents () { - return this.browserView.webContents - } - - get url () { - return this.webContents.getURL() - } - - get origin () { - return toOrigin(this.url) - } - - get title () { - var title = this.webContents.getTitle() - if (this.datInfo && this.datInfo.title && (!title || title.startsWith(this.origin))) { - // fallback to the dat.json title field if the page doesnt provide a title - title = this.datInfo.title - } - return title - } - - get siteTitle () { - try { - var hostname = ((parseDatURL(this.url)).hostname).replace(/\+(.+)$/, '') - if (this.datInfo) { - if (this.datInfo.domain) { - // use confirmed domain if available (because we give a checkmark for that) - return this.datInfo.domain - } - if ((this.datInfo.title || '').trim()) { - // use site title if it exists - return (this.datInfo.title || '').trim() - } - // pretty hash of the key otherwise - return prettyHash(this.datInfo.key) - } - if (this.url.startsWith('beaker://')) { - return `Beaker ${ucfirst(hostname)}` - } - return hostname - } catch (e) { - return '' - } - } - - get datDomain () { - return this.datInfo && this.datInfo.domain ? this.datInfo.domain : '' - } - - get isOwner () { - return this.datInfo && this.datInfo.isOwner - } - - get canGoBack () { - return this.webContents.canGoBack() - } - - get canGoForward () { - return this.webContents.canGoForward() - } - - get isAudioMuted () { - return this.webContents.isAudioMuted() - } - - get isCurrentlyAudible () { - return this.webContents.isCurrentlyAudible() - } - - get isLiveReloading () { - return !!this.liveReloadEvents - } - - get state () { - var state = _pick(this, STATE_VARS) - if (this.loadingURL) state.url = this.loadingURL - return state - } - - // management - // = - - loadURL (url) { - this.browserView.webContents.loadURL(url) - } - - getBounds () { - const win = this.browserWindow - var {width, height} = win.getContentBounds() - if (this.isSidebarActive) { - width = Math.floor(width / 2) // sidebar takes half the screen - } - return {x: 0, y: Y_POSITION, width, height: height - Y_POSITION} - } - - resize () { - this.browserView.setBounds(this.getBounds()) - this.browserView.setAutoResize({width: true, height: true}) - } - - activate () { - this.isActive = true - - const win = this.browserWindow - win.addBrowserView(this.browserView) - prompts.show(this.browserView) - permPrompt.show(this.browserView) - modals.show(this.browserView) - sidebars.show(this.browserView) - - this.resize() - this.webContents.focus() - } - - deactivate () { - this.browserWindow.removeBrowserView(this.browserView) - if (this.isActive) { - shellMenus.hide(this.browserWindow) // this will close the location menu if it's open - } - prompts.hide(this.browserView) - permPrompt.hide(this.browserView) - modals.hide(this.browserView) - sidebars.hide(this.browserView) - this.isActive = false - } - - destroy () { - this.deactivate() - prompts.close(this.browserView) - permPrompt.close(this.browserView) - modals.close(this.browserView) - this.browserView.destroy() - } - - async updateHistory () { - var url = this.url - var title = this.title - - if (/^(http|https|dat)/i.test(url)) { - historyDb.addVisit(0, {url, title}) - if (this.isPinned) { - savePins(this.browserWindow) - } - } - } - - toggleMuted () { - this.webContents.setAudioMuted(!this.isAudioMuted) - this.emitUpdateState() - } - - async openSidebar (panel) { - if (!this.isSidebarActive) { - this.toggleSidebar(panel) - } else { - this.updateSidebar(panel) - } - } - - async toggleSidebar (panel) { - if (!this.isSidebarActive) { - // create sidebar - let v = sidebars.create(this.browserView) - if (this.isActive) sidebars.show(this.browserView) - v.webContents.on('did-finish-load', () => this.updateSidebar(panel)) - this.isSidebarActive = true - } else { - if (panel) { - // if an panel is passed, toggle off if already on the view - // otherwise, just go to that view - let v = sidebars.get(this) - if (v) { - let currentPanel = await v.webContents.executeJavaScript(`window.sidebarGetCurrentPanel()`) - if (currentPanel === panel) { - sidebars.close(this.browserView) - this.isSidebarActive = false - } else { - this.updateSidebar(panel) - } - } - } else { - sidebars.close(this.browserView) - this.isSidebarActive = false - } - } - this.resize() - - // emit - this.emitUpdateState() - } - - updateSidebar (panel) { - var sidebarView = sidebars.get(this) - if (sidebarView) { - sidebarView.webContents.executeJavaScript(` - window.sidebarLoad("${this.url}", ${panel ? `"${panel}"` : undefined}) - `).catch(err => { - console.log('Failed to load sidebar', err) - }) - sidebarView.webContents.focus() - } - } - - async captureScreenshot () { - // capture screenshot on the root page of dat & http sites - var urlp = parseDatURL(this.url) - if (['dat:', 'http:', 'https:'].includes(urlp.protocol) && urlp.pathname === '/') { - // wait a sec to allow loading to finish - await new Promise(r => setTimeout(r, 1e3)) - - // capture the page - var image = await this.browserView.webContents.capturePage() - var orgsize = image.getSize() - var bounds = findImageBounds(image.toBitmap(), orgsize) - - // adjust the bounds to match the 100x80 aspect ratio - if (bounds.width < bounds.height) { - // adjust width - bounds.right = bounds.left + (bounds.height / .8)|0 - } else { - // adjust height - bounds.bottom = bounds.top + (bounds.width * .8)|0 - } - - // give some margin - bounds.left = Math.max(0, bounds.left - 20) - bounds.right = Math.min(orgsize.width, bounds.right + 20) - bounds.top = Math.max(0, bounds.top - 20) - bounds.bottom = Math.min(orgsize.height, bounds.bottom + 20) - - image = image - .crop(bounds) - .resize({width: 200, height: 160}) - await sitedataDb.set(this.url, 'screenshot', image.toDataURL()) - } - } - - // inpage finder - // = - - showInpageFind () { - if (this.isInpageFindActive) { - // go to next result on repeat "show" commands - this.moveInpageFind(1) - } else { - this.isInpageFindActive = true - this.currentInpageFindResults = {activeMatchOrdinal: 0, matches: 0} - this.emitUpdateState() - } - this.browserWindow.webContents.focus() - } - - hideInpageFind () { - this.webContents.stopFindInPage('clearSelection') - this.isInpageFindActive = false - this.currentInpageFindString = undefined - this.currentInpageFindResults = undefined - this.emitUpdateState() - } - - setInpageFindString (str, dir) { - var findNext = this.currentInpageFindString === str - this.currentInpageFindString = str - this.webContents.findInPage(this.currentInpageFindString, {findNext, forward: dir !== -1}) - } - - moveInpageFind (dir) { - this.webContents.findInPage(this.currentInpageFindString, {findNext: false, forward: dir !== -1}) - } - - // alternative protocols - // = - - async checkForDatAlternative (url) { - let u = (new URL(url)) - // try to do a name lookup - var siteHasDatAlternative = await beakerCore.dat.dns.resolveName(u.hostname).then( - res => Boolean(res), - err => false - ) - if (siteHasDatAlternative) { - var autoRedirectToDat = !!await beakerCore.dbs.settings.get('auto_redirect_to_dat') - if (autoRedirectToDat && !noRedirectHostnames.has(u.hostname)) { - // automatically redirect - let datUrl = url.replace(u.protocol, 'dat:') - this.loadURL(datUrl) - } else { - // track that dat is available - this.availableAlternative = 'dat:' - } - } else { - this.availableAlternative = '' - } - this.emitUpdateState() - } - - // live reloading - // = - - toggleLiveReloading (enable) { - if (typeof enable === 'undefined') { - enable = !this.liveReloadEvents - } - if (!enable) { - this.liveReloadEvents.close() - this.liveReloadEvents = false - } else if (this.datInfo) { - let archive = beakerCore.dat.library.getArchive(this.datInfo.key) - if (!archive) return - - let {version} = parseDatURL(this.url) - let {checkoutFS} = beakerCore.dat.library.getArchiveCheckout(archive, version) - this.liveReloadEvents = checkoutFS.pda.watch() - - let event = (this.datInfo.isOwner) ? 'changed' : 'invalidated' - const reload = _throttle(() => { - this.browserView.webContents.reload() - }, TRIGGER_LIVE_RELOAD_DEBOUNCE, {leading: false}) - this.liveReloadEvents.on('data', ([evt]) => { - if (evt === event) reload() - }) - // ^ note this throttle is run on the front edge. - // That means snappier reloads (no delay) but possible double reloads if multiple files change - } - this.emitUpdateState() - } - - stopLiveReloading () { - if (this.liveReloadEvents) { - this.liveReloadEvents.close() - this.liveReloadEvents = false - this.emitUpdateState() - } - } - - // custom renderers - // = - - async injectCustomRenderers () { - // determine content type - let contentType = getResourceContentType(this.url) || '' - let isJSON = contentType.startsWith('application/json') - let isPlainText = contentType.startsWith('text/plain') - - // json rendering - // inject the json render script - if (isJSON || (isPlainText && this.url.endsWith('.json'))) { - this.webContents.insertCSS(` - .hidden { display: none !important; } - .json-formatter-row { - font-family: Consolas, 'Lucida Console', Monaco, monospace !important; - line-height: 1.6 !important; - font-size: 13px; - } - .json-formatter-row > a > .json-formatter-preview-text { - transition: none !important; - } - nav { margin-bottom: 5px; user-select: none; } - nav > span { - cursor: pointer; - display: inline-block; - font-family: Consolas, "Lucida Console", Monaco, monospace; - cursor: pointer; - font-size: 13px; - background: rgb(250, 250, 250); - padding: 3px 5px; - margin-right: 5px; - } - nav > span.pressed { - box-shadow: inset 2px 2px 2px rgba(0,0,0,.05); - background: #ddd; - } - `) - let jsonpath = path.join(app.getAppPath(), 'json-renderer.build.js') - jsonpath = jsonpath.replace('app.asar', 'app.asar.unpacked') // fetch from unpacked dir - this.webContents.executeJavaScript(await fs.readFile(jsonpath, 'utf8')) - } - } - - // state fetching - // = - - // helper called by UIs to pull latest state if a change event has occurred - // eg called by the bookmark systems after the bookmark state has changed - async refreshState () { - await Promise.all([ - this.fetchIsBookmarked(true), - this.fetchAnnotations(true), - this.fetchDatInfo(true) - ]) - this.emitUpdateState() - } - - async fetchIsBookmarked (noEmit = false) { - var bookmark = await bookmarksDb.getBookmark(0, normalizeURL(this.url, { - stripFragment: false, - stripWWW: false, - removeQueryParameters: false, - removeTrailingSlash: true - })) - this.isBookmarked = !!bookmark - if (!noEmit) { - this.emitUpdateState() - } - } - - async fetchAnnotations (noEmit = false) { - this.numComments = 0 - - var userSession = getUserSessionFor(this.browserWindow.webContents) - var followedUsers = (await beakerCore.crawler.follows.list({filters: {authors: userSession.url}})).map(({topic}) => topic.url) - var authors = [userSession.url].concat(followedUsers) - - // TODO replace with native 'count' method - var cs = await beakerCore.crawler.comments.thread(this.url.replace('+preview', ''), {filters: {authors}}) - function countComments (comments) { - return comments.reduce((acc, comment) => acc + 1 + (comment.replies ? countComments(comment.replies) : 0), 0) - } - this.numComments = countComments(cs) - - if (!noEmit) { - this.emitUpdateState() - } - } - - async fetchDatInfo (noEmit = false) { - // clear existing state - this.peers = 0 - this.numFollowers = 0 - this.donateLinkHref = null - - if (!this.url.startsWith('dat://')) { - this.datInfo = null - return - } - - // fetch new state - try { - var key = await beakerCore.dat.dns.resolveName(this.url) - this.datInfo = await beakerCore.dat.library.getArchiveInfo(key) - this.peers = this.datInfo.peers - this.donateLinkHref = _get(this, 'datInfo.links.payment.0.href') - } catch (e) { - this.datInfo = null - } - if (this.datInfo) { - let userSession = getUserSessionFor(this.browserWindow.webContents) - let userFollows = await beakerCore.crawler.follows.list({filters: {authors: userSession.url}}) - let followAuthors = [userSession.url].concat(userFollows.map(f => f.topic.url)) - let siteFollowers = await beakerCore.crawler.follows.list({filters: {topics: this.datInfo.url, authors: followAuthors}}) - this.numFollowers = siteFollowers.length - } - if (!noEmit) this.emitUpdateState() - } - - async getPageMetadata () { - var metadata - try { - metadata = this.webContents.executeJavaScript(`window.__beakerGetPageMetadata()`) - } catch (e) { - // ignore - } - return metadata || {} - } - - // events - // = - - emitUpdateState () { - if (this.isHidden) return - emitUpdateState(this.browserWindow, this) - } - - onDidStartLoading (e) { - // update state - this.isLoading = true - this.loadingURL = null - this.isReceivingAssets = false - this.wasDatTimeout = false - - // emit - this.emitUpdateState() - } - - onDidStartNavigation (e, url, isInPlace, isMainFrame) { - if (!isMainFrame) return - - // turn off live reloading if we're leaving the domain - if (toOrigin(url) !== toOrigin(this.url)) { - this.stopLiveReloading() - } - - // update state - this.loadingURL = url - this.emitUpdateState() - } - - async onDidNavigate (e, url, httpResponseCode) { - // remove any active subwindows - prompts.close(this.browserView) - modals.close(this.browserView) - - // read zoom - zoom.setZoomFromSitedata(this) - - // update state - this.loadError = null - this.loadingURL = null - this.isReceivingAssets = true - this.favicons = null - await this.fetchIsBookmarked() - /* dont await */ this.fetchAnnotations() - await this.fetchDatInfo() - if (httpResponseCode === 504 && url.startsWith('dat://')) { - this.wasDatTimeout = true - } - if (this.isSidebarActive) { - this.updateSidebar() - } - if (httpResponseCode === 404 && this.isOwner) { - // prompt to create a page on 404 for owned sites - prompts.create(this.browserView.webContents, 'create-page', {url: this.url}) - } - - // emit - this.emitUpdateState() - } - - onDidNavigateInPage (e) { - this.updateHistory() - /* dont await */ this.fetchAnnotations() - if (this.isSidebarActive) { - this.updateSidebar() - } - } - - onDidStopLoading (e) { - this.updateHistory() - - // update state - this.isLoading = false - this.loadingURL = null - this.isReceivingAssets = false - - // check for dat alternatives - if (this.url.startsWith('https://')) { - this.checkForDatAlternative(this.url) - } else { - this.availableAlternative = '' - } - - // run custom renderer apps - this.injectCustomRenderers() - - // emit - windowMenu.onSetCurrentLocation(this.browserWindow, this.url) - this.emitUpdateState() - } - - onDomReady (e) { - // capture screenshot - this.captureScreenshot() - } - - onDidFailLoad (e, errorCode, errorDescription, validatedURL, isMainFrame) { - // ignore if this is a subresource - if (!isMainFrame) return - - // ignore aborts. why: - // - sometimes, aborts are caused by redirects. no biggy - // - if the user cancels, then we dont want to give an error screen - if (errorDescription == 'ERR_ABORTED' || errorCode == ERR_ABORTED) return - - // also ignore non-errors - if (errorCode == 0) return - - // update state - var isInsecureResponse = IS_CODE_INSECURE_RESPONSE(errorCode) - this.loadError = {isInsecureResponse, errorCode, errorDescription, validatedURL} - this.emitUpdateState() - - // render failure page - var errorPageHTML = errorPage(this.loadError) - this.webContents.executeJavaScript('document.documentElement.innerHTML = \'' + errorPageHTML + '\'') - } - - onUpdateTargetUrl (e, url) { - statusBar.set(this.browserWindow, url) - } - - onPageTitleUpdated (e, title) { - this.emitUpdateState() - } - - onPageFaviconUpdated (e, favicons) { - this.favicons = favicons && favicons[0] ? favicons : null - this.emitUpdateState() - } - - onNewWindow (e, url, frameName, disposition) { - e.preventDefault() - if (!this.isActive) return // only open if coming from the active tab - var setActive = (disposition === 'foreground-tab' || disposition === 'new-window') - var views = activeViews[this.browserWindow.id] - var tabIndex = views ? (views.indexOf(this) + 1) : undefined - create(this.browserWindow, url, {setActive, tabIndex, isSidebarActive: this.isSidebarActive}) - } - - onMediaChange (e) { - // our goal with this event handler is to detect that audio is playing - // this lets us then render an "audio playing" icon on the tab - // for whatever reason, the event consistently precedes the "is audible" being set by at most 1s - // so, we delay for 1s, then emit a state update - setTimeout(() => this.emitUpdateState(), 1e3) - } - - onFoundInPage (e, res) { - this.currentInpageFindResults = { - activeMatchOrdinal: res.activeMatchOrdinal, - matches: res.matches - } - this.emitUpdateState() - } -} - -// exported api -// = - -export function setup () { - // listen for webContents messages - ipcMain.on('BEAKER_MARK_NEXT_VIEW_SCRIPTCLOSEABLE', e => { - nextViewIsScriptCloseable = true - e.returnValue = true - }) - ipcMain.on('BEAKER_SCRIPTCLOSE_SELF', e => { - var browserView = BrowserView.fromWebContents(e.sender) - if (browserView) { - var view = findView(browserView) - if (view && view.isScriptClosable) { - remove(view.browserWindow, view) - e.returnValue = true - return - } - } - e.returnValue = false - }) - - // track peer-counts - function iterateViews (cb) { - for (let winId in activeViews) { - for (let view of activeViews[winId]) { - cb(view) - } - } - } - beakerCore.dat.library.createEventStream().on('data', ([evt, {details}]) => { - if (evt === 'updated') { - iterateViews(view => { - if (view.datInfo && view.datInfo.url === details.url) { - view.refreshState() - } - }) - } - if (evt === 'network-changed') { - iterateViews(view => { - if (view.datInfo && view.datInfo.url === details.url) { - // update peer count - view.peers = details.connections - view.emitUpdateState() - } - if (view.wasDatTimeout && view.url.startsWith(details.url)) { - // refresh if this was a timed-out dat site (peers have been found) - view.webContents.reload() - } - }) - } - }) -} - -export function getAll (win) { - win = getTopWindow(win) - return activeViews[win.id] || [] -} - -export function getByIndex (win, index) { - win = getTopWindow(win) - if (index === 'active') return getActive(win) - return getAll(win)[index] -} - -export function getIndexOfView (win, view) { - win = getTopWindow(win) - return getAll(win).indexOf(view) -} - -export function getAllPinned (win) { - win = getTopWindow(win) - return getAll(win).filter(p => p.isPinned) -} - -export function getActive (win) { - win = getTopWindow(win) - return getAll(win).find(view => view.isActive) -} - -export function findView (browserView) { - for (let winId in activeViews) { - for (let v of activeViews[winId]) { - if (v.browserView === browserView) { - return v - } - } - } -} - -export function findContainingWindow (browserView) { - for (let winId in activeViews) { - for (let v of activeViews[winId]) { - if (v.browserView === browserView) { - return v.browserWindow - } - } - } - for (let winId in preloadedNewTabViews) { - if (preloadedNewTabViews[winId].browserView === browserView) { - return preloadedNewTabViews[winId].browserWindow - } - } -} - -export function create ( - win, - url, - opts = { - setActive: false, - isPinned: false, - focusLocationBar: false, - isSidebarActive: false, - tabIndex: undefined, - sidebarPanel: undefined - } - ) { - url = url || DEFAULT_URL - win = getTopWindow(win) - var views = activeViews[win.id] = activeViews[win.id] || [] - - var view - var preloadedNewTabView = preloadedNewTabViews[win.id] - if (url === DEFAULT_URL && !opts.isPinned && preloadedNewTabView) { - // use the preloaded new-tab view - view = preloadedNewTabView - view.isHidden = false // no longer hidden - preloadedNewTabView = preloadedNewTabViews[win.id] = null - } else { - // create a new view - view = new View(win, {isPinned: opts.isPinned}) - view.loadURL(url) - } - - // add to active views - if (opts.isPinned) { - views.splice(indexOfLastPinnedView(win), 0, view) - } else { - if (typeof opts.tabIndex !== 'undefined' && opts.tabIndex !== -1) { - views.splice(opts.tabIndex, 0, view) - } else { - views.push(view) - } - } - - // make active if requested, or if none others are - if (opts.setActive || !getActive(win)) { - setActive(win, view) - } - emitReplaceState(win) - - if (opts.focusLocationBar) { - win.webContents.send('command', 'focus-location') - } - if (opts.isSidebarActive) { - view.toggleSidebar(opts.sidebarPanel) - } - - // create a new preloaded view if needed - if (!preloadedNewTabView) { - preloadedNewTabViews[win.id] = preloadedNewTabView = new View(win, {isHidden: true}) - preloadedNewTabView.loadURL(DEFAULT_URL) - } - - return view -} - -export async function remove (win, view) { - win = getTopWindow(win) - // find - var views = getAll(win) - var i = views.indexOf(view) - if (i == -1) { - return console.warn('view-manager remove() called for missing view', view) - } - - // give the 'onbeforeunload' a chance to run - var onBeforeUnloadReturnValue = await fireBeforeUnloadEvent(view.webContents) - if (onBeforeUnloadReturnValue) { - var choice = dialog.showMessageBox({ - type: 'question', - buttons: ['Leave', 'Stay'], - title: 'Do you want to leave this site?', - message: 'Changes you made may not be saved.', - defaultId: 0, - cancelId: 1 - }) - var leave = (choice === 0) - if (!leave) return - } - - // save, in case the user wants to restore it - closedURLs[win.id] = closedURLs[win.id] || [] - closedURLs[win.id].push(view.url) - - // set new active if that was - if (view.isActive && views.length > 1) { - setActive(win, views[i + 1] || views[i - 1]) - } - - // remove - view.stopLiveReloading() - views.splice(i, 1) - view.destroy() - - // persist pins w/o this one, if that was - if (view.isPinned) { - savePins(win) - } - - // close the window if that was the last view - if (views.length === 0) { - return win.close() - } - - // emit - emitReplaceState(win) -} - -export async function removeAllExcept (win, view) { - win = getTopWindow(win) - var views = getAll(win).slice() // .slice() to duplicate the list - for (let v of views) { - if (v !== view) { - await remove(win, v) - } - } -} - -export async function removeAllToRightOf (win, view) { - win = getTopWindow(win) - var toRemove = [] - var views = getAll(win) - let index = views.indexOf(view) - for (let i = 0; i < views.length; i++) { - if (i > index) toRemove.push(views[i]) - } - for (let v of toRemove) { - await remove(win, v) - } -} - -export function setActive (win, view) { - win = getTopWindow(win) - if (typeof view === 'number') { - view = getByIndex(win, view) - } - if (!view) return - - // deactivate the old view - var active = getActive(win) - if (active) { - active.deactivate() - } - - // activate the new view - view.activate() - windowMenu.onSetCurrentLocation(win, view.url) // give the window-menu a chance to handle the change - emitReplaceState(win) -} - -export function resize (win) { - var active = getActive(win) - if (active) active.resize() -} - -export function initializeFromSnapshot (win, snapshot) { - win = getTopWindow(win) - for (let url of snapshot) { - create(win, url) - } -} - -export function takeSnapshot (win) { - win = getTopWindow(win) - return getAll(win) - .filter(v => !v.isPinned) - .map(v => v.url) - .filter(Boolean) -} - -export function togglePinned (win, view) { - win = getTopWindow(win) - // move tab to the "end" of the pinned tabs - var views = getAll(win) - var oldIndex = views.indexOf(view) - var newIndex = indexOfLastPinnedView(win) - if (oldIndex < newIndex) newIndex-- - views.splice(oldIndex, 1) - views.splice(newIndex, 0, view) - - // update view state - view.isPinned = !view.isPinned - emitReplaceState(win) - - // persist - savePins(win) -} - -export function savePins (win) { - win = getTopWindow(win) - return settingsDb.set('pinned_tabs', JSON.stringify(getAllPinned(win).map(p => p.url))) -} - -export async function loadPins (win) { - win = getTopWindow(win) - var json = await settingsDb.get('pinned_tabs') - try { JSON.parse(json).forEach(url => create(win, url, {isPinned: true})) } - catch (e) {} -} - -export function reopenLastRemoved (win) { - win = getTopWindow(win) - var url = (closedURLs[win.id] || []).pop() - if (url) { - var view = create(win, url) - setActive(win, view) - return view - } -} - -export function reorder (win, oldIndex, newIndex) { - win = getTopWindow(win) - if (oldIndex === newIndex) { - return - } - var views = getAll(win) - var view = getByIndex(win, oldIndex) - views.splice(oldIndex, 1) - views.splice(newIndex, 0, view) - emitReplaceState(win) -} - -export function changeActiveBy (win, offset) { - win = getTopWindow(win) - var views = getAll(win) - var active = getActive(win) - if (views.length > 1) { - var i = views.indexOf(active) - if (i === -1) { return console.warn('Active page is not in the pages list! THIS SHOULD NOT HAPPEN!') } - - i += offset - if (i < 0) i = views.length - 1 - if (i >= views.length) i = 0 - - setActive(win, views[i]) - } -} - -export function changeActiveTo (win, index) { - win = getTopWindow(win) - var views = getAll(win) - if (index >= 0 && index < views.length) { - setActive(win, views[index]) - } -} - -export function changeActiveToLast (win) { - win = getTopWindow(win) - var views = getAll(win) - setActive(win, views[views.length - 1]) -} - -export function openOrFocusDownloadsPage (win) { - win = getTopWindow(win) - var views = getAll(win) - var downloadsView = views.find(v => v.url.startsWith('beaker://downloads')) - if (!downloadsView) { - downloadsView = create(win, 'beaker://downloads') - } - setActive(win, downloadsView) -} - -export function emitReplaceState (win) { - win = getTopWindow(win) - var state = {tabs: getWindowTabState(win), isFullscreen: win.isFullScreen()} - emit(win, 'replace-state', state) - win.emit('custom-pages-updated', takeSnapshot(win)) -} - -export function emitUpdateState (win, view) { - win = getTopWindow(win) - var index = typeof view === 'number' ? view : getAll(win).indexOf(view) - if (index === -1) { - console.warn('WARNING: attempted to update state of a view not on the window') - return - } - var state = getByIndex(win, index).state - emit(win, 'update-state', {index, state}) - win.emit('custom-pages-updated', takeSnapshot(win)) -} - -// rpc api -// = - -rpc.exportAPI('background-process-views', viewsRPCManifest, { - createEventStream () { - return emitStream(getEvents(getWindow(this.sender))) - }, - - async refreshState (tab) { - var win = getWindow(this.sender) - var view = getByIndex(win, tab) - if (view) { - view.refreshState() - } - }, - - async getState () { - var win = getWindow(this.sender) - return getWindowTabState(win) - }, - - async getTabState (tab, opts) { - var win = getWindow(this.sender) - var view = getByIndex(win, tab) - if (view) { - var state = Object.assign({}, view.state) - if (opts) { - if (opts.datInfo) state.datInfo = view.datInfo - if (opts.networkStats) state.networkStats = view.datInfo ? view.datInfo.networkStats : {} - if (opts.sitePerms) state.sitePerms = await beakerCore.dbs.sitedata.getPermissions(view.url) - } - return state - } - }, - - async getNetworkState (tab) { - var win = getWindow(this.sender) - var view = getByIndex(win, tab) - if (view && view.datInfo) { - var networkStats = await beakerCore.dat.library.getArchiveNetworkStats(view.datInfo.key) - return { - peers: view.peers, - networkStats - } - } - }, - - async getPageMetadata (tab) { - var win = getWindow(this.sender) - var view = getByIndex(win, tab) - if (view) return view.getPageMetadata() - return {} - }, - - async createTab (url, opts = {focusLocationBar: false, setActive: false, addToNoRedirects: false}) { - if (opts.addToNoRedirects) { - addToNoRedirects(url) - } - - var win = getWindow(this.sender) - var view = create(win, url, opts) - return getAll(win).indexOf(view) - }, - - async loadURL (index, url, opts = {addToNoRedirects: false}) { - if (opts.addToNoRedirects) { - addToNoRedirects(url) - } - - getByIndex(getWindow(this.sender), index).loadURL(url) - }, - - async closeTab (index) { - var win = getWindow(this.sender) - remove(win, getByIndex(win, index)) - }, - - async setActiveTab (index) { - var win = getWindow(this.sender) - setActive(win, getByIndex(win, index)) - }, - - async reorderTab (oldIndex, newIndex) { - var win = getWindow(this.sender) - reorder(win, oldIndex, newIndex) - }, - - async showTabContextMenu (index) { - var win = getWindow(this.sender) - var view = getByIndex(win, index) - var menu = Menu.buildFromTemplate([ - { label: 'New Tab', click: () => create(win, null, {setActive: true}) }, - { type: 'separator' }, - { label: 'Duplicate', click: () => create(win, view.url) }, - { label: (view.isPinned) ? 'Unpin Tab' : 'Pin Tab', click: () => togglePinned(win, view) }, - { label: (view.isAudioMuted) ? 'Unmute Tab' : 'Mute Tab', click: () => view.toggleMuted() }, - { type: 'separator' }, - { label: 'Close Tab', click: () => remove(win, view) }, - { label: 'Close Other Tabs', click: () => removeAllExcept(win, view) }, - { label: 'Close Tabs to the Right', click: () => removeAllToRightOf(win, view) }, - { type: 'separator' }, - { label: 'Reopen Closed Tab', click: () => reopenLastRemoved(win) } - ]) - menu.popup({window: win}) - }, - - async showLocationBarContextMenu (index) { - var win = getWindow(this.sender) - var view = getByIndex(win, index) - var clipboardContent = clipboard.readText() - var clipInfo = examineLocationInput(clipboardContent) - var menu = Menu.buildFromTemplate([ - { label: 'Cut', role: 'cut' }, - { label: 'Copy', role: 'copy' }, - { label: 'Paste', role: 'paste' }, - { label: `Paste and ${clipInfo.isProbablyUrl ? 'Go' : 'Search'}`, click: onPasteAndGo } - ]) - menu.popup({window: win}) - - function onPasteAndGo () { - // close the menu - shellMenus.hide(win) - win.webContents.send('command', 'unfocus-location') - - // load the URL - var url = clipInfo.isProbablyUrl ? clipInfo.vWithProtocol : clipInfo.vSearch - view.loadURL(url) - } - }, - - async goBack (index) { - getByIndex(getWindow(this.sender), index).webContents.goBack() - }, - - async goForward (index) { - getByIndex(getWindow(this.sender), index).webContents.goForward() - }, - - async stop (index) { - getByIndex(getWindow(this.sender), index).webContents.stop() - }, - - async reload (index) { - getByIndex(getWindow(this.sender), index).webContents.reload() - }, - - async resetZoom (index) { - zoom.zoomReset(getByIndex(getWindow(this.sender), index)) - }, - - async toggleLiveReloading (index, enabled) { - getByIndex(getWindow(this.sender), index).toggleLiveReloading(enabled) - }, - - async toggleSidebar (index, panel = undefined) { - getByIndex(getWindow(this.sender), index).toggleSidebar(panel) - }, - - async toggleDevTools (index) { - getByIndex(getWindow(this.sender), index).webContents.toggleDevTools() - }, - - async print (index) { - getByIndex(getWindow(this.sender), index).webContents.print() - }, - - async showInpageFind (index) { - getByIndex(getWindow(this.sender), index).showInpageFind() - }, - - async hideInpageFind (index) { - getByIndex(getWindow(this.sender), index).hideInpageFind() - }, - - async setInpageFindString (index, str, dir) { - getByIndex(getWindow(this.sender), index).setInpageFindString(str, dir) - }, - - async moveInpageFind (index, dir) { - getByIndex(getWindow(this.sender), index).moveInpageFind(dir) - }, - - async showLocationBar (opts) { - await locationBar.show(getWindow(this.sender), opts) - }, - - async hideLocationBar () { - await locationBar.hide(getWindow(this.sender)) - }, - - async runLocationBarCmd (cmd, opts) { - return locationBar.runCmd(getWindow(this.sender), cmd, opts) - }, - - async showMenu (id, opts) { - await shellMenus.show(getWindow(this.sender), id, opts) - }, - - async toggleMenu (id, opts) { - await shellMenus.toggle(getWindow(this.sender), id, opts) - }, - - async focusShellWindow () { - getWindow(this.sender).webContents.focus() - }, - - async onFaviconLoadSuccess (index, dataUrl) { - var view = getByIndex(getWindow(this.sender), index) - if (view) { - // if not a dat site, store the favicon - // (dat caches favicons through the dat/assets.js process in beaker-core) - if (!view.url.startsWith('dat:')) { - beakerCore.dbs.sitedata.set(view.url, 'favicon', dataUrl) - } - } - }, - - async onFaviconLoadError (index) { - var view = getByIndex(getWindow(this.sender), index) - if (view) { - view.favicons = null - view.emitUpdateState() - } - } -}) - -// internal methods -// = - -function getWindow (sender) { - return findWebContentsParentWindow(sender) -} - -// helper ensures that if a subwindow is called, we use the parent -function getTopWindow (win) { - if (!(win instanceof BrowserWindow)) { - return findWebContentsParentWindow(win) - } - while (win.getParentWindow()) { - win = win.getParentWindow() - } - return win -} - -function getEvents (win) { - if (!(win.id in windowEvents)) { - windowEvents[win.id] = new Events() - } - return windowEvents[win.id] -} - -function emit (win, ...args) { - getEvents(win).emit(...args) -} - -function getWindowTabState (win) { - return getAll(win).map(view => view.state) -} - -function indexOfLastPinnedView (win) { - var views = getAll(win) - var index = 0 - for (index; index < views.length; index++) { - if (!views[index].isPinned) break - } - return index -} - -function toOrigin (str) { - try { - var u = new URL(str) - return u.protocol + '//' + u.hostname - } catch (e) { return '' } -} - -function addToNoRedirects (url) { - try { - var u = new URL(url) - noRedirectHostnames.add(u.hostname) - } catch (e) { - console.log('Failed to add URL to noRedirectHostnames', url, e) - } -} - -async function fireBeforeUnloadEvent (wc) { - try { - if (wc.isLoading() || wc.isWaitingForResponse()) { - return // dont bother - } - return await Promise.race([ - wc.executeJavaScript(` - (function () { - let unloadEvent = new Event('beforeunload', {bubbles: false, cancelable: true}) - unloadEvent.returnValue = false - return window.dispatchEvent(unloadEvent) - })() - `), - new Promise(r => { - setTimeout(r, 500) // thread may be locked, so abort after 500ms - }) - ]) - } catch (e) { - // ignore - } -} - -// `nextViewIsScriptCloseable` is set by a message received prior to window.open() being called -// we capture the state of the flag on the next created view, then reset it -function takeIsScriptClosable () { - var b = nextViewIsScriptCloseable - nextViewIsScriptCloseable = false - return b -} \ No newline at end of file diff --git a/app/background-process/ui/window-menu.js b/app/background-process/ui/window-menu.js deleted file mode 100644 index b082be0799..0000000000 --- a/app/background-process/ui/window-menu.js +++ /dev/null @@ -1,538 +0,0 @@ -import * as beakerCore from '@beaker/core' -import { app, BrowserWindow, dialog, ipcMain, Menu } from 'electron' -import { createShellWindow, getFocusedDevToolsHost } from './windows' -import * as viewManager from './view-manager' -import * as viewZoom from './views/zoom' -import {download} from './downloads' - -// exported APIs -// = - -export function setup () { - setApplicationMenu({ noWindows: true }) - - // watch for changes to the currently active window - app.on('browser-window-focus', async (e, win) => { - try { - // fetch the current url - const url = viewManager.getActive(win).url - - // rebuild as needed - if (requiresRebuild(url)) { - setApplicationMenu({url}) - } - } catch (e) { - // `pages` not set yet - } - }) - - // watch for all windows to be closed - app.on('custom-window-all-closed', () => { - setApplicationMenu({ noWindows: true }) - }) - - // watch for any window to be opened - app.on('browser-window-created', () => { - setApplicationMenu() - }) -} - -export function onSetCurrentLocation (win, url) { - // check if this is the currently focused window - if (!url || win !== BrowserWindow.getFocusedWindow()) { - return - } - - // rebuild as needed - if (requiresRebuild(url)) { - setApplicationMenu({url}) - } -} - -export function setApplicationMenu (opts = {}) { - Menu.setApplicationMenu(Menu.buildFromTemplate(buildWindowMenu(opts))) -} - -export function buildWindowMenu (opts = {}) { - const isDat = opts.url && opts.url.startsWith('dat://') - const noWindows = opts.noWindows === true - - var darwinMenu = { - label: 'Beaker', - submenu: [ - { - label: 'Preferences', - accelerator: 'Command+,', - click (item, win) { - if (win) viewManager.create(win, 'beaker://settings', {setActive: true}) - else createShellWindow({ pages: ['beaker://settings'] }) - } - }, - { type: 'separator' }, - { label: 'Services', role: 'services', submenu: [] }, - { type: 'separator' }, - { label: 'Hide Beaker', accelerator: 'Command+H', role: 'hide' }, - { label: 'Hide Others', accelerator: 'Command+Alt+H', role: 'hideothers' }, - { label: 'Show All', role: 'unhide' }, - { type: 'separator' }, - { label: 'Quit', accelerator: 'Command+Q', click () { app.quit() }, reserved: true } - ] - } - - var fileMenu = { - label: 'File', - submenu: [ - { - label: 'New Tab', - accelerator: 'CmdOrCtrl+T', - click: function (item, win) { - if (win) { - viewManager.create(win, undefined, {setActive: true, focusLocationBar: true}) - } else { - createShellWindow() - } - }, - reserved: true - }, - { - label: 'New Window', - accelerator: 'CmdOrCtrl+N', - click: function () { createShellWindow() }, - reserved: true - }, - { - label: 'Reopen Closed Tab', - accelerator: 'CmdOrCtrl+Shift+T', - click: function (item, win) { - createWindowIfNone(win, (win) => { - viewManager.reopenLastRemoved(win) - }) - }, - reserved: true - }, - { - label: 'Open File', - accelerator: 'CmdOrCtrl+O', - click: function (item, win) { - createWindowIfNone(win, (win) => { - dialog.showOpenDialog({ title: 'Open file...', properties: ['openFile', 'createDirectory'] }, files => { - if (files && files[0]) { viewManager.create(win, 'file://' + files[0], {setActive: true}) } - }) - }) - } - }, - { - label: 'Open Location', - accelerator: 'CmdOrCtrl+L', - click: function (item, win) { - createWindowIfNone(win, (win) => { - win.webContents.send('command', 'focus-location') - }) - } - }, - { type: 'separator' }, - { - label: 'Save Page As...', - enabled: !noWindows, - accelerator: 'CmdOrCtrl+S', - click: async (item, win) => { - var view = viewManager.getActive(win) - if (!view) return - const url = view.url - const title = view.title - dialog.showSaveDialog({ title: `Save ${title} as...`, defaultPath: app.getPath('downloads') }, filepath => { - if (filepath) download(win, win.webContents, url, { saveAs: filepath, suppressNewDownloadEvent: true }) - }) - } - }, - { - label: 'Print...', - enabled: !noWindows, - accelerator: 'CmdOrCtrl+P', - click: (item, win) => { - var view = viewManager.getActive(win) - if (!view) return - view.webContents.print() - } - }, - { type: 'separator' }, - { - label: 'Close Window', - enabled: !noWindows, - accelerator: 'CmdOrCtrl+Shift+W', - click: function (item, win) { - if (win) win.close() - }, - reserved: true - }, - { - label: 'Close Tab', - enabled: !noWindows, - accelerator: 'CmdOrCtrl+W', - click: function (item, win) { - if (win) { - // a regular browser window - let active = viewManager.getActive(win) - if (active) viewManager.remove(win, active) - } else { - // devtools - let wc = getFocusedDevToolsHost() - if (wc) { - wc.closeDevTools() - } - } - }, - reserved: true - } - ] - } - - var editMenu = { - label: 'Edit', - submenu: [ - { label: 'Undo', enabled: !noWindows, accelerator: 'CmdOrCtrl+Z', selector: 'undo:', reserved: true }, - { label: 'Redo', enabled: !noWindows, accelerator: 'Shift+CmdOrCtrl+Z', selector: 'redo:', reserved: true }, - { type: 'separator' }, - { label: 'Cut', enabled: !noWindows, accelerator: 'CmdOrCtrl+X', selector: 'cut:', reserved: true }, - { label: 'Copy', enabled: !noWindows, accelerator: 'CmdOrCtrl+C', selector: 'copy:', reserved: true }, - { label: 'Paste', enabled: !noWindows, accelerator: 'CmdOrCtrl+V', selector: 'paste:', reserved: true }, - { label: 'Select All', enabled: !noWindows, accelerator: 'CmdOrCtrl+A', selector: 'selectAll:' }, - { - label: 'Find in Page', - enabled: !noWindows, - accelerator: 'CmdOrCtrl+F', - click: function (item, win) { - var view = viewManager.getActive(win) - if (view) view.showInpageFind() - } - }, - { - label: 'Find Next', - enabled: !noWindows, - accelerator: 'CmdOrCtrl+G', - click: function (item, win) { - var view = viewManager.getActive(win) - if (view) view.moveInpageFind(1) - } - }, - { - label: 'Find Previous', - enabled: !noWindows, - accelerator: 'Shift+CmdOrCtrl+G', - click: function (item, win) { - var view = viewManager.getActive(win) - if (view) view.moveInpageFind(-1) - } - } - ] - } - - var viewMenu = { - label: 'View', - submenu: [{ - label: 'Reload', - enabled: !noWindows, - accelerator: 'CmdOrCtrl+R', - click: function (item, win) { - if (win) { - let active = viewManager.getActive(win) - if (active) { - active.webContents.reload() - } - } - }, - reserved: true - }, - { - label: 'Hard Reload (Clear Cache)', - accelerator: 'CmdOrCtrl+Shift+R', - click: function (item, win) { - // HACK - // this is *super* lazy but it works - // clear all dat-dns cache on hard reload, to make sure the next - // load is fresh - // -prf - beakerCore.dat.dns.flushCache() - - if (win) { - let active = viewManager.getActive(win) - if (active) { - active.webContents.reloadIgnoringCache() - } - } - }, - reserved: true - }, - { type: 'separator' }, - { - label: 'Zoom In', - enabled: !noWindows, - accelerator: 'CmdOrCtrl+Plus', - reserved: true, - click: function (item, win) { - if (win) { - viewZoom.zoomIn(viewManager.getActive(win)) - } - } - }, - { - label: 'Zoom Out', - enabled: !noWindows, - accelerator: 'CmdOrCtrl+-', - reserved: true, - click: function (item, win) { - if (win) { - viewZoom.zoomOut(viewManager.getActive(win)) - } - } - }, - { - label: 'Actual Size', - enabled: !noWindows, - accelerator: 'CmdOrCtrl+0', - click: function (item, win) { - if (win) { - viewZoom.zoomReset(viewManager.getActive(win)) - } - } - }, - { type: 'separator' }, - { - type: 'submenu', - label: 'Advanced Tools', - submenu: [{ - label: 'Reload Shell-Window', - enabled: !noWindows, - click: function () { - BrowserWindow.getFocusedWindow().webContents.reloadIgnoringCache() - } - }, { - label: 'Toggle Shell-Window DevTools', - enabled: !noWindows, - click: function () { - BrowserWindow.getFocusedWindow().webContents.openDevTools({mode: 'detach'}) - } - }, - { type: 'separator' }, - { - label: 'Open Archives Debug Page', - enabled: !noWindows, - click: function (item, win) { - if (win) viewManager.create(win, 'beaker://internal-archives/', {setActive: true}) - } - }, { - label: 'Open Dat-DNS Cache Page', - enabled: !noWindows, - click: function (item, win) { - if (win) viewManager.create(win, 'beaker://dat-dns-cache/', {setActive: true}) - } - }, { - label: 'Open Debug Log Page', - enabled: !noWindows, - click: function (item, win) { - if (win) viewManager.create(win, 'beaker://debug-log/', {setActive: true}) - } - }] - }, - { - label: 'Toggle Sidebar', - enabled: !noWindows, - accelerator: 'CmdOrCtrl+B', - click: function (item, win) { - if (win) { - let active = viewManager.getActive(win) - if (active) active.toggleSidebar() - } - } - }, - { - label: 'Toggle Terminal', - enabled: !noWindows, - accelerator: 'Ctrl+`', - click: function (item, win) { - if (win) { - let active = viewManager.getActive(win) - if (active) active.toggleSidebar('terminal') - } - } - }, - { - label: 'Toggle DevTools', - enabled: !noWindows, - accelerator: (process.platform === 'darwin') ? 'Alt+CmdOrCtrl+I' : 'Shift+CmdOrCtrl+I', - click: function (item, win) { - if (win) { - let active = viewManager.getActive(win) - if (active) active.webContents.toggleDevTools() - } - }, - reserved: true - }, - { - label: 'Toggle Live Reloading', - enabled: !!isDat, - click: function (item, win) { - if (win) { - let active = viewManager.getActive(win) - if (active) active.toggleLiveReloading() - } - } - }, - { type: 'separator' }, - { - label: 'Full Screen', - enabled: !noWindows, - accelerator: (process.platform === 'darwin') ? 'Ctrl+Cmd+F' : 'F11', - role: 'toggleFullScreen' - } - ] - } - - var showHistoryAccelerator = 'Ctrl+h' - - if (process.platform === 'darwin') { - showHistoryAccelerator = 'Cmd+y' - } - - var historyMenu = { - label: 'History', - role: 'history', - submenu: [ - { - label: 'Back', - enabled: !noWindows, - accelerator: 'CmdOrCtrl+Left', - click: function (item, win) { - if (win) { - let active = viewManager.getActive(win) - if (active) active.webContents.goBack() - } - } - }, - { - label: 'Forward', - enabled: !noWindows, - accelerator: 'CmdOrCtrl+Right', - click: function (item, win) { - if (win) { - let active = viewManager.getActive(win) - if (active) active.webContents.goForward() - } - } - }, - { - label: 'Show Full History', - accelerator: showHistoryAccelerator, - click: function (item, win) { - if (win) viewManager.create(win, 'beaker://history', {setActive: true}) - else createShellWindow({ pages: ['beaker://history'] }) - } - }, - { type: 'separator' }, - { - label: 'Bookmark this Page', - enabled: !noWindows, - accelerator: 'CmdOrCtrl+D', - click: function (item, win) { - if (win) win.webContents.send('command', 'create-bookmark') - } - } - ] - } - - var windowMenu = { - label: 'Window', - role: 'window', - submenu: [ - { - label: 'Minimize', - accelerator: 'CmdOrCtrl+M', - role: 'minimize' - }, - { - label: 'Next Tab', - enabled: !noWindows, - accelerator: 'CmdOrCtrl+}', - click: function (item, win) { - if (win) viewManager.changeActiveBy(win, 1) - } - }, - { - label: 'Previous Tab', - enabled: !noWindows, - accelerator: 'CmdOrCtrl+{', - click: function (item, win) { - if (win) viewManager.changeActiveBy(win, -1) - } - } - ] - } - if (process.platform == 'darwin') { - windowMenu.submenu.push({ - type: 'separator' - }) - windowMenu.submenu.push({ - label: 'Bring All to Front', - role: 'front' - }) - } - - var helpMenu = { - label: 'Help', - role: 'help', - submenu: [ - { - label: 'Help', - accelerator: 'F1', - click: function (item, win) { - if (win) viewManager.create(win, 'https://beakerbrowser.com/docs/', {setActive: true}) - } - }, - { - label: 'Report Bug', - click: function (item, win) { - if (win) viewManager.create(win, 'https://github.com/beakerbrowser/beaker/issues', {setActive: true}) - } - }, - { - label: 'Mailing List', - click: function (item, win) { - if (win) viewManager.create(win, 'https://groups.google.com/forum/#!forum/beaker-browser', {setActive: true}) - } - } - ] - } - if (process.platform !== 'darwin') { - helpMenu.submenu.push({ type: 'separator' }) - helpMenu.submenu.push({ - label: 'About', - role: 'about', - click: function (item, win) { - if (win) viewManager.create(win, 'beaker://settings', {setActive: true}) - } - }) - } - - // assemble final menu - var menus = [fileMenu, editMenu, viewMenu, historyMenu, windowMenu, helpMenu] - if (process.platform === 'darwin') menus.unshift(darwinMenu) - return menus -} - -// internal helpers -// = - -var lastURLProtocol = false -function requiresRebuild (url) { - const urlProtocol = url ? url.split(':')[0] : false - // check if this is a change of protocol - const b = (lastURLProtocol !== urlProtocol) - lastURLProtocol = urlProtocol - return b -} - -function createWindowIfNone (win, onShow) { - if (win) return onShow(win) - win = createShellWindow() - win.once('show', onShow.bind(null, win)) -} diff --git a/app/bg/README.md b/app/bg/README.md new file mode 100644 index 0000000000..5bd5b65287 --- /dev/null +++ b/app/bg/README.md @@ -0,0 +1,14 @@ +# BG (background) + +This folder contains backend code that drives Beaker's main electron process. + +Notable folders: + + - `lib` is reusable code that's specific to backend. + - `dat` manages the dat daemon and any dat-specific behaviors. + - `dbs` contains all sqlite databases and manages any data persisted to them. + - `filesystem` manages the user's primary hyperdrive and manages any data which is persisted to it. This includes the dats library and users. + - `protocols` contains the custom URL scheme handlers. + - `rpc-manifests` contains the RPC manifests for internal RPC that's leveraged by `/app/fg` components. + - `ui` contains the code which manages windows, tabs, subwindows, and everything about the user interface. + - `web-apis` contains all interfaces which are exposed to the userland environment via RPC. It currently contains both fg and bg code (which maybe ought to change). diff --git a/app/background-process/adblocker.js b/app/bg/adblocker.js similarity index 100% rename from app/background-process/adblocker.js rename to app/bg/adblocker.js diff --git a/app/background-process/analytics.js b/app/bg/analytics.js similarity index 74% rename from app/background-process/analytics.js rename to app/bg/analytics.js index 1c466f3d87..2a0320566c 100644 --- a/app/background-process/analytics.js +++ b/app/bg/analytics.js @@ -1,4 +1,3 @@ -import * as beakerCore from '@beaker/core' import { app } from 'electron' import path from 'path' import crypto from 'crypto' @@ -8,9 +7,10 @@ import ms from 'ms' import jetpack from 'fs-jetpack' import concat from 'concat-stream' import osName from 'os-name' -const settingsDb = beakerCore.dbs.settings -import {ANALYTICS_DATA_FILE, ANALYTICS_SERVER, ANALYTICS_CHECKIN_INTERVAL} from '@beaker/core/lib/const' -const debug = beakerCore.debugLogger('beaker-analytics') +import * as settingsDb from './dbs/settings' +import {ANALYTICS_DATA_FILE, ANALYTICS_SERVER, ANALYTICS_CHECKIN_INTERVAL} from '../lib/const' +import * as logLib from './logger' +const logger = logLib.child({category: 'analytics'}) // exported methods // = @@ -30,23 +30,23 @@ async function checkin () { var pingData = await readPingData() if ((Date.now() - (pingData.lastPingTime || 0)) > ANALYTICS_CHECKIN_INTERVAL) { await sendPing(pingData) + pingData.lastPingTime = Date.now() + await writePingData(pingData) } - pingData.lastPingTime = Date.now() - await writePingData(pingData) } catch (e) { // failed, we'll reschedule another ping in 24 hours } } - // schedule another ping check in 24 hours - var to = setTimeout(checkin, ms('1d')) + // schedule another ping check in 3 hours + var to = setTimeout(checkin, ms('3h')) to.unref() } function sendPing (pingData) { return new Promise((resolve, reject) => { var qs = querystring.stringify({userId: pingData.id, os: osName(), beakerVersion: app.getVersion()}) - debug('Sending ping to %s: %s', ANALYTICS_SERVER, qs) + logger.info(`Sending ping to ${ANALYTICS_SERVER}: ${qs}`) var req = https.request({ method: 'POST', @@ -54,16 +54,16 @@ function sendPing (pingData) { path: '/ping?' + qs }, (res) => { if (res.statusCode === 204) { - debug('Ping succeeded') + logger.info('Ping succeeded') resolve() } else { res.setEncoding('utf8') - res.pipe(concat(body => debug('Ping failed', res.statusCode, body))) + res.pipe(concat(body => logger.info(`Ping failed: ${res.statusCode} ${JSON.stringify(body)}`))) reject() } }) req.on('error', err => { - debug('Ping failed', err) + logger.info(`Ping failed: ${err.toString()}`) reject() }) req.end() diff --git a/app/background-process/browser.js b/app/bg/browser.js similarity index 62% rename from app/background-process/browser.js rename to app/bg/browser.js index 7f99ce0329..8a53e8d749 100644 --- a/app/background-process/browser.js +++ b/app/bg/browser.js @@ -1,10 +1,8 @@ -import * as beakerCore from '@beaker/core' -import {app, dialog, BrowserWindow, webContents, ipcMain, shell, Menu, screen, session, nativeImage} from 'electron' -import {autoUpdater} from 'electron-updater' +import { app, dialog, BrowserWindow, BrowserView, webContents, ipcMain, shell, Menu, screen, session, nativeImage } from 'electron' +import { autoUpdater } from 'electron-updater' import os from 'os' import path from 'path' import fs from 'fs' -import slugify from 'slugify' import jetpack from 'fs-jetpack' import ICO from 'icojs' import toIco from 'to-ico' @@ -12,14 +10,21 @@ import emitStream from 'emit-stream' import EventEmitter from 'events' import LRU from 'lru' const exec = require('util').promisify(require('child_process').exec) -const debug = beakerCore.debugLogger('beaker') -const settingsDb = beakerCore.dbs.settings -import {open as openUrl} from './open-url' -import {getUserSessionFor, setUserSessionFor} from './ui/windows' -import * as viewManager from './ui/view-manager' +import * as logLib from './logger' +const logger = logLib.child({category: 'browser'}) +import * as settingsDb from './dbs/settings' +import { convertDatArchive } from './dat/index' +import { open as openUrl } from './open-url' +import * as windows from './ui/windows' +import * as tabManager from './ui/tab-manager' +import { updateSetupState } from './ui/setup-flow' import * as modals from './ui/subwindows/modals' -import { findWebContentsParentWindow } from '../lib/electron' -import {INVALID_SAVE_FOLDER_CHAR_REGEX} from '@beaker/core/lib/const' +import * as siteInfo from './ui/subwindows/site-info' +import { findWebContentsParentWindow } from './lib/electron' +import { getEnvVar } from './lib/env' +import * as hyperDaemon from './hyper/daemon' +import * as bookmarks from './filesystem/bookmarks' +import { setupDefaultProfile, getProfile, getDriveIdent } from './filesystem/index' // constants // = @@ -48,23 +53,22 @@ autoUpdater.autoDownload = false var updaterState = UPDATER_STATUS_IDLE var updaterError = false // has there been an error? -// where is the user in the setup flow? -var userSetupStatus = false -var userSetupStatusLookupPromise - // content-type tracker var resourceContentTypes = new LRU(100) // URL -> Content-Type +// certificate tracker +var originCerts = new LRU(100) // hostname -> {issuerName, subjectName, validExpiry} + // events emitted to rpc clients var browserEvents = new EventEmitter() process.on('unhandledRejection', (reason, p) => { console.error('Unhandled Rejection at: Promise', p, 'reason:', reason) - debug('Unhandled Rejection at: Promise', p, 'reason:', reason) + logger.error(`Unhandled Rejection at: Promise ${p.toString()} reason: ${reason.toString()}`) }) process.on('uncaughtException', (err) => { console.error('Uncaught exception:', err) - debug('Uncaught exception:', err) + logger.error(`Uncaught exception: ${err.toString()}`) }) // exported methods @@ -80,66 +84,88 @@ export async function setup () { autoUpdater.on('update-downloaded', onUpdateDownloaded) autoUpdater.on('error', onUpdateError) } catch (e) { - debug('[AUTO-UPDATE] error', e.toString()) + logger.error(`Auto-updater error: ${e.toString()}`) } setTimeout(scheduledAutoUpdate, 15e3) // wait 15s for first run } - // fetch user setup status - userSetupStatusLookupPromise = settingsDb.get('user-setup-status') - - // create a new user if none exists - var defaultUser = await beakerCore.users.getDefault() - if (!defaultUser) { - let newUserUrl = await beakerCore.dat.library.createNewArchive({ - title: 'Anonymous', - type: 'unwalled.garden/person' - }) - let archive = beakerCore.dat.library.getArchive(newUserUrl) - await archive.pda.writeFile('/thumb.jpg', await jetpack.cwd(__dirname).cwd('assets/img').readAsync('default-user-thumb.jpg', 'buffer'), 'binary') - await beakerCore.users.add('anonymous', newUserUrl, true) - } - // wire up events + app.on('browser-window-created', onBrowserWindowCreated) app.on('web-contents-created', onWebContentsCreated) - beakerCore.users.on('user-thumb-changed', onUserThumbChanged) // window.prompt handling // - we have use ipc directly instead of using rpc, because we need custom // response-lifecycle management in the main thread ipcMain.on('page-prompt-dialog', async (e, message, def) => { - try { - var res = await modals.create(e.sender, 'prompt', {message, default: def}) - e.returnValue = res && res.value ? res.value : false - } catch (e) { - e.returnValue = false + var res = await modals.create(e.sender, 'prompt', {message, default: def}).catch(e => false) + e.returnValue = res && res.value ? res.value : false + }) + + // HACK + // Electron has an issue where browserviews fail to calculate click regions after a resize + // https://github.com/electron/electron/issues/14038 + // we can solve this by forcing a recalculation after every resize + // -prf + ipcMain.on('resize-hackfix', (e, message) => { + var win = findWebContentsParentWindow(e.sender) + if (win) { + win.webContents.executeJavaScript(`if (window.forceUpdateDragRegions) { window.forceUpdateDragRegions() }`) } }) + // TEMPORARY HACK + // method to open the editor sidebar from untrusted pages + // -prf + ipcMain.on('temp-open-editor-sidebar', (e) => { + var win = findWebContentsParentWindow(e.sender) + if (win) { + tabManager.getActive(win).executeSidebarCommand('show-panel', 'editor-app') + } + }) + + // TEMPORARY HACK + // method to convert dats to hyperdrives + // -prf + ipcMain.on('temp-convert-dat', async (e, key) => { + var win = findWebContentsParentWindow(e.sender) + if (!win) return + var driveUrl = await convertDatArchive(key) + tabManager.create(win, driveUrl, {setActive: true}) + }) + // HACK // Electron doesn't give us a convenient way to check the content-types of responses + // or to fetch the certs of a hostname // so we track the last 100 responses' headers to accomplish this // -prf session.defaultSession.webRequest.onCompleted(onCompleted) + session.defaultSession.setCertificateVerifyProc((request, cb) => { + originCerts.set('https://' + request.hostname + '/', { + issuerName: request.certificate.issuerName, + subjectName: request.certificate.subjectName, + validExpiry: request.certificate.validExpiry + }) + cb(request.errorCode) + }) } export const WEBAPI = { createEventsStream, getInfo, + getDaemonStatus, + getDaemonNetworkStatus, + getProfile, checkForUpdates, restartBrowser, - getUserSession, - setUserSession, - showEditProfileModal, - getSetting, getSettings, setSetting, - getUserSetupStatus, - setUserSetupStatus, - getDefaultLocalPath, + updateSetupState, + setupDefaultProfile, + migrate08to09, setStartPageBackgroundImage, + getDefaultProtocolSettings, setAsDefaultProtocolClient, removeAsDefaultProtocolClient, @@ -149,25 +175,39 @@ export const WEBAPI = { readFile, getResourceContentType, + getCertificate, listBuiltinFavicons, getBuiltinFavicon, uploadFavicon, imageToIco, - openSidebar, - toggleSidebar, + reconnectHyperdriveDaemon () { + return hyperDaemon.setup() + }, + + executeSidebarCommand, + toggleSiteInfo, toggleLiveReloading, setWindowDimensions, setWindowDragModeEnabled, + setSidebarResizeModeEnabled, moveWindow, maximizeWindow, + resizeSiteInfo, showOpenDialog, showContextMenu, async showModal (name, opts) { return modals.create(this.sender, name, opts) }, + newWindow, gotoUrl, + getPageUrl, + refreshPage, + focusPage, + executeJavaScriptInPage, + injectCssInPage, + uninjectCssInPage, openUrl: (url, opts) => { openUrl(url, opts) }, // dont return anything openFolder, doWebcontentsCmd, @@ -192,7 +232,12 @@ export async function downloadURL (url) { this.sender.downloadURL(url) } -function readFile (pathname, opts) { +function readFile (obj, opts) { + var pathname = undefined + if (obj === 'beaker://std-cmds/index.json') { + pathname = path.join(__dirname, 'userland', 'std-cmds', 'index.json') + } + if (!pathname) return return new Promise((resolve, reject) => { fs.readFile(pathname, opts, (err, res) => { if (err) reject(err) @@ -207,6 +252,25 @@ export function getResourceContentType (url) { return resourceContentTypes.get(url) } +export async function getCertificate (url) { + try { + let urlp = new URL(url) + url = urlp.protocol + '//' + urlp.hostname + '/' + } catch (e) {} + var cert = originCerts.get(url) + if (cert) { + return Object.assign({type: 'tls'}, cert) + } else if (url.startsWith('beaker:')) { + return {type: 'beaker'} + } else if (url.startsWith('hyper://')) { + let ident = await getDriveIdent(url, true) + return { + type: 'hyperdrive', + ident + } + } +} + export async function listBuiltinFavicons ({filter, offset, limit} = {}) { if (filter) { filter = new RegExp(filter, 'i') @@ -263,19 +327,24 @@ export async function imageToIco (image) { return toIco(imageToPng, {resize: true}) } -async function openSidebar (panel) { - var win = findWebContentsParentWindow(this.sender) - viewManager.getActive(win).openSidebar(panel) +async function executeSidebarCommand (...args) { + return getSenderTab(this.sender).executeSidebarCommand(...args) } -async function toggleSidebar (panel) { +async function toggleSiteInfo (override) { var win = findWebContentsParentWindow(this.sender) - viewManager.getActive(win).toggleSidebar(panel) + if (override === true) { + siteInfo.show(win) + } else if (override === false) { + siteInfo.hide(win) + } else { + siteInfo.toggle(win) + } } export async function toggleLiveReloading (enabled) { var win = findWebContentsParentWindow(this.sender) - viewManager.getActive(win).toggleLiveReloading(enabled) + tabManager.getActive(win).toggleLiveReloading(enabled) } export async function setWindowDimensions ({width, height} = {}) { @@ -286,15 +355,15 @@ export async function setWindowDimensions ({width, height} = {}) { win.setSize(width, height) } -var _dragModeIntervals = {} +var _windowDragInterval = undefined export async function setWindowDragModeEnabled (enabled) { var win = findWebContentsParentWindow(this.sender) if (enabled) { - if (win.id in _dragModeIntervals) return + if (_windowDragInterval) return // poll the mouse cursor every 15ms var lastPt = screen.getCursorScreenPoint() - _dragModeIntervals[win.id] = setInterval(() => { + _windowDragInterval = setInterval(() => { var newPt = screen.getCursorScreenPoint() // if the mouse has moved, move the window accordingly @@ -308,15 +377,35 @@ export async function setWindowDragModeEnabled (enabled) { // if the mouse has moved out of the window, stop var bounds = win.getBounds() if (newPt.x < bounds.x || newPt.y < bounds.y || newPt.x > (bounds.x + bounds.width) || newPt.y > (bounds.y + bounds.height)) { - clearInterval(_dragModeIntervals[win.id]) - delete _dragModeIntervals[win.id] + clearInterval(_windowDragInterval) + _windowDragInterval = undefined } }, 15) } else { // stop the poll - if (!(win.id in _dragModeIntervals)) return - clearInterval(_dragModeIntervals[win.id]) - delete _dragModeIntervals[win.id] + if (!_windowDragInterval) return + clearInterval(_windowDragInterval) + _windowDragInterval = undefined + } +} + +var _sidebarResizeInterval = undefined +export async function setSidebarResizeModeEnabled (enabled) { + var win = findWebContentsParentWindow(this.sender) + var tab = tabManager.getActive(win) + if (!win || !tab) return + if (enabled) { + if (_sidebarResizeInterval) return + // poll the mouse cursor every 15ms + _sidebarResizeInterval = setInterval(() => { + var bounds = win.getBounds() + var pt = screen.getCursorScreenPoint() + tab.setSidebarWidth(pt.x - bounds.x) + }, 15) + } else { + if (!_sidebarResizeInterval) return + clearInterval(_sidebarResizeInterval) + _sidebarResizeInterval = undefined } } @@ -331,6 +420,12 @@ export async function maximizeWindow () { win.maximize() } +export function resizeSiteInfo (bounds) { + var win = findWebContentsParentWindow(this.sender) + if (!win) return + siteInfo.resize(win, bounds) +} + export function setStartPageBackgroundImage (srcPath, appendCurrentDir) { if (appendCurrentDir) { srcPath = path.join(__dirname, `/${srcPath}`) @@ -356,21 +451,24 @@ export async function getDefaultProtocolSettings () { // we can just use xdg-mime directly instead // see https://github.com/beakerbrowser/beaker/issues/915 // -prf - let [httpHandler, datHandler] = await Promise.all([ + let [httpHandler, hyperHandler, datHandler] = await Promise.all([ // If there is no default specified, be sure to catch any error // from exec and return '' otherwise Promise.all errors out. exec('xdg-mime query default "x-scheme-handler/http"').catch(err => ''), + exec('xdg-mime query default "x-scheme-handler/hyper"').catch(err => ''), exec('xdg-mime query default "x-scheme-handler/dat"').catch(err => '') ]) if (httpHandler && httpHandler.stdout) httpHandler = httpHandler.stdout + if (hyperHandler && hyperHandler.stdout) hyperHandler = hyperHandler.stdout if (datHandler && datHandler.stdout) datHandler = datHandler.stdout return { http: (httpHandler || '').toString().trim() === DOT_DESKTOP_FILENAME, + hyper: (hyperHandler || '').toString().trim() === DOT_DESKTOP_FILENAME, dat: (datHandler || '').toString().trim() === DOT_DESKTOP_FILENAME } } - return Promise.resolve(['http', 'dat'].reduce((res, x) => { + return Promise.resolve(['http', 'hyper', 'dat'].reduce((res, x) => { res[x] = app.isDefaultProtocolClient(x) return res }, {})) @@ -407,8 +505,26 @@ export function getInfo () { }, paths: { userData: app.getPath('userData') + }, + isDaemonActive: hyperDaemon.isActive() + } +} + +export async function getDaemonStatus () { + return hyperDaemon.getDaemonStatus() +} + +var crypto = require('hypercore-crypto') +export async function getDaemonNetworkStatus () { + var allStats = await hyperDaemon.getClient().drive.allStats() + for (let stats of allStats) { + stats[0].peerAddresses = await hyperDaemon.listPeerAddresses(crypto.discoveryKey(stats[0].metadata.key)) + for (let stat of stats) { + stat.metadata.key = stat.metadata.key.toString('hex') + stat.content.key = stat.content.key.toString('hex') } } + return allStats } export function checkForUpdates (opts = {}) { @@ -416,11 +532,11 @@ export function checkForUpdates (opts = {}) { if (updaterState != UPDATER_STATUS_IDLE) { return } // update global state - debug('[AUTO-UPDATE] Checking for a new version.') + logger.info('[AUTO-UPDATE] Checking for a new version.') updaterError = false setUpdaterState(UPDATER_STATUS_CHECKING) if (opts.prerelease) { - debug('[AUTO-UPDATE] Jumping to pre-releases.') + logger.info('[AUTO-UPDATE] Jumping to pre-releases.') autoUpdater.allowPrerelease = true } autoUpdater.checkForUpdates() @@ -433,41 +549,15 @@ export function restartBrowser () { if (updaterState == UPDATER_STATUS_DOWNLOADED) { // run the update installer autoUpdater.quitAndInstall() - debug('[AUTO-UPDATE] Quitting and installing.') + logger.info('[AUTO-UPDATE] Quitting and installing.') } else { - debug('Restarting Beaker by restartBrowser()') + logger.info('Restarting Beaker by restartBrowser()') // do a simple restart app.relaunch() setTimeout(() => app.exit(0), 1e3) } } -async function getUserSession () { - var sess = getUserSessionFor(this.sender) - // get session user info - if (!sess) return null - return beakerCore.users.get(sess.url) -} - -async function setUserSession (url) { - // normalize the url - var urlp = new URL(url) - url = urlp.origin - - // lookup the user - var user = await beakerCore.get(url) - if (!user) throw new Error('Invalid user URL') - - // set the session - var userSession = {url: user.url} - setUserSessionFor(this.sender, userSession) -} - -async function showEditProfileModal () { - var sess = await getUserSession.call(this) - return showShellModal(this.sender, 'edit-profile', sess) -} - export function getSetting (key) { return settingsDb.get(key) } @@ -480,18 +570,12 @@ export function setSetting (key, value) { return settingsDb.set(key, value) } -export async function getUserSetupStatus () { - // if not cached, defer to the lookup promise - return (userSetupStatus) || userSetupStatusLookupPromise -} - -export function setUserSetupStatus (status) { - userSetupStatus = status // cache - return settingsDb.set('user-setup-status', status) +export async function migrate08to09 () { + await bookmarks.migrateBookmarksFromSqlite() } const SCROLLBAR_WIDTH = 16 -export async function capturePage (url, opts) { +export async function capturePage (url, opts = {}) { var width = opts.width || 1024 var height = opts.height || 768 @@ -499,13 +583,17 @@ export async function capturePage (url, opts) { width: width + SCROLLBAR_WIDTH, height, show: false, - defaultEncoding: 'UTF-8', - partition: 'session-' + Date.now() + Math.random(), - preload: 'file://' + path.join(app.getAppPath(), 'webview-preload.build.js'), webPreferences: { - webSecurity: true, - allowRunningInsecureContent: false, - nativeWindowOpen: true + preload: 'file://' + path.join(app.getAppPath(), 'fg', 'webview-preload', 'index.build.js'), + nodeIntegrationInSubFrames: true, + contextIsolation: true, + webviewTag: false, + sandbox: true, + defaultEncoding: 'utf-8', + nativeWindowOpen: true, + nodeIntegration: false, + navigateOnDragDrop: true, + enableRemoteModule: false } }) win.loadURL(url) @@ -517,16 +605,14 @@ export async function capturePage (url, opts) { await new Promise(r => setTimeout(r, 200)) // give an extra 200ms for rendering // capture the page - var image = await new Promise((resolve, reject) => { - win.webContents.capturePage({x: 0, y: 0, width, height}, resolve) - }) + var image = await win.webContents.capturePage({x: 0, y: 0, width, height}) // resize if asked if (opts.resizeTo) { image = image.resize(opts.resizeTo) } - return image.toPNG() + return image } // rpc methods @@ -536,20 +622,17 @@ function createEventsStream () { return emitStream(browserEvents) } -function showOpenDialog (opts = {}) { +async function showOpenDialog (opts = {}) { var wc = this.sender - return new Promise((resolve) => { - dialog.showOpenDialog({ - title: opts.title, - buttonLabel: opts.buttonLabel, - filters: opts.filters, - properties: opts.properties, - defaultPath: opts.defaultPath - }, filenames => { - wc.focus() // return focus back to the the page - resolve(filenames) - }) + var res = await dialog.showOpenDialog({ + title: opts.title, + buttonLabel: opts.buttonLabel, + filters: opts.filters, + properties: opts.properties, + defaultPath: opts.defaultPath }) + wc.focus() // return focus back to the the page + return res.filePaths } function showContextMenu (menuDefinition) { @@ -569,7 +652,7 @@ function showContextMenu (menuDefinition) { } // add 'inspect element' in development - if (beakerCore.getEnvVar('NODE_ENV') === 'develop' || beakerCore.getEnvVar('NODE_ENV') === 'test') { + if (getEnvVar('NODE_ENV') === 'develop' || getEnvVar('NODE_ENV') === 'test') { menuDefinition.push({type: 'separator'}) menuDefinition.push({ label: 'Inspect Element', @@ -589,36 +672,52 @@ function showContextMenu (menuDefinition) { // show the menu var win = findWebContentsParentWindow(this.sender) var menu = Menu.buildFromTemplate(menuDefinition) - menu.popup({window: win}) - resolve(selection) + menu.popup({window: win, callback () { + resolve(selection) + }}) }) } +async function newWindow (state = {}) { + windows.createShellWindow(state) +} + async function gotoUrl (url) { - var win = findWebContentsParentWindow(this.sender) - viewManager.getActive(win).loadURL(url) + getSenderTab(this.sender).loadURL(url) } -function openFolder (folderPath) { - shell.openExternal('file://' + folderPath) +async function getPageUrl () { + return getSenderTab(this.sender).url } -async function getDefaultLocalPath (dir, title) { - // massage the title - title = typeof title === 'string' ? title : '' - title = title.replace(INVALID_SAVE_FOLDER_CHAR_REGEX, '') - if (!title.trim()) { - title = 'Untitled' - } - title = slugify(title).toLowerCase() +async function refreshPage () { + getSenderTab(this.sender).webContents.reload() +} - // find an available variant of title - let tryNum = 1 - let titleVariant = title - while (await jetpack.existsAsync(path.join(dir, titleVariant))) { - titleVariant = `${title}-${++tryNum}` - } - return path.join(dir, titleVariant) +async function focusPage () { + getSenderTab(this.sender).focus() +} + +async function executeJavaScriptInPage (js) { + return getSenderTab(this.sender).webContents.executeJavaScript(js, true) + .catch(err => { + if (err.toString().includes('Script failed to execute')) { + throw "Injected script failed to execute" + } + throw err + }) +} + +async function injectCssInPage (css) { + return getSenderTab(this.sender).webContents.insertCSS(css) +} + +async function uninjectCssInPage (key) { + return getSenderTab(this.sender).webContents.removeInsertedCSS(key) +} + +function openFolder (folderPath) { + shell.openExternal('file://' + folderPath) } async function doWebcontentsCmd (method, wcId, ...args) { @@ -633,6 +732,16 @@ async function doTest (test) { // internal methods // = +function getSenderTab (sender) { + var view = BrowserView.fromWebContents(sender) + if (view) { + let tab = tabManager.findTab(view) + if (tab) return tab + } + var win = findWebContentsParentWindow(sender) + return tabManager.getActive(win) +} + function setUpdaterState (state) { updaterState = state browserEvents.emit('updater-state-changed', {state}) @@ -662,29 +771,39 @@ function scheduledAutoUpdate () { // = function onUpdateAvailable () { - debug('[AUTO-UPDATE] New version available. Downloading...') + logger.info('[AUTO-UPDATE] New version available. Downloading...') autoUpdater.downloadUpdate() setUpdaterState(UPDATER_STATUS_DOWNLOADING) } function onUpdateNotAvailable () { - debug('[AUTO-UPDATE] No browser update available.') + logger.info('[AUTO-UPDATE] No browser update available.') setUpdaterState(UPDATER_STATUS_IDLE) } function onUpdateDownloaded () { - debug('[AUTO-UPDATE] New browser version downloaded. Ready to install.') + logger.info('[AUTO-UPDATE] New browser version downloaded. Ready to install.') setUpdaterState(UPDATER_STATUS_DOWNLOADED) } function onUpdateError (e) { console.error(e) - debug('[AUTO-UPDATE] error', e.toString(), e) + logger.error(`[AUTO-UPDATE] error: ${e.toString()}`) setUpdaterState(UPDATER_STATUS_IDLE) updaterError = {message: (e.toString() || '').split('\n')[0]} browserEvents.emit('updater-error', updaterError) } +function onBrowserWindowCreated (e, window) { + window.webContents.on('did-start-navigation', (e, url) => { + if (!url.startsWith('beaker://')) { + // never ever navigate the browser window out of trusted addresses + logger.info(`Destroyed browser window that attempted to navigate to ${url}`) + window.destroy() + } + }) +} + function onWebContentsCreated (e, webContents) { webContents.on('will-prevent-unload', onWillPreventUnload) webContents.on('remote-require', e => { @@ -697,12 +816,8 @@ function onWebContentsCreated (e, webContents) { }) } -function onUserThumbChanged (user) { - browserEvents.emit('user-thumb-changed', user) -} - function onWillPreventUnload (e) { - var choice = dialog.showMessageBox({ + var choice = dialog.showMessageBoxSync({ type: 'question', buttons: ['Leave', 'Stay'], title: 'Do you want to leave this site?', diff --git a/app/background-process/child-processes.js b/app/bg/child-processes.js similarity index 79% rename from app/background-process/child-processes.js rename to app/bg/child-processes.js index 3234c7353c..cb949a8217 100644 --- a/app/background-process/child-processes.js +++ b/app/bg/child-processes.js @@ -1,4 +1,3 @@ -import {app} from 'electron' import * as childProcess from 'child_process' // globals @@ -10,7 +9,7 @@ process.on('exit', closeAll) // exported api // = -export async function spawn (id, modulePath) { +export async function spawn (id, modulePath, ...args) { var fullModulePath = require.resolve(modulePath) const opts = { stdio: 'inherit', @@ -19,15 +18,13 @@ export async function spawn (id, modulePath) { }) } console.log(`Starting external process ${id}`) - var proc = childProcess.fork(fullModulePath, opts) + var proc = childProcess.fork(fullModulePath, args, opts) .on('error', console.log) .on('close', () => { delete processes[id] }) - proc.send({ - userDataPath: app.getPath('userData') - }) + proc.send({start: true}) await new Promise((resolve, reject) => { proc.once('message', resolve) @@ -46,4 +43,4 @@ export function closeAll () { for (let id in processes) { close(id) } -} +} \ No newline at end of file diff --git a/app/bg/dat/converter/README.md b/app/bg/dat/converter/README.md new file mode 100644 index 0000000000..677eae2780 --- /dev/null +++ b/app/bg/dat/converter/README.md @@ -0,0 +1,3 @@ +# Dat Converter + +This converter tool is maintained in a separate folder with its own node_modules to avoid conflicts with sodium-native. \ No newline at end of file diff --git a/app/bg/dat/converter/index.js b/app/bg/dat/converter/index.js new file mode 100644 index 0000000000..9831f2d146 --- /dev/null +++ b/app/bg/dat/converter/index.js @@ -0,0 +1 @@ +require('@beaker/dat-legacy-tools/bin.js') \ No newline at end of file diff --git a/app/bg/dat/converter/package.json b/app/bg/dat/converter/package.json new file mode 100644 index 0000000000..001f7d1f10 --- /dev/null +++ b/app/bg/dat/converter/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "@beaker/dat-legacy-tools": "^1.0.0" + } +} diff --git a/app/bg/dat/dns.js b/app/bg/dat/dns.js new file mode 100644 index 0000000000..3abcda47a2 --- /dev/null +++ b/app/bg/dat/dns.js @@ -0,0 +1,29 @@ +import { InvalidDomainName } from 'beaker-error-constants' +import * as logLib from '../logger' + +const DNS_PROVIDERS = [['cloudflare-dns.com', '/dns-query'], ['dns.google.com', '/resolve']] +const DNS_PROVIDER = DNS_PROVIDERS[Math.random() > 0.5 ? 1 : 0] + +// instantate a dns cache and export it +import datDnsFactory from 'dat-dns' + +const datDns = datDnsFactory({ + dnsHost: DNS_PROVIDER[0], + dnsPath: DNS_PROVIDER[1] +}) + +export default datDns + +// hook up log events +datDns.on('resolved', details => logger.debug('Resolved', {details})) +datDns.on('failed', details => logger.debug('Failed lookup', {details})) +datDns.on('cache-flushed', details => logger.debug('Cache flushed')) + +// wrap resolveName() with a better error +const resolveName = datDns.resolveName +datDns.resolveName = async function (name, opts, cb) { + return resolveName.apply(datDns, arguments) + .catch(_ => { + throw new InvalidDomainName() + }) +} diff --git a/app/bg/dat/index.js b/app/bg/dat/index.js new file mode 100644 index 0000000000..35fcc44fbb --- /dev/null +++ b/app/bg/dat/index.js @@ -0,0 +1,66 @@ +import { app } from 'electron' +import * as logLib from '../logger' +const logger = logLib.child({category: 'dat', subcategory: 'protocol'}) +import * as childProcess from 'child_process' +import { tmpdir } from 'os' +import { join } from 'path' +import mkdirp from 'mkdirp' +import rimraf from 'rimraf' +import pda from 'pauls-dat-api2' +import hyper from '../hyper/index' +import * as filesystem from '../filesystem/index' + +export function getStoragePathFor (key) { + return join(tmpdir(), 'dat', key) +} + +var downloadPromises = {} +export async function downloadDat (key) { + if (downloadPromises[key]) { + return downloadPromises[key] + } + + var storagePath = getStoragePathFor(key) + rimraf.sync(storagePath) + mkdirp.sync(storagePath) + + downloadPromises[key] = runConvertProcess( + app.getPath('userData'), + key, + storagePath + ) + + return downloadPromises[key] +} + +export async function convertDatArchive (key) { + await downloadDat(key) + + var storagePath = getStoragePathFor(key) + var drive = await hyper.drives.createNewDrive() + await pda.exportFilesystemToArchive({ + srcPath: storagePath, + dstArchive: drive.session.drive, + dstPath: '/', + inplaceImport: true + }) + await filesystem.configDrive(drive.url) + return drive.url +} + +async function runConvertProcess (...args) { + var fullModulePath = join(__dirname, 'bg', 'dat', 'converter', 'index.js') + const opts = { + stdio: 'inherit', + env: Object.assign({}, process.env, { + ELECTRON_RUN_AS_NODE: 1, + ELECTRON_NO_ASAR: 1 + }) + } + var proc = childProcess.fork(fullModulePath, args, opts) + + return new Promise((resolve, reject) => { + proc.on('error', reject) + proc.on('close', resolve) + }) +} diff --git a/app/bg/dbs/archives.js b/app/bg/dbs/archives.js new file mode 100644 index 0000000000..c6bf8803fa --- /dev/null +++ b/app/bg/dbs/archives.js @@ -0,0 +1,304 @@ +import path from 'path' +import url from 'url' +import mkdirp from 'mkdirp' +import Events from 'events' +import datEncoding from 'dat-encoding' +import jetpack from 'fs-jetpack' +import { InvalidArchiveKeyError } from 'beaker-error-constants' +import * as db from './profile-data-db' +import lock from '../../lib/lock' +import { HYPERDRIVE_HASH_REGEX } from '../../lib/const' +import * as hyperDns from '../hyper/dns' + +// typedefs +// = + +/** + * @typedef {import('../dat/daemon').DaemonHyperdrive} DaemonHyperdrive + * + * @typedef {Object} LibraryArchiveMeta + * @prop {string} key + * @prop {string} url + * @prop {string} title + * @prop {string} description + * @prop {string} type + * @prop {string} memberOf + * @prop {number} mtime + * @prop {number} size + * @prop {string} author + * @prop {string} forkOf + * @prop {boolean} writable + * @prop {number} lastAccessTime + * @prop {number} lastLibraryAccessTime + * + * @typedef {Object} MinimalLibraryArchiveRecord + * @prop {string} key + */ + +// globals +// = + +var datPath/** @type string - path to the dat folder */ +var events = new Events() + +// exported methods +// = + +/** + * @param {Object} opts + * @param {string} opts.userDataPath + */ +export function setup (opts) { + // make sure the folders exist + datPath = path.join(opts.userDataPath, 'Dat') + mkdirp.sync(path.join(datPath, 'Archives')) +} + +/** + * @returns {string} + */ +export function getDatPath () { + return datPath +} + +/** + * @description Get the path to an archive's files. + * @param {string | Buffer | DaemonHyperdrive} archiveOrKey + * @returns {string} + */ +// +export function getArchiveMetaPath (archiveOrKey) { + var key /** @type string */ + if (typeof archiveOrKey === 'string') { + key = archiveOrKey + } else if (Buffer.isBuffer(archiveOrKey)) { + key = datEncoding.toStr(archiveOrKey) + } else { + key = datEncoding.toStr(archiveOrKey.key) + } + return path.join(datPath, 'Archives', 'Meta', key.slice(0, 2), key.slice(2)) +} + +/** + * @description Delete all db entries and files for an archive. + * @param {string} key + * @returns {Promise} + */ +export async function deleteArchive (key) { + const path = getArchiveMetaPath(key) + const info = await jetpack.inspectTreeAsync(path) + await Promise.all([ + db.run(`DELETE FROM archives WHERE key=?`, key), + db.run(`DELETE FROM archives_meta WHERE key=?`, key), + jetpack.removeAsync(path) + ]) + return info ? info.size : 0 +} + +export const on = events.on.bind(events) +export const addListener = events.addListener.bind(events) +export const removeListener = events.removeListener.bind(events) + +/** + * @description Upsert the last-access time. + * @param {string | Buffer} key + * @param {string} [timeVar] + * @param {number} [value] + * @returns {Promise} + */ +export async function touch (key, timeVar = 'lastAccessTime', value = -1) { + var release = await lock('archives-db:meta') + try { + if (timeVar !== 'lastAccessTime' && timeVar !== 'lastLibraryAccessTime') { + timeVar = 'lastAccessTime' + } + if (value === -1) value = Date.now() + var keyStr = datEncoding.toStr(key) + await db.run(`UPDATE archives_meta SET ${timeVar}=? WHERE key=?`, [value, keyStr]) + await db.run(`INSERT OR IGNORE INTO archives_meta (key, ${timeVar}) VALUES (?, ?)`, [keyStr, value]) + } finally { + release() + } +} + +/** + * @param {string} key + * @returns {Promise} + */ +export async function hasMeta (key) { + // massage inputs + var keyStr = typeof key !== 'string' ? datEncoding.toStr(key) : key + if (!HYPERDRIVE_HASH_REGEX.test(keyStr)) { + try { + keyStr = await hyperDns.resolveName(keyStr) + } catch (e) { + return false + } + } + + // fetch + var meta = await db.get(` + SELECT + archives_meta.key + FROM archives_meta + WHERE archives_meta.key = ? + `, [keyStr]) + return !!meta +} + +/** + * @description + * Get a single archive's metadata. + * Returns an empty object on not-found. + * @param {string | Buffer} key + * @param {Object} [opts] + * @param {boolean} [opts.noDefault] + * @returns {Promise} + */ +export async function getMeta (key, {noDefault} = {noDefault: false}) { + // massage inputs + var keyStr = typeof key !== 'string' ? datEncoding.toStr(key) : key + var origKeyStr = keyStr + + // validate inputs + if (!HYPERDRIVE_HASH_REGEX.test(keyStr)) { + try { + keyStr = await hyperDns.resolveName(keyStr) + } catch (e) { + return noDefault ? undefined : defaultMeta(keyStr, origKeyStr) + } + } + + // fetch + var meta = await db.get(` + SELECT + archives_meta.*, + dat_dns.name as dnsName + FROM archives_meta + LEFT JOIN dat_dns ON dat_dns.key = archives_meta.key AND dat_dns.isCurrent = 1 + WHERE archives_meta.key = ? + GROUP BY archives_meta.key + `, [keyStr]) + if (!meta) { + return noDefault ? undefined : defaultMeta(keyStr, origKeyStr) + } + + // massage some values + meta.url = `hyper://${meta.dnsName || meta.key}/` + delete meta.dnsName + meta.writable = !!meta.isOwner + meta.memberOf = meta.memberOf || undefined + + // remove old attrs + delete meta.isOwner + delete meta.createdByTitle + delete meta.createdByUrl + delete meta.metaSize + delete meta.stagingSize + delete meta.stagingSizeLessIgnored + + return meta +} + +/** + * @description Write an archive's metadata. + * @param {string | Buffer} key + * @param {LibraryArchiveMeta} [value] + * @returns {Promise} + */ +export async function setMeta (key, value) { + // massage inputs + var keyStr = datEncoding.toStr(key) + + // validate inputs + if (!HYPERDRIVE_HASH_REGEX.test(keyStr)) { + throw new InvalidArchiveKeyError() + } + if (!value || typeof value !== 'object') { + return // dont bother + } + + // extract the desired values + var {title, description, type, memberOf, size, author, forkOf, mtime, writable} = value + title = typeof title === 'string' ? title : '' + description = typeof description === 'string' ? description : '' + type = typeof type === 'string' ? type : '' + memberOf = typeof memberOf === 'string' ? memberOf : '' + var isOwnerFlag = flag(writable) + if (typeof author === 'string') author = normalizeDriveUrl(author) + if (typeof forkOf === 'string') forkOf = normalizeDriveUrl(forkOf) + + // write + var release = await lock('archives-db:meta') + var {lastAccessTime, lastLibraryAccessTime} = await getMeta(keyStr) + try { + await db.run(` + INSERT OR REPLACE INTO + archives_meta (key, title, description, type, memberOf, mtime, size, author, forkOf, isOwner, lastAccessTime, lastLibraryAccessTime) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `, [keyStr, title, description, type, memberOf, mtime, size, author, forkOf, isOwnerFlag, lastAccessTime, lastLibraryAccessTime]) + } finally { + release() + } + events.emit('update:archive-meta', keyStr, value) +} + +export function listLegacyArchives () { + return db.all(`SELECT archives.*, archives_meta.title from archives JOIN archives_meta ON archives_meta.key = archives.key WHERE archives.isSaved = 1`) +} + +export function removeLegacyArchive (key) { + return db.all(`UPDATE archives SET isSaved = 0 WHERE key = ?`, [key]) +} + +// internal methods +// = + +/** + * @param {string} key + * @param {string} name + * @returns {LibraryArchiveMeta} + */ +function defaultMeta (key, name) { + return { + key, + url: `hyper://${name}/`, + title: undefined, + description: undefined, + type: undefined, + memberOf: undefined, + author: undefined, + forkOf: undefined, + mtime: 0, + writable: false, + lastAccessTime: 0, + lastLibraryAccessTime: 0, + size: 0 + } +} + +/** + * @param {boolean} b + * @returns {number} + */ +function flag (b) { + return b ? 1 : 0 +} + +/** + * @param {string} originURL + * @returns {string} + */ +export function extractOrigin (originURL) { + var urlp = url.parse(originURL) + if (!urlp || !urlp.host || !urlp.protocol) return + return (urlp.protocol + (urlp.slashes ? '//' : '') + urlp.host) +} + +function normalizeDriveUrl (url) { + var match = url.match(HYPERDRIVE_HASH_REGEX) + if (match) { + return `hyper://${match[0]}/` + } + return extractOrigin(url) +} \ No newline at end of file diff --git a/app/bg/dbs/audit-log.js b/app/bg/dbs/audit-log.js new file mode 100644 index 0000000000..29a908b19d --- /dev/null +++ b/app/bg/dbs/audit-log.js @@ -0,0 +1,165 @@ +import sqlite3 from 'sqlite3' +import path from 'path' +import { parseDriveUrl } from '../../lib/urls' +import knex from '../lib/knex' +import { cbPromise } from '../../lib/functions' +import { setupSqliteDB } from '../lib/db' +import EventEmitter from 'events' +import { Readable } from 'stream' + +// globals +// = + +var db +var migrations +var events = new EventEmitter() + +// exported methods +// = + +export const WEBAPI = { + listAuditLog: list, + streamAuditLog: stream, + getAuditLogStats: stats +} + +/** + * @param {Object} opts + * @param {string} opts.userDataPath + */ +export async function setup (opts) { + // open database + var dbPath = path.join(opts.userDataPath, 'AuditLog') + db = new sqlite3.Database(dbPath) + await setupSqliteDB(db, {migrations}, '[AUDIT-LOG]') + db.run('delete from hyperdrive_ops;') // clear history +} + +export async function record (caller, method, args, writeSize, fn, opts) { + var ts = Date.now() + try { + var res = await fn() + return res + } finally { + var runtime = Date.now() - ts + if (!opts || !opts.ignoreFast || runtime > 100) { + var target + if (args.url) { + target = extractHostname(args.url) + delete args.url + } + caller = extractOrigin(caller) + if (method === 'query' && args.drive) { + // massage the opts + args = Object.assign({}, args) + if (Array.isArray(args.drive)) { + args.drive = args.drive.map(d => d.url) + } else { + args.drive = args.drive.url + } + } + insert('hyperdrive_ops', { + caller, + method, + target, + args: args ? JSON.stringify(args) : args, + writeSize, + ts, + runtime + }) + if (writeSize) insert('hyperdrive_write_stats', {caller, writeSize}) + } + } +} + +export async function list ({keys, offset, limit} = {keys: [], offset: 0, limit: 100}) { + var query = knex('hyperdrive_ops').select(...(keys || [])).offset(offset).limit(limit).orderBy('rowid', 'desc') + var queryAsSql = query.toSQL() + return cbPromise(cb => db.all(queryAsSql.sql, queryAsSql.bindings, cb)) +} + +export async function stream () { + var s = new Readable({ + read () {}, + objectMode: true + }) + const onData = detail => s.push(['data', {detail}]) + events.on('insert', onData) + s.on('close', e => events.removeListener('insert', onData)) + return s +} + +export async function stats () { + var query = knex('hyperdrive_ops').select().orderBy('runtime', 'desc').toSQL() + var rows = await cbPromise(cb => db.all(query.sql, query.bindings, cb)) + var stats = { + runtime: { + avg: 0, + stdDev: 0, + longest10: rows.slice(0, 10) + } + } + stats.runtime.avg = rows.reduce((acc, row) => acc + row.runtime, 0) / rows.length + stats.runtime.stdDev = Math.sqrt( + (rows + .map(row => Math.pow(row.runtime - stats.runtime.avg, 2)) // (v-mean)^2 + .reduce((acc, v) => acc + v, 0) + ) / rows.length // averaged + ) + return stats +} + +// internal methods +// = + +function insert (table, data) { + var query = knex(table).insert(data) + var queryAsSql = query.toSQL() + db.run(queryAsSql.sql, queryAsSql.bindings) + events.emit('insert', data) +} + +/** + * @param {string} originURL + * @returns {string} + */ +function extractOrigin (originURL) { + if (!originURL || !originURL.includes('://')) return originURL + var urlp = parseDriveUrl(originURL) + if (!urlp || !urlp.host || !urlp.protocol) return + return (urlp.protocol + '//' + urlp.host + (urlp.port ? `:${urlp.port}` : '')) +} + +/** + * @param {string} originURL + * @returns {string} + */ +function extractHostname (originURL) { + var urlp = parseDriveUrl(originURL) + return urlp.host +} + +migrations = [ + // version 1 + function (cb) { + db.exec(` + CREATE TABLE hyperdrive_ops ( + caller NOT NULL, + method NOT NULL, + target, + args, + ts, + runtime, + writeSize + ); + CREATE INDEX hyperdrive_ops_caller ON hyperdrive_ops (caller); + CREATE INDEX hyperdrive_ops_target ON hyperdrive_ops (target); + CREATE TABLE hyperdrive_write_stats ( + caller NOT NULL, + writeSize + ); + CREATE INDEX hyperdrive_write_stats_caller ON hyperdrive_write_stats (caller); + PRAGMA user_version = 1; + `, cb) + } +] diff --git a/app/bg/dbs/dat-dns.js b/app/bg/dbs/dat-dns.js new file mode 100644 index 0000000000..0baba59078 --- /dev/null +++ b/app/bg/dbs/dat-dns.js @@ -0,0 +1,107 @@ +import EventEmitter from 'events' +import * as db from './profile-data-db' +import knex from '../lib/knex' +import lock from '../../lib/lock' + +// typedefs +// = + +/** + * @typedef {Object} DatDnsRecord + * @prop {string} name + * @prop {string} key + * @prop {boolean} isCurrent + * @prop {number} lastConfirmedAt + * @prop {number} firstConfirmedAt + */ + +// globals +// = + +const events = new EventEmitter() + +// exported api +// = + +export const on = events.on.bind(events) + +export const once = events.once.bind(events) +export const removeListener = events.removeListener.bind(events) + +/** + * @param {string} name + * @returns {Promise} + */ +export const getCurrentByName = async function (name) { + return massageDNSRecord(await db.get(knex('dat_dns').where({name, isCurrent: 1}))) +} + +/** + * @param {string} key + * @returns {Promise} + */ +export const getCurrentByKey = async function (key) { + return massageDNSRecord(await db.get(knex('dat_dns').where({key, isCurrent: 1}))) +} + +/** + * @param {Object} opts + * @param {string} opts.key + * @param {string} opts.name + * @returns {Promise} + */ +export const update = async function ({key, name}) { + var release = await lock('dat-dns-update:' + name) + try { + var old = await db.get(knex('dat_dns').where({name, isCurrent: 1})) + if (old && old.key !== key) { + // unset old + await db.run(knex('dat_dns').update({isCurrent: 0}).where({name})) + events.emit('updated', {key: old.key, name: undefined}) + } + + let curr = await db.get(knex('dat_dns').where({name, key})) + if (!curr) { + // insert new + await db.run(knex('dat_dns').insert({ + name, + key, + isCurrent: 1, + lastConfirmedAt: Date.now(), + firstConfirmedAt: Date.now() + })) + } else { + // update current + await db.run(knex('dat_dns').update({lastConfirmedAt: Date.now(), isCurrent: 1}).where({name, key})) + } + events.emit('updated', {key, name}) + } finally { + release() + } +} + +/** + * @param {string} key + * @returns {Promise} + */ +export const unset = async function (key) { + var curr = await db.get(knex('dat_dns').where({key, isCurrent: 1})) + if (curr) { + await db.run(knex('dat_dns').update({isCurrent: 0}).where({key})) + events.emit('updated', {key, name: undefined}) + } +} + +// internal methods +// = + +function massageDNSRecord (record) { + if (!record) return null + return { + name: record.name, + key: record.key, + isCurrent: Boolean(record.isCurrent), + lastConfirmedAt: record.lastConfirmedAt, + firstConfirmedAt: record.firstConfirmedAt + } +} \ No newline at end of file diff --git a/app/bg/dbs/history.js b/app/bg/dbs/history.js new file mode 100644 index 0000000000..01b23de359 --- /dev/null +++ b/app/bg/dbs/history.js @@ -0,0 +1,253 @@ +import lock from '../../lib/lock' +import * as db from './profile-data-db' + +// typedefs +// = + +class BadParamError extends Error { + /** + * @param {string} msg + */ + constructor (msg) { + super() + this.name = 'BadParamError' + this.message = msg + } +} + +/** + * @typedef {Object} Visit + * @prop {number} profileId + * @prop {string} url + * @prop {string} title + * @prop {number} ts + * + * @typedef {Object} VisitSearchResult + * @prop {string} offsets + * @prop {string} url + * @prop {string} title + * @prop {number} num_visits + */ + +// exported methods +// = + +/** + * @param {number} profileId + * @param {Object} values + * @param {string} values.url + * @param {string} values.title + * @returns {Promise} + */ +export const addVisit = async function (profileId, {url, title}) { + // validate parameters + if (!url || typeof url !== 'string') { + throw new BadParamError('url must be a string') + } + if (!title || typeof title !== 'string') { + throw new BadParamError('title must be a string') + } + + var release = await lock('history-db') + try { + await db.run('BEGIN TRANSACTION;') + + var ts = Date.now() + if (!url.startsWith('beaker://')) { // dont log stats on internal sites, keep them out of the search + // get current stats + var stats = await db.get('SELECT * FROM visit_stats WHERE url = ?;', [url]) + + // create or update stats + if (!stats) { + await db.run('INSERT INTO visit_stats (url, num_visits, last_visit_ts) VALUES (?, ?, ?);', [url, 1, ts]) + await db.run('INSERT INTO visit_fts (url, title) VALUES (?, ?);', [url, title]) + } else { + let num_visits = (+stats.num_visits || 1) + 1 + await db.run('UPDATE visit_stats SET num_visits = ?, last_visit_ts = ? WHERE url = ?;', [num_visits, ts, url]) + } + } + + // visited within 1 hour? + var visit = await db.get('SELECT rowid, * from visits WHERE profileId = ? AND url = ? AND ts > ? ORDER BY ts DESC LIMIT 1', [profileId, url, ts - 1000 * 60 * 60]) + if (visit) { + // update visit ts and title + await db.run('UPDATE visits SET ts = ?, title = ? WHERE rowid = ?', [ts, title, visit.rowid]) + } else { + // log visit + await db.run('INSERT INTO visits (profileId, url, title, ts) VALUES (?, ?, ?, ?);', [profileId, url, title, ts]) + } + + await db.run('COMMIT;') + } finally { + release() + } +} + +/** + * @param {number} profileId + * @param {Object} opts + * @param {string} [opts.search] + * @param {number} [opts.offset] + * @param {number} [opts.limit] + * @param {number} [opts.before] + * @param {number} [opts.after] + * @returns {Promise>} + */ +export const getVisitHistory = async function (profileId, {search, offset, limit, before, after}) { + var release = await lock('history-db') + try { + const params = /** @type Array */([ + profileId, + limit || 50, + offset || 0 + ]) + if (search) { + // prep search terms + params.push( + search + .toLowerCase() // all lowercase. (uppercase is interpretted as a directive by sqlite.) + .replace(/[:^*]/g, '') + // strip symbols that sqlite interprets. + '*' // allow partial matches + ) + return await db.all(` + SELECT visits.* + FROM visit_fts + LEFT JOIN visits ON visits.url = visit_fts.url + WHERE visits.profileId = ?1 AND visit_fts MATCH ?4 + ORDER BY visits.ts DESC + LIMIT ?2 OFFSET ?3 + `, params) + } + let timeWhere = '' + if (before && after) { + timeWhere += 'AND ts <= ?4 AND ts >= ?5' + params.push(before) + params.push(after) + } else if (before) { + timeWhere += 'AND ts <= ?4' + params.push(before) + } else if (after) { + timeWhere += 'AND ts >= ?4' + params.push(after) + } + return await db.all(` + SELECT * FROM visits + WHERE profileId = ?1 ${timeWhere} + ORDER BY ts DESC + LIMIT ?2 OFFSET ?3 + `, params) + } finally { + release() + } +} + +/** + * @param {number} profileId + * @param {Object} opts + * @param {number} [opts.offset] + * @param {number} [opts.limit] + * @returns {Promise>} + */ +export const getMostVisited = async function (profileId, { offset, limit }) { + var release = await lock('history-db') + try { + offset = offset || 0 + limit = limit || 50 + return await db.all(` + SELECT visit_stats.*, visits.title AS title + FROM visit_stats + LEFT JOIN visits ON visits.url = visit_stats.url + WHERE profileId = ? AND visit_stats.num_visits > 5 + GROUP BY visit_stats.url + ORDER BY num_visits DESC, last_visit_ts DESC + LIMIT ? OFFSET ? + `, [profileId, limit, offset]) + } finally { + release() + } +} + +/** + * @param {string} q + * @returns {Promise>} + */ +export const search = async function (q) { + if (!q || typeof q !== 'string') { + throw new BadParamError('q must be a string') + } + + var release = await lock('history-db') + try { + // prep search terms + q = q + .toLowerCase() // all lowercase. (uppercase is interpretted as a directive by sqlite.) + .replace(/[:^*]/g, '') // strip symbols that sqlite interprets + .replace(/[-]/g, ' ') + // strip symbols that sqlite interprets + '*' // allow partial matches + + // run query + return await db.all(` + SELECT offsets(visit_fts) as offsets, visit_fts.url, visit_fts.title, visit_stats.num_visits + FROM visit_fts + LEFT JOIN visit_stats ON visit_stats.url = visit_fts.url + WHERE visit_fts MATCH ? AND visit_stats.num_visits > 2 + ORDER BY visit_stats.num_visits DESC + LIMIT 10; + `, [q]) + } finally { + release() + } +} + +/** + * @param {string} url + * @returns {Promise} + */ +export const removeVisit = async function (url) { + // validate parameters + if (!url || typeof url !== 'string') { + throw new BadParamError('url must be a string') + } + + var release = await lock('history-db') + try { + db.serialize() + db.run('BEGIN TRANSACTION;') + db.run('DELETE FROM visits WHERE url = ?;', url) + db.run('DELETE FROM visit_stats WHERE url = ?;', url) + db.run('DELETE FROM visit_fts WHERE url = ?;', url) + await db.run('COMMIT;') + } finally { + db.parallelize() + release() + } +} + +/** + * @param {number} timestamp + * @returns {Promise} + */ +export const removeVisitsAfter = async function (timestamp) { + var release = await lock('history-db') + try { + db.serialize() + db.run('BEGIN TRANSACTION;') + db.run('DELETE FROM visits WHERE ts >= ?;', timestamp) + db.run('DELETE FROM visit_stats WHERE last_visit_ts >= ?;', timestamp) + await db.run('COMMIT;') + } finally { + db.parallelize() + release() + } +} + +/** + * @returns {Promise} + */ +export const removeAllVisits = async function () { + var release = await lock('history-db') + db.run('DELETE FROM visits;') + db.run('DELETE FROM visit_stats;') + db.run('DELETE FROM visit_fts;') + release() +} diff --git a/app/bg/dbs/index.js b/app/bg/dbs/index.js new file mode 100644 index 0000000000..22bdb44dbf --- /dev/null +++ b/app/bg/dbs/index.js @@ -0,0 +1,17 @@ +import * as archives from './archives' +import * as auditLog from './audit-log' +import * as history from './history' +import * as profileData from './profile-data-db' +import * as settings from './settings' +import * as sitedata from './sitedata' +import * as watchlist from './watchlist' + +export default { + archives, + auditLog, + history, + profileData, + settings, + sitedata, + watchlist +} diff --git a/app/bg/dbs/profile-data-db.js b/app/bg/dbs/profile-data-db.js new file mode 100644 index 0000000000..9a5ea83756 --- /dev/null +++ b/app/bg/dbs/profile-data-db.js @@ -0,0 +1,199 @@ +import sqlite3 from 'sqlite3' +import path from 'path' +import { cbPromise } from '../../lib/functions' +import { setupSqliteDB, handleQueryBuilder } from '../lib/db' + +import V1 from './schemas/profile-data.v1.sql' +import V2 from './schemas/profile-data.v2.sql' +import V3 from './schemas/profile-data.v3.sql' +import V4 from './schemas/profile-data.v4.sql' +import V5 from './schemas/profile-data.v5.sql' +import V6 from './schemas/profile-data.v6.sql' +import V7 from './schemas/profile-data.v7.sql' +import V8 from './schemas/profile-data.v8.sql' +import V9 from './schemas/profile-data.v9.sql' +import V10 from './schemas/profile-data.v10.sql' +import V11 from './schemas/profile-data.v11.sql' +import V12 from './schemas/profile-data.v12.sql' +import V13 from './schemas/profile-data.v13.sql' +import V14 from './schemas/profile-data.v14.sql' +import V15 from './schemas/profile-data.v15.sql' +import V16 from './schemas/profile-data.v16.sql' +import V17 from './schemas/profile-data.v17.sql' +import V18 from './schemas/profile-data.v18.sql' +import V19 from './schemas/profile-data.v19.sql' +import V20 from './schemas/profile-data.v20.sql' +import V21 from './schemas/profile-data.v21.sql' +import V22 from './schemas/profile-data.v22.sql' +import V23 from './schemas/profile-data.v23.sql' +import V24 from './schemas/profile-data.v24.sql' +import V25 from './schemas/profile-data.v25.sql' +import V26 from './schemas/profile-data.v26.sql' +import V27 from './schemas/profile-data.v27.sql' +import V28 from './schemas/profile-data.v28.sql' +import V29 from './schemas/profile-data.v29.sql' +import V30 from './schemas/profile-data.v30.sql' +import V31 from './schemas/profile-data.v31.sql' +import V32 from './schemas/profile-data.v32.sql' +import V33 from './schemas/profile-data.v33.sql' +import V34 from './schemas/profile-data.v34.sql' +import V35 from './schemas/profile-data.v35.sql' +import V36 from './schemas/profile-data.v36.sql' +import V37 from './schemas/profile-data.v37.sql' +import V38 from './schemas/profile-data.v38.sql' +import V39 from './schemas/profile-data.v39.sql' +import V40 from './schemas/profile-data.v40.sql' +import V41 from './schemas/profile-data.v41.sql' +import V42 from './schemas/profile-data.v42.sql' +import V43 from './schemas/profile-data.v43.sql' +import V44 from './schemas/profile-data.v44.sql' +import V45 from './schemas/profile-data.v45.sql' +import V46 from './schemas/profile-data.v46.sql' +import V47 from './schemas/profile-data.v47.sql' +import V48 from './schemas/profile-data.v48.sql' +import V49 from './schemas/profile-data.v49.sql' + +// typedefs +// = + +/** + * @typedef {Object} SQLiteResult + * @prop {string} lastID + */ + +// globals +// = + +var db +var migrations +var setupPromise + +// exported methods +// = + +/** + * @param {Object} opts + * @param {string} opts.userDataPath + */ +export const setup = function (opts) { + // open database + var dbPath = path.join(opts.userDataPath, 'Profiles') + db = new sqlite3.Database(dbPath) + setupPromise = setupSqliteDB(db, {migrations}, '[PROFILES]') +} + +/** + * @param {...(any)} args + * @return {Promise} + */ +export const get = async function (...args) { + await setupPromise + args = handleQueryBuilder(args) + return cbPromise(cb => db.get(...args, cb)) +} + +/** + * @param {...(any)} args + * @return {Promise>} + */ +export const all = async function (...args) { + await setupPromise + args = handleQueryBuilder(args) + return cbPromise(cb => db.all(...args, cb)) +} + +/** + * @param {...(any)} args + * @return {Promise} + */ +export const run = async function (...args) { + await setupPromise + args = handleQueryBuilder(args) + return cbPromise(cb => db.run(...args, function (err) { + if (err) cb(err) + else cb(null, {lastID: this.lastID}) + })) +} + +/** + * @returns {Promise} + */ +export const serialize = function () { + return db.serialize() +} + +/** + * @returns {Promise} + */ +export const parallelize = function () { + return db.parallelize() +} + +export const getSqliteInstance = () => db + +// internal methods +// = + +function setupDb (cb) { + db.exec(FULL_SCHEMA, cb) +} +migrations = [ + migration(V1), + migration(V2), + migration(V3), + migration(V4), + migration(V5), + migration(V6), + migration(V7), + migration(V8), + migration(V9), + migration(V10), + migration(V11), + migration(V12), + migration(V13), + migration(V14), + migration(V15), + migration(V16, {canFail: true}), // set canFail because we made a mistake in the rollout of this update, see https://github.com/beakerbrowser/beaker/issues/934 + migration(V17), + migration(V18), + migration(V19), + migration(V20), + migration(V21), + migration(V22, {canFail: true}), // canFail for the same reason as v16, ffs + migration(V23), + migration(V24), + migration(V25), + migration(V26), + migration(V27), + migration(V28), + migration(V29), + migration(V30), + migration(V31), + migration(V32), + migration(V33), + migration(V34), + migration(V35), + migration(V36), + migration(V37), + migration(V38), + migration(V39), + migration(V40), + migration(V41), + migration(V42), + migration(V43), + migration(V44), + migration(V45), + migration(V46), + migration(V47), + migration(V48), + migration(V49) +] +function migration (file, opts = {}) { + return cb => { + if (opts.canFail) { + var orgCb = cb + cb = () => orgCb() // suppress the error + } + db.exec(file, cb) + } +} diff --git a/app/bg/dbs/schemas/profile-data.v1.sql.js b/app/bg/dbs/schemas/profile-data.v1.sql.js new file mode 100644 index 0000000000..3a9c088285 --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v1.sql.js @@ -0,0 +1,62 @@ +export default ` +CREATE TABLE profiles ( + id INTEGER PRIMARY KEY NOT NULL, + url TEXT, + createdAt INTEGER DEFAULT (strftime('%s', 'now')) +); + +CREATE TABLE archives ( + profileId INTEGER NOT NULL, + key TEXT NOT NULL, + localPath TEXT, -- deprecated + isSaved INTEGER, + createdAt INTEGER DEFAULT (strftime('%s', 'now')) +); + +CREATE TABLE archives_meta ( + key TEXT PRIMARY KEY, + title TEXT, + description TEXT, + forkOf TEXT, + createdByUrl TEXT, -- deprecated + createdByTitle TEXT, -- deprecated + mtime INTEGER, + metaSize INTEGER, -- deprecated + stagingSize INTEGER, -- deprecated + isOwner INTEGER +); + +CREATE TABLE bookmarks ( + profileId INTEGER, + url TEXT NOT NULL, + title TEXT, + pinned INTEGER, + createdAt INTEGER DEFAULT (strftime('%s', 'now')), + + PRIMARY KEY (profileId, url), + FOREIGN KEY (profileId) REFERENCES profiles (id) ON DELETE CASCADE +); + +CREATE TABLE visits ( + profileId INTEGER, + url TEXT NOT NULL, + title TEXT NOT NULL, + ts INTEGER NOT NULL, + + FOREIGN KEY (profileId) REFERENCES profiles (id) ON DELETE CASCADE +); + +CREATE TABLE visit_stats ( + url TEXT NOT NULL, + num_visits INTEGER, + last_visit_ts INTEGER +); + +CREATE VIRTUAL TABLE visit_fts USING fts4 (url, title); +CREATE UNIQUE INDEX visits_stats_url ON visit_stats (url); + +-- default profile +INSERT INTO profiles (id) VALUES (0); + +PRAGMA user_version = 1; +` diff --git a/app/bg/dbs/schemas/profile-data.v10.sql.js b/app/bg/dbs/schemas/profile-data.v10.sql.js new file mode 100644 index 0000000000..a5fed4ced7 --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v10.sql.js @@ -0,0 +1,16 @@ +export default ` +-- list of the user's installed apps +-- deprecated +CREATE TABLE apps ( + profileId INTEGER NOT NULL, + name TEXT NOT NULL, + url TEXT, + updatedAt INTEGER DEFAULT (strftime('%s', 'now')), + createdAt INTEGER DEFAULT (strftime('%s', 'now')), + + PRIMARY KEY (profileId, name), + FOREIGN KEY (profileId) REFERENCES profiles (id) ON DELETE CASCADE +); + +PRAGMA user_version = 10; +` diff --git a/app/bg/dbs/schemas/profile-data.v11.sql.js b/app/bg/dbs/schemas/profile-data.v11.sql.js new file mode 100644 index 0000000000..3122aad0f8 --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v11.sql.js @@ -0,0 +1,14 @@ +export default ` +-- log of the user's app installations +-- deprecated +CREATE TABLE apps_log ( + profileId INTEGER NOT NULL, + name TEXT NOT NULL, + url TEXT, + ts INTEGER DEFAULT (strftime('%s', 'now')), + + FOREIGN KEY (profileId) REFERENCES profiles (id) ON DELETE CASCADE +); + +PRAGMA user_version = 11; +` diff --git a/app/bg/dbs/schemas/profile-data.v12.sql.js b/app/bg/dbs/schemas/profile-data.v12.sql.js new file mode 100644 index 0000000000..2cebe1c19c --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v12.sql.js @@ -0,0 +1,18 @@ +export default ` + +-- list of the active workspaces +-- deprecated +CREATE TABLE workspaces ( + profileId INTEGER NOT NULL, + name TEXT NOT NULL, + localFilesPath TEXT, + publishTargetUrl TEXT, + createdAt INTEGER DEFAULT (strftime('%s', 'now')), + updatedAt INTEGER DEFAULT (strftime('%s', 'now')), + + PRIMARY KEY (profileId, name), + FOREIGN KEY (profileId) REFERENCES profiles (id) ON DELETE CASCADE +); + +PRAGMA user_version = 12; +` diff --git a/app/bg/dbs/schemas/profile-data.v13.sql.js b/app/bg/dbs/schemas/profile-data.v13.sql.js new file mode 100644 index 0000000000..acdca1e471 --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v13.sql.js @@ -0,0 +1,7 @@ +export default ` + +-- add a field to track when last accessed in the library +ALTER TABLE archives_meta ADD COLUMN lastLibraryAccessTime INTEGER DEFAULT 0; + +PRAGMA user_version = 13; +` diff --git a/app/bg/dbs/schemas/profile-data.v14.sql.js b/app/bg/dbs/schemas/profile-data.v14.sql.js new file mode 100644 index 0000000000..36b043fd62 --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v14.sql.js @@ -0,0 +1,7 @@ +export default ` + +-- add a non-unique index to the visits table to speed up joins +CREATE INDEX visits_url ON visits (url); + +PRAGMA user_version = 14; +` diff --git a/app/bg/dbs/schemas/profile-data.v15.sql.js b/app/bg/dbs/schemas/profile-data.v15.sql.js new file mode 100644 index 0000000000..e81b6a4adb --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v15.sql.js @@ -0,0 +1,7 @@ +export default ` + +-- more default bookmarks +-- REMOVED + +PRAGMA user_version = 15; +` diff --git a/app/bg/dbs/schemas/profile-data.v16.sql.js b/app/bg/dbs/schemas/profile-data.v16.sql.js new file mode 100644 index 0000000000..d68bace2b9 --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v16.sql.js @@ -0,0 +1,7 @@ +export default ` + +-- add a field to track when last accessed in the library +ALTER TABLE bookmarks ADD COLUMN pinOrder INTEGER DEFAULT 0; + +PRAGMA user_version = 16; +` diff --git a/app/bg/dbs/schemas/profile-data.v17.sql.js b/app/bg/dbs/schemas/profile-data.v17.sql.js new file mode 100644 index 0000000000..2a6da70a4b --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v17.sql.js @@ -0,0 +1,7 @@ +export default ` + +-- add a field to track the folder where an archive is being synced +ALTER TABLE archives ADD COLUMN localSyncPath TEXT; + +PRAGMA user_version = 17; +` diff --git a/app/bg/dbs/schemas/profile-data.v18.sql.js b/app/bg/dbs/schemas/profile-data.v18.sql.js new file mode 100644 index 0000000000..2e45f183c3 --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v18.sql.js @@ -0,0 +1,16 @@ +export default ` + +-- add a database to track user-defined templates for new dat sites +CREATE TABLE templates ( + profileId INTEGER, + url TEXT NOT NULL, + title TEXT, + screenshot, + createdAt INTEGER DEFAULT (strftime('%s', 'now')), + + PRIMARY KEY (profileId, url), + FOREIGN KEY (profileId) REFERENCES profiles (id) ON DELETE CASCADE +); + +PRAGMA user_version = 18; +` diff --git a/app/bg/dbs/schemas/profile-data.v19.sql.js b/app/bg/dbs/schemas/profile-data.v19.sql.js new file mode 100644 index 0000000000..66d2c0502a --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v19.sql.js @@ -0,0 +1,19 @@ +export default ` + +-- add the 'hidden' flag to archives +ALTER TABLE archives ADD COLUMN hidden INTEGER DEFAULT 0; + +-- add a database for tracking draft dats +CREATE TABLE archive_drafts ( + profileId INTEGER, + masterKey TEXT, -- key of the master dat + draftKey TEXT, -- key of the draft dat + createdAt INTEGER DEFAULT (strftime('%s', 'now')), + + isActive INTEGER, -- is this the active draft? (deprecated) + + FOREIGN KEY (profileId) REFERENCES profiles (id) ON DELETE CASCADE +); + +PRAGMA user_version = 19; +` diff --git a/app/bg/dbs/schemas/profile-data.v2.sql.js b/app/bg/dbs/schemas/profile-data.v2.sql.js new file mode 100644 index 0000000000..2756dc34cf --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v2.sql.js @@ -0,0 +1,8 @@ +export default ` + +-- add variable to track the staging size less ignored files +-- deprecated +ALTER TABLE archives_meta ADD COLUMN stagingSizeLessIgnored INTEGER; + +PRAGMA user_version = 2; +` diff --git a/app/bg/dbs/schemas/profile-data.v20.sql.js b/app/bg/dbs/schemas/profile-data.v20.sql.js new file mode 100644 index 0000000000..68743ec25c --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v20.sql.js @@ -0,0 +1,7 @@ +export default ` + +-- watch localSyncPath and automatically publish changes (1) or not (0) +ALTER TABLE archives ADD COLUMN autoPublishLocal INTEGER DEFAULT 0; + +PRAGMA user_version = 20; +` diff --git a/app/bg/dbs/schemas/profile-data.v21.sql.js b/app/bg/dbs/schemas/profile-data.v21.sql.js new file mode 100644 index 0000000000..bcdcd19679 --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v21.sql.js @@ -0,0 +1,7 @@ +export default ` + +-- add size data to archives_meta +ALTER TABLE archives_meta ADD COLUMN size INTEGER DEFAULT 0; + +PRAGMA user_version = 21; +` diff --git a/app/bg/dbs/schemas/profile-data.v22.sql.js b/app/bg/dbs/schemas/profile-data.v22.sql.js new file mode 100644 index 0000000000..30f8c26ef9 --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v22.sql.js @@ -0,0 +1,7 @@ +export default ` + +-- automatically publish changes (0) or write to local folder (1) +ALTER TABLE archives ADD COLUMN previewMode INTEGER; + +PRAGMA user_version = 22; +` diff --git a/app/bg/dbs/schemas/profile-data.v23.sql.js b/app/bg/dbs/schemas/profile-data.v23.sql.js new file mode 100644 index 0000000000..1fc369278e --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v23.sql.js @@ -0,0 +1,18 @@ +export default ` + +-- add a database for watchlist feature +CREATE TABLE watchlist ( + profileId INTEGER NOT NULL, + url TEXT NOT NULL, + description TEXT NOT NULL, + seedWhenResolved BOOLEAN NOT NULL, + resolved BOOLEAN NOT NULL DEFAULT (0), + updatedAt INTEGER DEFAULT (strftime('%s', 'now')), + createdAt INTEGER DEFAULT (strftime('%s', 'now')), + + PRIMARY KEY (profileId, url), + FOREIGN KEY (profileId) REFERENCES profiles (id) ON DELETE CASCADE +); + +PRAGMA user_version = 23; +` \ No newline at end of file diff --git a/app/bg/dbs/schemas/profile-data.v24.sql.js b/app/bg/dbs/schemas/profile-data.v24.sql.js new file mode 100644 index 0000000000..6ceba86ee6 --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v24.sql.js @@ -0,0 +1,125 @@ +export default ` +-- description of the bookmark's content, often pulled from the bookmarked page +ALTER TABLE bookmarks ADD COLUMN description TEXT; + +-- sync the bookmark to the user's public profile +ALTER TABLE bookmarks ADD COLUMN isPublic INTEGER; + +CREATE TABLE users ( + id INTEGER PRIMARY KEY NOT NULL, + url TEXT, + isDefault INTEGER DEFAULT 0, + createdAt INTEGER +); + +-- list of sites being crawled +CREATE TABLE crawl_sources ( + id INTEGER PRIMARY KEY NOT NULL, + url TEXT NOT NULL +); + +-- tracking information on the crawl-state of the sources +CREATE TABLE crawl_sources_meta ( + crawlSourceId INTEGER NOT NULL, + crawlSourceVersion INTEGER NOT NULL, + crawlDataset TEXT NOT NULL, + crawlDatasetVersion INTEGER NOT NULL, + updatedAt INTEGER, + + FOREIGN KEY (crawlSourceId) REFERENCES crawl_sources (id) ON DELETE CASCADE +); + +-- crawled descriptions of other sites +CREATE TABLE crawl_site_descriptions ( + crawlSourceId INTEGER NOT NULL, + crawledAt INTEGER, + + url TEXT, + title TEXT, + description TEXT, + type TEXT, -- comma separated strings + + FOREIGN KEY (crawlSourceId) REFERENCES crawl_sources (id) ON DELETE CASCADE +); +CREATE VIRTUAL TABLE crawl_site_descriptions_fts_index USING fts5(title, description, content='crawl_site_descriptions'); + +-- triggers to keep crawl_site_descriptions_fts_index updated +CREATE TRIGGER crawl_site_descriptions_ai AFTER INSERT ON crawl_site_descriptions BEGIN + INSERT INTO crawl_site_descriptions_fts_index(rowid, title, description) VALUES (new.rowid, new.title, new.description); +END; +CREATE TRIGGER crawl_site_descriptions_ad AFTER DELETE ON crawl_site_descriptions BEGIN + INSERT INTO crawl_site_descriptions_fts_index(crawl_site_descriptions_fts_index, rowid, title, description) VALUES('delete', old.rowid, old.title, old.description); +END; +CREATE TRIGGER crawl_site_descriptions_au AFTER UPDATE ON crawl_site_descriptions BEGIN + INSERT INTO crawl_site_descriptions_fts_index(crawl_site_descriptions_fts_index, rowid, title, description) VALUES('delete', old.a, old.title, old.description); + INSERT INTO crawl_site_descriptions_fts_index(rowid, title, description) VALUES (new.rowid, new.title, new.description); +END; + +-- crawled posts +CREATE TABLE crawl_posts ( + crawlSourceId INTEGER NOT NULL, + pathname TEXT NOT NULL, + crawledAt INTEGER, + + body TEXT, + createdAt INTEGER, + updatedAt INTEGER, + + FOREIGN KEY (crawlSourceId) REFERENCES crawl_sources (id) ON DELETE CASCADE +); +CREATE VIRTUAL TABLE crawl_posts_fts_index USING fts5(body, content='crawl_posts'); + +-- triggers to keep crawl_posts_fts_index updated +CREATE TRIGGER crawl_posts_ai AFTER INSERT ON crawl_posts BEGIN + INSERT INTO crawl_posts_fts_index(rowid, body) VALUES (new.rowid, new.body); +END; +CREATE TRIGGER crawl_posts_ad AFTER DELETE ON crawl_posts BEGIN + INSERT INTO crawl_posts_fts_index(crawl_posts_fts_index, rowid, body) VALUES('delete', old.rowid, old.body); +END; +CREATE TRIGGER crawl_posts_au AFTER UPDATE ON crawl_posts BEGIN + INSERT INTO crawl_posts_fts_index(crawl_posts_fts_index, rowid, body) VALUES('delete', old.rowid, old.body); + INSERT INTO crawl_posts_fts_index(rowid, body) VALUES (new.rowid, new.body); +END; + +-- crawled bookmarks +CREATE TABLE crawl_bookmarks ( + crawlSourceId INTEGER NOT NULL, + pathname TEXT NOT NULL, + crawledAt INTEGER, + + href TEXT, + title TEXT, + description TEXT, + tags TEXT, + createdAt INTEGER, + updatedAt INTEGER, + + FOREIGN KEY (crawlSourceId) REFERENCES crawl_sources (id) ON DELETE CASCADE +); +CREATE VIRTUAL TABLE crawl_bookmarks_fts_index USING fts5(title, description, tags, content='crawl_bookmarks'); + +-- triggers to keep crawl_bookmarks_fts_index updated +CREATE TRIGGER crawl_bookmarks_ai AFTER INSERT ON crawl_bookmarks BEGIN + INSERT INTO crawl_bookmarks_fts_index(rowid, title, description, tags) VALUES (new.rowid, new.title, new.description, new.tags); +END; +CREATE TRIGGER crawl_bookmarks_ad AFTER DELETE ON crawl_bookmarks BEGIN + INSERT INTO crawl_bookmarks_fts_index(crawl_bookmarks_fts_index, rowid, title, description, tags) VALUES('delete', old.rowid, old.title, old.description, old.tags); +END; +CREATE TRIGGER crawl_bookmarks_au AFTER UPDATE ON crawl_bookmarks BEGIN + INSERT INTO crawl_bookmarks_fts_index(crawl_bookmarks_fts_index, rowid, title, description, tags) VALUES('delete', old.rowid, old.title, old.description, old.tags); + INSERT INTO crawl_bookmarks_fts_index(rowid, title, description, tags) VALUES (new.rowid, new.title, new.description, new.tags); +END; + +-- crawled follows +CREATE TABLE crawl_graph ( + crawlSourceId INTEGER NOT NULL, + crawledAt INTEGER, + + destUrl TEXT NOT NULL, + + PRIMARY KEY (crawlSourceId, destUrl), + FOREIGN KEY (crawlSourceId) REFERENCES crawl_sources (id) ON DELETE CASCADE +); + +PRAGMA user_version = 24; +` \ No newline at end of file diff --git a/app/bg/dbs/schemas/profile-data.v25.sql.js b/app/bg/dbs/schemas/profile-data.v25.sql.js new file mode 100644 index 0000000000..98e1538e36 --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v25.sql.js @@ -0,0 +1,18 @@ +export default ` + +-- crawled reactions +CREATE TABLE crawl_reactions ( + crawlSourceId INTEGER NOT NULL, + pathname TEXT NOT NULL, + crawledAt INTEGER, + + topic TEXT NOT NULL, + emojis TEXT NOT NULL, + + PRIMARY KEY (crawlSourceId, pathname), + FOREIGN KEY (crawlSourceId) REFERENCES crawl_sources (id) ON DELETE CASCADE +); +CREATE INDEX crawl_reactions_topic ON crawl_reactions (topic); + +PRAGMA user_version = 25; +` diff --git a/app/bg/dbs/schemas/profile-data.v26.sql.js b/app/bg/dbs/schemas/profile-data.v26.sql.js new file mode 100644 index 0000000000..a8cefd283b --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v26.sql.js @@ -0,0 +1,14 @@ +export default ` + +-- fix an incorrect trigger definition +DROP TRIGGER IF EXISTS crawl_site_descriptions_au; +CREATE TRIGGER crawl_site_descriptions_au AFTER UPDATE ON crawl_site_descriptions BEGIN + INSERT INTO crawl_site_descriptions_fts_index(crawl_site_descriptions_fts_index, rowid, title, description) VALUES('delete', old.rowid, old.title, old.description); + INSERT INTO crawl_site_descriptions_fts_index(rowid, title, description) VALUES (new.rowid, new.title, new.description); +END; + +-- rename 'graph' to 'follows' +ALTER TABLE crawl_graph RENAME TO crawl_follows; + +PRAGMA user_version = 26; +` diff --git a/app/bg/dbs/schemas/profile-data.v27.sql.js b/app/bg/dbs/schemas/profile-data.v27.sql.js new file mode 100644 index 0000000000..cd5972794c --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v27.sql.js @@ -0,0 +1,33 @@ +export default ` + +-- add crawled comments +CREATE TABLE crawl_comments ( + crawlSourceId INTEGER NOT NULL, + pathname TEXT NOT NULL, + crawledAt INTEGER, + + topic TEXT, + replyTo TEXT, + body TEXT, + createdAt INTEGER, + updatedAt INTEGER, + + FOREIGN KEY (crawlSourceId) REFERENCES crawl_sources (id) ON DELETE CASCADE +); +CREATE INDEX crawl_comments_topic ON crawl_comments (topic); +CREATE VIRTUAL TABLE crawl_comments_fts_index USING fts5(body, content='crawl_comments'); + +-- triggers to keep crawl_comments_fts_index updated +CREATE TRIGGER crawl_comments_ai AFTER INSERT ON crawl_comments BEGIN + INSERT INTO crawl_comments_fts_index(rowid, body) VALUES (new.rowid, new.body); +END; +CREATE TRIGGER crawl_comments_ad AFTER DELETE ON crawl_comments BEGIN + INSERT INTO crawl_comments_fts_index(crawl_comments_fts_index, rowid, body) VALUES('delete', old.rowid, old.body); +END; +CREATE TRIGGER crawl_comments_au AFTER UPDATE ON crawl_comments BEGIN + INSERT INTO crawl_comments_fts_index(crawl_comments_fts_index, rowid, body) VALUES('delete', old.rowid, old.body); + INSERT INTO crawl_comments_fts_index(rowid, body) VALUES (new.rowid, new.body); +END; + +PRAGMA user_version = 27; +` diff --git a/app/bg/dbs/schemas/profile-data.v28.sql.js b/app/bg/dbs/schemas/profile-data.v28.sql.js new file mode 100644 index 0000000000..1e70023db1 --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v28.sql.js @@ -0,0 +1,60 @@ +export default ` + +-- we're replacing the bookmark 'tags' field with a new normalized tags table +-- this requires replacing the entire bookmarks table because we need to add an id pkey + + +-- remove the old bookmarks tabes +DROP TRIGGER IF EXISTS crawl_bookmarks_ai; +DROP TRIGGER IF EXISTS crawl_bookmarks_ad; +DROP TRIGGER IF EXISTS crawl_bookmarks_au; +DROP TABLE IF EXISTS crawl_bookmarks_fts_index; +DROP TABLE IF EXISTS crawl_bookmarks; + + +-- add crawled tags +CREATE TABLE crawl_tags ( + id INTEGER PRIMARY KEY, + tag TEXT UNIQUE +); + +-- add crawled bookmarks +CREATE TABLE crawl_bookmarks ( + id INTEGER PRIMARY KEY, + crawlSourceId INTEGER NOT NULL, + pathname TEXT NOT NULL, + crawledAt INTEGER, + + href TEXT, + title TEXT, + description TEXT, + createdAt INTEGER, + updatedAt INTEGER, + + FOREIGN KEY (crawlSourceId) REFERENCES crawl_sources (id) ON DELETE CASCADE +); +CREATE VIRTUAL TABLE crawl_bookmarks_fts_index USING fts5(title, description, content='crawl_bookmarks'); + +-- triggers to keep crawl_bookmarks_fts_index updated +CREATE TRIGGER crawl_bookmarks_ai AFTER INSERT ON crawl_bookmarks BEGIN + INSERT INTO crawl_bookmarks_fts_index(rowid, title, description) VALUES (new.rowid, new.title, new.description); +END; +CREATE TRIGGER crawl_bookmarks_ad AFTER DELETE ON crawl_bookmarks BEGIN + INSERT INTO crawl_bookmarks_fts_index(crawl_bookmarks_fts_index, rowid, title, description) VALUES('delete', old.rowid, old.title, old.description); +END; +CREATE TRIGGER crawl_bookmarks_au AFTER UPDATE ON crawl_bookmarks BEGIN + INSERT INTO crawl_bookmarks_fts_index(crawl_bookmarks_fts_index, rowid, title, description) VALUES('delete', old.rowid, old.title, old.description); + INSERT INTO crawl_bookmarks_fts_index(rowid, title, description) VALUES (new.rowid, new.title, new.description); +END; + +-- add bookmark <-> tag join table +CREATE TABLE crawl_bookmarks_tags ( + crawlBookmarkId INTEGER, + crawlTagId INTEGER, + + FOREIGN KEY (crawlBookmarkId) REFERENCES crawl_bookmarks (id) ON DELETE CASCADE, + FOREIGN KEY (crawlTagId) REFERENCES crawl_tags (id) ON DELETE CASCADE +); + +PRAGMA user_version = 28; +` diff --git a/app/bg/dbs/schemas/profile-data.v29.sql.js b/app/bg/dbs/schemas/profile-data.v29.sql.js new file mode 100644 index 0000000000..a9af7074cb --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v29.sql.js @@ -0,0 +1,20 @@ +export default ` + +-- add crawled votes +CREATE TABLE crawl_votes ( + crawlSourceId INTEGER NOT NULL, + pathname TEXT NOT NULL, + crawledAt INTEGER, + + topic TEXT NOT NULL, + vote INTEGER NOT NULL, + createdAt INTEGER, + updatedAt INTEGER, + + PRIMARY KEY (crawlSourceId, pathname), + FOREIGN KEY (crawlSourceId) REFERENCES crawl_sources (id) ON DELETE CASCADE +); +CREATE INDEX crawl_votes_topic ON crawl_votes (topic); + +PRAGMA user_version = 29; +` \ No newline at end of file diff --git a/app/bg/dbs/schemas/profile-data.v3.sql.js b/app/bg/dbs/schemas/profile-data.v3.sql.js new file mode 100644 index 0000000000..e16c32b1d9 --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v3.sql.js @@ -0,0 +1,7 @@ +export default ` + +-- add variable to track the access times of archives +ALTER TABLE archives_meta ADD COLUMN lastAccessTime INTEGER DEFAULT 0; + +PRAGMA user_version = 3; +` diff --git a/app/bg/dbs/schemas/profile-data.v30.sql.js b/app/bg/dbs/schemas/profile-data.v30.sql.js new file mode 100644 index 0000000000..4d11409eb3 --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v30.sql.js @@ -0,0 +1,30 @@ +export default ` + +-- add crawled discussions +CREATE TABLE crawl_discussions ( + id INTEGER PRIMARY KEY, + crawlSourceId INTEGER NOT NULL, + pathname TEXT NOT NULL, + crawledAt INTEGER, + + title TEXT NOT NULL, + body TEXT, + href TEXT, + createdAt INTEGER, + updatedAt INTEGER, + + FOREIGN KEY (crawlSourceId) REFERENCES crawl_sources (id) ON DELETE CASCADE +); +CREATE INDEX crawl_discussions_url ON crawl_discussions (crawlSourceId, pathname); + +-- add discussion <-> tag join table +CREATE TABLE crawl_discussions_tags ( + crawlDiscussionId INTEGER, + crawlTagId INTEGER, + + FOREIGN KEY (crawlDiscussionId) REFERENCES crawl_discussions (id) ON DELETE CASCADE, + FOREIGN KEY (crawlTagId) REFERENCES crawl_tags (id) ON DELETE CASCADE +); + +PRAGMA user_version = 30; +` \ No newline at end of file diff --git a/app/bg/dbs/schemas/profile-data.v31.sql.js b/app/bg/dbs/schemas/profile-data.v31.sql.js new file mode 100644 index 0000000000..96b78fe144 --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v31.sql.js @@ -0,0 +1,46 @@ +export default ` + +-- add crawled media +CREATE TABLE crawl_media ( + id INTEGER PRIMARY KEY, + crawlSourceId INTEGER NOT NULL, + pathname TEXT NOT NULL, + crawledAt INTEGER, + + subtype TEXT NOT NULL, + href TEXT NOT NULL, + title TEXT NOT NULL, + description TEXT, + createdAt INTEGER, + updatedAt INTEGER, + + FOREIGN KEY (crawlSourceId) REFERENCES crawl_sources (id) ON DELETE CASCADE +); +CREATE INDEX crawl_media_url ON crawl_media (crawlSourceId, pathname); +CREATE INDEX crawl_media_subtype ON crawl_media (subtype); +CREATE INDEX crawl_media_href ON crawl_media (href); +CREATE VIRTUAL TABLE crawl_media_fts_index USING fts5(title, description, content='crawl_media'); + +-- triggers to keep crawl_media_fts_index updated +CREATE TRIGGER crawl_media_ai AFTER INSERT ON crawl_media BEGIN + INSERT INTO crawl_media_fts_index(rowid, title, description) VALUES (new.rowid, new.title, new.description); +END; +CREATE TRIGGER crawl_media_ad AFTER DELETE ON crawl_media BEGIN + INSERT INTO crawl_media_fts_index(crawl_media_fts_index, rowid, title, description) VALUES('delete', old.rowid, old.title, old.description); +END; +CREATE TRIGGER crawl_media_au AFTER UPDATE ON crawl_media BEGIN + INSERT INTO crawl_media_fts_index(crawl_media_fts_index, rowid, title, description) VALUES('delete', old.rowid, old.title, old.description); + INSERT INTO crawl_media_fts_index(rowid, title, description) VALUES (new.rowid, new.title, new.description); +END; + +-- add crawled media tags +CREATE TABLE crawl_media_tags ( + crawlMediaId INTEGER, + crawlTagId INTEGER, + + FOREIGN KEY (crawlMediaId) REFERENCES crawl_media (id) ON DELETE CASCADE, + FOREIGN KEY (crawlTagId) REFERENCES crawl_tags (id) ON DELETE CASCADE +); + +PRAGMA user_version = 31; +` \ No newline at end of file diff --git a/app/bg/dbs/schemas/profile-data.v32.sql.js b/app/bg/dbs/schemas/profile-data.v32.sql.js new file mode 100644 index 0000000000..eeea59f2c3 --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v32.sql.js @@ -0,0 +1,19 @@ +export default ` + +CREATE VIRTUAL TABLE crawl_discussions_fts_index USING fts5(title, body, content='crawl_discussions'); + +-- triggers to keep crawl_discussions_fts_index updated +CREATE TRIGGER crawl_discussions_ai AFTER INSERT ON crawl_discussions BEGIN + INSERT INTO crawl_discussions_fts_index(rowid, title, body) VALUES (new.rowid, new.title, new.body); +END; +CREATE TRIGGER crawl_discussions_ad AFTER DELETE ON crawl_discussions BEGIN + INSERT INTO crawl_discussions_fts_index(crawl_discussions_fts_index, rowid, title, body) VALUES('delete', old.rowid, old.title, old.body); +END; +CREATE TRIGGER crawl_discussions_au AFTER UPDATE ON crawl_discussions BEGIN + INSERT INTO crawl_discussions_fts_index(crawl_discussions_fts_index, rowid, title, body) VALUES('delete', old.rowid, old.title, old.body); + INSERT INTO crawl_discussions_fts_index(rowid, title, body) VALUES (new.rowid, new.title, new.body); +END; + +PRAGMA user_version = 32; + +` \ No newline at end of file diff --git a/app/bg/dbs/schemas/profile-data.v33.sql.js b/app/bg/dbs/schemas/profile-data.v33.sql.js new file mode 100644 index 0000000000..76a0acbf4a --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v33.sql.js @@ -0,0 +1,9 @@ +export default ` + +-- add label +ALTER TABLE users ADD COLUMN label TEXT; +-- add isTemporary +ALTER TABLE users ADD COLUMN isTemporary INTEGER DEFAULT 0; + +PRAGMA user_version = 33; +` \ No newline at end of file diff --git a/app/bg/dbs/schemas/profile-data.v34.sql.js b/app/bg/dbs/schemas/profile-data.v34.sql.js new file mode 100644 index 0000000000..4a9efc0b57 --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v34.sql.js @@ -0,0 +1,15 @@ +export default ` + +-- list of the users installed apps +CREATE TABLE installed_applications ( + id INTEGER PRIMARY KEY NOT NULL, + userId INTEGER NOT NULL, + enabled INTEGER DEFAULT 1, + url TEXT, + createdAt INTEGER, + + FOREIGN KEY (userId) REFERENCES users (id) ON DELETE CASCADE +); + +PRAGMA user_version = 34; +` \ No newline at end of file diff --git a/app/bg/dbs/schemas/profile-data.v35.sql.js b/app/bg/dbs/schemas/profile-data.v35.sql.js new file mode 100644 index 0000000000..79888512ce --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v35.sql.js @@ -0,0 +1,17 @@ +export default ` + +CREATE TABLE dat_dns ( + id INTEGER PRIMARY KEY, + name TEXT, + key TEXT, + isCurrent INTEGER, + lastConfirmedAt INTEGER, + firstConfirmedAt INTEGER +); +CREATE INDEX dat_dns_name ON dat_dns (name); +CREATE INDEX dat_dns_key ON dat_dns (key); + +ALTER TABLE crawl_sources ADD COLUMN datDnsId INTEGER; + +PRAGMA user_version = 35; +` \ No newline at end of file diff --git a/app/bg/dbs/schemas/profile-data.v36.sql.js b/app/bg/dbs/schemas/profile-data.v36.sql.js new file mode 100644 index 0000000000..7db1dc5f89 --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v36.sql.js @@ -0,0 +1,12 @@ +export default ` +CREATE TABLE user_site_sessions ( + id INTEGER PRIMARY KEY NOT NULL, + userId INTEGER NOT NULL, + url TEXT, + permissionsJson TEXT, + createdAt INTEGER, + + FOREIGN KEY (userId) REFERENCES users (id) ON DELETE CASCADE +); +PRAGMA user_version = 36; +` \ No newline at end of file diff --git a/app/bg/dbs/schemas/profile-data.v37.sql.js b/app/bg/dbs/schemas/profile-data.v37.sql.js new file mode 100644 index 0000000000..7312631f6a --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v37.sql.js @@ -0,0 +1,6 @@ +export default ` + +ALTER TABLE crawl_sources ADD COLUMN isPrivate INTEGER; + +PRAGMA user_version = 37; +` \ No newline at end of file diff --git a/app/bg/dbs/schemas/profile-data.v38.sql.js b/app/bg/dbs/schemas/profile-data.v38.sql.js new file mode 100644 index 0000000000..7c0c7b8553 --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v38.sql.js @@ -0,0 +1,6 @@ +export default ` + +ALTER TABLE archives_meta ADD COLUMN author TEXT; + +PRAGMA user_version = 38; +` \ No newline at end of file diff --git a/app/bg/dbs/schemas/profile-data.v39.sql.js b/app/bg/dbs/schemas/profile-data.v39.sql.js new file mode 100644 index 0000000000..ff21988eba --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v39.sql.js @@ -0,0 +1,34 @@ +export default ` +DROP TRIGGER IF EXISTS crawl_posts_ai; +DROP TRIGGER IF EXISTS crawl_posts_ad; +DROP TRIGGER IF EXISTS crawl_posts_au; +DROP TABLE IF EXISTS crawl_posts; + +-- crawled statuses +CREATE TABLE crawl_statuses ( + crawlSourceId INTEGER NOT NULL, + pathname TEXT NOT NULL, + crawledAt INTEGER, + + body TEXT, + createdAt INTEGER, + updatedAt INTEGER, + + FOREIGN KEY (crawlSourceId) REFERENCES crawl_sources (id) ON DELETE CASCADE +); +CREATE VIRTUAL TABLE crawl_statuses_fts_index USING fts5(body, content='crawl_statuses'); + +-- triggers to keep crawl_statuses_fts_index updated +CREATE TRIGGER crawl_statuses_ai AFTER INSERT ON crawl_statuses BEGIN + INSERT INTO crawl_statuses_fts_index(rowid, body) VALUES (new.rowid, new.body); +END; +CREATE TRIGGER crawl_statuses_ad AFTER DELETE ON crawl_statuses BEGIN + INSERT INTO crawl_statuses_fts_index(crawl_statuses_fts_index, rowid, body) VALUES('delete', old.rowid, old.body); +END; +CREATE TRIGGER crawl_statuses_au AFTER UPDATE ON crawl_statuses BEGIN + INSERT INTO crawl_statuses_fts_index(crawl_statuses_fts_index, rowid, body) VALUES('delete', old.rowid, old.body); + INSERT INTO crawl_statuses_fts_index(rowid, body) VALUES (new.rowid, new.body); +END; + +PRAGMA user_version = 39; +` \ No newline at end of file diff --git a/app/bg/dbs/schemas/profile-data.v4.sql.js b/app/bg/dbs/schemas/profile-data.v4.sql.js new file mode 100644 index 0000000000..913d84734b --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v4.sql.js @@ -0,0 +1,8 @@ +export default ` + +-- add flags to control swarming behaviors of archives +ALTER TABLE archives ADD COLUMN autoDownload INTEGER DEFAULT 1; +ALTER TABLE archives ADD COLUMN autoUpload INTEGER DEFAULT 1; + +PRAGMA user_version = 4; +` diff --git a/app/bg/dbs/schemas/profile-data.v40.sql.js b/app/bg/dbs/schemas/profile-data.v40.sql.js new file mode 100644 index 0000000000..1341c33345 --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v40.sql.js @@ -0,0 +1,21 @@ +export default ` + +DROP INDEX IF EXISTS crawl_reactions_topic; +DROP TABLE IF EXISTS crawl_reactions; + +-- crawled reactions +CREATE TABLE crawl_reactions ( + crawlSourceId INTEGER NOT NULL, + pathname TEXT NOT NULL, + crawledAt INTEGER, + + topic TEXT NOT NULL, + phrases TEXT NOT NULL, + + PRIMARY KEY (crawlSourceId, pathname), + FOREIGN KEY (crawlSourceId) REFERENCES crawl_sources (id) ON DELETE CASCADE +); +CREATE INDEX crawl_reactions_topic ON crawl_reactions (topic); + +PRAGMA user_version = 40; +` \ No newline at end of file diff --git a/app/bg/dbs/schemas/profile-data.v41.sql.js b/app/bg/dbs/schemas/profile-data.v41.sql.js new file mode 100644 index 0000000000..f28834a75f --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v41.sql.js @@ -0,0 +1,20 @@ +export default ` + +ALTER TABLE archives_meta ADD COLUMN type TEXT; + +-- crawled dats +CREATE TABLE crawl_dats ( + crawlSourceId INTEGER NOT NULL, + crawledAt INTEGER, + + key TEXT NOT NULL, + title TEXT, + description TEXT, + type TEXT, + + PRIMARY KEY (crawlSourceId, key), + FOREIGN KEY (crawlSourceId) REFERENCES crawl_sources (id) ON DELETE CASCADE +); + +PRAGMA user_version = 41; +` \ No newline at end of file diff --git a/app/bg/dbs/schemas/profile-data.v42.sql.js b/app/bg/dbs/schemas/profile-data.v42.sql.js new file mode 100644 index 0000000000..1f1761510f --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v42.sql.js @@ -0,0 +1,32 @@ +export default ` + +CREATE VIRTUAL TABLE crawl_dats_fts_index USING fts5(title, description, content='crawl_dats'); + +-- triggers to keep crawl_dats_fts_index updated +CREATE TRIGGER crawl_dats_ai AFTER INSERT ON crawl_dats BEGIN + INSERT INTO crawl_dats_fts_index(rowid, title, description) VALUES (new.rowid, new.title, new.description); +END; +CREATE TRIGGER crawl_dats_ad AFTER DELETE ON crawl_dats BEGIN + INSERT INTO crawl_dats_fts_index(crawl_dats_fts_index, rowid, title, description) VALUES('delete', old.rowid, old.title, old.description); +END; +CREATE TRIGGER crawl_dats_au AFTER UPDATE ON crawl_dats BEGIN + INSERT INTO crawl_dats_fts_index(crawl_dats_fts_index, rowid, title, description) VALUES('delete', old.rowid, old.title, old.description); + INSERT INTO crawl_dats_fts_index(rowid, title, description) VALUES (new.rowid, new.title, new.description); +END; + +CREATE VIRTUAL TABLE archives_meta_fts_index USING fts5(title, description, content='archives_meta'); + +-- triggers to keep archives_meta_fts_index updated +CREATE TRIGGER archives_meta_ai AFTER INSERT ON archives_meta BEGIN + INSERT INTO archives_meta_fts_index(rowid, title, description) VALUES (new.rowid, new.title, new.description); +END; +CREATE TRIGGER archives_meta_ad AFTER DELETE ON archives_meta BEGIN + INSERT INTO archives_meta_fts_index(archives_meta_fts_index, rowid, title, description) VALUES('delete', old.rowid, old.title, old.description); +END; +CREATE TRIGGER archives_meta_au AFTER UPDATE ON archives_meta BEGIN + INSERT INTO archives_meta_fts_index(archives_meta_fts_index, rowid, title, description) VALUES('delete', old.rowid, old.title, old.description); + INSERT INTO archives_meta_fts_index(rowid, title, description) VALUES (new.rowid, new.title, new.description); +END; + +PRAGMA user_version = 42; +` \ No newline at end of file diff --git a/app/bg/dbs/schemas/profile-data.v43.sql.js b/app/bg/dbs/schemas/profile-data.v43.sql.js new file mode 100644 index 0000000000..73f007c5ef --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v43.sql.js @@ -0,0 +1,50 @@ +export default ` + +-- remove deprecations +DROP TRIGGER IF EXISTS crawl_discussions_au; +DROP TRIGGER IF EXISTS crawl_discussions_ad; +DROP TRIGGER IF EXISTS crawl_discussions_ai; +DROP TRIGGER IF EXISTS crawl_media_au; +DROP TRIGGER IF EXISTS crawl_media_ad; +DROP TRIGGER IF EXISTS crawl_media_ai; +DROP TRIGGER IF EXISTS crawl_site_descriptions_au; +DROP TRIGGER IF EXISTS crawl_site_descriptions_ad; +DROP TRIGGER IF EXISTS crawl_site_descriptions_ai; +DROP TRIGGER IF EXISTS crawl_dats_au; +DROP TRIGGER IF EXISTS crawl_dats_ad; +DROP TRIGGER IF EXISTS crawl_dats_ai; +DROP TRIGGER IF EXISTS crawl_bookmarks_au; +DROP TRIGGER IF EXISTS crawl_bookmarks_ad; +DROP TRIGGER IF EXISTS crawl_bookmarks_ai; +DROP TRIGGER IF EXISTS crawl_comments_au; +DROP TRIGGER IF EXISTS crawl_comments_ad; +DROP TRIGGER IF EXISTS crawl_comments_ai; +DROP TRIGGER IF EXISTS crawl_statuses_au; +DROP TRIGGER IF EXISTS crawl_statuses_ad; +DROP TRIGGER IF EXISTS crawl_statuses_ai; +DROP TABLE IF EXISTS crawl_discussions_tags; +DROP TABLE IF EXISTS crawl_discussions_fts_index; +DROP TABLE IF EXISTS crawl_discussions; +DROP TABLE IF EXISTS crawl_media_tags; +DROP TABLE IF EXISTS crawl_media_fts_index; +DROP TABLE IF EXISTS crawl_media; +DROP TABLE IF EXISTS crawl_site_descriptions_fts_index; +DROP TABLE IF EXISTS crawl_site_descriptions; +DROP TABLE IF EXISTS crawl_dats_fts_index; +DROP TABLE IF EXISTS crawl_dats; +DROP TABLE IF EXISTS crawl_follows; +DROP TABLE IF EXISTS crawl_bookmarks_tags; +DROP TABLE IF EXISTS crawl_bookmarks_fts_index; +DROP TABLE IF EXISTS crawl_bookmarks; +DROP TABLE IF EXISTS crawl_votes; +DROP TABLE IF EXISTS crawl_reactions; +DROP TABLE IF EXISTS crawl_comments_fts_index; +DROP TABLE IF EXISTS crawl_comments; +DROP TABLE IF EXISTS crawl_statuses_fts_index; +DROP TABLE IF EXISTS crawl_statuses; +DROP TABLE IF EXISTS crawl_tags; +DROP TABLE IF EXISTS crawl_sources_meta; +DROP TABLE IF EXISTS crawl_sources; + +PRAGMA user_version = 43; +` \ No newline at end of file diff --git a/app/bg/dbs/schemas/profile-data.v44.sql.js b/app/bg/dbs/schemas/profile-data.v44.sql.js new file mode 100644 index 0000000000..626876edea --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v44.sql.js @@ -0,0 +1,8 @@ +export default ` + +CREATE TABLE setup_state ( + profileCreated INTEGER DEFAULT 0 +); + +PRAGMA user_version = 44; +` \ No newline at end of file diff --git a/app/bg/dbs/schemas/profile-data.v45.sql.js b/app/bg/dbs/schemas/profile-data.v45.sql.js new file mode 100644 index 0000000000..650d66e242 --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v45.sql.js @@ -0,0 +1,15 @@ +export default ` + +DROP TABLE IF EXISTS users; +DROP TABLE IF EXISTS user_site_sessions; +CREATE TABLE user_site_sessions ( + id INTEGER PRIMARY KEY NOT NULL, + siteOrigin TEXT, + userUrl TEXT, + permissionsJson TEXT, + createdAt INTEGER +); + +PRAGMA user_version = 45; +` + diff --git a/app/bg/dbs/schemas/profile-data.v46.sql.js b/app/bg/dbs/schemas/profile-data.v46.sql.js new file mode 100644 index 0000000000..4fa0945a85 --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v46.sql.js @@ -0,0 +1,7 @@ +export default ` + +-- add variable to user memberOf metadata +ALTER TABLE archives_meta ADD COLUMN memberOf TEXT; + +PRAGMA user_version = 46; +` diff --git a/app/bg/dbs/schemas/profile-data.v47.sql.js b/app/bg/dbs/schemas/profile-data.v47.sql.js new file mode 100644 index 0000000000..50792c4d57 --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v47.sql.js @@ -0,0 +1,9 @@ +export default ` + +DROP TABLE IF EXISTS setup_state; +CREATE TABLE setup_state ( + migrated08to09 INTEGER DEFAULT 0 +); + +PRAGMA user_version = 47; +` diff --git a/app/bg/dbs/schemas/profile-data.v48.sql.js b/app/bg/dbs/schemas/profile-data.v48.sql.js new file mode 100644 index 0000000000..5dd5aceced --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v48.sql.js @@ -0,0 +1,6 @@ +export default ` + +ALTER TABLE setup_state ADD COLUMN profileSetup INTEGER DEFAULT 0; + +PRAGMA user_version = 48; +` diff --git a/app/bg/dbs/schemas/profile-data.v49.sql.js b/app/bg/dbs/schemas/profile-data.v49.sql.js new file mode 100644 index 0000000000..4dc0b85588 --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v49.sql.js @@ -0,0 +1,6 @@ +export default ` + +ALTER TABLE setup_state ADD COLUMN hasVisitedProfile INTEGER DEFAULT 0; + +PRAGMA user_version = 49; +` diff --git a/app/bg/dbs/schemas/profile-data.v5.sql.js b/app/bg/dbs/schemas/profile-data.v5.sql.js new file mode 100644 index 0000000000..5b482da8ee --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v5.sql.js @@ -0,0 +1,7 @@ +export default ` + +-- more default bookmarks +-- REMOVED + +PRAGMA user_version = 5; +` diff --git a/app/bg/dbs/schemas/profile-data.v6.sql.js b/app/bg/dbs/schemas/profile-data.v6.sql.js new file mode 100644 index 0000000000..ce014403b2 --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v6.sql.js @@ -0,0 +1,7 @@ +export default ` + +-- add more flags to control swarming behaviors of archives +ALTER TABLE archives ADD COLUMN networked INTEGER DEFAULT 1; + +PRAGMA user_version = 6; +` diff --git a/app/bg/dbs/schemas/profile-data.v7.sql.js b/app/bg/dbs/schemas/profile-data.v7.sql.js new file mode 100644 index 0000000000..03c0d2b5af --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v7.sql.js @@ -0,0 +1,7 @@ +export default ` + +-- add a field to track rehost expiration (for timed rehosting) +ALTER TABLE archives ADD COLUMN expiresAt INTEGER; + +PRAGMA user_version = 7; +` diff --git a/app/bg/dbs/schemas/profile-data.v8.sql.js b/app/bg/dbs/schemas/profile-data.v8.sql.js new file mode 100644 index 0000000000..8235360923 --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v8.sql.js @@ -0,0 +1,8 @@ +export default ` + +-- add tags and notes to bookmarks +ALTER TABLE bookmarks ADD COLUMN tags TEXT; +ALTER TABLE bookmarks ADD COLUMN notes TEXT; + +PRAGMA user_version = 8; +` diff --git a/app/bg/dbs/schemas/profile-data.v9.sql.js b/app/bg/dbs/schemas/profile-data.v9.sql.js new file mode 100644 index 0000000000..e5af667da2 --- /dev/null +++ b/app/bg/dbs/schemas/profile-data.v9.sql.js @@ -0,0 +1,10 @@ +export default ` + +-- join table to list the archive's type fields +CREATE TABLE archives_meta_type ( + key TEXT, + type TEXT +); + +PRAGMA user_version = 9; +` diff --git a/app/bg/dbs/settings.js b/app/bg/dbs/settings.js new file mode 100644 index 0000000000..21c028fe87 --- /dev/null +++ b/app/bg/dbs/settings.js @@ -0,0 +1,124 @@ +import EventEmitter from 'events' +import sqlite3 from 'sqlite3' +import path from 'path' +import { cbPromise } from '../../lib/functions' +import { setupSqliteDB } from '../lib/db' +import { getEnvVar } from '../lib/env' + +// globals +// = +var db +var migrations +var setupPromise +var defaultSettings +var events = new EventEmitter() + +// exported methods +// = + +/** + * @param {Object} opts + * @param {string} opts.userDataPath + * @param {string} opts.homePath + */ +export const setup = function (opts) { + // open database + var dbPath = path.join(opts.userDataPath, 'Settings') + db = new sqlite3.Database(dbPath) + setupPromise = setupSqliteDB(db, {migrations}, '[SETTINGS]') + + defaultSettings = { + auto_update_enabled: 1, + auto_redirect_to_dat: 1, + custom_start_page: 'blank', + new_tab: 'beaker://desktop/', + run_background: 1, + start_page_background_image: '', + workspace_default_path: path.join(opts.homePath, 'Sites'), + default_dat_ignore: '.git\n.dat\nnode_modules\n*.log\n**/.DS_Store\nThumbs.db\n', + analytics_enabled: 1, + dat_bandwidth_limit_up: 0, + dat_bandwidth_limit_down: 0 + } +} + +export const on = events.on.bind(events) +export const once = events.once.bind(events) + +/** + * @param {string} key + * @param {string | number} value + * @returns {Promise} + */ +export async function set (key, value) { + await setupPromise.then(() => cbPromise(cb => { + db.run(` + INSERT OR REPLACE + INTO settings (key, value, ts) + VALUES (?, ?, ?) + `, [key, value, Date.now()], cb) + })) + events.emit('set', key, value) + events.emit('set:' + key, value) +} + +/** + * @param {string} key + * @returns {boolean | Promise} + */ +export const get = function (key) { + // env variables + if (key === 'no_welcome_tab') { + return (Number(getEnvVar('BEAKER_NO_WELCOME_TAB')) === 1) + } + // stored values + return setupPromise.then(() => cbPromise(cb => { + db.get(`SELECT value FROM settings WHERE key = ?`, [key], (err, row) => { + if (row) { row = row.value } + if (typeof row === 'undefined') { row = defaultSettings[key] } + cb(err, row) + }) + })) +} + +/** + * @returns {Promise} + */ +export const getAll = function () { + return setupPromise.then(v => cbPromise(cb => { + db.all(`SELECT key, value FROM settings`, (err, rows) => { + if (err) { return cb(err) } + + var obj = {} + rows.forEach(row => { obj[row.key] = row.value }) + obj = Object.assign({}, defaultSettings, obj) + obj.no_welcome_tab = (Number(getEnvVar('BEAKER_NO_WELCOME_TAB')) === 1) + cb(null, obj) + }) + })) +} + +// internal methods +// = + +migrations = [ + // version 1 + function (cb) { + db.exec(` + CREATE TABLE settings( + key PRIMARY KEY, + value, + ts + ); + INSERT INTO settings (key, value) VALUES ('auto_update_enabled', 1); + PRAGMA user_version = 1; + `, cb) + }, + // version 2 + function (cb) { + db.exec(` + INSERT INTO settings (key, value) VALUES ('start_page_background_image', ''); + PRAGMA user_version = 2 + `, cb) + } +] diff --git a/app/bg/dbs/sitedata.js b/app/bg/dbs/sitedata.js new file mode 100644 index 0000000000..921c1d6230 --- /dev/null +++ b/app/bg/dbs/sitedata.js @@ -0,0 +1,260 @@ +import sqlite3 from 'sqlite3' +import path from 'path' +import { parseDriveUrl } from '../../lib/urls' +import { cbPromise } from '../../lib/functions' +import { setupSqliteDB } from '../lib/db' + +// globals +// = + +var db +var migrations +var setupPromise + +// exported methods +// = + +/** + * @param {Object} opts + * @param {string} opts.userDataPath + */ +export function setup (opts) { + // open database + var dbPath = path.join(opts.userDataPath, 'SiteData') + db = new sqlite3.Database(dbPath) + setupPromise = setupSqliteDB(db, {migrations}, '[SITEDATA]') +} + +/** + * @param {string} url + * @param {string} key + * @param {number | string} value + * @param {Object} [opts] + * @param {boolean} [opts.dontExtractOrigin] + * @param {boolean} [opts.normalizeUrl] + * @returns {Promise} + */ +export async function set (url, key, value, opts) { + await setupPromise + var origin = opts && opts.dontExtractOrigin ? url : await extractOrigin(url) + if (!origin) return null + if (opts && opts.normalizeUrl) origin = normalizeUrl(origin) + return cbPromise(cb => { + db.run(` + INSERT OR REPLACE + INTO sitedata (origin, key, value) + VALUES (?, ?, ?) + `, [origin, key, value], cb) + }) +} + +/** + * @param {string} url + * @param {string} key + * @returns {Promise} + */ +export async function clear (url, key) { + await setupPromise + var origin = await extractOrigin(url) + if (!origin) return null + return cbPromise(cb => { + db.run(` + DELETE FROM sitedata WHERE origin = ? AND key = ? + `, [origin, key], cb) + }) +} + +/** + * @param {string} url + * @param {string} key + * @param {Object} [opts] + * @param {boolean} [opts.dontExtractOrigin] + * @param {boolean} [opts.normalizeUrl] + * @returns {Promise} + */ +export async function get (url, key, opts) { + await setupPromise + var origin = opts && opts.dontExtractOrigin ? url : await extractOrigin(url) + if (!origin) return null + if (opts && opts.normalizeUrl) origin = normalizeUrl(origin) + return cbPromise(cb => { + db.get(`SELECT value FROM sitedata WHERE origin = ? AND key = ?`, [origin, key], (err, res) => { + if (err) return cb(err) + cb(null, res && res.value) + }) + }) +} + +/** + * @param {string} url + * @param {string} key + * @returns {Promise} + */ +export function getPermission (url, key) { + return get(url, 'perm:' + key) +} + +/** + * @param {string} url + * @returns {Promise} + */ +export async function getPermissions (url) { + await setupPromise + var origin = await extractOrigin(url) + if (!origin) return null + return cbPromise(cb => { + db.all(`SELECT key, value FROM sitedata WHERE origin = ? AND key LIKE 'perm:%'`, [origin], (err, rows) => { + if (err) return cb(err) + + // convert to a dictionary + // TODO - pull defaults from browser settings + var perms = { /* js: true */ } + if (rows) rows.forEach(row => { perms[row.key.slice('5')] = row.value }) + cb(null, perms) + }) + }) +} + +/** + * @param {string} url + * @returns {Promise>} + */ +export async function getNetworkPermissions (url) { + await setupPromise + var origin = await extractOrigin(url) + if (!origin) return null + return cbPromise(cb => { + db.all(`SELECT key, value FROM sitedata WHERE origin = ? AND key LIKE 'perm:network:%'`, [origin], (err, rows) => { + if (err) return cb(err) + + // convert to array + var origins = /** @type string[] */([]) + if (rows) { + rows.forEach(row => { + if (row.value) origins.push(row.key.split(':').pop()) + }) + } + cb(null, origins) + }) + }) +} + +/** + * @param {string} url + * @param {string} key + * @param {string | number} value + * @returns {Promise} + */ +export function setPermission (url, key, value) { + value = value ? 1 : 0 + return set(url, 'perm:' + key, value) +} + +/** + * @param {string} url + * @param {string} key + * @returns {Promise} + */ +export function clearPermission (url, key) { + return clear(url, 'perm:' + key) +} + +/** + * @param {string} key + * @returns {Promise} + */ +export async function clearPermissionAllOrigins (key) { + await setupPromise + key = 'perm:' + key + return cbPromise(cb => { + db.run(` + DELETE FROM sitedata WHERE key = ? + `, [key], cb) + }) +} + +export const WEBAPI = { + get, + set, + getPermissions, + getPermission, + setPermission, + clearPermission, + clearPermissionAllOrigins +} + +// internal methods +// = + +/** + * @param {string} originURL + * @returns {Promise} + */ +async function extractOrigin (originURL) { + var urlp = parseDriveUrl(originURL) + if (!urlp || !urlp.host || !urlp.protocol) return + return (urlp.protocol + urlp.host + (urlp.port ? `:${urlp.port}` : '')) +} + +/** + * @param {string} originURL + * @returns {string} + */ +function normalizeUrl (originURL) { + try { + var urlp = new URL(originURL) + return (urlp.protocol + '//' + urlp.hostname + (urlp.port ? `:${urlp.port}` : '') + urlp.pathname).replace(/([/]$)/g, '') + } catch (e) {} + return originURL +} + +migrations = [ + // version 1 + // - includes favicons for default bookmarks + function (cb) { + db.exec(` + CREATE TABLE sitedata( + origin NOT NULL, + key NOT NULL, + value + ); + CREATE UNIQUE INDEX sitedata_origin_key ON sitedata (origin, key); + INSERT OR REPLACE INTO "sitedata" VALUES('https:duckduckgo.com','favicon','data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAQ3klEQVR4Xr1bC3BU13n+zr2rlZaHIskRFGzwLtJKQjwsBfMIxkHiEQkBZkWhCS0v0ThpQlqkjt1xGnssOVN7OvUE0TymcZIiXKeljSdakHnIULN262ZIGyNjENKupF3eIAxaSQhJu3vP6Zx79+7efWnvIpEzo9HOPf/5z/9/5///85//nkvwB2hXt+SXiRQlFORJgaFEnZIRlPHfhMGhPqMEbQLYZUlA26x3u0LPH5WY5FEwvmkrMjMhYAMjm1QlH3YeGRzCjhBqsM+wd3gelk+icRMKwDWbdbckSvtEJoZWeSIFlojUJkrigSfsrqaJ4jtuANw2c5ZBMNYSxvYByEogmIMR8iGhzMPAPAEE2ix2j1dLK/OBoYSAmJlAzISxlYDiInGalxFyIEB9jdF8UgVmXADwFQehrwKCOWpiLwi1C1Q8MtPutKt9qpKy3wsoYRBkwAiol1G08d/R4NywFdioIG0CE2yxAFMPmNAwHot4KADctiKzSKSDJGqFCBSB/PDb+cpwujQhYGPASsIYVzgaqLgLxvkwQtoI8KGfGuwWe4eHg5eGNBsHPJoPAxwSE2s43SO3gCu2Ahsh7KB2NbjAlAkNs4O+ecVm3c2ItE/AxMQCCqmNMPGAlr8QC4SXMVIzW2NxesBIyQKu2grqAfZqBGOBNHBf5M8MMNYCY8YCPTKNReMFyIEAgvMJxlrQKHlAGmbZnfV6J9INwBVb3kFA2B3awyG1iRBrnrC72rhVANL+OLFArxwp0lEPINbx1b5ms5ZI4O6otTbaNNveXaOHqS4AopWnYHaGgDwBgeGgAMID1B+8jS2HPhCSAhCtPKAw5shT4IwaCySjCZMKFiJj/pIQEIHe6+B/oxfOPkpwvAJQrlhipJWqso41+ZgAXLZZ9xOgNsxAUZ4HOQA8EIZaX8ESsK9shuXZNcjMzIyZc/TC7zB05jd4cPY02NDAowCkhgfJWFdF45N2V12iCRMCIEdyplWSNj15RFE+8rnCmltAVsWfgK3cjJz8uQkVpEMD6D/8Iwy2HJpwEAiBDMLlTZGWoD6PN2FcAPj+LQSkcxDVzG5s5Tnjwe+8iRlPrwjNYTKZwP8SNZ/7Enpf3gEOyES2uCBI8FKDWBovT4gLwJWN1jNMCKahTGqjAi0H0swCw7lEwnIXMN6/F+oempEv/55S+gz+aNEKZM14PGYojw+36jZNOAiUoBTwewQqnAFRdgdC4Zjd4iqPFiIGALfNuptoTJ8FmZFAmjtsEcnXbMqqzTAtXSMHRWFybEzQcuDWMPTBb3D/g+aJAUOClxn8Fr5oRLNojKDGEnWQigCAp5vEbwwpyoAGy1FnvWej9QwISXQwiUAjbdFKTPuLV2GYFrviyWDj7nD7+zvgc3ckI03ez5jD3OIqdz9XUE8AJXnjwKT5LNoDVCQAEcSSx3ys2+LeaN1NCImI+Akj6vYXMXvrN5ILNwaFAsJOcKsYb2OM1VhaXE2e9XluiKJ8DlEXVeUdAoCvvuQ3ukU18DFUQ/Q5Ip6NIdGDyp0o/vb3xyuzPJ7Hhhu1tnG7gyTBK6b5LJCMZSBolo0g+Ey1gjAAGwtrQdh+TkSBtryjzlJuPlDNZyzlZ+bjsfp/xvTp0ycEAM5koOUQ7v3i9YngJ7tx93MF5wQEy3GM1FlaOuXzSwiArvV5bjFoJmCsBqLfrnf1b63/FpZ986/HLeyhdy/gkvNzCGCo+fpTML2xRbaG8bSwFfCjtOLKkiR58o91W0IAuKusJUwk8hbHB1iPO7PdGwtrGRSLGKtJ6SbcfeFnWLp0qUx2+foAfnn4PC5f8SJzchqm507Gy3Xh/CARr08u9mLwvg85menw9g/D2XMPX5vuxp0DLyUTIWk/gbLirqqCPtXFicRKLcddvOYAdG/kKS+RU14G1pjX4qrjJkM0FdxEswxaFsD03TdgtVplkgMHP4H1ySxUrZqTVDA9BFe/sWrcVsCI4tLx9FQA0CjLkZGBCFpEMiE/f7oSWdu+GwLgv//vBlY8PTPZMN39d3/xOgaOjj9tjtZLBYXw6E8lY59q/gXHndndPCDqMH8+hgOQrQFAt2YJCAeHRnHkg3YUWnKxeP4T8nZ4bd9EnLZJXV5LZ6NT4waC6MsmXRsLbATKFgFQe15LV3UPT4WhL/HhAEzZ8i0UFxfHqMQVudE7gE2rijFz2tjZoDp4a9076HDfwYHvPQcOBh/r2bZ43FsiAXPMaXGVd2/MbwbkAivPCapJ94aIra4h7z1nffeGAqZ3JT9fXAlW8aehIKiO+/tfOvBOyzmsWpqHxfNnyf/1gLDApsTd7RtLkTk5A9/++jLc/NsdGP7sd3pFSkiX954zRl/SVZXfTAQFEQqh3GCAhwaoW+9sHIChZ20oL488Zyz/s5/KK8jNmCteZJkmK5WsqRYgB9TvPScD1/dvP0bfv/4o2dCk/YJBsAQCMAugvJADRqmddG2wnkHQ3CllpUQQs0iQIClHAA9m5uPqpr2oqqqKIN/3xlF8cLY79OzX+7ejyJKrhyX+98I1TJ2cHqLnANxLAIAhOwBDthTiO9KTnnAOBqGcUckrCMqWDzAH4QkQiJIn53MTWV9Yy4IZoR5paboJrj2vY9myZcjJyQkN4av/8j+2Bv14nuzLD9uiAeBKTyoeRoZlVP6vbXREwK2fT4PvZlrMdISRurxjnY1dqoszyUO61of9Pf+Yk7g2FNQTFjw96ZTY9eevo6h0EcxmXe89dHINk3nffQ2jn70lK5wxZxQcgLEaByCeJTCCBut7znqtzsSlAcAaBAApAnDNthc5S8qwcOHClJULDZD6wUbOA0MfhR6x4fPKM99lXXwDfQbceTcnrvIygyAAWp0nBIC7iyvhX70VK1YkT3ljNJH6QW++CNb3ji4l4xHx1e7/eCoetCcuwT1SAO5bFuDGuj0xgTCZRmygBfTaNwGpPxlpTD9f7aF2EwY+ngL+W1eLZwFOHgShBMEC7gI8COrMAtVJ/VNz4NnxSkwgHEsoNvRfoD0VuuRWiXiAG7pokleaK59q44ci67HORmfI7SUPca4Ll7skKlgMBpgZVfbJVJpn5yswf2lp6EyQbCztqQTT+LtK33f6C3IEN87wR7DgZj48xhaXbD45BAhCeSAAjygE8xzGHMRVld/MgqkhJ+D7JKDuk3rYKjQ3q/Zg8rI1WLRoka5B9PLXwF1A2wZ/Pxl3fh3eSnUxSolIyXPUBSagdtJZFU6FGSF1Rcc6Gzur9KfC6vz3llRi+CuxGWEi+XjQk/1f0/gK33hrWkoqceL0mX6M3ojd96MZFR53ko71hfw2i1rnaCCd6wpsar2MH4YKj3dVc7dgOqvA6iTDj+fjum0v1q5di7S05MLwcRyA6OjPQeBukMjcubLGGT5Z6Yw5I0gPugqPCbfe/mJC8AhjjoITrvLOqvBhCAzV5FJlkVkgau4veQpPdFtkq0gxF+AZYc/zsRlhsuUc/u12GEQ7BBONIdWCIGRQWelErfc/csBdKDECaCg87qzvXBcO+pQJFrkg0qF5KBClIEJZ6nHg8q5XYEkhEPJ5rv9gL+7/9jQmzxvGlHnDmFw8HBeMRIpxkO6dSmwx6rhYvSRP0YluFQDrfgRLYgBrLDrhqtOCkmwV1f7eNdtgfGZdzNF4rPFXX9qBB+cjj7qmOaMw5Y3CFDTxaOvgSg/3ZMhboh7fBxRlO9bF6ilbwKXKojJC1K1PIb5UlfqZoP+plehftVWOA3rbrR++hP7TwXqM3kEp0vEzwNzjznrtojImlM892eEIlcW1nRSoNqX7HKOjSqlMb+OB8Eb1Xrk2MNabYS2/z3/1Y9z91fjP+mPJmJ7uyx4eNZYJocqXssh8TAiAS5Wa7RDMUXzSVX6xsuCgAITuBekBoucv98u5gN6XJNz/r722Vw/rh6KhQNO8k86a9krrGRIu8zXMPalcpAoBcM5mzjING92MqHcChPKRjJG2yGfJZbi27QXMfPqZuDXCeKP5u0DnlsXJGT8EBWHwDpt8loyRjBIEizzqs9LgTdWIl6MdFdb9jATfDzC0Fbc6S9vXRSQOScW4+6wNhtWbUwqE7r02jPaM/2VotHA8sSs+0dnYXlFwjhDltRhhrLGoNXxlJgIAbgXpw0Y3VCvQMECQQTIE7s9dgjtrtqV0MrxnP4TbP5uQ94Bh8TQLCDXzY/COmnwWdfUjXEAdebGyqJ6w0OVDLyFEeVHCGK+jJboMHZrYlzsT17e9INcG4l2Wigeg//Z1dO1aHdM1miHgky9NxcX5UzDvwn18+X90H5vjys0IaZh3siPiEmXcKzLtFYVcWfXKe1txa2dp+1d5ykx07Vfuv/qhHAPUEtlPPm3AcOA+SnKXo3TacjyWEfsW+fLf7IzIB67OykDruscwkKmc9XN7fdjx9s1kBqj0M1Zd/L7THk+PaAZxAbi41loCQdDcB2JN81qdNRcri3aDyfeEx2y3/ngvvrh0ZahE9vzpyHM/B2LN7GoUZi/Eg8B9OPvOo+PjwzIAmQMB9Oam4dyi2BcpdW/qKI0RUjPvZEfTxYqCgwAJ72CUls475ZJvpGtbwmty7RW8MILw22GVsQ4QvEsrQNeGS2TRAKgCZItzMEp6ZRD0tKQAJJCRAHXFrcp9AN0AcMIYFHWC8GDOfPRuCJfIEgFw8y7DjMeSXlYNybz97ZuyK8RtCWVTrDcRwElnv7CWV4yE0AUpwkjNvFMdTTwmUMgXDmICYyAzB9drXg6VyCYKgC2Hb+OJqyPRungFsBru8xfXFu1mylX+YCygjvmnYq/G6XIBlehcmTlLMKafEcNBkW+mTfNbnTX8KO2jrDmiLziQA2BZpJTIXjv7HVwdDL8l4iQjPqBvMDUL2PPWdWT2h98JSECbUSDVc092eC5wn2dhn+d91DdaXuqI/DQnJRfQgpCWltastQTt5J99taieRH0ncGfDHkxdvlpOi9/8/Yvo7DsfMbfPD9y+R/F4rgBBULooBUqmLUfvlU9wa1LMSqP2H0JB0MtADix4v6M+7iIw6vD7/dXJlOdzJnUBrdQcZaZBGYCXEDTMb+1s5JaSlm7cr/b3L6uAr0wpkcUDYJJhCnxDuXDdVSwjLycPz8x6Bjuf2gHvqWa0//wVdOdPQo/VhGuzMuSVr3nrOghhTf5RXx1X7gIP1ErhJuSGvJ9bp56gmjIAfIA8KdXsDvJ7duogTGxYcLrDwYEwGDNqh2cV7bqz+XkzPxqfvPrvONrzL7JMXHG+Ba6ebZN/J2rOnavhu6VckCI04GFi2qGAb6SRK/7ZmqIyRqRXCcKxSaYTUMcXQ6/yDwUAH8TzhAAVDgpC+CtQ/pwDAUIOLHzfaW9ubs4yGo22FStW7PMbh0sOd/6TnASV5H55TMVV4fs/avVe+bt9TSKjh9T9+zxPxhjbF604pWgzCLQm3j6fDIyUXCCaGfd9Fu97QRrwQDTYufDF7zv5SxddGaSWP2PMIQhCOQdbIsIuSAEbBEPM53mEKLEgmaKJ+scFAGfKTV4UjfshaLIu7WwcDIiOzLW2LNOMWZ9mr9v6hbTc6XJSz5SPI0ONDfZlDX561jvq6TH3f3TMM+J2muMordBT1iRJSix4WOUf2gXiTahslxm1RPLvSij0eCSVlVZiAQ3GgvGym1AAtMKcX82TJGwikMpAYsw2NblZwMMgOgTgyML/DH+FmhqTxNTjdoFkgpyrLDKLEsoYg5lAkk2eQeAnzegM0ktA5cMKg/ghIfBIIhylJ1P/GjSZTNr+/wca6dPApxwOmgAAAABJRU5ErkJggg=='); + PRAGMA user_version = 1; + `, cb) + }, + // version 2 + // - more favicons for default bookmarks (removed) + function (cb) { + db.exec(`PRAGMA user_version = 2;`, cb) + }, + // version 3 + // - more favicons for default bookmarks (removed) + function (cb) { + db.exec(`PRAGMA user_version = 3;`, cb) + }, + // version 4 + // - more favicons for default bookmarks (removed) + function (cb) { + db.exec(`PRAGMA user_version = 4;`, cb) + }, + // version 5 + // - more favicons for default bookmarks (removed) + function (cb) { + db.exec(`PRAGMA user_version = 5;`, cb) + }, + // version 6 + // - more favicons (removed) + function (cb) { + db.exec(`PRAGMA user_version = 6;`, cb) + }, + // version 7 + // - more favicons + function (cb) { + db.exec(` + INSERT OR REPLACE INTO "sitedata" VALUES('https:beaker.network','favicon','data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACXklEQVQ4T6WTPWgTYRjH/88ladCI1RQKVScLDtl0UelSnZQWETFTsdQkipg7paWDupy3iILY4T76QVKjOJl2EqRTEXQoDgqCgoNCQFGwKLUfJrm795H3kisnddJ3ufde/u/v/T9fhP9cFLkv9yz/x8fPpRqN5G5mZYei+PFm0/1BlNqIx3/6jUaKenp61g3D8KQ2BGxeVtVCP4NvAzgcNUfAF4DfALQXoKuWVVqMAgKtqubyDCoR0AR4npneAXCJKMPgAQBd0iWB71jW7I3Wvr1UNd/HwAsCPggB1XHKC1EHmlY4Ihg2wIcIcIi2j5mm2QgAmqYlBa/PATTIhFOOWX4iz9Lpr0FOarWUUqlU6i0IL4BRY072OY6zFgCKxeEuUGIZwFPbKg/ouh4HIAzDEG0XNDIykpQQVS08YPCw8EXv5OT9j20H+YOC8YpApmWVrsjXpb1oCLqe7TCMarNYzF8H4RYLnJRhtgHnM4KVt2BM23b50t8A4Zmq5nQG3STQMcsqPQsAo6P5tOviG0DP19a8E9KqDCOsdTabjWUyiLUc5KogOut77r6pqYefA4AULy9/mgYhB6bLtl2aDJMrv6ZpNmXJNK1wRjDPE3gxkVgdnJio/tosY7F44QBIvAQQA9M111UezczMrLQcZrc1vM7TxHwXwB6F6Khplpa2dKKqFo4DXGUgTcASA+/B8EHYD6CfAI+Zh2x79nGY4C2trGkXe5n9MQaGAHS2hXUC5ohwzzTLr8PS/tGJkblgXdeVlZXazjqwK+F3xDwv9r27u3s1TGpbGzRZdBr/abB/AxcoGCCqR8KvAAAAAElFTkSuQmCC'); + INSERT OR REPLACE INTO "sitedata" VALUES('https:hyperdrive.network','favicon','data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAF4AAABeCAYAAACq0qNuAAAKNklEQVR4Xu2cDVBU1xXH/2fZRYS4KOyCiJ1o62QmHZM2Tmr9SCoZnbSZNu2IY0dFAYNB/EBLxFDECGo0RhFQ/EJB0gZsh7ZCMulYo2OcOHU0GJM2Mya1pn4kKp+uYARZdvd27rrg7rILu+89XHzcN864+7jn3Pt+7+y595x73iOIIyAEKCC9ik4hwAfICAR4AT5ABALUrbB4AT5ABALUrbB4AT5ABALUrbB4AT5ABALUrbB4AT5ABALUrbB4AT5ABALUrbB4NYJnjGkAhADQOq6P32jmdK38Mz/nfL7rHG/m/NkTInc53p+zfue/8882t3E4G563sXUCuEdEznpl365+s3jGWETexl1zvrl26bk7rS16c4eZGOz/NGBkB0Bg9v7t5x0n+Jeuv3Wf94zcBQQxxhjxG0xWroPr5vIEZmMgDe+sS7fXvohpgrU6W2hYKL4/dhxlZ6+wBQdrrwA4QET/lk3bSUG/gC8vLx9+9NjHq9rudiQCGA0HYPkDJwKYHTi3QMaguf+diAg2/r3r/652cvoMHjIEv/r1XFNSwosVANYS0R05+pxlFQefkVE19H9XDr/CmGYlGMY6uRmlxvyw9dieGv+jC2+++XougBqi+79WuYei4GfPZkEdHXN/CY1mLYGNZ4yGyh3gQJAnQtu0n718IiNjzutE9KUSY1IYfMJTHZ1sIxheAKBXYoADRYdOp20o2lP0l9HGERuIqEHuuBQDn5q6ylDX8O1qME0CQCMBBMkd3ACTtxmNUddLDxSWASgkolY541MEfG5ubvDnn19MAGleA9gTjCFYzqAGriyzTJk6/eus1SnFAMqI6J7UscoGzxdx8b9NmmQzWzYwYDKAMKmDeRTkdDqted/ewn8ZDBF5AI4SkVXKuGWDT0vLiL1R15gDZp1JRFH3l3jqPoxRxtbS/UXHAGQT0X+lXK0s8FVVVUPfPVS9mEBLCRjDGHRSBvEIylinTJ52PSsrtQTADiK66+81SAbP0wHx8QumW222XAKeYUCov50/yu11Om373j2FnxmNEesAnPA3pSAZfEpKxrjGpvo8IrzEGBvBo8dHGaT/Y2fMaIwylR4o+huA9UR03R8dkmCVlJSE//3IR+lgmlcBjFJBdOoPs+62ROj86aRpl7OzUvMBvENEPKHm0+E3eMaYNn524kyrxfZ7MPZDR/bRp87U2Eir1d7Zvavg9MiRkWuI6Lyv1+g3+MRFK5++3dS4QY3Rqa/QnNvxpFykwVBfdmDHOwDeJqIWX/T4BT4/P99w6tS5TAaar9Lo1Bdmntrcmzjx+Qs5a9I2Anjfl0Saz+AZY8Hxsxck2DptmQz4AYAhUkepPjnGdFqdaffuwqPR0RE5RHS5r2v0CTyPTlNTV06ub2zKA8MUtUenfUHz8neLwRD5TVnpTp5O2EdE7b3p8Ql8TkFB7Bcfnx9U0akU+AS0Pfvs87Vr16blADjd29q+T/CMsaGzZs1fbLFiGRF7fBBFpxLYM6bVaRuKd2yvGjXKuJGIGr0p6RU8j06TU5bNMDXfXjcYo1MJ5HkY2RkRGXHxYGnxFgB/JiKLJz29gs/dvP+Jz86e5CHxzwEWOfiiUyno7SUTdyZMmHRi3bp0vra/4Bf4M2fO6Ldu3b3CYrUO6uhUGnpYg4K0N4p35pfExhqLPCXRPFo8Yyxo4aL0+FvNJhGdSiTPa3EiIyLPHzy4cy2Ak+4TrUfwmzYV/vhsbW0eAXGMMb1wMVLo28tOmif85JlDuTmZeURkcol43VXW1dVFrV+/NSs6OnbOtBnTo58c972gqKgRvHJFSu+DToYX+zQ0mPDV19/i5LHj5ps3b17YuvWtbL1ey3eruouwXGjyBBiAJACrAHtNDC+/E4c8AtzSDwHgE233Brk7eJ4K4CnOGQAek9efkHYQ6ADwBYDXiOhUF5Vu8PbKQ+BlANsAjAPUv3f6EE2jjqcRAGwhIn4jHrzLwAF+JYA3AEQ8xEENhq543oZb+/KuzXFni+cFSLw+cAWA8Le3leHK5S/R2tqKtrY22GyKVimrFrZGQwgNDYVer8eYsU8ia3UKv1ZeAnLNwbeCT7LO4HkR0lsNDS2vZK95Y3hTU7Nq4TzMCzMYIrF5Ux6ioyN4JUI5gNW8EMoZ/JDGRtP2pct+l2Q2W8TEquDd0el0KN5ZaI6JGXEcwEJee+kMPiTl1fTC5qbmJLVU+SrITrYqo8FgKy3dcRrAAiK60g2+vLw8pKbmeBEDmy82OmRz9qTAOmXyC7VZWYsS+QTbDT49PX3I1Wu3iojYXMYQ3i9dD2KlfFM8Jibm3N49+fNdwCcnJ4eYTJ3C4vvJODj4sLCwc5UV+7mrudht8XHJySHhps4ikC1R+Hjl6XPwBKqtrq7g4B+4mri45JDwcA6eJTAm0gXKo4eVCJ/WVFe6WXxccoh+uKWAYJsnfLzy2O1PIwKfvlddyX28k6vh4MM7CwG2QK2rGr1+GKZOeREzZ8bBamWo+uuH+OTsCdy929YnaTmy9twM8YebqbamuoKvanqCJ7Kpch0/dszjKCzc1GNfwWy2YPny1ahv8P48mRzZ7mzkA/CDx8dza/3jH/Z63czh8JMXLvFo+XJknX9Gvbqa8PDO7Y7JVVXr+Jd+MQtpafG9upOinX/CRyc+6NFGjqw7eIerSSKi/zxYTjpWNWqMXPeXFPMkVa/gb9xoxpKlPDHresiR9QLeg4+3r2qsyWpbx/sCr77+FlIXp0sC7022h6th+OS9mkpu8a6Tq30dDzaPAcP6nOYfoQa+uIt9+w7jyD/4UzWuhxxZPy1efev4viZIXhmQmLQEra09X84hR7YneJzzHkAxW5Ian+DztiTk0DMycnD5ylWvv2E5sq7LSS/gHa5mgRrBcwDOQRD/Xl19Ev88/aFHS3e/C3Jk3QKonut4HrkSWILafPxAmI6cIleeMrjkspx0pAzmqe2VJwMHPM7WVFfOc9mB4mlhvclu8YlqdTWBvAH2tDBpTlUffpeDv9Ft8Xwj5JYA35/3xjrssWFHKir28XX8LU/ghY/vH/zmUbGj3t+7exuvMvjOZbO7uuY4TwsLH98P4Ilgfm7qjMrMzIVL3etqQhYtWl7U1GRS7XKyH3j6rNIQaWgvK7M//Z3B6yddCprq6u5uTl+xdKHZbBnhs0bRsE8C9wuaCtpiYiL4+8wyicjsXjs5q76+ecmanA1PNzc3DROPVvbJtM8GjhI+Fh0dcRvALgD8McxO9/p4/p7IiQBmb9lWOv3q5a9Gt7S0hLW3t5MoWu2Tsb2Bl6JVXi3MX5G7CcAHLkWrXWod5doGAL8BwKvKxvPq4cH6ThrfcPfaygzgEoD9jjf3fWdPISigWKiQQECAlwBNCREBXgmKEnQI8BKgKSEiwCtBUYIOAV4CNCVEBHglKErQIcBLgKaEiACvBEUJOgR4CdCUEBHglaAoQYcALwGaEiICvBIUJegQ4CVAU0JEgFeCogQdArwEaEqI/B9G/wubKQ/89wAAAABJRU5ErkJggg=='); + PRAGMA user_version = 7; + `, cb) + } +] diff --git a/app/bg/dbs/watchlist.js b/app/bg/dbs/watchlist.js new file mode 100644 index 0000000000..81df98644b --- /dev/null +++ b/app/bg/dbs/watchlist.js @@ -0,0 +1,79 @@ +import lock from '../../lib/lock' +import * as db from './profile-data-db' + +// typedefs +// = + +/** + * @typedef {Object} WatchedSite + * @prop {number} profileId + * @prop {string} url + * @prop {string} description + * @prop {boolean} seedWhenResolved + * @prop {boolean} resolved + * @prop {number} updatedAt + * @prop {number} createdAt + */ + +// exported methods +// = + +/** + * @param {number} profileId + * @param {string} url + * @param {Object} opts + * @param {string} opts.description + * @param {number} opts.seedWhenResolved + * @return {Promise} + */ +export async function addSite (profileId, url, opts) { + var release = await lock('watchlist-db') + try { + // get date for timestamp in seconds floored + var ts = (Date.now() / 1000 | 0) + + // check if site already being watched + var site = await db.get('SELECT rowid, * from watchlist WHERE profileId = ? AND url = ?', [profileId, url]) + if (!site) { + // add site to watch list + await db.run('INSERT INTO watchlist (profileId, url, description, seedWhenResolved, createdAt) VALUES (?, ?, ?, ?, ?);', [profileId, url, opts.description, opts.seedWhenResolved, ts]) + } + } finally { + release() + } + return db.get('SELECT rowid, * from watchlist WHERE profileId = ? AND url = ?', [profileId, url]) +} + +/** + * @param {number} profileId + * @returns {Promise>} + */ +export async function getSites (profileId) { + return db.all(`SELECT * FROM watchlist WHERE profileId = ?`, [profileId]) +} + +/** + * @param {number} profileId + * @param {WatchedSite} site + * @returns {Promise} + */ +export async function updateWatchlist (profileId, site) { + var updatedAt = (Date.now() / 1000 | 0) + + var release = await lock('watchlist-db') + try { + await db.run(`UPDATE watchlist SET seedWhenResolved = ?, resolved = ?, updatedAt = ? + WHERE profileId = ? AND url = ?`, [site.seedWhenResolved, site.resolved, updatedAt, profileId, site.url]) + } finally { + release() + } +} + +/** + * @param {number} profileId + * @param {string} url + * @return {Promise} + */ +export async function removeSite (profileId, url) { + return db.run(`DELETE FROM watchlist WHERE profileId = ? AND url = ?`, [profileId, url]) +} diff --git a/app/bg/filesystem/bookmarks.js b/app/bg/filesystem/bookmarks.js new file mode 100644 index 0000000000..4fe3545ea7 --- /dev/null +++ b/app/bg/filesystem/bookmarks.js @@ -0,0 +1,115 @@ +import { joinPath, slugify } from '../../lib/strings.js' +import { query } from './query.js' +import * as filesystem from './index' +import { URL } from 'url' +import * as profileDb from '../dbs/profile-data-db' + +// exported +// = + +/** + * @returns {Promise} + */ +export async function list () { + var files = (await query(filesystem.get(), { + path: '/bookmarks/*.goto' + })) + return files.map(massageBookmark) +} + +/** + * @param {string} href + * @returns {Promise} + */ +export async function get (href) { + href = normalizeUrl(href) + var bookmarks = await list() + return bookmarks.find(b => isSameUrl(b.href, href)) +} + +/** + * @param {Object} bookmark + * @param {string} bookmark.href + * @param {string} bookmark.title + * @param {Boolean} bookmark.pinned + * @returns {Promise} + */ +export async function add ({href, title, pinned}) { + var slug + href = normalizeUrl(href) + + try { + var hrefp = new URL(href) + if (hrefp.pathname === '/' && !hrefp.search && !hrefp.hash) { + // at the root path - use the hostname for the filename + slug = slugify(hrefp.hostname) + } else if (typeof title === 'string' && !!title.trim()) { + // use the title if available on subpages + slug = slugify(title.trim()) + } else { + // use parts of the url + slug = slugify(hrefp.hostname + hrefp.pathname + hrefp.search + hrefp.hash) + } + } catch (e) { + // weird URL, just use slugified version of it + slug = slugify(href) + } + slug = slug.toLowerCase() + + await remove(href) // in case this is an edit + + var filename = await filesystem.getAvailableName('/bookmarks', slug, 'goto') // avoid collisions + var path = joinPath('/bookmarks', filename) + await filesystem.get().pda.writeFile(path, '', {metadata: {href, title, pinned: pinned ? '1' : undefined}}) + return path +} + +/** + * @param {string} href + * @returns {Promise} + */ +export async function remove (href) { + href = normalizeUrl(href) + var file = (await query(filesystem.get(), { + path: '/bookmarks/*.goto', + metadata: {href} + }))[0] + if (!file) return + await filesystem.get().pda.unlink(file.path) +} + +export async function migrateBookmarksFromSqlite () { + var bookmarks = await profileDb.all(`SELECT * FROM bookmarks`) + for (let bookmark of bookmarks) { + await add({ + href: bookmark.url, + title: bookmark.title, + pinned: false // pinned: bookmark.pinned - DONT migrate this because 0.8 pinned bookmarks are often dat:// + }) + } +} + +// internal +// = + +function massageBookmark (file) { + return { + href: normalizeUrl(file.stat.metadata.href), + title: file.stat.metadata.title || file.stat.metadata.href, + pinned: !!file.stat.metadata.pinned + } +} + +function normalizeUrl (url) { + try { + var urlp = new URL(url) + return (urlp.protocol + '//' + urlp.hostname + (urlp.port ? `:${urlp.port}` : '') + urlp.pathname).replace(/([/]$)/g, '') + } catch (e) {} + return url +} + +var indexFileRe = /\/(index\.(htm|html|md))?$/i +function isSameUrl (a, b) { + if (a === b) return true + return a.replace(indexFileRe, '') === b.replace(indexFileRe, '') +} \ No newline at end of file diff --git a/app/bg/filesystem/index.js b/app/bg/filesystem/index.js new file mode 100644 index 0000000000..0575f57c43 --- /dev/null +++ b/app/bg/filesystem/index.js @@ -0,0 +1,369 @@ +import { BrowserWindow } from 'electron' +import { join as joinPath } from 'path' +import * as logLib from '../logger' +const logger = logLib.get().child({category: 'hyper', subcategory: 'filesystem'}) +import hyper from '../hyper/index' +import * as db from '../dbs/profile-data-db' +import * as archivesDb from '../dbs/archives' +import * as bookmarks from './bookmarks' +import * as trash from './trash' +import * as modals from '../ui/subwindows/modals' +import { PATHS } from '../../lib/const' +import lock from '../../lib/lock' + +// typedefs +// = + +/** + * @typedef {import('../hyper/daemon').DaemonHyperdrive} DaemonHyperdrive + * @typedef {import('../dbs/archives').LibraryArchiveMeta} LibraryArchiveMeta + * + * @typedef {Object} DriveConfig + * @property {string} key + * @property {Object} [forkOf] + * @property {string} [forkOf.key] + * @property {string} [forkOf.label] + * + * @typedef {Object} DriveIdent + * @property {boolean} internal + * @property {boolean} system + * @property {boolean} profile + * @property {boolean?} contact + */ + +// globals +// = + +var browsingProfile +var rootDrive +var profileDriveUrl +var drives = [] + +// exported api +// = + +/** + * @returns {DaemonHyperdrive} + */ +export function get () { + return rootDrive +} + +/** + * @param {string} url + * @returns {boolean} + */ +export function isRootUrl (url) { + return url === browsingProfile.url || url === 'hyper://system/' +} + +/** + * @returns {Promise} + */ +export async function setup () { + trash.setup() + + // create the root drive as needed + var isInitialCreation = false + browsingProfile = await db.get(`SELECT * FROM profiles WHERE id = 0`) + if (!browsingProfile.url) { + let drive = await hyper.drives.createNewRootDrive() + logger.info('Root drive created', {url: drive.url}) + await db.run(`UPDATE profiles SET url = ? WHERE id = 0`, [drive.url]) + browsingProfile.url = drive.url + isInitialCreation = true + } + if (!browsingProfile.url.endsWith('/')) browsingProfile.url += '/' + + // load root drive + hyper.dns.setLocal('system', browsingProfile.url) + rootDrive = await hyper.drives.getOrLoadDrive(browsingProfile.url, {persistSession: true}) + + // enforce root files structure + logger.info('Loading root drive', {url: browsingProfile.url}) + try { + // ensure common dirs + await ensureDir(PATHS.BOOKMARKS) + + // default bookmarks + if (isInitialCreation) { + await bookmarks.add({href: 'hyper://a8e9bd0f4df60ed5246a1b1f53d51a1feaeb1315266f769ac218436f12fda830/', title: 'Blahbity Blog', pinned: true}) + await bookmarks.add({href: 'https://docs.beakerbrowser.com/', title: 'Beaker Documentation', pinned: true}) + await bookmarks.add({href: 'https://beaker.dev/', title: 'Beaker Developer Portal', pinned: true}) + await bookmarks.add({href: 'https://github.com/beakerbrowser/beaker/discussions', title: 'Beaker Discussions', pinned: true}) + } + + // ensure all user mounts are set + // TODO + // for (let user of userList) { + // } + } catch (e) { + console.error('Error while constructing the root drive', e.toString()) + logger.error('Error while constructing the root drive', {error: e.toString()}) + } + + // load drive config + let profileObj = await getProfile() + let hostKeys = [] + if (profileObj) { + hostKeys.push(profileObj.key) + profileDriveUrl = `hyper://${profileObj.key}/` + } + try { + drives = JSON.parse(await rootDrive.pda.readFile('/drives.json')).drives + hostKeys = hostKeys.concat(drives.map(drive => drive.key)) + } catch (e) { + if (e.name !== 'NotFoundError') { + logger.info('Error while reading the drive configuration at /drives.json', {error: e.toString()}) + } + } + hyper.drives.ensureHosting(hostKeys) +} + +/** + * @param {string} url + * @param {boolean} [includeContacts] + * @returns {DriveIdent | Promise} + */ +export function getDriveIdent (url, includeContacts = false) { + var system = isRootUrl(url) + var profile = url === profileDriveUrl + if (includeContacts) { + return getAddressBook().then(addressBook => { + var key = /[0-9a-f]{64}/.exec(url)[0] + var contact = !!addressBook.contacts.find(c => c.key === key) + return {system, profile, internal: system || profile, contact} + }) + } + return {system, profile, internal: system || profile, contact: undefined} +} + +/** + * @param {Object} [opts] + * @param {boolean} [opts.includeSystem] + * @returns {Array} + */ +export function listDrives ({includeSystem} = {includeSystem: false}) { + var d = drives.slice() + if (includeSystem) { + d.unshift({key: rootDrive.url.slice('hyper://'.length)}) + } + return d +} + +/** + * @returns {Promise>} + */ +export async function listDriveMetas () { + return Promise.all(drives.map(d => archivesDb.getMeta(d.key))) +} + +/** + * @param {string} key + * @returns {DriveConfig} + */ +export function getDriveConfig (key) { + return listDrives().find(d => d.key === key) +} + +/** + * @param {string} url + * @param {Object} [opts] + * @param {Object} [opts.forkOf] + * @returns {Promise} + */ +export async function configDrive (url, {forkOf} = {forkOf: undefined}) { + var release = await lock('filesystem:drives') + try { + var key = await hyper.drives.fromURLToKey(url, true) + var driveCfg = drives.find(d => d.key === key) + if (!driveCfg) { + let drive = await hyper.drives.getOrLoadDrive(url) + let manifest = await drive.pda.readManifest().catch(_ => {}) + + driveCfg = /** @type DriveConfig */({key}) + if (forkOf && typeof forkOf === 'object') { + driveCfg.forkOf = forkOf + } + + if (!drive.writable) { + // announce the drive + drive.session.drive.configureNetwork({ + announce: true, + lookup: true + }) + } + + // for forks, we need to ensure: + // 1. the drives.json forkOf.key is the same as index.json forkOf value + // 2. there's a local forkOf.label + // 3. the parent is saved + if (manifest.forkOf && typeof manifest.forkOf === 'string') { + if (!driveCfg.forkOf) driveCfg.forkOf = {key: undefined, label: undefined} + driveCfg.forkOf.key = await hyper.drives.fromURLToKey(manifest.forkOf, true) + if (!driveCfg.forkOf.label) { + let message = 'Choose a label to save this fork under (e.g. "dev" or "bobs-changes")' + let promptRes = await modals.create(BrowserWindow.getFocusedWindow().webContents, 'prompt', {message}).catch(e => false) + if (!promptRes || !promptRes.value) return + driveCfg.forkOf.label = promptRes.value + } + + let parentDriveCfg = drives.find(d => d.key === driveCfg.forkOf.key) + if (!parentDriveCfg) { + drives.push({key: driveCfg.forkOf.key}) + } + } + + drives.push(driveCfg) + } else { + if (typeof forkOf !== 'undefined') { + if (forkOf && typeof forkOf === 'object') { + driveCfg.forkOf = forkOf + } else { + delete driveCfg.forkOf + } + } + } + await rootDrive.pda.writeFile('/drives.json', JSON.stringify({drives}, null, 2)) + } finally { + release() + } +} + +/** + * @param {string} url + * @returns {Promise} + */ +export async function removeDrive (url) { + var release = await lock('filesystem:drives') + try { + var key = await hyper.drives.fromURLToKey(url, true) + var driveIndex = drives.findIndex(drive => drive.key === key) + if (driveIndex === -1) return + let drive = await hyper.drives.getOrLoadDrive(url) + if (!drive.writable) { + // unannounce the drive + drive.session.drive.configureNetwork({ + announce: false, + lookup: true + }) + } + drives.splice(driveIndex, 1) + await rootDrive.pda.writeFile('/drives.json', JSON.stringify({drives}, null, 2)) + } finally { + release() + } +} + +/** + * @param {string} containingPath + * @param {string} basename + * @param {string} [ext] + * @param {string} [joiningChar] + * @returns {Promise} + */ +export async function getAvailableName (containingPath, basename, ext = undefined, joiningChar = '-') { + for (let i = 1; i < 1e9; i++) { + let name = ((i === 1) ? basename : `${basename}${joiningChar}${i}`) + (ext ? `.${ext}` : '') + let st = await stat(joinPath(containingPath, name)) + if (!st) return name + } + // yikes if this happens + throw new Error('Unable to find an available name for ' + basename) +} + +export async function setupDefaultProfile ({title, description, thumbBase64, thumbExt}) { + var drive = await hyper.drives.createNewDrive({title, description}) + if (thumbBase64) { + await drive.pda.writeFile(`/thumb.${thumbExt || 'png'}`, thumbBase64, 'base64') + } + await drive.pda.writeFile('/index.html', ` + + + + + + +
    +
    + +

    ${title}

    +
    +

    ${description || 'Welcome to my profile!'}

    +
    + + +`) + + await ensureAddressBook(drive.key.toString('hex')) + profileDriveUrl = drive.url +} + +export async function getAddressBook () { + var addressBook + try { addressBook = await rootDrive.pda.readFile('/address-book.json').then(JSON.parse) } + catch (e) { addressBook = {} } + addressBook.profiles = addressBook.profiles && Array.isArray(addressBook.profiles) ? addressBook.profiles : [] + addressBook.contacts = addressBook.contacts && Array.isArray(addressBook.contacts) ? addressBook.contacts : [] + return addressBook +} + +export async function ensureAddressBook (profileKey) { + var addressBook = await getAddressBook() + if (!addressBook.profiles.find(p => p.key === profileKey)) { + addressBook.profiles.push({key: profileKey}) + } + await rootDrive.pda.writeFile('/address-book.json', JSON.stringify(addressBook, null, 2)) +} + +export async function getProfile () { + try { + var addressBook = await rootDrive.pda.readFile('/address-book.json').then(JSON.parse) + return addressBook.profiles ? addressBook.profiles[0] : undefined + } catch (e) { + return undefined + } +} + +// internal methods +// = + +async function stat (path) { + try { return await rootDrive.pda.stat(path) } + catch (e) { return null } +} + +async function ensureDir (path) { + try { + let st = await stat(path) + if (!st) { + logger.info(`Creating directory ${path}`) + await rootDrive.pda.mkdir(path) + } else if (!st.isDirectory()) { + logger.error('Warning! Filesystem expects a folder but an unexpected file exists at this location.', {path}) + } + } catch (e) { + logger.error('Filesystem failed to make directory', {path: '' + path, error: e.toString()}) + } +} \ No newline at end of file diff --git a/app/bg/filesystem/query.js b/app/bg/filesystem/query.js new file mode 100644 index 0000000000..26440a837f --- /dev/null +++ b/app/bg/filesystem/query.js @@ -0,0 +1,453 @@ +import { basename } from 'path' +import * as hyperDns from '../hyper/dns' +import { joinPath } from '../../lib/strings' +import { chunkMapAsync } from '../../lib/functions' +import { HYPERDRIVE_HASH_REGEX } from '../../lib/const' +import * as auditLog from '../dbs/audit-log' + +// typedefs +// = + +/** + * @typedef {import('../dat/daemon').DaemonHyperdrive} DaemonHyperdrive + * + * @typedef {Object} FSQueryOpts + * @prop {string|string[]} path + * @prop {string} [type] + * @prop {string} [mount] + * @prop {Object} [metadata] + * @prop {string} [sort] - 'name', 'ctime', 'mtime' + * @prop {boolean} [reverse] + * @prop {number} [limit] + * @prop {number} [offset] + * + * @typedef {Object} Stat + * @prop {number} mode + * @prop {number} size + * @prop {number} offset + * @prop {number} blocks + * @prop {Date} atime + * @prop {Date} mtime + * @prop {Date} ctime + * @prop {Object} metadata + * @prop {Object} [mount] + * @prop {string} [mount.key] + * @prop {string} linkname + * + * @typedef {Object} FSQueryResult + * @prop {string} type + * @prop {string} path + * @prop {string} url + * @prop {Stat} stat + * @prop {string} drive + * @prop {string} [mount] + */ + +// exported api +// = + +// query({type: 'mount', path: ['/profile', '/profile/follows/*', '/profile/follows/*/follows/*']}) +// => [{type: 'mount', path: '/profile', stat, mount, drive}, {type: 'mount', path: '/profile/friend/bob', stat, mount, drive}, ...] + +// query({type: 'mount', mount: url, path: ['/profile/follows/*', '/profile/follows/*/follows/*']}) +// => [{type: 'mount', path: '/profile/friend/bob', stat, mount, drive}, ...] + +// query({type: 'file', metadata: {href: url}, path: ['/profile/comments', '/profile/follows/*/comments', '/profile/follows/*/follows/*/comments']}) +// => [{type: 'folder', path: '/profile/comments/foo.txt', stat, drive}] + +/** + * @param {DaemonHyperdrive} root + * @param {FSQueryOpts} opts + * @returns {Promise} + */ +export async function query (root, opts) { + // validate opts + if (!opts || !opts.path) throw new Error('The `path` parameter is required') + if (!(typeof opts.path === 'string' || (Array.isArray(opts.path) && opts.path.every(v => typeof v === 'string')))) { + throw new Error('The `path` parameter must be a string or array of strings') + } + if (opts.type && typeof opts.type !== 'string') { + throw new Error('The `type` parameter must be a string') + } + if (opts.mount && typeof opts.mount !== 'string') { + throw new Error('The `mount` parameter must be a string') + } + if (opts.metadata && typeof opts.metadata !== 'object') { + throw new Error('The `metadata` parameter must be an object') + } + + // massage opts + if (opts.mount) { + opts.mount = await hyperDns.resolveName(opts.mount) + opts.mount = HYPERDRIVE_HASH_REGEX.exec(opts.mount)[0] + } + + var keyToUrlCache = {} + async function keyToUrl (key) { + if (keyToUrlCache[key]) return keyToUrlCache[key] + var domain = await hyperDns.reverseResolve(key) + if (!domain) domain = key + keyToUrlCache[key] = `hyper://${domain}/` + return keyToUrlCache[key] + } + + // iterate all matching paths and match against the query + var candidates = await expandPaths(root, opts.path) + var results = [] + await chunkMapAsync(candidates, 100, async (item) => { + let path = item.name + let stat = item.stat + let localDriveKey = item.localDriveKey + let innerPath = item.innerPath + + var type = 'file' + if (stat.mount && stat.mount.key) type = 'mount' + else if (stat.isDirectory()) type = 'directory' + + if (opts.type && type !== opts.type) return + if (opts.mount && (type !== 'mount' || stat.mount.key.toString('hex') !== opts.mount)) return + if (opts.metadata) { + let metaMatch = true + for (let k in opts.metadata) { + if (stat.metadata[k] !== opts.metadata[k]) { + metaMatch = false + break + } + } + if (!metaMatch) return + } + + var drive = await keyToUrl(localDriveKey) + results.push({ + type, + path, + url: joinPath(drive, innerPath), + stat, + drive, + mount: type === 'mount' ? await keyToUrl(stat.mount.key.toString('hex')) : undefined + }) + }) + + if (opts.sort === 'name') { + results.sort((a, b) => (opts.reverse) ? basename(b.path).toLowerCase().localeCompare(basename(a.path).toLowerCase()) : basename(a.path).toLowerCase().localeCompare(basename(b.path).toLowerCase())) + } else if (opts.sort === 'mtime') { + results.sort((a, b) => (opts.reverse) ? b.stat.mtime - a.stat.mtime : a.stat.mtime - b.stat.mtime) + } else if (opts.sort === 'ctime') { + results.sort((a, b) => (opts.reverse) ? b.stat.ctime - a.stat.ctime : a.stat.ctime - b.stat.ctime) + } + + if (opts.offset && opts.limit) results = results.slice(opts.offset, opts.offset + opts.limit) + else if (opts.offset) results = results.slice(opts.offset) + else if (opts.limit) results = results.slice(0, opts.limit) + + return results +} + +// internal +// = + +async function expandPaths (root, patterns) { + var matches = [] + patterns = Array.isArray(patterns) ? patterns : [patterns] + await Promise.all(patterns.map(async (pattern) => { + // parse the pattern into a set of ops + let acc = [] + let ops = [] + for (let part of pattern.split('/')) { + if (part.includes('*')) { + ops.push(['push', acc.filter(Boolean).join('/')]) + ops.push(['match', part]) + acc = [] + } else { + acc.push(part) + } + } + if (acc.length) ops.push(['push', acc.join('/')]) + + // run the ops to assemble a list of matching paths + var workingPaths = [{name: '/', innerPath: '/', localDriveKey: root.key.toString('hex')}] + for (let i = 0; i < ops.length; i++) { + let op = ops[i] + let isLastOp = i === ops.length - 1 + let newWorkingPaths = [] + if (op[0] === 'push') { + // add the given segment to all working paths + if (isLastOp) { + newWorkingPaths = await Promise.all(workingPaths.map(async (workingPath) => { + var bname = basename(op[1]) + var folderpath = op[1].slice(0, bname.length * -1) + let readdirpath = joinPath(workingPath.name, folderpath) + let readdiropts = {includeStats: true} + let items = await auditLog.record( + '-query', + 'readdir', + Object.assign({url: root.url, path: readdirpath}, readdiropts), + undefined, + () => root.pda.readdir(readdirpath, readdiropts).catch(err => ([])) + ) + let item = items.find(item => item.name === bname) + if (!item) return undefined + item.localDriveKey = item.mount ? item.mount.key.toString('hex') : workingPath.localDriveKey + item.name = joinPath(workingPath.name, folderpath, item.name) + return item + })) + newWorkingPaths = newWorkingPaths.filter(Boolean) + } else { + newWorkingPaths = workingPaths.map(v => ({ + name: joinPath(v.name, op[1]), + innerPath: v.innerPath, + localDriveKey: v.localDriveKey, + stat: v.stat, + mount: v.mount + })) + } + } else if (op[0] === 'match') { + // compile a glob-matching regex from the segment + var re = new RegExp(`^${op[1].replace(/\*/g, '[^/]*')}$`, 'i') + + // read the files at each working path + for (let workingPath of workingPaths) { + let items = await auditLog.record( + '-query', + 'readdir', + {url: root.url, path: workingPath.name, includeStats: true}, + undefined, + () => root.pda.readdir(workingPath.name, {includeStats: true}).catch(e => []) + ) + for (let item of items) { + // add matching names to the working path + if (re.test(item.name)) { + item.localDriveKey = item.mount ? item.mount.key.toString('hex') : workingPath.localDriveKey + item.name = joinPath(workingPath.name, item.name) + newWorkingPaths.push(item) + } + } + } + } + workingPaths = newWorkingPaths + } + + // emit the results + for (let result of workingPaths) { + matches.push(result) + } + })) + return matches +} + +// TODO!! +// put these tests somewhere!! +// const _get = require('lodash.get') +// const _isEqual = require('lodash.isequal') +// const assert = require('assert') +// const toArray = require('async-iterator-to-array') + +// const RootMockPaths = { +// foo: { +// bar: { +// baz: { +// biz: {}, +// biz2: {}, +// biz3: {} +// }, +// baz2: { +// biz: {}, +// biz2: {}, +// biz3: {} +// }, +// baz3: { +// biz: {}, +// biz2: {}, +// biz3: {} +// } +// }, +// bar2: { +// baz: { +// biz: {}, +// biz2: {}, +// biz3: {} +// }, +// baz2: { +// biz: {}, +// biz2: {}, +// biz3: {} +// }, +// baz3: { +// biz: {}, +// biz2: {}, +// biz3: {} +// } +// }, +// bar3: { +// baz: { +// biz: {}, +// biz2: {}, +// biz3: {} +// }, +// baz2: { +// biz: {}, +// biz2: {}, +// biz3: {} +// }, +// baz3: { +// biz: {}, +// biz2: {}, +// biz3: {} +// } +// } +// }, +// foo2: { +// bar: { +// baz: { +// biz: {}, +// biz2: {}, +// biz3: {} +// }, +// baz2: { +// biz: {}, +// biz2: {}, +// biz3: {} +// }, +// baz3: { +// biz: {}, +// biz2: {}, +// biz3: {} +// } +// }, +// bar2: { +// baz: { +// biz: {}, +// biz2: {}, +// biz3: {} +// }, +// baz2: { +// biz: {}, +// biz2: {}, +// biz3: {} +// }, +// baz3: { +// biz: {}, +// biz2: {}, +// biz3: {} +// } +// }, +// bar3: { +// baz: { +// biz: {}, +// biz2: {}, +// biz3: {} +// }, +// baz2: { +// biz: {}, +// biz2: {}, +// biz3: {} +// }, +// baz3: { +// biz: {}, +// biz2: {}, +// biz3: {} +// } +// } +// }, +// foo3: { +// bar: { +// baz: { +// biz: {}, +// biz2: {}, +// biz3: {} +// }, +// baz2: { +// biz: {}, +// biz2: {}, +// biz3: {} +// }, +// baz3: { +// biz: {}, +// biz2: {}, +// biz3: {} +// } +// }, +// bar2: { +// baz: { +// biz: {}, +// biz2: {}, +// biz3: {} +// }, +// baz2: { +// biz: {}, +// biz2: {}, +// biz3: {} +// }, +// baz3: { +// biz: {}, +// biz2: {}, +// biz3: {} +// } +// }, +// bar3: { +// baz: { +// biz: {}, +// biz2: {}, +// biz3: {} +// }, +// baz2: { +// biz: {}, +// biz2: {}, +// biz3: {} +// }, +// baz3: { +// biz: {}, +// biz2: {}, +// biz3: {} +// } +// } +// } +// } + +// const RootMock = { +// async readdir (path) { +// path = path.replace(/\./g, '') +// path = path.split('/').filter(Boolean).join('.') +// if (!path) return Object.keys(RootMockPaths) +// return Object.keys(_get(RootMockPaths, path) || {}) +// } +// } + +// async function test () { +// assert(_isEqual(await toArray(expandPaths(RootMock, ['/'])), ['/'])) +// assert(_isEqual(await toArray(expandPaths(RootMock, ['/foo'])), ['/foo'])) +// assert(_isEqual(await toArray(expandPaths(RootMock, ['/*'])), ['/foo', '/foo2', '/foo3'])) +// assert(_isEqual(await toArray(expandPaths(RootMock, ['/*oo'])), ['/foo'])) +// assert(_isEqual(await toArray(expandPaths(RootMock, ['/*oo*'])), ['/foo', '/foo2', '/foo3'])) +// assert(_isEqual(await toArray(expandPaths(RootMock, ['/*/bar'])), ['/foo/bar', '/foo2/bar', '/foo3/bar'])) +// assert(_isEqual(await toArray(expandPaths(RootMock, ['/f*/bar'])), ['/foo/bar', '/foo2/bar', '/foo3/bar'])) +// assert(_isEqual(await toArray(expandPaths(RootMock, ['/foo/*'])), ['/foo/bar', '/foo/bar2', '/foo/bar3'])) +// assert(_isEqual(await toArray(expandPaths(RootMock, ['/*oo/*'])), ['/foo/bar', '/foo/bar2', '/foo/bar3'])) +// assert(_isEqual(await toArray(expandPaths(RootMock, ['/foo/*/baz'])), ['/foo/bar/baz', '/foo/bar2/baz', '/foo/bar3/baz'])) +// assert(_isEqual(await toArray(expandPaths(RootMock, ['/foo/*/baz/*'])), [ +// '/foo/bar/baz/biz', +// '/foo/bar/baz/biz2', +// '/foo/bar/baz/biz3', +// '/foo/bar2/baz/biz', +// '/foo/bar2/baz/biz2', +// '/foo/bar2/baz/biz3', +// '/foo/bar3/baz/biz', +// '/foo/bar3/baz/biz2', +// '/foo/bar3/baz/biz3' +// ])) +// assert(_isEqual(await toArray(expandPaths(RootMock, ['/foo/*/*/biz'])), [ +// '/foo/bar/baz/biz', +// '/foo/bar/baz2/biz', +// '/foo/bar/baz3/biz', +// '/foo/bar2/baz/biz', +// '/foo/bar2/baz2/biz', +// '/foo/bar2/baz3/biz', +// '/foo/bar3/baz/biz', +// '/foo/bar3/baz2/biz', +// '/foo/bar3/baz3/biz' +// ])) +// assert(_isEqual(await toArray(expandPaths(RootMock, ['/', '/foo', '/*/bar'])), ['/', '/foo', '/foo/bar', '/foo2/bar', '/foo3/bar'])) +// console.log('done') +// } + +// test() diff --git a/app/bg/filesystem/site-sessions.js b/app/bg/filesystem/site-sessions.js new file mode 100644 index 0000000000..ed72388793 --- /dev/null +++ b/app/bg/filesystem/site-sessions.js @@ -0,0 +1,90 @@ +import * as db from '../dbs/profile-data-db' +import knex from '../lib/knex' +import lock from '../../lib/lock' + +// typedefs +// = + +/** + * @typedef {Object} UserSiteSession + * @prop {number} id + * @prop {string} siteOrigin + * @prop {string} userUrl + * @prop {Object} permissions + * @prop {Date} createdAt + */ + +// globals +// = + +var sessions = {} // cache of active sessions + +// exported api +// = + +/** + * @param {string} siteOrigin + * @param {string} userUrl + * @param {Object} permissions + * @returns {Promise} + */ +export async function create (siteOrigin, userUrl, permissions) { + var release = await lock('user-site-sessions') + try { + delete sessions[siteOrigin] + await db.run(knex('user_site_sessions').where({siteOrigin}).delete()) + await db.run(knex('user_site_sessions').insert({ + siteOrigin, + userUrl, + permissionsJson: JSON.stringify(permissions || {}), + createdAt: Date.now() + })) + } finally { + release() + } + return get(siteOrigin) +} + +/** + * @param {string} siteOrigin + * @returns {Promise} + */ +export async function get (siteOrigin) { + var sess = sessions[siteOrigin] + if (sess) return sess + var record = massageRecord(await db.get(knex('user_site_sessions').where({siteOrigin}))) + if (record) sessions[siteOrigin] = record + return record +} + +/** + * @param {string} siteOrigin + * @returns {Promise} + */ +export async function destroy (siteOrigin) { + var release = await lock('user-site-sessions') + try { + delete sessions[siteOrigin] + await db.run(knex('user_site_sessions').where({siteOrigin}).delete()) + } finally { + release() + } +} + +// internal methods +// = + +/** + * @param {Object} record + * @returns {UserSiteSession} + */ +function massageRecord (record) { + if (!record) return null + return { + id: record.id, + siteOrigin: record.siteOrigin, + userUrl: record.userUrl, + permissions: JSON.parse(record.permissionsJson), + createdAt: new Date(record.createdAt) + } +} \ No newline at end of file diff --git a/app/bg/filesystem/trash.js b/app/bg/filesystem/trash.js new file mode 100644 index 0000000000..492aaad953 --- /dev/null +++ b/app/bg/filesystem/trash.js @@ -0,0 +1,124 @@ +import ms from 'ms' +import { join as joinPath } from 'path' +import * as filesystem from './index' +import { PATHS, TRASH_FIRST_COLLECT_WAIT, TRASH_REGULAR_COLLECT_WAIT, TRASH_EXPIRATION_AGE } from '../../lib/const' +import * as logLib from '../logger' +const logger = logLib.child({category: 'hyper', subcategory: 'trash-collector'}) + +// typedefs +// = + +/** + * @typedef {Object} CollectResult + * @prop {number} totalBytes + * @prop {number} totalItems + * + * @typedef {Object} TrashItem + * @prop {string} name + * @prop {Object} stat + */ + +// globals +// = + +var nextGCTimeout + +// exported API +// = + +export function setup () { + schedule(TRASH_FIRST_COLLECT_WAIT) +} + +/** + * @param {Object} [query] + * @param {boolean} [query.mounts] + * @param {number} [query.olderThan] + * @returns {Promise} + */ +export async function query (query = {}) { + return [] // TODO + // var items = /** @type TrashItem[] */([]) + // var names = await filesystem.get().pda.readdir(PATHS.TRASH) + // for (let name of names) { + // let st = await filesystem.get().pda.stat(joinPath(PATHS.TRASH, name)) + // if (query.mounts && !st.mount) { + // continue + // } + // if (query.olderThan) { + // if (Date.now() - st.mtime < query.olderThan) { + // continue + // } + // } + // items.push({name, stat: st}) + // } + // return items +} + +/** + * @param {Object} [opts] + * @param {number} [opts.olderThan] + * @returns {Promise} + */ +export async function collect ({olderThan} = {}) { + return // TODO + // logger.silly('Running GC') + // olderThan = typeof olderThan === 'number' ? olderThan : TRASH_EXPIRATION_AGE + + // // clear any scheduled GC + // if (nextGCTimeout) { + // clearTimeout(nextGCTimeout) + // nextGCTimeout = null + // } + + // // run the GC + // var totalBytes = 0 + // var startTime = Date.now() + + // // clear items in trash + // var trashItems = await query({olderThan}) + // if (trashItems.length) { + // logger.info(`Deleting ${trashItems.length} items in trash`) + // logger.silly('Items:', {urls: trashItems.map(a => a.name)}) + // } + // for (let item of trashItems) { + // let path = joinPath(PATHS.TRASH, item.name) + // if (item.stat.mount) { + // await filesystem.get().pda.unmount(path) + // } else if (item.stat.isDirectory()) { + // await filesystem.get().pda.rmdir(path, {recursive: true}) + // } else { + // await filesystem.get().pda.unlink(path) + // } + // totalBytes += item.stat.size + // } + + // // clear cached dats + // // TODO + // // fetch all drive metas with lastaccesstime older than DAT_CACHE_TIME + // // then delete the drive + // { + // // await datLibrary.removeFromTrash(trashItems[i].key) + // // totalBytes += await archivesDb.deleteArchive(trashItems[i].key) + // } + + // logger.silly(`GC completed in ${Date.now() - startTime} ms`) + + // // schedule the next GC + // schedule(TRASH_REGULAR_COLLECT_WAIT) + // logger.silly(`Scheduling next run to happen in ${ms(TRASH_REGULAR_COLLECT_WAIT)}`) + + // // return stats + // return {totalBytes, totalItems: trashItems.length} +} + +// helpers +// = + +/** + * @param {number} time + */ +function schedule (time) { + nextGCTimeout = setTimeout(collect, time) + nextGCTimeout.unref() +} diff --git a/app/bg/hyper/assets.js b/app/bg/hyper/assets.js new file mode 100644 index 0000000000..f73f07317e --- /dev/null +++ b/app/bg/hyper/assets.js @@ -0,0 +1,116 @@ +import Events from 'events' +import ICO from 'icojs' +import mime from 'mime' +import * as sitedata from '../dbs/sitedata' + +// constants +// = + +const ASSET_PATH_REGEX = /^\/?(favicon|thumb|cover).(jpg|jpeg|png|ico)$/i +const IDEAL_FAVICON_SIZE = 64 + +// typedefs +// = + +/** + * @typedef {import('./daemon').DaemonHyperdrive} DaemonHyperdrive + */ + +// globals +// = + +var events = new Events() + +// exported api +// = + +export const on = events.on.bind(events) + +export const addListener = events.addListener.bind(events) +export const removeListener = events.removeListener.bind(events) + +/** + * @description + * Crawl the given site for assets. + * + * @param {DaemonHyperdrive} drive - site to crawl. + * @param {string[]?} filenames - which files to check. + * @returns {Promise} + */ +export async function update (drive, filenames = null) { + // list target assets + if (!filenames) { + filenames = await drive.pda.readdir('/') + } + filenames = filenames.filter(v => ASSET_PATH_REGEX.test(v)) + + // read and cache each asset + for (let filename of filenames) { + try { + let assetType = extractAssetType(filename) + var dataUrl = await readAsset(drive, filename) + await sitedata.set(drive.url, assetType, dataUrl) + events.emit(`update:${assetType}:${drive.url}`) + } catch (e) { + console.log('Failed to update asset', filename, e) + } + } +} + +/** + * @description + * Check the drive history for changes to an asset + * + * @param {DaemonHyperdrive} drive + * @param {Number} startVersion + * @returns {Promise} + */ +export async function hasUpdates (drive, startVersion = 0) { + var changes = await drive.pda.diff(startVersion, '/') + for (let change of changes) { + if (ASSET_PATH_REGEX.test(change.name)) { + return true + } + } + return false +} + +// internal +// = + +/** + * Extract the asset type from the pathname + * @param {string} pathname + * @returns string + */ +function extractAssetType (pathname) { + if (/cover/.test(pathname)) return 'cover' + if (/thumb/.test(pathname)) return 'thumb' + return 'favicon' +} + +/** + * Reads the asset file as a dataurl + * - Converts any .ico to .png + * @param {DaemonHyperdrive} drive + * @param {string} pathname + * @returns string The asset as a data URL + */ +async function readAsset (drive, pathname) { + if (pathname.endsWith('.ico')) { + let data = await drive.pda.readFile(pathname, 'binary') + // select the best-fitting size + let images = await ICO.parse(data, 'image/png') + let image = images[0] + for (let i = 1; i < images.length; i++) { + if (Math.abs(images[i].width - IDEAL_FAVICON_SIZE) < Math.abs(image.width - IDEAL_FAVICON_SIZE)) { + image = images[i] + } + } + let buf = Buffer.from(image.buffer) + return `data:image/png;base64,${buf.toString('base64')}` + } else { + let data = await drive.pda.readFile(pathname, 'base64') + return `data:${mime.lookup(pathname)};base64,${data}` + } +} \ No newline at end of file diff --git a/app/bg/hyper/capabilities.js b/app/bg/hyper/capabilities.js new file mode 100644 index 0000000000..8abef7fe49 --- /dev/null +++ b/app/bg/hyper/capabilities.js @@ -0,0 +1,111 @@ +import * as base32 from 'base32.js' +import * as crypto from 'crypto' +import { PermissionsError } from 'beaker-error-constants' +import { parseDriveUrl } from '../../lib/urls' + +// typedefs +// = + +/** + * @typedef {Object} CapabilityMapping + * @prop {String} owningOrigin + * @prop {String} token + * @prop {Object} target + * @prop {String} target.key + * @prop {String} target.version + */ + +// globals +// = + +/** @type CapabilityMapping[] */ +var capabilities = [] + +// exported api +// = + +/** + * @param {string} capUrl + * @returns {CapabilityMapping} + */ +export function lookupCap (capUrl) { + var token = extractToken(capUrl) + if (!token) throw new Error('Invalid capability URL') + return capabilities.find(c => c.token === token) +} + +/** + * @param {String} origin + * @param {String} target + * @returns {String} + */ +export function createCap (origin, target) { + var token = generateToken() + capabilities.push({ + owningOrigin: origin, + token, + target: parseTarget(target) + }) + return `hyper://${token}.cap/` +} + +/** + * @param {String} origin + * @param {String} capUrl + * @param {String} target + * @returns {Void} + */ +export function modifyCap (origin, capUrl, target) { + var token = extractToken(capUrl) + if (!token) throw new Error('Invalid capability URL') + var cap = capabilities.find(c => c.token === token) + if (!cap) throw new Error('Capability does not exist') + + if (cap.owningOrigin !== origin) { + throw new PermissionsError('Cannot modify unowned capability') + } + + cap.target = parseTarget(target) +} + +/** + * @param {String} origin + * @param {String} capUrl + * @returns {Void} + */ +export function deleteCap (origin, capUrl) { + var token = extractToken(capUrl) + if (!token) throw new Error('Invalid capability URL') + var capIndex = capabilities.findIndex(c => c.token === token) + if (capIndex === -1) throw new Error('Capability does not exist') + + if (capabilities[capIndex].owningOrigin !== origin) { + throw new PermissionsError('Cannot modify unowned capability') + } + + capabilities.splice(capIndex, 1) +} + +// internal methods +// = + +function generateToken () { + var buf = crypto.randomBytes(8) + var encoder = new base32.Encoder({type: 'rfc4648', lc: true}) + return encoder.write(buf).finalize() +} + +function extractToken (capUrl) { + var matches = /^(hyper:\/\/)?([a-z0-9]+)\.cap\/?/.exec(capUrl) + return matches ? matches[2] : undefined +} + +function parseTarget (target) { + try { + var urlp = parseDriveUrl(target) + if (urlp.protocol !== 'hyper:') throw new Error() + return {key: urlp.hostname, version: urlp.version} + } catch (e) { + throw new Error('Invalid target hyper:// URL') + } +} \ No newline at end of file diff --git a/app/bg/hyper/daemon.js b/app/bg/hyper/daemon.js new file mode 100644 index 0000000000..d4d27e6417 --- /dev/null +++ b/app/bg/hyper/daemon.js @@ -0,0 +1,355 @@ +import { app } from 'electron' +import HyperdriveDaemon from 'hyperdrive-daemon' +import * as HyperdriveDaemonManager from 'hyperdrive-daemon/manager' +import { createMetadata } from 'hyperdrive-daemon/lib/metadata' +import constants from 'hyperdrive-daemon-client/lib/constants' +import { HyperdriveClient } from 'hyperdrive-daemon-client' +import datEncoding from 'dat-encoding' +import * as pda from 'pauls-dat-api2' +import pm2 from 'pm2' +import EventEmitter from 'events' +import { getEnvVar } from '../lib/env' +import * as logLib from '../logger' +const baseLogger = logLib.get() +const logger = baseLogger.child({category: 'hyper', subcategory: 'daemon'}) + +const SETUP_RETRIES = 100 +const CHECK_DAEMON_INTERVAL = 5e3 +const GARBAGE_COLLECT_SESSIONS_INTERVAL = 30e3 +const MAX_SESSION_AGE = 300e3 // 5min + +// typedefs +// = + +/** +* @typedef {Object} DaemonHyperdrive +* @prop {number} sessionId +* @prop {Buffer} key +* @prop {Buffer} discoveryKey +* @prop {string} url +* @prop {string} domain +* @prop {boolean} writable +* @prop {Boolean} persistSession +* @prop {Object} session +* @prop {Object} session.drive +* @prop {function(): Promise} session.close +* @prop {function(Object): Promise} session.configureNetwork +* @prop {function(): Promise} getInfo +* @prop {DaemonHyperdrivePDA} pda +* +* @typedef {Object} DaemonHyperdrivePDA +* @prop {Number} lastCallTime +* @prop {Number} numActiveStreams +* @prop {function(string): Promise} stat +* @prop {function(string, Object=): Promise} readFile +* @prop {function(string, Object=): Promise>} readdir +* @prop {function(string): Promise} readSize +* @prop {function(number, string?): Promise>} diff +* @prop {function(string, any, Object=): Promise} writeFile +* @prop {function(string): Promise} mkdir +* @prop {function(string, string): Promise} copy +* @prop {function(string, string): Promise} rename +* @prop {function(string, Object): Promise} updateMetadata +* @prop {function(string, string|string[]): Promise} deleteMetadata +* @prop {function(string): Promise} unlink +* @prop {function(string, Object=): Promise} rmdir +* @prop {function(string, string|Buffer): Promise} mount +* @prop {function(string): Promise} unmount +* @prop {function(string=): NodeJS.ReadableStream} watch +* @prop {function(): NodeJS.ReadableStream} createNetworkActivityStream +* @prop {function(): Promise} readManifest +* @prop {function(Object): Promise} writeManifest +* @prop {function(Object): Promise} updateManifest +*/ + +// globals +// = + +var client // client object created by hyperdrive-daemon-client +var isControllingDaemonProcess = false // did we start the process? +var isSettingUp = true +var isShuttingDown = false +var isDaemonActive = false +var isFirstConnect = true +var sessions = {} // map of keyStr => DaemonHyperdrive +var events = new EventEmitter() + +// exported apis +// = + +export const on = events.on.bind(events) + +export function getClient () { + return client +} + +export function isActive () { + if (isFirstConnect) { + // avoid the "inactive daemon" indicator during setup + return true + } + return isDaemonActive +} + +export async function getDaemonStatus () { + if (isDaemonActive) { + return Object.assign(await client.status(), {active: true}) + } + return {active: false} +} + +export async function setup () { + if (isSettingUp) { + isSettingUp = false + + // watch for the daemon process to die/revive + let interval = setInterval(() => { + pm2.list((err, processes) => { + var processExists = !!processes.find(p => p.name === 'hyperdrive' && p.pm2_env.status === 'online') + if (processExists && !isDaemonActive) { + isDaemonActive = true + isFirstConnect = false + events.emit('daemon-restored') + } else if (!processExists && isDaemonActive) { + isDaemonActive = false + events.emit('daemon-stopped') + } + }) + }, CHECK_DAEMON_INTERVAL) + interval.unref() + + events.on('daemon-restored', async () => { + logger.info('Hyperdrive daemon has been restored') + }) + events.on('daemon-stopped', async () => { + logger.info('Hyperdrive daemon has been lost') + isControllingDaemonProcess = false + }) + + // periodically close sessions + let interval2 = setInterval(() => { + let numClosed = 0 + let now = Date.now() + for (let key in sessions) { + if (sessions[key].persistSession) continue + if (sessions[key].pda.numActiveStreams > 0) continue + if (now - sessions[key].pda.lastCallTime < MAX_SESSION_AGE) continue + closeHyperdriveSession(key) + numClosed++ + } + if (numClosed > 0) { + logger.debug(`Closed ${numClosed} session(s) due to inactivity`) + } + }, GARBAGE_COLLECT_SESSIONS_INTERVAL) + interval2.unref() + } + + try { + client = new HyperdriveClient() + await client.ready() + logger.info('Connected to an external daemon.') + reconnectAllDriveSessions() + return + } catch (err) { + logger.info('Failed to connect to an external daemon. Launching the daemon...') + client = false + } + + if (getEnvVar('EMBED_HYPERDRIVE_DAEMON')) { + await createMetadata(`localhost:${constants.port}`) + var daemon = new HyperdriveDaemon() + await daemon.start() + process.on('exit', () => daemon.stop()) + } else { + isControllingDaemonProcess = true + logger.info('Starting daemon process, assuming process control') + await HyperdriveDaemonManager.start({ + interpreter: app.getPath('exe'), + env: Object.assign({}, process.env, {ELECTRON_RUN_AS_NODE: 1, ELECTRON_NO_ASAR: 1}), + noPM2DaemonMode: true, + memoryOnly: false, + heapSize: 4096, // 4GB heap + storage: constants.root + }) + } + + await attemptConnect() + reconnectAllDriveSessions() +} + +export function requiresShutdown () { + return isControllingDaemonProcess && !isShuttingDown +} + +export async function shutdown () { + if (isControllingDaemonProcess) { + isShuttingDown = true + return HyperdriveDaemonManager.stop() + } +} + +/** + * Gets a hyperdrives interface to the daemon for the given key + * + * @param {Object|string} opts + * @param {Buffer} [opts.key] + * @param {number} [opts.version] + * @param {Buffer} [opts.hash] + * @param {boolean} [opts.writable] + * @returns {DaemonHyperdrive} + */ +export function getHyperdriveSession (opts) { + return sessions[createSessionKey(opts)] +} + +/** + * Creates a hyperdrives interface to the daemon for the given key + * + * @param {Object} opts + * @param {Buffer} [opts.key] + * @param {number} [opts.version] + * @param {Buffer} [opts.hash] + * @param {boolean} [opts.writable] + * @param {String} [opts.domain] + * @returns {Promise} + */ +export async function createHyperdriveSession (opts) { + if (opts.key) { + let sessionKey = createSessionKey(opts) + if (sessions[sessionKey]) return sessions[sessionKey] + } + + const drive = await client.drive.get(opts) + const key = opts.key = datEncoding.toStr(drive.key) + var driveObj = { + key: drive.key, + discoveryKey: drive.discoveryKey, + url: `hyper://${opts.domain || key}/`, + writable: drive.writable, + domain: opts.domain, + persistSession: false, + + session: { + drive, + opts, + async close () { + delete sessions[key] + return this.drive.close() + } + }, + + async getInfo () { + var version = await this.session.drive.version() + return {version} + }, + + pda: createHyperdriveSessionPDA(drive) + } + var sessKey = createSessionKey(opts) + logger.debug(`Opening drive-session ${sessKey}`) + sessions[sessKey] = driveObj + return /** @type DaemonHyperdrive */(driveObj) +} + +/** + * Closes a hyperdrives interface to the daemon for the given key + * + * @param {Object|string} opts + * @param {Buffer} [opts.key] + * @param {number} [opts.version] + * @param {Buffer} [opts.hash] + * @param {boolean} [opts.writable] + * @returns {void} + */ +export function closeHyperdriveSession (opts) { + var key = createSessionKey(opts) + if (sessions[key]) { + logger.debug(`Closing drive-session ${key}`) + sessions[key].session.close() + delete sessions[key] + } +} + +export async function getPeerCount (key) { + if (!client) return 0 + var res = await client.drive.peerCounts([key]) + return res[0] +} + +export async function listPeerAddresses (discoveryKey) { + if (!client) return [] + var peers = await client.peers.listPeers(discoveryKey) + return peers.map(p => p.address) +} + +// internal methods +// = + +function createSessionKey (opts) { + if (typeof opts === 'string') { + return opts // assume it's already a session key + } + var key = opts.key.toString('hex') + if (opts.version) { + key += `+${opts.version}` + } + if ('writable' in opts) { + key += `+${opts.writable ? 'w' : 'ro'}` + } + return key +} + +async function attemptConnect () { + var connectBackoff = 100 + for (let i = 0; i < SETUP_RETRIES; i++) { + try { + client = new HyperdriveClient() + await client.ready() + } catch (e) { + logger.info('Failed to connect to daemon, retrying') + await new Promise(r => setTimeout(r, connectBackoff)) + connectBackoff += 100 + } + } +} + +async function reconnectAllDriveSessions () { + for (let sessionKey in sessions) { + await reconnectDriveSession(sessions[sessionKey]) + } +} + +async function reconnectDriveSession (driveObj) { + const drive = await client.drive.get(driveObj.session.opts) + driveObj.session.drive = drive + driveObj.pda = createHyperdriveSessionPDA(drive) +} + +/** + * Provides a pauls-dat-api2 object for the given drive + * @param {Object} drive + * @returns {DaemonHyperdrivePDA} + */ +function createHyperdriveSessionPDA (drive) { + var obj = { + lastCallTime: Date.now(), + numActiveStreams: 0 + } + for (let k in pda) { + if (typeof pda[k] === 'function') { + obj[k] = async (...args) => { + obj.lastCallTime = Date.now() + if (k === 'watch') { + obj.numActiveStreams++ + let stream = pda.watch.call(pda, drive, ...args) + stream.on('close', () => { + obj.numActiveStreams-- + }) + return stream + } + return pda[k].call(pda, drive, ...args) + } + } + } + return obj +} diff --git a/app/bg/hyper/debugging.js b/app/bg/hyper/debugging.js new file mode 100644 index 0000000000..2e01c01924 --- /dev/null +++ b/app/bg/hyper/debugging.js @@ -0,0 +1,57 @@ +import * as hyperDns from './dns' + +/** + * @returns {string} + */ +export const drivesDebugPage = function () { + var drives = [] // TODO getActiveDrives() + return ` + + ${Object.keys(drives).map(key => { + var a = drives[key] + return `
    +

    ${a.key.toString('hex')}

    + + + + + +
    Meta DKey${a.discoveryKey.toString('hex')}
    Content DKey${a.content.discoveryKey.toString('hex')}
    Meta Key${a.key.toString('hex')}
    Content Key${a.content.key.toString('hex')}
    +
    ` + }).join('')} + + ` +} + +/** + * @returns {string} + */ +export const datDnsCachePage = function () { + var cache = hyperDns.listCache() + return ` + +

    Dat DNS cache

    +

    + + ${Object.keys(cache).map(name => { + var key = cache[name] + return `` + }).join('')} +
    ${name}${key}
    + + + ` +} + +/** + * @returns {string} + */ +export const datDnsCacheJS = function () { + return ` + document.querySelector('button').addEventListener('click', clear) + async function clear () { + await beaker.drives.clearDnsCache() + location.reload() + } + ` +} diff --git a/app/bg/hyper/dns.js b/app/bg/hyper/dns.js new file mode 100644 index 0000000000..481739d5a7 --- /dev/null +++ b/app/bg/hyper/dns.js @@ -0,0 +1,82 @@ +import { InvalidDomainName } from 'beaker-error-constants' +import datDnsFactory from 'dat-dns' +import * as datDnsDb from '../dbs/dat-dns' +import * as drives from './drives' +import { HYPERDRIVE_HASH_REGEX } from '../../lib/const' +import * as capabilities from './capabilities' +import * as logLib from '../logger' +const logger = logLib.child({category: 'hyper', subcategory: 'dns'}) + +var localMapByName = {} +var localMapByKey = {} + +export function setLocal (name, url) { + var key = toHostname(url) + localMapByName[name] = key + localMapByKey[key] = name +} + +export async function resolveName (name) { + name = toHostname(name) + if (HYPERDRIVE_HASH_REGEX.test(name)) return name + return localMapByName[name] +} + +export async function reverseResolve (key) { + return localMapByKey[toHostname(key)] +} + +function toHostname (v) { + if (Buffer.isBuffer(v)) { + return v.toString('hex') + } + try { + var urlp = new URL(v) + return urlp.hostname + } catch (e) { + return v + } +} + +/* +TODO + +const DNS_PROVIDERS = [['cloudflare-dns.com', '/dns-query'], ['dns.google.com', '/resolve']] +const DNS_PROVIDER = DNS_PROVIDERS[Math.random() > 0.5 ? 1 : 0] +logger.info(`Using ${DNS_PROVIDER[0]} to resolve DNS lookups`) + +// instantate a dns cache and export it +const datDns = datDnsFactory({ + persistentCache: {read, write}, + dnsHost: DNS_PROVIDER[0], + dnsPath: DNS_PROVIDER[1] +}) + +export default datDns + +// hook up log events +datDns.on('resolved', details => logger.debug('Resolved', {details})) +datDns.on('failed', details => logger.debug('Failed lookup', {details})) +datDns.on('cache-flushed', details => logger.debug('Cache flushed')) + +// wrap resolveName() with a better error +const resolveName = datDns.resolveName +datDns.resolveName = async function (name, opts, cb) { + return resolveName.apply(datDns, arguments) + .catch(_ => { + throw new InvalidDomainName() + }) +} + +// persistent cache methods +async function read (name, err) { + // check the cache + var record = await datDnsDb.getCurrentByName(name) + if (!record) throw err + return record.key +} +async function write (name, key) { + if (HYPERDRIVE_HASH_REGEX.test(name)) return // dont write for raw urls + await drives.confirmDomain(key) +} +*/ \ No newline at end of file diff --git a/app/bg/hyper/drives.js b/app/bg/hyper/drives.js new file mode 100644 index 0000000000..ada5273910 --- /dev/null +++ b/app/bg/hyper/drives.js @@ -0,0 +1,549 @@ +import emitStream from 'emit-stream' +import EventEmitter from 'events' +import datEncoding from 'dat-encoding' +import { parseDriveUrl } from '../../lib/urls' +import _debounce from 'lodash.debounce' +import pda from 'pauls-dat-api2' +import { wait } from '../../lib/functions' +import * as logLib from '../logger' +const baseLogger = logLib.get() +const logger = baseLogger.child({category: 'hyper', subcategory: 'drives'}) + +// dbs +import * as archivesDb from '../dbs/archives' +import * as hyperDnsDb from '../dbs/dat-dns' + +// hyperdrive modules +import * as daemon from './daemon' +import * as driveAssets from './assets' +import * as hyperDns from './dns' + +// fs modules +import * as filesystem from '../filesystem/index' + +// constants +// = + +import { HYPERDRIVE_HASH_REGEX, DRIVE_MANIFEST_FILENAME } from '../../lib/const' +import { InvalidURLError, TimeoutError } from 'beaker-error-constants' + +// typedefs +// = + +/** + * @typedef {import('./daemon').DaemonHyperdrive} DaemonHyperdrive + */ + +// globals +// = + +var driveLoadPromises = {} // key -> promise +var drivesEvents = new EventEmitter() + +// exported API +// = + +export const on = drivesEvents.on.bind(drivesEvents) +export const addListener = drivesEvents.addListener.bind(drivesEvents) +export const removeListener = drivesEvents.removeListener.bind(drivesEvents) + +/** + * @return {Promise} + */ +export async function setup () { + // connect to the daemon + await daemon.setup() + + // TODO + // hyperDnsDb.on('updated', ({key, name}) => { + // var drive = getDrive(key) + // if (drive) { + // drive.domain = name + // } + // }) + + logger.info('Initialized dat daemon') +} + +/** + * @param {String[]} keys + * @returns {Promise} + */ +export async function ensureHosting (keys) { + var configs = await daemon.getClient().drive.allNetworkConfigurations() + for (let key of keys) { + let cfg = configs.get(key) + if (!cfg || !cfg.announce || !cfg.lookup) { + let drive = await getOrLoadDrive(key) + logger.info(`Reconfiguring network behavior for drive ${key}`) + await drive.session.drive.configureNetwork({ + announce: true, + lookup: true + }) + } + } +} + +/** + * @returns {NodeJS.ReadableStream} + */ +export function createEventStream () { + return emitStream.toStream(drivesEvents) +}; + +/** + * @param {string} key + * @returns {Promise} + */ +export function getDebugLog (key) { + return '' // TODO needed? daemon.getDebugLog(key) +}; + +/** + * @returns {NodeJS.ReadableStream} + */ +export function createDebugStream () { + // TODO needed? + // return daemon.createDebugStream() +}; + +// read metadata for the drive, and store it in the meta db +export async function pullLatestDriveMeta (drive, {updateMTime} = {}) { + try { + var key = drive.key.toString('hex') + + // trigger DNS update + // confirmDomain(key) DISABLED + + var version = await drive.session.drive.version() + if (version === drive.lastMetaPullVersion) { + return + } + var lastMetaPullVersion = drive.lastMetaPullVersion + drive.lastMetaPullVersion = version + + if (lastMetaPullVersion) { + driveAssets.hasUpdates(drive, lastMetaPullVersion).then(hasAssetUpdates => { + if (hasAssetUpdates) { + driveAssets.update(drive) + } + }) + } + + // read the drive meta and size on disk + var [manifest, oldMeta, size] = await Promise.all([ + drive.pda.readManifest().catch(_ => {}), + archivesDb.getMeta(key), + 0//drive.pda.readSize('/') + ]) + var {title, description, type, author, forkOf} = (manifest || {}) + var writable = drive.writable + var mtime = updateMTime ? Date.now() : oldMeta.mtime + var details = {title, description, type, forkOf, mtime, size, author, writable} + + // check for changes + if (!hasMetaChanged(details, oldMeta)) { + return + } + + // write the record + await archivesDb.setMeta(key, details) + + // emit the updated event + details.url = 'hyper://' + key + '/' + drivesEvents.emit('updated', {key, details, oldMeta}) + logger.info('Updated recorded metadata for hyperdrive', {key, details}) + return details + } catch (e) { + console.error('Error pulling meta', e) + } +} + +// drive creation +// = + +/** + * @returns {Promise} + */ +export async function createNewRootDrive () { + var drive = await loadDrive(null, {visibility: 'private'}) + await pullLatestDriveMeta(drive) + return drive +}; + +/** + * @param {Object} [manifest] + * @returns {Promise} + */ +export async function createNewDrive (manifest = {}) { + // create the drive + var drive = await loadDrive(null) + + // announce the drive + drive.session.drive.configureNetwork({ + announce: true, + lookup: true + }) + + // write the manifest and default datignore + await Promise.all([ + drive.pda.writeManifest(manifest) + // DISABLED drive.pda.writeFile('/.datignore', await settingsDb.get('default_dat_ignore'), 'utf8') + ]) + + // save the metadata + await pullLatestDriveMeta(drive) + + return drive +} + +/** + * @param {string} srcDriveUrl + * @param {Object} [opts] + * @returns {Promise} + */ +export async function forkDrive (srcDriveUrl, opts = {}) { + srcDriveUrl = fromKeyToURL(srcDriveUrl) + + // get the source drive + var srcDrive + var downloadRes = await Promise.race([ + (async function () { + srcDrive = await getOrLoadDrive(srcDriveUrl) + if (!srcDrive) { + throw new Error('Invalid drive key') + } + // return srcDrive.session.drive.download('/') TODO needed? + })(), + new Promise(r => setTimeout(() => r('timeout'), 60e3)) + ]) + if (downloadRes === 'timeout') { + throw new TimeoutError('Timed out while downloading source drive') + } + + // fetch source drive meta + var srcManifest = await srcDrive.pda.readManifest().catch(_ => {}) + srcManifest = srcManifest || {} + + // override any opts data + var dstManifest = { + title: (opts.title) ? opts.title : srcManifest.title, + description: (opts.description) ? opts.description : srcManifest.description, + forkOf: opts.detached ? undefined : fromKeyToURL(srcDriveUrl) + // author: manifest.author + } + for (let k in srcManifest) { + if (k === 'author') continue + if (!dstManifest[k]) { + dstManifest[k] = srcManifest[k] + } + } + + // create the new drive + var dstDrive = await createNewDrive(dstManifest) + + // copy files + var ignore = ['/.dat', '/.git', '/index.json'] + await pda.exportArchiveToArchive({ + srcArchive: srcDrive.session.drive, + dstArchive: dstDrive.session.drive, + skipUndownloadedFiles: false, + ignore + }) + + return dstDrive +}; + +// drive management +// = + +export async function loadDrive (key, opts) { + // validate key + if (key) { + if (!Buffer.isBuffer(key)) { + // existing dat + key = await fromURLToKey(key, true) + if (!HYPERDRIVE_HASH_REGEX.test(key)) { + throw new InvalidURLError() + } + key = datEncoding.toBuf(key) + } + } + + // fallback to the promise, if possible + var keyStr = key ? datEncoding.toStr(key) : null + if (keyStr && keyStr in driveLoadPromises) { + return driveLoadPromises[keyStr] + } + + // run and cache the promise + var p = loadDriveInner(key, opts) + if (key) driveLoadPromises[keyStr] = p + p.catch(err => { + console.error('Failed to load drive', keyStr, err.toString()) + }) + + // when done, clear the promise + if (key) { + const clear = () => delete driveLoadPromises[keyStr] + p.then(clear, clear) + } + + return p +} + +// main logic, separated out so we can capture the promise +async function loadDriveInner (key, opts) { + // fetch dns name if known + var domain = await hyperDns.reverseResolve(key) + // let dnsRecord = await hyperDnsDb.getCurrentByKey(datEncoding.toStr(key)) TODO + // drive.domain = dnsRecord ? dnsRecord.name : undefined + + // create the drive session with the daemon + var drive = await daemon.createHyperdriveSession({key, domain}) + drive.pullLatestDriveMeta = opts => pullLatestDriveMeta(drive, opts) + key = drive.key + + if (opts && opts.persistSession) { + drive.persistSession = true + } + + // update db + archivesDb.touch(drive.key).catch(err => console.error('Failed to update lastAccessTime for drive', drive.key, err)) + if (!drive.writable) { + await downloadHack(drive, DRIVE_MANIFEST_FILENAME) + } + await drive.pullLatestDriveMeta() + driveAssets.update(drive) + + return drive +} + +/** + * HACK to work around the incomplete daemon-client download() method -prf + */ +async function downloadHack (drive, path) { + if (!(await drive.pda.stat(path).catch(err => undefined))) return + let fileStats = (await drive.session.drive.fileStats(path)).get(path) + if (fileStats.downloadedBlocks >= fileStats.blocks) return + await drive.session.drive.download(path) + for (let i = 0; i < 10; i++) { + await wait(500) + fileStats = (await drive.session.drive.fileStats(path)).get(path) + if (fileStats.downloadedBlocks >= fileStats.blocks) { + return + } + } +} + +export function getDrive (key) { + key = fromURLToKey(key) + return daemon.getHyperdriveSession({key}) +} + +export async function getDriveCheckout (drive, version) { + var isHistoric = false + var checkoutFS = drive + if (typeof version !== 'undefined' && version !== null) { + let seq = parseInt(version) + if (Number.isNaN(seq)) { + if (version === 'latest') { + // ignore, we use latest by default + } else { + throw new Error('Invalid version identifier:' + version) + } + } else { + let latestVersion = await drive.session.drive.version() + if (version <= latestVersion) { + checkoutFS = await daemon.createHyperdriveSession({ + key: drive.key, + version, + writable: false, + domain: drive.domain + }) + isHistoric = true + } + } + } + return {isHistoric, checkoutFS} +}; + +export async function getOrLoadDrive (key, opts) { + key = await fromURLToKey(key, true) + var drive = getDrive(key) + if (drive) return drive + return loadDrive(key, opts) +} + +export async function unloadDrive (key) { + key = fromURLToKey(key, false) + daemon.closeHyperdriveSession({key}) +}; + +export function isDriveLoaded (key) { + key = fromURLToKey(key) + return !!daemon.getHyperdriveSession({key}) +} + +// drive fetch/query +// = + +export async function getDriveInfo (key, {ignoreCache, onlyCache} = {ignoreCache: false, onlyCache: false}) { + // get the drive + key = await fromURLToKey(key, true) + var drive + if (!onlyCache) { + drive = getDrive(key) + if (!drive && ignoreCache) { + drive = await loadDrive(key) + } + } + + var domain = drive ? drive.domain : await hyperDns.reverseResolve(key) + var url = `hyper://${domain || key}/` + + // fetch drive data + var meta, manifest, driveInfo + if (drive) { + await drive.pullLatestDriveMeta() + ;[meta, manifest, driveInfo] = await Promise.all([ + archivesDb.getMeta(key), + drive.pda.readManifest().catch(_ => {}), + drive.getInfo() + ]) + } else { + meta = await archivesDb.getMeta(key) + driveInfo = {version: undefined} + } + manifest = manifest || {} + if (filesystem.isRootUrl(url) && !meta.title) { + meta.title = 'My System Drive' + } + meta.key = key + meta.discoveryKey = drive ? drive.discoveryKey : undefined + meta.url = url + // meta.domain = drive.domain TODO + meta.links = manifest.links || {} + meta.manifest = manifest + meta.version = driveInfo.version + meta.peers = await daemon.getPeerCount(drive ? drive.key : new Buffer(key, 'hex')) + + return meta +} + +export async function clearFileCache (key) { + return {} // TODO daemon.clearFileCache(key, userSettings) +} + +/** + * @desc + * Get the primary URL for a given dat URL + * + * @param {string} url + * @returns {Promise} + */ +export async function getPrimaryUrl (url) { + var key = await fromURLToKey(url, true) + var datDnsRecord = await hyperDnsDb.getCurrentByKey(key) + if (!datDnsRecord) return `hyper://${key}/` + return `hyper://${datDnsRecord.name}/` +} + +/** + * @desc + * Check that the drive's index.json `domain` matches the current DNS + * If yes, write the confirmed entry to the dat_dns table + * + * @param {string} key + * @returns {Promise} + */ +export async function confirmDomain (key) { + // DISABLED + // hyper: does not currently use DNS + // -prf + + // // fetch the current domain from the manifest + // try { + // var drive = await getOrLoadDrive(key) + // var datJson = await drive.pda.readManifest() + // } catch (e) { + // return false + // } + // if (!datJson.domain) { + // await hyperDnsDb.unset(key) + // return false + // } + + // // confirm match with current DNS + // var dnsKey = await hyperDns.resolveName(datJson.domain) + // if (key !== dnsKey) { + // await hyperDnsDb.unset(key) + // return false + // } + + // // update mapping + // await hyperDnsDb.update({name: datJson.domain, key}) + // return true +} + +// helpers +// = + +export function fromURLToKey (url, lookupDns = false) { + if (Buffer.isBuffer(url)) { + return url + } + if (HYPERDRIVE_HASH_REGEX.test(url)) { + // simple case: given the key + return url + } + + var urlp = parseDriveUrl(url) + if (urlp.protocol !== 'hyper:' && urlp.protocol !== 'dat:') { + throw new InvalidURLError('URL must be a hyper: or dat: scheme') + } + if (!HYPERDRIVE_HASH_REGEX.test(urlp.host)) { + if (!lookupDns) { + throw new InvalidURLError('Hostname is not a valid hash') + } + return hyperDns.resolveName(urlp.host) + } + + return urlp.host +} + +export function fromKeyToURL (key) { + if (typeof key !== 'string') { + key = datEncoding.toStr(key) + } + if (!key.startsWith('hyper://')) { + return `hyper://${key}/` + } + return key +} + +function hasMetaChanged (m1, m2) { + for (let k of ['title', 'description', 'forkOf', 'size', 'author', 'writable', 'mtime']) { + if (!m1[k]) m1[k] = undefined + if (!m2[k]) m2[k] = undefined + if (k === 'forkOf') { + if (!isUrlsEq(m1[k], m2[k])) { + return true + } + } else { + if (m1[k] !== m2[k]) { + return true + } + } + } + return false +} + +var isUrlsEqRe = /([0-9a-f]{64})/i +function isUrlsEq (a, b) { + if (!a && !b) return true + if (typeof a !== typeof b) return false + var ma = isUrlsEqRe.exec(a) + var mb = isUrlsEqRe.exec(b) + return ma && mb && ma[1] === mb[1] +} \ No newline at end of file diff --git a/app/bg/hyper/index.js b/app/bg/hyper/index.js new file mode 100644 index 0000000000..9499a9ddd9 --- /dev/null +++ b/app/bg/hyper/index.js @@ -0,0 +1,19 @@ +import * as drives from './drives' +import * as assets from './assets' +import * as debug from './debugging' +import * as dns from './dns' +import * as watchlist from './watchlist' +import * as daemon from './daemon' + +export default { + drives, + assets, + debug, + dns, + watchlist, + daemon, + async setup (opts) { + await this.drives.setup(opts) + await this.watchlist.setup() + } +} diff --git a/app/bg/hyper/watchlist.js b/app/bg/hyper/watchlist.js new file mode 100644 index 0000000000..d7dd2c5a25 --- /dev/null +++ b/app/bg/hyper/watchlist.js @@ -0,0 +1,102 @@ +import EventEmitter from 'events' +import emitStream from 'emit-stream' +import * as logLib from '../logger' +const logger = logLib.child({category: 'hyper', subcategory: 'watchlist'}) + +// dat modules +import * as drives from './drives' + +import * as hyperDns from './dns' +import * as watchlistDb from '../dbs/watchlist' + +// globals +// = + +var watchlistEvents = new EventEmitter() + +// exported methods +// = + +export async function setup () { + try { + var watchedSites = await watchlistDb.getSites(0) + for (let site of watchedSites) { + watch(site) + } + } catch (err) { + logger.error('Error while loading watchlist', {err}) + throw new Error('Failed to load the watchlist') + } +} + +export async function addSite (profileId, url, opts) { + // validate parameters + if (!url || typeof url !== 'string') { + throw new Error('url must be a string') + } + if (!opts.description || typeof opts.description !== 'string') { + throw new Error('description must be a string') + } + if (typeof opts.seedWhenResolved !== 'boolean') { + throw new Error('seedWhenResolved must be a boolean') + } + if (!url.startsWith('hyper://')) { + url = 'hyper://' + url + '/' + } + + try { + var site = await watchlistDb.addSite(profileId, url, opts) + watch(site) + } catch (err) { + throw new Error('Failed to add to watchlist') + } +} + +export async function getSites (profileId) { + return watchlistDb.getSites(profileId) +} + +export async function updateWatchlist (profileId, site, opts) { + try { + await watchlistDb.updateWatchlist(profileId, site, opts) + } catch (err) { + throw new Error('Failed to update the watchlist') + } +} + +export async function removeSite (profileId, url) { + // validate parameters + if (!url || typeof url !== 'string') { + throw new Error('url must be a string') + } + return watchlistDb.removeSite(profileId, url) +} + +// events + +export function createEventsStream () { + return emitStream(watchlistEvents) +} + +// internal methods +// = + +async function watch (site) { + // resolve DNS + var key + try { + key = await hyperDns.resolveName(site.url) + } catch (e) {} + if (!key) { + // try again in 30s + setTimeout(watch, 30e3) + return + } + + // load drive + var drive = await drives.loadDrive(key) + if (site.resolved === 0) { + watchlistEvents.emit('resolved', site) + } + await updateWatchlist(0, site, {resolved: 1}) +} \ No newline at end of file diff --git a/app/bg/lib/db.js b/app/bg/lib/db.js new file mode 100644 index 0000000000..c853d538b7 --- /dev/null +++ b/app/bg/lib/db.js @@ -0,0 +1,111 @@ +import * as logLib from '../logger' +const logger = logLib.child({category: 'sqlite'}) +import FnQueue from 'function-queue' +import { cbPromise } from '../../lib/functions' +import _get from 'lodash.get' + +/** + * Create a transaction lock + * - returns a function which enforces FIFO execution on async behaviors, via a queue + * - call sig: txLock(cb => { ...; cb() }) + * - MUST call given cb to release the lock + * @returns {function(Function): void} + */ +const makeTxLock = exports.makeTxLock = function () { + var fnQueue = FnQueue() + return cb => fnQueue.push(cb) +} + +/** + * SQLite transactor, handles common needs for sqlite queries: + * 1. waits for the setupPromise + * 2. provides a cb handler that returns a promise + * 3. creates a transaction lock, and wraps the cb with it + * NOTE: + * Using the transactor does mean that the DB is locked into sequential operation. + * This is slower, but necessary if the SQLite instance has any transactions that + * do async work within them; eg, SELECT then UPDATE. + * Why: without the tx lock around all SQLite statements, you can end up injecting + * new commands into the active async transaction. + * If the DB doesn't do async transactions, you don't need the transactor. At time of + * writing this, only the history DB needed it. + * -prf + * @param {Promise} setupPromise + * @returns {function(Function): Promise} + */ +export const makeSqliteTransactor = function (setupPromise) { + var txLock = makeTxLock() + return function (fn) { + // 1. wait for the setup promise + return setupPromise.then(v => { + // 2. provide a cb handler + return cbPromise(cb => { + // 3. create a tx lock + txLock(endTx => { + // 3b. wrap the cb with the lock release + var cbWrapped = (err, res) => { + endTx() + cb(err, res) + } + // yeesh + fn(cbWrapped) + }) + }) + }) + } +} + +/** + * Configures SQLite db and runs needed migrations. + * @param {any} db + * @param {Object} opts + * @param {Function} [opts.setup] + * @param {Function[]} [opts.migrations] + */ +export const setupSqliteDB = function (db, {setup, migrations}, logTag) { + return new Promise((resolve, reject) => { + // configure connection + db.run('PRAGMA foreign_keys = ON;', (err) => { + if (err) { + console.error('Failed to enable FK support in SQLite', err) + } + }) + + // run migrations + db.get('PRAGMA user_version;', (err, res) => { + if (err) return reject(err) + + var version = (res && res.user_version) ? +res.user_version : 0 + var neededMigrations = (version === 0 && setup) ? [setup] : migrations.slice(version) + if (neededMigrations.length == 0) { return resolve() } + + logger.info(`${logTag} Database at version ${version}; Running ${neededMigrations.length} migrations`) + runNeededMigrations() + function runNeededMigrations (err) { + if (err) { + logger.error(`${logTag} Failed migration`) + console.log(err) + return reject(err) + } + + var migration = neededMigrations.shift() + if (!migration) { + // done + resolve() + return logger.info(`${logTag} Database migrations completed without error`) + } + + migration(runNeededMigrations) + } + }) + }) +} + +export const handleQueryBuilder = function (args) { + // detect query builders and replace the args + if (args[0] && _get(args[0], 'constructor.name') === 'Builder') { + var query = args[0].toSQL() + return [query.sql, query.bindings] + } + return args +} \ No newline at end of file diff --git a/app/lib/electron.js b/app/bg/lib/electron.js similarity index 100% rename from app/lib/electron.js rename to app/bg/lib/electron.js diff --git a/app/bg/lib/env.js b/app/bg/lib/env.js new file mode 100644 index 0000000000..99afbb5dc9 --- /dev/null +++ b/app/bg/lib/env.js @@ -0,0 +1,16 @@ +/** + * Helper to get environment variables, ignoring case + * @param {string} name + * @returns {string} + */ +export const getEnvVar = function (name) { + var ucv = process.env[name.toUpperCase()] + if (typeof ucv !== 'undefined') { + return ucv + } + var lcv = process.env[name.toLowerCase()] + if (typeof lcv !== 'undefined') { + return lcv + } + return undefined +} diff --git a/app/bg/lib/error-page.js b/app/bg/lib/error-page.js new file mode 100644 index 0000000000..d0a3924cf3 --- /dev/null +++ b/app/bg/lib/error-page.js @@ -0,0 +1,229 @@ +var errorPageCSS = ` +* { + box-sizing: border-box; +} +a { + text-decoration: none; + color: inherit; + cursor: pointer; +} +body { + background: #fff; + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Ubuntu, Cantarell, "Oxygen Sans", "Helvetica Neue", sans-serif; +} +.btn { + display: inline-block; + cursor: pointer; + color: #777; + border-radius: 2px; + background: #fafafa; + border: 1px solid #ddd; + font-size: 12px; + font-weight: 500; + height: 25px; + line-height: 2; + padding: 0 8px; + letter-spacing: .2px; + height: 26px; + font-weight: 400; +} +.btn * { + cursor: pointer; + line-height: 25px; + vertical-align: baseline; + display: inline-block; +} +.btn:focus { + outline-color: #007aff; +} +.btn:hover { + text-decoration: none; + background: #f0f0f0; +} +.btn.disabled, +.btn:disabled { + cursor: default; + color: #999999; + border: 1px solid #ccc; + box-shadow: none; +} +.btn.disabled .spinner, +.btn:disabled .spinner { + color: #aaa; +} +.btn.primary { + -webkit-font-smoothing: antialiased; + font-weight: 800; + background: #007aff; + color: #fff; + border: none; + transition: background .1s ease; +} +.btn.primary:hover { + background: #0074f2; +} +a.btn span { + vertical-align: baseline; +} +.btn.big { + font-size: 18px; + height: auto; + padding: 0px 12px; +} +a.link { + color: blue; + text-decoration: underline; +} +.right { + float: right; +} +.icon-wrapper { + vertical-align: top; + width: 70px; + font-size: 50px; + display: inline-block; + color: #555; + + i { + margin-top: -3px; + } +} +.error-wrapper { + display: inline-block; + width: 80%; +} +div.error-page-content { + max-width: 650px; + margin: auto; + transform: translateX(-20px); + margin-top: 30vh; + color: #777; + font-size: 14px; +} +div.error-page-content .description { + + p { + margin: 20px 0; + } +} +div.error-page-content i { + margin-right: 5px; +} +h1 { + margin: 0; + color: #333; + font-weight: 400; + font-size: 22px; +} +p.big { + font-size: 18px; +} +.icon { + float: right; +} +li { + margin-bottom: 0.5em; +} +li:last-child { + margin: 0; +} +.footer { + font-size: 14px; + color: #777; + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-top: 30px; + padding-top: 10px; + border-top: 1px solid #ddd; +} +` + +/** + * Generate an error page HTML + * @param {Object} e + * @param {string} e.resource + * @param {number} e.errorCode + * @param {string} e.errorDescription + * @param {string} e.errorInfo + * @param {string} e.title + * @param {string} e.validatedURL + * @returns {string} + */ +export default function (e) { + var title = 'This site can’t be reached' + var info = '' + var icon = 'fa-exclamation-circle' + var button = 'Try again' + var errorDescription + var moreHelp = '' + + if (typeof e === 'object') { + errorDescription = e.errorDescription || '' + info = e.errorInfo || '' + // remove trailing slash + var origin = e.validatedURL.slice(0, e.validatedURL.length - 1) + + // strip protocol + if (origin.startsWith('https://')) { + origin = origin.slice(8) + } else if (origin.startsWith('http://')) { + origin = origin.slice(7) + } + + switch (e.errorCode) { + case -106: + title = 'No internet connection' + info = `

    Your computer is not connected to the internet.

    Try:

    • Resetting your Wi-Fi connection
    • Checking your router and modem.
    ` + break + case -105: + icon = 'fa-frown-o' + info = `

    Couldn’t resolve the DNS address for ${origin}

    ` + break + case 404: + icon = 'fa-frown-o' + title = e.title || 'Page Not Found' + info = `

    ${e.errorInfo}

    ` + break + case -501: + title = 'Your connection is not secure' + info = `

    Beaker cannot establish a secure connection to the server for ${origin}.

    ` + icon = 'fa-close warning' + button = 'Go back' + break + case 504: + icon = 'fa-share-alt' + title = `Beaker is unable to access this ${e.resource} right now.` + errorDescription = `The p2p ${e.resource} was not reachable on the network.` + break + } + } else { + errorDescription = e + } + + return ` + + + + + + + +
    +
    + +
    +

    ${title}

    +
    + ${info} +
    + +
    +
    + + `.replace(/\n/g, '') +}; diff --git a/app/lib/bg/fs.js b/app/bg/lib/fs.js similarity index 100% rename from app/lib/bg/fs.js rename to app/bg/lib/fs.js diff --git a/app/lib/bg/image.js b/app/bg/lib/image.js similarity index 99% rename from app/lib/bg/image.js rename to app/bg/lib/image.js index 137acdfe64..ba425e4dbd 100644 --- a/app/lib/bg/image.js +++ b/app/bg/lib/image.js @@ -2,7 +2,7 @@ * @description * Takes in a bitmap buffer (from NativeImage) and finds the dimensions * of the actual image content, effectively trimming all whitespace. - * + * * @param {Buffer} buf * @param {Object} size * @param {number} size.width diff --git a/app/bg/lib/knex.js b/app/bg/lib/knex.js new file mode 100644 index 0000000000..70d8bf077d --- /dev/null +++ b/app/bg/lib/knex.js @@ -0,0 +1 @@ +export default require('knex')({client: 'sqlite3', useNullAsDefault: true}) \ No newline at end of file diff --git a/app/bg/lib/mime.js b/app/bg/lib/mime.js new file mode 100644 index 0000000000..9328966a68 --- /dev/null +++ b/app/bg/lib/mime.js @@ -0,0 +1,214 @@ +import through2 from 'through2' +import identifyFiletype from 'identify-filetype' +import mime from 'mime' +import path from 'path' +import textextensions from 'textextensions' +import binextensions from 'binary-extensions' +import concat from 'concat-stream' + +// config default mimetype +mime.default_type = 'text/plain' +const TEXT_TYPE_RE = /^text\/|^application\/(javascript|json)/ + +// typedefs +// = + +/** + * @typedef {import('stream').Transform} Transform + */ + +// exported api +// = + +/** + * @param {string} name + * @param {Buffer} [chunk] + * @returns {string} + */ +export function identify (name, chunk) { + // try to identify the type by the chunk contents + var mimeType + var identifiedExt = (chunk) ? identifyFiletype(chunk) : false + if (identifiedExt) { mimeType = mime.lookup(identifiedExt, 'text/plain') } + if (!mimeType) { + // fallback to using the entry name + mimeType = mime.lookup(name, 'text/plain') + } + mimeType = correctSomeMimeTypes(mimeType, name) + + // hackish fix + // the svg test can be a bit aggressive: html pages with + // inline svgs can be falsely interpretted as svgs + // double check that + if (identifiedExt === 'svg' && mime.lookup(name) === 'text/html') { + return 'text/html; charset=utf8' + } + + // assume utf-8 for text types + if (TEXT_TYPE_RE.test(mimeType)) { + mimeType += '; charset=utf8' + } + + return mimeType +} + +/** + * @param {string} name + * @param {function(string): any} cb + * @returns {Transform} + */ +export function identifyStream (name, cb) { + var first = true + return through2(function (chunk, enc, cb2) { + if (first) { + first = false + cb(identify(name, chunk)) + } + this.push(chunk) + cb2() + }) +} + +/** + * Guesses if the file is binary based on its path. + * Returns 'undefined' if no guess can be made. + * @param {string} filepath + * @returns {boolean | undefined} + */ +export function isFileNameBinary (filepath) { + const ext = path.extname(filepath) + if (binextensions.includes(ext)) return true + if (textextensions.includes(ext)) return false + // dont know +} + +/** + * Guesses if the file is binary based on its content. + * @param {Object} fsInstance - The filesystem to read from. + * @param {string} filepath + * @returns {Promise} + */ +export async function isFileContentBinary (fsInstance, filepath) { + return new Promise((resolve, reject) => { + const rs = fsInstance.createReadStream(filepath, {start: 0, end: 512}) + rs.on('error', reject) + rs.pipe(concat(buf => resolve(isBinaryCheck(buf)))) + }) +} + +/** + * For a given HTTP accept header, provide a list of file-extensions to try. + * @param {string | undefined} accept + * @returns {string[]} + */ +export function acceptHeaderExtensions (accept) { + var exts = [] + var parts = (accept || '').split(',') + if (parts.includes('text/html') || (parts.length === 1 && parts[0] === '*/*')) exts.push('.html') + if (parts.includes('text/css')) exts.push('.css') + if (parts.includes('image/*') || parts.includes('image/apng')) exts = exts.concat(['.png', '.jpg', '.jpeg', '.gif']) + return exts +} + +/** + * For a given HTTP accept header, is HTML wanted? + * @param {string | undefined} accept + * @returns {boolean} + */ +export function acceptHeaderWantsHTML (accept) { + var parts = (accept || '').split(',') + return parts.includes('text/html') +} + +/** + * Looks for byte patterns that indicate the 'bytes' chunk is from a binary file. + * pulled from https://github.com/gjtorikian/isBinaryFile + * @param {Buffer} bytes + * @returns {boolean} + */ +function isBinaryCheck (bytes) { + var size = bytes.length + if (size === 0) { + return false + } + + var suspicious_bytes = 0 + + // UTF-8 BOM + if (size >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF) { + return false + } + + // UTF-32 BOM + if (size >= 4 && bytes[0] === 0x00 && bytes[1] === 0x00 && bytes[2] == 0xFE && bytes[3] == 0xFF) { + return false + } + + // UTF-32 LE BOM + if (size >= 4 && bytes[0] == 0xFF && bytes[1] == 0xFE && bytes[2] === 0x00 && bytes[3] === 0x00) { + return false + } + + // GB BOM + if (size >= 4 && bytes[0] == 0x84 && bytes[1] == 0x31 && bytes[2] == 0x95 && bytes[3] == 0x33) { + return false + } + + if (size >= 5 && bytes.slice(0, 5).toString('utf8') === '%PDF-') { + /* PDF. This is binary. */ + return true + } + + // UTF-16 BE BOM + if (size >= 2 && bytes[0] == 0xFE && bytes[1] == 0xFF) { + return false + } + + // UTF-16 LE BOM + if (size >= 2 && bytes[0] == 0xFF && bytes[1] == 0xFE) { + return false + } + + for (var i = 0; i < size; i++) { + if (bytes[i] === 0) { // NULL byte--it's binary! + return true + } else if ((bytes[i] < 7 || bytes[i] > 14) && (bytes[i] < 32 || bytes[i] > 127)) { + // UTF-8 detection + if (bytes[i] > 193 && bytes[i] < 224 && i + 1 < size) { + i++ + if (bytes[i] > 127 && bytes[i] < 192) { + continue + } + } else if (bytes[i] > 223 && bytes[i] < 240 && i + 2 < size) { + i++ + if (bytes[i] > 127 && bytes[i] < 192 && bytes[i + 1] > 127 && bytes[i + 1] < 192) { + i++ + continue + } + } + suspicious_bytes++ + // Read at least 32 bytes before making a decision + if (i > 32 && (suspicious_bytes * 100) / size > 10) { + return true + } + } + } + + if ((suspicious_bytes * 100) / size > 10) { + return true + } + + return false +} + +/** + * @param {string} mimeType + * @param {string} name + * @returns {string} + */ +function correctSomeMimeTypes (mimeType, name) { + if (mimeType === 'video/quicktime' && name.endsWith('.mov')) { + return 'video/mp4' + } + return mimeType +} \ No newline at end of file diff --git a/app/bg/lib/session-perms.js b/app/bg/lib/session-perms.js new file mode 100644 index 0000000000..54029053a8 --- /dev/null +++ b/app/bg/lib/session-perms.js @@ -0,0 +1,85 @@ +import * as userSiteSessions from '../filesystem/site-sessions' +import * as drives from '../hyper/drives' +import * as archivesDb from '../dbs/archives' +import { PermissionsError } from 'beaker-error-constants' +import libTools from '@beaker/library-tools' + +// typedefs +// = + +/** + * @typedef {import('../filesystem/site-sessions').UserSiteSession} UserSiteSession + */ + +// exported api +// = + +export async function getSessionUserDrive (sender) { + var userSession = undefined // TODO windows.getUserSessionFor(sender) + if (!userSession) throw new Error('No active user session') + var key = await drives.fromURLToKey(userSession.url, true) + return drives.getDrive(key) +} + +/** + * @param {string} url + * @returns {Promise} + */ +export async function toDriveOrigin (url) { + if (!url.startsWith('hyper://')) { + throw new Error('Can only create sessions with hyperdrive sites') + } + return `hyper://${await drives.fromURLToKey(url, true)}/` +} + +/** + * @param {Object} sender + * @returns {Promise} + */ +export async function getSessionOrThrow (sender) { + if (await isTrustedApp(sender)) return + var userId = undefined // TODO await getSessionUserId(sender) + var session = await userSiteSessions.get(userId, await toDriveOrigin(sender.getURL())) + if (!session) { + throw new PermissionsError() + } + return session +} + +/** + * @param {Object} sender + * @param {string} perm eg 'unwalled.garden/api/comments' + * @param {string} cap eg 'read' or 'write' + * @returns {Promise} + */ +export async function assertCan (sender, perm, cap) { + if (await isTrustedApp(sender)) return + var sess = await getSessionOrThrow(sender) + if (!(await can(sess, perm, cap))) { + throw new PermissionsError() + } +} + +/** + * @param {Object} sender + * @returns {Promise} + */ +export async function isTrustedApp (sender) { + // TEMPORARY: hyperdrive.network is trusted + if (/^(beaker:|https?:\/\/(.*\.)?hyperdrive\.network(:|\/))/i.test(sender.getURL())) return true + return true +} + +// internal methods +// = + +/** + * @param {UserSiteSession} sess + * @param {string} perm eg 'unwalled.garden/api/comments' + * @param {string} cap eg 'read' or 'write' + * @returns {boolean} + */ +function can (sess, perm, cap) { + if (cap === 'read') return true // read permissions are all allowed, at this stage, if a session exists + return (sess.permissions[perm] || []).includes(cap) +} \ No newline at end of file diff --git a/app/bg/lib/tail-file.js b/app/bg/lib/tail-file.js new file mode 100644 index 0000000000..827f30730d --- /dev/null +++ b/app/bg/lib/tail-file.js @@ -0,0 +1,85 @@ +/** + * tail-file.js: TODO: add file header description. + * + * (C) 2010 Charlie Robbins + * (C) 2019 Paul Frazee + * MIT LICENCE + */ + +'use strict' + +import fs from 'fs' +import { StringDecoder } from 'string_decoder' +import { Stream } from 'readable-stream' + +/** + * Simple no-op function. + * @returns {undefined} + */ +function noop () {} + +/** + * Read and then tail the given file. + * The algorithm is fairly straight-forward: after hitting the end, it will attempt reads once a second. + * (It's poll-based rather than watch-based.) + * @param {string} file - Path to file. + * @returns {any} - TODO: add return description. + */ +export default (file) => { + const buffer = Buffer.alloc(64 * 1024) + const decode = new StringDecoder('utf8') + const stream = new Stream() + let pos = 0 + + stream.readable = true + stream.destroy = () => { + stream.destroyed = true + stream.emit('end') + stream.emit('close') + } + + fs.open(file, 'a+', '0644', async (err, fd) => { + if (err) { + stream.emit('error', err) + stream.destroy() + return + } + + while (true) { + if (stream.destroyed) { + // abort if stream destroyed + fs.close(fd, noop) + return + } + + // read next chunk + let bytes + try { + bytes = await new Promise((resolve, reject) => { + fs.read(fd, buffer, 0, buffer.length, pos, (err, bytes) => { + if (err) reject(err) + else resolve(bytes) + }) + }) + } catch (err) { + stream.emit('error', err) + stream.destroy() + return + } + + if (!bytes) { + // nothing read + // wait a second, then try to read again + await new Promise(resolve => setTimeout(resolve, 1e3)) + continue + } + + // decode and emit + let data = decode.write(buffer.slice(0, bytes)) + stream.emit('data', data) + pos += bytes + } + }) + + return stream +} diff --git a/app/bg/lib/zip.js b/app/bg/lib/zip.js new file mode 100644 index 0000000000..2f0f01a79f --- /dev/null +++ b/app/bg/lib/zip.js @@ -0,0 +1,50 @@ +import { join } from 'path' +import yazl from 'yazl' + +/** + * @typedef {import('../dat/daemon').DaemonHyperdrive} DaemonHyperdrive + * @typedef {import('stream').Readable} Readable + */ + +/** + * @param {DaemonHyperdrive} drive + * @param {string} [dirpath = '/'] + * @returns {Readable} + */ +export const toZipStream = function (drive, dirpath = '/') { + var zipfile = new yazl.ZipFile() + + // create listing stream + drive.pda.readdir(dirpath, {recursive: true}).then(async (paths) => { + for (let path of paths) { + let readPath = join(dirpath, path) + + // files only + try { + let entry = await drive.pda.stat(readPath) + if (!entry.isFile()) { + continue + } + } catch (e) { + // ignore, file must have been removed + continue + } + + // pipe each entry into the zip + zipfile.addBuffer(await drive.pda.readFile(readPath, 'binary'), path) + // NOTE + // for some reason using drive.createReadStream() to feed into the zipfile addReadStream() was not working with multiple files + // no idea why, maybe a sign of a bug in the dat-daemon's zip rpc + // -prf + } + zipfile.end() + }).catch(onerror) + + // on error, push to the output stream + function onerror (e) { + console.error('Error while producing zip stream', e) + zipfile.outputStream.emit('error', e) + } + + return zipfile.outputStream +} \ No newline at end of file diff --git a/app/bg/logger.js b/app/bg/logger.js new file mode 100644 index 0000000000..ae7a431904 --- /dev/null +++ b/app/bg/logger.js @@ -0,0 +1,223 @@ +import * as winston from 'winston' +import fs from 'fs' +import jetpack from 'fs-jetpack' +import fsReverse from 'fs-reverse' +import concat from 'concat-stream' +import pump from 'pump' +import split2 from 'split2' +import through2 from 'through2' +import { Readable } from 'stream' +import os from 'os' +import { join } from 'path' +const {combine, timestamp, json, simple, colorize, padLevels} = winston.format +import tailFile from './lib/tail-file' + +// typedefs +// = + +/** + * @typedef {Object} LogQueryOpts + * @prop {number} [logFile = 0] - Which logfile to read + * @prop {string} [sort = 'asc'] - Sort direction + * @prop {number} [since] - Start time + * @prop {number} [until] - End time + * @prop {number} [offset] - Event-slice offset + * @prop {number} [limit] - Max number of objects to output + * @prop {any} [filter] - Attribute filters + */ + +// globals +// = + +var logPath +const logger = winston.createLogger({ + level: 'silly' +}) + +// exported api +// = + +export async function setup (p) { + logPath = p + + // rotate logfiles from previous runs + await retireLogFile(5) + for (let i = 4; i >= 0; i--) { + await rotateLogFile(i) + } + + logger.add(new winston.transports.File({ + filename: logPath, + format: combine(timestamp(), json()) + })) + + // TODO if debug (pick an env var for this) + logger.add(new winston.transports.Console({ + level: 'verbose', + format: combine(colorize(), padLevels(), simple()) + })) + + logger.info('Logger started') +} + +export const get = () => logger +export const category = (category) => logger.child({category}) +export const child = (arg) => logger.child(arg) + +/** + * Query a slice of the log. + * @param {LogQueryOpts} [opts] + * @returns {Promise} + */ +export async function query (opts = {}) { + return new Promise((resolve, reject) => { + opts.limit = opts.limit || 100 + var readFn = opts.sort === 'desc' ? fsReverse : fs.createReadStream + var readStream = readFn(getLogPath(opts.logFile || 0), {encoding: 'utf8'}) + const nosplit = (readFn === fsReverse) // fs-reverse splits for us + pump( + readPipeline(readStream, opts, nosplit), + concat({encoding: 'object'}, res => resolve(/** @type any */(res))), + reject + ) + }) +} + +/** + * Create a read stream of the log. + * @param {LogQueryOpts} [opts] + * @returns {NodeJS.ReadStream} + */ +export function stream (opts = {}) { + var readStream = tailFile(getLogPath(opts.logFile || 0)) + return readPipeline(readStream, opts) +} + +export const WEBAPI = { + query, + stream: opts => { + opts = opts || {} + var s2 = new Readable({ + read () {}, + objectMode: true + }) + var s1 = stream(opts) + // convert to the emit-stream form + s1.on('data', v => { + s2.push(['data', v]) + }) + s1.on('error', v => s2.push(['error', v])) + s1.on('close', v => { + s2.push(['close', v]) + s2.destroy() + }) + s2.on('close', () => s1.destroy()) + return s2 + }, + async listDaemonLog () { + var file = await new Promise((resolve, reject) => { + pump( + fs.createReadStream(join(os.homedir(), '.hyperdrive/log.json'), {start: 0, end: 1e6, encoding: 'utf8'}), + concat(resolve), + reject + ) + }) + return file.split('\n').map(line => { + try { return JSON.parse(line) } + catch (e) { return undefined } + }).filter(Boolean) + } +} + +// internal methods +// = + +function massageFilters (filter) { + if (filter && typeof filter === 'object') { + // make each filter an array + for (let k in filter) { + filter[k] = Array.isArray(filter[k]) ? filter[k] : [filter[k]] + } + } else { + filter = false + } + return filter +} + +function getLogPath (num) { + if (num) return logPath + '.' + num + return logPath +} + +async function rotateLogFile (num) { + try { + var p = getLogPath(num) + var info = await jetpack.inspectAsync(p) + if (info && info.type === 'file') { + await jetpack.moveAsync(p, getLogPath(num + 1)) + } + } catch (err) { + console.error('rotateLogFile failed', num, err) + } +} + +async function retireLogFile (num) { + try { + var p = getLogPath(num) + var info = await jetpack.inspectAsync(p) + if (info && info.type === 'file') { + await jetpack.removeAsync(p) + } + } catch (err) { + console.error('retireLogFile failed', num, err) + } +} + +/** + * @param {any} readStream + * @param {LogQueryOpts} opts + * @returns {any} + */ +function readPipeline (readStream, opts, nosplit = false) { + var beforeOffset = 0 + var beforeLimit = 0 + var offset = opts.offset || 0 + var limit = opts.limit + var filter = massageFilters(opts.filter) + return pump([ + readStream, + nosplit ? undefined : split2(), + through2.obj(function (row, enc, cb) { + if (!row || !row.trim()) { + // skip empty + return cb() + } + + // offset filter + if (beforeOffset < offset) { + beforeOffset++ + return cb() + } + + // parse + row = JSON.parse(row) + + // timestamp range filter + var ts = (opts.since || opts.until) ? (new Date(row.timestamp)).getTime() : null + if ('since' in opts && ts < opts.since) return cb() + if ('until' in opts && ts > opts.until) return cb() + + // general string filters + if (filter) { + for (let k in filter) { + if (!filter[k].includes(row[k])) return cb() + } + } + + // emit + if (!limit || beforeLimit < limit) this.push(row) + if (limit && ++beforeLimit === limit) readStream.destroy() + cb() + }) + ].filter(Boolean)) +} \ No newline at end of file diff --git a/app/background-process/nat-port-forwarder.js b/app/bg/nat-port-forwarder.js similarity index 84% rename from app/background-process/nat-port-forwarder.js rename to app/bg/nat-port-forwarder.js index 0aac1bd3cf..3c81e00811 100644 --- a/app/background-process/nat-port-forwarder.js +++ b/app/bg/nat-port-forwarder.js @@ -1,8 +1,6 @@ -import * as beakerCore from '@beaker/core' -//import { app } from 'electron' import ms from 'ms' import natUpnp from 'nat-upnp' -import {DAT_SWARM_PORT} from '@beaker/core/lib/const' +import { DAT_SWARM_PORT } from '../lib/const' // exported methods // = @@ -33,7 +31,6 @@ async function openPort () { var to = setTimeout(openPort, ms('30m')) to.unref() } else { - console.log(err) // assuming errorCode 725 OnlyPermanentLeasesSupported and retry without a TTL opts.ttl = '0' // string not int client.portMapping(opts) diff --git a/app/background-process/open-url.js b/app/bg/open-url.js similarity index 84% rename from app/background-process/open-url.js rename to app/bg/open-url.js index 11a4f2a688..64e73698c6 100644 --- a/app/background-process/open-url.js +++ b/app/bg/open-url.js @@ -1,7 +1,7 @@ // handle OSX open-url event import {BrowserWindow, ipcMain} from 'electron' import * as windows from './ui/windows' -import * as viewManager from './ui/view-manager' +import * as tabManager from './ui/tab-manager' var queue = [] var isLoaded = false var isSetup = false @@ -11,7 +11,7 @@ export function setup () { isSetup = true ipcMain.on('shell-window:ready', function (e) { var win = BrowserWindow.fromWebContents(e.sender) - queue.forEach(url => viewManager.create(win, url)) + queue.forEach(url => tabManager.create(win, url)) queue.length = 0 isLoaded = true }) @@ -22,7 +22,7 @@ export function open (url, opts = {}) { var win = windows.getActiveWindow() if (isLoaded && win) { // send command now - viewManager.create(win, url, opts) + tabManager.create(win, url, opts) win.show() } else { // queue for later diff --git a/app/bg/protocols/asset.js b/app/bg/protocols/asset.js new file mode 100644 index 0000000000..3cc2e86bf6 --- /dev/null +++ b/app/bg/protocols/asset.js @@ -0,0 +1,164 @@ +/** + * asset:{type}{-dimension?}:{url} + * + * Helper protocol to serve site favicons and avatars from the cache. + * Examples: + * + * - asset:favicon:hyper://beakerbrowser.com + * - asset:favicon-32:hyper://beakerbrowser.com + * - asset:thumb:hyper://beakerbrowser.com + * - asset:cover:hyper://beakerbrowser.com + **/ + +import { screen, nativeImage } from 'electron' +import * as sitedata from '../dbs/sitedata' +import { capturePage } from '../browser' +import fs from 'fs' +import path from 'path' + +const NOT_FOUND = -6 // TODO I dont think this is the right code -prf + +var handler +var activeCaptures = {} // [url] => Promise + +export function setup () { + var DEFAULTS = { + favicon: {type: 'image/png', data: NOT_FOUND}, + thumb: {type: 'image/jpeg', data: NOT_FOUND}, + cover: {type: 'image/jpeg', data: NOT_FOUND}, + screenshot: {type: 'image/png', data: NOT_FOUND} + } + + // load defaults + fs.readFile(path.join(__dirname, './assets/img/favicons/default.png'), (err, buf) => { + if (err) { console.error('Failed to load default favicon', path.join(__dirname, './assets/img/default-favicon.png'), err) } + if (buf) { DEFAULTS.favicon.data = buf } + }) + fs.readFile(path.join(__dirname, './assets/img/default-screenshot.jpg'), (err, buf) => { + if (err) { console.error('Failed to load default thumb', path.join(__dirname, './assets/img/default-screenshot.jpg'), err) } + if (buf) { + DEFAULTS.thumb.data = buf + DEFAULTS.screenshot.data = buf + } + }) + fs.readFile(path.join(__dirname, './assets/img/default-cover.jpg'), (err, buf) => { + if (err) { console.error('Failed to load default cover', path.join(__dirname, './assets/img/default-cover.jpg'), err) } + if (buf) { DEFAULTS.cover.data = buf } + }) + + // detect if is retina + let display = screen.getPrimaryDisplay() + const isRetina = display.scaleFactor >= 2 + + // register favicon protocol + handler = async (request, cb) => { + // parse the URL + let {asset, url, size} = parseAssetUrl(request.url) + if (isRetina) { + size *= 2 + } + + // validate + if (asset !== 'favicon' && asset !== 'thumb' && asset !== 'cover' && asset !== 'screenshot') { + return cb({data: NOT_FOUND}) + } + + // hardcoded assets + if (asset !== 'screenshot' && url.startsWith('beaker://')) { + let name = /beaker:\/\/([^\/]+)/.exec(url)[1] + return servePng(path.join(__dirname, `./assets/img/favicons/${name}.png`), DEFAULTS[asset], cb) + } + + try { + // look up in db + let data + if (asset === 'screenshot') { + data = await sitedata.get(url, 'screenshot', {dontExtractOrigin: true, normalizeUrl: true}) + if (!data && !url.startsWith('dat:')) { + // try to fetch the screenshot + let p = activeCaptures[url] + if (!p) { + p = activeCaptures[url] = capturePage(url) + } + let nativeImg = await p + delete activeCaptures[url] + if (nativeImg) { + data = nativeImg.toDataURL() + await sitedata.set(url, 'screenshot', data, {dontExtractOrigin: true, normalizeUrl: true}) + } else { + return serveJpg(path.join(__dirname, `./assets/img/default-screenshot.jpg`), DEFAULTS[asset], cb) + } + } + } else { + data = await sitedata.get(url, asset) + if (!data && asset === 'thumb') { + // try fallback to screenshot + data = await sitedata.get(url, 'screenshot', {dontExtractOrigin: true, normalizeUrl: true}) + if (!data) { + // try fallback to favicon + data = await sitedata.get(url, 'favicon') + } + } + if (!data && asset === 'favicon') { + // try fallback to thumb + data = await sitedata.get(url, 'thumb') + } + } + if (data) { + if (size) { + let img = nativeImage.createFromDataURL(data) + data = img.resize({width: size}).toDataURL() + } + + // `data` is a data url ('data:image/png;base64,...') + // so, skip the beginning and pull out the data + let parts = data.split(',') + let mimeType = /data:([^;]+);base64/.exec(parts[0])[1] + data = parts[1] + if (data) { + return cb({ mimeType, data: Buffer.from(data, 'base64') }) + } + } + } catch (e) { + // ignore + console.log(e) + } + + cb(DEFAULTS[asset]) + } +} + +export function register (protocol) { + protocol.registerBufferProtocol('asset', handler) +} + +const ASSET_URL_RE = /^asset:([a-z]+)(-\d+)?:(.*)/ +function parseAssetUrl (str) { + const match = ASSET_URL_RE.exec(str) + var url + try { + let urlp = new URL(match[3]) + url = urlp.protocol + '//' + urlp.hostname + urlp.pathname + } catch (e) { + url = match[3] + } + return { + asset: match[1], + size: Math.abs(Number(match[2])), + url + } +} + +function servePng (p, fallback, cb) { + return fs.readFile(p, (err, buf) => { + if (buf) cb({mimeType: 'image/png', data: buf}) + else cb(fallback) + }) +} + +function serveJpg (p, fallback, cb) { + return fs.readFile(p, (err, buf) => { + if (buf) cb({mimeType: 'image/jpeg', data: buf}) + else cb(fallback) + }) +} \ No newline at end of file diff --git a/app/background-process/protocols/beaker.js b/app/bg/protocols/beaker.js similarity index 53% rename from app/background-process/protocols/beaker.js rename to app/bg/protocols/beaker.js index 1027c6a52b..adc6c11747 100644 --- a/app/background-process/protocols/beaker.js +++ b/app/bg/protocols/beaker.js @@ -1,8 +1,6 @@ -import {protocol} from 'electron' -import * as beakerCore from '@beaker/core' -import errorPage from '@beaker/core/lib/error-page' -import * as mime from '@beaker/core/lib/mime' -const {archivesDebugPage, datDnsCachePage, datDnsCacheJS} = beakerCore.dat.debug +import errorPage from '../lib/error-page' +import * as mime from '../lib/mime' +import { drivesDebugPage, datDnsCachePage, datDnsCacheJS } from '../hyper/debugging' import path from 'path' import url from 'url' import once from 'once' @@ -11,43 +9,52 @@ import jetpack from 'fs-jetpack' import intoStream from 'into-stream' import ICO from 'icojs' -const START_APP_PATH = path.dirname(require.resolve('@beaker/start-app')).replace('app.asar', 'app.asar.unpacked') -const LIBRARY_APP_PATH = path.dirname(require.resolve('@beaker/library-app')).replace('app.asar', 'app.asar.unpacked') -const SEARCH_APP_PATH = path.dirname(require.resolve('@beaker/search-app')).replace('app.asar', 'app.asar.unpacked') -const SIDEBAR_APP_PATH = path.dirname(require.resolve('@beaker/sidebar-app')).replace('app.asar', 'app.asar.unpacked') - // constants // = // content security policies const BEAKER_CSP = ` default-src 'self' beaker:; - img-src beaker-favicon: beaker: asset: data: dat: http: https; + img-src beaker: asset: data: hyper: http: https; script-src 'self' beaker: 'unsafe-eval'; - media-src 'self' beaker: dat:; + media-src 'self' beaker: hyper:; style-src 'self' 'unsafe-inline' beaker:; child-src 'self'; `.replace(/\n/g, '') +const BEAKER_DESKTOP_CSP = ` + default-src 'self' beaker:; + img-src beaker: asset: data: hyper: http: https; + script-src 'self' beaker: 'unsafe-eval'; + media-src 'self' beaker: hyper:; + style-src 'self' 'unsafe-inline' beaker:; + child-src 'self' hyper:; +`.replace(/\n/g, '') +const SIDEBAR_CSP = ` +default-src 'self' beaker:; +img-src beaker: asset: data: hyper: http: https; +script-src 'self' beaker: hyper: blob: 'unsafe-eval'; +media-src 'self' beaker: hyper:; +style-src 'self' 'unsafe-inline' beaker:; +child-src 'self' http://dev.hyperdrive.network:5000 https://hyperdrive.network; +`.replace(/\n/g, '') // exported api // = -export function setup () { +export function register (protocol) { // setup the protocol handler - protocol.registerStreamProtocol('beaker', beakerProtocol, err => { - if (err) throw new Error('Failed to create protocol: beaker. ' + err) - }) + protocol.registerStreamProtocol('beaker', beakerProtocol) } // internal methods // = async function beakerProtocol (request, respond) { - var cb = once((statusCode, status, contentType, path) => { + var cb = once((statusCode, status, contentType, path, CSP) => { const headers = { 'Cache-Control': 'no-cache', 'Content-Type': (contentType || 'text/html; charset=utf-8'), - 'Content-Security-Policy': BEAKER_CSP, + 'Content-Security-Policy': CSP || BEAKER_CSP, 'Access-Control-Allow-Origin': '*' } if (typeof path === 'string') { @@ -91,30 +98,32 @@ async function beakerProtocol (request, respond) { if (i !== -1) requestUrl = requestUrl.slice(0, i) } + // redirects from old pages + if (requestUrl.startsWith('beaker://start/')) { + return cb(200, 'OK', 'text/html', () => ``) + } + // browser ui if (requestUrl === 'beaker://shell-window/') { - return cb(200, 'OK', 'text/html; charset=utf-8', path.join(__dirname, 'shell-window.html')) + return cb(200, 'OK', 'text/html; charset=utf-8', path.join(__dirname, 'fg', 'shell-window', 'index.html')) } if (requestUrl === 'beaker://shell-window/main.js') { - return cb(200, 'OK', 'application/javascript; charset=utf-8', path.join(__dirname, 'shell-window.build.js')) - } - if (requestUrl === 'beaker://shell-window/main.css') { - return cb(200, 'OK', 'text/css; charset=utf-8', path.join(__dirname, 'stylesheets/shell-window.css')) + return cb(200, 'OK', 'application/javascript; charset=utf-8', path.join(__dirname, 'fg', 'shell-window', 'index.build.js')) } if (requestUrl === 'beaker://location-bar/') { - return cb(200, 'OK', 'text/html; charset=utf-8', path.join(__dirname, 'location-bar.html')) + return cb(200, 'OK', 'text/html; charset=utf-8', path.join(__dirname, 'fg', 'location-bar', 'index.html')) } if (requestUrl === 'beaker://shell-menus/') { - return cb(200, 'OK', 'text/html; charset=utf-8', path.join(__dirname, 'shell-menus.html')) + return cb(200, 'OK', 'text/html; charset=utf-8', path.join(__dirname, 'fg', 'shell-menus', 'index.html')) } if (requestUrl === 'beaker://prompts/') { - return cb(200, 'OK', 'text/html; charset=utf-8', path.join(__dirname, 'prompts.html')) + return cb(200, 'OK', 'text/html; charset=utf-8', path.join(__dirname, 'fg', 'prompts', 'index.html')) } if (requestUrl === 'beaker://perm-prompt/') { - return cb(200, 'OK', 'text/html; charset=utf-8', path.join(__dirname, 'perm-prompt.html')) + return cb(200, 'OK', 'text/html; charset=utf-8', path.join(__dirname, 'fg', 'perm-prompt', 'index.html')) } if (requestUrl === 'beaker://modals/') { - return cb(200, 'OK', 'text/html; charset=utf-8', path.join(__dirname, 'modals.html')) + return cb(200, 'OK', 'text/html; charset=utf-8', path.join(__dirname, 'fg', 'modals', 'index.html')) } if (requestUrl === 'beaker://assets/syntax-highlight.js') { return cb(200, 'OK', 'application/javascript; charset=utf-8', path.join(__dirname, 'assets/js/syntax-highlight.js')) @@ -123,36 +132,36 @@ async function beakerProtocol (request, respond) { return cb(200, 'OK', 'text/css; charset=utf-8', path.join(__dirname, 'assets/css/syntax-highlight.css')) } if (requestUrl === 'beaker://assets/icons.css') { - return cb(200, 'OK', 'text/css; charset=utf-8', path.join(__dirname, 'stylesheets/icons.css')) + return cb(200, 'OK', 'text/css; charset=utf-8', path.join(__dirname, 'fg/builtin-pages/stylesheets/icons.css')) } if (requestUrl === 'beaker://assets/font-awesome.css') { - return cb(200, 'OK', 'text/css; charset=utf-8', path.join(__dirname, 'stylesheets/fonts/font-awesome/css/all.min.css')) + return cb(200, 'OK', 'text/css; charset=utf-8', path.join(__dirname, 'assets/css/fa-all.min.css')) } - if (requestUrl === 'beaker://assets/fa-regular-400.woff2') { + if (requestUrl === 'beaker://assets/webfonts/fa-regular-400.woff2') { return cb(200, 'OK', 'text/css; charset=utf-8', path.join(__dirname, 'assets/fonts/fa-regular-400.woff2')) } - if (requestUrl === 'beaker://assets/fa-regular-400.woff') { + if (requestUrl === 'beaker://assets/webfonts/fa-regular-400.woff') { return cb(200, 'OK', 'text/css; charset=utf-8', path.join(__dirname, 'assets/fonts/fa-regular-400.woff')) } - if (requestUrl === 'beaker://assets/fa-regular-400.svg') { + if (requestUrl === 'beaker://assets/webfonts/fa-regular-400.svg') { return cb(200, 'OK', 'text/css; charset=utf-8', path.join(__dirname, 'assets/fonts/fa-regular-400.svg')) } - if (requestUrl === 'beaker://assets/fa-solid-900.woff2') { + if (requestUrl === 'beaker://assets/webfonts/fa-solid-900.woff2') { return cb(200, 'OK', 'text/css; charset=utf-8', path.join(__dirname, 'assets/fonts/fa-solid-900.woff2')) } - if (requestUrl === 'beaker://assets/fa-solid-900.woff') { + if (requestUrl === 'beaker://assets/webfonts/fa-solid-900.woff') { return cb(200, 'OK', 'text/css; charset=utf-8', path.join(__dirname, 'assets/fonts/fa-solid-900.woff')) } - if (requestUrl === 'beaker://assets/fa-solid-900.svg') { + if (requestUrl === 'beaker://assets/webfonts/fa-solid-900.svg') { return cb(200, 'OK', 'text/css; charset=utf-8', path.join(__dirname, 'assets/fonts/fa-solid-900.svg')) } - if (requestUrl === 'beaker://assets/fa-brands-400.woff2') { + if (requestUrl === 'beaker://assets/webfonts/fa-brands-400.woff2') { return cb(200, 'OK', 'text/css; charset=utf-8', path.join(__dirname, 'assets/fonts/fa-brands-400.woff2')) } - if (requestUrl === 'beaker://assets/fa-brands-400.woff') { + if (requestUrl === 'beaker://assets/webfonts/fa-brands-400.woff') { return cb(200, 'OK', 'text/css; charset=utf-8', path.join(__dirname, 'assets/fonts/fa-brands-400.woff')) } - if (requestUrl === 'beaker://assets/fa-brands-400.svg') { + if (requestUrl === 'beaker://assets/webfonts/fa-brands-400.svg') { return cb(200, 'OK', 'text/css; charset=utf-8', path.join(__dirname, 'assets/fonts/fa-brands-400.svg')) } if (requestUrl === 'beaker://assets/font-photon-entypo') { @@ -164,6 +173,9 @@ async function beakerProtocol (request, respond) { if (requestUrl === 'beaker://assets/font-source-sans-pro-le') { return cb(200, 'OK', 'application/font-woff2', path.join(__dirname, 'assets/fonts/source-sans-pro-le.woff2')) } + if (requestUrl === 'beaker://assets/logo-black.svg') { + return cb(200, 'OK', 'image/svg+xml', path.join(__dirname, 'assets/img/logo-black.svg')) + } if (requestUrl.startsWith('beaker://assets/logo2')) { return cb(200, 'OK', 'image/png', path.join(__dirname, 'assets/img/logo2.png')) } @@ -173,6 +185,13 @@ async function beakerProtocol (request, respond) { if (requestUrl.startsWith('beaker://assets/default-user-thumb')) { return cb(200, 'OK', 'image/jpeg', path.join(__dirname, 'assets/img/default-user-thumb.jpg')) } + if (requestUrl.startsWith('beaker://setup/default-user-thumb')) { + // rehost under beaker://setup because there's a CSP bug stopping beaker://setup from accessing beaker://assets + return cb(200, 'OK', 'image/jpeg', path.join(__dirname, 'assets/img/default-user-thumb.jpg')) + } + if (requestUrl.startsWith('beaker://assets/default-frontend-thumb')) { + return cb(200, 'OK', 'image/jpeg', path.join(__dirname, 'assets/img/default-frontend-thumb.jpg')) + } if (requestUrl.startsWith('beaker://assets/search-icon-large')) { return cb(200, 'OK', 'image/jpeg', path.join(__dirname, 'assets/img/search-icon-large.png')) } @@ -186,68 +205,102 @@ async function beakerProtocol (request, respond) { let imgPath = requestUrl.slice('beaker://assets/img/templates/'.length) return cb(200, 'OK', 'image/png', path.join(__dirname, `assets/img/templates/${imgPath}`)) } + if (requestUrl.startsWith('beaker://assets/img/frontends/')) { + let imgPath = requestUrl.slice('beaker://assets/img/frontends/'.length) + return cb(200, 'OK', 'image/png', path.join(__dirname, `assets/img/frontends/${imgPath}`)) + } + if (requestUrl.startsWith('beaker://assets/img/drive-types/')) { + let imgPath = requestUrl.slice('beaker://assets/img/drive-types/'.length) + return cb(200, 'OK', 'image/png', path.join(__dirname, `assets/img/drive-types/${imgPath}`)) + } - // builtin pages - if (requestUrl === 'beaker://assets/builtin-pages.css') { - return cb(200, 'OK', 'text/css; charset=utf-8', path.join(__dirname, 'stylesheets/builtin-pages.css')) + // userland + if (requestUrl === 'beaker://app-stdlib' || requestUrl.startsWith('beaker://app-stdlib/')) { + return serveAppAsset(requestUrl, path.join(__dirname, 'userland', 'app-stdlib'), cb) } - if (requestUrl.startsWith('beaker://assets/img/onboarding/')) { - let imgPath = requestUrl.slice('beaker://assets/img/onboarding/'.length) - return cb(200, 'OK', 'image/svg+xml', path.join(__dirname, `assets/img/onboarding/${imgPath}`)) + if (requestUrl === 'beaker://diff' || requestUrl.startsWith('beaker://diff/')) { + return serveAppAsset(requestUrl, path.join(__dirname, 'userland', 'diff'), cb) } - if (requestUrl === 'beaker://start' || requestUrl.startsWith('beaker://start/')) { - return serveAppAsset(requestUrl, START_APP_PATH, cb, {fallbackToIndexHTML: true}) + if (requestUrl === 'beaker://library' || requestUrl.startsWith('beaker://library/')) { + return serveAppAsset(requestUrl, path.join(__dirname, 'userland', 'library'), cb, {fallbackToIndexHTML: true}) + } + if (requestUrl === 'beaker://drive-view' || requestUrl.startsWith('beaker://drive-view/')) { + return serveAppAsset(requestUrl, path.join(__dirname, 'userland', 'drive-view'), cb) + } + if (requestUrl === 'beaker://cmd-pkg' || requestUrl.startsWith('beaker://cmd-pkg/')) { + return serveAppAsset(requestUrl, path.join(__dirname, 'userland', 'cmd-pkg'), cb) + } + if (requestUrl === 'beaker://std-cmds' || requestUrl.startsWith('beaker://std-cmds/')) { + return serveAppAsset(requestUrl, path.join(__dirname, 'userland', 'std-cmds'), cb) + } + if (requestUrl === 'beaker://site-info' || requestUrl.startsWith('beaker://site-info/')) { + return serveAppAsset(requestUrl, path.join(__dirname, 'userland', 'site-info'), cb, {fallbackToIndexHTML: true}) + } + if (requestUrl === 'beaker://setup' || requestUrl.startsWith('beaker://setup/')) { + return serveAppAsset(requestUrl, path.join(__dirname, 'userland', 'setup'), cb, {fallbackToIndexHTML: true}) + } + if (requestUrl === 'beaker://init' || requestUrl.startsWith('beaker://init/')) { + return serveAppAsset(requestUrl, path.join(__dirname, 'userland', 'init'), cb, {fallbackToIndexHTML: true}) } if (requestUrl === 'beaker://sidebar' || requestUrl.startsWith('beaker://sidebar/')) { - return serveAppAsset(requestUrl, SIDEBAR_APP_PATH, cb, {fallbackToIndexHTML: true}) + return serveAppAsset(requestUrl, path.join(__dirname, 'userland', 'sidebar'), cb, { + fallbackToIndexHTML: true, + CSP: SIDEBAR_CSP + }) } - if (requestUrl === 'beaker://library' || requestUrl.startsWith('beaker://library/')) { - return serveAppAsset(requestUrl, LIBRARY_APP_PATH, cb) + if (requestUrl === 'beaker://editor' || requestUrl.startsWith('beaker://editor/')) { + return serveAppAsset(requestUrl, path.join(__dirname, 'userland', 'editor'), cb, {fallbackToIndexHTML: true}) + } + if (requestUrl === 'beaker://webterm' || requestUrl.startsWith('beaker://webterm/')) { + return serveAppAsset(requestUrl, path.join(__dirname, 'userland', 'webterm'), cb, { + fallbackToIndexHTML: true, + CSP: SIDEBAR_CSP + }) } - if (requestUrl === 'beaker://search' || requestUrl.startsWith('beaker://search/')) { - return serveAppAsset(requestUrl, SEARCH_APP_PATH, cb) + if (requestUrl === 'beaker://desktop' || requestUrl.startsWith('beaker://desktop/')) { + return serveAppAsset(requestUrl, path.join(__dirname, 'userland', 'desktop'), cb, { + CSP: BEAKER_DESKTOP_CSP, + fallbackToIndexHTML: true, + }) } - if (requestUrl === 'beaker://history/') { - return cb(200, 'OK', 'text/html; charset=utf-8', path.join(__dirname, 'builtin-pages/history.html')) + if (requestUrl === 'beaker://history' || requestUrl.startsWith('beaker://history/')) { + return serveAppAsset(requestUrl, path.join(__dirname, 'userland', 'history'), cb) } - if (requestUrl === 'beaker://history/main.js') { - return cb(200, 'OK', 'application/javascript; charset=utf-8', path.join(__dirname, 'builtin-pages/build/history.build.js')) + if (requestUrl === 'beaker://settings' || requestUrl.startsWith('beaker://settings/')) { + return serveAppAsset(requestUrl, path.join(__dirname, 'userland', 'settings'), cb) } - if (requestUrl === 'beaker://downloads/') { - return cb(200, 'OK', 'text/html; charset=utf-8', path.join(__dirname, 'builtin-pages/downloads.html')) + + // builtin pages + if (requestUrl === 'beaker://assets/builtin-pages.css') { + return cb(200, 'OK', 'text/css; charset=utf-8', path.join(__dirname, 'fg/builtin-pages/stylesheets/builtin-pages.css')) } - if (requestUrl === 'beaker://downloads/main.js') { - return cb(200, 'OK', 'application/javascript; charset=utf-8', path.join(__dirname, 'builtin-pages/build/downloads.build.js')) + if (requestUrl.startsWith('beaker://assets/img/onboarding/')) { + let imgPath = requestUrl.slice('beaker://assets/img/onboarding/'.length) + return cb(200, 'OK', 'image/png', path.join(__dirname, `assets/img/onboarding/${imgPath}`)) } if (requestUrl === 'beaker://swarm-debugger/main.css') { - return cb(200, 'OK', 'text/css; charset=utf-8', path.join(__dirname, 'stylesheets/builtin-pages/swarm-debugger.css')) + return cb(200, 'OK', 'text/css; charset=utf-8', path.join(__dirname, 'fg/builtin-pages/stylesheets/builtin-pages/swarm-debugger.css')) } if (requestUrl === 'beaker://swarm-debugger/main.js') { - return cb(200, 'OK', 'application/javascript; charset=utf-8', path.join(__dirname, 'builtin-pages/build/swarm-debugger.build.js')) + return cb(200, 'OK', 'application/javascript; charset=utf-8', path.join(__dirname, 'fg/builtin-pages/build/swarm-debugger.build.js')) } if (requestUrl === 'beaker://swarm-debugger/' || requestUrl.startsWith('beaker://swarm-debugger/')) { - return cb(200, 'OK', 'text/html; charset=utf-8', path.join(__dirname, 'builtin-pages/swarm-debugger.html')) - } - if (requestUrl === 'beaker://settings/') { - return cb(200, 'OK', 'text/html; charset=utf-8', path.join(__dirname, 'builtin-pages/settings.html')) - } - if (requestUrl === 'beaker://settings/main.js') { - return cb(200, 'OK', 'application/javascript; charset=utf-8', path.join(__dirname, 'builtin-pages/build/settings.build.js')) + return cb(200, 'OK', 'text/html; charset=utf-8', path.join(__dirname, 'fg/builtin-pages/swarm-debugger.html')) } if (requestUrl === 'beaker://watchlist/main.css') { - return cb(200, 'OK', 'text/css; charset=utf-8', path.join(__dirname, 'stylesheets/builtin-pages/watchlist.css')) + return cb(200, 'OK', 'text/css; charset=utf-8', path.join(__dirname, 'fg/builtin-pages/stylesheets/builtin-pages/watchlist.css')) } if (requestUrl === 'beaker://watchlist/main.js') { - return cb(200, 'OK', 'application/javascript; charset=utf-8', path.join(__dirname, 'builtin-pages/build/watchlist.build.js')) + return cb(200, 'OK', 'application/javascript; charset=utf-8', path.join(__dirname, 'fg/builtin-pages/build/watchlist.build.js')) } if (requestUrl === 'beaker://watchlist/') { - return cb(200, 'OK', 'text/html; charset=utf-8', path.join(__dirname, 'builtin-pages/watchlist.html')) + return cb(200, 'OK', 'text/html; charset=utf-8', path.join(__dirname, 'fg/builtin-pages/watchlist.html')) } if (requestUrl === 'beaker://editor/main.css') { - return cb(200, 'OK', 'text/css; charset=utf-8', path.join(__dirname, 'stylesheets/builtin-pages/editor.css')) + return cb(200, 'OK', 'text/css; charset=utf-8', path.join(__dirname, 'fg/builtin-pages/stylesheets/builtin-pages/editor.css')) } if (requestUrl === 'beaker://editor/main.js') { - return cb(200, 'OK', 'application/javascript; charset=utf-8', path.join(__dirname, 'builtin-pages/build/editor.build.js')) + return cb(200, 'OK', 'application/javascript; charset=utf-8', path.join(__dirname, 'fg/builtin-pages/build/editor.build.js')) } if (requestUrl === 'beaker://assets/monaco.js') { return cb(200, 'OK', 'application/javascript; charset=utf-8', path.join(__dirname, 'assets/js/editor/monaco.js')) @@ -261,12 +314,12 @@ async function beakerProtocol (request, respond) { return cb(200, 'OK', 'text/css; charset=utf-8', path.join(__dirname, `assets/js/editor/vs/${filePath}`)) } if (requestUrl.startsWith('beaker://editor/')) { - return cb(200, 'OK', 'text/html; charset=utf-8', path.join(__dirname, 'builtin-pages/editor.html')) + return cb(200, 'OK', 'text/html; charset=utf-8', path.join(__dirname, 'fg/builtin-pages/editor.html')) } // debugging - if (requestUrl === 'beaker://internal-archives/') { - return cb(200, 'OK', 'text/html; charset=utf-8', archivesDebugPage) + if (requestUrl === 'beaker://active-drives/') { + return cb(200, 'OK', 'text/html; charset=utf-8', drivesDebugPage) } if (requestUrl === 'beaker://dat-dns-cache/') { return cb(200, 'OK', 'text/html; charset=utf-8', datDnsCachePage) @@ -274,31 +327,32 @@ async function beakerProtocol (request, respond) { if (requestUrl === 'beaker://dat-dns-cache/main.js') { return cb(200, 'OK', 'application/javascript; charset=utf-8', datDnsCacheJS) } - if (requestUrl.startsWith('beaker://debug-log/')) { - const PAGE_SIZE = 1e6 - var start = queryParams.start ? (+queryParams.start) : 0 - let content = await beakerCore.getLogFileContent(start, start + PAGE_SIZE) - var pagination = `

    Showing bytes ${start} - ${start + PAGE_SIZE}. Next page

    ` - return respond({ - statusCode: 200, - headers: { - 'Content-Type': 'text/html; charset=utf-8', - 'Content-Security-Policy': BEAKER_CSP, - 'Access-Control-Allow-Origin': '*' - }, - data: intoStream(` - ${pagination} -
    ${content.replace(//g, '>')}
    - ${pagination} - `) - }) - } + // TODO replace? + // if (requestUrl.startsWith('beaker://debug-log/')) { + // const PAGE_SIZE = 1e6 + // var start = queryParams.start ? (+queryParams.start) : 0 + // let content = await beakerCore.getLogFileContent(start, start + PAGE_SIZE) + // var pagination = `

    Showing bytes ${start} - ${start + PAGE_SIZE}. Next page

    ` + // return respond({ + // statusCode: 200, + // headers: { + // 'Content-Type': 'text/html; charset=utf-8', + // 'Content-Security-Policy': BEAKER_CSP, + // 'Access-Control-Allow-Origin': '*' + // }, + // data: intoStream(` + // ${pagination} + //
    ${content.replace(//g, '>')}
    + // ${pagination} + // `) + // }) + // } return cb(404, 'Not Found') } // helper to serve requests to app packages -async function serveAppAsset (requestUrl, dirPath, cb, {fallbackToIndexHTML} = {fallbackToIndexHTML: false}) { +async function serveAppAsset (requestUrl, dirPath, cb, {CSP, fallbackToIndexHTML} = {CSP: undefined, fallbackToIndexHTML: false}) { // resolve the file path const urlp = new URL(requestUrl) var pathname = urlp.pathname @@ -322,5 +376,5 @@ async function serveAppAsset (requestUrl, dirPath, cb, {fallbackToIndexHTML} = { var contentType = mime.identify(filepath) // serve - cb(200, 'OK', contentType, filepath) + cb(200, 'OK', contentType, filepath, CSP) } \ No newline at end of file diff --git a/app/bg/protocols/dat.js b/app/bg/protocols/dat.js new file mode 100644 index 0000000000..08d8a9d66f --- /dev/null +++ b/app/bg/protocols/dat.js @@ -0,0 +1,77 @@ +import { getStoragePathFor, downloadDat } from '../dat/index' +import { URL } from 'url' +import datDns from '../dat/dns' +import { promises as fsp } from 'fs' + +/** + * HACK + * Electron has an issue that's causing file read streams to fail to serve + * Reading into memory seems to resolve the issue + * https://github.com/electron/electron/issues/21018 + * -prf + */ +import { PassThrough } from 'stream' +function intoStream (text) { + const rv = new PassThrough() + rv.push(text) + rv.push(null) + return rv +} + +// exported api +// = + +export function register (protocol) { + protocol.registerStreamProtocol('dat', electronHandler) +} + +export const electronHandler = async function (request, respond) { + try { + var urlp = new URL(request.url) + var key = await datDns.resolveName(urlp.hostname) + + let path = getStoragePathFor (key) + await downloadDat(key) + + var files = await fsp.readdir(path) + + respond({ + statusCode: 200, + headers: {'Content-Type': 'text/html'}, + data: intoStream(` + + + +

    Dat legacy converter

    +

    Dat has been upgraded to the Hypercore Protocol. Old dat archives are no longer compatible with Beaker.

    +

    You can convert this dat into a Hyperdrive to continue using its content.

    +

    +

    Files

    +
    ${files.join('\n')}
    + + + + +`) + }) + } catch (e) { + console.log('failed', e) + respond({ + statusCode: 400, + headers: {'Content-Type': 'text/html'}, + data: intoStream(`

    Failed to load Dat

    ${e.toString()}
    `) + }) + } +} diff --git a/app/bg/protocols/hyper.js b/app/bg/protocols/hyper.js new file mode 100644 index 0000000000..8e357fd568 --- /dev/null +++ b/app/bg/protocols/hyper.js @@ -0,0 +1,401 @@ +import { parseDriveUrl } from '../../lib/urls' +import parseRange from 'range-parser' +import once from 'once' +import * as logLib from '../logger' +const logger = logLib.child({category: 'hyper', subcategory: 'hyper-scheme'}) +// import intoStream from 'into-stream' +import markdown from '../../lib/markdown' +import * as drives from '../hyper/drives' +import * as filesystem from '../filesystem/index' +import * as capabilities from '../hyper/capabilities' +import datServeResolvePath from '@beaker/dat-serve-resolve-path' +import errorPage from '../lib/error-page' +import * as mime from '../lib/mime' +import * as auditLog from '../dbs/audit-log' + +const md = markdown({ + allowHTML: true, + useHeadingIds: true, + useHeadingAnchors: false, + hrefMassager: undefined, + highlight: undefined +}) + +/** + * HACK + * Electron has an issue that's causing file read streams to fail to serve + * Reading into memory seems to resolve the issue + * https://github.com/electron/electron/issues/21018 + * -prf + */ +import { PassThrough } from 'stream' +function intoStream (text) { + const rv = new PassThrough() + rv.push(text) + rv.push(null) + return rv +} + +// constants +// = + +// how long till we give up? +const REQUEST_TIMEOUT_MS = 30e3 // 30 seconds + +// exported api +// = + +export function register (protocol) { + protocol.registerStreamProtocol('hyper', protocolHandler) +} + +export const protocolHandler = async function (request, respond) { + respond = once(respond) + const respondRedirect = (url) => { + respond({ + statusCode: 200, + headers: {'Content-Type': 'text/html', 'Allow-CSP-From': '*'}, + data: intoStream(``) + }) + } + const respondError = (code, status, errorPageInfo) => { + if (errorPageInfo) { + errorPageInfo.validatedURL = request.url + errorPageInfo.errorCode = code + } + var accept = request.headers.Accept || '' + if (accept.includes('text/html')) { + respond({ + statusCode: code, + headers: { + 'Content-Type': 'text/html', + 'Content-Security-Policy': "default-src 'unsafe-inline' beaker:;", + 'Access-Control-Allow-Origin': '*', + 'Allow-CSP-From': '*' + }, + data: intoStream(errorPage(errorPageInfo || (code + ' ' + status))) + }) + } else { + respond({statusCode: code}) + } + } + var fileReadStream + var headersSent = false + var drive + var cspHeader = undefined + + // validate request + var urlp = parseDriveUrl(request.url, true) + if (!urlp.host) { + return respondError(404, 'Drive Not Found', { + title: 'Site Not Found', + errorDescription: 'Invalid URL', + errorInfo: `${request.url} is an invalid hyper:// URL` + }) + } + if (request.method !== 'GET' && request.method !== 'HEAD') { + return respondError(405, 'Method Not Supported') + } + + // resolve the name + var driveKey + var driveVersion + if (urlp.host.endsWith('.cap')) { + let cap = capabilities.lookupCap(urlp.host) + if (!cap) { + return respondError(404, 'No record found for ' + urlp.host, { + errorDescription: 'Invalid capability record', + errorInfo: `No record found for hyper://${urlp.host}` + }) + } + driveKey = cap.target.key + driveVersion = cap.target.version + } else { + try { + driveKey = await drives.fromURLToKey(urlp.host, true) + driveVersion = urlp.version + } catch (err) { + return respondError(404, 'No DNS record found for ' + urlp.host, { + errorDescription: 'No DNS record found', + errorInfo: `No DNS record found for hyper://${urlp.host}` + }) + } + } + + // protect the system drive + if (filesystem.isRootUrl(`hyper://${driveKey}/`)) { + // HACK + // electron's CORS protection doesnt seem to be working + // so we're going to handle all system-drive requests by redirecting + // to the files explorer + // -prf + return respondRedirect(`https://hyperdrive.network/${urlp.host}${urlp.version ? ('+' + urlp.version) : ''}${urlp.pathname || ''}`) + } + + auditLog.record('-browser', 'serve', {url: urlp.origin, path: urlp.pathname}, undefined, async () => { + try { + // start searching the network + drive = await drives.getOrLoadDrive(driveKey) + } catch (err) { + logger.warn(`Failed to open drive ${driveKey}`, {err}) + return respondError(500, 'Failed') + } + + // parse path + var filepath = decodeURIComponent(urlp.path) + if (!filepath) filepath = '/' + if (filepath.indexOf('?') !== -1) filepath = filepath.slice(0, filepath.indexOf('?')) // strip off any query params + var hasTrailingSlash = filepath.endsWith('/') + + // checkout version if needed + try { + var {checkoutFS} = await drives.getDriveCheckout(drive, driveVersion) + } catch (err) { + logger.warn(`Failed to open drive checkout ${driveKey}`, {err}) + return respondError(500, 'Failed') + } + + // read the manifest (it's needed in a couple places) + var manifest + try { manifest = await checkoutFS.pda.readManifest() } catch (e) { manifest = null } + + // check to see if we actually have data from the drive + var version = await checkoutFS.session.drive.version() + if (version === 0) { + return respondError(404, 'Hyperdrive not found', { + title: 'Hyperdrive Not Found', + errorDescription: 'No peers hosting this drive were found', + errorInfo: 'You may still be connecting to peers - try reloading the page.' + }) + } + + // read manifest CSP + if (manifest && manifest.csp && typeof manifest.csp === 'string') { + cspHeader = manifest.csp + } + + // check for the presence of a frontend + var frontend = false + if (await checkoutFS.pda.stat('/.ui/ui.html').catch(e => false)) { + frontend = true + } + const serveFrontendHTML = async () => { + return respond({ + statusCode: 200, + headers: { + 'Content-Type': 'text/html', + 'Access-Control-Allow-Origin': '*', + 'Allow-CSP-From': '*', + 'Content-Security-Policy': cspHeader + }, + data: intoStream(await checkoutFS.pda.readFile('/.ui/ui.html')) // TODO use stream + }) + } + + // lookup entry + var statusCode = 200 + var headers = {} + var entry = await datServeResolvePath(checkoutFS.pda, manifest, urlp, request.headers.Accept) + + var canExecuteHTML = true + if (entry && !frontend) { + // dont execute HTML if in a mount and no frontend is running + let pathParts = entry.path.split('/').filter(Boolean) + pathParts.pop() // skip target, just need to check parent dirs + while (pathParts.length) { + let path = '/' + pathParts.join('/') + let stat = await checkoutFS.pda.stat(path).catch(e => undefined) + if (stat && stat.mount) { + canExecuteHTML = false + break + } + pathParts.pop() + } + } + + // handle folder + if (entry && entry.isDirectory()) { + + // make sure there's a trailing slash + if (!hasTrailingSlash) { + return respondRedirect(`hyper://${urlp.host}${urlp.version ? ('+' + urlp.version) : ''}${urlp.pathname || ''}/${urlp.search || ''}`) + } + + // frontend + if (frontend) { + return serveFrontendHTML() + } + + // directory listing + return respond({ + statusCode: 200, + headers: { + 'Content-Type': 'text/html', + 'Access-Control-Allow-Origin': '*', + 'Allow-CSP-From': '*', + 'Cache-Control': 'no-cache', + 'Content-Security-Policy': `default-src 'self' beaker:` + }, + data: intoStream(` + + + + + + +`) + }) + } + + // frontend + if (mime.acceptHeaderWantsHTML(request.headers.Accept) && frontend) { + return serveFrontendHTML() + } + + // handle not found + if (!entry) { + + // error page + return respondError(404, 'File Not Found', { + errorDescription: 'File Not Found', + errorInfo: `Beaker could not find the file ${urlp.path}`, + title: 'File Not Found' + }) + } + + // handle .goto redirects + if (entry.path.endsWith('.goto') && entry.metadata.href) { + try { + let u = new URL(entry.metadata.href) // make sure it's a valid url + return respondRedirect(entry.metadata.href) + } catch (e) { + // pass through + } + } + + // TODO + // Electron is being really aggressive about caching and not following the headers correctly + // caching is disabled till we can figure out why + // -prf + // caching if-match + // const ETag = 'block-' + entry.offset + // if (request.headers['if-none-match'] === ETag) { + // return respondError(304, 'Not Modified') + // } + + // handle range + headers['Accept-Ranges'] = 'bytes' + var range = request.headers.Range || request.headers.range + if (range) range = parseRange(entry.size, range) + if (range && range.type === 'bytes') { + range = range[0] // only handle first range given + statusCode = 206 + headers['Content-Range'] = 'bytes ' + range.start + '-' + range.end + '/' + entry.size + headers['Content-Length'] = '' + (range.end - range.start + 1) + } else { + if (entry.size) { + headers['Content-Length'] = '' + (entry.size) + } + } + + Object.assign(headers, { + 'Content-Security-Policy': cspHeader, + 'Access-Control-Allow-Origin': '*', + 'Allow-CSP-From': '*', + 'Cache-Control': 'no-cache' + }) + + // markdown rendering + if (!range && entry.path.endsWith('.md') && mime.acceptHeaderWantsHTML(request.headers.Accept)) { + let content = await checkoutFS.pda.readFile(entry.path, 'utf8') + let contentType = canExecuteHTML ? 'text/html' : 'text/plain' + content = canExecuteHTML + ? ` + + + + + + ${md.render(content)} + +` + : content + return respond({ + statusCode: 200, + headers: Object.assign(headers, { + 'Content-Type': contentType + }), + data: intoStream(content) + }) + } + + // fetch the entry and stream the response + // HACK solution until electron issue resolved -prf + headersSent = true + var mimeType = mime.identify(entry.path) + if (!canExecuteHTML && mimeType.includes('text/html')) { + mimeType = 'text/plain' + } + Object.assign(headers, {'Content-Type': mimeType}) + var data = await checkoutFS.pda.readFile(entry.path, 'binary') + if (range) { + data = data.slice(range.start, range.end + 1) + } + respond({ + statusCode, + headers, + data: intoStream(data) + }) + }) + /*fileReadStream = checkoutFS.pda.createReadStream(entry.path, range) + var dataStream = fileReadStream + .pipe(mime.identifyStream(entry.path, mimeType => { + + // disable html as needed + if (!canExecuteHTML && mimeType.includes('html')) { + mimeType = 'text/plain' + } + + // send headers, now that we can identify the data + headersSent = true + Object.assign(headers, { + 'Content-Type': mimeType + }) + // TODO + // Electron is being really aggressive about caching and not following the headers correctly + // caching is disabled till we can figure out why + // -prf + // if (ETag) { + // Object.assign(headers, {ETag}) + // } else { + // Object.assign(headers, {'Cache-Control': 'no-cache'}) + // } + + if (request.method === 'HEAD') { + dataStream.destroy() // stop reading data + respond({statusCode: 204, headers, data: intoStream('')}) + } else { + respond({statusCode, headers, data: dataStream}) + } + })) + + // handle empty files + fileReadStream.once('end', () => { + if (!headersSent) { + respond({ + statusCode: 200, + headers: { + 'Content-Security-Policy': cspHeader, + 'Access-Control-Allow-Origin': '*' + }, + data: intoStream('') + }) + } + }) + + // handle read-stream errors + fileReadStream.once('error', err => { + logger.warn('Error reading file', {url: drive.url, path: entry.path, err}) + if (!headersSent) respondError(500, 'Failed to read file') + })*/ +} diff --git a/app/background-process/protocols/intent.js b/app/bg/protocols/intent.js similarity index 90% rename from app/background-process/protocols/intent.js rename to app/bg/protocols/intent.js index 8b39a42c62..6a2466475b 100644 --- a/app/background-process/protocols/intent.js +++ b/app/bg/protocols/intent.js @@ -1,6 +1,6 @@ import {protocol} from 'electron' import querystring from 'querystring' -import errorPage from '@beaker/core/lib/error-page' +import errorPage from '../lib/error-page' import intoStream from 'into-stream' // exported api @@ -8,9 +8,7 @@ import intoStream from 'into-stream' export function setup () { // setup the protocol handler - protocol.registerStreamProtocol('intent', intentProtocol, err => { - if (err) throw new Error('Failed to create protocol: intent. ' + err) - }) + protocol.registerStreamProtocol('intent', intentProtocol) } // internal diff --git a/app/background-process/rpc-manifests/location-bar.js b/app/bg/rpc-manifests/location-bar.js similarity index 100% rename from app/background-process/rpc-manifests/location-bar.js rename to app/bg/rpc-manifests/location-bar.js diff --git a/app/background-process/rpc-manifests/modals.js b/app/bg/rpc-manifests/modals.js similarity index 100% rename from app/background-process/rpc-manifests/modals.js rename to app/bg/rpc-manifests/modals.js diff --git a/app/background-process/rpc-manifests/perm-prompt.js b/app/bg/rpc-manifests/perm-prompt.js similarity index 100% rename from app/background-process/rpc-manifests/perm-prompt.js rename to app/bg/rpc-manifests/perm-prompt.js diff --git a/app/background-process/rpc-manifests/prompts.js b/app/bg/rpc-manifests/prompts.js similarity index 51% rename from app/background-process/rpc-manifests/prompts.js rename to app/bg/rpc-manifests/prompts.js index 167c2ededc..7a9ac410ac 100644 --- a/app/background-process/rpc-manifests/prompts.js +++ b/app/bg/rpc-manifests/prompts.js @@ -1,6 +1,7 @@ export default { close: 'promise', + closeEditProfilePromptForever: 'promise', createTab: 'promise', loadURL: 'promise', - openSidebar: 'promise' + executeSidebarCommand: 'promise' } \ No newline at end of file diff --git a/app/background-process/rpc-manifests/shell-menus.js b/app/bg/rpc-manifests/shell-menus.js similarity index 62% rename from app/background-process/rpc-manifests/shell-menus.js rename to app/bg/rpc-manifests/shell-menus.js index 7ce8a14dca..90899df116 100644 --- a/app/background-process/rpc-manifests/shell-menus.js +++ b/app/bg/rpc-manifests/shell-menus.js @@ -5,5 +5,7 @@ export default { createModal: 'promise', loadURL: 'promise', resizeSelf: 'promise', - showInpageFind: 'promise' + showInpageFind: 'promise', + getWindowMenu: 'promise', + triggerWindowMenuItemById: 'promise' } \ No newline at end of file diff --git a/app/background-process/rpc-manifests/views.js b/app/bg/rpc-manifests/views.js similarity index 91% rename from app/background-process/rpc-manifests/views.js rename to app/bg/rpc-manifests/views.js index 70f88e6daf..4e99028d51 100644 --- a/app/background-process/rpc-manifests/views.js +++ b/app/bg/rpc-manifests/views.js @@ -18,7 +18,7 @@ export default { reload: 'promise', resetZoom: 'promise', toggleLiveReloading: 'promise', - toggleSidebar: 'promise', + executeSidebarCommand: 'promise', toggleDevTools: 'promise', print: 'promise', showInpageFind: 'promise', @@ -30,6 +30,8 @@ export default { runLocationBarCmd: 'promise', showMenu: 'promise', toggleMenu: 'promise', + updateMenu: 'promise', + toggleSiteInfo: 'promise', focusShellWindow: 'promise', onFaviconLoadSuccess: 'promise', onFaviconLoadError: 'promise' diff --git a/app/background-process/test-driver.js b/app/bg/test-driver.js similarity index 74% rename from app/background-process/test-driver.js rename to app/bg/test-driver.js index f293ee26ce..19ca9ec1d9 100644 --- a/app/background-process/test-driver.js +++ b/app/bg/test-driver.js @@ -1,14 +1,14 @@ import dgram from 'dgram' -import {ipcMain} from 'electron' -import * as beakerCore from '@beaker/core' +import { ipcMain } from 'electron' import * as windows from './ui/windows' -import * as viewManager from './ui/view-manager' +import * as tabManager from './ui/tab-manager' import * as permPrompt from './ui/subwindows/perm-prompt' import * as modals from './ui/subwindows/modals' +import { getEnvVar } from './lib/env' const LOG_MESSAGES = false -var testPort = +beakerCore.getEnvVar('BEAKER_TEST_DRIVER') +var testPort = +getEnvVar('BEAKER_TEST_DRIVER') var sock // exported api @@ -72,20 +72,20 @@ async function onMessage (message) { const METHODS = { newTab () { var win = getActiveWindow() - var view = viewManager.create(win, undefined, {setActive: true}) - return viewManager.getIndexOfView(win, view) + var tab = tabManager.create(win, undefined, {setActive: true}) + return tabManager.getIndexOfTab(win, tab) }, navigateTo (page, url) { - var view = viewManager.getByIndex(getActiveWindow(), page) - var loadPromise = new Promise(resolve => view.webContents.once('dom-ready', () => resolve())) - view.loadURL(url) + var tab = tabManager.getByIndex(getActiveWindow(), page) + var loadPromise = new Promise(resolve => tab.webContents.once('dom-ready', () => resolve())) + tab.loadURL(url) return loadPromise }, getUrl (page) { - var view = viewManager.getByIndex(getActiveWindow(), page) - return view.url + var tab = tabManager.getByIndex(getActiveWindow(), page) + return tab.url }, async executeJavascriptInShell (js) { @@ -95,21 +95,21 @@ const METHODS = { }, async executeJavascriptOnPage (page, js) { - var view = viewManager.getByIndex(getActiveWindow(), page) - var res = await view.webContents.executeJavaScript(js) + var tab = tabManager.getByIndex(getActiveWindow(), page) + var res = await tab.webContents.executeJavaScript(js) return res }, async executeJavascriptInPermPrompt (page, js) { - var view = viewManager.getByIndex(getActiveWindow(), page).browserView - var prompt = await waitFor(() => permPrompt.get(view)) + var tab = tabManager.getByIndex(getActiveWindow(), page).browserView + var prompt = await waitFor(() => permPrompt.get(tab)) var res = await prompt.webContents.executeJavaScript(js) return res }, async executeJavascriptInModal (page, js) { - var view = viewManager.getByIndex(getActiveWindow(), page).browserView - var modal = await waitFor(() => modals.get(view)) + var tab = tabManager.getByIndex(getActiveWindow(), page).browserView + var modal = await waitFor(() => modals.get(tab)) var res = await modal.webContents.executeJavaScript(js) return res } diff --git a/app/background-process/ui/context-menu.js b/app/bg/ui/context-menu.js similarity index 64% rename from app/background-process/ui/context-menu.js rename to app/bg/ui/context-menu.js index 6d90b1fe67..5965c914ba 100644 --- a/app/background-process/ui/context-menu.js +++ b/app/bg/ui/context-menu.js @@ -1,8 +1,18 @@ -import * as beakerCore from '@beaker/core' import { app, Menu, clipboard, BrowserWindow, dialog } from 'electron' import path from 'path' -import * as viewManager from './view-manager' +import * as tabManager from './tab-manager' +import { getAddedWindowSettings, toggleShellInterface } from './windows' import { download } from './downloads' +import { runDrivePropertiesFlow } from './util' + +// NOTE +// subtle but important!! +// the menu instance needs to be kept in the global scope +// otherwise the JS GC will kick in and clean up the menu object +// which causes the context-menu to destroy prematurely +// see https://github.com/electron/electron/issues/19424 +// -prf +var menuInstance export default function registerContextMenu () { // register the context menu on every created webContents @@ -10,22 +20,17 @@ export default function registerContextMenu () { webContents.on('context-menu', async (e, props) => { var menuItems = [] const { mediaFlags, editFlags } = props + const isHyperdrive = props.pageURL.startsWith('hyper://') const hasText = props.selectionText.trim().length > 0 const can = type => editFlags[`can${type}`] && hasText - const isDat = props.pageURL.startsWith('dat://') - const isMisspelled = props.selectionText && beakerCore.spellChecker.isMisspelled(props.selectionText) - const spellingSuggestions = isMisspelled && beakerCore.spellChecker.getSuggestions(props.selectionText).slice(0, 5) - // var isOwner = false - // if (isDat) { - // let key = await beakerCore.dat.dns.resolveName(props.pageURL) - // let archive = beakerCore.dat.library.getArchive(key) - // isOwner = archive && archive.writable - // } + const isMisspelled = false//TODOprops.selectionText && spellChecker.isMisspelled(props.selectionText) + const spellingSuggestions = false//TODOisMisspelled && spellChecker.getSuggestions(props.selectionText).slice(0, 5) // get the focused window, ignore if not available (not in focus) // - fromWebContents(webContents) doesnt seem to work, maybe because webContents is often a webview? var targetWindow = BrowserWindow.getFocusedWindow() if (!targetWindow) { return } + const addedWindowSettings = getAddedWindowSettings(targetWindow) // ignore clicks on the shell window if (props.pageURL == 'beaker://shell-window/') { return } @@ -37,17 +42,16 @@ export default function registerContextMenu () { `) // helper to run a download prompt for media - const downloadPrompt = (field, ext) => (item, win) => { + const downloadPrompt = (field, ext) => async (item, win) => { var defaultPath = path.join(app.getPath('downloads'), path.basename(props[field])) if (ext && defaultPath.split('/').pop().indexOf('.') === -1) defaultPath += ext - dialog.showSaveDialog({ title: `Save ${props.mediaType} as...`, defaultPath }, filepath => { - if (filepath) { download(win, webContents, props[field], { saveAs: filepath }) } - }) + var {filePath} = await dialog.showSaveDialog({ title: `Save ${props.mediaType} as...`, defaultPath }) + if (filePath) { download(win, webContents, props[field], { saveAs: filePath }) } } // links - if (props.linkURL && props.mediaType === 'none') { - menuItems.push({ label: 'Open Link in New Tab', click: (item, win) => viewManager.create(win, props.linkURL, {setActive: true}) }) + if (props.linkURL) { + menuItems.push({ label: 'Open Link in New Tab', click: (item, win) => tabManager.create(win, props.linkURL, {setActive: true, adjacentActive: true}) }) menuItems.push({ label: 'Copy Link Address', click: () => clipboard.writeText(props.linkURL) }) menuItems.push({ type: 'separator' }) } @@ -57,7 +61,7 @@ export default function registerContextMenu () { menuItems.push({ label: 'Save Image As...', click: downloadPrompt('srcURL') }) menuItems.push({ label: 'Copy Image', click: () => webContents.copyImageAt(props.x, props.y) }) menuItems.push({ label: 'Copy Image URL', click: () => clipboard.writeText(props.srcURL) }) - menuItems.push({ label: 'Open Image in New Tab', click: (item, win) => viewManager.create(win, props.srcURL) }) + menuItems.push({ label: 'Open Image in New Tab', click: (item, win) => tabManager.create(win, props.srcURL, {adjacentActive: true}) }) menuItems.push({ type: 'separator' }) } @@ -73,7 +77,7 @@ export default function registerContextMenu () { if (props.mediaType == 'video') { menuItems.push({ label: 'Save Video As...', click: downloadPrompt('srcURL') }) menuItems.push({ label: 'Copy Video URL', click: () => clipboard.writeText(props.srcURL) }) - menuItems.push({ label: 'Open Video in New Tab', click: (item, win) => viewManager.create(win, props.srcURL) }) + menuItems.push({ label: 'Open Video in New Tab', click: (item, win) => tabManager.create(win, props.srcURL, {adjacentActive: true}) }) menuItems.push({ type: 'separator' }) } @@ -81,17 +85,18 @@ export default function registerContextMenu () { if (props.mediaType == 'audio') { menuItems.push({ label: 'Save Audio As...', click: downloadPrompt('srcURL') }) menuItems.push({ label: 'Copy Audio URL', click: () => clipboard.writeText(props.srcURL) }) - menuItems.push({ label: 'Open Audio in New Tab', click: (item, win) => viewManager.create(win, props.srcURL) }) + menuItems.push({ label: 'Open Audio in New Tab', click: (item, win) => tabManager.create(win, props.srcURL, {adjacentActive: true}) }) menuItems.push({ type: 'separator' }) } // spell check - if (props.isMisspelled !== '' && props.isEditable) { - for (let i in spellingSuggestions) { - menuItems.push({ label: spellingSuggestions[i], click: (item, win) => webContents.replaceMisspelling(item.label) }) - } - menuItems.push({ type: 'separator' }) - } + // TODO + // if (props.isMisspelled !== '' && props.isEditable) { + // for (let i in spellingSuggestions) { + // menuItems.push({ label: spellingSuggestions[i], click: (item, win) => webContents.replaceMisspelling(item.label, {adjacentActive: true}) }) + // } + // menuItems.push({ type: 'separator' }) + // } // clipboard if (props.isEditable) { @@ -115,7 +120,7 @@ export default function registerContextMenu () { searchPreviewStr += '"' } var query = 'https://duckduckgo.com/?q=' + encodeURIComponent(props.selectionText.substr(0, 500)) // Limit query to prevent too long query error from DDG - menuItems.push({ label: 'Search DuckDuckGo for "' + searchPreviewStr, click: (item, win) => viewManager.create(win, query) }) + menuItems.push({ label: 'Search DuckDuckGo for "' + searchPreviewStr, click: (item, win) => tabManager.create(win, query, {adjacentActive: true}) }) menuItems.push({ type: 'separator' }) } @@ -135,29 +140,58 @@ export default function registerContextMenu () { click: () => webContents.reload() }) menuItems.push({ type: 'separator' }) + if (!addedWindowSettings.isAppWindow) { + menuItems.push({ + type: 'checkbox', + label: 'Always on Top', + checked: targetWindow.isAlwaysOnTop(), + click: function () { + targetWindow.setAlwaysOnTop(!targetWindow.isAlwaysOnTop()) + } + }) + menuItems.push({ + label: 'Toggle Browser UI', + click: function () { + toggleShellInterface(targetWindow) + } + }) + menuItems.push({ type: 'separator' }) + } menuItems.push({ - label: 'Save Page As...', + label: 'Export Page As...', click: downloadPrompt('pageURL', '.html') }) menuItems.push({ label: 'Print...', click: () => webContents.print() }) - menuItems.push({ - label: 'About This Site', - click: (item, win) => { - viewManager.getActive(win).toggleSidebar('site') - } - }) menuItems.push({ type: 'separator' }) + if (isHyperdrive) { + let driveInfo = tabManager.getActive(targetWindow).driveInfo + let key = driveInfo ? driveInfo.key : undefined + menuItems.push({ + label: 'Edit Source', + click: async (item, win) => { + tabManager.getActive(win).executeSidebarCommand('show-panel', 'editor-app') + } + }) + menuItems.push({ + label: 'Explore Files', + click: async (item, win) => { + tabManager.getActive(win).executeSidebarCommand('show-panel', 'files-explorer-app') + } + }) + menuItems.push({ + label: 'Drive Properties', + click: async (item, win) => { + runDrivePropertiesFlow(win, key) + } + }) + menuItems.push({ type: 'separator' }) + } } - menuItems.push({ - label: 'Edit Source', - click: (item, win) => { - viewManager.getActive(win).toggleSidebar('editor') - } - }) + menuItems.push({ type: 'separator' }) menuItems.push({ label: 'Inspect Element', click: item => { @@ -167,8 +201,8 @@ export default function registerContextMenu () { }) // show menu - var menu = Menu.buildFromTemplate(menuItems) - menu.popup({ window: targetWindow }) + menuInstance = Menu.buildFromTemplate(menuItems) + menuInstance.popup({ window: targetWindow }) }) }) } diff --git a/app/background-process/ui/default-state.js b/app/bg/ui/default-state.js similarity index 89% rename from app/background-process/ui/default-state.js rename to app/bg/ui/default-state.js index 6f77715b11..b51784afd5 100644 --- a/app/background-process/ui/default-state.js +++ b/app/bg/ui/default-state.js @@ -25,10 +25,12 @@ export function defaultWindowState () { minWidth, minHeight, pages: defaultPageState(), - userSession: null + isAlwaysOnTop: false, + isShellInterfaceHidden: false, + isAppWindow: false } } export function defaultPageState () { - return [ 'beaker://start' ] + return [] } diff --git a/app/background-process/ui/downloads.js b/app/bg/ui/downloads.js similarity index 93% rename from app/background-process/ui/downloads.js rename to app/bg/ui/downloads.js index 0d8458f667..e9cc49f6df 100644 --- a/app/background-process/ui/downloads.js +++ b/app/bg/ui/downloads.js @@ -1,13 +1,11 @@ import path from 'path' import fs from 'fs' -import { app, dialog, shell } from 'electron' -import mime from 'mime' +import { app, dialog, shell, BrowserView } from 'electron' import speedometer from 'speedometer' import emitStream from 'emit-stream' import EventEmitter from 'events' import parseDataURL from 'data-urls' -import { requestPermission } from './permissions' -import { openOrFocusDownloadsPage } from './view-manager' +import { openOrFocusDownloadsPage, findTab, remove as removeTab } from './tab-manager' // globals // = @@ -48,6 +46,14 @@ export function registerListener (win, opts = {}) { openOrFocusDownloadsPage(win) } + if (!wc.getURL()) { + // download was triggered when the user opened a new tab + // close the tab and do the download instead + let view = BrowserView.fromWebContents(wc) + let tab = view ? findTab(view) : undefined + if (tab) removeTab(tab.browserWindow, tab) + } + var lastBytes = 0 item.on('updated', () => { // set name if not already done @@ -135,7 +141,7 @@ function createEventsStream () { } function getDownloads () { - return Promise.resolve(downloads.map(toJSON)) + return Promise.resolve(downloads.map(d => toJSON(d))) } function pause (id) { diff --git a/app/bg/ui/init-window.js b/app/bg/ui/init-window.js new file mode 100644 index 0000000000..644ec939b1 --- /dev/null +++ b/app/bg/ui/init-window.js @@ -0,0 +1,45 @@ +import * as path from 'path' +import { BrowserWindow } from 'electron' +import { ICON_PATH } from './windows' + +// globals +// = + +var initWindow + +// exported api +// = + +export function open () { + initWindow = new BrowserWindow({ + autoHideMenuBar: true, + fullscreenable: false, + resizable: false, + fullscreenWindowTitle: true, + frame: false, + width: 400, + height: 300, + backgroundColor: '#fff', + webPreferences: { + preload: path.join(__dirname, 'fg', 'webview-preload', 'index.build.js'), + defaultEncoding: 'utf-8', + nodeIntegration: false, + contextIsolation: true, + webviewTag: false, + sandbox: true, + webSecurity: true, + enableRemoteModule: false, + allowRunningInsecureContent: false + }, + icon: ICON_PATH, + show: true + }) + initWindow.loadURL(`beaker://init/`) +} + +export function close () { + if (initWindow) { + initWindow.close() + initWindow = undefined + } +} diff --git a/app/background-process/ui/keybindings.js b/app/bg/ui/keybindings.js similarity index 92% rename from app/background-process/ui/keybindings.js rename to app/bg/ui/keybindings.js index 7a80dc8965..db05812c56 100644 --- a/app/background-process/ui/keybindings.js +++ b/app/bg/ui/keybindings.js @@ -8,10 +8,9 @@ import _flattenDeep from 'lodash.flattendeep' import isAccelerator from 'electron-is-accelerator' import equals from 'keyboardevents-areequal' import {toKeyEvent} from 'keyboardevent-from-electron-accelerator' -import {buildWindowMenu} from './window-menu' +import {buildWindowMenu, triggerMenuItemById} from './window-menu' const IS_DARWIN = process.platform === 'darwin' -const KEYBINDINGS = extractKeybindings(buildWindowMenu()) const registeredKBs = {} // map of [window.id] => keybindings // exported api @@ -60,6 +59,7 @@ export function createGlobalKeybindingsHandler (win) { // this is used, for instance, to reserve "Cmd/Ctrl + T" so that an app cannot pre-empt it // (the window-menu's accelerators are typically handled *after* the active view's input handlers) export function createKeybindingProtectionsHandler (win) { + const KEYBINDINGS = extractKeybindings(buildWindowMenu({win})) return (e, input) => { if (input.type !== 'keyDown') return var key = input.key @@ -77,7 +77,7 @@ export function createKeybindingProtectionsHandler (win) { } if (match) { e.preventDefault() - match.cmd(null, win) + triggerMenuItemById(match.menuLabel, match.id) } } } @@ -86,14 +86,15 @@ export function createKeybindingProtectionsHandler (win) { // = // recurse the window menu and extract all 'accelerator' values with reserved=true -function extractKeybindings (menuNode) { +function extractKeybindings (menuNode, menuLabel) { if (menuNode.accelerator && menuNode.click && menuNode.reserved) { return { binding: convertAcceleratorToBinding(menuNode.accelerator), - cmd: menuNode.click + id: menuNode.id, + menuLabel } } else if (menuNode.submenu) { - return menuNode.submenu.map(extractKeybindings).filter(Boolean) + return menuNode.submenu.map(item => extractKeybindings(item, menuNode.label)).filter(Boolean) } else if (Array.isArray(menuNode)) { return _flattenDeep(menuNode.map(extractKeybindings).filter(Boolean)) } diff --git a/app/background-process/ui/permissions.js b/app/bg/ui/permissions.js similarity index 80% rename from app/background-process/ui/permissions.js rename to app/bg/ui/permissions.js index a44b63cf06..85aea1cab0 100644 --- a/app/background-process/ui/permissions.js +++ b/app/bg/ui/permissions.js @@ -1,13 +1,11 @@ import { session, BrowserView } from 'electron' -import { PERMS, getPermId } from '@beaker/permissions' -import * as beakerCore from '@beaker/core' -const dat = beakerCore.dat -const sitedata = beakerCore.dbs.sitedata +import { PERMS, getPermId } from '../../lib/permissions' +import hyper from '../hyper/index' +import * as sitedata from '../dbs/sitedata' import _get from 'lodash.get' -import pda from 'pauls-dat-api' -import parseDatURL from 'parse-dat-url' +import { parseDriveUrl } from '../../lib/urls' import * as permPromptSubwindow from './subwindows/perm-prompt' -import * as viewManager from './view-manager' +import * as tabManager from './tab-manager' import {PermissionsError, UserDeniedError} from 'beaker-error-constants' // globals @@ -66,25 +64,25 @@ export function denyAllRequests (win) { } export async function checkLabsPerm ({perm, labApi, apiDocsUrl, sender}) { - var urlp = parseDatURL(sender.getURL()) + var urlp = parseDriveUrl(sender.getURL()) if (urlp.protocol === 'beaker:') return true - if (urlp.protocol === 'dat:') { + if (urlp.protocol === 'hyper:') { // resolve name - let key = await dat.dns.resolveName(urlp.hostname) + let key = await hyper.dns.resolveName(urlp.hostname) - // check dat.json for opt-in + // check index.json for opt-in let isOptedIn = false - let archive = dat.library.getArchive(key) - if (archive) { - let {checkoutFS} = dat.library.getArchiveCheckout(archive, urlp.version) - let manifest = await pda.readManifest(checkoutFS).catch(_ => {}) + let drive = hyper.drives.getDrive(key) + if (drive) { + let {checkoutFS} = await hyper.drives.getDriveCheckout(drive, urlp.version) + let manifest = await checkoutFS.pda.readManifest().catch(_ => {}) let apis = _get(manifest, 'experimental.apis') if (apis && Array.isArray(apis)) { isOptedIn = apis.includes(labApi) } } if (!isOptedIn) { - throw new PermissionsError(`You must include "${labApi}" in your dat.json experimental.apis list. See ${apiDocsUrl} for more information.`) + throw new PermissionsError(`You must include "${labApi}" in your index.json experimental.apis list. See ${apiDocsUrl} for more information.`) } // ask user @@ -106,14 +104,6 @@ async function onPermissionRequestHandler (webContents, permission, cb, opts) { return cb(true) } - // HACK - // until the applications system can be properly implemented, - // allow all requests from dat://beaker.social/, our default social app - // -prf - if (url.startsWith('dat://beaker.social/') && permission === 'dangerousAppControl') { - return cb(true) - } - // look up the containing window var {win, view} = getContaining(webContents) if (!win || !view) { @@ -127,6 +117,11 @@ async function onPermissionRequestHandler (webContents, permission, cb, opts) { if (PERM && PERM.alwaysAllow) return cb(true) if (PERM && PERM.alwaysDisallow) return cb(false) + // special cases + if (permission === 'openExternal' && opts.externalURL.startsWith('mailto:')) { + return cb(true) + } + // check the sitedatadb var res = await sitedata.getPermission(url, permission).catch(err => undefined) if (res === 1) return cb(true) @@ -168,7 +163,7 @@ async function onPermissionRequestHandler (webContents, permission, cb, opts) { function getContaining (webContents) { var view = BrowserView.fromWebContents(webContents) if (view) { - var win = viewManager.findContainingWindow(view) + var win = tabManager.findContainingWindow(view) return {win, view} } return {} diff --git a/app/background-process/ui/session-watcher.js b/app/bg/ui/session-watcher.js similarity index 81% rename from app/background-process/ui/session-watcher.js rename to app/bg/ui/session-watcher.js index b81abb2e24..1aa61dd8ed 100644 --- a/app/background-process/ui/session-watcher.js +++ b/app/bg/ui/session-watcher.js @@ -5,6 +5,7 @@ import _isEqual from 'lodash.isequal' import {defaultPageState} from './default-state' const SNAPSHOT_PATH = 'shell-window-state.json' +var lastRecordedPositioning = {} // exported api // = @@ -31,15 +32,17 @@ export default class SessionWatcher { stopRecording () { this.recording = false } watchWindow (win, initialState) { + const winId = win.id let state = initialState this.snapshot.windows.push(state) let watcher = new WindowWatcher(win, initialState) - this.watchers[win.id] = watcher + this.watchers[winId] = watcher watcher.on('change', (nextState) => { if (this.recording) { let { windows } = this.snapshot let i = windows.indexOf(state) + if (i === -1) return state = windows[i] = nextState this.writeSnapshot() } @@ -47,12 +50,11 @@ export default class SessionWatcher { watcher.on('remove', () => { if (this.recording) { - let { windows } = this.snapshot - let i = windows.indexOf(state) - this.snapshot.windows = windows.splice(1, i) + let i = this.snapshot.windows.indexOf(state) + this.snapshot.windows.splice(i, 1) this.writeSnapshot() } - delete this.watchers[win.id] + delete this.watchers[winId] watcher.removeAllListeners() }) } @@ -83,6 +85,10 @@ export default class SessionWatcher { } } +export function getLastRecordedPositioning () { + return lastRecordedPositioning +} + // internal methods // = @@ -92,6 +98,7 @@ class WindowWatcher extends EventEmitter { this.handleClosed = this.handleClosed.bind(this) this.handlePagesUpdated = this.handlePagesUpdated.bind(this) this.handlePositionChange = this.handlePositionChange.bind(this) + this.handleAlwaysOnTopChanged = this.handleAlwaysOnTopChanged.bind(this) // right now this class trusts that the initial state is correctly formed by this point this.snapshot = initialState @@ -99,6 +106,7 @@ class WindowWatcher extends EventEmitter { win.on('closed', this.handleClosed) win.on('resize', debounce(this.handlePositionChange, 1000)) win.on('moved', this.handlePositionChange) + win.on('always-on-top-changed', this.handleAlwaysOnTopChanged) win.on('custom-pages-updated', this.handlePagesUpdated) } @@ -128,18 +136,13 @@ class WindowWatcher extends EventEmitter { } handlePositionChange () { - Object.assign(this.snapshot, getCurrentPosition(this.getWindow())) + lastRecordedPositioning = this.getWindow().getBounds() + Object.assign(this.snapshot, lastRecordedPositioning) this.emit('change', this.snapshot) } -} -function getCurrentPosition (win) { - var position = win.getPosition() - var size = win.getSize() - return { - x: position[0], - y: position[1], - width: size[0], - height: size[1] + handleAlwaysOnTopChanged (e, isAlwaysOnTop) { + this.snapshot.isAlwaysOnTop = isAlwaysOnTop + this.emit('change', this.snapshot) } } diff --git a/app/bg/ui/setup-flow.js b/app/bg/ui/setup-flow.js new file mode 100644 index 0000000000..e04adcbda1 --- /dev/null +++ b/app/bg/ui/setup-flow.js @@ -0,0 +1,86 @@ +import * as path from 'path' +import { URLSearchParams } from 'url' +import { BrowserWindow } from 'electron' +import { ICON_PATH } from './windows' +import * as profileDb from '../dbs/profile-data-db' +import * as filesystem from '../filesystem/index' +import knex from '../lib/knex' + +// globals +// = + +var setupWindow + +// exported api +// = + +export var hasVisitedProfile = false + +export async function runSetupFlow () { + var setupState = await profileDb.get('SELECT * FROM setup_state') + if (!setupState) { + setupState = {migrated08to09: 0, profileSetup: 0, hasVisitedProfile: 0} + await profileDb.run(knex('setup_state').insert(setupState)) + } + hasVisitedProfile = setupState.hasVisitedProfile === 1 + + // TODO + // do we even need to track profileSetup in setup_state? + // might be better to just use the address-book.json state + // -prf + var hasProfile = !!(await filesystem.getProfile()) + if (setupState.profileSetup && !hasProfile) { + setupState.profileSetup = 0 + await profileDb.run(knex('setup_state').update(setupState)) + } else if (!setupState.profileSetup && hasProfile) { + setupState.profileSetup = 1 + await profileDb.run(knex('setup_state').update(setupState)) + } + + var needsSetup = !setupState.profileSetup || !setupState.migrated08to09 + if (needsSetup) { + setupWindow = new BrowserWindow({ + // titleBarStyle: 'hiddenInset', + autoHideMenuBar: true, + fullscreenable: false, + resizable: false, + fullscreenWindowTitle: true, + frame: false, + width: 600, + height: 500, + backgroundColor: '#334', + webPreferences: { + preload: path.join(__dirname, 'fg', 'webview-preload', 'index.build.js'), + defaultEncoding: 'utf-8', + nodeIntegration: false, + contextIsolation: true, + webviewTag: false, + sandbox: true, + webSecurity: true, + enableRemoteModule: false, + allowRunningInsecureContent: false + }, + icon: ICON_PATH, + show: true + }) + setupWindow.loadURL(`beaker://setup/?${(new URLSearchParams(setupState)).toString()}`) + await new Promise(r => setupWindow.once('close', r)) + setupWindow = undefined + } +} + +export async function updateSetupState (obj) { + await profileDb.run(knex('setup_state').update(obj)) + + // HACK + // window.close() isnt working within the UI thread for some reason + // so use this as a cue to close the window + // -prf + var setupState = await profileDb.get('SELECT * FROM setup_state') + if (setupWindow && setupState.profileSetup && setupState.migrated08to09) setupWindow.close() +} + +export async function setHasVisitedProfile () { + hasVisitedProfile = true + await profileDb.run(knex('setup_state').update({hasVisitedProfile: 1})) +} \ No newline at end of file diff --git a/app/background-process/ui/subwindows/location-bar.js b/app/bg/ui/subwindows/location-bar.js similarity index 87% rename from app/background-process/ui/subwindows/location-bar.js rename to app/bg/ui/subwindows/location-bar.js index d0ae27bb1d..e5cfede67c 100644 --- a/app/background-process/ui/subwindows/location-bar.js +++ b/app/bg/ui/subwindows/location-bar.js @@ -11,7 +11,9 @@ import Events from 'events' import { BrowserWindow, BrowserView } from 'electron' import * as rpc from 'pauls-electron-rpc' import locationBarRPCManifest from '../../rpc-manifests/location-bar' -import * as viewManager from '../view-manager' +import * as tabManager from '../tab-manager' +import * as filesystem from '../../filesystem/index' +import { joinPath } from '../../../lib/strings' // globals // = @@ -27,7 +29,7 @@ export function setup (parentWindow) { var view = views[parentWindow.id] = new BrowserView({ webPreferences: { defaultEncoding: 'utf-8', - preload: path.join(__dirname, 'location-bar.build.js') + preload: path.join(__dirname, 'fg', 'location-bar', 'index.build.js') } }) view.setAutoResize({width: true, height: false}) @@ -39,7 +41,7 @@ export function setup (parentWindow) { export function destroy (parentWindow) { if (get(parentWindow)) { - get(parentWindow).close() + get(parentWindow).destroy() delete views[parentWindow.id] } } @@ -110,13 +112,18 @@ rpc.exportAPI('background-process-location-bar', locationBarRPCManifest, { async createTab (url) { var win = getParentWindow(this.sender) hide(win) // always close the location bar - viewManager.create(win, url, {setActive: true}) + tabManager.create(win, url, {setActive: true}) }, async loadURL (url) { var win = getParentWindow(this.sender) hide(win) // always close the location bar - viewManager.getActive(win).loadURL(url) + var active = tabManager.getActive(win) + if (url.startsWith('/')) { + // relative to current origin + url = joinPath(active.origin, url) + } + active.loadURL(url) get(win).webContents.send('command', 'unfocus-location') // we have to manually unfocus the location bar }, diff --git a/app/background-process/ui/subwindows/modals.js b/app/bg/ui/subwindows/modals.js similarity index 54% rename from app/background-process/ui/subwindows/modals.js rename to app/bg/ui/subwindows/modals.js index b97aeaa62d..3b3ae82b8f 100644 --- a/app/background-process/ui/subwindows/modals.js +++ b/app/bg/ui/subwindows/modals.js @@ -11,15 +11,15 @@ import path from 'path' import { app, BrowserWindow, BrowserView } from 'electron' import * as rpc from 'pauls-electron-rpc' import { ModalActiveError } from 'beaker-error-constants' -import * as viewManager from '../view-manager' +import * as tabManager from '../tab-manager' import modalsRPCManifest from '../../rpc-manifests/modals' -import { findWebContentsParentWindow } from '../../../lib/electron' +import { findWebContentsParentWindow } from '../../lib/electron' // globals // = const MARGIN_SIZE = 10 -var views = {} // map of {[parentView.id] => BrowserView} +var views = {} // map of {[tab.id] => BrowserView} // exported api // = @@ -35,19 +35,19 @@ export function setup (parentWindow) { export function destroy (parentWindow) { // destroy all under this window - for (let view of viewManager.getAll(parentWindow)) { - if (view.id in views) { - views[view.id].destroy() - delete views[view.id] + for (let tab of tabManager.getAll(parentWindow)) { + if (tab.id in views) { + views[tab.id].destroy() + delete views[tab.id] } } } export function reposition (parentWindow) { // reposition all under this window - for (let view of viewManager.getAll(parentWindow)) { - if (view.id in views) { - setBounds(views[view.id], parentWindow) + for (let tab of tabManager.getAll(parentWindow)) { + if (tab.id in views) { + setBounds(views[tab.id], parentWindow) } } } @@ -56,27 +56,38 @@ export async function create (webContents, modalName, params = {}) { // find parent window var parentWindow = BrowserWindow.fromWebContents(webContents) var parentView = BrowserView.fromWebContents(webContents) + var tab if (parentView && !parentWindow) { // if there's no window, then a web page or "sub-window" created the prompt // use its containing window + tab = tabManager.findTab(parentView) parentWindow = findWebContentsParentWindow(parentView.webContents) + if (!tab) { + // this can happen when the passed `webContents` is a shell-menu or similar sub-window + tab = tabManager.getActive(parentWindow) + } } else if (!parentView) { // if there's no view, then the shell window created the prompt // attach it to the active view - parentView = viewManager.getActive(parentWindow) - parentWindow = parentView.browserWindow + tab = tabManager.getActive(parentWindow) + parentWindow = tab.browserWindow } // make sure a prompt window doesnt already exist - if (parentView.id in views) { + if (tab.id in views) { throw new ModalActiveError() } + // wait for tab to be actives + if (!tab.isActive) { + await tab.awaitActive() + } + // create the view - var view = views[parentView.id] = new BrowserView({ + var view = views[tab.id] = new BrowserView({ webPreferences: { defaultEncoding: 'utf-8', - preload: path.join(__dirname, 'modals.build.js') + preload: path.join(__dirname, 'fg', 'modals', 'index.build.js') } }) view.modalName = modalName @@ -100,40 +111,40 @@ export async function create (webContents, modalName, params = {}) { // destroy the window parentWindow.removeBrowserView(view) view.destroy() - delete views[parentView.id] + delete views[tab.id] // return/throw if (err) throw err return result } -export function get (parentView) { - return views[parentView.id] +export function get (tab) { + return views[tab.id] } -export function show (parentView) { - if (parentView.id in views) { - var win = viewManager.findContainingWindow(parentView) - if (!win) win = findWebContentsParentWindow(views[parentView.id].webContents) - if (win) win.addBrowserView(views[parentView.id]) +export function show (tab) { + if (tab.id in views) { + var win = tabManager.findContainingWindow(tab) + if (!win) win = findWebContentsParentWindow(views[tab.id].webContents) + if (win) win.addBrowserView(views[tab.id]) } } -export function hide (parentView) { - if (parentView.id in views) { - var win = viewManager.findContainingWindow(parentView) - if (!win) win = findWebContentsParentWindow(views[parentView.id].webContents) - if (win) win.removeBrowserView(views[parentView.id]) +export function hide (tab) { + if (tab.id in views) { + var win = tabManager.findContainingWindow(tab) + if (!win) win = findWebContentsParentWindow(views[tab.id].webContents) + if (win) win.removeBrowserView(views[tab.id]) } } -export function close (parentView) { - if (parentView.id in views) { - var view = views[parentView.id] - var win = viewManager.findContainingWindow(parentView) - win.removeBrowserView(view) +export function close (tab) { + if (tab.id in views) { + var view = views[tab.id] + var win = tabManager.findContainingWindow(tab) + if (win) win.removeBrowserView(view) view.destroy() - delete views[parentView.id] + delete views[tab.id] } } @@ -143,7 +154,7 @@ export function close (parentView) { rpc.exportAPI('background-process-modals', modalsRPCManifest, { async createTab (url) { var win = findWebContentsParentWindow(this.sender) - viewManager.create(win, url, {setActive: true}) + tabManager.create(win, url, {setActive: true, adjacentActive: true}) }, async resizeSelf (dimensions) { @@ -157,27 +168,28 @@ rpc.exportAPI('background-process-modals', modalsRPCManifest, { // = function getDefaultWidth (view) { - if (view.modalName === 'create-archive') { - return 960 - } + if (view.modalName === 'select-drive') return 600 + if (view.modalName === 'select-file') return 800 + if (view.modalName === 'select-contact') return 700 return 500 } function getDefaultHeight (view) { - if (view.modalName === 'create-archive') { - return 600 - } + if (view.modalName === 'select-file') return 460 + if (view.modalName === 'select-contact') return 460 return 300 } function setBounds (view, parentWindow, {width, height} = {}) { var parentBounds = parentWindow.getContentBounds() - width = Math.min(width || getDefaultWidth(view), parentBounds.width - 20) - height = Math.min(height || getDefaultHeight(view), parentBounds.height - 20) + // HACK workaround the lack of view.getBounds() -prf + view.currentBounds = view.currentBounds || {width: undefined, height: undefined} + view.currentBounds.width = Math.min(width || view.currentBounds.width || getDefaultWidth(view), parentBounds.width - 20) + view.currentBounds.height = Math.min(height || view.currentBounds.height || getDefaultHeight(view), parentBounds.height - 20) view.setBounds({ - x: Math.round(parentBounds.width / 2) - Math.round(width / 2) - MARGIN_SIZE, // centered + x: Math.round(parentBounds.width / 2) - Math.round(view.currentBounds.width / 2) - MARGIN_SIZE, // centered y: 72, - width: width + (MARGIN_SIZE * 2), - height: height + MARGIN_SIZE + width: view.currentBounds.width + (MARGIN_SIZE * 2), + height: view.currentBounds.height + MARGIN_SIZE }) } diff --git a/app/background-process/ui/subwindows/perm-prompt.js b/app/bg/ui/subwindows/perm-prompt.js similarity index 79% rename from app/background-process/ui/subwindows/perm-prompt.js rename to app/bg/ui/subwindows/perm-prompt.js index dcd692daa6..c01a8e869d 100644 --- a/app/background-process/ui/subwindows/perm-prompt.js +++ b/app/bg/ui/subwindows/perm-prompt.js @@ -10,15 +10,15 @@ import path from 'path' import { BrowserWindow, BrowserView } from 'electron' import * as rpc from 'pauls-electron-rpc' -import * as viewManager from '../view-manager' +import * as tabManager from '../tab-manager' import permPromptRPCManifest from '../../rpc-manifests/perm-prompt' -import { findWebContentsParentWindow } from '../../../lib/electron' +import { findWebContentsParentWindow } from '../../lib/electron' // globals // = const MARGIN_SIZE = 10 -var views = {} // map of {[parentView.id] => BrowserView} +var views = {} // map of {[tab.id] => BrowserView} // exported api // = @@ -28,19 +28,19 @@ export function setup (parentWindow) { export function destroy (parentWindow) { // destroy all under this window - for (let view of viewManager.getAll(parentWindow)) { - if (view.id in views) { - views[view.id].destroy() - delete views[view.id] + for (let tab of tabManager.getAll(parentWindow)) { + if (tab.id in views) { + views[tab.id].destroy() + delete views[tab.id] } } } export function reposition (parentWindow) { // reposition all under this window - for (let view of viewManager.getAll(parentWindow)) { - if (view.id in views) { - setBounds(views[view.id], parentWindow) + for (let tab of tabManager.getAll(parentWindow)) { + if (tab.id in views) { + setBounds(views[tab.id], parentWindow) } } } @@ -51,11 +51,16 @@ export async function create (parentWindow, parentView, params) { return false // abort } + var tab = tabManager.findTab(parentView) + if (tab && !tab.isActive) { + await tab.awaitActive() + } + // create the window var view = views[parentView.id] = new BrowserView({ webPreferences: { defaultEncoding: 'utf-8', - preload: path.join(__dirname, 'perm-prompt.build.js') + preload: path.join(__dirname, 'fg', 'perm-prompt', 'index.build.js') } }) parentWindow.addBrowserView(view) @@ -87,7 +92,7 @@ export function get (parentView) { export function show (parentView) { if (parentView.id in views) { - var win = viewManager.findContainingWindow(parentView) + var win = tabManager.findContainingWindow(parentView) if (!win) win = findWebContentsParentWindow(views[parentView.id].webContents) if (win) win.addBrowserView(views[parentView.id]) } @@ -95,7 +100,7 @@ export function show (parentView) { export function hide (parentView) { if (parentView.id in views) { - var win = viewManager.findContainingWindow(parentView) + var win = tabManager.findContainingWindow(parentView) if (!win) win = findWebContentsParentWindow(views[parentView.id].webContents) if (win) win.removeBrowserView(views[parentView.id]) } @@ -114,7 +119,7 @@ export function close (parentView) { rpc.exportAPI('background-process-perm-prompt', permPromptRPCManifest, { async createTab (url) { var win = findWebContentsParentWindow(this.sender) - viewManager.create(win, url, {setActive: true}) + tabManager.create(win, url, {setActive: true, adjacentActive: true}) }, async resizeSelf (dimensions) { diff --git a/app/background-process/ui/subwindows/prompts.js b/app/bg/ui/subwindows/prompts.js similarity index 50% rename from app/background-process/ui/subwindows/prompts.js rename to app/bg/ui/subwindows/prompts.js index 1336d6e60e..94efc9a528 100644 --- a/app/background-process/ui/subwindows/prompts.js +++ b/app/bg/ui/subwindows/prompts.js @@ -10,15 +10,17 @@ import path from 'path' import { BrowserWindow, BrowserView } from 'electron' import * as rpc from 'pauls-electron-rpc' -import * as viewManager from '../view-manager' +import * as tabManager from '../tab-manager' import promptsRPCManifest from '../../rpc-manifests/prompts' -import { findWebContentsParentWindow } from '../../../lib/electron' +import { findWebContentsParentWindow } from '../../lib/electron' +import { getAddedWindowSettings } from '../windows' +import * as setupFlow from '../setup-flow' // globals // = const MARGIN_SIZE = 10 -var views = {} // map of {[parentView.id] => BrowserView} +var views = {} // map of {[tab.id] => BrowserView} // exported api // = @@ -28,55 +30,62 @@ export function setup (parentWindow) { export function destroy (parentWindow) { // destroy all under this window - for (let view of viewManager.getAll(parentWindow)) { - if (view.id in views) { - views[view.id].destroy() - delete views[view.id] + for (let tab of tabManager.getAll(parentWindow)) { + if (tab.id in views) { + views[tab.id].destroy() + delete views[tab.id] } } } export function reposition (parentWindow) { // reposition all under this window - for (let view of viewManager.getAll(parentWindow)) { - if (view.id in views) { - setBounds(views[view.id], parentWindow) + for (let tab of tabManager.getAll(parentWindow)) { + if (tab.id in views) { + setBounds(views[tab.id], tab, parentWindow) } } } export async function create (webContents, promptName, params = {}) { - // find parent window + // find parent window & tab var parentWindow = BrowserWindow.fromWebContents(webContents) var parentView = BrowserView.fromWebContents(webContents) + var tab if (parentView && !parentWindow) { // if there's no window, then a web page or "sub-window" created the prompt // use its containing window - parentWindow = findWebContentsParentWindow(parentView.webContents) + tab = tabManager.findTab(parentView) + parentWindow = findWebContentsParentWindow(tab.webContents) } else if (!parentView) { // if there's no view, then the shell window created the prompt // attach it to the active view - parentView = viewManager.getActive(parentWindow) - parentWindow = parentView.browserWindow + tab = tabManager.getActive(parentWindow) + parentWindow = tab.browserWindow } // make sure a prompt window doesnt already exist - if (parentView.id in views) { + if (tab.id in views) { return } + if (!tab.isActive) { + await tab.awaitActive() + } + // create the view - var view = views[parentView.id] = new BrowserView({ + var view = views[tab.id] = new BrowserView({ webPreferences: { defaultEncoding: 'utf-8', - preload: path.join(__dirname, 'prompts.build.js') + preload: path.join(__dirname, 'fg', 'prompts', 'index.build.js') } }) view.promptName = promptName - if (viewManager.getActive(parentWindow).id === parentView.id) { + view.tab = tab + if (tabManager.getActive(parentWindow).id === tab.id) { parentWindow.addBrowserView(view) } - setBounds(view, parentWindow) + setBounds(view, tab, parentWindow) view.webContents.on('console-message', (e, level, message) => { console.log('Prompts window says:', message) }) @@ -84,38 +93,38 @@ export async function create (webContents, promptName, params = {}) { await view.webContents.executeJavaScript(`showPrompt("${promptName}", ${JSON.stringify(params)})`) } -export function get (parentView) { - return views[parentView.id] +export function get (tab) { + return views[tab.id] } -export function show (parentView) { - if (parentView.id in views) { - var view = views[parentView.id] - var win = viewManager.findContainingWindow(parentView) +export function show (tab) { + if (tab.id in views) { + var view = views[tab.id] + var win = tabManager.findContainingWindow(tab) if (!win) win = findWebContentsParentWindow(view.webContents) if (win) { win.addBrowserView(view) - setBounds(view, win) + setBounds(view, tab, win) } } } -export function hide (parentView) { - if (parentView.id in views) { - var win = viewManager.findContainingWindow(parentView) - if (!win) win = findWebContentsParentWindow(views[parentView.id].webContents) - if (win) win.removeBrowserView(views[parentView.id]) +export function hide (tab) { + if (tab.id in views) { + var win = tabManager.findContainingWindow(tab) + if (!win) win = findWebContentsParentWindow(views[tab.id].webContents) + if (win) win.removeBrowserView(views[tab.id]) } } -export function close (parentView) { - if (parentView.id in views) { - var view = views[parentView.id] - var win = viewManager.findContainingWindow(parentView) - if (!win) win = findWebContentsParentWindow(views[parentView.id].webContents) +export function close (tab) { + if (tab.id in views) { + var view = views[tab.id] + var win = tabManager.findContainingWindow(tab) + if (!win) win = findWebContentsParentWindow(views[tab.id].webContents) if (win) win.removeBrowserView(view) view.destroy() - delete views[parentView.id] + delete views[tab.id] } } @@ -124,43 +133,62 @@ export function close (parentView) { rpc.exportAPI('background-process-prompts', promptsRPCManifest, { async close () { - close(this.sender) + close(getTabForSender(this.sender)) + }, + + async closeEditProfilePromptForever () { + close(getTabForSender(this.sender)) + await setupFlow.setHasVisitedProfile() }, async createTab (url) { var win = findWebContentsParentWindow(this.sender) - viewManager.create(win, url, {setActive: true}) + tabManager.create(win, url, {setActive: true, adjacentActive: true}) }, async loadURL (url) { var win = findWebContentsParentWindow(this.sender) - viewManager.getActive(win).loadURL(url) + tabManager.getActive(win).loadURL(url) }, - async openSidebar (panel) { + async executeSidebarCommand (...args) { var win = findWebContentsParentWindow(this.sender) - viewManager.getActive(win).openSidebar(panel) + tabManager.getActive(win).executeSidebarCommand(...args) } }) // internal methods // = +function getTabForSender (sender) { + var view = BrowserView.fromWebContents(sender) + for (let id in views) { + if (views[id] === view) { + return view.tab + } + } + return undefined +} + function getDefaultWidth (view) { - return 360 + if (view.promptName === 'edit-profile') { + return 450 + } + return 380 } function getDefaultHeight (view) { return 80 } -function setBounds (view, parentWindow, {width, height} = {}) { +function setBounds (view, tab, parentWindow, {width, height} = {}) { var parentBounds = parentWindow.getContentBounds() width = Math.min(width || getDefaultWidth(view), parentBounds.width - 20) height = Math.min(height || getDefaultHeight(view), parentBounds.height - 20) + var y = getAddedWindowSettings(parentWindow).isShellInterfaceHidden ? 10 : 105 view.setBounds({ - x: 0, - y: 85, + x: tab.isSidebarActive ? tab.sidebarWidth : 0, + y, width: width + (MARGIN_SIZE * 2), height: height + MARGIN_SIZE }) diff --git a/app/background-process/ui/subwindows/shell-menus.js b/app/bg/ui/subwindows/shell-menus.js similarity index 62% rename from app/background-process/ui/subwindows/shell-menus.js rename to app/bg/ui/subwindows/shell-menus.js index 4c101f81ff..a35ae70f7d 100644 --- a/app/background-process/ui/subwindows/shell-menus.js +++ b/app/bg/ui/subwindows/shell-menus.js @@ -13,15 +13,16 @@ import Events from 'events' import { BrowserWindow, BrowserView } from 'electron' import * as rpc from 'pauls-electron-rpc' import { createShellWindow } from '../windows' -import * as viewManager from '../view-manager' +import * as tabManager from '../tab-manager' import * as modals from './modals' +import { getToolbarMenu, triggerMenuItemById } from '../window-menu' import shellMenusRPCManifest from '../../rpc-manifests/shell-menus' // globals // = const MARGIN_SIZE = 10 -const IS_RIGHT_ALIGNED = ['browser', 'users', 'bookmark', 'donate', 'site-tools', 'preview-mode-tools'] +const IS_RIGHT_ALIGNED = ['browser', 'bookmark', 'network', 'peers', 'share', 'site', 'donate'] var events = new Events() var views = {} // map of {[parentWindow.id] => BrowserView} @@ -32,7 +33,7 @@ export function setup (parentWindow) { var view = views[parentWindow.id] = new BrowserView({ webPreferences: { defaultEncoding: 'utf-8', - preload: path.join(__dirname, 'shell-menus.build.js') + preload: path.join(__dirname, 'fg', 'shell-menus', 'index.build.js') } }) view.webContents.on('console-message', (e, level, message) => { @@ -55,56 +56,70 @@ export function get (parentWindow) { export function reposition (parentWindow) { var view = get(parentWindow) if (view) { - let parentBounds = parentWindow.getContentBounds() const setBounds = (b) => { - // HACK workaround the lack of view.getBounds() -prf - if (view.currentBounds) { - b = view.currentBounds // use existing bounds + if (view.currentDimensions) { + Object.assign(b, view.currentDimensions) + } else { + adjustDimensions(b) } - view.currentBounds = b // store new bounds - view.setBounds(adjustBounds(view, parentWindow, b)) + adjustPosition(b, view, parentWindow) + view.setBounds(b) } if (view.menuId === 'browser') { setBounds({ - x: 5, + x: 10, y: 72, width: 230, height: 350 }) - } else if (view.menuId === 'users') { + } else if (view.menuId === 'network') { setBounds({ - x: parentBounds.width - view.boundsOpt.right, + x: 70, y: 72, - width: 250, - height: 350 + width: 230, + height: 400 }) } else if (view.menuId === 'bookmark') { setBounds({ - x: parentBounds.width - view.boundsOpt.left, + x: view.boundsOpt.rightOffset, + y: 72, + width: 250, + height: 200 + }) + } else if (view.menuId === 'toolbar') { + setBounds({ + x: view.boundsOpt.left, y: view.boundsOpt.top, - width: 300, - height: 400 + width: 250, + height: 550 }) } else if (view.menuId === 'donate') { setBounds({ - x: parentBounds.width - view.boundsOpt.left, - y: view.boundsOpt.top, + x: view.boundsOpt.rightOffset, + y: 72, width: 350, height: 90 }) - } else if (view.menuId === 'site-tools') { + } else if (view.menuId === 'share') { setBounds({ - x: parentBounds.width - view.boundsOpt.left, - y: view.boundsOpt.top, - width: 220, - height: 152 + x: view.boundsOpt.rightOffset, + y: 72, + width: 310, + height: 120 }) - } else if (view.menuId === 'preview-mode-tools') { + } else if (view.menuId === 'peers') { setBounds({ - x: parentBounds.width - view.boundsOpt.left, - y: view.boundsOpt.top, - width: 220, - height: 122 + x: view.boundsOpt.rightOffset, + y: 72, + width: 200, + height: 350 + }) + } else if (view.menuId === 'site') { + setBounds({ + x: view.boundsOpt.rightOffset, + y: 72, + width: 250, + height: 350 }) } } @@ -121,6 +136,16 @@ export async function toggle (parentWindow, menuId, opts) { } } +export async function update (parentWindow, opts) { + var view = get(parentWindow) + if (view && view.isVisible) { + view.boundsOpt = opts && opts.bounds ? opts.bounds : view.boundsOpt + reposition(parentWindow) + var params = opts && opts.params ? opts.params : {} + await view.webContents.executeJavaScript(`updateMenu(${JSON.stringify(params)})`) + } +} + export async function show (parentWindow, menuId, opts) { var view = get(parentWindow) if (view) { @@ -146,7 +171,7 @@ export function hide (parentWindow) { if (view) { view.webContents.executeJavaScript(`reset('${view.menuId}')`) parentWindow.removeBrowserView(view) - view.currentBounds = null + view.currentDimensions = null view.isVisible = false events.emit('hide') } @@ -167,7 +192,7 @@ rpc.exportAPI('background-process-shell-menus', shellMenusRPCManifest, { async createTab (url) { var win = getParentWindow(this.sender) hide(win) // always close the menu - viewManager.create(win, url, {setActive: true}) + tabManager.create(win, url, {setActive: true}) }, async createModal (name, opts) { @@ -177,44 +202,49 @@ rpc.exportAPI('background-process-shell-menus', shellMenusRPCManifest, { async loadURL (url) { var win = getParentWindow(this.sender) hide(win) // always close the menu - viewManager.getActive(win).loadURL(url) + tabManager.getActive(win).loadURL(url) }, async resizeSelf (dimensions) { var view = BrowserView.fromWebContents(this.sender) - // HACK view.currentBounds is set in reposition() -prf - dimensions = Object.assign({}, view.currentBounds || {}, dimensions) - view.setBounds(adjustBounds(view, getParentWindow(this.sender), dimensions)) - view.currentBounds = dimensions + if (!view.isVisible) return + adjustDimensions(dimensions) + view.currentDimensions = dimensions + reposition(getParentWindow(this.sender)) }, async showInpageFind () { var win = getParentWindow(this.sender) - var view = viewManager.getActive(win) - if (view) view.showInpageFind() + var tab = tabManager.getActive(win) + if (tab) tab.showInpageFind() + }, + + async getWindowMenu () { + return getToolbarMenu() + }, + + async triggerWindowMenuItemById (menu, id) { + return triggerMenuItemById(menu, id) } }) // internal methods // = -/** - * @description - * Ajust the bounds for margin and for right-alignment (as needed) - */ -function adjustBounds (view, parentWindow, bounds) { - let parentBounds = parentWindow.getContentBounds() - var isRightAligned = IS_RIGHT_ALIGNED.includes(view.menuId) - return { - x: isRightAligned - ? (parentBounds.width - bounds.width - bounds.x - MARGIN_SIZE) - : (bounds.x - MARGIN_SIZE), - y: bounds.y, - width: bounds.width + (MARGIN_SIZE * 2), - height: bounds.height + MARGIN_SIZE +function adjustPosition (bounds, view, parentWindow) { + if (IS_RIGHT_ALIGNED.includes(view.menuId)) { + let parentBounds = parentWindow.getContentBounds() + bounds.x = (parentBounds.width - bounds.width - bounds.x) + MARGIN_SIZE + } else { + bounds.x = bounds.x - MARGIN_SIZE } } +function adjustDimensions (bounds) { + bounds.width = bounds.width + (MARGIN_SIZE * 2), + bounds.height = bounds.height + MARGIN_SIZE +} + function getParentWindow (sender) { var view = BrowserView.fromWebContents(sender) for (let id in views) { diff --git a/app/bg/ui/subwindows/sidebars.js b/app/bg/ui/subwindows/sidebars.js new file mode 100644 index 0000000000..88ac08923e --- /dev/null +++ b/app/bg/ui/subwindows/sidebars.js @@ -0,0 +1,121 @@ +/** + * Sidebar + * + * NOTES + * - Sidebar views are created as-needed, and desroyed when not in use + * - Sidebar views are attached to individual BrowserView instances + * - Sidebar views are shown and hidden based on whether its owning BrowserView is visible + */ + +import path from 'path' +import { BrowserView } from 'electron' +import * as tabManager from '../tab-manager' +import { findWebContentsParentWindow } from '../../lib/electron' +import { getAddedWindowSettings } from '../windows' + +// globals +// = + +export const SIDEBAR_Y = 76 +export const SIDEBAR_EDGE_PADDING = 6 +export const HALF_SIDEBAR_EDGE_PADDING = SIDEBAR_EDGE_PADDING / 2 +var views = {} // map of {[tab.id] => BrowserView} + +// exported api +// = + +export function setup (parentWindow) { +} + +export function destroy (parentWindow) { + // destroy all under this window + for (let tab of tabManager.getAll(parentWindow)) { + if (tab.id in views) { + views[tab.id].destroy() + delete views[tab.id] + } + } +} + +export function reposition (parentWindow) { + // reposition all under this window + for (let tab of tabManager.getAll(parentWindow)) { + if (tab.id in views) { + setBounds(views[tab.id], tab) + } + } +} + +export function repositionOne (tab) { + if (tab.id in views) { + setBounds(views[tab.id], tab) + } +} + +export function create (tab) { + // make sure a sidebar doesnt already exist + if (tab.id in views) { + return + } + + // create the view + var view = views[tab.id] = new BrowserView({ + webPreferences: { + preload: path.join(__dirname, 'fg', 'webview-preload', 'index.build.js'), + nodeIntegrationInSubFrames: true, + contextIsolation: true, + webviewTag: false, + sandbox: true, + defaultEncoding: 'utf-8', + nativeWindowOpen: true, + nodeIntegration: false, + scrollBounce: true, + navigateOnDragDrop: true + } + }) + return view +} + +export function get (tab) { + return views[tab.id] +} + +export function findContainingWindow (sidebarView) { + return findWebContentsParentWindow(sidebarView.webContents) +} + +export function show (tab) { + if (tab.id in views) { + tab.browserWindow.addBrowserView(views[tab.id]) + setBounds(views[tab.id], tab) + } +} + +export function hide (tab) { + if (tab.id in views) { + tab.browserWindow.removeBrowserView(views[tab.id]) + } +} + +export function close (tab) { + if (tab.id in views) { + tab.browserWindow.removeBrowserView(views[tab.id]) + views[tab.id].destroy() + delete views[tab.id] + } +} + +// internal methods +// = + +function setBounds (sidebarView, tab) { + var parentBounds = tab.browserWindow.getContentBounds() + var y = getAddedWindowSettings(tab.browserWindow).isShellInterfaceHidden ? 0 : (SIDEBAR_Y + tabManager.TOOLBAR_HEIGHT) + var height = parentBounds.height - y + sidebarView.setBounds({ + x: 0, + y, + width: tab.sidebarWidth - HALF_SIDEBAR_EDGE_PADDING, + height + }) +} diff --git a/app/bg/ui/subwindows/site-info.js b/app/bg/ui/subwindows/site-info.js new file mode 100644 index 0000000000..ba7c27dccd --- /dev/null +++ b/app/bg/ui/subwindows/site-info.js @@ -0,0 +1,155 @@ +/** + * Site Infos + * + * NOTES + * - There can only ever be one Site Info view for a given browser window + * - Site Info views are created with each browser window and then shown/hidden as needed + * - The Site Info view contains the UIs for multiple menus and swaps between them as needed + * - When unfocused, the Site Info view is hidden (it's meant to act as a popup menu) + */ + +import path from 'path' +import Events from 'events' +import { BrowserWindow, BrowserView } from 'electron' +import * as tabManager from '../tab-manager' +import { getAddedWindowSettings } from '../windows' + +// globals +// = + +const MARGIN_SIZE = 10 +var events = new Events() +var views = {} // map of {[parentWindow.id] => BrowserView} + +// exported api +// = + +export function setup (parentWindow) { + var view = views[parentWindow.id] = new BrowserView({ + webPreferences: { + preload: path.join(__dirname, 'fg', 'webview-preload', 'index.build.js'), + nodeIntegrationInSubFrames: true, + contextIsolation: true, + webviewTag: false, + sandbox: true, + defaultEncoding: 'utf-8', + nativeWindowOpen: true, + nodeIntegration: false, + scrollBounce: true, + navigateOnDragDrop: true + } + }) + view.webContents.on('console-message', (e, level, message) => { + console.log('Site-Info window says:', message) + }) + view.webContents.loadURL('beaker://site-info/') +} + +export function destroy (parentWindow) { + if (get(parentWindow)) { + get(parentWindow).destroy() + delete views[parentWindow.id] + } +} + +export function get (parentWindow) { + return views[parentWindow.id] +} + +export function reposition (parentWindow) { + var view = get(parentWindow) + if (view) { + const setBounds = (b) => { + // HACK workaround the lack of view.getBounds() -prf + if (view.currentBounds) { + b = view.currentBounds // use existing bounds + } + view.currentBounds = b // store new bounds + view.setBounds(adjustBounds(view, parentWindow, b)) + } + setBounds({ + x: view.boundsOpt ? view.boundsOpt.left : 170, + y: getAddedWindowSettings(parentWindow).isShellInterfaceHidden ? 10 : 72, + width: 420, + height: 350 + }) + } +} + +export function resize (parentWindow, bounds = {}) { + var view = get(parentWindow) + if (view && view.currentBounds) { + view.currentBounds.width = bounds.width || view.currentBounds.width + view.currentBounds.height = bounds.height || view.currentBounds.height + view.setBounds(adjustBounds(view, parentWindow, view.currentBounds)) + } +} + +export function toggle (parentWindow, opts) { + var view = get(parentWindow) + if (view) { + if (view.isVisible) { + return hide(parentWindow) + } else { + return show(parentWindow, opts) + } + } +} + +export async function show (parentWindow, opts) { + var view = get(parentWindow) + if (view) { + view.boundsOpt = opts && opts.bounds + parentWindow.addBrowserView(view) + reposition(parentWindow) + view.isVisible = true + + var params = opts && opts.params ? opts.params : {} + params.url = tabManager.getActive(parentWindow).url + await view.webContents.executeJavaScript(`init(${JSON.stringify(params)})`) + view.webContents.focus() + + // await till hidden + await new Promise(resolve => { + events.once('hide', resolve) + }) + } +} + +export function hide (parentWindow) { + var view = get(parentWindow) + if (view) { + view.webContents.executeJavaScript(`reset()`) + parentWindow.removeBrowserView(view) + view.currentBounds = null + view.isVisible = false + events.emit('hide') + } +} + +// internal methods +// = + +/** + * @description + * Ajust the bounds for margin + */ +function adjustBounds (view, parentWindow, bounds) { + let parentBounds = parentWindow.getContentBounds() + return { + x: bounds.x - MARGIN_SIZE, + y: bounds.y, + width: bounds.width + (MARGIN_SIZE * 2), + height: bounds.height + MARGIN_SIZE + } +} + +function getParentWindow (sender) { + var view = BrowserView.fromWebContents(sender) + for (let id in views) { + if (views[id] === view) { + return BrowserWindow.fromId(+id) + } + } + throw new Error('Parent window not found') +} \ No newline at end of file diff --git a/app/background-process/ui/subwindows/status-bar.js b/app/bg/ui/subwindows/status-bar.js similarity index 84% rename from app/background-process/ui/subwindows/status-bar.js rename to app/bg/ui/subwindows/status-bar.js index ac9d4dc724..4c25021baa 100644 --- a/app/background-process/ui/subwindows/status-bar.js +++ b/app/bg/ui/subwindows/status-bar.js @@ -6,9 +6,9 @@ * - Status Bar views are created with each browser window and then shown/hidden as needed */ import { BrowserView } from 'electron' +import path from 'path' -const WIDTH = 400 -const HEIGHT = 24 +const HEIGHT = 25 // globals // = @@ -24,7 +24,7 @@ export function setup (parentWindow) { defaultEncoding: 'utf-8' } }) - view.webContents.loadFile('status-bar.html') + view.webContents.loadFile(path.join(__dirname, 'fg', 'status-bar', 'index.html')) view.webContents.executeJavaScript(`set(false)`) show(parentWindow) } @@ -43,8 +43,8 @@ export function get (parentWindow) { export function reposition (parentWindow) { var view = get(parentWindow) if (view) { - var {height} = parentWindow.getContentBounds() - view.setBounds({x: 0, y: height - HEIGHT, width: WIDTH, height: HEIGHT}) + var {width, height} = parentWindow.getContentBounds() + view.setBounds({x: 0, y: height - HEIGHT, width, height: HEIGHT}) } } diff --git a/app/bg/ui/subwindows/tab-switcher.js b/app/bg/ui/subwindows/tab-switcher.js new file mode 100644 index 0000000000..d7358cd30a --- /dev/null +++ b/app/bg/ui/subwindows/tab-switcher.js @@ -0,0 +1,114 @@ +/** + * Prompts + * + * NOTES + * - There is one tab-switcher per BrowserWindow instance + * - Status Bar views are created with each browser window and then shown/hidden as needed + */ + +import path from 'path' +import { BrowserWindow, BrowserView } from 'electron' +import * as rpc from 'pauls-electron-rpc' +import * as tabManager from '../tab-manager' +// import promptsRPCManifest from '../../rpc-manifests/prompts' +import { findWebContentsParentWindow } from '../../lib/electron' + +// constants +// = + +const VIEW_MARGIN = 10 +const TAB_ENTRY_WIDTH = 120 +const TAB_GAP = 10 +const HEIGHT = 114 + (VIEW_MARGIN * 2) + +// globals +// = + +var views = {} // map of {[parentWindow.id] => BrowserView} + +// exported api +// = + +export function setup (parentWindow) { + var view = views[parentWindow.id] = new BrowserView({ + webPreferences: { + defaultEncoding: 'utf-8' + } + }) + view.webContents.loadFile(path.join(__dirname, 'fg', 'tab-switcher', 'index.html')) +} + +export function destroy (parentWindow) { + if (get(parentWindow)) { + get(parentWindow).destroy() + delete views[parentWindow.id] + } +} + +export function get (parentWindow) { + return views[parentWindow.id] +} + +export function reposition (parentWindow) { + var view = get(parentWindow) + if (view) { + var {width, height} = parentWindow.getContentBounds() + var numTabs = (view.tabs ? view.tabs.length : 0) + + var viewWidth = ( + (numTabs * TAB_ENTRY_WIDTH) // tab entry width + + ((numTabs - 1) * TAB_GAP) // tab entry grid-gap + + 20 // tab-switcher body padding + + (VIEW_MARGIN * 2) // tab-switcher body margin + ) + var viewHeight = HEIGHT + + if (viewWidth > (width - 100)) { + viewWidth = width - 100 + } + + view.setBounds({x: ((width / 2) - (viewWidth/ 2))|0, y: ((height / 2) - (viewHeight / 2))|0, width: viewWidth, height: viewHeight}) + } +} + +export function show (parentWindow) { + var view = get(parentWindow) + if (view) { + // read the current tabs state + var defaultCurrentSelection = tabManager.getPreviousTabIndex(parentWindow) + var tabs = tabManager.getAll(parentWindow).map(tab => ({ + url: tab.url, + title: tab.title + })) + view.tabs = tabs + + // render + parentWindow.addBrowserView(view) + view.webContents.executeJavaScript(` + window.setTabs(${JSON.stringify(tabs)}, ${defaultCurrentSelection}) + `) + reposition(parentWindow) + } +} + +export async function hide (parentWindow) { + var view = get(parentWindow) + if (view) { + var selectedTabIndex = await view.webContents.executeJavaScript(` + window.getSelection() + `) + if (typeof selectedTabIndex === 'number') { + tabManager.setActive(parentWindow, selectedTabIndex) + } + parentWindow.removeBrowserView(view) + } +} + +export async function moveSelection (parentWindow, dir) { + var view = get(parentWindow) + if (view) { + await view.webContents.executeJavaScript(` + window.moveSelection(${dir}) + `) + } +} diff --git a/app/bg/ui/tab-manager.js b/app/bg/ui/tab-manager.js new file mode 100644 index 0000000000..4e066799e9 --- /dev/null +++ b/app/bg/ui/tab-manager.js @@ -0,0 +1,1880 @@ +import { app, dialog, BrowserView, BrowserWindow, Menu, clipboard, ipcMain, webContents } from 'electron' +import errorPage from '../lib/error-page' +import * as libTools from '@beaker/library-tools' +import path from 'path' +import { promises as fs } from 'fs' +import { EventEmitter } from 'events' +import _throttle from 'lodash.throttle' +import { parseDriveUrl } from '../../lib/urls' +import emitStream from 'emit-stream' +import _get from 'lodash.get' +import _pick from 'lodash.pick' +import * as rpc from 'pauls-electron-rpc' +import viewsRPCManifest from '../rpc-manifests/views' +import * as zoom from './tabs/zoom' +import * as shellMenus from './subwindows/shell-menus' +import * as locationBar from './subwindows/location-bar' +import * as statusBar from './subwindows/status-bar' +import * as prompts from './subwindows/prompts' +import * as permPrompt from './subwindows/perm-prompt' +import * as modals from './subwindows/modals' +import * as sidebars from './subwindows/sidebars' +import * as siteInfo from './subwindows/site-info' +import * as windowMenu from './window-menu' +import { createShellWindow, getAddedWindowSettings, getOrCreateNonAppWindow } from './windows' +import { getResourceContentType } from '../browser' +import * as setupFlow from './setup-flow' +import { examineLocationInput } from '../../lib/urls' +import { clamp } from '../../lib/math' +import { DRIVE_KEY_REGEX, slugify } from '../../lib/strings' +import { findWebContentsParentWindow } from '../lib/electron' +import * as sitedataDb from '../dbs/sitedata' +import * as settingsDb from '../dbs/settings' +import * as historyDb from '../dbs/history' +import * as filesystem from '../filesystem/index' +import * as bookmarks from '../filesystem/bookmarks' +import hyper from '../hyper/index' + +const ERR_ABORTED = -3 +const ERR_CONNECTION_REFUSED = -102 +const ERR_INSECURE_RESPONSE = -501 +const TLS_ERROR_CODES = Object.values({ + ERR_NO_SSL_VERSIONS_ENABLED: -112, + ERR_SSL_VERSION_OR_CIPHER_MISMATCH: -113, + ERR_SSL_RENEGOTIATION_REQUESTED: -114, + ERR_PROXY_AUTH_UNSUPPORTED: -115, + ERR_CERT_ERROR_IN_SSL_RENEGOTIATION: -116, + ERR_BAD_SSL_CLIENT_AUTH_CERT: -117, + ERR_SSL_NO_RENEGOTIATION: -123, + ERR_SSL_WEAK_SERVER_EPHEMERAL_DH_KEY: -129, + ERR_PROXY_CERTIFICATE_INVALID: -136, + ERR_SSL_HANDSHAKE_NOT_COMPLETED: -148, + ERR_SSL_BAD_PEER_PUBLIC_KEY: -149, + ERR_SSL_PINNED_KEY_NOT_IN_CERT_CHAIN: -150, + ERR_CLIENT_AUTH_CERT_TYPE_UNSUPPORTED: -151, + ERR_SSL_DECRYPT_ERROR_ALERT: -153, + ERR_SSL_SERVER_CERT_CHANGED: -156, + ERR_SSL_UNRECOGNIZED_NAME_ALERT: -159, + ERR_SSL_SERVER_CERT_BAD_FORMAT: -167, + ERR_CT_STH_PARSING_FAILED: -168, + ERR_CT_STH_INCOMPLETE: -169, + ERR_CT_CONSISTENCY_PROOF_PARSING_FAILED: -171, + ERR_SSL_OBSOLETE_CIPHER: -172, + ERR_SSL_VERSION_INTERFERENCE: -175, + ERR_EARLY_DATA_REJECTED: -178, + ERR_WRONG_VERSION_ON_EARLY_DATA: -179, + ERR_TLS13_DOWNGRADE_DETECTED: -180 +}) +const IS_CODE_INSECURE_RESPONSE = x => x === ERR_CONNECTION_REFUSED || x === ERR_INSECURE_RESPONSE || (x <= -200 && x > -300) || TLS_ERROR_CODES.includes(x) + +const Y_POSITION = 76 +export const TOOLBAR_HEIGHT = 18 +const TRIGGER_LIVE_RELOAD_DEBOUNCE = 500 // throttle live-reload triggers by this amount + +// the variables which are automatically sent to the shell-window for rendering +const STATE_VARS = [ + 'url', + 'title', + 'siteTitle', + 'siteSubtitle', + 'siteIcon', + 'siteTrust', + 'driveDomain', + 'isSystemDrive', + 'writable', + 'peers', + 'favicons', + 'zoom', + 'loadError', + 'isActive', + 'isPinned', + 'isBookmarked', + 'isLoading', + 'isReceivingAssets', + 'canGoBack', + 'canGoForward', + 'isAudioMuted', + 'isCurrentlyAudible', + 'isSidebarActive', + 'sidebarPanels', + 'sidebarWidth', + 'isInpageFindActive', + 'currentInpageFindString', + 'currentInpageFindResults', + // 'availableAlternative', + 'donateLinkHref', + 'isLiveReloading' +] + +// globals +// = + +var activeTabs = {} // map of {[win.id]: Array} +var preloadedNewTabs = {} // map of {[win.id]: Tab} +var lastSelectedTabIndex = {} // map of {[win.id]: Number} +var closedURLs = {} // map of {[win.id]: Array} +var windowEvents = {} // mapof {[win.id]: EventEmitter} +var noRedirectHostnames = new Set() // set of hostnames which have drive-redirection disabled +var nextTabIsScriptCloseable = false // will the next tab created be "script closable"? +var defaultUrl = 'beaker://desktop/' + +// classes +// = + +class Tab extends EventEmitter { + constructor (win, opts = {isPinned: false, isHidden: false}) { + super() + this.browserWindow = win + this.browserView = new BrowserView({ + webPreferences: { + preload: path.join(__dirname, 'fg', 'webview-preload', 'index.build.js'), + nodeIntegrationInSubFrames: true, + contextIsolation: true, + webviewTag: false, + sandbox: true, + defaultEncoding: 'utf-8', + nativeWindowOpen: true, + nodeIntegration: false, + scrollBounce: true, + navigateOnDragDrop: true, + enableRemoteModule: false, + safeDialogs: true + } + }) + this.browserView.setBackgroundColor('#fff') + + // webview state + this.loadingURL = null // URL being loaded, if any + this.isLoading = false // is the tab loading? + this.isReceivingAssets = false // has the webview started receiving assets in the current load-cycle? + this.favicons = null // array of favicon URLs + this.zoom = 0 // what's the current zoom level? + this.loadError = null // page error state, if any + this.previouslyFocusedWebcontents = undefined // the webcontents which was focused when the tab was last deactivated + this.mainFrameId = undefined // the frameRoutingId of the main frame + this.frameUrls = {} // map of frameRoutingId -> string (url) + + // browser state + this.isHidden = opts.isHidden // is this tab hidden from the user? used for the preloaded tab + this.isActive = false // is this the active page in the window? + this.isPinned = Boolean(opts.isPinned) // is this page pinned? + this.isSidebarActive = false // is the sidebar open? + this.sidebarPanels = new Set() // the active sidebar panels + this.sidebarWidth = undefined // what is the current sidebar width? + this.liveReloadEvents = null // live-reload event stream + this.isInpageFindActive = false // is the inpage-finder UI active? + this.currentInpageFindString = undefined // what's the current inpage-finder query string? + this.currentInpageFindResults = undefined // what's the current inpage-finder query results? + this.isScriptClosable = takeIsScriptClosable() // can this tab be closed by `window.close` ? + + // helper state + this.peers = 0 // how many peers does the site have? + this.isBookmarked = false // is the active page bookmarked? + this.driveInfo = null // metadata about the site if viewing a hyperdrive + this.confirmedAuthorTitle = undefined // the title of the confirmed author of the site + this.donateLinkHref = null // the URL of the donate site, if set by the index.json + this.availableAlternative = '' // tracks if there's alternative protocol available for the site + this.wasDriveTimeout = false // did the last navigation result in a timed-out hyperdrive? + + // wire up events + this.webContents.on('will-navigate', this.onWillNavigate.bind(this)) + this.webContents.on('did-start-loading', this.onDidStartLoading.bind(this)) + this.webContents.on('did-start-navigation', this.onDidStartNavigation.bind(this)) + this.webContents.on('did-navigate', this.onDidNavigate.bind(this)) + this.webContents.on('did-navigate-in-page', this.onDidNavigateInPage.bind(this)) + this.webContents.on('did-stop-loading', this.onDidStopLoading.bind(this)) + this.webContents.on('dom-ready', this.onDomReady.bind(this)) + this.webContents.on('did-fail-load', this.onDidFailLoad.bind(this)) + this.webContents.on('update-target-url', this.onUpdateTargetUrl.bind(this)) + this.webContents.on('page-title-updated', this.onPageTitleUpdated.bind(this)) // NOTE page-title-updated isn't documented on webContents but it is supported + this.webContents.on('page-favicon-updated', this.onPageFaviconUpdated.bind(this)) + this.webContents.on('new-window', this.onNewWindow.bind(this)) + this.webContents.on('media-started-playing', this.onMediaChange.bind(this)) + this.webContents.on('media-paused', this.onMediaChange.bind(this)) + this.webContents.on('found-in-page', this.onFoundInPage.bind(this)) + + // security - deny these events + const deny = e => e.preventDefault() + this.webContents.on('remote-require', deny) + this.webContents.on('remote-get-global', deny) + this.webContents.on('remote-get-builtin', deny) + this.webContents.on('remote-get-current-window', deny) + this.webContents.on('remote-get-current-web-contents', deny) + this.webContents.on('remote-get-guest-web-contents', deny) + } + + get id () { + return this.browserView.id + } + + get webContents () { + return this.browserView.webContents + } + + get url () { + return this.webContents.getURL() + } + + get origin () { + return toOrigin(this.url) + } + + get title () { + var title = this.webContents.getTitle() + if (this.driveInfo && this.driveInfo.title && (!title || toOrigin(title) === this.origin)) { + // fallback to the index.json title field if the page doesnt provide a title + title = this.driveInfo.title + } + return title + } + + get siteTitle () { + try { + var urlp = new URL(this.loadingURL || this.url) + var hostname = urlp.hostname + if (DRIVE_KEY_REGEX.test(hostname)) { + hostname = hostname.replace(DRIVE_KEY_REGEX, v => `${v.slice(0, 6)}..${v.slice(-2)}`) + } + if (hostname.includes('+')) { + hostname = hostname.replace(/\+[\d]+/, '') + } + if (this.driveInfo) { + var ident = _get(this.driveInfo, 'ident', {}) + if (ident.system) { + return 'My System Drive' + } + if (this.driveInfo.writable || ident.contact) { + if (this.driveInfo.title) { + return this.driveInfo.title + } + } + } + if (urlp.protocol === 'beaker:') { + return 'Beaker' + } + return hostname + (urlp.port ? `:${urlp.port}` : '') + } catch (e) { + return '' + } + } + + get siteSubtitle () { + if (this.driveInfo) { + var origin = this.origin + var version = /\+([\d]+)/.exec(origin) ? `v${/\+([\d]+)/.exec(origin)[1]}` : '' + var forkLabel = _get(filesystem.listDrives().find(d => d.key === this.driveInfo.key), 'forkOf.label', '') + return [forkLabel, version].filter(Boolean).join(' ') + } + return '' + } + + get siteIcon () { + if (this.driveInfo) { + var ident = this.driveInfo.ident || {} + if (ident.contact || ident.profile) { + return 'fas fa-user-circle' + } + if (this.driveInfo.writable) { + return 'fas fa-check-circle' + } + } + var url = this.loadingURL || this.url + if (url.startsWith('https:') && !(this.loadError && this.loadError.isInsecureResponse)) { + return 'fas fa-check-circle' + } + if (url.startsWith('beaker:')) { + return 'beaker-logo' + } + return 'fas fa-info-circle' + } + + get siteTrust () { + try { + var urlp = new URL(this.loadingURL || this.url) + if (this.loadError && this.loadError.isInsecureResponse) { + return 'untrusted' + } + if (['https:', 'beaker:'].includes(urlp.protocol)) { + return 'trusted' + } + if (urlp.protocol === 'http:') { + return 'untrusted' + } + if (urlp.protocol === 'hyper:' && this.driveInfo) { + if (this.driveInfo.writable || this.driveInfo.ident.internal || this.driveInfo.ident.contact) { + return 'trusted' + } + } + } catch (e) { + } + return 'notrust' + } + + get driveDomain () { + return _get(this.driveInfo, 'domain', '') + } + + get isSystemDrive () { + return _get(this.driveInfo, 'ident.system', false) + } + + get writable () { + return _get(this.driveInfo, 'writable', false) + } + + get canGoBack () { + return this.webContents.canGoBack() + } + + get canGoForward () { + return this.webContents.canGoForward() + } + + get isAudioMuted () { + return this.webContents.isAudioMuted() + } + + get isCurrentlyAudible () { + return this.webContents.isCurrentlyAudible() + } + + get isLiveReloading () { + return !!this.liveReloadEvents + } + + get state () { + var state = _pick(this, STATE_VARS) + state.sidebarPanels = Array.from(state.sidebarPanels) // convert from a set to an array + if (this.loadingURL) state.url = this.loadingURL + return state + } + + getIPCSenderInfo (event) { + if (event.sender === this.webContents) { + return { + url: this.frameUrls[event.frameId], + isMainFrame: event.frameId === this.mainFrameId + } + } + if (sidebars.get(this) && event.sender === sidebars.get(this).webContents) { + return { + url: sidebars.get(this).webContents.getURL(), + isMainFrame: true + } + } + return {url: '', isMainFrame: false} + } + + // management + // = + + loadURL (url, opts) { + if (url === '$new_tab') { + url = defaultUrl + } + if (getAddedWindowSettings(this.browserWindow).isAppWindow) { + if (this.url && toOrigin(this.url) !== toOrigin(url)) { + // we never navigate out of app windows + // instead, create a new tab, which will cause it to open in a normal window + create(this.browserWindow, url, {setActive: true}) + } + } else { + this.browserView.webContents.loadURL(url, opts) + } + } + + calculateBounds (windowBounds) { + var x = 0 + var y = Y_POSITION + TOOLBAR_HEIGHT + var {width, height} = windowBounds + if (getAddedWindowSettings(this.browserWindow).isShellInterfaceHidden) { + y = 0 + } + if (this.isSidebarActive) { + // sidebar takes left side of the screen + x = (this.sidebarWidth + sidebars.HALF_SIDEBAR_EDGE_PADDING) + width -= (this.sidebarWidth + sidebars.HALF_SIDEBAR_EDGE_PADDING) + } + return {x, y: y, width, height: height - y} + } + + resize () { + var {width, height} = this.browserWindow.getContentBounds() + if (this.isSidebarActive && width < this.sidebarWidth + 100) { + // shrink the sidebar to ensure the content stays visible + this.setSidebarWidth(width - 100) + } + this.browserView.setBounds(this.calculateBounds({width, height})) + prompts.reposition(this.browserWindow) + } + + activate () { + this.isActive = true + + const win = this.browserWindow + win.addBrowserView(this.browserView) + sidebars.show(this) + prompts.show(this.browserView) + permPrompt.show(this.browserView) + modals.show(this.browserView) + + this.resize() + if (this.previouslyFocusedWebcontents && !this.previouslyFocusedWebcontents.isDestroyed()) { + this.previouslyFocusedWebcontents.focus() + } else { + this.webContents.focus() + } + this.previouslyFocusedWebcontents = undefined + this.emit('activated') + } + + focus () { + if (this.isSidebarActive) { + let sidebar = sidebars.get(this) + if (sidebar) sidebar.webContents.focus() + } else { + this.webContents.focus() + } + } + + deactivate () { + this.browserWindow.removeBrowserView(this.browserView) + if (this.isActive) { + shellMenus.hide(this.browserWindow) // this will close the location menu if it's open + } + prompts.hide(this.browserView) + permPrompt.hide(this.browserView) + modals.hide(this.browserView) + sidebars.hide(this) + siteInfo.hide(this.browserWindow) + var wasActive = this.isActive + this.isActive = false + if (wasActive) { + this.emit('deactivated') + } + } + + destroy () { + this.deactivate() + prompts.close(this.browserView) + permPrompt.close(this.browserView) + modals.close(this.browserView) + sidebars.close(this) + this.browserView.destroy() + this.emit('destroyed') + } + + awaitActive () { + return new Promise((resolve, reject) => { + const activated = () => { + this.removeListener('activated', activated) + this.removeListener('destroyed', destroyed) + resolve() + } + const destroyed = () => { + this.removeListener('activated', activated) + this.removeListener('destroyed', destroyed) + reject() + } + this.on('activated', activated) + this.on('destroyed', destroyed) + }) + } + + transferWindow (targetWindow) { + this.deactivate() + prompts.close(this.browserView) + permPrompt.close(this.browserView) + modals.close(this.browserView) + + this.browserWindow = targetWindow + } + + async updateHistory () { + var url = this.url + var title = this.title + + if (url !== 'beaker://desktop/' && url !== 'beaker://history/') { + historyDb.addVisit(0, {url, title}) + if (this.isPinned) { + savePins(this.browserWindow) + } + } + } + + toggleMuted () { + this.webContents.setAudioMuted(!this.isAudioMuted) + this.emitUpdateState() + } + + async openSidebar () { + if (this.isSidebarActive) return + + let v = sidebars.create(this) + v.webContents.loadURL('beaker://sidebar/') + var onDidFinishLoad = new Promise(r => v.webContents.on('did-finish-load', r)) + + this.sidebarWidth = clamp((this.browserWindow.getContentBounds().width / 2)|0, 100, 800) + if (this.isActive) { + sidebars.show(this) + v.webContents.focus() + } + + this.isSidebarActive = true + this.sidebarPanels.clear() + this.resize() + this.emitUpdateState() + await onDidFinishLoad + } + + async closeSidebar () { + if (!this.isSidebarActive) return + sidebars.close(this) + this.isSidebarActive = false + this.sidebarPanels.clear() + this.resize() + this.emitUpdateState() + } + + async executeSidebarCommand (cmd, ...args) { + if (getAddedWindowSettings(this.browserWindow).isAppWindow) { + // if in appwindow mode, open 'show-panel' as a new tab and ignore any other commands + if (cmd === 'show-panel') { + let url + let ctx = args[1] || this.url + switch (args[0]) { + case 'editor-app': url = `beaker://editor/?url=${encodeURI(ctx)}`; break + case 'files-explorer-app': url = `https://hyperdrive.network/${encodeURI(ctx.slice('hyper://'.length))}`; break + case 'web-term': url = `beaker://webterm/?url=${encodeURI(ctx)}`; break + } + if (url) { + create(this.browserWindow, url, {setActive: true}) + } + } + return + } + + const wc = () => sidebars.get(this).webContents + const execJs = (js) => wc().executeJavaScript(js) + switch (cmd) { + case 'hide-panel': + case 'close': + if (!this.isSidebarActive) return + default: + await this.openSidebar() + } + switch (cmd) { + case 'show-panel': wc().focus(); await execJs(`showPanel("${args[0]}", "${args[1] || this.url}")`); break + case 'toggle-panel': wc().focus(); await execJs(`togglePanel("${args[0]}", "${args[1] || this.url}")`); break + case 'hide-panel': await execJs(`hidePanel("${args[0]}")`); break + case 'set-context': await execJs(`setContext("${args[0]}", "${args[1] || this.url}")`); break + case 'set-all-contexts': await execJs(`setAllContexts("${args[0] || this.url}")`); break + case 'focus-panel': wc().focus(); await execJs(`setFocus("${args[0]}")`); break + case 'close': this.closeSidebar(); break + } + switch (cmd) { + case 'show-panel': this.sidebarPanels.add(args[0]); this.emitUpdateState(); break + case 'hide-panel': this.sidebarPanels.delete(args[0]); this.emitUpdateState(); break + case 'toggle-panel': + if (!this.sidebarPanels.has(args[0])) { + this.sidebarPanels.add(args[0]) + } else { + this.sidebarPanels.delete(args[0]) + } + this.emitUpdateState() + break + } + } + + setSidebarWidth (v) { + if (this.isSidebarActive) { + this.sidebarWidth = clamp(v|0, 100, this.browserWindow.getContentBounds().width - 100) + this.resize() + sidebars.repositionOne(this) + this.emitUpdateState({sidebarWidth: this.sidebarWidth}) + } + } + + async captureScreenshot () { + try { + // wait a sec to allow loading to finish + await new Promise(r => setTimeout(r, 2e3)) + + // capture the page + this.browserView.webContents.incrementCapturerCount({width: 1000, height: 800}, !this.isActive) + var image = await this.browserView.webContents.capturePage() + this.browserView.webContents.decrementCapturerCount(!this.isActive) + var bounds = image.getSize() + if (bounds.width === 0 || bounds.height === 0) return + if (bounds.width <= bounds.height) return // only save if it's a useful image + await sitedataDb.set(this.url, 'screenshot', image.toDataURL(), {dontExtractOrigin: true, normalizeUrl: true}) + } catch (e) { + // ignore, can happen if the tab was closed during wait + console.log('Failed to capture page screenshot', e) + } + } + + // inpage finder + // = + + showInpageFind () { + if (this.isInpageFindActive) { + // go to next result on repeat "show" commands + this.moveInpageFind(1) + } else { + this.isInpageFindActive = true + this.currentInpageFindResults = {activeMatchOrdinal: 0, matches: 0} + this.emitUpdateState() + } + this.browserWindow.webContents.focus() + } + + hideInpageFind () { + this.webContents.stopFindInPage('clearSelection') + this.isInpageFindActive = false + this.currentInpageFindString = undefined + this.currentInpageFindResults = undefined + this.emitUpdateState() + } + + setInpageFindString (str, dir) { + var findNext = this.currentInpageFindString === str + this.currentInpageFindString = str + this.webContents.findInPage(this.currentInpageFindString, {findNext, forward: dir !== -1}) + } + + moveInpageFind (dir) { + if (!this.currentInpageFindString) return + this.webContents.findInPage(this.currentInpageFindString, {findNext: false, forward: dir !== -1}) + } + + // alternative protocols + // = + + async checkForHyperdriveAlternative (url) { + return // DISABLED +/* + let u = (new URL(url)) + // try to do a name lookup + var siteHasDatAlternative = await hyper.dns.resolveName(u.hostname).then( + res => Boolean(res), + err => false + ) + if (siteHasDatAlternative) { + var autoRedirectToDat = !!await settingsDb.get('auto_redirect_to_dat') + if (autoRedirectToDat && !noRedirectHostnames.has(u.hostname)) { + // automatically redirect + let datUrl = url.replace(u.protocol, 'dat:') + this.loadURL(datUrl) + } else { + // track that dat is available + this.availableAlternative = 'dat:' + } + } else { + this.availableAlternative = '' + } + this.emitUpdateState()*/ + } + + // live reloading + // = + + async toggleLiveReloading (enable) { + if (typeof enable === 'undefined') { + enable = !this.liveReloadEvents + } + if (this.liveReloadEvents) { + this.liveReloadEvents.destroy() + this.liveReloadEvents = false + } else if (this.driveInfo) { + let drive = hyper.drives.getDrive(this.driveInfo.key) + if (!drive) return + + let {version} = parseDriveUrl(this.url) + let {checkoutFS} = await hyper.drives.getDriveCheckout(drive, version) + this.liveReloadEvents = await checkoutFS.pda.watch() + + const reload = _throttle(() => { + this.browserView.webContents.reload() + }, TRIGGER_LIVE_RELOAD_DEBOUNCE, {leading: false}) + this.liveReloadEvents.on('data', ([evt]) => { + if (evt === 'changed') reload() + }) + // ^ note this throttle is run on the front edge. + // That means snappier reloads (no delay) but possible double reloads if multiple files change + } + this.emitUpdateState() + } + + stopLiveReloading () { + if (this.liveReloadEvents) { + this.liveReloadEvents.destroy() + this.liveReloadEvents = false + this.emitUpdateState() + } + } + + // custom renderers + // = + + async injectCustomRenderers () { + // determine content type + let contentType = getResourceContentType(this.url) || '' + let isPlainText = contentType.startsWith('text/plain') + let isJSON = contentType.startsWith('application/json') || (isPlainText && this.url.endsWith('.json')) + let isJS = contentType.includes('/javascript') || (isPlainText && this.url.endsWith('.js')) + let isCSS = contentType.startsWith('text/css') || (isPlainText && this.url.endsWith('.css')) + + // json rendering + // inject the json render script + if (isJSON) { + this.webContents.insertCSS(` + .hidden { display: none !important; } + .json-formatter-row { + font-family: Consolas, 'Lucida Console', Monaco, monospace !important; + line-height: 1.6 !important; + font-size: 13px; + } + .json-formatter-row > a > .json-formatter-preview-text { + transition: none !important; + } + nav { margin-bottom: 5px; user-select: none; } + nav > span { + cursor: pointer; + display: inline-block; + font-family: Consolas, "Lucida Console", Monaco, monospace; + cursor: pointer; + font-size: 13px; + background: rgb(250, 250, 250); + padding: 3px 5px; + margin-right: 5px; + } + nav > span.pressed { + box-shadow: inset 2px 2px 2px rgba(0,0,0,.05); + background: #ddd; + } + `) + let jsonpath = path.join(app.getAppPath(), 'fg', 'json-renderer', 'index.build.js') + jsonpath = jsonpath.replace('app.asar', 'app.asar.unpacked') // fetch from unpacked dir + try { + await this.webContents.executeJavaScript(await fs.readFile(jsonpath, 'utf8')) + } catch (e) { + // ignore + } + } + // js/css syntax highlighting + if (isJS || isCSS) { + this.webContents.insertCSS(` + .hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: white; + color: black; + } + .hljs-comment, .hljs-quote, .hljs-variable { color: #008000; } + .hljs-keyword, .hljs-selector-tag, .hljs-built_in, .hljs-name, .hljs-tag { color: #00f; } + .hljs-string, .hljs-title, .hljs-section, .hljs-attribute, .hljs-literal, .hljs-template-tag, .hljs-template-variable, .hljs-type, .hljs-addition { color: #a31515; } + .hljs-deletion, .hljs-selector-attr, .hljs-selector-pseudo, .hljs-meta { color: #2b91af; } + .hljs-doctag { color: #808080; } + .hljs-attr { color: #f00; } + .hljs-symbol, .hljs-bullet, .hljs-link { color: #00b0e8; } + .hljs-emphasis { font-style: italic; } + .hljs-strong { font-weight: bold; } + `) + let scriptpath = path.join(app.getAppPath(), 'fg', 'syntax-highlighter', 'index.js') + scriptpath = scriptpath.replace('app.asar', 'app.asar.unpacked') // fetch from unpacked dir + try { + await this.webContents.executeJavaScript(await fs.readFile(scriptpath, 'utf8')) + } catch (e) { + // ignore + } + } + } + + // state fetching + // = + + // helper called by UIs to pull latest state if a change event has occurred + // eg called by the bookmark systems after the bookmark state has changed + async refreshState () { + await Promise.all([ + this.fetchIsBookmarked(true), + this.fetchDriveInfo(true) + ]) + this.emitUpdateState() + } + + async fetchIsBookmarked (noEmit = false) { + var wasBookmarked = this.isBookmarked + this.isBookmarked = !!(await bookmarks.get(this.url)) + if (this.isBookmarked && !wasBookmarked) { + this.captureScreenshot() + } + if (!noEmit) { + this.emitUpdateState() + } + } + + async fetchDriveInfo (noEmit = false) { + // clear existing state + this.peers = 0 + this.confirmedAuthorTitle = undefined + this.donateLinkHref = null + + if (!this.url.startsWith('hyper://')) { + this.driveInfo = null + return + } + + // fetch new state + var key + try { + key = await hyper.dns.resolveName(this.url) + this.driveInfo = await hyper.drives.getDriveInfo(key) + this.driveInfo.ident = await filesystem.getDriveIdent(this.driveInfo.url, true) + this.peers = this.driveInfo.peers + this.donateLinkHref = _get(this, 'driveInfo.links.payment.0.href') + } catch (e) { + this.driveInfo = null + } + if (!noEmit) this.emitUpdateState() + } + + async getPageMetadata () { + var metadata + try { + metadata = this.webContents.executeJavaScript(`window.__beakerGetPageMetadata()`) + } catch (e) { + // ignore + } + return metadata || {} + } + + // events + // = + + emitUpdateState (state = undefined) { + if (this.isHidden) return + emitUpdateState(this.browserWindow, this, state) + } + + onWillNavigate (e, url) { + if (getAddedWindowSettings(this.browserWindow).isAppWindow) { + if (this.url && toOrigin(this.url) !== toOrigin(url)) { + // we never navigate out of app windows + // instead, create a new tab, which will cause it to open in a normal window + e.preventDefault() + create(this.browserWindow, url, {setActive: true}) + } + } + } + + onDidStartLoading (e) { + // update state + this.isLoading = true + this.loadingURL = null + this.isReceivingAssets = false + this.wasDriveTimeout = false + + // emit + this.emitUpdateState() + } + + onDidStartNavigation (e, url, isInPlace, isMainFrame, frameProcessId, frameRoutingId) { + this.frameUrls[frameRoutingId] = url + if (!isMainFrame) return + this.mainFrameId = frameRoutingId + var origin = toOrigin(url) + + // turn off live reloading if we're leaving the domain + if (origin !== toOrigin(this.url)) { + this.stopLiveReloading() + } + + // update state + this.loadingURL = url + this.emitUpdateState() + } + + async onDidNavigate (e, url, httpResponseCode) { + // remove any active subwindows + prompts.close(this.browserView) + modals.close(this.browserView) + + // read zoom + zoom.setZoomFromSitedata(this) + + // update state + this.loadError = null + this.loadingURL = null + this.isReceivingAssets = true + this.favicons = null + this.frameUrls = {[this.mainFrameId]: url} // drop all non-main-frame URLs + if (this.isSidebarActive) { + this.executeSidebarCommand('set-all-contexts', this.url) + } + await this.fetchIsBookmarked() + await this.fetchDriveInfo() + if (httpResponseCode === 504 && url.startsWith('hyper://')) { + this.wasDriveTimeout = true + } + if (httpResponseCode === 404 && this.writable) { + // prompt to create a page on 404 for owned sites + prompts.create(this.browserView.webContents, 'create-page', {url: this.url}) + } else if (!setupFlow.hasVisitedProfile && this.driveInfo && this.driveInfo.ident.profile) { + prompts.create(this.browserView.webContents, 'edit-profile', {url: this.url}) + } + + // emit + this.emitUpdateState() + } + + onDidNavigateInPage (e) { + this.fetchIsBookmarked() + this.updateHistory() + } + + onDidStopLoading () { + this.updateHistory() + + // update state + this.isLoading = false + this.loadingURL = null + this.isReceivingAssets = false + + // check for hyperdrive alternatives + if (this.url.startsWith('https://')) { + this.checkForHyperdriveAlternative(this.url) + } else { + this.availableAlternative = '' + } + + // run custom renderer apps + this.injectCustomRenderers() + + // emit + windowMenu.onSetCurrentLocation(this.browserWindow, this.url) + this.emitUpdateState() + } + + onDomReady (e) { + // HACK + // sometimes 'did-stop-loading' doesnt get fired + // not sure why, but 'dom-ready' indicates that loading is done + // if still isLoading or isReceivingAssets, run the did-stop-loading handler + // -prf + if (this.isLoading || this.isReceivingAssets) { + this.onDidStopLoading() + } + } + + async onDidFailLoad (e, errorCode, errorDescription, validatedURL, isMainFrame) { + // ignore if this is a subresource + if (!isMainFrame) return + + // ignore aborts. why: + // - sometimes, aborts are caused by redirects. no biggy + // - if the user cancels, then we dont want to give an error screen + if (errorDescription == 'ERR_ABORTED' || errorCode == ERR_ABORTED) return + + // also ignore non-errors + if (errorCode == 0) return + + // update state + var isInsecureResponse = IS_CODE_INSECURE_RESPONSE(errorCode) + this.loadError = {isInsecureResponse, errorCode, errorDescription, validatedURL} + this.emitUpdateState() + + // render failure page + var errorPageHTML = errorPage(this.loadError) + try { + await this.webContents.executeJavaScript('document.documentElement.innerHTML = \'' + errorPageHTML + '\'') + } catch (e) { + // ignore + } + } + + onUpdateTargetUrl (e, url) { + if (this.browserWindow.isDestroyed()) return + statusBar.set(this.browserWindow, url) + } + + onPageTitleUpdated (e, title) { + if (this.browserWindow.isDestroyed()) return + if (getAddedWindowSettings(this.browserWindow).isAppWindow) { + this.browserWindow.setTitle(title) + } + this.emitUpdateState() + } + + onPageFaviconUpdated (e, favicons) { + this.favicons = favicons && favicons[0] ? favicons : null + this.emitUpdateState() + } + + onNewWindow (e, url, frameName, disposition, options) { + e.preventDefault() + if (!this.isActive) return // only open if coming from the active tab + var setActive = (disposition === 'foreground-tab' || disposition === 'new-window') + var newTab = create(this.browserWindow, url, {setActive, adjacentActive: true}) + } + + onMediaChange (e) { + // our goal with this event handler is to detect that audio is playing + // this lets us then render an "audio playing" icon on the tab + // for whatever reason, the event consistently precedes the "is audible" being set by at most 1s + // so, we delay for 1s, then emit a state update + setTimeout(() => this.emitUpdateState(), 1e3) + } + + onFoundInPage (e, res) { + this.currentInpageFindResults = { + activeMatchOrdinal: res.activeMatchOrdinal, + matches: res.matches + } + this.emitUpdateState() + } +} + +// exported api +// = + +export async function setup () { + defaultUrl = String(await settingsDb.get('new_tab')) + settingsDb.on('set:new_tab', newValue => { + defaultUrl = newValue + + // reset preloaded tabs since they are now on the wrong URL + for (let k in preloadedNewTabs) { + preloadedNewTabs[k].destroy() + } + preloadedNewTabs = {} + }) + + // listen for webContents messages + ipcMain.on('BEAKER_MARK_NEXT_TAB_SCRIPTCLOSEABLE', e => { + nextTabIsScriptCloseable = true + e.returnValue = true + }) + ipcMain.on('BEAKER_SCRIPTCLOSE_SELF', e => { + var browserView = BrowserView.fromWebContents(e.sender) + if (browserView) { + var tab = findTab(browserView) + if (tab && tab.isScriptClosable) { + remove(tab.browserWindow, tab) + e.returnValue = true + return + } + } + e.returnValue = false + }) + ipcMain.on('BEAKER_WC_FOCUSED', e => { + var browserView = BrowserView.fromWebContents(e.sender) + var tab = browserView ? findTab(browserView) : undefined + if (tab) tab.previouslyFocusedWebcontents = e.sender + }) + + // track daemon connectivity + hyper.daemon.on('daemon-restored', () => emitReplaceStateAllWindows()) + hyper.daemon.on('daemon-stopped', () => emitReplaceStateAllWindows()) + + // track peer-counts + function iterateTabs (cb) { + for (let winId in activeTabs) { + for (let tab of activeTabs[winId]) { + cb(tab) + } + } + } + hyper.drives.on('updated', ({details}) => { + iterateTabs(tab => { + if (tab.driveInfo && tab.driveInfo.url === details.url) { + tab.refreshState() + } + }) + }) +} + +export function getAll (win) { + win = getTopWindow(win) + return activeTabs[win.id] || [] +} + +export function getByIndex (win, index) { + win = getTopWindow(win) + if (index === 'active') return getActive(win) + return getAll(win)[index] +} + +export function getIndexOfTab (win, tab) { + win = getTopWindow(win) + return getAll(win).indexOf(tab) +} + +export function getAllPinned (win) { + win = getTopWindow(win) + return getAll(win).filter(p => p.isPinned) +} + +export function getActive (win) { + win = getTopWindow(win) + return getAll(win).find(tab => tab.isActive) +} + +export function findTab (browserView) { + for (let winId in activeTabs) { + for (let tab of activeTabs[winId]) { + if (tab.browserView === browserView) { + return tab + } + if (tab.isSidebarActive && sidebars.get(tab) === browserView) { + return tab + } + } + } +} + +export function findContainingWindow (browserView) { + for (let winId in activeTabs) { + for (let v of activeTabs[winId]) { + if (v.browserView === browserView) { + return v.browserWindow + } + } + } + for (let winId in preloadedNewTabs) { + if (preloadedNewTabs[winId].browserView === browserView) { + return preloadedNewTabs[winId].browserWindow + } + } +} + +export function create ( + win, + url, + opts = { + setActive: false, + isPinned: false, + focusLocationBar: false, + adjacentActive: false, + tabIndex: undefined, + sidebarPanels: undefined + } + ) { + url = url || defaultUrl + if (url.startsWith('devtools://')) { + return // dont create tabs for this + } + win = getTopWindow(win) + if (getAddedWindowSettings(win).isAppWindow) { + // app-windows cant have multiple tabs, so find another window + win = getOrCreateNonAppWindow() + win.focus() + win.moveTop() + } + var tabs = activeTabs[win.id] = activeTabs[win.id] || [] + + var tab + var preloadedNewTab = preloadedNewTabs[win.id] + var loadWhenReady = false + if (url === defaultUrl && !opts.isPinned && preloadedNewTab) { + // use the preloaded tab + tab = preloadedNewTab + tab.isHidden = false // no longer hidden + preloadedNewTab = preloadedNewTabs[win.id] = null + } else { + // create a new tab + tab = new Tab(win, {isPinned: opts.isPinned}) + // tab.loadURL(url) + loadWhenReady = true + } + + // add to active tabs + if (opts.isPinned) { + tabs.splice(indexOfLastPinnedTab(win), 0, tab) + } else { + let tabIndex = (typeof opts.tabIndex !== 'undefined' && opts.tabIndex !== -1) ? opts.tabIndex : undefined + if (opts.adjacentActive) { + let active = getActive(win) + let lastPinIndex = indexOfLastPinnedTab(win) + tabIndex = active ? tabs.indexOf(active) : undefined + if (tabIndex === -1) tabIndex = undefined + else if (tabIndex < lastPinIndex) tabIndex = lastPinIndex + else tabIndex++ + } + if (typeof tabIndex !== 'undefined') { + tabs.splice(tabIndex, 0, tab) + } else { + tabs.push(tab) + } + } + if (loadWhenReady) { + // NOTE + // `loadURL()` triggers some events (eg app.on('web-contents-created')) + // which need to be handled *after* the tab is added to the listing + // thus this `loadWhenReady` logic + // -prf + tab.loadURL(url) + } + + // make active if requested, or if none others are + if (opts.setActive || !getActive(win)) { + setActive(win, tab) + } + emitReplaceState(win) + + if (opts.focusLocationBar) { + win.webContents.send('command', 'focus-location') + } + if (opts.sidebarPanels) { + for (let p of opts.sidebarPanels) { + tab.executeSidebarCommand('show-panel', p) + } + } + + // create a new preloaded tab if needed + if (!preloadedNewTab) { + createPreloadedNewTab(win) + } + + return tab +} + +export async function remove (win, tab) { + win = getTopWindow(win) + // find + var tabs = getAll(win) + var i = tabs.indexOf(tab) + if (i == -1) { + return console.warn('tab-manager remove() called for missing tab', tab) + } + + // give the 'onbeforeunload' a chance to run + var onBeforeUnloadReturnValue = await fireBeforeUnloadEvent(tab.webContents) + if (onBeforeUnloadReturnValue) { + var choice = dialog.showMessageBoxSync({ + type: 'question', + buttons: ['Leave', 'Stay'], + title: 'Do you want to leave this site?', + message: 'Changes you made may not be saved.', + defaultId: 0, + cancelId: 1 + }) + var leave = (choice === 0) + if (!leave) return + } + + // save, in case the user wants to restore it + closedURLs[win.id] = closedURLs[win.id] || [] + closedURLs[win.id].push(tab.url) + + // set new active if that was + if (tab.isActive && tabs.length > 1) { + setActive(win, tabs[i + 1] || tabs[i - 1]) + } + + // remove + tab.stopLiveReloading() + tabs.splice(i, 1) + tab.destroy() + + // persist pins w/o this one, if that was + if (tab.isPinned) { + savePins(win) + } + + // close the window if that was the last tab + if (tabs.length === 0) { + return win.close() + } + + // emit + emitReplaceState(win) +} + +export async function removeAllExcept (win, tab) { + win = getTopWindow(win) + var tabs = getAll(win).slice() // .slice() to duplicate the list + for (let t of tabs) { + if (t !== tab) { + await remove(win, t) + } + } +} + +export async function removeAllToRightOf (win, tab) { + win = getTopWindow(win) + var toRemove = [] + var tabs = getAll(win) + let index = tabs.indexOf(tab) + for (let i = 0; i < tabs.length; i++) { + if (i > index) toRemove.push(tabs[i]) + } + for (let t of toRemove) { + await remove(win, t) + } +} + +export function setActive (win, tab) { + win = getTopWindow(win) + if (typeof tab === 'number') { + tab = getByIndex(win, tab) + } + if (!tab) return + + // deactivate the old tab + var active = getActive(win) + if (active) { + if (active === tab) { + return + } + active.deactivate() + lastSelectedTabIndex[win.id] = getAll(win).indexOf(active) + } + + // activate the new tab + tab.activate() + windowMenu.onSetCurrentLocation(win) // give the window-menu a chance to handle the change + emitReplaceState(win) +} + +export function resize (win) { + var active = getActive(win) + if (active) active.resize() +} + +export function initializeFromSnapshot (win, snapshot) { + win = getTopWindow(win) + for (let url of snapshot) { + create(win, url) + } +} + +export function takeSnapshot (win) { + win = getTopWindow(win) + return getAll(win) + .filter(v => !v.isPinned) + .map(v => v.url) + .filter(Boolean) +} + +export async function popOutTab (tab) { + var newWin = createShellWindow() + await new Promise(r => newWin.once('custom-pages-ready', r)) + transferTabToWindow(tab, newWin) + removeAllExcept(newWin, tab) +} + +export function transferTabToWindow (tab, targetWindow) { + var sourceWindow = tab.browserWindow + + // find + var sourceTabs = getAll(sourceWindow) + var i = sourceTabs.indexOf(tab) + if (i == -1) { + return console.warn('tab-manager transferTabToWindow() called for missing tab', tab) + } + + // remove + var shouldCloseSource = false + sourceTabs.splice(i, 1) + if (tab.isPinned) savePins(sourceWindow) + if (sourceTabs.length === 0) { + shouldCloseSource = true + } else { + if (tab.isActive) { + // console.log('changing active', (sourceTabs[i + 1] || sourceTabs[i - 1]).url) + // setActive(sourceWindow, sourceTabs[i + 1] || sourceTabs[i - 1]) + changeActiveToLast(sourceWindow) + } + emitReplaceState(sourceWindow) + } + + // transfer to the new window + tab.transferWindow(targetWindow) + var targetTabs = getAll(targetWindow) + if (tab.isPinned) { + targetTabs.splice(indexOfLastPinnedTab(targetWindow), 0, tab) + savePins(targetWindow) + } else { + targetTabs.push(tab) + } + emitReplaceState(targetWindow) + + if (shouldCloseSource) { + sourceWindow.close() + } +} + +export function togglePinned (win, tab) { + win = getTopWindow(win) + // move tab to the "end" of the pinned tabs + var tabs = getAll(win) + var oldIndex = tabs.indexOf(tab) + var newIndex = indexOfLastPinnedTab(win) + if (oldIndex < newIndex) newIndex-- + tabs.splice(oldIndex, 1) + tabs.splice(newIndex, 0, tab) + + // update tab state + tab.isPinned = !tab.isPinned + emitReplaceState(win) + + // persist + savePins(win) +} + +export function savePins (win) { + win = getTopWindow(win) + return settingsDb.set('pinned_tabs', JSON.stringify(getAllPinned(win).map(p => p.url))) +} + +export async function loadPins (win) { + win = getTopWindow(win) + var json = await settingsDb.get('pinned_tabs') + try { JSON.parse(json).forEach(url => create(win, url, {isPinned: true})) } + catch (e) {} +} + +export function reopenLastRemoved (win) { + win = getTopWindow(win) + var url = (closedURLs[win.id] || []).pop() + if (url) { + var tab = create(win, url) + setActive(win, tab) + return tab + } +} + +export function reorder (win, oldIndex, newIndex) { + win = getTopWindow(win) + if (oldIndex === newIndex) { + return + } + var tabs = getAll(win) + var tab = getByIndex(win, oldIndex) + tabs.splice(oldIndex, 1) + tabs.splice(newIndex, 0, tab) + emitReplaceState(win) +} + +export function changeActiveBy (win, offset) { + win = getTopWindow(win) + var tabs = getAll(win) + var active = getActive(win) + if (tabs.length > 1) { + var i = tabs.indexOf(active) + if (i === -1) { return console.warn('Active page is not in the pages list! THIS SHOULD NOT HAPPEN!') } + + i += offset + if (i < 0) i = tabs.length - 1 + if (i >= tabs.length) i = 0 + + setActive(win, tabs[i]) + } +} + +export function changeActiveTo (win, index) { + win = getTopWindow(win) + var tabs = getAll(win) + if (index >= 0 && index < tabs.length) { + setActive(win, tabs[index]) + } +} + +export function changeActiveToLast (win) { + win = getTopWindow(win) + var tabs = getAll(win) + setActive(win, tabs[tabs.length - 1]) +} + +export function getPreviousTabIndex (win) { + var index = lastSelectedTabIndex[win.id] + if (typeof index !== 'number') return 0 + if (index < 0 || index >= getAll(win).length) return 0 + return index +} + +export function openOrFocusDownloadsPage (win) { + win = getTopWindow(win) + var tabs = getAll(win) + var downloadsTab = tabs.find(v => v.url.startsWith('beaker://library/downloads')) + if (!downloadsTab) { + downloadsTab = create(win, 'beaker://library/downloads') + } + setActive(win, downloadsTab) +} + +export function emitReplaceStateAllWindows () { + for (let win of BrowserWindow.getAllWindows()) { + emitReplaceState(win) + } +} + +export function emitReplaceState (win) { + win = getTopWindow(win) + var state = { + tabs: getWindowTabState(win), + isFullscreen: win.isFullScreen(), + isShellInterfaceHidden: getAddedWindowSettings(win).isShellInterfaceHidden, + isDaemonActive: hyper.daemon.isActive() + } + emit(win, 'replace-state', state) + win.emit('custom-pages-updated', takeSnapshot(win)) +} + +export function emitUpdateState (win, tab, state = undefined) { + win = getTopWindow(win) + var index = typeof tab === 'number' ? tab : getAll(win).indexOf(tab) + if (index === -1) { + console.warn('WARNING: attempted to update state of a tab not on the window') + return + } + state = state || getByIndex(win, index).state + emit(win, 'update-state', {index, state}) + win.emit('custom-pages-updated', takeSnapshot(win)) +} + +// rpc api +// = + +rpc.exportAPI('background-process-views', viewsRPCManifest, { + createEventStream () { + return emitStream(getEvents(getWindow(this.sender))) + }, + + async refreshState (tab) { + var win = getWindow(this.sender) + tab = getByIndex(win, tab) + if (tab) { + tab.refreshState() + } + }, + + async getState () { + var win = getWindow(this.sender) + return getWindowTabState(win) + }, + + async getTabState (tab, opts) { + var win = getWindow(this.sender) + tab = getByIndex(win, tab) + if (tab) { + var state = Object.assign({}, tab.state) + if (opts) { + if (opts.driveInfo) state.driveInfo = tab.driveInfo + if (opts.sitePerms) state.sitePerms = await sitedataDb.getPermissions(tab.url) + } + return state + } + }, + + async getNetworkState (tab, opts) { + var win = getWindow(this.sender) + tab = getByIndex(win, tab) + if (tab && tab.driveInfo) { + let peers = await hyper.daemon.getPeerCount(Buffer.from(tab.driveInfo.key, 'hex')) + let peerAddresses = undefined + if (opts && opts.includeAddresses) { + peerAddresses = await hyper.daemon.listPeerAddresses(tab.driveInfo.discoveryKey) + } + return {peers, peerAddresses} + } + }, + + async getPageMetadata (tab) { + var win = getWindow(this.sender) + tab = getByIndex(win, tab) + if (tab) return tab.getPageMetadata() + return {} + }, + + async createTab (url, opts = {focusLocationBar: false, setActive: false, addToNoRedirects: false}) { + if (opts.addToNoRedirects) { + addToNoRedirects(url) + } + + var win = getWindow(this.sender) + var tab = create(win, url, opts) + return getAll(win).indexOf(tab) + }, + + async loadURL (index, url, opts = {addToNoRedirects: false}) { + if (opts.addToNoRedirects) { + addToNoRedirects(url) + } + + getByIndex(getWindow(this.sender), index).loadURL(url) + }, + + async closeTab (index) { + var win = getWindow(this.sender) + remove(win, getByIndex(win, index)) + }, + + async setActiveTab (index) { + var win = getWindow(this.sender) + setActive(win, getByIndex(win, index)) + }, + + async reorderTab (oldIndex, newIndex) { + var win = getWindow(this.sender) + reorder(win, oldIndex, newIndex) + }, + + async showTabContextMenu (index) { + var win = getWindow(this.sender) + var tab = getByIndex(win, index) + var menu = Menu.buildFromTemplate([ + { label: (tab.isPinned) ? 'Unpin Tab' : 'Pin Tab', click: () => togglePinned(win, tab) }, + { label: 'Pop Out Tab', click: () => popOutTab(tab) }, + { label: 'Duplicate Tab', click: () => create(win, tab.url, {adjacentActive: true}) }, + { label: (tab.isAudioMuted) ? 'Unmute Tab' : 'Mute Tab', click: () => tab.toggleMuted() }, + { type: 'separator' }, + { label: 'Close Tab', click: () => remove(win, tab) }, + { label: 'Close Other Tabs', click: () => removeAllExcept(win, tab) }, + { label: 'Close Tabs to the Right', click: () => removeAllToRightOf(win, tab) }, + { type: 'separator' }, + { label: 'New Tab', click: () => create(win, null, {setActive: true}) }, + { label: 'Reopen Closed Tab', click: () => reopenLastRemoved(win) } + ]) + menu.popup() + }, + + async showLocationBarContextMenu (index) { + var win = getWindow(this.sender) + var tab = getByIndex(win, index) + var clipboardContent = clipboard.readText() + var clipInfo = examineLocationInput(clipboardContent) + var menu = Menu.buildFromTemplate([ + { label: 'Cut', role: 'cut' }, + { label: 'Copy', role: 'copy' }, + { label: 'Paste', role: 'paste' }, + { label: `Paste and ${clipInfo.isProbablyUrl ? 'Go' : 'Search'}`, click: onPasteAndGo } + ]) + menu.popup({window: win}) + + function onPasteAndGo () { + // close the menu + shellMenus.hide(win) + win.webContents.send('command', 'unfocus-location') + + // load the URL + var url = clipInfo.isProbablyUrl ? clipInfo.vWithProtocol : clipInfo.vSearch + tab.loadURL(url) + } + }, + + async goBack (index) { + getByIndex(getWindow(this.sender), index).webContents.goBack() + }, + + async goForward (index) { + getByIndex(getWindow(this.sender), index).webContents.goForward() + }, + + async stop (index) { + getByIndex(getWindow(this.sender), index).webContents.stop() + }, + + async reload (index) { + getByIndex(getWindow(this.sender), index).webContents.reload() + }, + + async resetZoom (index) { + zoom.zoomReset(getByIndex(getWindow(this.sender), index)) + }, + + async toggleLiveReloading (index, enabled) { + getByIndex(getWindow(this.sender), index).toggleLiveReloading(enabled) + }, + + async executeSidebarCommand (index, ...args) { + getByIndex(getWindow(this.sender), index).executeSidebarCommand(...args) + }, + + async toggleDevTools (index) { + getByIndex(getWindow(this.sender), index).webContents.toggleDevTools() + }, + + async print (index) { + getByIndex(getWindow(this.sender), index).webContents.print() + }, + + async showInpageFind (index) { + getByIndex(getWindow(this.sender), index).showInpageFind() + }, + + async hideInpageFind (index) { + getByIndex(getWindow(this.sender), index).hideInpageFind() + }, + + async setInpageFindString (index, str, dir) { + getByIndex(getWindow(this.sender), index).setInpageFindString(str, dir) + }, + + async moveInpageFind (index, dir) { + getByIndex(getWindow(this.sender), index).moveInpageFind(dir) + }, + + async showLocationBar (opts) { + await locationBar.show(getWindow(this.sender), opts) + }, + + async hideLocationBar () { + await locationBar.hide(getWindow(this.sender)) + }, + + async runLocationBarCmd (cmd, opts) { + return locationBar.runCmd(getWindow(this.sender), cmd, opts) + }, + + async showMenu (id, opts) { + await shellMenus.show(getWindow(this.sender), id, opts) + }, + + async toggleMenu (id, opts) { + await shellMenus.toggle(getWindow(this.sender), id, opts) + }, + + async updateMenu (opts) { + await shellMenus.update(getWindow(this.sender), opts) + }, + + async toggleSiteInfo (opts) { + await siteInfo.toggle(getWindow(this.sender), opts) + }, + + async focusShellWindow () { + getWindow(this.sender).webContents.focus() + }, + + async onFaviconLoadSuccess (index, dataUrl) { + var tab = getByIndex(getWindow(this.sender), index) + if (tab) { + // if not a hyperdrive site, store the favicon + // (hyperdrive caches favicons through the hyperdrive/assets.js process) + if (!tab.url.startsWith('hyper:')) { + sitedataDb.set(tab.url, 'favicon', dataUrl) + } + } + }, + + async onFaviconLoadError (index) { + var tab = getByIndex(getWindow(this.sender), index) + if (tab) { + tab.favicons = null + tab.emitUpdateState() + } + } +}) + +// internal methods +// = + +function getWindow (sender) { + return findWebContentsParentWindow(sender) +} + +// helper ensures that if a subwindow is called, we use the parent +function getTopWindow (win) { + if (!(win instanceof BrowserWindow)) { + return findWebContentsParentWindow(win) + } + while (win.getParentWindow()) { + win = win.getParentWindow() + } + return win +} + +function getEvents (win) { + if (!(win.id in windowEvents)) { + windowEvents[win.id] = new EventEmitter() + } + return windowEvents[win.id] +} + +function emit (win, ...args) { + getEvents(win).emit(...args) +} + +function getWindowTabState (win) { + return getAll(win).map(tab => tab.state) +} + +function indexOfLastPinnedTab (win) { + var tabs = getAll(win) + var index = 0 + for (index; index < tabs.length; index++) { + if (!tabs[index].isPinned) break + } + return index +} + +function toOrigin (str) { + try { + var u = new URL(str) + return u.protocol + '//' + u.hostname + (u.port ? `:${u.port}` : '') + '/' + } catch (e) { return '' } +} + +function addToNoRedirects (url) { + try { + var u = new URL(url) + noRedirectHostnames.add(u.hostname) + } catch (e) { + console.log('Failed to add URL to noRedirectHostnames', url, e) + } +} + +async function fireBeforeUnloadEvent (wc) { + try { + if (wc.isLoading() || wc.isWaitingForResponse()) { + return // dont bother + } + return await Promise.race([ + wc.executeJavaScript(` + (function () { + let unloadEvent = new Event('beforeunload', {bubbles: false, cancelable: true}) + unloadEvent.returnValue = false + return window.dispatchEvent(unloadEvent) + })() + `), + new Promise(r => { + setTimeout(r, 500) // thread may be locked, so abort after 500ms + }) + ]) + } catch (e) { + // ignore + } +} + +// `nextTabIsScriptCloseable` is set by a message received prior to window.open() being called +// we capture the state of the flag on the next created tab, then reset it +function takeIsScriptClosable () { + var b = nextTabIsScriptCloseable + nextTabIsScriptCloseable = false + return b +} + +/** + * NOTE + * preloading a tab generates a slight performance cost which was interrupting the UI + * (it manifested as a noticeable delay in the location bar) + * by delaying before creating the preloaded tab, we avoid overloading any threads + * and disguise the performance overhead + * -prf + */ +var _createPreloadedNewTabTOs = {} // map of {[win.id] => timeout} +function createPreloadedNewTab (win) { + var id = win.id + if (_createPreloadedNewTabTOs[id]) { + clearTimeout(_createPreloadedNewTabTOs[id]) + } + _createPreloadedNewTabTOs[id] = setTimeout(() => { + _createPreloadedNewTabTOs[id] = null + preloadedNewTabs[id] = new Tab(win, {isHidden: true}) + preloadedNewTabs[id].loadURL(defaultUrl) + }, 1e3) +} \ No newline at end of file diff --git a/app/background-process/ui/views/zoom.js b/app/bg/ui/tabs/zoom.js similarity index 83% rename from app/background-process/ui/views/zoom.js rename to app/bg/ui/tabs/zoom.js index cb2f7bbe9d..1b295e662c 100644 --- a/app/background-process/ui/views/zoom.js +++ b/app/bg/ui/tabs/zoom.js @@ -1,7 +1,6 @@ import {URL} from 'url' -import * as beakerCore from '@beaker/core' -import * as viewManager from '../view-manager' -const sitedataDb = beakerCore.dbs.sitedata +import * as tabManager from '../tab-manager' +import * as sitedataDb from '../../dbs/sitedata' const ZOOM_STEP = 0.5 @@ -35,7 +34,7 @@ export function setZoom (view, z) { sitedataDb.set(view.url, 'zoom', view.zoom) // update all pages at the origin - viewManager.getAll(view.browserWindow).forEach(v => { + tabManager.getAll(view.browserWindow).forEach(v => { if (v !== view && v.origin === origin) { v.zoom = z } diff --git a/app/bg/ui/tray-icon.js b/app/bg/ui/tray-icon.js new file mode 100644 index 0000000000..fb83ede7e9 --- /dev/null +++ b/app/bg/ui/tray-icon.js @@ -0,0 +1,53 @@ +import { app, Tray, Menu, BrowserWindow } from 'electron' +import path from 'path' +import { createShellWindow } from './windows' +import * as tabManager from './tab-manager' +import * as settingsDb from '../dbs/settings' + +const IS_WIN = process.platform === 'win32' +const IS_LINUX = process.platform === 'linux' +const ICON = IS_LINUX || IS_WIN ? 'assets/img/tray-icon-white.png' : 'assets/img/tray-icon.png' + +// globals +// = + +var tray + +// exported api +// = + +export function setup () { + tray = new Tray(path.join(__dirname, ICON)) + tray.setToolTip('Beaker Browser') + tray.on('click', e => tray.popupContextMenu()) + settingsDb.on('set:run_background', buildMenu) + buildMenu() +} + +// internal +// = + +async function buildMenu () { + var runBackground = !!(await settingsDb.get('run_background')) + const contextMenu = Menu.buildFromTemplate([ + {label: 'Open new tab', click: onClickOpen}, + {type: 'separator'}, + {type: 'checkbox', label: 'Let Beaker run in the background', checked: runBackground, click: () => onTogglePersist(!runBackground)}, + {label: 'Quit Beaker', click: () => app.quit()} + ]) + tray.setContextMenu(contextMenu) +} + +function onClickOpen () { + var win = BrowserWindow.getAllWindows()[0] + if (win) { + win.show() + tabManager.create(win, undefined, {setActive: true}) + } else { + createShellWindow() + } +} + +function onTogglePersist (v) { + settingsDb.set('run_background', v ? 1 : 0) +} \ No newline at end of file diff --git a/app/bg/ui/util.js b/app/bg/ui/util.js new file mode 100644 index 0000000000..6e4da48e95 --- /dev/null +++ b/app/bg/ui/util.js @@ -0,0 +1,92 @@ +import * as modals from './subwindows/modals' +import shellWebAPI from '../web-apis/bg/shell' +import drivesWebAPI from '../web-apis/bg/drives' +import hyper from '../hyper/index' +import * as filesystem from '../filesystem/index' +import pda from 'pauls-dat-api2' +import { UserDeniedError } from 'beaker-error-constants' + +export async function runSelectFileDialog (win, opts = {}) { + var res + try { + res = await modals.create(win.webContents, 'select-file', opts) + } catch (e) { + if (e.name !== 'Error') throw e // only rethrow if a specific error + } + if (!res) throw new UserDeniedError() + return res +} + +export async function runNewDriveFlow (win) { + let res + try { + res = await modals.create(win.webContents, 'create-drive', {}) + } catch (e) { + if (e.name !== 'Error') { + throw e // only rethrow if a specific error + } + } + if (!res || !res.url) throw new UserDeniedError() + return res.url +} + +export async function runNewDriveFromFolderFlow (folderPath) { + let newDrive + try { + let manifest = {title: folderPath.split('/').pop()} + newDrive = await hyper.drives.createNewDrive(manifest) + await filesystem.configDrive(newDrive.url) + } catch (e) { + console.log(e) + throw e + } + + await pda.exportFilesystemToArchive({ + srcPath: folderPath, + dstArchive: newDrive.session.drive, + dstPath: '/', + inplaceImport: true + }) + + return newDrive.url +} + +export async function runForkFlow (win, url, {detached} = {detached: false}) { + var res + try { + let forks = await drivesWebAPI.getForks(url) + res = await modals.create(win.webContents, 'fork-drive', {url, forks, detached}) + } catch (e) { + if (e.name !== 'Error') { + throw e // only rethrow if a specific error + } + } + if (!res || !res.url) throw new UserDeniedError() + return res.url +} + +export async function runDrivePropertiesFlow (win, key) { + await shellWebAPI.drivePropertiesDialog.call({sender: win}, key) +} + +export async function exportDriveToFilesystem (sourceUrl, targetPath) { + var drive = await hyper.drives.getOrLoadDrive(sourceUrl) + return pda.exportArchiveToFilesystem({ + srcArchive: drive.session.drive, + srcPath: '/', + dstPath: targetPath, + overwriteExisting: true, + skipUndownloadedFiles: false + }) +} + +export async function importFilesystemToDrive (srcPath, targetUrl, {preserveFolder} = {preserveFolder: false}) { + var targetUrlp = new URL(targetUrl) + var drive = await hyper.drives.getOrLoadDrive(targetUrlp.hostname) + return pda.exportFilesystemToArchive({ + srcPath, + dstArchive: drive.session.drive, + dstPath: targetUrlp.pathname, + inplaceImport: !preserveFolder + }) +} \ No newline at end of file diff --git a/app/bg/ui/window-menu.js b/app/bg/ui/window-menu.js new file mode 100644 index 0000000000..86537c39d2 --- /dev/null +++ b/app/bg/ui/window-menu.js @@ -0,0 +1,838 @@ +import { app, BrowserWindow, dialog, Menu } from 'electron' +import { createShellWindow, toggleShellInterface, getActiveWindow, getFocusedDevToolsHost, getAddedWindowSettings } from './windows' +import { runSelectFileDialog, runForkFlow, runDrivePropertiesFlow, exportDriveToFilesystem, importFilesystemToDrive } from './util' +import * as tabManager from './tab-manager' +import * as viewZoom from './tabs/zoom' +import { download } from './downloads' +import hyper from '../hyper/index' +import * as settingsDb from '../dbs/settings' + +// globals +// = + +var currentMenuTemplate + +// exported APIs +// = + +export function setup () { + setApplicationMenu({noWindows: true}) + + // watch for changes to the currently active window + app.on('browser-window-focus', async (e, win) => { + try { + setApplicationMenu() + } catch (e) { + // `pages` not set yet + } + }) + + // watch for all windows to be closed + app.on('custom-window-all-closed', () => { + setApplicationMenu({noWindows: true}) + }) + + // watch for any window to be opened + app.on('browser-window-created', () => { + setApplicationMenu() + }) +} + +export function onSetCurrentLocation (win) { + // check if this is the currently focused window + if (win !== BrowserWindow.getFocusedWindow()) { + return + } + setApplicationMenu() +} + +export function setApplicationMenu (opts = {}) { + currentMenuTemplate = buildWindowMenu(opts) + Menu.setApplicationMenu(Menu.buildFromTemplate(currentMenuTemplate)) +} + +export function buildWindowMenu (opts = {}) { + var win = opts.noWindows ? undefined : opts.win ? opts.win : getActiveWindow() + if (win && win.isDestroyed()) win = undefined + const noWindows = !win + const addedWindowSettings = getAddedWindowSettings(win) + const isAppWindow = addedWindowSettings.isAppWindow + const tab = !noWindows && win ? tabManager.getActive(win) : undefined + const url = tab ? (tab.url || tab.loadingURL) : '' + const isDriveSite = url.startsWith('hyper://') + const driveInfo = isDriveSite ? tab.driveInfo : undefined + const isWritable = driveInfo && driveInfo.writable + + var darwinMenu = { + label: 'Beaker', + submenu: [ + { + label: 'Preferences', + accelerator: 'Cmd+,', + click (item) { + if (win) tabManager.create(win, 'beaker://settings', {setActive: true}) + else createShellWindow({ pages: ['beaker://settings'] }) + } + }, + { type: 'separator' }, + { label: 'Services', role: 'services', submenu: [] }, + { type: 'separator' }, + { label: 'Hide Beaker', accelerator: 'Cmd+H', role: 'hide' }, + { label: 'Hide Others', accelerator: 'Cmd+Alt+H', role: 'hideothers' }, + { label: 'Show All', role: 'unhide' }, + { type: 'separator' }, + { + id: 'quit', + label: 'Quit', + accelerator: 'Cmd+Q', + async click () { + var runBackground = await settingsDb.get('run_background') + if (runBackground == 1) { + for (let win of BrowserWindow.getAllWindows()) { + win.close() + } + } else { + app.quit() + } + }, + reserved: true + } + ] + } + + var fileMenu = { + label: 'File', + submenu: [ + { + id: 'newTab', + label: 'New Tab', + accelerator: 'CmdOrCtrl+T', + click: function (item) { + if (win) { + tabManager.create(win, undefined, {setActive: true, focusLocationBar: true}) + } else { + createShellWindow() + } + }, + reserved: true + }, + { + id: 'newWindow', + label: 'New Window', + accelerator: 'CmdOrCtrl+N', + click: function () { createShellWindow() }, + reserved: true + }, + // { + // id: 'newFile', + // label: 'New File', + // enabled: !noWindows && !isAppWindow, + // click: function (item) { + // createWindowIfNone(win, async (win) => { + // var res = await runSelectFileDialog(win, { + // saveMode: true, + // title: 'New File', + // buttonLabel: 'Create File', + // drive: opts && url && url.startsWith('hyper:') ? url : undefined + // }) + // let drive = await hyper.drives.getOrLoadDrive(res.origin) + // await drive.pda.writeFile(res.path, '') + // tabManager.create(win, res.url, {setActive: true, adjacentActive: true, sidebarPanels: ['editor-app']}) + // }) + // } + // }, + // { + // id: 'newFolder', + // label: 'New Folder', + // enabled: !noWindows && !isAppWindow, + // click: function (item) { + // createWindowIfNone(win, async (win) => { + // var res = await runSelectFileDialog(win, { + // saveMode: true, + // title: 'New Folder', + // buttonLabel: 'Create Folder', + // drive: opts && url && url.startsWith('hyper:') ? url : undefined + // }) + // let drive = await hyper.drives.getOrLoadDrive(res.origin) + // await drive.pda.mkdir(res.path) + // tabManager.create(win, res.url, {setActive: true, adjacentActive: true}) + // }) + // } + // }, + { type: 'separator' }, + { + id: 'openFile', + label: 'Open File', + accelerator: 'CmdOrCtrl+O', + click: item => { + createWindowIfNone(win, async (win) => { + var res = await runSelectFileDialog(win, { + buttonLabel: 'Open File' + }) + tabManager.create(win, res[0].url, {setActive: true, adjacentActive: true}) + }) + } + }, + { type: 'separator' }, + // TODO + // { + // id: 'savePageAs', + // label: 'Save Page As...', + // enabled: !noWindows && !isAppWindow, + // accelerator: 'CmdOrCtrl+Shift+S', + // click: async (item) => { + // createWindowIfNone(getWin(), async (win) => { + // if (!tab) return + // const {url, title} = tab + // var res = await runSelectFileDialog(win, { + // saveMode: true, + // title: `Save ${title} as...`, + // buttonLabel: 'Save Page', + // defaultFilename: url.split('/').filter(Boolean).pop() || 'index.html', + // drive: url.startsWith('hyper:') ? url : undefined + // }) + // let drive = await hyper.drives.getOrLoadDrive(res.origin) + // await drive.pda.writeFile(res.path, await fetch(url)) + // tabManager.create(win, res.url, {setActive: true, adjacentActive: true}) + // }) + // } + // }, + { + id: 'exportPageAs', + label: 'Export Page As...', + enabled: !noWindows && !isAppWindow, + click: async (item) => { + if (!tab) return + const {url, title} = tab + var {filePath} = await dialog.showSaveDialog({ title: `Save ${title} as...`, defaultPath: app.getPath('downloads') }) + if (filePath) download(win, win.webContents, url, { saveAs: filePath, suppressNewDownloadEvent: true }) + } + }, + { + id: 'print', + label: 'Print', + enabled: !noWindows, + accelerator: 'CmdOrCtrl+P', + click: (item) => { + if (!tab) return + tab.webContents.print() + } + }, + { type: 'separator' }, + { + id: 'reopenClosedTab', + label: 'Reopen Closed Tab', + accelerator: 'CmdOrCtrl+Shift+T', + click: function (item) { + createWindowIfNone(win, (win) => { + tabManager.reopenLastRemoved(win) + }) + }, + reserved: true + }, + { + id: 'closeTab', + label: 'Close Tab', + enabled: !noWindows, + accelerator: 'CmdOrCtrl+W', + click: function (item) { + if (win) { + // a regular browser window + let active = tabManager.getActive(win) + if (active) { + if (active.isSidebarActive) { + active.closeSidebar() + } else { + tabManager.remove(win, active) + } + } + } else { + // devtools + let wc = getFocusedDevToolsHost() + if (wc) { + wc.closeDevTools() + } + } + }, + reserved: true + }, + { + id: 'closeWindow', + label: 'Close Window', + enabled: !noWindows, + accelerator: 'CmdOrCtrl+Shift+W', + click: function (item) { + if (win) win.close() + }, + reserved: true + } + ] + } + + var editMenu = { + label: 'Edit', + submenu: [ + { id: 'undo', label: 'Undo', enabled: !noWindows, accelerator: 'CmdOrCtrl+Z', selector: 'undo:', reserved: true }, + { id: 'redo', label: 'Redo', enabled: !noWindows, accelerator: 'Shift+CmdOrCtrl+Z', selector: 'redo:', reserved: true }, + { type: 'separator' }, + { id: 'cut', label: 'Cut', enabled: !noWindows, accelerator: 'CmdOrCtrl+X', selector: 'cut:', reserved: true }, + { id: 'copy', label: 'Copy', enabled: !noWindows, accelerator: 'CmdOrCtrl+C', selector: 'copy:', reserved: true }, + { id: 'paste', label: 'Paste', enabled: !noWindows, accelerator: 'CmdOrCtrl+V', selector: 'paste:', reserved: true }, + { id: 'selectAll', label: 'Select All', enabled: !noWindows, accelerator: 'CmdOrCtrl+A', selector: 'selectAll:' }, + { type: 'separator' }, + { + id: 'findInPage', + label: 'Find in Page', + enabled: !noWindows && !isAppWindow, + accelerator: 'CmdOrCtrl+F', + click: function (item) { + if (tab) tab.showInpageFind() + } + }, + { + id: 'findNext', + label: 'Find Next', + enabled: !noWindows && !isAppWindow, + accelerator: 'CmdOrCtrl+G', + click: function (item) { + if (tab) tab.moveInpageFind(1) + } + }, + { + id: 'findPrevious', + label: 'Find Previous', + enabled: !noWindows && !isAppWindow, + accelerator: 'Shift+CmdOrCtrl+G', + click: function (item) { + if (tab) tab.moveInpageFind(-1) + } + } + ] + } + + var viewMenu = { + label: 'View', + submenu: [ + { + id: 'reload', + label: 'Reload', + enabled: !noWindows, + accelerator: 'CmdOrCtrl+R', + click: function (item) { + if (tab) tab.webContents.reload() + }, + reserved: true + }, + { + id: 'hardReload', + label: 'Hard Reload (Clear Cache)', + accelerator: 'CmdOrCtrl+Shift+R', + enabled: !noWindows, + click: function (item) { + // HACK + // this is *super* lazy but it works + // clear all hyper-dns cache on hard reload, to make sure the next + // load is fresh + // -prf + hyper.dns.flushCache() + if (tab) tab.webContents.reloadIgnoringCache() + }, + reserved: true + }, + {type: 'separator'}, + { + id: 'zoomIn', + label: 'Zoom In', + enabled: !noWindows, + accelerator: 'CmdOrCtrl+Plus', + reserved: true, + click: function (item) { + if (tab) viewZoom.zoomIn(tab) + } + }, + { + id: 'zoomOut', + label: 'Zoom Out', + enabled: !noWindows, + accelerator: 'CmdOrCtrl+-', + reserved: true, + click: function (item) { + if (tab) viewZoom.zoomOut(tab) + } + }, + { + id: 'actualSize', + label: 'Actual Size', + enabled: !noWindows, + accelerator: 'CmdOrCtrl+0', + click: function (item) { + if (tab) viewZoom.zoomReset(tab) + } + } + ] + } + + var driveMenu = { + label: 'Drive', + submenu: [ + { + id: 'toggleFilesExplorer', + label: 'Explore Files', + enabled: !noWindows && !isAppWindow && !!isDriveSite, + accelerator: 'CmdOrCtrl+E', + click: async function (item) { + if (tab) tab.executeSidebarCommand('toggle-panel', 'files-explorer-app') + } + }, + {type: 'separator'}, + { + id: 'forkDrive', + label: 'Fork Drive', + enabled: !!isDriveSite, + async click (item) { + if (win) { + let newUrl = await runForkFlow(win, url) + tabManager.create(win, newUrl, {setActive: true}) + } + } + }, + { + id: 'diffMerge', + label: 'Diff / Merge', + enabled: !!isDriveSite, + async click (item) { + if (win) tabManager.create(win, `beaker://diff/?base=${url}`, {setActive: true}) + } + }, + { type: 'separator' }, + { + id: 'importFiles', + label: 'Import Files', + enabled: !noWindows && !isAppWindow && isDriveSite && isWritable, + click: async (item) => { + if (!driveInfo || !driveInfo.writable) return + var {filePaths} = await dialog.showOpenDialog({ + title: `Import Files`, + buttonLabel: 'Select File(s)', + properties: ['openFile', 'multiSelections'] + }) + if (!filePaths[0]) return + var res = await runSelectFileDialog(win, { + title: 'Choose where to import to', + buttonLabel: 'Import File(s)', + drive: driveInfo.url, + select: 'folder' + }) + var targetUrl = res[0].url + let confirmation = await dialog.showMessageBox({ + type: 'question', + message: `Import ${filePaths.length > 1 ? `${filePaths.length} files` : filePaths[0]} to ${targetUrl}? Any conflicting files will be overwritten.`, + buttons: ['OK', 'Cancel'] + }) + if (confirmation.response !== 0) return + for (let filePath of filePaths) { + await importFilesystemToDrive(filePath, targetUrl) + } + dialog.showMessageBox({message: 'Import complete'}) + } + }, + { + id: 'importFolder', + label: 'Import Folder', + enabled: !noWindows && !isAppWindow && isDriveSite && isWritable, + click: async (item) => { + if (!driveInfo || !driveInfo.writable) return + var {filePaths} = await dialog.showOpenDialog({ + title: `Import Folder`, + buttonLabel: 'Select Folder(s)', + properties: ['openDirectory', 'multiSelections'] + }) + if (!filePaths[0]) return + var res = await runSelectFileDialog(win, { + title: 'Choose where to import to', + buttonLabel: 'Import Folder(s)', + drive: driveInfo.url, + select: 'folder' + }) + var targetUrl = res[0].url + let confirmation = await dialog.showMessageBox({ + type: 'question', + message: `Import ${filePaths.length > 1 ? `${filePaths.length} folders` : filePaths[0]} to ${targetUrl}? Any conflicting files will be overwritten.`, + buttons: ['OK', 'Cancel'] + }) + if (confirmation.response !== 0) return + for (let filePath of filePaths) { + await importFilesystemToDrive(filePath, targetUrl, {preserveFolder: true}) + } + dialog.showMessageBox({message: 'Import complete'}) + } + }, + { + id: 'exportFiles', + label: 'Export Files', + enabled: !noWindows && !isAppWindow && isDriveSite, + click: async (item) => { + if (!driveInfo) return + var {filePaths} = await dialog.showOpenDialog({ + title: `Export Drive Files`, + buttonLabel: 'Export', + properties: ['openDirectory', 'createDirectory'] + }) + if (!filePaths[0]) return + let confirmation = await dialog.showMessageBox({ + type: 'question', + message: `Export ${driveInfo.title || driveInfo.key} to ${filePaths[0]}? Any conflicting files will be overwritten.`, + buttons: ['OK', 'Cancel'] + }) + if (confirmation.response !== 0) return + await exportDriveToFilesystem(driveInfo.url, filePaths[0]) + dialog.showMessageBox({message: 'Export complete'}) + } + }, + {type: 'separator'}, + { + id: 'driveProperties', + label: 'Drive Properties', + enabled: !!isDriveSite, + async click (item) { + if (win) runDrivePropertiesFlow(win, hyper.drives.fromURLToKey(url)) + } + } + ] + } + + var showHistoryAccelerator = 'Ctrl+H' + + if (process.platform === 'darwin') { + showHistoryAccelerator = 'Cmd+Y' + } + + var historyMenu = { + label: 'History', + role: 'history', + submenu: [ + { + id: 'back', + label: 'Back', + enabled: !noWindows, + accelerator: 'CmdOrCtrl+Left', + click: function (item) { + if (tab) tab.webContents.goBack() + } + }, + { + id: 'forward', + label: 'Forward', + enabled: !noWindows, + accelerator: 'CmdOrCtrl+Right', + click: function (item) { + if (tab) tab.webContents.goForward() + } + }, + { + id: 'showFullHistory', + label: 'Show Full History', + accelerator: showHistoryAccelerator, + click: function (item) { + if (win) tabManager.create(win, 'beaker://history', {setActive: true}) + else createShellWindow({ pages: ['beaker://history'] }) + } + }, + { type: 'separator' }, + { + id: 'bookmarkThisPage', + label: 'Bookmark this Page', + enabled: !noWindows, + accelerator: 'CmdOrCtrl+D', + click: function (item) { + if (win) win.webContents.send('command', 'create-bookmark') + } + } + ] + } + + var developerMenu = { + label: 'Developer', + submenu: [ + { + type: 'submenu', + label: 'Advanced Tools', + submenu: [ + { + label: 'Reload Shell-Window', + enabled: !noWindows, + click: function () { + win.webContents.reloadIgnoringCache() + } + }, + { + label: 'Toggle Shell-Window DevTools', + enabled: !noWindows, + click: function () { + win.webContents.openDevTools({mode: 'detach'}) + } + }, + { type: 'separator' }, + { + label: 'Open Hyperdrives Debug Page', + enabled: !noWindows, + click: function (item) { + if (win) tabManager.create(win, 'beaker://active-drives/', {setActive: true}) + } + }, { + label: 'Open Dat-DNS Cache Page', + enabled: !noWindows, + click: function (item) { + if (win) tabManager.create(win, 'beaker://hyper-dns-cache/', {setActive: true}) + } + }, { + label: 'Open Debug Log Page', + enabled: !noWindows, + click: function (item) { + if (win) tabManager.create(win, 'beaker://debug-log/', {setActive: true}) + } + } + ] + }, + { + id: 'toggleDevTools', + label: 'Toggle DevTools', + enabled: !noWindows, + accelerator: (process.platform === 'darwin') ? 'Alt+CmdOrCtrl+I' : 'Shift+CmdOrCtrl+I', + click: function (item) { + if (tab) tab.webContents.toggleDevTools() + }, + reserved: true + }, + { + id: 'toggleEditor', + label: 'Toggle Editor', + enabled: !noWindows && !isAppWindow, + accelerator: 'CmdOrCtrl+B', + click: async function (item) { + if (tab) tab.executeSidebarCommand('toggle-panel', 'editor-app') + } + }, + { + id: 'toggleTerminal', + label: 'Toggle Terminal', + enabled: !noWindows && !isAppWindow, + accelerator: 'Ctrl+`', + click: function (item) { + if (tab) tab.executeSidebarCommand('toggle-panel', 'web-term') + } + }, + { + id: 'toggleLiveReloading', + label: 'Toggle Live Reloading', + enabled: !!isDriveSite, + click: function (item) { + if (tab) tab.toggleLiveReloading() + } + } + ] + } + + const gotoTabShortcut = index => ({ + label: `Tab ${index}`, + enabled: !noWindows, + accelerator: `CmdOrCtrl+${index}`, + click: function (item) { + if (win) tabManager.setActive(win, index - 1) + } + }) + var windowMenu = { + label: 'Window', + role: 'window', + submenu: [ + { + type: 'checkbox', + label: 'Always on Top', + checked: (win ? win.isAlwaysOnTop() : false), + click: function () { + if (win) win.setAlwaysOnTop(!win.isAlwaysOnTop()) + } + }, + { + label: 'Minimize', + accelerator: 'CmdOrCtrl+M', + role: 'minimize' + }, + {type: 'separator'}, + { + label: 'Full Screen', + enabled: !noWindows, + accelerator: (process.platform === 'darwin') ? 'Ctrl+Cmd+F' : 'F11', + role: 'toggleFullScreen' + }, + { + label: 'Toggle Browser UI', + enabled: !noWindows && !isAppWindow, + accelerator: 'CmdOrCtrl+Shift+H', + click: function (item) { + if (win) toggleShellInterface(win) + } + }, + {type: 'separator'}, + { + label: 'Focus Location Bar', + accelerator: 'CmdOrCtrl+L', + click: function (item) { + createWindowIfNone(win, (win) => { + win.webContents.send('command', 'focus-location') + }) + } + }, + {type: 'separator'}, + { + label: 'Next Tab', + enabled: !noWindows, + accelerator: (process.platform === 'darwin') ? 'Alt+CmdOrCtrl+Right' : 'CmdOrCtrl+PageDown', + click: function (item) { + if (win) tabManager.changeActiveBy(win, 1) + } + }, + { + label: 'Previous Tab', + enabled: !noWindows, + accelerator: (process.platform === 'darwin') ? 'Alt+CmdOrCtrl+Left' : 'CmdOrCtrl+PageUp', + click: function (item) { + if (win) tabManager.changeActiveBy(win, -1) + } + }, + { + label: 'Tab Shortcuts', + type: 'submenu', + submenu: [ + gotoTabShortcut(1), + gotoTabShortcut(2), + gotoTabShortcut(3), + gotoTabShortcut(4), + gotoTabShortcut(5), + gotoTabShortcut(6), + gotoTabShortcut(7), + gotoTabShortcut(8), + { + label: `Last Tab`, + enabled: !noWindows, + accelerator: `CmdOrCtrl+9`, + click: function (item) { + if (win) tabManager.setActive(win, tabManager.getAll(win).slice(-1)[0]) + } + } + ] + }, + { + label: 'Pop Out Tab', + enabled: !noWindows && !isAppWindow, + accelerator: 'Shift+CmdOrCtrl+P', + click: function (item) { + if (tab) tabManager.popOutTab(tab) + } + } + ] + } + if (process.platform == 'darwin') { + windowMenu.submenu.push({ + type: 'separator' + }) + windowMenu.submenu.push({ + label: 'Bring All to Front', + role: 'front' + }) + } + + var helpMenu = { + label: 'Help', + role: 'help', + submenu: [ + { + id: 'beakerHelp', + label: 'Beaker Help', + accelerator: 'F1', + click: function (item) { + if (win) tabManager.create(win, 'https://docs.beakerbrowser.com/', {setActive: true}) + } + }, + { + id: 'developerPortal', + label: 'Developer Portal', + click: function (item) { + if (win) tabManager.create(win, 'https://beaker.dev/', {setActive: true}) + } + }, + {type: 'separator'}, + { + id: 'reportIssue', + label: 'Report Issue', + click: function (item) { + if (win) tabManager.create(win, 'https://github.com/beakerbrowser/beaker/issues', {setActive: true}) + } + }, + { + id: 'beakerDiscussions', + label: 'Discussion Forum', + click: function (item) { + if (win) tabManager.create(win, 'https://github.com/beakerbrowser/beaker/discussions', {setActive: true}) + } + } + ] + } + if (process.platform !== 'darwin') { + helpMenu.submenu.push({ type: 'separator' }) + helpMenu.submenu.push({ + label: 'About', + role: 'about', + click: function (item) { + if (win) tabManager.create(win, 'beaker://settings', {setActive: true}) + } + }) + } + + // assemble final menu + var menus = [fileMenu, editMenu, viewMenu, driveMenu, historyMenu, developerMenu, windowMenu, helpMenu] + if (process.platform === 'darwin') menus.unshift(darwinMenu) + return menus +} + +export function getToolbarMenu () { + if (!currentMenuTemplate) return {} + const get = label => toToolbarItems(currentMenuTemplate.find(menu => menu.label === label).submenu) + function toToolbarItems (items){ + return items.map(item => { + if (item.type === 'separator') { + return {separator: true} + } + if (!item.id) return false + return { + id: item.id, + label: item.label, + accelerator: item.accelerator, + enabled: typeof item.enabled === 'boolean' ? item.enabled : true + } + }).filter(Boolean) + } + return { + File: get('File'), + Drive: get('Drive'), + Developer: get('Developer'), + Help: get('Help') + } +} + +export function triggerMenuItemById (menuLabel, id) { + if (!currentMenuTemplate) return + var items = currentMenuTemplate.find(menu => menu.label === menuLabel).submenu + if (!items) return + var item = items.find(item => item.id === id) + return item.click() +} + +// internal helpers +// = + +function createWindowIfNone (win, onShow) { + if (win) return onShow(win) + win = createShellWindow() + win.once('show', onShow.bind(null, win)) +} diff --git a/app/background-process/ui/windows.js b/app/bg/ui/windows.js similarity index 56% rename from app/background-process/ui/windows.js rename to app/bg/ui/windows.js index b577848856..897d323a4f 100644 --- a/app/background-process/ui/windows.js +++ b/app/bg/ui/windows.js @@ -1,9 +1,8 @@ -import * as beakerCore from '@beaker/core' import {app, BrowserWindow, BrowserView, ipcMain, webContents, dialog} from 'electron' import {defaultBrowsingSessionState, defaultWindowState} from './default-state' -import SessionWatcher from './session-watcher' +import SessionWatcher, { getLastRecordedPositioning } from './session-watcher' import jetpack from 'fs-jetpack' -import * as viewManager from './view-manager' +import * as tabManager from './tab-manager' import { createGlobalKeybindingsHandler, createKeybindingProtectionsHandler, @@ -20,10 +19,17 @@ import * as promptsSubwindow from './subwindows/prompts' import * as permPromptSubwindow from './subwindows/perm-prompt' import * as modalsSubwindow from './subwindows/modals' import * as sidebarsSubwindow from './subwindows/sidebars' -import { findWebContentsParentWindow } from '../../lib/electron' -const settingsDb = beakerCore.dbs.settings +import * as siteInfoSubwindow from './subwindows/site-info' +import * as tabSwitcherSubwindow from './subwindows/tab-switcher' +import { findWebContentsParentWindow } from '../lib/electron' +import * as settingsDb from '../dbs/settings' +import { getEnvVar } from '../lib/env' +import _pick from 'lodash.pick' +import * as logLib from '../logger' +const logger = logLib.child({category: 'browser'}) const IS_WIN = process.platform === 'win32' +const IS_LINUX = process.platform === 'linux' const subwindows = { statusBar: statusBarSubwindow, locationBar: locationBarSubwindow, @@ -31,20 +37,25 @@ const subwindows = { prompts: promptsSubwindow, permPrompt: permPromptSubwindow, modals: modalsSubwindow, - sidebars: sidebarsSubwindow + sidebars: sidebarsSubwindow, + siteInfo: siteInfoSubwindow, + tabSwitcher: tabSwitcherSubwindow } // globals // = -let userDataDir -let numActiveWindows = 0 -let firstWindow = null -let sessionWatcher = null -let focusedDevtoolsHost -let hasFirstWindowLoaded = false + +var userDataDir +var numActiveWindows = 0 +var firstWindow = null +var sessionWatcher = null +var focusedDevtoolsHost +var hasFirstWindowLoaded = false +var isTabSwitcherActive = {} // map of {[window.id] => Boolean} +var windowAddedSettings = {} // map of {[window.id] => Object} const BROWSING_SESSION_PATH = './shell-window-state.json' -const ICON_PATH = path.join(__dirname, (process.platform === 'win32') ? './assets/img/logo.ico' : './assets/img/logo.png') -const PRELOAD_PATH = path.join(__dirname, 'shell-window.build.js') +export const ICON_PATH = path.join(__dirname, (process.platform === 'win32') ? './assets/img/logo.ico' : './assets/img/logo.png') +export const PRELOAD_PATH = path.join(__dirname, 'fg', 'shell-window', 'index.build.js') // exported methods // = @@ -52,6 +63,11 @@ const PRELOAD_PATH = path.join(__dirname, 'shell-window.build.js') export async function setup () { // config userDataDir = jetpack.cwd(app.getPath('userData')) + sessionWatcher = new SessionWatcher(userDataDir) + var previousSessionState = getPreviousBrowsingSession() + var customStartPage = await settingsDb.get('custom_start_page') + var isTestDriverActive = !!getEnvVar('BEAKER_TEST_DRIVER') + var isOpenUrlEnvVar = !!getEnvVar('BEAKER_OPEN_URL') // set up app events app.on('activate', () => { @@ -60,39 +76,53 @@ export async function setup () { else app.on('ready', ensureOneWindowExists) }) ipcMain.on('new-window', () => createShellWindow()) - app.on('custom-window-all-closed', () => { - if (process.platform !== 'darwin') app.quit() + app.on('custom-window-all-closed', async () => { + if (process.platform !== 'darwin') { + var runBackground = await settingsDb.get('run_background') + if (runBackground != 1) { + app.quit() + } + } }) openURL.setup() - viewManager.setup() + await tabManager.setup() app.on('before-quit', async e => { - sessionWatcher.exit() sessionWatcher.stopRecording() + sessionWatcher.exit() }) app.on('web-contents-created', async (e, wc) => { // await setup - await new Promise(resolve => wc.once('dom-ready', resolve)) + await new Promise(resolve => wc.once('did-start-loading', resolve)) // handle shell-window webcontents const window = BrowserWindow.fromWebContents(wc) if (window) { // attach global keybindings + wc.on('before-input-event', globalTabSwitcherKeyHandler) wc.on('before-input-event', createGlobalKeybindingsHandler(window)) return } // handle tab & sidebar webcontents - const parentView = BrowserView.fromWebContents(wc) + var parentView = BrowserView.fromWebContents(wc) if (!parentView) return - const parentWindow = findWebContentsParentWindow(parentView) - if (!parentWindow) return + var parentWindow = findWebContentsParentWindow(parentView) + if (!parentWindow) { + parentWindow = tabManager.findContainingWindow(parentView) + if (!parentWindow) { + parentWindow = sidebarsSubwindow.findContainingWindow(parentView) + if (!parentWindow) { + return + } + } + } // attach global keybindings + wc.on('before-input-event', globalTabSwitcherKeyHandler) wc.on('before-input-event', createGlobalKeybindingsHandler(parentWindow)) - // attach keybinding protections wc.on('before-input-event', createKeybindingProtectionsHandler(parentWindow)) // HACK @@ -104,7 +134,7 @@ export async function setup () { wc.devToolsWebContents.executeJavaScript('InspectorFrontendHost.openInNewTab = (url) => window.open(url)') wc.devToolsWebContents.on('new-window', (e, url) => { if (url.startsWith('chrome-devtools://')) return // ignore - viewManager.create(parentWindow, url, {setActive: true}) + tabManager.create(parentWindow, url, {setActive: true, adjacentActive: true}) }) } }) @@ -120,12 +150,6 @@ export async function setup () { } }) - let previousSessionState = getPreviousBrowsingSession() - sessionWatcher = new SessionWatcher(userDataDir) - let customStartPage = await settingsDb.get('custom_start_page') - let isTestDriverActive = !!beakerCore.getEnvVar('BEAKER_TEST_DRIVER') - let isOpenUrlEnvVar = !!beakerCore.getEnvVar('BEAKER_OPEN_URL') - if (!isTestDriverActive && !isOpenUrlEnvVar && (customStartPage === 'previous' || (!previousSessionState.cleanExit && userWantsToRestoreSession()))) { // restore old window restoreBrowsingSession(previousSessionState) @@ -141,23 +165,37 @@ export async function setup () { } if (isOpenUrlEnvVar) { // use the env var if specified - opts.pages = [beakerCore.getEnvVar('BEAKER_OPEN_URL')] + opts.pages = [getEnvVar('BEAKER_OPEN_URL')] } // create new window createShellWindow(opts) } } -export function createShellWindow (windowState) { +export function createShellWindow (windowState, createOpts = {dontInitPages: false}) { + if (!sessionWatcher) { + logger.error('Attempted to create a shell window prior to setup', {stack: (new Error()).stack}) + return + } // create window - let state = ensureVisibleOnSomeDisplay(Object.assign({}, defaultWindowState(), windowState)) + let state = ensureVisibleOnSomeDisplay(Object.assign({}, defaultWindowState(), lastWindowPositioning(), windowState)) var { x, y, width, height, minWidth, minHeight } = state - var win = new BrowserWindow({ - titleBarStyle: 'hiddenInset', - autoHideMenuBar: true, + var frameSettings = { + titleBarStyle: 'hidden', + trafficLightPosition: {x: 12, y: 20}, + frame: IS_LINUX, + title: undefined + } + if (state.isAppWindow) { + frameSettings.titleBarStyle = 'default' + frameSettings.trafficLightPosition = undefined + frameSettings.frame = true + } + var win = new BrowserWindow(Object.assign({ + autoHideMenuBar: false, fullscreenable: true, fullscreenWindowTitle: true, - frame: !IS_WIN, + alwaysOnTop: state.isAlwaysOnTop, x, y, width, @@ -179,7 +217,7 @@ export function createShellWindow (windowState) { }, icon: ICON_PATH, show: false // will show when ready - }) + }, frameSettings)) win.once('ready-to-show', () => { win.show() if (!hasFirstWindowLoaded) { @@ -194,22 +232,13 @@ export function createShellWindow (windowState) { win.loadURL('beaker://shell-window') sessionWatcher.watchWindow(win, state) - // load the user session - if (!isValidUserSession(state.userSession)) { - beakerCore.users.getDefault().then(defaultUser => { - if (defaultUser) { - setUserSessionFor(win.webContents, {url: defaultUser.url}) - } - }) - } - numActiveWindows++ if (numActiveWindows === 1) { firstWindow = win.webContents.id } ipcMain.on('shell-window:ready', handlePagesReady) - win.on('closed', () => { + win.on('close', () => { ipcMain.removeListener('shell-window:ready', handlePagesReady) for (let k in subwindows) { subwindows[k].destroy(win) @@ -222,53 +251,64 @@ export function createShellWindow (windowState) { if (sender === win.webContents) { if (win.webContents.id === firstWindow) { // if this is the first window opened (since app start or since all windows closing) - viewManager.loadPins(win) + tabManager.loadPins(win) + } + if (!createOpts.dontInitPages) { + tabManager.initializeFromSnapshot(win, state.pages) + if (tabManager.getAll(win).length === 0) { + tabManager.create(win) // create new_tab + } + } + if (state.isAppWindow) { + setIsAppWindow(win, true) + state.isShellInterfaceHidden = true // must be hidden } - viewManager.initializeFromSnapshot(win, state.pages) + if (state.isShellInterfaceHidden) { + setShellInterfaceHidden(win, true) + } + win.emit('custom-pages-ready') // DISABLED // not sure whether we'll need this // -prf // run setup modal - // let isTestDriverActive = !!beakerCore.getEnvVar('BEAKER_TEST_DRIVER') - // let hasDoneSetup = Number(await beakerCore.dbs.sitedata.get('beaker://shell-window', 'has_done_setup')) === 1 - // if (!!beakerCore.getEnvVar('BEAKER_RUN_SETUP_FLOW')) { + // let isTestDriverActive = !!getEnvVar('BEAKER_TEST_DRIVER') + // let hasDoneSetup = Number(await sitedataDb.get('beaker://shell-window', 'has_done_setup')) === 1 + // if (!!getEnvVar('BEAKER_RUN_SETUP_FLOW')) { // hasDoneSetup = false // } // if (!isTestDriverActive && !hasDoneSetup) { // subwindows.modals.create(win.webContents, 'setup') - // await beakerCore.dbs.sitedata.set('beaker://shell-window', 'has_done_setup', 1) + // await sitedataDb.set('beaker://shell-window', 'has_done_setup', 1) // } } } // register shortcuts - for (var i = 1; i <= 8; i++) { registerGlobalKeybinding(win, 'CmdOrCtrl+' + i, onTabSelect(win, i - 1)) } - registerGlobalKeybinding(win, 'CmdOrCtrl+9', onLastTab(win)) - registerGlobalKeybinding(win, 'Ctrl+Tab', onNextTab(win)) - registerGlobalKeybinding(win, 'Ctrl+Shift+Tab', onPrevTab(win)) - registerGlobalKeybinding(win, 'Ctrl+PageUp', onPrevTab(win)) - registerGlobalKeybinding(win, 'Ctrl+PageDown', onNextTab(win)) - registerGlobalKeybinding(win, 'CmdOrCtrl+[', onGoBack(win)) - registerGlobalKeybinding(win, 'CmdOrCtrl+]', onGoForward(win)) - registerGlobalKeybinding(win, 'Alt+D', onFocusLocation(win)) - registerGlobalKeybinding(win, 'F5', onReload(win)) - registerGlobalKeybinding(win, 'F6', onFocusLocation(win)) + registerGlobalKeybinding(win, 'CmdOrCtrl+[', onGoBack) + registerGlobalKeybinding(win, 'CmdOrCtrl+]', onGoForward) + registerGlobalKeybinding(win, 'Alt+D', onFocusLocation) + registerGlobalKeybinding(win, 'F5', onReload) + registerGlobalKeybinding(win, 'F6', onFocusLocation) // register event handlers - win.on('browser-backward', onGoBack(win)) - win.on('browser-forward', onGoForward(win)) - win.on('scroll-touch-begin', sendScrollTouchBegin) - win.on('scroll-touch-end', sendToWebContents('scroll-touch-end')) - win.on('focus', sendToWebContents('focus')) + win.on('browser-backward', onGoBack) + win.on('browser-forward', onGoForward) + // win.on('scroll-touch-begin', sendScrollTouchBegin) // TODO readd? + // win.on('scroll-touch-end', sendToWebContents('scroll-touch-end')) // TODO readd? + win.on('focus', e => { + // sendToWebContents('focus')(e) TODO readd? + var active = tabManager.getActive(win) + if (active) active.focus() + }) win.on('blur', e => { statusBarSubwindow.set(win, false) // hide the statusbar on blur - sendToWebContents('blur')(e) + // sendToWebContents('blur')(e) TODO readd? }) win.on('app-command', (e, cmd) => { onAppCommand(win, e, cmd) }) win.on('enter-full-screen', e => { // update UI - viewManager.emitReplaceState(win) + tabManager.emitReplaceState(win) // TODO // registerGlobalKeybinding(win, 'Esc', onEscape(win)) @@ -276,14 +316,22 @@ export function createShellWindow (windowState) { }) win.on('leave-full-screen', e => { // update UI - viewManager.emitReplaceState(win) + tabManager.emitReplaceState(win) // TODO // unregisterGlobalKeybinding(win, 'Esc') // sendToWebContents('leave-full-screen')(e) }) + function onMaxChange () { + tabManager.resize(win) + // on ubuntu, the maximize/unmaximize animations require multiple resizings + setTimeout(() => tabManager.resize(win), 250) + setTimeout(() => tabManager.resize(win), 500) + } + win.on('maximize', onMaxChange) + win.on('unmaximize', onMaxChange) win.on('resize', () => { - viewManager.resize(win) + tabManager.resize(win) for (let k in subwindows) { subwindows[k].reposition(win) } @@ -299,7 +347,7 @@ export function createShellWindow (windowState) { } export function getActiveWindow () { - // try to pull the focused window; if there isnt one, fallback to the last created + // try to pull the `focus`ed window; if there isnt one, fallback to the last created var win = BrowserWindow.getFocusedWindow() if (!win || win.webContents.getURL() !== 'beaker://shell-window/') { win = BrowserWindow.getAllWindows().filter(win => win.webContents.getURL() === 'beaker://shell-window/').pop() @@ -328,45 +376,51 @@ export async function getFocusedWebContents (win) { } } -export function ensureOneWindowExists () { - if (numActiveWindows === 0) { - createShellWindow() +export function getOrCreateNonAppWindow () { + var nonAppWin = BrowserWindow.getAllWindows().filter(win => !getAddedWindowSettings(win).isAppWindow).pop() + if (!nonAppWin) { + nonAppWin = createShellWindow({pages: []}, {dontInitPages: true}) } + return nonAppWin } -export function getUserSessionFor (wc) { - // fetch current session - var win = findWebContentsParentWindow(wc) - if (!win) win = viewManager.findContainingWindow(BrowserView.fromWebContents(wc)) - if (!win) win = sidebarsSubwindow.findContainingWindow(BrowserView.fromWebContents(wc)) - var sess = sessionWatcher.getState(win).userSession +export function getAddedWindowSettings (win) { + if (!win || !win.id) return {} + return windowAddedSettings[win.id] || {} +} - // return if good - if (sess && beakerCore.users.isUser(sess.url)) { - return sess - } +export function updateAddedWindowSettings (win, settings) { + windowAddedSettings[win.id] = Object.assign(getAddedWindowSettings(win), settings) +} - // fallback to default - let defUserUrl = beakerCore.users.getDefaultUrl() - if (defUserUrl) { - sess = {url: defUserUrl} - setUserSessionFor(wc, sess) - console.log('Window had to fallback to default user:', sess.url) - return sess +export function ensureOneWindowExists () { + if (numActiveWindows === 0) { + createShellWindow() } +} - console.error('No user session available for window') - return null +export function setIsAppWindow (win, isAppWindow) { + updateAddedWindowSettings(win, {isAppWindow}) + sessionWatcher.updateState(win, {isAppWindow}) } -export function setUserSessionFor (wc, userSession) { - var win = findWebContentsParentWindow(wc) - if (!win) win = viewManager.findContainingWindow(BrowserView.fromWebContents(wc)) - return sessionWatcher.updateState(win, {userSession}) +export function toggleShellInterface (win) { + setShellInterfaceHidden(win, !getAddedWindowSettings(win).isShellInterfaceHidden) } -export function openProfileEditor (wc, sess) { - return showShellModal(wc, 'edit-profile', sess) +export function setShellInterfaceHidden (win, isShellInterfaceHidden) { + if (getAddedWindowSettings(win).isAppWindow) { + // app-window-mode forces the interface to be hidden + isShellInterfaceHidden = true + } + + updateAddedWindowSettings(win, {isShellInterfaceHidden}) + if (win.setWindowButtonVisibility && !getAddedWindowSettings(win).isAppWindow) { + win.setWindowButtonVisibility(!isShellInterfaceHidden) + } + sessionWatcher.updateState(win, {isShellInterfaceHidden}) + tabManager.emitReplaceState(win) + win.emit('resize') } // internal methods @@ -380,9 +434,9 @@ function windowWithinBounds (windowState, bounds) { } function userWantsToRestoreSession () { - let answer = dialog.showMessageBox({ + let answer = dialog.showMessageBoxSync({ type: 'question', - message: 'Sorry! It looks like Beaker crashed', + message: 'Sorry! It looks like Beaker did not exit properly', detail: 'Would you like to restore your previous browsing session?', buttons: [ 'Restore Session', 'Start New Session' ], defaultId: 0, @@ -396,10 +450,6 @@ function restoreBrowsingSession (previousSessionState) { if (windows.length) { for (let windowState of windows) { if (windowState) { - if (windowState.userSession && windowState.userSession.isTemporary) { - // dont recreate temporary user sessions - continue - } createShellWindow(windowState) } } @@ -415,10 +465,19 @@ function getPreviousBrowsingSession () { } catch (err) { // For some reason json can't be read (might be corrupted). // No worries, we have defaults. + console.error('Failed to read previous browsing session state', err) } return Object.assign({}, defaultBrowsingSessionState(), restoredState) } +function lastWindowPositioning () { + var activeWin = getActiveWindow() + if (activeWin) { + return activeWin.getBounds() + } + return getLastRecordedPositioning() +} + function ensureVisibleOnSomeDisplay (windowState) { // HACK // for some reason, electron.screen comes back null sometimes @@ -430,7 +489,7 @@ function ensureVisibleOnSomeDisplay (windowState) { if (!visible) { // Window is partially or fully not visible now. // Reset it to safe defaults. - return defaultWindowState(windowState) + return Object.assign({}, windowState, _pick(defaultWindowState(), ['x', 'y', 'width', 'height', 'minWidth', 'minHeight'])) } return windowState } @@ -452,36 +511,24 @@ function onClose (win) { } } -function onTabSelect (win, tabIndex) { - return () => viewManager.setActive(win, tabIndex) -} - -function onLastTab (win) { - return () => viewManager.setActive(win, viewManager.getAll(win).slice(-1)[0]) -} - -function onNextTab (win) { - return () => viewManager.changeActiveBy(win, 1) -} - -function onPrevTab (win) { - return () => viewManager.changeActiveBy(win, -1) -} - -function onGoBack (win) { - return () => viewManager.getActive(win).webContents.goBack() +function onGoBack () { + var win = BrowserWindow.getFocusedWindow() + tabManager.getActive(win).webContents.goBack() } -function onGoForward (win) { - return () => viewManager.getActive(win).webContents.goForward() +function onGoForward () { + var win = BrowserWindow.getFocusedWindow() + tabManager.getActive(win).webContents.goForward() } -function onReload (win) { - return () => viewManager.getActive(win).webContents.reload() +function onReload () { + var win = BrowserWindow.getFocusedWindow() + tabManager.getActive(win).webContents.reload() } -function onFocusLocation (win) { - return () => win.webContents.send('command', 'focus-location') +function onFocusLocation () { + var win = BrowserWindow.getFocusedWindow() + win.webContents.send('command', 'focus-location') } function onAppCommand (win, e, cmd) { @@ -489,10 +536,10 @@ function onAppCommand (win, e, cmd) { // see https://electronjs.org/docs/all#event-app-command-windows switch (cmd) { case 'browser-backward': - viewManager.getActive(win).webContents.goBack() + tabManager.getActive(win).webContents.goBack() break case 'browser-forward': - viewManager.getActive(win).webContents.goForward() + tabManager.getActive(win).webContents.goForward() break default: break @@ -503,24 +550,27 @@ function onEscape (win) { return () => win.webContents.send('window-event', 'leave-page-full-screen') } -// window event handlers +// tab switcher input handling // = -function sendToWebContents (event) { - return e => e.sender.webContents.send('window-event', event) -} - -function sendScrollTouchBegin (e) { - // get the cursor x/y within the window - const screen = getScreenAPI() - if (!screen) return - var cursorPos = screen.getCursorScreenPoint() - var winPos = e.sender.getBounds() - cursorPos.x -= winPos.x; cursorPos.y -= winPos.y - e.sender.webContents.send('window-event', 'scroll-touch-begin', { - cursorX: cursorPos.x, - cursorY: cursorPos.y - }) +function globalTabSwitcherKeyHandler (e, input) { + var win = getActiveWindow() + + if (input.type === 'keyDown' && input.key === 'Tab' && input.control) { + if (!isTabSwitcherActive[win.id]) { + isTabSwitcherActive[win.id] = true + tabSwitcherSubwindow.show(win) + } else { + if (input.shift) { + tabSwitcherSubwindow.moveSelection(win, -1) + } else { + tabSwitcherSubwindow.moveSelection(win, 1) + } + } + } else if (isTabSwitcherActive[win.id] && input.type === 'keyUp' && input.key === 'Control') { + isTabSwitcherActive[win.id] = false + tabSwitcherSubwindow.hide(win) + } } // helpers @@ -528,8 +578,4 @@ function sendScrollTouchBegin (e) { function getScreenAPI () { return require('electron').screen -} - -function isValidUserSession (userSession) { - return userSession && typeof userSession === 'object' } \ No newline at end of file diff --git a/app/bg/web-apis/bg.js b/app/bg/web-apis/bg.js new file mode 100644 index 0000000000..1462ff6fc1 --- /dev/null +++ b/app/bg/web-apis/bg.js @@ -0,0 +1,128 @@ +import { BrowserView } from 'electron' +import * as rpc from 'pauls-electron-rpc' +import { findTab } from '../ui/tab-manager' + +// TEMPORARY: hyperdrive.network is trusted +const INTERNAL_ORIGIN_REGEX = /^(beaker:|https?:\/\/(.*\.)?hyperdrive\.network(:|\/))/i +const SITE_ORIGIN_REGEX = /^(beaker:|hyper:|https?:|data:)/i +const IFRAME_WHITELIST = [ + 'hyperdrive.loadDrive', + 'hyperdrive.getInfo', + 'hyperdrive.diff', + 'hyperdrive.stat', + 'hyperdrive.readFile', + 'hyperdrive.readdir', + 'hyperdrive.query', + 'hyperdrive.watch', + 'hyperdrive.resolveName' +] + +// internal manifests +import loggerManifest from './manifests/internal/logger' +import drivesManifest from './manifests/internal/drives' +import beakerBrowserManifest from './manifests/internal/browser' +import beakerFilesystemManifest from './manifests/internal/beaker-filesystem' +import bookmarksManifest from './manifests/internal/bookmarks' +import datLegacyManifest from './manifests/internal/dat-legacy' +import downloadsManifest from './manifests/internal/downloads' +import historyManifest from './manifests/internal/history' +import sitedataManifest from './manifests/internal/sitedata' +import watchlistManifest from './manifests/internal/watchlist' + +// internal apis +import { WEBAPI as loggerAPI } from '../logger' +import { WEBAPI as auditLogAPI } from '../dbs/audit-log' +import drivesAPI from './bg/drives' +import * as bookmarksAPI from '../filesystem/bookmarks' +import beakerFilesystemAPI from './bg/beaker-filesystem' +import datLegacyAPI from './bg/dat-legacy' +import historyAPI from './bg/history' +import { WEBAPI as sitedataAPI } from '../dbs/sitedata' +import watchlistAPI from './bg/watchlist' +import { WEBAPI as downloadsAPI } from '../ui/downloads' +import { WEBAPI as beakerBrowserAPI } from '../browser' + +// external manifests +import capabilitiesManifest from './manifests/external/capabilities' +import contactsManifest from './manifests/external/contacts' +import hyperdriveManifest from './manifests/external/hyperdrive' +import markdownManifest from './manifests/external/markdown' +import peersocketsManifest from './manifests/external/peersockets' +import shellManifest from './manifests/external/shell' + +// external apis +import capabilitiesAPI from './bg/capabilities' +import contactsAPI from './bg/contacts' +import hyperdriveAPI from './bg/hyperdrive' +import markdownAPI from './bg/markdown' +import peersocketsAPI from './bg/peersockets' +import shellAPI from './bg/shell' + +// experimental manifests +import experimentalCapturePageManifest from './manifests/external/experimental/capture-page' +import experimentalDatPeersManifest from './manifests/external/experimental/dat-peers' +import experimentalGlobalFetchManifest from './manifests/external/experimental/global-fetch' + +// experimental apis +import experimentalCapturePageAPI from './bg/experimental/capture-page' +import experimentalDatPeersAPI from './bg/experimental/dat-peers' +import experimentalGlobalFetchAPI from './bg/experimental/global-fetch' + +// exported api +// = + +export const setup = function () { + // internal apis + rpc.exportAPI('logger', loggerManifest, Object.assign({}, auditLogAPI, loggerAPI), internalOnly) + rpc.exportAPI('beaker-browser', beakerBrowserManifest, beakerBrowserAPI, internalOnly) + rpc.exportAPI('beaker-filesystem', beakerFilesystemManifest, beakerFilesystemAPI, internalOnly) + rpc.exportAPI('bookmarks', bookmarksManifest, bookmarksAPI, internalOnly) + rpc.exportAPI('dat-legacy', datLegacyManifest, datLegacyAPI, internalOnly) + rpc.exportAPI('downloads', downloadsManifest, downloadsAPI, internalOnly) + rpc.exportAPI('drives', drivesManifest, drivesAPI, internalOnly) + rpc.exportAPI('history', historyManifest, historyAPI, internalOnly) + rpc.exportAPI('sitedata', sitedataManifest, sitedataAPI, internalOnly) + rpc.exportAPI('watchlist', watchlistManifest, watchlistAPI, internalOnly) + + // external apis + rpc.exportAPI('capabilities', capabilitiesManifest, capabilitiesAPI, secureOnly('capabilities')) + rpc.exportAPI('contacts', contactsManifest, contactsAPI, secureOnly('contacts')) + rpc.exportAPI('hyperdrive', hyperdriveManifest, hyperdriveAPI, secureOnly('hyperdrive')) + rpc.exportAPI('markdown', markdownManifest, markdownAPI) + rpc.exportAPI('peersockets', peersocketsManifest, peersocketsAPI, secureOnly('peersockets')) + rpc.exportAPI('shell', shellManifest, shellAPI, secureOnly('shell')) + + // experimental apis + rpc.exportAPI('experimental-capture-page', experimentalCapturePageManifest, experimentalCapturePageAPI, secureOnly) + rpc.exportAPI('experimental-dat-peers', experimentalDatPeersManifest, experimentalDatPeersAPI, secureOnly) + rpc.exportAPI('experimental-global-fetch', experimentalGlobalFetchManifest, experimentalGlobalFetchAPI, secureOnly) +} + +function internalOnly (event, methodName, args) { + if (!(event && event.sender)) { + return false + } + var senderInfo = getSenderInfo(event) + return senderInfo.isMainFrame && INTERNAL_ORIGIN_REGEX.test(senderInfo.url) +} + +const secureOnly = apiName => (event, methodName, args) => { + if (!(event && event.sender)) { + return false + } + var senderInfo = getSenderInfo(event) + if (!SITE_ORIGIN_REGEX.test(senderInfo.url)) { + return false + } + if (!senderInfo.isMainFrame) { + return IFRAME_WHITELIST.includes(`${apiName}.${methodName}`) + } + return true +} + +function getSenderInfo (event) { + var view = BrowserView.fromWebContents(event.sender) + var tab = (view) ? findTab(view) : undefined + if (tab) return tab.getIPCSenderInfo(event) + return {isMainFrame: true, url: event.sender.getURL()} +} \ No newline at end of file diff --git a/app/bg/web-apis/bg/beaker-filesystem.js b/app/bg/web-apis/bg/beaker-filesystem.js new file mode 100644 index 0000000000..eb563b1113 --- /dev/null +++ b/app/bg/web-apis/bg/beaker-filesystem.js @@ -0,0 +1,27 @@ +import { PermissionsError } from 'beaker-error-constants' +import * as filesystem from '../../filesystem/index' + +// typedefs +// = + +/** + * @typedef {Object} BeakerFilesystemPublicAPIRootRecord + * @prop {string} url + */ + +// exported api +// = + +export default { + /** + * @returns {BeakerFilesystemPublicAPIRootRecord} + */ + get () { + if (!this.sender.getURL().startsWith('beaker:')) { + throw new PermissionsError() + } + return { + url: filesystem.get().url + } + } +} diff --git a/app/bg/web-apis/bg/capabilities.js b/app/bg/web-apis/bg/capabilities.js new file mode 100644 index 0000000000..b621e1f1a5 --- /dev/null +++ b/app/bg/web-apis/bg/capabilities.js @@ -0,0 +1,35 @@ +import { parseDriveUrl } from '../../../lib/urls' +import * as capabilities from '../../hyper/capabilities' + +// exported api +// = + +export default { + /** + * @param {String} target + * @returns {Promise} + */ + async create (target) { + var origin = parseDriveUrl(this.sender.getURL()).origin + return capabilities.createCap(origin, target) + }, + + /** + * @param {String} capUrl + * @param {String} target + * @returns {Promise} + */ + async modify (capUrl, target) { + var origin = parseDriveUrl(this.sender.getURL()).origin + return capabilities.modifyCap(origin, capUrl, target) + }, + + /** + * @param {String} capUrl + * @returns {Promise} + */ + async delete (capUrl) { + var origin = parseDriveUrl(this.sender.getURL()).origin + return capabilities.deleteCap(origin, capUrl) + } +} \ No newline at end of file diff --git a/app/bg/web-apis/bg/contacts.js b/app/bg/web-apis/bg/contacts.js new file mode 100644 index 0000000000..2ba360ecf0 --- /dev/null +++ b/app/bg/web-apis/bg/contacts.js @@ -0,0 +1,166 @@ +import * as modals from '../../ui/subwindows/modals' +import * as drives from '../../hyper/drives' +import * as filesystem from '../../filesystem/index' +import * as permissions from '../../ui/permissions' +import { UserDeniedError, PermissionsError } from 'beaker-error-constants' +import { HYPERDRIVE_HASH_REGEX } from '../../../lib/const' + +// typedefs +// = + +/** + * @typedef {Object} BeakerContactPublicAPIContactRecord + * @prop {string} url + * @prop {string} title + * @prop {string} description + */ + +// exported api +// = + +export default { + /** + * @returns {Promise} + */ + async requestProfile () { + var addressBook = await readAddressBook() + addressBook.profiles = await assembleRecords(addressBook.profiles) + + var res + try { + res = await modals.create(this.sender, 'select-contact', {multiple: false, showProfilesOnly: true, addressBook}) + } catch (e) { + if (e.name !== 'Error') { + throw e // only rethrow if a specific error + } + } + if (!res) throw new UserDeniedError() + return res && res.contacts ? res.contacts[0] : undefined + }, + + /** + * @returns {Promise} + */ + async requestContact () { + var addressBook = await readAddressBook() + addressBook.profiles = await assembleRecords(addressBook.profiles) + addressBook.contacts = await assembleRecords(addressBook.contacts) + + var res + try { + res = await modals.create(this.sender, 'select-contact', {multiple: false, addressBook}) + } catch (e) { + if (e.name !== 'Error') { + throw e // only rethrow if a specific error + } + } + if (!res) throw new UserDeniedError() + return res && res.contacts ? res.contacts[0] : undefined + }, + + /** + * @returns {Promise>} + */ + async requestContacts () { + var addressBook = await readAddressBook() + addressBook.profiles = await assembleRecords(addressBook.profiles) + addressBook.contacts = await assembleRecords(addressBook.contacts) + + var res + try { + res = await modals.create(this.sender, 'select-contact', {multiple: true, addressBook}) + } catch (e) { + if (e.name !== 'Error') { + throw e // only rethrow if a specific error + } + } + if (!res) throw new UserDeniedError() + return res && res.contacts ? res.contacts : undefined + }, + + /** + * @param {string} url + * @returns {Promise} + */ + async requestAddContact (url) { + var res + try { + res = await modals.create(this.sender, 'add-contact', {url}) + } catch (e) { + if (e.name !== 'Error') { + throw e // only rethrow if a specific error + } + } + if (!res) throw new UserDeniedError() + + var addressBook = await readAddressBook() + var existingContact = addressBook.contacts.find(contact => contact.key === res.key) + if (!existingContact) { + addressBook.contacts.push({key: res.key}) + } + await filesystem.get().pda.writeFile('/address-book.json', JSON.stringify(addressBook, null, 2)) + + if (res.host) { + await filesystem.configDrive(res.key) + } + }, + + /** + * @returns {Promise>} + */ + async list () { + if (!(await permissions.requestPermission('contactsList', this.sender))) { + throw new UserDeniedError() + } + + var addressBook = await readAddressBook() + return assembleRecords(addressBook.contacts) + }, + + async remove (url) { + if (!isBeakerApp(this.sender)) { + throw new PermissionsError() + } + var key = await drives.fromURLToKey(url, true) + var addressBook = await readAddressBook() + var index = addressBook.contacts.findIndex(contact => contact.key === key) + if (index !== -1) { + addressBook.contacts.splice(index, 1) + } + await filesystem.get().pda.writeFile('/address-book.json', JSON.stringify(addressBook, null, 2)) + } +} + +// internal methods +// = + +async function readAddressBook () { + const sysDrive = filesystem.get().pda + var addressBook = await sysDrive.readFile('/address-book.json').then(JSON.parse).catch(e => undefined) + if (!addressBook || typeof addressBook !== 'object') addressBook = {} + if (!addressBook.contacts || !Array.isArray(addressBook.contacts)) addressBook.contacts = [] + if (!addressBook.profiles || !Array.isArray(addressBook.profiles)) addressBook.profiles = [] + return addressBook +} + +async function assembleRecords (contactsList) { + var records = [] + for (let contact of contactsList) { + if (typeof contact.key !== 'string' || !HYPERDRIVE_HASH_REGEX.test(contact.key)) continue + let url = `hyper://${contact.key}/` + let info = await drives.getDriveInfo(contact.key, {ignoreCache: false, onlyCache: true}).catch(e => ({})) + records.push({ + url, + title: info.title || '', + description: info.description || '' + }) + } + return records +} + +function isBeakerApp (sender) { + if (/^(beaker:|https?:\/\/(.*\.)?hyperdrive\.network(:|\/))/.test(sender.getURL())) { + return true + } + return false +} \ No newline at end of file diff --git a/app/bg/web-apis/bg/dat-legacy.js b/app/bg/web-apis/bg/dat-legacy.js new file mode 100644 index 0000000000..8e807c389b --- /dev/null +++ b/app/bg/web-apis/bg/dat-legacy.js @@ -0,0 +1,14 @@ +import * as archivesDb from '../../dbs/archives' + +// exported api +// = + +export default { + async list () { + return archivesDb.listLegacyArchives() + }, + + async remove (key) { + return archivesDb.removeLegacyArchive(key) + } +} \ No newline at end of file diff --git a/app/bg/web-apis/bg/drives.js b/app/bg/web-apis/bg/drives.js new file mode 100644 index 0000000000..d3bc21a15f --- /dev/null +++ b/app/bg/web-apis/bg/drives.js @@ -0,0 +1,131 @@ +import hyper from '../../hyper/index' +import * as drives from '../../hyper/drives' +import * as archivesDb from '../../dbs/archives' +import { listDrives, configDrive, removeDrive, getDriveIdent } from '../../filesystem/index' +import * as trash from '../../filesystem/trash' + +// exported api +// = + +export default { + async get (key) { + key = await drives.fromURLToKey(key, true) + var drive = listDrives().find(drive => drive.key === key) + var info = await drives.getDriveInfo(key).catch(e => ({})) + var url = `hyper://${key}/` + var ident = getDriveIdent(url) + return { + key, + url, + info, + saved: !!drive, + forkOf: drive ? drive.forkOf : undefined, + ident + } + }, + + async list (opts) { + return assembleRecords(listDrives(opts)) + }, + + async getPeerCount (url) { + var key = hyper.drives.fromURLToKey(url) + return hyper.daemon.getPeerCount(Buffer.from(key, 'hex')) + }, + + async getForks (key) { + key = await drives.fromURLToKey(key, true) + var drivesList = listDrives() + var rootDrive = drivesList.find(drive => drive.key === key) + if (!rootDrive) return assembleRecords([{key}]) + + // find root of the tree + var seenKeys = new Set() // used to break cycles + while (rootDrive && rootDrive.forkOf && rootDrive.forkOf.key && !seenKeys.has(rootDrive.forkOf.key)) { + seenKeys.add(rootDrive.key) + rootDrive = drivesList.find(drive2 => drive2.key === rootDrive.forkOf.key) + } + if (!rootDrive) return [] + + // build the tree + var forks = [] + function addForksOf (drive) { + if (forks.includes(drive)) return // cycle + forks.push(drive) + for (let drive2 of drivesList) { + if (drive2.forkOf && drive2.forkOf.key === drive.key) { + addForksOf(drive2) + } + } + } + addForksOf(rootDrive) + + return assembleRecords(forks) + }, + + async configure (key, opts) { + return configDrive(key, opts) + }, + + async remove (key) { + return removeDrive(key) + }, + + async collectTrash () { + return trash.collect({olderThan: 0}) + }, + + async delete (url) { + // TODO + // var drive = await drives.getOrLoadDrive(url) + // assertDriveDeletable(drive.key) + // await datLibrary.configureDrive(drive, {isSaved: false}) + // await drives.unloadDrive(drive.key) + // var bytes = await archivesDb.deleteArchive(drive.key) + // return {bytes} + }, + + async touch (key, timeVar, value) { + return archivesDb.touch(key, timeVar, value) + }, + + async clearFileCache (url) { + return drives.clearFileCache(await drives.fromURLToKey(url, true)) + }, + + clearDnsCache () { + hyper.dns.flushCache() + }, + + createEventStream () { + return drives.createEventStream() + }, + + getDebugLog (key) { + return drives.getDebugLog(key) + }, + + createDebugStream () { + return drives.createDebugStream() + } +} + +// internal methods +// = + +async function assembleRecords (drivesList) { + var records = [] + for (let drive of drivesList) { + let url = `hyper://${drive.key}/` + let ident = getDriveIdent(url) + records.push({ + key: drive.key, + url, + info: await drives.getDriveInfo(drive.key).catch(e => ({})), + saved: true, + forkOf: drive ? drive.forkOf : undefined, + ident + }) + } + return records +} \ No newline at end of file diff --git a/app/bg/web-apis/bg/experimental/capture-page.js b/app/bg/web-apis/bg/experimental/capture-page.js new file mode 100644 index 0000000000..f7c988a74c --- /dev/null +++ b/app/bg/web-apis/bg/experimental/capture-page.js @@ -0,0 +1,67 @@ +import * as beakerBrowser from '../../../browser' +import * as permissions from '../../../ui/permissions' +import { URL } from 'url' + +// constants +// = + +const API_DOCS_URL = 'https://beakerbrowser.com/docs/apis/experimental-capturepage.html' +const API_PERM_ID = 'experimentalCapturePage' +const LAB_API_ID = 'capturePage' + +// exported api +// = + +export default { + async capturePage (url, opts = {}) { + // validate inputs + if (!url && typeof url !== 'string') { + throw new Error('The first argument must be a URL string') + } + if (opts && typeof opts !== 'object') { + throw new Error('The second argument must be an options object') + } + if (opts) { + if ('width' in opts) { + if (typeof opts.width !== 'number') throw new Error('The width option must be a number') + if (opts.width <= 0 || opts.width > 1600) throw new Error('The width option must between 1 and 1600') + } + if ('height' in opts) { + if (typeof opts.height !== 'number') throw new Error('The height option must be a number') + if (opts.height <= 0 || opts.height > 1200) throw new Error('The height option must between 1 and 1200') + } + if ('resizeTo' in opts) { + if (typeof opts.resizeTo !== 'object') throw new Error('The resizeTo option must be an object') + if ('width' in opts.resizeTo) { + if (typeof opts.resizeTo.width !== 'number') throw new Error('The resizeTo.width option must be a number') + if (opts.resizeTo.width <= 0 || opts.resizeTo.width > 1600) throw new Error('The resizeTo.width option must between 1 and 1600') + } + if ('height' in opts.resizeTo) { + if (typeof opts.resizeTo.height !== 'number') throw new Error('The resizeTo.height option must be a number') + if (opts.resizeTo.height <= 0 || opts.resizeTo.height > 1200) throw new Error('The resizeTo.height option must between 1 and 1200') + } + } + } + + // parse url + var urlp + try { urlp = new URL(url) } + catch (e) { throw new Error('The first argument must be a URL string') } + + if (['http:', 'https:', 'hyper:'].indexOf(urlp.protocol) === -1) { + throw new Error('Can only capture pages served over http, https, or hyper') + } + + // check perms + await permissions.checkLabsPerm({ + perm: API_PERM_ID + ':' + url, + labApi: LAB_API_ID, + apiDocsUrl: API_DOCS_URL, + sender: this.sender + }) + + // run method + var img = await beakerBrowser.capturePage(url, opts) + return img.toPNG() + } +} diff --git a/app/bg/web-apis/bg/experimental/dat-peers.js b/app/bg/web-apis/bg/experimental/dat-peers.js new file mode 100644 index 0000000000..8bedb20f68 --- /dev/null +++ b/app/bg/web-apis/bg/experimental/dat-peers.js @@ -0,0 +1,81 @@ +import { parseDriveUrl } from '../../../../lib/urls' +import { PermissionsError } from 'beaker-error-constants' +import * as permissions from '../../../ui/permissions' +import * as drives from '../../../hyper/drives' +import * as hyperDns from '../../../hyper/dns' +import { HYPERDRIVE_HASH_REGEX } from '../../../../lib/const' + +// constants +// = + +const API_DOCS_URL = 'https://beakerbrowser.com/docs/apis/experimental-datpeers.html' +const API_PERM_ID = 'experimentalDatPeers' +const LAB_API_ID = 'datPeers' +const LAB_PERMS_OBJ = {perm: API_PERM_ID, labApi: LAB_API_ID, apiDocsUrl: API_DOCS_URL} + +// exported api +// = + +export default { + async list () { + await permissions.checkLabsPerm(Object.assign({sender: this.sender}, LAB_PERMS_OBJ)) + var drive = await getSenderDrive(this.sender) + // TODO return drives.getDaemon().ext_listPeers(drive.key.toString('hex')) + }, + + async get (peerId) { + await permissions.checkLabsPerm(Object.assign({sender: this.sender}, LAB_PERMS_OBJ)) + var drive = await getSenderDrive(this.sender) + // TODO return drives.getDaemon().ext_getPeer(drive.key.toString('hex'), peerId) + }, + + async broadcast (data) { + await permissions.checkLabsPerm(Object.assign({sender: this.sender}, LAB_PERMS_OBJ)) + var drive = await getSenderDrive(this.sender) + // TODO return drives.getDaemon().ext_broadcastEphemeralMessage(drive.key.toString('hex'), data) + }, + + async send (peerId, data) { + await permissions.checkLabsPerm(Object.assign({sender: this.sender}, LAB_PERMS_OBJ)) + var drive = await getSenderDrive(this.sender) + // TODO return drives.getDaemon().ext_sendEphemeralMessage(drive.key.toString('hex'), peerId, data) + }, + + async getSessionData () { + await permissions.checkLabsPerm(Object.assign({sender: this.sender}, LAB_PERMS_OBJ)) + var drive = await getSenderDrive(this.sender) + // TODO return drives.getDaemon().ext_getSessionData(drive.key.toString('hex')) + }, + + async setSessionData (sessionData) { + await permissions.checkLabsPerm(Object.assign({sender: this.sender}, LAB_PERMS_OBJ)) + var drive = await getSenderDrive(this.sender) + // TODO return drives.getDaemon().ext_setSessionData(drive.key.toString('hex'), sessionData) + }, + + async createEventStream () { + await permissions.checkLabsPerm(Object.assign({sender: this.sender}, LAB_PERMS_OBJ)) + var drive = await getSenderDrive(this.sender) + // TODO return drives.getDaemon().ext_createDatPeersStream(drive.key.toString('hex')) + }, + + async getOwnPeerId () { + await permissions.checkLabsPerm(Object.assign({sender: this.sender}, LAB_PERMS_OBJ)) + // TODO return drives.getDaemon().ext_getOwnPeerId() + } +} + +// internal methods +// = + +async function getSenderDrive (sender) { + var url = sender.getURL() + if (!url.startsWith('hyper:')) { + throw new PermissionsError('Only hyper:// sites can use the datPeers API') + } + var urlp = parseDriveUrl(url) + if (!HYPERDRIVE_HASH_REGEX.test(urlp.host)) { + urlp.host = await hyperDns.resolveName(url) + } + return drives.getDrive(urlp.host) +} diff --git a/app/bg/web-apis/bg/experimental/global-fetch.js b/app/bg/web-apis/bg/experimental/global-fetch.js new file mode 100644 index 0000000000..18bcd24119 --- /dev/null +++ b/app/bg/web-apis/bg/experimental/global-fetch.js @@ -0,0 +1,68 @@ +import * as permissions from '../../../ui/permissions' +import http from 'http' +import https from 'https' +import concat from 'concat-stream' +import { URL } from 'url' + +// constants +// = + +const API_DOCS_URL = 'https://beakerbrowser.com/docs/apis/experimental-globalfetch.html' +const API_PERM_ID = 'experimentalGlobalFetch' +const LAB_API_ID = 'globalFetch' + +// exported api +// = + +export default { + async fetch (reqOptions, reqBody) { + // parse url + var urlp = new URL(reqOptions.url) + reqOptions.protocol = urlp.protocol + reqOptions.host = urlp.host + reqOptions.path = urlp.pathname + urlp.search + urlp.hash + + // check perms + await permissions.checkLabsPerm({ + perm: API_PERM_ID + ':' + reqOptions.protocol + '//' + reqOptions.host, + labApi: LAB_API_ID, + apiDocsUrl: API_DOCS_URL, + sender: this.sender + }) + + if (reqOptions.protocol !== 'https:' && reqOptions.protocol !== 'http:') { + throw new Error('Can only send requests to http or https URLs') + } + + return new Promise((resolve, reject) => { + // start request + var proto = urlp.protocol === 'https:' ? https : http + var reqStream = proto.request(reqOptions, resStream => { + resStream.pipe(concat(resStream, resBody => { + // resolve with response + resolve({ + status: resStream.statusCode, + statusText: resStream.statusMessage, + headers: resStream.headers, + body: resBody + }) + })) + + // handle errors + resStream.on('error', err => { + reject(new Error('Network request failed')) + }) + resStream.on('abort', err => { + reject(new Error('Aborted')) + }) + }) + + // send data + if (reqBody) { + reqStream.send(reqBody) + } + + reqStream.end() + }) + } +} diff --git a/app/bg/web-apis/bg/history.js b/app/bg/web-apis/bg/history.js new file mode 100644 index 0000000000..73ed3d8175 --- /dev/null +++ b/app/bg/web-apis/bg/history.js @@ -0,0 +1,34 @@ +import * as historyDb from '../../dbs/history' + +// exported api +// = + +export default { + async addVisit (...args) { + return historyDb.addVisit(0, ...args) + }, + + async getVisitHistory (...args) { + return historyDb.getVisitHistory(0, ...args) + }, + + async getMostVisited (...args) { + return historyDb.getMostVisited(0, ...args) + }, + + async search (...args) { + return historyDb.search(...args) + }, + + async removeVisit (...args) { + return historyDb.removeVisit(...args) + }, + + async removeAllVisits (...args) { + return historyDb.removeAllVisits(...args) + }, + + async removeVisitsAfter (...args) { + return historyDb.removeVisitsAfter(...args) + } +} diff --git a/app/bg/web-apis/bg/hyperdrive.js b/app/bg/web-apis/bg/hyperdrive.js new file mode 100644 index 0000000000..72740c4480 --- /dev/null +++ b/app/bg/web-apis/bg/hyperdrive.js @@ -0,0 +1,855 @@ +import path from 'path' +import { parseDriveUrl } from '../../../lib/urls' +import pda from 'pauls-dat-api2' +import pick from 'lodash.pick' +import _get from 'lodash.get' +import _flattenDeep from 'lodash.flattendeep' +import * as modals from '../../ui/subwindows/modals' +import * as permissions from '../../ui/permissions' +import * as hyperDns from '../../hyper/dns' +import * as capabilities from '../../hyper/capabilities' +import * as drives from '../../hyper/drives' +import * as archivesDb from '../../dbs/archives' +import * as auditLog from '../../dbs/audit-log' +import { timer } from '../../../lib/time' +import * as filesystem from '../../filesystem/index' +import { query } from '../../filesystem/query' +import drivesAPI from './drives' +import { DRIVE_MANIFEST_FILENAME, DRIVE_CONFIGURABLE_FIELDS, HYPERDRIVE_HASH_REGEX, DAT_QUOTA_DEFAULT_BYTES_ALLOWED, DRIVE_VALID_PATH_REGEX, DEFAULT_DRIVE_API_TIMEOUT } from '../../../lib/const' +import { PermissionsError, UserDeniedError, QuotaExceededError, ArchiveNotWritableError, InvalidURLError, ProtectedFileNotWritableError, InvalidPathError } from 'beaker-error-constants' + +// exported api +// = + +const isSenderBeaker = (sender) => /^(beaker:|https?:\/\/(.*\.)?hyperdrive\.network(:|\/))/.test(sender.getURL()) + +const to = (opts) => + (opts && typeof opts.timeout !== 'undefined') + ? opts.timeout + : DEFAULT_DRIVE_API_TIMEOUT + +export default { + async createDrive ({title, description, author, visibility, prompt} = {}) { + var newDriveUrl + + // only allow these vars to be set by beaker, for now + if (!isSenderBeaker(this.sender)) { + visibility = undefined + author = undefined // TODO _get(windows.getUserSessionFor(this.sender), 'url') + } + + if (prompt !== false) { + // run the creation modal + let res + try { + res = await modals.create(this.sender, 'create-drive', {title, description, author, visibility}) + } catch (e) { + if (e.name !== 'Error') { + throw e // only rethrow if a specific error + } + } + if (!res || !res.url) throw new UserDeniedError() + newDriveUrl = res.url + } else { + // no modal, ask for permission + await assertCreateDrivePermission(this.sender) + + // create + let newDrive + try { + let manifest = {title, description, /*TODO author,*/} + newDrive = await drives.createNewDrive(manifest) + await filesystem.configDrive(newDrive.url) + } catch (e) { + console.log(e) + throw e + } + newDriveUrl = newDrive.url + } + let newDriveKey = await lookupUrlDriveKey(newDriveUrl) + + if (!isSenderBeaker(this.sender)) { + // grant write permissions to the creating app + permissions.grantPermission('modifyDrive:' + newDriveKey, this.sender.getURL()) + } + return newDriveUrl + }, + + async forkDrive (url, {detached, title, description, label, prompt} = {}) { + var newDriveUrl + + // only allow these vars to be set by beaker, for now + if (!isSenderBeaker(this.sender)) { + title = description = detached = label = prompt = undefined + } + + if (prompt !== false) { + // run the fork modal + let res + let forks = await drivesAPI.getForks(url) + try { + res = await modals.create(this.sender, 'fork-drive', {url, forks, detached, label}) + } catch (e) { + if (e.name !== 'Error') { + throw e // only rethrow if a specific error + } + } + if (!res || !res.url) throw new UserDeniedError() + newDriveUrl = res.url + } else { + // no modal, ask for permission + await assertCreateDrivePermission(this.sender) + + let key = await lookupUrlDriveKey(url) + + // save the parent if needed + if (!filesystem.getDriveConfig(key)) { + await filesystem.configDrive(key) + } + + // create + let newDrive = await drives.forkDrive(key, { + title: detached ? title : undefined, + description: detached ? description : undefined, + detached + }) + await filesystem.configDrive(newDrive.url, { + forkOf: detached ? undefined : {key, label} + }) + newDriveUrl = newDrive.url + } + + return newDriveUrl + }, + + async loadDrive (url) { + if (!url || typeof url !== 'string') { + return Promise.reject(new InvalidURLError()) + } + var urlp = parseDriveUrl(url) + await lookupDrive(this.sender, urlp.hostname, urlp.version) + return Promise.resolve(true) + }, + + async getInfo (url, opts = {}) { + return auditLog.record(this.sender.getURL(), 'getInfo', {url}, undefined, () => ( + timer(to(opts), async (checkin, pause, resume) => { + var urlp = parseDriveUrl(url) + var {driveKey, version} = await lookupDrive(this.sender, urlp.hostname, urlp.version, true) + var info = await drives.getDriveInfo(driveKey) + var isCap = urlp.hostname.endsWith('.cap') + + // request from beaker internal sites: give all data + if (isSenderBeaker(this.sender)) { + return info + } + + pause() // dont count against timeout, there may be user prompts + await assertReadPermission(driveKey, this.sender) + resume() + + // request from userland: return a subset of the data + return { + key: isCap ? urlp.hostname : info.key, + url: isCap ? urlp.origin : info.url, + // domain: info.domain, TODO + writable: info.writable, + + // state + version: info.version, + peers: info.peers, + + // manifest + title: info.title, + description: info.description + } + }) + )) + }, + + async configure (url, settings, opts) { + return auditLog.record(this.sender.getURL(), 'configure', {url, ...settings}, undefined, () => ( + timer(to(opts), async (checkin, pause, resume) => { + checkin('looking up drive') + + var urlp = parseDriveUrl(url) + var {drive, checkoutFS, isHistoric} = await lookupDrive(this.sender, urlp.hostname, urlp.version) + if (isHistoric) throw new ArchiveNotWritableError('Cannot modify a historic version') + if (!settings || typeof settings !== 'object') throw new Error('Invalid argument') + + // handle 'visibility' specially + // also, only allow beaker to set 'visibility' for now + if (('visibility' in settings) && isSenderBeaker(this.sender)) { + // TODO uwg await datLibrary.configureDrive(drive, {visibility: settings.visibility}) + } + + // only allow beaker to set these manifest updates for now + if (!isSenderBeaker(this.sender)) { + delete settings.author + } + + // manifest updates + let manifestUpdates = pick(settings, DRIVE_CONFIGURABLE_FIELDS) + if (Object.keys(manifestUpdates).length === 0) { + // no manifest updates + return + } + + pause() // dont count against timeout, there may be user prompts + var senderOrigin = archivesDb.extractOrigin(this.sender.getURL()) + await assertWritePermission(drive, this.sender) + await assertQuotaPermission(drive, senderOrigin, Buffer.byteLength(JSON.stringify(settings), 'utf8')) + resume() + + checkin('updating drive') + await checkoutFS.pda.updateManifest(manifestUpdates) + await drives.pullLatestDriveMeta(drive) + }) + )) + }, + + async diff (url, other, opts = {}) { + var urlp = parseDriveUrl(url) + var url = urlp.origin + var prefix = urlp.pathname + return auditLog.record(this.sender.getURL(), 'diff', {url, other, prefix}, undefined, () => ( + timer(to(opts), async (checkin, pause, resume) => { + checkin('looking up drive') + const {checkoutFS} = await lookupDrive(this.sender, url, urlp.version) + pause() // dont count against timeout, there may be user prompts + await assertReadPermission(checkoutFS, this.sender) + resume() + checkin('diffing') + return checkoutFS.pda.diff(other, prefix) + }) + )) + }, + + async stat (url, opts = {}) { + var urlp = parseDriveUrl(url) + var url = urlp.origin + var filepath = normalizeFilepath(urlp.pathname || '') + return auditLog.record(this.sender.getURL(), 'stat', {url, filepath}, undefined, () => ( + timer(to(opts), async (checkin, pause, resume) => { + checkin('looking up drive') + const {checkoutFS} = await lookupDrive(this.sender, urlp.hostname, urlp.version) + pause() // dont count against timeout, there may be user prompts + await assertReadPermission(checkoutFS, this.sender) + resume() + checkin('stating file') + return checkoutFS.pda.stat(filepath, opts) + }) + )) + }, + + async readFile (url, opts = {}) { + var urlp = parseDriveUrl(url) + var url = urlp.origin + var filepath = normalizeFilepath(urlp.pathname || '') + return auditLog.record(this.sender.getURL(), 'readFile', {url, filepath, opts}, undefined, () => ( + timer(to(opts), async (checkin, pause, resume) => { + checkin('looking up drive') + const {checkoutFS} = await lookupDrive(this.sender, urlp.hostname, urlp.version) + pause() // dont count against timeout, there may be user prompts + await assertReadPermission(checkoutFS, this.sender) + resume() + checkin('reading file') + return checkoutFS.pda.readFile(filepath, opts) + }) + )) + }, + + async writeFile (url, data, opts = {}) { + var urlp = parseDriveUrl(url) + var url = urlp.origin + var filepath = normalizeFilepath(urlp.pathname || '') + const sourceSize = Buffer.byteLength(data, opts.encoding) + return auditLog.record(this.sender.getURL(), 'writeFile', {url, filepath}, sourceSize, () => ( + timer(to(opts), async (checkin, pause, resume) => { + checkin('looking up drive') + const {drive, checkoutFS, isHistoric} = await lookupDrive(this.sender, urlp.hostname, urlp.version) + if (isHistoric) throw new ArchiveNotWritableError('Cannot modify a historic version') + + pause() // dont count against timeout, there may be user prompts + const senderOrigin = archivesDb.extractOrigin(this.sender.getURL()) + await assertWritePermission(drive, this.sender) + await assertQuotaPermission(drive, senderOrigin, sourceSize) + assertValidFilePath(filepath) + assertUnprotectedFilePath(filepath, this.sender) + resume() + + checkin('writing file') + return checkoutFS.pda.writeFile(filepath, data, opts) + }) + )) + }, + + async unlink (url, opts = {}) { + var urlp = parseDriveUrl(url) + var url = urlp.origin + var filepath = normalizeFilepath(urlp.pathname || '') + return auditLog.record(this.sender.getURL(), 'unlink', {url, filepath}, undefined, () => ( + timer(to(opts), async (checkin, pause, resume) => { + checkin('looking up drive') + const {drive, checkoutFS, isHistoric} = await lookupDrive(this.sender, urlp.hostname, urlp.version) + if (isHistoric) throw new ArchiveNotWritableError('Cannot modify a historic version') + + pause() // dont count against timeout, there may be user prompts + await assertWritePermission(drive, this.sender) + assertUnprotectedFilePath(filepath, this.sender) + resume() + + checkin('deleting file') + return checkoutFS.pda.unlink(filepath) + }) + )) + }, + + async copy (url, dstpath, opts = {}) { + var urlp = parseDriveUrl(url) + var url = urlp.origin + var srcpath = normalizeFilepath(urlp.pathname || '') + dstpath = normalizeFilepath(dstpath || '') + const src = await lookupDrive(this.sender, urlp.hostname, urlp.version) + const sourceSize = await src.drive.pda.readSize(srcpath) + return auditLog.record(this.sender.getURL(), 'copy', {url, srcpath, dstpath}, sourceSize, () => ( + timer(to(opts), async (checkin, pause, resume) => { + checkin('searching for drive') + + const dst = await lookupDrive(this.sender, dstpath.includes('://') ? dstpath : url) + + if (srcpath.includes('://')) srcpath = (new URL(srcpath)).pathname + if (dstpath.includes('://')) dstpath = (new URL(dstpath)).pathname + + pause() // dont count against timeout, there may be user prompts + const senderOrigin = archivesDb.extractOrigin(this.sender.getURL()) + await assertWritePermission(dst.drive, this.sender) + assertUnprotectedFilePath(dstpath, this.sender) + await assertQuotaPermission(dst.drive, senderOrigin, sourceSize) + resume() + + checkin('copying') + return src.checkoutFS.pda.copy(srcpath, dst.checkoutFS.session.drive, dstpath) + }) + )) + }, + + async rename (url, dstpath, opts = {}) { + var urlp = parseDriveUrl(url) + var url = urlp.origin + var srcpath = normalizeFilepath(urlp.pathname || '') + dstpath = normalizeFilepath(dstpath || '') + return auditLog.record(this.sender.getURL(), 'rename', {url, srcpath, dstpath}, undefined, () => ( + timer(to(opts), async (checkin, pause, resume) => { + checkin('searching for drive') + + const src = await lookupDrive(this.sender, urlp.hostname, urlp.version) + const dst = await lookupDrive(this.sender, dstpath.includes('://') ? dstpath : url) + + if (srcpath.includes('://')) srcpath = (new URL(srcpath)).pathname + if (dstpath.includes('://')) dstpath = (new URL(dstpath)).pathname + + pause() // dont count against timeout, there may be user prompts + await assertWritePermission(dst.drive, this.sender) + assertValidPath(dstpath) + assertUnprotectedFilePath(srcpath, this.sender) + assertUnprotectedFilePath(dstpath, this.sender) + resume() + + checkin('renaming file') + return src.checkoutFS.pda.rename(srcpath, dst.checkoutFS.session.drive, dstpath) + }) + )) + }, + + async updateMetadata (url, metadata, opts = {}) { + var urlp = parseDriveUrl(url) + var url = urlp.origin + var filepath = normalizeFilepath(urlp.pathname || '') + return auditLog.record(this.sender.getURL(), 'updateMetadata', {url, filepath, metadata}, undefined, () => ( + timer(to(opts), async (checkin, pause, resume) => { + checkin('looking up drive') + const {drive, checkoutFS, isHistoric} = await lookupDrive(this.sender, urlp.hostname, urlp.version) + if (isHistoric) throw new ArchiveNotWritableError('Cannot modify a historic version') + + pause() // dont count against timeout, there may be user prompts + await assertWritePermission(drive, this.sender) + assertValidPath(filepath) + resume() + + checkin('updating metadata') + return checkoutFS.pda.updateMetadata(filepath, metadata) + }) + )) + }, + + async deleteMetadata (url, keys, opts = {}) { + var urlp = parseDriveUrl(url) + var url = urlp.origin + var filepath = normalizeFilepath(urlp.pathname || '') + return auditLog.record(this.sender.getURL(), 'deleteMetadata', {url, filepath, keys}, undefined, () => ( + timer(to(opts), async (checkin, pause, resume) => { + checkin('looking up drive') + const {drive, checkoutFS, isHistoric} = await lookupDrive(this.sender, urlp.hostname, urlp.version) + if (isHistoric) throw new ArchiveNotWritableError('Cannot modify a historic version') + + pause() // dont count against timeout, there may be user prompts + await assertWritePermission(drive, this.sender) + assertValidPath(filepath) + resume() + + checkin('updating metadata') + return checkoutFS.pda.deleteMetadata(filepath, keys) + }) + )) + }, + + async readdir (url, opts = {}) { + var urlp = parseDriveUrl(url) + var url = urlp.origin + var filepath = normalizeFilepath(urlp.pathname || '') + return auditLog.record(this.sender.getURL(), 'readdir', {url, filepath, opts}, undefined, () => ( + timer(to(opts), async (checkin, pause, resume) => { + checkin('searching for drive') + const {checkoutFS} = await lookupDrive(this.sender, urlp.hostname, urlp.version) + + pause() // dont count against timeout, there may be user prompts + await assertReadPermission(checkoutFS, this.sender) + resume() + + checkin('reading directory') + var names = await checkoutFS.pda.readdir(filepath, opts) + if (opts.includeStats) { + names = names.map(obj => ({name: obj.name, stat: obj.stat})) + } + return names + }) + )) + }, + + async mkdir (url, opts) { + var urlp = parseDriveUrl(url) + var url = urlp.origin + var filepath = normalizeFilepath(urlp.pathname || '') + return auditLog.record(this.sender.getURL(), 'mkdir', {url, filepath, opts}, undefined, () => ( + timer(to(opts), async (checkin, pause, resume) => { + checkin('searching for drive') + const {drive, checkoutFS, isHistoric} = await lookupDrive(this.sender, urlp.hostname, urlp.version) + if (isHistoric) throw new ArchiveNotWritableError('Cannot modify a historic version') + + pause() // dont count against timeout, there may be user prompts + await assertWritePermission(drive, this.sender) + await assertValidPath(filepath) + assertUnprotectedFilePath(filepath, this.sender) + resume() + + checkin('making directory') + return checkoutFS.pda.mkdir(filepath, opts) + }) + )) + }, + + async rmdir (url, opts = {}) { + var urlp = parseDriveUrl(url) + var url = urlp.origin + var filepath = normalizeFilepath(urlp.pathname || '') + return auditLog.record(this.sender.getURL(), 'rmdir', {url, filepath, opts}, undefined, () => ( + timer(to(opts), async (checkin, pause, resume) => { + checkin('searching for drive') + const {drive, checkoutFS, isHistoric} = await lookupDrive(this.sender, urlp.hostname, urlp.version) + if (isHistoric) throw new ArchiveNotWritableError('Cannot modify a historic version') + + pause() // dont count against timeout, there may be user prompts + await assertWritePermission(drive, this.sender) + assertUnprotectedFilePath(filepath, this.sender) + resume() + + checkin('removing directory') + return checkoutFS.pda.rmdir(filepath, opts) + }) + )) + }, + + async symlink (url, linkname, opts) { + var urlp = parseDriveUrl(url) + var url = urlp.origin + var target = normalizeFilepath(urlp.pathname || '') + linkname = normalizeFilepath(linkname || '') + return auditLog.record(this.sender.getURL(), 'symlink', {url, target, linkname}, undefined, () => ( + timer(to(opts), async (checkin, pause, resume) => { + checkin('searching for drive') + const {drive, checkoutFS, isHistoric} = await lookupDrive(this.sender, urlp.hostname, urlp.version) + if (isHistoric) throw new ArchiveNotWritableError('Cannot modify a historic version') + + pause() // dont count against timeout, there may be user prompts + await assertWritePermission(drive, this.sender) + await assertValidPath(linkname) + assertUnprotectedFilePath(linkname, this.sender) + resume() + + checkin('symlinking') + return checkoutFS.pda.symlink(target, linkname) + }) + )) + }, + + async mount (url, mount, opts) { + var urlp = parseDriveUrl(url) + var url = urlp.origin + var filepath = normalizeFilepath(urlp.pathname || '') + if (mount.includes('.cap')) throw new Error('Unable to mount capability URLs') + return auditLog.record(this.sender.getURL(), 'mount', {url, filepath, opts}, undefined, () => ( + timer(to(opts), async (checkin, pause, resume) => { + checkin('searching for drive') + const {drive, checkoutFS, isHistoric} = await lookupDrive(this.sender, urlp.hostname, urlp.version) + if (isHistoric) throw new ArchiveNotWritableError('Cannot modify a historic version') + + pause() // dont count against timeout, there may be user prompts + await assertWritePermission(drive, this.sender) + await assertValidPath(filepath) + assertUnprotectedFilePath(filepath, this.sender) + resume() + + checkin('mounting drive') + return checkoutFS.pda.mount(filepath, mount) + }) + )) + }, + + async unmount (url, opts = {}) { + var urlp = parseDriveUrl(url) + var url = urlp.origin + var filepath = normalizeFilepath(urlp.pathname || '') + return auditLog.record(this.sender.getURL(), 'unmount', {url, filepath, opts}, undefined, () => ( + timer(to(opts), async (checkin, pause, resume) => { + checkin('searching for drive') + const {drive, checkoutFS, isHistoric} = await lookupDrive(this.sender, urlp.hostname, urlp.version) + if (isHistoric) throw new ArchiveNotWritableError('Cannot modify a historic version') + + pause() // dont count against timeout, there may be user prompts + await assertWritePermission(drive, this.sender) + assertUnprotectedFilePath(filepath, this.sender) + resume() + + checkin('unmounting drive') + return checkoutFS.pda.unmount(filepath) + }) + )) + }, + + async query (opts) { + if (!opts.drive) return [] + if (!Array.isArray(opts.drive)) opts.drive = [opts.drive] + return auditLog.record(this.sender.getURL(), 'query', opts, undefined, () => ( + timer(to(opts), async (checkin, pause, resume) => { + checkin('looking up drives') + var capUrls = {} + for (let i = 0; i < opts.drive.length; i++) { + let urlp = parseDriveUrl(opts.drive[i]) + opts.drive[i] = (await + auditLog.record('-query', 'lookupDrive', {url: opts.drive[i]}, undefined, () => ( + lookupDrive(this.sender, urlp.hostname, urlp.version) + ), {ignoreFast: true}) + ).checkoutFS + if (urlp.hostname.endsWith('.cap')) { + capUrls[opts.drive[i].key.toString('hex')] = urlp.hostname + } + } + pause() // dont count against timeout, there may be user prompts + for (let drive of opts.drive) { + await assertReadPermission(drive, this.sender) + } + resume() + checkin('running query') + var queriesResults = await Promise.all(opts.drive.map(drive => query(drive, opts))) + var results = _flattenDeep(queriesResults) + if (opts.drive.length > 1) { + // HACK resort and slice here because each query was run separately -prf + if (opts.sort === 'name') { + results.sort((a, b) => (opts.reverse) ? path.basename(b.path).toLowerCase().localeCompare(path.basename(a.path).toLowerCase()) : path.basename(a.path).toLowerCase().localeCompare(path.basename(b.path).toLowerCase())) + } else if (opts.sort === 'mtime') { + results.sort((a, b) => (opts.reverse) ? b.stat.mtime - a.stat.mtime : a.stat.mtime - b.stat.mtime) + } else if (opts.sort === 'ctime') { + results.sort((a, b) => (opts.reverse) ? b.stat.ctime - a.stat.ctime : a.stat.ctime - b.stat.ctime) + } + if (opts.offset && opts.limit) results = results.slice(opts.offset, opts.offset + opts.limit) + else if (opts.offset) results = results.slice(opts.offset) + else if (opts.limit) results = results.slice(0, opts.limit) + } + if (Object.keys(capUrls).length > 0) { + // mask capability URLs + for (let res of results) { + for (let key in capUrls) { + res.drive = res.drive.replace(key, capUrls[key]) + res.url = res.url.replace(key, capUrls[key]) + } + } + } + return results + }) + )) + }, + + async watch (url, pathPattern) { + var {drive} = await lookupDrive(this.sender, url) + await assertReadPermission(drive, this.sender) + return drive.pda.watch(pathPattern) + }, + + async createNetworkActivityStream (url) { + var {drive} = await lookupDrive(this.sender, url) + await assertReadPermission(drive, this.sender) + return drive.pda.createNetworkActivityStream() + }, + + async beakerDiff (srcUrl, dstUrl, opts) { + assertBeakerOnly(this.sender) + if (!srcUrl || typeof srcUrl !== 'string') { + throw new InvalidURLError('The first parameter of diff() must be a hyperdrive URL') + } + if (!dstUrl || typeof dstUrl !== 'string') { + throw new InvalidURLError('The second parameter of diff() must be a hyperdrive URL') + } + var [src, dst] = await Promise.all([lookupDrive(this.sender, srcUrl), lookupDrive(this.sender, dstUrl)]) + return pda.diff(src.checkoutFS.pda, src.filepath, dst.checkoutFS.pda, dst.filepath, opts) + }, + + async beakerMerge (srcUrl, dstUrl, opts) { + assertBeakerOnly(this.sender) + if (!srcUrl || typeof srcUrl !== 'string') { + throw new InvalidURLError('The first parameter of merge() must be a hyperdrive URL') + } + if (!dstUrl || typeof dstUrl !== 'string') { + throw new InvalidURLError('The second parameter of merge() must be a hyperdrive URL') + } + var [src, dst] = await Promise.all([lookupDrive(this.sender, srcUrl), lookupDrive(this.sender, dstUrl)]) + if (!dst.drive.writable) throw new ArchiveNotWritableError('The destination drive is not writable') + if (dst.isHistoric) throw new ArchiveNotWritableError('Cannot modify a historic version') + return pda.merge(src.checkoutFS.pda, src.filepath, dst.checkoutFS.pda, dst.filepath, opts) + }, + + async importFromFilesystem (opts) { + assertBeakerOnly(this.sender) + var {checkoutFS, filepath, isHistoric} = await lookupDrive(this.sender, opts.dst) + if (isHistoric) throw new ArchiveNotWritableError('Cannot modify a historic version') + return pda.exportFilesystemToArchive({ + srcPath: opts.src, + dstArchive: checkoutFS.session ? checkoutFS.session.drive : checkoutFS, + dstPath: filepath, + ignore: opts.ignore, + inplaceImport: opts.inplaceImport !== false, + dryRun: opts.dryRun + }) + }, + + async exportToFilesystem (opts) { + assertBeakerOnly(this.sender) + + // TODO do we need to replace this? -prf + // if (await checkFolderIsEmpty(opts.dst) === false) { + // return + // } + + var {checkoutFS, filepath} = await lookupDrive(this.sender, opts.src) + return pda.exportArchiveToFilesystem({ + srcArchive: checkoutFS.session ? checkoutFS.session.drive : checkoutFS, + srcPath: filepath, + dstPath: opts.dst, + ignore: opts.ignore, + overwriteExisting: opts.overwriteExisting, + skipUndownloadedFiles: opts.skipUndownloadedFiles + }) + }, + + async exportToDrive (opts) { + assertBeakerOnly(this.sender) + var src = await lookupDrive(this.sender, opts.src) + var dst = await lookupDrive(this.sender, opts.dst) + if (dst.isHistoric) throw new ArchiveNotWritableError('Cannot modify a historic version') + return pda.exportArchiveToArchive({ + srcArchive: src.checkoutFS.session ? src.checkoutFS.session.drive : src.checkoutFS, + srcPath: src.filepath, + dstArchive: dst.checkoutFS.session ? dst.checkoutFS.session.drive : dst.checkoutFS, + dstPath: dst.filepath, + ignore: opts.ignore, + skipUndownloadedFiles: opts.skipUndownloadedFiles + }) + } +} + +// internal helpers +// = + +// helper to check if filepath refers to a file that userland is not allowed to edit directly +function assertUnprotectedFilePath (filepath, sender) { + if (isSenderBeaker(sender)) { + return // can write any file + } + if (filepath === '/' + DRIVE_MANIFEST_FILENAME) { + throw new ProtectedFileNotWritableError() + } +} + +// temporary helper to make sure the call is made by a beaker: page +function assertBeakerOnly (sender) { + if (!isSenderBeaker(sender)) { + throw new PermissionsError() + } +} + +async function assertCreateDrivePermission (sender) { + // beaker: always allowed + if (isSenderBeaker(sender)) { + return true + } + + // ask the user + let allowed = await permissions.requestPermission('createDrive', sender) + if (!allowed) { + throw new UserDeniedError() + } +} + +async function assertReadPermission (drive, sender) { + var driveUrl + if (typeof drive === 'string') { + driveUrl = `hyper://${await drives.fromURLToKey(drive, true)}/` + } else { + driveUrl = drive.url + } + + if (filesystem.isRootUrl(driveUrl)) { + if (isSenderBeaker(sender)) { + return true + } + throw new PermissionsError('Cannot read the system drive') + } + + return true +} + +async function assertWritePermission (drive, sender) { + var newDriveKey = drive.key.toString('hex') + var details = await drives.getDriveInfo(newDriveKey) + const perm = ('modifyDrive:' + newDriveKey) + + // beaker: always allowed + if (isSenderBeaker(sender)) { + return true + } + + // self-modification ALWAYS allowed + var senderDatKey = await lookupUrlDriveKey(sender.getURL()) + if (senderDatKey === newDriveKey) { + return true + } + + // ensure the sender is allowed to write + var allowed = await permissions.queryPermission(perm, sender) + if (allowed) return true + + // ask the user + allowed = await permissions.requestPermission(perm, sender, { title: details.title }) + if (!allowed) throw new UserDeniedError() + return true +} + +async function assertDeleteDrivePermission (drive, sender) { + var driveKey = drive.key.toString('hex') + const perm = ('deleteDrive:' + driveKey) + + // beaker: always allowed + if (isSenderBeaker(sender)) { + return true + } + + // ask the user + var details = await drives.getDriveInfo(driveKey) + var allowed = await permissions.requestPermission(perm, sender, { title: details.title }) + if (!allowed) throw new UserDeniedError() + return true +} + +async function assertQuotaPermission (drive, senderOrigin, byteLength) { + // beaker: always allowed + if (senderOrigin.startsWith('beaker:')) { + return + } + + // fetch the drive meta + const meta = await archivesDb.getMeta(drive.key) + + // fallback to default quota + var bytesAllowed = /* TODO userSettings.bytesAllowed ||*/ DAT_QUOTA_DEFAULT_BYTES_ALLOWED + + // check the new size + var newSize = (meta.size + byteLength) + if (newSize > bytesAllowed) { + throw new QuotaExceededError() + } +} + +function assertValidFilePath (filepath) { + if (filepath.slice(-1) === '/') { + throw new InvalidPathError('Files can not have a trailing slash') + } + assertValidPath(filepath) +} + +function assertValidPath (fileOrFolderPath) { + if (!DRIVE_VALID_PATH_REGEX.test(fileOrFolderPath)) { + throw new InvalidPathError('Path contains invalid characters') + } +} + +function normalizeFilepath (str) { + str = decodeURIComponent(str) + if (!str.includes('://') && str.charAt(0) !== '/') { + str = '/' + str + } + return str +} + +// helper to handle the URL argument that's given to most args +// - can get a hyperdrive hash, or hyperdrive url +// - sets checkoutFS to what's requested by version +export async function lookupDrive (sender, driveHostname, version, dontGetDrive = false) { + var driveKey + + if (driveHostname.endsWith('.cap')) { + let cap = capabilities.lookupCap(driveHostname) + if (cap) { + driveKey = cap.target.key + version = cap.target.version + } else { + throw new Error('Capability does not exist') + } + } + + if (!driveKey) { + driveKey = await drives.fromURLToKey(driveHostname, true) + } + + if (dontGetDrive) { + return {driveKey, version} + } + + var drive = drives.getDrive(driveKey) + if (!drive) drive = await drives.loadDrive(driveKey) + var {checkoutFS, isHistoric} = await drives.getDriveCheckout(drive, version) + return {drive, version, isHistoric, checkoutFS} +} + +async function lookupUrlDriveKey (url) { + if (HYPERDRIVE_HASH_REGEX.test(url)) return url + if (!url.startsWith('hyper://')) { + return false // not a drive site + } + + var urlp = parseDriveUrl(url) + try { + return await hyperDns.resolveName(urlp.hostname) + } catch (e) { + return false + } +} diff --git a/app/bg/web-apis/bg/markdown.js b/app/bg/web-apis/bg/markdown.js new file mode 100644 index 0000000000..8ea6fa9dff --- /dev/null +++ b/app/bg/web-apis/bg/markdown.js @@ -0,0 +1,15 @@ +import markdown from '../../../lib/markdown' + +const md = markdown({ + allowHTML: false, + useHeadingIds: false, + useHeadingAnchors: false, + hrefMassager: undefined, + highlight: undefined +}) + +export default { + toHTML (str) { + return md.render(str) + } +} \ No newline at end of file diff --git a/app/bg/web-apis/bg/peersockets.js b/app/bg/web-apis/bg/peersockets.js new file mode 100644 index 0000000000..3ca5b1fc37 --- /dev/null +++ b/app/bg/web-apis/bg/peersockets.js @@ -0,0 +1,64 @@ +import { Duplex, Readable } from 'streamx' +import { getClient } from '../../hyper/daemon' +import * as drives from '../../hyper/drives' +import { PermissionsError } from 'beaker-error-constants' + +// exported api +// = + +export default { + async join (topic) { + var drive = await getSenderDrive(this.sender) + topic = massageTopic(topic, drive.discoveryKey) + + var stream = new Duplex({ + write (data, cb) { + if (!Array.isArray(data) || typeof data[0] === 'undefined' || typeof data[1] === 'undefined') { + console.debug('Incorrectly formed message from peersockets send API', data) + return cb(null) + } + topicHandle.send(data[0], data[1]) + cb(null) + } + }) + stream.objectMode = true + var topicHandle = getClient().peersockets.join(topic, { + onmessage (peerId, message) { + stream.push(['message', {peerId, message}]) + } + }) + drive.pda.numActiveStreams++ + stream.on('close', () => { + drive.pda.numActiveStreams-- + topicHandle.close() + }) + + return stream + }, + + async watch () { + var drive = await getSenderDrive(this.sender) + var stream = new Readable() + var stopwatch = getClient().peers.watchPeers(drive.discoveryKey, { + onjoin: async (peerId) => stream.push(['join', {peerId}]), + onleave: (peerId) => stream.push(['leave', {peerId}]) + }) + stream.on('close', () => stopwatch()) + return stream + } +} + +// internal methods +// = + +async function getSenderDrive (sender) { + var url = sender.getURL() + if (!url.startsWith('hyper://')) { + throw new PermissionsError('PeerSockets are only available on hyper:// origins') + } + return drives.getOrLoadDrive(url) +} + +function massageTopic (topic, discoveryKey) { + return `webapp/${discoveryKey.toString('hex')}/${topic}` +} \ No newline at end of file diff --git a/app/bg/web-apis/bg/shell.js b/app/bg/web-apis/bg/shell.js new file mode 100644 index 0000000000..717a403aa4 --- /dev/null +++ b/app/bg/web-apis/bg/shell.js @@ -0,0 +1,261 @@ +import { BrowserView, dialog } from 'electron' +import os from 'os' +import pda from 'pauls-dat-api2' +import * as tabManager from '../../ui/tab-manager' +import * as modals from '../../ui/subwindows/modals' +import * as filesystem from '../../filesystem/index' +import * as drives from '../../hyper/drives' +import { lookupDrive } from './hyperdrive' +import { parseDriveUrl } from '../../../lib/urls' +import { joinPath } from '../../../lib/strings' +import assert from 'assert' +import { UserDeniedError, ArchiveNotWritableError } from 'beaker-error-constants' +import _pick from 'lodash.pick' + +// typedefs +// = + +// exported api +// = + +export default { + /** + * @param {string} url + * @returns {Promise} + */ + async drivePropertiesDialog (url) { + assert(url && typeof url === 'string', '`url` must be a string') + var drive = await drives.getOrLoadDrive(url) + var info = await drives.getDriveInfo(url) + await modals.create(this.sender, 'drive-properties', { + url: info.url, + writable: info.writable, + props: _pick(info, ['title', 'description']) + }) + }, + + /** + * @param {Object} [opts] + * @param {string} [opts.title] + * @param {string} [opts.buttonLabel] + * @param {string} [opts.drive] + * @param {string} [opts.defaultPath] + * @param {string[]} [opts.select] + * @param {Object} [opts.filters] + * @param {string[]} [opts.filters.extensions] + * @param {boolean} [opts.filters.writable] + * @param {boolean} [opts.filters.networked] + * @param {boolean} [opts.allowMultiple] + * @param {boolean} [opts.disallowCreate] + * @returns {Promise} + */ + async selectFileDialog (opts = {}) { + // validate + assert(opts && typeof opts === 'object', 'Must pass an options object') + assert(!opts.title || typeof opts.title === 'string', '.title must be a string') + assert(!opts.buttonLabel || typeof opts.buttonLabel === 'string', '.buttonLabel must be a string') + assert(!opts.drive || typeof opts.drive === 'string', '.drive must be a string') + assert(!opts.defaultPath || typeof opts.defaultPath === 'string', '.defaultPath must be a string') + assert(!opts.select || isStrArray(opts.select), '.select must be an array of strings') + if (opts.filters) { + assert(typeof opts.filters === 'object', '.filters must be an object') + assert(!opts.filters.extensions || isStrArray(opts.filters.extensions), '.filters.extensions must be an array of strings') + assert(!opts.filters.writable || typeof opts.filters.writable === 'boolean', '.filters.writable must be a boolean') + assert(!opts.filters.networked || typeof opts.filters.networked === 'boolean', '.filters.networked must be a boolean') + } + assert(!opts.allowMultiple || typeof opts.allowMultiple === 'boolean', '.filters.allowMultiple must be a boolean') + assert(!opts.disallowCreate || typeof opts.disallowCreate === 'boolean', '.filters.disallowCreate must be a boolean') + + // initiate the modal + var res + try { + res = await modals.create(this.sender, 'select-file', opts) + } catch (e) { + if (e.name !== 'Error') { + throw e // only rethrow if a specific error + } + } + if (!res) throw new UserDeniedError() + return res + }, + + /** + * @param {Object} [opts] + * @param {string} [opts.title] + * @param {string} [opts.buttonLabel] + * @param {string} [opts.drive] + * @param {string} [opts.defaultPath] + * @param {string} [opts.defaultFilename] + * @param {string} [opts.extension] + * @param {Object} [opts.filters] + * @param {string[]} [opts.filters.extensions] + * @param {boolean} [opts.filters.networked] + * @returns {Promise} + */ + async saveFileDialog (opts = {}) { + // validate + assert(opts && typeof opts === 'object', 'Must pass an options object') + assert(!opts.title || typeof opts.title === 'string', '.title must be a string') + assert(!opts.buttonLabel || typeof opts.buttonLabel === 'string', '.buttonLabel must be a string') + assert(!opts.drive || typeof opts.drive === 'string', '.drive must be a string') + assert(!opts.defaultPath || typeof opts.defaultPath === 'string', '.defaultPath must be a string') + assert(!opts.defaultFilename || typeof opts.defaultFilename === 'string', '.defaultFilename must be a string') + if (opts.filters) { + assert(typeof opts.filters === 'object', '.filters must be an object') + assert(!opts.filters.extensions || isStrArray(opts.filters.extensions), '.filters.extensions must be an array of strings') + assert(!opts.filters.networked || typeof opts.filters.networked === 'boolean', '.filters.networked must be a boolean') + } + + // initiate the modal + opts.saveMode = true + var res + try { + res = await modals.create(this.sender, 'select-file', opts) + } catch (e) { + if (e.name !== 'Error') { + throw e // only rethrow if a specific error + } + } + if (!res) throw new UserDeniedError() + return res + }, + + /** + * @param {Object} [opts] + * @param {string} [opts.title] + * @param {string} [opts.buttonLabel] + * @param {boolean} [opts.writable] + * @param {string} [opts.type] + * @returns {Promise} + */ + async selectDriveDialog (opts = {}) { + // validate + assert(opts && typeof opts === 'object', 'Must pass an options object') + assert(!opts.title || typeof opts.title === 'string', '.title must be a string') + assert(!opts.buttonLabel || typeof opts.buttonLabel === 'string', '.buttonLabel must be a string') + assert(!opts.type || typeof opts.type === 'string', '.type must be a string') + assert(!opts.writable || typeof opts.writable === 'boolean', '.writable must be a boolean') + + // initiate the modal + var res + try { + res = await modals.create(this.sender, 'select-drive', opts) + if (res && res.gotoCreate) { + res = await modals.create(this.sender, 'create-drive', opts) + } + } catch (e) { + if (e.name !== 'Error') { + throw e // only rethrow if a specific error + } + } + if (!res || !res.url) throw new UserDeniedError() + return res.url + }, + + /** + * Can only be used by beaker:// sites + * + * @returns {Promise} + */ + async executeSidebarCommand (...args) { + var tab = tabManager.findTab(BrowserView.fromWebContents(this.sender)) + if (!tab) return + + var isAllowed = isBeakerApp(this.sender) + if (isAllowed) { + return tab.executeSidebarCommand(...args) + } + }, + + async importFilesDialog (url) { + if (!(await isBeakerApp(this.sender))) return + + var res = await dialog.showOpenDialog({ + title: 'Import files', + buttonLabel: 'Import', + properties: ['openFile', 'multiSelections', 'createDirectory'] + }) + if (res.filePaths.length) { + var urlp = parseDriveUrl(url) + var {checkoutFS, isHistoric} = await lookupDrive(this.sender, urlp.hostname, urlp.version) + if (isHistoric) throw new ArchiveNotWritableError('Cannot modify a historic version') + for (let srcPath of res.filePaths) { + await pda.exportFilesystemToArchive({ + srcPath, + dstArchive: checkoutFS.session ? checkoutFS.session.drive : checkoutFS, + dstPath: urlp.pathname, + ignore: ['index.json'], + inplaceImport: false, + dryRun: false + }) + } + return {numImported: res.filePaths.length} + } + return {numImported: 0} + }, + + async importFoldersDialog (url) { + if (!(await isBeakerApp(this.sender))) return + + var res = await dialog.showOpenDialog({ + title: 'Import folders', + buttonLabel: 'Import', + properties: ['openDirectory', 'multiSelections', 'createDirectory'] + }) + if (res.filePaths.length) { + var urlp = parseDriveUrl(url) + var {checkoutFS, isHistoric} = await lookupDrive(this.sender, urlp.hostname, urlp.version) + if (isHistoric) throw new ArchiveNotWritableError('Cannot modify a historic version') + for (let srcPath of res.filePaths) { + await pda.exportFilesystemToArchive({ + srcPath, + dstArchive: checkoutFS.session ? checkoutFS.session.drive : checkoutFS, + dstPath: urlp.pathname, + inplaceImport: false, + dryRun: false + }) + } + return {numImported: res.filePaths.length} + } + return {numImported: 0} + }, + + async exportFilesDialog (urls) { + if (!(await isBeakerApp(this.sender))) return + + var res = await dialog.showOpenDialog({ + title: 'Export files', + buttonLabel: 'Export', + properties: ['openDirectory', 'createDirectory'] + }) + if (res.filePaths.length) { + var baseDstPath = res.filePaths[0] + urls = Array.isArray(urls) ? urls : [urls] + for (let srcUrl of urls) { + var urlp = parseDriveUrl(srcUrl) + let {checkoutFS} = await lookupDrive(this.sender, urlp.hostname, urlp.version) + let dstPath = joinPath(baseDstPath, urlp.pathname.split('/').pop()) + await pda.exportArchiveToFilesystem({ + srcArchive: checkoutFS.session ? checkoutFS.session.drive : checkoutFS, + srcPath: urlp.pathname, + dstPath, + overwriteExisting: false, + skipUndownloadedFiles: false + }) + } + return {numExported: res.filePaths.length} + } + return {numExported: 0} + } +} + +async function isBeakerApp (sender) { + if (/^(beaker:|https?:\/\/(.*\.)?hyperdrive\.network(:|\/))/.test(sender.getURL())) { + return true + } + return false +} + +function isStrArray (v) { + return (Array.isArray(v) && v.every(el => typeof el === 'string')) +} diff --git a/app/bg/web-apis/bg/watchlist.js b/app/bg/web-apis/bg/watchlist.js new file mode 100644 index 0000000000..e18ff38755 --- /dev/null +++ b/app/bg/web-apis/bg/watchlist.js @@ -0,0 +1,29 @@ +import * as hyperWatchlist from '../../hyper/watchlist' + +// exported api +// = + +export default { + async add (url, opts) { + return hyperWatchlist.addSite(0, url, opts) + }, + + async list () { + return hyperWatchlist.getSites(0) + }, + + async update (site) { + return hyperWatchlist.updateWatchlist(0, site) + }, + + async remove (url) { + return hyperWatchlist.removeSite(0, url) + }, + + // events + // = + + createEventsStream () { + return hyperWatchlist.createEventsStream() + } +} diff --git a/app/bg/web-apis/fg.js b/app/bg/web-apis/fg.js new file mode 100644 index 0000000000..66dfb3003b --- /dev/null +++ b/app/bg/web-apis/fg.js @@ -0,0 +1,33 @@ +import * as rpc from 'pauls-electron-rpc' +import * as hyperdrive from './fg/hyperdrive' +import * as internal from './fg/internal' +import * as external from './fg/external' +import * as experimental from './fg/experimental' +import { ipcRenderer, contextBridge } from 'electron' + +export const setup = function () { + // setup APIs + var beaker = {} + if (['beaker:', 'hyper:', 'https:', 'http:', 'data:'].includes(window.location.protocol) || + window.location.hostname.endsWith('hyperdrive.network') /* TEMPRARY */) { + beaker.hyperdrive = hyperdrive.setup(rpc) + Object.assign(beaker, external.setup(rpc)) + } + if (['beaker:', 'hyper:'].includes(window.location.protocol)) { + contextBridge.exposeInMainWorld('experimental', experimental.setup(rpc)) // TODO remove? + // TEMPORARY + contextBridge.exposeInMainWorld('__internalBeakerEditor', { + open: () => ipcRenderer.send('temp-open-editor-sidebar') + }) + } + if (window.location.protocol === 'beaker:' || /* TEMPRARY */ window.location.hostname.endsWith('hyperdrive.network')) { + Object.assign(beaker, internal.setup(rpc)) + } + contextBridge.exposeInMainWorld('beaker', beaker) + if (window.location.protocol === 'dat:') { + // TEMPORARY + contextBridge.exposeInMainWorld('__internalBeakerDatArchive', { + convert: key => ipcRenderer.send('temp-convert-dat', key) + }) + } +} \ No newline at end of file diff --git a/app/bg/web-apis/fg/event-target.js b/app/bg/web-apis/fg/event-target.js new file mode 100644 index 0000000000..a70570543c --- /dev/null +++ b/app/bg/web-apis/fg/event-target.js @@ -0,0 +1,131 @@ +// this emulates the implementation of event-targets by browsers + +const LISTENERS = Symbol() // eslint-disable-line +const CREATE_STREAM = Symbol() // eslint-disable-line +const STREAM_EVENTS = Symbol() // eslint-disable-line +const STREAM = Symbol() // eslint-disable-line +const PREP_EVENT = Symbol() // eslint-disable-line + +export class EventTarget { + constructor () { + this[LISTENERS] = {} + this.addEventListener = this.addEventListener.bind(this) + this.removeEventListener = this.removeEventListener.bind(this) + this.dispatchEvent = this.dispatchEvent.bind(this) + } + + addEventListener (type, callback) { + if (!(type in this[LISTENERS])) { + this[LISTENERS][type] = [] + } + this[LISTENERS][type].push(callback) + } + + removeEventListener (type, callback) { + if (!(type in this[LISTENERS])) { + return + } + var stack = this[LISTENERS][type] + var i = stack.findIndex(cb => cb === callback) + if (i !== -1) { + stack.splice(i, 1) + } + } + + dispatchEvent (event) { + if (!(event.type in this[LISTENERS])) { + return + } + event.target = this + var stack = this[LISTENERS][event.type] + stack.forEach(cb => cb.call(this, event)) + } +} + +export class EventTargetFromStream extends EventTarget { + constructor (createStreamFn, events, eventPrepFn) { + super() + this[CREATE_STREAM] = createStreamFn + this[STREAM_EVENTS] = events + this[PREP_EVENT] = eventPrepFn + this[STREAM] = null + } + + addEventListener (type, callback) { + if (!this[STREAM]) { + // create the event stream + let s = this[STREAM] = fromEventStream(this[CREATE_STREAM]()) + // proxy all events + this[STREAM_EVENTS].forEach(event => { + s.addEventListener(event, details => { + details = details || {} + if (this[PREP_EVENT]) { + details = this[PREP_EVENT](event, details) + } + details.target = this + this.dispatchEvent(new Event(event, details)) + }) + }) + } + return super.addEventListener(type, callback) + } +} + +export class Event { + constructor (type, opts) { + this.type = type + for (var k in opts) { + this[k] = opts[k] + } + Object.defineProperty(this, 'bubbles', {value: false}) + Object.defineProperty(this, 'cancelBubble', {value: false}) + Object.defineProperty(this, 'cancelable', {value: false}) + Object.defineProperty(this, 'composed', {value: false}) + Object.defineProperty(this, 'currentTarget', {value: this.target}) + Object.defineProperty(this, 'deepPath', {value: []}) + Object.defineProperty(this, 'defaultPrevented', {value: false}) + Object.defineProperty(this, 'eventPhase', {value: 2}) // Event.AT_TARGET + Object.defineProperty(this, 'timeStamp', {value: Date.now()}) + Object.defineProperty(this, 'isTrusted', {value: true}) + Object.defineProperty(this, 'createEvent', {value: () => undefined}) + Object.defineProperty(this, 'composedPath', {value: () => []}) + Object.defineProperty(this, 'initEvent', {value: () => undefined}) + Object.defineProperty(this, 'preventDefault', {value: () => undefined}) + Object.defineProperty(this, 'stopImmediatePropagation', {value: () => undefined}) + Object.defineProperty(this, 'stopPropagation', {value: () => undefined}) + } +} + +export function bindEventStream (stream, target) { + stream.on('data', data => { + var event = data[1] || {} + event.type = data[0] + target.dispatchEvent(event) + }) +} + +export function fromEventStream (stream) { + var target = new EventTarget() + bindEventStream(stream, target) + target.close = () => { + target.listeners = {} + stream.close() + } + return target +} + +export function fromAsyncEventStream (asyncStream) { + var target = new EventTarget() + asyncStream.then( + stream => bindEventStream(stream, target), + err => { + target.dispatchEvent({type: 'error', details: err}) + target.close() + } + ) + target.close = () => { + target.listeners = {} + asyncStream.then(stream => stream.close()) + } + return target +}; diff --git a/app/bg/web-apis/fg/experimental.js b/app/bg/web-apis/fg/experimental.js new file mode 100644 index 0000000000..f43f40e7b0 --- /dev/null +++ b/app/bg/web-apis/fg/experimental.js @@ -0,0 +1,79 @@ +/* globals Request Response fetch */ + +import { EventTargetFromStream } from './event-target' + +import errors from 'beaker-error-constants' +import experimentalGlobalFetchManifest from '../manifests/external/experimental/global-fetch' +import experimentalCapturePageManifest from '../manifests/external/experimental/capture-page' +import experimentalDatPeersManifest from '../manifests/external/experimental/dat-peers' + +export const setup = function (rpc) { + const experimental = {} + const opts = {timeout: false, errors} + + // hyperdrive or internal only + if (['beaker:', 'hyper:'].includes(window.location.protocol)) { + const globalFetchRPC = rpc.importAPI('experimental-global-fetch', experimentalGlobalFetchManifest, opts) + const capturePageRPC = rpc.importAPI('experimental-capture-page', experimentalCapturePageManifest, opts) + const datPeersRPC = rpc.importAPI('experimental-dat-peers', experimentalDatPeersManifest, opts) + + // experimental.globalFetch + experimental.globalFetch = async function globalFetch (input, init) { + var request = new Request(input, init) + if (request.method !== 'HEAD' && request.method !== 'GET') { + throw new Error('Only HEAD and GET requests are currently supported by globalFetch()') + } + try { + var responseData = await globalFetchRPC.fetch({ + method: request.method, + url: request.url, + headers: request.headers + }) + return new Response(responseData.body, responseData) + } catch (e) { + if (e.message === 'Can only send requests to http or https URLs' && request.url.startsWith('hyper://')) { + // we can just use `fetch` for hyper:// URLs, because hyper:// does not enforce CORS + return fetch(input, init) + } + throw e + } + } + + // experimental.capturePage + experimental.capturePage = capturePageRPC.capturePage + + // experimental.datPeers + class DatPeer { + constructor (id, sessionData) { + this.id = id + this.sessionData = sessionData + } + send (data) { + datPeersRPC.send(this.id, data) + } + } + function prepDatPeersEvents (event, details) { + var peer = new DatPeer(details.peerId, details.sessionData) + delete details.peerId + delete details.sessionData + details.peer = peer + return details + } + const datPeersEvents = ['connect', 'message', 'session-data', 'disconnect'] + experimental.datPeers = new EventTargetFromStream(datPeersRPC.createEventStream.bind(datPeersRPC), datPeersEvents, prepDatPeersEvents) + experimental.datPeers.list = async () => { + var peers = await datPeersRPC.list() + return peers.map(p => new DatPeer(p.id, p.sessionData)) + } + experimental.datPeers.get = async (peerId) => { + var {sessionData} = await datPeersRPC.get(peerId) + return new DatPeer(peerId, sessionData) + } + experimental.datPeers.broadcast = datPeersRPC.broadcast + experimental.datPeers.getSessionData = datPeersRPC.getSessionData + experimental.datPeers.setSessionData = datPeersRPC.setSessionData + experimental.datPeers.getOwnPeerId = datPeersRPC.getOwnPeerId + } + + return experimental +} diff --git a/app/bg/web-apis/fg/external.js b/app/bg/web-apis/fg/external.js new file mode 100644 index 0000000000..9023f2a2e4 --- /dev/null +++ b/app/bg/web-apis/fg/external.js @@ -0,0 +1,60 @@ +import { fromEventStream } from './event-target' +import errors from 'beaker-error-constants' +import capabilitiesManifest from '../manifests/external/capabilities' +import contactsManifest from '../manifests/external/contacts' +import markdownManifest from '../manifests/external/markdown' +import peersocketsManifest from '../manifests/external/peersockets' +import shellManifest from '../manifests/external/shell' + +const RPC_OPTS = { timeout: false, errors } + +export const setup = function (rpc) { + var capabilities = rpc.importAPI('capabilities', capabilitiesManifest, RPC_OPTS) + var contacts = rpc.importAPI('contacts', contactsManifest, RPC_OPTS) + var markdown = rpc.importAPI('markdown', markdownManifest, RPC_OPTS) + var shell = rpc.importAPI('shell', shellManifest, RPC_OPTS) + + var peersocketsRPC = rpc.importAPI('peersockets', peersocketsManifest, RPC_OPTS) + var peersockets = { + join (topic) { + var stream = peersocketsRPC.join(topic) + var obj = fromEventStream(stream) + obj.send = (peerId, msg) => { + stream.write([peerId, msg]) + } + return obj + }, + watch () { + return fromEventStream(peersocketsRPC.watch()) + } + } + + var _terminalCommands = [] + var terminal = { + getCommands () { + return (_terminalCommands || []).slice().map(obj => Object.assign({}, obj)) + }, + registerCommand (command) { + if (!command || typeof command !== 'object') throw new Error('Command must be an object') + if (!command.handle || typeof command.handle !== 'function') throw new Error('Command must have a \`handle\` function') + if (!command.name || typeof command.name !== 'string') throw new Error('Command must have a \`name\` string') + if (command.help && typeof command.help !== 'string') throw new Error('The \`help\` attribute on a command must be a string') + if (command.usage && typeof command.usage !== 'string') throw new Error('The \`usage\` attribute on a command must be a string') + + let i = _terminalCommands.findIndex(c => c.name === command.name) + if (i !== -1) throw new Error('A "' + command.name + '" command has already been registered') + _terminalCommands.push({ + handle: command.handle, + name: command.name, + help: command.help, + usage: command.usage + }) + }, + unregisterCommand (name) { + let i = _terminalCommands.findIndex(c => c.name === name) + if (i !== -1) _terminalCommands.splice(i, 1) + } + } + + return {capabilities, contacts, markdown, peersockets, shell, terminal} +} diff --git a/app/bg/web-apis/fg/hyperdrive.js b/app/bg/web-apis/fg/hyperdrive.js new file mode 100644 index 0000000000..d20d5fe3a8 --- /dev/null +++ b/app/bg/web-apis/fg/hyperdrive.js @@ -0,0 +1,356 @@ +import errors from 'beaker-error-constants' +import { parseDriveUrl } from '../../../lib/urls' +import hyperdriveManifest from '../manifests/external/hyperdrive' +import filesystemManifest from '../manifests/internal/beaker-filesystem' +import { EventTarget, Event, fromEventStream } from './event-target' +import { createStat } from './stat' + +const isDriveUrlRe = /^(hyper:\/\/)?[^\/]+/i + +export function setup (rpc) { + // create the rpc apis + const hyperdriveRPC = rpc.importAPI('hyperdrive', hyperdriveManifest, { timeout: false, errors }) + + function massageUrl (url) { + if (!url) url = '/' + if (typeof url !== 'string') { + if (typeof url.url === 'string') { + // passed in another drive instance + url = url.url + } else if (typeof url.href === 'string') { + // passed in window.location + url = url.href + } else { + throw new Error('Invalid hyper:// URL') + } + } + if (location.protocol === 'hyper:') { + if (!isDriveUrlRe.test(url)) { + url = joinPath('hyper://' + location.hostname, url) + } + } else if (!url.startsWith('hyper://')) { + // didnt include the scheme + url = 'hyper://' + url + } + if (!isDriveUrlRe.test(url)) { + // whoops not a valid hyper:// url + throw new Error('Invalid URL: must be a hyper:// URL') + } + return url + } + + function joinPath (a = '', b = '') { + ;[a, b] = [String(a), String(b)] + var [aSlash, bSlash] = [a.endsWith('/'), b.startsWith('/')] + if (!aSlash && !bSlash) return a + '/' + b + if (aSlash && bSlash) return a + b.slice(1) + return a + b + } + + function isNotUrlish (v) { + if (!v) return true + if (typeof v === 'string') return false + if (typeof v === 'object') { + if (typeof v.url === 'string') return false + if (typeof v.href === 'string') return false + } + return true + } + + function createScopedAPI (url) { + url = massageUrl(url) + const urlParsed = parseDriveUrl(url) + url = 'hyper://' + urlParsed.hostname + (urlParsed.version ? `+${urlParsed.version}` : '') + '/' + + // instruct backend to load + hyperdriveRPC.loadDrive(url) + + return { + get url () { return url }, + get version () { return urlParsed.version }, + + async getInfo (opts = {}) { + return hyperdriveRPC.getInfo(url, opts) + }, + + async configure (info, opts = {}) { + return hyperdriveRPC.configure(url, info, opts) + }, + + checkout (version) { + version = version ? `+${version}` : '' + return createScopedAPI(`hyper://${urlParsed.hostname}${version}/`) + }, + + async diff (prefix, other, opts = {}) { + other = other && typeof other === 'object' && other.version ? other.version : other + var res = await hyperdriveRPC.diff(joinPath(url, prefix), other, prefix, opts) + for (let change of res) { + if (change.value.stat) { + change.value.stat = createStat(change.value.stat) + } + } + return res + }, + + async stat (path, opts = {}) { + return createStat(await hyperdriveRPC.stat(joinPath(url, path), opts)) + }, + + async readFile (path, opts = {}) { + return hyperdriveRPC.readFile(joinPath(url, path), opts) + }, + + async writeFile (path, data, opts = {}) { + return hyperdriveRPC.writeFile(joinPath(url, path), data, opts) + }, + + async unlink (path, opts = {}) { + return hyperdriveRPC.unlink(joinPath(url, path), opts) + }, + + async copy (path, dstPath, opts = {}) { + return hyperdriveRPC.copy(joinPath(url, path), dstPath, opts) + }, + + async rename (path, dstPath, opts = {}) { + return hyperdriveRPC.rename(joinPath(url, path), dstPath, opts) + }, + + async updateMetadata (path, metadata, opts = {}) { + return hyperdriveRPC.updateMetadata(joinPath(url, path), metadata, opts) + }, + + async deleteMetadata (path, keys, opts = {}) { + return hyperdriveRPC.deleteMetadata(joinPath(url, path), keys, opts) + }, + + async readdir (path = '/', opts = {}) { + var names = await hyperdriveRPC.readdir(joinPath(url, path), opts) + if (opts.includeStats) { + names.forEach(name => { name.stat = createStat(name.stat) }) + } + return names + }, + + async mkdir (path, opts = {}) { + return hyperdriveRPC.mkdir(joinPath(url, path), opts) + }, + + async rmdir (path, opts = {}) { + return hyperdriveRPC.rmdir(joinPath(url, path), opts) + }, + + async symlink (path, linkname, opts = {}) { + return hyperdriveRPC.symlink(joinPath(url, path), linkname, opts) + }, + + async mount (path, opts = {}) { + if (opts.url) opts = opts.url + return hyperdriveRPC.mount(joinPath(url, path), opts) + }, + + async unmount (path, opts = {}) { + return hyperdriveRPC.unmount(joinPath(url, path), opts) + }, + + async query (opts) { + if (typeof opts === 'string') { + opts = {path: [opts]} + } + opts.drive = [url] + var res = await hyperdriveRPC.query(opts) + res.forEach(item => { + if (item.stat) item.stat = createStat(item.stat) + }) + return res + }, + + watch (pathSpec = null, onChanged = null) { + // usage: (onChanged) + if (typeof pathSpec === 'function') { + onChanged = pathSpec + pathSpec = null + } + var evts = fromEventStream(hyperdriveRPC.watch(url, pathSpec)) + if (onChanged) { + evts.addEventListener('changed', onChanged) + } + return evts + } + } + } + + var api = { + drive (url) { + return createScopedAPI(url) + }, + + createDrive (opts = {}) { + return hyperdriveRPC.createDrive(opts) + .then(newUrl => createScopedAPI(newUrl)) + }, + + forkDrive (url, opts = {}) { + url = massageUrl(url) + const urlParsed = parseDriveUrl(url) + return hyperdriveRPC.forkDrive(urlParsed.hostname, opts) + .then(newUrl => createScopedAPI(newUrl)) + }, + + async getInfo (url, opts) { + if (isNotUrlish(url) && !opts) { + opts = url + url = '' + } + url = massageUrl(url) + return hyperdriveRPC.getInfo(url, opts) + }, + + async configure (url, info, opts) { + if (isNotUrlish(url) && !opts && !info) { + info = url + url = '' + } + url = massageUrl(url) + return hyperdriveRPC.configure(url, info, opts) + }, + + checkout (url, version) { + if (isNotUrlish(url) && !version) { + version = url + url = '' + } + url = massageUrl(url) + const urlParsed = parseDriveUrl(url) + version = version ? `+${version}` : '' + return createScopedAPI(`hyper://${urlParsed.hostname}${version}/`) + }, + + async diff (url, other, opts = {}) { + url = massageUrl(url) + other = other && typeof other === 'object' && other.version ? other.version : other + var res = await hyperdriveRPC.diff(url, other, opts) + for (let change of res) { + if (change.value.stat) { + change.value.stat = createStat(change.value.stat) + } + } + return res + }, + + async stat (url, opts = {}) { + url = massageUrl(url) + return createStat(await hyperdriveRPC.stat(url, opts)) + }, + + async readFile (url, opts = {}) { + url = massageUrl(url) + return hyperdriveRPC.readFile(url, opts) + }, + + async writeFile (url, data, opts = {}) { + url = massageUrl(url) + return hyperdriveRPC.writeFile(url, data, opts) + }, + + async unlink (url, opts = {}) { + url = massageUrl(url) + return hyperdriveRPC.unlink(url, opts) + }, + + async copy (url, dstPath, opts = {}) { + url = massageUrl(url) + return hyperdriveRPC.copy(url, dstPath, opts) + }, + + async rename (url, dstPath, opts = {}) { + url = massageUrl(url) + return hyperdriveRPC.rename(url, dstPath, opts) + }, + + async updateMetadata (url, metadata, opts = {}) { + url = massageUrl(url) + return hyperdriveRPC.updateMetadata(url, metadata, opts) + }, + + async deleteMetadata (url, keys, opts = {}) { + url = massageUrl(url) + return hyperdriveRPC.deleteMetadata(url, keys, opts) + }, + + async readdir (url, opts = {}) { + url = massageUrl(url) + var names = await hyperdriveRPC.readdir(url, opts) + if (opts.includeStats) { + names.forEach(name => { name.stat = createStat(name.stat) }) + } + return names + }, + + async mkdir (url, opts = {}) { + url = massageUrl(url) + return hyperdriveRPC.mkdir(url, opts) + }, + + async rmdir (url, opts = {}) { + url = massageUrl(url) + return hyperdriveRPC.rmdir(url, opts) + }, + + async symlink (url, linkname, opts = {}) { + url = massageUrl(url) + return hyperdriveRPC.symlink(url, linkname, opts) + }, + + async mount (url, opts = {}) { + url = massageUrl(url) + if (opts.url) opts = opts.url + return hyperdriveRPC.mount(url, opts) + }, + + async unmount (url, opts = {}) { + url = massageUrl(url) + return hyperdriveRPC.unmount(url, opts) + }, + + async query (opts) { + if (typeof opts === 'string') { + opts = {path: [opts]} + } + if (!opts.drive && location.protocol === 'hyper:') { + opts.drive = [location.hostname] + } + var res = await hyperdriveRPC.query(opts) + res.forEach(item => { + if (item.stat) item.stat = createStat(item.stat) + }) + return res + } + } + + // add internal methods + if (window.location.protocol === 'beaker:') { + api.importFromFilesystem = async function (opts = {}) { + return hyperdriveRPC.importFromFilesystem(opts) + } + api.exportToFilesystem = async function (opts = {}) { + return hyperdriveRPC.exportToFilesystem(opts) + } + api.exportToDrive = async function (opts = {}) { + return hyperdriveRPC.exportToDrive(opts) + } + api.diff = async function (srcUrl, dstUrl, opts = {}) { + if (srcUrl && typeof srcUrl.url === 'string') srcUrl = srcUrl.url + if (dstUrl && typeof dstUrl.url === 'string') dstUrl = dstUrl.url + return hyperdriveRPC.beakerDiff(srcUrl, dstUrl, opts) + } + api.merge = async function (srcUrl, dstUrl, opts = {}) { + if (srcUrl && typeof srcUrl.url === 'string') srcUrl = srcUrl.url + if (dstUrl && typeof dstUrl.url === 'string') dstUrl = dstUrl.url + return hyperdriveRPC.beakerMerge(srcUrl, dstUrl, opts) + } + } + + return api +} diff --git a/app/bg/web-apis/fg/internal.js b/app/bg/web-apis/fg/internal.js new file mode 100644 index 0000000000..8221ae6ad0 --- /dev/null +++ b/app/bg/web-apis/fg/internal.js @@ -0,0 +1,67 @@ +import { EventTarget, bindEventStream, fromEventStream } from './event-target' +import errors from 'beaker-error-constants' +import loggerManifest from '../manifests/internal/logger' +import beakerBrowserManifest from '../manifests/internal/browser' +import bookmarksManifest from '../manifests/internal/bookmarks' +import datLegacyManifest from '../manifests/internal/dat-legacy' +import downloadsManifest from '../manifests/internal/downloads' +import drivesManifest from '../manifests/internal/drives' +import historyManifest from '../manifests/internal/history' +import sitedataManifest from '../manifests/internal/sitedata' +import watchlistManifest from '../manifests/internal/watchlist' + +export const setup = function (rpc) { + const internal = {} + const opts = { timeout: false, errors } + + const loggerRPC = rpc.importAPI('logger', loggerManifest, opts) + const beakerBrowserRPC = rpc.importAPI('beaker-browser', beakerBrowserManifest, opts) + const bookmarksRPC = rpc.importAPI('bookmarks', bookmarksManifest, opts) + const downloadsRPC = rpc.importAPI('downloads', downloadsManifest, opts) + const drivesRPC = rpc.importAPI('drives', drivesManifest, opts) + const datLegacyRPC = rpc.importAPI('dat-legacy', datLegacyManifest, opts) + const historyRPC = rpc.importAPI('history', historyManifest, opts) + const sitedataRPC = rpc.importAPI('sitedata', sitedataManifest, opts) + const watchlistRPC = rpc.importAPI('watchlist', watchlistManifest, opts) + + // attach APIs + internal.browser = Object.assign({}, beakerBrowserRPC) + internal.browser.createEventsStream = () => fromEventStream(beakerBrowserRPC.createEventsStream()) + internal.bookmarks = Object.assign({}, bookmarksRPC) + internal.datLegacy = datLegacyRPC + internal.downloads = Object.assign({}, downloadsRPC) + internal.downloads.createEventsStream = () => fromEventStream(downloadsRPC.createEventsStream()) + internal.history = Object.assign({}, historyRPC) + internal.logger = Object.assign({}, loggerRPC) + internal.logger.stream = (opts) => fromEventStream(loggerRPC.stream(opts)) + internal.logger.streamAuditLog = () => fromEventStream(loggerRPC.streamAuditLog()) + internal.sitedata = Object.assign({}, sitedataRPC) + internal.watchlist = Object.assign({}, watchlistRPC) + internal.watchlist.createEventsStream = () => fromEventStream(watchlistRPC.createEventsStream()) + + // internal.drives + internal.drives = new EventTarget() + internal.drives.get = drivesRPC.get + internal.drives.list = drivesRPC.list + internal.drives.getPeerCount = drivesRPC.getPeerCount + internal.drives.getForks = drivesRPC.getForks + internal.drives.configure = drivesRPC.configure + internal.drives.remove = drivesRPC.remove + internal.drives.listTrash = drivesRPC.listTrash + internal.drives.collectTrash = drivesRPC.collectTrash + internal.drives.delete = drivesRPC.delete + internal.drives.touch = drivesRPC.touch + internal.drives.clearFileCache = drivesRPC.clearFileCache + internal.drives.clearDnsCache = drivesRPC.clearDnsCache + internal.drives.getDebugLog = drivesRPC.getDebugLog + internal.drives.createDebugStream = () => fromEventStream(drivesRPC.createDebugStream()) + // window.addEventListener('load', () => { + // try { + // bindEventStream(drivesRPC.createEventStream(), internal.drives) + // } catch (e) { + // // permissions error + // } + // }) + + return internal +} diff --git a/app/bg/web-apis/fg/stat.js b/app/bg/web-apis/fg/stat.js new file mode 100644 index 0000000000..a86aa6a248 --- /dev/null +++ b/app/bg/web-apis/fg/stat.js @@ -0,0 +1,57 @@ +// http://man7.org/linux/man-pages/man2/stat.2.html +// mirrored from hyperdrive/lib/stat.js + +function toHex (buf) { + return buf.reduce((memo, i) => ( + memo + ('0' + i.toString(16)).slice(-2) // pad with leading 0 if <16 + ), '') +} + +const IFSOCK = 49152 // 0b1100... +const IFLNK = 40960 // 0b1010... +const IFREG = 32768 // 0b1000... +const IFBLK = 24576 // 0b0110... +const IFDIR = 16384 // 0b0100... +const IFCHR = 8192 // 0b0010... +const IFIFO = 4096 // 0b0001... + +export function createStat (data) { + /* + TODO- are the following attrs needed? + this.dev = 0 + this.nlink = 1 + this.rdev = 0 + this.blksize = 0 + this.ino = 0 + this.uid = data ? data.uid : 0 + this.gid = data ? data.gid : 0 */ + + var mode = data ? data.mode : 0 + return { + mode, + size: data ? data.size : 0, + offset: data ? data.offset : 0, + blocks: data ? data.blocks : 0, + downloaded: data ? data.downloaded : 0, + atime: new Date(data ? data.mtime : 0), // we just set this to mtime ... + mtime: new Date(data ? data.mtime : 0), + ctime: new Date(data ? data.ctime : 0), + mount: data && data.mount && data.mount.key ? {key: toHex(data.mount.key)} : null, + linkname: data ? data.linkname : null, + metadata: data ? data.metadata : {}, + + isSocket: check(mode, IFSOCK), + isSymbolicLink: check(mode, IFLNK), + isFile: check(mode, IFREG), + isBlockDevice: check(mode, IFBLK), + isDirectory: check(mode, IFDIR), + isCharacterDevice: check(mode, IFCHR), + isFIFO: check(mode, IFIFO) + } +} + +function check (mode, mask) { + return function () { + return (mask & mode) === mask + } +} \ No newline at end of file diff --git a/app/bg/web-apis/manifests/external/README.md b/app/bg/web-apis/manifests/external/README.md new file mode 100644 index 0000000000..9c9bb8222e --- /dev/null +++ b/app/bg/web-apis/manifests/external/README.md @@ -0,0 +1,3 @@ +# External APIs + +These are RPC APIs which are exported to userland, and need to be treated as potentially hostile. \ No newline at end of file diff --git a/app/bg/web-apis/manifests/external/capabilities.js b/app/bg/web-apis/manifests/external/capabilities.js new file mode 100644 index 0000000000..6b9f5c885c --- /dev/null +++ b/app/bg/web-apis/manifests/external/capabilities.js @@ -0,0 +1,5 @@ +export default { + create: 'promise', + modify: 'promise', + delete: 'promise' +} \ No newline at end of file diff --git a/app/bg/web-apis/manifests/external/contacts.js b/app/bg/web-apis/manifests/external/contacts.js new file mode 100644 index 0000000000..5c0b6a27b1 --- /dev/null +++ b/app/bg/web-apis/manifests/external/contacts.js @@ -0,0 +1,8 @@ +export default { + requestProfile: 'promise', + requestContact: 'promise', + requestContacts: 'promise', + requestAddContact: 'promise', + list: 'promise', + remove: 'promise' +} \ No newline at end of file diff --git a/app/bg/web-apis/manifests/external/experimental/capture-page.js b/app/bg/web-apis/manifests/external/experimental/capture-page.js new file mode 100644 index 0000000000..786c2b609e --- /dev/null +++ b/app/bg/web-apis/manifests/external/experimental/capture-page.js @@ -0,0 +1,3 @@ +export default { + capturePage: 'promise' +} diff --git a/app/bg/web-apis/manifests/external/experimental/dat-peers.js b/app/bg/web-apis/manifests/external/experimental/dat-peers.js new file mode 100644 index 0000000000..448ce0f178 --- /dev/null +++ b/app/bg/web-apis/manifests/external/experimental/dat-peers.js @@ -0,0 +1,10 @@ +export default { + list: 'promise', + get: 'promise', + broadcast: 'promise', + send: 'promise', + getSessionData: 'promise', + setSessionData: 'promise', + getOwnPeerId: 'promise', + createEventStream: 'readable' +} diff --git a/app/bg/web-apis/manifests/external/experimental/global-fetch.js b/app/bg/web-apis/manifests/external/experimental/global-fetch.js new file mode 100644 index 0000000000..73bcf9f49c --- /dev/null +++ b/app/bg/web-apis/manifests/external/experimental/global-fetch.js @@ -0,0 +1,3 @@ +export default { + fetch: 'promise' +} diff --git a/app/bg/web-apis/manifests/external/hyperdrive.js b/app/bg/web-apis/manifests/external/hyperdrive.js new file mode 100644 index 0000000000..ffda2a5051 --- /dev/null +++ b/app/bg/web-apis/manifests/external/hyperdrive.js @@ -0,0 +1,40 @@ +export default { + loadDrive: 'promise', + createDrive: 'promise', + forkDrive: 'promise', + + getInfo: 'promise', + configure: 'promise', + diff: 'promise', + + stat: 'promise', + readFile: 'promise', + writeFile: 'promise', + unlink: 'promise', + copy: 'promise', + rename: 'promise', + updateMetadata: 'promise', + deleteMetadata: 'promise', + + readdir: 'promise', + mkdir: 'promise', + rmdir: 'promise', + + symlink: 'promise', + + mount: 'promise', + unmount: 'promise', + + query: 'promise', + + watch: 'readable', + createNetworkActivityStream: 'readable', + + resolveName: 'promise', + + beakerDiff: 'promise', + beakerMerge: 'promise', + importFromFilesystem: 'promise', + exportToFilesystem: 'promise', + exportToDrive: 'promise' +} diff --git a/app/bg/web-apis/manifests/external/markdown.js b/app/bg/web-apis/manifests/external/markdown.js new file mode 100644 index 0000000000..ff35063119 --- /dev/null +++ b/app/bg/web-apis/manifests/external/markdown.js @@ -0,0 +1,3 @@ +export default { + toHTML: 'sync' +} \ No newline at end of file diff --git a/app/bg/web-apis/manifests/external/peersockets.js b/app/bg/web-apis/manifests/external/peersockets.js new file mode 100644 index 0000000000..7721e6450d --- /dev/null +++ b/app/bg/web-apis/manifests/external/peersockets.js @@ -0,0 +1,4 @@ +export default { + join: 'duplex', + watch: 'readable' +} diff --git a/app/bg/web-apis/manifests/external/shell.js b/app/bg/web-apis/manifests/external/shell.js new file mode 100644 index 0000000000..18e9e13fc6 --- /dev/null +++ b/app/bg/web-apis/manifests/external/shell.js @@ -0,0 +1,10 @@ +export default { + drivePropertiesDialog: 'promise', + selectFileDialog: 'promise', + saveFileDialog: 'promise', + selectDriveDialog: 'promise', + executeSidebarCommand: 'promise', + importFilesDialog: 'promise', + importFoldersDialog: 'promise', + exportFilesDialog: 'promise' +} \ No newline at end of file diff --git a/app/bg/web-apis/manifests/internal/README.md b/app/bg/web-apis/manifests/internal/README.md new file mode 100644 index 0000000000..36a1d4c874 --- /dev/null +++ b/app/bg/web-apis/manifests/internal/README.md @@ -0,0 +1,3 @@ +# Internal APIs + +These are RPC APIs which are exported to builtin interfaces, and need to be kept away from potentially hostile codepaths. \ No newline at end of file diff --git a/app/bg/web-apis/manifests/internal/beaker-filesystem.js b/app/bg/web-apis/manifests/internal/beaker-filesystem.js new file mode 100644 index 0000000000..e0aee04d56 --- /dev/null +++ b/app/bg/web-apis/manifests/internal/beaker-filesystem.js @@ -0,0 +1,3 @@ +export default { + get: 'sync' +} \ No newline at end of file diff --git a/app/bg/web-apis/manifests/internal/bookmarks.js b/app/bg/web-apis/manifests/internal/bookmarks.js new file mode 100644 index 0000000000..294f736046 --- /dev/null +++ b/app/bg/web-apis/manifests/internal/bookmarks.js @@ -0,0 +1,6 @@ +export default { + list: 'promise', + get: 'promise', + add: 'promise', + remove: 'promise' +} diff --git a/app/bg/web-apis/manifests/internal/browser.js b/app/bg/web-apis/manifests/internal/browser.js new file mode 100644 index 0000000000..be95de8c21 --- /dev/null +++ b/app/bg/web-apis/manifests/internal/browser.js @@ -0,0 +1,61 @@ +export default { + createEventsStream: 'readable', + getInfo: 'promise', + getDaemonStatus: 'promise', + getDaemonNetworkStatus: 'promise', + getProfile: 'promise', + checkForUpdates: 'promise', + restartBrowser: 'sync', + + getSettings: 'promise', + getSetting: 'promise', + setSetting: 'promise', + updateSetupState: 'promise', + setupDefaultProfile: 'promise', + migrate08to09: 'promise', + setStartPageBackgroundImage: 'promise', + + getDefaultProtocolSettings: 'promise', + setAsDefaultProtocolClient: 'promise', + removeAsDefaultProtocolClient: 'promise', + + listBuiltinFavicons: 'promise', + getBuiltinFavicon: 'promise', + uploadFavicon: 'promise', + imageToIco: 'promise', + + reconnectHyperdriveDaemon: 'promise', + + fetchBody: 'promise', + downloadURL: 'promise', + readFile: 'promise', + + getResourceContentType: 'sync', + getCertificate: 'promise', + + executeSidebarCommand: 'promise', + toggleSiteInfo: 'promise', + toggleLiveReloading: 'promise', + setWindowDimensions: 'promise', + setWindowDragModeEnabled: 'promise', + setSidebarResizeModeEnabled: 'promise', + moveWindow: 'promise', + maximizeWindow: 'promise', + resizeSiteInfo: 'promise', + showOpenDialog: 'promise', + showContextMenu: 'promise', + showModal: 'promise', + newWindow: 'promise', + openUrl: 'promise', + gotoUrl: 'promise', + getPageUrl: 'promise', + refreshPage: 'promise', + focusPage: 'promise', + executeJavaScriptInPage: 'promise', + injectCssInPage: 'promise', + uninjectCssInPage: 'promise', + openFolder: 'promise', + doWebcontentsCmd: 'promise', + doTest: 'promise', + closeModal: 'sync' +} diff --git a/app/bg/web-apis/manifests/internal/dat-legacy.js b/app/bg/web-apis/manifests/internal/dat-legacy.js new file mode 100644 index 0000000000..039bac9a38 --- /dev/null +++ b/app/bg/web-apis/manifests/internal/dat-legacy.js @@ -0,0 +1,4 @@ +export default { + list: 'promise', + remove: 'promise' +} \ No newline at end of file diff --git a/app/bg/web-apis/manifests/internal/downloads.js b/app/bg/web-apis/manifests/internal/downloads.js new file mode 100644 index 0000000000..0638de6afb --- /dev/null +++ b/app/bg/web-apis/manifests/internal/downloads.js @@ -0,0 +1,10 @@ +export default { + getDownloads: 'promise', + pause: 'promise', + resume: 'promise', + cancel: 'promise', + remove: 'promise', + open: 'promise', + showInFolder: 'promise', + createEventsStream: 'readable' +} diff --git a/app/bg/web-apis/manifests/internal/drives.js b/app/bg/web-apis/manifests/internal/drives.js new file mode 100644 index 0000000000..bbf7cb4abf --- /dev/null +++ b/app/bg/web-apis/manifests/internal/drives.js @@ -0,0 +1,17 @@ +export default { + get: 'promise', + list: 'promise', + getPeerCount: 'promise', + getForks: 'promise', + configure: 'promise', + remove: 'promise', + listTrash: 'promise', + collectTrash: 'promise', + delete: 'promise', + touch: 'promise', + clearFileCache: 'promise', + clearDnsCache: 'promise', + createEventStream: 'readable', + getDebugLog: 'promise', + createDebugStream: 'readable' +} diff --git a/app/bg/web-apis/manifests/internal/history.js b/app/bg/web-apis/manifests/internal/history.js new file mode 100644 index 0000000000..1fb50908d0 --- /dev/null +++ b/app/bg/web-apis/manifests/internal/history.js @@ -0,0 +1,9 @@ +export default { + addVisit: 'promise', + getVisitHistory: 'promise', + getMostVisited: 'promise', + search: 'promise', + removeVisit: 'promise', + removeAllVisits: 'promise', + removeVisitsAfter: 'promise' +} diff --git a/app/bg/web-apis/manifests/internal/logger.js b/app/bg/web-apis/manifests/internal/logger.js new file mode 100644 index 0000000000..03cd238131 --- /dev/null +++ b/app/bg/web-apis/manifests/internal/logger.js @@ -0,0 +1,8 @@ +export default { + stream: 'readable', + query: 'promise', + listDaemonLog: 'promise', + listAuditLog: 'promise', + streamAuditLog: 'readable', + getAuditLogStats: 'promise', +} \ No newline at end of file diff --git a/app/bg/web-apis/manifests/internal/sitedata.js b/app/bg/web-apis/manifests/internal/sitedata.js new file mode 100644 index 0000000000..1ce95c8c34 --- /dev/null +++ b/app/bg/web-apis/manifests/internal/sitedata.js @@ -0,0 +1,9 @@ +export default { + get: 'promise', + set: 'promise', + getPermissions: 'promise', + getPermission: 'promise', + setPermission: 'promise', + clearPermission: 'promise', + clearPermissionAllOrigins: 'promise' +} diff --git a/app/bg/web-apis/manifests/internal/users.js b/app/bg/web-apis/manifests/internal/users.js new file mode 100644 index 0000000000..fa58a40416 --- /dev/null +++ b/app/bg/web-apis/manifests/internal/users.js @@ -0,0 +1,11 @@ +export default { + list: 'promise', + get: 'promise', + showCreateDialog: 'promise', + create: 'promise', + createTemporary: 'promise', + add: 'promise', + edit: 'promise', + remove: 'promise', + setupDefault: 'promise' +} diff --git a/app/bg/web-apis/manifests/internal/watchlist.js b/app/bg/web-apis/manifests/internal/watchlist.js new file mode 100644 index 0000000000..a624fa8ca1 --- /dev/null +++ b/app/bg/web-apis/manifests/internal/watchlist.js @@ -0,0 +1,9 @@ +export default { + add: 'promise', + list: 'promise', + update: 'promise', + remove: 'promise', + + // events + createEventsStream: 'readable' +} diff --git a/app/dat-daemon.js b/app/dat-daemon.js deleted file mode 100644 index 400068bfc1..0000000000 --- a/app/dat-daemon.js +++ /dev/null @@ -1,20 +0,0 @@ -const {join} = require('path') -const rpcAPI = require('pauls-electron-rpc') -const beakerCoreDatDaemon = require('@beaker/core/dat/daemon') - -process.on('uncaughtException', (err) => { - console.error('Uncaught exception:', err) -}) - -process.on('disconnect', () => { - process.exit() -}) - -process.once('message', firstMsg => { - beakerCoreDatDaemon.setup({ - rpcAPI, - logfilePath: join(firstMsg.userDataPath, 'dat.log') - }) - process.send({ready: true}) - console.log('dat-daemon ready') -}) \ No newline at end of file diff --git a/app/fg/README.md b/app/fg/README.md new file mode 100644 index 0000000000..1224ebf0f2 --- /dev/null +++ b/app/fg/README.md @@ -0,0 +1,15 @@ +# FG (foreground) + +This folder contains frontend code that drives Beaker's UI. + +Each folder is a self-contained UI component. Many folders have a 1:1 connection with a file in `/app/bg/ui/subwindows/*` such as `perm-prompt`, `prompts`, and `shell-menus`. + +Notable folders: + + - `lib` is reusable code that's specific to frontend. + - `shell-window` is the primary shell UI of Beaker. + - `builtin-pages` is largely legacy code which is either inactive or waiting for an update. Unlike the rest of Beaker's UI, it still uses the "yo-yo" templating module and .less (whereas the rest of Beaker uses LitHTML and LitElements). It also really ought to be in `/app/userland` (my mistake) but I'm not bothering to fix it because this code really ought to be replaced and removed. + + ## RPC + + Beaker's Web APIs are not available in these components (except for everything in `builtin-pages`, see the note above). Therefore all RPC with the Electron process needs to be setup manually (thus the `bg-process-rpc.js` pattern). \ No newline at end of file diff --git a/app/builtin-pages/com/add-watchlist-item-popup.js b/app/fg/builtin-pages/com/add-watchlist-item-popup.js similarity index 95% rename from app/builtin-pages/com/add-watchlist-item-popup.js rename to app/fg/builtin-pages/com/add-watchlist-item-popup.js index 5fb56f8ee9..7a758dd190 100644 --- a/app/builtin-pages/com/add-watchlist-item-popup.js +++ b/app/fg/builtin-pages/com/add-watchlist-item-popup.js @@ -145,8 +145,8 @@ function validate () { urlError.textContent = 'Please enter a site.' urlError.style.opacity = 1 success = false - } else if (!/dat?:\/\//.test(url) && !/[a-f0-9]{64}/.test(url)) { - urlError.textContent = 'Please enter a valid dat:// url' + } else if (!/(web|drive)?:\/\//.test(url) && !/[a-f0-9]{64}/.test(url)) { + urlError.textContent = 'Please enter a valid drive:// or web:// url' urlError.style.opacity = 1 success = false } diff --git a/app/builtin-pages/com/archive/archive-comparison.js b/app/fg/builtin-pages/com/archive/archive-comparison.js similarity index 99% rename from app/builtin-pages/com/archive/archive-comparison.js rename to app/fg/builtin-pages/com/archive/archive-comparison.js index 686772cc3c..760004a751 100644 --- a/app/builtin-pages/com/archive/archive-comparison.js +++ b/app/fg/builtin-pages/com/archive/archive-comparison.js @@ -5,7 +5,7 @@ import yo from 'yo-yo' import renderDiff from './diff' import renderArchiveSelectBtn from './archive-select-btn' import renderBackLink from '../back-link' -import {pluralize, shortenHash} from '../../../lib/strings' +import {pluralize, shortenHash} from '../../../../lib/strings' // exported api // = diff --git a/app/builtin-pages/com/archive/archive-history.js b/app/fg/builtin-pages/com/archive/archive-history.js similarity index 99% rename from app/builtin-pages/com/archive/archive-history.js rename to app/fg/builtin-pages/com/archive/archive-history.js index 02a4398664..fba12b3f90 100644 --- a/app/builtin-pages/com/archive/archive-history.js +++ b/app/fg/builtin-pages/com/archive/archive-history.js @@ -1,6 +1,6 @@ /* globals DatArchive */ -import * as yo from 'yo-yo' +import yo from 'yo-yo' const FETCH_COUNT = 200 diff --git a/app/builtin-pages/com/archive/archive-select-btn.js b/app/fg/builtin-pages/com/archive/archive-select-btn.js similarity index 98% rename from app/builtin-pages/com/archive/archive-select-btn.js rename to app/fg/builtin-pages/com/archive/archive-select-btn.js index 738f502cc4..6b3762b4ea 100644 --- a/app/builtin-pages/com/archive/archive-select-btn.js +++ b/app/fg/builtin-pages/com/archive/archive-select-btn.js @@ -2,7 +2,6 @@ import yo from 'yo-yo' import toggleable, {closeAllToggleables} from '../toggleable' -import {shortenHash} from '../../../lib/strings' // globals // = diff --git a/app/builtin-pages/com/archive/diff.js b/app/fg/builtin-pages/com/archive/diff.js similarity index 100% rename from app/builtin-pages/com/archive/diff.js rename to app/fg/builtin-pages/com/archive/diff.js diff --git a/app/builtin-pages/com/back-link.js b/app/fg/builtin-pages/com/back-link.js similarity index 100% rename from app/builtin-pages/com/back-link.js rename to app/fg/builtin-pages/com/back-link.js diff --git a/app/builtin-pages/com/builtin-pages-nav.js b/app/fg/builtin-pages/com/builtin-pages-nav.js similarity index 100% rename from app/builtin-pages/com/builtin-pages-nav.js rename to app/fg/builtin-pages/com/builtin-pages-nav.js diff --git a/app/builtin-pages/com/context-input.js b/app/fg/builtin-pages/com/context-input.js similarity index 98% rename from app/builtin-pages/com/context-input.js rename to app/fg/builtin-pages/com/context-input.js index 535e469797..034650a9cf 100644 --- a/app/builtin-pages/com/context-input.js +++ b/app/fg/builtin-pages/com/context-input.js @@ -1,4 +1,4 @@ -import * as yo from 'yo-yo' +import yo from 'yo-yo' import * as contextMenu from './context-menu' // exported api diff --git a/app/builtin-pages/com/context-menu.js b/app/fg/builtin-pages/com/context-menu.js similarity index 98% rename from app/builtin-pages/com/context-menu.js rename to app/fg/builtin-pages/com/context-menu.js index c6f191d618..57ad1468b5 100644 --- a/app/builtin-pages/com/context-menu.js +++ b/app/fg/builtin-pages/com/context-menu.js @@ -1,5 +1,5 @@ import yo from 'yo-yo' -import {findParent} from '../../lib/fg/event-handlers' +import {findParent} from '../../lib/event-handlers' // globals // = diff --git a/app/builtin-pages/com/crop-popup.js b/app/fg/builtin-pages/com/crop-popup.js similarity index 100% rename from app/builtin-pages/com/crop-popup.js rename to app/fg/builtin-pages/com/crop-popup.js diff --git a/app/builtin-pages/com/downloads-list.js b/app/fg/builtin-pages/com/downloads-list.js similarity index 96% rename from app/builtin-pages/com/downloads-list.js rename to app/fg/builtin-pages/com/downloads-list.js index addc509722..de9cf19166 100644 --- a/app/builtin-pages/com/downloads-list.js +++ b/app/fg/builtin-pages/com/downloads-list.js @@ -1,7 +1,7 @@ -import * as yo from 'yo-yo' +import yo from 'yo-yo' import prettyBytes from 'pretty-bytes' -import { ucfirst, getHostname } from '../../lib/strings' -import { downloadTimestamp } from '../../lib/time' +import { ucfirst, getHostname } from '../../../lib/strings' +import { downloadTimestamp } from '../../../lib/time' // exported api // = diff --git a/app/builtin-pages/com/dropdown-menu-bar.js b/app/fg/builtin-pages/com/dropdown-menu-bar.js similarity index 98% rename from app/builtin-pages/com/dropdown-menu-bar.js rename to app/fg/builtin-pages/com/dropdown-menu-bar.js index d5fa8b6cf2..9db5a62e0d 100644 --- a/app/builtin-pages/com/dropdown-menu-bar.js +++ b/app/fg/builtin-pages/com/dropdown-menu-bar.js @@ -1,6 +1,6 @@ /* globals Event */ -import * as yo from 'yo-yo' +import yo from 'yo-yo' // exported api // = diff --git a/app/builtin-pages/com/editor/home.js b/app/fg/builtin-pages/com/editor/home.js similarity index 95% rename from app/builtin-pages/com/editor/home.js rename to app/fg/builtin-pages/com/editor/home.js index abf9deaa92..02fb461e45 100644 --- a/app/builtin-pages/com/editor/home.js +++ b/app/fg/builtin-pages/com/editor/home.js @@ -1,7 +1,7 @@ import yo from 'yo-yo' -import {pluralize} from '../../../lib/strings' -import {emit} from '../../../lib/fg/event-handlers' -import createMd from '../../../lib/fg/markdown' +import {pluralize} from '../../../../lib/strings' +import {emit} from '../../../lib/event-handlers' +import createMd from '../../../../lib/markdown' // exported api // = @@ -141,11 +141,11 @@ function renderReadme (archiveInfo, readmeMd) { highlight (str, lang) { if (lang && hljs.getLanguage(lang)) { try { - return hljs.highlight(lang, str).value; + return hljs.highlight(lang, str).value } catch (__) {} } - - return ''; // use external default escaping + + return '' // use external default escaping } }) diff --git a/app/builtin-pages/com/editor/hotkeys.js b/app/fg/builtin-pages/com/editor/hotkeys.js similarity index 97% rename from app/builtin-pages/com/editor/hotkeys.js rename to app/fg/builtin-pages/com/editor/hotkeys.js index 6dfa196b27..4f944949e8 100644 --- a/app/builtin-pages/com/editor/hotkeys.js +++ b/app/fg/builtin-pages/com/editor/hotkeys.js @@ -1,4 +1,4 @@ -import {emit} from '../../../lib/fg/event-handlers' +import {emit} from '../../../lib/event-handlers' const HOTKEYS = [ {modifiers: ['CmdOrCtrl'], key: 's', fn: () => emit('editor-save-active-model')} diff --git a/app/builtin-pages/com/editor/import-popup.js b/app/fg/builtin-pages/com/editor/import-popup.js similarity index 100% rename from app/builtin-pages/com/editor/import-popup.js rename to app/fg/builtin-pages/com/editor/import-popup.js diff --git a/app/builtin-pages/com/editor/models.js b/app/fg/builtin-pages/com/editor/models.js similarity index 100% rename from app/builtin-pages/com/editor/models.js rename to app/fg/builtin-pages/com/editor/models.js diff --git a/app/builtin-pages/com/editor/settings-form.js b/app/fg/builtin-pages/com/editor/settings-form.js similarity index 99% rename from app/builtin-pages/com/editor/settings-form.js rename to app/fg/builtin-pages/com/editor/settings-form.js index 44ea9e8973..92f784f090 100644 --- a/app/builtin-pages/com/editor/settings-form.js +++ b/app/fg/builtin-pages/com/editor/settings-form.js @@ -1,5 +1,5 @@ import yo from 'yo-yo' -import { writeToClipboard, emit } from '../../../lib/fg/event-handlers' +import { writeToClipboard, emit } from '../../../lib/event-handlers' import _get from 'lodash.get' import * as toast from '../toast' @@ -519,7 +519,7 @@ async function onSubmitSettings (e, workingCheckout, workingDatJson) { workingDatJson.application.permissions[perm] = Array.from(capset) } } - + // write manifest await workingCheckout.writeFile('/dat.json', JSON.stringify(workingDatJson, null, 2)) @@ -545,7 +545,7 @@ function onToggleExpanded (id) { document.querySelector(`section#${expandedSection}`).classList.add('collapsed') } catch (e) { /* ignore */ } } - + if (expandedSection === id) expandedSection = false else expandedSection = id sessionStorage.expandedSection = expandedSection diff --git a/app/builtin-pages/com/editor/sidebar.js b/app/fg/builtin-pages/com/editor/sidebar.js similarity index 98% rename from app/builtin-pages/com/editor/sidebar.js rename to app/fg/builtin-pages/com/editor/sidebar.js index 27848600a5..14c2554903 100644 --- a/app/builtin-pages/com/editor/sidebar.js +++ b/app/fg/builtin-pages/com/editor/sidebar.js @@ -4,8 +4,8 @@ import _get from 'lodash.get' import {FSArchiveFolder_BeingCreated, FSArchiveFile_BeingCreated} from 'beaker-virtual-fs' import * as contextMenu from '../context-menu' import * as contextInput from '../context-input' -import {findParent, writeToClipboard} from '../../../lib/fg/event-handlers' -import {pluralize} from '../../../lib/strings' +import {findParent, writeToClipboard} from '../../../lib/event-handlers' +import {pluralize} from '../../../../lib/strings' // globals // = diff --git a/app/builtin-pages/com/editor/siteinfo-dropdown.js b/app/fg/builtin-pages/com/editor/siteinfo-dropdown.js similarity index 98% rename from app/builtin-pages/com/editor/siteinfo-dropdown.js rename to app/fg/builtin-pages/com/editor/siteinfo-dropdown.js index 0734b4f535..910ec4d4bd 100644 --- a/app/builtin-pages/com/editor/siteinfo-dropdown.js +++ b/app/fg/builtin-pages/com/editor/siteinfo-dropdown.js @@ -1,6 +1,6 @@ import yo from 'yo-yo' import _get from 'lodash.get' -import {emit} from '../../../lib/fg/event-handlers' +import {emit} from '../../../lib/event-handlers' import toggleable2, {closeAllToggleables} from '../toggleable2' // globals diff --git a/app/builtin-pages/com/editor/tabs.js b/app/fg/builtin-pages/com/editor/tabs.js similarity index 97% rename from app/builtin-pages/com/editor/tabs.js rename to app/fg/builtin-pages/com/editor/tabs.js index 5a7c0d532d..c47d4674f5 100644 --- a/app/builtin-pages/com/editor/tabs.js +++ b/app/fg/builtin-pages/com/editor/tabs.js @@ -2,7 +2,7 @@ import yo from 'yo-yo' import * as contextMenu from '../context-menu' import toggleable2 from '../toggleable2' import * as toast from '../toast' -import {writeToClipboard, emit} from '../../../lib/fg/event-handlers' +import {writeToClipboard, emit} from '../../../lib/event-handlers' // rendering // = diff --git a/app/builtin-pages/com/editor/toolbar.js b/app/fg/builtin-pages/com/editor/toolbar.js similarity index 100% rename from app/builtin-pages/com/editor/toolbar.js rename to app/fg/builtin-pages/com/editor/toolbar.js diff --git a/app/builtin-pages/com/files-browser/file-editor.js b/app/fg/builtin-pages/com/files-browser/file-editor.js similarity index 99% rename from app/builtin-pages/com/files-browser/file-editor.js rename to app/fg/builtin-pages/com/files-browser/file-editor.js index d730c9fe57..862a642235 100644 --- a/app/builtin-pages/com/files-browser/file-editor.js +++ b/app/fg/builtin-pages/com/files-browser/file-editor.js @@ -1,6 +1,6 @@ /* globals ace */ -import * as yo from 'yo-yo' +import yo from 'yo-yo' var orgApplyDeltaFn diff --git a/app/builtin-pages/com/files-browser/file-preview.js b/app/fg/builtin-pages/com/files-browser/file-preview.js similarity index 98% rename from app/builtin-pages/com/files-browser/file-preview.js rename to app/fg/builtin-pages/com/files-browser/file-preview.js index 71285438e8..ac94d5ee18 100644 --- a/app/builtin-pages/com/files-browser/file-preview.js +++ b/app/fg/builtin-pages/com/files-browser/file-preview.js @@ -1,6 +1,6 @@ /* globals hljs */ -import * as yo from 'yo-yo' +import yo from 'yo-yo' import mime from 'mime' import {render as renderFileEditor} from './file-editor' import BINARY_EXTENSIONS from 'binary-extensions' diff --git a/app/builtin-pages/com/files-browser/files-browser.js b/app/fg/builtin-pages/com/files-browser/files-browser.js similarity index 100% rename from app/builtin-pages/com/files-browser/files-browser.js rename to app/fg/builtin-pages/com/files-browser/files-browser.js diff --git a/app/builtin-pages/com/files-browser/files-flat-view.js b/app/fg/builtin-pages/com/files-browser/files-flat-view.js similarity index 98% rename from app/builtin-pages/com/files-browser/files-flat-view.js rename to app/fg/builtin-pages/com/files-browser/files-flat-view.js index 1ade517009..c102923025 100644 --- a/app/builtin-pages/com/files-browser/files-flat-view.js +++ b/app/fg/builtin-pages/com/files-browser/files-flat-view.js @@ -8,11 +8,11 @@ import * as contextMenu from '../context-menu' import * as contextInput from '../context-input' import * as toast from '../toast' import toggleable2 from '../toggleable2' -import {DAT_VALID_PATH_REGEX} from '@beaker/core/lib/const' -import {writeToClipboard} from '../../../lib/fg/event-handlers' +import {DAT_VALID_PATH_REGEX} from '../../../../lib/const' +import {writeToClipboard} from '../../../lib/event-handlers' import renderFilePreview from './file-preview' import {render as renderFileEditor} from './file-editor' -import {shorten, pluralize} from '../../../lib/strings' +import {shorten, pluralize} from '../../../../lib/strings' // exported api // = diff --git a/app/builtin-pages/com/library/localsyncpath-popup.js b/app/fg/builtin-pages/com/library/localsyncpath-popup.js similarity index 98% rename from app/builtin-pages/com/library/localsyncpath-popup.js rename to app/fg/builtin-pages/com/library/localsyncpath-popup.js index 2d9dd12fae..0ecd1eb0f9 100644 --- a/app/builtin-pages/com/library/localsyncpath-popup.js +++ b/app/fg/builtin-pages/com/library/localsyncpath-popup.js @@ -1,7 +1,6 @@ /* globals beaker */ import yo from 'yo-yo' -import {shortenHash} from '../../../lib/strings' import closeIcon from '../../icon/close' // globals diff --git a/app/builtin-pages/com/library/view-compare.js b/app/fg/builtin-pages/com/library/view-compare.js similarity index 100% rename from app/builtin-pages/com/library/view-compare.js rename to app/fg/builtin-pages/com/library/view-compare.js diff --git a/app/builtin-pages/com/library/view-local-compare.js b/app/fg/builtin-pages/com/library/view-local-compare.js similarity index 100% rename from app/builtin-pages/com/library/view-local-compare.js rename to app/fg/builtin-pages/com/library/view-local-compare.js diff --git a/app/builtin-pages/com/notice-banner.js b/app/fg/builtin-pages/com/notice-banner.js similarity index 100% rename from app/builtin-pages/com/notice-banner.js rename to app/fg/builtin-pages/com/notice-banner.js diff --git a/app/builtin-pages/com/onboarding-popup.js b/app/fg/builtin-pages/com/onboarding-popup.js similarity index 99% rename from app/builtin-pages/com/onboarding-popup.js rename to app/fg/builtin-pages/com/onboarding-popup.js index e292d4df91..d6c69a55b6 100644 --- a/app/builtin-pages/com/onboarding-popup.js +++ b/app/fg/builtin-pages/com/onboarding-popup.js @@ -1,6 +1,6 @@ /* globals beaker DatArchive localStorage */ -import * as yo from 'yo-yo' +import yo from 'yo-yo' // globals // = diff --git a/app/builtin-pages/com/peer-history-graph.js b/app/fg/builtin-pages/com/peer-history-graph.js similarity index 98% rename from app/builtin-pages/com/peer-history-graph.js rename to app/fg/builtin-pages/com/peer-history-graph.js index 38c751cabb..3676eecc0a 100644 --- a/app/builtin-pages/com/peer-history-graph.js +++ b/app/fg/builtin-pages/com/peer-history-graph.js @@ -1,5 +1,5 @@ import yo from 'yo-yo' -import {pluralize} from '../../lib/strings' +import {pluralize} from '../../../lib/strings' const MINMAX = 5 const EDGE_PADDING = 5 diff --git a/app/builtin-pages/com/segmented-progress-bar.js b/app/fg/builtin-pages/com/segmented-progress-bar.js similarity index 100% rename from app/builtin-pages/com/segmented-progress-bar.js rename to app/fg/builtin-pages/com/segmented-progress-bar.js diff --git a/app/builtin-pages/com/settings/crawler-status.js b/app/fg/builtin-pages/com/settings/crawler-status.js similarity index 97% rename from app/builtin-pages/com/settings/crawler-status.js rename to app/fg/builtin-pages/com/settings/crawler-status.js index c052d96fde..ef6b07208b 100644 --- a/app/builtin-pages/com/settings/crawler-status.js +++ b/app/fg/builtin-pages/com/settings/crawler-status.js @@ -3,9 +3,9 @@ import yo from 'yo-yo' import * as toast from '../toast' import * as contextMenu from '../context-menu' -import {niceDate} from '../../../lib/time' -import {getHostname} from '../../../lib/strings' -import {writeToClipboard} from '../../../lib/fg/event-handlers' +import {niceDate} from '../../../../lib/time' +import {getHostname} from '../../../../lib/strings' +import {writeToClipboard} from '../../../lib/event-handlers' // globals // = diff --git a/app/builtin-pages/com/settings/dat-cache.js b/app/fg/builtin-pages/com/settings/dat-cache.js similarity index 97% rename from app/builtin-pages/com/settings/dat-cache.js rename to app/fg/builtin-pages/com/settings/dat-cache.js index 09ec97a549..661cbc5aae 100644 --- a/app/builtin-pages/com/settings/dat-cache.js +++ b/app/fg/builtin-pages/com/settings/dat-cache.js @@ -5,9 +5,9 @@ import prettyBytes from 'pretty-bytes' import throttle from 'lodash.throttle' import * as toast from '../toast' import * as contextMenu from '../context-menu' -import {pluralize} from '../../../lib/strings' -import {niceDate} from '../../../lib/time' -import {writeToClipboard} from '../../../lib/fg/event-handlers' +import {pluralize} from '../../../../lib/strings' +import {niceDate} from '../../../../lib/time' +import {writeToClipboard} from '../../../lib/event-handlers' // globals // = diff --git a/app/builtin-pages/com/settings/edit-bookmark-popup.js b/app/fg/builtin-pages/com/settings/edit-bookmark-popup.js similarity index 100% rename from app/builtin-pages/com/settings/edit-bookmark-popup.js rename to app/fg/builtin-pages/com/settings/edit-bookmark-popup.js diff --git a/app/builtin-pages/com/settings/explorer-popup.js b/app/fg/builtin-pages/com/settings/explorer-popup.js similarity index 98% rename from app/builtin-pages/com/settings/explorer-popup.js rename to app/fg/builtin-pages/com/settings/explorer-popup.js index 6e609fcb9b..bd674ef921 100644 --- a/app/builtin-pages/com/settings/explorer-popup.js +++ b/app/fg/builtin-pages/com/settings/explorer-popup.js @@ -1,7 +1,7 @@ /* globals beaker */ import yo from 'yo-yo' -import {writeToClipboard} from '../../../lib/fg/event-handlers' +import {writeToClipboard} from '../../../lib/event-handlers' import closeIcon from '../../icon/close' import * as contextMenu from '../context-menu' import * as toast from '../toast' diff --git a/app/builtin-pages/com/settings/favicon-maker.js b/app/fg/builtin-pages/com/settings/favicon-maker.js similarity index 100% rename from app/builtin-pages/com/settings/favicon-maker.js rename to app/fg/builtin-pages/com/settings/favicon-maker.js diff --git a/app/builtin-pages/com/settings/favicon-picker.js b/app/fg/builtin-pages/com/settings/favicon-picker.js similarity index 98% rename from app/builtin-pages/com/settings/favicon-picker.js rename to app/fg/builtin-pages/com/settings/favicon-picker.js index 74103292c5..0beab5a934 100644 --- a/app/builtin-pages/com/settings/favicon-picker.js +++ b/app/fg/builtin-pages/com/settings/favicon-picker.js @@ -1,6 +1,6 @@ /* globals beaker */ -import * as yo from 'yo-yo' +import yo from 'yo-yo' import * as faviconMakerPopup from './favicon-maker' // globals diff --git a/app/builtin-pages/com/settings/logger.js b/app/fg/builtin-pages/com/settings/logger.js similarity index 98% rename from app/builtin-pages/com/settings/logger.js rename to app/fg/builtin-pages/com/settings/logger.js index a9cc952d30..222158966c 100644 --- a/app/builtin-pages/com/settings/logger.js +++ b/app/fg/builtin-pages/com/settings/logger.js @@ -3,8 +3,7 @@ import yo from 'yo-yo' import * as toast from '../toast' import * as contextMenu from '../context-menu' -import {niceDate} from '../../../lib/time' -import {writeToClipboard} from '../../../lib/fg/event-handlers' +import {writeToClipboard} from '../../../lib/event-handlers' import _debounce from 'lodash.debounce' const AVAILABLE_LEVELS = ['error', 'warn', 'info', 'verbose', 'debug', 'silly'] diff --git a/app/builtin-pages/com/settings/settings-field.js b/app/fg/builtin-pages/com/settings/settings-field.js similarity index 100% rename from app/builtin-pages/com/settings/settings-field.js rename to app/fg/builtin-pages/com/settings/settings-field.js diff --git a/app/builtin-pages/com/share-popup.js b/app/fg/builtin-pages/com/share-popup.js similarity index 95% rename from app/builtin-pages/com/share-popup.js rename to app/fg/builtin-pages/com/share-popup.js index 23acbcbc3b..ecffc7f353 100644 --- a/app/builtin-pages/com/share-popup.js +++ b/app/fg/builtin-pages/com/share-popup.js @@ -1,6 +1,6 @@ import yo from 'yo-yo' import * as toast from './toast' -import {writeToClipboard} from '../../lib/fg/event-handlers' +import {writeToClipboard} from '../../lib/event-handlers' // exported api // = diff --git a/app/builtin-pages/com/tabs.js b/app/fg/builtin-pages/com/tabs.js similarity index 92% rename from app/builtin-pages/com/tabs.js rename to app/fg/builtin-pages/com/tabs.js index 969bf9db37..e00dd428cb 100644 --- a/app/builtin-pages/com/tabs.js +++ b/app/fg/builtin-pages/com/tabs.js @@ -1,4 +1,4 @@ -import * as yo from 'yo-yo' +import yo from 'yo-yo' export default function tabs (current, tabs) { return yo` diff --git a/app/builtin-pages/com/toast.js b/app/fg/builtin-pages/com/toast.js similarity index 100% rename from app/builtin-pages/com/toast.js rename to app/fg/builtin-pages/com/toast.js diff --git a/app/builtin-pages/com/toggleable.js b/app/fg/builtin-pages/com/toggleable.js similarity index 98% rename from app/builtin-pages/com/toggleable.js rename to app/fg/builtin-pages/com/toggleable.js index 083be1af03..09d592d581 100644 --- a/app/builtin-pages/com/toggleable.js +++ b/app/fg/builtin-pages/com/toggleable.js @@ -1,4 +1,4 @@ -import { findParent } from '../../lib/fg/event-handlers' +import { findParent } from '../../lib/event-handlers' // globals // = diff --git a/app/builtin-pages/com/toggleable2.js b/app/fg/builtin-pages/com/toggleable2.js similarity index 98% rename from app/builtin-pages/com/toggleable2.js rename to app/fg/builtin-pages/com/toggleable2.js index 36d0e2459e..e80746b013 100644 --- a/app/builtin-pages/com/toggleable2.js +++ b/app/fg/builtin-pages/com/toggleable2.js @@ -1,5 +1,5 @@ import yo from 'yo-yo' -import { findParent } from '../../lib/fg/event-handlers' +import { findParent } from '../../lib/event-handlers' // globals // = diff --git a/app/builtin-pages/downloads.html b/app/fg/builtin-pages/downloads.html similarity index 100% rename from app/builtin-pages/downloads.html rename to app/fg/builtin-pages/downloads.html diff --git a/app/builtin-pages/editor.html b/app/fg/builtin-pages/editor.html similarity index 100% rename from app/builtin-pages/editor.html rename to app/fg/builtin-pages/editor.html diff --git a/app/builtin-pages/history.html b/app/fg/builtin-pages/history.html similarity index 100% rename from app/builtin-pages/history.html rename to app/fg/builtin-pages/history.html diff --git a/app/builtin-pages/icon/avatar.js b/app/fg/builtin-pages/icon/avatar.js similarity index 95% rename from app/builtin-pages/icon/avatar.js rename to app/fg/builtin-pages/icon/avatar.js index ba38367d14..52273cf4b6 100644 --- a/app/builtin-pages/icon/avatar.js +++ b/app/fg/builtin-pages/icon/avatar.js @@ -1,4 +1,4 @@ -import * as svg from '../../lib/fg/svg' +import * as svg from '../../lib/svg' export default function render () { return svg.render(` diff --git a/app/builtin-pages/icon/box.js b/app/fg/builtin-pages/icon/box.js similarity index 97% rename from app/builtin-pages/icon/box.js rename to app/fg/builtin-pages/icon/box.js index 4a9c5be98a..aa05811e79 100644 --- a/app/builtin-pages/icon/box.js +++ b/app/fg/builtin-pages/icon/box.js @@ -1,4 +1,4 @@ -import * as svg from '../../lib/fg/svg' +import * as svg from '../../lib/svg' export default function render () { return svg.render(` diff --git a/app/builtin-pages/icon/close.js b/app/fg/builtin-pages/icon/close.js similarity index 92% rename from app/builtin-pages/icon/close.js rename to app/fg/builtin-pages/icon/close.js index 1d4e6fd4bf..31557ef8a3 100644 --- a/app/builtin-pages/icon/close.js +++ b/app/fg/builtin-pages/icon/close.js @@ -1,4 +1,4 @@ -import * as svg from '../../lib/fg/svg' +import * as svg from '../../lib/svg' export default function render () { return svg.render(` diff --git a/app/builtin-pages/icon/file-o.js b/app/fg/builtin-pages/icon/file-o.js similarity index 97% rename from app/builtin-pages/icon/file-o.js rename to app/fg/builtin-pages/icon/file-o.js index 99fb7a8683..765446bc66 100644 --- a/app/builtin-pages/icon/file-o.js +++ b/app/fg/builtin-pages/icon/file-o.js @@ -1,4 +1,4 @@ -import * as svg from '../../lib/fg/svg' +import * as svg from '../../lib/svg' export default function render () { return svg.render(` diff --git a/app/builtin-pages/icon/file-text-o.js b/app/fg/builtin-pages/icon/file-text-o.js similarity index 97% rename from app/builtin-pages/icon/file-text-o.js rename to app/fg/builtin-pages/icon/file-text-o.js index 7cfad2b50f..7c250f1f3a 100644 --- a/app/builtin-pages/icon/file-text-o.js +++ b/app/fg/builtin-pages/icon/file-text-o.js @@ -1,4 +1,4 @@ -import * as svg from '../../lib/fg/svg' +import * as svg from '../../lib/svg' export default function render () { return svg.render(` diff --git a/app/builtin-pages/icon/filesystem.js b/app/fg/builtin-pages/icon/filesystem.js similarity index 95% rename from app/builtin-pages/icon/filesystem.js rename to app/fg/builtin-pages/icon/filesystem.js index 162c0d96ff..c078319741 100644 --- a/app/builtin-pages/icon/filesystem.js +++ b/app/fg/builtin-pages/icon/filesystem.js @@ -1,4 +1,4 @@ -import * as svg from '../../lib/fg/svg' +import * as svg from '../../lib/svg' export default function render () { return svg.render(` diff --git a/app/builtin-pages/icon/folder-color.js b/app/fg/builtin-pages/icon/folder-color.js similarity index 98% rename from app/builtin-pages/icon/folder-color.js rename to app/fg/builtin-pages/icon/folder-color.js index 6b83690533..986c828aa9 100644 --- a/app/builtin-pages/icon/folder-color.js +++ b/app/fg/builtin-pages/icon/folder-color.js @@ -1,4 +1,4 @@ -import * as svg from '../../lib/fg/svg' +import * as svg from '../../lib/svg' export default function render () { return svg.render(` diff --git a/app/builtin-pages/icon/folder.js b/app/fg/builtin-pages/icon/folder.js similarity index 95% rename from app/builtin-pages/icon/folder.js rename to app/fg/builtin-pages/icon/folder.js index 0f495d65f6..73ed983ee6 100644 --- a/app/builtin-pages/icon/folder.js +++ b/app/fg/builtin-pages/icon/folder.js @@ -1,4 +1,4 @@ -import * as svg from '../../lib/fg/svg' +import * as svg from '../../lib/svg' export default function render () { return svg.render(` diff --git a/app/builtin-pages/icon/gear-small.js b/app/fg/builtin-pages/icon/gear-small.js similarity index 97% rename from app/builtin-pages/icon/gear-small.js rename to app/fg/builtin-pages/icon/gear-small.js index 3a5ec19156..e57f75c689 100644 --- a/app/builtin-pages/icon/gear-small.js +++ b/app/fg/builtin-pages/icon/gear-small.js @@ -1,5 +1,4 @@ -import * as yo from 'yo-yo' -import * as svg from '../../lib/fg/svg' +import * as svg from '../../lib/svg' export default function render () { return svg.render(` diff --git a/app/builtin-pages/icon/gear.js b/app/fg/builtin-pages/icon/gear.js similarity index 96% rename from app/builtin-pages/icon/gear.js rename to app/fg/builtin-pages/icon/gear.js index 45131c569a..20cd3140ac 100644 --- a/app/builtin-pages/icon/gear.js +++ b/app/fg/builtin-pages/icon/gear.js @@ -1,4 +1,4 @@ -import * as svg from '../../lib/fg/svg' +import * as svg from '../../lib/svg' export default function render () { return svg.render(` diff --git a/app/builtin-pages/icon/globe.js b/app/fg/builtin-pages/icon/globe.js similarity index 99% rename from app/builtin-pages/icon/globe.js rename to app/fg/builtin-pages/icon/globe.js index 7516b9908a..a8a4085382 100644 --- a/app/builtin-pages/icon/globe.js +++ b/app/fg/builtin-pages/icon/globe.js @@ -1,4 +1,4 @@ -import * as svg from '../../lib/fg/svg' +import * as svg from '../../lib/svg' export default function render () { return svg.render(` diff --git a/app/builtin-pages/icon/grid.js b/app/fg/builtin-pages/icon/grid.js similarity index 95% rename from app/builtin-pages/icon/grid.js rename to app/fg/builtin-pages/icon/grid.js index 42444c69e0..e64644cb9e 100644 --- a/app/builtin-pages/icon/grid.js +++ b/app/fg/builtin-pages/icon/grid.js @@ -1,4 +1,4 @@ -import * as svg from '../../lib/fg/svg' +import * as svg from '../../lib/svg' export default function render () { return svg.render(` diff --git a/app/builtin-pages/icon/heart.js b/app/fg/builtin-pages/icon/heart.js similarity index 96% rename from app/builtin-pages/icon/heart.js rename to app/fg/builtin-pages/icon/heart.js index 765d69301e..f46be1ee6e 100644 --- a/app/builtin-pages/icon/heart.js +++ b/app/fg/builtin-pages/icon/heart.js @@ -1,4 +1,4 @@ -import * as svg from '../../lib/fg/svg' +import * as svg from '../../lib/svg' export default function render () { return svg.render(` diff --git a/app/builtin-pages/icon/history.js b/app/fg/builtin-pages/icon/history.js similarity index 94% rename from app/builtin-pages/icon/history.js rename to app/fg/builtin-pages/icon/history.js index 8f464146fa..af729e5ce0 100644 --- a/app/builtin-pages/icon/history.js +++ b/app/fg/builtin-pages/icon/history.js @@ -1,4 +1,4 @@ -import * as svg from '../../lib/fg/svg' +import * as svg from '../../lib/svg' export default function render () { return svg.render(` diff --git a/app/builtin-pages/icon/home-grayscale.js b/app/fg/builtin-pages/icon/home-grayscale.js similarity index 98% rename from app/builtin-pages/icon/home-grayscale.js rename to app/fg/builtin-pages/icon/home-grayscale.js index 6624b3b8f5..284de868b6 100644 --- a/app/builtin-pages/icon/home-grayscale.js +++ b/app/fg/builtin-pages/icon/home-grayscale.js @@ -1,4 +1,4 @@ -import * as svg from '../../lib/fg/svg' +import * as svg from '../../lib/svg' export default function render () { return svg.render(` diff --git a/app/builtin-pages/icon/home.js b/app/fg/builtin-pages/icon/home.js similarity index 99% rename from app/builtin-pages/icon/home.js rename to app/fg/builtin-pages/icon/home.js index 6bf179a48d..557743244f 100644 --- a/app/builtin-pages/icon/home.js +++ b/app/fg/builtin-pages/icon/home.js @@ -1,4 +1,4 @@ -import * as svg from '../../lib/fg/svg' +import * as svg from '../../lib/svg' export default function render () { return svg.render(` diff --git a/app/builtin-pages/icon/list-expanded.js b/app/fg/builtin-pages/icon/list-expanded.js similarity index 94% rename from app/builtin-pages/icon/list-expanded.js rename to app/fg/builtin-pages/icon/list-expanded.js index 4abba48082..3aff69f68d 100644 --- a/app/builtin-pages/icon/list-expanded.js +++ b/app/fg/builtin-pages/icon/list-expanded.js @@ -1,4 +1,4 @@ -import * as svg from '../../lib/fg/svg' +import * as svg from '../../lib/svg' export default function render () { return svg.render(` diff --git a/app/builtin-pages/icon/list.js b/app/fg/builtin-pages/icon/list.js similarity index 95% rename from app/builtin-pages/icon/list.js rename to app/fg/builtin-pages/icon/list.js index 28be94021e..52c4269564 100644 --- a/app/builtin-pages/icon/list.js +++ b/app/fg/builtin-pages/icon/list.js @@ -1,4 +1,4 @@ -import * as svg from '../../lib/fg/svg' +import * as svg from '../../lib/svg' export default function render () { return svg.render(` diff --git a/app/builtin-pages/icon/network.js b/app/fg/builtin-pages/icon/network.js similarity index 96% rename from app/builtin-pages/icon/network.js rename to app/fg/builtin-pages/icon/network.js index 4f6c0e2866..fb043e1a8e 100644 --- a/app/builtin-pages/icon/network.js +++ b/app/fg/builtin-pages/icon/network.js @@ -1,4 +1,4 @@ -import * as svg from '../../lib/fg/svg' +import * as svg from '../../lib/svg' export default function render () { return svg.render(` diff --git a/app/builtin-pages/icon/news.js b/app/fg/builtin-pages/icon/news.js similarity index 95% rename from app/builtin-pages/icon/news.js rename to app/fg/builtin-pages/icon/news.js index 5854335b79..0bd8463e68 100644 --- a/app/builtin-pages/icon/news.js +++ b/app/fg/builtin-pages/icon/news.js @@ -1,4 +1,4 @@ -import * as svg from '../../lib/fg/svg' +import * as svg from '../../lib/svg' export default function render () { return svg.render(` diff --git a/app/builtin-pages/icon/padlock.js b/app/fg/builtin-pages/icon/padlock.js similarity index 95% rename from app/builtin-pages/icon/padlock.js rename to app/fg/builtin-pages/icon/padlock.js index 22edc66968..0cd8554266 100644 --- a/app/builtin-pages/icon/padlock.js +++ b/app/fg/builtin-pages/icon/padlock.js @@ -1,4 +1,4 @@ -import * as svg from '../../lib/fg/svg' +import * as svg from '../../lib/svg' export default function render () { return svg.render(` diff --git a/app/builtin-pages/icon/pencil.js b/app/fg/builtin-pages/icon/pencil.js similarity index 97% rename from app/builtin-pages/icon/pencil.js rename to app/fg/builtin-pages/icon/pencil.js index 9e21695ef0..4252899f5a 100644 --- a/app/builtin-pages/icon/pencil.js +++ b/app/fg/builtin-pages/icon/pencil.js @@ -1,4 +1,4 @@ -import * as svg from '../../lib/fg/svg' +import * as svg from '../../lib/svg' export default function render () { return svg.render(` diff --git a/app/builtin-pages/icon/photos.js b/app/fg/builtin-pages/icon/photos.js similarity index 99% rename from app/builtin-pages/icon/photos.js rename to app/fg/builtin-pages/icon/photos.js index ae4b97fe33..d2ca76d595 100644 --- a/app/builtin-pages/icon/photos.js +++ b/app/fg/builtin-pages/icon/photos.js @@ -1,4 +1,4 @@ -import * as svg from '../../lib/fg/svg' +import * as svg from '../../lib/svg' export default function render () { return svg.render(` diff --git a/app/builtin-pages/icon/pin.js b/app/fg/builtin-pages/icon/pin.js similarity index 97% rename from app/builtin-pages/icon/pin.js rename to app/fg/builtin-pages/icon/pin.js index 9ad8f139f5..3dfdc1f80a 100644 --- a/app/builtin-pages/icon/pin.js +++ b/app/fg/builtin-pages/icon/pin.js @@ -1,4 +1,4 @@ -import * as svg from '../../lib/fg/svg' +import * as svg from '../../lib/svg' export default function render () { return svg.render(` diff --git a/app/builtin-pages/icon/replies.js b/app/fg/builtin-pages/icon/replies.js similarity index 96% rename from app/builtin-pages/icon/replies.js rename to app/fg/builtin-pages/icon/replies.js index 89a0508ca0..febc640ce3 100644 --- a/app/builtin-pages/icon/replies.js +++ b/app/fg/builtin-pages/icon/replies.js @@ -1,4 +1,4 @@ -import * as svg from '../../lib/fg/svg' +import * as svg from '../../lib/svg' export default function render () { return svg.render(` diff --git a/app/builtin-pages/icon/restore.js b/app/fg/builtin-pages/icon/restore.js similarity index 95% rename from app/builtin-pages/icon/restore.js rename to app/fg/builtin-pages/icon/restore.js index 91929c02e3..257f42bfd0 100644 --- a/app/builtin-pages/icon/restore.js +++ b/app/fg/builtin-pages/icon/restore.js @@ -1,4 +1,4 @@ -import * as svg from '../../lib/fg/svg' +import * as svg from '../../lib/svg' export default function render () { return svg.render(` diff --git a/app/builtin-pages/icon/rss.js b/app/fg/builtin-pages/icon/rss.js similarity index 98% rename from app/builtin-pages/icon/rss.js rename to app/fg/builtin-pages/icon/rss.js index 3fda1198ec..3a215f3894 100644 --- a/app/builtin-pages/icon/rss.js +++ b/app/fg/builtin-pages/icon/rss.js @@ -1,4 +1,4 @@ -import * as svg from '../../lib/fg/svg' +import * as svg from '../../lib/svg' export default function render () { return svg.render(` diff --git a/app/builtin-pages/icon/search.js b/app/fg/builtin-pages/icon/search.js similarity index 96% rename from app/builtin-pages/icon/search.js rename to app/fg/builtin-pages/icon/search.js index 000b76d0f9..51427d36ed 100644 --- a/app/builtin-pages/icon/search.js +++ b/app/fg/builtin-pages/icon/search.js @@ -1,4 +1,4 @@ -import * as svg from '../../lib/fg/svg' +import * as svg from '../../lib/svg' export default function render () { return svg.render(` diff --git a/app/builtin-pages/icon/star-fill.js b/app/fg/builtin-pages/icon/star-fill.js similarity index 94% rename from app/builtin-pages/icon/star-fill.js rename to app/fg/builtin-pages/icon/star-fill.js index b9f34b0ad1..6d8329e2c9 100644 --- a/app/builtin-pages/icon/star-fill.js +++ b/app/fg/builtin-pages/icon/star-fill.js @@ -1,4 +1,4 @@ -import * as svg from '../../lib/fg/svg' +import * as svg from '../../lib/svg' export default function render () { return svg.render(` diff --git a/app/builtin-pages/icon/star.js b/app/fg/builtin-pages/icon/star.js similarity index 94% rename from app/builtin-pages/icon/star.js rename to app/fg/builtin-pages/icon/star.js index 13efb2a5b9..ae87c9cce6 100644 --- a/app/builtin-pages/icon/star.js +++ b/app/fg/builtin-pages/icon/star.js @@ -1,4 +1,4 @@ -import * as svg from '../../lib/fg/svg' +import * as svg from '../../lib/svg' export default function render () { return svg.render(` diff --git a/app/builtin-pages/icon/three-dots.js b/app/fg/builtin-pages/icon/three-dots.js similarity index 93% rename from app/builtin-pages/icon/three-dots.js rename to app/fg/builtin-pages/icon/three-dots.js index 305f80ddf9..80ac7568fa 100644 --- a/app/builtin-pages/icon/three-dots.js +++ b/app/fg/builtin-pages/icon/three-dots.js @@ -1,4 +1,4 @@ -import * as svg from '../../lib/fg/svg' +import * as svg from '../../lib/svg' export default function render () { return svg.render(` diff --git a/app/builtin-pages/icon/trash-grayscale.js b/app/fg/builtin-pages/icon/trash-grayscale.js similarity index 96% rename from app/builtin-pages/icon/trash-grayscale.js rename to app/fg/builtin-pages/icon/trash-grayscale.js index 266794254c..88eb318a5f 100644 --- a/app/builtin-pages/icon/trash-grayscale.js +++ b/app/fg/builtin-pages/icon/trash-grayscale.js @@ -1,4 +1,4 @@ -import * as svg from '../../lib/fg/svg' +import * as svg from '../../lib/svg' export default function render () { return svg.render(` diff --git a/app/builtin-pages/icon/trash.js b/app/fg/builtin-pages/icon/trash.js similarity index 95% rename from app/builtin-pages/icon/trash.js rename to app/fg/builtin-pages/icon/trash.js index 637d143ba7..6d90b73b2d 100644 --- a/app/builtin-pages/icon/trash.js +++ b/app/fg/builtin-pages/icon/trash.js @@ -1,4 +1,4 @@ -import * as svg from '../../lib/fg/svg' +import * as svg from '../../lib/svg' export default function render () { return svg.render(` diff --git a/app/builtin-pages/icon/videos.js b/app/fg/builtin-pages/icon/videos.js similarity index 99% rename from app/builtin-pages/icon/videos.js rename to app/fg/builtin-pages/icon/videos.js index c20bb89c18..a27b4c7f31 100644 --- a/app/builtin-pages/icon/videos.js +++ b/app/fg/builtin-pages/icon/videos.js @@ -1,4 +1,4 @@ -import * as svg from '../../lib/fg/svg' +import * as svg from '../../lib/svg' export default function render () { return svg.render(` diff --git a/app/builtin-pages/settings.html b/app/fg/builtin-pages/settings.html similarity index 100% rename from app/builtin-pages/settings.html rename to app/fg/builtin-pages/settings.html diff --git a/app/stylesheets/base/colors.less b/app/fg/builtin-pages/stylesheets/base/colors.less similarity index 100% rename from app/stylesheets/base/colors.less rename to app/fg/builtin-pages/stylesheets/base/colors.less diff --git a/app/stylesheets/base/reset.less b/app/fg/builtin-pages/stylesheets/base/reset.less similarity index 100% rename from app/stylesheets/base/reset.less rename to app/fg/builtin-pages/stylesheets/base/reset.less diff --git a/app/stylesheets/base/spacing.less b/app/fg/builtin-pages/stylesheets/base/spacing.less similarity index 100% rename from app/stylesheets/base/spacing.less rename to app/fg/builtin-pages/stylesheets/base/spacing.less diff --git a/app/stylesheets/base/typography.less b/app/fg/builtin-pages/stylesheets/base/typography.less similarity index 100% rename from app/stylesheets/base/typography.less rename to app/fg/builtin-pages/stylesheets/base/typography.less diff --git a/app/fg/builtin-pages/stylesheets/builtin-pages.css b/app/fg/builtin-pages/stylesheets/builtin-pages.css new file mode 100644 index 0000000000..1c0b4f5ac8 --- /dev/null +++ b/app/fg/builtin-pages/stylesheets/builtin-pages.css @@ -0,0 +1,4368 @@ +@charset "UTF-8"; +/*! + * ===================================================== + * Photon v0.1.1 + * Copyright 2015 Connor Sears + * Licensed under MIT (https://github.com/connors/proton/blob/master/LICENSE) + * + * v0.1.1 designed by @connors. + * + * MODIFIED by prf!! (some stuff stripped out, and added) + * ===================================================== + */ +audio, +canvas, +progress, +sub, +sup, +video { + vertical-align: baseline; +} +body, +html { + height: 100%; +} +hr, +html, +label { + overflow: hidden; +} +audio:not([controls]) { + display: none; +} +a:active, +a:hover { + outline: 0; +} +abbr[title] { + border-bottom: 1px dotted; +} +b, +strong { + font-weight: 700; +} +dfn { + font-style: italic; +} +h1 { + margin: 0.67em 0; + font-size: 36px; +} +small { + font-size: 80%; +} +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; +} +sup { + top: -0.5em; +} +.pane-group, +.window { + top: 0; + left: 0; + right: 0; +} +sub { + bottom: -0.25em; +} +pre { + overflow: auto; +} +code, +kbd, +pre, +samp { + font-family: monospace,monospace; + font-size: 1em; +} +button, +input, +optgroup, +select, +textarea { + color: inherit; + font: inherit; + margin: 0; +} +input[type=number]::-webkit-inner-spin-button, +input[type=number]::-webkit-outer-spin-button { + height: auto; +} +input[type=search]::-webkit-search-cancel-button, +input[type=search]::-webkit-search-decoration { + -webkit-appearance: none; +} +fieldset { + border: 1px solid silver; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} +legend { + border: 0; + padding: 0; +} +* { + -webkit-box-sizing: border-box; + box-sizing: border-box; +} +html { + width: 100%; +} +body { + padding: 0; + margin: 0; + font-family: system, -apple-system, ".SFNSDisplay-Regular", "Helvetica Neue", Helvetica, "Segoe UI", sans-serif; + font-size: 13px; + line-height: 1.6; + color: #333; + background-color: transparent; +} +hr { + margin: 15px 0; + background: 0 0; + border: 0; + border-bottom: 1px solid #ddd; +} +h1, +h2, +h3, +h4, +h5, +h6 { + margin-top: 20px; + margin-bottom: 10px; + font-weight: 500; +} +h2 { + font-size: 30px; +} +h3 { + font-size: 24px; +} +h4 { + font-size: 18px; +} +h5 { + font-size: 14px; +} +h6 { + font-size: 12px; +} +.window { + position: absolute; + bottom: 0; + display: flex; + flex-direction: column; + background-color: #fff; +} +.window-content { + position: relative; + overflow-y: auto; + display: flex; + flex: 1; +} +label { + margin-bottom: 5px; +} +input[type=search] { + -webkit-appearance: textfield; + box-sizing: border-box; +} +input[type=checkbox], +input[type=radio] { + margin: 4px 0 0; + line-height: normal; +} +table { + border-spacing: 0; + width: 100%; + border: 0; + border-collapse: separate; + text-align: left; +} +thead { + background-color: #f5f5f4; +} +tbody { + background-color: #fff; +} +td, +th { + padding: 2px 15px; +} +td:last-child, +th:last-child { + border-right: 0; +} +.flex-spacer { + flex: 1; +} +.system-font, +body, +.window-content.builtin { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Ubuntu, Cantarell, "Oxygen Sans", "Helvetica Neue", sans-serif; +} +.code-font, +code, +#share-popup .popup-inner input, +#library-localsyncpath-popup .path-container .path, +#editor-import-popup .changes, +.editor-home .readme pre, +.editor-home .readme code, +.path-container .path { + font-family: Consolas, 'Lucida Console', Monaco, monospace; +} +code { + font-style: normal !important; +} +.red-x { + position: relative; +} +.red-x:after { + content: 'x'; + display: block; + position: absolute; + top: -4px; + left: 1px; + font-family: sans-serif; + font-size: 13px; + font-weight: bold; + color: red; + -webkit-text-stroke: 1px #fff; +} +.red-x.fa-rss:after { + width: 14px; +} +*, +*:before, +*:after { + box-sizing: border-box; +} +body { + margin: 0; +} +ul, +ol { + list-style: none; + padding: 0; + margin: 0; +} +a { + text-decoration: none; + color: inherit; +} +a[href], +input[type='submit'], +input[type='image'], +label[for], +select, +button { + cursor: pointer; +} +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0; +} +button { + background: none; + outline-color: transparent; + border: none; +} +.flex, +.btn-bar, +.toggle, +.builtin-wrapper.builtin-wrapper.downloads-wrapper .ll-row.download .metadata, +.dat-cache .heading, +.dat-cache .ll-row.archive .buttons, +.builtin-wrapper.settings-wrapper .dat-bandwidth .inputs, +.message, +#library-localsyncpath-popup .path-container, +.rehost-slider .labels, +.drag-hint .icons, +.revision-header, +.files-browser-header, +.archive-history .archive-history-item, +.dropdown.share p.copy-url, +.ll-row, +.autocomplete-result, +.crawler-status .heading, +.builtin-wrapper, +.builtin-wrapper .subtitle-heading, +.builtin-wrapper .lined-heading, +.builtin-wrapper .ll-column-headings, +.builtin-wrapper .ll-row, +.builtin-wrapper .ll-row .buttons, +.builtin-wrapper .ll-row .link, +.builtin-wrapper .ll-row .actions, +.builtin-main .module .module-heading, +.builtin-main .module .module-footer, +.builtin-main .section-content .input-group, +.builtin-main .module-content .input-group, +.builtin-main .section-content.coming-soon:before, +.builtin-main .module-content.coming-soon:before, +.builtin-main .section-content.coming-soon:after, +.builtin-main .module-content.coming-soon:after, +#builtin-loading-wrapper .loading, +.path-container { + display: flex; + flex-wrap: wrap; + flex-direction: row; +} +.vertical-scroll { + overflow-y: scroll; +} +.shadow { + box-shadow: 0 3px 15px rgba(0, 0, 0, 0.25); +} +.dropdown-shadow, +.dropdown-items { + box-shadow: 0 2px 15px rgba(0, 0, 0, 0.3); +} +.dropdown-shadow-subtle, +.dropdown-items.subtle-shadow { + box-shadow: 0 2px 15px rgba(0, 0, 0, 0.15); +} +.overflow-ellipsis, +.revision-header .path, +.files-browser-header .path, +.archive-history .archive-history-item .path, +.archive-history .archive-history-item .sync-path, +.autocomplete-result .title, +.autocomplete-result .label, +.builtin-wrapper .ll-row .title, +.builtin-wrapper .ll-row .url { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.hidden { + display: none !important; +} +.btn { + display: inline-block; + padding: 5px 10px; + border: 1px solid #ddd; + background: #fafafa; + color: #333; + border-radius: 3px; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); + outline: 0; + line-height: 1.3; + cursor: default !important; +} +.btn * { + cursor: default !important; +} +.btn.white { + background: #fff; +} +.btn.pressed { + box-shadow: inset 0px 0 5px rgba(0, 0, 0, 0.1); + background: linear-gradient(to top, #ddd, #ccc); +} +.btn.pressed:hover { + box-shadow: inset 0px 0 2px rgba(0, 0, 0, 0.1); + background: linear-gradient(to top, #ddd, #ccc); + cursor: default; +} +.btn:hover { + text-decoration: none; +} +.btn[disabled="disabled"], +.btn.disabled, +.btn:disabled { + background: #fafafa !important; + color: rgba(0, 0, 0, 0.4) !important; + border: 1px solid #eee !important; + font-weight: 400 !important; + box-shadow: none !important; + -webkit-font-smoothing: initial !important; +} +.btn[disabled="disabled"] .spinner, +.btn.disabled .spinner, +.btn:disabled .spinner { + color: #aaa !important; +} +.btn[disabled="disabled"]:hover, +.btn.disabled:hover, +.btn:disabled:hover { + background: #fafafa; +} +.btn .spinner { + display: inline-block; + position: relative; + top: 1px; + color: inherit; +} +.btn.warning { + color: #fff; + background: #cc2f26; + border-color: #99231d; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1); +} +.btn.warning.pressed, +.btn.warning:hover { + background: #c42d25; + border-color: #c42d25; +} +.btn.success { + background: #41bb56; + color: #fff; + border-color: #2e833c; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1); +} +.btn.success.pressed, +.btn.success:hover { + background: #3baa4e; + border-color: #3baa4e; +} +.btn.transparent { + border-color: transparent; + background: none; + font-weight: 400; + box-shadow: none; +} +.btn.transparent:hover { + background: rgba(0, 0, 0, 0.075); + color: #424242; +} +.btn.transparent.disabled { + border-color: transparent !important; + background: none !important; +} +.btn.transparent.pressed { + background: linear-gradient(to top, #f5f3f3, #ececec); + border-color: #dadada; +} +.btn.primary { + background: #5289f7; + border-color: #275fce; + color: #fff; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1); +} +.btn.btn-split-group { + height: auto; + padding: 0; +} +.btn.btn-split-group .btn-split-section { + height: 28px; + padding: 0 10px; + border-right: 1px solid #ddd; +} +.btn.btn-split-group .btn-split-section:last-child { + border-right: 0; +} +.btn-group { + display: inline-flex; +} +.btn-group .btn { + margin: 0 !important; + border-radius: 0; + border-right-width: 0; +} +.btn-group > :first-child { + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; + margin-right: 0; +} +.btn-group > :last-child { + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; + border-right-width: 1px; + margin-left: 0; +} +.dropdown-btn-container { + display: inline-block; + position: relative; +} +.dropdown-btn-container.open .dropdown-btn-list { + display: block; +} +.dropdown-btn-container.open .toggleable.btn { + background: #dadada; + box-shadow: inset 0px 1px 0px rgba(0, 0, 0, 0.07); +} +.dropdown-btn-list { + display: none; + position: absolute; + z-index: 1; + top: 24px; + right: 0; + background: #fff; + border: 1px solid #d9d9d9; + width: 149px; + box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.1); + border-radius: 2px; + padding: 2px 0; +} +.dropdown-btn-list * { + cursor: pointer; +} +.dropdown-btn-list hr { + margin: 2px 0; +} +.dropdown-btn-list .dropdown-item, +.dropdown-btn-list > div, +.dropdown-btn-list > a { + width: 100%; + display: block; + padding: 7px 7px; + color: #333; +} +.dropdown-btn-list .dropdown-item i, +.dropdown-btn-list > div i, +.dropdown-btn-list > a i { + width: 15px; + display: inline-block; + text-align: center; +} +.dropdown-btn-list .dropdown-item.disabled, +.dropdown-btn-list > div.disabled, +.dropdown-btn-list > a.disabled { + color: #b8b8b8; +} +.dropdown-btn-list .dropdown-item:hover, +.dropdown-btn-list > div:hover, +.dropdown-btn-list > a:hover { + text-decoration: none; +} +.dropdown-btn-list .dropdown-item:hover:not(.disabled), +.dropdown-btn-list > div:hover:not(.disabled), +.dropdown-btn-list > a:hover:not(.disabled) { + background: #2864dc; + color: #fff; +} +.dropdown-btn-list > div, +.dropdown-btn-list .dropdown-item { + padding: 8px 15px; + border-bottom: 1px solid #d9d9d9; +} +.dropdown-btn-list > div:last-child, +.dropdown-btn-list .dropdown-item:last-child { + border: 0; +} +.dropdown-btn-list > div .title, +.dropdown-btn-list .dropdown-item .title { + font-weight: 600; + margin-bottom: 5px; +} +.dropdown-btn-list > div .title i, +.dropdown-btn-list .dropdown-item .title i { + margin-right: 2px; +} +.dropdown-btn-list > div:hover .desc, +.dropdown-btn-list .dropdown-item:hover .desc { + color: #fff; +} +.btn-bar .btn { + border-radius: 0 !important; +} +.btn-bar .btn:first-child { + border-radius: 2px 0 0 2px !important; +} +.btn-bar .btn:last-child { + border-radius: 0 2px 2px 0 !important; +} +.btn-bar .btn:not(:first-child) { + border-left: 0; +} +a.btn span { + vertical-align: baseline; +} +.btn.nofocus:focus, +button.nofocus:focus { + outline: 0; + box-shadow: none; +} +.toolbar { + position: relative; +} +.toolbar .btn, +.toolbar .btn-group { + margin-right: 10px; +} +.toolbar .btn { + background: #fff; +} +.toolbar .btn:hover { + background: #eee; +} +.spinner { + display: inline-block; + height: 14px; + width: 14px; + animation: rotate 1s infinite linear; + color: #aaa; + border: 1.5px solid; + border-right-color: transparent; + border-radius: 50%; + transition: color 0.25s; +} +.spinner.reverse { + animation: rotate 2s infinite linear reverse; +} +@keyframes rotate { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} +textarea { + line-height: 1.4; +} +input, +textarea { + height: 30px; + padding: 0 7px; + border-radius: 4px; + color: rgba(51, 51, 51, 0.95); + border: 1px solid #d9d9d9; +} +input[type="checkbox"], +textarea[type="checkbox"], +input[type="radio"], +textarea[type="radio"], +input[type="range"], +textarea[type="range"] { + padding: 0; +} +input[type="checkbox"]:focus, +textarea[type="checkbox"]:focus, +input[type="radio"]:focus, +textarea[type="radio"]:focus, +input[type="range"]:focus, +textarea[type="range"]:focus { + box-shadow: none; +} +input[type="radio"], +textarea[type="radio"] { + width: 14px; + height: 14px; + outline: none; + -webkit-appearance: none; + border-radius: 50%; + cursor: pointer; + transition: border 0.1s ease; +} +input[type="radio"]:hover, +textarea[type="radio"]:hover { + border: 1px solid #2864dc; +} +input[type="radio"]:checked, +textarea[type="radio"]:checked { + border: 4.5px solid #2864dc; +} +input[type="file"], +textarea[type="file"] { + padding: 0; + border: 0; + line-height: 1; +} +input[type="file"]:focus, +textarea[type="file"]:focus { + border: 0; + box-shadow: none; +} +input.roomy, +textarea.roomy, +#share-popup .popup-inner input { + padding: 7px 9px; +} +input:focus, +textarea:focus, +select:focus { + outline: 0; + border: 1px solid rgba(41, 95, 203, 0.8); + box-shadow: 0 0 0 2px rgba(41, 95, 203, 0.2); +} +input.error, +textarea.error, +select.error { + border: 1px solid rgba(209, 48, 39, 0.75); +} +input.error:focus, +textarea.error:focus, +select.error:focus { + box-shadow: 0 0 0 2px rgba(204, 47, 38, 0.15); +} +input.nofocus:focus, +textarea.nofocus:focus, +select.nofocus:focus { + outline: 0; + box-shadow: none; + border: initial; +} +input.inline { + height: auto; + border: 1px solid transparent; + border-radius: 0; + background: transparent; + cursor: text; + padding: 3px 5px; + line-height: 1; +} +input.inline:focus, +input.inline:hover { + border: 1px solid #ccc; + box-shadow: none; +} +input.inline:focus { + background: #fff; +} +input.underline { + width: 100%; + height: auto; + padding: 0; + border: 0; + border-radius: 0; + background: transparent; + cursor: text; + line-height: 1; +} +.input-file-picker { + display: flex; + align-items: center; + padding: 3px; + border-radius: 2px; + border: 1px solid #d9d9d9; + color: #707070; +} +.input-file-picker span { + flex: 1; + padding-left: 3px; +} +::-webkit-input-placeholder { + color: rgba(0, 0, 0, 0.5); + font-size: 0.8rem; +} +label { + font-weight: 500; +} +input[disabled][data-tooltip], +label[disabled][data-tooltip] { + cursor: help; +} +input[disabled][data-tooltip] *, +label[disabled][data-tooltip] * { + cursor: help; +} +.toggle { + align-items: center; + flex-direction: row-reverse; + justify-content: space-between; + margin-bottom: 10px; + cursor: pointer; + overflow: initial; +} +.toggle.unweirded { + flex-direction: row; + justify-content: initial; +} +.toggle.unweirded .switch { + margin-right: 10px; +} +.toggle * { + cursor: pointer; +} +.toggle.disabled { + cursor: default; +} +.toggle.disabled * { + cursor: default; +} +.toggle input { + display: none; +} +.toggle .text { + font-weight: 400; +} +.toggle .switch { + display: inline-block; + position: relative; + width: 32px; + height: 17px; +} +.toggle .switch:before, +.toggle .switch:after { + position: absolute; + display: block; + content: ''; +} +.toggle .switch:before { + width: 100%; + height: 100%; + border-radius: 40px; + background: #dadada; +} +.toggle .switch:after { + width: 11px; + height: 11px; + border-radius: 50%; + left: 3px; + top: 3px; + background: #fafafa; + transition: transform 0.15s ease; +} +.toggle input:checked:not(:disabled) + .switch:before { + background: #41b855; +} +.toggle input:checked:not(:disabled) + .switch:after { + transform: translateX(15px); +} +.toggle.disabled { + color: #b8b8b8; +} +label.checkbox-container { + display: flex; + align-items: center; + height: 15px; + font-weight: 400; +} +label.checkbox-container input[type="checkbox"] { + width: 15px; + height: 15px; + margin: 0 5px 0 0; +} +.modal-wrapper { + position: fixed; + z-index: 1; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.65); +} +.modal-wrapper.modal-wrapper-light { + background: rgba(0, 0, 0, 0.15); +} +.modal { + padding: 20px; + margin: 100px auto 0 auto; + background: #fff; + border-radius: 2px; +} +.modal h1.title { + font-size: 16px; + border-bottom: 1px solid #eee; + font-weight: 500; +} +.modal .help-text { + color: #707070; + color: rgba(0, 0, 0, 0.6); + font-style: italic; +} +.modal .footnote { + position: fixed; + bottom: 50px; + width: 90%; +} +.modal form { + padding-top: 10px; +} +.modal form textarea, +.modal form input, +.modal form .input { + display: block; + width: 100%; + margin: 5px 0 15px 0; +} +.modal form textarea { + resize: none; + padding: 7px; + height: 55px; +} +.modal .form-actions { + text-align: right; +} +.modal .fork-dat-progress { + border: 1px solid #ddd; + border-left: 0; + border-right: 0; + padding: 15px 0; + margin: 15px 0; +} +.modal .fork-dat-progress progress { + display: block; + width: 100%; +} +.template-selector .template-selector-title { + padding: 5px 10px 15px; + font-weight: 500; + font-size: 11px; +} +.template-selector .template-selector-grid { + display: grid; + grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr; + padding: 10px 20px; +} +@media (max-width: 1450px) { + .template-selector .template-selector-grid { + grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr; + } +} +@media (max-width: 1300px) { + .template-selector .template-selector-grid { + grid-template-columns: 1fr 1fr 1fr 1fr 1fr; + } +} +@media (max-width: 1100px) { + .template-selector .template-selector-grid { + grid-template-columns: 1fr 1fr 1fr 1fr; + } +} +@media (max-width: 920px) { + .template-selector .template-selector-grid { + grid-template-columns: 1fr 1fr 1fr; + } +} +.template-selector .template-selector-grid .template-item { + cursor: pointer; + width: 150px; + padding: 20px 0; + align-items: center; +} +.template-selector .template-selector-grid .template-item img { + display: block; + width: 40px; + height: 40px; + margin: 0 auto; +} +.template-selector .template-selector-grid .template-item .label { + text-align: center; + padding: 15px 0 0; + font-size: 12px; +} +.template-selector .template-selector-grid .template-item:hover { + background: #eee; +} +.template-selector .template-selector-grid .template-item.disabled { + cursor: default; +} +.template-selector .template-selector-grid .template-item.disabled img, +.template-selector .template-selector-grid .template-item.disabled .label { + opacity: 0.25; +} +.template-selector .template-selector-grid .template-item.disabled:hover { + position: relative; +} +.template-selector .template-selector-grid .template-item.disabled:hover:before { + content: 'Coming soon'; + position: absolute; + left: 0; + top: 35%; + text-align: center; + width: 100%; + color: red; + opacity: 1; + font-weight: 500; +} +.template-selector .template-configuration { + min-width: 400px; + padding: 0 20px; +} +.template-selector .template-configuration .template-form-field { + display: flex; + align-items: center; + margin-bottom: 10px; +} +.template-selector .template-configuration .template-form-field label { + flex: 0 0 100px; +} +.template-selector .template-configuration .template-form-field input { + flex: 1; +} +.template-selector .template-selector-actions { + display: flex; + padding: 10px; +} +.template-selector .template-selector-actions .btn { + margin-left: 10px; +} +.template-selector .template-selector-actions .btn.left { + margin-left: 0; + margin-right: auto; +} +.toast-wrapper { + position: fixed; + top: 20px; + right: 20px; + z-index: 20000; + transition: opacity 0.1s ease; +} +.toast-wrapper.hidden { + opacity: 0; +} +.toast { + position: relative; + min-width: 350px; + max-width: 450px; + background: #ddd; + margin: 0; + padding: 10px 15px; + border-radius: 4px; + font-size: 16px; + color: #fff; + background: rgba(0, 0, 0, 0.75); + -webkit-font-smoothing: antialiased; + font-weight: 600; +} +.toast.error { + padding-left: 38px; +} +.toast.success { + padding-left: 48px; +} +.toast.success:before, +.toast.error:before { + position: absolute; + left: 18px; + top: 5px; + display: block; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Ubuntu, Cantarell, "Oxygen Sans", "Helvetica Neue", sans-serif; + font-size: 22px; + font-weight: bold; +} +.toast.primary { + background: #2864dc; +} +.toast.success { + background: #26b33e; +} +.toast.success:before { + content: '✓'; +} +.toast.error { + background: #c72e25; +} +.toast.error:before { + content: '!'; +} +.toast .toast-btn { + position: absolute; + right: 15px; + color: inherit; + text-decoration: underline; + cursor: pointer; +} +.link, +.editor-home .readme a { + color: #295fcb; + cursor: pointer; + border: none; + outline: none; + background: none; + padding: 0; +} +.link:hover { + text-decoration: underline; +} +.link.disabled { + color: #999999; + cursor: default; +} +.link.disabled:hover { + text-decoration: none; +} +.builtin-wrapper.history-wrapper .ll-row:not(:last-of-type) { + border-bottom: 1px solid #e6e6e6; + border-top: 0; +} +.builtin-wrapper.builtin-wrapper.downloads-wrapper .builtin-main { + max-width: 900px; +} +.builtin-wrapper.builtin-wrapper.downloads-wrapper .ll-row.download .title { + max-width: 375px; +} +.builtin-wrapper.builtin-wrapper.downloads-wrapper .ll-row.download .metadata { + align-items: center; +} +.builtin-wrapper.builtin-wrapper.downloads-wrapper .ll-row.download .metadata > *:not(:first-child) { + margin-left: 5px; +} +.builtin-wrapper.builtin-wrapper.downloads-wrapper .ll-row.download .metadata > * { + margin-right: 3px; +} +.builtin-wrapper.builtin-wrapper.downloads-wrapper .ll-row.download .buttons.controls { + opacity: 1; +} +.builtin-wrapper.builtin-wrapper.downloads-wrapper .ll-row.download .link { + color: #2864dc; + cursor: pointer; +} +.builtin-wrapper.builtin-wrapper.downloads-wrapper .ll-row.download .link:hover { + text-decoration: underline; +} +.builtin-wrapper.builtin-wrapper.downloads-wrapper .ll-row.download .btn.trash { + opacity: 0; +} +.builtin-wrapper.builtin-wrapper.downloads-wrapper .ll-row.download .btn.trash:hover { + color: #d43128; +} +.builtin-wrapper.builtin-wrapper.downloads-wrapper .ll-row.download:hover .btn.trash { + opacity: 1; +} +.builtin-wrapper.builtin-wrapper.downloads-wrapper .ll-row.download.progressing { + height: auto; + padding: 8px 15px; +} +.builtin-wrapper.builtin-wrapper.downloads-wrapper .ll-row.download.progressing .progress-ui { + flex: 1; + margin-right: 20px; +} +.builtin-wrapper.builtin-wrapper.downloads-wrapper .ll-row.download.progressing .controls { + margin-left: 0; + margin-right: 20px; +} +.builtin-wrapper.builtin-wrapper.downloads-wrapper .ll-row.download.progressing .metadata { + margin-left: auto; +} +.dropdown { + position: relative; +} +.dropdown i { + cursor: pointer; +} +.dropdown.open .toggleable:not(.primary) { + background: #dadada; + box-shadow: inset 0 0 3px rgba(0, 0, 0, 0.1); + border-color: transparent; + outline: 0; +} +.toggleable-container .dropdown-items { + display: none; +} +.toggleable-container.hover:hover .dropdown-items, +.toggleable-container.open .dropdown-items { + display: block; +} +.dropdown-items { + width: 270px; + position: absolute; + right: 0px; + z-index: 3000; + background: #fff; + border: 1px solid #dadada; + border-radius: 4px; +} +.dropdown-items .section { + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + padding: 5px 0; +} +.dropdown-items .section-header { + padding: 2px 10px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.dropdown-items .section-header.light { + color: #b8b8b8; + font-weight: 500; +} +.dropdown-items.thin { + width: 170px; +} +.dropdown-items.wide { + width: 400px; +} +.dropdown-items.compact .dropdown-item { + padding: 2px 15px; + border-bottom: 0; +} +.dropdown-items.compact .description { + margin-left: 0; +} +.dropdown-items.compact hr { + margin: 5px 0; +} +.dropdown-items.roomy .dropdown-item { + padding: 10px 15px; +} +.dropdown-items.no-border .dropdown-item { + border-bottom: 0; +} +.dropdown-items.center { + left: 50%; + transform: translateX(-50%); +} +.dropdown-items.left { + right: initial; + left: 0; +} +.dropdown-items.over { + top: 0; +} +.dropdown-items.top { + bottom: calc(100% + 5px); +} +.dropdown-items.with-triangle:before { + content: ''; + position: absolute; + top: -8px; + right: 10px; + width: 12px; + height: 12px; + z-index: 3; + width: 0; + height: 0; + border-left: 8px solid transparent; + border-right: 8px solid transparent; + border-bottom: 8px solid #fff; +} +.dropdown-items.with-triangle.left:before { + left: 10px; +} +.dropdown-items.with-triangle.center:before { + left: 43%; +} +.dropdown-title { + border-bottom: 1px solid #eee; + padding: 2px 8px; + font-size: 11px; + color: gray; +} +.dropdown-item { + display: block; + width: 100%; + padding: 5px 15px; + border-bottom: 1px solid #eee; +} +.dropdown-item.disabled { + opacity: 0.25; +} +.dropdown-item .fa-check-square { + color: #2864dc; +} +.dropdown-item .fa-check-square, +.dropdown-item .fa-square-o { + font-size: 14px; +} +.dropdown-item .fa-check { + font-size: 11.5px; +} +.dropdown-item.no-border { + border-bottom: 0; +} +.dropdown-item:hover:not(.no-hover) { + background: #eee; + cursor: pointer; +} +.dropdown-item:hover:not(.no-hover) i:not(.fa-check-square) { + color: #333; +} +.dropdown-item:hover:not(.no-hover) .description { + color: #333; +} +.dropdown-item:hover:not(.no-hover).disabled { + background: inherit; + cursor: default; +} +.dropdown-item .fa, +.dropdown-item i { + display: inline-block; + width: 20px; + color: rgba(0, 0, 0, 0.65); +} +.dropdown-item img { + display: inline-block; + width: 16px; + position: relative; + top: 3px; + margin-right: 6px; +} +.dropdown-item .btn .fa { + color: inherit; +} +.dropdown-item .label { + font-weight: 500; + margin-bottom: 3px; +} +.dropdown-item .description { + color: #707070; + margin: 0; + margin-left: 23px; + margin-bottom: 3px; + line-height: 1.5; +} +.dropdown-item .description.small { + font-size: 12.5px; +} +.dropdown-item:first-of-type { + border-radius: 2px 2px 0 0; +} +.dropdown-item:last-of-type { + border-radius: 0 0 2px 2px; +} +.context-menu { + position: absolute; + z-index: 10000; +} +.context-menu .dropdown-items:not(.custom) { + width: auto; + white-space: nowrap; +} +.context-menu .dropdown-items:not(.custom) .dropdown-item { + padding-right: 30px; +} +.dat-cache { + margin: 0 -15px; +} +.dat-cache.loading { + margin: 0; +} +.dat-cache .dat-cache-actions { + padding: 0 10px 10px; +} +.dat-cache .heading { + align-items: center; + padding: 6px 15px; + color: #333; + font-size: 11px; +} +.dat-cache .heading .title { + flex: 1; +} +.dat-cache .heading .peers, +.dat-cache .heading .size, +.dat-cache .heading .mtime { + width: 100px; +} +.dat-cache .heading .buttons { + width: 110px; +} +.dat-cache .heading a { + color: #333 !important; + cursor: pointer; +} +.dat-cache .ll-row.archive { + border-right: 0; + border-left: 0; +} +.dat-cache .ll-row.archive .title { + flex: 1; +} +.dat-cache .ll-row.archive .peers, +.dat-cache .ll-row.archive .size, +.dat-cache .ll-row.archive .mtime { + color: rgba(0, 0, 0, 0.5); + flex: 0 0 100px; + white-space: nowrap; + overflow: hidden; +} +.dat-cache .ll-row.archive .buttons { + width: 110px; + overflow: visible; + opacity: 1; +} +.dat-cache .ll-row.archive .buttons .btn { + margin-left: 5px; +} +.dat-cache .ll-row.archive .buttons .btn.copy-url, +.dat-cache .ll-row.archive .buttons .btn.remove-workspace, +.dat-cache .ll-row.archive .buttons .btn.remove-archive { + opacity: 0; + border: 0; + transition: 0.2s ease opacity; + margin-left: -3px; + color: rgba(0, 0, 0, 0.65); +} +.dat-cache .ll-row.archive .buttons .btn.remove-workspace, +.dat-cache .ll-row.archive .buttons .btn.remove-archive { + color: #cc2f26; +} +.logger .controls { + position: sticky; + top: 0; + background: #fff; +} +.logger .standard-controls { + display: flex; + align-items: center; + color: #555; + padding: 6px 0; +} +.logger .standard-controls input { + display: none; +} +.logger .standard-controls .spacer { + flex: 1; +} +.logger .standard-controls .divider { + margin-left: 20px; + margin-right: 5px; + border-right: 1px solid #ccc; + height: 14px; +} +.logger .standard-controls .divider.thin { + margin-left: 5px; +} +.logger .standard-controls label { + margin-left: 15px; + margin-bottom: 0; + font-weight: normal; +} +.logger .standard-controls .status { + margin-left: 8px; +} +.logger .logger-row td { + padding: 4px; + border-top: 1px solid #fff; +} +.logger .logger-row.purple { + background: #fdf3ff; + color: #9C27B0; +} +.logger .logger-row.blue { + background: #e6f4ff; + color: #2196F3; +} +.logger .logger-row.cyan { + background: #e7fcff; + color: #00BCD4; +} +.logger .logger-row.green { + background: #f1f9f1; + color: #4CAF50; +} +.logger .logger-row.yellow { + background: #fff3cc; + color: #FFC107; +} +.logger .logger-row.red { + background: #fddee9; + color: #E91E63; +} +.logger .logger-row:hover { + background: #fff; +} +.logger .logger-row .msg { + color: #111; +} +.logger .logger-row .level, +.logger .logger-row .category, +.logger .logger-row .subcategory, +.logger .logger-row .timestamp { + white-space: nowrap; +} +.logger .logger-row .level { + font-weight: 500; +} +.logger .logger-row small { + color: rgba(0, 0, 0, 0.5); +} +.builtin-wrapper.settings-wrapper ul { + margin-bottom: 1em; +} +.builtin-wrapper.settings-wrapper .builtin-sidebar .builtin-pages-nav { + position: relative; + left: -21px; + font-size: 16px; +} +.builtin-wrapper.settings-wrapper .builtin-sidebar .nav-item { + font-size: 15px; +} +@media (min-width: 1050px) { + .builtin-wrapper.settings-wrapper .builtin-sidebar .nav-item { + margin-bottom: 5px; + } +} +.builtin-wrapper.settings-wrapper .builtin-sidebar .nav-item.active { + font-weight: normal; +} +.builtin-wrapper.settings-wrapper .subtitle-heading { + margin-bottom: 10px; +} +.builtin-wrapper.settings-wrapper h3 { + margin-bottom: 5px; + font-size: inherit; + font-weight: 500; +} +.builtin-wrapper.settings-wrapper .section { + padding: 20px 0; + margin: 0 20px; + border-top: 1px solid #eee; +} +.builtin-wrapper.settings-wrapper .section.default-browser .toggle { + justify-content: flex-end; + width: 100px; +} +.builtin-wrapper.settings-wrapper .section.default-browser .toggle .text { + width: 45px; +} +.builtin-wrapper.settings-wrapper .section.default-to-dat .toggle { + justify-content: flex-end; +} +.builtin-wrapper.settings-wrapper .section.default-to-dat .toggle .text { + margin-right: 10px; +} +.builtin-wrapper.settings-wrapper .section.analytics .toggle { + justify-content: flex-end; +} +.builtin-wrapper.settings-wrapper .section.analytics .toggle .text { + width: 110px; +} +.builtin-wrapper.settings-wrapper .view { + background: none; + border: 0; + padding: 0; +} +.builtin-wrapper.settings-wrapper .view > .section { + padding: 15px 20px; + margin: 0 0 10px; + border: 1px solid #e6e6e6; + background: #fff; +} +.builtin-wrapper.settings-wrapper .section-group { + display: flex; +} +.builtin-wrapper.settings-wrapper .section-group .section { + flex: 1; +} +.builtin-wrapper.settings-wrapper .section-group .section:first-of-type { + margin-right: 0; + border-right: 1px solid #e6e6e6; +} +.builtin-wrapper.settings-wrapper .section-group .section:last-of-type { + margin-left: 0; + padding-left: 20px; +} +.builtin-wrapper.settings-wrapper .auto-updater .spinner { + display: inline-block; +} +.builtin-wrapper.settings-wrapper .auto-updater input { + height: auto; +} +.builtin-wrapper.settings-wrapper .auto-updater .btn { + margin-right: 5px; +} +.builtin-wrapper.settings-wrapper .auto-updater .up-to-date { + color: #3fb453; +} +.builtin-wrapper.settings-wrapper.protocols a { + margin-left: 5px; +} +.builtin-wrapper.settings-wrapper .on-startup .radio-group { + display: grid; + grid-template-columns: auto auto; + align-items: center; + justify-content: start; + grid-gap: 0 10px; + margin-left: 0; +} +.builtin-wrapper.settings-wrapper .on-startup .radio-group input[type="radio"] { + margin: 0; +} +.builtin-wrapper.settings-wrapper .on-startup label { + font-weight: 400; + margin-bottom: 0; +} +.builtin-wrapper.settings-wrapper .dat-bandwidth .inputs > div { + margin-right: 5px; +} +.builtin-wrapper.settings-wrapper .dat-bandwidth .inputs label { + display: block; +} +.builtin-wrapper.settings-wrapper .default-dat-ignore textarea { + width: 500px; + height: 140px; + padding: 4px 7px; +} +.builtin-wrapper.settings-wrapper .dat-cache .title { + max-width: none; +} +.builtin-wrapper.settings-wrapper.help a { + text-decoration: none; +} +.builtin-wrapper.settings-wrapper .default-application { + display: flex; + margin-bottom: 10px; +} +.builtin-wrapper.settings-wrapper .default-application a, +.builtin-wrapper.settings-wrapper .default-application .btn, +.builtin-wrapper.settings-wrapper .default-application .domain { + height: 36px; + line-height: 34px; +} +.builtin-wrapper.settings-wrapper .default-application .group { + display: flex; + margin-right: 10px; +} +.builtin-wrapper.settings-wrapper .default-application .group > * { + border-radius: 0; + border-right-width: 0; + padding: 0 10px; + height: 36px; +} +.builtin-wrapper.settings-wrapper .default-application .group > :first-child { + border-top-left-radius: 2px; + border-bottom-left-radius: 2px; +} +.builtin-wrapper.settings-wrapper .default-application .group > :last-child { + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + border-right-width: 1px; +} +.builtin-wrapper.settings-wrapper .default-application .group > input:focus { + border-right-width: 1px; + box-shadow: none; +} +.builtin-wrapper.settings-wrapper .default-application .group > input:focus + * { + border-left-width: 0; +} +.builtin-wrapper.settings-wrapper .default-application .group .domain { + border: 1px solid #d9d9d9; + border-right: 0; + width: 80px; + color: #666; +} +.builtin-wrapper.settings-wrapper .default-application .group .value { + width: 280px; + text-overflow: ellipsis; + border: 1px solid #d9d9d9; + border-left: 0; + border-right: 0; +} +.builtin-wrapper.settings-wrapper .more-info { + display: flex; + color: rgba(0, 0, 0, 0.6); + margin: 30px 0; +} +.builtin-wrapper.settings-wrapper .more-info i { + margin: 7px 16px; +} +.builtin-wrapper.settings-wrapper .more-info > div { + flex: 1; + max-width: 36em; +} +.builtin-wrapper.settings-wrapper .more-info h4 { + margin-bottom: 10px; +} +.builtin-wrapper.settings-wrapper .user { + display: flex; + padding: 10px; + border-top: 1px solid #ddd; + align-items: center; +} +.builtin-wrapper.settings-wrapper .user:last-child { + border-bottom: 1px solid #ddd; +} +.builtin-wrapper.settings-wrapper .user .user-thumb { + width: 90px; +} +.builtin-wrapper.settings-wrapper .user .user-thumb img { + border-radius: 50%; + width: 70px; + height: 70px; + object-fit: cover; +} +.builtin-wrapper.settings-wrapper .user .user-title { + font-size: 16px; +} +.builtin-wrapper.settings-wrapper .user .user-title small { + color: gray; +} +ul.settings-section { + margin-left: 10px; +} +.message { + justify-content: flex-start; + align-items: center; + min-width: 500px; + margin: 0 auto 15px auto; + padding: 10px 15px; + background: #eee; + border: 1px solid #ddd; + font-size: 13.5px; +} +.message strong { + font-weight: 600; +} +.message > i { + font-size: 16px; + margin-right: 10px; +} +.message .btn { + background: transparent; + border-color: #707070; + margin-left: auto; +} +.message .btn:hover { + background: rgba(112, 112, 112, 0.15); +} +.message ul { + list-style: disc; + margin-left: 20px; +} +.message.success, +.message.primary, +.message.info, +.message.error { + border-color: transparent; +} +.message.success { + background: #d2f6d8; + color: #3f6e47; +} +.message.success i { + color: #3f6e47; +} +.message.success .btn { + border-color: #3f6e47; + color: #3f6e47; +} +.message.success .btn:focus, +.message.success .btn:active { + border-color: #3f6e47; +} +.message.success .btn:hover { + background: rgba(63, 110, 71, 0.15); +} +.message.primary { + background: #dfe8fa; + color: #335291; +} +.message.primary i { + color: #335291; +} +.message.primary .btn { + border-color: #335291; + color: #335291; +} +.message.primary .btn:focus, +.message.primary .btn:active { + border-color: #335291; +} +.message.primary .btn:hover { + background: rgba(51, 82, 145, 0.15); +} +.message.info { + background: #ffeacc; + color: #7a5726; +} +.message.info i { + color: #7a5726; +} +.message.info .btn { + border-color: #7a5726; + color: #7a5726; +} +.message.info .btn:focus, +.message.info .btn:active { + border-color: #7a5726; +} +.message.info .btn:hover { + background: rgba(122, 87, 38, 0.15); +} +.message.error { + background: #ffd8d6; + color: #993c37; +} +.message.error i { + color: #993c37; +} +.message.error .btn { + border-color: #993c37; + color: #993c37; +} +.message.error .btn:focus, +.message.error .btn:active { + border-color: #993c37; +} +.message.error .btn:hover { + background: rgba(153, 60, 55, 0.15); +} +.message a:not(.btn) { + text-decoration: underline; +} +.popup-wrapper { + position: fixed; + left: 0; + top: 0; + width: 100%; + height: 100%; + z-index: 6000; + background: rgba(0, 0, 0, 0.45); + font-style: normal; + overflow-y: auto; +} +.popup-inner { + background: #fff; + box-shadow: 0 2px 25px rgba(0, 0, 0, 0.3); + border: 1px solid rgba(0, 0, 0, 0.55); + border-radius: 4px; + width: 450px; + margin: 80px auto 0; + overflow: hidden; +} +.popup-inner .error { + color: #d80b00 !important; + margin: 10px 0 !important; + font-style: italic; +} +.popup-inner .head { + position: relative; + background: #fafafa; + padding: 7px 12px; + width: 100%; + border-bottom: 1px solid #eee; + border-radius: 4px 4px 0 0; +} +.popup-inner .head .title { + font-size: 0.95rem; + font-weight: 500; +} +.popup-inner .head .close-btn { + position: absolute; + top: 10px; + right: 12px; +} +.popup-inner .body { + padding: 12px; +} +.popup-inner .body > div:not(:first-child) { + margin-top: 20px; +} +.popup-inner p:first-child { + margin-top: 0; +} +.popup-inner p:last-child { + margin-bottom: 0; +} +.popup-inner select { + height: 28px; +} +.popup-inner textarea, +.popup-inner label:not(.checkbox-container), +.popup-inner select, +.popup-inner input { + display: block; + width: 100%; +} +.popup-inner label.toggle { + display: flex; + justify-content: flex-end; +} +.popup-inner label.toggle .text { + margin-right: 10px; +} +.popup-inner label.toggle input { + display: none; +} +.popup-inner label { + margin-bottom: 3px; + color: rgba(51, 51, 51, 0.9); +} +.popup-inner textarea, +.popup-inner input { + margin-bottom: 10px; +} +.popup-inner textarea { + height: 50px; + padding-top: 3px; +} +.popup-inner .actions { + display: flex; + justify-content: flex-end; + align-items: center; + margin-top: 15px; + padding-top: 10px; + border-top: 1px solid #eee; +} +.popup-inner .actions .left, +.popup-inner .actions .link { + margin-right: auto; +} +.popup-inner .actions .btn, +.popup-inner .actions .success, +.popup-inner .actions .primary { + margin-left: 5px; +} +.popup-inner .actions .spinner { + width: 10px; + height: 10px; + border-width: 1.2px; +} +#crop-popup .popup-inner { + width: 298px; +} +#crop-popup .popup-inner .controls { + width: 256px; + margin: 1rem 0 0; +} +#crop-popup .popup-inner canvas { + display: block; + border: 1px solid gray; + border-radius: 50%; +} +#crop-popup .popup-inner input[type="range"] { + width: 256px; +} +#crop-popup .popup-inner .btns { + text-align: right; +} +#share-popup .popup-inner div { + margin: 10px 0; + line-height: 1; +} +#share-popup .popup-inner input { + color: #333; + width: 100%; + margin-top: 10px; + font-size: 0.85rem; +} +#share-popup .popup-inner input:focus { + outline: none; +} +#share-popup .popup-inner .btn { + float: right; +} +#share-popup .popup-inner .info { + margin: 10px 0; + color: #707070; + font-size: 0.8rem; +} +#share-popup .popup-inner .info i { + margin-right: 3px; +} +#explorer-popup .popup-inner { + width: 80vw; + max-width: 900px; + min-width: 600px; +} +#explorer-popup .head .filter-control input { + height: 26px; + margin: 0; + width: calc(100% - 30px); +} +#explorer-popup .explorer-body .suggestions { + overflow-y: auto; + max-height: calc(100vh - 200px); + padding: 20px 30px; +} +#explorer-popup .explorer-body .suggestions .empty { + color: rgba(0, 0, 0, 0.5); +} +#explorer-popup .explorer-body .suggestions .group { + padding: 0 0 20px; +} +#explorer-popup .explorer-body .suggestions .group .group-title { + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + color: rgba(0, 0, 0, 0.5); + margin-bottom: 10px; + padding-bottom: 2px; + padding-left: 2px; + letter-spacing: -0.5px; +} +#explorer-popup .explorer-body .suggestions .group .group-items .suggestion { + display: flex; + align-items: center; + padding: 10px; + overflow: hidden; + user-select: none; +} +#explorer-popup .explorer-body .suggestions .group .group-items .suggestion .favicon { + width: 32px; + height: 32px; +} +#explorer-popup .explorer-body .suggestions .group .group-items .suggestion .favicon.rounded { + border-radius: 50%; +} +#explorer-popup .explorer-body .suggestions .group .group-items .suggestion .title { + white-space: nowrap; +} +#explorer-popup .explorer-body .suggestions .group .group-items .suggestion:hover { + background: #eee; +} +#explorer-popup .explorer-body .suggestions.defaults .group .group-items { + display: grid; + grid-template-columns: repeat(6, 1fr); +} +@media (min-width: 1300px) { + #explorer-popup .explorer-body .suggestions.defaults .group .group-items { + grid-template-columns: repeat(8, 1fr); + } +} +#explorer-popup .explorer-body .suggestions.defaults .group .group-items .suggestion { + flex-direction: column; +} +#explorer-popup .explorer-body .suggestions.defaults .group .group-items .suggestion .favicon { + margin-bottom: 6px; +} +#explorer-popup .explorer-body .suggestions.query-results .group .group-items .suggestion .favicon { + margin-right: 10px; +} +#library-localsyncpath-popup .popup-inner { + width: 530px; + overflow: initial; +} +#library-localsyncpath-popup .path-container { + align-items: center; + justify-content: space-between; + width: 100%; + height: 28px; + border: 1px solid #ddd; + border-radius: 4px 2px 2px 4px; +} +#library-localsyncpath-popup .path-container .btn { + border: 0; + border-left: 1px solid #ddd; + height: 26px; +} +#library-localsyncpath-popup .path-container .path { + flex: 1; + height: 100%; + line-height: 28px; + padding: 0 7px; + margin: 0; + border: 0; +} +#library-localsyncpath-popup .message { + margin-top: 10px; +} +#library-copydat-popup strong { + font-weight: 600; +} +#library-copydat-popup .downloader .download-desc { + font-size: 12px; + margin-top: 3px; + color: #707070; + text-align: center; +} +#editor-import-popup .popup-inner { + width: 90vw; + max-width: 800px; +} +#editor-import-popup .changes { + max-height: 50vh; + font-size: 12px; + white-space: nowrap; + overflow-y: auto; + border: 1px solid #ccc; + margin-top: 0; +} +#editor-import-popup .changes > * { + overflow-x: hidden; + text-overflow: ellipsis; + padding: 1px 3px; +} +#editor-import-popup .changes .added { + background: #efffef; + color: #013e01; +} +#editor-import-popup .changes .added .fas { + color: green; +} +#editor-import-popup .changes .updated { + background: #ffffd5; + color: #5f5f1e; +} +#editor-import-popup .changes .updated .fas { + color: #b99814; +} +#editor-import-popup .changes .removed { + background: #ffd5d5; + color: maroon; +} +#editor-import-popup .changes .removed .fas { + color: maroon; +} +#editor-import-popup .loading { + display: flex; + align-items: center; + padding: 30px; + font-size: 14px; + background: #fafafa; +} +#editor-import-popup .loading .spinner { + margin-right: 5px; +} +progress { + display: none; +} +@keyframes progress-active { + 0% { + width: 0%; + } + 50% { + width: 0%; + } + 100% { + width: 100%; + } +} +.progress-ui { + position: relative; + width: 100%; + height: 20px; + border-radius: 2px; + background: #e0e0e0; +} +.progress-ui .completed { + position: absolute; + min-width: 30px; + height: 100%; + padding: 0 5px; + background: #bbb; + color: rgba(255, 255, 255, 0.9); + font-size: 0.75rem; + font-weight: 500; + text-align: right; + line-height: 20px; + border-radius: 2px; +} +.progress-ui .label { + position: absolute; + width: 100%; + top: calc(100% + 5px); + text-align: center; + font-size: 0.8rem; +} +.progress-ui.active .completed:before { + position: absolute; + left: 0; + display: block; + content: ''; + z-index: 3; + height: 100%; + animation: 3s ease progress-active infinite; + animation-delay: 1s; + border-radius: 4px; + background: linear-gradient(to right, rgba(255, 255, 255, 0.2) 5%, rgba(255, 255, 255, 0.1)); +} +.progress-ui.small { + height: 10px; +} +.progress-ui.blue .completed { + background: #2864dc; +} +.progress-ui.green .completed { + background: #44c35a; +} +.context-input { + position: absolute; +} +.context-input .dropdown-items { + width: 300px !important; + padding: 10px; +} +.context-input .dropdown-items form { + display: flex; +} +.context-input .dropdown-items input { + flex: 1; + height: 28px; +} +.context-input .dropdown-items button { + margin-left: 5px; +} +.context-input .dropdown-items.with-triangle.left:before { + border-bottom-color: rgba(0, 0, 0, 0.25); + left: 20px; +} +.badge { + display: inline-flex; + align-items: center; + justify-content: space-around; + padding: 0 5px; + margin-left: 5px; + background: #eaeaea; + color: #707070; + border-radius: 2px; + font-size: 11.75px; +} +.badge:hover { + text-decoration: none; +} +.badge.read-only { + text-transform: uppercase; + font-size: 11px; + letter-spacing: 0.2px; +} +.badge.green { + background: #41b855; + color: #fff; +} +.badge.blue { + background: #2864dc; + color: #fff; +} +.badge.warning { + background: #d13027; + color: #fff; +} +.rehost-slider { + margin: 0 10px; + padding: 10px 0; +} +.rehost-slider .labels { + align-items: center; +} +.rehost-slider .labels .policy { + flex: 1; +} +.rehost-slider .toggle { + flex-direction: row; + justify-content: initial; + margin: 0; +} +.rehost-slider .toggle .switch { + margin-right: 10px; +} +.rehost-slider .slider { + margin-top: 10px; +} +.rehost-slider input[type=range] { + width: 100%; +} +.rehost-slider .fa-circle { + margin-right: 3px; + font-size: 12px; +} +.rehost-slider .fa-circle.green { + color: #4cd964; +} +.rehost-slider .fa-circle.yellow { + color: #ffcc00; +} +.rehost-slider .fa-circle.red { + color: #ff3b30; +} +.hover-swapper > .default, +.hover-swapper > .hover { + pointer-events: none; +} +.hover-swapper > .default { + display: inline-block; +} +.hover-swapper > .hover { + display: none; +} +.hover-swapper:hover > .default { + display: none; +} +.hover-swapper:hover > .hover { + display: inline-block; +} +*[data-tooltip] { + position: relative; +} +*[data-tooltip]:hover:before, +*[data-tooltip]:hover:after { + display: block; + z-index: 1000; + transition: opacity 0.01s ease; + transition-delay: 0.2s; +} +*[data-tooltip]:hover:after { + opacity: 1; +} +*[data-tooltip]:hover:before { + transform: translate(-50%, 0); + opacity: 1; +} +*[data-tooltip]:before { + opacity: 0; + transform: translate(-50%, 0); + position: absolute; + top: 33px; + left: 50%; + z-index: 3000; + content: attr(data-tooltip); + background: rgba(17, 17, 17, 0.95); + font-size: 0.7rem; + border: 0; + border-radius: 4px; + padding: 7px 10px; + color: rgba(255, 255, 255, 0.925); + text-transform: none; + text-align: center; + font-weight: 300; + white-space: pre; + line-height: 1; + pointer-events: none; +} +*[data-tooltip]:after { + opacity: 0; + position: absolute; + left: calc(50% - 6px); + top: 28px; + content: ''; + width: 0; + height: 0; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid rgba(17, 17, 17, 0.95); + pointer-events: none; +} +.window-content.builtin.watchlist { + position: relative; +} +.drag-hint { + box-sizing: border-box; + display: none; + position: absolute; + top: 0; + left: 0; + z-index: 3; + width: 100%; + height: 100%; + padding: 20px; + padding-top: 30vh; + background: rgba(240, 247, 253, 0.95); + color: rgba(0, 0, 0, 0.5); + color: #707070; + text-align: center; +} +.drag-hint:before { + display: block; + content: ''; + position: absolute; + left: 15px; + top: 15px; + width: calc(100% - 30px); + height: calc(100% - 30px); + border: 5px dashed rgba(0, 0, 0, 0.15); +} +.drag-hint .icons { + justify-content: center; + align-items: center; + margin-bottom: 15px; +} +.drag-hint .icons .fa { + font-size: 34px; +} +.drag-hint .icons .fa:nth-child(2), +.drag-hint .icons .fa:nth-child(4) { + font-size: 40px; +} +.drag-hint .icons .fa:nth-of-type(3) { + font-size: 48px; +} +.drag-hint .icons .fa:not(:last-child) { + margin-right: 10px; +} +.drag-hint h1 { + font-size: 1.5rem; + font-weight: 500; +} +.drag-hint p { + margin: 0; + font-size: 0.9rem; +} +body.drag .drag-hint { + display: block; +} +@media (min-width: 1050px) { + .builtin-wrapper.watchlist .builtin-main { + max-width: none; + margin: 0 100px 0 200px; + } +} +.builtin-wrapper.watchlist .group { + margin-bottom: 10px; +} +.builtin-wrapper.watchlist .ll-row.watchlist-item .watchlist-item-status, +.builtin-wrapper.watchlist .ll-column-headings .watchlist-item-status { + width: 90px; +} +.builtin-wrapper.watchlist .ll-row.watchlist-item .watchlist-item-title, +.builtin-wrapper.watchlist .ll-column-headings .watchlist-item-title { + flex: 3; + max-width: none; +} +.builtin-wrapper.watchlist .ll-row.watchlist-item .date, +.builtin-wrapper.watchlist .ll-column-headings .date { + min-width: 120px; + flex: 0.9; +} +.builtin-wrapper.watchlist .ll-row.watchlist-item .buttons, +.builtin-wrapper.watchlist .ll-column-headings .buttons { + width: 45px; +} +.builtin-wrapper.watchlist .ll-column-headings { + padding: 0 20px 10px; +} +.builtin-wrapper.watchlist .ll-row.watchlist-item { + height: 50px; + padding: 0 20px; +} +.builtin-wrapper.watchlist .ll-row.watchlist-item.menu-open .buttons, +.builtin-wrapper.watchlist .ll-row.watchlist-item:hover .buttons, +.builtin-wrapper.watchlist .ll-row.watchlist-item.menu-open .btn, +.builtin-wrapper.watchlist .ll-row.watchlist-item:hover .btn, +.builtin-wrapper.watchlist .ll-row.watchlist-item.menu-open .btn.trash, +.builtin-wrapper.watchlist .ll-row.watchlist-item:hover .btn.trash { + opacity: 1; +} +.builtin-wrapper.watchlist .ll-row.watchlist-item.menu-open.resolved .watchlist-item-title, +.builtin-wrapper.watchlist .ll-row.watchlist-item:hover.resolved .watchlist-item-title { + text-decoration: underline; +} +.builtin-wrapper.watchlist .ll-row.watchlist-item:not(.resolved):hover { + cursor: default; + background: #fff; +} +.builtin-wrapper.watchlist .ll-row.watchlist-item:not(.resolved):hover span, +.builtin-wrapper.watchlist .ll-row.watchlist-item:not(.resolved):hover img { + cursor: default; +} +.builtin-wrapper.watchlist .ll-row.watchlist-item .watchlist-item-status { + text-align: right; + padding-right: 15px; +} +.builtin-wrapper.watchlist .ll-row.watchlist-item .watchlist-item-title { + font-size: 13.5px; + color: gray; +} +.builtin-wrapper.watchlist .ll-row.watchlist-item .watchlist-item-title.empty { + overflow: initial; +} +.builtin-wrapper.watchlist .ll-row.watchlist-item .date { + font-weight: 300; + font-size: 12.5px; + color: #707070; +} +.builtin-wrapper.watchlist .ll-row.watchlist-item .buttons { + opacity: 1; +} +.builtin-wrapper.watchlist .ll-row.watchlist-item .buttons .trash { + margin-right: 4px; + opacity: 0; +} +.builtin-wrapper.watchlist .ll-row.watchlist-item .toggle { + justify-content: flex-end; + margin: 0; +} +.builtin-wrapper.watchlist .ll-row.watchlist-item .toggle label { + margin: 0; +} +.builtin-wrapper.watchlist .ll-row.watchlist-item.resolved .description { + color: #2864dc; +} +.builtin-wrapper.watchlist .watchlist-modal { + position: relative; +} +.builtin-wrapper.watchlist .watchlist-modal .modal-container { + display: none; +} +.builtin-wrapper.watchlist .watchlist-modal.open .modal-container { + display: block; +} +.builtin-wrapper.watchlist .modal-container { + position: absolute; + right: 0; + width: 300px; + margin-top: 5px; + background: white; + z-index: 3000; + border: 1px solid #dadada; + border-radius: 4px; + padding: 10px; +} +.builtin-wrapper.watchlist .modal-container .watchlist-input { + position: relative; +} +.builtin-wrapper.watchlist .modal-container .watchlist-input input { + width: 280px; + cursor: text; +} +.builtin-wrapper.watchlist .modal-container .watchlist-input .validate { + display: block; + font-size: 12px; + text-align: right; + color: #cc0000; + opacity: 0; + transition: opacity 1s; +} +.builtin-wrapper.watchlist .modal-container .watchlist-input #description { + padding-right: 40px; +} +.builtin-wrapper.watchlist .modal-container .watchlist-input #counter { + position: absolute; + top: 2px; + right: 0; +} +.builtin-wrapper.watchlist .modal-container .toggle { + width: 280px; + margin-bottom: 10px; +} +.builtin-wrapper.watchlist .modal-container .subtle-shadow { + box-shadow: 0 2px 15px rgba(0, 0, 0, 0.15); +} +.revision-header, +.files-browser-header { + align-items: center; + height: 50px; + padding: 0 10px; + background: #fff; + border: 1px solid #ddd; +} +.revision-header .path, +.files-browser-header .path { + max-width: 250px; + margin-right: 10px; +} +.editor-sidebar { + display: flex; + flex-flow: column; + overflow-y: scroll; + text-overflow: ellipsis; + padding-left: 4px; + padding-top: 4px; +} +.editor-sidebar::-webkit-scrollbar { + display: none; +} +.editor-sidebar .file-tree-container { + height: 100%; +} +.editor-sidebar .title button { + font-weight: 500; +} +.editor-sidebar .file-controls { + display: flex; + padding: 0 2px; + font-size: 11px; + margin: 0 0 4px; + background: #f5f5f5; +} +.editor-sidebar .file-controls .label { + padding: 3px 13px; + border-radius: 3px; + flex: 1; +} +.editor-sidebar .file-controls button { + padding: 5px 7px; +} +.editor-sidebar .file-tree-header { + padding: 3px; +} +.editor-sidebar .file-tree { + margin-bottom: 10px; +} +.editor-sidebar .subtree { + padding-left: 10px; +} +.editor-sidebar .revision-indicator { + border-radius: 50%; +} +.editor-sidebar .filetree { + font-size: 13px; + color: #444; + flex: 1; +} +.editor-sidebar .filetree * { + cursor: pointer; +} +.editor-sidebar .item { + display: flex; + align-items: center; + padding: 2px 5px; + cursor: pointer; + white-space: nowrap; +} +.editor-sidebar .item i { + color: #444; +} +.editor-sidebar .item i.fa-caret-down { + transform: rotate(-45deg); +} +.editor-sidebar .item i + .name { + margin-left: 5px; +} +.editor-sidebar .item .name { + flex: 1; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} +.editor-sidebar .item:hover { + background: #ededf0; +} +.editor-sidebar .item.root { + background: transparent; + padding: 5px 10px; + margin: 0 0 4px; + border-bottom: 1px solid #ddd; + cursor: default; +} +.editor-sidebar .item.root .name { + color: rgba(0, 0, 0, 0.75); +} +.editor-sidebar .item.root .hover-ctrls { + display: block; +} +.editor-sidebar .item.deleted { + color: #bf8989; + font-style: italic; +} +.editor-sidebar .item.deleted i { + color: #bf8989; +} +.editor-sidebar .item.highlighted { + background: #dddde0; +} +.editor-sidebar .item.selected { + background: #2864dc; + color: #fff; +} +.editor-sidebar .item.selected i { + color: inherit; +} +.editor-sidebar .new-node input { + width: 98%; +} +.editor-sidebar .uncommitted-changes { + width: 100%; + text-align: left; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +#editor-sidebar-drag-handle { + width: 4px; + cursor: col-resize; +} +#editor-sidebar-drag-handle:hover { + background: rgba(0, 0, 0, 0.04); +} +#editor-sidebar-drag-handle i { + display: none; + position: relative; + top: 40vh; + font-size: 11px; + color: gray; + left: 2px; +} +#editor-sidebar-drag-handle.wide { + width: 8px; +} +#editor-sidebar-drag-handle.wide i { + display: inline; +} +.editor-tabs { + display: flex; + height: 39px; + padding-top: 4px; + overflow-x: scroll; +} +.editor-tabs::-webkit-scrollbar { + display: none; +} +.editor-tabs .tab { + height: 100%; + line-height: 35px; + z-index: 1; + cursor: pointer; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + border-bottom: 1px solid #ccc; + padding: 1px 11px 0 11px; +} +.editor-tabs .tab.active { + padding: 0 10px; + border: 1px solid #ccc; + border-bottom-color: transparent; +} +.editor-tabs .tab i { + opacity: 0; + padding-left: 10px; + z-index: 100; + cursor: pointer; +} +.editor-tabs .tab:hover i, +.editor-tabs .tab.active i { + opacity: 1; +} +.editor-tabs .tab.new-file { + color: #666; + flex: 0 0 35px; +} +.editor-tabs .tab.new-file:hover { + background: #eee; +} +.editor-tabs .unused-space { + flex: 1; + border-bottom: 1px solid #ccc; +} +.editor-home { + padding: 30px; + height: 100%; + overflow: auto; + user-select: text; +} +.editor-home h3.viewing { + border: 1px solid #ddd; + background: #fff; + padding: 30px; +} +.editor-home .readme { + font-family: inherit; + line-height: 1.5; + color: rgba(0, 0, 0, 0.65); +} +.editor-home .readme h1 { + font-weight: bold; +} +.editor-home .readme h1, +.editor-home .readme h2, +.editor-home .readme h3 { + margin: 20px 0; +} +.editor-home .readme ul, +.editor-home .readme ol { + margin-bottom: 15px; + list-style: inherit; + margin-left: 30px; +} +.editor-home .readme pre, +.editor-home .readme code { + font-size: 13.5px; + background: #f5f5f5; + color: #333; + border-radius: 2px; +} +.editor-home .readme pre { + padding: 15px; + border: 0; +} +.editor-home .readme code { + padding: 3px 5px; +} +.editor-home .readme img { + max-width: 100%; +} +.editor-home .readme a:hover { + text-decoration: underline; +} +.editor-home .readme > :first-child { + margin-top: 0; +} +.editor-home .readme > :last-child { + margin-bottom: 0; +} +.editor-home .readme .anchor-link { + visibility: hidden; +} +.editor-home .readme h1:hover .anchor-link, +.editor-home .readme h2:hover .anchor-link, +.editor-home .readme h3:hover .anchor-link, +.editor-home .readme h4:hover .anchor-link, +.editor-home .readme h5:hover .anchor-link { + visibility: visible; +} +.editor-home .uncommitted-changes { + border: 1px solid #ddd; + background: #fff; + padding: 30px; + margin-bottom: -5vh; +} +.editor-home .uncommitted-changes .btns { + margin-bottom: 10px; +} +.editor-home .uncommitted-changes a { + cursor: pointer; + color: rgba(0, 0, 0, 0.65); +} +.editor-home .uncommitted-changes a.add { + color: #116520; +} +.editor-home .uncommitted-changes a.mod { + color: #9a7c05; +} +.editor-home .uncommitted-changes a.del { + color: #86120c; +} +.editor-home .uncommitted-changes .revision-indicator { + border-radius: 50%; + margin-left: 4px; + margin-right: 4px; +} +.editor-home .uncommitted-changes a:hover { + text-decoration: underline; +} +.editor-home .error-notice { + margin: -10px -10px 30px; +} +.editor-home .hotkeys { + margin-top: 20px; + color: rgba(0, 0, 0, 0.5); + border-top: 1px solid #ddd; + padding-top: 30px; +} +.settings-form .settings-form-inner { + background: #fff; + border: 1px solid #ccc; + border-top: 0; +} +.settings-form #save-settings-form { + margin: 4px 0; +} +.settings-form section { + border-top: 1px solid #ccc; +} +.settings-form section .heading { + display: flex; + align-items: center; + cursor: pointer; + background: #fafafa; +} +.settings-form section .heading .expander { + padding: 4px 6px; + font-size: 16px; +} +.settings-form section .heading .expander .fa-caret-right { + display: none; +} +.settings-form section .heading .title { + font-weight: 500; +} +.settings-form section.collapsed .heading .expander .fa-caret-right { + display: inline-block; +} +.settings-form section.collapsed .heading .expander .fa-caret-down { + display: none; +} +.settings-form section.collapsed .controls { + display: none !important; +} +.settings-form section .controls { + max-height: 50vh; + overflow-y: auto; + padding: 12px 12px; + border-top: 1px solid #ddd; +} +.settings-form section .controls.perms-grid { + display: grid; + grid-template-columns: 1fr; + grid-gap: 10px; + max-width: 740px; +} +.settings-form .control { + margin-bottom: 16px; + width: 300px; +} +.settings-form .control label { + display: block; + font-weight: 500; +} +.settings-form .control input, +.settings-form .control textarea { + width: 300px; +} +.settings-form .control input[type="checkbox"] { + display: inline-block; + width: auto; + height: auto; + margin: 0 8px; +} +.settings-form .control textarea { + height: auto; + padding: 4px 7px; + resize: vertical; +} +.settings-form .control .btn.white { + background: #fff; +} +.settings-form .control .toggle { + display: flex; + margin-top: 5px; +} +.settings-form .control .toggle input { + display: none; +} +.settings-form .control .thumbnail { + width: 100px; + height: 80px; + border: 1px solid #ccc; + border-radius: 4px; + object-fit: cover; + background: #fff; +} +.settings-form .control .thumbnail:not(.readonly):hover { + cursor: pointer; + border-color: #999; +} +.settings-form .control .dns-control { + padding: 10px; + border: 1px solid #ccc; + border-radius: 3px; +} +.settings-form .control .dns-control .dns-control-msg { + margin-bottom: 6px; + font-size: 14px; +} +.settings-form .control .dns-control .dns-control-msg.readonly { + margin-bottom: 0; +} +.settings-form .control .dns-control .dns-control-msg .fa-exclamation-triangle { + color: #eda504; +} +.settings-form .control .help { + display: flex; + align-items: baseline; + color: #929292; + padding: 8px; +} +.settings-form .control .help .fa-fw { + margin-right: 8px; + color: #d8d8d8; +} +.settings-form .control.syncpath p { + margin: 6px 0; +} +.settings-form .control.syncpath p.copy-path { + display: flex; + margin-top: 5px; + margin-bottom: 0; +} +.settings-form .control.syncpath p.copy-path input { + flex: 1; + font-size: 12px; + border-color: #eee; + background: #eee; + margin-right: 5px; +} +.settings-form .control.syncpath p.copy-path .btn.full-width { + margin-top: 10px; +} +.settings-form .control.permission { + border: 1px solid #ddd; + background: #fff; + width: auto; + border-radius: 4px; + padding: 12px 16px; + margin: 0; +} +.settings-form .control.permission p { + margin: 4px 0 6px; +} +.settings-form .control.permission label { + padding: 2px 0; + font-weight: 400; +} +.archive-history-dropdown .dropdown-items { + width: 275px !important; +} +.archive-history { + font-size: 12.5px; +} +.archive-history .archive-history-header { + padding: 5px 10px; + border-bottom: 1px solid #ddd; + font-weight: 500; +} +.archive-history .archive-history-body { + max-height: 350px; + overflow-x: hidden; + overflow-y: auto; +} +.archive-history .archive-history-body.error { + padding: 10px; +} +.archive-history hr { + margin: 0 0 5px; +} +.archive-history .archive-history-item { + flex-wrap: nowrap; + align-items: center; + height: 35px; + padding: 0 10px; + border-bottom: 1px solid #eee; + cursor: pointer; +} +.archive-history .archive-history-item.no-border { + border-color: transparent; +} +.archive-history .archive-history-item .fa { + margin-right: 5px; +} +.archive-history .archive-history-item .version { + min-width: 32px; + margin-left: auto; +} +.archive-history .archive-history-item .path { + max-width: 145px; + margin-left: 3px; +} +.archive-history .archive-history-item .sync-path { + max-width: 123px; + margin-left: 5px; +} +.archive-history .archive-history-item:hover { + background: #f0f0f0; +} +.archive-history .archive-history-item.selected { + background: #2864dc; + color: #fff; +} +.revision-indicator { + display: inline-block; + width: 8px; + height: 8px; + margin-top: -0.4px; + margin-left: -2px; +} +.revision-indicator.add { + background: #44c35a; +} +.revision-indicator.mod { + background: #fac800; +} +.revision-indicator.del { + background: #d93229; +} +.dropdown.share .dropdown-items { + width: 350px; +} +.dropdown.share .dropdown-item { + background: #fff; + cursor: default; + padding: 15px; +} +.dropdown.share .dropdown-item p { + margin-left: 0; + color: #333; +} +.dropdown.share p.copy-url { + margin-top: 5px; + margin-bottom: 0; +} +.dropdown.share p.copy-url input { + flex: 1; + font-size: 12px; + border-color: #eee; + background: #eee; + margin-right: 5px; +} +.dropdown.share p.copy-url .btn.full-width { + margin-top: 10px; +} +.markdown { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 1.42857143; +} +.markdown h1, +.markdown h2, +.markdown h3, +.markdown h4, +.markdown h5, +.markdown h6 { + font-family: inherit; + font-weight: 500; + line-height: 1.1; + color: inherit; +} +.markdown h1 small, +.markdown h2 small, +.markdown h3 small, +.markdown h4 small, +.markdown h5 small, +.markdown h6 small { + font-weight: normal; + line-height: 1; + color: #999; +} +.markdown h1, +.markdown h2, +.markdown h3 { + margin-top: 20px; + margin-bottom: 10px; +} +.markdown h1 small, +.markdown h2 small, +.markdown h3 small, +.markdown h1 .small, +.markdown h2 .small, +.markdown h3 .small { + font-size: 65%; +} +.markdown h4, +.markdown h5, +.markdown h6 { + margin-top: 10px; + margin-bottom: 10px; +} +.markdown h4 small, +.markdown h5 small, +.markdown h6 small, +.markdown h4 .small, +.markdown h5 .small, +.markdown h6 .small { + font-size: 75%; +} +.markdown h1 { + font-size: 2em; +} +.markdown h2 { + font-size: 1.75em; +} +.markdown h3 { + font-size: 1.5em; +} +.markdown h4 { + font-size: 1.25em; +} +.markdown h5 { + font-size: 1.15em; +} +.markdown h6 { + font-size: 1.1em; +} +.markdown p { + margin: 0 0 10px; +} +.markdown small { + font-size: 85%; +} +.markdown ul, +.markdown ol { + margin-top: 0; + margin-bottom: 10px; +} +.markdown ul ul, +.markdown ol ul, +.markdown ul ol, +.markdown ol ol { + margin-bottom: 0; +} +.markdown blockquote { + padding: 10px 20px; + margin: 0 0 20px; + font-size: 17.5px; + border-left: 5px solid #ccc; +} +.markdown blockquote p:last-child, +.markdown blockquote ul:last-child, +.markdown blockquote ol:last-child { + margin-bottom: 0; +} +.markdown blockquote footer, +.markdown blockquote small { + display: block; + font-size: 80%; + line-height: 1.42857143; + color: #ccc; +} +.markdown blockquote footer:before, +.markdown blockquote small:before { + content: '\2014 \00A0'; +} +.markdown code, +.markdown kbd, +.markdown pre, +.markdown samp { + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; +} +.markdown code { + padding: 2px 4px; + font-size: 90%; + color: #c7254e; + background-color: #f9f2f4; + border-radius: 4px; +} +.markdown kbd { + padding: 2px 4px; + font-size: 90%; + color: #fff; + background-color: #333; + border-radius: 3px; + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.markdown kbd kbd { + padding: 0; + font-size: 100%; + font-weight: 500; + box-shadow: none; +} +.markdown pre { + display: block; + padding: 9.5px; + margin: 0 0 10px; + font-size: 13px; + line-height: 1.42857143; + word-break: break-all; + word-wrap: break-word; + color: #777; + background-color: #f5f5f5; + border: 1px solid #ccc; + border-radius: 4px; +} +.markdown pre code { + padding: 0; + font-size: inherit; + color: inherit; + white-space: pre-wrap; + background-color: transparent; + border: 0; +} +.markdown .pre-scrollable { + max-height: 340px; + overflow-y: scroll; +} +.muted { + color: gray; +} +.thin { + font-weight: 300; +} +.icon-info { + color: transparent; + -webkit-text-stroke: 1px #333; +} +.editor-wrapper { + display: flex; + height: 100%; + flex-flow: row nowrap; + user-select: none; +} +.editor-container { + flex: 1; + margin-right: 5px; +} +.editor-container #editor, +.editor-container #diffEditor, +.editor-container #imageViewer, +.editor-container #genericViewer { + height: calc(100% - 70px); + background-color: white; + border-left: 1px solid #ccc; + border-right: 1px solid #ccc; +} +.editor-container #imageViewer { + position: relative; +} +.editor-container #imageViewer img { + max-width: 100%; + height: auto; + position: absolute; + top: 25px; + left: 25px; +} +.editor-container #genericViewer { + height: calc(100% - 40px); +} +.editor-container #genericViewer .opaque-binary { + text-align: left; + padding: 20px 30px; +} +.editor-container #genericViewer .deleted-filediff { + padding: 20px 40px; +} +.editor-container #genericViewer .deleted-filediff .path { + color: #c37b7b; + font-style: italic; +} +.editor-container #genericViewer .deleted-filediff .path .revision-indicator { + margin: 0 4px 0 0; +} +.editor-settings-sidebar { + flex: 0 0 340px; + margin-right: 5px; + margin-top: 34px; +} +.editor-settings-sidebar .ctrls { + margin: 4px 0; + display: flex; + flex: 0 0 auto; +} +.editor-settings-sidebar .ctrls > * { + margin-right: 4px; +} +.editor-settings-sidebar .ctrls .readonly { + height: 30px; + line-height: 30px; + padding: 0 10px; +} +.editor-settings-sidebar.collapsed { + flex: 0 0 34px; +} +.editor-settings-sidebar.collapsed .ctrls { + flex-direction: column; +} +.editor-settings-sidebar.collapsed .ctrls .btn { + width: 34px; +} +.editor-settings-sidebar.collapsed .ctrls > * { + margin-right: 0; + margin-bottom: 4px; +} +.editor-toolbar { + display: flex; + border-left: 1px solid #ccc; + border-right: 1px solid #ccc; + border-bottom: 1px solid #ddd; + padding: 0 4px; +} +.editor-toolbar:empty { + display: none; +} +.editor-toolbar .btn { + border-radius: 0; + line-height: 28px; +} +.editor-toolbar .btn i { + font-size: 11px; + position: relative; + top: -1px; +} +.editor-toolbar .divider { + width: 1px; + border-left: 1px solid #ddd; + margin: 4px; +} +.editor-toolbar .spacer { + flex: 1; +} +h3 { + font-size: 1.2em; + font-weight: 300; + margin-bottom: 10px; + line-height: 1; +} +.quick-link { + margin-bottom: 30px; +} +.quick-link div { + color: rgba(0, 0, 0, 0.65); +} +.cover-screen { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 1000; + background: #fff; + font-size: 18px; + font-weight: 300; + color: #333; +} +.cover-screen.gray { + background: #fafafa; +} +.cover-screen .cover-screen-centered { + position: absolute; + top: 40%; + left: 50%; + transform: translate(-50%, -50%); +} +.cover-screen .cover-screen-leftpane { + position: absolute; + background: #fff; + padding: 30px 80px 20px 70px; + height: 100%; +} +.cover-screen .loading { + font-size: 20px; +} +.cover-screen .loading .spinner { + margin-right: 5px; +} +.cover-screen .load-error { + width: 550px; +} +.cover-screen .load-error > div { + margin-bottom: 16px; +} +.cover-screen .load-error small { + font-size: 14px; + color: #999; +} +.loading-notice { + display: flex; + align-items: center; + font-size: 14px; + padding: 20px; +} +.loading-notice .spinner { + margin-right: 5px; +} +.error-notice { + padding: 20px; + color: #b55757; +} +.monaco-editor .line-numbers { + color: #999 !important; +} +.monaco-editor .current-line ~ .line-numbers { + color: #222 !important; +} +.links-list { + user-select: none; +} +.links-list .link:not(button) { + overflow: hidden; + white-space: nowrap; + text-overflow: clip; + color: #333; + margin-right: 5px; + max-width: calc(100% - 45px); +} +.links-list .link:not(button):hover { + text-decoration: none; +} +.links-list .ll-progressbar { + flex: 0 0 100px; + padding: 6px 8px; +} +.links-list .ll-progressbar progress { + width: 80px; + position: relative; + top: 3px; +} +.links-list .ll-progress { + padding: 6px 8px; + color: #999; + min-width: 45px; + text-align: right; +} +.links-list .ll-status { + padding: 6px 8px; + color: #999; + text-align: right; +} +.links-list .ll-serve { + padding: 6px 0; + flex: 0 0 90px; + text-align: right; +} +.ll-heading { + font-size: 18px; +} +.ll-sticky-heading { + position: sticky; + top: 0px; + z-index: 1; + padding: 5px 0; + margin: 0; + background: #f7f7f7; + border-bottom: 1px solid #e6e6e6; + margin-bottom: -1px; +} +.ll-row { + user-select: none; + flex-wrap: nowrap; + position: relative; + height: 33px; + padding: 0 5px; + justify-content: space-between; +} +.ll-row * { + text-decoration: none; + vertical-align: middle; + cursor: pointer; +} +.ll-row .title, +.ll-row .url { + margin-right: 5px; +} +.ll-row .favicon { + display: inline; + flex-basis: 20px; + width: 20px; + height: 20px; + margin-right: 10px; +} +.ll-row .favicon.rounded { + border-radius: 50%; +} +.ll-row i.favicon { + width: auto; + height: auto; + margin-left: 1px; +} +.ll-row .url { + color: #b8b8b8; +} +.ll-row .buttons .action, +.ll-row .actions .action { + display: inline-block; + opacity: 0; +} +.ll-row .buttons .action.pinned, +.ll-row .actions .action.pinned { + opacity: 1; +} +.ll-row .buttons .action.pinned .icon, +.ll-row .actions .action.pinned .icon { + color: #2864dc; +} +.ll-row .buttons .btn.trash:hover .fa-trash-o, +.ll-row .actions .btn.trash:hover .fa-trash-o { + color: #d93229; +} +.ll-row .buttons .action.plain:not(.toggleable) i, +.ll-row .actions .action.plain:not(.toggleable) i { + font-size: 14.25px; + text-align: center; + margin-left: 12px; + color: #aaa; +} +.ll-row .buttons .action.plain:not(.toggleable) i:hover, +.ll-row .actions .action.plain:not(.toggleable) i:hover { + color: #333; +} +.ll-row .buttons .action.plain:not(.toggleable) i.fa-star, +.ll-row .actions .action.plain:not(.toggleable) i.fa-star { + color: #ffcc00; + -webkit-text-stroke: 1px #f7c600; +} +.ll-row .buttons .action.plain:not(.toggleable) i.pin, +.ll-row .actions .action.plain:not(.toggleable) i.pin { + width: 10px; + margin-right: -1px; +} +.ll-row .buttons .action.plain:not(.toggleable) i.fa-trash-o, +.ll-row .actions .action.plain:not(.toggleable) i.fa-trash-o { + color: #e6352b; +} +.ll-row .inputs input { + display: inline-block; + height: 20px; +} +.ll-row:hover, +.ll-row .editing { + background: #f4f7fd; +} +.ll-row:hover .action, +.ll-row .editing .action { + opacity: 1; +} +.close-btn .icon { + width: 13px; + height: 13px; + padding: 2px; + border-radius: 50%; + cursor: pointer; +} +.close-btn .icon * { + cursor: pointer; + stroke: #707070; +} +.close-btn .icon:hover { + background: rgba(0, 0, 0, 0.1); +} +.close-btn.square .icon { + height: 18px; + width: 18px; + border-radius: 2px; + padding: 4px; +} +.search-container { + position: relative; + width: 100%; + height: 32px; + z-index: 3; +} +.search-container .filter-btn { + position: absolute; + left: calc(100% + 3px); + top: 1px; +} +.search-container .filter-btn .dropdown-items { + right: -3px; +} +.search-container .dropdown-items.filters { + width: 200px; +} +.search-container .spinner, +.search-container .close-btn, +.search-container .search { + position: absolute; +} +.search-container i.fa-search { + position: absolute; + left: 10px; + top: 8.75px; + color: rgba(0, 0, 0, 0.5); +} +.search-container .spinner { + left: 8px; + top: 8px; +} +.search-container .spinner.hidden { + display: none; +} +.search-container .close-btn { + right: 7px; + top: 7px; +} +.search-container input.search { + left: 0; + top: 0; + width: 100%; + height: 100%; + padding: 0 10px; + padding-left: 27px; +} +.search-container input.search:invalid + .close-btn { + opacity: 0; +} +.autocomplete-container { + position: relative; + width: 100%; +} +.autocomplete-results { + position: absolute; + left: 0; + z-index: 5; + width: 100%; + margin-bottom: 10px; + overflow: hidden; + background: #fff; + border-radius: 4px; + border: 1px solid #ddd; + box-shadow: 0 6px 20px rgba(0, 0, 0, 0.05); +} +.autocomplete-result-group { + margin-bottom: 6px; +} +.autocomplete-result-group-title { + padding: 4px 10px; + border-bottom: 1px solid #ddd; + color: rgba(0, 0, 0, 0.5); +} +.autocomplete-result { + flex-wrap: nowrap; + align-items: center; + height: 40px; + padding: 0 10px; + border-left: 3px solid transparent; + cursor: pointer; +} +.autocomplete-result * { + cursor: pointer; +} +.autocomplete-result .icon { + width: 24px; + text-align: center; + margin-right: 10px; +} +.autocomplete-result .icon.rounded { + border-radius: 50%; +} +.autocomplete-result .favicon { + height: 24px; +} +.autocomplete-result .title { + margin-right: 5px; +} +.autocomplete-result .label { + color: rgba(0, 0, 0, 0.475); +} +.autocomplete-result:hover { + background: #f7f7f7; + border-color: #ddd; +} +.autocomplete-result.active { + background: rgba(40, 100, 220, 0.07); + border-color: #2864dc; +} +.crawler-status { + margin: 0 -15px; +} +.crawler-status.loading { + margin: 0; +} +.crawler-status .crawler-actions { + padding: 0px 10px 10px; +} +.crawler-status .heading { + align-items: center; + padding: 6px 15px; + color: #333; + font-size: 11px; +} +.crawler-status .heading .title { + width: 155px; +} +.crawler-status .heading .crawl-state { + flex: 1; +} +.crawler-status .heading a { + color: #333 !important; + cursor: pointer; +} +.crawler-status .ll-row.archive { + border-right: 0; + border-left: 0; +} +.crawler-status .ll-row.archive:hover { + text-decoration: none !important; +} +.crawler-status .ll-row.archive :not(a) { + cursor: auto; +} +.crawler-status .ll-row.archive .title, +.crawler-status .ll-row.archive .crawl-state { + white-space: nowrap; + overflow: hidden; +} +.crawler-status .ll-row.archive .title { + width: 120px; +} +.crawler-status .ll-row.archive .crawl-state { + color: rgba(0, 0, 0, 0.5); + flex: 1; +} +.crawler-status .ll-row.archive .error { + color: #ff3b30; +} +.window { + position: initial; +} +.window-content.builtin { + position: initial; + background: #f7f7f7; +} +.builtin-wrapper { + justify-content: space-around; + flex-wrap: wrap; + width: 100%; + height: 100vh; + background: #f7f7f7; +} +.builtin-wrapper > em.empty { + color: rgba(0, 0, 0, 0.7); +} +.builtin-wrapper .subtitle-heading { + justify-content: space-between; + font-weight: 400; + font-size: 0.7rem; + text-transform: uppercase; + padding-bottom: 5px; + letter-spacing: 0.15px; + color: rgba(0, 0, 0, 0.75); +} +.builtin-wrapper .subtitle-heading:not(:first-of-type) { + margin-top: 40px; +} +.builtin-wrapper .subtitle-heading .link { + text-transform: initial; + letter-spacing: 0; + font-size: 0.775rem; +} +.builtin-wrapper .lined-heading { + position: relative; + align-items: center; + font-size: 1.15rem; + margin-bottom: 25px; + font-weight: 400; +} +.builtin-wrapper .lined-heading i { + margin-right: 7px; +} +.builtin-wrapper .lined-heading:before { + margin-right: 10px; +} +.builtin-wrapper .lined-heading:after { + margin-left: 10px; +} +.builtin-wrapper .lined-heading:before, +.builtin-wrapper .lined-heading:after { + display: block; + content: ''; + flex: 1; + height: 1px; + background: #eee; +} +.builtin-wrapper .lined-heading .lined-heading-action { + position: absolute; + right: 0; + top: 0; + padding: 0 10px; + background: #fff; +} +.builtin-wrapper .lined-heading .lined-heading-action .fa { + font-size: 18px; +} +.builtin-wrapper .lined-heading .lined-heading-action .fa.fa-times { + -webkit-text-stroke: 2px #fff; +} +.builtin-wrapper .ll-column-headings { + align-items: center; + justify-content: flex-start; +} +.builtin-wrapper .ll-row { + justify-content: flex-start; + position: relative; + align-items: center; + padding: 0 15px; + height: 40px; + background: #fff; + border: 1px solid #e6e6e6; +} +.builtin-wrapper .ll-row:not(:last-of-type) { + border-bottom: 0; +} +.builtin-wrapper .ll-row .checkbox { + position: absolute; + right: -35px; + margin: 0; + padding: 0 10px; + font-size: 1.15rem; +} +.builtin-wrapper .ll-row .checkbox .fa { + opacity: 0; + color: #ccc; +} +.builtin-wrapper .ll-row .checkbox input { + display: none; +} +.builtin-wrapper .ll-row .checkbox input:checked + .fa { + opacity: 1 !important; + color: #41b855; +} +.builtin-wrapper .ll-row .buttons { + align-items: center; + justify-content: flex-end; + margin-left: auto; + opacity: 0; +} +.builtin-wrapper .ll-row .link { + align-items: center; + flex-wrap: nowrap; +} +.builtin-wrapper .ll-row:hover { + background: #f9f9f9; +} +.builtin-wrapper .ll-row:hover .buttons { + opacity: 1; +} +.builtin-wrapper .ll-row:hover .actions { + background: #f9f9f9; +} +.builtin-wrapper .ll-row:hover .checkbox .fa { + opacity: 1; +} +.builtin-wrapper .ll-row.selected { + background: rgba(40, 100, 220, 0.1); + border-color: #c3d4f5; +} +.builtin-wrapper .ll-row.selected + .ll-row { + border-top: 1px solid #c3d4f5; +} +.builtin-wrapper .ll-row.selected:hover { + background: rgba(40, 100, 220, 0.1); +} +.builtin-wrapper .ll-row .title { + max-width: 450px; + display: inline-block; + margin-right: 5px; + color: #333 !important; +} +.builtin-wrapper .ll-row .url { + max-width: 200px; + margin-right: 20px; + color: #999; + font-weight: 300; +} +.builtin-wrapper .ll-row .actions { + align-items: center; + line-height: 100%; + position: absolute; + right: 10px; +} +.builtin-subheader { + margin-bottom: 10px; +} +.builtin-subheader .actions { + text-align: right; + margin-top: 10px; +} +.builtin-main, +.builtin-sidebar { + display: block; +} +@media (min-width: 1050px) { + .builtin-main, + .builtin-sidebar { + display: inline-block !important; + } +} +.builtin-sidebar .builtin-pages-nav { + margin-bottom: 10px; + user-select: none; +} +.builtin-sidebar .builtin-pages-nav div { + display: inline-block; + padding: 5px 15px 2px 5px; + margin-top: -5px; + margin-left: -5px; + font-weight: 500; +} +.builtin-sidebar .builtin-pages-nav div img { + display: inline-block; + position: relative; + top: -2px; + width: 32px; + margin-right: 5px; + vertical-align: middle; +} +.builtin-sidebar .nav-item { + padding: 0 8px; + color: rgba(51, 51, 51, 0.9); + cursor: pointer; + border-radius: 2px; + display: inline-block; + margin-bottom: 20px; +} +.builtin-sidebar .nav-item img { + position: relative; + top: 3px; + margin-right: 5px; + width: 16px; +} +.builtin-sidebar .nav-item .fa { + display: none; +} +.builtin-sidebar .nav-item.active { + background: #2864dc; + color: #fff; +} +.builtin-sidebar .nav-item:hover:not(.active) { + color: #2864dc; +} +@media (min-width: 1050px) { + .builtin-sidebar .nav-item { + display: flex; + align-items: center; + position: relative; + height: 25px; + padding: 7px 0; + margin-bottom: 0; + } + .builtin-sidebar .nav-item img { + top: 0; + } + .builtin-sidebar .nav-item .fa { + display: none; + width: 12px; + margin-left: -12px; + position: relative; + top: 1px; + } + .builtin-sidebar .nav-item:hover .icon { + color: inherit; + } + .builtin-sidebar .nav-item.active { + color: #2864dc; + font-weight: 500; + background: none; + } + .builtin-sidebar .nav-item.active .fa { + display: block; + } +} +@media (min-width: 1050px) { + .builtin-sidebar { + position: fixed; + top: 25px; + left: 15px; + width: 200px; + z-index: 1; + } + .builtin-sidebar .section { + margin-bottom: 20px; + } + .builtin-sidebar .section > div { + padding: 0 20px; + } + .builtin-sidebar .section h2 { + padding: 1px 20px; + } + .builtin-sidebar .section h2.clickable { + cursor: pointer; + } + .builtin-sidebar .section h2 .icon { + width: 9px; + height: 9px; + margin-left: 3px; + } + .builtin-sidebar .section h2 .icon * { + fill: rgba(0, 0, 0, 0.25); + transition: fill 0.1s ease; + } + .builtin-sidebar .section h2:hover .icon * { + fill: rgba(0, 0, 0, 0.5); + } + .builtin-sidebar .section hr { + max-width: 10px; + margin: 10px 20px; + } +} +.builtin-sidebar .nav-item.sub-nav-item { + margin-left: 15px; +} +.builtin-sidebar .nav-item.sub-nav-item.active { + font-weight: 500; + color: #333; +} +.builtin-main { + width: 100%; + max-width: 700px; + padding: 25px 5px; + height: 100%; +} +@media (min-width: 725px) { + .builtin-main { + padding: 25px 0; + } +} +.builtin-main > div:not(.builtin-sidebar) { + margin-bottom: 25px; +} +.builtin-main .view { + padding: 15px; + background: #fff; +} +.builtin-main .view.empty { + text-align: center; + max-width: 600px; + margin: auto; + background: none; +} +.builtin-main .view.empty i { + font-size: 60px; + color: rgba(0, 0, 0, 0.15); +} +.builtin-main .view.empty i.fa-search { + margin-bottom: 10px; + -webkit-text-stroke: 3px #f7f7f7; +} +.builtin-main .view.empty .label { + font-size: 0.95rem; + margin-bottom: 5px; +} +.builtin-main .view.empty p { + color: #707070; + font-weight: 300; +} +.builtin-main .view p { + margin-top: 0; +} +.builtin-main .view p:last-child { + margin-bottom: 0; +} +.builtin-main .view a:not(.btn) { + color: #2864dc; +} +.builtin-main .view a:not(.btn):hover { + text-decoration: underline; +} +.builtin-main .view ul { + list-style: disc; + margin-left: 20px; +} +.builtin-main .view.fullwidth { + max-width: none; +} +.builtin-main .module { + background: #fff; + border-radius: 2px; +} +.builtin-main .module:not(:last-of-type) { + margin-bottom: 20px; +} +.builtin-main .module .module-heading { + align-items: center; + justify-content: space-between; + background: #fafafa; + padding: 7px 10px; + border: 1px solid #ddd; + border-radius: 4px 4px 0 0; + font-size: 0.85rem; + font-weight: 500; +} +.builtin-main .module .module-heading i { + margin-right: 5px; +} +.builtin-main .module .module-content { + padding: 15px; + border-left: 1px solid #ddd; + border-right: 1px solid #ddd; +} +.builtin-main .module .module-content.bordered { + border-bottom: 1px solid #ddd; +} +.builtin-main .module .module-content .empty { + color: #707070; + padding: 0; +} +.builtin-main .module table { + border: 1px solid #ddd; + border-radius: 2px; + font-size: 12.5px; + margin-bottom: 5px; +} +.builtin-main .module table tr { + display: block; + width: 100%; + padding: 3px 10px; +} +.builtin-main .module table tr:nth-child(2n+1) { + background: #f7f7f7; +} +.builtin-main .module table tr:not(:last-child) { + border-bottom: 1px solid #eee; +} +.builtin-main .module table tr:first-child { + border-bottom: 1px solid #ddd; +} +.builtin-main .module table td { + padding: 0; +} +.builtin-main .module table th { + display: block; + width: 100%; + font-weight: 600; + padding: 0; +} +.builtin-main .module .module-footer { + border: 1px solid #ddd; + border-radius: 0 0 2px 2px; +} +.builtin-main .module .module-footer > div { + flex-basis: 100%; + text-align: center; + font-weight: 500; + padding: 10px; +} +.builtin-main .module .module-footer > div .value { + font-size: 0.85rem; + font-weight: 500; +} +.builtin-main .module .module-footer > div .label { + font-size: 0.8rem; + color: rgba(0, 0, 0, 0.4); +} +.builtin-main .module .module-footer > div:not(:last-child) { + border-right: 1px solid #ddd; +} +.builtin-main .module .module-footer.two > div { + flex-basis: 50%; +} +.builtin-main .section h3:not(.subtitle-heading), +.builtin-main .module h3:not(.subtitle-heading) { + font-size: inherit; + font-weight: 500; + margin-bottom: 2px; + margin-top: 25px; +} +.builtin-main .section h3:not(.subtitle-heading).no-margin, +.builtin-main .module h3:not(.subtitle-heading).no-margin { + margin: 0; +} +.builtin-main .section-heading, +.builtin-main .module-heading { + font-size: 1rem; + font-weight: 500; + margin-bottom: 10px; +} +.builtin-main .section-content form, +.builtin-main .module-content form { + margin-top: 5px; +} +.builtin-main .section-content textarea, +.builtin-main .module-content textarea, +.builtin-main .section-content input[type="text"], +.builtin-main .module-content input[type="text"] { + width: 370px; +} +.builtin-main .section-content .input-group, +.builtin-main .module-content .input-group { + align-items: center; +} +.builtin-main .section-content .input-group h3, +.builtin-main .module-content .input-group h3 { + margin-bottom: 5px; +} +.builtin-main .section-content .input-group .btn, +.builtin-main .module-content .input-group .btn { + margin-left: 5px; +} +.builtin-main .section-content .input-group.radiolist, +.builtin-main .module-content .input-group.radiolist { + margin-bottom: 7px; +} +.builtin-main .section-content .input-group.radiolist input[type="radio"], +.builtin-main .module-content .input-group.radiolist input[type="radio"], +.builtin-main .section-content .input-group.radiolist input[type="checkbox"], +.builtin-main .module-content .input-group.radiolist input[type="checkbox"] { + margin: 0; +} +.builtin-main .section-content .input-group.radiolist input[type="text"], +.builtin-main .module-content .input-group.radiolist input[type="text"] { + flex: 1; +} +.builtin-main .section-content .input-group.radiolist label, +.builtin-main .module-content .input-group.radiolist label { + font-weight: 400; + margin-bottom: 0; +} +.builtin-main .section-content .input-group.radiolist label, +.builtin-main .module-content .input-group.radiolist label, +.builtin-main .section-content .input-group.radiolist input[type="radio"], +.builtin-main .module-content .input-group.radiolist input[type="radio"], +.builtin-main .section-content .input-group.radiolist input[type="checkbox"], +.builtin-main .module-content .input-group.radiolist input[type="checkbox"] { + margin-right: 10px; +} +.builtin-main .section-content .input-group.radiolist .btn, +.builtin-main .module-content .input-group.radiolist .btn { + margin-left: auto; +} +.builtin-main .section-content .hint, +.builtin-main .module-content .hint { + padding-top: 10px; + border-top: 1px dashed #ddd; +} +.builtin-main .section-content.coming-soon, +.builtin-main .module-content.coming-soon { + position: relative; +} +.builtin-main .section-content.coming-soon:before, +.builtin-main .module-content.coming-soon:before, +.builtin-main .section-content.coming-soon:after, +.builtin-main .module-content.coming-soon:after { + justify-content: center; + align-items: center; + position: absolute; + left: 0; + top: 0; + content: ''; + width: 100%; + height: 100%; +} +.builtin-main .section-content.coming-soon:before, +.builtin-main .module-content.coming-soon:before { + background: rgba(255, 255, 255, 0.55); +} +.builtin-main .section-content.coming-soon:after, +.builtin-main .module-content.coming-soon:after { + width: 160px; + height: 30px; + left: 50%; + top: 50%; + content: 'Coming soon'; + text-transform: uppercase; + color: #707070; + font-size: 1.3rem; + letter-spacing: 1px; + font-weight: 300; + transform: translate(-50%, -50%) rotate(-15deg); + text-shadow: 0 0 25px #fff; +} +.builtin-hint { + max-width: 450px; + margin: 20px auto; + color: rgba(0, 0, 0, 0.65); + text-align: center; +} +.builtin-hint h2, +.builtin-hint h3 { + font-size: inherit; +} +.builtin-hint p { + margin: 0; +} +.learn-more, +.learn-more-link { + font-size: 0.75rem; + margin-left: 5px; +} +#builtin-loading-wrapper { + position: absolute; + left: 0; + top: 0; + z-index: 3000; + width: 100%; + height: 100%; + background: none; +} +#builtin-loading-wrapper .loading { + align-items: center; + justify-content: center; + width: 200px; + margin: auto; + margin-top: 20%; + padding: 15px; + border-radius: 4px; + background: rgba(0, 0, 0, 0.7); + color: rgba(255, 255, 255, 0.92); + font-size: 16px; +} +#builtin-loading-wrapper .loading .spinner { + display: inline-block; + width: 22px; + height: 22px; + margin-right: 10px; + color: rgba(255, 255, 255, 0.92); +} +.path-container { + align-items: center; + justify-content: space-between; + width: 100%; + height: 28px; + border: 1px solid #ddd; + border-radius: 4px 2px 2px 4px; +} +.path-container .btn { + border: 0; + border-left: 1px solid #dadada; + border-radius: 0 2px 2px 0; + height: 26px; +} +.path-container .path { + flex: 1; + height: 100%; + line-height: 28px; + padding: 0 7px; + margin: 0; + border: 0; + font-size: 12.75px; +} +.builtin-main.fullwidth { + max-width: none; + display: flex !important; +} +.builtin-main.fullwidth .builtin-sidebar { + position: relative; + top: 0; + left: 0; + width: 240px; + padding: 0 40px; +} +.builtin-main.fullwidth .view { + flex: 1; + margin-right: 10px; +} +.builtin-main.fullwidth .view.not-fullwidth { + max-width: 700px; +} +@media (max-width: 1050px) { + .builtin-main.fullwidth { + flex-direction: column; + } + .builtin-main.fullwidth .builtin-sidebar { + width: 100%; + } + .builtin-main.fullwidth .view { + margin-left: 10px; + } + .builtin-main.fullwidth .view.not-fullwidth { + margin: 0 auto; + } +} diff --git a/app/stylesheets/builtin-pages.less b/app/fg/builtin-pages/stylesheets/builtin-pages.less similarity index 100% rename from app/stylesheets/builtin-pages.less rename to app/fg/builtin-pages/stylesheets/builtin-pages.less diff --git a/app/fg/builtin-pages/stylesheets/builtin-pages/downloads.css b/app/fg/builtin-pages/stylesheets/builtin-pages/downloads.css new file mode 100644 index 0000000000..74c765e8dc --- /dev/null +++ b/app/fg/builtin-pages/stylesheets/builtin-pages/downloads.css @@ -0,0 +1,73 @@ +.flex, +.builtin-wrapper.builtin-wrapper.downloads-wrapper .ll-row.download .metadata { + display: flex; + flex-wrap: wrap; + flex-direction: row; +} +.link { + color: #295fcb; + cursor: pointer; + border: none; + outline: none; + background: none; + padding: 0; +} +.link:hover { + text-decoration: underline; +} +.link.disabled { + color: #999999; + cursor: default; +} +.link.disabled:hover { + text-decoration: none; +} +.builtin-wrapper.builtin-wrapper.downloads-wrapper .builtin-main { + max-width: 900px; +} +.builtin-wrapper.builtin-wrapper.downloads-wrapper .ll-row.download .title { + max-width: 375px; +} +.builtin-wrapper.builtin-wrapper.downloads-wrapper .ll-row.download .metadata { + align-items: center; +} +.builtin-wrapper.builtin-wrapper.downloads-wrapper .ll-row.download .metadata > *:not(:first-child) { + margin-left: 5px; +} +.builtin-wrapper.builtin-wrapper.downloads-wrapper .ll-row.download .metadata > * { + margin-right: 3px; +} +.builtin-wrapper.builtin-wrapper.downloads-wrapper .ll-row.download .buttons.controls { + opacity: 1; +} +.builtin-wrapper.builtin-wrapper.downloads-wrapper .ll-row.download .link { + color: #2864dc; + cursor: pointer; +} +.builtin-wrapper.builtin-wrapper.downloads-wrapper .ll-row.download .link:hover { + text-decoration: underline; +} +.builtin-wrapper.builtin-wrapper.downloads-wrapper .ll-row.download .btn.trash { + opacity: 0; +} +.builtin-wrapper.builtin-wrapper.downloads-wrapper .ll-row.download .btn.trash:hover { + color: #d43128; +} +.builtin-wrapper.builtin-wrapper.downloads-wrapper .ll-row.download:hover .btn.trash { + opacity: 1; +} +.builtin-wrapper.builtin-wrapper.downloads-wrapper .ll-row.download.progressing { + height: auto; + padding: 8px 15px; +} +.builtin-wrapper.builtin-wrapper.downloads-wrapper .ll-row.download.progressing .progress-ui { + flex: 1; + margin-right: 20px; +} +.builtin-wrapper.builtin-wrapper.downloads-wrapper .ll-row.download.progressing .controls { + margin-left: 0; + margin-right: 20px; +} +.builtin-wrapper.builtin-wrapper.downloads-wrapper .ll-row.download.progressing .metadata { + margin-left: auto; +} diff --git a/app/stylesheets/builtin-pages/downloads.less b/app/fg/builtin-pages/stylesheets/builtin-pages/downloads.less similarity index 100% rename from app/stylesheets/builtin-pages/downloads.less rename to app/fg/builtin-pages/stylesheets/builtin-pages/downloads.less diff --git a/app/fg/builtin-pages/stylesheets/builtin-pages/editor.css b/app/fg/builtin-pages/stylesheets/builtin-pages/editor.css new file mode 100644 index 0000000000..c08fb9cd35 --- /dev/null +++ b/app/fg/builtin-pages/stylesheets/builtin-pages/editor.css @@ -0,0 +1,888 @@ +.editor-sidebar { + display: flex; + flex-flow: column; + overflow-y: scroll; + text-overflow: ellipsis; + padding-left: 4px; + padding-top: 4px; +} +.editor-sidebar::-webkit-scrollbar { + display: none; +} +.editor-sidebar .file-tree-container { + height: 100%; +} +.editor-sidebar .title button { + font-weight: 500; +} +.editor-sidebar .file-controls { + display: flex; + padding: 0 2px; + font-size: 11px; + margin: 0 0 4px; + background: #f5f5f5; +} +.editor-sidebar .file-controls .label { + padding: 3px 13px; + border-radius: 3px; + flex: 1; +} +.editor-sidebar .file-controls button { + padding: 5px 7px; +} +.editor-sidebar .file-tree-header { + padding: 3px; +} +.editor-sidebar .file-tree { + margin-bottom: 10px; +} +.editor-sidebar .subtree { + padding-left: 10px; +} +.editor-sidebar .revision-indicator { + border-radius: 50%; +} +.editor-sidebar .filetree { + font-size: 13px; + color: #444; + flex: 1; +} +.editor-sidebar .filetree * { + cursor: pointer; +} +.editor-sidebar .item { + display: flex; + align-items: center; + padding: 2px 5px; + cursor: pointer; + white-space: nowrap; +} +.editor-sidebar .item i { + color: #444; +} +.editor-sidebar .item i.fa-caret-down { + transform: rotate(-45deg); +} +.editor-sidebar .item i + .name { + margin-left: 5px; +} +.editor-sidebar .item .name { + flex: 1; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} +.editor-sidebar .item:hover { + background: #ededf0; +} +.editor-sidebar .item.root { + background: transparent; + padding: 5px 10px; + margin: 0 0 4px; + border-bottom: 1px solid #ddd; + cursor: default; +} +.editor-sidebar .item.root .name { + color: rgba(0, 0, 0, 0.75); +} +.editor-sidebar .item.root .hover-ctrls { + display: block; +} +.editor-sidebar .item.deleted { + color: #bf8989; + font-style: italic; +} +.editor-sidebar .item.deleted i { + color: #bf8989; +} +.editor-sidebar .item.highlighted { + background: #dddde0; +} +.editor-sidebar .item.selected { + background: #2864dc; + color: #fff; +} +.editor-sidebar .item.selected i { + color: inherit; +} +.editor-sidebar .new-node input { + width: 98%; +} +.editor-sidebar .uncommitted-changes { + width: 100%; + text-align: left; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +#editor-sidebar-drag-handle { + width: 4px; + cursor: col-resize; +} +#editor-sidebar-drag-handle:hover { + background: rgba(0, 0, 0, 0.04); +} +#editor-sidebar-drag-handle i { + display: none; + position: relative; + top: 40vh; + font-size: 11px; + color: gray; + left: 2px; +} +#editor-sidebar-drag-handle.wide { + width: 8px; +} +#editor-sidebar-drag-handle.wide i { + display: inline; +} +.editor-tabs { + display: flex; + height: 39px; + padding-top: 4px; + overflow-x: scroll; +} +.editor-tabs::-webkit-scrollbar { + display: none; +} +.editor-tabs .tab { + height: 100%; + line-height: 35px; + z-index: 1; + cursor: pointer; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + border-bottom: 1px solid #ccc; + padding: 1px 11px 0 11px; +} +.editor-tabs .tab.active { + padding: 0 10px; + border: 1px solid #ccc; + border-bottom-color: transparent; +} +.editor-tabs .tab i { + opacity: 0; + padding-left: 10px; + z-index: 100; + cursor: pointer; +} +.editor-tabs .tab:hover i, +.editor-tabs .tab.active i { + opacity: 1; +} +.editor-tabs .tab.new-file { + color: #666; + flex: 0 0 35px; +} +.editor-tabs .tab.new-file:hover { + background: #eee; +} +.editor-tabs .unused-space { + flex: 1; + border-bottom: 1px solid #ccc; +} +.editor-home { + padding: 30px; + height: 100%; + overflow: auto; + user-select: text; +} +.editor-home h3.viewing { + border: 1px solid #ddd; + background: #fff; + padding: 30px; +} +.editor-home .readme { + font-family: inherit; + line-height: 1.5; + color: rgba(0, 0, 0, 0.65); +} +.editor-home .readme h1 { + font-weight: bold; +} +.editor-home .readme h1, +.editor-home .readme h2, +.editor-home .readme h3 { + margin: 20px 0; +} +.editor-home .readme ul, +.editor-home .readme ol { + margin-bottom: 15px; + list-style: inherit; + margin-left: 30px; +} +.editor-home .readme pre, +.editor-home .readme code { + font-size: 13.5px; + background: #f5f5f5; + color: #333; + border-radius: 2px; +} +.editor-home .readme pre { + padding: 15px; + border: 0; +} +.editor-home .readme code { + padding: 3px 5px; +} +.editor-home .readme img { + max-width: 100%; +} +.editor-home .readme a:hover { + text-decoration: underline; +} +.editor-home .readme > :first-child { + margin-top: 0; +} +.editor-home .readme > :last-child { + margin-bottom: 0; +} +.editor-home .readme .anchor-link { + visibility: hidden; +} +.editor-home .readme h1:hover .anchor-link, +.editor-home .readme h2:hover .anchor-link, +.editor-home .readme h3:hover .anchor-link, +.editor-home .readme h4:hover .anchor-link, +.editor-home .readme h5:hover .anchor-link { + visibility: visible; +} +.editor-home .uncommitted-changes { + border: 1px solid #ddd; + background: #fff; + padding: 30px; + margin-bottom: -5vh; +} +.editor-home .uncommitted-changes .btns { + margin-bottom: 10px; +} +.editor-home .uncommitted-changes a { + cursor: pointer; + color: rgba(0, 0, 0, 0.65); +} +.editor-home .uncommitted-changes a.add { + color: #116520; +} +.editor-home .uncommitted-changes a.mod { + color: #9a7c05; +} +.editor-home .uncommitted-changes a.del { + color: #86120c; +} +.editor-home .uncommitted-changes .revision-indicator { + border-radius: 50%; + margin-left: 4px; + margin-right: 4px; +} +.editor-home .uncommitted-changes a:hover { + text-decoration: underline; +} +.editor-home .error-notice { + margin: -10px -10px 30px; +} +.editor-home .hotkeys { + margin-top: 20px; + color: rgba(0, 0, 0, 0.5); + border-top: 1px solid #ddd; + padding-top: 30px; +} +.settings-form .settings-form-inner { + background: #fff; + border: 1px solid #ccc; + border-top: 0; +} +.settings-form #save-settings-form { + margin: 4px 0; +} +.settings-form section { + border-top: 1px solid #ccc; +} +.settings-form section .heading { + display: flex; + align-items: center; + cursor: pointer; + background: #fafafa; +} +.settings-form section .heading .expander { + padding: 4px 6px; + font-size: 16px; +} +.settings-form section .heading .expander .fa-caret-right { + display: none; +} +.settings-form section .heading .title { + font-weight: 500; +} +.settings-form section.collapsed .heading .expander .fa-caret-right { + display: inline-block; +} +.settings-form section.collapsed .heading .expander .fa-caret-down { + display: none; +} +.settings-form section.collapsed .controls { + display: none !important; +} +.settings-form section .controls { + max-height: 50vh; + overflow-y: auto; + padding: 12px 12px; + border-top: 1px solid #ddd; +} +.settings-form section .controls.perms-grid { + display: grid; + grid-template-columns: 1fr; + grid-gap: 10px; + max-width: 740px; +} +.settings-form .control { + margin-bottom: 16px; + width: 300px; +} +.settings-form .control label { + display: block; + font-weight: 500; +} +.settings-form .control input, +.settings-form .control textarea { + width: 300px; +} +.settings-form .control input[type="checkbox"] { + display: inline-block; + width: auto; + height: auto; + margin: 0 8px; +} +.settings-form .control textarea { + height: auto; + padding: 4px 7px; + resize: vertical; +} +.settings-form .control .btn.white { + background: #fff; +} +.settings-form .control .toggle { + display: flex; + margin-top: 5px; +} +.settings-form .control .toggle input { + display: none; +} +.settings-form .control .thumbnail { + width: 100px; + height: 80px; + border: 1px solid #ccc; + border-radius: 4px; + object-fit: cover; + background: #fff; +} +.settings-form .control .thumbnail:not(.readonly):hover { + cursor: pointer; + border-color: #999; +} +.settings-form .control .dns-control { + padding: 10px; + border: 1px solid #ccc; + border-radius: 3px; +} +.settings-form .control .dns-control .dns-control-msg { + margin-bottom: 6px; + font-size: 14px; +} +.settings-form .control .dns-control .dns-control-msg.readonly { + margin-bottom: 0; +} +.settings-form .control .dns-control .dns-control-msg .fa-exclamation-triangle { + color: #eda504; +} +.settings-form .control .help { + display: flex; + align-items: baseline; + color: #929292; + padding: 8px; +} +.settings-form .control .help .fa-fw { + margin-right: 8px; + color: #d8d8d8; +} +.settings-form .control.syncpath p { + margin: 6px 0; +} +.settings-form .control.syncpath p.copy-path { + display: flex; + margin-top: 5px; + margin-bottom: 0; +} +.settings-form .control.syncpath p.copy-path input { + flex: 1; + font-size: 12px; + border-color: #eee; + background: #eee; + margin-right: 5px; +} +.settings-form .control.syncpath p.copy-path .btn.full-width { + margin-top: 10px; +} +.settings-form .control.permission { + border: 1px solid #ddd; + background: #fff; + width: auto; + border-radius: 4px; + padding: 12px 16px; + margin: 0; +} +.settings-form .control.permission p { + margin: 4px 0 6px; +} +.settings-form .control.permission label { + padding: 2px 0; + font-weight: 400; +} +.archive-history-dropdown .dropdown-items { + width: 275px !important; +} +.archive-history { + font-size: 12.5px; +} +.archive-history .archive-history-header { + padding: 5px 10px; + border-bottom: 1px solid #ddd; + font-weight: 500; +} +.archive-history .archive-history-body { + max-height: 350px; + overflow-x: hidden; + overflow-y: auto; +} +.archive-history .archive-history-body.error { + padding: 10px; +} +.archive-history hr { + margin: 0 0 5px; +} +.archive-history .archive-history-item { + flex-wrap: nowrap; + align-items: center; + height: 35px; + padding: 0 10px; + border-bottom: 1px solid #eee; + cursor: pointer; +} +.archive-history .archive-history-item.no-border { + border-color: transparent; +} +.archive-history .archive-history-item .fa { + margin-right: 5px; +} +.archive-history .archive-history-item .version { + min-width: 32px; + margin-left: auto; +} +.archive-history .archive-history-item .path { + max-width: 145px; + margin-left: 3px; +} +.archive-history .archive-history-item .sync-path { + max-width: 123px; + margin-left: 5px; +} +.archive-history .archive-history-item:hover { + background: #f0f0f0; +} +.archive-history .archive-history-item.selected { + background: #2864dc; + color: #fff; +} +.revision-indicator { + display: inline-block; + width: 8px; + height: 8px; + margin-top: -0.4px; + margin-left: -2px; +} +.revision-indicator.add { + background: #44c35a; +} +.revision-indicator.mod { + background: #fac800; +} +.revision-indicator.del { + background: #d93229; +} +.dropdown.share .dropdown-items { + width: 350px; +} +.dropdown.share .dropdown-item { + background: #fff; + cursor: default; + padding: 15px; +} +.dropdown.share .dropdown-item p { + margin-left: 0; + color: #333; +} +.dropdown.share p.copy-url { + margin-top: 5px; + margin-bottom: 0; +} +.dropdown.share p.copy-url input { + flex: 1; + font-size: 12px; + border-color: #eee; + background: #eee; + margin-right: 5px; +} +.dropdown.share p.copy-url .btn.full-width { + margin-top: 10px; +} +.markdown { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 1.42857143; +} +.markdown h1, +.markdown h2, +.markdown h3, +.markdown h4, +.markdown h5, +.markdown h6 { + font-family: inherit; + font-weight: 500; + line-height: 1.1; + color: inherit; +} +.markdown h1 small, +.markdown h2 small, +.markdown h3 small, +.markdown h4 small, +.markdown h5 small, +.markdown h6 small { + font-weight: normal; + line-height: 1; + color: #999; +} +.markdown h1, +.markdown h2, +.markdown h3 { + margin-top: 20px; + margin-bottom: 10px; +} +.markdown h1 small, +.markdown h2 small, +.markdown h3 small, +.markdown h1 .small, +.markdown h2 .small, +.markdown h3 .small { + font-size: 65%; +} +.markdown h4, +.markdown h5, +.markdown h6 { + margin-top: 10px; + margin-bottom: 10px; +} +.markdown h4 small, +.markdown h5 small, +.markdown h6 small, +.markdown h4 .small, +.markdown h5 .small, +.markdown h6 .small { + font-size: 75%; +} +.markdown h1 { + font-size: 2em; +} +.markdown h2 { + font-size: 1.75em; +} +.markdown h3 { + font-size: 1.5em; +} +.markdown h4 { + font-size: 1.25em; +} +.markdown h5 { + font-size: 1.15em; +} +.markdown h6 { + font-size: 1.1em; +} +.markdown p { + margin: 0 0 10px; +} +.markdown small { + font-size: 85%; +} +.markdown ul, +.markdown ol { + margin-top: 0; + margin-bottom: 10px; +} +.markdown ul ul, +.markdown ol ul, +.markdown ul ol, +.markdown ol ol { + margin-bottom: 0; +} +.markdown blockquote { + padding: 10px 20px; + margin: 0 0 20px; + font-size: 17.5px; + border-left: 5px solid #ccc; +} +.markdown blockquote p:last-child, +.markdown blockquote ul:last-child, +.markdown blockquote ol:last-child { + margin-bottom: 0; +} +.markdown blockquote footer, +.markdown blockquote small { + display: block; + font-size: 80%; + line-height: 1.42857143; + color: #ccc; +} +.markdown blockquote footer:before, +.markdown blockquote small:before { + content: '\2014 \00A0'; +} +.markdown code, +.markdown kbd, +.markdown pre, +.markdown samp { + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; +} +.markdown code { + padding: 2px 4px; + font-size: 90%; + color: #c7254e; + background-color: #f9f2f4; + border-radius: 4px; +} +.markdown kbd { + padding: 2px 4px; + font-size: 90%; + color: #fff; + background-color: #333; + border-radius: 3px; + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.markdown kbd kbd { + padding: 0; + font-size: 100%; + font-weight: 500; + box-shadow: none; +} +.markdown pre { + display: block; + padding: 9.5px; + margin: 0 0 10px; + font-size: 13px; + line-height: 1.42857143; + word-break: break-all; + word-wrap: break-word; + color: #777; + background-color: #f5f5f5; + border: 1px solid #ccc; + border-radius: 4px; +} +.markdown pre code { + padding: 0; + font-size: inherit; + color: inherit; + white-space: pre-wrap; + background-color: transparent; + border: 0; +} +.markdown .pre-scrollable { + max-height: 340px; + overflow-y: scroll; +} +.muted { + color: gray; +} +.thin { + font-weight: 300; +} +.icon-info { + color: transparent; + -webkit-text-stroke: 1px #333; +} +.editor-wrapper { + display: flex; + height: 100%; + flex-flow: row nowrap; + user-select: none; +} +.editor-container { + flex: 1; + margin-right: 5px; +} +.editor-container #editor, +.editor-container #diffEditor, +.editor-container #imageViewer, +.editor-container #genericViewer { + height: calc(100% - 70px); + background-color: white; + border-left: 1px solid #ccc; + border-right: 1px solid #ccc; +} +.editor-container #imageViewer { + position: relative; +} +.editor-container #imageViewer img { + max-width: 100%; + height: auto; + position: absolute; + top: 25px; + left: 25px; +} +.editor-container #genericViewer { + height: calc(100% - 40px); +} +.editor-container #genericViewer .opaque-binary { + text-align: left; + padding: 20px 30px; +} +.editor-container #genericViewer .deleted-filediff { + padding: 20px 40px; +} +.editor-container #genericViewer .deleted-filediff .path { + color: #c37b7b; + font-style: italic; +} +.editor-container #genericViewer .deleted-filediff .path .revision-indicator { + margin: 0 4px 0 0; +} +.editor-settings-sidebar { + flex: 0 0 340px; + margin-right: 5px; + margin-top: 34px; +} +.editor-settings-sidebar .ctrls { + margin: 4px 0; + display: flex; + flex: 0 0 auto; +} +.editor-settings-sidebar .ctrls > * { + margin-right: 4px; +} +.editor-settings-sidebar .ctrls .readonly { + height: 30px; + line-height: 30px; + padding: 0 10px; +} +.editor-settings-sidebar.collapsed { + flex: 0 0 34px; +} +.editor-settings-sidebar.collapsed .ctrls { + flex-direction: column; +} +.editor-settings-sidebar.collapsed .ctrls .btn { + width: 34px; +} +.editor-settings-sidebar.collapsed .ctrls > * { + margin-right: 0; + margin-bottom: 4px; +} +.editor-toolbar { + display: flex; + border-left: 1px solid #ccc; + border-right: 1px solid #ccc; + border-bottom: 1px solid #ddd; + padding: 0 4px; +} +.editor-toolbar:empty { + display: none; +} +.editor-toolbar .btn { + border-radius: 0; + line-height: 28px; +} +.editor-toolbar .btn i { + font-size: 11px; + position: relative; + top: -1px; +} +.editor-toolbar .divider { + width: 1px; + border-left: 1px solid #ddd; + margin: 4px; +} +.editor-toolbar .spacer { + flex: 1; +} +h3 { + font-size: 1.2em; + font-weight: 300; + margin-bottom: 10px; + line-height: 1; +} +.quick-link { + margin-bottom: 30px; +} +.quick-link div { + color: rgba(0, 0, 0, 0.65); +} +.cover-screen { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 1000; + background: #fff; + font-size: 18px; + font-weight: 300; + color: #333; +} +.cover-screen.gray { + background: #fafafa; +} +.cover-screen .cover-screen-centered { + position: absolute; + top: 40%; + left: 50%; + transform: translate(-50%, -50%); +} +.cover-screen .cover-screen-leftpane { + position: absolute; + background: #fff; + padding: 30px 80px 20px 70px; + height: 100%; +} +.cover-screen .loading { + font-size: 20px; +} +.cover-screen .loading .spinner { + margin-right: 5px; +} +.cover-screen .load-error { + width: 550px; +} +.cover-screen .load-error > div { + margin-bottom: 16px; +} +.cover-screen .load-error small { + font-size: 14px; + color: #999; +} +.loading-notice { + display: flex; + align-items: center; + font-size: 14px; + padding: 20px; +} +.loading-notice .spinner { + margin-right: 5px; +} +.error-notice { + padding: 20px; + color: #b55757; +} +.monaco-editor .line-numbers { + color: #999 !important; +} +.monaco-editor .current-line ~ .line-numbers { + color: #222 !important; +} diff --git a/app/stylesheets/builtin-pages/editor.less b/app/fg/builtin-pages/stylesheets/builtin-pages/editor.less similarity index 100% rename from app/stylesheets/builtin-pages/editor.less rename to app/fg/builtin-pages/stylesheets/builtin-pages/editor.less diff --git a/app/fg/builtin-pages/stylesheets/builtin-pages/history.css b/app/fg/builtin-pages/stylesheets/builtin-pages/history.css new file mode 100644 index 0000000000..a4af033c06 --- /dev/null +++ b/app/fg/builtin-pages/stylesheets/builtin-pages/history.css @@ -0,0 +1,22 @@ +.link { + color: #295fcb; + cursor: pointer; + border: none; + outline: none; + background: none; + padding: 0; +} +.link:hover { + text-decoration: underline; +} +.link.disabled { + color: #999999; + cursor: default; +} +.link.disabled:hover { + text-decoration: none; +} +.builtin-wrapper.history-wrapper .ll-row:not(:last-of-type) { + border-bottom: 1px solid #e6e6e6; + border-top: 0; +} diff --git a/app/stylesheets/builtin-pages/history.less b/app/fg/builtin-pages/stylesheets/builtin-pages/history.less similarity index 100% rename from app/stylesheets/builtin-pages/history.less rename to app/fg/builtin-pages/stylesheets/builtin-pages/history.less diff --git a/app/fg/builtin-pages/stylesheets/builtin-pages/settings.css b/app/fg/builtin-pages/stylesheets/builtin-pages/settings.css new file mode 100644 index 0000000000..8642251b21 --- /dev/null +++ b/app/fg/builtin-pages/stylesheets/builtin-pages/settings.css @@ -0,0 +1,631 @@ +.vertical-scroll { + overflow-y: scroll; +} +.shadow { + box-shadow: 0 3px 15px rgba(0, 0, 0, 0.25); +} +.dropdown-shadow, +.dropdown-items { + box-shadow: 0 2px 15px rgba(0, 0, 0, 0.3); +} +.dropdown-shadow-subtle, +.dropdown-items.subtle-shadow { + box-shadow: 0 2px 15px rgba(0, 0, 0, 0.15); +} +.overflow-ellipsis { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.hidden { + display: none !important; +} +.dropdown { + position: relative; +} +.dropdown i { + cursor: pointer; +} +.dropdown.open .toggleable:not(.primary) { + background: #dadada; + box-shadow: inset 0 0 3px rgba(0, 0, 0, 0.1); + border-color: transparent; + outline: 0; +} +.toggleable-container .dropdown-items { + display: none; +} +.toggleable-container.hover:hover .dropdown-items, +.toggleable-container.open .dropdown-items { + display: block; +} +.dropdown-items { + width: 270px; + position: absolute; + right: 0px; + z-index: 3000; + background: #fff; + border: 1px solid #dadada; + border-radius: 4px; +} +.dropdown-items .section { + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + padding: 5px 0; +} +.dropdown-items .section-header { + padding: 2px 10px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.dropdown-items .section-header.light { + color: #b8b8b8; + font-weight: 500; +} +.dropdown-items.thin { + width: 170px; +} +.dropdown-items.wide { + width: 400px; +} +.dropdown-items.compact .dropdown-item { + padding: 2px 15px; + border-bottom: 0; +} +.dropdown-items.compact .description { + margin-left: 0; +} +.dropdown-items.compact hr { + margin: 5px 0; +} +.dropdown-items.roomy .dropdown-item { + padding: 10px 15px; +} +.dropdown-items.no-border .dropdown-item { + border-bottom: 0; +} +.dropdown-items.center { + left: 50%; + transform: translateX(-50%); +} +.dropdown-items.left { + right: initial; + left: 0; +} +.dropdown-items.over { + top: 0; +} +.dropdown-items.top { + bottom: calc(100% + 5px); +} +.dropdown-items.with-triangle:before { + content: ''; + position: absolute; + top: -8px; + right: 10px; + width: 12px; + height: 12px; + z-index: 3; + width: 0; + height: 0; + border-left: 8px solid transparent; + border-right: 8px solid transparent; + border-bottom: 8px solid #fff; +} +.dropdown-items.with-triangle.left:before { + left: 10px; +} +.dropdown-items.with-triangle.center:before { + left: 43%; +} +.dropdown-title { + border-bottom: 1px solid #eee; + padding: 2px 8px; + font-size: 11px; + color: gray; +} +.dropdown-item { + display: block; + width: 100%; + padding: 5px 15px; + border-bottom: 1px solid #eee; +} +.dropdown-item.disabled { + opacity: 0.25; +} +.dropdown-item .fa-check-square { + color: #2864dc; +} +.dropdown-item .fa-check-square, +.dropdown-item .fa-square-o { + font-size: 14px; +} +.dropdown-item .fa-check { + font-size: 11.5px; +} +.dropdown-item.no-border { + border-bottom: 0; +} +.dropdown-item:hover:not(.no-hover) { + background: #eee; + cursor: pointer; +} +.dropdown-item:hover:not(.no-hover) i:not(.fa-check-square) { + color: #333; +} +.dropdown-item:hover:not(.no-hover) .description { + color: #333; +} +.dropdown-item:hover:not(.no-hover).disabled { + background: inherit; + cursor: default; +} +.dropdown-item .fa, +.dropdown-item i { + display: inline-block; + width: 20px; + color: rgba(0, 0, 0, 0.65); +} +.dropdown-item img { + display: inline-block; + width: 16px; + position: relative; + top: 3px; + margin-right: 6px; +} +.dropdown-item .btn .fa { + color: inherit; +} +.dropdown-item .label { + font-weight: 500; + margin-bottom: 3px; +} +.dropdown-item .description { + color: #707070; + margin: 0; + margin-left: 23px; + margin-bottom: 3px; + line-height: 1.5; +} +.dropdown-item .description.small { + font-size: 12.5px; +} +.dropdown-item:first-of-type { + border-radius: 2px 2px 0 0; +} +.dropdown-item:last-of-type { + border-radius: 0 0 2px 2px; +} +.context-menu { + position: absolute; + z-index: 10000; +} +.context-menu .dropdown-items:not(.custom) { + width: auto; + white-space: nowrap; +} +.context-menu .dropdown-items:not(.custom) .dropdown-item { + padding-right: 30px; +} +.toast-wrapper { + position: fixed; + top: 20px; + right: 20px; + z-index: 20000; + transition: opacity 0.1s ease; +} +.toast-wrapper.hidden { + opacity: 0; +} +.toast { + position: relative; + min-width: 350px; + max-width: 450px; + background: #ddd; + margin: 0; + padding: 10px 15px; + border-radius: 4px; + font-size: 16px; + color: #fff; + background: rgba(0, 0, 0, 0.75); + -webkit-font-smoothing: antialiased; + font-weight: 600; +} +.toast.error { + padding-left: 38px; +} +.toast.success { + padding-left: 48px; +} +.toast.success:before, +.toast.error:before { + position: absolute; + left: 18px; + top: 5px; + display: block; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Ubuntu, Cantarell, "Oxygen Sans", "Helvetica Neue", sans-serif; + font-size: 22px; + font-weight: bold; +} +.toast.primary { + background: #2864dc; +} +.toast.success { + background: #26b33e; +} +.toast.success:before { + content: '✓'; +} +.toast.error { + background: #c72e25; +} +.toast.error:before { + content: '!'; +} +.toast .toast-btn { + position: absolute; + right: 15px; + color: inherit; + text-decoration: underline; + cursor: pointer; +} +.dat-cache { + margin: 0 -15px; +} +.dat-cache.loading { + margin: 0; +} +.dat-cache .dat-cache-actions { + padding: 0 10px 10px; +} +.dat-cache .heading { + align-items: center; + padding: 6px 15px; + color: #333; + font-size: 11px; +} +.dat-cache .heading .title { + flex: 1; +} +.dat-cache .heading .peers, +.dat-cache .heading .size, +.dat-cache .heading .mtime { + width: 100px; +} +.dat-cache .heading .buttons { + width: 110px; +} +.dat-cache .heading a { + color: #333 !important; + cursor: pointer; +} +.dat-cache .ll-row.archive { + border-right: 0; + border-left: 0; +} +.dat-cache .ll-row.archive .title { + flex: 1; +} +.dat-cache .ll-row.archive .peers, +.dat-cache .ll-row.archive .size, +.dat-cache .ll-row.archive .mtime { + color: rgba(0, 0, 0, 0.5); + flex: 0 0 100px; + white-space: nowrap; + overflow: hidden; +} +.dat-cache .ll-row.archive .buttons { + width: 110px; + overflow: visible; + opacity: 1; +} +.dat-cache .ll-row.archive .buttons .btn { + margin-left: 5px; +} +.dat-cache .ll-row.archive .buttons .btn.copy-url, +.dat-cache .ll-row.archive .buttons .btn.remove-workspace, +.dat-cache .ll-row.archive .buttons .btn.remove-archive { + opacity: 0; + border: 0; + transition: 0.2s ease opacity; + margin-left: -3px; + color: rgba(0, 0, 0, 0.65); +} +.dat-cache .ll-row.archive .buttons .btn.remove-workspace, +.dat-cache .ll-row.archive .buttons .btn.remove-archive { + color: #cc2f26; +} +.logger .controls { + position: sticky; + top: 0; + background: #fff; +} +.logger .standard-controls { + display: flex; + align-items: center; + color: #555; + padding: 6px 0; +} +.logger .standard-controls input { + display: none; +} +.logger .standard-controls .spacer { + flex: 1; +} +.logger .standard-controls .divider { + margin-left: 20px; + margin-right: 5px; + border-right: 1px solid #ccc; + height: 14px; +} +.logger .standard-controls .divider.thin { + margin-left: 5px; +} +.logger .standard-controls label { + margin-left: 15px; + margin-bottom: 0; + font-weight: normal; +} +.logger .standard-controls .status { + margin-left: 8px; +} +.logger .logger-row td { + padding: 4px; + border-top: 1px solid #fff; +} +.logger .logger-row.purple { + background: #fdf3ff; + color: #9C27B0; +} +.logger .logger-row.blue { + background: #e6f4ff; + color: #2196F3; +} +.logger .logger-row.cyan { + background: #e7fcff; + color: #00BCD4; +} +.logger .logger-row.green { + background: #f1f9f1; + color: #4CAF50; +} +.logger .logger-row.yellow { + background: #fff3cc; + color: #FFC107; +} +.logger .logger-row.red { + background: #fddee9; + color: #E91E63; +} +.logger .logger-row:hover { + background: #fff; +} +.logger .logger-row .msg { + color: #111; +} +.logger .logger-row .level, +.logger .logger-row .category, +.logger .logger-row .subcategory, +.logger .logger-row .timestamp { + white-space: nowrap; +} +.logger .logger-row .level { + font-weight: 500; +} +.logger .logger-row small { + color: rgba(0, 0, 0, 0.5); +} +.builtin-wrapper.settings-wrapper ul { + margin-bottom: 1em; +} +.builtin-wrapper.settings-wrapper .builtin-sidebar .builtin-pages-nav { + position: relative; + left: -21px; + font-size: 16px; +} +.builtin-wrapper.settings-wrapper .builtin-sidebar .nav-item { + font-size: 15px; +} +@media (min-width: 1050px) { + .builtin-wrapper.settings-wrapper .builtin-sidebar .nav-item { + margin-bottom: 5px; + } +} +.builtin-wrapper.settings-wrapper .builtin-sidebar .nav-item.active { + font-weight: normal; +} +.builtin-wrapper.settings-wrapper .subtitle-heading { + margin-bottom: 10px; +} +.builtin-wrapper.settings-wrapper h3 { + margin-bottom: 5px; + font-size: inherit; + font-weight: 500; +} +.builtin-wrapper.settings-wrapper .section { + padding: 20px 0; + margin: 0 20px; + border-top: 1px solid #eee; +} +.builtin-wrapper.settings-wrapper .section.default-browser .toggle { + justify-content: flex-end; + width: 100px; +} +.builtin-wrapper.settings-wrapper .section.default-browser .toggle .text { + width: 45px; +} +.builtin-wrapper.settings-wrapper .section.default-to-dat .toggle { + justify-content: flex-end; +} +.builtin-wrapper.settings-wrapper .section.default-to-dat .toggle .text { + margin-right: 10px; +} +.builtin-wrapper.settings-wrapper .section.analytics .toggle { + justify-content: flex-end; +} +.builtin-wrapper.settings-wrapper .section.analytics .toggle .text { + width: 110px; +} +.builtin-wrapper.settings-wrapper .view { + background: none; + border: 0; + padding: 0; +} +.builtin-wrapper.settings-wrapper .view > .section { + padding: 15px 20px; + margin: 0 0 10px; + border: 1px solid #e6e6e6; + background: #fff; +} +.builtin-wrapper.settings-wrapper .section-group { + display: flex; +} +.builtin-wrapper.settings-wrapper .section-group .section { + flex: 1; +} +.builtin-wrapper.settings-wrapper .section-group .section:first-of-type { + margin-right: 0; + border-right: 1px solid #e6e6e6; +} +.builtin-wrapper.settings-wrapper .section-group .section:last-of-type { + margin-left: 0; + padding-left: 20px; +} +.builtin-wrapper.settings-wrapper .auto-updater .spinner { + display: inline-block; +} +.builtin-wrapper.settings-wrapper .auto-updater input { + height: auto; +} +.builtin-wrapper.settings-wrapper .auto-updater .btn { + margin-right: 5px; +} +.builtin-wrapper.settings-wrapper .auto-updater .up-to-date { + color: #3fb453; +} +.builtin-wrapper.settings-wrapper.protocols a { + margin-left: 5px; +} +.builtin-wrapper.settings-wrapper .on-startup .radio-group { + display: grid; + grid-template-columns: auto auto; + align-items: center; + justify-content: start; + grid-gap: 0 10px; + margin-left: 0; +} +.builtin-wrapper.settings-wrapper .on-startup .radio-group input[type="radio"] { + margin: 0; +} +.builtin-wrapper.settings-wrapper .on-startup label { + font-weight: 400; + margin-bottom: 0; +} +.builtin-wrapper.settings-wrapper .dat-bandwidth .inputs > div { + margin-right: 5px; +} +.builtin-wrapper.settings-wrapper .dat-bandwidth .inputs label { + display: block; +} +.builtin-wrapper.settings-wrapper .default-dat-ignore textarea { + width: 500px; + height: 140px; + padding: 4px 7px; +} +.builtin-wrapper.settings-wrapper .dat-cache .title { + max-width: none; +} +.builtin-wrapper.settings-wrapper.help a { + text-decoration: none; +} +.builtin-wrapper.settings-wrapper .default-application { + display: flex; + margin-bottom: 10px; +} +.builtin-wrapper.settings-wrapper .default-application a, +.builtin-wrapper.settings-wrapper .default-application .btn, +.builtin-wrapper.settings-wrapper .default-application .domain { + height: 36px; + line-height: 34px; +} +.builtin-wrapper.settings-wrapper .default-application .group { + display: flex; + margin-right: 10px; +} +.builtin-wrapper.settings-wrapper .default-application .group > * { + border-radius: 0; + border-right-width: 0; + padding: 0 10px; + height: 36px; +} +.builtin-wrapper.settings-wrapper .default-application .group > :first-child { + border-top-left-radius: 2px; + border-bottom-left-radius: 2px; +} +.builtin-wrapper.settings-wrapper .default-application .group > :last-child { + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + border-right-width: 1px; +} +.builtin-wrapper.settings-wrapper .default-application .group > input:focus { + border-right-width: 1px; + box-shadow: none; +} +.builtin-wrapper.settings-wrapper .default-application .group > input:focus + * { + border-left-width: 0; +} +.builtin-wrapper.settings-wrapper .default-application .group .domain { + border: 1px solid #d9d9d9; + border-right: 0; + width: 80px; + color: #666; +} +.builtin-wrapper.settings-wrapper .default-application .group .value { + width: 280px; + text-overflow: ellipsis; + border: 1px solid #d9d9d9; + border-left: 0; + border-right: 0; +} +.builtin-wrapper.settings-wrapper .more-info { + display: flex; + color: rgba(0, 0, 0, 0.6); + margin: 30px 0; +} +.builtin-wrapper.settings-wrapper .more-info i { + margin: 7px 16px; +} +.builtin-wrapper.settings-wrapper .more-info > div { + flex: 1; + max-width: 36em; +} +.builtin-wrapper.settings-wrapper .more-info h4 { + margin-bottom: 10px; +} +.builtin-wrapper.settings-wrapper .user { + display: flex; + padding: 10px; + border-top: 1px solid #ddd; + align-items: center; +} +.builtin-wrapper.settings-wrapper .user:last-child { + border-bottom: 1px solid #ddd; +} +.builtin-wrapper.settings-wrapper .user .user-thumb { + width: 90px; +} +.builtin-wrapper.settings-wrapper .user .user-thumb img { + border-radius: 50%; + width: 70px; + height: 70px; + object-fit: cover; +} +.builtin-wrapper.settings-wrapper .user .user-title { + font-size: 16px; +} +.builtin-wrapper.settings-wrapper .user .user-title small { + color: gray; +} +ul.settings-section { + margin-left: 10px; +} diff --git a/app/stylesheets/builtin-pages/settings.less b/app/fg/builtin-pages/stylesheets/builtin-pages/settings.less similarity index 100% rename from app/stylesheets/builtin-pages/settings.less rename to app/fg/builtin-pages/stylesheets/builtin-pages/settings.less diff --git a/app/fg/builtin-pages/stylesheets/builtin-pages/swarm-debugger.css b/app/fg/builtin-pages/stylesheets/builtin-pages/swarm-debugger.css new file mode 100644 index 0000000000..3dbde300fb --- /dev/null +++ b/app/fg/builtin-pages/stylesheets/builtin-pages/swarm-debugger.css @@ -0,0 +1,653 @@ +*, +*:before, +*:after { + box-sizing: border-box; +} +body { + margin: 0; +} +ul, +ol { + list-style: none; + padding: 0; + margin: 0; +} +a { + text-decoration: none; + color: inherit; +} +a[href], +input[type='submit'], +input[type='image'], +label[for], +select, +button { + cursor: pointer; +} +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0; +} +button { + background: none; + outline-color: transparent; + border: none; +} +.system-font, +body, +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Ubuntu, Cantarell, "Oxygen Sans", "Helvetica Neue", sans-serif; +} +.code-font, +code { + font-family: Consolas, 'Lucida Console', Monaco, monospace; +} +code { + font-style: normal !important; +} +.red-x { + position: relative; +} +.red-x:after { + content: 'x'; + display: block; + position: absolute; + top: -4px; + left: 1px; + font-family: sans-serif; + font-size: 13px; + font-weight: bold; + color: red; + -webkit-text-stroke: 1px #fff; +} +.red-x.fa-rss:after { + width: 14px; +} +textarea { + line-height: 1.4; +} +input, +textarea { + height: 30px; + padding: 0 7px; + border-radius: 4px; + color: rgba(51, 51, 51, 0.95); + border: 1px solid #d9d9d9; +} +input[type="checkbox"], +textarea[type="checkbox"], +input[type="radio"], +textarea[type="radio"], +input[type="range"], +textarea[type="range"] { + padding: 0; +} +input[type="checkbox"]:focus, +textarea[type="checkbox"]:focus, +input[type="radio"]:focus, +textarea[type="radio"]:focus, +input[type="range"]:focus, +textarea[type="range"]:focus { + box-shadow: none; +} +input[type="radio"], +textarea[type="radio"] { + width: 14px; + height: 14px; + outline: none; + -webkit-appearance: none; + border-radius: 50%; + cursor: pointer; + transition: border 0.1s ease; +} +input[type="radio"]:hover, +textarea[type="radio"]:hover { + border: 1px solid #2864dc; +} +input[type="radio"]:checked, +textarea[type="radio"]:checked { + border: 4.5px solid #2864dc; +} +input[type="file"], +textarea[type="file"] { + padding: 0; + border: 0; + line-height: 1; +} +input[type="file"]:focus, +textarea[type="file"]:focus { + border: 0; + box-shadow: none; +} +input.roomy, +textarea.roomy { + padding: 7px 9px; +} +input:focus, +textarea:focus, +select:focus { + outline: 0; + border: 1px solid rgba(41, 95, 203, 0.8); + box-shadow: 0 0 0 2px rgba(41, 95, 203, 0.2); +} +input.error, +textarea.error, +select.error { + border: 1px solid rgba(209, 48, 39, 0.75); +} +input.error:focus, +textarea.error:focus, +select.error:focus { + box-shadow: 0 0 0 2px rgba(204, 47, 38, 0.15); +} +input.nofocus:focus, +textarea.nofocus:focus, +select.nofocus:focus { + outline: 0; + box-shadow: none; + border: initial; +} +input.inline { + height: auto; + border: 1px solid transparent; + border-radius: 0; + background: transparent; + cursor: text; + padding: 3px 5px; + line-height: 1; +} +input.inline:focus, +input.inline:hover { + border: 1px solid #ccc; + box-shadow: none; +} +input.inline:focus { + background: #fff; +} +input.underline { + width: 100%; + height: auto; + padding: 0; + border: 0; + border-radius: 0; + background: transparent; + cursor: text; + line-height: 1; +} +.input-file-picker { + display: flex; + align-items: center; + padding: 3px; + border-radius: 2px; + border: 1px solid #d9d9d9; + color: #707070; +} +.input-file-picker span { + flex: 1; + padding-left: 3px; +} +::-webkit-input-placeholder { + color: rgba(0, 0, 0, 0.5); + font-size: 0.8rem; +} +label { + font-weight: 500; +} +input[disabled][data-tooltip], +label[disabled][data-tooltip] { + cursor: help; +} +input[disabled][data-tooltip] *, +label[disabled][data-tooltip] * { + cursor: help; +} +.toggle { + align-items: center; + flex-direction: row-reverse; + justify-content: space-between; + margin-bottom: 10px; + cursor: pointer; + overflow: initial; +} +.toggle.unweirded { + flex-direction: row; + justify-content: initial; +} +.toggle.unweirded .switch { + margin-right: 10px; +} +.toggle * { + cursor: pointer; +} +.toggle.disabled { + cursor: default; +} +.toggle.disabled * { + cursor: default; +} +.toggle input { + display: none; +} +.toggle .text { + font-weight: 400; +} +.toggle .switch { + display: inline-block; + position: relative; + width: 32px; + height: 17px; +} +.toggle .switch:before, +.toggle .switch:after { + position: absolute; + display: block; + content: ''; +} +.toggle .switch:before { + width: 100%; + height: 100%; + border-radius: 40px; + background: #dadada; +} +.toggle .switch:after { + width: 11px; + height: 11px; + border-radius: 50%; + left: 3px; + top: 3px; + background: #fafafa; + transition: transform 0.15s ease; +} +.toggle input:checked:not(:disabled) + .switch:before { + background: #41b855; +} +.toggle input:checked:not(:disabled) + .switch:after { + transform: translateX(15px); +} +.toggle.disabled { + color: #b8b8b8; +} +label.checkbox-container { + display: flex; + align-items: center; + height: 15px; + font-weight: 400; +} +label.checkbox-container input[type="checkbox"] { + width: 15px; + height: 15px; + margin: 0 5px 0 0; +} +.flex, +.toggle, +.btn-bar { + display: flex; + flex-wrap: wrap; + flex-direction: row; +} +.btn { + display: inline-block; + padding: 5px 10px; + border: 1px solid #ddd; + background: #fafafa; + color: #333; + border-radius: 3px; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); + outline: 0; + line-height: 1.3; + cursor: default !important; +} +.btn * { + cursor: default !important; +} +.btn.white { + background: #fff; +} +.btn.pressed { + box-shadow: inset 0px 0 5px rgba(0, 0, 0, 0.1); + background: linear-gradient(to top, #ddd, #ccc); +} +.btn.pressed:hover { + box-shadow: inset 0px 0 2px rgba(0, 0, 0, 0.1); + background: linear-gradient(to top, #ddd, #ccc); + cursor: default; +} +.btn:hover { + text-decoration: none; +} +.btn[disabled="disabled"], +.btn.disabled, +.btn:disabled { + background: #fafafa !important; + color: rgba(0, 0, 0, 0.4) !important; + border: 1px solid #eee !important; + font-weight: 400 !important; + box-shadow: none !important; + -webkit-font-smoothing: initial !important; +} +.btn[disabled="disabled"] .spinner, +.btn.disabled .spinner, +.btn:disabled .spinner { + color: #aaa !important; +} +.btn[disabled="disabled"]:hover, +.btn.disabled:hover, +.btn:disabled:hover { + background: #fafafa; +} +.btn .spinner { + display: inline-block; + position: relative; + top: 1px; + color: inherit; +} +.btn.warning { + color: #fff; + background: #cc2f26; + border-color: #99231d; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1); +} +.btn.warning.pressed, +.btn.warning:hover { + background: #c42d25; + border-color: #c42d25; +} +.btn.success { + background: #41bb56; + color: #fff; + border-color: #2e833c; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1); +} +.btn.success.pressed, +.btn.success:hover { + background: #3baa4e; + border-color: #3baa4e; +} +.btn.transparent { + border-color: transparent; + background: none; + font-weight: 400; + box-shadow: none; +} +.btn.transparent:hover { + background: rgba(0, 0, 0, 0.075); + color: #424242; +} +.btn.transparent.disabled { + border-color: transparent !important; + background: none !important; +} +.btn.transparent.pressed { + background: linear-gradient(to top, #f5f3f3, #ececec); + border-color: #dadada; +} +.btn.primary { + background: #5289f7; + border-color: #275fce; + color: #fff; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1); +} +.btn.btn-split-group { + height: auto; + padding: 0; +} +.btn.btn-split-group .btn-split-section { + height: 28px; + padding: 0 10px; + border-right: 1px solid #ddd; +} +.btn.btn-split-group .btn-split-section:last-child { + border-right: 0; +} +.btn-group { + display: inline-flex; +} +.btn-group .btn { + margin: 0 !important; + border-radius: 0; + border-right-width: 0; +} +.btn-group > :first-child { + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; + margin-right: 0; +} +.btn-group > :last-child { + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; + border-right-width: 1px; + margin-left: 0; +} +.dropdown-btn-container { + display: inline-block; + position: relative; +} +.dropdown-btn-container.open .dropdown-btn-list { + display: block; +} +.dropdown-btn-container.open .toggleable.btn { + background: #dadada; + box-shadow: inset 0px 1px 0px rgba(0, 0, 0, 0.07); +} +.dropdown-btn-list { + display: none; + position: absolute; + z-index: 1; + top: 24px; + right: 0; + background: #fff; + border: 1px solid #d9d9d9; + width: 149px; + box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.1); + border-radius: 2px; + padding: 2px 0; +} +.dropdown-btn-list * { + cursor: pointer; +} +.dropdown-btn-list hr { + margin: 2px 0; +} +.dropdown-btn-list .dropdown-item, +.dropdown-btn-list > div, +.dropdown-btn-list > a { + width: 100%; + display: block; + padding: 7px 7px; + color: #333; +} +.dropdown-btn-list .dropdown-item i, +.dropdown-btn-list > div i, +.dropdown-btn-list > a i { + width: 15px; + display: inline-block; + text-align: center; +} +.dropdown-btn-list .dropdown-item.disabled, +.dropdown-btn-list > div.disabled, +.dropdown-btn-list > a.disabled { + color: #b8b8b8; +} +.dropdown-btn-list .dropdown-item:hover, +.dropdown-btn-list > div:hover, +.dropdown-btn-list > a:hover { + text-decoration: none; +} +.dropdown-btn-list .dropdown-item:hover:not(.disabled), +.dropdown-btn-list > div:hover:not(.disabled), +.dropdown-btn-list > a:hover:not(.disabled) { + background: #2864dc; + color: #fff; +} +.dropdown-btn-list > div, +.dropdown-btn-list .dropdown-item { + padding: 8px 15px; + border-bottom: 1px solid #d9d9d9; +} +.dropdown-btn-list > div:last-child, +.dropdown-btn-list .dropdown-item:last-child { + border: 0; +} +.dropdown-btn-list > div .title, +.dropdown-btn-list .dropdown-item .title { + font-weight: 600; + margin-bottom: 5px; +} +.dropdown-btn-list > div .title i, +.dropdown-btn-list .dropdown-item .title i { + margin-right: 2px; +} +.dropdown-btn-list > div:hover .desc, +.dropdown-btn-list .dropdown-item:hover .desc { + color: #fff; +} +.btn-bar .btn { + border-radius: 0 !important; +} +.btn-bar .btn:first-child { + border-radius: 2px 0 0 2px !important; +} +.btn-bar .btn:last-child { + border-radius: 0 2px 2px 0 !important; +} +.btn-bar .btn:not(:first-child) { + border-left: 0; +} +a.btn span { + vertical-align: baseline; +} +.btn.nofocus:focus, +button.nofocus:focus { + outline: 0; + box-shadow: none; +} +.vertical-scroll { + overflow-y: scroll; +} +.shadow { + box-shadow: 0 3px 15px rgba(0, 0, 0, 0.25); +} +.dropdown-shadow { + box-shadow: 0 2px 15px rgba(0, 0, 0, 0.3); +} +.dropdown-shadow-subtle { + box-shadow: 0 2px 15px rgba(0, 0, 0, 0.15); +} +.overflow-ellipsis { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.hidden { + display: none !important; +} +* { + box-sizing: border-box; +} +html, +body, +main { + background: #fff; +} +body { + font-size: 14px; +} +.header { + position: sticky; + top: 0; + z-index: 2; +} +h1 { + padding: 0.3rem 0.5rem; + background: #8bc34a; + color: #204a10; + font-weight: normal; + height: 40px; +} +#filter { + position: absolute; + top: 10px; + right: 10px; +} +#filter input { + width: 400px; + border-color: #33691e; +} +nav { + border-bottom: 1px solid #33691e; + background: #8bc34a; + padding: 0.2rem 0.4rem 0; + height: 28px; +} +nav a { + display: inline-block; + padding: 0.2rem 0.5rem; + border: 1px solid transparent; + border-bottom: 0; + border-top-left-radius: 3px; + border-top-right-radius: 3px; + cursor: pointer; +} +nav a:hover { + background: rgba(255, 255, 255, 0.25); +} +nav a.active { + background: #fff; + border-color: #33691e; + position: relative; +} +nav a.active:after { + content: ''; + display: block; + position: absolute; + background: #fff; + left: 0; + width: 100%; + bottom: -1px; + height: 1px; +} +main .gray { + color: #bbb; +} +main table thead { + font-weight: bold; +} +main table thead td { + position: sticky; + top: 68px; + background: #fff; + border-bottom: 1px solid #ddd; +} +main table tbody tr:hover { + background: #eee; +} +main table tbody td:hover { + background: #ddd; +} +main table td { + padding: 10px; + border-right: 1px solid #ddd; +} +main section { + padding: 1rem; +} +main section.columns { + display: flex; +} +main section.columns > div { + margin-right: 1rem; +} +main .peers > div { + padding: 0.1rem 0; + font-family: Consolas, 'Lucida Console', Monaco, monospace; +} +main .log tbody td { + cursor: pointer; +} +td { + padding-right: 10px; +} diff --git a/app/stylesheets/builtin-pages/swarm-debugger.less b/app/fg/builtin-pages/stylesheets/builtin-pages/swarm-debugger.less similarity index 100% rename from app/stylesheets/builtin-pages/swarm-debugger.less rename to app/fg/builtin-pages/stylesheets/builtin-pages/swarm-debugger.less diff --git a/app/fg/builtin-pages/stylesheets/builtin-pages/watchlist.css b/app/fg/builtin-pages/stylesheets/builtin-pages/watchlist.css new file mode 100644 index 0000000000..8f395b0776 --- /dev/null +++ b/app/fg/builtin-pages/stylesheets/builtin-pages/watchlist.css @@ -0,0 +1,1272 @@ +.link { + color: #295fcb; + cursor: pointer; + border: none; + outline: none; + background: none; + padding: 0; +} +.link:hover { + text-decoration: underline; +} +.link.disabled { + color: #999999; + cursor: default; +} +.link.disabled:hover { + text-decoration: none; +} +textarea { + line-height: 1.4; +} +input, +textarea { + height: 30px; + padding: 0 7px; + border-radius: 4px; + color: rgba(51, 51, 51, 0.95); + border: 1px solid #d9d9d9; +} +input[type="checkbox"], +textarea[type="checkbox"], +input[type="radio"], +textarea[type="radio"], +input[type="range"], +textarea[type="range"] { + padding: 0; +} +input[type="checkbox"]:focus, +textarea[type="checkbox"]:focus, +input[type="radio"]:focus, +textarea[type="radio"]:focus, +input[type="range"]:focus, +textarea[type="range"]:focus { + box-shadow: none; +} +input[type="radio"], +textarea[type="radio"] { + width: 14px; + height: 14px; + outline: none; + -webkit-appearance: none; + border-radius: 50%; + cursor: pointer; + transition: border 0.1s ease; +} +input[type="radio"]:hover, +textarea[type="radio"]:hover { + border: 1px solid #2864dc; +} +input[type="radio"]:checked, +textarea[type="radio"]:checked { + border: 4.5px solid #2864dc; +} +input[type="file"], +textarea[type="file"] { + padding: 0; + border: 0; + line-height: 1; +} +input[type="file"]:focus, +textarea[type="file"]:focus { + border: 0; + box-shadow: none; +} +input.roomy, +textarea.roomy, +#share-popup .popup-inner input { + padding: 7px 9px; +} +input:focus, +textarea:focus, +select:focus { + outline: 0; + border: 1px solid rgba(41, 95, 203, 0.8); + box-shadow: 0 0 0 2px rgba(41, 95, 203, 0.2); +} +input.error, +textarea.error, +select.error { + border: 1px solid rgba(209, 48, 39, 0.75); +} +input.error:focus, +textarea.error:focus, +select.error:focus { + box-shadow: 0 0 0 2px rgba(204, 47, 38, 0.15); +} +input.nofocus:focus, +textarea.nofocus:focus, +select.nofocus:focus { + outline: 0; + box-shadow: none; + border: initial; +} +input.inline { + height: auto; + border: 1px solid transparent; + border-radius: 0; + background: transparent; + cursor: text; + padding: 3px 5px; + line-height: 1; +} +input.inline:focus, +input.inline:hover { + border: 1px solid #ccc; + box-shadow: none; +} +input.inline:focus { + background: #fff; +} +input.underline { + width: 100%; + height: auto; + padding: 0; + border: 0; + border-radius: 0; + background: transparent; + cursor: text; + line-height: 1; +} +.input-file-picker { + display: flex; + align-items: center; + padding: 3px; + border-radius: 2px; + border: 1px solid #d9d9d9; + color: #707070; +} +.input-file-picker span { + flex: 1; + padding-left: 3px; +} +::-webkit-input-placeholder { + color: rgba(0, 0, 0, 0.5); + font-size: 0.8rem; +} +label { + font-weight: 500; +} +input[disabled][data-tooltip], +label[disabled][data-tooltip] { + cursor: help; +} +input[disabled][data-tooltip] *, +label[disabled][data-tooltip] * { + cursor: help; +} +.toggle { + align-items: center; + flex-direction: row-reverse; + justify-content: space-between; + margin-bottom: 10px; + cursor: pointer; + overflow: initial; +} +.toggle.unweirded { + flex-direction: row; + justify-content: initial; +} +.toggle.unweirded .switch { + margin-right: 10px; +} +.toggle * { + cursor: pointer; +} +.toggle.disabled { + cursor: default; +} +.toggle.disabled * { + cursor: default; +} +.toggle input { + display: none; +} +.toggle .text { + font-weight: 400; +} +.toggle .switch { + display: inline-block; + position: relative; + width: 32px; + height: 17px; +} +.toggle .switch:before, +.toggle .switch:after { + position: absolute; + display: block; + content: ''; +} +.toggle .switch:before { + width: 100%; + height: 100%; + border-radius: 40px; + background: #dadada; +} +.toggle .switch:after { + width: 11px; + height: 11px; + border-radius: 50%; + left: 3px; + top: 3px; + background: #fafafa; + transition: transform 0.15s ease; +} +.toggle input:checked:not(:disabled) + .switch:before { + background: #41b855; +} +.toggle input:checked:not(:disabled) + .switch:after { + transform: translateX(15px); +} +.toggle.disabled { + color: #b8b8b8; +} +label.checkbox-container { + display: flex; + align-items: center; + height: 15px; + font-weight: 400; +} +label.checkbox-container input[type="checkbox"] { + width: 15px; + height: 15px; + margin: 0 5px 0 0; +} +.message { + justify-content: flex-start; + align-items: center; + min-width: 500px; + margin: 0 auto 15px auto; + padding: 10px 15px; + background: #eee; + border: 1px solid #ddd; + font-size: 13.5px; +} +.message strong { + font-weight: 600; +} +.message > i { + font-size: 16px; + margin-right: 10px; +} +.message .btn { + background: transparent; + border-color: #707070; + margin-left: auto; +} +.message .btn:hover { + background: rgba(112, 112, 112, 0.15); +} +.message ul { + list-style: disc; + margin-left: 20px; +} +.message.success, +.message.primary, +.message.info, +.message.error { + border-color: transparent; +} +.message.success { + background: #d2f6d8; + color: #3f6e47; +} +.message.success i { + color: #3f6e47; +} +.message.success .btn { + border-color: #3f6e47; + color: #3f6e47; +} +.message.success .btn:focus, +.message.success .btn:active { + border-color: #3f6e47; +} +.message.success .btn:hover { + background: rgba(63, 110, 71, 0.15); +} +.message.primary { + background: #dfe8fa; + color: #335291; +} +.message.primary i { + color: #335291; +} +.message.primary .btn { + border-color: #335291; + color: #335291; +} +.message.primary .btn:focus, +.message.primary .btn:active { + border-color: #335291; +} +.message.primary .btn:hover { + background: rgba(51, 82, 145, 0.15); +} +.message.info { + background: #ffeacc; + color: #7a5726; +} +.message.info i { + color: #7a5726; +} +.message.info .btn { + border-color: #7a5726; + color: #7a5726; +} +.message.info .btn:focus, +.message.info .btn:active { + border-color: #7a5726; +} +.message.info .btn:hover { + background: rgba(122, 87, 38, 0.15); +} +.message.error { + background: #ffd8d6; + color: #993c37; +} +.message.error i { + color: #993c37; +} +.message.error .btn { + border-color: #993c37; + color: #993c37; +} +.message.error .btn:focus, +.message.error .btn:active { + border-color: #993c37; +} +.message.error .btn:hover { + background: rgba(153, 60, 55, 0.15); +} +.message a:not(.btn) { + text-decoration: underline; +} +.popup-wrapper { + position: fixed; + left: 0; + top: 0; + width: 100%; + height: 100%; + z-index: 6000; + background: rgba(0, 0, 0, 0.45); + font-style: normal; + overflow-y: auto; +} +.popup-inner { + background: #fff; + box-shadow: 0 2px 25px rgba(0, 0, 0, 0.3); + border: 1px solid rgba(0, 0, 0, 0.55); + border-radius: 4px; + width: 450px; + margin: 80px auto 0; + overflow: hidden; +} +.popup-inner .error { + color: #d80b00 !important; + margin: 10px 0 !important; + font-style: italic; +} +.popup-inner .head { + position: relative; + background: #fafafa; + padding: 7px 12px; + width: 100%; + border-bottom: 1px solid #eee; + border-radius: 4px 4px 0 0; +} +.popup-inner .head .title { + font-size: 0.95rem; + font-weight: 500; +} +.popup-inner .head .close-btn { + position: absolute; + top: 10px; + right: 12px; +} +.popup-inner .body { + padding: 12px; +} +.popup-inner .body > div:not(:first-child) { + margin-top: 20px; +} +.popup-inner p:first-child { + margin-top: 0; +} +.popup-inner p:last-child { + margin-bottom: 0; +} +.popup-inner select { + height: 28px; +} +.popup-inner textarea, +.popup-inner label:not(.checkbox-container), +.popup-inner select, +.popup-inner input { + display: block; + width: 100%; +} +.popup-inner label.toggle { + display: flex; + justify-content: flex-end; +} +.popup-inner label.toggle .text { + margin-right: 10px; +} +.popup-inner label.toggle input { + display: none; +} +.popup-inner label { + margin-bottom: 3px; + color: rgba(51, 51, 51, 0.9); +} +.popup-inner textarea, +.popup-inner input { + margin-bottom: 10px; +} +.popup-inner textarea { + height: 50px; + padding-top: 3px; +} +.popup-inner .actions { + display: flex; + justify-content: flex-end; + align-items: center; + margin-top: 15px; + padding-top: 10px; + border-top: 1px solid #eee; +} +.popup-inner .actions .left, +.popup-inner .actions .link { + margin-right: auto; +} +.popup-inner .actions .btn, +.popup-inner .actions .success, +.popup-inner .actions .primary { + margin-left: 5px; +} +.popup-inner .actions .spinner { + width: 10px; + height: 10px; + border-width: 1.2px; +} +#crop-popup .popup-inner { + width: 298px; +} +#crop-popup .popup-inner .controls { + width: 256px; + margin: 1rem 0 0; +} +#crop-popup .popup-inner canvas { + display: block; + border: 1px solid gray; + border-radius: 50%; +} +#crop-popup .popup-inner input[type="range"] { + width: 256px; +} +#crop-popup .popup-inner .btns { + text-align: right; +} +#share-popup .popup-inner div { + margin: 10px 0; + line-height: 1; +} +#share-popup .popup-inner input { + color: #333; + width: 100%; + margin-top: 10px; + font-size: 0.85rem; +} +#share-popup .popup-inner input:focus { + outline: none; +} +#share-popup .popup-inner .btn { + float: right; +} +#share-popup .popup-inner .info { + margin: 10px 0; + color: #707070; + font-size: 0.8rem; +} +#share-popup .popup-inner .info i { + margin-right: 3px; +} +#explorer-popup .popup-inner { + width: 80vw; + max-width: 900px; + min-width: 600px; +} +#explorer-popup .head .filter-control input { + height: 26px; + margin: 0; + width: calc(100% - 30px); +} +#explorer-popup .explorer-body .suggestions { + overflow-y: auto; + max-height: calc(100vh - 200px); + padding: 20px 30px; +} +#explorer-popup .explorer-body .suggestions .empty { + color: rgba(0, 0, 0, 0.5); +} +#explorer-popup .explorer-body .suggestions .group { + padding: 0 0 20px; +} +#explorer-popup .explorer-body .suggestions .group .group-title { + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + color: rgba(0, 0, 0, 0.5); + margin-bottom: 10px; + padding-bottom: 2px; + padding-left: 2px; + letter-spacing: -0.5px; +} +#explorer-popup .explorer-body .suggestions .group .group-items .suggestion { + display: flex; + align-items: center; + padding: 10px; + overflow: hidden; + user-select: none; +} +#explorer-popup .explorer-body .suggestions .group .group-items .suggestion .favicon { + width: 32px; + height: 32px; +} +#explorer-popup .explorer-body .suggestions .group .group-items .suggestion .favicon.rounded { + border-radius: 50%; +} +#explorer-popup .explorer-body .suggestions .group .group-items .suggestion .title { + white-space: nowrap; +} +#explorer-popup .explorer-body .suggestions .group .group-items .suggestion:hover { + background: #eee; +} +#explorer-popup .explorer-body .suggestions.defaults .group .group-items { + display: grid; + grid-template-columns: repeat(6, 1fr); +} +@media (min-width: 1300px) { + #explorer-popup .explorer-body .suggestions.defaults .group .group-items { + grid-template-columns: repeat(8, 1fr); + } +} +#explorer-popup .explorer-body .suggestions.defaults .group .group-items .suggestion { + flex-direction: column; +} +#explorer-popup .explorer-body .suggestions.defaults .group .group-items .suggestion .favicon { + margin-bottom: 6px; +} +#explorer-popup .explorer-body .suggestions.query-results .group .group-items .suggestion .favicon { + margin-right: 10px; +} +#library-localsyncpath-popup .popup-inner { + width: 530px; + overflow: initial; +} +#library-localsyncpath-popup .path-container { + align-items: center; + justify-content: space-between; + width: 100%; + height: 28px; + border: 1px solid #ddd; + border-radius: 4px 2px 2px 4px; +} +#library-localsyncpath-popup .path-container .btn { + border: 0; + border-left: 1px solid #ddd; + height: 26px; +} +#library-localsyncpath-popup .path-container .path { + flex: 1; + height: 100%; + line-height: 28px; + padding: 0 7px; + margin: 0; + border: 0; +} +#library-localsyncpath-popup .message { + margin-top: 10px; +} +#library-copydat-popup strong { + font-weight: 600; +} +#library-copydat-popup .downloader .download-desc { + font-size: 12px; + margin-top: 3px; + color: #707070; + text-align: center; +} +#editor-import-popup .popup-inner { + width: 90vw; + max-width: 800px; +} +#editor-import-popup .changes { + max-height: 50vh; + font-size: 12px; + white-space: nowrap; + overflow-y: auto; + border: 1px solid #ccc; + margin-top: 0; +} +#editor-import-popup .changes > * { + overflow-x: hidden; + text-overflow: ellipsis; + padding: 1px 3px; +} +#editor-import-popup .changes .added { + background: #efffef; + color: #013e01; +} +#editor-import-popup .changes .added .fas { + color: green; +} +#editor-import-popup .changes .updated { + background: #ffffd5; + color: #5f5f1e; +} +#editor-import-popup .changes .updated .fas { + color: #b99814; +} +#editor-import-popup .changes .removed { + background: #ffd5d5; + color: maroon; +} +#editor-import-popup .changes .removed .fas { + color: maroon; +} +#editor-import-popup .loading { + display: flex; + align-items: center; + padding: 30px; + font-size: 14px; + background: #fafafa; +} +#editor-import-popup .loading .spinner { + margin-right: 5px; +} +.vertical-scroll { + overflow-y: scroll; +} +.shadow { + box-shadow: 0 3px 15px rgba(0, 0, 0, 0.25); +} +.dropdown-shadow, +.dropdown-items { + box-shadow: 0 2px 15px rgba(0, 0, 0, 0.3); +} +.dropdown-shadow-subtle, +.dropdown-items.subtle-shadow { + box-shadow: 0 2px 15px rgba(0, 0, 0, 0.15); +} +.overflow-ellipsis, +.revision-header .path, +.files-browser-header .path { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.hidden { + display: none !important; +} +.dropdown { + position: relative; +} +.dropdown i { + cursor: pointer; +} +.dropdown.open .toggleable:not(.primary) { + background: #dadada; + box-shadow: inset 0 0 3px rgba(0, 0, 0, 0.1); + border-color: transparent; + outline: 0; +} +.toggleable-container .dropdown-items { + display: none; +} +.toggleable-container.hover:hover .dropdown-items, +.toggleable-container.open .dropdown-items { + display: block; +} +.dropdown-items { + width: 270px; + position: absolute; + right: 0px; + z-index: 3000; + background: #fff; + border: 1px solid #dadada; + border-radius: 4px; +} +.dropdown-items .section { + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + padding: 5px 0; +} +.dropdown-items .section-header { + padding: 2px 10px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.dropdown-items .section-header.light { + color: #b8b8b8; + font-weight: 500; +} +.dropdown-items.thin { + width: 170px; +} +.dropdown-items.wide { + width: 400px; +} +.dropdown-items.compact .dropdown-item { + padding: 2px 15px; + border-bottom: 0; +} +.dropdown-items.compact .description { + margin-left: 0; +} +.dropdown-items.compact hr { + margin: 5px 0; +} +.dropdown-items.roomy .dropdown-item { + padding: 10px 15px; +} +.dropdown-items.no-border .dropdown-item { + border-bottom: 0; +} +.dropdown-items.center { + left: 50%; + transform: translateX(-50%); +} +.dropdown-items.left { + right: initial; + left: 0; +} +.dropdown-items.over { + top: 0; +} +.dropdown-items.top { + bottom: calc(100% + 5px); +} +.dropdown-items.with-triangle:before { + content: ''; + position: absolute; + top: -8px; + right: 10px; + width: 12px; + height: 12px; + z-index: 3; + width: 0; + height: 0; + border-left: 8px solid transparent; + border-right: 8px solid transparent; + border-bottom: 8px solid #fff; +} +.dropdown-items.with-triangle.left:before { + left: 10px; +} +.dropdown-items.with-triangle.center:before { + left: 43%; +} +.dropdown-title { + border-bottom: 1px solid #eee; + padding: 2px 8px; + font-size: 11px; + color: gray; +} +.dropdown-item { + display: block; + width: 100%; + padding: 5px 15px; + border-bottom: 1px solid #eee; +} +.dropdown-item.disabled { + opacity: 0.25; +} +.dropdown-item .fa-check-square { + color: #2864dc; +} +.dropdown-item .fa-check-square, +.dropdown-item .fa-square-o { + font-size: 14px; +} +.dropdown-item .fa-check { + font-size: 11.5px; +} +.dropdown-item.no-border { + border-bottom: 0; +} +.dropdown-item:hover:not(.no-hover) { + background: #eee; + cursor: pointer; +} +.dropdown-item:hover:not(.no-hover) i:not(.fa-check-square) { + color: #333; +} +.dropdown-item:hover:not(.no-hover) .description { + color: #333; +} +.dropdown-item:hover:not(.no-hover).disabled { + background: inherit; + cursor: default; +} +.dropdown-item .fa, +.dropdown-item i { + display: inline-block; + width: 20px; + color: rgba(0, 0, 0, 0.65); +} +.dropdown-item img { + display: inline-block; + width: 16px; + position: relative; + top: 3px; + margin-right: 6px; +} +.dropdown-item .btn .fa { + color: inherit; +} +.dropdown-item .label { + font-weight: 500; + margin-bottom: 3px; +} +.dropdown-item .description { + color: #707070; + margin: 0; + margin-left: 23px; + margin-bottom: 3px; + line-height: 1.5; +} +.dropdown-item .description.small { + font-size: 12.5px; +} +.dropdown-item:first-of-type { + border-radius: 2px 2px 0 0; +} +.dropdown-item:last-of-type { + border-radius: 0 0 2px 2px; +} +progress { + display: none; +} +@keyframes progress-active { + 0% { + width: 0%; + } + 50% { + width: 0%; + } + 100% { + width: 100%; + } +} +.progress-ui { + position: relative; + width: 100%; + height: 20px; + border-radius: 2px; + background: #e0e0e0; +} +.progress-ui .completed { + position: absolute; + min-width: 30px; + height: 100%; + padding: 0 5px; + background: #bbb; + color: rgba(255, 255, 255, 0.9); + font-size: 0.75rem; + font-weight: 500; + text-align: right; + line-height: 20px; + border-radius: 2px; +} +.progress-ui .label { + position: absolute; + width: 100%; + top: calc(100% + 5px); + text-align: center; + font-size: 0.8rem; +} +.progress-ui.active .completed:before { + position: absolute; + left: 0; + display: block; + content: ''; + z-index: 3; + height: 100%; + animation: 3s ease progress-active infinite; + animation-delay: 1s; + border-radius: 4px; + background: linear-gradient(to right, rgba(255, 255, 255, 0.2) 5%, rgba(255, 255, 255, 0.1)); +} +.progress-ui.small { + height: 10px; +} +.progress-ui.blue .completed { + background: #2864dc; +} +.progress-ui.green .completed { + background: #44c35a; +} +.context-input { + position: absolute; +} +.context-input .dropdown-items { + width: 300px !important; + padding: 10px; +} +.context-input .dropdown-items form { + display: flex; +} +.context-input .dropdown-items input { + flex: 1; + height: 28px; +} +.context-input .dropdown-items button { + margin-left: 5px; +} +.context-input .dropdown-items.with-triangle.left:before { + border-bottom-color: rgba(0, 0, 0, 0.25); + left: 20px; +} +.badge { + display: inline-flex; + align-items: center; + justify-content: space-around; + padding: 0 5px; + margin-left: 5px; + background: #eaeaea; + color: #707070; + border-radius: 2px; + font-size: 11.75px; +} +.badge:hover { + text-decoration: none; +} +.badge.read-only { + text-transform: uppercase; + font-size: 11px; + letter-spacing: 0.2px; +} +.badge.green { + background: #41b855; + color: #fff; +} +.badge.blue { + background: #2864dc; + color: #fff; +} +.badge.warning { + background: #d13027; + color: #fff; +} +.rehost-slider { + margin: 0 10px; + padding: 10px 0; +} +.rehost-slider .labels { + align-items: center; +} +.rehost-slider .labels .policy { + flex: 1; +} +.rehost-slider .toggle { + flex-direction: row; + justify-content: initial; + margin: 0; +} +.rehost-slider .toggle .switch { + margin-right: 10px; +} +.rehost-slider .slider { + margin-top: 10px; +} +.rehost-slider input[type=range] { + width: 100%; +} +.rehost-slider .fa-circle { + margin-right: 3px; + font-size: 12px; +} +.rehost-slider .fa-circle.green { + color: #4cd964; +} +.rehost-slider .fa-circle.yellow { + color: #ffcc00; +} +.rehost-slider .fa-circle.red { + color: #ff3b30; +} +.hover-swapper > .default, +.hover-swapper > .hover { + pointer-events: none; +} +.hover-swapper > .default { + display: inline-block; +} +.hover-swapper > .hover { + display: none; +} +.hover-swapper:hover > .default { + display: none; +} +.hover-swapper:hover > .hover { + display: inline-block; +} +*[data-tooltip] { + position: relative; +} +*[data-tooltip]:hover:before, +*[data-tooltip]:hover:after { + display: block; + z-index: 1000; + transition: opacity 0.01s ease; + transition-delay: 0.2s; +} +*[data-tooltip]:hover:after { + opacity: 1; +} +*[data-tooltip]:hover:before { + transform: translate(-50%, 0); + opacity: 1; +} +*[data-tooltip]:before { + opacity: 0; + transform: translate(-50%, 0); + position: absolute; + top: 33px; + left: 50%; + z-index: 3000; + content: attr(data-tooltip); + background: rgba(17, 17, 17, 0.95); + font-size: 0.7rem; + border: 0; + border-radius: 4px; + padding: 7px 10px; + color: rgba(255, 255, 255, 0.925); + text-transform: none; + text-align: center; + font-weight: 300; + white-space: pre; + line-height: 1; + pointer-events: none; +} +*[data-tooltip]:after { + opacity: 0; + position: absolute; + left: calc(50% - 6px); + top: 28px; + content: ''; + width: 0; + height: 0; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid rgba(17, 17, 17, 0.95); + pointer-events: none; +} +.window-content.builtin.watchlist { + position: relative; +} +.drag-hint { + box-sizing: border-box; + display: none; + position: absolute; + top: 0; + left: 0; + z-index: 3; + width: 100%; + height: 100%; + padding: 20px; + padding-top: 30vh; + background: rgba(240, 247, 253, 0.95); + color: rgba(0, 0, 0, 0.5); + color: #707070; + text-align: center; +} +.drag-hint:before { + display: block; + content: ''; + position: absolute; + left: 15px; + top: 15px; + width: calc(100% - 30px); + height: calc(100% - 30px); + border: 5px dashed rgba(0, 0, 0, 0.15); +} +.drag-hint .icons { + justify-content: center; + align-items: center; + margin-bottom: 15px; +} +.drag-hint .icons .fa { + font-size: 34px; +} +.drag-hint .icons .fa:nth-child(2), +.drag-hint .icons .fa:nth-child(4) { + font-size: 40px; +} +.drag-hint .icons .fa:nth-of-type(3) { + font-size: 48px; +} +.drag-hint .icons .fa:not(:last-child) { + margin-right: 10px; +} +.drag-hint h1 { + font-size: 1.5rem; + font-weight: 500; +} +.drag-hint p { + margin: 0; + font-size: 0.9rem; +} +body.drag .drag-hint { + display: block; +} +@media (min-width: 1050px) { + .builtin-wrapper.watchlist .builtin-main { + max-width: none; + margin: 0 100px 0 200px; + } +} +.builtin-wrapper.watchlist .group { + margin-bottom: 10px; +} +.builtin-wrapper.watchlist .ll-row.watchlist-item .watchlist-item-status, +.builtin-wrapper.watchlist .ll-column-headings .watchlist-item-status { + width: 90px; +} +.builtin-wrapper.watchlist .ll-row.watchlist-item .watchlist-item-title, +.builtin-wrapper.watchlist .ll-column-headings .watchlist-item-title { + flex: 3; + max-width: none; +} +.builtin-wrapper.watchlist .ll-row.watchlist-item .date, +.builtin-wrapper.watchlist .ll-column-headings .date { + min-width: 120px; + flex: 0.9; +} +.builtin-wrapper.watchlist .ll-row.watchlist-item .buttons, +.builtin-wrapper.watchlist .ll-column-headings .buttons { + width: 45px; +} +.builtin-wrapper.watchlist .ll-column-headings { + padding: 0 20px 10px; +} +.builtin-wrapper.watchlist .ll-row.watchlist-item { + height: 50px; + padding: 0 20px; +} +.builtin-wrapper.watchlist .ll-row.watchlist-item.menu-open .buttons, +.builtin-wrapper.watchlist .ll-row.watchlist-item:hover .buttons, +.builtin-wrapper.watchlist .ll-row.watchlist-item.menu-open .btn, +.builtin-wrapper.watchlist .ll-row.watchlist-item:hover .btn, +.builtin-wrapper.watchlist .ll-row.watchlist-item.menu-open .btn.trash, +.builtin-wrapper.watchlist .ll-row.watchlist-item:hover .btn.trash { + opacity: 1; +} +.builtin-wrapper.watchlist .ll-row.watchlist-item.menu-open.resolved .watchlist-item-title, +.builtin-wrapper.watchlist .ll-row.watchlist-item:hover.resolved .watchlist-item-title { + text-decoration: underline; +} +.builtin-wrapper.watchlist .ll-row.watchlist-item:not(.resolved):hover { + cursor: default; + background: #fff; +} +.builtin-wrapper.watchlist .ll-row.watchlist-item:not(.resolved):hover span, +.builtin-wrapper.watchlist .ll-row.watchlist-item:not(.resolved):hover img { + cursor: default; +} +.builtin-wrapper.watchlist .ll-row.watchlist-item .watchlist-item-status { + text-align: right; + padding-right: 15px; +} +.builtin-wrapper.watchlist .ll-row.watchlist-item .watchlist-item-title { + font-size: 13.5px; + color: gray; +} +.builtin-wrapper.watchlist .ll-row.watchlist-item .watchlist-item-title.empty { + overflow: initial; +} +.builtin-wrapper.watchlist .ll-row.watchlist-item .date { + font-weight: 300; + font-size: 12.5px; + color: #707070; +} +.builtin-wrapper.watchlist .ll-row.watchlist-item .buttons { + opacity: 1; +} +.builtin-wrapper.watchlist .ll-row.watchlist-item .buttons .trash { + margin-right: 4px; + opacity: 0; +} +.builtin-wrapper.watchlist .ll-row.watchlist-item .toggle { + justify-content: flex-end; + margin: 0; +} +.builtin-wrapper.watchlist .ll-row.watchlist-item .toggle label { + margin: 0; +} +.builtin-wrapper.watchlist .ll-row.watchlist-item.resolved .description { + color: #2864dc; +} +.builtin-wrapper.watchlist .watchlist-modal { + position: relative; +} +.builtin-wrapper.watchlist .watchlist-modal .modal-container { + display: none; +} +.builtin-wrapper.watchlist .watchlist-modal.open .modal-container { + display: block; +} +.builtin-wrapper.watchlist .modal-container { + position: absolute; + right: 0; + width: 300px; + margin-top: 5px; + background: white; + z-index: 3000; + border: 1px solid #dadada; + border-radius: 4px; + padding: 10px; +} +.builtin-wrapper.watchlist .modal-container .watchlist-input { + position: relative; +} +.builtin-wrapper.watchlist .modal-container .watchlist-input input { + width: 280px; + cursor: text; +} +.builtin-wrapper.watchlist .modal-container .watchlist-input .validate { + display: block; + font-size: 12px; + text-align: right; + color: #cc0000; + opacity: 0; + transition: opacity 1s; +} +.builtin-wrapper.watchlist .modal-container .watchlist-input #description { + padding-right: 40px; +} +.builtin-wrapper.watchlist .modal-container .watchlist-input #counter { + position: absolute; + top: 2px; + right: 0; +} +.builtin-wrapper.watchlist .modal-container .toggle { + width: 280px; + margin-bottom: 10px; +} +.builtin-wrapper.watchlist .modal-container .subtle-shadow { + box-shadow: 0 2px 15px rgba(0, 0, 0, 0.15); +} +.revision-header, +.files-browser-header { + align-items: center; + height: 50px; + padding: 0 10px; + background: #fff; + border: 1px solid #ddd; +} +.revision-header .path, +.files-browser-header .path { + max-width: 250px; + margin-right: 10px; +} diff --git a/app/stylesheets/builtin-pages/watchlist.less b/app/fg/builtin-pages/stylesheets/builtin-pages/watchlist.less similarity index 100% rename from app/stylesheets/builtin-pages/watchlist.less rename to app/fg/builtin-pages/stylesheets/builtin-pages/watchlist.less diff --git a/app/stylesheets/components/archive-comparison.less b/app/fg/builtin-pages/stylesheets/components/archive-comparison.less similarity index 100% rename from app/stylesheets/components/archive-comparison.less rename to app/fg/builtin-pages/stylesheets/components/archive-comparison.less diff --git a/app/stylesheets/components/archive-history.less b/app/fg/builtin-pages/stylesheets/components/archive-history.less similarity index 100% rename from app/stylesheets/components/archive-history.less rename to app/fg/builtin-pages/stylesheets/components/archive-history.less diff --git a/app/stylesheets/components/archive-select-btn.less b/app/fg/builtin-pages/stylesheets/components/archive-select-btn.less similarity index 100% rename from app/stylesheets/components/archive-select-btn.less rename to app/fg/builtin-pages/stylesheets/components/archive-select-btn.less diff --git a/app/stylesheets/components/autocomplete-results.less b/app/fg/builtin-pages/stylesheets/components/autocomplete-results.less similarity index 100% rename from app/stylesheets/components/autocomplete-results.less rename to app/fg/builtin-pages/stylesheets/components/autocomplete-results.less diff --git a/app/stylesheets/components/badge.less b/app/fg/builtin-pages/stylesheets/components/badge.less similarity index 100% rename from app/stylesheets/components/badge.less rename to app/fg/builtin-pages/stylesheets/components/badge.less diff --git a/app/stylesheets/components/buttons.less b/app/fg/builtin-pages/stylesheets/components/buttons.less similarity index 100% rename from app/stylesheets/components/buttons.less rename to app/fg/builtin-pages/stylesheets/components/buttons.less diff --git a/app/stylesheets/components/close-btn.less b/app/fg/builtin-pages/stylesheets/components/close-btn.less similarity index 100% rename from app/stylesheets/components/close-btn.less rename to app/fg/builtin-pages/stylesheets/components/close-btn.less diff --git a/app/stylesheets/components/context-input.less b/app/fg/builtin-pages/stylesheets/components/context-input.less similarity index 100% rename from app/stylesheets/components/context-input.less rename to app/fg/builtin-pages/stylesheets/components/context-input.less diff --git a/app/stylesheets/components/context-menu.less b/app/fg/builtin-pages/stylesheets/components/context-menu.less similarity index 100% rename from app/stylesheets/components/context-menu.less rename to app/fg/builtin-pages/stylesheets/components/context-menu.less diff --git a/app/stylesheets/components/crawler-status.less b/app/fg/builtin-pages/stylesheets/components/crawler-status.less similarity index 100% rename from app/stylesheets/components/crawler-status.less rename to app/fg/builtin-pages/stylesheets/components/crawler-status.less diff --git a/app/stylesheets/components/dat-cache.less b/app/fg/builtin-pages/stylesheets/components/dat-cache.less similarity index 100% rename from app/stylesheets/components/dat-cache.less rename to app/fg/builtin-pages/stylesheets/components/dat-cache.less diff --git a/app/stylesheets/components/diff.less b/app/fg/builtin-pages/stylesheets/components/diff.less similarity index 100% rename from app/stylesheets/components/diff.less rename to app/fg/builtin-pages/stylesheets/components/diff.less diff --git a/app/stylesheets/components/dropdown-menu-bar.less b/app/fg/builtin-pages/stylesheets/components/dropdown-menu-bar.less similarity index 100% rename from app/stylesheets/components/dropdown-menu-bar.less rename to app/fg/builtin-pages/stylesheets/components/dropdown-menu-bar.less diff --git a/app/stylesheets/components/dropdown.less b/app/fg/builtin-pages/stylesheets/components/dropdown.less similarity index 100% rename from app/stylesheets/components/dropdown.less rename to app/fg/builtin-pages/stylesheets/components/dropdown.less diff --git a/app/stylesheets/components/editor/home.less b/app/fg/builtin-pages/stylesheets/components/editor/home.less similarity index 100% rename from app/stylesheets/components/editor/home.less rename to app/fg/builtin-pages/stylesheets/components/editor/home.less diff --git a/app/stylesheets/components/editor/settings-form.less b/app/fg/builtin-pages/stylesheets/components/editor/settings-form.less similarity index 100% rename from app/stylesheets/components/editor/settings-form.less rename to app/fg/builtin-pages/stylesheets/components/editor/settings-form.less diff --git a/app/stylesheets/components/editor/sidebar.less b/app/fg/builtin-pages/stylesheets/components/editor/sidebar.less similarity index 100% rename from app/stylesheets/components/editor/sidebar.less rename to app/fg/builtin-pages/stylesheets/components/editor/sidebar.less diff --git a/app/stylesheets/components/editor/tabs.less b/app/fg/builtin-pages/stylesheets/components/editor/tabs.less similarity index 100% rename from app/stylesheets/components/editor/tabs.less rename to app/fg/builtin-pages/stylesheets/components/editor/tabs.less diff --git a/app/stylesheets/components/favicon-maker-popup.less b/app/fg/builtin-pages/stylesheets/components/favicon-maker-popup.less similarity index 100% rename from app/stylesheets/components/favicon-maker-popup.less rename to app/fg/builtin-pages/stylesheets/components/favicon-maker-popup.less diff --git a/app/stylesheets/components/favicon-picker.less b/app/fg/builtin-pages/stylesheets/components/favicon-picker.less similarity index 100% rename from app/stylesheets/components/favicon-picker.less rename to app/fg/builtin-pages/stylesheets/components/favicon-picker.less diff --git a/app/stylesheets/components/files-browser.less b/app/fg/builtin-pages/stylesheets/components/files-browser.less similarity index 100% rename from app/stylesheets/components/files-browser.less rename to app/fg/builtin-pages/stylesheets/components/files-browser.less diff --git a/app/stylesheets/components/files-browser/breadcrumbs.less b/app/fg/builtin-pages/stylesheets/components/files-browser/breadcrumbs.less similarity index 100% rename from app/stylesheets/components/files-browser/breadcrumbs.less rename to app/fg/builtin-pages/stylesheets/components/files-browser/breadcrumbs.less diff --git a/app/stylesheets/components/files-browser/files-tree-view.less b/app/fg/builtin-pages/stylesheets/components/files-browser/files-tree-view.less similarity index 100% rename from app/stylesheets/components/files-browser/files-tree-view.less rename to app/fg/builtin-pages/stylesheets/components/files-browser/files-tree-view.less diff --git a/app/stylesheets/components/files-browser/nav-sidebar.less b/app/fg/builtin-pages/stylesheets/components/files-browser/nav-sidebar.less similarity index 100% rename from app/stylesheets/components/files-browser/nav-sidebar.less rename to app/fg/builtin-pages/stylesheets/components/files-browser/nav-sidebar.less diff --git a/app/stylesheets/components/files-browser/preview-sidebar.less b/app/fg/builtin-pages/stylesheets/components/files-browser/preview-sidebar.less similarity index 100% rename from app/stylesheets/components/files-browser/preview-sidebar.less rename to app/fg/builtin-pages/stylesheets/components/files-browser/preview-sidebar.less diff --git a/app/stylesheets/components/hover-swapper.less b/app/fg/builtin-pages/stylesheets/components/hover-swapper.less similarity index 100% rename from app/stylesheets/components/hover-swapper.less rename to app/fg/builtin-pages/stylesheets/components/hover-swapper.less diff --git a/app/stylesheets/components/inputs.less b/app/fg/builtin-pages/stylesheets/components/inputs.less similarity index 100% rename from app/stylesheets/components/inputs.less rename to app/fg/builtin-pages/stylesheets/components/inputs.less diff --git a/app/stylesheets/components/link.less b/app/fg/builtin-pages/stylesheets/components/link.less similarity index 100% rename from app/stylesheets/components/link.less rename to app/fg/builtin-pages/stylesheets/components/link.less diff --git a/app/stylesheets/components/links-list.less b/app/fg/builtin-pages/stylesheets/components/links-list.less similarity index 100% rename from app/stylesheets/components/links-list.less rename to app/fg/builtin-pages/stylesheets/components/links-list.less diff --git a/app/stylesheets/components/logger.less b/app/fg/builtin-pages/stylesheets/components/logger.less similarity index 100% rename from app/stylesheets/components/logger.less rename to app/fg/builtin-pages/stylesheets/components/logger.less diff --git a/app/stylesheets/components/markdown.less b/app/fg/builtin-pages/stylesheets/components/markdown.less similarity index 100% rename from app/stylesheets/components/markdown.less rename to app/fg/builtin-pages/stylesheets/components/markdown.less diff --git a/app/stylesheets/components/messages.less b/app/fg/builtin-pages/stylesheets/components/messages.less similarity index 100% rename from app/stylesheets/components/messages.less rename to app/fg/builtin-pages/stylesheets/components/messages.less diff --git a/app/stylesheets/components/modal.less b/app/fg/builtin-pages/stylesheets/components/modal.less similarity index 100% rename from app/stylesheets/components/modal.less rename to app/fg/builtin-pages/stylesheets/components/modal.less diff --git a/app/stylesheets/components/notice-banner.less b/app/fg/builtin-pages/stylesheets/components/notice-banner.less similarity index 100% rename from app/stylesheets/components/notice-banner.less rename to app/fg/builtin-pages/stylesheets/components/notice-banner.less diff --git a/app/stylesheets/components/onboarding-popup.less b/app/fg/builtin-pages/stylesheets/components/onboarding-popup.less similarity index 100% rename from app/stylesheets/components/onboarding-popup.less rename to app/fg/builtin-pages/stylesheets/components/onboarding-popup.less diff --git a/app/stylesheets/components/opaque-binary.less b/app/fg/builtin-pages/stylesheets/components/opaque-binary.less similarity index 100% rename from app/stylesheets/components/opaque-binary.less rename to app/fg/builtin-pages/stylesheets/components/opaque-binary.less diff --git a/app/stylesheets/components/popups.less b/app/fg/builtin-pages/stylesheets/components/popups.less similarity index 100% rename from app/stylesheets/components/popups.less rename to app/fg/builtin-pages/stylesheets/components/popups.less diff --git a/app/stylesheets/components/popups/crop.less b/app/fg/builtin-pages/stylesheets/components/popups/crop.less similarity index 100% rename from app/stylesheets/components/popups/crop.less rename to app/fg/builtin-pages/stylesheets/components/popups/crop.less diff --git a/app/stylesheets/components/popups/editor-import.less b/app/fg/builtin-pages/stylesheets/components/popups/editor-import.less similarity index 100% rename from app/stylesheets/components/popups/editor-import.less rename to app/fg/builtin-pages/stylesheets/components/popups/editor-import.less diff --git a/app/stylesheets/components/popups/explorer.less b/app/fg/builtin-pages/stylesheets/components/popups/explorer.less similarity index 100% rename from app/stylesheets/components/popups/explorer.less rename to app/fg/builtin-pages/stylesheets/components/popups/explorer.less diff --git a/app/stylesheets/components/popups/library-copydat.less b/app/fg/builtin-pages/stylesheets/components/popups/library-copydat.less similarity index 100% rename from app/stylesheets/components/popups/library-copydat.less rename to app/fg/builtin-pages/stylesheets/components/popups/library-copydat.less diff --git a/app/stylesheets/components/popups/library-localsyncpath.less b/app/fg/builtin-pages/stylesheets/components/popups/library-localsyncpath.less similarity index 100% rename from app/stylesheets/components/popups/library-localsyncpath.less rename to app/fg/builtin-pages/stylesheets/components/popups/library-localsyncpath.less diff --git a/app/stylesheets/components/popups/share.less b/app/fg/builtin-pages/stylesheets/components/popups/share.less similarity index 100% rename from app/stylesheets/components/popups/share.less rename to app/fg/builtin-pages/stylesheets/components/popups/share.less diff --git a/app/stylesheets/components/progress.less b/app/fg/builtin-pages/stylesheets/components/progress.less similarity index 100% rename from app/stylesheets/components/progress.less rename to app/fg/builtin-pages/stylesheets/components/progress.less diff --git a/app/stylesheets/components/protip.less b/app/fg/builtin-pages/stylesheets/components/protip.less similarity index 100% rename from app/stylesheets/components/protip.less rename to app/fg/builtin-pages/stylesheets/components/protip.less diff --git a/app/stylesheets/components/rehost-slider.less b/app/fg/builtin-pages/stylesheets/components/rehost-slider.less similarity index 100% rename from app/stylesheets/components/rehost-slider.less rename to app/fg/builtin-pages/stylesheets/components/rehost-slider.less diff --git a/app/stylesheets/components/revision-indicator.less b/app/fg/builtin-pages/stylesheets/components/revision-indicator.less similarity index 100% rename from app/stylesheets/components/revision-indicator.less rename to app/fg/builtin-pages/stylesheets/components/revision-indicator.less diff --git a/app/stylesheets/components/search-input.less b/app/fg/builtin-pages/stylesheets/components/search-input.less similarity index 100% rename from app/stylesheets/components/search-input.less rename to app/fg/builtin-pages/stylesheets/components/search-input.less diff --git a/app/stylesheets/components/segmented-progress-bar.less b/app/fg/builtin-pages/stylesheets/components/segmented-progress-bar.less similarity index 100% rename from app/stylesheets/components/segmented-progress-bar.less rename to app/fg/builtin-pages/stylesheets/components/segmented-progress-bar.less diff --git a/app/stylesheets/components/share-dropdown.less b/app/fg/builtin-pages/stylesheets/components/share-dropdown.less similarity index 100% rename from app/stylesheets/components/share-dropdown.less rename to app/fg/builtin-pages/stylesheets/components/share-dropdown.less diff --git a/app/stylesheets/components/spinner.less b/app/fg/builtin-pages/stylesheets/components/spinner.less similarity index 100% rename from app/stylesheets/components/spinner.less rename to app/fg/builtin-pages/stylesheets/components/spinner.less diff --git a/app/stylesheets/components/tabs.less b/app/fg/builtin-pages/stylesheets/components/tabs.less similarity index 100% rename from app/stylesheets/components/tabs.less rename to app/fg/builtin-pages/stylesheets/components/tabs.less diff --git a/app/stylesheets/components/tag.less b/app/fg/builtin-pages/stylesheets/components/tag.less similarity index 100% rename from app/stylesheets/components/tag.less rename to app/fg/builtin-pages/stylesheets/components/tag.less diff --git a/app/stylesheets/components/template-selector.less b/app/fg/builtin-pages/stylesheets/components/template-selector.less similarity index 100% rename from app/stylesheets/components/template-selector.less rename to app/fg/builtin-pages/stylesheets/components/template-selector.less diff --git a/app/stylesheets/components/toast.less b/app/fg/builtin-pages/stylesheets/components/toast.less similarity index 100% rename from app/stylesheets/components/toast.less rename to app/fg/builtin-pages/stylesheets/components/toast.less diff --git a/app/stylesheets/components/toolbar.less b/app/fg/builtin-pages/stylesheets/components/toolbar.less similarity index 100% rename from app/stylesheets/components/toolbar.less rename to app/fg/builtin-pages/stylesheets/components/toolbar.less diff --git a/app/stylesheets/components/tooltip.less b/app/fg/builtin-pages/stylesheets/components/tooltip.less similarity index 100% rename from app/stylesheets/components/tooltip.less rename to app/fg/builtin-pages/stylesheets/components/tooltip.less diff --git a/app/stylesheets/fonts/font-awesome/css/all.css b/app/fg/builtin-pages/stylesheets/fonts/font-awesome/css/all.css similarity index 100% rename from app/stylesheets/fonts/font-awesome/css/all.css rename to app/fg/builtin-pages/stylesheets/fonts/font-awesome/css/all.css diff --git a/app/stylesheets/fonts/font-awesome/css/all.min.css b/app/fg/builtin-pages/stylesheets/fonts/font-awesome/css/all.min.css similarity index 100% rename from app/stylesheets/fonts/font-awesome/css/all.min.css rename to app/fg/builtin-pages/stylesheets/fonts/font-awesome/css/all.min.css diff --git a/app/stylesheets/fonts/source-sans-pro.less b/app/fg/builtin-pages/stylesheets/fonts/source-sans-pro.less similarity index 100% rename from app/stylesheets/fonts/source-sans-pro.less rename to app/fg/builtin-pages/stylesheets/fonts/source-sans-pro.less diff --git a/app/fg/builtin-pages/stylesheets/icons.css b/app/fg/builtin-pages/stylesheets/icons.css new file mode 100644 index 0000000000..2a52e1d737 --- /dev/null +++ b/app/fg/builtin-pages/stylesheets/icons.css @@ -0,0 +1,872 @@ +@font-face { + font-family: "photon-entypo"; + src: url("beaker://assets/font-photon-entypo") format("woff"); + font-weight: normal; + font-style: normal; +} +.icon:before { + font-family: photon-entypo; + position: relative; + display: inline-block; + speak: none; + font-size: 100%; + font-style: normal; + font-variant: normal; + text-transform: none; + font-weight: 400; + line-height: 1; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +.icon-note:before { + content: '\e800'; +} +.icon-note-beamed:before { + content: '\e801'; +} +.icon-music:before { + content: '\e802'; +} +.icon-search:before { + content: '\e803'; +} +.icon-flashlight:before { + content: '\e804'; +} +.icon-mail:before { + content: '\e805'; +} +.icon-heart:before { + content: '\e806'; +} +.icon-heart-empty:before { + content: '\e807'; +} +.icon-star:before { + content: '\e808'; +} +.icon-star-empty:before { + content: '\e809'; +} +.icon-user:before { + content: '\e80a'; +} +.icon-users:before { + content: '\e80b'; +} +.icon-user-add:before { + content: '\e80c'; +} +.icon-video:before { + content: '\e80d'; +} +.icon-picture:before { + content: '\e80e'; +} +.icon-camera:before { + content: '\e80f'; +} +.icon-layout:before { + content: '\e810'; +} +.icon-menu:before { + content: '\e811'; +} +.icon-check:before { + content: '\e812'; +} +.icon-cancel:before { + content: '\e813'; +} +.icon-cancel-circled:before { + content: '\e814'; +} +.icon-cancel-squared:before { + content: '\e815'; +} +.icon-plus:before { + content: '\e816'; +} +.icon-plus-circled:before { + content: '\e817'; +} +.icon-plus-squared:before { + content: '\e818'; +} +.icon-minus:before { + content: '\e819'; +} +.icon-minus-circled:before { + content: '\e81a'; +} +.icon-minus-squared:before { + content: '\e81b'; +} +.icon-help:before { + content: '\e81c'; +} +.icon-help-circled:before { + content: '\e81d'; +} +.icon-info:before { + content: '\e81e'; +} +.icon-info-circled:before { + content: '\e81f'; +} +.icon-back:before { + content: '\e820'; +} +.icon-home:before { + content: '\e821'; +} +.icon-link:before { + content: '\e822'; +} +.icon-attach:before { + content: '\e823'; +} +.icon-lock:before { + content: '\e824'; +} +.icon-lock-open:before { + content: '\e825'; +} +.icon-eye:before { + content: '\e826'; +} +.icon-tag:before { + content: '\e827'; +} +.icon-bookmark:before { + content: '\e828'; +} +.icon-bookmarks:before { + content: '\e829'; +} +.icon-flag:before { + content: '\e82a'; +} +.icon-thumbs-up:before { + content: '\e82b'; +} +.icon-thumbs-down:before { + content: '\e82c'; +} +.icon-download:before { + content: '\e82d'; +} +.icon-upload:before { + content: '\e82e'; +} +.icon-upload-cloud:before { + content: '\e82f'; +} +.icon-reply:before { + content: '\e830'; +} +.icon-reply-all:before { + content: '\e831'; +} +.icon-forward:before { + content: '\e832'; +} +.icon-quote:before { + content: '\e833'; +} +.icon-code:before { + content: '\e834'; +} +.icon-export:before { + content: '\e835'; +} +.icon-pencil:before { + content: '\e836'; +} +.icon-feather:before { + content: '\e837'; +} +.icon-print:before { + content: '\e838'; +} +.icon-retweet:before { + content: '\e839'; +} +.icon-keyboard:before { + content: '\e83a'; +} +.icon-comment:before { + content: '\e83b'; +} +.icon-chat:before { + content: '\e83c'; +} +.icon-bell:before { + content: '\e83d'; +} +.icon-attention:before { + content: '\e83e'; +} +.icon-alert:before { + content: '\e83f'; +} +.icon-vcard:before { + content: '\e840'; +} +.icon-address:before { + content: '\e841'; +} +.icon-location:before { + content: '\e842'; +} +.icon-map:before { + content: '\e843'; +} +.icon-direction:before { + content: '\e844'; +} +.icon-compass:before { + content: '\e845'; +} +.icon-cup:before { + content: '\e846'; +} +.icon-trash:before { + content: '\e847'; +} +.icon-doc:before { + content: '\e848'; +} +.icon-docs:before { + content: '\e849'; +} +.icon-doc-landscape:before { + content: '\e84a'; +} +.icon-doc-text:before { + content: '\e84b'; +} +.icon-doc-text-inv:before { + content: '\e84c'; +} +.icon-newspaper:before { + content: '\e84d'; +} +.icon-book-open:before { + content: '\e84e'; +} +.icon-book:before { + content: '\e84f'; +} +.icon-folder:before { + content: '\e850'; +} +.icon-archive:before { + content: '\e851'; +} +.icon-box:before { + content: '\e852'; +} +.icon-rss:before { + content: '\e853'; +} +.icon-phone:before { + content: '\e854'; +} +.icon-cog:before { + content: '\e855'; +} +.icon-tools:before { + content: '\e856'; +} +.icon-share:before { + content: '\e857'; +} +.icon-shareable:before { + content: '\e858'; +} +.icon-basket:before { + content: '\e859'; +} +.icon-bag:before { + content: '\e85a'; +} +.icon-calendar:before { + content: '\e85b'; +} +.icon-login:before { + content: '\e85c'; +} +.icon-logout:before { + content: '\e85d'; +} +.icon-mic:before { + content: '\e85e'; +} +.icon-mute:before { + content: '\e85f'; +} +.icon-sound:before { + content: '\e860'; +} +.icon-volume:before { + content: '\e861'; +} +.icon-clock:before { + content: '\e862'; +} +.icon-hourglass:before { + content: '\e863'; +} +.icon-lamp:before { + content: '\e864'; +} +.icon-light-down:before { + content: '\e865'; +} +.icon-light-up:before { + content: '\e866'; +} +.icon-adjust:before { + content: '\e867'; +} +.icon-block:before { + content: '\e868'; +} +.icon-resize-full:before { + content: '\e869'; +} +.icon-resize-small:before { + content: '\e86a'; +} +.icon-popup:before { + content: '\e86b'; +} +.icon-publish:before { + content: '\e86c'; +} +.icon-window:before { + content: '\e86d'; +} +.icon-arrow-combo:before { + content: '\e86e'; +} +.icon-down-circled:before { + content: '\e86f'; +} +.icon-left-circled:before { + content: '\e870'; +} +.icon-right-circled:before { + content: '\e871'; +} +.icon-up-circled:before { + content: '\e872'; +} +.icon-down-open:before { + content: '\e873'; +} +.icon-left-open:before { + content: '\e874'; +} +.icon-right-open:before { + content: '\e875'; +} +.icon-up-open:before { + content: '\e876'; +} +.icon-down-open-mini:before { + content: '\e877'; +} +.icon-left-open-mini:before { + content: '\e878'; +} +.icon-right-open-mini:before { + content: '\e879'; +} +.icon-up-open-mini:before { + content: '\e87a'; +} +.icon-down-open-big:before { + content: '\e87b'; +} +.icon-left-open-big:before { + content: '\e87c'; +} +.icon-right-open-big:before { + content: '\e87d'; +} +.icon-up-open-big:before { + content: '\e87e'; +} +.icon-down:before { + content: '\e87f'; +} +.icon-left:before { + content: '\e880'; +} +.icon-right:before { + content: '\e881'; +} +.icon-up:before { + content: '\e882'; +} +.icon-down-dir:before { + content: '\e883'; +} +.icon-left-dir:before { + content: '\e884'; +} +.icon-right-dir:before { + content: '\e885'; +} +.icon-up-dir:before { + content: '\e886'; +} +.icon-down-bold:before { + content: '\e887'; +} +.icon-left-bold:before { + content: '\e888'; +} +.icon-right-bold:before { + content: '\e889'; +} +.icon-up-bold:before { + content: '\e88a'; +} +.icon-down-thin:before { + content: '\e88b'; +} +.icon-left-thin:before { + content: '\e88c'; +} +.icon-right-thin:before { + content: '\e88d'; +} +.icon-up-thin:before { + content: '\e88e'; +} +.icon-ccw:before { + content: '\e88f'; +} +.icon-cw:before { + content: '\e890'; +} +.icon-arrows-ccw:before { + content: '\e891'; +} +.icon-level-down:before { + content: '\e892'; +} +.icon-level-up:before { + content: '\e893'; +} +.icon-shuffle:before { + content: '\e894'; +} +.icon-loop:before { + content: '\e895'; +} +.icon-switch:before { + content: '\e896'; +} +.icon-play:before { + content: '\e897'; +} +.icon-stop:before { + content: '\e898'; +} +.icon-pause:before { + content: '\e899'; +} +.icon-record:before { + content: '\e89a'; +} +.icon-to-end:before { + content: '\e89b'; +} +.icon-to-start:before { + content: '\e89c'; +} +.icon-fast-forward:before { + content: '\e89d'; +} +.icon-fast-backward:before { + content: '\e89e'; +} +.icon-progress-0:before { + content: '\e89f'; +} +.icon-progress-1:before { + content: '\e8a0'; +} +.icon-progress-2:before { + content: '\e8a1'; +} +.icon-progress-3:before { + content: '\e8a2'; +} +.icon-target:before { + content: '\e8a3'; +} +.icon-palette:before { + content: '\e8a4'; +} +.icon-list:before { + content: '\e8a5'; +} +.icon-list-add:before { + content: '\e8a6'; +} +.icon-signal:before { + content: '\e8a7'; +} +.icon-trophy:before { + content: '\e8a8'; +} +.icon-battery:before { + content: '\e8a9'; +} +.icon-back-in-time:before { + content: '\e8aa'; +} +.icon-monitor:before { + content: '\e8ab'; +} +.icon-mobile:before { + content: '\e8ac'; +} +.icon-network:before { + content: '\e8ad'; +} +.icon-cd:before { + content: '\e8ae'; +} +.icon-inbox:before { + content: '\e8af'; +} +.icon-install:before { + content: '\e8b0'; +} +.icon-globe:before { + content: '\e8b1'; +} +.icon-cloud:before { + content: '\e8b2'; +} +.icon-cloud-thunder:before { + content: '\e8b3'; +} +.icon-flash:before { + content: '\e8b4'; +} +.icon-moon:before { + content: '\e8b5'; +} +.icon-flight:before { + content: '\e8b6'; +} +.icon-paper-plane:before { + content: '\e8b7'; +} +.icon-leaf:before { + content: '\e8b8'; +} +.icon-lifebuoy:before { + content: '\e8b9'; +} +.icon-mouse:before { + content: '\e8ba'; +} +.icon-briefcase:before { + content: '\e8bb'; +} +.icon-suitcase:before { + content: '\e8bc'; +} +.icon-dot:before { + content: '\e8bd'; +} +.icon-dot-2:before { + content: '\e8be'; +} +.icon-dot-3:before { + content: '\e8bf'; +} +.icon-brush:before { + content: '\e8c0'; +} +.icon-magnet:before { + content: '\e8c1'; +} +.icon-infinity:before { + content: '\e8c2'; +} +.icon-erase:before { + content: '\e8c3'; +} +.icon-chart-pie:before { + content: '\e8c4'; +} +.icon-chart-line:before { + content: '\e8c5'; +} +.icon-chart-bar:before { + content: '\e8c6'; +} +.icon-chart-area:before { + content: '\e8c7'; +} +.icon-tape:before { + content: '\e8c8'; +} +.icon-graduation-cap:before { + content: '\e8c9'; +} +.icon-language:before { + content: '\e8ca'; +} +.icon-ticket:before { + content: '\e8cb'; +} +.icon-water:before { + content: '\e8cc'; +} +.icon-droplet:before { + content: '\e8cd'; +} +.icon-air:before { + content: '\e8ce'; +} +.icon-credit-card:before { + content: '\e8cf'; +} +.icon-floppy:before { + content: '\e8d0'; +} +.icon-clipboard:before { + content: '\e8d1'; +} +.icon-megaphone:before { + content: '\e8d2'; +} +.icon-database:before { + content: '\e8d3'; +} +.icon-drive:before { + content: '\e8d4'; +} +.icon-bucket:before { + content: '\e8d5'; +} +.icon-thermometer:before { + content: '\e8d6'; +} +.icon-key:before { + content: '\e8d7'; +} +.icon-flow-cascade:before { + content: '\e8d8'; +} +.icon-flow-branch:before { + content: '\e8d9'; +} +.icon-flow-tree:before { + content: '\e8da'; +} +.icon-flow-line:before { + content: '\e8db'; +} +.icon-flow-parallel:before { + content: '\e8dc'; +} +.icon-rocket:before { + content: '\e8dd'; +} +.icon-gauge:before { + content: '\e8de'; +} +.icon-traffic-cone:before { + content: '\e8df'; +} +.icon-cc:before { + content: '\e8e0'; +} +.icon-cc-by:before { + content: '\e8e1'; +} +.icon-cc-nc:before { + content: '\e8e2'; +} +.icon-cc-nc-eu:before { + content: '\e8e3'; +} +.icon-cc-nc-jp:before { + content: '\e8e4'; +} +.icon-cc-sa:before { + content: '\e8e5'; +} +.icon-cc-nd:before { + content: '\e8e6'; +} +.icon-cc-pd:before { + content: '\e8e7'; +} +.icon-cc-zero:before { + content: '\e8e8'; +} +.icon-cc-share:before { + content: '\e8e9'; +} +.icon-cc-remix:before { + content: '\e8ea'; +} +.icon-github:before { + content: '\e8eb'; +} +.icon-github-circled:before { + content: '\e8ec'; +} +.icon-flickr:before { + content: '\e8ed'; +} +.icon-flickr-circled:before { + content: '\e8ee'; +} +.icon-vimeo:before { + content: '\e8ef'; +} +.icon-vimeo-circled:before { + content: '\e8f0'; +} +.icon-twitter:before { + content: '\e8f1'; +} +.icon-twitter-circled:before { + content: '\e8f2'; +} +.icon-facebook:before { + content: '\e8f3'; +} +.icon-facebook-circled:before { + content: '\e8f4'; +} +.icon-facebook-squared:before { + content: '\e8f5'; +} +.icon-gplus:before { + content: '\e8f6'; +} +.icon-gplus-circled:before { + content: '\e8f7'; +} +.icon-pinterest:before { + content: '\e8f8'; +} +.icon-pinterest-circled:before { + content: '\e8f9'; +} +.icon-tumblr:before { + content: '\e8fa'; +} +.icon-tumblr-circled:before { + content: '\e8fb'; +} +.icon-linkedin:before { + content: '\e8fc'; +} +.icon-linkedin-circled:before { + content: '\e8fd'; +} +.icon-dribbble:before { + content: '\e8fe'; +} +.icon-dribbble-circled:before { + content: '\e8ff'; +} +.icon-stumbleupon:before { + content: '\e900'; +} +.icon-stumbleupon-circled:before { + content: '\e901'; +} +.icon-lastfm:before { + content: '\e902'; +} +.icon-lastfm-circled:before { + content: '\e903'; +} +.icon-rdio:before { + content: '\e904'; +} +.icon-rdio-circled:before { + content: '\e905'; +} +.icon-spotify:before { + content: '\e906'; +} +.icon-spotify-circled:before { + content: '\e907'; +} +.icon-qq:before { + content: '\e908'; +} +.icon-instagram:before { + content: '\e909'; +} +.icon-dropbox:before { + content: '\e90a'; +} +.icon-evernote:before { + content: '\e90b'; +} +.icon-flattr:before { + content: '\e90c'; +} +.icon-skype:before { + content: '\e90d'; +} +.icon-skype-circled:before { + content: '\e90e'; +} +.icon-renren:before { + content: '\e90f'; +} +.icon-sina-weibo:before { + content: '\e910'; +} +.icon-paypal:before { + content: '\e911'; +} +.icon-picasa:before { + content: '\e912'; +} +.icon-soundcloud:before { + content: '\e913'; +} +.icon-mixi:before { + content: '\e914'; +} +.icon-behance:before { + content: '\e915'; +} +.icon-google-circles:before { + content: '\e916'; +} +.icon-vkontakte:before { + content: '\e917'; +} +.icon-smashing:before { + content: '\e918'; +} +.icon-sweden:before { + content: '\e919'; +} +.icon-db-shape:before { + content: '\e91a'; +} +.icon-logo-db:before { + content: '\e91b'; +} diff --git a/app/stylesheets/icons.less b/app/fg/builtin-pages/stylesheets/icons.less similarity index 100% rename from app/stylesheets/icons.less rename to app/fg/builtin-pages/stylesheets/icons.less diff --git a/app/fg/builtin-pages/stylesheets/photon.min.css b/app/fg/builtin-pages/stylesheets/photon.min.css new file mode 100644 index 0000000000..bb9bc25c0e --- /dev/null +++ b/app/fg/builtin-pages/stylesheets/photon.min.css @@ -0,0 +1,203 @@ +@charset "UTF-8"; +/*! + * ===================================================== + * Photon v0.1.1 + * Copyright 2015 Connor Sears + * Licensed under MIT (https://github.com/connors/proton/blob/master/LICENSE) + * + * v0.1.1 designed by @connors. + * + * MODIFIED by prf!! (some stuff stripped out, and added) + * ===================================================== + */ +audio, +canvas, +progress, +sub, +sup, +video { + vertical-align: baseline; +} +body, +html { + height: 100%; +} +hr, +html, +label { + overflow: hidden; +} +audio:not([controls]) { + display: none; +} +a:active, +a:hover { + outline: 0; +} +abbr[title] { + border-bottom: 1px dotted; +} +b, +strong { + font-weight: 700; +} +dfn { + font-style: italic; +} +h1 { + margin: 0.67em 0; + font-size: 36px; +} +small { + font-size: 80%; +} +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; +} +sup { + top: -0.5em; +} +.pane-group, +.window { + top: 0; + left: 0; + right: 0; +} +sub { + bottom: -0.25em; +} +pre { + overflow: auto; +} +code, +kbd, +pre, +samp { + font-family: monospace,monospace; + font-size: 1em; +} +button, +input, +optgroup, +select, +textarea { + color: inherit; + font: inherit; + margin: 0; +} +input[type=number]::-webkit-inner-spin-button, +input[type=number]::-webkit-outer-spin-button { + height: auto; +} +input[type=search]::-webkit-search-cancel-button, +input[type=search]::-webkit-search-decoration { + -webkit-appearance: none; +} +fieldset { + border: 1px solid silver; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} +legend { + border: 0; + padding: 0; +} +* { + -webkit-box-sizing: border-box; + box-sizing: border-box; +} +html { + width: 100%; +} +body { + padding: 0; + margin: 0; + font-family: system, -apple-system, ".SFNSDisplay-Regular", "Helvetica Neue", Helvetica, "Segoe UI", sans-serif; + font-size: 13px; + line-height: 1.6; + color: #333; + background-color: transparent; +} +hr { + margin: 15px 0; + background: 0 0; + border: 0; + border-bottom: 1px solid #ddd; +} +h1, +h2, +h3, +h4, +h5, +h6 { + margin-top: 20px; + margin-bottom: 10px; + font-weight: 500; +} +h2 { + font-size: 30px; +} +h3 { + font-size: 24px; +} +h4 { + font-size: 18px; +} +h5 { + font-size: 14px; +} +h6 { + font-size: 12px; +} +.window { + position: absolute; + bottom: 0; + display: flex; + flex-direction: column; + background-color: #fff; +} +.window-content { + position: relative; + overflow-y: auto; + display: flex; + flex: 1; +} +label { + margin-bottom: 5px; +} +input[type=search] { + -webkit-appearance: textfield; + box-sizing: border-box; +} +input[type=checkbox], +input[type=radio] { + margin: 4px 0 0; + line-height: normal; +} +table { + border-spacing: 0; + width: 100%; + border: 0; + border-collapse: separate; + text-align: left; +} +thead { + background-color: #f5f5f4; +} +tbody { + background-color: #fff; +} +td, +th { + padding: 2px 15px; +} +td:last-child, +th:last-child { + border-right: 0; +} +.flex-spacer { + flex: 1; +} diff --git a/app/stylesheets/photon.min.less b/app/fg/builtin-pages/stylesheets/photon.min.less similarity index 100% rename from app/stylesheets/photon.min.less rename to app/fg/builtin-pages/stylesheets/photon.min.less diff --git a/app/stylesheets/utils/decorators.less b/app/fg/builtin-pages/stylesheets/utils/decorators.less similarity index 100% rename from app/stylesheets/utils/decorators.less rename to app/fg/builtin-pages/stylesheets/utils/decorators.less diff --git a/app/stylesheets/utils/flex.less b/app/fg/builtin-pages/stylesheets/utils/flex.less similarity index 100% rename from app/stylesheets/utils/flex.less rename to app/fg/builtin-pages/stylesheets/utils/flex.less diff --git a/app/builtin-pages/swarm-debugger.html b/app/fg/builtin-pages/swarm-debugger.html similarity index 100% rename from app/builtin-pages/swarm-debugger.html rename to app/fg/builtin-pages/swarm-debugger.html diff --git a/app/builtin-pages/views/downloads.js b/app/fg/builtin-pages/views/downloads.js similarity index 100% rename from app/builtin-pages/views/downloads.js rename to app/fg/builtin-pages/views/downloads.js diff --git a/app/builtin-pages/views/editor.js b/app/fg/builtin-pages/views/editor.js similarity index 99% rename from app/builtin-pages/views/editor.js rename to app/fg/builtin-pages/views/editor.js index 854a00daa3..0a8191b78a 100644 --- a/app/builtin-pages/views/editor.js +++ b/app/fg/builtin-pages/views/editor.js @@ -14,7 +14,7 @@ import * as models from '../com/editor/models' import * as toast from '../com/toast' import renderArchiveHistory from '../com/archive/archive-history' import * as contextMenu from '../com/context-menu' -import {writeToClipboard} from '../../lib/fg/event-handlers' +import {writeToClipboard} from '../../lib/event-handlers' import toggleable2, {closeAllToggleables} from '../com/toggleable2' import * as localSyncPathPopup from '../com/library/localsyncpath-popup' const profiles = navigator.importSystemAPI('unwalled-garden-profiles') @@ -450,7 +450,7 @@ function update () { ${settingsForm.render(workingCheckout, isReadonly, archive.info, workingDatJson)} - `, + ` ) } else { yo.update( @@ -468,7 +468,7 @@ function update () { - `, + ` ) } yo.update( @@ -1057,7 +1057,7 @@ function onClickArchiveMenu (e) { _get(archive, 'info.userSettings.isSaved') ? {icon: 'fas fa-trash', label: 'Remove from my websites', click: onArchiveUnsave} : {icon: 'fas fa-save', label: 'Add to my websites', click: onArchiveSave}, - {icon: 'link', label: 'Copy link', click: () => {writeToClipboard(archive.url); toast.create('Link copied to clipboard')}}, + {icon: 'link', label: 'Copy link', click: () => { writeToClipboard(archive.url); toast.create('Link copied to clipboard') }}, {icon: 'far fa-clone', label: 'Duplicate this site', click: onFork} ] }) diff --git a/app/builtin-pages/views/history.js b/app/fg/builtin-pages/views/history.js similarity index 98% rename from app/builtin-pages/views/history.js rename to app/fg/builtin-pages/views/history.js index 24a1f9ce76..f2b4589c1a 100644 --- a/app/builtin-pages/views/history.js +++ b/app/fg/builtin-pages/views/history.js @@ -2,7 +2,7 @@ const yo = require('yo-yo') const moment = require('moment') -import {getHostname} from '../../lib/strings' +import {getHostname} from '../../../lib/strings' import debounce from 'lodash.debounce' import renderCloseIcon from '../icon/close' import renderBuiltinPagesNav from '../com/builtin-pages-nav' @@ -199,7 +199,7 @@ function renderRow (row, i) { return yo`
    - + ${row.title.replace(/[^\x00-\x7F]/g, '')} ${getHostname(row.url)} diff --git a/app/builtin-pages/views/settings.js b/app/fg/builtin-pages/views/settings.js similarity index 99% rename from app/builtin-pages/views/settings.js rename to app/fg/builtin-pages/views/settings.js index 906b1cdda0..fab83effa9 100644 --- a/app/builtin-pages/views/settings.js +++ b/app/fg/builtin-pages/views/settings.js @@ -7,8 +7,6 @@ import DatCache from '../com/settings/dat-cache' import CrawlerStatus from '../com/settings/crawler-status' import renderBuiltinPagesNav from '../com/builtin-pages-nav' -const DEFAULT_APP_NAMES = ['start', 'feed', 'profile', 'library', 'bookmarks', 'search'] - // globals // = @@ -22,7 +20,6 @@ var logger = new Logger() var datCache = new DatCache() var crawlerStatus = new CrawlerStatus() - // main // = @@ -152,7 +149,6 @@ function renderUsers () { e.stopPropagation() var opts = await beaker.browser.showModal('user', user) - await beaker.users.edit(user.url, opts) Object.assign(user, opts) renderToPage() } diff --git a/app/builtin-pages/views/swarm-debugger.js b/app/fg/builtin-pages/views/swarm-debugger.js similarity index 99% rename from app/builtin-pages/views/swarm-debugger.js rename to app/fg/builtin-pages/views/swarm-debugger.js index 22ec81d161..b4e02af234 100644 --- a/app/builtin-pages/views/swarm-debugger.js +++ b/app/fg/builtin-pages/views/swarm-debugger.js @@ -1,7 +1,7 @@ /* globals DatArchive beaker */ -import * as yo from 'yo-yo' -import {shortenHash} from '../../lib/strings' +import yo from 'yo-yo' +import {shortenHash} from '../../../lib/strings' const COLUMN_SETS = { discovery: ['archiveKey', 'event', 'peer', 'trafficType', 'messageId', 'message'], diff --git a/app/builtin-pages/views/watchlist.js b/app/fg/builtin-pages/views/watchlist.js similarity index 99% rename from app/builtin-pages/views/watchlist.js rename to app/fg/builtin-pages/views/watchlist.js index 37ea6818bc..1252630dca 100644 --- a/app/builtin-pages/views/watchlist.js +++ b/app/fg/builtin-pages/views/watchlist.js @@ -1,7 +1,7 @@ /* globals DatArchive beaker confirm localStorage */ import yo from 'yo-yo' -import {niceDate} from '../../lib/time' +import {niceDate} from '../../../lib/time' import * as toast from '../com/toast' import * as addWatchlistItemPopup from '../com/add-watchlist-item-popup' import renderBuiltinPagesNav from '../com/builtin-pages-nav' diff --git a/app/builtin-pages/watchlist.html b/app/fg/builtin-pages/watchlist.html similarity index 100% rename from app/builtin-pages/watchlist.html rename to app/fg/builtin-pages/watchlist.html diff --git a/app/fg/json-renderer/index.js b/app/fg/json-renderer/index.js new file mode 100644 index 0000000000..d3fc31be1a --- /dev/null +++ b/app/fg/json-renderer/index.js @@ -0,0 +1,28 @@ + +const TWOMB = 2097152 // in bytes + +function parse (str) { + if (str === '') return false + if (str.length > TWOMB) return false // too much json, bro + try { + return JSON.parse(str) + } catch (e) { + return false + } +} + +var el = document.querySelector('body > pre') + +// try to parse +var obj = parse(el.textContent) +if (obj) { + var json = JSON.stringify(obj, null, 2) + json = json.replace(/^(\s+)(".+":)/gmi, (v, ws, key) => `${ws}${key}`) + json = json.replace(/<\/span> (".+")/gmi, (v, str) => ` ${str}`) + json = json.replace(/^(\s+)(".+")(,?)$/gmi, (v, ws, str, comma) => `${ws}${str}${comma}`) + json = json.replace(/<\/span> ([0-9]+)/gmi, (v, num) => ` ${num}`) + json = json.replace(/^(\s+)([0-9]+)(,?)$/gmi, (v, ws, num, comma) => `${ws}${num}${comma}`) + json = json.replace(/<\/span> (true|false)/gmi, (v, bool) => ` ${bool}`) + json = json.replace(/^(\s+)(true|false)(,?)$/gmi, (v, ws, bool, comma) => `${ws}${bool}${comma}`) + el.innerHTML = json +} diff --git a/app/lib/fg/color-thief.js b/app/fg/lib/color-thief.js similarity index 100% rename from app/lib/fg/color-thief.js rename to app/fg/lib/color-thief.js diff --git a/app/lib/default-user-thumb.jpg.js b/app/fg/lib/default-user-thumb.jpg.js similarity index 100% rename from app/lib/default-user-thumb.jpg.js rename to app/fg/lib/default-user-thumb.jpg.js diff --git a/app/lib/fg/drag-drop.js b/app/fg/lib/drag-drop.js similarity index 100% rename from app/lib/fg/drag-drop.js rename to app/fg/lib/drag-drop.js diff --git a/app/lib/fg/event-handlers.js b/app/fg/lib/event-handlers.js similarity index 90% rename from app/lib/fg/event-handlers.js rename to app/fg/lib/event-handlers.js index 5e06f8e442..40c145728d 100644 --- a/app/lib/fg/event-handlers.js +++ b/app/fg/lib/event-handlers.js @@ -1,7 +1,5 @@ /* globals Event beaker */ -import * as yo from 'yo-yo' - export function pushUrl (e) { // ignore ctrl/cmd+click if (e.metaKey) { return } @@ -32,7 +30,8 @@ export function findParent (node, test) { } export function writeToClipboard (str) { - var textarea = yo`` + var textarea = document.createElement('textarea') + textarea.textContent = str document.body.appendChild(textarea) textarea.select() document.execCommand('copy') @@ -68,5 +67,5 @@ export function adjustWindowHeight (sel) { } export function emit (name, detail = null) { - document.dispatchEvent(new CustomEvent(name, {detail})) + document.dispatchEvent(new CustomEvent(name, {detail, bubbles: true, composed: true})) } \ No newline at end of file diff --git a/app/lib/fg/img.js b/app/fg/lib/img.js similarity index 97% rename from app/lib/fg/img.js rename to app/fg/lib/img.js index 380d809a60..7719d576f2 100644 --- a/app/lib/fg/img.js +++ b/app/fg/lib/img.js @@ -17,6 +17,8 @@ export function urlToData (url, width, height, cb) { // like urlToData, but loads all images and takes the one that fits the target dimensions best export async function urlsToData (urls) { + if (!urls || !urls.length) return false + // load all images var imgs = await Promise.all(urls.map(url => { return new Promise(resolve => { diff --git a/app/lib/fg/progress-pie-svg.js b/app/fg/lib/progress-pie-svg.js similarity index 100% rename from app/lib/fg/progress-pie-svg.js rename to app/fg/lib/progress-pie-svg.js diff --git a/app/lib/fg/svg.js b/app/fg/lib/svg.js similarity index 100% rename from app/lib/fg/svg.js rename to app/fg/lib/svg.js diff --git a/app/location-bar.html b/app/fg/location-bar/index.html similarity index 95% rename from app/location-bar.html rename to app/fg/location-bar/index.html index 20f985157d..e5b39d8590 100644 --- a/app/location-bar.html +++ b/app/fg/location-bar/index.html @@ -7,7 +7,7 @@ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Ubuntu, Cantarell, "Oxygen Sans", "Helvetica Neue", sans-serif; margin: 0; background: transparent; - border-radius: 4px; + border-radius: 16px; border: 1px solid #bbb; box-shadow: 0 2px 3px rgba(0,0,0,.3); font-size: 12px; diff --git a/app/location-bar.js b/app/fg/location-bar/index.js similarity index 61% rename from app/location-bar.js rename to app/fg/location-bar/index.js index e9c4aa5430..7d4a56adf7 100644 --- a/app/location-bar.js +++ b/app/fg/location-bar/index.js @@ -1,24 +1,27 @@ /* globals customElements */ import * as rpc from 'pauls-electron-rpc' -import { LitElement, html, css } from './vendor/lit-element/lit-element' -import { repeat } from './vendor/lit-element/lit-html/directives/repeat' -import { classMap } from './vendor/lit-element/lit-html/directives/class-map' -import { unsafeHTML } from './vendor/lit-element/lit-html/directives/unsafe-html' -import { examineLocationInput } from './lib/urls' +import { LitElement, html, css } from '../vendor/lit-element/lit-element' +import { repeat } from '../vendor/lit-element/lit-html/directives/repeat' +import { classMap } from '../vendor/lit-element/lit-html/directives/class-map' +import { unsafeHTML } from '../vendor/lit-element/lit-html/directives/unsafe-html' +import { examineLocationInput } from '../../lib/urls' +import { joinPath } from '../../lib/strings' import _uniqWith from 'lodash.uniqwith' -import browserManifest from '@beaker/core/web-apis/manifests/internal/browser' -import bookmarksManifest from '@beaker/core/web-apis/manifests/external/bookmarks' -import historyManifest from '@beaker/core/web-apis/manifests/internal/history' -import searchManifest from '@beaker/core/web-apis/manifests/external/search' -import locationBarManifest from './background-process/rpc-manifests/location-bar' -import viewsManifest from './background-process/rpc-manifests/views' +import browserManifest from '../../bg/web-apis/manifests/internal/browser' +import bookmarksManifest from '../../bg/web-apis/manifests/internal/bookmarks' +import hyperdriveManifest from '../../bg/web-apis/manifests/external/hyperdrive' +import historyManifest from '../../bg/web-apis/manifests/internal/history' +import locationBarManifest from '../../bg/rpc-manifests/location-bar' +import beakerFsManifest from '../../bg/web-apis/manifests/internal/beaker-filesystem' +import viewsManifest from '../../bg/rpc-manifests/views' const bg = { beakerBrowser: rpc.importAPI('beaker-browser', browserManifest), bookmarks: rpc.importAPI('bookmarks', bookmarksManifest), + hyperdrive: rpc.importAPI('hyperdrive', hyperdriveManifest), history: rpc.importAPI('history', historyManifest), - search: rpc.importAPI('search', searchManifest), locationBar: rpc.importAPI('background-process-location-bar', locationBarManifest), + beakerFs: rpc.importAPI('beaker-filesystem', beakerFsManifest), views: rpc.importAPI('background-process-views', viewsManifest) } @@ -35,9 +38,18 @@ class LocationBar extends LitElement { constructor () { super() this.reset() + this.fetchBrowserInfo() - // fetch platform information - var {platform} = bg.beakerBrowser.getInfo() + // disallow right click + window.addEventListener('contextmenu', e => e.preventDefault()) + + // export interface + window.setup = () => this.reset() + window.command = (command, opts) => this.onCommand(command, opts) + } + + async fetchBrowserInfo () { + var {platform} = await bg.beakerBrowser.getInfo() window.platform = platform if (platform === 'darwin') { document.body.classList.add('darwin') @@ -45,22 +57,17 @@ class LocationBar extends LitElement { if (platform === 'win32') { document.body.classList.add('win32') } - - // disallow right click - window.addEventListener('contextmenu', e => e.preventDefault()) - - // export interface - window.setup = () => this.reset() - window.command = (command, opts) => this.onCommand(command, opts) } reset () { - this.userUrl = null + this.currentTabLocation = undefined this.inputValue = '' this.inputQuery = '' this.autocompleteResults = [] this.currentSelection = 0 this.hoveredSearch = '' + this.queryIdCounter = 0 + this.bookmarksFetch = bg.bookmarks.list() } selectResult (result) { @@ -138,63 +145,34 @@ class LocationBar extends LitElement { if (r.search) { return html`
    -
    ${r.search} - ${r.title}
    +
    ${r.search} - ${r.title}
    ` } if (r.isGoto) { return html`
    -
    ${r.title}
    +
    ${r.title}
    ` } - if (r.record && r.record.type === 'site') { + if (r.isDriveEntry) { return html` -
    -
    -
    - - ${r.titleDecorated ? unsafeHTML(r.titleDecorated) : r.title} - ${r.descriptionDecorated ? html`| ${unsafeHTML(r.descriptionDecorated)}` : ''} - -
    -
    - - ${r.url === this.userUrl ? html`This is you` : 'Followed by you'} - ${toNiceUrl(r.url)} -
    -
    +
    +
    ${r.nameDecorated}
    +
    ${r.path}
    ` } - if (r.record && r.record.type === 'unwalled.garden/bookmark') { - let isAuthorYou = r.record.author.url === this.userUrl - let authorTitle = isAuthorYou ? html`you`: (r.record.author.title || 'Anonymous') + if (r.isBookmark) { return html` -
    -
    -
    - - ${r.titleDecorated ? unsafeHTML(r.titleDecorated) : r.title} - ${r.descriptionDecorated ? html`| ${unsafeHTML(r.descriptionDecorated)}` : ''} - -
    -
    - - Bookmarked by ${authorTitle} - ${toNiceUrl(r.url)} -
    -
    +
    +
    ${r.titleDecorated}
    +
    ${r.urlDecorated}
    ` } return html`
    -
    -
    - - ${r.titleDecorated ? unsafeHTML(r.titleDecorated) : r.title} - ${r.descriptionDecorated ? html`| ${unsafeHTML(r.descriptionDecorated)}` : ''} - -
    -
    ${toNiceUrl(r.urlDecorated ? unsafeHTML(r.urlDecorated) : r.url)}
    +
    ${r.titleDecorated ? unsafeHTML(r.titleDecorated) : r.title}
    +
    + ${toNiceUrl(r.urlDecorated ? unsafeHTML(r.urlDecorated) : r.url)}
    ` } @@ -205,12 +183,9 @@ class LocationBar extends LitElement { onCommand (cmd, opts) { switch (cmd) { case 'set-value': - if (opts.value && opts.value !== this.inputValue) { + if (!opts.value || opts.value !== this.inputValue) { this.inputQuery = this.inputValue = opts.value this.currentSelection = 0 - if (this.inputValue.startsWith('beaker://start')) { - this.inputQuery = this.inputValue = '' - } // update the input var input = this.shadowRoot.querySelector('input') @@ -241,30 +216,46 @@ class LocationBar extends LitElement { onInputLocation (e) { var value = e.currentTarget.value.trim() - if (value && this.inputValue !== value) { + if (!value || this.inputValue !== value) { this.inputQuery = this.inputValue = value // update the current value this.currentSelection = 0 // reset the selection this.queryAutocomplete() } } - onKeydownLocation (e) { - // on enter + async onKeydownLocation (e) { if (e.key === 'Enter') { e.preventDefault() - this.selectResult(this.autocompleteResults[this.currentSelection]) return } - // on escape if (e.key === 'Escape') { e.preventDefault() bg.locationBar.close() return } - // on keycode navigations + if (e.key === 'Tab') { + e.preventDefault() + let unmatchedNamePortion = undefined + let selection = this.autocompleteResults[this.currentSelection] + if (selection.isDriveEntry) { + unmatchedNamePortion = selection.unmatchedNamePortion + if (isFolder(selection)) unmatchedNamePortion += '/' + } else { + let driveEntries = this.autocompleteResults.filter(r => r.isDriveEntry) + if (driveEntries.length === 1) { + unmatchedNamePortion = driveEntries[0].unmatchedNamePortion + if (isFolder(driveEntries[0])) unmatchedNamePortion += '/' + } + } + if (unmatchedNamePortion) { + this.shadowRoot.querySelector('input').value = this.inputValue = this.inputQuery + unmatchedNamePortion + this.queryAutocomplete() + } + } + var up = (e.key === 'ArrowUp' || (e.ctrlKey && e.key === 'p')) var down = (e.key === 'ArrowDown' || (e.ctrlKey && e.key === 'n')) if (up || down) { @@ -277,11 +268,12 @@ class LocationBar extends LitElement { onContextMenu (e) { e.preventDefault() + e.stopPropagation() bg.views.showLocationBarContextMenu('active') } onInputBlur (e) { - setTimeout(() => bg.locationBar.close(), 100) + setTimeout(() => bg.locationBar.close(), 200) } onClickResult (e) { @@ -295,34 +287,15 @@ class LocationBar extends LitElement { } async queryAutocomplete () { - if (!this.userUrl) { - let userSession = await bg.beakerBrowser.getUserSession().catch(err => null) - this.userUrl = userSession ? userSession.url : null - } - + var queryId = ++this.queryIdCounter this.inputValue = this.inputValue.trim() - var finalResults - var [crawlerResults, historyResults] = await Promise.all([ - bg.search.query({query: this.inputValue, filters: {datasets: ['sites', 'unwalled.garden/bookmark']}, limit: 10}), - bg.history.search(this.inputValue) - ]) - - // console.log({ - // historyResults, - // crawlerResults - // }) - - // decorate results with bolded regions - var searchTerms = this.inputValue.replace(/[:^*-./]/g, ' ').split(' ').filter(Boolean) - crawlerResults.results.forEach(r => highlightSearchResult(searchTerms, crawlerResults.highlightNonce, r)) - historyResults.forEach(r => highlightHistoryResult(searchTerms, r)) // figure out what we're looking at - var {vWithProtocol, vSearch, isProbablyUrl, isGuessingTheScheme} = examineLocationInput(this.inputValue) + var {vWithProtocol, vSearch, isProbablyUrl, isGuessingTheScheme} = examineLocationInput(this.inputValue || '/') // set the top results accordingly - var gotoResult = { url: vWithProtocol, title: 'Go to ' + this.inputValue, isGuessingTheScheme, isGoto: true } + var gotoResult = { url: vWithProtocol, title: 'Go to ' + (this.inputValue || '/'), isGuessingTheScheme, isGoto: true } var searchResult = { search: this.inputValue, title: `Search DuckDuckGo for "${this.inputValue}"`, @@ -331,8 +304,89 @@ class LocationBar extends LitElement { if (isProbablyUrl) finalResults = [gotoResult, searchResult] else finalResults = [searchResult, gotoResult] + // optimistically update the first two results + this.autocompleteResults = finalResults.concat(this.autocompleteResults.slice(2)) + this.requestUpdate() + this.updateComplete.then(() => this.resize()) + + // determine the URL that the user is targeting (only if it references a drive) + var inputDriveUrl = undefined + var inputDriveUrlp = undefined + var isDriveUrlRe = /^hyper:\/\//i + if (this.inputValue.startsWith('/')) { + if (!this.currentTabLocation) { + this.currentTabLocation = (await bg.views.getTabState('active').catch(e => ({url: ''}))).url + } + if (isDriveUrlRe.test(this.currentTabLocation)) { + try { + let urlp = new URL(this.currentTabLocation) + inputDriveUrl = joinPath(urlp.origin, this.inputValue.slice(1)) + } catch (e) { + // ignore, bad url + } + } + } else if (isDriveUrlRe.test(this.inputValue)) { + inputDriveUrl = this.inputValue + } + if (inputDriveUrl) { + try { + inputDriveUrlp = new URL(inputDriveUrl) + } catch (e) { + // just ignore + } + } + + var [driveResults, historyResults, bookmarks] = await Promise.all([ + inputDriveUrlp ? searchDrive(inputDriveUrlp) : [], + this.inputValue ? bg.history.search(this.inputValue) : [], + this.bookmarksFetch + ]) + + var bookmarkResults = [] + { + let query = this.inputValue.toLowerCase() + for (let bookmark of bookmarks) { + let titleIndex = bookmark.title.toLowerCase().indexOf(query) + let hrefIndex = bookmark.href.indexOf(query) + if (titleIndex === -1 && hrefIndex === -1) { + continue + } + + var titleDecorated = bookmark.title + if (titleIndex !== -1) { + let t = bookmark.title + let start = titleIndex + let end = start + query.length + titleDecorated = html`${t.slice(0, start)}${t.slice(start, end)}${t.slice(end)}` + } + + var urlDecorated = bookmark.href + if (hrefIndex !== -1) { + let h = bookmark.href + let start = hrefIndex + let end = start + query.length + urlDecorated = html`${h.slice(0, start)}${h.slice(start, end)}${h.slice(end)}` + } + + bookmarkResults.push({ + isBookmark: true, + url: bookmark.href, + urlDecorated, + titleDecorated + }) + } + } + + // abort if changes to the input have occurred since triggering these queries + if (queryId !== this.queryIdCounter) return + + // decorate results with bolded regions + var searchTerms = this.inputValue.replace(/[:^*-./]/g, ' ').split(' ').filter(Boolean) + historyResults.forEach(r => highlightHistoryResult(searchTerms, r)) + // add search results - finalResults = finalResults.concat(crawlerResults.results) + finalResults = finalResults.concat(driveResults) + finalResults = finalResults.concat(bookmarkResults) finalResults = finalResults.concat(historyResults) // remove duplicates @@ -365,13 +419,13 @@ class LocationBar extends LitElement { LocationBar.styles = [css` .wrapper { background: #fff; - border-radius: 4px; + border-radius: 16px; } input { box-sizing: border-box; border: 0; - border-radius: 4px; + border-radius: 16px; padding: 0 54px; line-height: 26px; @@ -382,8 +436,8 @@ input { color: #222; font-size: 13.5px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Ubuntu, Cantarell, "Oxygen Sans", "Helvetica Neue", sans-serif; - font-weight: 500; - letter-spacing: -.2px; + font-weight: 400; + letter-spacing: 0.5px; } input:focus { @@ -402,84 +456,48 @@ input:focus { line-height: 20px; width: calc(100vw - 46px); overflow: hidden; + border-top: 1px solid #eef; } -.result .icon { - flex: 0 0 42px; +.result:last-child { + border-bottom: 1px solid #eef; } -.result .info { - flex: 1; +.result .icon { + flex: 0 0 42px; } .result .icon img { - width: 32px; - height: 32px; -} - -.result .icon .avatar { - border-radius: 50%; - object-fit: cover; + width: 16px; + height: 16px; + margin-left: 7px; } .result .icon .fa, -.result .icon .fas { +.result .icon .fas, +.result .icon .far { font-size: 13px; color: #707070; margin-left: 9px; } -.result .content-column, -.result .search-column { - font-size: 15px; -} - -.result .url-column, -.result .content-column, -.result .search-column { +.result .title, +.result .provenance { + font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; -} - -.result .url-column { - color: #707070; -} - -.result .title, -.result .description { color: #1f55c1; } -.result .description { - margin-left: 2px; -} - -.result .tags { - margin-left: 5px; -} - -.provenance { - font-size: 13px; - color: #555; -} - -.provenance .fas { - font-size: 11px; - position: relative; - top: -1px; - margin-right: 2px; - color: gray; -} - -.provenance .url { - margin-left: 5px; +.result .title, +.result .provenance { + flex: 1; } -.is-you { - color: #3a3d4e; - font-weight: 500; +.result .provenance { + color: #778; } .result.selected { @@ -491,37 +509,36 @@ input:focus { } .result:hover { - background: #eee; + background: #f6f6fd; } .search-engines { - border-top: 1px solid #ddd; - background: #f7f7f7; - border-bottom-left-radius: 4px; - border-bottom-right-radius: 4px; + border-bottom-left-radius: 16px; + border-bottom-right-radius: 16px; } .search-engines .label { - padding: 6px 8px; - border-bottom: 1px solid #ddd; + padding: 12px 18px 8px; font-size: 11px; } .search-engines .list { display: flex; align-items: center; + padding-bottom: 4px; } .search-engines .list a { - flex: 0 0 60px; + border-radius: 50%; + flex: 0 0 42px; text-align: center; - border-right: 1px solid #ddd; padding: 8px 0; + margin-left: 12px; cursor: pointer; } .search-engines .list a:hover { - background: #eee; + background: #f0f0f8; } .search-engines .list a img { @@ -536,44 +553,56 @@ customElements.define('location-bar', LocationBar) // = const TRAILING_SLASH_REGEX = /(\/$)/ -const PREVIEW_REGEX = /(\+preview)/ function normalizeURL (str = '') { - return str.replace(TRAILING_SLASH_REGEX, '').replace(PREVIEW_REGEX, '') + return str.replace(TRAILING_SLASH_REGEX, '') } function makeSafe (str = '') { return str.replace(//g, '>').replace(/&/g, '&').replace(/"/g, '"') } -const DAT_KEY_REGEX = /([0-9a-f]{64})/ig +const DRIVE_KEY_REGEX = /([0-9a-f]{64})/ig function toNiceUrl (str) { if (typeof str !== 'string') return str - return str.replace(DAT_KEY_REGEX, (_, m) => `${m.slice(0, 6)}..${m.slice(-2)}`) + return str.replace(DRIVE_KEY_REGEX, (_, m) => `${m.slice(0, 6)}..${m.slice(-2)}`) } -// helper for crawler search results -// - search results are returned from beaker's search APIs with nonces wrapping the highlighted sections -// - e.g. a search for "test" might return "the {500}test{/500} result" -// - in this case, we want to highlight at a more fine grain (the search terms) -// - so we strip the nonces and use the search terms for highlighting -function highlightSearchResult (searchTerms, nonce, result) { - var start = new RegExp(`\\{${nonce}\\}`, 'g') // eg {500} - var end = new RegExp(`\\{/${nonce}\\}`, 'g') // eg {/500} - var termRe = new RegExp(`(${searchTerms.join('|')})`, 'gi') // eg '(beaker|browser)' - const highlight = str => makeSafe(str).replace(start, '').replace(end, '').replace(termRe, (_, term) => `${term}`) - - if (result.record.type === 'site') { - result.titleDecorated = highlight(result.title) - result.descriptionDecorated = highlight(result.description) - } else if (result.record.type === 'unwalled.garden/bookmark') { - result.url = result.content.href - result.titleDecorated = highlight(result.content.title) - result.descriptionDecorated = '' - if (result.content.description) result.descriptionDecorated += highlight(result.content.description) - if (result.content.tags && result.content.tags.filter(Boolean).length) result.descriptionDecorated += `(${highlight(result.content.tags.join(' '))})` +async function searchDrive (urlp) { + try { + var nameFilter = undefined + var parentFolderPath = urlp.pathname + if (!parentFolderPath.endsWith('/')) { + let parts = parentFolderPath.split('/') + nameFilter = parts.pop() + parentFolderPath = parts.join('/') + } + + var items = await bg.hyperdrive.readdir(joinPath(urlp.origin, parentFolderPath), {timeout: 2e3, includeStats: true}) + if (nameFilter) { + nameFilter = nameFilter.toLowerCase() + items = items.filter(item => item.name.toLowerCase().startsWith(nameFilter)) + } + items.sort((a, b) => a.name.localeCompare(b.name)) + + for (let item of items) { + item.isDriveEntry = true + item.unmatchedNamePortion = nameFilter ? item.name.slice(nameFilter.length) : item.name + item.nameDecorated = nameFilter ? html`${nameFilter}${item.unmatchedNamePortion}` : item.name + item.path = joinPath(parentFolderPath, item.name) + item.url = joinPath(urlp.origin, item.path) + } + + return items + } catch (e) { + console.debug('Failed to readdir', e) + return [] } } +function isFolder (item) { + return (item.stat.mode & 16384) === 16384 +} + // helper for history search results // - takes in the current search (tokenized) and a result object // - mutates `result` so that matching text is bold diff --git a/app/fg/modals/add-contact.js b/app/fg/modals/add-contact.js new file mode 100644 index 0000000000..2034415300 --- /dev/null +++ b/app/fg/modals/add-contact.js @@ -0,0 +1,239 @@ +/* globals customElements */ +import { LitElement, html, css } from '../vendor/lit-element/lit-element' +import * as bg from './bg-process-rpc' +import commonCSS from './common.css' +import inputsCSS from './inputs.css' +import buttonsCSS from './buttons.css' +import spinnerCSS from './spinner.css' +import './img-fallbacks.js' + +class AddContactModal extends LitElement { + static get properties () { + return { + info: {type: Object}, + error: {type: String} + } + } + + static get styles () { + return [commonCSS, inputsCSS, buttonsCSS, spinnerCSS, css` + .wrapper { + padding: 0; + } + + h1.title { + font-size: 17px; + padding: 14px 20px; + border-color: #f0f0f7; + margin: 0; + } + + form { + padding: 0; + margin: 0; + } + + .loading { + display: flex; + align-items: center; + padding: 20px; + font-size: 15px; + border-bottom: 1px solid #f0f0f7; + } + + .loading .spinner { + margin-right: 10px; + } + + .error { + padding: 20px; + margin: 0; + font-size: 15px; + color: #555; + border-bottom: 1px solid #f0f0f7; + } + + .contact { + display: flex; + align-items: center; + height: 108px; + padding: 10px 20px; + border-bottom: 1px solid #f0f0f7; + box-sizing: border-box; + } + + .contact img { + border-radius: 50%; + object-fit: cover; + width: 80px; + height: 80px; + margin-right: 16px; + box-sizing: border-box; + } + + .contact .title { + font-size: 23px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .contact .description { + font-size: 17px; + } + + .contact .info { + flex: 1; + } + + .contact.selected { + background: #2864dc; + color: #fff; + } + + .host-prompt { + background: #f3f3f8; + padding: 12px 24px; + } + + .host-prompt label { + margin: 0; + display: flex; + align-items: center; + } + + .host-prompt input { + display: inline; + margin: 0 8px 0 0; + width: auto; + height: auto; + } + + .form-actions { + display: flex; + justify-content: space-between; + align-items: center; + padding: 14px 20px; + text-align: left; + } + `] + } + + constructor () { + super() + this.cbs = undefined + this.info = undefined + this.error = undefined + } + + init (params, cbs) { + this.url = params.url + this.cbs = cbs + this.info = undefined + this.error = undefined + this.requestUpdate() + this.tryFetch() + } + + async tryFetch () { + try { + this.error = undefined + var info = await bg.hyperdrive.getInfo(this.url) + if (info.version === 0) { + this.error = 'Unable to find this hyperdrive on the network' + } else { + this.info = info + } + } catch (e) { + this.cbs.reject(e.message) + } + } + + get hostChecked () { + try { + return this.shadowRoot.querySelector('[name=host]').checked + } catch (e) { + return false + } + } + + // rendering + // = + + render () { + return html` + +
    +

    Add to Address Book

    + +
    + ${this.error ? html` +
    + ${this.error} +
    + ` : this.info ? html` +
    + + + + +
    +
    ${this.info.title}
    +
    ${this.info.description}
    +
    +
    + ` : html` +
    + Loading contact info... +
    + `} + + ${this.info ? html` +
    + +
    + ` : ''} + +
    + + ${this.error ? html` + + ` : html` + + `} +
    +
    +
    + ` + } + + // event handlers + // = + + updated () { + // adjust size based on rendering + var height = this.shadowRoot.querySelector('div').clientHeight + bg.modals.resizeSelf({height}) + } + + onClickCancel (e) { + e.preventDefault() + this.cbs.reject(new Error('Canceled')) + } + + async onSubmit (e) { + e.preventDefault() + if (this.info) { + this.cbs.resolve({key: this.info.key, host: this.hostChecked}) + } else { + this.tryFetch() + } + } +} + +customElements.define('add-contact-modal', AddContactModal) \ No newline at end of file diff --git a/app/modals/basic-auth.js b/app/fg/modals/basic-auth.js similarity index 100% rename from app/modals/basic-auth.js rename to app/fg/modals/basic-auth.js diff --git a/app/fg/modals/bg-process-rpc.js b/app/fg/modals/bg-process-rpc.js new file mode 100644 index 0000000000..50291f55ff --- /dev/null +++ b/app/fg/modals/bg-process-rpc.js @@ -0,0 +1,14 @@ +import * as rpc from 'pauls-electron-rpc' +import browserManifest from '../../bg/web-apis/manifests/internal/browser' +import contactsManifest from '../../bg/web-apis/manifests/external/contacts' +import drivesManifest from '../../bg/web-apis/manifests/internal/drives' +import hyperdriveManifest from '../../bg/web-apis/manifests/external/hyperdrive' +import modalsManifest from '../../bg/rpc-manifests/modals' +import beakerFsManifest from '../../bg/web-apis/manifests/internal/beaker-filesystem' + +export const beakerBrowser = rpc.importAPI('beaker-browser', browserManifest) +export const contacts = rpc.importAPI('contacts', contactsManifest) +export const drives = rpc.importAPI('drives', drivesManifest) +export const hyperdrive = rpc.importAPI('hyperdrive', hyperdriveManifest) +export const modals = rpc.importAPI('background-process-modals', modalsManifest) +export const beakerFs = rpc.importAPI('beaker-filesystem', beakerFsManifest) \ No newline at end of file diff --git a/app/modals/buttons.css.js b/app/fg/modals/buttons.css.js similarity index 100% rename from app/modals/buttons.css.js rename to app/fg/modals/buttons.css.js diff --git a/app/shell-menus/buttons2.css.js b/app/fg/modals/buttons2.css.js similarity index 85% rename from app/shell-menus/buttons2.css.js rename to app/fg/modals/buttons2.css.js index 8ac8c72f73..c8198dca18 100644 --- a/app/shell-menus/buttons2.css.js +++ b/app/fg/modals/buttons2.css.js @@ -53,6 +53,23 @@ button.noborder { border-color: transparent; } +button.transparent { + background: transparent; + border-color: transparent; + box-shadow: none; +} + +button.transparent:hover { + background: #f5f5f5; +} + +button.transparent.pressed { + background: rgba(0,0,0,.1); + box-shadow: inset 0 1px 2px rgba(0,0,0,.25); + color: inherit; +} + + .radio-group button { background: transparent; border: 0; diff --git a/app/modals/common.css.js b/app/fg/modals/common.css.js similarity index 78% rename from app/modals/common.css.js rename to app/fg/modals/common.css.js index b591ca56ed..cf273c63dd 100644 --- a/app/modals/common.css.js +++ b/app/fg/modals/common.css.js @@ -3,6 +3,7 @@ import {css} from '../vendor/lit-element/lit-element' export default css` .wrapper { padding: 10px 20px; + user-select: none; } h1.title { @@ -29,7 +30,8 @@ form { form textarea, form input, -form .input { +form .input, +form details { display: block; width: 100%; margin: 5px 0 15px 0; @@ -41,6 +43,16 @@ form textarea { height: 55px; } +details input, +details textarea, +details .input { + margin-bottom: 0; +} + +details summary { + outline: 0; +} + .form-actions { text-align: right; } diff --git a/app/fg/modals/create-drive.js b/app/fg/modals/create-drive.js new file mode 100644 index 0000000000..f489b855f7 --- /dev/null +++ b/app/fg/modals/create-drive.js @@ -0,0 +1,226 @@ +/* globals customElements */ +import { LitElement, html, css } from '../vendor/lit-element/lit-element' +import * as bg from './bg-process-rpc' +import commonCSS from './common.css' +import inputsCSS from './inputs.css' +import buttonsCSS from './buttons2.css' +import spinnerCSS from './spinner.css' +import _groupBy from 'lodash.groupby' + +class CreateDriveModal extends LitElement { + static get properties () { + return { + title: {type: String}, + description: {type: String}, + fromFolderPath: {type: String}, + errors: {type: Object} + } + } + + static get styles () { + return [commonCSS, inputsCSS, buttonsCSS, spinnerCSS, css` + .wrapper { + padding: 0; + } + + h1.title { + padding: 14px 20px; + margin: 0; + border-color: #bbb; + } + + form { + padding: 14px 20px; + margin: 0; + } + + form input { + font-size: 14px; + height: 34px; + padding: 0 10px; + border-color: #bbb; + margin-top: 0; + } + + hr { + border: 0; + border-top: 1px solid #ddd; + margin: 20px 0; + } + + .from-folder-path { + background: #f3f3f8; + padding: 10px 12px; + margin-bottom: 10px; + border-radius: 4px; + } + + .form-actions { + display: flex; + } + + .form-actions button { + padding: 6px 12px; + font-size: 12px; + } + + .form-actions button:first-child { + margin-right: 5px; + } + + .form-actions button:last-child { + margin-left: auto; + } + + .tip { + background: #fafafd; + margin: 10px -20px -14px; + padding: 10px 18px; + color: gray; + } + + .tip a { + color: inherit; + text-decoration: none; + } + + .tip a:hover { + text-decoration: underline; + } + `] + } + + constructor () { + super() + this.cbs = undefined + this.title = '' + this.description = '' + this.author = undefined + this.fromFolderPath = undefined + this.errors = {} + } + + async init (params, cbs) { + this.cbs = cbs + this.title = params.title || '' + this.description = params.description || '' + this.author = undefined // this.author = params.author + await this.requestUpdate() + } + + updated () { + this.adjustHeight() + } + + adjustHeight () { + var height = this.shadowRoot.querySelector('div').clientHeight + bg.modals.resizeSelf({height}) + } + + // rendering + // = + + render () { + return html` + +
    +

    + Create New Hyperdrive +

    +
    +
    + + ${this.errors.title ? html`
    ${this.errors.title}
    ` : ''} + + ${this.fromFolderPath ? html` +
    + Import from folder: ${this.fromFolderPath} Cancel +
    + ` : ''} +
    + +
    + + + +
    + + +
    +
    + ` + } + + // event handlers + // = + + onChangeTitle (e) { + this.title = e.target.value.trim() + } + + onChangeDescription (e) { + this.description = e.target.value.trim() + } + + onClickCancel (e) { + e.preventDefault() + this.cbs.reject(new Error('Canceled')) + } + + async onSubmit (e) { + e.preventDefault() + + if (!this.title) { + this.errors = {title: 'Required'} + return + } + + this.shadowRoot.querySelector('button[type="submit"]').innerHTML = `
    ` + Array.from(this.shadowRoot.querySelectorAll('button'), b => b.setAttribute('disabled', 'disabled')) + + try { + var url = await bg.hyperdrive.createDrive({ + title: this.title, + description: this.description, + author: this.author, + prompt: false + }) + if (this.fromFolderPath) { + await bg.hyperdrive.importFromFilesystem({src: this.fromFolderPath, dst: url}) + } + this.cbs.resolve({url}) + } catch (e) { + this.cbs.reject(e.message || e.toString()) + } + } + + async onClickFromFolder (e) { + let btn = e.currentTarget + e.preventDefault() + + var folder = await bg.beakerBrowser.showOpenDialog({ + title: 'Select folder', + buttonLabel: 'Use folder', + properties: ['openDirectory'] + }) + if (!folder || !folder.length) return + this.fromFolderPath = folder[0] + } + + onClickCancelFromFolder (e) { + e.preventDefault() + this.fromFolderPath = undefined + } + + onClickLink (e) { + e.preventDefault() + bg.beakerBrowser.openUrl(e.currentTarget.dataset.href, {setActive: true}) + } +} + +customElements.define('create-drive-modal', CreateDriveModal) \ No newline at end of file diff --git a/app/fg/modals/drive-properties.js b/app/fg/modals/drive-properties.js new file mode 100644 index 0000000000..839c843ca5 --- /dev/null +++ b/app/fg/modals/drive-properties.js @@ -0,0 +1,220 @@ +/* globals customElements */ +import { LitElement, html, css } from '../vendor/lit-element/lit-element' +import { repeat } from '../vendor/lit-element/lit-html/directives/repeat' +import * as bg from './bg-process-rpc' +import commonCSS from './common.css' +import inputsCSS from './inputs.css' +import buttonsCSS from './buttons2.css' +import { ucfirst, joinPath } from '../../lib/strings' + +class DrivePropertiesModal extends LitElement { + static get styles () { + return [commonCSS, inputsCSS, buttonsCSS, css` + .wrapper { + padding: 0; + } + + h1.title { + padding: 14px 20px; + margin: 0; + border-color: #bbb; + } + + form { + padding: 0; + margin: 0; + } + + .props { + background: #fafafa; + } + + .prop { + display: flex; + align-items: center; + border-bottom: 1px dashed #ccc; + } + + .prop:last-child { + border-bottom: 0; + } + + .prop .key { + flex: 0 0 100px; + padding: 8px 8px 8px 20px; + border-right: 1px dashed #ccc; + font-weight: 500; + } + + .prop input { + border-radius: 0; + margin: 0; + border: 0; + padding: 0; + height: auto; + } + + .prop .img-input, + .prop .other-input, + .prop input[type="text"] { + flex: 1; + font-size: 14px; + padding: 8px; + background: #fafafa; + } + + .prop.writable .img-input:hover, + .prop.writable .other-input:hover, + .prop.writable input[type="text"]:hover { + background: #f0f0f0; + } + + .prop input[type="text"]:focus { + box-shadow: none; + } + + .prop.writable input[type="text"]:focus { + background: #f0f0f0; + } + + .prop .img-input { + display: flex; + align-items: center; + } + + .prop img { + width: 16px; + height: 16px; + object-fit: cover; + margin-right: 5px; + } + + .form-actions { + display: flex; + justify-content: space-between; + border-top: 1px dashed #ccc; + padding: 8px 10px; + } + + .form-actions button { + padding: 6px 12px; + font-size: 12px; + } + `] + } + + constructor () { + super() + this.cbs = undefined + this.url = '' + this.writable = false + this.props = {} + } + + async init (params, cbs) { + this.cbs = cbs + this.url = params.url + this.writable = params.writable + this.props = params.props || {} + this.props.title = this.props.title || '' + this.props.description = this.props.description || '' + await this.requestUpdate() + this.adjustHeight() + } + + adjustHeight () { + var height = this.shadowRoot.querySelector('div').clientHeight + bg.modals.resizeSelf({height}) + } + + // rendering + // = + + render () { + return html` + +
    +

    + Drive Properties +

    + +
    +
    + ${repeat(Object.entries(this.props), entry => entry[0], entry => this.renderProp(...entry))} + +
    +
    Thumbnail
    +
    + + +
    +
    +
    + +
    + + +
    +
    +
    + ` + } + + renderProp (key, value) { + return html` +
    +
    ${ucfirst(key)}
    + +
    + ` + } + + // event handlers + // = + + onInputChange (e) { + this.requestUpdate() + } + + onClickCancel (e) { + e.preventDefault() + this.cbs.resolve() + } + + async onSubmit (e) { + e.preventDefault() + + if (!this.writable) { + return this.cbs.resolve() + } + + var newProps = Object.fromEntries(new FormData(e.currentTarget)) + + // handle thumb file + var thumbInput = this.shadowRoot.querySelector('#thumb-input') + if (thumbInput.files[0]) { + let file = thumbInput.files[0] + let ext = file.name.split('.').pop() + let reader = new FileReader() + let bufPromise = new Promise((resolve, reject) => { + reader.onload = e => resolve(e.target.result) + reader.onerror = reject + }) + reader.readAsArrayBuffer(file) + + await Promise.all([ + bg.hyperdrive.unlink(joinPath(this.url, '/thumb.png')).catch(e => null), + bg.hyperdrive.unlink(joinPath(this.url, '/thumb.jpg')).catch(e => null), + bg.hyperdrive.unlink(joinPath(this.url, '/thumb.jpeg')).catch(e => null) + ]) + await bg.hyperdrive.writeFile(joinPath(this.url, `/thumb.${ext}`), await bufPromise) + } + + // handle props + await bg.hyperdrive.configure(this.url, newProps).catch(e => null) + + this.cbs.resolve() + } +} + +customElements.define('drive-properties-modal', DrivePropertiesModal) \ No newline at end of file diff --git a/app/fg/modals/fork-drive.js b/app/fg/modals/fork-drive.js new file mode 100644 index 0000000000..b18251ee30 --- /dev/null +++ b/app/fg/modals/fork-drive.js @@ -0,0 +1,364 @@ +/* globals customElements */ +import { LitElement, html, css } from '../vendor/lit-element/lit-element' +import { repeat } from '../vendor/lit-element/lit-html/directives/repeat' +import prettyHash from 'pretty-hash' +import * as bg from './bg-process-rpc' +import commonCSS from './common.css' +import inputsCSS from './inputs.css' +import buttonsCSS from './buttons.css' +import spinnerCSS from './spinner.css' + +const STATES = { + READY: 0, + DOWNLOADING: 1, + CLONING: 2 +} + +class ForkDriveModal extends LitElement { + static get properties () { + return { + state: {type: Number}, + label: {type: String}, + title: {type: String}, + description: {type: String}, + isDetached: {type: Boolean} + } + } + + static get styles () { + return [commonCSS, inputsCSS, buttonsCSS, spinnerCSS, css` + .wrapper { + padding: 0; + } + + form { + padding: 14px 20px; + margin: 0; + } + + .loading { + padding: 20px 22px 20px; + font-size: 15px; + font-style: normal; + border-bottom: 1px solid #ccd; + color: rgba(0, 0, 0, 0.6); + } + + .tabbed-nav { + display: flex; + align-items: center; + font-size: 17px; + letter-spacing: 0.5px; + margin: -4px -16px 14px; + } + + .tabbed-nav span { + min-width: 5px; + border: 1px solid transparent; + border-bottom: 1px solid #bbb; + height: 28px; + } + + .tabbed-nav span.spacer { + flex: 1; + } + + .tabbed-nav a { + color: inherit; + border: 1px solid transparent; + border-bottom: 1px solid #bbb; + cursor: pointer; + border-top-left-radius: 2px; + border-top-right-radius: 2px; + padding: 4px 18px; + } + + .tabbed-nav a.active { + border: 1px solid #bbb; + border-bottom: 1px solid transparent; + } + + .columns { + display: grid; + grid-template-columns: auto 1fr; + grid-gap: 12px; + } + + input { + font-size: 14px; + height: 34px; + padding: 0 10px; + border-color: #bbb; + } + + select { + -webkit-appearance: none; + display: inline-block; + font-size: 13px; + font-weight: 500; + padding: 8px 30px 8px 10px; + max-width: 100%; + border: 1px solid #bbc; + border-radius: 4px; + outline: 0; + background-color: #fff; + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAARVBMVEUAAAAsPlAsPlAsPlAsPlAsPlAsPlAsPlAsPlAsPlAsPlAsPlAsPlAsPlAsPlAsPlAsPlAsPlAsPlAsPlAsPlAsPlAsPlAz1sU3AAAAFnRSTlMAAwQMERkbIikuVWl0dXeDtLXF5PH5X4+8lwAAAIxJREFUSInt0TcCwjAQRNFvE5dkwKD7H5WGINsKszWa+r9qoO1ftjqc1B0N2DyDYwNcPX0Ia0Yf2HFx9Y+e7u4Be6B3CAOXsPcTqrDvd5qw6G1FxL0ipn1dzPuaWPZlkepLIt3nRa7PiXyfFqU+Jcr9UtT6uaj3U6H0sdD6n1D7j9B76M7jbevo29rgBddTP/7iwZL3AAAAAElFTkSuQmCC); + background-repeat: no-repeat; + background-position: right .7em top 50%, 0 0; + background-size: .65em auto, 100%; + } + + .help { + opacity: 0.6; + } + + .help.with-icon { + padding-left: 16px; + position: relative; + } + + .help.with-icon .fas { + position: absolute; + left: -2px; + top: 1px; + font-size: 11px; + } + + input + .help { + margin-top: -8px; + } + + hr { + border: 0; + border-top: 1px solid #ddd; + margin: 10px 0; + } + + .form-actions { + display: flex; + align-items: center; + justify-content: space-between; + } + + .fork-dat-progress { + font-size: 14px; + } + `] + } + + constructor () { + super() + + // internal state + this.driveInfo = null + this.state = STATES.READY + + // params + this.cbs = null + this.forks = [] + this.base = undefined + this.label = '' + this.title = '' + this.description = '' + this.isDetached = false + } + + async init (params, cbs) { + // store params + this.cbs = cbs + this.forks = params.forks + this.base = this.forks.find(fork => fork.url === params.url) || this.forks[0] + this.isDetached = params.detached || false + this.label = params.label || '' + await this.requestUpdate() + + // fetch drive info + this.driveInfo = await bg.hyperdrive.getInfo(this.base.url) + this.title = this.driveInfo.title || '' + this.description = this.driveInfo.description || '' + await this.requestUpdate() + this.adjustHeight() + } + + updated () { + this.adjustHeight() + } + + adjustHeight () { + var height = this.shadowRoot.querySelector('div').clientHeight + bg.modals.resizeSelf({height}) + } + + // rendering + // = + + render () { + if (!this.driveInfo) { + return this.renderLoading() + } + + var progressEl + var actionBtn + switch (this.state) { + case STATES.READY: + progressEl = html`
    Ready to ${this.isDetached ? 'make a copy' : 'fork'}.
    ` + actionBtn = html`` + break + case STATES.DOWNLOADING: + progressEl = html`
    Downloading remaining files...
    ` + actionBtn = html`` + break + case STATES.CLONING: + progressEl = html`
    Downloading and copying...
    ` + actionBtn = html`` + break + } + + const navItem = (v, label) => html` + this.onSetDetached(v)}>${label} + ` + const baseOpt = (fork) => { + return html` + + ` + } + return html` + +
    +
    +
    + + ${navItem(false, 'Fork')} + ${navItem(true, 'Copy')} + +
    + + ${this.isDetached ? html` +

    Make an independent copy of the drive.

    + + + + + ` : html` +

    A fork is a linked copy of the drive which is used for making changes and then merging into the original.

    +
    +
    + +
    + +
    +
    + +
    + + +

    The label will help you identify the fork.

    +
    +
    + `} + +
    + +
    + ${progressEl} +
    + + ${actionBtn} +
    +
    +
    +
    + ` + } + + renderLoading () { + return html` +
    +
    Loading...
    +
    +
    +
    +
    + + +
    +
    +
    +
    + ` + } + + // event handlers + // = + + onSetDetached (v) { + this.isDetached = v + } + + async onChangeBase (e) { + this.base = this.forks.find(fork => fork.url === e.currentTarget.value) + this.driveInfo = await bg.hyperdrive.getInfo(this.base.url) + this.requestUpdate() + } + + onChangeLabel (e) { + this.label = e.target.value + } + + onChangeTitle (e) { + this.title = e.target.value + } + + onChangeDescription (e) { + this.description = e.target.value + } + + onClickCancel (e) { + e.preventDefault() + this.cbs.reject(new Error('Canceled')) + } + + async onSubmit (e) { + e.preventDefault() + + if (this.isDetached) { + if (!this.title.trim()) return + } else { + if (!this.label.trim()) return + } + + // this.state = STATES.DOWNLOADING + // await bg.hyperdrive.download(this.base.url) + + this.state = STATES.CLONING + try { + var url = await bg.hyperdrive.forkDrive(this.base.url, { + detached: this.isDetached, + title: this.title, + description: this.description, + label: this.label, + prompt: false + }) + this.cbs.resolve({url}) + } catch (e) { + this.cbs.reject(e.message || e.toString()) + } + } +} + +customElements.define('fork-drive-modal', ForkDriveModal) \ No newline at end of file diff --git a/app/fg/modals/img-fallbacks.js b/app/fg/modals/img-fallbacks.js new file mode 100644 index 0000000000..be9a862397 --- /dev/null +++ b/app/fg/modals/img-fallbacks.js @@ -0,0 +1,39 @@ +import { LitElement, html, css } from '../vendor/lit-element/lit-element' + +/* +Usage: + + + + + + +*/ + +export class ImgFallbacks extends LitElement { + static get properties () { + return { + currentImage: {type: Number} + } + } + + constructor () { + super() + this.currentImage = 1 + } + + render () { + return html`` + } + + onSlotChange (e) { + var img = this.shadowRoot.querySelector('slot').assignedElements()[0] + if (img) img.addEventListener('error', this.onError.bind(this)) + } + + onError (e) { + this.currentImage = this.currentImage + 1 + } +} + +customElements.define('beaker-img-fallbacks', ImgFallbacks) diff --git a/app/modals.html b/app/fg/modals/index.html similarity index 96% rename from app/modals.html rename to app/fg/modals/index.html index ecd47dd0e1..3020730dbd 100644 --- a/app/modals.html +++ b/app/fg/modals/index.html @@ -15,7 +15,6 @@ display: block; background: #fff; border: 1px solid #bbb; - border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,.4); margin: 0 10px 10px; overflow: hidden; diff --git a/app/modals.js b/app/fg/modals/index.js similarity index 58% rename from app/modals.js rename to app/fg/modals/index.js index aef65381c5..a847011131 100644 --- a/app/modals.js +++ b/app/fg/modals/index.js @@ -1,16 +1,18 @@ /* globals customElements */ -import { LitElement, html } from './vendor/lit-element/lit-element' -import * as bg from './modals/bg-process-rpc' -import './modals/setup' -import './modals/create-archive' -import './modals/fork-archive' -import './modals/select-archive' -import './modals/select-file' -import './modals/prompt' -import './modals/basic-auth' -import './modals/user' -import './modals/create-user-session' -import './modals/install-application' +import { LitElement, html } from '../vendor/lit-element/lit-element' +import * as bg from './bg-process-rpc' +import './setup' +import './create-drive' +import './fork-drive' +import './drive-properties' +import './select-drive' +import './select-file' +import './prompt' +import './basic-auth' +import './user-editor' +import './user-select' +import './add-contact' +import './select-contact' class ModalsWrapper extends LitElement { static get properties () { @@ -23,12 +25,21 @@ class ModalsWrapper extends LitElement { super() this.currentModal = null this.cbs = null + this.fetchBrowserInfo() // export interface window.runModal = this.runModal.bind(this) - // fetch platform information - var {platform} = bg.beakerBrowser.getInfo() + // global event listeners + window.addEventListener('keydown', e => { + if (e.key === 'Escape') { + this.cbs.reject(new Error('Canceled')) + } + }) + } + + async fetchBrowserInfo () { + var {platform} = await bg.beakerBrowser.getInfo() window.platform = platform if (platform === 'darwin') { document.body.classList.add('darwin') @@ -36,13 +47,6 @@ class ModalsWrapper extends LitElement { if (platform === 'win32') { document.body.classList.add('win32') } - - // global event listeners - window.addEventListener('keydown', e => { - if (e.key === 'Escape') { - this.cbs.reject(new Error('Canceled')) - } - }) } async runModal (name, params) { @@ -66,24 +70,28 @@ class ModalsWrapper extends LitElement { switch (this.currentModal) { case 'setup': return html`` - case 'create-archive': - return html`` - case 'fork-archive': - return html`` - case 'select-archive': - return html`` + case 'create-drive': + return html`` + case 'fork-drive': + return html`` + case 'drive-properties': + return html`` + case 'select-drive': + return html`` case 'select-file': return html`` case 'prompt': return html`` case 'basic-auth': return html`` - case 'user': - return html`` - case 'create-user-session': - return html`` - case 'install-application': - return html`` + case 'user-editor': + return html`` + case 'user-select': + return html`` + case 'add-contact': + return html`` + case 'select-contact': + return html`` } return html`
    ` } diff --git a/app/modals/inputs.css.js b/app/fg/modals/inputs.css.js similarity index 100% rename from app/modals/inputs.css.js rename to app/fg/modals/inputs.css.js diff --git a/app/modals/prompt.js b/app/fg/modals/prompt.js similarity index 99% rename from app/modals/prompt.js rename to app/fg/modals/prompt.js index 66470d594d..3fc0ed3018 100644 --- a/app/modals/prompt.js +++ b/app/fg/modals/prompt.js @@ -26,7 +26,6 @@ class PromptModal extends LitElement { bg.modals.resizeSelf({width, height}) } - firstUpdated () { this.shadowRoot.querySelector('input').focus() } diff --git a/app/fg/modals/select-contact.js b/app/fg/modals/select-contact.js new file mode 100644 index 0000000000..f308f6b73f --- /dev/null +++ b/app/fg/modals/select-contact.js @@ -0,0 +1,235 @@ +/* globals customElements */ +import { LitElement, html, css } from '../vendor/lit-element/lit-element' +import { classMap } from '../vendor/lit-element/lit-html/directives/class-map' +import { repeat } from '../vendor/lit-element/lit-html/directives/repeat' +import * as bg from './bg-process-rpc' +import commonCSS from './common.css' +import inputsCSS from './inputs.css' +import buttonsCSS from './buttons.css' +import './img-fallbacks.js' + +class SelectContactModal extends LitElement { + static get properties () { + return { + selection: {type: Array}, + multiple: {type: Boolean} + } + } + + static get styles () { + return [commonCSS, inputsCSS, buttonsCSS, css` + .wrapper { + padding: 0; + } + + h1.title { + font-size: 17px; + padding: 14px 20px; + border-color: #dddde0; + margin: 0; + } + + form { + padding: 0; + margin: 0; + } + + .contacts { + height: 400px; + overflow: auto; + box-sizing: border-box; + border-bottom: 1px solid #dddde0; + background: #f3f3f8; + } + + .profiles-only.contacts { + height: 200px; + } + + .contact { + display: flex; + align-items: center; + border-bottom: 1px solid #dddde0; + padding: 12px 20px; + height: 55px; + box-sizing: border-box; + } + + .contact .checkmark { + width: 30px; + } + + .contact .checkmark .fa-circle { + color: #0002; + } + + .contact img { + border-radius: 50%; + object-fit: cover; + width: 32px; + height: 32px; + margin-right: 16px; + box-sizing: border-box; + border: 1px solid #fff; + } + + .contact .title { + flex: 1; + font-size: 14px; + font-weight: 600; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .contact .description { + flex: 1; + font-size: 14px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .contact .profile-badge { + width: 80px; + } + + .contact .profile-badge span { + font-size: 10px; + background: #fff; + color: #778; + padding: 2px 8px; + border-radius: 8px; + } + + .contact.selected { + background: #2864dc; + color: #fff; + } + + .form-actions { + display: flex; + justify-content: space-between; + align-items: center; + padding: 14px 20px; + text-align: left; + } + `] + } + + constructor () { + super() + this.cbs = undefined + this.contacts = [] + this.selection = [] + this.multiple = undefined + this.showProfilesOnly = undefined + } + + async init (params, cbs) { + this.cbs = cbs + this.multiple = params.multiple + this.showProfilesOnly = !!params.showProfilesOnly + + var contacts = params.addressBook.contacts || [] + if (params.addressBook.profiles && params.addressBook.profiles.length) { + var profiles = params.addressBook.profiles.map(p => Object.assign(p, {isProfile: true})) + if (this.showProfilesOnly) { + contacts = profiles + } else { + contacts = contacts.concat(profiles) + } + } + contacts.sort((a, b) => a.title.localeCompare(b.title)) + + this.contacts = contacts + + this.selection = [] + await this.requestUpdate() + } + + // rendering + // = + + render () { + return html` + +
    +

    + ${this.showProfilesOnly ? ` + Select Profile + ` : ` + Select Contact${this.multiple ? 's' : ''} + `} +

    + +
    +
    + ${repeat(this.contacts, contact => this.renderContact(contact))} +
    + +
    + + +
    +
    +
    + ` + } + + renderContact (contact) { + var selected = this.selection.includes(contact) + return html` +
    this.onClickContact(e, contact)}> +
    + +
    + + + + +
    ${contact.title || 'Anonymous'}
    +
    ${contact.description}
    +
    ${contact.isProfile ? html`My Profile` : ''}
    +
    + ` + } + + // event handlers + // = + + updated () { + // adjust size based on rendering + var height = this.shadowRoot.querySelector('div').clientHeight + bg.modals.resizeSelf({height}) + } + + onClickContact (e, contact) { + if (this.multiple) { + if (this.selection.includes(contact)) { + this.selection.splice(this.selection.indexOf(contact), 1) + } else { + this.selection.push(contact) + } + this.requestUpdate() + } else { + this.selection = [contact] + } + } + + onClickCancel (e) { + e.preventDefault() + this.cbs.reject(new Error('Canceled')) + } + + async onSubmit (e) { + e.preventDefault() + this.cbs.resolve({contacts: this.selection.map(contact => ({ + url: contact.url, + title: contact.title, + description: contact.description + }))}) + } +} + +customElements.define('select-contact-modal', SelectContactModal) \ No newline at end of file diff --git a/app/fg/modals/select-drive.js b/app/fg/modals/select-drive.js new file mode 100644 index 0000000000..fabc93fb32 --- /dev/null +++ b/app/fg/modals/select-drive.js @@ -0,0 +1,373 @@ +/* globals customElements */ +import { LitElement, html, css } from '../vendor/lit-element/lit-element' +import * as bg from './bg-process-rpc' +import commonCSS from './common.css' +import inputsCSS from './inputs.css' +import buttonsCSS from './buttons.css' +import spinnerCSS from './spinner.css' + +class SelectDriveModal extends LitElement { + static get properties () { + return { + currentTitleFilter: {type: String}, + title: {type: String}, + description: {type: String}, + selectedDriveUrl: {type: String} + } + } + + static get styles () { + return [commonCSS, inputsCSS, buttonsCSS, spinnerCSS, css` + .wrapper, + form { + padding: 0; + margin: 0; + } + + .form-actions { + display: flex; + padding: 0px 15px 10px; + text-align: left; + } + + .form-actions .left { + flex: 1; + } + + .form-actions .btn.cancel { + margin-right: 5px; + } + + h1.title { + padding: 14px 20px; + margin: 0; + border-color: #bbb; + } + + .view { + overflow: hidden; + padding: 10px 15px; + } + + .drive-picker .filter-container { + position: relative; + overflow: visible; + height: 35px; + margin-bottom: 4px; + } + + .drive-picker .filter-container i { + position: absolute; + left: 15px; + top: 13px; + color: #b8b8b8; + z-index: 3; + } + + .drive-picker .type-container { + margin-bottom: 10px; + background: #eee; + padding: 10px; + } + + .drive-picker .filter { + position: absolute; + left: 0; + top: 0; + border: 0; + margin: 0; + height: 35px; + padding: 0 35px; + border: 1px solid #dde; + border-radius: 0; + } + + .drive-picker .filter:focus { + background: #fff; + border: 1.5px solid rgba(40, 100, 220, 0.8); + box-shadow: none; + } + + .drives-list { + height: 350px; + overflow-y: auto; + border: 1px solid #dde; + } + + .drives-list .loading { + display: flex; + padding: 10px; + align-items: center; + color: gray; + } + + .drives-list .loading .spinner { + margin-right: 5px; + } + + .drives-list .empty { + padding: 5px 10px; + color: gray; + } + + .drives-list .drive { + display: flex; + align-items: center; + padding: 10px; + border-bottom: 1px solid #dde; + } + + .drives-list .drive:last-child { + border-bottom: 0; + } + + .drive .thumb { + display: block; + width: 32px; + height: 32px; + margin-right: 16px; + } + + .drives-list .drive .info { + flex: 1; + } + + .drives-list .drive .title { + font-size: 15px; + font-weight: 500; + } + + .drives-list .drive .description { + letter-spacing: -0.2px; + } + + .drives-list .drive.selected { + background: #2864dc; + color: #fff; + } + + .drives-list .drive.selected .thumb { + border-radius: 4px; + box-shadow: 0 1px 2px #0003; + } + + .drives-list .drive.selected .info .hash, + .drives-list .drive.selected .info .readonly { + color: rgba(255, 255, 255, 0.9); + font-weight: 100; + } + `] + } + + constructor () { + super() + + // state + this.currentTitleFilter = '' + this.selectedDriveUrl = '' + this.drives = undefined + + // params + this.customTitle = '' + this.buttonLabel = 'Select' + this.type = null + this.writable = undefined + this.cbs = null + } + + async init (params, cbs) { + this.cbs = cbs + this.customTitle = params.title || '' + this.buttonLabel = params.buttonLabel || 'Select' + this.type = params.type + this.writable = params.writable + await this.requestUpdate() + this.adjustHeight() + + this.drives = await bg.drives.list({includeSystem: true}) + + // move forks onto their parents + this.drives = this.drives.filter(drive => { + if (drive.forkOf) { + let parent = this.drives.find(d => d.key === drive.forkOf.key) + if (parent) { + parent.forks = parent.forks || [] + parent.forks.push(drive) + return false + } + } + return true + }) + this.drives.sort((a, b) => (a.info.type || '').localeCompare(b.info.type || '') || (a.info.title).localeCompare(b.info.title)) + + await this.requestUpdate() + this.adjustHeight() + } + + adjustHeight () { + // adjust height based on rendering + var height = this.shadowRoot.querySelector('div').clientHeight + bg.modals.resizeSelf({height}) + } + + get hasValidSelection () { + return !!this.selectedDriveUrl || isDriveUrl(this.currentTitleFilter) + } + + // rendering + // = + + render () { + return html` + +
    +
    +

    ${this.customTitle || 'Select a drive'}

    + +
    + ${this.renderFilters()} +
    + + +
    + ${isDriveUrl(this.currentTitleFilter) ? html` + ` : html` + ${this.renderDrivesList()} + `} +
    + +
    +
    + +
    +
    + + +
    +
    +
    +
    + ` + } + + renderFilters () { + if (!this.type && typeof this.writable === 'undefined') return '' + return html` +
    + + ${typeof this.writable !== 'undefined' ? html` + ${this.writable ? 'Editable' : 'Read-only'} + ` : ''} + ${this.type ? Array.isArray(this.type) ? this.type.join(', ') : this.type : ''} + only + +
    ` + } + + renderDrivesList () { + if (!this.drives) { + return html`
    • Loading...
    ` + } + + var filtered = this.drives + if (this.type) filtered = filtered.filter(drive => drive.info.type === this.type) + if (typeof this.writable === 'boolean') { + filtered = filtered.filter(drive => drive.info.writable === this.writable) + } + if (this.currentTitleFilter) { + filtered = filtered.filter(a => a.info.title && a.info.title.toLowerCase().includes(this.currentTitleFilter)) + } + + if (!filtered.length) { + return html`
    No drives found
    ` + } + + return html`
    ${filtered.map(a => this.renderDrive(a))}
    ` + } + + renderDrive (drive) { + var isSelected = this.selectedDriveUrl === drive.url + return html` +
    + +
    +
    + ${drive.info.title || html`Untitled`} +
    +
    +
    ${drive.info.description.slice(0, 60)}
    +
    +
    +
    + ` + } + + // event handlers + // = + + onChangeTitleFilter (e) { + this.currentTitleFilter = e.target.value.toLowerCase() + if (this.selectedDriveUrl && isDriveUrl(this.currentTitleFilter)) { + this.selectedDriveUrl = undefined + } + } + + onChangeSelecteddrive (e) { + this.selectedDriveUrl = e.currentTarget.dataset.url + } + + onDblClickdrive (e) { + e.preventDefault() + this.selectedDriveUrl = e.currentTarget.dataset.url + this.onSubmit() + } + + onClickCancel (e) { + e.preventDefault() + this.cbs.reject(new Error('Canceled')) + } + + onClickCreate (e) { + this.cbs.resolve({gotoCreate: true}) + } + + onSubmit (e) { + if (e) e.preventDefault() + this.cbs.resolve({url: this.selectedDriveUrl ? this.selectedDriveUrl : (new URL(this.currentTitleFilter)).origin}) + } +} + +customElements.define('select-drive-modal', SelectDriveModal) + +function isDriveUrl (v = '') { + try { + var urlp = new URL(v) + return urlp.protocol === 'hyper:' + } catch (e) { + return false + } +} + +function toOrigin (v = '') { + try { + var urlp = new URL(v) + return urlp.protocol + '//' + urlp.hostname + } catch (e) { + return '' + } +} \ No newline at end of file diff --git a/app/fg/modals/select-file.js b/app/fg/modals/select-file.js new file mode 100644 index 0000000000..6610a498d6 --- /dev/null +++ b/app/fg/modals/select-file.js @@ -0,0 +1,673 @@ +/* globals customElements */ +import { LitElement, html, css } from '../vendor/lit-element/lit-element' +import { classMap } from '../vendor/lit-element/lit-html/directives/class-map' +import { joinPath } from '../../lib/strings' +import { createStat } from '../../bg/web-apis/fg/stat' +import * as bg from './bg-process-rpc' +import commonCSS from './common.css' +import inputsCSS from './inputs.css' +import buttonsCSS from './buttons2.css' + +const SHORTCUTS = [ + {url: 'virtual:my-device', title: 'My Device', icon: 'fas fa-laptop'}, + {url: 'virtual:my-drives', title: 'My Drives', icon: 'far fa-hdd'}, + {url: 'virtual:hosting', title: 'Hosting', icon: 'fas fa-share-alt'}, + {url: 'virtual:contacts', title: 'Contacts', icon: 'fas fa-users'}, +] + +class SelectFileModal extends LitElement { + static get properties () { + return { + path: {type: String}, + files: {type: Array}, + selectedPaths: {type: Array} + } + } + + static get styles () { + return [commonCSS, inputsCSS, buttonsCSS, css` + .title { + background: #fff; + border: 0; + padding: 6px; + text-align: center; + font-size: 14px; + font-weight: 500; + } + + .wrapper { + padding: 0 15px 10px; + } + + form { + padding: 0; + margin: 0; + } + + .layout { + display: grid; + grid-gap: 10px; + grid-template-columns: 180px 1fr; + } + + .form-actions { + display: flex; + text-align: left; + } + + .form-actions .left { + flex: 1; + } + + .form-actions .btn.cancel { + margin-right: 5px; + } + + .shortcuts-list { + background: #f5f5fa; + box-sizing: border-box; + padding: 5px 0; + height: 379px; + overflow-y: auto; + } + + .shortcuts-list .shortcut { + padding: 8px 10px; + } + + .shortcuts-list .shortcut.selected { + background: #0031; + font-weight: 500; + } + + .shortcuts-list .shortcut .fa-fw { + margin-right: 5px; + } + + .path { + display: flex; + align-items: center; + padding: 0 0 4px; + } + + .path .fa-fw { + margin-right: 4px; + } + + .path > div { + cursor: pointer; + margin-right: 4px; + max-width: 100px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .path > div:hover { + text-decoration: underline; + } + + .filename { + display: flex; + align-items: center; + margin: 6px 0 10px; + background: #f3f3fa; + border-radius: 4px; + padding-left: 10px; + } + + .filename label { + margin-right: 10px; + font-weight: normal; + } + + .filename input { + flex: 1; + margin: 0; + font-size: 13px; + } + + .files-list { + border-radius: 4px; + height: 378px; + box-sizing: border-box; + overflow-y: scroll; + border: 1px solid #ccc; + user-select: none; + cursor: default; + padding: 4px 0; + margin-bottom: 10px; + } + + .path + .files-list { + height: 358px; + } + + .files-list.grid { + display: grid; + grid-template-columns: repeat(auto-fill, 120px); + grid-template-rows: repeat(auto-fill, 74px); + gap: 15px; + padding: 12px; + } + + .files-list .item { + display: flex; + width: 100%; + align-items: center; + padding: 6px 10px; + } + + .files-list.grid .item { + flex-direction: column; + box-sizing: border-box; + } + + .files-list .item.disabled { + font-style: italic; + color: #aaa; + } + + .files-list .item .fa-fw, + .files-list .item .favicon { + margin-right: 5px; + } + + .files-list.grid .item .fa-fw { + font-size: 28px; + margin: 5px 0 10px; + } + + .files-list.grid .item .favicon { + width: 32px; + height: 32px; + object-fit: cover; + margin: 5px 0 10px; + } + + .files-list .item .fa-folder { + color: #9ec2e0; + } + + .files-list .item .fa-file { + -webkit-text-stroke: 1px #9a9aab; + color: #fff; + } + + .files-list .item .name { + flex: 1; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .files-list .item.selected { + background: #2864dc; + color: #fff; + } + + .files-list .item.selected .fa-fw { + text-shadow: 0 1px 2px #0006; + } + + .drive-changer { + display: flex; + } + + .drive-changer > * { + border-radius: 0 !important; + } + + .drive-changer > *:first-child { + border-top-left-radius: 4px !important; + border-bottom-left-radius: 4px !important; + } + + .drive-changer > *:last-child { + border-top-right-radius: 4px !important; + border-bottom-right-radius: 4px !important; + } + + .drive-changer input { + flex: 0 0 300px; + height: auto; + margin: 0; + border-left: 0; + border-right: 0; + } + `] + } + + get isVirtualListing () { + return this.drive && this.drive.startsWith('virtual:') + } + + get virtualTitle () { + var s = SHORTCUTS.find(s => s.url === this.drive) + return s ? s.title : '' + } + + constructor () { + super() + + // state + this.drives = [] + this.contacts = [] + this.path = '/' + this.files = [] + this.selectedPaths = [] + this.driveInfo = null + + // params + this.saveMode = false + this.drive = null + this.defaultPath = '/' + this.defaultFilename = '' + this.title = '' + this.buttonLabel = '' + this.select = ['file', 'folder', 'mount'] + this.filters = { + extensions: undefined, + writable: undefined, + networked: undefined + } + this.allowMultiple = false + this.disallowCreate = false + this.cbs = null + } + + async init (params, cbs) { + this.cbs = cbs + this.saveMode = params.saveMode + this.drive = params.drive || 'virtual:my-device' + this.path = params.defaultPath || '/' + this.defaultFilename = params.defaultFilename || '' + this.title = params.title || '' + this.buttonLabel = params.buttonLabel || (this.saveMode ? 'Save' : 'Select') + if (params.select) this.select = params.select + if (params.filters) { + if ('extensions' in params.filters) { + this.filters.extensions = params.filters.extensions + } + if ('writable' in params.filters) { + this.filters.writable = params.filters.writable + } + if ('networked' in params.filters) { + this.filters.networked = params.filters.networked + } + } + this.allowMultiple = !this.saveMode && params.allowMultiple + this.disallowCreate = params.disallowCreate + if (!this.title) { + if (this.saveMode) { + this.title = 'Save file...' + } else { + let canSelect = v => this.select.includes(v) + let [file, folder, mount] = [canSelect('file'), canSelect('folder'), canSelect('mount')] + if (file && (folder || mount)) { + this.title = 'Select files or folders' + } else if (file && !(folder || mount)) { + this.title = 'Select files' + } else if (folder) { + this.title = 'Select folders' + } else if (mount) { + this.title = 'Select drives' + } + } + } + + this.driveInfo = !this.isVirtualListing ? await bg.hyperdrive.getInfo(this.drive) : undefined + await this.readdir() + this.updateComplete.then(_ => { + this.adjustHeight() + if (this.saveMode) { + this.filenameInput.value = this.defaultFilename + this.focusInput() + this.requestUpdate() + } + }) + + this.drives = await bg.drives.list() + this.drives.push({url: 'hyper://system/', info: {title: 'System Drive', writable: true}}) + this.drives.sort((a, b) => (a.info.title || '').toLowerCase().localeCompare(b.info.title || '')) + this.contacts = await bg.contacts.list() + this.contacts.push(await bg.hyperdrive.getInfo((await bg.beakerBrowser.getProfile()).key)) + this.contacts.sort((a, b) => (a.title || '').toLowerCase().localeCompare(b.title || '')) + if (this.isVirtualListing) { + this.readvirtual() + } + } + + adjustHeight () { + // adjust height based on rendering + var height = this.shadowRoot.querySelector('div').clientHeight + bg.modals.resizeSelf({height}) + } + + focusInput () { + var el = this.filenameInput + el.focus() + el.selectionStart = el.selectionEnd = 0 + } + + async goto (path) { + this.path = path + await this.readdir() + this.selectedPaths = [] + if (this.saveMode) { + this.filenameInput.value = this.defaultFilename + this.focusInput() + } + } + + async readvirtual () { + const vfile = (url, icon, name) => ({ + isVirtual: true, + path: url, + icon: url.startsWith('hyper:') + ? url === 'hyper://system/' + ? html`` + : html`` + : html``, + name, + stat: {isFile: () => true, isDirectory: () => false}, + }) + switch (this.drive) { + case 'virtual:my-device': + this.files = [ + vfile('virtual:my-drives', 'far fa-hdd', 'My Drives'), + vfile('virtual:hosting', 'fas fa-share-alt', 'Hosting'), + vfile('virtual:contacts', 'fas fa-users', 'Contacts') + ] + break + case 'virtual:my-drives': + this.files = this.drives.filter(d => d.info.writable).map(drive => vfile(drive.url, undefined, drive.info.title)) + break + case 'virtual:hosting': + this.files = this.drives.filter(d => !d.info.writable).map(drive => vfile(drive.url, undefined, drive.info.title)) + break + case 'virtual:contacts': + this.files = this.contacts.map(contact => vfile(contact.url, undefined, contact.title || 'Anonymous')) + break + } + } + + async readdir () { + if (this.isVirtualListing) { + return this.readvirtual() + } + + var files = await bg.hyperdrive.readdir(joinPath(this.drive, this.path), {includeStats: true}) + files.forEach(file => { + file.stat = createStat(file.stat) + file.path = joinPath(this.path, file.name) + }) + files.sort(sortFiles) + this.files = files + } + + getFile (path) { + return this.files.find(f => f.path === path) + } + + canSelectFile (file) { + if (this.isVirtualListing) { + return true // can always select items in virtual lists + } + if (!this.driveInfo) { + return false // probably still loading + } + if (defined(this.filters.networked) && this.filters.networked !== this.driveInfo.networked) { + return false + } + if (defined(this.filters.writable) && this.filters.writable !== this.driveInfo.writable) { + return false + } + if (this.saveMode && !this.driveInfo.writable) { + return false + } + if (file.stat.isFile()) { + if (!this.select.includes('file')) { + return false + } + if (this.filters.extensions) { + let hasExt = this.filters.extensions.some(ext => file.name.endsWith(ext)) + if (!hasExt) { + return false + } + } + return true + } else { + return this.select.includes('folder') || this.select.includes('mount') + } + } + + get filenameInput () { + return this.shadowRoot.querySelector('input') + } + + get hasValidSelection () { + if (this.saveMode) { + if (!this.driveInfo || !this.driveInfo.writable) return false + let inputValue = this.filenameInput && this.filenameInput.value + if (!inputValue) return false + let file = this.getFile(joinPath(this.path, inputValue)) + if (file && file.stat.isDirectory()) return false + return true + } else { + if (this.selectedPaths.length === 0) { + if (this.select.includes('folder')) return true // can select current location + return false + } + if (this.filters.extensions) { + // if there's an extensions requirement, + // folders can still be selected but they're not valid targets + return this.selectedPaths.every(path => this.filters.extensions.some(ext => path.endsWith(ext))) + } + return true + } + } + + // rendering + // = + + render () { + return html` + +
    +
    ${this.title}
    +
    +
    + ${this.saveMode + ? html` +
    + + this.requestUpdate()}> +
    + ` + : ''} + +
    +
    +
    + ${SHORTCUTS.map(shortcut => this.renderShortcut(shortcut))} +
    +
    +
    +
    + ${this.renderPath()} +
    + ${this.renderFilesList()} +
    +
    + +
    +
    +
    + + +
    +
    +
    +
    +
    + ` + } + + renderShortcut (shortcut) { + const cls = classMap({ + shortcut: true, + selected: this.drive === shortcut.url + }) + return html` +
    +
    + + ${shortcut.title} +
    +
    + ` + } + + renderPath () { + var pathParts = this.path.split('/').filter(Boolean) + var pathAcc = [] + var topTitle = this.driveInfo ? (this.driveInfo.title || 'Untitled') : this.virtualTitle + return [html` +
    this.onClickPath(e, '/')}>${topTitle}
    + + `].concat(pathParts.map(part => { + pathAcc.push(part) + var path = '/' + pathAcc.join('/') + return html` +
    this.onClickPath(e, path)}>${part}
    + + ` + })) + } + + renderFilesList () { + return html` +
    + ${this.files.map(file => this.renderFile(file))} +
    + ` + } + + renderFile (file) { + // TODO mounts + var isSelected = this.selectedPaths.includes(file.path) + var disabled = !this.canSelectFile(file) + const cls = classMap({ + item: true, + file: file.stat.isFile(), + folder: file.stat.isDirectory(), + selected: isSelected, + disabled + }) + return html` +
    + ${file.icon ? file.icon : html` + + `} + ${file.name} +
    + ` + } + + // event handlers + // = + + async onSelectShortcut (e) { + e.preventDefault() + e.stopPropagation() + this.drive = e.currentTarget.dataset.url + this.driveInfo = undefined + this.goto('/') + } + + onClickPath (e, path) { + e.preventDefault() + this.goto(path) + } + + onSelectFile (e) { + var path = e.currentTarget.dataset.path + if (this.allowMultiple && (e.ctrlKey || e.metaKey) && !this.isVirtualListing) { + this.selectedPaths = this.selectedPaths.concat([path]) + } else { + this.selectedPaths = [path] + } + if (this.saveMode) { + this.filenameInput.value = path.split('/').pop() + } + } + + onDblClickFile (e) { + e.preventDefault() + var file = this.getFile(e.currentTarget.dataset.path) + if (file.stat.isDirectory()) { + this.goto(file.path) + } else { + this.selectedPaths = [file.path] + this.onSubmit() + } + } + + onClickCancel (e) { + e.preventDefault() + this.cbs.reject(new Error('Canceled')) + } + + async onSubmit (e) { + if (e) e.preventDefault() + + if (this.isVirtualListing) { + this.drive = this.selectedPaths[0] + if (this.drive.startsWith('hyper://')) { + this.driveInfo = await bg.hyperdrive.getInfo(this.drive) + } + return this.goto('/') + } + + const makeSelectionObj = path => ({path, origin: this.drive, url: joinPath(this.drive, path)}) + if (this.saveMode) { + let path = joinPath(this.path, this.filenameInput.value) + if (this.getFile(path)) { + if (!confirm('Overwrite this file?')) { + return + } + } + this.cbs.resolve(makeSelectionObj(path)) + } else { + if (this.select.includes('folder') && this.selectedPaths.length === 0) { + // use current location + this.selectedPaths = [this.path] + } + this.cbs.resolve(this.selectedPaths.map(makeSelectionObj)) + } + } +} + +customElements.define('select-file-modal', SelectFileModal) + +function sortFiles (a, b) { + if (a.stat.isDirectory() && !b.stat.isDirectory()) return -1 + if (!a.stat.isDirectory() && b.stat.isDirectory()) return 1 + return a.name.localeCompare(b.name) +} + +function defined (v) { + return typeof v !== 'undefined' +} \ No newline at end of file diff --git a/app/modals/setup.js b/app/fg/modals/setup.js similarity index 100% rename from app/modals/setup.js rename to app/fg/modals/setup.js diff --git a/app/modals/spinner.css.js b/app/fg/modals/spinner.css.js similarity index 100% rename from app/modals/spinner.css.js rename to app/fg/modals/spinner.css.js diff --git a/app/modals/user.js b/app/fg/modals/user-editor.js similarity index 54% rename from app/modals/user.js rename to app/fg/modals/user-editor.js index fb523dffec..54c225f6fd 100644 --- a/app/modals/user.js +++ b/app/fg/modals/user-editor.js @@ -1,49 +1,90 @@ /* globals customElements */ import { LitElement, html, css } from '../vendor/lit-element/lit-element' import * as bg from './bg-process-rpc' -import slugify from 'slugify' import commonCSS from './common.css' import inputsCSS from './inputs.css' import buttonsCSS from './buttons.css' import defaultUserThumbJpg from '../lib/default-user-thumb.jpg' -const LABEL_REGEX = /[a-z0-9-]/i - -class UserModal extends LitElement { +class UserEditorModal extends LitElement { static get properties () { return { thumbDataURL: {type: String}, thumbExt: {type: String}, - label: {type: String}, title: {type: String}, description: {type: String}, - setDefault: {type: Boolean}, errors: {type: Object} } } + static get styles () { + return [commonCSS, inputsCSS, buttonsCSS, css` + .wrapper { + padding: 0; + } + + h1.title { + font-size: 17px; + padding: 14px 20px; + margin: 0; + border-color: #bbb; + } + + form { + padding: 14px 20px; + margin: 0; + } + + input { + font-size: 14px; + height: 34px; + padding: 0 10px; + border-color: #bbb; + } + + .img-ctrl { + display: flex; + flex-direction: column; + align-items: center; + } + + img { + border-radius: 50%; + object-fit: cover; + width: 130px; + height: 130px; + margin-bottom: 10px; + } + + input[type="file"] { + display: none; + } + + .form-actions { + display: flex; + justify-content: space-between; + align-items: center; + text-align: left; + } + `] + } + constructor () { super() this.cbs = null this.userUrl = '' this.thumbDataURL = undefined this.thumbExt = undefined - this.label = '' this.title = '' this.description = '' - this.setDefault = false - this.isTemporary = false this.errors = {} } async init (params, cbs) { this.cbs = cbs this.userUrl = params.url || '' - this.label = params.label || '' this.title = params.title || '' this.description = params.description || '' - this.setDefault = params.isDefault - this.isTemporary = params.isTemporary if (!this.userUrl) { // use default thumb this.thumbDataURL = defaultUserThumbJpg @@ -73,35 +114,12 @@ class UserModal extends LitElement { ${this.errors.title ? html`
    ${this.errors.title}
    ` : ''} - + ${this.errors.description ? html`
    ${this.errors.description}
    ` : ''} - - - ${this.errors.label ? html`
    ${this.errors.label}
    ` : ''} - -
    -
    - ${this.isTemporary ? html`
    ` : html` - - `} -
    - - -
    + +
    @@ -135,21 +153,12 @@ class UserModal extends LitElement { onChangeTitle (e) { this.title = e.target.value.trim() - this.label = slugify(this.title).toLowerCase() } onChangeDescription (e) { this.description = e.target.value.trim() } - onChangeLabel (e) { - this.label = slugify(e.target.value.trim()) - } - - onToggleSetDefault (e) { - this.setDefault = !this.setDefault - } - onClickCancel (e) { e.preventDefault() this.cbs.reject(new Error('Canceled')) @@ -161,11 +170,6 @@ class UserModal extends LitElement { // validate this.errors = {} if (!this.title) this.errors.title = 'Required' - if (!this.label) this.errors.label = 'Required' - else if (!LABEL_REGEX.test(this.label)) this.errors.label = 'Must be letters, numbers, or dashes (no spaces)' - var existingUsers = await bg.users.list() - var existingLabelUser = existingUsers.find(u => u.label === this.label) - if (existingLabelUser && existingLabelUser.url !== this.userUrl) this.errors.label = 'This label is already in use' if (Object.keys(this.errors).length > 0) { return this.requestUpdate() } @@ -175,52 +179,13 @@ class UserModal extends LitElement { this.cbs.resolve({ title: this.title, description: this.description, - label: this.label, thumbBase64, - thumbExt: this.thumbExt, - setDefault: this.setDefault + thumbExt: this.thumbExt }) } catch (e) { this.cbs.reject(e.message || e.toString()) } } } -UserModal.styles = [commonCSS, inputsCSS, buttonsCSS, css` -.img-ctrl { - display: flex; - flex-direction: column; - align-items: center; -} - -img { - border-radius: 50%; - object-fit: cover; - width: 130px; - height: 130px; - margin-bottom: 10px; -} - -hr { - border: 0; - border-top: 1px solid #ccc; - margin: 20px 0; -} - -input[type="file"] { - display: none; -} - -.toggle .text { - font-size: 13px; - margin-left: 8px; -} - -.form-actions { - display: flex; - justify-content: space-between; - align-items: center; - text-align: left; -} -`] -customElements.define('user-modal', UserModal) \ No newline at end of file +customElements.define('user-editor-modal', UserEditorModal) \ No newline at end of file diff --git a/app/fg/modals/user-select.js b/app/fg/modals/user-select.js new file mode 100644 index 0000000000..6f31fa004a --- /dev/null +++ b/app/fg/modals/user-select.js @@ -0,0 +1,179 @@ +/* globals customElements */ +import { LitElement, html, css } from '../vendor/lit-element/lit-element' +import { classMap } from '../vendor/lit-element/lit-html/directives/class-map' +import { repeat } from '../vendor/lit-element/lit-html/directives/repeat' +import * as bg from './bg-process-rpc' +import commonCSS from './common.css' +import inputsCSS from './inputs.css' +import buttonsCSS from './buttons.css' + +class UserSelectModal extends LitElement { + static get properties () { + return { + selection: {type: String} + } + } + + static get styles () { + return [commonCSS, inputsCSS, buttonsCSS, css` + .wrapper { + padding: 0; + } + + h1.title { + font-size: 17px; + padding: 14px 20px; + border-color: #bbb; + margin: 0; + } + + form { + padding: 0; + margin: 0; + } + + .users { + display: grid; + grid-template-columns: repeat(auto-fill, 150px); + gap: 14px 4px; + padding: 14px 20px; + max-height: 300px; + overflow: auto; + box-sizing: border-box; + border-bottom: 1px solid #bbb; + } + + .user { + text-align: center; + } + + .user .title { + font-size: 15px; + max-width: 150px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .user.selected .title span { + display: inline-block; + padding: 0 4px; + border-radius: 4px; + } + + .user img, + .user .create-img { + border-radius: 50%; + object-fit: cover; + width: 100px; + height: 100px; + margin-bottom: 10px; + box-sizing: border-box; + } + + .user .create-img { + margin: 0 auto 10px; + background: #f3f3f8; + color: #0003; + font-size: 30px; + padding: 36px 0; + } + + .user.selected .title span { + background: #2864dc; + color: #fff; + } + + .form-actions { + display: flex; + justify-content: space-between; + align-items: center; + padding: 14px 20px; + text-align: left; + } + `] + } + + constructor () { + super() + this.cbs = null + this.users = [] + this.selection = undefined + } + + async init (params, cbs) { + this.cbs = cbs + this.users = params.users || [] + this.selection = undefined + await this.requestUpdate() + } + + // rendering + // = + + render () { + return html` + +
    +

    New session

    + +
    +
    + ${repeat(this.users, user => this.renderUser(user))} + ${this.renderCreateUser()} +
    + +
    + + +
    +
    +
    + ` + } + + renderUser (user) { + var selected = this.selection === user.url + return html` +
    { this.selection = user.url }}> + +
    ${user.title}
    +
    + ` + } + + renderCreateUser () { + var selected = this.selection === 'create' + return html` +
    { this.selection = 'create' }}> +
    +
    New User
    +
    + ` + } + + // event handlers + // = + + updated () { + // adjust size based on rendering + var height = this.shadowRoot.querySelector('div').clientHeight + bg.modals.resizeSelf({height}) + } + + onClickCancel (e) { + e.preventDefault() + this.cbs.reject(new Error('Canceled')) + } + + async onSubmit (e) { + e.preventDefault() + if (this.selection === 'create') { + this.cbs.resolve({gotoCreate: true}) + } else { + this.cbs.resolve({url: this.selection}) + } + } +} + +customElements.define('user-select-modal', UserSelectModal) \ No newline at end of file diff --git a/app/fg/perm-prompt/bg-process-rpc.js b/app/fg/perm-prompt/bg-process-rpc.js new file mode 100644 index 0000000000..1425e1169f --- /dev/null +++ b/app/fg/perm-prompt/bg-process-rpc.js @@ -0,0 +1,8 @@ +import * as rpc from 'pauls-electron-rpc' +import browserManifest from '../../bg/web-apis/manifests/internal/browser' +import hyperdriveManifest from '../../bg/web-apis/manifests/external/hyperdrive' +import permPromptManifest from '../../bg/rpc-manifests/perm-prompt' + +export const beakerBrowser = rpc.importAPI('beaker-browser', browserManifest) +export const hyperdrive = rpc.importAPI('hyperdrive', hyperdriveManifest) +export const permPrompt = rpc.importAPI('background-process-perm-prompt', permPromptManifest) \ No newline at end of file diff --git a/app/perm-prompt/buttons.css.js b/app/fg/perm-prompt/buttons.css.js similarity index 100% rename from app/perm-prompt/buttons.css.js rename to app/fg/perm-prompt/buttons.css.js diff --git a/app/perm-prompt.html b/app/fg/perm-prompt/index.html similarity index 100% rename from app/perm-prompt.html rename to app/fg/perm-prompt/index.html diff --git a/app/perm-prompt.js b/app/fg/perm-prompt/index.js similarity index 84% rename from app/perm-prompt.js rename to app/fg/perm-prompt/index.js index 9f569dd23d..25f790980c 100644 --- a/app/perm-prompt.js +++ b/app/fg/perm-prompt/index.js @@ -1,11 +1,11 @@ /* globals customElements */ -import { PERMS, PERM_ICONS, renderPermDesc, getPermId, getPermParam } from '@beaker/permissions' -import { LitElement, html, css } from './vendor/lit-element/lit-element' +import { PERMS, PERM_ICONS, renderPermDesc, getPermId, getPermParam } from '../../lib/permissions' +import { LitElement, html, css } from '../vendor/lit-element/lit-element' import prettyHash from 'pretty-hash' -import * as bg from './perm-prompt/bg-process-rpc' -import buttonsCSS from './perm-prompt/buttons.css' +import * as bg from './bg-process-rpc' +import buttonsCSS from './buttons.css' -const IS_DAT_KEY_REGEX = /^[0-9a-f]{64}$/i +const IS_DRIVE_KEY_REGEX = /^[0-9a-f]{64}$/i class PermPrompt extends LitElement { constructor () { @@ -23,8 +23,12 @@ class PermPrompt extends LitElement { window.clickAccept = () => this.shadowRoot.querySelector('.prompt-accept').click() window.clickReject = () => this.shadowRoot.querySelector('.prompt-reject').click() + this.fetchBrowserInfo() + } + + async fetchBrowserInfo () { // fetch platform information - var {platform} = bg.beakerBrowser.getInfo() + var {platform} = await bg.beakerBrowser.getInfo() window.platform = platform if (platform === 'darwin') { document.body.classList.add('darwin') @@ -47,12 +51,12 @@ class PermPrompt extends LitElement { this.isPermExperimental = PERM.experimental // fetch dat title if needed - if (!this.permOpts.title && IS_DAT_KEY_REGEX.test(this.permParam)) { - let archiveKey = this.permParam - let archiveInfo - try { archiveInfo = await bg.datArchive.getInfo(archiveKey) } + if (!this.permOpts.title && IS_DRIVE_KEY_REGEX.test(this.permParam)) { + let driveKey = this.permParam + let driveInfo + try { driveInfo = await bg.hyperdrive.getInfo(driveKey) } catch (e) { /* ignore */ } - this.permOpts.title = archiveInfo && archiveInfo.title ? archiveInfo.title : prettyHash(this.permParam) + this.permOpts.title = driveInfo && driveInfo.title ? driveInfo.title : prettyHash(this.permParam) } // create the prompt diff --git a/app/fg/prompts/bg-process-rpc.js b/app/fg/prompts/bg-process-rpc.js new file mode 100644 index 0000000000..487b16fdf9 --- /dev/null +++ b/app/fg/prompts/bg-process-rpc.js @@ -0,0 +1,6 @@ +import * as rpc from 'pauls-electron-rpc' +import promptsManifest from '../../bg/rpc-manifests/prompts' +import hyperdriveManifest from '../../bg/web-apis/manifests/external/hyperdrive' + +export const prompts = rpc.importAPI('background-process-prompts', promptsManifest) +export const hyperdrive = rpc.importAPI('hyperdrive', hyperdriveManifest) \ No newline at end of file diff --git a/app/prompts/buttons.css.js b/app/fg/prompts/buttons.css.js similarity index 100% rename from app/prompts/buttons.css.js rename to app/fg/prompts/buttons.css.js diff --git a/app/prompts/create-page.js b/app/fg/prompts/create-page.js similarity index 73% rename from app/prompts/create-page.js rename to app/fg/prompts/create-page.js index 863f1328c2..09721cc530 100644 --- a/app/prompts/create-page.js +++ b/app/fg/prompts/create-page.js @@ -3,6 +3,7 @@ import { LitElement, html, css } from '../vendor/lit-element/lit-element' import _get from 'lodash.get' import buttonsCSS from './buttons.css' import * as bg from './bg-process-rpc' +import { joinPath } from '../../lib/strings' class CreatePagePrompt extends LitElement { static get properties () { @@ -29,11 +30,10 @@ class CreatePagePrompt extends LitElement { } async init (params) { - this.url = params.url + this.url = params.url await this.requestUpdate() } - // rendering // = @@ -60,29 +60,19 @@ class CreatePagePrompt extends LitElement { if (!path || path.endsWith('/')) { path = `${path}index.${ext}` } else if (path.endsWith(`.${ext}`)) { - path = path + // path = path (noop) } else if (/.(md|html)$/i.test(path)) { path = `${path.replace(/.(md|html)$/i, '')}.${ext}` } else { path = `${path}.${ext}` } - // ensure the parent directory exists - var pathParts = path.split('/').filter(Boolean).slice(0, -1) - var pathAgg = [] - for (let pathPart of pathParts) { - try { - pathAgg.push(pathPart) - await bg.datArchive.mkdir(urlp.hostname, pathAgg.join('/')) - } catch (e) { - // ignore, dir already exists (probably) - } - } - // create the file - await bg.datArchive.writeFile(urlp.hostname, path, '') - bg.prompts.openSidebar('editor') - bg.prompts.loadURL(`${urlp.origin}${path}`) + await bg.hyperdrive.writeFile(joinPath(urlp.hostname, path), '') + let newUrl = joinPath(urlp.origin, path) + bg.prompts.executeSidebarCommand('show-panel', 'editor-app') + bg.prompts.executeSidebarCommand('set-context', 'editor-app', newUrl) + bg.prompts.loadURL(newUrl) } } diff --git a/app/fg/prompts/edit-profile.js b/app/fg/prompts/edit-profile.js new file mode 100644 index 0000000000..76ee36fd3a --- /dev/null +++ b/app/fg/prompts/edit-profile.js @@ -0,0 +1,63 @@ +/* globals customElements */ +import { LitElement, html, css } from '../vendor/lit-element/lit-element' +import _get from 'lodash.get' +import buttonsCSS from './buttons.css' +import * as bg from './bg-process-rpc' +import { joinPath } from '../../lib/strings' + +class EditProfilePrompt extends LitElement { + static get properties () { + return { + url: {type: String} + } + } + + static get styles () { + return [buttonsCSS, css` + .wrapper { + overflow: hidden; + padding: 10px 16px; + } + button { + margin-left: 10px; + } + `] + } + + constructor () { + super() + this.reset() + } + + reset () { + } + + async init (params) { + this.url = params.url + await this.requestUpdate() + } + + // rendering + // = + + render () { + return html` + +
    + This is your profile! You can customize it with the editor. + +
    + ` + } + + // events + // = + + async onClickEdit () { + await bg.prompts.executeSidebarCommand('show-panel', 'editor-app') + await bg.prompts.executeSidebarCommand('set-context', 'editor-app', this.url) + await bg.prompts.closeEditProfilePromptForever() + } +} + +customElements.define('edit-profile-prompt', EditProfilePrompt) diff --git a/app/prompts.html b/app/fg/prompts/index.html similarity index 100% rename from app/prompts.html rename to app/fg/prompts/index.html diff --git a/app/prompts.js b/app/fg/prompts/index.js similarity index 85% rename from app/prompts.js rename to app/fg/prompts/index.js index 426a5b1454..72f68ab867 100644 --- a/app/prompts.js +++ b/app/fg/prompts/index.js @@ -1,6 +1,7 @@ /* globals customElements */ -import { LitElement, html } from './vendor/lit-element/lit-element' -import './prompts/create-page' +import { LitElement, html } from '../vendor/lit-element/lit-element' +import './create-page' +import './edit-profile' class PromptsWrapper extends LitElement { static get properties () { @@ -41,6 +42,8 @@ class PromptsWrapper extends LitElement { switch (this.currentPrompt) { case 'create-page': return html`` + case 'edit-profile': + return html`` } return html`
    ` } diff --git a/app/fg/shell-menus/bg-process-rpc.js b/app/fg/shell-menus/bg-process-rpc.js new file mode 100644 index 0000000000..226d21ffb3 --- /dev/null +++ b/app/fg/shell-menus/bg-process-rpc.js @@ -0,0 +1,24 @@ +import * as rpc from 'pauls-electron-rpc' +import browserManifest from '../../bg/web-apis/manifests/internal/browser' +import drivesManifest from '../../bg/web-apis/manifests/internal/drives' +import bookmarksManifest from '../../bg/web-apis/manifests/internal/bookmarks' +import historyManifest from '../../bg/web-apis/manifests/internal/history' +import sitedataManifest from '../../bg/web-apis/manifests/internal/sitedata' +import downloadsManifest from '../../bg/web-apis/manifests/internal/downloads' +import hyperdriveManifest from '../../bg/web-apis/manifests/external/hyperdrive' +import shellManifest from '../../bg/web-apis/manifests/external/shell' +import beakerFsManifest from '../../bg/web-apis/manifests/internal/beaker-filesystem' +import shellMenusManifest from '../../bg/rpc-manifests/shell-menus' +import viewsManifest from '../../bg/rpc-manifests/views' + +export const beakerBrowser = rpc.importAPI('beaker-browser', browserManifest) +export const drives = rpc.importAPI('drives', drivesManifest) +export const bookmarks = rpc.importAPI('bookmarks', bookmarksManifest) +export const history = rpc.importAPI('history', historyManifest) +export const sitedata = rpc.importAPI('sitedata', sitedataManifest) +export const downloads = rpc.importAPI('downloads', downloadsManifest) +export const hyperdrive = rpc.importAPI('hyperdrive', hyperdriveManifest) +export const shell = rpc.importAPI('shell', shellManifest) +export const beakerFs = rpc.importAPI('beaker-filesystem', beakerFsManifest) +export const shellMenus = rpc.importAPI('background-process-shell-menus', shellMenusManifest) +export const views = rpc.importAPI('background-process-views', viewsManifest) \ No newline at end of file diff --git a/app/fg/shell-menus/bookmark.js b/app/fg/shell-menus/bookmark.js new file mode 100644 index 0000000000..dea136b3a5 --- /dev/null +++ b/app/fg/shell-menus/bookmark.js @@ -0,0 +1,192 @@ +/* globals customElements */ +import { LitElement, html, css } from '../vendor/lit-element/lit-element' +import * as bg from './bg-process-rpc' +import commonCSS from './common.css' +import inputsCSS from './inputs.css' +import buttonsCSS from './buttons.css' + +class BookmarkMenu extends LitElement { + static get properties () { + return { + href: {type: String}, + title: {type: String}, + pinned: {type: Boolean} + } + } + + constructor () { + super() + this.reset() + } + + reset () { + this.bookmark = null + this.href = '' + this.title = '' + this.pinned = false + this.existingBookmark = undefined + } + + async init (params) { + this.existingBookmark = await bg.bookmarks.get(params.url) + if (this.existingBookmark) { + this.href = this.existingBookmark.href || params.url + this.title = this.existingBookmark.title || params.metadata.title || '' + this.pinned = this.existingBookmark.pinned + } else { + this.href = params.url + this.title = params.metadata.title || '' + } + await this.requestUpdate() + + // focus and highlight input + var input = this.shadowRoot.querySelector('input[type=text]') + input.focus() + input.setSelectionRange(0, input.value.length) + } + + // rendering + // = + + render () { + return html` + +
    +
    +
    + + +
    + +
    + + +
    + +
    + +
    + +
    + + +
    +
    +
    + ` + } + + // events + // = + + async onSaveBookmark (e) { + e.preventDefault() + await bg.bookmarks.add({ + href: this.href, + title: this.title, + pinned: this.pinned + }) + bg.views.refreshState('active') + bg.shellMenus.close() + } + + async onClickCancel (e) { + e.preventDefault() + if (this.existingBookmark) { + await bg.bookmarks.remove(this.href) + bg.views.refreshState('active') + } + bg.shellMenus.close() + } + + onChangeHref (e) { + this.href = e.target.value + } + + onChangeTitle (e) { + this.title = e.target.value + } + + onChangePinned (e) { + this.pinned = !this.pinned + } +} +BookmarkMenu.styles = [commonCSS, inputsCSS, buttonsCSS, css` +.wrapper { + box-sizing: border-box; + padding: 15px 15px 0; + color: #333; + background: #fff; + height: 200px; + overflow: hidden; +} + +form { + font-size: 13px; + margin: 0; +} + +.input-group { + display: flex; + flex-direction: column; + margin-bottom: 10px; +} + +.input-group label { + display: block; + font-size: 12px; + margin-bottom: 2px; +} + +.input-group input, +.input-group textarea { + display: inline-block; + font-size: 0.725rem; +} + +.input-group textarea { + height: 50px; + padding-top: 5px; + resize: none; +} + +.input-group input[type=text] { + height: 28px; + line-height: 28px; + color: rgba(0, 0, 0, 0.75); +} + +.input-group input[type=checkbox] { + display: inline; + width: auto; + height: auto; + margin: 0 5px; +} + +.input-group.public { + margin: 14px 0; +} + +.buttons { + display: flex; + justify-content: flex-end; + padding: 0; + margin: 15px -20px 0; +} + +.buttons button { + height: 40px; + flex: 1; + text-align: center; + border-radius: 0; +} +`] + +customElements.define('bookmark-menu', BookmarkMenu) diff --git a/app/fg/shell-menus/browser.js b/app/fg/shell-menus/browser.js new file mode 100644 index 0000000000..da81d70d1a --- /dev/null +++ b/app/fg/shell-menus/browser.js @@ -0,0 +1,402 @@ +/* globals customElements */ +import { LitElement, html, css } from '../vendor/lit-element/lit-element' +import { repeat } from '../vendor/lit-element/lit-html/directives/repeat' +import * as bg from './bg-process-rpc' +import commonCSS from './common.css' + +class BrowserMenu extends LitElement { + static get properties () { + return { + submenu: {type: String} + } + } + + constructor () { + super() + this.submenu = '' + this.isDarwin = false + this.windowMenuItems = undefined + this.bookmarks = [] + } + + reset () { + this.submenu = '' + } + + async init () { + await this.requestUpdate() + let [browserInfo, menuItems, bookmarks] = await Promise.all([ + bg.beakerBrowser.getInfo(), + bg.shellMenus.getWindowMenu(), + bg.bookmarks.list({sortBy: 'title'}) + ]) + bookmarks.sort((a, b) => (a.title || '').toLowerCase().localeCompare((b.title || '').toLowerCase())) + this.browserInfo = browserInfo + this.isDarwin = browserInfo.platform === 'darwin' + this.windowMenuItems = menuItems + this.bookmarks = bookmarks + await this.requestUpdate() + } + + render () { + if (this.submenu === 'bookmarks') { + return this.renderBookmarks() + } else if (this.submenu === 'library') { + return this.renderLibrary() + } else if (this.submenu) { + return this.renderWindowMenu(this.submenu) + } + + // auto-updater + var autoUpdaterEl = html`` + if (this.browserInfo && this.browserInfo.updater.isBrowserUpdatesSupported && this.browserInfo.updater.state === 'downloaded') { + autoUpdaterEl = html` +
    + +
    + ` + } + + return html` + +
    + ${autoUpdaterEl} + +
    + + + + + ${''/**/} +
    + +
    + + + + + +
    + +
    + + + +
    +
    + ` + } + + renderBookmarks () { + return html` + +
    +
    + +

    Bookmarks

    +
    + +
    + ${repeat(this.bookmarks, b => b.href, b => html` + + `)} +
    +
    ` + } + + renderLibrary () { + return html` + +
    +
    + +

    My Library

    +
    + +
    + + + + + + + + + + + +
    + +
    + +
    +
    ` + } + + renderWindowMenu (menu) { + var items = this.windowMenuItems[menu] + if (!items) return html`` + return html` + +
    +
    + +

    ${menu}

    +
    +
    + ${repeat(items, (item, i) => item.id || i, item => item.separator + ? html`
    ` + : html` + + ` + )} +
    +
    ` + } + + renderAccelerator (accel) { + if (!accel) return + const command = '⌘' + const control = '^' + const commandOrControl = this.isDarwin ? command : control + return accel + .replace(/\+/g, '') + .replace('CmdOrCtrl', commandOrControl) + .replace('Alt', '⌥') + .replace('Cmd', command) + .replace('Ctrl', control) + .replace('Shift', '⇧') + .replace('Plus', '+') + .replace('Left', '←') + .replace('Right', '→') + .replace('`', '~') + } + + // events + // = + + updated () { + // adjust dimensions based on rendering + var width = this.shadowRoot.querySelector('div').clientWidth + var height = this.shadowRoot.querySelector('div').clientHeight + bg.shellMenus.resizeSelf({width, height}) + } + + onShowSubmenu (v) { + this.submenu = v + } + + onOpenNewTab () { + bg.shellMenus.createTab() + bg.shellMenus.close() + } + + onClickMenuItem (menu, id) { + return async (e) => { + bg.shellMenus.triggerWindowMenuItemById(menu, id) + bg.shellMenus.close() + } + } + + async onNewHyperdrive () { + bg.shellMenus.close() + const url = await bg.hyperdrive.createDrive() + bg.beakerBrowser.openUrl(url, {setActive: true}) + } + + async onNewHyperdriveFromFolder (e) { + bg.shellMenus.close() + + var folder = await bg.beakerBrowser.showOpenDialog({ + title: 'Select folder', + buttonLabel: 'Use folder', + properties: ['openDirectory'] + }) + if (!folder || !folder.length) return + + var url = await bg.hyperdrive.createDrive({ + title: folder[0].split('/').pop(), + prompt: false + }) + await bg.hyperdrive.importFromFilesystem({src: folder[0], dst: url}) + + bg.beakerBrowser.openUrl(url, {setActive: true}) + } + + onClickDownloads (e) { + this.shouldPersistDownloadsIndicator = false + bg.shellMenus.createTab('beaker://library/downloads') + bg.shellMenus.close() + } + + onOpenPage (e, url) { + bg.shellMenus.createTab(url) + bg.shellMenus.close() + } + + onClickRestart () { + bg.shellMenus.close() + bg.beakerBrowser.restartBrowser() + } +} +BrowserMenu.styles = [commonCSS, css` +.wrapper { + width: 230px; +} + +.wrapper::-webkit-scrollbar { + display: none; +} + +.section:last-child { + border-bottom: 0; +} + +.section.auto-updater { + padding-bottom: 0; + border-bottom: 0; +} + +.section.gray { + padding: 2px 0; + background: #f5f5fa; +} + +.section.gray .menu-item:hover { + background: #e5e5ee; +} + +.section.scrollable { + max-height: 400px; + overflow-y: auto; +} + +.menu-item-group { + display: flex; +} + +.menu-item-group > .menu-item:first-child { + padding-right: 8px; +} + +.menu-item-group > .menu-item:last-child { + padding-left: 8px; +} + +.menu-item-group > .menu-item .shortcut { + padding-left: 10px; +} + +.menu-item { + height: 32px; +} + +.menu-item[disabled] { + color: #99a; +} + +.menu-item[disabled]:hover { + background: none; +} + +.menu-item.auto-updater { + height: 35px; + background: #DCEDC8; + border-top: 1px solid #c5e1a5; + border-bottom: 1px solid #c5e1a5; + color: #000; +} + +.menu-item.auto-updater i { + color: #7CB342; +} + +.menu-item.auto-updater:hover { + background: #d0e7b5; +} + +.menu-item i.more { + margin-left: auto; + padding-right: 0; + text-align: right; +} + +.menu-item .more, +.menu-item .shortcut { + color: #777; + margin-left: auto; +} + +.menu-item .shortcut { + font-size: 12px; + -webkit-font-smoothing: antialiased; +} +`] + +customElements.define('browser-menu', BrowserMenu) \ No newline at end of file diff --git a/app/shell-menus/buttons.css.js b/app/fg/shell-menus/buttons.css.js similarity index 100% rename from app/shell-menus/buttons.css.js rename to app/fg/shell-menus/buttons.css.js diff --git a/app/modals/buttons2.css.js b/app/fg/shell-menus/buttons2.css.js similarity index 100% rename from app/modals/buttons2.css.js rename to app/fg/shell-menus/buttons2.css.js diff --git a/app/shell-menus/common.css.js b/app/fg/shell-menus/common.css.js similarity index 77% rename from app/shell-menus/common.css.js rename to app/fg/shell-menus/common.css.js index 6a52e54345..913d9e5453 100644 --- a/app/shell-menus/common.css.js +++ b/app/fg/shell-menus/common.css.js @@ -20,10 +20,13 @@ hr { height: 25px; padding: 0 15px; cursor: default; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .menu-item:hover { - background: #eee; + background: #ededf2; } .menu-item i { @@ -35,6 +38,13 @@ hr { margin-left: -5px; } +.menu-item .favicon { + flex: 0 0 16px; + width: 16px; + height: 16px; + margin-right: 10px; +} + .menu-item.disabled { opacity: 0.5; } @@ -43,6 +53,12 @@ hr { background: none; } +.menu-item .label { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + .menu-item i.more { margin-left: auto; padding-right: 0; @@ -56,11 +72,13 @@ hr { height: 35px; line-height: 35px; text-align: center; + border-bottom: 1px solid #ccc; } .header h2 { font-size: 12.5px; margin: 0; + padding-right: 10px; } .header .btn { diff --git a/app/shell-menus/create.js b/app/fg/shell-menus/create.js similarity index 91% rename from app/shell-menus/create.js rename to app/fg/shell-menus/create.js index e1bacc9d36..de09ae2853 100644 --- a/app/shell-menus/create.js +++ b/app/fg/shell-menus/create.js @@ -11,11 +11,11 @@ class CreateMenu extends LitElement { } reset () { - this.datInfo = null + this.driveInfo = null } async init (params) { - this.datInfo = (await bg.views.getTabState('active', {datInfo: true})).datInfo + this.driveInfo = (await bg.views.getTabState('active', {driveInfo: true})).driveInfo await this.requestUpdate() } diff --git a/app/shell-menus/donate.js b/app/fg/shell-menus/donate.js similarity index 89% rename from app/shell-menus/donate.js rename to app/fg/shell-menus/donate.js index 14a93f54f3..7ce2b02ad7 100644 --- a/app/shell-menus/donate.js +++ b/app/fg/shell-menus/donate.js @@ -17,12 +17,12 @@ class DonateMenu extends LitElement { } reset () { - this.datInfo = null + this.driveInfo = null } async init (params) { this.url = params.url - this.datInfo = (await bg.views.getTabState('active', {datInfo: true})).datInfo + this.driveInfo = (await bg.views.getTabState('active', {driveInfo: true})).driveInfo await this.requestUpdate() } @@ -44,8 +44,8 @@ class DonateMenu extends LitElement { } render () { - var title = _get(this, 'datInfo.title', 'this site') - const paymentLink = String(_get(this, 'datInfo.links.payment.0.href')) + var title = _get(this, 'driveInfo.title', 'this site') + const paymentLink = String(_get(this, 'driveInfo.links.payment.0.href')) return html` diff --git a/app/shell-menus/hoverable.js b/app/fg/shell-menus/hoverable.js similarity index 100% rename from app/shell-menus/hoverable.js rename to app/fg/shell-menus/hoverable.js diff --git a/app/shell-menus.html b/app/fg/shell-menus/index.html similarity index 91% rename from app/shell-menus.html rename to app/fg/shell-menus/index.html index eb49014988..b1d6880b03 100644 --- a/app/shell-menus.html +++ b/app/fg/shell-menus/index.html @@ -17,8 +17,8 @@ display: block; background: #fff; border: 1px solid #bbb; - border-radius: 4px; - box-shadow: 0 2px 4px rgba(0,0,0,.4); + border-radius: 0; + box-shadow: 0 2px 3px rgba(0,0,0,.1); margin: 0 10px 10px; overflow: hidden; } diff --git a/app/fg/shell-menus/index.js b/app/fg/shell-menus/index.js new file mode 100644 index 0000000000..ce2c4d3fe8 --- /dev/null +++ b/app/fg/shell-menus/index.js @@ -0,0 +1,131 @@ +/* globals customElements */ +import _debounce from 'lodash.debounce' +import { ipcRenderer } from 'electron' +import { LitElement, html } from '../vendor/lit-element/lit-element' +import * as bg from './bg-process-rpc' +import './browser' +import './toolbar' +import './bookmark' +import './donate' +import './network' +import './peers' +import './share' +import './site' + +class MenusWrapper extends LitElement { + static get properties () { + return { + currentMenu: {type: String} + } + } + + constructor () { + super() + this.currentParams = null + this.setup() + } + + async setup () { + // export interface + const reset = (name) => { + if (!name.endsWith('-menu')) name += '-menu' + try { this.shadowRoot.querySelector(name).reset() } + catch (e) { /* ignore */ } + } + const init = (name) => { + try { return this.shadowRoot.querySelector(name).init(this.currentParams) } + catch (e) { console.log(e) /* ignore */ } + } + window.openMenu = async (v, params) => { + this.currentMenu = v + this.currentParams = params + reset(`${v}-menu`) + await this.updateComplete + await init(`${v}-menu`) + } + window.updateMenu = async (params) => { + this.currentParams = Object.assign(this.currentParams || {}, params) + try { return this.shadowRoot.querySelector(`${this.currentMenu}-menu`).updateMenu(this.currentParams) } + catch (e) { /* ignore */ } + } + window.reset = reset + + // global event listeners + window.addEventListener('blur', e => { + try { + // check if any menu needs to stay open + if (this.shadowRoot.querySelector('[active-menu]').hasAttribute('stay-open')) { + return + } + } catch (e) { + // ignore + } + + bg.shellMenus.close() + + // reset any active state + reset(`${this.currentMenu}-menu`) + + // unset the menu so that we can unrender the current + // (this stops a FOUC issue) + this.currentMenu = null + }) + window.addEventListener('keydown', e => { + if (e.key === 'Escape') { + bg.shellMenus.close() + } + }) + + // fetch platform information + var browserInfo = await bg.beakerBrowser.getInfo() + window.platform = browserInfo.platform + if (browserInfo.platform === 'darwin') { + document.body.classList.add('darwin') + } + if (browserInfo.platform === 'win32') { + document.body.classList.add('win32') + } + } + + render () { + return html`
    ${this.renderMenu()}
    ` + } + + renderMenu () { + switch (this.currentMenu) { + case 'browser': + return html`` + case 'toolbar': + return html`` + case 'bookmark': + return html`` + case 'donate': + return html`` + case 'network': + return html`` + case 'peers': + return html`` + case 'share': + return html`` + case 'site': + return html`` + } + return html`
    ` + } + + onContextMenu (e) { + e.preventDefault() // disable context menu + } +} + +customElements.define('menus-wrapper', MenusWrapper) + +// HACK +// Electron has an issue where browserviews fail to calculate click regions after a resize +// https://github.com/electron/electron/issues/14038 +// we can solve this by forcing a recalculation after every resize +// -prf + +const forceUpdateDragRegions = _debounce(() => ipcRenderer.send('resize-hackfix'), 100, {leading: true}) +window.addEventListener('resize', forceUpdateDragRegions) +document.addEventListener('DOMContentLoaded', forceUpdateDragRegions) diff --git a/app/shell-menus/inputs.css.js b/app/fg/shell-menus/inputs.css.js similarity index 100% rename from app/shell-menus/inputs.css.js rename to app/fg/shell-menus/inputs.css.js diff --git a/app/fg/shell-menus/network.js b/app/fg/shell-menus/network.js new file mode 100644 index 0000000000..04ee44c87c --- /dev/null +++ b/app/fg/shell-menus/network.js @@ -0,0 +1,138 @@ +/* globals customElements */ +import { LitElement, html, css } from '../vendor/lit-element/lit-element' +import _get from 'lodash.get' +import * as bg from './bg-process-rpc' +import inputsCSS from './inputs.css' + +class NetworkMenu extends LitElement { + static get properties () { + return { + daemonStatus: {type: Object} + } + } + + constructor () { + super() + this.reset() + } + + reset () { + this.daemonStatus = undefined + } + + async init (params) { + this.daemonStatus = await bg.beakerBrowser.getDaemonStatus() + } + + // rendering + // = + + render () { + if (!this.daemonStatus) return html`
    ` + return html` + +
    +
    +

    + Network Status +

    +
    + +
    + + + + + + +
    Hole-punchable: + ${this.daemonStatus.holepunchable + ? html` Yes` + : html` No` + } +
    Remote Address: ${this.daemonStatus.remoteAddress || 'Unknown'}
    + ${!this.daemonStatus.holepunchable ? html` + + ` : ''} +
    +
    + ` + } + + // events + // = + + updated () { + // adjust dimensions based on rendering + var width = this.shadowRoot.querySelector('div').clientWidth + var height = this.shadowRoot.querySelector('div').clientHeight + bg.shellMenus.resizeSelf({width, height}) + } + + onClickLink (e) { + e.preventDefault() + bg.beakerBrowser.openUrl(e.currentTarget.getAttribute('href'), {setActive: true}) + } +} +NetworkMenu.styles = [inputsCSS, css` +.wrapper { + color: #333; + width: 300px; +} + +.header { + font-size: 12px; + font-weight: 500; + padding: 10px; + border-bottom: 1px solid #dde; +} + +h1.page-title { + font-size: 0.825rem; + font-weight: 500; + margin: 0; +} + +.body { + padding: 8px; + user-select: text; +} + +table { + font-size: 12px; + white-space: nowrap; +} + +table tr td:first-child { + font-weight: 500; + padding-right: 5px; +} + +table tr td:last-child { + font-family: monospace; + letter-spacing: 0.5px; +} + +.fa-exclamation-triangle { + color: #FF8F00; +} + +.help { + padding: 2px 3px 0; +} + +.help a { + text-decoration: none; + color: gray; +} + +.help a:hover { + text-decoration: underline; +} +`] + +customElements.define('network-menu', NetworkMenu) diff --git a/app/shell-menus/peers.js b/app/fg/shell-menus/peers.js similarity index 58% rename from app/shell-menus/peers.js rename to app/fg/shell-menus/peers.js index 1675972ddb..9096ec4b72 100644 --- a/app/shell-menus/peers.js +++ b/app/fg/shell-menus/peers.js @@ -2,12 +2,11 @@ import { LitElement, html, css } from '../vendor/lit-element/lit-element' import prettyBytes from 'pretty-bytes' import _get from 'lodash.get' -import {pluralize} from '../lib/strings' +import {pluralize} from '../../lib/strings' import * as bg from './bg-process-rpc' import inputsCSS from './inputs.css' const NETWORK_STATS_POLL_INTERVAL = 500 // ms -const HELP_DOCS_URL = 'https://beakerbrowser.com/docs/how-beaker-works/peer-to-peer-websites' class PeersMenu extends LitElement { static get properties () { @@ -18,12 +17,14 @@ class PeersMenu extends LitElement { constructor () { super() - this.pollInterval = null + this.pollInterval = undefined this.reset() } reset () { - this.datInfo = null + this.driveInfo = undefined + this.driveCfg = undefined + this.peers = [] if (this.pollInterval) { clearInterval(this.pollInterval) } @@ -31,72 +32,81 @@ class PeersMenu extends LitElement { async init (params) { this.url = params.url - this.datInfo = (await bg.views.getTabState('active', {datInfo: true})).datInfo - await this.requestUpdate() + this.driveInfo = (await bg.views.getTabState('active', {driveInfo: true})).driveInfo + this.driveCfg = await bg.drives.get(this.url) + const getPeers = async () => { + var state = await bg.views.getNetworkState('active', {includeAddresses: true}) + this.peers = state ? state.peerAddresses : 0 + return this.requestUpdate() + } + await getPeers() // periodically fetch updates - this.pollInterval = setInterval(async () => { - this.datInfo.networkStats = await bg.views.getTabState('active', {networkStats: true}) - this.requestUpdate() - }, NETWORK_STATS_POLL_INTERVAL) + this.pollInterval = setInterval(getPeers, NETWORK_STATS_POLL_INTERVAL) // adjust height based on rendering + var width = this.shadowRoot.querySelector('div').clientWidth var height = this.shadowRoot.querySelector('div').clientHeight - bg.shellMenus.resizeSelf({height}) + bg.shellMenus.resizeSelf({width, height}) } // rendering // = render () { - var isOwner = _get(this, 'datInfo.isOwner', false) - var isSaved = _get(this, 'datInfo.userSettings.isSaved', false) - var peers = _get(this, 'datInfo.peers', 0) - var downloadTotal = _get(this, 'datInfo.networkStats.downloadTotal', 0) - var uploadTotal = _get(this, 'datInfo.networkStats.uploadTotal', 0) + var writable = _get(this, 'driveInfo.writable', false) + var isSaved = _get(this, 'driveCfg.saved', false) + var peers = this.peers + // var downloadTotal = _get(this, 'driveInfo.networkStats.downloadTotal', 0) + // var uploadTotal = _get(this, 'driveInfo.networkStats.uploadTotal', 0) return html`
    - +

    - ${_get(this, 'datInfo.title', html`Untitled`)} + ${_get(this, 'driveInfo.title', html`Untitled`)}

    - ${peers} ${pluralize(peers, 'peer')} seeding these files. - this.onOpenPage(HELP_DOCS_URL)}>Learn more. + ${peers.length} ${pluralize(peers.length, 'peer')} connected.
    -
    + ${''/*
    ${prettyBytes(downloadTotal)}
    ${prettyBytes(uploadTotal)}
    -
    +
    */}
    - ${isOwner + ${writable ? '' : html` `} -
    + ${peers.length > 0 ? html` +
    + ${peers.map(p => html`
    ${p}
    `)} +
    + ` : ''} + + ${''/*
    ` @@ -110,9 +120,14 @@ class PeersMenu extends LitElement { bg.shellMenus.close() } - async onToggleSeeding () { - this.datInfo.userSettings.isSaved = !this.datInfo.userSettings.isSaved - await bg.archives.setUserSettings(this.datInfo.key, {isSaved: this.datInfo.userSettings.isSaved}) + async onToggleHosting () { + if (!this.driveCfg || !this.driveCfg.saved) { + this.driveCfg = {saved: true} + await bg.drives.configure(this.url) + } else { + this.driveCfg = {saved: false} + await bg.drives.remove(this.url) + } bg.views.refreshState('active') } } @@ -194,6 +209,16 @@ h1.page-title { .network-url:hover { text-decoration: underline; } + +.addresses { + padding: 5px 10px; + color: #707070; + line-height: 1.6; + font-family: monospace; + font-size: 0.9em; + overflow: auto; + max-height: 100px; +} `] customElements.define('peers-menu', PeersMenu) diff --git a/app/fg/shell-menus/share.js b/app/fg/shell-menus/share.js new file mode 100644 index 0000000000..b617f76811 --- /dev/null +++ b/app/fg/shell-menus/share.js @@ -0,0 +1,164 @@ +/* globals customElements */ +import { LitElement, html, css } from '../vendor/lit-element/lit-element' +import * as bg from './bg-process-rpc' +import commonCSS from './common.css' +import inputsCSS from './inputs.css' +import buttonsCSS from './buttons2.css' +import { joinPath } from '../../lib/strings' + +class ShareMenu extends LitElement { + static get properties () { + return { + hasCopied: {type: Boolean} + } + } + + static get styles () { + return [commonCSS, inputsCSS, buttonsCSS, css` + .wrapper { + position: relative; + box-sizing: border-box; + padding: 12px; + color: #333; + background: #fff; + overflow: hidden; + } + + h4 { + margin: 0 0 8px; + } + + .ctrl { + display: flex; + align-items: center; + } + + .ctrl button { + height: 30px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-right: 0; + cursor: pointer; + } + + .ctrl input { + flex: 1; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + box-shadow: none !important; + border-color: rgba(41, 95, 203, 0.8); + } + + .copied-notice { + position: absolute; + z-index: 1; + top: 12px; + right: 12px; + background: rgba(0, 0, 0, 0.7); + color: #fff; + padding: 2px 7px; + font-size: 10px; + border-radius: 4px; + box-shadow: 0 1px 3px rgba(0,0,0,.2); + text-shadow: 0 1px 3px rgba(0,0,0,.9); + } + `] + } + + constructor () { + super() + this.reset() + } + + reset () { + this.url = undefined + this.canShare = undefined + this.hasCopied = false + } + + async init (params) { + var shareableUrl = undefined + if (params.url.startsWith('hyper://')) { + // establish the shareable url + try { + let driveInfo + let urlp = new URL(params.url) + let pathParts = urlp.pathname.split('/').filter(Boolean) + let pathAcc = [] + + // find the drive that owns the location + while (pathParts.length > 0) { + let st = await bg.hyperdrive.stat(joinPath(urlp.origin, pathParts.join('/'))) + if (st.mount) { + driveInfo = await bg.hyperdrive.getInfo(st.mount.key) + break + } + pathAcc.unshift(pathParts.pop()) + } + if (!driveInfo) { + driveInfo = await bg.hyperdrive.getInfo(urlp.origin) + } + + // make sure it can be shared + if (driveInfo && driveInfo.url !== 'hyper://system/') { + shareableUrl = driveInfo.url + '/' + pathAcc.join('/') + urlp.search + urlp.hash + } + } catch (e) { + console.debug(e) + } + } else { + // can always share as-is + shareableUrl = params.url + } + + this.canShare = !!shareableUrl + this.url = shareableUrl + await this.requestUpdate() + + // focus and highlight input + if (this.canShare) { + var input = this.shadowRoot.querySelector('input[type=text]') + input.focus() + input.setSelectionRange(0, input.value.length) + } + } + + // rendering + // = + + render () { + return html` + +
    + ${this.canShare ? html` + ${this.hasCopied ? html`Copied to your clipboard` : ''} +

    Share this location

    +

    Anyone with the link can view this location.

    +
    + + e.preventDefault()} /> +
    + ` : html` +

    Share this location

    +
    This location is private and cannot be shared.
    + `} +
    + ` + } + + // events + // = + + onClickCopy () { + var input = this.shadowRoot.querySelector('input') + input.select() + document.execCommand('copy') + this.hasCopied = true + + setTimeout(() => { + this.hasCopied = false + }, 1e3) + } +} + +customElements.define('share-menu', ShareMenu) diff --git a/app/fg/shell-menus/site.js b/app/fg/shell-menus/site.js new file mode 100644 index 0000000000..2f71abafb4 --- /dev/null +++ b/app/fg/shell-menus/site.js @@ -0,0 +1,100 @@ +/* globals customElements */ +import { LitElement, html, css } from '../vendor/lit-element/lit-element' +import * as bg from './bg-process-rpc' +import commonCSS from './common.css' +import { writeToClipboard } from '../lib/event-handlers' + +class SiteMenu extends LitElement { + static get properties () { + return { + } + } + + constructor () { + super() + } + + reset () { + this.submenu = '' + this.url = undefined + this.driveInfo = undefined + } + + async init (params) { + this.url = params.url + this.requestUpdate() + + if (this.url.startsWith('hyper://')) { + try { + this.driveInfo = await bg.hyperdrive.getInfo(this.url) + } catch (e) { + console.debug(e) + } + this.requestUpdate() + } + } + + render () { + return html` + +
    +
    + + ${this.driveInfo ? html` + + ` : ''} +
    +
    + ` + } + + // events + // = + + updated () { + // adjust dimensions based on rendering + var width = this.shadowRoot.querySelector('div').clientWidth + var height = this.shadowRoot.querySelector('div').clientHeight + bg.shellMenus.resizeSelf({width, height}) + } + + onOpenPage (e, url) { + bg.shellMenus.createTab(url) + bg.shellMenus.close() + } + + onCopyURL () { + writeToClipboard(this.url) + bg.shellMenus.close() + } + + onCopyDriveKey () { + writeToClipboard(this.driveInfo.key) + bg.shellMenus.close() + } +} +SiteMenu.styles = [commonCSS, css` +.wrapper { + width: 230px; +} + +.wrapper::-webkit-scrollbar { + display: none; +} + +.section:last-child { + border-bottom: 0; +} + +.menu-item { + height: 40px; +} +`] + +customElements.define('site-menu', SiteMenu) \ No newline at end of file diff --git a/app/fg/shell-menus/toolbar.js b/app/fg/shell-menus/toolbar.js new file mode 100644 index 0000000000..9cff0a4436 --- /dev/null +++ b/app/fg/shell-menus/toolbar.js @@ -0,0 +1,211 @@ +/* globals customElements */ +import { LitElement, html, css } from '../vendor/lit-element/lit-element' +import { repeat } from '../vendor/lit-element/lit-html/directives/repeat' +import * as bg from './bg-process-rpc' +import commonCSS from './common.css' + +class ToolbarMenu extends LitElement { + static get properties () { + return { + menu: {type: String}, + menuItems: {type: Object} + } + } + + constructor () { + super() + this.menu = undefined + this.url = undefined + this.bookmarks = [] + this.isDarwin = false + this.menuItems = {} + } + + reset () { + this.menu = undefined + this.menuItems = {} + this.bookmarks = [] + } + + async init (params, isUpdate = false) { + this.menu = params && params.menu ? params.menu : undefined + if (!isUpdate) { + let [browserInfo, menuItems] = await Promise.all([ + bg.beakerBrowser.getInfo(), + bg.shellMenus.getWindowMenu() + ]) + this.isDarwin = browserInfo.platform === 'darwin' + this.menuItems = menuItems + } + if (this.menu === 'bookmarks') { + this.bookmarks = await bg.bookmarks.list({sortBy: 'title'}) + this.bookmarks.sort((a, b) => (a.title || '').toLowerCase().localeCompare((b.title || '').toLowerCase())) + await this.requestUpdate() + } + } + + async updateMenu (params) { + return this.init(params, true) + } + + updated () { + // adjust dimensions based on rendering + if (!this.menu) return + try { + var width = this.shadowRoot.querySelector('div').clientWidth + var height = this.shadowRoot.querySelector('div').clientHeight + bg.shellMenus.resizeSelf({width, height}) + } catch (e) {} + } + + render () { + if (!this.menu) return html`` + return this[`render_${this.menu}`]() + } + + renderAccelerator (accel) { + if (!accel) return + const command = '⌘' + const control = '^' + const commandOrControl = this.isDarwin ? command : control + return accel + .replace(/\+/g, '') + .replace('CmdOrCtrl', commandOrControl) + .replace('Alt', '⌥') + .replace('Cmd', command) + .replace('Ctrl', control) + .replace('Shift', '⇧') + .replace('Plus', '+') + .replace('Left', '←') + .replace('Right', '→') + .replace('`', '~') + } + + renderMenuItems (menu) { + var items = this.menuItems[menu] + if (!items) return html`` + return html` + +
    +
    + ${repeat(items, (item, i) => item.id || i, item => item.separator + ? html`
    ` + : html` + + ` + )} +
    +
    + ` + } + + render_file () { + return this.renderMenuItems('File') + } + + render_drive () { + return this.renderMenuItems('Drive') + } + + render_bookmarks () { + return html` + +
    +
    + ${repeat(this.bookmarks, b => b.href, b => html` + + `)} +
    +
    + ` + } + render_developer () { + return this.renderMenuItems('Developer') + } + + render_help () { + return this.renderMenuItems('Help') + } + + // events + // = + + onClickMenuItem (menu, id) { + return async (e) => { + bg.shellMenus.triggerWindowMenuItemById(menu, id) + bg.shellMenus.close() + } + } + + onOpenPage (e, url) { + bg.shellMenus.createTab(url) + bg.shellMenus.close() + } +} +ToolbarMenu.styles = [commonCSS, css` +.wrapper { + width: 200px; +} + +.wrapper.wide { + width: 250px; +} + +.wrapper::-webkit-scrollbar { + display: none; +} + +.section:last-child { + border-bottom: 0; +} + +.section.scrollable { + max-height: 500px; + overflow-y: auto; +} + +.menu-item { + height: 30px; +} + +.menu-item[disabled] { + color: #99a; +} + +.menu-item[disabled]:hover { + background: none; +} + +.menu-item i.right { + font-size: 11px; + margin-left: auto; + padding-right: 0; + position: relative; + left: 5px; +} + +.menu-item i.more { + margin-left: auto; + padding-right: 0; + text-align: right; +} + +.menu-item .more, +.menu-item .shortcut { + color: #777; + margin-left: auto; +} + +.menu-item .shortcut { + font-size: 12px; + -webkit-font-smoothing: antialiased; +} +`] + +customElements.define('toolbar-menu', ToolbarMenu) \ No newline at end of file diff --git a/app/fg/shell-window/bg-process-rpc.js b/app/fg/shell-window/bg-process-rpc.js new file mode 100644 index 0000000000..899076de0d --- /dev/null +++ b/app/fg/shell-window/bg-process-rpc.js @@ -0,0 +1,10 @@ +import * as rpc from 'pauls-electron-rpc' +import browserManifest from '../../bg/web-apis/manifests/internal/browser' +import watchlistManifest from '../../bg/web-apis/manifests/internal/watchlist' +import viewsManifest from '../../bg/rpc-manifests/views' +import hyperdriveManifest from '../../bg/web-apis/manifests/external/hyperdrive' + +export const beakerBrowser = rpc.importAPI('beaker-browser', browserManifest) +export const watchlist = rpc.importAPI('watchlist', watchlistManifest) +export const views = rpc.importAPI('background-process-views', viewsManifest) +export const hyperdrive = rpc.importAPI('hyperdrive', hyperdriveManifest) \ No newline at end of file diff --git a/app/shell-window.html b/app/fg/shell-window/index.html similarity index 58% rename from app/shell-window.html rename to app/fg/shell-window/index.html index b60b582b49..fbc5b54558 100644 --- a/app/shell-window.html +++ b/app/fg/shell-window/index.html @@ -7,26 +7,46 @@ + \ No newline at end of file diff --git a/app/vendor/README.md b/app/fg/vendor/README.md similarity index 100% rename from app/vendor/README.md rename to app/fg/vendor/README.md diff --git a/app/vendor/lit-element/CHANGELOG.md b/app/fg/vendor/lit-element/CHANGELOG.md similarity index 100% rename from app/vendor/lit-element/CHANGELOG.md rename to app/fg/vendor/lit-element/CHANGELOG.md diff --git a/app/vendor/lit-element/CONTRIBUTING.md b/app/fg/vendor/lit-element/CONTRIBUTING.md similarity index 100% rename from app/vendor/lit-element/CONTRIBUTING.md rename to app/fg/vendor/lit-element/CONTRIBUTING.md diff --git a/app/vendor/lit-element/LICENSE b/app/fg/vendor/lit-element/LICENSE similarity index 100% rename from app/vendor/lit-element/LICENSE rename to app/fg/vendor/lit-element/LICENSE diff --git a/app/vendor/lit-element/README.md b/app/fg/vendor/lit-element/README.md similarity index 100% rename from app/vendor/lit-element/README.md rename to app/fg/vendor/lit-element/README.md diff --git a/app/vendor/lit-element/lib/css-tag.js b/app/fg/vendor/lit-element/lib/css-tag.js similarity index 100% rename from app/vendor/lit-element/lib/css-tag.js rename to app/fg/vendor/lit-element/lib/css-tag.js diff --git a/app/vendor/lit-element/lib/css-tag.js.map b/app/fg/vendor/lit-element/lib/css-tag.js.map similarity index 100% rename from app/vendor/lit-element/lib/css-tag.js.map rename to app/fg/vendor/lit-element/lib/css-tag.js.map diff --git a/app/vendor/lit-element/lib/decorators.js b/app/fg/vendor/lit-element/lib/decorators.js similarity index 100% rename from app/vendor/lit-element/lib/decorators.js rename to app/fg/vendor/lit-element/lib/decorators.js diff --git a/app/vendor/lit-element/lib/decorators.js.map b/app/fg/vendor/lit-element/lib/decorators.js.map similarity index 100% rename from app/vendor/lit-element/lib/decorators.js.map rename to app/fg/vendor/lit-element/lib/decorators.js.map diff --git a/app/vendor/lit-element/lib/updating-element.js b/app/fg/vendor/lit-element/lib/updating-element.js similarity index 100% rename from app/vendor/lit-element/lib/updating-element.js rename to app/fg/vendor/lit-element/lib/updating-element.js diff --git a/app/vendor/lit-element/lib/updating-element.js.map b/app/fg/vendor/lit-element/lib/updating-element.js.map similarity index 100% rename from app/vendor/lit-element/lib/updating-element.js.map rename to app/fg/vendor/lit-element/lib/updating-element.js.map diff --git a/app/vendor/lit-element/lit-element.js b/app/fg/vendor/lit-element/lit-element.js similarity index 100% rename from app/vendor/lit-element/lit-element.js rename to app/fg/vendor/lit-element/lit-element.js diff --git a/app/vendor/lit-element/lit-element.js.map b/app/fg/vendor/lit-element/lit-element.js.map similarity index 100% rename from app/vendor/lit-element/lit-element.js.map rename to app/fg/vendor/lit-element/lit-element.js.map diff --git a/app/vendor/lit-element/lit-html/CHANGELOG.md b/app/fg/vendor/lit-element/lit-html/CHANGELOG.md similarity index 100% rename from app/vendor/lit-element/lit-html/CHANGELOG.md rename to app/fg/vendor/lit-element/lit-html/CHANGELOG.md diff --git a/app/vendor/lit-element/lit-html/CODE_OF_CONDUCT.md b/app/fg/vendor/lit-element/lit-html/CODE_OF_CONDUCT.md similarity index 100% rename from app/vendor/lit-element/lit-html/CODE_OF_CONDUCT.md rename to app/fg/vendor/lit-element/lit-html/CODE_OF_CONDUCT.md diff --git a/app/vendor/lit-element/lit-html/CONTRIBUTING.md b/app/fg/vendor/lit-element/lit-html/CONTRIBUTING.md similarity index 100% rename from app/vendor/lit-element/lit-html/CONTRIBUTING.md rename to app/fg/vendor/lit-element/lit-html/CONTRIBUTING.md diff --git a/app/vendor/lit-element/lit-html/LICENSE b/app/fg/vendor/lit-element/lit-html/LICENSE similarity index 100% rename from app/vendor/lit-element/lit-html/LICENSE rename to app/fg/vendor/lit-element/lit-html/LICENSE diff --git a/app/vendor/lit-element/lit-html/README.md b/app/fg/vendor/lit-element/lit-html/README.md similarity index 100% rename from app/vendor/lit-element/lit-html/README.md rename to app/fg/vendor/lit-element/lit-html/README.md diff --git a/app/vendor/lit-element/lit-html/directives/async-append.js b/app/fg/vendor/lit-element/lit-html/directives/async-append.js similarity index 100% rename from app/vendor/lit-element/lit-html/directives/async-append.js rename to app/fg/vendor/lit-element/lit-html/directives/async-append.js diff --git a/app/vendor/lit-element/lit-html/directives/async-append.js.map b/app/fg/vendor/lit-element/lit-html/directives/async-append.js.map similarity index 100% rename from app/vendor/lit-element/lit-html/directives/async-append.js.map rename to app/fg/vendor/lit-element/lit-html/directives/async-append.js.map diff --git a/app/vendor/lit-element/lit-html/directives/async-replace.js b/app/fg/vendor/lit-element/lit-html/directives/async-replace.js similarity index 100% rename from app/vendor/lit-element/lit-html/directives/async-replace.js rename to app/fg/vendor/lit-element/lit-html/directives/async-replace.js diff --git a/app/vendor/lit-element/lit-html/directives/async-replace.js.map b/app/fg/vendor/lit-element/lit-html/directives/async-replace.js.map similarity index 100% rename from app/vendor/lit-element/lit-html/directives/async-replace.js.map rename to app/fg/vendor/lit-element/lit-html/directives/async-replace.js.map diff --git a/app/vendor/lit-element/lit-html/directives/cache.js b/app/fg/vendor/lit-element/lit-html/directives/cache.js similarity index 100% rename from app/vendor/lit-element/lit-html/directives/cache.js rename to app/fg/vendor/lit-element/lit-html/directives/cache.js diff --git a/app/vendor/lit-element/lit-html/directives/cache.js.map b/app/fg/vendor/lit-element/lit-html/directives/cache.js.map similarity index 100% rename from app/vendor/lit-element/lit-html/directives/cache.js.map rename to app/fg/vendor/lit-element/lit-html/directives/cache.js.map diff --git a/app/vendor/lit-element/lit-html/directives/class-map.js b/app/fg/vendor/lit-element/lit-html/directives/class-map.js similarity index 100% rename from app/vendor/lit-element/lit-html/directives/class-map.js rename to app/fg/vendor/lit-element/lit-html/directives/class-map.js diff --git a/app/vendor/lit-element/lit-html/directives/class-map.js.map b/app/fg/vendor/lit-element/lit-html/directives/class-map.js.map similarity index 100% rename from app/vendor/lit-element/lit-html/directives/class-map.js.map rename to app/fg/vendor/lit-element/lit-html/directives/class-map.js.map diff --git a/app/vendor/lit-element/lit-html/directives/guard.js b/app/fg/vendor/lit-element/lit-html/directives/guard.js similarity index 100% rename from app/vendor/lit-element/lit-html/directives/guard.js rename to app/fg/vendor/lit-element/lit-html/directives/guard.js diff --git a/app/vendor/lit-element/lit-html/directives/guard.js.map b/app/fg/vendor/lit-element/lit-html/directives/guard.js.map similarity index 100% rename from app/vendor/lit-element/lit-html/directives/guard.js.map rename to app/fg/vendor/lit-element/lit-html/directives/guard.js.map diff --git a/app/vendor/lit-element/lit-html/directives/if-defined.js b/app/fg/vendor/lit-element/lit-html/directives/if-defined.js similarity index 100% rename from app/vendor/lit-element/lit-html/directives/if-defined.js rename to app/fg/vendor/lit-element/lit-html/directives/if-defined.js diff --git a/app/vendor/lit-element/lit-html/directives/if-defined.js.map b/app/fg/vendor/lit-element/lit-html/directives/if-defined.js.map similarity index 100% rename from app/vendor/lit-element/lit-html/directives/if-defined.js.map rename to app/fg/vendor/lit-element/lit-html/directives/if-defined.js.map diff --git a/app/vendor/lit-element/lit-html/directives/repeat.js b/app/fg/vendor/lit-element/lit-html/directives/repeat.js similarity index 100% rename from app/vendor/lit-element/lit-html/directives/repeat.js rename to app/fg/vendor/lit-element/lit-html/directives/repeat.js diff --git a/app/vendor/lit-element/lit-html/directives/repeat.js.map b/app/fg/vendor/lit-element/lit-html/directives/repeat.js.map similarity index 100% rename from app/vendor/lit-element/lit-html/directives/repeat.js.map rename to app/fg/vendor/lit-element/lit-html/directives/repeat.js.map diff --git a/app/vendor/lit-element/lit-html/directives/style-map.js b/app/fg/vendor/lit-element/lit-html/directives/style-map.js similarity index 100% rename from app/vendor/lit-element/lit-html/directives/style-map.js rename to app/fg/vendor/lit-element/lit-html/directives/style-map.js diff --git a/app/vendor/lit-element/lit-html/directives/style-map.js.map b/app/fg/vendor/lit-element/lit-html/directives/style-map.js.map similarity index 100% rename from app/vendor/lit-element/lit-html/directives/style-map.js.map rename to app/fg/vendor/lit-element/lit-html/directives/style-map.js.map diff --git a/app/vendor/lit-element/lit-html/directives/unsafe-html.js b/app/fg/vendor/lit-element/lit-html/directives/unsafe-html.js similarity index 100% rename from app/vendor/lit-element/lit-html/directives/unsafe-html.js rename to app/fg/vendor/lit-element/lit-html/directives/unsafe-html.js diff --git a/app/vendor/lit-element/lit-html/directives/unsafe-html.js.map b/app/fg/vendor/lit-element/lit-html/directives/unsafe-html.js.map similarity index 100% rename from app/vendor/lit-element/lit-html/directives/unsafe-html.js.map rename to app/fg/vendor/lit-element/lit-html/directives/unsafe-html.js.map diff --git a/app/vendor/lit-element/lit-html/directives/until.js b/app/fg/vendor/lit-element/lit-html/directives/until.js similarity index 100% rename from app/vendor/lit-element/lit-html/directives/until.js rename to app/fg/vendor/lit-element/lit-html/directives/until.js diff --git a/app/vendor/lit-element/lit-html/directives/until.js.map b/app/fg/vendor/lit-element/lit-html/directives/until.js.map similarity index 100% rename from app/vendor/lit-element/lit-html/directives/until.js.map rename to app/fg/vendor/lit-element/lit-html/directives/until.js.map diff --git a/app/vendor/lit-element/lit-html/lib/default-template-processor.js b/app/fg/vendor/lit-element/lit-html/lib/default-template-processor.js similarity index 100% rename from app/vendor/lit-element/lit-html/lib/default-template-processor.js rename to app/fg/vendor/lit-element/lit-html/lib/default-template-processor.js diff --git a/app/vendor/lit-element/lit-html/lib/default-template-processor.js.map b/app/fg/vendor/lit-element/lit-html/lib/default-template-processor.js.map similarity index 100% rename from app/vendor/lit-element/lit-html/lib/default-template-processor.js.map rename to app/fg/vendor/lit-element/lit-html/lib/default-template-processor.js.map diff --git a/app/vendor/lit-element/lit-html/lib/directive.js b/app/fg/vendor/lit-element/lit-html/lib/directive.js similarity index 100% rename from app/vendor/lit-element/lit-html/lib/directive.js rename to app/fg/vendor/lit-element/lit-html/lib/directive.js diff --git a/app/vendor/lit-element/lit-html/lib/directive.js.map b/app/fg/vendor/lit-element/lit-html/lib/directive.js.map similarity index 100% rename from app/vendor/lit-element/lit-html/lib/directive.js.map rename to app/fg/vendor/lit-element/lit-html/lib/directive.js.map diff --git a/app/vendor/lit-element/lit-html/lib/dom.js b/app/fg/vendor/lit-element/lit-html/lib/dom.js similarity index 100% rename from app/vendor/lit-element/lit-html/lib/dom.js rename to app/fg/vendor/lit-element/lit-html/lib/dom.js diff --git a/app/vendor/lit-element/lit-html/lib/dom.js.map b/app/fg/vendor/lit-element/lit-html/lib/dom.js.map similarity index 100% rename from app/vendor/lit-element/lit-html/lib/dom.js.map rename to app/fg/vendor/lit-element/lit-html/lib/dom.js.map diff --git a/app/vendor/lit-element/lit-html/lib/modify-template.js b/app/fg/vendor/lit-element/lit-html/lib/modify-template.js similarity index 100% rename from app/vendor/lit-element/lit-html/lib/modify-template.js rename to app/fg/vendor/lit-element/lit-html/lib/modify-template.js diff --git a/app/vendor/lit-element/lit-html/lib/modify-template.js.map b/app/fg/vendor/lit-element/lit-html/lib/modify-template.js.map similarity index 100% rename from app/vendor/lit-element/lit-html/lib/modify-template.js.map rename to app/fg/vendor/lit-element/lit-html/lib/modify-template.js.map diff --git a/app/vendor/lit-element/lit-html/lib/part.js b/app/fg/vendor/lit-element/lit-html/lib/part.js similarity index 100% rename from app/vendor/lit-element/lit-html/lib/part.js rename to app/fg/vendor/lit-element/lit-html/lib/part.js diff --git a/app/vendor/lit-element/lit-html/lib/part.js.map b/app/fg/vendor/lit-element/lit-html/lib/part.js.map similarity index 100% rename from app/vendor/lit-element/lit-html/lib/part.js.map rename to app/fg/vendor/lit-element/lit-html/lib/part.js.map diff --git a/app/vendor/lit-element/lit-html/lib/parts.js b/app/fg/vendor/lit-element/lit-html/lib/parts.js similarity index 100% rename from app/vendor/lit-element/lit-html/lib/parts.js rename to app/fg/vendor/lit-element/lit-html/lib/parts.js diff --git a/app/vendor/lit-element/lit-html/lib/parts.js.map b/app/fg/vendor/lit-element/lit-html/lib/parts.js.map similarity index 100% rename from app/vendor/lit-element/lit-html/lib/parts.js.map rename to app/fg/vendor/lit-element/lit-html/lib/parts.js.map diff --git a/app/vendor/lit-element/lit-html/lib/render-options.js b/app/fg/vendor/lit-element/lit-html/lib/render-options.js similarity index 100% rename from app/vendor/lit-element/lit-html/lib/render-options.js rename to app/fg/vendor/lit-element/lit-html/lib/render-options.js diff --git a/app/vendor/lit-element/lit-html/lib/render-options.js.map b/app/fg/vendor/lit-element/lit-html/lib/render-options.js.map similarity index 100% rename from app/vendor/lit-element/lit-html/lib/render-options.js.map rename to app/fg/vendor/lit-element/lit-html/lib/render-options.js.map diff --git a/app/vendor/lit-element/lit-html/lib/render.js b/app/fg/vendor/lit-element/lit-html/lib/render.js similarity index 100% rename from app/vendor/lit-element/lit-html/lib/render.js rename to app/fg/vendor/lit-element/lit-html/lib/render.js diff --git a/app/vendor/lit-element/lit-html/lib/render.js.map b/app/fg/vendor/lit-element/lit-html/lib/render.js.map similarity index 100% rename from app/vendor/lit-element/lit-html/lib/render.js.map rename to app/fg/vendor/lit-element/lit-html/lib/render.js.map diff --git a/app/vendor/lit-element/lit-html/lib/shady-render.js b/app/fg/vendor/lit-element/lit-html/lib/shady-render.js similarity index 100% rename from app/vendor/lit-element/lit-html/lib/shady-render.js rename to app/fg/vendor/lit-element/lit-html/lib/shady-render.js diff --git a/app/vendor/lit-element/lit-html/lib/shady-render.js.map b/app/fg/vendor/lit-element/lit-html/lib/shady-render.js.map similarity index 100% rename from app/vendor/lit-element/lit-html/lib/shady-render.js.map rename to app/fg/vendor/lit-element/lit-html/lib/shady-render.js.map diff --git a/app/vendor/lit-element/lit-html/lib/template-factory.js b/app/fg/vendor/lit-element/lit-html/lib/template-factory.js similarity index 100% rename from app/vendor/lit-element/lit-html/lib/template-factory.js rename to app/fg/vendor/lit-element/lit-html/lib/template-factory.js diff --git a/app/vendor/lit-element/lit-html/lib/template-factory.js.map b/app/fg/vendor/lit-element/lit-html/lib/template-factory.js.map similarity index 100% rename from app/vendor/lit-element/lit-html/lib/template-factory.js.map rename to app/fg/vendor/lit-element/lit-html/lib/template-factory.js.map diff --git a/app/vendor/lit-element/lit-html/lib/template-instance.js b/app/fg/vendor/lit-element/lit-html/lib/template-instance.js similarity index 100% rename from app/vendor/lit-element/lit-html/lib/template-instance.js rename to app/fg/vendor/lit-element/lit-html/lib/template-instance.js diff --git a/app/vendor/lit-element/lit-html/lib/template-instance.js.map b/app/fg/vendor/lit-element/lit-html/lib/template-instance.js.map similarity index 100% rename from app/vendor/lit-element/lit-html/lib/template-instance.js.map rename to app/fg/vendor/lit-element/lit-html/lib/template-instance.js.map diff --git a/app/vendor/lit-element/lit-html/lib/template-processor.js b/app/fg/vendor/lit-element/lit-html/lib/template-processor.js similarity index 100% rename from app/vendor/lit-element/lit-html/lib/template-processor.js rename to app/fg/vendor/lit-element/lit-html/lib/template-processor.js diff --git a/app/vendor/lit-element/lit-html/lib/template-processor.js.map b/app/fg/vendor/lit-element/lit-html/lib/template-processor.js.map similarity index 100% rename from app/vendor/lit-element/lit-html/lib/template-processor.js.map rename to app/fg/vendor/lit-element/lit-html/lib/template-processor.js.map diff --git a/app/vendor/lit-element/lit-html/lib/template-result.js b/app/fg/vendor/lit-element/lit-html/lib/template-result.js similarity index 100% rename from app/vendor/lit-element/lit-html/lib/template-result.js rename to app/fg/vendor/lit-element/lit-html/lib/template-result.js diff --git a/app/vendor/lit-element/lit-html/lib/template-result.js.map b/app/fg/vendor/lit-element/lit-html/lib/template-result.js.map similarity index 100% rename from app/vendor/lit-element/lit-html/lib/template-result.js.map rename to app/fg/vendor/lit-element/lit-html/lib/template-result.js.map diff --git a/app/vendor/lit-element/lit-html/lib/template.js b/app/fg/vendor/lit-element/lit-html/lib/template.js similarity index 100% rename from app/vendor/lit-element/lit-html/lib/template.js rename to app/fg/vendor/lit-element/lit-html/lib/template.js diff --git a/app/vendor/lit-element/lit-html/lib/template.js.map b/app/fg/vendor/lit-element/lit-html/lib/template.js.map similarity index 100% rename from app/vendor/lit-element/lit-html/lib/template.js.map rename to app/fg/vendor/lit-element/lit-html/lib/template.js.map diff --git a/app/vendor/lit-element/lit-html/lit-html.js b/app/fg/vendor/lit-element/lit-html/lit-html.js similarity index 100% rename from app/vendor/lit-element/lit-html/lit-html.js rename to app/fg/vendor/lit-element/lit-html/lit-html.js diff --git a/app/vendor/lit-element/lit-html/lit-html.js.map b/app/fg/vendor/lit-element/lit-html/lit-html.js.map similarity index 100% rename from app/vendor/lit-element/lit-html/lit-html.js.map rename to app/fg/vendor/lit-element/lit-html/lit-html.js.map diff --git a/app/vendor/lit-element/lit-html/polyfills/template_polyfill.js b/app/fg/vendor/lit-element/lit-html/polyfills/template_polyfill.js similarity index 100% rename from app/vendor/lit-element/lit-html/polyfills/template_polyfill.js rename to app/fg/vendor/lit-element/lit-html/polyfills/template_polyfill.js diff --git a/app/vendor/lit-element/lit-html/polyfills/template_polyfill.js.map b/app/fg/vendor/lit-element/lit-html/polyfills/template_polyfill.js.map similarity index 100% rename from app/vendor/lit-element/lit-html/polyfills/template_polyfill.js.map rename to app/fg/vendor/lit-element/lit-html/polyfills/template_polyfill.js.map diff --git a/app/webview-preload/execute-javascript.js b/app/fg/webview-preload/execute-javascript.js similarity index 100% rename from app/webview-preload/execute-javascript.js rename to app/fg/webview-preload/execute-javascript.js diff --git a/app/webview-preload/exit-full-screen-hackfix.js b/app/fg/webview-preload/exit-full-screen-hackfix.js similarity index 100% rename from app/webview-preload/exit-full-screen-hackfix.js rename to app/fg/webview-preload/exit-full-screen-hackfix.js diff --git a/app/fg/webview-preload/index.js b/app/fg/webview-preload/index.js new file mode 100644 index 0000000000..470c861f5d --- /dev/null +++ b/app/fg/webview-preload/index.js @@ -0,0 +1,25 @@ +import { ipcRenderer } from 'electron' +import { setup as setupWebAPIs } from '../../bg/web-apis/fg.js' +import { setup as setupPrompt } from './prompt' +import { setup as setupExecuteJavascript } from './execute-javascript' +import setupExitFullScreenHackfix from './exit-full-screen-hackfix' +// import readableStreamAsyncIteratorPolyfill from './readable-stream-async-iterator-polyfill' +import windowOpenCloseHackfix from './window-open-close-hackfix' +import resizeHackfix from './resize-hackfix' +// import './read-page-metadata' DISABLED wasnt working effectively -prf + + +// HACKS +setupExitFullScreenHackfix() +// readableStreamAsyncIteratorPolyfill() +windowOpenCloseHackfix() +resizeHackfix() + +setupWebAPIs() +setupPrompt() +setupExecuteJavascript() + +window.addEventListener('focus', e => { + // track focus + ipcRenderer.send('BEAKER_WC_FOCUSED') +}) \ No newline at end of file diff --git a/app/fg/webview-preload/prompt.js b/app/fg/webview-preload/prompt.js new file mode 100644 index 0000000000..5c07093aa5 --- /dev/null +++ b/app/fg/webview-preload/prompt.js @@ -0,0 +1,24 @@ +/* +This provides window.prompt(), which electron does not do for us. +*/ + +import { ipcRenderer, contextBridge, webFrame } from 'electron' + +// exported api +// = + +export function setup () { + // we have use ipc directly instead of using rpc, because we need custom + // repsonse-lifecycle management in the main thread + contextBridge.exposeInMainWorld('__internalPrompt__', { + prompt: (message, def) => { + return ipcRenderer.sendSync('page-prompt-dialog', message, def) + } + }) + webFrame.executeJavaScript(` + Object.defineProperty(window, 'prompt', { + get: () => (message, def) => window.__internalPrompt__.prompt(message, def), + set: () => {} + }) + `) +} diff --git a/app/webview-preload/read-page-metadata.js b/app/fg/webview-preload/read-page-metadata.js similarity index 100% rename from app/webview-preload/read-page-metadata.js rename to app/fg/webview-preload/read-page-metadata.js diff --git a/app/webview-preload/readable-stream-async-iterator-polyfill.js b/app/fg/webview-preload/readable-stream-async-iterator-polyfill.js similarity index 100% rename from app/webview-preload/readable-stream-async-iterator-polyfill.js rename to app/fg/webview-preload/readable-stream-async-iterator-polyfill.js diff --git a/app/fg/webview-preload/resize-hackfix.js b/app/fg/webview-preload/resize-hackfix.js new file mode 100644 index 0000000000..b10ffc52ea --- /dev/null +++ b/app/fg/webview-preload/resize-hackfix.js @@ -0,0 +1,24 @@ +// HACK +// Electron has an issue where browserviews fail to calculate click regions after a resize +// https://github.com/electron/electron/issues/14038 +// we can solve this by forcing a recalculation after every resize +// -prf + +import {ipcRenderer} from 'electron' + +export default function () { + const setTimeoutFn = setTimeout + const clearTimeoutFn = clearTimeout + var to = undefined + + function queueHackfix () { + if (to) clearTimeoutFn(to) + to = setTimeoutFn(() => { + ipcRenderer.send('resize-hackfix') + to = undefined + }, 500) + } + + window.addEventListener('resize', queueHackfix) + document.addEventListener('DOMContentLoaded', queueHackfix) +} diff --git a/app/fg/webview-preload/window-open-close-hackfix.js b/app/fg/webview-preload/window-open-close-hackfix.js new file mode 100644 index 0000000000..d188e241cc --- /dev/null +++ b/app/fg/webview-preload/window-open-close-hackfix.js @@ -0,0 +1,36 @@ +// HACK +// window.close() will just crash the page's webcontents +// the proper behavior is this: +// - if the page was opened by a script, then close the tab +// - otherwise, do nothing + +import { ipcRenderer, contextBridge, webFrame } from 'electron' + +export default function () { + contextBridge.exposeInMainWorld('__internalOpen__', { + markNextTabScriptClosable: () => { + ipcRenderer.sendSync('BEAKER_MARK_NEXT_TAB_SCRIPTCLOSEABLE') + }, + tryClose: () => { + return ipcRenderer.sendSync('BEAKER_SCRIPTCLOSE_SELF') + } + }) + webFrame.executeJavaScript(` + var origOpen = window.open + Object.defineProperty(window, 'open', { + get: () => function (...args) { + if (args[1] !== '_self') window.__internalOpen__.markNextTabScriptClosable() + return origOpen.apply(window, args) + }, + set: () => {} + }) + Object.defineProperty(window, 'close', { + get: () => function () { + if (!window.__internalOpen__.tryClose()) { + console.warn('Scripts may not close windows that were not opened by script.') + } + }, + set: () => {} + }) + `) +} \ No newline at end of file diff --git a/app/json-renderer.js b/app/json-renderer.js deleted file mode 100644 index e357d542be..0000000000 --- a/app/json-renderer.js +++ /dev/null @@ -1,62 +0,0 @@ -import JSONFormatter from 'json-formatter-js' - -const TWOMB = 2097152 // in bytes - -function parse (str) { - if (str === '') return false - if (str.length > TWOMB) return false // too much json, bro - try { - return JSON.parse(str) - } catch (e) { - return false - } -} - -var views = { - unformatted: document.querySelector('body > pre'), - formatted: undefined -} -var navBtns = { - unformatted: document.createElement('span'), - formatted: document.createElement('span') -} -navBtns.unformatted.textContent = 'Raw' -navBtns.formatted.textContent = 'Formatted' - -function setView (view) { - for (var k in views) { - views[k].classList.add('hidden') - navBtns[k].classList.remove('pressed') - } - views[view].classList.remove('hidden') - navBtns[view].classList.add('pressed') -} - -Object.keys(navBtns).forEach(view => { - navBtns[view].addEventListener('click', () => setView(view)) -}) - -// try to parse -var obj = parse(views.unformatted.textContent) -if (obj) { - // render the formatted el - var formatter = new JSONFormatter(obj, 1, { - hoverPreviewEnabled: true, - hoverPreviewArrayCount: 100, - hoverPreviewFieldCount: 5, - animateOpen: false, - animateClose: false, - useToJSON: true - }) - views.formatted = formatter.render() - document.body.append(views.formatted) - - // render the nav - var nav = document.createElement('nav') - nav.append(navBtns.formatted) - nav.append(navBtns.unformatted) - document.body.prepend(nav) - - // set the current view - setView('formatted') -} diff --git a/app/lib/README.md b/app/lib/README.md index 627d75e632..a0a18a520b 100644 --- a/app/lib/README.md +++ b/app/lib/README.md @@ -1,3 +1,3 @@ -[bg folder](./bg): shared code for the background process +# Lib (shared code) -[fg folder](./fg): shared code for the frontend processes \ No newline at end of file +This folder contains reusable code for both frontend and backend. If you have reusable code that only works on one or the other, store it in `/app/bg/lib` or `/app/fg/lib` as is appropriate. \ No newline at end of file diff --git a/app/lib/fg/anchor-markdown-header.js b/app/lib/anchor-markdown-header.js similarity index 75% rename from app/lib/fg/anchor-markdown-header.js rename to app/lib/anchor-markdown-header.js index 4eabe97480..fe521129b4 100644 --- a/app/lib/fg/anchor-markdown-header.js +++ b/app/lib/anchor-markdown-header.js @@ -1,7 +1,7 @@ /** https://github.com/thlorenz/anchor-markdown-header -Copyright 2013 Thorsten Lorenz. +Copyright 2013 Thorsten Lorenz. All rights reserved. Permission is hereby granted, free of charge, to any person @@ -26,32 +26,30 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import emojiRegex from 'emoji-regex'; +import emojiRegex from 'emoji-regex' -function basicGithubId(text) { - return text.replace(/ /g,'-') +function basicGithubId (text) { + return text.replace(/ /g, '-') // escape codes .replace(/%([abcdef]|\d){2,2}/ig, '') // single chars that are removed - .replace(/[\/?!:\[\]`.,()*"';{}+=<>~\$|#@&–—]/g,'') + .replace(/[\/?!:\[\]`.,()*"';{}+=<>~\$|#@&–—]/g, '') // CJK punctuations that are removed .replace(/[。?!,、;:“”【】()〔〕[]﹃﹄“ ”‘’﹁﹂—…-~《》〈〉「」]/g, '') - ; - } -function getGithubId(text, repetition) { - text = basicGithubId(text); +function getGithubId (text, repetition) { + text = basicGithubId(text) // If no repetition, or if the repetition is 0 then ignore. Otherwise append '-' and the number. if (repetition) { - text += '-' + repetition; + text += '-' + repetition } // Strip emojis text = text.replace(emojiRegex(), '') - return text; + return text } /** @@ -63,33 +61,33 @@ function getGithubId(text, repetition) { * @param repetition {Number} The nth occurrence of this header text, starting with 0. Not required for the 0th instance. * @return {String} The header anchor id */ -export default function anchorMarkdownHeader(header, repetition) { - var replace; - var customEncodeURI = encodeURI; +export default function anchorMarkdownHeader (header, repetition) { + var replace + var customEncodeURI = encodeURI - replace = getGithubId; - customEncodeURI = function(uri) { - var newURI = encodeURI(uri); + replace = getGithubId + customEncodeURI = function (uri) { + var newURI = encodeURI(uri) // encodeURI replaces the zero width joiner character // (used to generate emoji sequences, e.g.Female Construction Worker 👷🏼‍♀️) // github doesn't URL encode them, so we replace them after url encoding to preserve the zwj character. - return newURI.replace(/%E2%80%8D/g, '\u200D'); - }; + return newURI.replace(/%E2%80%8D/g, '\u200D') + } - function asciiOnlyToLowerCase(input) { - var result = ''; + function asciiOnlyToLowerCase (input) { + var result = '' for (var i = 0; i < input.length; ++i) { if (input[i] >= 'A' && input[i] <= 'Z') { - result += input[i].toLowerCase(); + result += input[i].toLowerCase() } else { - result += input[i]; + result += input[i] } } - return result; + return result } - var href = replace(asciiOnlyToLowerCase(header.trim()), repetition); + var href = replace(asciiOnlyToLowerCase(header.trim()), repetition) - return customEncodeURI(href); + return customEncodeURI(href) }; \ No newline at end of file diff --git a/app/lib/bg/dat-archive.js b/app/lib/bg/dat-archive.js deleted file mode 100644 index 7db57c756c..0000000000 --- a/app/lib/bg/dat-archive.js +++ /dev/null @@ -1,178 +0,0 @@ -// this is a backend version of the DatArchive API -// it was originally created so that we can use ingestdb in the backend - -import dat from '../../background-process/web-apis/dat-archive' -import {fromAsyncEventStream} from '../web-apis/event-target' -import parseDatURL from 'parse-dat-url' - -const URL_PROMISE = Symbol('URL_PROMISE') - -// wrap the dat RPC in a replacement context -const context = { - sender: { getURL: () => 'beaker://internal/' } -} -var datCtx = {} -for (var k in dat) { - datCtx[k] = dat[k].bind(context) -} - -export default class DatArchive { - constructor (url) { - // basic URL validation - if (!url || typeof url !== 'string') { - throw new Error('Invalid dat:// URL') - } - - // parse the URL - const urlParsed = parseDatURL(url) - if (!urlParsed || urlParsed.protocol !== 'dat:') { - throw new Error('Invalid URL: must be a dat:// URL') - } - url = 'dat://' + urlParsed.hostname - - // load into the 'active' (in-memory) cache - datCtx.loadArchive(url) - - // resolve the URL (DNS) - const urlPromise = DatArchive.resolveName(url).then(url => { - if (urlParsed.version) { - url += `+${urlParsed.version}` - } - return 'dat://' + url - }) - Object.defineProperty(this, URL_PROMISE, { - enumerable: false, - value: urlPromise - }) - - // define this.url as a frozen getter - Object.defineProperty(this, 'url', { - enumerable: true, - value: url - }) - } - - static create (opts = {}) { - return datCtx.createArchive(opts) - .then(newUrl => new DatArchive(newUrl)) - } - - static fork (url, opts = {}) { - url = (typeof url.url === 'string') ? url.url : url - if (!isDatURL(url)) { - throw new Error('Invalid URL: must be a dat:// URL') - } - return datCtx.forkArchive(url, opts) - .then(newUrl => new DatArchive(newUrl)) - } - - static unlink (url) { - url = (typeof url.url === 'string') ? url.url : url - if (!isDatURL(url)) { - throw new Error('Invalid URL: must be a dat:// URL') - } - return datCtx.unlinkArchive(url) - } - - async getInfo (opts = {}) { - var url = await this[URL_PROMISE] - return datCtx.getInfo(url, opts) - } - - async configure (info, opts = {}) { - var url = await this[URL_PROMISE] - return datCtx.configure(url, info, opts) - } - - async history (opts = {}) { - var url = await this[URL_PROMISE] - return datCtx.history(url, opts) - } - - async stat (path, opts = {}) { - var url = await this[URL_PROMISE] - url = joinPath(url, path) - return datCtx.stat(url, opts) - } - - async readFile (path, opts = {}) { - var url = await this[URL_PROMISE] - url = joinPath(url, path) - return datCtx.readFile(url, opts) - } - - async writeFile (path, data, opts = {}) { - var url = await this[URL_PROMISE] - url = joinPath(url, path) - return datCtx.writeFile(url, data, opts) - } - - async unlink (path) { - var url = await this[URL_PROMISE] - url = joinPath(url, path) - return datCtx.unlink(url) - } - - async download (path = '/', opts = {}) { - var url = await this[URL_PROMISE] - url = joinPath(url, path) - return datCtx.download(url, opts) - } - - async readdir (path = '/', opts = {}) { - var url = await this[URL_PROMISE] - url = joinPath(url, path) - return datCtx.readdir(url, opts) - } - - async mkdir (path) { - var url = await this[URL_PROMISE] - url = joinPath(url, path) - return datCtx.mkdir(url) - } - - async rmdir (path, opts = {}) { - var url = await this[URL_PROMISE] - url = joinPath(url, path) - return datCtx.rmdir(url, opts) - } - - watch (pathSpec = null) { - return fromAsyncEventStream(datCtx.watch(this.url, pathSpec)) - } - - createNetworkActivityStream () { - return fromAsyncEventStream(datCtx.createNetworkActivityStream(this.url)) - } - - static async importFromFilesystem (opts = {}) { - return datCtx.importFromFilesystem(opts) - } - - static async exportToFilesystem (opts = {}) { - return datCtx.exportToFilesystem(opts) - } - - static async exportToArchive (opts = {}) { - return datCtx.exportToArchive(opts) - } - - static async resolveName (name) { - return datCtx.resolveName(name) - } - - static selectArchive (opts = {}) { - return datCtx.selectArchive(opts) - .then(url => new DatArchive(url)) - } -} - -function isDatURL (url) { - var urlp = parseDatURL(url) - return urlp && urlp.protocol === 'dat:' -} - -function joinPath (url, path) { - if (path.charAt(0) === '/') return url + path - return url + '/' + path -} diff --git a/app/lib/const.js b/app/lib/const.js new file mode 100644 index 0000000000..858315b6e1 --- /dev/null +++ b/app/lib/const.js @@ -0,0 +1,49 @@ +import bytes from 'bytes' +import ms from 'ms' +import { join as joinPath } from 'path' + +// native FS file paths +export const ANALYTICS_DATA_FILE = 'analytics-ping.json' +export const ANALYTICS_SERVER = 'analytics.beakerbrowser.com' +export const ANALYTICS_CHECKIN_INTERVAL = ms('6h') + +// hyperdrive FS file paths +export const PATHS = { + BOOKMARKS: '/bookmarks' +} + +// hyperdrive trash management +export const TRASH_EXPIRATION_AGE = ms('7d') // how old do items need to be before deleting them from the trash? +export const TRASH_FIRST_COLLECT_WAIT = ms('30s') // how long after process start to do first collect? +export const TRASH_REGULAR_COLLECT_WAIT = ms('15m') // how long between collections? + +// 64 char hex +export const HYPERDRIVE_HASH_REGEX = /^[0-9a-f]{64}$/i +export const HYPERDRIVE_URL_REGEX = /^(?:(hyper|dat):\/\/)?([0-9a-f]{64})/i + +// url file paths +export const DRIVE_VALID_PATH_REGEX = /^[a-z0-9\-._~!$&'()*+,;=:@/\s]+$/i +export const INVALID_SAVE_FOLDER_CHAR_REGEX = /[^0-9a-zA-Z-_ ]/g + +// dat settings +export const DAT_SWARM_PORT = 3282 +export const DRIVE_MANIFEST_FILENAME = 'index.json' +let quotaEnvVar = process.env.BEAKER_DAT_QUOTA_DEFAULT_BYTES_ALLOWED || process.env.beaker_dat_quota_default_bytes_allowed +export const DAT_QUOTA_DEFAULT_BYTES_ALLOWED = bytes.parse(quotaEnvVar || '500mb') +export const DAT_CACHE_TIME = ms('7d') +export const DEFAULT_DAT_DNS_TTL = ms('1h') +export const MAX_DAT_DNS_TTL = ms('7d') +export const DEFAULT_DRIVE_API_TIMEOUT = ms('60s') + +// index.json manifest fields which can be changed by configure() +export const DRIVE_CONFIGURABLE_FIELDS = [ + 'title', + 'description', + 'author' +] + +// workspace settings +export const WORKSPACE_VALID_NAME_REGEX = /^[a-z][a-z0-9-]*$/i + +// git-url validator +export const IS_GIT_URL_REGEX = /(?:git|ssh|https?|git@[-\w.]+):(\/\/)?(.*?)(\.git)(\/?|#[-\d\w._]+?)$/ diff --git a/app/lib/fg/archive-progress-monitor.js b/app/lib/fg/archive-progress-monitor.js deleted file mode 100644 index 69d1934f9b..0000000000 --- a/app/lib/fg/archive-progress-monitor.js +++ /dev/null @@ -1,73 +0,0 @@ -import {EventEmitter} from 'events' -import throttle from 'lodash.throttle' - -// constants -// = - -// how much time to wait between throttle emits -const EMIT_CHANGED_WAIT = 30 - -export default class ArchiveProgressMonitor extends EventEmitter { - constructor (archive) { - super() - this.archive = archive - this.downloaded = 0 - this.blocks = 0 - this.onDownload = this.onDownload.bind(this) - - // create a throttled 'change' emiter - this.emitChanged = throttle(() => this.emit('changed'), EMIT_CHANGED_WAIT) - } - - async fetchAllStats () { - // list all files - var entries = await this.archive.readdir('/', {recursive: true, stat: true}) - - // count blocks - this.downloaded = 0 - this.blocks = 0 - entries.forEach(entry => { - this.downloaded += entry.stat.downloaded - this.blocks += entry.stat.blocks - }) - } - - startListening () { - // start watching network activity - this.archive.addEventListener('download', this.onDownload) - this.interval = setInterval(() => this.fetchAllStats(), 10e3) // refetch stats every 10s - return this.fetchAllStats() - } - - stopListening () { - clearInterval(this.interval) - this.archive.removeEventListener('download', this.onDownload) - } - - get current () { - return Math.min(Math.round(this.downloaded / this.blocks * 100), 100) - } - - get isComplete () { - return this.downloaded >= this.blocks - } - - onDownload (e) { - // we dont need perfect precision -- - // rather than check if the block is one of ours, assume it is - // we'll refetch the full stats every 10s to correct inaccuracies - // (and we shouldnt be downloading historic data anyway) - this.downloaded++ - - // is this a block in one of our files? - // for (var k in this.allfiles) { - // let file = this.allfiles[k] - // let block = e.block - file.content.blockOffset - // if (block >= 0 && block < file.blocks) { - // file.downloaded++ - // this.downloaded++ - // } - // } - this.emitChanged() - } -} diff --git a/app/lib/fg/img-with-fallbacks.js b/app/lib/fg/img-with-fallbacks.js deleted file mode 100644 index ac71cb5051..0000000000 --- a/app/lib/fg/img-with-fallbacks.js +++ /dev/null @@ -1,16 +0,0 @@ -import yo from 'yo-yo' - -// to try /avatar.png, /avatar.jpg, /avatar.gif, in that order: -// imgWithFallbacks([`${f.url}/avatar.png`, `${f.url}/avatar.jpg`, `${f.url}/avatar.gif`], {cls: 'avatar'}) -export function imgWithFallbacks (urls, {cls} = {}) { - const url = urls.shift() - return yo` - onerror(e, urls, cls)} /> - ` -} - -function onerror (e, urls, cls) { - if (urls.length > 0) { - yo.update(e.target, imgWithFallbacks(urls, cls)) - } -} diff --git a/app/lib/fg/rehost-slider.js b/app/lib/fg/rehost-slider.js deleted file mode 100644 index 5eece0d64a..0000000000 --- a/app/lib/fg/rehost-slider.js +++ /dev/null @@ -1,158 +0,0 @@ -/* globals beaker DatArchive */ - -import _get from 'lodash.get' -import * as yo from 'yo-yo' -import bytes from 'bytes' -import moment from 'moment' -import {EventEmitter} from 'events' -import ArchiveProgressMonitor from './archive-progress-monitor' -import ProgressPieSVG from './progress-pie-svg' - -const ONEDAY = 0 -const ONEWEEK = 1 -const ONEMONTH = 2 -const FOREVER = 3 -const TIMELENS = [ - () => yo`1 day`, - () => yo`1 week`, - () => yo`1 month`, - () => yo`Forever` -] - -export class RehostSlider extends EventEmitter { - constructor (siteInfo) { - super() - this.id = `rehost-slider-${Date.now()}` - this.siteInfo = siteInfo - this.sliderState = undefined - this.progressMonitor = new ArchiveProgressMonitor(new DatArchive(siteInfo.key)) - this.progressMonitor.on('changed', e => this.rerender()) - } - - async setup () { - await this.progressMonitor.startListening() - await this.refreshState() - } - - async refreshState () { - this.sliderState = undefined - this.siteInfo = await (new DatArchive(this.siteInfo.key)).getInfo() - this.rerender() - } - - teardown () { - if (this.progressMonitor) { - this.progressMonitor.stopListening() - this.progressMonitor = null - } - } - - rerender () { - var el = document.querySelector('#' + this.id) - if (el) yo.update(el, this.render()) - } - - render () { - if (this.siteInfo.isOwner) { - return '' - } - - // calculate the current state - const isSaved = _get(this, 'siteInfo.userSettings.isSaved', false) - const expiresAt = _get(this, 'siteInfo.userSettings.expiresAt', undefined) - const now = Date.now() - const timeRemaining = (isSaved && expiresAt && expiresAt > now) ? moment.duration(expiresAt - now) : null - var currentSetting - if (!expiresAt) currentSetting = FOREVER - else if (timeRemaining && timeRemaining.asMonths() > 0.5) currentSetting = ONEMONTH - else if (timeRemaining && timeRemaining.asWeeks() > 0.5) currentSetting = ONEWEEK - else currentSetting = ONEDAY - - // configure rendering params - const sliderState = typeof this.sliderState === 'undefined' - ? currentSetting - : this.sliderState - const statusClass = sliderState == FOREVER ? 'green' : 'yellow' - const statusLabel = timeRemaining && typeof this.sliderState === 'undefined' - ? yo`Seeding (${timeRemaining.humanize()} remaining)` - : TIMELENS[sliderState]() - const size = (this.siteInfo && this.siteInfo.size) ? bytes(this.siteInfo.size, 'mb') : '' - - // render the dropdown if open - return yo` -
    - - ${isSaved - ? yo` -
    -
    - this.onChangeTimelen(e)} /> - - - - - - -
    - -
    -
    - - ${statusLabel} -
    -
    - ${size} - ${this.progressMonitor.current < 100 - ? ProgressPieSVG(this.progressMonitor.current, {size: '10px', color1: '#ccc', color2: '#3579ff'}) - : ''} -
    -
    -
    ` - : ''} -
    ` - } - - async onToggleSeeding (e) { - this.sliderState = FOREVER - - // update the archive settings - var expiresAt = 0 - var isSaved = !this.siteInfo.userSettings.isSaved - await beaker.archives.setUserSettings(this.siteInfo.key, {isSaved, expiresAt}) - Object.assign(this.siteInfo.userSettings, {isSaved, expiresAt}) - - this.rerender() - } - - async onChangeTimelen (e) { - this.sliderState = e.target.value - - // update the archive settings - var expiresAt = 0 - if (this.sliderState == ONEDAY) expiresAt = +(moment().add(1, 'day')) - if (this.sliderState == ONEWEEK) expiresAt = +(moment().add(1, 'week')) - if (this.sliderState == ONEMONTH) expiresAt = +(moment().add(1, 'month')) - await beaker.archives.setUserSettings(this.siteInfo.key, {expiresAt}) - Object.assign(this.siteInfo.userSettings, {expiresAt}) - - this.rerender() - } -} diff --git a/app/lib/fg/template-selector.js b/app/lib/fg/template-selector.js deleted file mode 100644 index 13ff541c62..0000000000 --- a/app/lib/fg/template-selector.js +++ /dev/null @@ -1,112 +0,0 @@ -/* globals beaker DatArchive confirm */ - -import * as yo from 'yo-yo' -import {EventEmitter} from 'events' - -// TODO this is incomplete - -export class TemplateSelector extends EventEmitter { - constructor (siteInfo) { - super() - this.el = null - this.userTemplates = [] - } - - async setup () { - this.userTemplates = await beaker.templates.list() - this.rerender() - } - - // rendering - // = - - rerender () { - if (this.el) { - yo.update(this.el, this.render()) - } - } - - render () { - var el = yo` -
    -
    - ${this.userTemplates.map(t => this.renderTemplateItem(t, true))} -
    -
    ` - this.el = this.el || el - return el - } - - renderTemplateItem ({url, title, disabled}) { - var screenshotUrl = `beaker://templates/screenshot/${url}` - - return yo` -
    this.onSelectTemplate(e, url)} - oncontextmenu=${disabled ? undefined : e => this.onContextmenuTemplate(e, {url, title})} - > - -
    ${title}
    - this.onClickDeleteTemplate(template) } - // ] - // await contextMenu.create({x: e.clientX, y: e.clientY, items}) - // } - } - - async onClickDeleteTemplate (template) { - // confirm - if (!confirm(`Remove ${template.title}?`)) { - return - } - - // remove - await beaker.templates.remove(template.url) - this.selectedTemplate = 'blank' - this.setup() - } -} diff --git a/app/lib/functions.js b/app/lib/functions.js new file mode 100644 index 0000000000..5789fcec5a --- /dev/null +++ b/app/lib/functions.js @@ -0,0 +1,55 @@ +/** + * Helper to make node-style CBs into promises + * @example + * cbPromise(cb => myNodeStyleMethod(cb)).then(...) + * @param {function(Function): any} method + * @returns {Promise} + */ +export function cbPromise (method) { + return new Promise((resolve, reject) => { + method((err, value) => { + if (err) reject(err) + else resolve(value) + }) + }) +} + +/** + * Helper to run an async operation against an array in chunks + * @example + * var res = await chunkAsync(values, v => fetchAsync(v), 3) // chunks of 3s + * @param {any[]} arr + * @param {Number} chunkSize + * @param {(value: any, index: number, array: any[]) => Promise} cb + * @returns {Promise} + */ +export async function chunkMapAsync (arr, chunkSize, cb) { + const resultChunks = [] + for (let chunk of chunkArray(arr, chunkSize)) { + resultChunks.push(await Promise.all(chunk.map(cb))) + } + return resultChunks.flat() + +} + +/** + * Helper to split an array into chunks + * @param {any[]} arr + * @param {Number} chunkSize + * @returns {Array} + */ +export function chunkArray (arr, chunkSize) { + const result = [] + for (let i = 0; i < arr.length; i += chunkSize) { + result.push(arr.slice(i, i + chunkSize)) + } + return result +} + +/** + * Async function which resolves after the given ms + * @param {Number} ms + */ +export async function wait (ms = 1) { + return new Promise(resolve => setTimeout(resolve, ms)) +} \ No newline at end of file diff --git a/app/lib/lock.js b/app/lib/lock.js new file mode 100644 index 0000000000..dae8b91dbf --- /dev/null +++ b/app/lib/lock.js @@ -0,0 +1,24 @@ +import AwaitLock from 'await-lock' + +// wraps await-lock in a simpler interface, with many possible locks +var locks = {} + +/** + * Create a new lock + * @example + * var lock = require('./lock') + * async function foo () { + * var release = await lock('bar') + * // ... + * release() + * } + * @param {string} key + * @returns {Promise} + */ +export default async function (key) { + if (!(key in locks)) locks[key] = new AwaitLock() + + var lock = locks[key] + await lock.acquireAsync() + return lock.release.bind(lock) +}; diff --git a/app/lib/fg/markdown.js b/app/lib/markdown.js similarity index 97% rename from app/lib/fg/markdown.js rename to app/lib/markdown.js index 5c06391dee..1b7b320ce2 100644 --- a/app/lib/fg/markdown.js +++ b/app/lib/markdown.js @@ -7,7 +7,7 @@ export default function create ({allowHTML, useHeadingIds, useHeadingAnchors, hr xhtmlOut: false, // Use '/' to close single tags (
    ) breaks: true, // Convert '\n' in paragraphs into
    langPrefix: 'language-', // CSS language prefix for fenced blocks - linkify: true, // Autoconvert URL-like text to links + linkify: false, // Autoconvert URL-like text to links // Enable some language-neutral replacement + quotes beautification typographer: true, @@ -61,4 +61,3 @@ export default function create ({allowHTML, useHeadingIds, useHeadingAnchors, hr return md } - diff --git a/app/lib/math.js b/app/lib/math.js new file mode 100644 index 0000000000..6372ec51c2 --- /dev/null +++ b/app/lib/math.js @@ -0,0 +1,3 @@ +export function clamp (v, min, max) { + return Math.min(Math.max(v, min), max) +} \ No newline at end of file diff --git a/app/lib/permissions.js b/app/lib/permissions.js new file mode 100644 index 0000000000..eed04a965a --- /dev/null +++ b/app/lib/permissions.js @@ -0,0 +1,265 @@ +const isDriveRegex = /^[a-z0-9]{64}/i + +export const PERMS = { + js: { + persist: true, + idempotent: true, + alwaysDisallow: false, + requiresRefresh: true, + experimental: false + }, + network: { + persist: true, + idempotent: true, + alwaysDisallow: false, + requiresRefresh: true, + experimental: false + }, + createDrive: { + persist: false, + idempotent: false, + alwaysDisallow: false, + requiresRefresh: false, + experimental: false + }, + modifyDrive: { + persist: 'allow', // dont persist 'deny' + idempotent: true, + alwaysDisallow: false, + requiresRefresh: false, + experimental: false + }, + deleteDrive: { + persist: false, + idempotent: false, + alwaysDisallow: false, + requiresRefresh: false, + experimental: false + }, + media: { + persist: false, + idempotent: true, + alwaysDisallow: false, + requiresRefresh: false, + experimental: false + }, + geolocation: { + persist: false, + idempotent: true, + alwaysDisallow: true, // NOTE geolocation is disabled, right now + requiresRefresh: false, + experimental: false + }, + notifications: { + persist: true, + idempotent: true, + alwaysDisallow: false, + requiresRefresh: false, + experimental: false + }, + midiSysex: { + persist: false, + idempotent: true, + alwaysDisallow: false, + requiresRefresh: false, + experimental: false + }, + pointerLock: { + persist: false, + idempotent: true, + alwaysDisallow: false, + requiresRefresh: false, + experimental: false + }, + fullscreen: { + persist: true, + idempotent: false, + alwaysAllow: false, + alwaysDisallow: false, + requiresRefresh: false, + experimental: false + }, + download: { + persist: false, + idempotent: false, + alwaysDisallow: false, + requiresRefresh: false, + experimental: false + }, + dangerousAppControl: { + persist: true, + idempotent: false, + alwaysAllow: false, + requiresRefresh: false, + experimental: false + }, + openExternal: { + persist: false, + idempotent: false, + alwaysDisallow: false, + requiresRefresh: false, + experimental: false + }, + experimentalLibrary: { + persist: true, + idempotent: true, + alwaysDisallow: false, + requiresRefresh: false, + experimental: true + }, + experimentalLibraryRequestAdd: { + persist: false, + idempotent: false, + alwaysDisallow: false, + requiresRefresh: false, + experimental: true + }, + experimentalLibraryRequestRemove: { + persist: false, + idempotent: false, + alwaysDisallow: false, + requiresRefresh: false, + experimental: true + }, + experimentalGlobalFetch: { + persist: true, + idempotent: true, + alwaysDisallow: false, + requiresRefresh: false, + experimental: true + }, + experimentalDatPeers: { + persist: true, + idempotent: true, + alwaysAllow: true, + alwaysDisallow: false, + requiresRefresh: false, + experimental: true + }, + experimentalCapturePage: { + persist: false, + idempotent: false, + alwaysDisallow: false, + requiresRefresh: false, + experimental: true + }, + contactsList: { + persist: 'allow', // dont persist 'deny' + idempotent: true, + alwaysDisallow: false, + requiresRefresh: false, + experimental: false + }, +} + +export const PERM_ICONS = { + js: 'fas fa-code', + network: 'fas fa-cloud', + createDrive: 'fas fa-folder-open', + modifyDrive: 'fas fa-folder-open', + deleteDrive: 'fas fa-folder-open', + media: 'fas fa-video', + geolocation: 'fas fa-map-marked', + notifications: 'fas fa-bell', + midiSysex: 'fas fa-headphones', + pointerLock: 'fas fa-mouse-pointer', + fullscreen: 'fas fa-arrows-alt', + download: 'fas fa-download', + openExternal: 'fas fa-external-link-alt', + experimentalLibrary: 'fas fa-book', + experimentalLibraryRequestAdd: 'fas fa-upload', + experimentalLibraryRequestRemove: 'fas fa-times', + experimentalGlobalFetch: 'fas fa-download', + experimentalDatPeers: 'fas fa-exchange-alt', + experimentalCapturePage: 'fas fa-camera', + dangerousAppControl: 'fas fa-flask', + contactsList: 'fas fa-user-friends' +} + +export function renderPermDesc ({html, bg, url, permId, permParam, permOpts}) { + const api = bg ? (bg.shellMenus || bg.permPrompt) : null + const openUrl = url => e => { + e.preventDefault() + e.stopPropagation() + url = isDriveRegex.test(url) ? `hyper://${url}/` : url + if (api) api.createTab(url) + else beaker.browser.openUrl(url, {setActive: true}) + } + const mediaTypeToTool = v => ({video: 'camera', audio: 'microphone'})[v] + switch (permId) { + case 'js': return 'Run Javascript' + case 'media': return `Use your ${(permOpts.mediaTypes || ['video', 'audio']).map(mediaTypeToTool).join(' and ')}` + case 'geolocation': return 'Know your location' + case 'notifications': return 'Create desktop notifications' + case 'midiSysex': return 'Access your MIDI devices' + case 'pointerLock': return 'Lock your cursor' + case 'fullscreen': return 'Go fullscreen' + case 'openExternal': return `Open this URL in another program: ${shorten(permOpts.externalURL, 128)}` + case 'experimentalLibrary': return 'Read and modify your Library' + case 'experimentalDatPeers': return 'Send and receive messages with peers' + case 'dangerousAppControl': return 'Read and write your data, including bookmarks, archives, and files' + case 'contactsList': return 'Read your address-book contacts' + + case 'network': + if (permParam === '*') return 'Access the network freely' + return 'contact ' + permParam + + case 'download': + return html`Download ${permOpts.filename}` + + case 'createDrive': + if (permOpts.title) return `Create a new Hyperdrive, "${permOpts.title}"` + return 'Create a new Hyperdrive' + + case 'modifyDrive': + { + let viewArchive = openUrl(permParam) + return html`Write files to ${permOpts.title}` + } + + case 'deleteDrive': + { + let viewArchive = openUrl(permParam) + return html`Delete the archive ${permOpts.title}` + } + + case 'experimentalLibraryRequestAdd': + { + let viewArchive = openUrl(permParam) + return html`Host ${permOpts.title}` + } + + case 'experimentalLibraryRequestRemove': + { + let viewArchive = openUrl(permParam) + return html`Stop hosting ${permOpts.title}` + } + + case 'experimentalGlobalFetch': + { + let viewPage = openUrl(permParam) + return html`Fetch data from ${permParam}` + } + + case 'experimentalCapturePage': + { + let viewPage = openUrl(permParam) + return html`Take a screenshot of ${permParam}` + } + } +} + +export function getPermId (permissionToken) { + return permissionToken.split(':')[0] +} + +export function getPermParam (permissionToken) { + return permissionToken.split(':').slice(1).join(':') +} + +function shorten (str, n = 6) { + if (str.length > (n + 3)) { + return str.slice(0, n) + '...' + } + return str +} \ No newline at end of file diff --git a/app/lib/schemas.js b/app/lib/schemas.js new file mode 100644 index 0000000000..a9112beab0 --- /dev/null +++ b/app/lib/schemas.js @@ -0,0 +1,12 @@ +import Ajv from 'ajv' +import { join as joinPath } from 'path' +import { readFileSync } from 'fs' + +const ajv = (new Ajv()) +var validators = {} + +export function getValidator (name) { + if (validators[name]) return validators[name] + validators[name] = ajv.compile(JSON.parse(readFileSync(joinPath(__dirname, 'lib', 'schemas', name), 'utf8'))) + return validators[name] +} \ No newline at end of file diff --git a/app/lib/schemas/application.json b/app/lib/schemas/application.json new file mode 100644 index 0000000000..42cbeffbb2 --- /dev/null +++ b/app/lib/schemas/application.json @@ -0,0 +1,24 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "allOf": [{"$ref": "index.json"}], + "title": "Application", + "type": "object", + "required": ["type"], + "properties": { + "type": { + "type": "string", + "const": "application" + }, + "driveTypes": { + "type": "array", + "items": { + "type": "object", + "required": ["id", "title"], + "properties": { + "id": {"type": "string"}, + "title": {"type": "string"} + } + } + } + } +} \ No newline at end of file diff --git a/app/lib/schemas/index.json b/app/lib/schemas/index.json new file mode 100644 index 0000000000..507acf188a --- /dev/null +++ b/app/lib/schemas/index.json @@ -0,0 +1,31 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "index.json", + "title": "Index.json", + "description": "The common fields in every index.json", + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string" + }, + "author": { + "type": "string", + "format": "url" + }, + "links": { + "type": "object", + "additionalProperties": { + "type": ["object", "array"], + "items": { + "type": "object" + } + } + } + } +} \ No newline at end of file diff --git a/app/lib/schemas/library.json b/app/lib/schemas/library.json new file mode 100644 index 0000000000..c7084b7f9e --- /dev/null +++ b/app/lib/schemas/library.json @@ -0,0 +1,40 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Library", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "const": "library" + }, + "drives": { + "type": "array", + "items": { + "type": "object", + "required": [ + "key" + ], + "properties": { + "key": { + "type": "string", + "pattern": "^[0-9a-fA-F]{64}$" + }, + "isHosting": { + "type": "boolean" + }, + "visibility": { + "type": "string", + "enum": ["private", "unlisted", "public"] + }, + "savedAt": { + "type": "string", + "format": "date-time" + } + } + } + } + } +} \ No newline at end of file diff --git a/app/lib/schemas/program-registry.json b/app/lib/schemas/program-registry.json new file mode 100644 index 0000000000..c1a79a3cd5 --- /dev/null +++ b/app/lib/schemas/program-registry.json @@ -0,0 +1,37 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Program Registry", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "const": "program-registry" + }, + "installed": { + "type": "array", + "items": { + "type": "object", + "required": ["url", "manifest"], + "properties": { + "url": { + "type": "string", + "format": "url" + }, + "key": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "version": { + "type": ["number", "string"] + }, + "manifest": { + "type": "object" + } + } + } + } + } +} \ No newline at end of file diff --git a/app/lib/schemas/type-pkg.json b/app/lib/schemas/type-pkg.json new file mode 100644 index 0000000000..4ff2ae9c80 --- /dev/null +++ b/app/lib/schemas/type-pkg.json @@ -0,0 +1,24 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "allOf": [{"$ref": "index.json"}], + "title": "Type Package", + "type": "object", + "required": ["type"], + "properties": { + "type": { + "type": "string", + "const": "type-pkg" + }, + "driveTypes": { + "type": "array", + "items": { + "type": "object", + "required": ["id", "title"], + "properties": { + "id": {"type": "string"}, + "title": {"type": "string"} + } + } + } + } +} \ No newline at end of file diff --git a/app/lib/schemas/type-registry.json b/app/lib/schemas/type-registry.json new file mode 100644 index 0000000000..e70ec06fa9 --- /dev/null +++ b/app/lib/schemas/type-registry.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Type Registry", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "const": "type-registry" + }, + "installed": { + "type": "array", + "items": { + "type": "object", + "required": ["url", "manifest"], + "properties": { + "url": { + "type": "string", + "format": "url" + }, + "key": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "version": { + "type": ["number", "string"] + }, + "manifest": { + "type": "object" + } + } + } + }, + "defaultDriveHandlers": { + "type": "object", + "additionalProperties": { "type": "string" } + } + } +} \ No newline at end of file diff --git a/app/lib/schemas/unwalled.garden/bookmark.json b/app/lib/schemas/unwalled.garden/bookmark.json new file mode 100644 index 0000000000..64ecf8e52f --- /dev/null +++ b/app/lib/schemas/unwalled.garden/bookmark.json @@ -0,0 +1,44 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://unwalled.garden/bookmark.json", + "type": "object", + "title": "Bookmark", + "description": "A saved/shared link to some URL.", + "required": ["type", "href", "title", "createdAt"], + "properties": { + "type": { + "type": "string", + "description": "The object's type", + "const": "unwalled.garden/bookmark" + }, + "href": { + "type": "string", + "format": "uri", + "maxLength": 10000 + }, + "title": { + "type": "string", + "maxLength": 280 + }, + "description": { + "type": "string", + "maxLength": 560 + }, + "tags": { + "type": "array", + "items": { + "type": "string", + "maxLength": 100, + "pattern": "^[A-Za-z][A-Za-z0-9-_?]*$" + } + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + } + } +} \ No newline at end of file diff --git a/app/lib/schemas/unwalled.garden/comment.json b/app/lib/schemas/unwalled.garden/comment.json new file mode 100644 index 0000000000..84d071208c --- /dev/null +++ b/app/lib/schemas/unwalled.garden/comment.json @@ -0,0 +1,44 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://unwalled.garden/comment.json", + "type": "object", + "title": "Comment", + "description": "A text post about some resource.", + "required": [ + "type", + "topic", + "body", + "createdAt" + ], + "properties": { + "type": { + "type": "string", + "description": "The object's type", + "const": "unwalled.garden/comment" + }, + "topic": { + "type": "string", + "description": "What this comment is about", + "format": "uri" + }, + "replyTo": { + "type": "string", + "description": "What this comment is replying to", + "format": "uri" + }, + "body": { + "type": "string", + "description": "The post's text content" + }, + "createdAt": { + "type": "string", + "format": "date-time", + "description": "The time of this post's creation" + }, + "updatedAt": { + "type": "string", + "format": "date-time", + "description": "The time of this post's last edit" + } + } +} \ No newline at end of file diff --git a/app/lib/schemas/unwalled.garden/dats.json b/app/lib/schemas/unwalled.garden/dats.json new file mode 100644 index 0000000000..d6e0bf0f80 --- /dev/null +++ b/app/lib/schemas/unwalled.garden/dats.json @@ -0,0 +1,42 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://unwalled.garden/dats.json", + "type": "object", + "title": "Dats", + "description": "A list of dats.", + "required": [ + "type", + "dats" + ], + "properties": { + "type": { + "type": "string", + "const": "unwalled.garden/dats" + }, + "dats": { + "type": "array", + "items": { + "type": "object", + "required": ["key"], + "properties": { + "key": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": ["string", "array"], + "items": { + "type": "string" + } + } + } + } + } + } +} \ No newline at end of file diff --git a/app/lib/schemas/unwalled.garden/follows.json b/app/lib/schemas/unwalled.garden/follows.json new file mode 100644 index 0000000000..28c2346fde --- /dev/null +++ b/app/lib/schemas/unwalled.garden/follows.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://unwalled.garden/follows.json", + "type": "object", + "title": "Follows", + "description": "A list of data subscriptions.", + "required": [ + "type", + "urls" + ], + "properties": { + "type": { + "type": "string", + "const": "unwalled.garden/follows" + }, + "urls": { + "type": "array", + "description": "The followed URLs", + "items": { + "type": "string", + "format": "uri" + } + } + } +} \ No newline at end of file diff --git a/app/lib/schemas/unwalled.garden/reaction.json b/app/lib/schemas/unwalled.garden/reaction.json new file mode 100644 index 0000000000..adc6143120 --- /dev/null +++ b/app/lib/schemas/unwalled.garden/reaction.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://unwalled.garden/reaction.json", + "type": "object", + "title": "Reaction", + "description": "An string annotation on some resource.", + "required": [ + "type", + "topic", + "phrases" + ], + "properties": { + "type": { + "type": "string", + "const": "unwalled.garden/reaction" + }, + "topic": { + "type": "string", + "description": "What this reaction is about", + "format": "uri" + }, + "phrases": { + "type": "array", + "description": "The reaction phrases.", + "items": { + "type": "string", + "pattern": "^[a-z ]+$", + "maxLength": 20 + } + } + } +} \ No newline at end of file diff --git a/app/lib/schemas/unwalled.garden/status.json b/app/lib/schemas/unwalled.garden/status.json new file mode 100644 index 0000000000..45223919c3 --- /dev/null +++ b/app/lib/schemas/unwalled.garden/status.json @@ -0,0 +1,26 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://unwalled.garden/status.json", + "type": "object", + "title": "Status", + "description": "A broadcasted piece of content.", + "required": ["type", "body", "createdAt"], + "properties": { + "type": { + "type": "string", + "const": "unwalled.garden/status" + }, + "body": { + "type": "string", + "maxLength": 1000000 + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + } + } +} \ No newline at end of file diff --git a/app/lib/schemas/unwalled.garden/vote.json b/app/lib/schemas/unwalled.garden/vote.json new file mode 100644 index 0000000000..dc4d9255c7 --- /dev/null +++ b/app/lib/schemas/unwalled.garden/vote.json @@ -0,0 +1,35 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://unwalled.garden/vote.json", + "type": "object", + "title": "Vote", + "description": "A vote up or down on some resource.", + "required": [ + "type", + "topic", + "vote", + "createdAt" + ], + "properties": { + "type": { + "type": "string", + "const": "unwalled.garden/vote" + }, + "topic": { + "type": "string", + "format": "uri" + }, + "vote": { + "type": "number", + "enum": [-1, 1] + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + } + } +} \ No newline at end of file diff --git a/app/lib/schemas/webterm.sh/cmd-pkg.json b/app/lib/schemas/webterm.sh/cmd-pkg.json new file mode 100644 index 0000000000..3648c972b0 --- /dev/null +++ b/app/lib/schemas/webterm.sh/cmd-pkg.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "allOf": [{"$ref": "index.json"}], + "type": "object", + "required": ["type"], + "properties": { + "type": { + "type": "string", + "const": "webterm.sh/cmd-pkg", + }, + "commands": { + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "name": {"type": "string"}, + "help": {"type": "string"}, + "usage": {"type": "string"}, + "options": { + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "name": {"type": "string"}, + "abbr": {"type": "string"}, + "help": {"type": "string"}, + "boolean": {"type": "boolean"}, + "default": {"type": ["boolean", "number", "string"]}, + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/app/lib/strings.js b/app/lib/strings.js index 2e48adf11c..168e21571d 100644 --- a/app/lib/strings.js +++ b/app/lib/strings.js @@ -1,7 +1,7 @@ /* globals window */ const URL = typeof window === 'undefined' ? require('url').URL : window.URL -export const DAT_KEY_REGEX = /[0-9a-f]{64}/i +export const DRIVE_KEY_REGEX = /[0-9a-f]{64}/i export function ucfirst (str) { if (!str) str = '' @@ -22,8 +22,8 @@ export function shorten (str, n = 6) { } export function shortenHash (str, n = 6) { - if (str.startsWith('dat://')) { - return 'dat://' + shortenHash(str.slice('dat://'.length).replace(/\/$/, '')) + '/' + if (str.startsWith('hyper://')) { + return 'hyper://' + shortenHash(str.slice('hyper://'.length).replace(/\/$/, '')) + '/' } if (str.length > (n + 5)) { return str.slice(0, n) + '..' + str.slice(-2) @@ -41,11 +41,24 @@ export function highlight (str = '', nonce = 0) { return str.replace(start, '').replace(end, '') } +export function joinPath (...args) { + var str = args[0] + for (let v of args.slice(1)) { + v = v && typeof v === 'string' ? v : '' + let left = str.endsWith('/') + let right = v.startsWith('/') + if (left !== right) str += v + else if (left) str += v.slice(1) + else str += '/' + v + } + return str +} + export function getHostname (str) { try { const u = new URL(str) - if (u.protocol === 'dat:' && u.hostname.length === 64) { - return 'dat://' + shortenHash(u.hostname) + if (u.protocol === 'hyper:' && u.hostname.length === 64) { + return 'hyper://' + shortenHash(u.hostname) } return u.hostname } catch (e) { @@ -57,7 +70,7 @@ export function toNiceUrl (str) { if (!str) return '' try { var urlParsed = new URL(str) - if (DAT_KEY_REGEX.test(urlParsed.hostname)) { + if (DRIVE_KEY_REGEX.test(urlParsed.hostname)) { urlParsed.hostname = `${urlParsed.hostname.slice(0, 4)}..${urlParsed.hostname.slice(-2)}` } return urlParsed.toString() @@ -65,4 +78,27 @@ export function toNiceUrl (str) { // ignore, not a url } return str +} + +const reservedChars = /[^A-Za-z0-9]/g +const continuousDashes = /(-[-]+)/g +const endingDashes = /([-]+$)/g +export function slugify (str = '') { + return str.replace(reservedChars, '-').replace(continuousDashes, '-').replace(endingDashes, '') +} + +export function slugifyUrl (str = '') { + try { + let url = new URL(str) + str = url.protocol + url.hostname + url.pathname + url.search + url.hash + } catch (e) { + // ignore + } + return slugify(str) +} + +export function toHex (buf) { + return buf.reduce((memo, i) => ( + memo + ('0' + i.toString(16)).slice(-2) // pad with leading 0 if <16 + ), '') } \ No newline at end of file diff --git a/app/lib/templates.js b/app/lib/templates.js deleted file mode 100644 index 8d5764f1d0..0000000000 --- a/app/lib/templates.js +++ /dev/null @@ -1,23 +0,0 @@ -export const SITE_TEMPLATES = [ - {id: 'web-page', title: 'Web page'}, - {id: 'file-share', title: 'File share'}, - {id: 'image-collection', title: 'Image collection'}, - {id: 'music-album', title: 'Album', disabled: true}, - {id: 'video', title: 'Video', disabled: true}, - {id: 'podcast', title: 'Podcast', disabled: true}, - {id: 'module', title: 'Code Module', disabled: true}, - {id: 'blank', title: 'Empty project'} -] - -export async function createSiteFromTemplate (template) { - template = template === 'blank' ? false : template - var archive = await DatArchive.create({template, prompt: false}) - - if (!template) { - // for the blank template, go to the source view - return `beaker://editor/${archive.url}` - } else { - // go to the site - return archive.url - } -} \ No newline at end of file diff --git a/app/lib/urls.js b/app/lib/urls.js index fe30e44fe9..8adca9a0b0 100644 --- a/app/lib/urls.js +++ b/app/lib/urls.js @@ -1,10 +1,15 @@ +const isNode = typeof window === 'undefined' +const parse = isNode ? require('url').parse : browserParse + export const isDatHashRegex = /^[a-z0-9]{64}/i const isIPAddressRegex = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/ +const isPath = /^\// // helper to determine what the user may be inputting into the locaiton bar export function examineLocationInput (v) { // does the value look like a url? var isProbablyUrl = (!v.includes(' ') && ( + isPath.test(v) || /\.[A-z]/.test(v) || isIPAddressRegex.test(v) || isDatHashRegex.test(v) || @@ -16,9 +21,9 @@ export function examineLocationInput (v) { )) var vWithProtocol = v var isGuessingTheScheme = false - if (isProbablyUrl && !v.includes('://') && !(v.startsWith('beaker:') || v.startsWith('data:') || v.startsWith('intent:'))) { + if (isProbablyUrl && !isPath.test(v) && !v.includes('://') && !(v.startsWith('beaker:') || v.startsWith('data:') || v.startsWith('intent:'))) { if (isDatHashRegex.test(v)) { - vWithProtocol = 'dat://' + v + vWithProtocol = 'hyper://' + v } else if (v.startsWith('localhost') || isIPAddressRegex.test(v)) { vWithProtocol = 'http://' + v } else { @@ -28,4 +33,35 @@ export function examineLocationInput (v) { } var vSearch = 'https://duckduckgo.com/?q=' + v.split(' ').map(encodeURIComponent).join('+') return {vWithProtocol, vSearch, isProbablyUrl, isGuessingTheScheme} +} + +const SCHEME_REGEX = /[a-z]+:\/\//i +// 1 2 3 4 +const VERSION_REGEX = /^(hyper:\/\/)?([^/]+)(\+[^/]+)(.*)$/i +export function parseDriveUrl (str, parseQS) { + // prepend the scheme if it's missing + if (!SCHEME_REGEX.test(str)) { + str = 'hyper://' + str + } + + var parsed, version = null, match = VERSION_REGEX.exec(str) + if (match) { + // run typical parse with version segment removed + parsed = parse((match[1] || '') + (match[2] || '') + (match[4] || ''), parseQS) + version = match[3].slice(1) + } else { + parsed = parse(str, parseQS) + } + if (isNode) parsed.href = str // overwrite href to include actual original + else parsed.path = parsed.pathname // to match node + if (!parsed.query && parsed.searchParams) { + parsed.query = Object.fromEntries(parsed.searchParams) // to match node + } + parsed.version = version // add version segment + if (!parsed.origin) parsed.origin = `hyper://${parsed.hostname}/` + return parsed +} + +function browserParse (str) { + return new URL(str) } \ No newline at end of file diff --git a/app/main.js b/app/main.js new file mode 100644 index 0000000000..1aa9757969 --- /dev/null +++ b/app/main.js @@ -0,0 +1,198 @@ +require('tls').DEFAULT_ECDH_CURVE = 'auto' // HACK (prf) fix Node 8.9.x TLS issues, see https://github.com/nodejs/node/issues/19359 +process.noAsar = true + +// DEBUG +// Error.stackTraceLimit = Infinity +// require('events').defaultMaxListeners = 1e3 // pls stfu + +// This is main process of Electron, started as first thing when your +// app starts. This script is running through entire life of your application. +// It doesn't have any windows which you can see on screen, but we can open +// window from here. + +import { app, protocol } from 'electron' +import { join } from 'path' + +import { getEnvVar } from './bg/lib/env' +import * as logger from './bg/logger' +import * as beakerBrowser from './bg/browser' +import * as adblocker from './bg/adblocker' +import * as analytics from './bg/analytics' +import * as portForwarder from './bg/nat-port-forwarder' +import dbs from './bg/dbs/index' +import hyper from './bg/hyper/index' +import * as filesystem from './bg/filesystem/index' +import * as webapis from './bg/web-apis/bg' + +import * as initWindow from './bg/ui/init-window' +import { runSetupFlow } from './bg/ui/setup-flow' +import * as windows from './bg/ui/windows' +import * as windowMenu from './bg/ui/window-menu' +import registerContextMenu from './bg/ui/context-menu' +import * as trayIcon from './bg/ui/tray-icon' +import * as downloads from './bg/ui/downloads' +import * as permissions from './bg/ui/permissions' + +import * as beakerProtocol from './bg/protocols/beaker' +import * as assetProtocol from './bg/protocols/asset' +import * as hyperProtocol from './bg/protocols/hyper' +import * as datProtocol from './bg/protocols/dat' + +import * as testDriver from './bg/test-driver' +import * as openURL from './bg/open-url' + +// setup +// = + +const log = logger.get().child({category: 'browser', subcategory: 'init'}) + +// read config from env vars +if (getEnvVar('BEAKER_USER_DATA_PATH')) { + console.log('User data path set by environment variables') + console.log('userData:', getEnvVar('BEAKER_USER_DATA_PATH')) + app.setPath('userData', getEnvVar('BEAKER_USER_DATA_PATH')) +} +if (getEnvVar('BEAKER_TEST_DRIVER')) { + testDriver.setup() +} +process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = '1' // we know, we know + +// enable the sandbox +app.enableSandbox() + +// enable process reuse to speed up navigations +// see https://github.com/electron/electron/issues/18397 +app.allowRendererProcessReuse = true + +// configure the protocols +protocol.registerSchemesAsPrivileged([ + {scheme: 'dat', privileges: {standard: true, secure: true, allowServiceWorkers: true, supportFetchAPI: true, corsEnabled: true}}, + {scheme: 'hyper', privileges: {standard: true, secure: true, allowServiceWorkers: true, supportFetchAPI: true, corsEnabled: true}}, + {scheme: 'beaker', privileges: {standard: true, secure: true, allowServiceWorkers: true, supportFetchAPI: true, corsEnabled: true}} +]) + +// handle OS event to open URLs +app.on('open-url', (e, url) => { + e.preventDefault() // we are handling it + // wait for ready (not waiting can trigger errors) + if (app.isReady()) openURL.open(url) + else app.on('ready', () => openURL.open(url)) +}) + +// handle OS event to open files +app.on('open-file', (e, filepath) => { + e.preventDefault() // we are handling it + // wait for ready (not waiting can trigger errors) + if (app.isReady()) openURL.open(`file://${filepath}`) + else app.on('ready', () => openURL.open(`file://${filepath}`)) +}) + +app.on('ready', async function () { + beakerProtocol.register(protocol) + initWindow.open() + portForwarder.setup() + + // record some common opts + var commonOpts = { + userDataPath: app.getPath('userData'), + homePath: app.getPath('home') + } + + // initiate log + await logger.setup(join(commonOpts.userDataPath, 'beaker.log')) + + // setup databases + log.info('Initializing databases') + for (let k in dbs) { + if (dbs[k].setup) { + dbs[k].setup(commonOpts) + } + } + + // start subsystems + // (order is important) + log.info('Starting hyperdrive') + await hyper.setup(commonOpts) + log.info('Initializing hyperdrive filesystem') + await filesystem.setup() + log.info('Initializing Web APIs') + webapis.setup() + log.info('Initializing browser') + await beakerBrowser.setup() + adblocker.setup() + analytics.setup() + + // protocols + log.info('Registering protocols') + assetProtocol.setup() + assetProtocol.register(protocol) + hyperProtocol.register(protocol) + datProtocol.register(protocol) + + initWindow.close() + + // setup flow + log.info('Running setup flow') + await runSetupFlow() + + // ui + log.info('Initializing window menu') + windowMenu.setup() + log.info('Initializing context menus') + registerContextMenu() + log.info('Initializing tray icon') + trayIcon.setup() + log.info('Initializing browser windows') + windows.setup() + log.info('Initializing downloads manager') + downloads.setup() + log.info('Initializing permissions manager') + permissions.setup() + log.info('Program setup complete') +}) + +app.on('window-all-closed', () => { + // do nothing +}) + +app.on('will-quit', async (e) => { + if (hyper.daemon.requiresShutdown()) { + e.preventDefault() + log.info('Delaying shutdown to teardown the daemon') + await hyper.daemon.shutdown() + app.quit() + } +}) + +app.on('quit', () => { + log.info('Program quit') + portForwarder.closePort() +}) + +app.on('custom-ready-to-show', () => { + // our first window is ready to show, do any additional setup +}) + +// only run one instance +const isFirstInstance = app.requestSingleInstanceLock() +if (!isFirstInstance) { + app.exit() +} else { + handleArgv(process.argv) + app.on('second-instance', (event, argv, workingDirectory) => { + log.info('Second instance opened', {argv}) + handleArgv(argv) + + // focus/create a window + windows.ensureOneWindowExists() + }) +} +function handleArgv (argv) { + if (process.platform !== 'darwin') { + // look for URLs, windows & linux use argv instead of open-url + let url = argv.find(v => v.indexOf('://') !== -1) + if (url) { + openURL.open(url) + } + } +} diff --git a/app/modals/bg-process-rpc.js b/app/modals/bg-process-rpc.js deleted file mode 100644 index 3edfcd129f..0000000000 --- a/app/modals/bg-process-rpc.js +++ /dev/null @@ -1,16 +0,0 @@ -import * as rpc from 'pauls-electron-rpc' -import browserManifest from '@beaker/core/web-apis/manifests/internal/browser' -import applicationsManifest from '@beaker/core/web-apis/manifests/internal/applications' -import usersManifest from '@beaker/core/web-apis/manifests/internal/users' -import archivesManifest from '@beaker/core/web-apis/manifests/internal/archives' -import datArchiveManifest from '@beaker/core/web-apis/manifests/external/dat-archive' -import profilesManifest from '@beaker/core/web-apis/manifests/external/unwalled-garden-profiles' -import modalsManifest from '../background-process/rpc-manifests/modals' - -export const beakerBrowser = rpc.importAPI('beaker-browser', browserManifest) -export const users = rpc.importAPI('users', usersManifest) -export const applications = rpc.importAPI('applications', applicationsManifest) -export const archives = rpc.importAPI('archives', archivesManifest) -export const datArchive = rpc.importAPI('dat-archive', datArchiveManifest) -export const profiles = rpc.importAPI('unwalled-garden-profiles', profilesManifest) -export const modals = rpc.importAPI('background-process-modals', modalsManifest) \ No newline at end of file diff --git a/app/modals/create-archive.js b/app/modals/create-archive.js deleted file mode 100644 index 05970a714a..0000000000 --- a/app/modals/create-archive.js +++ /dev/null @@ -1,286 +0,0 @@ -/* globals customElements */ -import { LitElement, html, css } from '../vendor/lit-element/lit-element' -import { classMap } from '../vendor/lit-element/lit-html/directives/class-map' -import { ucfirst } from '../lib/strings' -import * as bg from './bg-process-rpc' -import commonCSS from './common.css' -import inputsCSS from './inputs.css' -import buttonsCSS from './buttons2.css' - -const BASIC_TEMPLATES = [ - {url: 'blank', title: 'No Theme', thumb: html``} -] - -class CreateArchiveModal extends LitElement { - static get properties () { - return { - title: {type: String}, - description: {type: String}, - currentTheme: {type: String}, - errors: {type: Object} - } - } - - constructor () { - super() - this.cbs = null - this.title = '' - this.description = '' - this.type = null - this.links = null - this.networked = true - this.themes = [] - this.currentTheme = 'blank' - this.errors = {} - - // export interface - window.createArchiveClickSubmit = () => this.shadowRoot.querySelector('button[type="submit"]').click() - window.createArchiveClickCancel = () => this.shadowRoot.querySelector('.cancel').click() - } - - async init (params, cbs) { - this.cbs = cbs - this.title = params.title || '' - this.description = params.description || '' - this.type = params.type ? Array.isArray(params.type) ? params.type[0] : params.type : '' - this.links = params.links - this.networked = ('networked' in params) ? params.networked : true - this.themes = BASIC_TEMPLATES.concat( - await bg.archives.list({type: 'unwalled.garden/theme', isSaved: true}) - ) - await this.requestUpdate() - } - - // rendering - // = - - render () { - const template = (url, title, thumb) => { - const cls = classMap({template: true, selected: url === this.currentTheme}) - return html` -
    this.onClickTemplate(e, url)}> - ${thumb ? thumb : html``} -
    ${title}
    -
    - ` - } - - return html` - -
    -

    New Website

    - -
    -
    -
    -
    - ${this.themes.map(t => template(t.url, t.title, t.thumb))} -
    -
    - -
    - - - ${this.errors.title ? html`
    ${this.errors.title}
    ` : ''} - - - - -
    - - -
    -
    -
    -
    -
    - ` - } - - // event handlers - // = - - async onClickTemplate (e, url) { - this.currentTheme = url - await this.updateComplete - this.shadowRoot.querySelector('input').focus() // focus the title input - } - - onChangeTitle (e) { - this.title = e.target.value.trim() - } - - onChangeDescription (e) { - this.description = e.target.value.trim() - } - - onChangeType (e) { - this.type = e.target.value.trim() - } - - onClickCancel (e) { - e.preventDefault() - this.cbs.reject(new Error('Canceled')) - } - - async onSubmit (e) { - e.preventDefault() - - if (!this.title) { - this.errors = {title: 'Required'} - return - } - - try { - var url - if (!this.currentTheme.startsWith('dat:')) { - // using builtin template - url = await bg.datArchive.createArchive({ - title: this.title, - description: this.description, - type: '', - networked: this.networked, - links: this.links, - prompt: false - }) - } else { - // using a theme - await bg.datArchive.download(this.currentTheme) - url = await bg.datArchive.createArchive({ - title: this.title, - description: this.description, - type: '', - networked: this.networked, - links: this.links, - prompt: false - }) - // TODO mount the theme instead of copying - await bg.datArchive.exportToArchive({ - src: this.currentTheme, - dst: url + 'theme', - skipUndownloadedFiles: false - }) - } - this.cbs.resolve({url}) - } catch (e) { - this.cbs.reject(e.message || e.toString()) - } - } -} -CreateArchiveModal.styles = [commonCSS, inputsCSS, buttonsCSS, css` -.wrapper { - padding: 0; -} - -h1.title { - padding: 14px 20px; - margin: 0; - border-color: #ddd; -} - -form { - padding: 0; - margin: 0; -} - -hr { - border: 0; - border-top: 1px solid #eee; - margin: 20px 0; -} - -.layout { - display: flex; - user-select: none; -} - -.layout .themes { - width: 624px; -} - -.layout .inputs { - min-width: 200px; - flex: 1; - padding: 20px; -} - -.themes { - height: 468px; - overflow-y: auto; - background: #fafafa; - border-right: 1px solid #ddd; -} - -.themes-heading { - margin: 20px 20px 0px; - padding-bottom: 5px; - border-bottom: 1px solid #ddd; - color: gray; - font-size: 11px; -} - -.themes-selector { - display: grid; - grid-gap: 20px; - padding: 10px 20px; - grid-template-columns: repeat(3, 1fr); - align-items: baseline; -} - -.template { - width: 160px; - padding: 10px; - border-radius: 4px; -} - -.template img, -.template .icon { - display: block; - margin: 0 auto; - width: 150px; - height: 120px; - margin-bottom: 10px; - object-fit: scale-down; - background: #fff; - border: 1px solid #ccc; - border-radius: 3px; -} - -.template .icon { - text-align: center; - font-size: 24px; -} - -.template .icon .fa-fw { - line-height: 80px; -} - -.template .title { - text-align: center; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.template:hover { - background: #eee; -} - -.template.selected { - background: rgb(63, 119, 232); -} - -.template.selected .title { - color: #fff; - font-weight: 500; - text-shadow: 0 1px 2px rgba(0,0,0,.35); -} - -.template.selected img { - border: 1px solid #fff; - box-shadow: 0 1px 2px rgba(0,0,0,.15); -} - -`] - -customElements.define('create-archive-modal', CreateArchiveModal) \ No newline at end of file diff --git a/app/modals/create-user-session.js b/app/modals/create-user-session.js deleted file mode 100644 index af45436542..0000000000 --- a/app/modals/create-user-session.js +++ /dev/null @@ -1,156 +0,0 @@ -/* globals customElements */ -import { LitElement, html, css } from '../vendor/lit-element/lit-element' -import { classMap } from '../vendor/lit-element/lit-html/directives/class-map' -import { repeat } from '../vendor/lit-element/lit-html/directives/repeat' -import { toNiceUrl } from '../lib/strings' -import * as bg from './bg-process-rpc' -import commonCSS from './common.css' -import inputsCSS from './inputs.css' -import buttonsCSS from './buttons2.css' - -class CreateUserSessionModal extends LitElement { - constructor () { - super() - this.cbs = null - this.site = null - this.user = null - this.permissions = null - } - - async init (params, cbs) { - this.cbs = cbs - this.site = params.site - this.user = params.user - this.permissions = params.permissions - await this.requestUpdate() - } - - // rendering - // = - - render () { - if (!this.site || !this.user) { - return html`
    ` - } - return html` - -
    -
    -
    - -
    -
    ${this.user.title}
    -
    ${toNiceUrl(this.user.url)}
    -
    -
    - -

    This site would like to:

    -
    -
    Read your public profile
    - ${repeat(this.permissions, perm => html` -
    - - ${perm.description} - ${perm.caps.join(', ')} -
    - `)} -
    - -
    - - -
    -
    -
    - ` - } - - // event handlers - // = - - updated () { - // adjust height based on rendering - var width = this.shadowRoot.querySelector('div').clientWidth - var height = this.shadowRoot.querySelector('div').clientHeight - bg.modals.resizeSelf({width, height}) - } - - onClickCancel (e) { - e.preventDefault() - this.cbs.reject(new Error('Canceled')) - } - - async onSubmit (e) { - e.preventDefault() - this.cbs.resolve(true) - } -} -CreateUserSessionModal.styles = [commonCSS, inputsCSS, buttonsCSS, css` -.wrapper { - padding: 12px 15px 12px; - width: 340px; -} - -form { - padding: 0; - margin: 0; -} - -.user { - text-align: center; -} - -.user img { - width: 80px; - height: 80px; - border-radius: 50%; - object-fit: cover; - margin-bottom: 10px; -} - -.user .title { - font-size: 18px; - font-weight: 500; -} - -h3 { - margin: 1em 0; - font-weight: 600; - text-align: center; -} - -.permissions { - margin-bottom: 10px; - font-size: 13px; - border: 1px solid #e5e5e5; - border-bottom: 0; - border-radius: 3px; - background: #fafafa; - color: #555; -} - -.permissions > div { - padding: 10px; - border-bottom: 1px solid #e5e5e5; -} - -.permissions .fa-fw { - margin-right: 5px; -} - -.permissions .caps { - float: right; - font-weight: 500; -} - -.form-actions { - display: flex; - justify-content: space-between; -} - -.form-actions button { - padding: 8px 12px; -} -`] - -customElements.define('create-user-session-modal', CreateUserSessionModal) \ No newline at end of file diff --git a/app/modals/fork-archive.js b/app/modals/fork-archive.js deleted file mode 100644 index 36c456316c..0000000000 --- a/app/modals/fork-archive.js +++ /dev/null @@ -1,177 +0,0 @@ -/* globals customElements */ -import { LitElement, html } from '../vendor/lit-element/lit-element' -import prettyHash from 'pretty-hash' -import * as bg from './bg-process-rpc' -import commonCSS from './common.css' -import inputsCSS from './inputs.css' -import buttonsCSS from './buttons.css' -import spinnerCSS from './spinner.css' - -const STATES = { - READY: 0, - DOWNLOADING: 1, - FORKING: 2 -} - -class ForkArchiveModal extends LitElement { - static get properties () { - return { - state: {type: Number}, - title: {type: String}, - description: {type: String} - } - } - - constructor () { - super() - - // internal state - this.archiveInfo = null - this.state = STATES.READY - - // params - this.cbs = null - this.url = '' - this.title = '' - this.description = '' - this.type = null - this.links = null - this.networked = true - - // export interface - window.forkArchiveClickSubmit = () => this.shadowRoot.querySelector('button[type="submit"]').click() - window.forkArchiveClickCancel = () => this.shadowRoot.querySelector('.cancel').click() - } - - async init (params, cbs) { - // store params - this.cbs = cbs - this.url = params.url - this.title = params.title || '' - this.description = params.description || '' - this.type = params.type - this.links = params.links - this.networked = ('networked' in params) ? params.networked : true - await this.requestUpdate() - - // fetch archive info - this.url = await bg.datArchive.resolveName(params.url) - this.archiveInfo = await bg.datArchive.getInfo(this.url) - if (!this.title) this.title = this.archiveInfo.title - if (!this.description) this.description = this.archiveInfo.description - await this.requestUpdate() - - // adjust height based on rendering - var height = this.shadowRoot.querySelector('div').clientHeight - bg.modals.resizeSelf({height}) - } - - // rendering - // = - - render () { - if (!this.archiveInfo) { - return this.renderLoading() - } - - var progressEl - var actionBtn - switch (this.state) { - case STATES.READY: - progressEl = html`
    Ready to copy.
    ` - actionBtn = html`` - break - case STATES.DOWNLOADING: - progressEl = html`
    Downloading remaining files...
    ` - actionBtn = html`` - break - case STATES.FORKING: - progressEl = html`
    Copying...
    ` - actionBtn = html`` - break - } - - return html` -
    -

    Make a copy of ${this.archiveInfo.title ? `"${this.archiveInfo.title}"` : prettyHash(this.archiveInfo.key)}

    - -
    - - - - - - - ${progressEl} - -
    - - ${actionBtn} -
    -
    -
    - ` - } - - renderLoading () { - return html` -
    -

    Make a copy

    -

    Loading...

    -
    - - - - - - -
    - - -
    -
    -
    - ` - } - - // event handlers - // = - - onChangeTitle (e) { - this.title = e.target.value - } - - onChangeDescription (e) { - this.description = e.target.value - } - - onClickCancel (e) { - e.preventDefault() - this.cbs.reject(new Error('Canceled')) - } - - async onSubmit (e) { - e.preventDefault() - - this.state = STATES.DOWNLOADING - await bg.datArchive.download(this.url) - - this.state = STATES.FORKING - try { - var url = await bg.datArchive.forkArchive(this.url, { - title: this.title, - description: this.description, - type: this.type, - networked: this.networked, - links: this.links, - prompt: false - }) - this.cbs.resolve({url}) - } catch (e) { - this.cbs.reject(e.message || e.toString()) - } - } -} -ForkArchiveModal.styles = [commonCSS, inputsCSS, buttonsCSS, spinnerCSS] - -customElements.define('fork-archive-modal', ForkArchiveModal) \ No newline at end of file diff --git a/app/modals/install-application.js b/app/modals/install-application.js deleted file mode 100644 index a25ba75617..0000000000 --- a/app/modals/install-application.js +++ /dev/null @@ -1,99 +0,0 @@ -/* globals customElements */ -import { LitElement, html, css } from '../vendor/lit-element/lit-element' -import { classMap } from '../vendor/lit-element/lit-html/directives/class-map' -import { repeat } from '../vendor/lit-element/lit-html/directives/repeat' -import { ucfirst } from '../lib/strings' -import * as bg from './bg-process-rpc' -import commonCSS from './common.css' -import inputsCSS from './inputs.css' -import buttonsCSS from './buttons2.css' - -class InstallApplicationModal extends LitElement { - constructor () { - super() - this.cbs = null - this.url = '' - this.appInfo = null - } - - async init (params, cbs) { - this.cbs = cbs - this.url = params.url - this.appInfo = await bg.applications.getInfo(params.url) - console.log(this.appInfo) - await this.requestUpdate() - } - - // rendering - // = - - render () { - if (!this.appInfo) return html`
    ` - return html` -
    -

    Install ${this.appInfo.title || 'application'}

    - -
    - ${this.appInfo.description ? html`

    ${this.appInfo.description}

    ` : ''} - - ${this.appInfo.permissions.length > 0 ? html` -

    Permissions:

    -
      - ${repeat(this.appInfo.permissions, perm => html` -
    • ${perm.description}
    • - `)} -
    - ` : ''} - -
    - - -
    -
    -
    - ` - } - - // event handlers - // = - - updated () { - // adjust height based on rendering - var height = this.shadowRoot.querySelector('div').clientHeight - bg.modals.resizeSelf({height}) - } - - onClickCancel (e) { - e.preventDefault() - this.cbs.reject(new Error('Canceled')) - } - - async onSubmit (e) { - e.preventDefault() - - try { - await bg.applications.install(this.url) - this.cbs.resolve(true) - } catch (e) { - this.cbs.reject(e.message || e.toString()) - } - } -} -InstallApplicationModal.styles = [commonCSS, inputsCSS, buttonsCSS, css` -.wrapper { - padding: 10px 20px 16px; -} - -form { - padding: 0; - margin: 0; -} - -h3 { - margin: 1em 0; - font-weight: 600; -} - -`] - -customElements.define('install-application-modal', InstallApplicationModal) \ No newline at end of file diff --git a/app/modals/select-archive.js b/app/modals/select-archive.js deleted file mode 100644 index 3543402801..0000000000 --- a/app/modals/select-archive.js +++ /dev/null @@ -1,418 +0,0 @@ -/* globals customElements */ -import { LitElement, html, css } from '../vendor/lit-element/lit-element' -import * as bg from './bg-process-rpc' -import commonCSS from './common.css' -import inputsCSS from './inputs.css' -import buttonsCSS from './buttons.css' -import {shortenHash} from '../lib/strings' - -const VIEWS = { - SELECT: 0, - CREATE: 1 -} - -class SelectArchiveModal extends LitElement { - static get properties () { - return { - currentView: {type: Number}, - currentTitleFilter: {type: String}, - title: {type: String}, - description: {type: String}, - selectedArchiveKey: {type: String} - } - } - - constructor () { - super() - - // state - this.currentView = VIEWS.SELECT - this.currentTitleFilter = '' - this.title = '' - this.description = '' - this.selectedArchiveKey = '' - this.archives = [] - - // params - this.customTitle = '' - this.buttonLabel = 'Select' - this.type = null - this.cbs = null - - // export interface - window.selectArchiveClickSubmit = () => this.shadowRoot.querySelector('button[type="submit"]').click() - window.selectArchiveClickCancel = () => this.shadowRoot.querySelector('.cancel').click() - window.window.selectArchiveClickNewArchive = () => { - this.shadowRoot.querySelector('.btn[data-content="newArchive"]').click() - return this.requestUpdate() - } - window.selectArchiveClickItem = (key) => { - this.shadowRoot.querySelector(`li[data-key="${key}"]`).click() - this.selectedArchiveKey = key - return this.requestUpdate() - } - window.selectArchiveClickAnyItem = () => { - this.shadowRoot.querySelector(`li[data-key]`).click() - this.selectedArchiveKey = this.archives[0].key - return this.requestUpdate() - } - window.window.selectArchiveSetValueTitle = (v) => { - this.shadowRoot.querySelector('input[name="title"]').value = v - this.title = v - } - } - - async init (params, cbs) { - this.cbs = cbs - this.customTitle = params.title || '' - this.buttonLabel = params.buttonLabel || 'Select' - this.type = params.filters && params.filters.type - await this.requestUpdate() - - this.archives = await bg.archives.list({ - isSaved: true, - isOwner: (params.filters && params.filters.isOwner), - type: this.type - }) - this.type = params.filters && params.filters.type - this.archives.sort((a, b) => (a.title || '').localeCompare(b.title || '')) - await this.requestUpdate() - this.adjustHeight() - } - - adjustHeight () { - // adjust height based on rendering - var height = this.shadowRoot.querySelector('div').clientHeight - bg.modals.resizeSelf({height}) - } - - // rendering - // = - - render () { - return html` - -
    - ${this.currentView === VIEWS.SELECT - ? this.renderSelect() - : this.renderCreate()} -
    - ` - } - - renderSelect () { - return html` -
    -

    ${this.customTitle || 'Select an archive'}

    - -
    -
    - - -
    - ${this.renderArchivesList()} - ${this.renderTypeFilter()} -
    - -
    -
    - -
    -
    - - -
    -
    -
    ` - } - - renderCreate () { - return html` -
    -

    ${this.customTitle || 'Select an archive'}

    -
    - - - - - -
    - -
    -
    - -
    -
    - - -
    -
    -
    ` - } - - renderTypeFilter () { - if (!this.type) return '' - var types = Array.isArray(this.type) ? this.type : [this.type] - return html` -
    - Type: ${types.join(', ')} -
    ` - } - - renderArchivesList () { - var filtered = this.archives - if (this.currentTitleFilter) { - filtered = filtered.filter(a => a.title && a.title.toLowerCase().includes(this.currentTitleFilter)) - } - - if (!filtered.length) { - return html`
    • No archives found
    ` - } - - return html`
      ${filtered.map(a => this.renderArchive(a))}
    ` - } - - renderArchive (archive) { - var isSelected = this.selectedArchiveKey === archive.key - return html` -
  • -
    - - - - ${archive.title || 'Untitled'} - - - ${archive.isOwner ? '' : html`read-only`} - - ${shortenHash(archive.url)} -
    -
  • - ` - } - - // event handlers - // = - - onChangeTitle (e) { - this.selectedArchiveKey = '' - this.title = e.target.value - } - - onChangeDescription (e) { - this.selectedArchiveKey = '' - this.description = e.target.value - } - - onChangeTitleFilter (e) { - this.currentTitleFilter = e.target.value.toLowerCase() - } - - onChangeSelectedArchive (e) { - this.selectedArchiveKey = e.currentTarget.dataset.key - } - - onDblClickArchive (e) { - e.preventDefault() - this.selectedArchiveKey = e.currentTarget.dataset.key - this.onSubmit() - } - - async onClickGotoCreateView (e) { - this.currentView = VIEWS.CREATE - await this.updateComplete - this.adjustHeight() - } - - async onClickGotoSelectView (e) { - this.currentView = VIEWS.SELECT - await this.updateComplete - this.adjustHeight() - } - - onClickCancel (e) { - e.preventDefault() - this.cbs.reject(new Error('Canceled')) - } - - async onSubmit (e) { - if (e) e.preventDefault() - if (this.currentView === VIEWS.CREATE) { - try { - var url = await bg.datArchive.createArchive({ - title: this.title, - description: this.description, - type: this.type, - prompt: false - }) - this.cbs.resolve({url}) - } catch (e) { - this.cbs.reject(e.message || e.toString()) - } - } else { - this.cbs.resolve({url: `dat://${this.selectedArchiveKey}`}) - } - } -} -SelectArchiveModal.styles = [commonCSS, inputsCSS, buttonsCSS, css` -ul { - list-style: none; - padding: 0; - margin: 0; -} - -.form-actions { - display: flex; - text-align: left; -} - -.form-actions .left { - flex: 1; -} - -.form-actions .btn.cancel { - margin-right: 5px; -} - -h1.title { - border: 0; - margin: 10px 0; -} - -.archive-picker { - overflow: hidden; - margin-bottom: 15px; -} - -.archive-picker .filter-container { - position: relative; - margin: 10px 0; - overflow: visible; - height: 40px; -} - -.archive-picker .filter-container i { - position: absolute; - left: 15px; - top: 13px; - color: #b8b8b8; - z-index: 3; -} - -.archive-picker .type-container { - margin-top: 10px; - background: #eee; - padding: 10px; - border-radius: 4px; -} - -.archive-picker .filter { - position: absolute; - left: 0; - top: 0; - border: 0; - margin: 0; - height: 35px; - padding: 0 35px; - border-radius: 0; - background: #fafafa; - border: 1px solid #ddd; - border-radius: 4px; -} - -.archive-picker .filter:focus { - background: #fff; - border: 1.5px solid rgba(40, 100, 220, 0.8); - box-shadow: none; -} - -.archives-list { - height: 350px; - overflow-y: auto; - border: 1px solid #ddd; -} - -.archives-list .empty { - padding: 5px 10px; - color: gray; -} - -.archives-list .archive { - padding: 4px 10px; -} - -.archives-list .archive:nth-child(even) { - background: #f7f7f7; -} - -.archives-list .archive .info { - display: flex; - width: 100%; - align-items: center; -} - -.archives-list .archive .info .favicon { - width: 16px; - height: 16px; - margin-right: 5px; -} - -.archives-list .archive .info .title { - flex: 1; -} - -.archives-list .archive .info .readonly { - font-size: 9.5px; - padding: 0 5px; - margin-right: 5px; - border: 1px solid #d9d9d9; - border-radius: 2px; - text-transform: uppercase; - color: #707070; - white-space: nowrap; -} - -.archives-list .archive .info .hash { - color: rgba(0, 0, 0, 0.55); - margin-left: auto; - width: 100px; -} - -.archives-list .archive:hover { - background: #f0f0f0; -} - -.archives-list .archive.selected { - background: #2864dc; - color: #fff; -} - -.archives-list .archive.selected .info .hash, -.archives-list .archive.selected .info .readonly { - color: rgba(255, 255, 255, 0.9); - font-weight: 100; -} - -.create-archive { - height: 250px; -} - -.create-archive textarea { - height: 97px; -} -`] - -customElements.define('select-archive-modal', SelectArchiveModal) \ No newline at end of file diff --git a/app/modals/select-file.js b/app/modals/select-file.js deleted file mode 100644 index 9774974621..0000000000 --- a/app/modals/select-file.js +++ /dev/null @@ -1,457 +0,0 @@ -/* globals customElements */ -import { LitElement, html, css } from '../vendor/lit-element/lit-element' -import { classMap } from '../vendor/lit-element/lit-html/directives/class-map' -import { join as joinPath } from 'path' -import Stat from '@beaker/core/web-apis/fg/stat' -import * as bg from './bg-process-rpc' -import commonCSS from './common.css' -import inputsCSS from './inputs.css' -import buttonsCSS from './buttons.css' - -class SelectFileModal extends LitElement { - static get properties () { - return { - path: {type: String}, - files: {type: Array}, - selectedPaths: {type: Array} - } - } - - constructor () { - super() - - // state - this.path = '/' - this.files = [] - this.selectedPaths = [] - this.archiveInfo = null - - // params - this.saveMode = false - this.archive = null - this.defaultPath = '/' - this.defaultFilename = '' - this.title = '' - this.buttonLabel = '' - this.select = ['file', 'folder', 'archive'] - this.filters = { - extensions: undefined, - writable: undefined, - networked: undefined - } - this.allowMultiple = false - this.disallowCreate = false - this.cbs = null - } - - async init (params, cbs) { - this.cbs = cbs - this.saveMode = params.saveMode - this.archive = params.archive - this.path = params.defaultPath || '/' - this.defaultFilename = params.defaultFilename || '' - this.title = params.title || '' - this.buttonLabel = params.buttonLabel || (this.saveMode ? 'Save' : 'Select') - if (params.select) this.select = params.select - if (params.filters) { - if ('extensions' in params.filters) { - this.filters.extensions = params.filters.extensions - } - if ('writable' in params.filters) { - this.filters.writable = params.filters.writable - } - if ('networked' in params.filters) { - this.filters.networked = params.filters.networked - } - } - this.allowMultiple = !this.saveMode && params.allowMultiple - this.disallowCreate = params.disallowCreate - if (!this.title) { - if (this.saveMode) { - this.title = 'Save file...' - } else { - let canSelect = v => this.select.includes(v) - let [file, folder, mount] = [canSelect('file'), canSelect('folder'), canSelect('mount')] - if (file && (folder || mount)) { - this.title = 'Select files or folders' - } else if (file && !(folder || mount)) { - this.title = 'Select files' - } else if (folder) { - this.title = 'Select folders' - } else if (mount) { - this.title = 'Select dat archives' - } - } - } - - this.archiveInfo = await bg.datArchive.getInfo(this.archive) - await this.readdir() - this.updateComplete.then(_ => { - this.adjustHeight() - if (this.saveMode) { - this.filenameInput.value = this.defaultFilename - this.focusInput() - this.requestUpdate() - } - }) - } - - adjustHeight () { - // adjust height based on rendering - var height = this.shadowRoot.querySelector('div').clientHeight - bg.modals.resizeSelf({height}) - } - - focusInput () { - var el = this.filenameInput - el.focus() - el.selectionStart = el.selectionEnd = 0 - } - - async goto (path) { - this.path = path - await this.readdir() - this.selectedPaths = [] - if (this.saveMode) { - this.filenameInput.value = this.defaultFilename - this.focusInput() - } - } - - async readdir () { - var files = await bg.datArchive.readdir(this.archive, this.path, {stat: true}) - files.forEach(file => { - file.stat = new Stat(file.stat) - file.path = joinPath(this.path, file.name) - }) - files.sort(sortFiles) - this.files = files - } - - getFile (path) { - return this.files.find(f => f.path === path) - } - - canSelectFile (file) { - if (defined(this.filters.networked) && this.filters.networked !== this.archiveInfo.networked) { - return false - } - if (defined(this.filters.writable) && this.filters.writable !== this.archiveInfo.isOwner) { // TODO change isOwner to writable - return false - } - if (this.saveMode && !this.archiveInfo.isOwner) { // TODO change isOwner to writable - return false - } - if (file.stat.isFile()) { - if (!this.select.includes('file')) { - return false - } - if (this.filters.extensions) { - let hasExt = this.filters.extensions.some(ext => file.name.endsWith(ext)) - if (!hasExt) { - return false - } - } - return true - } else { - return this.select.includes('folder') || this.select.includes('archive') - } - } - - get filenameInput () { - return this.shadowRoot.querySelector('input') - } - - get hasValidSelection () { - if (this.saveMode) { - let inputValue = this.filenameInput && this.filenameInput.value - if (!inputValue) return false - let file = this.getFile(joinPath(this.path, inputValue)) - if (file && file.stat.isDirectory()) return false - return true - } else { - if (this.selectedPaths.length === 0) return false - if (this.filters.extensions) { - // if there's an extensions requirement, - // folders can still be selected but they're not valid targets - return this.selectedPaths.every(path => this.filters.extensions.some(ext => path.endsWith(ext))) - } - return true - } - } - - // rendering - // = - - render () { - return html` - -
    -
    ${this.title}
    -
    -
    - ${this.saveMode - ? html` -
    - - this.requestUpdate()}> -
    - ` - : ''} - -
    - ${this.renderPath()} -
    - -
    - ${this.renderFilesList()} -
    - -
    -
    -
    - - -
    -
    -
    -
    -
    - ` - } - - renderPath () { - var pathParts = this.path.split('/').filter(Boolean) - var pathAcc = [] - return [html` -
    this.onClickPath(e, '/')}>${this.archiveInfo && this.archiveInfo.title || 'Untitled'}
    - - `].concat(pathParts.map(part => { - pathAcc.push(part) - var path = '/' + pathAcc.join('/') - return html` -
    this.onClickPath(e, path)}>${part}
    - - ` - })) - } - - renderFilesList () { - return html` -
    - ${this.files.map(file => this.renderFile(file))} -
    - ` - } - - renderFile (file) { - // TODO mounts - var isSelected = this.selectedPaths.includes(file.path) - var disabled = !this.canSelectFile(file) - const cls = classMap({ - item: true, - file: file.stat.isFile(), - folder: file.stat.isDirectory(), - selected: isSelected, - disabled - }) - return html` -
    -
    - - ${file.name} -
    -
    - ` - } - - // event handlers - // = - - onClickPath (e, path) { - e.preventDefault() - this.goto(path) - } - - onSelectFile (e) { - var path = e.currentTarget.dataset.path - if (this.allowMultiple && (e.ctrlKey || e.metaKey)) { - this.selectedPaths = this.selectedPaths.concat([path]) - } else { - this.selectedPaths = [path] - } - if (this.saveMode) { - this.filenameInput.value = path.split('/').pop() - } - } - - onDblClickFile (e) { - e.preventDefault() - var file = this.getFile(e.currentTarget.dataset.path) - if (file.stat.isDirectory()) { - this.goto(file.path) - } else { - this.selectedPaths = [file.path] - this.onSubmit() - } - } - - onClickCancel (e) { - e.preventDefault() - this.cbs.reject(new Error('Canceled')) - } - - async onSubmit (e) { - if (e) e.preventDefault() - if (this.saveMode) { - let path = joinPath(this.path, this.filenameInput.value) - if (this.getFile(path)) { - if (!confirm('Overwrite this file?')) { - return - } - } - this.cbs.resolve({path}) - } else { - this.cbs.resolve({paths: this.selectedPaths}) - } - } -} -SelectFileModal.styles = [commonCSS, inputsCSS, buttonsCSS, css` -.title { - background: #fff; - border: 0; - padding: 10px 10px 0; - text-align: center; - font-size: 14px; - font-weight: 500; -} - -.wrapper { - padding: 15px; -} - -form { - padding: 0; - margin: 0; -} - -.form-actions { - display: flex; - text-align: left; -} - -.form-actions .left { - flex: 1; -} - -.form-actions .btn.cancel { - margin-right: 5px; -} - -.path { - display: flex; - align-items: center; - border: 1px solid #ccc; - border-bottom: 0; - padding: 4px 6px; - background: #eee; -} - -.path .fa-fw { - margin-right: 4px; -} - -.path > div { - cursor: pointer; - margin-right: 4px; - max-width: 100px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.path > div:hover { - text-decoration: underline; -} - -.view { - overflow: hidden; - margin-bottom: 10px; -} - -.filename { - display: flex; - align-items: center; - margin: 0 0 10px; -} - -.filename label { - margin-right: 10px; - font-weight: normal; -} - -.filename input { - flex: 1; - margin: 0; - font-size: 13px; -} - -.files-list { - height: 350px; - overflow-y: scroll; - border: 1px solid #ccc; - user-select: none; - cursor: default; - padding: 2px 0; -} - -.files-list .item { - padding: 6px 10px; -} - -.files-list .item.disabled { - font-style: italic; - color: #aaa; -} - -.files-list .item .info { - display: flex; - width: 100%; - align-items: center; -} - -.files-list .item .info .fa-fw { - margin-right: 5px; -} - -.files-list .item .info .name { - flex: 1; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.files-list .item.selected { - background: #2864dc; - color: #fff; -} -`] - -customElements.define('select-file-modal', SelectFileModal) - -function sortFiles (a, b) { - if (a.stat.isDirectory() && !b.stat.isDirectory()) return -1 - if (!a.stat.isDirectory() && b.stat.isDirectory()) return 1 - return a.name.localeCompare(b.name) -} - -function defined (v) { - return typeof v !== 'undefined' -} \ No newline at end of file diff --git a/app/new-shell-window/bg-process-rpc.js b/app/new-shell-window/bg-process-rpc.js deleted file mode 100644 index e59b7fbaaa..0000000000 --- a/app/new-shell-window/bg-process-rpc.js +++ /dev/null @@ -1,14 +0,0 @@ -import * as rpc from 'pauls-electron-rpc' -import browserManifest from '@beaker/core/web-apis/manifests/internal/browser' -import applicationsManifest from '@beaker/core/web-apis/manifests/internal/applications' -import bookmarksManifest from '@beaker/core/web-apis/manifests/external/bookmarks' -import watchlistManifest from '@beaker/core/web-apis/manifests/internal/watchlist' -import viewsManifest from '../background-process/rpc-manifests/views' -import datArchiveManifest from '@beaker/core/web-apis/manifests/external/dat-archive' - -export const beakerBrowser = rpc.importAPI('beaker-browser', browserManifest) -export const applications = rpc.importAPI('applications', applicationsManifest) -export const bookmarks = rpc.importAPI('bookmarks', bookmarksManifest) -export const watchlist = rpc.importAPI('watchlist', watchlistManifest) -export const views = rpc.importAPI('background-process-views', viewsManifest) -export const datArchive = rpc.importAPI('dat-archive', datArchiveManifest) \ No newline at end of file diff --git a/app/new-shell-window/navbar/site-info.js b/app/new-shell-window/navbar/site-info.js deleted file mode 100644 index ebb7cea868..0000000000 --- a/app/new-shell-window/navbar/site-info.js +++ /dev/null @@ -1,172 +0,0 @@ -/* globals customElements */ -import {LitElement, html, css} from '../../vendor/lit-element/lit-element' -import _get from 'lodash.get' -import * as bg from '../bg-process-rpc' -import { isDatHashRegex } from '../../lib/urls' -import { classMap } from '../../vendor/lit-element/lit-html/directives/class-map' -import buttonResetCSS from './button-reset.css' - -class NavbarSiteInfo extends LitElement { - static get properties () { - return { - url: {type: String}, - siteTitle: {type: String}, - datDomain: {type: String}, - isOwner: {type: Boolean}, - peers: {type: Number}, - numFollowers: {type: Number}, - loadError: {type: Object} - } - } - - constructor () { - super() - this.url = '' - this.siteTitle = '' - this.datDomain = '' - this.isOwner = false - this.peers = 0 - this.numFollowers = 0 - this.loadError = null - } - - get scheme () { - try { - return (new URL(this.url)).protocol - } catch (e) { - return '' - } - } - - get hostname () { - try { - return (new URL(this.url)).hostname - } catch (e) { - return '' - } - } - - // rendering - // = - - render () { - const scheme = this.scheme - var innerHTML - if (scheme) { - const isHttps = scheme === 'https:' - const isInsecureResponse = _get(this, 'loadError.isInsecureResponse') - if ((isHttps && !isInsecureResponse) || scheme === 'beaker:') { - innerHTML = html` - - ${this.siteTitle} - ` - } else if (scheme === 'http:') { - innerHTML = html` - - ${this.siteTitle} - ` - } else if (isHttps && isInsecureResponse) { - innerHTML = html` - - ${this.siteTitle} - ` - } else if (scheme === 'dat:') { - innerHTML = html` - - ${this.isOwner ? html` - Your Site - ` : ''} - ${this.siteTitle} - ${this.numFollowers > 0 ? html` - - ${this.numFollowers} - ` : ''} - ` - } - } - - if (!innerHTML) { - return html`` - } - - return html` - - - ` - } - - // events - // = - - async onClickButton () { - bg.views.toggleSidebar('active', 'site') - } -} -NavbarSiteInfo.styles = [buttonResetCSS, css` -:host { - display: block; -} - -button { - border-right: 1px solid #ccc; - border-radius: 0; - height: 26px; - line-height: 26px; - padding: 0 10px; -} - -button:hover { - background: #eee; -} - -button.hidden { - display: none; -} - -.fa { - font-size: 11px; - line-height: 25px; - color: gray; -} - -.fa-user { - font-size: 9px; - position: relative; - top: -1px; -} - -.fa-caret-down { - color: #adadad; - margin-left: 2px; -} - -.label { - margin-left: 2px; - margin-right: 2px; - font-variant-numeric: tabular-nums; - font-weight: 500; -} - -.label.darkbg { - background: rgba(0,0,0,.08); - padding: 2px 4px; - border-radius: 3px; - color: #444; - font-size: 10px; -} - -.secure { - color: var(--color-secure); -} - -.warning { - color: var(--color-warning); -} - -.insecure { - color: var(--color-insecure); -} -`] -customElements.define('shell-window-navbar-site-info', NavbarSiteInfo) diff --git a/app/package.json b/app/package.json index 1b1fce0886..ff57a6cf0c 100644 --- a/app/package.json +++ b/app/package.json @@ -3,99 +3,90 @@ "productName": "Beaker Browser", "description": "An Experimental Peer-to-Peer Web Browser.", "homepage": "https://beakerbrowser.com/", - "version": "0.9.0-prerelease.4", - "author": "Paul Frazee ", - "copyright": "© 2019, Blue Link Labs", - "main": "background-process.build.js", + "version": "1.0.0-prerelease.1", + "author": "Blue Link Labs ", + "copyright": "© 2020, Blue Link Labs", + "main": "main.build.js", "dependencies": { - "@beaker/core": "github:beakerbrowser/beaker-core#blue", - "@beaker/dat-archive-file-diff": "^1.0.0", - "@beaker/library-app": "^3.0.1", - "@beaker/permissions": "^1.0.1", - "@beaker/search-app": "^2.1.0", - "@beaker/sidebar-app": "^1.0.0", - "@beaker/start-app": "^3.0.0", - "anymatch": "^1.3.2", - "await-lock": "^1.1.3", + "@beaker/dat-serve-resolve-path": "^1.0.0", + "@beaker/library-tools": "^1.0.0", + "ajv": "^6.10.2", + "anymatch": "^2.0.0", + "await-lock": "^1.2.1", + "base32.js": "^0.1.0", "beaker-error-constants": "^1.4.0", "beaker-virtual-fs": "git://github.com/beakerbrowser/beaker-virtual-fs.git#a4b3112df5328e99e63e1786823b3705ab031c77", - "binary-extensions": "^1.10.0", + "binary-extensions": "^1.13.1", "builtin-pages-lib": "git://github.com/beakerbrowser/builtin-pages-lib.git#b23ed29856324d6036bc08bc19d653d98de52003", - "bytes": "^2.5.0", - "choo": "^5.6.2", + "bytes": "^3.1.0", "circular-append-file": "^1.0.1", - "co": "^4.6.0", "concat-stream": "^1.6.2", - "dat-encoding": "^4.0.2", + "dat-dns": "^3.2.1", + "dat-encoding": "^5.0.1", "data-urls": "^1.0.0", - "debug": "git://github.com/beakerbrowser/debug.git#8d79a96e2c5f79527d7580960be3da9ae246ed8e", - "diff": "^3.4.0", - "diff-file-tree": "^2.1.1", - "dns-txt": "^2.0.2", - "drag-drop": "^2.14.0", - "du": "git://github.com/beakerbrowser/node-du.git#8db9ed862712a964289d2d07e58f2d7ec543a56e", + "diff": "^3.5.0", + "diff-file-tree": "^2.3.2", + "discovery-swarm": "^6.0.0", "electron-is-accelerator": "^0.1.2", "electron-updater": "^4.0.0", "emit-stream": "^0.1.2", "emoji-regex": "^8.0.0", - "from2": "^2.3.0", - "from2-encoding": "^1.0.0", - "from2-string": "^1.1.0", - "fs-jetpack": "^0.9.0", + "fs-jetpack": "^1.3.1", + "fs-reverse": "0.0.3", "function-queue": "0.0.12", - "hyperdrive-staging-area": "^2.4.0", - "hyperdrive-to-zip-stream": "^2.0.0", - "icojs": "^0.11.0", + "hyperdrive-daemon": "^1.13.2", + "hyperdrive-daemon-client": "^1.14.3", + "icojs": "^0.12.3", "identify-filetype": "^1.0.0", "into-stream": "^3.1.0", - "json-formatter-js": "^2.2.0", "keyboardevent-from-electron-accelerator": "^1.1.0", "keyboardevents-areequal": "^0.2.2", - "listen-random-port": "^1.0.0", + "knex": "^0.17.6", + "lodash.clonedeep": "^4.5.0", "lodash.debounce": "^4.0.8", + "lodash.difference": "^4.5.0", + "lodash.differenceby": "^4.8.0", "lodash.flattendeep": "^4.4.0", "lodash.get": "^4.4.2", "lodash.groupby": "^4.6.0", "lodash.isequal": "^4.5.0", "lodash.pick": "^4.4.0", "lodash.throttle": "^4.1.1", + "lodash.uniqby": "^4.7.0", "lodash.uniqwith": "^4.5.0", "lru": "^3.1.0", - "markdown-it": "^8.4.2", + "markdown-it": "^10.0.0", "mime": "^1.4.0", "mkdirp": "^0.5.1", - "moment": "^2.23.0", - "monaco-editor": "^0.14.3", - "ms": "^2.0.0", - "multicb": "^1.2.2", - "nan": "2.14.0", + "moment": "^2.24.0", + "ms": "^2.1.2", "nat-upnp": "^1.1.1", - "normalize-url": "^1.9.1", + "normalize-url": "^3.3.0", "once": "^1.4.0", + "os-locale": "^3.1.0", "os-name": "^2.0.1", "page-metadata-parser": "^1.1.3", - "parse-dat-url": "^3.0.1", - "pauls-dat-api": "^8.0.1", - "pauls-electron-rpc": "^5.0.0", - "pauls-word-boundary": "^1.0.0", - "pify": "^2.3.0", + "pauls-dat-api2": "github:beakerbrowser/pauls-dat-api2", + "pauls-electron-rpc": "^5.0.5", + "pify": "^3.0.0", + "pm2": "^4.2.3", "pretty-bytes": "^3.0.1", "pretty-hash": "^1.0.0", - "pump": "^1.0.1", - "random-access-file": "^1.3.1", + "pump": "^3.0.0", "range-parser": "^1.2.0", - "scoped-fs": "^1.3.0", - "slugify": "^1.3.4", - "sodium-signatures": "^2.1.1", - "sortablejs": "1.7.0", + "rimraf": "^2.6.3", + "slugify": "^1.3.5", "speedometer": "^1.0.0", "split2": "^2.2.0", - "textextensions": "^2.2.0", - "through2": "^2.0.1", + "sqlite3": "^4.1.0", + "stream-throttle": "^0.1.3", + "streamx": "^2.6.0", + "textextensions": "^2.5.0", + "through2": "^2.0.5", "to-ico": "^1.1.5", - "unused-filename": "^0.1.0", - "utp-native": "^2.0.0", - "yo-yo": "^1.4.0", - "zerr": "^1.0.4" + "utp-native": "^2.1.7", + "winston": "github:winstonjs/winston#b4ced895b3e1ead8a616590189b003cfd9d7acca", + "yazl": "^2.5.1", + "yo-yo": "^1.4.0" } } diff --git a/app/perm-prompt/bg-process-rpc.js b/app/perm-prompt/bg-process-rpc.js deleted file mode 100644 index a7d6807a9b..0000000000 --- a/app/perm-prompt/bg-process-rpc.js +++ /dev/null @@ -1,8 +0,0 @@ -import * as rpc from 'pauls-electron-rpc' -import browserManifest from '@beaker/core/web-apis/manifests/internal/browser' -import datArchiveManifest from '@beaker/core/web-apis/manifests/external/dat-archive' -import permPromptManifest from '../background-process/rpc-manifests/perm-prompt' - -export const beakerBrowser = rpc.importAPI('beaker-browser', browserManifest) -export const datArchive = rpc.importAPI('dat-archive', datArchiveManifest) -export const permPrompt = rpc.importAPI('background-process-perm-prompt', permPromptManifest) \ No newline at end of file diff --git a/app/prompts/bg-process-rpc.js b/app/prompts/bg-process-rpc.js deleted file mode 100644 index 8c486b33be..0000000000 --- a/app/prompts/bg-process-rpc.js +++ /dev/null @@ -1,6 +0,0 @@ -import * as rpc from 'pauls-electron-rpc' -import promptsManifest from '../background-process/rpc-manifests/prompts' -import datArchiveManifest from '@beaker/core/web-apis/manifests/external/dat-archive' - -export const prompts = rpc.importAPI('background-process-prompts', promptsManifest) -export const datArchive = rpc.importAPI('dat-archive', datArchiveManifest) \ No newline at end of file diff --git a/app/shell-menus.js b/app/shell-menus.js deleted file mode 100644 index 4fb786d8be..0000000000 --- a/app/shell-menus.js +++ /dev/null @@ -1,98 +0,0 @@ -/* globals customElements */ -import { LitElement, html } from './vendor/lit-element/lit-element' -import * as bg from './shell-menus/bg-process-rpc' -import './shell-menus/browser' -import './shell-menus/users' -import './shell-menus/bookmark' -import './shell-menus/donate' -import './shell-menus/site-tools' -import './shell-menus/preview-mode-tools' - -class MenusWrapper extends LitElement { - static get properties () { - return { - currentMenu: {type: String} - } - } - - constructor () { - super() - this.currentParams = null - - // fetch platform information - var {platform} = bg.beakerBrowser.getInfo() - window.platform = platform - if (platform === 'darwin') { - document.body.classList.add('darwin') - } - if (platform === 'win32') { - document.body.classList.add('win32') - } - - // export interface - const reset = (name) => { - if (!name.endsWith('-menu')) name += '-menu' - try { this.shadowRoot.querySelector(name).reset() } - catch (e) { /* ignore */ } - } - const init = (name) => { - try { return this.shadowRoot.querySelector(name).init(this.currentParams) } - catch (e) { console.log(e) /* ignore */ } - } - window.openMenu = async (v, params) => { - this.currentMenu = v - this.currentParams = params - reset(`${v}-menu`) - await this.updateComplete - await init(`${v}-menu`) - } - window.reset = reset - - // global event listeners - window.addEventListener('blur', e => { - bg.shellMenus.close() - - // reset any active state - reset(`${this.currentMenu}-menu`) - - // unset the menu so that we can unrender the current - // (this stops a FOUC issue) - this.currentMenu = null - }) - window.addEventListener('keydown', e => { - if (e.key === 'Escape') { - bg.shellMenus.close() - } - }) - } - - render () { - return html`
    ${this.renderMenu()}
    ` - } - - renderMenu () { - switch (this.currentMenu) { - case 'browser': - return html`` - case 'users': - return html`` - case 'location': - return html`` - case 'bookmark': - return html`` - case 'donate': - return html`` - case 'site-tools': - return html`` - case 'preview-mode-tools': - return html`` - } - return html`
    ` - } - - onContextMenu (e) { - e.preventDefault() // disable context menu - } -} - -customElements.define('menus-wrapper', MenusWrapper) \ No newline at end of file diff --git a/app/shell-menus/bg-process-rpc.js b/app/shell-menus/bg-process-rpc.js deleted file mode 100644 index b78abb1bb5..0000000000 --- a/app/shell-menus/bg-process-rpc.js +++ /dev/null @@ -1,28 +0,0 @@ -import * as rpc from 'pauls-electron-rpc' -import browserManifest from '@beaker/core/web-apis/manifests/internal/browser' -import usersManifest from '@beaker/core/web-apis/manifests/internal/users' -import applicationsManifest from '@beaker/core/web-apis/manifests/internal/applications' -import archivesManifest from '@beaker/core/web-apis/manifests/internal/archives' -import bookmarksManifest from '@beaker/core/web-apis/manifests/external/bookmarks' -import historyManifest from '@beaker/core/web-apis/manifests/internal/history' -import sitedataManifest from '@beaker/core/web-apis/manifests/internal/sitedata' -import downloadsManifest from '@beaker/core/web-apis/manifests/internal/downloads' -import datArchiveManifest from '@beaker/core/web-apis/manifests/external/dat-archive' -import shellMenusManifest from '../background-process/rpc-manifests/shell-menus' -import viewsManifest from '../background-process/rpc-manifests/views' -import profilesManifest from '@beaker/core/web-apis/manifests/external/unwalled-garden-profiles' -import followsManifest from '@beaker/core/web-apis/manifests/external/unwalled-garden-follows' - -export const beakerBrowser = rpc.importAPI('beaker-browser', browserManifest) -export const users = rpc.importAPI('users', usersManifest) -export const applications = rpc.importAPI('applications', applicationsManifest) -export const archives = rpc.importAPI('archives', archivesManifest) -export const bookmarks = rpc.importAPI('bookmarks', bookmarksManifest) -export const history = rpc.importAPI('history', historyManifest) -export const sitedata = rpc.importAPI('sitedata', sitedataManifest) -export const downloads = rpc.importAPI('downloads', downloadsManifest) -export const datArchive = rpc.importAPI('dat-archive', datArchiveManifest) -export const shellMenus = rpc.importAPI('background-process-shell-menus', shellMenusManifest) -export const views = rpc.importAPI('background-process-views', viewsManifest) -export const profiles = rpc.importAPI('unwalled-garden-profiles', profilesManifest) -export const follows = rpc.importAPI('unwalled-garden-follows', followsManifest) \ No newline at end of file diff --git a/app/shell-menus/bookmark.js b/app/shell-menus/bookmark.js deleted file mode 100644 index 815b5c2254..0000000000 --- a/app/shell-menus/bookmark.js +++ /dev/null @@ -1,306 +0,0 @@ -/* globals customElements */ -import { LitElement, html, css } from '../vendor/lit-element/lit-element' -import * as bg from './bg-process-rpc' -import commonCSS from './common.css' -import inputsCSS from './inputs.css' -import buttonsCSS from './buttons.css' -import _pick from 'lodash.pick' -import _isEqual from 'lodash.isequal' - -class BookmarkMenu extends LitElement { - static get properties () { - return { - href: {type: String}, - title: {type: String}, - description: {type: String}, - tags: {type: String}, - pinned: {type: Boolean}, - isPublic: {type: Boolean} - } - } - - constructor () { - super() - this.reset() - } - - reset () { - this.bookmark = null - this.bookmarkIsNew = false - this.href = '' - this.title = '' - this.description = '' - this.tags = '' - this.pinned = false - this.isPublic = false - } - - async init (params) { - this.bookmarkIsNew = params.bookmarkIsNew - const b = this.bookmark = await bg.bookmarks.get(params.url) - if (b && b.tags) b.tags = tagsToString(b.tags) - if (b) { - this.href = b.href - this.title = b.title - this.description = b.description - this.tags = b.tags - this.pinned = b.pinned - this.isPublic = b.isPublic - } else { - this.href = params.url - } - await this.requestUpdate() - - // focus and highlight input - var input = this.shadowRoot.querySelector('input') - input.focus() - input.setSelectionRange(0, input.value.length) - } - - get canSave () { - if (this.bookmarkIsNew) { - return true - } - return !_isEqual( - _pick(this, ['href', 'title', 'description', 'tags', 'pinned', 'isPublic']), - _pick(this.bookmark, ['href', 'title', 'description', 'tags', 'pinned', 'isPublic']) - ) - } - - // rendering - // = - - render () { - return html` - -
    -
    - - Edit this bookmark -
    - -
    -
    - - -
    - -
    - - -
    - -
    - - -
    -
    - - - -
    - -
    - - - -
    -
    -
    - ` - } - - // events - // = - - async onSaveBookmark (e) { - e.preventDefault() - if (!this.canSave) { - return - } - - // update bookmark - var b = this.bookmark - b.href = this.href - b.title = this.title - b.description = this.description - b.tags = this.tags.split(' ').filter(Boolean) - b.isPublic = this.isPublic - b.pinned = this.pinned - await bg.bookmarks.add(b) - bg.views.refreshState('active') - bg.shellMenus.close() - } - - async onClickRemoveBookmark (e) { - var b = this.bookmark - if (!b) return - await bg.bookmarks.remove(b.href) - bg.views.refreshState('active') - bg.shellMenus.close() - } - - onChangeTitle (e) { - this.title = e.target.value - } - - onChangeDescription (e) { - this.description = e.target.value - } - - onChangeTags (e) { - this.tags = e.target.value - } - - onChangePinned (e) { - this.pinned = e.target.checked - } - - onChangePublic (e, v) { - this.isPublic = e.target.checked - } -} -BookmarkMenu.styles = [commonCSS, inputsCSS, buttonsCSS, css` -.wrapper { - box-sizing: border-box; - padding: 15px; - color: #333; - background: #fff; - height: 400px; - overflow: hidden; -} - -h3 { - font-size: 0.625rem; - text-transform: uppercase; - letter-spacing: 0.2px; - color: rgba(0, 0, 0, 0.5); - margin-bottom: 10px; -} - -.header { - display: flex; - align-items: center; - font-size: 0.875rem; - font-weight: 500; - margin-bottom: 15px; - border: 0; -} - -.fa-star { - border: none; - font-size: 24px; - margin-right: 10px; - color: transparent; - text-shadow: 0px 0px 4px rgba(255, 255, 255, 0.3); - background-color: #bbb; - -webkit-background-clip: text; -} - -form { - font-size: 13px; - margin: 0; -} - -.input-group { - display: flex; - flex-direction: column; - margin-bottom: 10px; -} - -.input-group label { - display: block; - font-size: 12px; - margin-bottom: 2px; -} - -.other-options { - margin-top: 20px; -} - -.other-options .input-group { - flex-direction: row; - align-items: center; -} - -.other-options .input-group label { - display: inline-block; - margin-right: auto; - font-weight: normal; - font-size: 13px; -} - -.other-options .toggle { - border-top: 1px solid #ddd; - background: #fafafa; - margin: 0 -20px; - padding: 12px 20px; - line-height: 1; -} - -.other-options .toggle:hover { - background: #eee; -} - -.input-group input, -.input-group textarea { - display: inline-block; - font-size: 0.725rem; -} - -.input-group textarea { - height: 50px; - padding-top: 5px; -} - -.input-group input { - height: 28px; - line-height: 28px; - color: rgba(0, 0, 0, 0.75); -} - -.buttons { - display: flex; - justify-content: flex-end; - padding: 12px 20px; - margin: 0 -20px; - border-top: 1px solid #ddd; - text-align: right; -} - -.buttons .btn { - margin-left: 5px; -} - -.buttons .btn.remove { - margin-right: auto; - margin-left: 0; -} -`] - -customElements.define('bookmark-menu', BookmarkMenu) - -// internal methods -// = - -function tagsToString (tags) { - return (tags || []).join(' ') -} diff --git a/app/shell-menus/browser.js b/app/shell-menus/browser.js deleted file mode 100644 index de0960d38f..0000000000 --- a/app/shell-menus/browser.js +++ /dev/null @@ -1,351 +0,0 @@ -/* globals customElements */ -import { LitElement, html, css } from '../vendor/lit-element/lit-element' -import { fromEventStream } from '@beaker/core/web-apis/fg/event-target' -import moment from 'moment' -import * as bg from './bg-process-rpc' -import commonCSS from './common.css' - -class BrowserMenu extends LitElement { - static get properties () { - return { - submenu: {type: String} - } - } - - constructor () { - super() - - this.browserInfo = bg.beakerBrowser.getInfo() - const isDarwin = this.browserInfo.platform === 'darwin' - const cmdOrCtrlChar = isDarwin ? '⌘' : '^' - this.accelerators = { - newWindow: cmdOrCtrlChar + 'N', - newTab: cmdOrCtrlChar + 'T', - findInPage: cmdOrCtrlChar + 'F', - history: cmdOrCtrlChar + (isDarwin ? 'Y' : 'H'), - openFile: cmdOrCtrlChar + 'O' - } - - this.submenu = '' - this.sumProgress = null // null means no active downloads - this.shouldPersistDownloadsIndicator = false - - // wire up events - var dlEvents = fromEventStream(bg.downloads.createEventsStream()) - dlEvents.addEventListener('sum-progress', this.onDownloadsSumProgress.bind(this)) - } - - reset () { - this.submenu = '' - } - - async init () { - this.profile = await bg.beakerBrowser.getUserSession().catch(err => undefined) - await this.requestUpdate() - } - - render () { - if (!this.profile) { - return html`
    ` - } - - if (this.submenu === 'create-new') { - return this.renderCreateNew() - } - - // auto-updater - var autoUpdaterEl = html`` - if (this.browserInfo && this.browserInfo.updater.isBrowserUpdatesSupported && this.browserInfo.updater.state === 'downloaded') { - autoUpdaterEl = html` -
    - -
    - ` - } - - // render the progress bar if downloading anything - var progressEl = '' - if (this.shouldPersistDownloadsIndicator && this.sumProgress && this.sumProgress.receivedBytes <= this.sumProgress.totalBytes) { - progressEl = html`` - } - - return html` - -
    - ${autoUpdaterEl} - -
    - - - - - -
    - -
    - -
    - -
    - - - - - -
    - -
    - - - -
    -
    - ` - } - - renderCreateNew () { - return html` - -
    -
    - -

    Create New

    -
    - -
    - - - - - -
    -
    ` - } - - // events - // = - - updated () { - // adjust dimensions based on rendering - var width = this.shadowRoot.querySelector('div').clientWidth - var height = this.shadowRoot.querySelector('div').clientHeight - bg.shellMenus.resizeSelf({width, height}) - } - - onShowSubmenu (v) { - this.submenu = v - } - - onOpenNewWindow () { - bg.shellMenus.createWindow() - bg.shellMenus.close() - } - - onOpenNewTab () { - bg.shellMenus.createTab() - bg.shellMenus.close() - } - - async onOpenFile () { - bg.shellMenus.close() - var files = await bg.beakerBrowser.showOpenDialog({ - title: 'Open file...', - properties: ['openFile', 'createDirectory'] - }) - if (files && files[0]) { - bg.shellMenus.createTab('file://' + files[0]) - } - } - - onClickDownloads (e) { - this.shouldPersistDownloadsIndicator = false - bg.shellMenus.createTab('beaker://downloads') - bg.shellMenus.close() - } - - onDownloadsSumProgress (sumProgress) { - this.shouldPersistDownloadsIndicator = true - this.sumProgress = sumProgress - this.requestUpdate() - } - - async onCreateSite (e) { - bg.shellMenus.close() - - // create a new archive - const url = await bg.datArchive.createArchive() - bg.beakerBrowser.openUrl(url, {setActive: true, isSidebarActive: true, sidebarPanel: 'site'}) - } - - /*async onCreateSiteFromFolder (e) { - bg.shellMenus.close() - - // ask user for folder - const folder = await bg.beakerBrowser.showOpenDialog({ - title: 'Select folder', - buttonLabel: 'Use folder', - properties: ['openDirectory'] - }) - if (!folder || !folder.length) return - - // create a new archive - const url = await bg.datArchive.createArchive({prompt: false}) - await bg.archives.setLocalSyncPath(url, folder[0], {previewMode: true}) - bg.shellMenus.createTab('beaker://editor/' + url) - bg.shellMenus.close() - }*/ - - /*async onShareFiles (e) { - bg.shellMenus.close() - - // ask user for files - const filesOnly = this.browserInfo.platform === 'linux' || this.browserInfo.platform === 'win32' - const files = await bg.beakerBrowser.showOpenDialog({ - title: 'Select files to share', - buttonLabel: 'Share files', - properties: ['openFile', filesOnly ? false : 'openDirectory', 'multiSelections'].filter(Boolean) - }) - if (!files || !files.length) return - - // create the dat and import the files - const url = await bg.datArchive.createArchive({ - title: `Shared files (${moment().format('M/DD/YYYY h:mm:ssa')})`, - description: `Files shared with Beaker`, - prompt: false - }) - await Promise.all(files.map(src => bg.datArchive.importFromFilesystem({src, dst: url, inplaceImport: false}))) - - // open the new archive in the editor - bg.shellMenus.createTab('beaker://editor/' + url) - bg.shellMenus.close() - }*/ - - onOpenPage (e, url) { - bg.shellMenus.createTab(url) - bg.shellMenus.close() - } - - onClickRestart () { - bg.shellMenus.close() - bg.beakerBrowser.restartBrowser() - } -} -BrowserMenu.styles = [commonCSS, css` -.wrapper { - width: 230px; -} - -.wrapper.twocol { - display: flex; - width: 400px; -} - -.column { - flex: 1; -} - -.column:first-child { - flex: 0 0 160px; - background: #fafafa; - border-right: 1px solid #ddd; -} - -.wrapper::-webkit-scrollbar { - display: none; -} - -.section:last-child { - border-bottom: 0; -} - -.section.auto-updater { - padding-bottom: 0; - border-bottom: 0; -} - -.menu-item.auto-updater { - height: 35px; - background: #DCEDC8; - border-top: 1px solid #c5e1a5; - border-bottom: 1px solid #c5e1a5; - color: #000; -} - -.menu-item.auto-updater i { - color: #7CB342; -} - -.menu-item.auto-updater:hover { - background: #d0e7b5; -} - -.menu-item i.more { - margin-left: auto; - padding-right: 0; - text-align: right; -} - -.menu-item .more, -.menu-item .shortcut { - color: #777; - margin-left: auto; -} - -.menu-item .shortcut { - font-size: 12px; - -webkit-font-smoothing: antialiased; -} - -.menu-item.downloads progress { - margin-left: 20px; - width: 100px; -} -`] - -customElements.define('browser-menu', BrowserMenu) \ No newline at end of file diff --git a/app/shell-menus/preview-mode-tools.js b/app/shell-menus/preview-mode-tools.js deleted file mode 100644 index afbd65131b..0000000000 --- a/app/shell-menus/preview-mode-tools.js +++ /dev/null @@ -1,117 +0,0 @@ -/* globals customElements */ -import { LitElement, html, css } from '../vendor/lit-element/lit-element' -import { classMap } from '../vendor/lit-element/lit-html/directives/class-map' -import _get from 'lodash.get' -import * as bg from './bg-process-rpc' -import commonCSS from './common.css' - -class PreviewModeToolsMenu extends LitElement { - constructor () { - super() - this.reset() - } - - reset () { - this.url = null - this.datKey = null - } - - async init (params) { - this.url = params.url - this.origin = toOrigin(this.url) - this.datKey = await bg.datArchive.resolveName(this.url) - this.hasChanges = (await bg.archives.diffLocalSyncPathListing(this.datKey, {compareContent: true, shallow: true})).length > 0 - await this.requestUpdate() - } - - // rendering - // = - - render () { - const changesCls = classMap({ - 'menu-item': true, - disabled: !this.hasChanges - }) - return html` - -
    - - -
    -
    - - Review changes -
    -
    - - Commit all changes -
    -
    - ` - } - - // events - // = - - onClickGotoPreview () { - bg.views.loadURL('active', `${this.origin}+preview`) - bg.shellMenus.close() - } - - onClickGotoLive () { - bg.views.loadURL('active', `${this.origin}`) - bg.shellMenus.close() - } - - onClickGotoReview () { - bg.views.loadURL('active', `beaker://editor/${this.origin}`) - bg.shellMenus.close() - } - - async onClickCommit () { - var url = this.url - var datKey = this.datKey - - if (!confirm('Commit all changes?')) { - bg.shellMenus.close() - return - } - - var currentDiff = await bg.archives.diffLocalSyncPathListing(datKey, {compareContent: true, shallow: true}) - var paths = fileDiffsToPaths(currentDiff) - await bg.archives.publishLocalSyncPathListing(datKey, {shallow: false, paths}) - - bg.views.loadURL('active', url) // reload the page - bg.shellMenus.close() - } -} -PreviewModeToolsMenu.styles = [commonCSS, css` -.wrapper { - padding: 4px 0; -} -`] - -customElements.define('preview-mode-tools-menu', PreviewModeToolsMenu) - -function toOrigin (url) { - try { - let urlp = new URL(url) - return `${urlp.protocol}//${urlp.hostname}`.replace('+preview', '') - } catch (e) { - return url - } -} - - -function fileDiffsToPaths (filediff) { - return filediff.map(d => { - if (d.type === 'dir') return d.path + '/' // indicate that this is a folder - return d.path - }) -} diff --git a/app/shell-menus/site-tools.js b/app/shell-menus/site-tools.js deleted file mode 100644 index a8cbf5b199..0000000000 --- a/app/shell-menus/site-tools.js +++ /dev/null @@ -1,183 +0,0 @@ -/* globals customElements */ -import { LitElement, html, css } from '../vendor/lit-element/lit-element' -import _get from 'lodash.get' -import * as bg from './bg-process-rpc' -import {writeToClipboard} from '../lib/fg/event-handlers' -import commonCSS from './common.css' - -class SiteToolsMenu extends LitElement { - static get properties () { - return { - submenu: {type: String} - } - } - - constructor () { - super() - this.reset() - } - - reset () { - this.tabState = null - this.submenu = '' - } - - get datInfo () { - if (!this.tabState) return null - return this.tabState.datInfo - } - - get isDat () { - return !!this.datInfo - } - - get isSaved () { - return this.datInfo && this.datInfo.userSettings && this.datInfo.userSettings.isSaved - } - - async init (params) { - this.tabState = await bg.views.getTabState('active', {datInfo: true}) - await this.requestUpdate() - } - - // rendering - // = - - render () { - if (this.submenu === 'devtools') { - return html` - -
    -
    - -

    Developer tools

    -
    -
    - - -
    - ` - } - return html` - -
    - ${this.isDat ? html` - ${this.isSaved ? html` - - ` : html` - - `} - -
    - -
    - - ` : ''} - - -
    - ` - } - - // events - // = - - updated () { - // adjust height based on rendering - var height = this.shadowRoot.querySelector('div').clientHeight - bg.shellMenus.resizeSelf({height}) - } - - onShowSubmenu (v) { - this.submenu = v - } - - async onToggleSaved () { - if (this.isSaved) { - await bg.archives.remove(this.datInfo.url) - } else { - await bg.archives.add(this.datInfo.url) - } - bg.shellMenus.close() - } - - async onClickViewFiles () { - await bg.shellMenus.createTab(`beaker://library/?view=files&dat=${encodeURIComponent(`dat://${this.datInfo.key}`)}`) - bg.shellMenus.close() - } - - async onClickViewSource () { - await bg.views.toggleSidebar('active', 'editor') - bg.shellMenus.close() - } - - async onClickFork () { - const forkUrl = await bg.datArchive.forkArchive(this.datInfo.key, {prompt: true}).catch(() => false) - if (forkUrl) { - bg.shellMenus.loadURL(`beaker://editor/${forkUrl}`) - } - bg.shellMenus.close() - } - - async onToggleLiveReloading () { - await bg.views.toggleLiveReloading('active') - bg.shellMenus.close() - } - - async onClickDownloadZip () { - bg.beakerBrowser.downloadURL(`dat://${this.datInfo.key}?download_as=zip`) - bg.shellMenus.close() - } - - async onToggleDevtools () { - await bg.views.toggleDevTools('active') - bg.shellMenus.close() - } - - async onClickSavePage () { - bg.beakerBrowser.downloadURL(this.tabState.url) - bg.shellMenus.close() - } - - async onClickPrint () { - bg.views.print('active') - bg.shellMenus.close() - } -} -SiteToolsMenu.styles = [commonCSS, css` -.wrapper { - padding: 4px 0; -} -`] - -customElements.define('site-tools-menu', SiteToolsMenu) diff --git a/app/shell-menus/users.js b/app/shell-menus/users.js deleted file mode 100644 index c9467072ef..0000000000 --- a/app/shell-menus/users.js +++ /dev/null @@ -1,216 +0,0 @@ -/* globals customElements */ -import { LitElement, html, css } from '../vendor/lit-element/lit-element' -import _get from 'lodash.get' -import * as bg from './bg-process-rpc' -import { writeToClipboard } from '../lib/fg/event-handlers' -import commonCSS from './common.css' - -class UsersMenu extends LitElement { - static get properties () { - return { - submenu: {type: String} - } - } - - constructor () { - super() - this.reset() - } - - reset () { - this.user = null - this.users = [] - this.submenu = '' - } - - async init (params) { - this.user = await bg.users.getCurrent().catch(err => undefined) - this.users = await bg.users.list() - console.log(this.user, this.users) - await this.requestUpdate() - } - - // rendering - // = - - render () { - if (!this.user) return html`
    ` - if (this.submenu === 'switch-user') { - return this.renderSwitchUser() - } - return html` - -
    - - -
    - - - - - - -
    - - -
    -
    - ` - } - - renderSwitchUser () { - return html` - -
    -
    - -

    Switch user

    -
    - -
    - - ${this.users.map(user => html` - - `)} - -
    - - - -
    ` - } - - // events - // = - - updated () { - // adjust dimensions based on rendering - var height = (this.shadowRoot.querySelector('div').clientHeight|0) - if (height) { - bg.shellMenus.resizeSelf({height}) - } - } - - onShowSubmenu (v) { - this.submenu = v - } - - onOpenPage (e, url) { - bg.shellMenus.createTab(url) - bg.shellMenus.close() - } - - onCopyMyAddress (e) { - writeToClipboard(this.user.url) - bg.shellMenus.close() - } - - async onEditMyProfile (e) { - var userUrl = this.user.url - bg.shellMenus.close() - var opts = await bg.shellMenus.createModal('user', this.user) - await bg.users.edit(userUrl, opts) - } - - onOpenUser (e, user) { - bg.shellMenus.createWindow({userSession: user}) - bg.shellMenus.close() - } - - async onCreateNewUser () { - bg.shellMenus.close() - var opts = await bg.shellMenus.createModal('user', {}) - var user = await bg.users.create(opts) - bg.shellMenus.createWindow({userSession: {url: user.url}}) - } - - async onCreateTemporaryUser () { - bg.shellMenus.close() - var user = await bg.users.createTemporary() - bg.shellMenus.createWindow({userSession: {url: user.url, isTemporary: true}}) - } -} -UsersMenu.styles = [commonCSS, css` -.wrapper { - padding: 4px 0; -} - -.menu-item { - height: 40px; -} - -.menu-item i { - margin: 0; - padding-right: 8px; -} - -.menu-item.user { - display: flex; - align-items: center; - font-size: 15px; - font-weight: 400; - height: 60px; -} - -.menu-item.user img { - margin-right: 14px; - height: 40px; - width: 40px; - border-radius: 50%; -} - -.menu-item.user .user-label { - font-size: 12px; -} - -.menu-item.current-user { - height: 76px; - border-bottom: 1px solid #ccc; - cursor: default; - font-size: 18px; -} - -.menu-item.current-user:hover { - background: inherit; -} - -.menu-item.current-user img { - height: 48px; - width: 48px; -} -`] - -customElements.define('users-menu', UsersMenu) diff --git a/app/shell-window.js b/app/shell-window.js deleted file mode 100644 index 8522f128f1..0000000000 --- a/app/shell-window.js +++ /dev/null @@ -1,143 +0,0 @@ -/* globals customElements */ - -import { ipcRenderer } from 'electron' -import { LitElement, html } from './vendor/lit-element/lit-element' -import * as bg from './new-shell-window/bg-process-rpc' -import { fromEventStream } from '@beaker/core/web-apis/fg/event-target' -import './new-shell-window/win32' -import './new-shell-window/tabs' -import './new-shell-window/navbar' - -// setup -document.addEventListener('DOMContentLoaded', () => { - ipcRenderer.send('shell-window:ready') -}) - -class ShellWindowUI extends LitElement { - static get properties () { - return { - tabs: {type: Array}, - isUpdateAvailable: {type: Boolean}, - numWatchlistNotifications: {type: Number}, - userUrl: {type: String}, - userThumbUrl: {type: String}, - isFullscreen: {type: Boolean} - } - } - - constructor () { - super() - this.tabs = [] - this.isUpdateAvailable = false - this.numWatchlistNotifications = 0 - this.userUrl = '' - this.userThumbUrl = 'asset:thumb:default' - this.isFullscreen = false - this.activeTabIndex = -1 - - // fetch platform information - var {platform} = bg.beakerBrowser.getInfo() - window.platform = platform - if (platform === 'darwin') { - document.body.classList.add('darwin') - } - if (platform === 'win32') { - document.body.classList.add('win32') - } - - // handle drag/drop of files - window.addEventListener('drop', onDragDrop, false) - function onDragDrop (event) { - var files = Array.from(event.dataTransfer.files).slice(0, 10) - var setActive = true - for (let file of files) { - bg.views.createTab(`file://${file.path}`, {setActive}) - setActive = false - } - } - - // listen to state updates to the window's tabs states - var viewEvents = fromEventStream(bg.views.createEventStream()) - viewEvents.addEventListener('replace-state', ({tabs, isFullscreen}) => { - this.tabs = tabs - this.isFullscreen = isFullscreen - this.stateHasChanged() - }) - viewEvents.addEventListener('update-state', ({index, state}) => { - if (this.tabs[index]) { - Object.assign(this.tabs[index], state) - } - this.stateHasChanged() - }) - - // listen to state updates on the auto-updater - var browserEvents = fromEventStream(bg.beakerBrowser.createEventsStream()) - browserEvents.addEventListener('updater-state-changed', this.onUpdaterStateChange.bind(this)) - browserEvents.addEventListener('user-thumb-changed', this.onUserThumbChanged.bind(this)) - - // listen to state updates on the watchlist - var wlEvents = fromEventStream(bg.watchlist.createEventsStream()) - wlEvents.addEventListener('resolved', () => { - this.numWatchlistNotifications++ - }) - - // fetch initial tab state - bg.views.getState().then(state => { - this.tabs = state - this.stateHasChanged() - }) - this.isUpdateAvailable = bg.beakerBrowser.getInfo().updater.state === 'downloaded' - bg.beakerBrowser.getUserSession().then(user => { - this.userUrl = user.url - this.userThumbUrl = `asset:thumb:${user.url}` - }) - } - - get activeTab () { - return this.tabs[this.activeTabIndex] - } - - stateHasChanged () { - // update active index - this.activeTabIndex = this.tabs.findIndex(tab => tab.isActive) - - this.requestUpdate() - this.shadowRoot.querySelector('shell-window-tabs').requestUpdate() - if (this.activeTab) { - this.shadowRoot.querySelector('shell-window-navbar').requestUpdate() - } - } - - // rendering - // = - - render () { - return html` - - - - ` - } - - // event handlers - // = - - onUpdaterStateChange (e) { - this.isUpdateAvailable = (e && e.state === 'downloaded') - } - - onUserThumbChanged (e) { - if (e.url === this.userUrl) { - this.userThumbUrl = `asset:thumb:${e.url}?cache=${Date.now()}` - } - } -} - -customElements.define('shell-window', ShellWindowUI) \ No newline at end of file diff --git a/app/userland/README.md b/app/userland/README.md new file mode 100644 index 0000000000..d07f5efb9a --- /dev/null +++ b/app/userland/README.md @@ -0,0 +1,9 @@ +# Userland + +This folder contains frontend (aka "fg") code which is executed in the same environment as any userland page. This means that, unlike the code in `/app/fg`, the standard Web APIs are available (due to `webview-preload.js` being injected). + +Each folder in `/app/userland` is hosted at its own domain under `beaker://`. You can think of each folder being its own app. (Note: the "viewer" app contains multiple sub apps.) + +The `beaker://app-stdlib` contains a number of components which are reused across userland apps. When possible, userland apps are not built. A build-step is used when the userland app needs to share code with beaker's internal code, as is the case for "library" and "site-info." + +Generally speaking, every app in `/app/userland` should be a candidate to be moved into hyperdrive. If there's no chance an app be may moved into a hyperdrive, it should probably be put into `/app/fg`. \ No newline at end of file diff --git a/app/userland/app-stdlib/.gitignore b/app/userland/app-stdlib/.gitignore new file mode 100644 index 0000000000..2507223a7f --- /dev/null +++ b/app/userland/app-stdlib/.gitignore @@ -0,0 +1,2 @@ +.datignore +.DS_STORE diff --git a/app/userland/app-stdlib/css/buttons.css b/app/userland/app-stdlib/css/buttons.css new file mode 100644 index 0000000000..6a88d0d181 --- /dev/null +++ b/app/userland/app-stdlib/css/buttons.css @@ -0,0 +1,221 @@ +@import "./reset.css"; +@import "./colors.css"; + +.link { + color: var(--blue); +} + +.link:hover { + text-decoration: underline; +} + +.btn.nofocus, +.btn[disabled="disabled"], +.btn.disabled, +.btn:disabled { + outline: 0; + box-shadow: none; +} + +.btn { + display: inline-block; + height: 30px; + padding: 0 10px; + border: 1px solid #ddd; + background: #fafafa; + border-radius: 2px; + color: var(--color-text); + font-size: 13px; + line-height: 26px; + letter-spacing: 0.25px; + font-weight: 400; + cursor: pointer; + text-decoration: none; +} + +.btn.small { + height: 24px; + line-height: 20px; +} + +.btn.small * { + vertical-align: top; + line-height: 20px; +} + +.btn.plain { + background: none; + border: none; + color: var(--color-text--muted); + line-height: 28px; + padding: 0 3px; +} + +.btn.plain:hover { + color: var(--color-text); + background: none; +} + +.btn.plain:focus { + box-shadow: none; +} + +.btn i { + line-height: 100%; + line-height: 30px; + vertical-align: middle; +} + +.btn i:last-child { + margin-left: 2px; + margin-right: 0; +} + +.btn i:first-child { + margin-left: 0; + margin-right: 2px; +} + +.btn i:first-child:last-child { + margin-left: 0; + margin-right: 0; +} + +.btn:focus { + outline: none; + box-shadow: 1px solid var(--color-focus-box-shadow); +} + +.btn.full-width { + width: 100%; +} + +.btn.center { + text-align: center; +} + +.btn.thick { + font-size: 14px; + font-weight: normal; + height: 35px; + line-height: 32px; + padding: 0 12px; +} + +.btn.pressed { + box-shadow: inset 0px 0 5px rgba(0, 0, 0, 0.1); + background: linear-gradient(to top, #ddd, #ccc); +} + +.btn.pressed:hover { + box-shadow: inset 0px 0 2px rgba(0, 0, 0, 0.1); + background: linear-gradient(to top, #ddd, #ccc); + cursor: default; +} + +.btn:hover { + text-decoration: none; + background: #eee; +} + +.btn[disabled="disabled"], +.btn.disabled, +.btn:disabled { + cursor: default !important; + background: #fafafa !important; + color: rgba(0, 0, 0, 0.4) !important; + border: 1px solid #eee !important; + font-weight: 400 !important; + -webkit-font-smoothing: initial !important; +} + +.btn[disabled="disabled"] .spinner, +.btn.disabled .spinner, +.btn:disabled .spinner { + color: #aaa !important; +} + +.btn[disabled="disabled"]:hover, +.btn.disabled:hover, +.btn:disabled:hover { + background: #fafafa; +} + +.btn[disabled="disabled"] *, +.btn.disabled *, +.btn:disabled * { + cursor: default !important; +} + +.btn .spinner { + display: inline-block; + position: relative; + top: 1px; + color: inherit; +} + +.btn.warning { + color: #fff; + background: #cc2f26; + border-color: #cc2f26; +} + +.btn.warning.pressed, +.btn.warning:hover { + background: #c42d25; + border-color: #c42d25; +} + +.btn.success { + background: #41bb56; + color: #fff; + border-color: #41bb56; +} + +.btn.success.pressed, +.btn.success:hover { + background: #3baa4e; + border-color: #3baa4e; +} + +.btn.transparent { + border-color: transparent; + background: none; + font-weight: 400; +} + +.btn.transparent:hover { + background: rgba(0, 0, 0, 0.075); + color: #424242; +} + +.btn.transparent.disabled { + border-color: transparent !important; + background: none !important; +} + +.btn.transparent.pressed { + background: linear-gradient(to top, #f5f3f3, #ececec); + border-color: #dadada; +} + +.btn.primary { + background: #2864dc; + color: #fff; + border: 1px solid #2864dc; + transition: background 0.1s ease; +} + +.btn.primary.pressed { + box-shadow: inset 0px 0 5px rgba(0, 0, 0, 0.25); +} + +.btn.primary:hover { + background: #2357bf; +} + +.btn.nofocus:focus, +button.nofocus:focus { + outline: 0; + box-shadow: none; +} diff --git a/app/userland/app-stdlib/css/buttons.css.js b/app/userland/app-stdlib/css/buttons.css.js new file mode 100644 index 0000000000..331678762e --- /dev/null +++ b/app/userland/app-stdlib/css/buttons.css.js @@ -0,0 +1,228 @@ +import {css} from '../vendor/lit-element/lit-element.js' +import resetcss from './reset.css.js' +import colorscss from './colors.css.js' +const cssStr = css` +${resetcss} +${colorscss} + +.link { + color: var(--blue); +} + +.link:hover { + text-decoration: underline; +} + +.btn.nofocus, +.btn[disabled="disabled"], +.btn.disabled, +.btn:disabled { + outline: 0; + box-shadow: none; +} + +.btn { + display: inline-block; + height: 30px; + padding: 0 10px; + border: 1px solid #ddd; + background: #fafafa; + border-radius: 2px; + color: var(--color-text); + font-size: 13px; + line-height: 26px; + letter-spacing: 0.25px; + font-weight: 400; + cursor: pointer; + text-decoration: none; +} + +.btn.small { + height: 24px; + line-height: 20px; +} + +.btn.small * { + vertical-align: top; + line-height: 20px; +} + +.btn.plain { + background: none; + border: none; + color: var(--color-text--muted); + line-height: 28px; + padding: 0 3px; +} + +.btn.plain:hover { + color: var(--color-text); + background: none; +} + +.btn.plain:focus { + box-shadow: none; +} + +.btn i { + line-height: 100%; + line-height: 30px; + vertical-align: middle; +} + +.btn i:last-child { + margin-left: 2px; + margin-right: 0; +} + +.btn i:first-child { + margin-left: 0; + margin-right: 2px; +} + +.btn i:first-child:last-child { + margin-left: 0; + margin-right: 0; +} + +.btn:focus { + outline: none; + box-shadow: 1px solid var(--color-focus-box-shadow); +} + +.btn.full-width { + width: 100%; +} + +.btn.center { + text-align: center; +} + +.btn.thick { + font-size: 14px; + font-weight: normal; + height: 35px; + line-height: 32px; + padding: 0 12px; +} + +.btn.pressed { + box-shadow: inset 0px 0 5px rgba(0, 0, 0, 0.1); + background: linear-gradient(to top, #ddd, #ccc); +} + +.btn.pressed:hover { + box-shadow: inset 0px 0 2px rgba(0, 0, 0, 0.1); + background: linear-gradient(to top, #ddd, #ccc); + cursor: default; +} + +.btn:hover { + text-decoration: none; + background: #eee; +} + +.btn[disabled="disabled"], +.btn.disabled, +.btn:disabled { + cursor: default !important; + background: #fafafa !important; + color: rgba(0, 0, 0, 0.4) !important; + border: 1px solid #eee !important; + font-weight: 400 !important; + -webkit-font-smoothing: initial !important; +} + +.btn[disabled="disabled"] .spinner, +.btn.disabled .spinner, +.btn:disabled .spinner { + color: #aaa !important; +} + +.btn[disabled="disabled"]:hover, +.btn.disabled:hover, +.btn:disabled:hover { + background: #fafafa; +} + +.btn[disabled="disabled"] *, +.btn.disabled *, +.btn:disabled * { + cursor: default !important; +} + +.btn .spinner { + display: inline-block; + position: relative; + top: 1px; + color: inherit; +} + +.btn.warning { + color: #fff; + background: #cc2f26; + border-color: #cc2f26; +} + +.btn.warning.pressed, +.btn.warning:hover { + background: #c42d25; + border-color: #c42d25; +} + +.btn.success { + background: #41bb56; + color: #fff; + border-color: #41bb56; +} + +.btn.success.pressed, +.btn.success:hover { + background: #3baa4e; + border-color: #3baa4e; +} + +.btn.transparent { + border-color: transparent; + background: none; + font-weight: 400; +} + +.btn.transparent:hover { + background: rgba(0, 0, 0, 0.075); + color: #424242; +} + +.btn.transparent.disabled { + border-color: transparent !important; + background: none !important; +} + +.btn.transparent.pressed { + background: linear-gradient(to top, #f5f3f3, #ececec); + border-color: #dadada; +} + +.btn.primary { + background: #2864dc; + color: #fff; + border: 1px solid #2864dc; + transition: background 0.1s ease; +} + +.btn.primary.pressed { + box-shadow: inset 0px 0 5px rgba(0, 0, 0, 0.25); +} + +.btn.primary:hover { + background: #2357bf; +} + +.btn.nofocus:focus, +button.nofocus:focus { + outline: 0; + box-shadow: none; +} + +` +export default cssStr diff --git a/app/userland/app-stdlib/css/buttons2.css b/app/userland/app-stdlib/css/buttons2.css new file mode 100644 index 0000000000..066f249a23 --- /dev/null +++ b/app/userland/app-stdlib/css/buttons2.css @@ -0,0 +1,142 @@ +/** + * New button styles + * We should replace buttons.css with this + */ +@import "./colors.css"; + +button { + background: #fff; + border: 1px solid var(--border-color); + border-radius: 3px; + box-shadow: 0 1px 1px rgba(0,0,0,.05); + padding: 5px 10px; + color: #333; + outline: 0; + cursor: pointer; +} + +button:hover { + background: #f5f5f5; +} + +button:active { + background: #eee; +} + +button.big { + padding: 6px 12px; +} + +button.block { + display: block; + width: 100%; +} + +button.pressed { + box-shadow: inset 0 1px 1px rgba(0,0,0,.5); + background: #6d6d79; + color: rgba(255,255,255,1); + border-color: transparent; + border-radius: 4px; +} + +button.primary { + background: #5289f7; + border-color: var(--blue); + color: #fff; + box-shadow: 0 1px 1px rgba(0,0,0,.1); +} + +button.primary:hover { + background: rgb(73, 126, 234); +} + +button.gray { + background: #fafafa; +} + +button.gray:hover { + background: #f5f5f5; +} + +button[disabled] { + border-color: var(--border-color) !important; + background: #fff !important; + color: #999 !important; + cursor: default !important; +} + +button.rounded { + border-radius: 16px; +} + +button.flat { + box-shadow: none; +} + +button.noborder { + border-color: transparent; +} + +button.transparent { + background: transparent; + border-color: transparent; + box-shadow: none; +} + +button.transparent[disabled] { + border-color: transparent !important; +} + +button.transparent:hover { + background: #f5f5fa; +} + +button.transparent.pressed { + background: rgba(0,0,0,.1); + box-shadow: inset 0 1px 2px rgba(0,0,0,.25); + color: inherit; +} + +.radio-group button { + background: transparent; + border: 0; + box-shadow: none; +} + +.radio-group button.pressed { + background: #6d6d79; + border-radius: 30px; +} + +.btn-group { + display: inline-flex; +} + +.btn-group button { + border-radius: 0; + border-right-width: 0; +} + +.btn-group button:first-child { + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; +} + +.btn-group button:last-child { + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; + border-right-width: 1px; +} + +.btn-group.rounded button:first-child { + border-top-left-radius: 14px; + border-bottom-left-radius: 14px; + padding-left: 14px; +} + +.btn-group.rounded button:last-child { + border-top-right-radius: 14px; + border-bottom-right-radius: 14px; + padding-right: 14px; +} \ No newline at end of file diff --git a/app/userland/app-stdlib/css/buttons2.css.js b/app/userland/app-stdlib/css/buttons2.css.js new file mode 100644 index 0000000000..e80e174933 --- /dev/null +++ b/app/userland/app-stdlib/css/buttons2.css.js @@ -0,0 +1,147 @@ +import {css} from '../vendor/lit-element/lit-element.js' +import colorscss from './colors.css.js' +const cssStr = css` +/** + * New button styles + * We should replace buttons.css with this + */ +${colorscss} + +button { + background: #fff; + border: 1px solid var(--border-color); + border-radius: 3px; + box-shadow: 0 1px 1px rgba(0,0,0,.05); + padding: 5px 10px; + color: #333; + outline: 0; + cursor: pointer; +} + +button:hover { + background: #f5f5f5; +} + +button:active { + background: #eee; +} + +button.big { + padding: 6px 12px; +} + +button.block { + display: block; + width: 100%; +} + +button.pressed { + box-shadow: inset 0 1px 1px rgba(0,0,0,.5); + background: #6d6d79; + color: rgba(255,255,255,1); + border-color: transparent; + border-radius: 4px; +} + +button.primary { + background: #5289f7; + border-color: var(--blue); + color: #fff; + box-shadow: 0 1px 1px rgba(0,0,0,.1); +} + +button.primary:hover { + background: rgb(73, 126, 234); +} + +button.gray { + background: #fafafa; +} + +button.gray:hover { + background: #f5f5f5; +} + +button[disabled] { + border-color: var(--border-color) !important; + background: #fff !important; + color: #999 !important; + cursor: default !important; +} + +button.rounded { + border-radius: 16px; +} + +button.flat { + box-shadow: none; +} + +button.noborder { + border-color: transparent; +} + +button.transparent { + background: transparent; + border-color: transparent; + box-shadow: none; +} + +button.transparent[disabled] { + border-color: transparent !important; +} + +button.transparent:hover { + background: #f5f5fa; +} + +button.transparent.pressed { + background: rgba(0,0,0,.1); + box-shadow: inset 0 1px 2px rgba(0,0,0,.25); + color: inherit; +} + +.radio-group button { + background: transparent; + border: 0; + box-shadow: none; +} + +.radio-group button.pressed { + background: #6d6d79; + border-radius: 30px; +} + +.btn-group { + display: inline-flex; +} + +.btn-group button { + border-radius: 0; + border-right-width: 0; +} + +.btn-group button:first-child { + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; +} + +.btn-group button:last-child { + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; + border-right-width: 1px; +} + +.btn-group.rounded button:first-child { + border-top-left-radius: 14px; + border-bottom-left-radius: 14px; + padding-left: 14px; +} + +.btn-group.rounded button:last-child { + border-top-right-radius: 14px; + border-bottom-right-radius: 14px; + padding-right: 14px; +} +` +export default cssStr diff --git a/app/userland/app-stdlib/css/colors.css b/app/userland/app-stdlib/css/colors.css new file mode 100644 index 0000000000..936b02019a --- /dev/null +++ b/app/userland/app-stdlib/css/colors.css @@ -0,0 +1,22 @@ +body { + /* common simple colors */ + --red: rgb(255, 59, 48); + --orange: rgb(255, 149, 0); + --yellow: rgb(255, 204, 0); + --lime: #E6EE9C; + --green: rgb(76, 217, 100); + --teal: rgb(90, 200, 250); + --blue: #2864dc; + --purple: rgb(88, 86, 214); + --pink: rgb(255, 45, 85); + + /* common element colors */ + --color-text: #333; + --color-text--muted: gray; + --color-text--light: #aaa; + --color-text--dark: #111; + --color-link: #295fcb; + --color-focus-box-shadow: rgba(41, 95, 203, 0.8); + --border-color: #d4d7dc; + --light-border-color: #e4e7ec; +} \ No newline at end of file diff --git a/app/userland/app-stdlib/css/colors.css.js b/app/userland/app-stdlib/css/colors.css.js new file mode 100644 index 0000000000..edc95319ca --- /dev/null +++ b/app/userland/app-stdlib/css/colors.css.js @@ -0,0 +1,27 @@ +import {css} from '../vendor/lit-element/lit-element.js' + +const cssStr = css` +body { + /* common simple colors */ + --red: rgb(255, 59, 48); + --orange: rgb(255, 149, 0); + --yellow: rgb(255, 204, 0); + --lime: #E6EE9C; + --green: rgb(76, 217, 100); + --teal: rgb(90, 200, 250); + --blue: #2864dc; + --purple: rgb(88, 86, 214); + --pink: rgb(255, 45, 85); + + /* common element colors */ + --color-text: #333; + --color-text--muted: gray; + --color-text--light: #aaa; + --color-text--dark: #111; + --color-link: #295fcb; + --color-focus-box-shadow: rgba(41, 95, 203, 0.8); + --border-color: #d4d7dc; + --light-border-color: #e4e7ec; +} +` +export default cssStr diff --git a/app/userland/app-stdlib/css/com/autocomplete.css b/app/userland/app-stdlib/css/com/autocomplete.css new file mode 100644 index 0000000000..b8399ff1c2 --- /dev/null +++ b/app/userland/app-stdlib/css/com/autocomplete.css @@ -0,0 +1,76 @@ +.autocomplete-container { + position: relative; + width: 100%; +} + +.autocomplete-results { + position: absolute; + left: 0; + z-index: 5; + width: 100%; + margin-bottom: 10px; + overflow: hidden; + background: #fff; + border-radius: 4px; + border: 1px solid #ddd; + box-shadow: 0 6px 20px rgba(0,0,0,.05); +} + +.autocomplete-result-group { + margin-bottom: 6px; +} + +.autocomplete-result-group-title { + padding: 4px 10px; + border-bottom: 1px solid #ddd; + color: rgba(0,0,0,.5); +} + +.autocomplete-result { + display: flex; + flex-wrap: nowrap; + align-items: center; + height: 40px; + padding: 0 10px; + border-left: 3px solid transparent; + cursor: pointer; +} + +.autocomplete-result .icon { + width: 24px; + height: 24px; + text-align: center; + margin-right: 10px; +} + +.autocomplete-result .icon.rounded { + border-radius: 50%; + object-fit: cover; +} + +.autocomplete-result .title, +.autocomplete-result .label { + white-space: pre; + overflow: hidden; + text-overflow: ellipsis; +} + +.autocomplete-result .title { + margin-right: 5px; + flex: auto 0; +} + +.autocomplete-result .label { + color: rgba(0,0,0,.475); + flex: 1; +} + +.autocomplete-result:hover { + background: #f7f7f7; + border-color: #ddd; +} + +.autocomplete-result.active { + background: rgba(40, 100, 220, 0.07); + border-color: #2864dc; +} \ No newline at end of file diff --git a/app/userland/app-stdlib/css/com/autocomplete.css.js b/app/userland/app-stdlib/css/com/autocomplete.css.js new file mode 100644 index 0000000000..63b5979c68 --- /dev/null +++ b/app/userland/app-stdlib/css/com/autocomplete.css.js @@ -0,0 +1,81 @@ +import {css} from '../../vendor/lit-element/lit-element.js' + +const cssStr = css` +.autocomplete-container { + position: relative; + width: 100%; +} + +.autocomplete-results { + position: absolute; + left: 0; + z-index: 5; + width: 100%; + margin-bottom: 10px; + overflow: hidden; + background: #fff; + border-radius: 4px; + border: 1px solid #ddd; + box-shadow: 0 6px 20px rgba(0,0,0,.05); +} + +.autocomplete-result-group { + margin-bottom: 6px; +} + +.autocomplete-result-group-title { + padding: 4px 10px; + border-bottom: 1px solid #ddd; + color: rgba(0,0,0,.5); +} + +.autocomplete-result { + display: flex; + flex-wrap: nowrap; + align-items: center; + height: 40px; + padding: 0 10px; + border-left: 3px solid transparent; + cursor: pointer; +} + +.autocomplete-result .icon { + width: 24px; + height: 24px; + text-align: center; + margin-right: 10px; +} + +.autocomplete-result .icon.rounded { + border-radius: 50%; + object-fit: cover; +} + +.autocomplete-result .title, +.autocomplete-result .label { + white-space: pre; + overflow: hidden; + text-overflow: ellipsis; +} + +.autocomplete-result .title { + margin-right: 5px; + flex: auto 0; +} + +.autocomplete-result .label { + color: rgba(0,0,0,.475); + flex: 1; +} + +.autocomplete-result:hover { + background: #f7f7f7; + border-color: #ddd; +} + +.autocomplete-result.active { + background: rgba(40, 100, 220, 0.07); + border-color: #2864dc; +} +` +export default cssStr diff --git a/app/userland/app-stdlib/css/com/dropdown.css b/app/userland/app-stdlib/css/com/dropdown.css new file mode 100644 index 0000000000..001b10d7a5 --- /dev/null +++ b/app/userland/app-stdlib/css/com/dropdown.css @@ -0,0 +1,233 @@ +.dropdown { + position: relative; +} + +.dropdown.open .toggleable:not(.primary) { + background: #dadada; + box-shadow: inset 0 0 3px rgba(0, 0, 0, 0.1); + border-color: transparent; + outline: 0; +} + +.toggleable-container .dropdown-items { + display: none; +} + +.toggleable-container.hover:hover .dropdown-items, +.toggleable-container.open .dropdown-items { + display: block; +} + +.dropdown-items { + width: 270px; + position: absolute; + right: 0px; + z-index: 3000; + background: #fff; + border: 1px solid #dadada; + border-radius: 0px; + box-shadow: 0 2px 15px rgba(0, 0, 0, 0.3); + overflow: hidden; +} + +.dropdown-items .section { + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + padding: 5px 0; +} + +.dropdown-items .section-header { + padding: 2px 10px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.dropdown-items .section-header.light { + color: var(--color-text--light); + font-weight: 500; +} + +.dropdown-items .section-header.small { + font-size: 12px; +} + +.dropdown-items hr { + border: 0; + border-bottom: 1px solid #ddd; +} + +.dropdown-items.thin { + width: 170px; +} + +.dropdown-items.wide { + width: 400px; +} + +.dropdown-items.compact .dropdown-item { + padding: 2px 15px; + border-bottom: 0; +} + +.dropdown-items.compact .description { + margin-left: 0; +} + +.dropdown-items.compact hr { + margin: 5px 0; +} + +.dropdown-items.roomy .dropdown-item { + padding: 10px 15px; +} + +.dropdown-items.very-roomy .dropdown-item { + padding: 20px 30px; +} + +.dropdown-items.no-border .dropdown-item { + border-bottom: 0; +} + +.dropdown-items.center { + left: 50%; + right: unset; + transform: translateX(-50%); +} + +.dropdown-items.left { + right: initial; + left: 0; +} + +.dropdown-items.over { + top: 0; +} + +.dropdown-items.top { + bottom: calc(100% + 5px); +} + +.dropdown-items.with-triangle:before { + content: ''; + position: absolute; + top: -8px; + right: 10px; + width: 12px; + height: 12px; + z-index: 3; + width: 0; + height: 0; + border-left: 8px solid transparent; + border-right: 8px solid transparent; + border-bottom: 8px solid #fff; +} + +.dropdown-items.with-triangle.left:before { + left: 10px; +} + +.dropdown-items.with-triangle.center:before { + left: 43%; +} + +.dropdown-title { + border-bottom: 1px solid #eee; + padding: 2px 8px; + font-size: 11px; + color: gray; +} + +.dropdown-item { + display: block; + padding: 7px 15px; + border-bottom: 1px solid #eee; +} + +.dropdown-item.disabled { + opacity: 0.25; +} + +.dropdown-item .fa-check-square { + color: var(--color-blue); +} + +.dropdown-item .fa-check-square, +.dropdown-item .fa-square-o { + font-size: 14px; +} + +.dropdown-item .fa-check { + font-size: 11.5px; +} + +.dropdown-item.no-border { + border-bottom: 0; +} + +.dropdown-item:hover:not(.no-hover) { + background: #eee; + cursor: pointer; +} + +.dropdown-item:hover:not(.no-hover) i:not(.fa-check-square) { + color: var(--color-text); +} + +.dropdown-item:hover:not(.no-hover) .description { + color: var(--color-text); +} + +.dropdown-item:hover:not(.no-hover).disabled { + background: inherit; + cursor: default; +} + +.dropdown-item .fa, +.dropdown-item i { + display: inline-block; + width: 20px; + color: rgba(0, 0, 0, 0.65); +} + +.dropdown-item .fa-fw { + margin-left: -3px; + margin-right: 3px; +} + +.dropdown-item img { + display: inline-block; + width: 16px; + position: relative; + top: 3px; + margin-right: 6px; +} + +.dropdown-item .btn .fa { + color: inherit; +} + +.dropdown-item .label { + font-weight: 500; + margin-bottom: 3px; +} + +.dropdown-item .description { + color: var(--color-text--muted); + margin: 0; + margin-left: 23px; + margin-bottom: 3px; + line-height: 1.5; +} + +.dropdown-item .description.small { + font-size: 12.5px; +} + +.dropdown-item:first-of-type { + border-radius: 2px 2px 0 0; +} + +.dropdown-item:last-of-type { + border-radius: 0 0 2px 2px; +} \ No newline at end of file diff --git a/app/userland/app-stdlib/css/com/dropdown.css.js b/app/userland/app-stdlib/css/com/dropdown.css.js new file mode 100644 index 0000000000..0d781e59b5 --- /dev/null +++ b/app/userland/app-stdlib/css/com/dropdown.css.js @@ -0,0 +1,238 @@ +import {css} from '../../vendor/lit-element/lit-element.js' + +const cssStr = css` +.dropdown { + position: relative; +} + +.dropdown.open .toggleable:not(.primary) { + background: #dadada; + box-shadow: inset 0 0 3px rgba(0, 0, 0, 0.1); + border-color: transparent; + outline: 0; +} + +.toggleable-container .dropdown-items { + display: none; +} + +.toggleable-container.hover:hover .dropdown-items, +.toggleable-container.open .dropdown-items { + display: block; +} + +.dropdown-items { + width: 270px; + position: absolute; + right: 0px; + z-index: 3000; + background: #fff; + border: 1px solid #dadada; + border-radius: 0px; + box-shadow: 0 2px 15px rgba(0, 0, 0, 0.3); + overflow: hidden; +} + +.dropdown-items .section { + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + padding: 5px 0; +} + +.dropdown-items .section-header { + padding: 2px 10px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.dropdown-items .section-header.light { + color: var(--color-text--light); + font-weight: 500; +} + +.dropdown-items .section-header.small { + font-size: 12px; +} + +.dropdown-items hr { + border: 0; + border-bottom: 1px solid #ddd; +} + +.dropdown-items.thin { + width: 170px; +} + +.dropdown-items.wide { + width: 400px; +} + +.dropdown-items.compact .dropdown-item { + padding: 2px 15px; + border-bottom: 0; +} + +.dropdown-items.compact .description { + margin-left: 0; +} + +.dropdown-items.compact hr { + margin: 5px 0; +} + +.dropdown-items.roomy .dropdown-item { + padding: 10px 15px; +} + +.dropdown-items.very-roomy .dropdown-item { + padding: 20px 30px; +} + +.dropdown-items.no-border .dropdown-item { + border-bottom: 0; +} + +.dropdown-items.center { + left: 50%; + right: unset; + transform: translateX(-50%); +} + +.dropdown-items.left { + right: initial; + left: 0; +} + +.dropdown-items.over { + top: 0; +} + +.dropdown-items.top { + bottom: calc(100% + 5px); +} + +.dropdown-items.with-triangle:before { + content: ''; + position: absolute; + top: -8px; + right: 10px; + width: 12px; + height: 12px; + z-index: 3; + width: 0; + height: 0; + border-left: 8px solid transparent; + border-right: 8px solid transparent; + border-bottom: 8px solid #fff; +} + +.dropdown-items.with-triangle.left:before { + left: 10px; +} + +.dropdown-items.with-triangle.center:before { + left: 43%; +} + +.dropdown-title { + border-bottom: 1px solid #eee; + padding: 2px 8px; + font-size: 11px; + color: gray; +} + +.dropdown-item { + display: block; + padding: 7px 15px; + border-bottom: 1px solid #eee; +} + +.dropdown-item.disabled { + opacity: 0.25; +} + +.dropdown-item .fa-check-square { + color: var(--color-blue); +} + +.dropdown-item .fa-check-square, +.dropdown-item .fa-square-o { + font-size: 14px; +} + +.dropdown-item .fa-check { + font-size: 11.5px; +} + +.dropdown-item.no-border { + border-bottom: 0; +} + +.dropdown-item:hover:not(.no-hover) { + background: #eee; + cursor: pointer; +} + +.dropdown-item:hover:not(.no-hover) i:not(.fa-check-square) { + color: var(--color-text); +} + +.dropdown-item:hover:not(.no-hover) .description { + color: var(--color-text); +} + +.dropdown-item:hover:not(.no-hover).disabled { + background: inherit; + cursor: default; +} + +.dropdown-item .fa, +.dropdown-item i { + display: inline-block; + width: 20px; + color: rgba(0, 0, 0, 0.65); +} + +.dropdown-item .fa-fw { + margin-left: -3px; + margin-right: 3px; +} + +.dropdown-item img { + display: inline-block; + width: 16px; + position: relative; + top: 3px; + margin-right: 6px; +} + +.dropdown-item .btn .fa { + color: inherit; +} + +.dropdown-item .label { + font-weight: 500; + margin-bottom: 3px; +} + +.dropdown-item .description { + color: var(--color-text--muted); + margin: 0; + margin-left: 23px; + margin-bottom: 3px; + line-height: 1.5; +} + +.dropdown-item .description.small { + font-size: 12.5px; +} + +.dropdown-item:first-of-type { + border-radius: 2px 2px 0 0; +} + +.dropdown-item:last-of-type { + border-radius: 0 0 2px 2px; +} +` +export default cssStr diff --git a/app/userland/app-stdlib/css/com/files-explorer.css b/app/userland/app-stdlib/css/com/files-explorer.css new file mode 100644 index 0000000000..f13e92c276 --- /dev/null +++ b/app/userland/app-stdlib/css/com/files-explorer.css @@ -0,0 +1,99 @@ +@import "../buttons2.css"; + +:host { + display: block; +} + +:host([fullheight]) .listing { + height: calc(100vh - 49px); + overflow-y: auto; +} + +.toolbar { + display: flex; + align-items: center; + height: 26px; + background: #334; + padding-left: 5px; +} + +.toolbar button { + padding: 0 8px; + height: 26px; + line-height: 24px; + color: #eee; + border-radius: 0; +} + +.toolbar button:hover { + background: rgba(255, 255, 255, 0.1); +} + +.toolbar .text { + overflow: hidden; + text-overflow: ellipsis; +} + +.toolbar .spacer { + flex: 1; +} + +.path { + padding: 0 4px; + font-size: 12px; + color: #bbb; + overflow-x: auto; + white-space: nowrap; +} + +.path a { + display: block; + padding: 4px; +} + +.path a:hover { + cursor: default; +} + +.path .fa-angle-right { + padding: 2px; +} + +.listing .item { + display: flex; + align-items: center; + padding: 4px 8px; + cursor: pointer; +} + +.listing .item:hover { + background: #444; +} + +.listing .item .icon { + padding-right: 6px; +} + +.listing .item .name { + flex: 1; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.listing .item .size { + color: rgba(255, 255, 255,.5); +} + +@media (max-width: 600px) { + .toolbar .btn-label { + display: none; + } +} + +@media (min-width: 601px) { + .tooltip-onsmall[data-tooltip]:hover:after, + .tooltip-onsmall[data-tooltip]:hover:before { + display: none; + } +} \ No newline at end of file diff --git a/app/userland/app-stdlib/css/com/files-explorer.css.js b/app/userland/app-stdlib/css/com/files-explorer.css.js new file mode 100644 index 0000000000..f6e726c4f8 --- /dev/null +++ b/app/userland/app-stdlib/css/com/files-explorer.css.js @@ -0,0 +1,104 @@ +import {css} from '../../vendor/lit-element/lit-element.js' +import buttons2css from '../buttons2.css.js' +const cssStr = css` +${buttons2css} + +:host { + display: block; +} + +:host([fullheight]) .listing { + height: calc(100vh - 49px); + overflow-y: auto; +} + +.toolbar { + display: flex; + align-items: center; + height: 26px; + background: #334; + padding-left: 5px; +} + +.toolbar button { + padding: 0 8px; + height: 26px; + line-height: 24px; + color: #eee; + border-radius: 0; +} + +.toolbar button:hover { + background: rgba(255, 255, 255, 0.1); +} + +.toolbar .text { + overflow: hidden; + text-overflow: ellipsis; +} + +.toolbar .spacer { + flex: 1; +} + +.path { + padding: 0 4px; + font-size: 12px; + color: #bbb; + overflow-x: auto; + white-space: nowrap; +} + +.path a { + display: block; + padding: 4px; +} + +.path a:hover { + cursor: default; +} + +.path .fa-angle-right { + padding: 2px; +} + +.listing .item { + display: flex; + align-items: center; + padding: 4px 8px; + cursor: pointer; +} + +.listing .item:hover { + background: #444; +} + +.listing .item .icon { + padding-right: 6px; +} + +.listing .item .name { + flex: 1; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.listing .item .size { + color: rgba(255, 255, 255,.5); +} + +@media (max-width: 600px) { + .toolbar .btn-label { + display: none; + } +} + +@media (min-width: 601px) { + .tooltip-onsmall[data-tooltip]:hover:after, + .tooltip-onsmall[data-tooltip]:hover:before { + display: none; + } +} +` +export default cssStr diff --git a/app/userland/app-stdlib/css/com/history-autocomplete.css b/app/userland/app-stdlib/css/com/history-autocomplete.css new file mode 100644 index 0000000000..a471081014 --- /dev/null +++ b/app/userland/app-stdlib/css/com/history-autocomplete.css @@ -0,0 +1,37 @@ +@import "../common.css"; +@import "./search-input.css"; +@import "./autocomplete.css"; + +:host { + --input-bg-color: #fff; + --input-border-radius: 4px; +} + +.search-container, +input.search { + position: relative; + width: 100%; + height: 36px; + font-size: 13px; +} + +.search-container input.search { + background: var(--input-bg-color); + border-radius: var(--input-border-radius); + height: 30px; + padding: 0 7px; +} + +.search-container input.search:focus { + box-shadow: 0 0 0 2px rgba(41, 95, 203, 0.2); +} + +.search-container > .fa-search { + font-size: 13px; + left: 14px; + top: 10px; +} + +input.search::-webkit-input-placeholder { + font-size: 13px; +} diff --git a/app/userland/app-stdlib/css/com/history-autocomplete.css.js b/app/userland/app-stdlib/css/com/history-autocomplete.css.js new file mode 100644 index 0000000000..68a3ba18e9 --- /dev/null +++ b/app/userland/app-stdlib/css/com/history-autocomplete.css.js @@ -0,0 +1,45 @@ +import {css} from '../../vendor/lit-element/lit-element.js' +import commoncss from '../common.css.js' +import searchinputcss from './search-input.css.js' +import autocompletecss from './autocomplete.css.js' +const cssStr = css` +${commoncss} +${searchinputcss} +${autocompletecss} + +:host { + --input-bg-color: #fff; + --input-border-radius: 4px; +} + +.search-container, +input.search { + position: relative; + width: 100%; + height: 36px; + font-size: 13px; +} + +.search-container input.search { + background: var(--input-bg-color); + border-radius: var(--input-border-radius); + height: 30px; + padding: 0 7px; +} + +.search-container input.search:focus { + box-shadow: 0 0 0 2px rgba(41, 95, 203, 0.2); +} + +.search-container > .fa-search { + font-size: 13px; + left: 14px; + top: 10px; +} + +input.search::-webkit-input-placeholder { + font-size: 13px; +} + +` +export default cssStr diff --git a/app/userland/app-stdlib/css/com/hover-menu.css b/app/userland/app-stdlib/css/com/hover-menu.css new file mode 100644 index 0000000000..16c67147d1 --- /dev/null +++ b/app/userland/app-stdlib/css/com/hover-menu.css @@ -0,0 +1,73 @@ +@import "../colors.css"; + +:host { + --menu-label-font-size: 12px; +} + +.menu-label { + font-size: var(--menu-label-font-size); +} + +.dropdown { + display: inline-block; + position: relative; + color: var(--color-text); + padding: 6px 6px; + cursor: pointer; + user-select: none; +} + +.dropdown:hover { + background: #f5f5f5; +} + +.dropdown-menu { + position: absolute; + top: 24px; + background: #fff; + padding: 6px 0; + border: 1px solid #bbb; + border-radius: 2px; + box-shadow: 0 2px 3px rgba(0,0,0,.1); + z-index: 1; +} + +:host([right]) .dropdown-menu { + right: 0; +} + +.heading { + cursor: default !important; + padding: 6px 30px 6px 14px; + color: var(--color-text--moted); + font-size: 10px; + font-weight: 500; +} + +.item { + display: block; + padding: 12px 30px 12px 14px; + min-width: 60px; + color: var(--color-text); + font-size: 12px; + white-space: nowrap; +} + +.item:not(.disabled):hover { + background: #f5f5f5; +} + +.item.disabled { + color: var(--color-text--muted); + cursor: default !important; +} + +.item .fa-fw { + margin-left: 2px; + margin-right: 10px; +} + +hr { + border: 0; + border-top: 1px solid #ddd; +} \ No newline at end of file diff --git a/app/userland/app-stdlib/css/com/hover-menu.css.js b/app/userland/app-stdlib/css/com/hover-menu.css.js new file mode 100644 index 0000000000..9eaf603be8 --- /dev/null +++ b/app/userland/app-stdlib/css/com/hover-menu.css.js @@ -0,0 +1,78 @@ +import {css} from '../../vendor/lit-element/lit-element.js' +import colorscss from '../colors.css.js' +const cssStr = css` +${colorscss} + +:host { + --menu-label-font-size: 12px; +} + +.menu-label { + font-size: var(--menu-label-font-size); +} + +.dropdown { + display: inline-block; + position: relative; + color: var(--color-text); + padding: 6px 6px; + cursor: pointer; + user-select: none; +} + +.dropdown:hover { + background: #f5f5f5; +} + +.dropdown-menu { + position: absolute; + top: 24px; + background: #fff; + padding: 6px 0; + border: 1px solid #bbb; + border-radius: 2px; + box-shadow: 0 2px 3px rgba(0,0,0,.1); + z-index: 1; +} + +:host([right]) .dropdown-menu { + right: 0; +} + +.heading { + cursor: default !important; + padding: 6px 30px 6px 14px; + color: var(--color-text--moted); + font-size: 10px; + font-weight: 500; +} + +.item { + display: block; + padding: 12px 30px 12px 14px; + min-width: 60px; + color: var(--color-text); + font-size: 12px; + white-space: nowrap; +} + +.item:not(.disabled):hover { + background: #f5f5f5; +} + +.item.disabled { + color: var(--color-text--muted); + cursor: default !important; +} + +.item .fa-fw { + margin-left: 2px; + margin-right: 10px; +} + +hr { + border: 0; + border-top: 1px solid #ddd; +} +` +export default cssStr diff --git a/app/userland/app-stdlib/css/com/message.css b/app/userland/app-stdlib/css/com/message.css new file mode 100644 index 0000000000..8e7adc0102 --- /dev/null +++ b/app/userland/app-stdlib/css/com/message.css @@ -0,0 +1,22 @@ +.message { + display: flex; + padding: 16px; +} + +.message.error { + border: 1px solid #de1c1c; + border-radius: 4px; + background: #ffafaf; + color: #920101; +} + +.message .icon { + margin-right: 16px; + font-size: 21px; +} + +.message .title { + font-size: 18px; + font-weight: 500; + margin-bottom: 10px; +} \ No newline at end of file diff --git a/app/userland/app-stdlib/css/com/message.css.js b/app/userland/app-stdlib/css/com/message.css.js new file mode 100644 index 0000000000..0f45fe21ed --- /dev/null +++ b/app/userland/app-stdlib/css/com/message.css.js @@ -0,0 +1,27 @@ +import {css} from '../../vendor/lit-element/lit-element.js' + +const cssStr = css` +.message { + display: flex; + padding: 16px; +} + +.message.error { + border: 1px solid #de1c1c; + border-radius: 4px; + background: #ffafaf; + color: #920101; +} + +.message .icon { + margin-right: 16px; + font-size: 21px; +} + +.message .title { + font-size: 18px; + font-weight: 500; + margin-bottom: 10px; +} +` +export default cssStr diff --git a/app/userland/app-stdlib/css/com/popups.css b/app/userland/app-stdlib/css/com/popups.css new file mode 100644 index 0000000000..939d31c385 --- /dev/null +++ b/app/userland/app-stdlib/css/com/popups.css @@ -0,0 +1,133 @@ +@import "../buttons.css"; +@import "../inputs.css"; + +.popup-wrapper { + position: fixed; + left: 0; + top: 0; + width: 100%; + height: 100%; + z-index: 6000; + background: rgba(0, 0, 0, 0.45); + font-style: normal; + overflow-y: auto; +} + +.popup-inner { + background: #fff; + box-shadow: 0 2px 25px rgba(0, 0, 0, 0.3); + border: 1px solid rgba(0, 0, 0, 0.55); + border-radius: 4px; + width: 450px; + margin: 80px auto; + overflow: hidden; +} + +.popup-inner .error { + color: #d80b00 !important; + margin: 10px 0 !important; + font-style: italic; +} + +.popup-inner .head { + position: relative; + background: #f1f1f6; + padding: 7px 12px; + width: 100%; + border-bottom: 1px solid #e0e0ee; + border-radius: 4px 4px 0 0; +} + +.popup-inner .head .title { + font-size: 0.95rem; + font-weight: 500; +} + +.popup-inner .head .close-btn { + position: absolute; + top: 8px; + right: 12px; + cursor: pointer; +} + +.popup-inner .body { + padding: 12px; +} + +.popup-inner .body > div:not(:first-child) { + margin-top: 20px; +} + +.popup-inner p:first-child { + margin-top: 0; +} + +.popup-inner p:last-child { + margin-bottom: 0; +} + +.popup-inner select { + height: 28px; +} + +.popup-inner textarea, +.popup-inner label:not(.checkbox-container), +.popup-inner select, +.popup-inner input { + display: block; + width: 100%; +} + +.popup-inner label.toggle { + display: flex; + justify-content: flex-start; +} + +.popup-inner label.toggle .text { + margin-right: 10px; +} + +.popup-inner label.toggle input { + display: none; +} + +.popup-inner label { + margin-bottom: 3px; + color: rgba(51, 51, 51, 0.9); +} + +.popup-inner textarea, +.popup-inner input { + margin-bottom: 10px; +} + +.popup-inner textarea { + height: 60px; + resize: vertical; +} + +.popup-inner .actions { + display: flex; + justify-content: flex-end; + align-items: center; + margin-top: 15px; + padding-top: 10px; + border-top: 1px solid #eee; +} + +.popup-inner .actions .left, +.popup-inner .actions .link { + margin-right: auto; +} + +.popup-inner .actions .btn, +.popup-inner .actions .success, +.popup-inner .actions .primary { + margin-left: 5px; +} + +.popup-inner .actions .spinner { + width: 10px; + height: 10px; + border-width: 1.2px; +} \ No newline at end of file diff --git a/app/userland/app-stdlib/css/com/popups.css.js b/app/userland/app-stdlib/css/com/popups.css.js new file mode 100644 index 0000000000..c2ba1ff274 --- /dev/null +++ b/app/userland/app-stdlib/css/com/popups.css.js @@ -0,0 +1,139 @@ +import {css} from '../../vendor/lit-element/lit-element.js' +import buttonscss from '../buttons.css.js' +import inputscss from '../inputs.css.js' +const cssStr = css` +${buttonscss} +${inputscss} + +.popup-wrapper { + position: fixed; + left: 0; + top: 0; + width: 100%; + height: 100%; + z-index: 6000; + background: rgba(0, 0, 0, 0.45); + font-style: normal; + overflow-y: auto; +} + +.popup-inner { + background: #fff; + box-shadow: 0 2px 25px rgba(0, 0, 0, 0.3); + border: 1px solid rgba(0, 0, 0, 0.55); + border-radius: 4px; + width: 450px; + margin: 80px auto; + overflow: hidden; +} + +.popup-inner .error { + color: #d80b00 !important; + margin: 10px 0 !important; + font-style: italic; +} + +.popup-inner .head { + position: relative; + background: #f1f1f6; + padding: 7px 12px; + width: 100%; + border-bottom: 1px solid #e0e0ee; + border-radius: 4px 4px 0 0; +} + +.popup-inner .head .title { + font-size: 0.95rem; + font-weight: 500; +} + +.popup-inner .head .close-btn { + position: absolute; + top: 8px; + right: 12px; + cursor: pointer; +} + +.popup-inner .body { + padding: 12px; +} + +.popup-inner .body > div:not(:first-child) { + margin-top: 20px; +} + +.popup-inner p:first-child { + margin-top: 0; +} + +.popup-inner p:last-child { + margin-bottom: 0; +} + +.popup-inner select { + height: 28px; +} + +.popup-inner textarea, +.popup-inner label:not(.checkbox-container), +.popup-inner select, +.popup-inner input { + display: block; + width: 100%; +} + +.popup-inner label.toggle { + display: flex; + justify-content: flex-start; +} + +.popup-inner label.toggle .text { + margin-right: 10px; +} + +.popup-inner label.toggle input { + display: none; +} + +.popup-inner label { + margin-bottom: 3px; + color: rgba(51, 51, 51, 0.9); +} + +.popup-inner textarea, +.popup-inner input { + margin-bottom: 10px; +} + +.popup-inner textarea { + height: 60px; + resize: vertical; +} + +.popup-inner .actions { + display: flex; + justify-content: flex-end; + align-items: center; + margin-top: 15px; + padding-top: 10px; + border-top: 1px solid #eee; +} + +.popup-inner .actions .left, +.popup-inner .actions .link { + margin-right: auto; +} + +.popup-inner .actions .btn, +.popup-inner .actions .success, +.popup-inner .actions .primary { + margin-left: 5px; +} + +.popup-inner .actions .spinner { + width: 10px; + height: 10px; + border-width: 1.2px; +} +` +export default cssStr diff --git a/app/userland/app-stdlib/css/com/progress.css b/app/userland/app-stdlib/css/com/progress.css new file mode 100644 index 0000000000..dabdcbcb8a --- /dev/null +++ b/app/userland/app-stdlib/css/com/progress.css @@ -0,0 +1,66 @@ +@keyframes progress-active { + 0% { + width: 0%; + } + 50% { + width: 0%; + } + 100% { + width: 100%; + } +} + +.progress-ui { + position: relative; + width: 100%; + height: 20px; + border-radius: 2px; + background: #e0e0e0; +} + +.progress-ui .completed { + position: absolute; + min-width: 30px; + height: 100%; + padding: 0 5px; + background: #bbb; + color: rgba(255, 255, 255, 0.9); + font-size: 0.75rem; + font-weight: 500; + text-align: right; + line-height: 20px; + border-radius: 2px; +} + +.progress-ui .label { + position: absolute; + width: 100%; + top: calc(100% + 5px); + text-align: center; + font-size: 0.8rem; +} + +.progress-ui.active .completed:before { + position: absolute; + left: 0; + display: block; + content: ''; + z-index: 3; + height: 100%; + animation: 3s ease progress-active infinite; + animation-delay: 1s; + border-radius: 4px; + background: linear-gradient(to right, rgba(255, 255, 255, 0.2) 5%, rgba(255, 255, 255, 0.1)); +} + +.progress-ui.small { + height: 10px; +} + +.progress-ui.blue .completed { + background: #2864dc; +} + +.progress-ui.green .completed { + background: #44c35a; +} \ No newline at end of file diff --git a/app/userland/app-stdlib/css/com/progress.css.js b/app/userland/app-stdlib/css/com/progress.css.js new file mode 100644 index 0000000000..463fe48766 --- /dev/null +++ b/app/userland/app-stdlib/css/com/progress.css.js @@ -0,0 +1,71 @@ +import {css} from '../../vendor/lit-element/lit-element.js' + +const cssStr = css` +@keyframes progress-active { + 0% { + width: 0%; + } + 50% { + width: 0%; + } + 100% { + width: 100%; + } +} + +.progress-ui { + position: relative; + width: 100%; + height: 20px; + border-radius: 2px; + background: #e0e0e0; +} + +.progress-ui .completed { + position: absolute; + min-width: 30px; + height: 100%; + padding: 0 5px; + background: #bbb; + color: rgba(255, 255, 255, 0.9); + font-size: 0.75rem; + font-weight: 500; + text-align: right; + line-height: 20px; + border-radius: 2px; +} + +.progress-ui .label { + position: absolute; + width: 100%; + top: calc(100% + 5px); + text-align: center; + font-size: 0.8rem; +} + +.progress-ui.active .completed:before { + position: absolute; + left: 0; + display: block; + content: ''; + z-index: 3; + height: 100%; + animation: 3s ease progress-active infinite; + animation-delay: 1s; + border-radius: 4px; + background: linear-gradient(to right, rgba(255, 255, 255, 0.2) 5%, rgba(255, 255, 255, 0.1)); +} + +.progress-ui.small { + height: 10px; +} + +.progress-ui.blue .completed { + background: #2864dc; +} + +.progress-ui.green .completed { + background: #44c35a; +} +` +export default cssStr diff --git a/app/userland/app-stdlib/css/com/search-input.css b/app/userland/app-stdlib/css/com/search-input.css new file mode 100644 index 0000000000..bc3d2467b8 --- /dev/null +++ b/app/userland/app-stdlib/css/com/search-input.css @@ -0,0 +1,31 @@ +.spinner, +.close-btn, +.search { + position: absolute; +} + +input.search { + left: 0; + top: 0; + width: 100%; + height: 40px; + padding: 0 10px; + padding-left: 47px; + border-radius: 4px; +} + +input.search:invalid + .close-btn { + opacity: 0; +} + +input:focus { + box-shadow: none; +} + +.search-container > i.fa-search { + position: absolute; + font-size: 16px; + left: 17px; + top: 11px; + color: rgba(0,0,0,0.4); +} \ No newline at end of file diff --git a/app/userland/app-stdlib/css/com/search-input.css.js b/app/userland/app-stdlib/css/com/search-input.css.js new file mode 100644 index 0000000000..fa8ade6143 --- /dev/null +++ b/app/userland/app-stdlib/css/com/search-input.css.js @@ -0,0 +1,36 @@ +import {css} from '../../vendor/lit-element/lit-element.js' + +const cssStr = css` +.spinner, +.close-btn, +.search { + position: absolute; +} + +input.search { + left: 0; + top: 0; + width: 100%; + height: 40px; + padding: 0 10px; + padding-left: 47px; + border-radius: 4px; +} + +input.search:invalid + .close-btn { + opacity: 0; +} + +input:focus { + box-shadow: none; +} + +.search-container > i.fa-search { + position: absolute; + font-size: 16px; + left: 17px; + top: 11px; + color: rgba(0,0,0,0.4); +} +` +export default cssStr diff --git a/app/userland/app-stdlib/css/com/spinner.css b/app/userland/app-stdlib/css/com/spinner.css new file mode 100644 index 0000000000..4f77d52f3e --- /dev/null +++ b/app/userland/app-stdlib/css/com/spinner.css @@ -0,0 +1,20 @@ +.spinner { + display: inline-block; + height: 14px; + width: 14px; + animation: rotate 1s infinite linear; + color: #aaa; + border: 1.5px solid; + border-right-color: transparent; + border-radius: 50%; + transition: color 0.25s; +} + +.spinner.reverse { + animation: rotate 2s infinite linear reverse; +} + +@keyframes rotate { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} \ No newline at end of file diff --git a/app/userland/app-stdlib/css/com/spinner.css.js b/app/userland/app-stdlib/css/com/spinner.css.js new file mode 100644 index 0000000000..13ad28289e --- /dev/null +++ b/app/userland/app-stdlib/css/com/spinner.css.js @@ -0,0 +1,25 @@ +import {css} from '../../vendor/lit-element/lit-element.js' + +const cssStr = css` +.spinner { + display: inline-block; + height: 14px; + width: 14px; + animation: rotate 1s infinite linear; + color: #aaa; + border: 1.5px solid; + border-right-color: transparent; + border-radius: 50%; + transition: color 0.25s; +} + +.spinner.reverse { + animation: rotate 2s infinite linear reverse; +} + +@keyframes rotate { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} +` +export default cssStr diff --git a/app/userland/app-stdlib/css/com/table.css b/app/userland/app-stdlib/css/com/table.css new file mode 100644 index 0000000000..ddcb54dc8d --- /dev/null +++ b/app/userland/app-stdlib/css/com/table.css @@ -0,0 +1,55 @@ +.heading, +.row { + display: flex; + padding: 0 20px; + justify-content: flex-start; + align-items: center; +} + +.heading { + height: 40px; +} + +.heading span { + cursor: pointer; +} + +.row { + height: 50px; + background: #fff; + border: 1px solid #e6e6e6; +} + +.row:not(:last-of-type) { + border-bottom: 0; +} + +.row:hover { + background: #fafafa; +} + +.row.selected { + background: rgba(40, 100, 220, 0.1); + border-color: #c3d4f5; +} + +.row.selected + .row { + border-top: 1px solid #c3d4f5; +} + +.row.selected:hover { + background: rgba(40, 100, 220, 0.15); +} + +a.row { + text-decoration: none; + color: inherit; +} + +.col { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + margin-right: 5px; + vertical-align: middle; +} \ No newline at end of file diff --git a/app/userland/app-stdlib/css/com/table.css.js b/app/userland/app-stdlib/css/com/table.css.js new file mode 100644 index 0000000000..c103f145e6 --- /dev/null +++ b/app/userland/app-stdlib/css/com/table.css.js @@ -0,0 +1,60 @@ +import {css} from '../../vendor/lit-element/lit-element.js' + +const cssStr = css` +.heading, +.row { + display: flex; + padding: 0 20px; + justify-content: flex-start; + align-items: center; +} + +.heading { + height: 40px; +} + +.heading span { + cursor: pointer; +} + +.row { + height: 50px; + background: #fff; + border: 1px solid #e6e6e6; +} + +.row:not(:last-of-type) { + border-bottom: 0; +} + +.row:hover { + background: #fafafa; +} + +.row.selected { + background: rgba(40, 100, 220, 0.1); + border-color: #c3d4f5; +} + +.row.selected + .row { + border-top: 1px solid #c3d4f5; +} + +.row.selected:hover { + background: rgba(40, 100, 220, 0.15); +} + +a.row { + text-decoration: none; + color: inherit; +} + +.col { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + margin-right: 5px; + vertical-align: middle; +} +` +export default cssStr diff --git a/app/userland/app-stdlib/css/com/tabs-nav.css b/app/userland/app-stdlib/css/com/tabs-nav.css new file mode 100644 index 0000000000..3c4c975ea7 --- /dev/null +++ b/app/userland/app-stdlib/css/com/tabs-nav.css @@ -0,0 +1,16 @@ +:host { + display: flex; +} + +a { + border-bottom: 2px solid transparent; + cursor: pointer; +} + +a:hover { + border-bottom-color: #aaa; +} + +a.active { + border-bottom-color: var(--blue); +} \ No newline at end of file diff --git a/app/userland/app-stdlib/css/com/tabs-nav.css.js b/app/userland/app-stdlib/css/com/tabs-nav.css.js new file mode 100644 index 0000000000..cc994435ae --- /dev/null +++ b/app/userland/app-stdlib/css/com/tabs-nav.css.js @@ -0,0 +1,21 @@ +import {css} from '../../vendor/lit-element/lit-element.js' + +const cssStr = css` +:host { + display: flex; +} + +a { + border-bottom: 2px solid transparent; + cursor: pointer; +} + +a:hover { + border-bottom-color: #aaa; +} + +a.active { + border-bottom-color: var(--blue); +} +` +export default cssStr diff --git a/app/userland/app-stdlib/css/com/toast.css b/app/userland/app-stdlib/css/com/toast.css new file mode 100644 index 0000000000..f60ae51ba9 --- /dev/null +++ b/app/userland/app-stdlib/css/com/toast.css @@ -0,0 +1,62 @@ +.toast-wrapper { + position: fixed; + top: 20px; + right: 20px; + z-index: 20000; + transition: opacity 0.1s ease; +} +.toast-wrapper.hidden { + opacity: 0; +} +.toast { + position: relative; + min-width: 350px; + max-width: 450px; + background: #ddd; + margin: 0; + padding: 10px 15px; + border-radius: 4px; + font-size: 16px; + color: #fff; + background: rgba(0, 0, 0, 0.75); + -webkit-font-smoothing: antialiased; + font-weight: 600; +} +.toast.error { + padding-left: 38px; +} +.toast.success { + padding-left: 48px; +} +.toast.success:before, +.toast.error:before { + position: absolute; + left: 18px; + top: 5px; + display: block; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Ubuntu, Cantarell, "Oxygen Sans", "Helvetica Neue", sans-serif; + font-size: 22px; + font-weight: bold; +} +.toast.primary { + background: var(--color-blue); +} +.toast.success { + background: #26b33e; +} +.toast.success:before { + content: '✓'; +} +.toast.error { + background: #c72e25; +} +.toast.error:before { + content: '!'; +} +.toast .toast-btn { + position: absolute; + right: 15px; + color: inherit; + text-decoration: underline; + cursor: pointer; +} \ No newline at end of file diff --git a/app/userland/app-stdlib/css/com/toast.css.js b/app/userland/app-stdlib/css/com/toast.css.js new file mode 100644 index 0000000000..f707576b1b --- /dev/null +++ b/app/userland/app-stdlib/css/com/toast.css.js @@ -0,0 +1,73 @@ +import {css} from '../../vendor/lit-element/lit-element.js' + +const cssStr = css` +:host { + --toast-min-width: 350px; + --toast-padding: 10px 15px; + --toast-font-size: 16px; +} + +.toast-wrapper { + position: fixed; + top: 20px; + right: 20px; + z-index: 20000; + transition: opacity 0.1s ease; +} +.toast-wrapper.hidden { + opacity: 0; +} +.toast { + position: relative; + min-width: var(--toast-min-width); + max-width: 450px; + background: #ddd; + margin: 0; + padding: var(--toast-padding); + border-radius: 4px; + font-size: var(--toast-font-size); + color: #fff; + background: rgba(0, 0, 0, 0.75); + -webkit-font-smoothing: antialiased; + font-weight: 600; +} +.toast.error { + padding-left: 38px; +} +.toast.success { + padding-left: 48px; +} +.toast.success:before, +.toast.error:before { + position: absolute; + left: 18px; + top: 5px; + display: block; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Ubuntu, Cantarell, "Oxygen Sans", "Helvetica Neue", sans-serif; + font-size: 22px; + font-weight: bold; +} +.toast.primary { + background: var(--color-blue); +} +.toast.success { + background: #26b33e; +} +.toast.success:before { + content: '✓'; +} +.toast.error { + background: #c72e25; +} +.toast.error:before { + content: '!'; +} +.toast .toast-btn { + position: absolute; + right: 15px; + color: inherit; + text-decoration: underline; + cursor: pointer; +} +` +export default cssStr diff --git a/app/userland/app-stdlib/css/common.css b/app/userland/app-stdlib/css/common.css new file mode 100644 index 0000000000..0d0aa3df91 --- /dev/null +++ b/app/userland/app-stdlib/css/common.css @@ -0,0 +1,9 @@ +@import "./reset.css"; +@import "./typography.css"; +@import "./buttons.css"; +@import "./inputs.css"; + +body { + background: #f5f5f7; + color: #333; +} \ No newline at end of file diff --git a/app/userland/app-stdlib/css/common.css.js b/app/userland/app-stdlib/css/common.css.js new file mode 100644 index 0000000000..61c09cafaf --- /dev/null +++ b/app/userland/app-stdlib/css/common.css.js @@ -0,0 +1,17 @@ +import {css} from '../vendor/lit-element/lit-element.js' +import resetcss from './reset.css.js' +import typographycss from './typography.css.js' +import buttonscss from './buttons.css.js' +import inputscss from './inputs.css.js' +const cssStr = css` +${resetcss} +${typographycss} +${buttonscss} +${inputscss} + +body { + background: #f5f5f7; + color: #333; +} +` +export default cssStr diff --git a/app/userland/app-stdlib/css/fa-mod.css b/app/userland/app-stdlib/css/fa-mod.css new file mode 100644 index 0000000000..422eca6f8c --- /dev/null +++ b/app/userland/app-stdlib/css/fa-mod.css @@ -0,0 +1,11 @@ +.fa-mod { + position: relative; + margin-right: 3px; +} + +.fa-mod :last-child { + position: absolute; + font-size: 50%; + top: 42%; + right: 4px; +} \ No newline at end of file diff --git a/app/userland/app-stdlib/css/fa-mod.css.js b/app/userland/app-stdlib/css/fa-mod.css.js new file mode 100644 index 0000000000..f683eda293 --- /dev/null +++ b/app/userland/app-stdlib/css/fa-mod.css.js @@ -0,0 +1,16 @@ +import {css} from '../vendor/lit-element/lit-element.js' + +const cssStr = css` +.fa-mod { + position: relative; + margin-right: 3px; +} + +.fa-mod :last-child { + position: absolute; + font-size: 50%; + top: 42%; + right: 4px; +} +` +export default cssStr diff --git a/app/userland/app-stdlib/css/fontawesome.css b/app/userland/app-stdlib/css/fontawesome.css new file mode 100644 index 0000000000..a74835e25b --- /dev/null +++ b/app/userland/app-stdlib/css/fontawesome.css @@ -0,0 +1 @@ +.fa,.fab,.fal,.far,.fas{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:.08em solid #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{animation:fa-spin 2s infinite linear}.fa-pulse{animation:fa-spin 1s infinite steps(8)}@keyframes fa-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";transform:scaleX(-1)}.fa-flip-vertical{transform:scaleY(-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical,.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{transform:scale(-1)}:root .fa-flip-both,:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2.5em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-500px:before{content:"\f26e"}.fa-accessible-icon:before{content:"\f368"}.fa-accusoft:before{content:"\f369"}.fa-acquisitions-incorporated:before{content:"\f6af"}.fa-ad:before{content:"\f641"}.fa-address-book:before{content:"\f2b9"}.fa-address-card:before{content:"\f2bb"}.fa-adjust:before{content:"\f042"}.fa-adn:before{content:"\f170"}.fa-adobe:before{content:"\f778"}.fa-adversal:before{content:"\f36a"}.fa-affiliatetheme:before{content:"\f36b"}.fa-air-freshener:before{content:"\f5d0"}.fa-algolia:before{content:"\f36c"}.fa-align-center:before{content:"\f037"}.fa-align-justify:before{content:"\f039"}.fa-align-left:before{content:"\f036"}.fa-align-right:before{content:"\f038"}.fa-alipay:before{content:"\f642"}.fa-allergies:before{content:"\f461"}.fa-amazon:before{content:"\f270"}.fa-amazon-pay:before{content:"\f42c"}.fa-ambulance:before{content:"\f0f9"}.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-amilia:before{content:"\f36d"}.fa-anchor:before{content:"\f13d"}.fa-android:before{content:"\f17b"}.fa-angellist:before{content:"\f209"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-down:before{content:"\f107"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angry:before{content:"\f556"}.fa-angrycreative:before{content:"\f36e"}.fa-angular:before{content:"\f420"}.fa-ankh:before{content:"\f644"}.fa-app-store:before{content:"\f36f"}.fa-app-store-ios:before{content:"\f370"}.fa-apper:before{content:"\f371"}.fa-apple:before{content:"\f179"}.fa-apple-alt:before{content:"\f5d1"}.fa-apple-pay:before{content:"\f415"}.fa-archive:before{content:"\f187"}.fa-archway:before{content:"\f557"}.fa-arrow-alt-circle-down:before{content:"\f358"}.fa-arrow-alt-circle-left:before{content:"\f359"}.fa-arrow-alt-circle-right:before{content:"\f35a"}.fa-arrow-alt-circle-up:before{content:"\f35b"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-down:before{content:"\f063"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrows-alt:before{content:"\f0b2"}.fa-arrows-alt-h:before{content:"\f337"}.fa-arrows-alt-v:before{content:"\f338"}.fa-artstation:before{content:"\f77a"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asterisk:before{content:"\f069"}.fa-asymmetrik:before{content:"\f372"}.fa-at:before{content:"\f1fa"}.fa-atlas:before{content:"\f558"}.fa-atlassian:before{content:"\f77b"}.fa-atom:before{content:"\f5d2"}.fa-audible:before{content:"\f373"}.fa-audio-description:before{content:"\f29e"}.fa-autoprefixer:before{content:"\f41c"}.fa-avianex:before{content:"\f374"}.fa-aviato:before{content:"\f421"}.fa-award:before{content:"\f559"}.fa-aws:before{content:"\f375"}.fa-baby:before{content:"\f77c"}.fa-baby-carriage:before{content:"\f77d"}.fa-backspace:before{content:"\f55a"}.fa-backward:before{content:"\f04a"}.fa-bacon:before{content:"\f7e5"}.fa-balance-scale:before{content:"\f24e"}.fa-ban:before{content:"\f05e"}.fa-band-aid:before{content:"\f462"}.fa-bandcamp:before{content:"\f2d5"}.fa-barcode:before{content:"\f02a"}.fa-bars:before{content:"\f0c9"}.fa-baseball-ball:before{content:"\f433"}.fa-basketball-ball:before{content:"\f434"}.fa-bath:before{content:"\f2cd"}.fa-battery-empty:before{content:"\f244"}.fa-battery-full:before{content:"\f240"}.fa-battery-half:before{content:"\f242"}.fa-battery-quarter:before{content:"\f243"}.fa-battery-three-quarters:before{content:"\f241"}.fa-bed:before{content:"\f236"}.fa-beer:before{content:"\f0fc"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-bell:before{content:"\f0f3"}.fa-bell-slash:before{content:"\f1f6"}.fa-bezier-curve:before{content:"\f55b"}.fa-bible:before{content:"\f647"}.fa-bicycle:before{content:"\f206"}.fa-bimobject:before{content:"\f378"}.fa-binoculars:before{content:"\f1e5"}.fa-biohazard:before{content:"\f780"}.fa-birthday-cake:before{content:"\f1fd"}.fa-bitbucket:before{content:"\f171"}.fa-bitcoin:before{content:"\f379"}.fa-bity:before{content:"\f37a"}.fa-black-tie:before{content:"\f27e"}.fa-blackberry:before{content:"\f37b"}.fa-blender:before{content:"\f517"}.fa-blender-phone:before{content:"\f6b6"}.fa-blind:before{content:"\f29d"}.fa-blog:before{content:"\f781"}.fa-blogger:before{content:"\f37c"}.fa-blogger-b:before{content:"\f37d"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-bold:before{content:"\f032"}.fa-bolt:before{content:"\f0e7"}.fa-bomb:before{content:"\f1e2"}.fa-bone:before{content:"\f5d7"}.fa-bong:before{content:"\f55c"}.fa-book:before{content:"\f02d"}.fa-book-dead:before{content:"\f6b7"}.fa-book-medical:before{content:"\f7e6"}.fa-book-open:before{content:"\f518"}.fa-book-reader:before{content:"\f5da"}.fa-bookmark:before{content:"\f02e"}.fa-bowling-ball:before{content:"\f436"}.fa-box:before{content:"\f466"}.fa-box-open:before{content:"\f49e"}.fa-boxes:before{content:"\f468"}.fa-braille:before{content:"\f2a1"}.fa-brain:before{content:"\f5dc"}.fa-bread-slice:before{content:"\f7ec"}.fa-briefcase:before{content:"\f0b1"}.fa-briefcase-medical:before{content:"\f469"}.fa-broadcast-tower:before{content:"\f519"}.fa-broom:before{content:"\f51a"}.fa-brush:before{content:"\f55d"}.fa-btc:before{content:"\f15a"}.fa-bug:before{content:"\f188"}.fa-building:before{content:"\f1ad"}.fa-bullhorn:before{content:"\f0a1"}.fa-bullseye:before{content:"\f140"}.fa-burn:before{content:"\f46a"}.fa-buromobelexperte:before{content:"\f37f"}.fa-bus:before{content:"\f207"}.fa-bus-alt:before{content:"\f55e"}.fa-business-time:before{content:"\f64a"}.fa-buysellads:before{content:"\f20d"}.fa-calculator:before{content:"\f1ec"}.fa-calendar:before{content:"\f133"}.fa-calendar-alt:before{content:"\f073"}.fa-calendar-check:before{content:"\f274"}.fa-calendar-day:before{content:"\f783"}.fa-calendar-minus:before{content:"\f272"}.fa-calendar-plus:before{content:"\f271"}.fa-calendar-times:before{content:"\f273"}.fa-calendar-week:before{content:"\f784"}.fa-camera:before{content:"\f030"}.fa-camera-retro:before{content:"\f083"}.fa-campground:before{content:"\f6bb"}.fa-canadian-maple-leaf:before{content:"\f785"}.fa-candy-cane:before{content:"\f786"}.fa-cannabis:before{content:"\f55f"}.fa-capsules:before{content:"\f46b"}.fa-car:before{content:"\f1b9"}.fa-car-alt:before{content:"\f5de"}.fa-car-battery:before{content:"\f5df"}.fa-car-crash:before{content:"\f5e1"}.fa-car-side:before{content:"\f5e4"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-caret-square-down:before{content:"\f150"}.fa-caret-square-left:before{content:"\f191"}.fa-caret-square-right:before{content:"\f152"}.fa-caret-square-up:before{content:"\f151"}.fa-caret-up:before{content:"\f0d8"}.fa-carrot:before{content:"\f787"}.fa-cart-arrow-down:before{content:"\f218"}.fa-cart-plus:before{content:"\f217"}.fa-cash-register:before{content:"\f788"}.fa-cat:before{content:"\f6be"}.fa-cc-amazon-pay:before{content:"\f42d"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-apple-pay:before{content:"\f416"}.fa-cc-diners-club:before{content:"\f24c"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-cc-visa:before{content:"\f1f0"}.fa-centercode:before{content:"\f380"}.fa-centos:before{content:"\f789"}.fa-certificate:before{content:"\f0a3"}.fa-chair:before{content:"\f6c0"}.fa-chalkboard:before{content:"\f51b"}.fa-chalkboard-teacher:before{content:"\f51c"}.fa-charging-station:before{content:"\f5e7"}.fa-chart-area:before{content:"\f1fe"}.fa-chart-bar:before{content:"\f080"}.fa-chart-line:before{content:"\f201"}.fa-chart-pie:before{content:"\f200"}.fa-check:before{content:"\f00c"}.fa-check-circle:before{content:"\f058"}.fa-check-double:before{content:"\f560"}.fa-check-square:before{content:"\f14a"}.fa-cheese:before{content:"\f7ef"}.fa-chess:before{content:"\f439"}.fa-chess-bishop:before{content:"\f43a"}.fa-chess-board:before{content:"\f43c"}.fa-chess-king:before{content:"\f43f"}.fa-chess-knight:before{content:"\f441"}.fa-chess-pawn:before{content:"\f443"}.fa-chess-queen:before{content:"\f445"}.fa-chess-rook:before{content:"\f447"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-down:before{content:"\f078"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-chevron-up:before{content:"\f077"}.fa-child:before{content:"\f1ae"}.fa-chrome:before{content:"\f268"}.fa-church:before{content:"\f51d"}.fa-circle:before{content:"\f111"}.fa-circle-notch:before{content:"\f1ce"}.fa-city:before{content:"\f64f"}.fa-clinic-medical:before{content:"\f7f2"}.fa-clipboard:before{content:"\f328"}.fa-clipboard-check:before{content:"\f46c"}.fa-clipboard-list:before{content:"\f46d"}.fa-clock:before{content:"\f017"}.fa-clone:before{content:"\f24d"}.fa-closed-captioning:before{content:"\f20a"}.fa-cloud:before{content:"\f0c2"}.fa-cloud-download-alt:before{content:"\f381"}.fa-cloud-meatball:before{content:"\f73b"}.fa-cloud-moon:before{content:"\f6c3"}.fa-cloud-moon-rain:before{content:"\f73c"}.fa-cloud-rain:before{content:"\f73d"}.fa-cloud-showers-heavy:before{content:"\f740"}.fa-cloud-sun:before{content:"\f6c4"}.fa-cloud-sun-rain:before{content:"\f743"}.fa-cloud-upload-alt:before{content:"\f382"}.fa-cloudscale:before{content:"\f383"}.fa-cloudsmith:before{content:"\f384"}.fa-cloudversify:before{content:"\f385"}.fa-cocktail:before{content:"\f561"}.fa-code:before{content:"\f121"}.fa-code-branch:before{content:"\f126"}.fa-codepen:before{content:"\f1cb"}.fa-codiepie:before{content:"\f284"}.fa-coffee:before{content:"\f0f4"}.fa-cog:before{content:"\f013"}.fa-cogs:before{content:"\f085"}.fa-coins:before{content:"\f51e"}.fa-columns:before{content:"\f0db"}.fa-comment:before{content:"\f075"}.fa-comment-alt:before{content:"\f27a"}.fa-comment-dollar:before{content:"\f651"}.fa-comment-dots:before{content:"\f4ad"}.fa-comment-medical:before{content:"\f7f5"}.fa-comment-slash:before{content:"\f4b3"}.fa-comments:before{content:"\f086"}.fa-comments-dollar:before{content:"\f653"}.fa-compact-disc:before{content:"\f51f"}.fa-compass:before{content:"\f14e"}.fa-compress:before{content:"\f066"}.fa-compress-arrows-alt:before{content:"\f78c"}.fa-concierge-bell:before{content:"\f562"}.fa-confluence:before{content:"\f78d"}.fa-connectdevelop:before{content:"\f20e"}.fa-contao:before{content:"\f26d"}.fa-cookie:before{content:"\f563"}.fa-cookie-bite:before{content:"\f564"}.fa-copy:before{content:"\f0c5"}.fa-copyright:before{content:"\f1f9"}.fa-couch:before{content:"\f4b8"}.fa-cpanel:before{content:"\f388"}.fa-creative-commons:before{content:"\f25e"}.fa-creative-commons-by:before{content:"\f4e7"}.fa-creative-commons-nc:before{content:"\f4e8"}.fa-creative-commons-nc-eu:before{content:"\f4e9"}.fa-creative-commons-nc-jp:before{content:"\f4ea"}.fa-creative-commons-nd:before{content:"\f4eb"}.fa-creative-commons-pd:before{content:"\f4ec"}.fa-creative-commons-pd-alt:before{content:"\f4ed"}.fa-creative-commons-remix:before{content:"\f4ee"}.fa-creative-commons-sa:before{content:"\f4ef"}.fa-creative-commons-sampling:before{content:"\f4f0"}.fa-creative-commons-sampling-plus:before{content:"\f4f1"}.fa-creative-commons-share:before{content:"\f4f2"}.fa-creative-commons-zero:before{content:"\f4f3"}.fa-credit-card:before{content:"\f09d"}.fa-critical-role:before{content:"\f6c9"}.fa-crop:before{content:"\f125"}.fa-crop-alt:before{content:"\f565"}.fa-cross:before{content:"\f654"}.fa-crosshairs:before{content:"\f05b"}.fa-crow:before{content:"\f520"}.fa-crown:before{content:"\f521"}.fa-crutch:before{content:"\f7f7"}.fa-css3:before{content:"\f13c"}.fa-css3-alt:before{content:"\f38b"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-cut:before{content:"\f0c4"}.fa-cuttlefish:before{content:"\f38c"}.fa-d-and-d:before{content:"\f38d"}.fa-d-and-d-beyond:before{content:"\f6ca"}.fa-dashcube:before{content:"\f210"}.fa-database:before{content:"\f1c0"}.fa-deaf:before{content:"\f2a4"}.fa-delicious:before{content:"\f1a5"}.fa-democrat:before{content:"\f747"}.fa-deploydog:before{content:"\f38e"}.fa-deskpro:before{content:"\f38f"}.fa-desktop:before{content:"\f108"}.fa-dev:before{content:"\f6cc"}.fa-deviantart:before{content:"\f1bd"}.fa-dharmachakra:before{content:"\f655"}.fa-dhl:before{content:"\f790"}.fa-diagnoses:before{content:"\f470"}.fa-diaspora:before{content:"\f791"}.fa-dice:before{content:"\f522"}.fa-dice-d20:before{content:"\f6cf"}.fa-dice-d6:before{content:"\f6d1"}.fa-dice-five:before{content:"\f523"}.fa-dice-four:before{content:"\f524"}.fa-dice-one:before{content:"\f525"}.fa-dice-six:before{content:"\f526"}.fa-dice-three:before{content:"\f527"}.fa-dice-two:before{content:"\f528"}.fa-digg:before{content:"\f1a6"}.fa-digital-ocean:before{content:"\f391"}.fa-digital-tachograph:before{content:"\f566"}.fa-directions:before{content:"\f5eb"}.fa-discord:before{content:"\f392"}.fa-discourse:before{content:"\f393"}.fa-divide:before{content:"\f529"}.fa-dizzy:before{content:"\f567"}.fa-dna:before{content:"\f471"}.fa-dochub:before{content:"\f394"}.fa-docker:before{content:"\f395"}.fa-dog:before{content:"\f6d3"}.fa-dollar-sign:before{content:"\f155"}.fa-dolly:before{content:"\f472"}.fa-dolly-flatbed:before{content:"\f474"}.fa-donate:before{content:"\f4b9"}.fa-door-closed:before{content:"\f52a"}.fa-door-open:before{content:"\f52b"}.fa-dot-circle:before{content:"\f192"}.fa-dove:before{content:"\f4ba"}.fa-download:before{content:"\f019"}.fa-draft2digital:before{content:"\f396"}.fa-drafting-compass:before{content:"\f568"}.fa-dragon:before{content:"\f6d5"}.fa-draw-polygon:before{content:"\f5ee"}.fa-dribbble:before{content:"\f17d"}.fa-dribbble-square:before{content:"\f397"}.fa-dropbox:before{content:"\f16b"}.fa-drum:before{content:"\f569"}.fa-drum-steelpan:before{content:"\f56a"}.fa-drumstick-bite:before{content:"\f6d7"}.fa-drupal:before{content:"\f1a9"}.fa-dumbbell:before{content:"\f44b"}.fa-dumpster:before{content:"\f793"}.fa-dumpster-fire:before{content:"\f794"}.fa-dungeon:before{content:"\f6d9"}.fa-dyalog:before{content:"\f399"}.fa-earlybirds:before{content:"\f39a"}.fa-ebay:before{content:"\f4f4"}.fa-edge:before{content:"\f282"}.fa-edit:before{content:"\f044"}.fa-egg:before{content:"\f7fb"}.fa-eject:before{content:"\f052"}.fa-elementor:before{content:"\f430"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-ello:before{content:"\f5f1"}.fa-ember:before{content:"\f423"}.fa-empire:before{content:"\f1d1"}.fa-envelope:before{content:"\f0e0"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-text:before{content:"\f658"}.fa-envelope-square:before{content:"\f199"}.fa-envira:before{content:"\f299"}.fa-equals:before{content:"\f52c"}.fa-eraser:before{content:"\f12d"}.fa-erlang:before{content:"\f39d"}.fa-ethereum:before{content:"\f42e"}.fa-ethernet:before{content:"\f796"}.fa-etsy:before{content:"\f2d7"}.fa-euro-sign:before{content:"\f153"}.fa-exchange-alt:before{content:"\f362"}.fa-exclamation:before{content:"\f12a"}.fa-exclamation-circle:before{content:"\f06a"}.fa-exclamation-triangle:before{content:"\f071"}.fa-expand:before{content:"\f065"}.fa-expand-arrows-alt:before{content:"\f31e"}.fa-expeditedssl:before{content:"\f23e"}.fa-external-link-alt:before{content:"\f35d"}.fa-external-link-square-alt:before{content:"\f360"}.fa-eye:before{content:"\f06e"}.fa-eye-dropper:before{content:"\f1fb"}.fa-eye-slash:before{content:"\f070"}.fa-facebook:before{content:"\f09a"}.fa-facebook-f:before{content:"\f39e"}.fa-facebook-messenger:before{content:"\f39f"}.fa-facebook-square:before{content:"\f082"}.fa-fantasy-flight-games:before{content:"\f6dc"}.fa-fast-backward:before{content:"\f049"}.fa-fast-forward:before{content:"\f050"}.fa-fax:before{content:"\f1ac"}.fa-feather:before{content:"\f52d"}.fa-feather-alt:before{content:"\f56b"}.fa-fedex:before{content:"\f797"}.fa-fedora:before{content:"\f798"}.fa-female:before{content:"\f182"}.fa-fighter-jet:before{content:"\f0fb"}.fa-figma:before{content:"\f799"}.fa-file:before{content:"\f15b"}.fa-file-alt:before{content:"\f15c"}.fa-file-archive:before{content:"\f1c6"}.fa-file-audio:before{content:"\f1c7"}.fa-file-code:before{content:"\f1c9"}.fa-file-contract:before{content:"\f56c"}.fa-file-csv:before{content:"\f6dd"}.fa-file-download:before{content:"\f56d"}.fa-file-excel:before{content:"\f1c3"}.fa-file-export:before{content:"\f56e"}.fa-file-image:before{content:"\f1c5"}.fa-file-import:before{content:"\f56f"}.fa-file-invoice:before{content:"\f570"}.fa-file-invoice-dollar:before{content:"\f571"}.fa-file-medical:before{content:"\f477"}.fa-file-medical-alt:before{content:"\f478"}.fa-file-pdf:before{content:"\f1c1"}.fa-file-powerpoint:before{content:"\f1c4"}.fa-file-prescription:before{content:"\f572"}.fa-file-signature:before{content:"\f573"}.fa-file-upload:before{content:"\f574"}.fa-file-video:before{content:"\f1c8"}.fa-file-word:before{content:"\f1c2"}.fa-fill:before{content:"\f575"}.fa-fill-drip:before{content:"\f576"}.fa-film:before{content:"\f008"}.fa-filter:before{content:"\f0b0"}.fa-fingerprint:before{content:"\f577"}.fa-fire:before{content:"\f06d"}.fa-fire-alt:before{content:"\f7e4"}.fa-fire-extinguisher:before{content:"\f134"}.fa-firefox:before{content:"\f269"}.fa-first-aid:before{content:"\f479"}.fa-first-order:before{content:"\f2b0"}.fa-first-order-alt:before{content:"\f50a"}.fa-firstdraft:before{content:"\f3a1"}.fa-fish:before{content:"\f578"}.fa-fist-raised:before{content:"\f6de"}.fa-flag:before{content:"\f024"}.fa-flag-checkered:before{content:"\f11e"}.fa-flag-usa:before{content:"\f74d"}.fa-flask:before{content:"\f0c3"}.fa-flickr:before{content:"\f16e"}.fa-flipboard:before{content:"\f44d"}.fa-flushed:before{content:"\f579"}.fa-fly:before{content:"\f417"}.fa-folder:before{content:"\f07b"}.fa-folder-minus:before{content:"\f65d"}.fa-folder-open:before{content:"\f07c"}.fa-folder-plus:before{content:"\f65e"}.fa-font:before{content:"\f031"}.fa-font-awesome:before{content:"\f2b4"}.fa-font-awesome-alt:before{content:"\f35c"}.fa-font-awesome-flag:before{content:"\f425"}.fa-font-awesome-logo-full:before{content:"\f4e6"}.fa-fonticons:before{content:"\f280"}.fa-fonticons-fi:before{content:"\f3a2"}.fa-football-ball:before{content:"\f44e"}.fa-fort-awesome:before{content:"\f286"}.fa-fort-awesome-alt:before{content:"\f3a3"}.fa-forumbee:before{content:"\f211"}.fa-forward:before{content:"\f04e"}.fa-foursquare:before{content:"\f180"}.fa-free-code-camp:before{content:"\f2c5"}.fa-freebsd:before{content:"\f3a4"}.fa-frog:before{content:"\f52e"}.fa-frown:before{content:"\f119"}.fa-frown-open:before{content:"\f57a"}.fa-fulcrum:before{content:"\f50b"}.fa-funnel-dollar:before{content:"\f662"}.fa-futbol:before{content:"\f1e3"}.fa-galactic-republic:before{content:"\f50c"}.fa-galactic-senate:before{content:"\f50d"}.fa-gamepad:before{content:"\f11b"}.fa-gas-pump:before{content:"\f52f"}.fa-gavel:before{content:"\f0e3"}.fa-gem:before{content:"\f3a5"}.fa-genderless:before{content:"\f22d"}.fa-get-pocket:before{content:"\f265"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-ghost:before{content:"\f6e2"}.fa-gift:before{content:"\f06b"}.fa-gifts:before{content:"\f79c"}.fa-git:before{content:"\f1d3"}.fa-git-square:before{content:"\f1d2"}.fa-github:before{content:"\f09b"}.fa-github-alt:before{content:"\f113"}.fa-github-square:before{content:"\f092"}.fa-gitkraken:before{content:"\f3a6"}.fa-gitlab:before{content:"\f296"}.fa-gitter:before{content:"\f426"}.fa-glass-cheers:before{content:"\f79f"}.fa-glass-martini:before{content:"\f000"}.fa-glass-martini-alt:before{content:"\f57b"}.fa-glass-whiskey:before{content:"\f7a0"}.fa-glasses:before{content:"\f530"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-globe:before{content:"\f0ac"}.fa-globe-africa:before{content:"\f57c"}.fa-globe-americas:before{content:"\f57d"}.fa-globe-asia:before{content:"\f57e"}.fa-globe-europe:before{content:"\f7a2"}.fa-gofore:before{content:"\f3a7"}.fa-golf-ball:before{content:"\f450"}.fa-goodreads:before{content:"\f3a8"}.fa-goodreads-g:before{content:"\f3a9"}.fa-google:before{content:"\f1a0"}.fa-google-drive:before{content:"\f3aa"}.fa-google-play:before{content:"\f3ab"}.fa-google-plus:before{content:"\f2b3"}.fa-google-plus-g:before{content:"\f0d5"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-wallet:before{content:"\f1ee"}.fa-gopuram:before{content:"\f664"}.fa-graduation-cap:before{content:"\f19d"}.fa-gratipay:before{content:"\f184"}.fa-grav:before{content:"\f2d6"}.fa-greater-than:before{content:"\f531"}.fa-greater-than-equal:before{content:"\f532"}.fa-grimace:before{content:"\f57f"}.fa-grin:before{content:"\f580"}.fa-grin-alt:before{content:"\f581"}.fa-grin-beam:before{content:"\f582"}.fa-grin-beam-sweat:before{content:"\f583"}.fa-grin-hearts:before{content:"\f584"}.fa-grin-squint:before{content:"\f585"}.fa-grin-squint-tears:before{content:"\f586"}.fa-grin-stars:before{content:"\f587"}.fa-grin-tears:before{content:"\f588"}.fa-grin-tongue:before{content:"\f589"}.fa-grin-tongue-squint:before{content:"\f58a"}.fa-grin-tongue-wink:before{content:"\f58b"}.fa-grin-wink:before{content:"\f58c"}.fa-grip-horizontal:before{content:"\f58d"}.fa-grip-lines:before{content:"\f7a4"}.fa-grip-lines-vertical:before{content:"\f7a5"}.fa-grip-vertical:before{content:"\f58e"}.fa-gripfire:before{content:"\f3ac"}.fa-grunt:before{content:"\f3ad"}.fa-guitar:before{content:"\f7a6"}.fa-gulp:before{content:"\f3ae"}.fa-h-square:before{content:"\f0fd"}.fa-hacker-news:before{content:"\f1d4"}.fa-hacker-news-square:before{content:"\f3af"}.fa-hackerrank:before{content:"\f5f7"}.fa-hamburger:before{content:"\f805"}.fa-hammer:before{content:"\f6e3"}.fa-hamsa:before{content:"\f665"}.fa-hand-holding:before{content:"\f4bd"}.fa-hand-holding-heart:before{content:"\f4be"}.fa-hand-holding-usd:before{content:"\f4c0"}.fa-hand-lizard:before{content:"\f258"}.fa-hand-middle-finger:before{content:"\f806"}.fa-hand-paper:before{content:"\f256"}.fa-hand-peace:before{content:"\f25b"}.fa-hand-point-down:before{content:"\f0a7"}.fa-hand-point-left:before{content:"\f0a5"}.fa-hand-point-right:before{content:"\f0a4"}.fa-hand-point-up:before{content:"\f0a6"}.fa-hand-pointer:before{content:"\f25a"}.fa-hand-rock:before{content:"\f255"}.fa-hand-scissors:before{content:"\f257"}.fa-hand-spock:before{content:"\f259"}.fa-hands:before{content:"\f4c2"}.fa-hands-helping:before{content:"\f4c4"}.fa-handshake:before{content:"\f2b5"}.fa-hanukiah:before{content:"\f6e6"}.fa-hard-hat:before{content:"\f807"}.fa-hashtag:before{content:"\f292"}.fa-hat-wizard:before{content:"\f6e8"}.fa-haykal:before{content:"\f666"}.fa-hdd:before{content:"\f0a0"}.fa-heading:before{content:"\f1dc"}.fa-headphones:before{content:"\f025"}.fa-headphones-alt:before{content:"\f58f"}.fa-headset:before{content:"\f590"}.fa-heart:before{content:"\f004"}.fa-heart-broken:before{content:"\f7a9"}.fa-heartbeat:before{content:"\f21e"}.fa-helicopter:before{content:"\f533"}.fa-highlighter:before{content:"\f591"}.fa-hiking:before{content:"\f6ec"}.fa-hippo:before{content:"\f6ed"}.fa-hips:before{content:"\f452"}.fa-hire-a-helper:before{content:"\f3b0"}.fa-history:before{content:"\f1da"}.fa-hockey-puck:before{content:"\f453"}.fa-holly-berry:before{content:"\f7aa"}.fa-home:before{content:"\f015"}.fa-hooli:before{content:"\f427"}.fa-hornbill:before{content:"\f592"}.fa-horse:before{content:"\f6f0"}.fa-horse-head:before{content:"\f7ab"}.fa-hospital:before{content:"\f0f8"}.fa-hospital-alt:before{content:"\f47d"}.fa-hospital-symbol:before{content:"\f47e"}.fa-hot-tub:before{content:"\f593"}.fa-hotdog:before{content:"\f80f"}.fa-hotel:before{content:"\f594"}.fa-hotjar:before{content:"\f3b1"}.fa-hourglass:before{content:"\f254"}.fa-hourglass-end:before{content:"\f253"}.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-start:before{content:"\f251"}.fa-house-damage:before{content:"\f6f1"}.fa-houzz:before{content:"\f27c"}.fa-hryvnia:before{content:"\f6f2"}.fa-html5:before{content:"\f13b"}.fa-hubspot:before{content:"\f3b2"}.fa-i-cursor:before{content:"\f246"}.fa-ice-cream:before{content:"\f810"}.fa-icicles:before{content:"\f7ad"}.fa-id-badge:before{content:"\f2c1"}.fa-id-card:before{content:"\f2c2"}.fa-id-card-alt:before{content:"\f47f"}.fa-igloo:before{content:"\f7ae"}.fa-image:before{content:"\f03e"}.fa-images:before{content:"\f302"}.fa-imdb:before{content:"\f2d8"}.fa-inbox:before{content:"\f01c"}.fa-indent:before{content:"\f03c"}.fa-industry:before{content:"\f275"}.fa-infinity:before{content:"\f534"}.fa-info:before{content:"\f129"}.fa-info-circle:before{content:"\f05a"}.fa-instagram:before{content:"\f16d"}.fa-intercom:before{content:"\f7af"}.fa-internet-explorer:before{content:"\f26b"}.fa-invision:before{content:"\f7b0"}.fa-ioxhost:before{content:"\f208"}.fa-italic:before{content:"\f033"}.fa-itunes:before{content:"\f3b4"}.fa-itunes-note:before{content:"\f3b5"}.fa-java:before{content:"\f4e4"}.fa-jedi:before{content:"\f669"}.fa-jedi-order:before{content:"\f50e"}.fa-jenkins:before{content:"\f3b6"}.fa-jira:before{content:"\f7b1"}.fa-joget:before{content:"\f3b7"}.fa-joint:before{content:"\f595"}.fa-joomla:before{content:"\f1aa"}.fa-journal-whills:before{content:"\f66a"}.fa-js:before{content:"\f3b8"}.fa-js-square:before{content:"\f3b9"}.fa-jsfiddle:before{content:"\f1cc"}.fa-kaaba:before{content:"\f66b"}.fa-kaggle:before{content:"\f5fa"}.fa-key:before{content:"\f084"}.fa-keybase:before{content:"\f4f5"}.fa-keyboard:before{content:"\f11c"}.fa-keycdn:before{content:"\f3ba"}.fa-khanda:before{content:"\f66d"}.fa-kickstarter:before{content:"\f3bb"}.fa-kickstarter-k:before{content:"\f3bc"}.fa-kiss:before{content:"\f596"}.fa-kiss-beam:before{content:"\f597"}.fa-kiss-wink-heart:before{content:"\f598"}.fa-kiwi-bird:before{content:"\f535"}.fa-korvue:before{content:"\f42f"}.fa-landmark:before{content:"\f66f"}.fa-language:before{content:"\f1ab"}.fa-laptop:before{content:"\f109"}.fa-laptop-code:before{content:"\f5fc"}.fa-laptop-medical:before{content:"\f812"}.fa-laravel:before{content:"\f3bd"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-laugh:before{content:"\f599"}.fa-laugh-beam:before{content:"\f59a"}.fa-laugh-squint:before{content:"\f59b"}.fa-laugh-wink:before{content:"\f59c"}.fa-layer-group:before{content:"\f5fd"}.fa-leaf:before{content:"\f06c"}.fa-leanpub:before{content:"\f212"}.fa-lemon:before{content:"\f094"}.fa-less:before{content:"\f41d"}.fa-less-than:before{content:"\f536"}.fa-less-than-equal:before{content:"\f537"}.fa-level-down-alt:before{content:"\f3be"}.fa-level-up-alt:before{content:"\f3bf"}.fa-life-ring:before{content:"\f1cd"}.fa-lightbulb:before{content:"\f0eb"}.fa-line:before{content:"\f3c0"}.fa-link:before{content:"\f0c1"}.fa-linkedin:before{content:"\f08c"}.fa-linkedin-in:before{content:"\f0e1"}.fa-linode:before{content:"\f2b8"}.fa-linux:before{content:"\f17c"}.fa-lira-sign:before{content:"\f195"}.fa-list:before{content:"\f03a"}.fa-list-alt:before{content:"\f022"}.fa-list-ol:before{content:"\f0cb"}.fa-list-ul:before{content:"\f0ca"}.fa-location-arrow:before{content:"\f124"}.fa-lock:before{content:"\f023"}.fa-lock-open:before{content:"\f3c1"}.fa-long-arrow-alt-down:before{content:"\f309"}.fa-long-arrow-alt-left:before{content:"\f30a"}.fa-long-arrow-alt-right:before{content:"\f30b"}.fa-long-arrow-alt-up:before{content:"\f30c"}.fa-low-vision:before{content:"\f2a8"}.fa-luggage-cart:before{content:"\f59d"}.fa-lyft:before{content:"\f3c3"}.fa-magento:before{content:"\f3c4"}.fa-magic:before{content:"\f0d0"}.fa-magnet:before{content:"\f076"}.fa-mail-bulk:before{content:"\f674"}.fa-mailchimp:before{content:"\f59e"}.fa-male:before{content:"\f183"}.fa-mandalorian:before{content:"\f50f"}.fa-map:before{content:"\f279"}.fa-map-marked:before{content:"\f59f"}.fa-map-marked-alt:before{content:"\f5a0"}.fa-map-marker:before{content:"\f041"}.fa-map-marker-alt:before{content:"\f3c5"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-markdown:before{content:"\f60f"}.fa-marker:before{content:"\f5a1"}.fa-mars:before{content:"\f222"}.fa-mars-double:before{content:"\f227"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mask:before{content:"\f6fa"}.fa-mastodon:before{content:"\f4f6"}.fa-maxcdn:before{content:"\f136"}.fa-medal:before{content:"\f5a2"}.fa-medapps:before{content:"\f3c6"}.fa-medium:before{content:"\f23a"}.fa-medium-m:before{content:"\f3c7"}.fa-medkit:before{content:"\f0fa"}.fa-medrt:before{content:"\f3c8"}.fa-meetup:before{content:"\f2e0"}.fa-megaport:before{content:"\f5a3"}.fa-meh:before{content:"\f11a"}.fa-meh-blank:before{content:"\f5a4"}.fa-meh-rolling-eyes:before{content:"\f5a5"}.fa-memory:before{content:"\f538"}.fa-mendeley:before{content:"\f7b3"}.fa-menorah:before{content:"\f676"}.fa-mercury:before{content:"\f223"}.fa-meteor:before{content:"\f753"}.fa-microchip:before{content:"\f2db"}.fa-microphone:before{content:"\f130"}.fa-microphone-alt:before{content:"\f3c9"}.fa-microphone-alt-slash:before{content:"\f539"}.fa-microphone-slash:before{content:"\f131"}.fa-microscope:before{content:"\f610"}.fa-microsoft:before{content:"\f3ca"}.fa-minus:before{content:"\f068"}.fa-minus-circle:before{content:"\f056"}.fa-minus-square:before{content:"\f146"}.fa-mitten:before{content:"\f7b5"}.fa-mix:before{content:"\f3cb"}.fa-mixcloud:before{content:"\f289"}.fa-mizuni:before{content:"\f3cc"}.fa-mobile:before{content:"\f10b"}.fa-mobile-alt:before{content:"\f3cd"}.fa-modx:before{content:"\f285"}.fa-monero:before{content:"\f3d0"}.fa-money-bill:before{content:"\f0d6"}.fa-money-bill-alt:before{content:"\f3d1"}.fa-money-bill-wave:before{content:"\f53a"}.fa-money-bill-wave-alt:before{content:"\f53b"}.fa-money-check:before{content:"\f53c"}.fa-money-check-alt:before{content:"\f53d"}.fa-monument:before{content:"\f5a6"}.fa-moon:before{content:"\f186"}.fa-mortar-pestle:before{content:"\f5a7"}.fa-mosque:before{content:"\f678"}.fa-motorcycle:before{content:"\f21c"}.fa-mountain:before{content:"\f6fc"}.fa-mouse-pointer:before{content:"\f245"}.fa-mug-hot:before{content:"\f7b6"}.fa-music:before{content:"\f001"}.fa-napster:before{content:"\f3d2"}.fa-neos:before{content:"\f612"}.fa-network-wired:before{content:"\f6ff"}.fa-neuter:before{content:"\f22c"}.fa-newspaper:before{content:"\f1ea"}.fa-nimblr:before{content:"\f5a8"}.fa-nintendo-switch:before{content:"\f418"}.fa-node:before{content:"\f419"}.fa-node-js:before{content:"\f3d3"}.fa-not-equal:before{content:"\f53e"}.fa-notes-medical:before{content:"\f481"}.fa-npm:before{content:"\f3d4"}.fa-ns8:before{content:"\f3d5"}.fa-nutritionix:before{content:"\f3d6"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-oil-can:before{content:"\f613"}.fa-old-republic:before{content:"\f510"}.fa-om:before{content:"\f679"}.fa-opencart:before{content:"\f23d"}.fa-openid:before{content:"\f19b"}.fa-opera:before{content:"\f26a"}.fa-optin-monster:before{content:"\f23c"}.fa-osi:before{content:"\f41a"}.fa-otter:before{content:"\f700"}.fa-outdent:before{content:"\f03b"}.fa-page4:before{content:"\f3d7"}.fa-pagelines:before{content:"\f18c"}.fa-pager:before{content:"\f815"}.fa-paint-brush:before{content:"\f1fc"}.fa-paint-roller:before{content:"\f5aa"}.fa-palette:before{content:"\f53f"}.fa-palfed:before{content:"\f3d8"}.fa-pallet:before{content:"\f482"}.fa-paper-plane:before{content:"\f1d8"}.fa-paperclip:before{content:"\f0c6"}.fa-parachute-box:before{content:"\f4cd"}.fa-paragraph:before{content:"\f1dd"}.fa-parking:before{content:"\f540"}.fa-passport:before{content:"\f5ab"}.fa-pastafarianism:before{content:"\f67b"}.fa-paste:before{content:"\f0ea"}.fa-patreon:before{content:"\f3d9"}.fa-pause:before{content:"\f04c"}.fa-pause-circle:before{content:"\f28b"}.fa-paw:before{content:"\f1b0"}.fa-paypal:before{content:"\f1ed"}.fa-peace:before{content:"\f67c"}.fa-pen:before{content:"\f304"}.fa-pen-alt:before{content:"\f305"}.fa-pen-fancy:before{content:"\f5ac"}.fa-pen-nib:before{content:"\f5ad"}.fa-pen-square:before{content:"\f14b"}.fa-pencil-alt:before{content:"\f303"}.fa-pencil-ruler:before{content:"\f5ae"}.fa-penny-arcade:before{content:"\f704"}.fa-people-carry:before{content:"\f4ce"}.fa-pepper-hot:before{content:"\f816"}.fa-percent:before{content:"\f295"}.fa-percentage:before{content:"\f541"}.fa-periscope:before{content:"\f3da"}.fa-person-booth:before{content:"\f756"}.fa-phabricator:before{content:"\f3db"}.fa-phoenix-framework:before{content:"\f3dc"}.fa-phoenix-squadron:before{content:"\f511"}.fa-phone:before{content:"\f095"}.fa-phone-slash:before{content:"\f3dd"}.fa-phone-square:before{content:"\f098"}.fa-phone-volume:before{content:"\f2a0"}.fa-php:before{content:"\f457"}.fa-pied-piper:before{content:"\f2ae"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-pied-piper-hat:before{content:"\f4e5"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-piggy-bank:before{content:"\f4d3"}.fa-pills:before{content:"\f484"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-p:before{content:"\f231"}.fa-pinterest-square:before{content:"\f0d3"}.fa-pizza-slice:before{content:"\f818"}.fa-place-of-worship:before{content:"\f67f"}.fa-plane:before{content:"\f072"}.fa-plane-arrival:before{content:"\f5af"}.fa-plane-departure:before{content:"\f5b0"}.fa-play:before{content:"\f04b"}.fa-play-circle:before{content:"\f144"}.fa-playstation:before{content:"\f3df"}.fa-plug:before{content:"\f1e6"}.fa-plus:before{content:"\f067"}.fa-plus-circle:before{content:"\f055"}.fa-plus-square:before{content:"\f0fe"}.fa-podcast:before{content:"\f2ce"}.fa-poll:before{content:"\f681"}.fa-poll-h:before{content:"\f682"}.fa-poo:before{content:"\f2fe"}.fa-poo-storm:before{content:"\f75a"}.fa-poop:before{content:"\f619"}.fa-portrait:before{content:"\f3e0"}.fa-pound-sign:before{content:"\f154"}.fa-power-off:before{content:"\f011"}.fa-pray:before{content:"\f683"}.fa-praying-hands:before{content:"\f684"}.fa-prescription:before{content:"\f5b1"}.fa-prescription-bottle:before{content:"\f485"}.fa-prescription-bottle-alt:before{content:"\f486"}.fa-print:before{content:"\f02f"}.fa-procedures:before{content:"\f487"}.fa-product-hunt:before{content:"\f288"}.fa-project-diagram:before{content:"\f542"}.fa-pushed:before{content:"\f3e1"}.fa-puzzle-piece:before{content:"\f12e"}.fa-python:before{content:"\f3e2"}.fa-qq:before{content:"\f1d6"}.fa-qrcode:before{content:"\f029"}.fa-question:before{content:"\f128"}.fa-question-circle:before{content:"\f059"}.fa-quidditch:before{content:"\f458"}.fa-quinscape:before{content:"\f459"}.fa-quora:before{content:"\f2c4"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-quran:before{content:"\f687"}.fa-r-project:before{content:"\f4f7"}.fa-radiation:before{content:"\f7b9"}.fa-radiation-alt:before{content:"\f7ba"}.fa-rainbow:before{content:"\f75b"}.fa-random:before{content:"\f074"}.fa-raspberry-pi:before{content:"\f7bb"}.fa-ravelry:before{content:"\f2d9"}.fa-react:before{content:"\f41b"}.fa-reacteurope:before{content:"\f75d"}.fa-readme:before{content:"\f4d5"}.fa-rebel:before{content:"\f1d0"}.fa-receipt:before{content:"\f543"}.fa-recycle:before{content:"\f1b8"}.fa-red-river:before{content:"\f3e3"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-alien:before{content:"\f281"}.fa-reddit-square:before{content:"\f1a2"}.fa-redhat:before{content:"\f7bc"}.fa-redo:before{content:"\f01e"}.fa-redo-alt:before{content:"\f2f9"}.fa-registered:before{content:"\f25d"}.fa-renren:before{content:"\f18b"}.fa-reply:before{content:"\f3e5"}.fa-reply-all:before{content:"\f122"}.fa-replyd:before{content:"\f3e6"}.fa-republican:before{content:"\f75e"}.fa-researchgate:before{content:"\f4f8"}.fa-resolving:before{content:"\f3e7"}.fa-restroom:before{content:"\f7bd"}.fa-retweet:before{content:"\f079"}.fa-rev:before{content:"\f5b2"}.fa-ribbon:before{content:"\f4d6"}.fa-ring:before{content:"\f70b"}.fa-road:before{content:"\f018"}.fa-robot:before{content:"\f544"}.fa-rocket:before{content:"\f135"}.fa-rocketchat:before{content:"\f3e8"}.fa-rockrms:before{content:"\f3e9"}.fa-route:before{content:"\f4d7"}.fa-rss:before{content:"\f09e"}.fa-rss-square:before{content:"\f143"}.fa-ruble-sign:before{content:"\f158"}.fa-ruler:before{content:"\f545"}.fa-ruler-combined:before{content:"\f546"}.fa-ruler-horizontal:before{content:"\f547"}.fa-ruler-vertical:before{content:"\f548"}.fa-running:before{content:"\f70c"}.fa-rupee-sign:before{content:"\f156"}.fa-sad-cry:before{content:"\f5b3"}.fa-sad-tear:before{content:"\f5b4"}.fa-safari:before{content:"\f267"}.fa-sass:before{content:"\f41e"}.fa-satellite:before{content:"\f7bf"}.fa-satellite-dish:before{content:"\f7c0"}.fa-save:before{content:"\f0c7"}.fa-schlix:before{content:"\f3ea"}.fa-school:before{content:"\f549"}.fa-screwdriver:before{content:"\f54a"}.fa-scribd:before{content:"\f28a"}.fa-scroll:before{content:"\f70e"}.fa-sd-card:before{content:"\f7c2"}.fa-search:before{content:"\f002"}.fa-search-dollar:before{content:"\f688"}.fa-search-location:before{content:"\f689"}.fa-search-minus:before{content:"\f010"}.fa-search-plus:before{content:"\f00e"}.fa-searchengin:before{content:"\f3eb"}.fa-seedling:before{content:"\f4d8"}.fa-sellcast:before{content:"\f2da"}.fa-sellsy:before{content:"\f213"}.fa-server:before{content:"\f233"}.fa-servicestack:before{content:"\f3ec"}.fa-shapes:before{content:"\f61f"}.fa-share:before{content:"\f064"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-share-square:before{content:"\f14d"}.fa-shekel-sign:before{content:"\f20b"}.fa-shield-alt:before{content:"\f3ed"}.fa-ship:before{content:"\f21a"}.fa-shipping-fast:before{content:"\f48b"}.fa-shirtsinbulk:before{content:"\f214"}.fa-shoe-prints:before{content:"\f54b"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-shopping-cart:before{content:"\f07a"}.fa-shopware:before{content:"\f5b5"}.fa-shower:before{content:"\f2cc"}.fa-shuttle-van:before{content:"\f5b6"}.fa-sign:before{content:"\f4d9"}.fa-sign-in-alt:before{content:"\f2f6"}.fa-sign-language:before{content:"\f2a7"}.fa-sign-out-alt:before{content:"\f2f5"}.fa-signal:before{content:"\f012"}.fa-signature:before{content:"\f5b7"}.fa-sim-card:before{content:"\f7c4"}.fa-simplybuilt:before{content:"\f215"}.fa-sistrix:before{content:"\f3ee"}.fa-sitemap:before{content:"\f0e8"}.fa-sith:before{content:"\f512"}.fa-skating:before{content:"\f7c5"}.fa-sketch:before{content:"\f7c6"}.fa-skiing:before{content:"\f7c9"}.fa-skiing-nordic:before{content:"\f7ca"}.fa-skull:before{content:"\f54c"}.fa-skull-crossbones:before{content:"\f714"}.fa-skyatlas:before{content:"\f216"}.fa-skype:before{content:"\f17e"}.fa-slack:before{content:"\f198"}.fa-slack-hash:before{content:"\f3ef"}.fa-slash:before{content:"\f715"}.fa-sleigh:before{content:"\f7cc"}.fa-sliders-h:before{content:"\f1de"}.fa-slideshare:before{content:"\f1e7"}.fa-smile:before{content:"\f118"}.fa-smile-beam:before{content:"\f5b8"}.fa-smile-wink:before{content:"\f4da"}.fa-smog:before{content:"\f75f"}.fa-smoking:before{content:"\f48d"}.fa-smoking-ban:before{content:"\f54d"}.fa-sms:before{content:"\f7cd"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-snowboarding:before{content:"\f7ce"}.fa-snowflake:before{content:"\f2dc"}.fa-snowman:before{content:"\f7d0"}.fa-snowplow:before{content:"\f7d2"}.fa-socks:before{content:"\f696"}.fa-solar-panel:before{content:"\f5ba"}.fa-sort:before{content:"\f0dc"}.fa-sort-alpha-down:before{content:"\f15d"}.fa-sort-alpha-up:before{content:"\f15e"}.fa-sort-amount-down:before{content:"\f160"}.fa-sort-amount-up:before{content:"\f161"}.fa-sort-down:before{content:"\f0dd"}.fa-sort-numeric-down:before{content:"\f162"}.fa-sort-numeric-up:before{content:"\f163"}.fa-sort-up:before{content:"\f0de"}.fa-soundcloud:before{content:"\f1be"}.fa-sourcetree:before{content:"\f7d3"}.fa-spa:before{content:"\f5bb"}.fa-space-shuttle:before{content:"\f197"}.fa-speakap:before{content:"\f3f3"}.fa-spider:before{content:"\f717"}.fa-spinner:before{content:"\f110"}.fa-splotch:before{content:"\f5bc"}.fa-spotify:before{content:"\f1bc"}.fa-spray-can:before{content:"\f5bd"}.fa-square:before{content:"\f0c8"}.fa-square-full:before{content:"\f45c"}.fa-square-root-alt:before{content:"\f698"}.fa-squarespace:before{content:"\f5be"}.fa-stack-exchange:before{content:"\f18d"}.fa-stack-overflow:before{content:"\f16c"}.fa-stamp:before{content:"\f5bf"}.fa-star:before{content:"\f005"}.fa-star-and-crescent:before{content:"\f699"}.fa-star-half:before{content:"\f089"}.fa-star-half-alt:before{content:"\f5c0"}.fa-star-of-david:before{content:"\f69a"}.fa-star-of-life:before{content:"\f621"}.fa-staylinked:before{content:"\f3f5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-steam-symbol:before{content:"\f3f6"}.fa-step-backward:before{content:"\f048"}.fa-step-forward:before{content:"\f051"}.fa-stethoscope:before{content:"\f0f1"}.fa-sticker-mule:before{content:"\f3f7"}.fa-sticky-note:before{content:"\f249"}.fa-stop:before{content:"\f04d"}.fa-stop-circle:before{content:"\f28d"}.fa-stopwatch:before{content:"\f2f2"}.fa-store:before{content:"\f54e"}.fa-store-alt:before{content:"\f54f"}.fa-strava:before{content:"\f428"}.fa-stream:before{content:"\f550"}.fa-street-view:before{content:"\f21d"}.fa-strikethrough:before{content:"\f0cc"}.fa-stripe:before{content:"\f429"}.fa-stripe-s:before{content:"\f42a"}.fa-stroopwafel:before{content:"\f551"}.fa-studiovinari:before{content:"\f3f8"}.fa-stumbleupon:before{content:"\f1a4"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-subscript:before{content:"\f12c"}.fa-subway:before{content:"\f239"}.fa-suitcase:before{content:"\f0f2"}.fa-suitcase-rolling:before{content:"\f5c1"}.fa-sun:before{content:"\f185"}.fa-superpowers:before{content:"\f2dd"}.fa-superscript:before{content:"\f12b"}.fa-supple:before{content:"\f3f9"}.fa-surprise:before{content:"\f5c2"}.fa-suse:before{content:"\f7d6"}.fa-swatchbook:before{content:"\f5c3"}.fa-swimmer:before{content:"\f5c4"}.fa-swimming-pool:before{content:"\f5c5"}.fa-synagogue:before{content:"\f69b"}.fa-sync:before{content:"\f021"}.fa-sync-alt:before{content:"\f2f1"}.fa-syringe:before{content:"\f48e"}.fa-table:before{content:"\f0ce"}.fa-table-tennis:before{content:"\f45d"}.fa-tablet:before{content:"\f10a"}.fa-tablet-alt:before{content:"\f3fa"}.fa-tablets:before{content:"\f490"}.fa-tachometer-alt:before{content:"\f3fd"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-tape:before{content:"\f4db"}.fa-tasks:before{content:"\f0ae"}.fa-taxi:before{content:"\f1ba"}.fa-teamspeak:before{content:"\f4f9"}.fa-teeth:before{content:"\f62e"}.fa-teeth-open:before{content:"\f62f"}.fa-telegram:before{content:"\f2c6"}.fa-telegram-plane:before{content:"\f3fe"}.fa-temperature-high:before{content:"\f769"}.fa-temperature-low:before{content:"\f76b"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-tenge:before{content:"\f7d7"}.fa-terminal:before{content:"\f120"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-th:before{content:"\f00a"}.fa-th-large:before{content:"\f009"}.fa-th-list:before{content:"\f00b"}.fa-the-red-yeti:before{content:"\f69d"}.fa-theater-masks:before{content:"\f630"}.fa-themeco:before{content:"\f5c6"}.fa-themeisle:before{content:"\f2b2"}.fa-thermometer:before{content:"\f491"}.fa-thermometer-empty:before{content:"\f2cb"}.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-think-peaks:before{content:"\f731"}.fa-thumbs-down:before{content:"\f165"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbtack:before{content:"\f08d"}.fa-ticket-alt:before{content:"\f3ff"}.fa-times:before{content:"\f00d"}.fa-times-circle:before{content:"\f057"}.fa-tint:before{content:"\f043"}.fa-tint-slash:before{content:"\f5c7"}.fa-tired:before{content:"\f5c8"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-toilet:before{content:"\f7d8"}.fa-toilet-paper:before{content:"\f71e"}.fa-toolbox:before{content:"\f552"}.fa-tools:before{content:"\f7d9"}.fa-tooth:before{content:"\f5c9"}.fa-torah:before{content:"\f6a0"}.fa-torii-gate:before{content:"\f6a1"}.fa-tractor:before{content:"\f722"}.fa-trade-federation:before{content:"\f513"}.fa-trademark:before{content:"\f25c"}.fa-traffic-light:before{content:"\f637"}.fa-train:before{content:"\f238"}.fa-tram:before{content:"\f7da"}.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-trash:before{content:"\f1f8"}.fa-trash-alt:before{content:"\f2ed"}.fa-trash-restore:before{content:"\f829"}.fa-trash-restore-alt:before{content:"\f82a"}.fa-tree:before{content:"\f1bb"}.fa-trello:before{content:"\f181"}.fa-tripadvisor:before{content:"\f262"}.fa-trophy:before{content:"\f091"}.fa-truck:before{content:"\f0d1"}.fa-truck-loading:before{content:"\f4de"}.fa-truck-monster:before{content:"\f63b"}.fa-truck-moving:before{content:"\f4df"}.fa-truck-pickup:before{content:"\f63c"}.fa-tshirt:before{content:"\f553"}.fa-tty:before{content:"\f1e4"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-tv:before{content:"\f26c"}.fa-twitch:before{content:"\f1e8"}.fa-twitter:before{content:"\f099"}.fa-twitter-square:before{content:"\f081"}.fa-typo3:before{content:"\f42b"}.fa-uber:before{content:"\f402"}.fa-ubuntu:before{content:"\f7df"}.fa-uikit:before{content:"\f403"}.fa-umbrella:before{content:"\f0e9"}.fa-umbrella-beach:before{content:"\f5ca"}.fa-underline:before{content:"\f0cd"}.fa-undo:before{content:"\f0e2"}.fa-undo-alt:before{content:"\f2ea"}.fa-uniregistry:before{content:"\f404"}.fa-universal-access:before{content:"\f29a"}.fa-university:before{content:"\f19c"}.fa-unlink:before{content:"\f127"}.fa-unlock:before{content:"\f09c"}.fa-unlock-alt:before{content:"\f13e"}.fa-untappd:before{content:"\f405"}.fa-upload:before{content:"\f093"}.fa-ups:before{content:"\f7e0"}.fa-usb:before{content:"\f287"}.fa-user:before{content:"\f007"}.fa-user-alt:before{content:"\f406"}.fa-user-alt-slash:before{content:"\f4fa"}.fa-user-astronaut:before{content:"\f4fb"}.fa-user-check:before{content:"\f4fc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-clock:before{content:"\f4fd"}.fa-user-cog:before{content:"\f4fe"}.fa-user-edit:before{content:"\f4ff"}.fa-user-friends:before{content:"\f500"}.fa-user-graduate:before{content:"\f501"}.fa-user-injured:before{content:"\f728"}.fa-user-lock:before{content:"\f502"}.fa-user-md:before{content:"\f0f0"}.fa-user-minus:before{content:"\f503"}.fa-user-ninja:before{content:"\f504"}.fa-user-nurse:before{content:"\f82f"}.fa-user-plus:before{content:"\f234"}.fa-user-secret:before{content:"\f21b"}.fa-user-shield:before{content:"\f505"}.fa-user-slash:before{content:"\f506"}.fa-user-tag:before{content:"\f507"}.fa-user-tie:before{content:"\f508"}.fa-user-times:before{content:"\f235"}.fa-users:before{content:"\f0c0"}.fa-users-cog:before{content:"\f509"}.fa-usps:before{content:"\f7e1"}.fa-ussunnah:before{content:"\f407"}.fa-utensil-spoon:before{content:"\f2e5"}.fa-utensils:before{content:"\f2e7"}.fa-vaadin:before{content:"\f408"}.fa-vector-square:before{content:"\f5cb"}.fa-venus:before{content:"\f221"}.fa-venus-double:before{content:"\f226"}.fa-venus-mars:before{content:"\f228"}.fa-viacoin:before{content:"\f237"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-vial:before{content:"\f492"}.fa-vials:before{content:"\f493"}.fa-viber:before{content:"\f409"}.fa-video:before{content:"\f03d"}.fa-video-slash:before{content:"\f4e2"}.fa-vihara:before{content:"\f6a7"}.fa-vimeo:before{content:"\f40a"}.fa-vimeo-square:before{content:"\f194"}.fa-vimeo-v:before{content:"\f27d"}.fa-vine:before{content:"\f1ca"}.fa-vk:before{content:"\f189"}.fa-vnv:before{content:"\f40b"}.fa-volleyball-ball:before{content:"\f45f"}.fa-volume-down:before{content:"\f027"}.fa-volume-mute:before{content:"\f6a9"}.fa-volume-off:before{content:"\f026"}.fa-volume-up:before{content:"\f028"}.fa-vote-yea:before{content:"\f772"}.fa-vr-cardboard:before{content:"\f729"}.fa-vuejs:before{content:"\f41f"}.fa-walking:before{content:"\f554"}.fa-wallet:before{content:"\f555"}.fa-warehouse:before{content:"\f494"}.fa-water:before{content:"\f773"}.fa-weebly:before{content:"\f5cc"}.fa-weibo:before{content:"\f18a"}.fa-weight:before{content:"\f496"}.fa-weight-hanging:before{content:"\f5cd"}.fa-weixin:before{content:"\f1d7"}.fa-whatsapp:before{content:"\f232"}.fa-whatsapp-square:before{content:"\f40c"}.fa-wheelchair:before{content:"\f193"}.fa-whmcs:before{content:"\f40d"}.fa-wifi:before{content:"\f1eb"}.fa-wikipedia-w:before{content:"\f266"}.fa-wind:before{content:"\f72e"}.fa-window-close:before{content:"\f410"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-windows:before{content:"\f17a"}.fa-wine-bottle:before{content:"\f72f"}.fa-wine-glass:before{content:"\f4e3"}.fa-wine-glass-alt:before{content:"\f5ce"}.fa-wix:before{content:"\f5cf"}.fa-wizards-of-the-coast:before{content:"\f730"}.fa-wolf-pack-battalion:before{content:"\f514"}.fa-won-sign:before{content:"\f159"}.fa-wordpress:before{content:"\f19a"}.fa-wordpress-simple:before{content:"\f411"}.fa-wpbeginner:before{content:"\f297"}.fa-wpexplorer:before{content:"\f2de"}.fa-wpforms:before{content:"\f298"}.fa-wpressr:before{content:"\f3e4"}.fa-wrench:before{content:"\f0ad"}.fa-x-ray:before{content:"\f497"}.fa-xbox:before{content:"\f412"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-y-combinator:before{content:"\f23b"}.fa-yahoo:before{content:"\f19e"}.fa-yandex:before{content:"\f413"}.fa-yandex-international:before{content:"\f414"}.fa-yarn:before{content:"\f7e3"}.fa-yelp:before{content:"\f1e9"}.fa-yen-sign:before{content:"\f157"}.fa-yin-yang:before{content:"\f6ad"}.fa-yoast:before{content:"\f2b1"}.fa-youtube:before{content:"\f167"}.fa-youtube-square:before{content:"\f431"}.fa-zhihu:before{content:"\f63f"}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}@font-face{font-family:"Font Awesome 5 Brands";font-style:normal;font-weight:normal;font-display:auto;src:url(../webfonts/fa-brands-400.eot);src:url(../webfonts/fa-brands-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.woff) format("woff"),url(../webfonts/fa-brands-400.ttf) format("truetype"),url(../webfonts/fa-brands-400.svg#fontawesome) format("svg")}.fab{font-family:"Font Awesome 5 Brands"}@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:400;font-display:auto;src:url(../webfonts/fa-regular-400.eot);src:url(../webfonts/fa-regular-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.woff) format("woff"),url(../webfonts/fa-regular-400.ttf) format("truetype"),url(../webfonts/fa-regular-400.svg#fontawesome) format("svg")}.far{font-weight:400}@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:900;font-display:auto;src:url(../webfonts/fa-solid-900.eot);src:url(../webfonts/fa-solid-900.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.woff) format("woff"),url(../webfonts/fa-solid-900.ttf) format("truetype"),url(../webfonts/fa-solid-900.svg#fontawesome) format("svg")}.fa,.far,.fas{font-family:"Font Awesome 5 Free"}.fa,.fas{font-weight:900} \ No newline at end of file diff --git a/app/userland/app-stdlib/css/fontawesome.css.js b/app/userland/app-stdlib/css/fontawesome.css.js new file mode 100644 index 0000000000..6b1ed46583 --- /dev/null +++ b/app/userland/app-stdlib/css/fontawesome.css.js @@ -0,0 +1,6 @@ +import {css} from '../vendor/lit-element/lit-element.js' + +const cssStr = css` +.fa,.fab,.fal,.far,.fas{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:.08em solid #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{animation:fa-spin 2s infinite linear}.fa-pulse{animation:fa-spin 1s infinite steps(8)}@keyframes fa-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";transform:scaleX(-1)}.fa-flip-vertical{transform:scaleY(-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical,.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{transform:scale(-1)}:root .fa-flip-both,:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2.5em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-500px:before{content:"\f26e"}.fa-accessible-icon:before{content:"\f368"}.fa-accusoft:before{content:"\f369"}.fa-acquisitions-incorporated:before{content:"\f6af"}.fa-ad:before{content:"\f641"}.fa-address-book:before{content:"\f2b9"}.fa-address-card:before{content:"\f2bb"}.fa-adjust:before{content:"\f042"}.fa-adn:before{content:"\f170"}.fa-adobe:before{content:"\f778"}.fa-adversal:before{content:"\f36a"}.fa-affiliatetheme:before{content:"\f36b"}.fa-air-freshener:before{content:"\f5d0"}.fa-algolia:before{content:"\f36c"}.fa-align-center:before{content:"\f037"}.fa-align-justify:before{content:"\f039"}.fa-align-left:before{content:"\f036"}.fa-align-right:before{content:"\f038"}.fa-alipay:before{content:"\f642"}.fa-allergies:before{content:"\f461"}.fa-amazon:before{content:"\f270"}.fa-amazon-pay:before{content:"\f42c"}.fa-ambulance:before{content:"\f0f9"}.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-amilia:before{content:"\f36d"}.fa-anchor:before{content:"\f13d"}.fa-android:before{content:"\f17b"}.fa-angellist:before{content:"\f209"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-down:before{content:"\f107"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angry:before{content:"\f556"}.fa-angrycreative:before{content:"\f36e"}.fa-angular:before{content:"\f420"}.fa-ankh:before{content:"\f644"}.fa-app-store:before{content:"\f36f"}.fa-app-store-ios:before{content:"\f370"}.fa-apper:before{content:"\f371"}.fa-apple:before{content:"\f179"}.fa-apple-alt:before{content:"\f5d1"}.fa-apple-pay:before{content:"\f415"}.fa-archive:before{content:"\f187"}.fa-archway:before{content:"\f557"}.fa-arrow-alt-circle-down:before{content:"\f358"}.fa-arrow-alt-circle-left:before{content:"\f359"}.fa-arrow-alt-circle-right:before{content:"\f35a"}.fa-arrow-alt-circle-up:before{content:"\f35b"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-down:before{content:"\f063"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrows-alt:before{content:"\f0b2"}.fa-arrows-alt-h:before{content:"\f337"}.fa-arrows-alt-v:before{content:"\f338"}.fa-artstation:before{content:"\f77a"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asterisk:before{content:"\f069"}.fa-asymmetrik:before{content:"\f372"}.fa-at:before{content:"\f1fa"}.fa-atlas:before{content:"\f558"}.fa-atlassian:before{content:"\f77b"}.fa-atom:before{content:"\f5d2"}.fa-audible:before{content:"\f373"}.fa-audio-description:before{content:"\f29e"}.fa-autoprefixer:before{content:"\f41c"}.fa-avianex:before{content:"\f374"}.fa-aviato:before{content:"\f421"}.fa-award:before{content:"\f559"}.fa-aws:before{content:"\f375"}.fa-baby:before{content:"\f77c"}.fa-baby-carriage:before{content:"\f77d"}.fa-backspace:before{content:"\f55a"}.fa-backward:before{content:"\f04a"}.fa-bacon:before{content:"\f7e5"}.fa-balance-scale:before{content:"\f24e"}.fa-ban:before{content:"\f05e"}.fa-band-aid:before{content:"\f462"}.fa-bandcamp:before{content:"\f2d5"}.fa-barcode:before{content:"\f02a"}.fa-bars:before{content:"\f0c9"}.fa-baseball-ball:before{content:"\f433"}.fa-basketball-ball:before{content:"\f434"}.fa-bath:before{content:"\f2cd"}.fa-battery-empty:before{content:"\f244"}.fa-battery-full:before{content:"\f240"}.fa-battery-half:before{content:"\f242"}.fa-battery-quarter:before{content:"\f243"}.fa-battery-three-quarters:before{content:"\f241"}.fa-bed:before{content:"\f236"}.fa-beer:before{content:"\f0fc"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-bell:before{content:"\f0f3"}.fa-bell-slash:before{content:"\f1f6"}.fa-bezier-curve:before{content:"\f55b"}.fa-bible:before{content:"\f647"}.fa-bicycle:before{content:"\f206"}.fa-bimobject:before{content:"\f378"}.fa-binoculars:before{content:"\f1e5"}.fa-biohazard:before{content:"\f780"}.fa-birthday-cake:before{content:"\f1fd"}.fa-bitbucket:before{content:"\f171"}.fa-bitcoin:before{content:"\f379"}.fa-bity:before{content:"\f37a"}.fa-black-tie:before{content:"\f27e"}.fa-blackberry:before{content:"\f37b"}.fa-blender:before{content:"\f517"}.fa-blender-phone:before{content:"\f6b6"}.fa-blind:before{content:"\f29d"}.fa-blog:before{content:"\f781"}.fa-blogger:before{content:"\f37c"}.fa-blogger-b:before{content:"\f37d"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-bold:before{content:"\f032"}.fa-bolt:before{content:"\f0e7"}.fa-bomb:before{content:"\f1e2"}.fa-bone:before{content:"\f5d7"}.fa-bong:before{content:"\f55c"}.fa-book:before{content:"\f02d"}.fa-book-dead:before{content:"\f6b7"}.fa-book-medical:before{content:"\f7e6"}.fa-book-open:before{content:"\f518"}.fa-book-reader:before{content:"\f5da"}.fa-bookmark:before{content:"\f02e"}.fa-bowling-ball:before{content:"\f436"}.fa-box:before{content:"\f466"}.fa-box-open:before{content:"\f49e"}.fa-boxes:before{content:"\f468"}.fa-braille:before{content:"\f2a1"}.fa-brain:before{content:"\f5dc"}.fa-bread-slice:before{content:"\f7ec"}.fa-briefcase:before{content:"\f0b1"}.fa-briefcase-medical:before{content:"\f469"}.fa-broadcast-tower:before{content:"\f519"}.fa-broom:before{content:"\f51a"}.fa-brush:before{content:"\f55d"}.fa-btc:before{content:"\f15a"}.fa-bug:before{content:"\f188"}.fa-building:before{content:"\f1ad"}.fa-bullhorn:before{content:"\f0a1"}.fa-bullseye:before{content:"\f140"}.fa-burn:before{content:"\f46a"}.fa-buromobelexperte:before{content:"\f37f"}.fa-bus:before{content:"\f207"}.fa-bus-alt:before{content:"\f55e"}.fa-business-time:before{content:"\f64a"}.fa-buysellads:before{content:"\f20d"}.fa-calculator:before{content:"\f1ec"}.fa-calendar:before{content:"\f133"}.fa-calendar-alt:before{content:"\f073"}.fa-calendar-check:before{content:"\f274"}.fa-calendar-day:before{content:"\f783"}.fa-calendar-minus:before{content:"\f272"}.fa-calendar-plus:before{content:"\f271"}.fa-calendar-times:before{content:"\f273"}.fa-calendar-week:before{content:"\f784"}.fa-camera:before{content:"\f030"}.fa-camera-retro:before{content:"\f083"}.fa-campground:before{content:"\f6bb"}.fa-canadian-maple-leaf:before{content:"\f785"}.fa-candy-cane:before{content:"\f786"}.fa-cannabis:before{content:"\f55f"}.fa-capsules:before{content:"\f46b"}.fa-car:before{content:"\f1b9"}.fa-car-alt:before{content:"\f5de"}.fa-car-battery:before{content:"\f5df"}.fa-car-crash:before{content:"\f5e1"}.fa-car-side:before{content:"\f5e4"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-caret-square-down:before{content:"\f150"}.fa-caret-square-left:before{content:"\f191"}.fa-caret-square-right:before{content:"\f152"}.fa-caret-square-up:before{content:"\f151"}.fa-caret-up:before{content:"\f0d8"}.fa-carrot:before{content:"\f787"}.fa-cart-arrow-down:before{content:"\f218"}.fa-cart-plus:before{content:"\f217"}.fa-cash-register:before{content:"\f788"}.fa-cat:before{content:"\f6be"}.fa-cc-amazon-pay:before{content:"\f42d"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-apple-pay:before{content:"\f416"}.fa-cc-diners-club:before{content:"\f24c"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-cc-visa:before{content:"\f1f0"}.fa-centercode:before{content:"\f380"}.fa-centos:before{content:"\f789"}.fa-certificate:before{content:"\f0a3"}.fa-chair:before{content:"\f6c0"}.fa-chalkboard:before{content:"\f51b"}.fa-chalkboard-teacher:before{content:"\f51c"}.fa-charging-station:before{content:"\f5e7"}.fa-chart-area:before{content:"\f1fe"}.fa-chart-bar:before{content:"\f080"}.fa-chart-line:before{content:"\f201"}.fa-chart-pie:before{content:"\f200"}.fa-check:before{content:"\f00c"}.fa-check-circle:before{content:"\f058"}.fa-check-double:before{content:"\f560"}.fa-check-square:before{content:"\f14a"}.fa-cheese:before{content:"\f7ef"}.fa-chess:before{content:"\f439"}.fa-chess-bishop:before{content:"\f43a"}.fa-chess-board:before{content:"\f43c"}.fa-chess-king:before{content:"\f43f"}.fa-chess-knight:before{content:"\f441"}.fa-chess-pawn:before{content:"\f443"}.fa-chess-queen:before{content:"\f445"}.fa-chess-rook:before{content:"\f447"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-down:before{content:"\f078"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-chevron-up:before{content:"\f077"}.fa-child:before{content:"\f1ae"}.fa-chrome:before{content:"\f268"}.fa-church:before{content:"\f51d"}.fa-circle:before{content:"\f111"}.fa-circle-notch:before{content:"\f1ce"}.fa-city:before{content:"\f64f"}.fa-clinic-medical:before{content:"\f7f2"}.fa-clipboard:before{content:"\f328"}.fa-clipboard-check:before{content:"\f46c"}.fa-clipboard-list:before{content:"\f46d"}.fa-clock:before{content:"\f017"}.fa-clone:before{content:"\f24d"}.fa-closed-captioning:before{content:"\f20a"}.fa-cloud:before{content:"\f0c2"}.fa-cloud-download-alt:before{content:"\f381"}.fa-cloud-meatball:before{content:"\f73b"}.fa-cloud-moon:before{content:"\f6c3"}.fa-cloud-moon-rain:before{content:"\f73c"}.fa-cloud-rain:before{content:"\f73d"}.fa-cloud-showers-heavy:before{content:"\f740"}.fa-cloud-sun:before{content:"\f6c4"}.fa-cloud-sun-rain:before{content:"\f743"}.fa-cloud-upload-alt:before{content:"\f382"}.fa-cloudscale:before{content:"\f383"}.fa-cloudsmith:before{content:"\f384"}.fa-cloudversify:before{content:"\f385"}.fa-cocktail:before{content:"\f561"}.fa-code:before{content:"\f121"}.fa-code-branch:before{content:"\f126"}.fa-codepen:before{content:"\f1cb"}.fa-codiepie:before{content:"\f284"}.fa-coffee:before{content:"\f0f4"}.fa-cog:before{content:"\f013"}.fa-cogs:before{content:"\f085"}.fa-coins:before{content:"\f51e"}.fa-columns:before{content:"\f0db"}.fa-comment:before{content:"\f075"}.fa-comment-alt:before{content:"\f27a"}.fa-comment-dollar:before{content:"\f651"}.fa-comment-dots:before{content:"\f4ad"}.fa-comment-medical:before{content:"\f7f5"}.fa-comment-slash:before{content:"\f4b3"}.fa-comments:before{content:"\f086"}.fa-comments-dollar:before{content:"\f653"}.fa-compact-disc:before{content:"\f51f"}.fa-compass:before{content:"\f14e"}.fa-compress:before{content:"\f066"}.fa-compress-arrows-alt:before{content:"\f78c"}.fa-concierge-bell:before{content:"\f562"}.fa-confluence:before{content:"\f78d"}.fa-connectdevelop:before{content:"\f20e"}.fa-contao:before{content:"\f26d"}.fa-cookie:before{content:"\f563"}.fa-cookie-bite:before{content:"\f564"}.fa-copy:before{content:"\f0c5"}.fa-copyright:before{content:"\f1f9"}.fa-couch:before{content:"\f4b8"}.fa-cpanel:before{content:"\f388"}.fa-creative-commons:before{content:"\f25e"}.fa-creative-commons-by:before{content:"\f4e7"}.fa-creative-commons-nc:before{content:"\f4e8"}.fa-creative-commons-nc-eu:before{content:"\f4e9"}.fa-creative-commons-nc-jp:before{content:"\f4ea"}.fa-creative-commons-nd:before{content:"\f4eb"}.fa-creative-commons-pd:before{content:"\f4ec"}.fa-creative-commons-pd-alt:before{content:"\f4ed"}.fa-creative-commons-remix:before{content:"\f4ee"}.fa-creative-commons-sa:before{content:"\f4ef"}.fa-creative-commons-sampling:before{content:"\f4f0"}.fa-creative-commons-sampling-plus:before{content:"\f4f1"}.fa-creative-commons-share:before{content:"\f4f2"}.fa-creative-commons-zero:before{content:"\f4f3"}.fa-credit-card:before{content:"\f09d"}.fa-critical-role:before{content:"\f6c9"}.fa-crop:before{content:"\f125"}.fa-crop-alt:before{content:"\f565"}.fa-cross:before{content:"\f654"}.fa-crosshairs:before{content:"\f05b"}.fa-crow:before{content:"\f520"}.fa-crown:before{content:"\f521"}.fa-crutch:before{content:"\f7f7"}.fa-css3:before{content:"\f13c"}.fa-css3-alt:before{content:"\f38b"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-cut:before{content:"\f0c4"}.fa-cuttlefish:before{content:"\f38c"}.fa-d-and-d:before{content:"\f38d"}.fa-d-and-d-beyond:before{content:"\f6ca"}.fa-dashcube:before{content:"\f210"}.fa-database:before{content:"\f1c0"}.fa-deaf:before{content:"\f2a4"}.fa-delicious:before{content:"\f1a5"}.fa-democrat:before{content:"\f747"}.fa-deploydog:before{content:"\f38e"}.fa-deskpro:before{content:"\f38f"}.fa-desktop:before{content:"\f108"}.fa-dev:before{content:"\f6cc"}.fa-deviantart:before{content:"\f1bd"}.fa-dharmachakra:before{content:"\f655"}.fa-dhl:before{content:"\f790"}.fa-diagnoses:before{content:"\f470"}.fa-diaspora:before{content:"\f791"}.fa-dice:before{content:"\f522"}.fa-dice-d20:before{content:"\f6cf"}.fa-dice-d6:before{content:"\f6d1"}.fa-dice-five:before{content:"\f523"}.fa-dice-four:before{content:"\f524"}.fa-dice-one:before{content:"\f525"}.fa-dice-six:before{content:"\f526"}.fa-dice-three:before{content:"\f527"}.fa-dice-two:before{content:"\f528"}.fa-digg:before{content:"\f1a6"}.fa-digital-ocean:before{content:"\f391"}.fa-digital-tachograph:before{content:"\f566"}.fa-directions:before{content:"\f5eb"}.fa-discord:before{content:"\f392"}.fa-discourse:before{content:"\f393"}.fa-divide:before{content:"\f529"}.fa-dizzy:before{content:"\f567"}.fa-dna:before{content:"\f471"}.fa-dochub:before{content:"\f394"}.fa-docker:before{content:"\f395"}.fa-dog:before{content:"\f6d3"}.fa-dollar-sign:before{content:"\f155"}.fa-dolly:before{content:"\f472"}.fa-dolly-flatbed:before{content:"\f474"}.fa-donate:before{content:"\f4b9"}.fa-door-closed:before{content:"\f52a"}.fa-door-open:before{content:"\f52b"}.fa-dot-circle:before{content:"\f192"}.fa-dove:before{content:"\f4ba"}.fa-download:before{content:"\f019"}.fa-draft2digital:before{content:"\f396"}.fa-drafting-compass:before{content:"\f568"}.fa-dragon:before{content:"\f6d5"}.fa-draw-polygon:before{content:"\f5ee"}.fa-dribbble:before{content:"\f17d"}.fa-dribbble-square:before{content:"\f397"}.fa-dropbox:before{content:"\f16b"}.fa-drum:before{content:"\f569"}.fa-drum-steelpan:before{content:"\f56a"}.fa-drumstick-bite:before{content:"\f6d7"}.fa-drupal:before{content:"\f1a9"}.fa-dumbbell:before{content:"\f44b"}.fa-dumpster:before{content:"\f793"}.fa-dumpster-fire:before{content:"\f794"}.fa-dungeon:before{content:"\f6d9"}.fa-dyalog:before{content:"\f399"}.fa-earlybirds:before{content:"\f39a"}.fa-ebay:before{content:"\f4f4"}.fa-edge:before{content:"\f282"}.fa-edit:before{content:"\f044"}.fa-egg:before{content:"\f7fb"}.fa-eject:before{content:"\f052"}.fa-elementor:before{content:"\f430"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-ello:before{content:"\f5f1"}.fa-ember:before{content:"\f423"}.fa-empire:before{content:"\f1d1"}.fa-envelope:before{content:"\f0e0"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-text:before{content:"\f658"}.fa-envelope-square:before{content:"\f199"}.fa-envira:before{content:"\f299"}.fa-equals:before{content:"\f52c"}.fa-eraser:before{content:"\f12d"}.fa-erlang:before{content:"\f39d"}.fa-ethereum:before{content:"\f42e"}.fa-ethernet:before{content:"\f796"}.fa-etsy:before{content:"\f2d7"}.fa-euro-sign:before{content:"\f153"}.fa-exchange-alt:before{content:"\f362"}.fa-exclamation:before{content:"\f12a"}.fa-exclamation-circle:before{content:"\f06a"}.fa-exclamation-triangle:before{content:"\f071"}.fa-expand:before{content:"\f065"}.fa-expand-arrows-alt:before{content:"\f31e"}.fa-expeditedssl:before{content:"\f23e"}.fa-external-link-alt:before{content:"\f35d"}.fa-external-link-square-alt:before{content:"\f360"}.fa-eye:before{content:"\f06e"}.fa-eye-dropper:before{content:"\f1fb"}.fa-eye-slash:before{content:"\f070"}.fa-facebook:before{content:"\f09a"}.fa-facebook-f:before{content:"\f39e"}.fa-facebook-messenger:before{content:"\f39f"}.fa-facebook-square:before{content:"\f082"}.fa-fantasy-flight-games:before{content:"\f6dc"}.fa-fast-backward:before{content:"\f049"}.fa-fast-forward:before{content:"\f050"}.fa-fax:before{content:"\f1ac"}.fa-feather:before{content:"\f52d"}.fa-feather-alt:before{content:"\f56b"}.fa-fedex:before{content:"\f797"}.fa-fedora:before{content:"\f798"}.fa-female:before{content:"\f182"}.fa-fighter-jet:before{content:"\f0fb"}.fa-figma:before{content:"\f799"}.fa-file:before{content:"\f15b"}.fa-file-alt:before{content:"\f15c"}.fa-file-archive:before{content:"\f1c6"}.fa-file-audio:before{content:"\f1c7"}.fa-file-code:before{content:"\f1c9"}.fa-file-contract:before{content:"\f56c"}.fa-file-csv:before{content:"\f6dd"}.fa-file-download:before{content:"\f56d"}.fa-file-excel:before{content:"\f1c3"}.fa-file-export:before{content:"\f56e"}.fa-file-image:before{content:"\f1c5"}.fa-file-import:before{content:"\f56f"}.fa-file-invoice:before{content:"\f570"}.fa-file-invoice-dollar:before{content:"\f571"}.fa-file-medical:before{content:"\f477"}.fa-file-medical-alt:before{content:"\f478"}.fa-file-pdf:before{content:"\f1c1"}.fa-file-powerpoint:before{content:"\f1c4"}.fa-file-prescription:before{content:"\f572"}.fa-file-signature:before{content:"\f573"}.fa-file-upload:before{content:"\f574"}.fa-file-video:before{content:"\f1c8"}.fa-file-word:before{content:"\f1c2"}.fa-fill:before{content:"\f575"}.fa-fill-drip:before{content:"\f576"}.fa-film:before{content:"\f008"}.fa-filter:before{content:"\f0b0"}.fa-fingerprint:before{content:"\f577"}.fa-fire:before{content:"\f06d"}.fa-fire-alt:before{content:"\f7e4"}.fa-fire-extinguisher:before{content:"\f134"}.fa-firefox:before{content:"\f269"}.fa-first-aid:before{content:"\f479"}.fa-first-order:before{content:"\f2b0"}.fa-first-order-alt:before{content:"\f50a"}.fa-firstdraft:before{content:"\f3a1"}.fa-fish:before{content:"\f578"}.fa-fist-raised:before{content:"\f6de"}.fa-flag:before{content:"\f024"}.fa-flag-checkered:before{content:"\f11e"}.fa-flag-usa:before{content:"\f74d"}.fa-flask:before{content:"\f0c3"}.fa-flickr:before{content:"\f16e"}.fa-flipboard:before{content:"\f44d"}.fa-flushed:before{content:"\f579"}.fa-fly:before{content:"\f417"}.fa-folder:before{content:"\f07b"}.fa-folder-minus:before{content:"\f65d"}.fa-folder-open:before{content:"\f07c"}.fa-folder-plus:before{content:"\f65e"}.fa-font:before{content:"\f031"}.fa-font-awesome:before{content:"\f2b4"}.fa-font-awesome-alt:before{content:"\f35c"}.fa-font-awesome-flag:before{content:"\f425"}.fa-font-awesome-logo-full:before{content:"\f4e6"}.fa-fonticons:before{content:"\f280"}.fa-fonticons-fi:before{content:"\f3a2"}.fa-football-ball:before{content:"\f44e"}.fa-fort-awesome:before{content:"\f286"}.fa-fort-awesome-alt:before{content:"\f3a3"}.fa-forumbee:before{content:"\f211"}.fa-forward:before{content:"\f04e"}.fa-foursquare:before{content:"\f180"}.fa-free-code-camp:before{content:"\f2c5"}.fa-freebsd:before{content:"\f3a4"}.fa-frog:before{content:"\f52e"}.fa-frown:before{content:"\f119"}.fa-frown-open:before{content:"\f57a"}.fa-fulcrum:before{content:"\f50b"}.fa-funnel-dollar:before{content:"\f662"}.fa-futbol:before{content:"\f1e3"}.fa-galactic-republic:before{content:"\f50c"}.fa-galactic-senate:before{content:"\f50d"}.fa-gamepad:before{content:"\f11b"}.fa-gas-pump:before{content:"\f52f"}.fa-gavel:before{content:"\f0e3"}.fa-gem:before{content:"\f3a5"}.fa-genderless:before{content:"\f22d"}.fa-get-pocket:before{content:"\f265"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-ghost:before{content:"\f6e2"}.fa-gift:before{content:"\f06b"}.fa-gifts:before{content:"\f79c"}.fa-git:before{content:"\f1d3"}.fa-git-square:before{content:"\f1d2"}.fa-github:before{content:"\f09b"}.fa-github-alt:before{content:"\f113"}.fa-github-square:before{content:"\f092"}.fa-gitkraken:before{content:"\f3a6"}.fa-gitlab:before{content:"\f296"}.fa-gitter:before{content:"\f426"}.fa-glass-cheers:before{content:"\f79f"}.fa-glass-martini:before{content:"\f000"}.fa-glass-martini-alt:before{content:"\f57b"}.fa-glass-whiskey:before{content:"\f7a0"}.fa-glasses:before{content:"\f530"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-globe:before{content:"\f0ac"}.fa-globe-africa:before{content:"\f57c"}.fa-globe-americas:before{content:"\f57d"}.fa-globe-asia:before{content:"\f57e"}.fa-globe-europe:before{content:"\f7a2"}.fa-gofore:before{content:"\f3a7"}.fa-golf-ball:before{content:"\f450"}.fa-goodreads:before{content:"\f3a8"}.fa-goodreads-g:before{content:"\f3a9"}.fa-google:before{content:"\f1a0"}.fa-google-drive:before{content:"\f3aa"}.fa-google-play:before{content:"\f3ab"}.fa-google-plus:before{content:"\f2b3"}.fa-google-plus-g:before{content:"\f0d5"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-wallet:before{content:"\f1ee"}.fa-gopuram:before{content:"\f664"}.fa-graduation-cap:before{content:"\f19d"}.fa-gratipay:before{content:"\f184"}.fa-grav:before{content:"\f2d6"}.fa-greater-than:before{content:"\f531"}.fa-greater-than-equal:before{content:"\f532"}.fa-grimace:before{content:"\f57f"}.fa-grin:before{content:"\f580"}.fa-grin-alt:before{content:"\f581"}.fa-grin-beam:before{content:"\f582"}.fa-grin-beam-sweat:before{content:"\f583"}.fa-grin-hearts:before{content:"\f584"}.fa-grin-squint:before{content:"\f585"}.fa-grin-squint-tears:before{content:"\f586"}.fa-grin-stars:before{content:"\f587"}.fa-grin-tears:before{content:"\f588"}.fa-grin-tongue:before{content:"\f589"}.fa-grin-tongue-squint:before{content:"\f58a"}.fa-grin-tongue-wink:before{content:"\f58b"}.fa-grin-wink:before{content:"\f58c"}.fa-grip-horizontal:before{content:"\f58d"}.fa-grip-lines:before{content:"\f7a4"}.fa-grip-lines-vertical:before{content:"\f7a5"}.fa-grip-vertical:before{content:"\f58e"}.fa-gripfire:before{content:"\f3ac"}.fa-grunt:before{content:"\f3ad"}.fa-guitar:before{content:"\f7a6"}.fa-gulp:before{content:"\f3ae"}.fa-h-square:before{content:"\f0fd"}.fa-hacker-news:before{content:"\f1d4"}.fa-hacker-news-square:before{content:"\f3af"}.fa-hackerrank:before{content:"\f5f7"}.fa-hamburger:before{content:"\f805"}.fa-hammer:before{content:"\f6e3"}.fa-hamsa:before{content:"\f665"}.fa-hand-holding:before{content:"\f4bd"}.fa-hand-holding-heart:before{content:"\f4be"}.fa-hand-holding-usd:before{content:"\f4c0"}.fa-hand-lizard:before{content:"\f258"}.fa-hand-middle-finger:before{content:"\f806"}.fa-hand-paper:before{content:"\f256"}.fa-hand-peace:before{content:"\f25b"}.fa-hand-point-down:before{content:"\f0a7"}.fa-hand-point-left:before{content:"\f0a5"}.fa-hand-point-right:before{content:"\f0a4"}.fa-hand-point-up:before{content:"\f0a6"}.fa-hand-pointer:before{content:"\f25a"}.fa-hand-rock:before{content:"\f255"}.fa-hand-scissors:before{content:"\f257"}.fa-hand-spock:before{content:"\f259"}.fa-hands:before{content:"\f4c2"}.fa-hands-helping:before{content:"\f4c4"}.fa-handshake:before{content:"\f2b5"}.fa-hanukiah:before{content:"\f6e6"}.fa-hard-hat:before{content:"\f807"}.fa-hashtag:before{content:"\f292"}.fa-hat-wizard:before{content:"\f6e8"}.fa-haykal:before{content:"\f666"}.fa-hdd:before{content:"\f0a0"}.fa-heading:before{content:"\f1dc"}.fa-headphones:before{content:"\f025"}.fa-headphones-alt:before{content:"\f58f"}.fa-headset:before{content:"\f590"}.fa-heart:before{content:"\f004"}.fa-heart-broken:before{content:"\f7a9"}.fa-heartbeat:before{content:"\f21e"}.fa-helicopter:before{content:"\f533"}.fa-highlighter:before{content:"\f591"}.fa-hiking:before{content:"\f6ec"}.fa-hippo:before{content:"\f6ed"}.fa-hips:before{content:"\f452"}.fa-hire-a-helper:before{content:"\f3b0"}.fa-history:before{content:"\f1da"}.fa-hockey-puck:before{content:"\f453"}.fa-holly-berry:before{content:"\f7aa"}.fa-home:before{content:"\f015"}.fa-hooli:before{content:"\f427"}.fa-hornbill:before{content:"\f592"}.fa-horse:before{content:"\f6f0"}.fa-horse-head:before{content:"\f7ab"}.fa-hospital:before{content:"\f0f8"}.fa-hospital-alt:before{content:"\f47d"}.fa-hospital-symbol:before{content:"\f47e"}.fa-hot-tub:before{content:"\f593"}.fa-hotdog:before{content:"\f80f"}.fa-hotel:before{content:"\f594"}.fa-hotjar:before{content:"\f3b1"}.fa-hourglass:before{content:"\f254"}.fa-hourglass-end:before{content:"\f253"}.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-start:before{content:"\f251"}.fa-house-damage:before{content:"\f6f1"}.fa-houzz:before{content:"\f27c"}.fa-hryvnia:before{content:"\f6f2"}.fa-html5:before{content:"\f13b"}.fa-hubspot:before{content:"\f3b2"}.fa-i-cursor:before{content:"\f246"}.fa-ice-cream:before{content:"\f810"}.fa-icicles:before{content:"\f7ad"}.fa-id-badge:before{content:"\f2c1"}.fa-id-card:before{content:"\f2c2"}.fa-id-card-alt:before{content:"\f47f"}.fa-igloo:before{content:"\f7ae"}.fa-image:before{content:"\f03e"}.fa-images:before{content:"\f302"}.fa-imdb:before{content:"\f2d8"}.fa-inbox:before{content:"\f01c"}.fa-indent:before{content:"\f03c"}.fa-industry:before{content:"\f275"}.fa-infinity:before{content:"\f534"}.fa-info:before{content:"\f129"}.fa-info-circle:before{content:"\f05a"}.fa-instagram:before{content:"\f16d"}.fa-intercom:before{content:"\f7af"}.fa-internet-explorer:before{content:"\f26b"}.fa-invision:before{content:"\f7b0"}.fa-ioxhost:before{content:"\f208"}.fa-italic:before{content:"\f033"}.fa-itunes:before{content:"\f3b4"}.fa-itunes-note:before{content:"\f3b5"}.fa-java:before{content:"\f4e4"}.fa-jedi:before{content:"\f669"}.fa-jedi-order:before{content:"\f50e"}.fa-jenkins:before{content:"\f3b6"}.fa-jira:before{content:"\f7b1"}.fa-joget:before{content:"\f3b7"}.fa-joint:before{content:"\f595"}.fa-joomla:before{content:"\f1aa"}.fa-journal-whills:before{content:"\f66a"}.fa-js:before{content:"\f3b8"}.fa-js-square:before{content:"\f3b9"}.fa-jsfiddle:before{content:"\f1cc"}.fa-kaaba:before{content:"\f66b"}.fa-kaggle:before{content:"\f5fa"}.fa-key:before{content:"\f084"}.fa-keybase:before{content:"\f4f5"}.fa-keyboard:before{content:"\f11c"}.fa-keycdn:before{content:"\f3ba"}.fa-khanda:before{content:"\f66d"}.fa-kickstarter:before{content:"\f3bb"}.fa-kickstarter-k:before{content:"\f3bc"}.fa-kiss:before{content:"\f596"}.fa-kiss-beam:before{content:"\f597"}.fa-kiss-wink-heart:before{content:"\f598"}.fa-kiwi-bird:before{content:"\f535"}.fa-korvue:before{content:"\f42f"}.fa-landmark:before{content:"\f66f"}.fa-language:before{content:"\f1ab"}.fa-laptop:before{content:"\f109"}.fa-laptop-code:before{content:"\f5fc"}.fa-laptop-medical:before{content:"\f812"}.fa-laravel:before{content:"\f3bd"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-laugh:before{content:"\f599"}.fa-laugh-beam:before{content:"\f59a"}.fa-laugh-squint:before{content:"\f59b"}.fa-laugh-wink:before{content:"\f59c"}.fa-layer-group:before{content:"\f5fd"}.fa-leaf:before{content:"\f06c"}.fa-leanpub:before{content:"\f212"}.fa-lemon:before{content:"\f094"}.fa-less:before{content:"\f41d"}.fa-less-than:before{content:"\f536"}.fa-less-than-equal:before{content:"\f537"}.fa-level-down-alt:before{content:"\f3be"}.fa-level-up-alt:before{content:"\f3bf"}.fa-life-ring:before{content:"\f1cd"}.fa-lightbulb:before{content:"\f0eb"}.fa-line:before{content:"\f3c0"}.fa-link:before{content:"\f0c1"}.fa-linkedin:before{content:"\f08c"}.fa-linkedin-in:before{content:"\f0e1"}.fa-linode:before{content:"\f2b8"}.fa-linux:before{content:"\f17c"}.fa-lira-sign:before{content:"\f195"}.fa-list:before{content:"\f03a"}.fa-list-alt:before{content:"\f022"}.fa-list-ol:before{content:"\f0cb"}.fa-list-ul:before{content:"\f0ca"}.fa-location-arrow:before{content:"\f124"}.fa-lock:before{content:"\f023"}.fa-lock-open:before{content:"\f3c1"}.fa-long-arrow-alt-down:before{content:"\f309"}.fa-long-arrow-alt-left:before{content:"\f30a"}.fa-long-arrow-alt-right:before{content:"\f30b"}.fa-long-arrow-alt-up:before{content:"\f30c"}.fa-low-vision:before{content:"\f2a8"}.fa-luggage-cart:before{content:"\f59d"}.fa-lyft:before{content:"\f3c3"}.fa-magento:before{content:"\f3c4"}.fa-magic:before{content:"\f0d0"}.fa-magnet:before{content:"\f076"}.fa-mail-bulk:before{content:"\f674"}.fa-mailchimp:before{content:"\f59e"}.fa-male:before{content:"\f183"}.fa-mandalorian:before{content:"\f50f"}.fa-map:before{content:"\f279"}.fa-map-marked:before{content:"\f59f"}.fa-map-marked-alt:before{content:"\f5a0"}.fa-map-marker:before{content:"\f041"}.fa-map-marker-alt:before{content:"\f3c5"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-markdown:before{content:"\f60f"}.fa-marker:before{content:"\f5a1"}.fa-mars:before{content:"\f222"}.fa-mars-double:before{content:"\f227"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mask:before{content:"\f6fa"}.fa-mastodon:before{content:"\f4f6"}.fa-maxcdn:before{content:"\f136"}.fa-medal:before{content:"\f5a2"}.fa-medapps:before{content:"\f3c6"}.fa-medium:before{content:"\f23a"}.fa-medium-m:before{content:"\f3c7"}.fa-medkit:before{content:"\f0fa"}.fa-medrt:before{content:"\f3c8"}.fa-meetup:before{content:"\f2e0"}.fa-megaport:before{content:"\f5a3"}.fa-meh:before{content:"\f11a"}.fa-meh-blank:before{content:"\f5a4"}.fa-meh-rolling-eyes:before{content:"\f5a5"}.fa-memory:before{content:"\f538"}.fa-mendeley:before{content:"\f7b3"}.fa-menorah:before{content:"\f676"}.fa-mercury:before{content:"\f223"}.fa-meteor:before{content:"\f753"}.fa-microchip:before{content:"\f2db"}.fa-microphone:before{content:"\f130"}.fa-microphone-alt:before{content:"\f3c9"}.fa-microphone-alt-slash:before{content:"\f539"}.fa-microphone-slash:before{content:"\f131"}.fa-microscope:before{content:"\f610"}.fa-microsoft:before{content:"\f3ca"}.fa-minus:before{content:"\f068"}.fa-minus-circle:before{content:"\f056"}.fa-minus-square:before{content:"\f146"}.fa-mitten:before{content:"\f7b5"}.fa-mix:before{content:"\f3cb"}.fa-mixcloud:before{content:"\f289"}.fa-mizuni:before{content:"\f3cc"}.fa-mobile:before{content:"\f10b"}.fa-mobile-alt:before{content:"\f3cd"}.fa-modx:before{content:"\f285"}.fa-monero:before{content:"\f3d0"}.fa-money-bill:before{content:"\f0d6"}.fa-money-bill-alt:before{content:"\f3d1"}.fa-money-bill-wave:before{content:"\f53a"}.fa-money-bill-wave-alt:before{content:"\f53b"}.fa-money-check:before{content:"\f53c"}.fa-money-check-alt:before{content:"\f53d"}.fa-monument:before{content:"\f5a6"}.fa-moon:before{content:"\f186"}.fa-mortar-pestle:before{content:"\f5a7"}.fa-mosque:before{content:"\f678"}.fa-motorcycle:before{content:"\f21c"}.fa-mountain:before{content:"\f6fc"}.fa-mouse-pointer:before{content:"\f245"}.fa-mug-hot:before{content:"\f7b6"}.fa-music:before{content:"\f001"}.fa-napster:before{content:"\f3d2"}.fa-neos:before{content:"\f612"}.fa-network-wired:before{content:"\f6ff"}.fa-neuter:before{content:"\f22c"}.fa-newspaper:before{content:"\f1ea"}.fa-nimblr:before{content:"\f5a8"}.fa-nintendo-switch:before{content:"\f418"}.fa-node:before{content:"\f419"}.fa-node-js:before{content:"\f3d3"}.fa-not-equal:before{content:"\f53e"}.fa-notes-medical:before{content:"\f481"}.fa-npm:before{content:"\f3d4"}.fa-ns8:before{content:"\f3d5"}.fa-nutritionix:before{content:"\f3d6"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-oil-can:before{content:"\f613"}.fa-old-republic:before{content:"\f510"}.fa-om:before{content:"\f679"}.fa-opencart:before{content:"\f23d"}.fa-openid:before{content:"\f19b"}.fa-opera:before{content:"\f26a"}.fa-optin-monster:before{content:"\f23c"}.fa-osi:before{content:"\f41a"}.fa-otter:before{content:"\f700"}.fa-outdent:before{content:"\f03b"}.fa-page4:before{content:"\f3d7"}.fa-pagelines:before{content:"\f18c"}.fa-pager:before{content:"\f815"}.fa-paint-brush:before{content:"\f1fc"}.fa-paint-roller:before{content:"\f5aa"}.fa-palette:before{content:"\f53f"}.fa-palfed:before{content:"\f3d8"}.fa-pallet:before{content:"\f482"}.fa-paper-plane:before{content:"\f1d8"}.fa-paperclip:before{content:"\f0c6"}.fa-parachute-box:before{content:"\f4cd"}.fa-paragraph:before{content:"\f1dd"}.fa-parking:before{content:"\f540"}.fa-passport:before{content:"\f5ab"}.fa-pastafarianism:before{content:"\f67b"}.fa-paste:before{content:"\f0ea"}.fa-patreon:before{content:"\f3d9"}.fa-pause:before{content:"\f04c"}.fa-pause-circle:before{content:"\f28b"}.fa-paw:before{content:"\f1b0"}.fa-paypal:before{content:"\f1ed"}.fa-peace:before{content:"\f67c"}.fa-pen:before{content:"\f304"}.fa-pen-alt:before{content:"\f305"}.fa-pen-fancy:before{content:"\f5ac"}.fa-pen-nib:before{content:"\f5ad"}.fa-pen-square:before{content:"\f14b"}.fa-pencil-alt:before{content:"\f303"}.fa-pencil-ruler:before{content:"\f5ae"}.fa-penny-arcade:before{content:"\f704"}.fa-people-carry:before{content:"\f4ce"}.fa-pepper-hot:before{content:"\f816"}.fa-percent:before{content:"\f295"}.fa-percentage:before{content:"\f541"}.fa-periscope:before{content:"\f3da"}.fa-person-booth:before{content:"\f756"}.fa-phabricator:before{content:"\f3db"}.fa-phoenix-framework:before{content:"\f3dc"}.fa-phoenix-squadron:before{content:"\f511"}.fa-phone:before{content:"\f095"}.fa-phone-slash:before{content:"\f3dd"}.fa-phone-square:before{content:"\f098"}.fa-phone-volume:before{content:"\f2a0"}.fa-php:before{content:"\f457"}.fa-pied-piper:before{content:"\f2ae"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-pied-piper-hat:before{content:"\f4e5"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-piggy-bank:before{content:"\f4d3"}.fa-pills:before{content:"\f484"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-p:before{content:"\f231"}.fa-pinterest-square:before{content:"\f0d3"}.fa-pizza-slice:before{content:"\f818"}.fa-place-of-worship:before{content:"\f67f"}.fa-plane:before{content:"\f072"}.fa-plane-arrival:before{content:"\f5af"}.fa-plane-departure:before{content:"\f5b0"}.fa-play:before{content:"\f04b"}.fa-play-circle:before{content:"\f144"}.fa-playstation:before{content:"\f3df"}.fa-plug:before{content:"\f1e6"}.fa-plus:before{content:"\f067"}.fa-plus-circle:before{content:"\f055"}.fa-plus-square:before{content:"\f0fe"}.fa-podcast:before{content:"\f2ce"}.fa-poll:before{content:"\f681"}.fa-poll-h:before{content:"\f682"}.fa-poo:before{content:"\f2fe"}.fa-poo-storm:before{content:"\f75a"}.fa-poop:before{content:"\f619"}.fa-portrait:before{content:"\f3e0"}.fa-pound-sign:before{content:"\f154"}.fa-power-off:before{content:"\f011"}.fa-pray:before{content:"\f683"}.fa-praying-hands:before{content:"\f684"}.fa-prescription:before{content:"\f5b1"}.fa-prescription-bottle:before{content:"\f485"}.fa-prescription-bottle-alt:before{content:"\f486"}.fa-print:before{content:"\f02f"}.fa-procedures:before{content:"\f487"}.fa-product-hunt:before{content:"\f288"}.fa-project-diagram:before{content:"\f542"}.fa-pushed:before{content:"\f3e1"}.fa-puzzle-piece:before{content:"\f12e"}.fa-python:before{content:"\f3e2"}.fa-qq:before{content:"\f1d6"}.fa-qrcode:before{content:"\f029"}.fa-question:before{content:"\f128"}.fa-question-circle:before{content:"\f059"}.fa-quidditch:before{content:"\f458"}.fa-quinscape:before{content:"\f459"}.fa-quora:before{content:"\f2c4"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-quran:before{content:"\f687"}.fa-r-project:before{content:"\f4f7"}.fa-radiation:before{content:"\f7b9"}.fa-radiation-alt:before{content:"\f7ba"}.fa-rainbow:before{content:"\f75b"}.fa-random:before{content:"\f074"}.fa-raspberry-pi:before{content:"\f7bb"}.fa-ravelry:before{content:"\f2d9"}.fa-react:before{content:"\f41b"}.fa-reacteurope:before{content:"\f75d"}.fa-readme:before{content:"\f4d5"}.fa-rebel:before{content:"\f1d0"}.fa-receipt:before{content:"\f543"}.fa-recycle:before{content:"\f1b8"}.fa-red-river:before{content:"\f3e3"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-alien:before{content:"\f281"}.fa-reddit-square:before{content:"\f1a2"}.fa-redhat:before{content:"\f7bc"}.fa-redo:before{content:"\f01e"}.fa-redo-alt:before{content:"\f2f9"}.fa-registered:before{content:"\f25d"}.fa-renren:before{content:"\f18b"}.fa-reply:before{content:"\f3e5"}.fa-reply-all:before{content:"\f122"}.fa-replyd:before{content:"\f3e6"}.fa-republican:before{content:"\f75e"}.fa-researchgate:before{content:"\f4f8"}.fa-resolving:before{content:"\f3e7"}.fa-restroom:before{content:"\f7bd"}.fa-retweet:before{content:"\f079"}.fa-rev:before{content:"\f5b2"}.fa-ribbon:before{content:"\f4d6"}.fa-ring:before{content:"\f70b"}.fa-road:before{content:"\f018"}.fa-robot:before{content:"\f544"}.fa-rocket:before{content:"\f135"}.fa-rocketchat:before{content:"\f3e8"}.fa-rockrms:before{content:"\f3e9"}.fa-route:before{content:"\f4d7"}.fa-rss:before{content:"\f09e"}.fa-rss-square:before{content:"\f143"}.fa-ruble-sign:before{content:"\f158"}.fa-ruler:before{content:"\f545"}.fa-ruler-combined:before{content:"\f546"}.fa-ruler-horizontal:before{content:"\f547"}.fa-ruler-vertical:before{content:"\f548"}.fa-running:before{content:"\f70c"}.fa-rupee-sign:before{content:"\f156"}.fa-sad-cry:before{content:"\f5b3"}.fa-sad-tear:before{content:"\f5b4"}.fa-safari:before{content:"\f267"}.fa-sass:before{content:"\f41e"}.fa-satellite:before{content:"\f7bf"}.fa-satellite-dish:before{content:"\f7c0"}.fa-save:before{content:"\f0c7"}.fa-schlix:before{content:"\f3ea"}.fa-school:before{content:"\f549"}.fa-screwdriver:before{content:"\f54a"}.fa-scribd:before{content:"\f28a"}.fa-scroll:before{content:"\f70e"}.fa-sd-card:before{content:"\f7c2"}.fa-search:before{content:"\f002"}.fa-search-dollar:before{content:"\f688"}.fa-search-location:before{content:"\f689"}.fa-search-minus:before{content:"\f010"}.fa-search-plus:before{content:"\f00e"}.fa-searchengin:before{content:"\f3eb"}.fa-seedling:before{content:"\f4d8"}.fa-sellcast:before{content:"\f2da"}.fa-sellsy:before{content:"\f213"}.fa-server:before{content:"\f233"}.fa-servicestack:before{content:"\f3ec"}.fa-shapes:before{content:"\f61f"}.fa-share:before{content:"\f064"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-share-square:before{content:"\f14d"}.fa-shekel-sign:before{content:"\f20b"}.fa-shield-alt:before{content:"\f3ed"}.fa-ship:before{content:"\f21a"}.fa-shipping-fast:before{content:"\f48b"}.fa-shirtsinbulk:before{content:"\f214"}.fa-shoe-prints:before{content:"\f54b"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-shopping-cart:before{content:"\f07a"}.fa-shopware:before{content:"\f5b5"}.fa-shower:before{content:"\f2cc"}.fa-shuttle-van:before{content:"\f5b6"}.fa-sign:before{content:"\f4d9"}.fa-sign-in-alt:before{content:"\f2f6"}.fa-sign-language:before{content:"\f2a7"}.fa-sign-out-alt:before{content:"\f2f5"}.fa-signal:before{content:"\f012"}.fa-signature:before{content:"\f5b7"}.fa-sim-card:before{content:"\f7c4"}.fa-simplybuilt:before{content:"\f215"}.fa-sistrix:before{content:"\f3ee"}.fa-sitemap:before{content:"\f0e8"}.fa-sith:before{content:"\f512"}.fa-skating:before{content:"\f7c5"}.fa-sketch:before{content:"\f7c6"}.fa-skiing:before{content:"\f7c9"}.fa-skiing-nordic:before{content:"\f7ca"}.fa-skull:before{content:"\f54c"}.fa-skull-crossbones:before{content:"\f714"}.fa-skyatlas:before{content:"\f216"}.fa-skype:before{content:"\f17e"}.fa-slack:before{content:"\f198"}.fa-slack-hash:before{content:"\f3ef"}.fa-slash:before{content:"\f715"}.fa-sleigh:before{content:"\f7cc"}.fa-sliders-h:before{content:"\f1de"}.fa-slideshare:before{content:"\f1e7"}.fa-smile:before{content:"\f118"}.fa-smile-beam:before{content:"\f5b8"}.fa-smile-wink:before{content:"\f4da"}.fa-smog:before{content:"\f75f"}.fa-smoking:before{content:"\f48d"}.fa-smoking-ban:before{content:"\f54d"}.fa-sms:before{content:"\f7cd"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-snowboarding:before{content:"\f7ce"}.fa-snowflake:before{content:"\f2dc"}.fa-snowman:before{content:"\f7d0"}.fa-snowplow:before{content:"\f7d2"}.fa-socks:before{content:"\f696"}.fa-solar-panel:before{content:"\f5ba"}.fa-sort:before{content:"\f0dc"}.fa-sort-alpha-down:before{content:"\f15d"}.fa-sort-alpha-up:before{content:"\f15e"}.fa-sort-amount-down:before{content:"\f160"}.fa-sort-amount-up:before{content:"\f161"}.fa-sort-down:before{content:"\f0dd"}.fa-sort-numeric-down:before{content:"\f162"}.fa-sort-numeric-up:before{content:"\f163"}.fa-sort-up:before{content:"\f0de"}.fa-soundcloud:before{content:"\f1be"}.fa-sourcetree:before{content:"\f7d3"}.fa-spa:before{content:"\f5bb"}.fa-space-shuttle:before{content:"\f197"}.fa-speakap:before{content:"\f3f3"}.fa-spider:before{content:"\f717"}.fa-spinner:before{content:"\f110"}.fa-splotch:before{content:"\f5bc"}.fa-spotify:before{content:"\f1bc"}.fa-spray-can:before{content:"\f5bd"}.fa-square:before{content:"\f0c8"}.fa-square-full:before{content:"\f45c"}.fa-square-root-alt:before{content:"\f698"}.fa-squarespace:before{content:"\f5be"}.fa-stack-exchange:before{content:"\f18d"}.fa-stack-overflow:before{content:"\f16c"}.fa-stamp:before{content:"\f5bf"}.fa-star:before{content:"\f005"}.fa-star-and-crescent:before{content:"\f699"}.fa-star-half:before{content:"\f089"}.fa-star-half-alt:before{content:"\f5c0"}.fa-star-of-david:before{content:"\f69a"}.fa-star-of-life:before{content:"\f621"}.fa-staylinked:before{content:"\f3f5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-steam-symbol:before{content:"\f3f6"}.fa-step-backward:before{content:"\f048"}.fa-step-forward:before{content:"\f051"}.fa-stethoscope:before{content:"\f0f1"}.fa-sticker-mule:before{content:"\f3f7"}.fa-sticky-note:before{content:"\f249"}.fa-stop:before{content:"\f04d"}.fa-stop-circle:before{content:"\f28d"}.fa-stopwatch:before{content:"\f2f2"}.fa-store:before{content:"\f54e"}.fa-store-alt:before{content:"\f54f"}.fa-strava:before{content:"\f428"}.fa-stream:before{content:"\f550"}.fa-street-view:before{content:"\f21d"}.fa-strikethrough:before{content:"\f0cc"}.fa-stripe:before{content:"\f429"}.fa-stripe-s:before{content:"\f42a"}.fa-stroopwafel:before{content:"\f551"}.fa-studiovinari:before{content:"\f3f8"}.fa-stumbleupon:before{content:"\f1a4"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-subscript:before{content:"\f12c"}.fa-subway:before{content:"\f239"}.fa-suitcase:before{content:"\f0f2"}.fa-suitcase-rolling:before{content:"\f5c1"}.fa-sun:before{content:"\f185"}.fa-superpowers:before{content:"\f2dd"}.fa-superscript:before{content:"\f12b"}.fa-supple:before{content:"\f3f9"}.fa-surprise:before{content:"\f5c2"}.fa-suse:before{content:"\f7d6"}.fa-swatchbook:before{content:"\f5c3"}.fa-swimmer:before{content:"\f5c4"}.fa-swimming-pool:before{content:"\f5c5"}.fa-synagogue:before{content:"\f69b"}.fa-sync:before{content:"\f021"}.fa-sync-alt:before{content:"\f2f1"}.fa-syringe:before{content:"\f48e"}.fa-table:before{content:"\f0ce"}.fa-table-tennis:before{content:"\f45d"}.fa-tablet:before{content:"\f10a"}.fa-tablet-alt:before{content:"\f3fa"}.fa-tablets:before{content:"\f490"}.fa-tachometer-alt:before{content:"\f3fd"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-tape:before{content:"\f4db"}.fa-tasks:before{content:"\f0ae"}.fa-taxi:before{content:"\f1ba"}.fa-teamspeak:before{content:"\f4f9"}.fa-teeth:before{content:"\f62e"}.fa-teeth-open:before{content:"\f62f"}.fa-telegram:before{content:"\f2c6"}.fa-telegram-plane:before{content:"\f3fe"}.fa-temperature-high:before{content:"\f769"}.fa-temperature-low:before{content:"\f76b"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-tenge:before{content:"\f7d7"}.fa-terminal:before{content:"\f120"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-th:before{content:"\f00a"}.fa-th-large:before{content:"\f009"}.fa-th-list:before{content:"\f00b"}.fa-the-red-yeti:before{content:"\f69d"}.fa-theater-masks:before{content:"\f630"}.fa-themeco:before{content:"\f5c6"}.fa-themeisle:before{content:"\f2b2"}.fa-thermometer:before{content:"\f491"}.fa-thermometer-empty:before{content:"\f2cb"}.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-think-peaks:before{content:"\f731"}.fa-thumbs-down:before{content:"\f165"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbtack:before{content:"\f08d"}.fa-ticket-alt:before{content:"\f3ff"}.fa-times:before{content:"\f00d"}.fa-times-circle:before{content:"\f057"}.fa-tint:before{content:"\f043"}.fa-tint-slash:before{content:"\f5c7"}.fa-tired:before{content:"\f5c8"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-toilet:before{content:"\f7d8"}.fa-toilet-paper:before{content:"\f71e"}.fa-toolbox:before{content:"\f552"}.fa-tools:before{content:"\f7d9"}.fa-tooth:before{content:"\f5c9"}.fa-torah:before{content:"\f6a0"}.fa-torii-gate:before{content:"\f6a1"}.fa-tractor:before{content:"\f722"}.fa-trade-federation:before{content:"\f513"}.fa-trademark:before{content:"\f25c"}.fa-traffic-light:before{content:"\f637"}.fa-train:before{content:"\f238"}.fa-tram:before{content:"\f7da"}.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-trash:before{content:"\f1f8"}.fa-trash-alt:before{content:"\f2ed"}.fa-trash-restore:before{content:"\f829"}.fa-trash-restore-alt:before{content:"\f82a"}.fa-tree:before{content:"\f1bb"}.fa-trello:before{content:"\f181"}.fa-tripadvisor:before{content:"\f262"}.fa-trophy:before{content:"\f091"}.fa-truck:before{content:"\f0d1"}.fa-truck-loading:before{content:"\f4de"}.fa-truck-monster:before{content:"\f63b"}.fa-truck-moving:before{content:"\f4df"}.fa-truck-pickup:before{content:"\f63c"}.fa-tshirt:before{content:"\f553"}.fa-tty:before{content:"\f1e4"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-tv:before{content:"\f26c"}.fa-twitch:before{content:"\f1e8"}.fa-twitter:before{content:"\f099"}.fa-twitter-square:before{content:"\f081"}.fa-typo3:before{content:"\f42b"}.fa-uber:before{content:"\f402"}.fa-ubuntu:before{content:"\f7df"}.fa-uikit:before{content:"\f403"}.fa-umbrella:before{content:"\f0e9"}.fa-umbrella-beach:before{content:"\f5ca"}.fa-underline:before{content:"\f0cd"}.fa-undo:before{content:"\f0e2"}.fa-undo-alt:before{content:"\f2ea"}.fa-uniregistry:before{content:"\f404"}.fa-universal-access:before{content:"\f29a"}.fa-university:before{content:"\f19c"}.fa-unlink:before{content:"\f127"}.fa-unlock:before{content:"\f09c"}.fa-unlock-alt:before{content:"\f13e"}.fa-untappd:before{content:"\f405"}.fa-upload:before{content:"\f093"}.fa-ups:before{content:"\f7e0"}.fa-usb:before{content:"\f287"}.fa-user:before{content:"\f007"}.fa-user-alt:before{content:"\f406"}.fa-user-alt-slash:before{content:"\f4fa"}.fa-user-astronaut:before{content:"\f4fb"}.fa-user-check:before{content:"\f4fc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-clock:before{content:"\f4fd"}.fa-user-cog:before{content:"\f4fe"}.fa-user-edit:before{content:"\f4ff"}.fa-user-friends:before{content:"\f500"}.fa-user-graduate:before{content:"\f501"}.fa-user-injured:before{content:"\f728"}.fa-user-lock:before{content:"\f502"}.fa-user-md:before{content:"\f0f0"}.fa-user-minus:before{content:"\f503"}.fa-user-ninja:before{content:"\f504"}.fa-user-nurse:before{content:"\f82f"}.fa-user-plus:before{content:"\f234"}.fa-user-secret:before{content:"\f21b"}.fa-user-shield:before{content:"\f505"}.fa-user-slash:before{content:"\f506"}.fa-user-tag:before{content:"\f507"}.fa-user-tie:before{content:"\f508"}.fa-user-times:before{content:"\f235"}.fa-users:before{content:"\f0c0"}.fa-users-cog:before{content:"\f509"}.fa-usps:before{content:"\f7e1"}.fa-ussunnah:before{content:"\f407"}.fa-utensil-spoon:before{content:"\f2e5"}.fa-utensils:before{content:"\f2e7"}.fa-vaadin:before{content:"\f408"}.fa-vector-square:before{content:"\f5cb"}.fa-venus:before{content:"\f221"}.fa-venus-double:before{content:"\f226"}.fa-venus-mars:before{content:"\f228"}.fa-viacoin:before{content:"\f237"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-vial:before{content:"\f492"}.fa-vials:before{content:"\f493"}.fa-viber:before{content:"\f409"}.fa-video:before{content:"\f03d"}.fa-video-slash:before{content:"\f4e2"}.fa-vihara:before{content:"\f6a7"}.fa-vimeo:before{content:"\f40a"}.fa-vimeo-square:before{content:"\f194"}.fa-vimeo-v:before{content:"\f27d"}.fa-vine:before{content:"\f1ca"}.fa-vk:before{content:"\f189"}.fa-vnv:before{content:"\f40b"}.fa-volleyball-ball:before{content:"\f45f"}.fa-volume-down:before{content:"\f027"}.fa-volume-mute:before{content:"\f6a9"}.fa-volume-off:before{content:"\f026"}.fa-volume-up:before{content:"\f028"}.fa-vote-yea:before{content:"\f772"}.fa-vr-cardboard:before{content:"\f729"}.fa-vuejs:before{content:"\f41f"}.fa-walking:before{content:"\f554"}.fa-wallet:before{content:"\f555"}.fa-warehouse:before{content:"\f494"}.fa-water:before{content:"\f773"}.fa-weebly:before{content:"\f5cc"}.fa-weibo:before{content:"\f18a"}.fa-weight:before{content:"\f496"}.fa-weight-hanging:before{content:"\f5cd"}.fa-weixin:before{content:"\f1d7"}.fa-whatsapp:before{content:"\f232"}.fa-whatsapp-square:before{content:"\f40c"}.fa-wheelchair:before{content:"\f193"}.fa-whmcs:before{content:"\f40d"}.fa-wifi:before{content:"\f1eb"}.fa-wikipedia-w:before{content:"\f266"}.fa-wind:before{content:"\f72e"}.fa-window-close:before{content:"\f410"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-windows:before{content:"\f17a"}.fa-wine-bottle:before{content:"\f72f"}.fa-wine-glass:before{content:"\f4e3"}.fa-wine-glass-alt:before{content:"\f5ce"}.fa-wix:before{content:"\f5cf"}.fa-wizards-of-the-coast:before{content:"\f730"}.fa-wolf-pack-battalion:before{content:"\f514"}.fa-won-sign:before{content:"\f159"}.fa-wordpress:before{content:"\f19a"}.fa-wordpress-simple:before{content:"\f411"}.fa-wpbeginner:before{content:"\f297"}.fa-wpexplorer:before{content:"\f2de"}.fa-wpforms:before{content:"\f298"}.fa-wpressr:before{content:"\f3e4"}.fa-wrench:before{content:"\f0ad"}.fa-x-ray:before{content:"\f497"}.fa-xbox:before{content:"\f412"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-y-combinator:before{content:"\f23b"}.fa-yahoo:before{content:"\f19e"}.fa-yandex:before{content:"\f413"}.fa-yandex-international:before{content:"\f414"}.fa-yarn:before{content:"\f7e3"}.fa-yelp:before{content:"\f1e9"}.fa-yen-sign:before{content:"\f157"}.fa-yin-yang:before{content:"\f6ad"}.fa-yoast:before{content:"\f2b1"}.fa-youtube:before{content:"\f167"}.fa-youtube-square:before{content:"\f431"}.fa-zhihu:before{content:"\f63f"}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}@font-face{font-family:"Font Awesome 5 Brands";font-style:normal;font-weight:normal;font-display:auto;src:url(../webfonts/fa-brands-400.eot);src:url(../webfonts/fa-brands-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.woff) format("woff"),url(../webfonts/fa-brands-400.ttf) format("truetype"),url(../webfonts/fa-brands-400.svg#fontawesome) format("svg")}.fab{font-family:"Font Awesome 5 Brands"}@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:400;font-display:auto;src:url(../webfonts/fa-regular-400.eot);src:url(../webfonts/fa-regular-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.woff) format("woff"),url(../webfonts/fa-regular-400.ttf) format("truetype"),url(../webfonts/fa-regular-400.svg#fontawesome) format("svg")}.far{font-weight:400}@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:900;font-display:auto;src:url(../webfonts/fa-solid-900.eot);src:url(../webfonts/fa-solid-900.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.woff) format("woff"),url(../webfonts/fa-solid-900.ttf) format("truetype"),url(../webfonts/fa-solid-900.svg#fontawesome) format("svg")}.fa,.far,.fas{font-family:"Font Awesome 5 Free"}.fa,.fas{font-weight:900} +` +export default cssStr diff --git a/app/userland/app-stdlib/css/inputs.css b/app/userland/app-stdlib/css/inputs.css new file mode 100644 index 0000000000..df1d801018 --- /dev/null +++ b/app/userland/app-stdlib/css/inputs.css @@ -0,0 +1,270 @@ +textarea { + line-height: 1.4; +} + +input, +textarea { + height: 30px; + padding: 0 7px; + border-radius: 4px; + color: rgba(51, 51, 51, 0.95); + border: 1px solid #d9d9d9; +} +textarea { + padding: 7px; +} + +input[type="checkbox"], +textarea[type="checkbox"], +input[type="radio"], +textarea[type="radio"], +input[type="range"], +textarea[type="range"] { + padding: 0; +} + +input[type="checkbox"]:focus, +textarea[type="checkbox"]:focus, +input[type="radio"]:focus, +textarea[type="radio"]:focus, +input[type="range"]:focus, +textarea[type="range"]:focus { + box-shadow: none; +} + +input[type="radio"], +textarea[type="radio"] { + width: 14px; + height: 14px; + outline: none; + -webkit-appearance: none; + border-radius: 50%; + cursor: pointer; + transition: border 0.1s ease; +} + +input[type="radio"]:hover, +textarea[type="radio"]:hover { + border: 1px solid var(--color-blue); +} + +input[type="radio"]:checked, +textarea[type="radio"]:checked { + border: 4.5px solid var(--color-blue); +} + +input[type="file"], +textarea[type="file"] { + padding: 0; + border: 0; + line-height: 1; +} + +input[type="file"]:focus, +textarea[type="file"]:focus { + border: 0; + box-shadow: none; +} + +input:focus, +textarea:focus, +select:focus { + outline: 0; + border: 1px solid rgba(41, 95, 203, 0.8); + box-shadow: 0 0 0 2px rgba(41, 95, 203, 0.2); +} + +input.error, +textarea.error, +select.error { + border: 1px solid rgba(209, 48, 39, 0.75); +} + +input.error:focus, +textarea.error:focus, +select.error:focus { + box-shadow: 0 0 0 2px rgba(204, 47, 38, 0.15); +} + +input.nofocus:focus, +textarea.nofocus:focus, +select.nofocus:focus { + outline: 0; + box-shadow: none; + border: initial; +} + +input.inline { + height: auto; + border: 1px solid transparent; + border-radius: 0; + background: transparent; + cursor: text; + padding: 3px 5px; + line-height: 1; +} + +input.big, +textarea.big { + height: 38px; + padding: 0 10px; + font-size: 14px; +} + +textarea.big { + padding: 5px 10px; +} + +input.huge, +textarea.huge { + height: 40px; + padding: 0 10px; + font-size: 18px; +} + +textarea.huge { + padding: 5px 10px; +} + +input.inline:focus, +input.inline:hover { + border: 1px solid #ccc; + box-shadow: none; +} + +input.inline:focus { + background: #fff; +} + +.input-file-picker { + display: flex; + align-items: center; + padding: 3px; + border-radius: 2px; + border: 1px solid #d9d9d9; + color: var(--color-text--muted); +} + +.input-file-picker span { + flex: 1; + padding-left: 3px; +} + +::-webkit-input-placeholder { + color: rgba(0, 0, 0, 0.5); + font-size: 0.8rem; +} + +.big::-webkit-input-placeholder, +.huge::-webkit-input-placeholder { + font-size: 0.9em; +} + +label { + font-weight: 500; +} + +input[disabled][data-tooltip], +label[disabled][data-tooltip] { + cursor: help; +} + +input[disabled][data-tooltip] *, +label[disabled][data-tooltip] * { + cursor: help; +} + +label.required:after { + content: '*'; + color: red; +} + +.toggle { + display: flex; + align-items: center; + flex-direction: row; + margin-bottom: 10px; + cursor: pointer; + overflow: initial; +} + +.toggle .switch { + margin-right: 10px; +} + +.toggle * { + cursor: pointer; +} + +.toggle.disabled { + cursor: default; +} + +.toggle.disabled * { + cursor: default; +} + +.toggle input { + display: none; +} + +.toggle .text { + font-weight: 400; +} + +.toggle .switch { + display: inline-block; + position: relative; + width: 32px; + height: 17px; +} + +.toggle .switch:before, +.toggle .switch:after { + position: absolute; + display: block; + content: ''; +} + +.toggle .switch:before { + width: 100%; + height: 100%; + border-radius: 40px; + background: #dadada; +} + +.toggle .switch:after { + width: 11px; + height: 11px; + border-radius: 50%; + left: 3px; + top: 3px; + background: #fafafa; + transition: transform 0.15s ease; +} + +.toggle input:checked:not(:disabled) + .switch:before { + background: #41b855; +} + +.toggle input:checked:not(:disabled) + .switch:after { + transform: translateX(15px); +} + +.toggle.disabled { + color: var(--color-text--light); +} + +label.checkbox-container { + display: flex; + align-items: center; + height: 15px; + font-weight: 400; +} + +label.checkbox-container input[type="checkbox"] { + width: 15px; + height: 15px; + margin: 0 5px 0 0; +} + diff --git a/app/userland/app-stdlib/css/inputs.css.js b/app/userland/app-stdlib/css/inputs.css.js new file mode 100644 index 0000000000..38928d0e08 --- /dev/null +++ b/app/userland/app-stdlib/css/inputs.css.js @@ -0,0 +1,276 @@ +import {css} from '../vendor/lit-element/lit-element.js' + +const cssStr = css` +textarea { + line-height: 1.4; +} + +input, +textarea { + height: 30px; + padding: 0 7px; + border-radius: 4px; + color: rgba(51, 51, 51, 0.95); + border: 1px solid #d9d9d9; +} +textarea { + padding: 7px; +} + +input[type="checkbox"], +textarea[type="checkbox"], +input[type="radio"], +textarea[type="radio"], +input[type="range"], +textarea[type="range"] { + padding: 0; +} + +input[type="checkbox"]:focus, +textarea[type="checkbox"]:focus, +input[type="radio"]:focus, +textarea[type="radio"]:focus, +input[type="range"]:focus, +textarea[type="range"]:focus { + box-shadow: none; +} + +input[type="radio"], +textarea[type="radio"] { + width: 14px; + height: 14px; + outline: none; + -webkit-appearance: none; + border-radius: 50%; + cursor: pointer; + transition: border 0.1s ease; +} + +input[type="radio"]:hover, +textarea[type="radio"]:hover { + border: 1px solid var(--color-blue); +} + +input[type="radio"]:checked, +textarea[type="radio"]:checked { + border: 4.5px solid var(--color-blue); +} + +input[type="file"], +textarea[type="file"] { + padding: 0; + border: 0; + line-height: 1; +} + +input[type="file"]:focus, +textarea[type="file"]:focus { + border: 0; + box-shadow: none; +} + +input:focus, +textarea:focus, +select:focus { + outline: 0; + border: 1px solid rgba(41, 95, 203, 0.8); + box-shadow: 0 0 0 2px rgba(41, 95, 203, 0.2); +} + +input.error, +textarea.error, +select.error { + border: 1px solid rgba(209, 48, 39, 0.75); +} + +input.error:focus, +textarea.error:focus, +select.error:focus { + box-shadow: 0 0 0 2px rgba(204, 47, 38, 0.15); +} + +input.nofocus:focus, +textarea.nofocus:focus, +select.nofocus:focus { + outline: 0; + box-shadow: none; + border: initial; +} + +input.inline { + height: auto; + border: 1px solid transparent; + border-radius: 0; + background: transparent; + cursor: text; + padding: 3px 5px; + line-height: 1; +} + +input.big, +textarea.big { + height: 38px; + padding: 0 10px; + font-size: 14px; +} + +textarea.big { + padding: 5px 10px; +} + +input.huge, +textarea.huge { + height: 40px; + padding: 0 10px; + font-size: 18px; +} + +textarea.huge { + padding: 5px 10px; +} + +input.inline:focus, +input.inline:hover { + border: 1px solid #ccc; + box-shadow: none; +} + +input.inline:focus { + background: #fff; +} + +.input-file-picker { + display: flex; + align-items: center; + padding: 3px; + border-radius: 2px; + border: 1px solid #d9d9d9; + color: var(--color-text--muted); +} + +.input-file-picker span { + flex: 1; + padding-left: 3px; +} + +::-webkit-input-placeholder { + color: rgba(0, 0, 0, 0.5); + font-size: 0.8rem; +} + +.big::-webkit-input-placeholder, +.huge::-webkit-input-placeholder { + font-size: 0.9em; +} + +label { + font-weight: 500; +} + +input[disabled][data-tooltip], +label[disabled][data-tooltip] { + cursor: help; +} + +input[disabled][data-tooltip] *, +label[disabled][data-tooltip] * { + cursor: help; +} + +label.required:after { + content: '*'; + color: red; +} + +.toggle { + display: flex; + align-items: center; + flex-direction: row; + margin-bottom: 10px; + cursor: pointer; + overflow: initial; +} + +.toggle .switch { + margin-right: 10px; +} + +.toggle * { + cursor: pointer; +} + +.toggle.disabled { + cursor: default; +} + +.toggle.disabled * { + cursor: default; +} + +.toggle input { + display: none; +} + +.toggle .text { + font-weight: 400; +} + +.toggle .switch { + display: inline-block; + position: relative; + width: 32px; + height: 17px; +} + +.toggle .switch:before, +.toggle .switch:after { + position: absolute; + display: block; + content: ''; +} + +.toggle .switch:before { + width: 100%; + height: 100%; + border-radius: 40px; + background: #dadada; +} + +.toggle .switch:after { + width: 11px; + height: 11px; + border-radius: 50%; + left: 3px; + top: 3px; + background: #fafafa; + transition: transform 0.15s ease; +} + +.toggle input:checked:not(:disabled) + .switch:before { + background: #41b855; +} + +.toggle input:checked:not(:disabled) + .switch:after { + transform: translateX(15px); +} + +.toggle.disabled { + color: var(--color-text--light); +} + +label.checkbox-container { + display: flex; + align-items: center; + height: 15px; + font-weight: 400; +} + +label.checkbox-container input[type="checkbox"] { + width: 15px; + height: 15px; + margin: 0 5px 0 0; +} + + +` +export default cssStr diff --git a/app/userland/app-stdlib/css/reset.css b/app/userland/app-stdlib/css/reset.css new file mode 100644 index 0000000000..effe6c7e15 --- /dev/null +++ b/app/userland/app-stdlib/css/reset.css @@ -0,0 +1,20 @@ +*, +*:before, +*:after { + box-sizing: border-box; +} + +body { + margin: 0; +} + +a { + text-decoration: none; + color: inherit; +} + +button { + background: none; + outline-color: transparent; + border: none; +} diff --git a/app/userland/app-stdlib/css/reset.css.js b/app/userland/app-stdlib/css/reset.css.js new file mode 100644 index 0000000000..76df0878d0 --- /dev/null +++ b/app/userland/app-stdlib/css/reset.css.js @@ -0,0 +1,26 @@ +import {css} from '../vendor/lit-element/lit-element.js' + +const cssStr = css` +*, +*:before, +*:after { + box-sizing: border-box; +} + +body { + margin: 0; +} + +a { + text-decoration: none; + color: inherit; +} + +button { + background: none; + outline-color: transparent; + border: none; +} + +` +export default cssStr diff --git a/app/userland/app-stdlib/css/tooltip.css b/app/userland/app-stdlib/css/tooltip.css new file mode 100644 index 0000000000..c53d1327c0 --- /dev/null +++ b/app/userland/app-stdlib/css/tooltip.css @@ -0,0 +1,97 @@ +*[data-tooltip] { + position: relative; +} + +*[data-tooltip]:hover:before, +*[data-tooltip]:hover:after { + display: block; + z-index: 1000; + transition: opacity 0.01s ease; + transition-delay: 0.2s; +} + +*[data-tooltip]:hover:after { + opacity: 1; +} + +*[data-tooltip]:hover:before { + transform: translate(-50%, 0); + opacity: 1; +} + +*[data-tooltip]:before { + opacity: 0; + transform: translate(-50%, 0); + position: absolute; + top: 33px; + left: 50%; + z-index: 3000; + content: attr(data-tooltip); + background: rgba(17, 17, 17, 0.95); + font-size: 0.7rem; + border: 0; + border-radius: 4px; + padding: 7px 10px; + color: rgba(255, 255, 255, 0.925); + text-transform: none; + text-align: center; + font-weight: 500; + white-space: pre; + line-height: 1; + pointer-events: none; +} + +*[data-tooltip]:after { + opacity: 0; + position: absolute; + left: calc(50% - 6px); + top: 28px; + content: ''; + width: 0; + height: 0; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid rgba(17, 17, 17, 0.95); + pointer-events: none; +} + +.tooltip-nodelay[data-tooltip]:hover:before, +.tooltip-nodelay[data-tooltip]:hover:after { + transition-delay: initial; +} + +.tooltip-right[data-tooltip]:before { + top: 50%; + left: calc(100% + 6px); + transform: translate(0, -50%); + line-height: 0.9; +} + +.tooltip-right[data-tooltip]:after { + top: 50%; + left: calc(100% + 0px); + transform: translate(0, -50%); + border: 0; + border-top: 6px solid transparent; + border-bottom: 6px solid transparent; + border-right: 6px solid rgba(17, 17, 17, 0.95); +} + +.tooltip-left[data-tooltip]:before { + top: 50%; + left: auto; + right: calc(100% + 6px); + transform: translate(0, -50%); + line-height: 0.9; +} + +.tooltip-left[data-tooltip]:after { + top: 50%; + left: auto; + right: calc(100% + 0px); + transform: translate(0, -50%); + border: 0; + border-top: 6px solid transparent; + border-bottom: 6px solid transparent; + border-left: 6px solid rgba(17, 17, 17, 0.95); +} \ No newline at end of file diff --git a/app/userland/app-stdlib/css/tooltip.css.js b/app/userland/app-stdlib/css/tooltip.css.js new file mode 100644 index 0000000000..2fae778e2f --- /dev/null +++ b/app/userland/app-stdlib/css/tooltip.css.js @@ -0,0 +1,102 @@ +import {css} from '../vendor/lit-element/lit-element.js' + +const cssStr = css` +*[data-tooltip] { + position: relative; +} + +*[data-tooltip]:hover:before, +*[data-tooltip]:hover:after { + display: block; + z-index: 1000; + transition: opacity 0.01s ease; + transition-delay: 0.2s; +} + +*[data-tooltip]:hover:after { + opacity: 1; +} + +*[data-tooltip]:hover:before { + transform: translate(-50%, 0); + opacity: 1; +} + +*[data-tooltip]:before { + opacity: 0; + transform: translate(-50%, 0); + position: absolute; + top: 33px; + left: 50%; + z-index: 3000; + content: attr(data-tooltip); + background: rgba(17, 17, 17, 0.95); + font-size: 0.7rem; + border: 0; + border-radius: 4px; + padding: 7px 10px; + color: rgba(255, 255, 255, 0.925); + text-transform: none; + text-align: center; + font-weight: 500; + white-space: pre; + line-height: 1; + pointer-events: none; +} + +*[data-tooltip]:after { + opacity: 0; + position: absolute; + left: calc(50% - 6px); + top: 28px; + content: ''; + width: 0; + height: 0; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid rgba(17, 17, 17, 0.95); + pointer-events: none; +} + +.tooltip-nodelay[data-tooltip]:hover:before, +.tooltip-nodelay[data-tooltip]:hover:after { + transition-delay: initial; +} + +.tooltip-right[data-tooltip]:before { + top: 50%; + left: calc(100% + 6px); + transform: translate(0, -50%); + line-height: 0.9; +} + +.tooltip-right[data-tooltip]:after { + top: 50%; + left: calc(100% + 0px); + transform: translate(0, -50%); + border: 0; + border-top: 6px solid transparent; + border-bottom: 6px solid transparent; + border-right: 6px solid rgba(17, 17, 17, 0.95); +} + +.tooltip-left[data-tooltip]:before { + top: 50%; + left: auto; + right: calc(100% + 6px); + transform: translate(0, -50%); + line-height: 0.9; +} + +.tooltip-left[data-tooltip]:after { + top: 50%; + left: auto; + right: calc(100% + 0px); + transform: translate(0, -50%); + border: 0; + border-top: 6px solid transparent; + border-bottom: 6px solid transparent; + border-left: 6px solid rgba(17, 17, 17, 0.95); +} +` +export default cssStr diff --git a/app/userland/app-stdlib/css/typography.css b/app/userland/app-stdlib/css/typography.css new file mode 100644 index 0000000000..b9961422bb --- /dev/null +++ b/app/userland/app-stdlib/css/typography.css @@ -0,0 +1,13 @@ +body { + --system-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Ubuntu, Cantarell, "Oxygen Sans", "Helvetica Neue", sans-serif; + --code-font: Consolas, 'Lucida Console', Monaco, monospace; +} + +body { + font-family: var(--system-font); +} + +code { + font-family: var(--code-font); + font-style: normal; +} diff --git a/app/userland/app-stdlib/css/typography.css.js b/app/userland/app-stdlib/css/typography.css.js new file mode 100644 index 0000000000..f21bde8644 --- /dev/null +++ b/app/userland/app-stdlib/css/typography.css.js @@ -0,0 +1,19 @@ +import {css} from '../vendor/lit-element/lit-element.js' + +const cssStr = css` +body { + --system-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Ubuntu, Cantarell, "Oxygen Sans", "Helvetica Neue", sans-serif; + --code-font: Consolas, 'Lucida Console', Monaco, monospace; +} + +body { + font-family: var(--system-font); +} + +code { + font-family: var(--code-font); + font-style: normal; +} + +` +export default cssStr diff --git a/app/userland/app-stdlib/data/emoji-list.js b/app/userland/app-stdlib/data/emoji-list.js new file mode 100644 index 0000000000..efb4731f7d --- /dev/null +++ b/app/userland/app-stdlib/data/emoji-list.js @@ -0,0 +1,1619 @@ + +export const SUGGESTED = [ + '❤', + '👀', + '🔥', + '🎉', + '✨', + '🆒', + '🙂', + '😂', + '😅', + '😢', + '😐', + '😮', + '😡', + '😤', + '🤭', + '🤔', + '🤨', + '🤯', + '👍', + '👎', + '👆', + '👏', + '🙌', + '🙏', + '👋', + '💪', + '💅', + '✊', + '👌', + '🤘' +] + +export const GROUPS = [ + { + 'name': 'Smileys & Emotion', + 'emojis': [ + '😀', + '😃', + '😄', + '😁', + '😆', + '😅', + '🤣', + '😂', + '🙂', + '🙃', + '😉', + '😊', + '😇', + '🥰', + '😍', + '🤩', + '😘', + '😗', + '😚', + '😙', + '😋', + '😛', + '😜', + '🤪', + '😝', + '🤑', + '🤗', + '🤭', + '🤫', + '🤔', + '🤐', + '🤨', + '😐', + '😑', + '😶', + '😏', + '😒', + '🙄', + '😬', + '🤥', + '😌', + '😔', + '😪', + '🤤', + '😴', + '😷', + '🤒', + '🤕', + '🤢', + '🤮', + '🤧', + '🥵', + '🥶', + '🥴', + '😵', + '🤯', + '🤠', + '🥳', + '😎', + '🤓', + '🧐', + '😕', + '😟', + '🙁', + '☹️', + '😮', + '😯', + '😲', + '😳', + '🥺', + '😦', + '😧', + '😨', + '😰', + '😥', + '😢', + '😭', + '😱', + '😖', + '😣', + '😞', + '😓', + '😩', + '😫', + '😤', + '😡', + '😠', + '🤬', + '😈', + '👿', + '💀', + '☠️', + '💩', + '🤡', + '👹', + '👺', + '👻', + '👽', + '👾', + '🤖', + '😺', + '😸', + '😹', + '😻', + '😼', + '😽', + '🙀', + '😿', + '😾', + '🙈', + '🙉', + '🙊', + '💋', + '💌', + '💘', + '💝', + '💖', + '💗', + '💓', + '💞', + '💕', + '💟', + '❣️', + '💔', + '❤️', + '🧡', + '💛', + '💚', + '💙', + '💜', + '🖤', + '💯', + '💢', + '💥', + '💫', + '💦', + '💨', + '🕳️', + '💣', + '💬', + '👁️‍🗨️', + '🗨️', + '🗯️', + '💭', + '💤' + ] + }, + { + 'name': 'People & Body', + 'emojis': [ + '👋', + '🤚', + '🖐️', + '🖐', + '✋', + '🖖', + '👌', + '✌️', + '✌', + '🤞', + '🤟', + '🤘', + '🤙', + '👈', + '👉', + '👆', + '👇', + '☝️', + '☝', + '👍', + '👎', + '✊', + '👊', + '🤛', + '🤜', + '👏', + '🙌', + '👐', + '🤲', + '🤝', + '🙏', + '✍️', + '✍', + '💅', + '🤳', + '💪', + '🦵', + '🦶', + '👂', + '👃', + '🧠', + '🦷', + '🦴', + '👀', + '👁️', + '👅', + '👄', + '👶', + '🧒', + '👦', + '👧', + '🧑', + '👱', + '👨', + '🧔', + '👩', + '🧓', + '👴', + '👵', + '🙍', + '🙎', + '🙅', + '🙆', + '💁', + '🙋', + '🙇', + '🤦', + '🤷', + '👨‍⚕️', + '👩‍⚕️', + '👨‍🎓', + '👩‍🎓', + '👨‍🏫', + '👩‍🏫', + '👨‍⚖️', + '👩‍⚖️', + '👨‍🌾', + '👩‍🌾', + '👨‍🍳', + '👩‍🍳', + '👨‍🔧', + '👩‍🔧', + '👨‍🏭', + '👩‍🏭', + '👨‍💼', + '👩‍💼', + '👨‍🔬', + '👩‍🔬', + '👨‍💻', + '👩‍💻', + '👨‍🎤', + '👩‍🎤', + '👨‍🎨', + '👩‍🎨', + '👨‍✈️', + '👩‍✈️', + '👨‍🚀', + '👩‍🚀', + '👨‍🚒', + '👩‍🚒', + '👮', + '🕵️', + '🕵', + '💂', + '👷', + '🤴', + '👸', + '👳', + '👲', + '🧕', + '🤵', + '👰', + '🤰', + '🤱', + '👼', + '🎅', + '🤶', + '🧙', + '🧚', + '🧛', + '🧜', + '🧝', + '🧞', + '🧟', + '💆', + '💇', + '🚶', + '🏃', + '💃', + '🕺', + '🕴️', + '🕴', + '👯', + '🧖', + '🧗', + '🤺', + '🏇', + '⛷️', + '🏂', + '🏌️', + '🏌', + '🏄', + '🚣', + '🏊', + '⛹️', + '⛹', + '🏋️', + '🏋', + '🚴', + '🚵', + '🤸', + '🤼', + '🤽', + '🤾', + '🤹', + '🧘', + '🛀', + '🛌', + '👭', + '👫', + '👬', + '💏', + '👪', + '👨‍👩‍👦', + '👨‍👩‍👧', + '👨‍👩‍👧‍👦', + '👨‍👩‍👦‍👦', + '👨‍👩‍👧‍👧', + '👨‍👨‍👦', + '👨‍👨‍👧', + '👨‍👨‍👧‍👦', + '👨‍👨‍👦‍👦', + '👨‍👨‍👧‍👧', + '👩‍👩‍👦', + '👩‍👩‍👧', + '👩‍👩‍👧‍👦', + '👩‍👩‍👦‍👦', + '👩‍👩‍👧‍👧', + '👨‍👦', + '👨‍👦‍👦', + '👨‍👧', + '👨‍👧‍👦', + '👨‍👧‍👧', + '👩‍👦', + '👩‍👦‍👦', + '👩‍👧', + '👩‍👧‍👦', + '👩‍👧‍👧', + '🗣️', + '👤', + '👥', + '👣' + ] + }, + { + 'name': 'Animals & Nature', + 'emojis': [ + '🐵', + '🐒', + '🦍', + '🐶', + '🐕', + '🐩', + '🐺', + '🦊', + '🦝', + '🐱', + '🐈', + '🦁', + '🐯', + '🐅', + '🐆', + '🐴', + '🐎', + '🦄', + '🦓', + '🦌', + '🐮', + '🐂', + '🐃', + '🐄', + '🐷', + '🐖', + '🐗', + '🐽', + '🐏', + '🐑', + '🐐', + '🐪', + '🐫', + '🦙', + '🦒', + '🐘', + '🦏', + '🦛', + '🐭', + '🐁', + '🐀', + '🐹', + '🐰', + '🐇', + '🐿️', + '🦔', + '🦇', + '🐻', + '🐨', + '🐼', + '🦘', + '🦡', + '🐾', + '🦃', + '🐔', + '🐓', + '🐣', + '🐤', + '🐥', + '🐦', + '🐧', + '🕊️', + '🦅', + '🦆', + '🦢', + '🦉', + '🦚', + '🦜', + '🐸', + '🐊', + '🐢', + '🦎', + '🐍', + '🐲', + '🐉', + '🦕', + '🦖', + '🐳', + '🐋', + '🐬', + '🐟', + '🐠', + '🐡', + '🦈', + '🐙', + '🐚', + '🐌', + '🦋', + '🐛', + '🐜', + '🐝', + '🐞', + '🦗', + '🕷️', + '🕸️', + '🦂', + '🦟', + '🦠', + '💐', + '🌸', + '💮', + '🏵️', + '🌹', + '🥀', + '🌺', + '🌻', + '🌼', + '🌷', + '🌱', + '🌲', + '🌳', + '🌴', + '🌵', + '🌾', + '🌿', + '☘️', + '🍀', + '🍁', + '🍂', + '🍃' + ] + }, + { + 'name': 'Food & Drink', + 'emojis': [ + '🍇', + '🍈', + '🍉', + '🍊', + '🍋', + '🍌', + '🍍', + '🥭', + '🍎', + '🍏', + '🍐', + '🍑', + '🍒', + '🍓', + '🥝', + '🍅', + '🥥', + '🥑', + '🍆', + '🥔', + '🥕', + '🌽', + '🌶️', + '🥒', + '🥬', + '🥦', + '🍄', + '🥜', + '🌰', + '🍞', + '🥐', + '🥖', + '🥨', + '🥯', + '🥞', + '🧀', + '🍖', + '🍗', + '🥩', + '🥓', + '🍔', + '🍟', + '🍕', + '🌭', + '🥪', + '🌮', + '🌯', + '🥙', + '🥚', + '🍳', + '🥘', + '🍲', + '🥣', + '🥗', + '🍿', + '🧂', + '🥫', + '🍱', + '🍘', + '🍙', + '🍚', + '🍛', + '🍜', + '🍝', + '🍠', + '🍢', + '🍣', + '🍤', + '🍥', + '🥮', + '🍡', + '🥟', + '🥠', + '🥡', + '🦀', + '🦞', + '🦐', + '🦑', + '🍦', + '🍧', + '🍨', + '🍩', + '🍪', + '🎂', + '🍰', + '🧁', + '🥧', + '🍫', + '🍬', + '🍭', + '🍮', + '🍯', + '🍼', + '🥛', + '☕', + '🍵', + '🍶', + '🍾', + '🍷', + '🍸', + '🍹', + '🍺', + '🍻', + '🥂', + '🥃', + '🥤', + '🥢', + '🍽️', + '🍴', + '🥄', + '🏺' + ] + }, + { + 'name': 'Travel & Places', + 'emojis': [ + '🌍', + '🌎', + '🌏', + '🌐', + '🗺️', + '🗾', + '🧭', + '🏔️', + '⛰️', + '🌋', + '🗻', + '🏕️', + '🏖️', + '🏜️', + '🏝️', + '🏞️', + '🏟️', + '🏛️', + '🏗️', + '🧱', + '🏘️', + '🏚️', + '🏠', + '🏡', + '🏢', + '🏣', + '🏤', + '🏥', + '🏦', + '🏨', + '🏩', + '🏪', + '🏫', + '🏬', + '🏭', + '🏯', + '🏰', + '💒', + '🗼', + '🗽', + '⛪', + '🕌', + '🕍', + '⛩️', + '🕋', + '⛲', + '⛺', + '🌁', + '🌃', + '🏙️', + '🌄', + '🌅', + '🌆', + '🌇', + '🌉', + '♨️', + '🎠', + '🎡', + '🎢', + '💈', + '🎪', + '🚂', + '🚃', + '🚄', + '🚅', + '🚆', + '🚇', + '🚈', + '🚉', + '🚊', + '🚝', + '🚞', + '🚋', + '🚌', + '🚍', + '🚎', + '🚐', + '🚑', + '🚒', + '🚓', + '🚔', + '🚕', + '🚖', + '🚗', + '🚘', + '🚙', + '🚚', + '🚛', + '🚜', + '🏎️', + '🏍️', + '🛵', + '🚲', + '🛴', + '🛹', + '🚏', + '🛣️', + '🛤️', + '🛢️', + '⛽', + '🚨', + '🚥', + '🚦', + '🛑', + '🚧', + '⚓', + '⛵', + '🛶', + '🚤', + '🛳️', + '⛴️', + '🛥️', + '🚢', + '✈️', + '🛩️', + '🛫', + '🛬', + '💺', + '🚁', + '🚟', + '🚠', + '🚡', + '🛰️', + '🚀', + '🛸', + '🛎️', + '🧳', + '⌛', + '⏳', + '⌚', + '⏰', + '⏱️', + '⏲️', + '🕰️', + '🕛', + '🕧', + '🕐', + '🕜', + '🕑', + '🕝', + '🕒', + '🕞', + '🕓', + '🕟', + '🕔', + '🕠', + '🕕', + '🕡', + '🕖', + '🕢', + '🕗', + '🕣', + '🕘', + '🕤', + '🕙', + '🕥', + '🕚', + '🕦', + '🌑', + '🌒', + '🌓', + '🌔', + '🌕', + '🌖', + '🌗', + '🌘', + '🌙', + '🌚', + '🌛', + '🌜', + '🌡️', + '☀️', + '🌝', + '🌞', + '⭐', + '🌟', + '🌠', + '🌌', + '☁️', + '⛅', + '⛈️', + '🌤️', + '🌥️', + '🌦️', + '🌧️', + '🌨️', + '🌩️', + '🌪️', + '🌫️', + '🌬️', + '🌀', + '🌈', + '🌂', + '☂️', + '☔', + '⛱️', + '⚡', + '❄️', + '☃️', + '⛄', + '☄️', + '🔥', + '💧', + '🌊' + ] + }, + { + 'name': 'Activities', + 'emojis': [ + '🎃', + '🎄', + '🎆', + '🎇', + '🧨', + '✨', + '🎈', + '🎉', + '🎊', + '🎋', + '🎍', + '🎎', + '🎏', + '🎐', + '🎑', + '🧧', + '🎀', + '🎁', + '🎗️', + '🎟️', + '🎫', + '🎖️', + '🏆', + '🏅', + '🥇', + '🥈', + '🥉', + '⚽', + '⚾', + '🥎', + '🏀', + '🏐', + '🏈', + '🏉', + '🎾', + '🥏', + '🎳', + '🏏', + '🏑', + '🏒', + '🥍', + '🏓', + '🏸', + '🥊', + '🥋', + '🥅', + '⛳', + '⛸️', + '🎣', + '🎽', + '🎿', + '🛷', + '🥌', + '🎯', + '🎱', + '🔮', + '🧿', + '🎮', + '🕹️', + '🎰', + '🎲', + '🧩', + '🧸', + '♠️', + '♥️', + '♦️', + '♣️', + '♟️', + '🃏', + '🀄', + '🎴', + '🎭', + '🖼️', + '🎨', + '🧵', + '🧶' + ] + }, + { + 'name': 'Objects', + 'emojis': [ + '👓', + '🕶️', + '🥽', + '🥼', + '👔', + '👕', + '👖', + '🧣', + '🧤', + '🧥', + '🧦', + '👗', + '👘', + '👙', + '👚', + '👛', + '👜', + '👝', + '🛍️', + '🎒', + '👞', + '👟', + '🥾', + '🥿', + '👠', + '👡', + '👢', + '👑', + '👒', + '🎩', + '🎓', + '🧢', + '⛑️', + '📿', + '💄', + '💍', + '💎', + '🔇', + '🔈', + '🔉', + '🔊', + '📢', + '📣', + '📯', + '🔔', + '🔕', + '🎼', + '🎵', + '🎶', + '🎙️', + '🎚️', + '🎛️', + '🎤', + '🎧', + '📻', + '🎷', + '🎸', + '🎹', + '🎺', + '🎻', + '🥁', + '📱', + '📲', + '☎️', + '📞', + '📟', + '📠', + '🔋', + '🔌', + '💻', + '🖥️', + '🖨️', + '⌨️', + '🖱️', + '🖲️', + '💽', + '💾', + '💿', + '📀', + '🧮', + '🎥', + '🎞️', + '📽️', + '🎬', + '📺', + '📷', + '📸', + '📹', + '📼', + '🔍', + '🔎', + '🕯️', + '💡', + '🔦', + '🏮', + '📔', + '📕', + '📖', + '📗', + '📘', + '📙', + '📚', + '📓', + '📒', + '📃', + '📜', + '📄', + '📰', + '🗞️', + '📑', + '🔖', + '🏷️', + '💰', + '💴', + '💵', + '💶', + '💷', + '💸', + '💳', + '🧾', + '💹', + '💱', + '💲', + '✉️', + '📧', + '📨', + '📩', + '📤', + '📥', + '📦', + '📫', + '📪', + '📬', + '📭', + '📮', + '🗳️', + '✏️', + '✒️', + '🖋️', + '🖊️', + '🖌️', + '🖍️', + '📝', + '💼', + '📁', + '📂', + '🗂️', + '📅', + '📆', + '🗒️', + '🗓️', + '📇', + '📈', + '📉', + '📊', + '📋', + '📌', + '📍', + '📎', + '🖇️', + '📏', + '📐', + '✂️', + '🗃️', + '🗄️', + '🗑️', + '🔒', + '🔓', + '🔏', + '🔐', + '🔑', + '🗝️', + '🔨', + '⛏️', + '⚒️', + '🛠️', + '⚔️', + '🏹', + '🛡️', + '🔧', + '🔩', + '⚙️', + '🗜️', + '⚖️', + '🔗', + '⛓️', + '🧰', + '🧲', + '⚗️', + '🧪', + '🧫', + '🧬', + '🔬', + '🔭', + '📡', + '💉', + '💊', + '🚪', + '🛏️', + '🛋️', + '🚽', + '🚿', + '🛁', + '🧴', + '🧷', + '🧹', + '🧺', + '🧻', + '🧼', + '🧽', + '🧯', + '🛒', + '🚬', + '⚰️', + '⚱️', + '🗿' + ] + }, + { + 'name': 'Symbols', + 'emojis': [ + '🏧', + '🚮', + '🚰', + '♿', + '🚹', + '🚺', + '🚻', + '🚼', + '🚾', + '🛂', + '🛃', + '🛄', + '🛅', + '⚠️', + '🚸', + '⛔', + '🚫', + '🚳', + '🚭', + '🚯', + '🚱', + '🚷', + '📵', + '🔞', + '☢️', + '☣️', + '⬆️', + '↗️', + '➡️', + '↘️', + '⬇️', + '↙️', + '⬅️', + '↖️', + '↕️', + '↔️', + '↩️', + '↪️', + '⤴️', + '⤵️', + '🔃', + '🔄', + '🔙', + '🔚', + '🔛', + '🔜', + '🔝', + '🛐', + '⚛️', + '🕉️', + '✡️', + '☸️', + '☯️', + '✝️', + '☦️', + '☪️', + '☮️', + '🕎', + '🔯', + '♈', + '♉', + '♊', + '♋', + '♌', + '♍', + '♎', + '♏', + '♐', + '♑', + '♒', + '♓', + '⛎', + '🔀', + '🔁', + '🔂', + '▶️', + '⏩', + '⏭️', + '⏯️', + '◀️', + '⏪', + '⏮️', + '🔼', + '⏫', + '🔽', + '⏬', + '⏸️', + '⏹️', + '⏺️', + '⏏️', + '🎦', + '🔅', + '🔆', + '📶', + '📳', + '📴', + '♀️', + '♂️', + '⚕️', + '♾️', + '♻️', + '⚜️', + '🔱', + '📛', + '🔰', + '⭕', + '✅', + '☑️', + '✔️', + '✖️', + '❌', + '❎', + '➕', + '➖', + '➗', + '➰', + '➿', + '〽️', + '✳️', + '✴️', + '❇️', + '‼️', + '⁉️', + '❓', + '❔', + '❕', + '❗', + '〰️', + '©️', + '®️', + '™️', + '#️⃣', + '*️⃣', + '0️⃣', + '1️⃣', + '2️⃣', + '3️⃣', + '4️⃣', + '5️⃣', + '6️⃣', + '7️⃣', + '8️⃣', + '9️⃣', + '🔟', + '🔠', + '🔡', + '🔢', + '🔣', + '🔤', + '🅰️', + '🆎', + '🅱️', + '🆑', + '🆒', + '🆓', + 'ℹ️', + '🆔', + 'Ⓜ️', + '🆕', + '🆖', + '🅾️', + '🆗', + '🅿️', + '🆘', + '🆙', + '🆚', + '🈁', + '🈂️', + '🈷️', + '🈶', + '🈯', + '🉐', + '🈹', + '🈚', + '🈲', + '🉑', + '🈸', + '🈴', + '🈳', + '㊗️', + '㊙️', + '🈺', + '🈵', + '🔴', + '🔵', + '⚫', + '⚪', + '⬛', + '⬜', + '◼️', + '◻️', + '◾', + '◽', + '▪️', + '▫️', + '🔶', + '🔷', + '🔸', + '🔹', + '🔺', + '🔻', + '💠', + '🔘', + '🔳', + '🔲' + ] + }, + { + 'name': 'Flags', + 'emojis': [ + '🏁', + '🚩', + '🎌', + '🏴', + '🏳️', + '🏳️‍🌈', + '🇦🇨', + '🇦🇩', + '🇦🇪', + '🇦🇫', + '🇦🇬', + '🇦🇮', + '🇦🇱', + '🇦🇲', + '🇦🇴', + '🇦🇶', + '🇦🇷', + '🇦🇸', + '🇦🇹', + '🇦🇺', + '🇦🇼', + '🇦🇽', + '🇦🇿', + '🇧🇦', + '🇧🇧', + '🇧🇩', + '🇧🇪', + '🇧🇫', + '🇧🇬', + '🇧🇭', + '🇧🇮', + '🇧🇯', + '🇧🇱', + '🇧🇲', + '🇧🇳', + '🇧🇴', + '🇧🇶', + '🇧🇷', + '🇧🇸', + '🇧🇹', + '🇧🇻', + '🇧🇼', + '🇧🇾', + '🇧🇿', + '🇨🇦', + '🇨🇨', + '🇨🇩', + '🇨🇫', + '🇨🇬', + '🇨🇭', + '🇨🇮', + '🇨🇰', + '🇨🇱', + '🇨🇲', + '🇨🇳', + '🇨🇴', + '🇨🇵', + '🇨🇷', + '🇨🇺', + '🇨🇻', + '🇨🇼', + '🇨🇽', + '🇨🇾', + '🇨🇿', + '🇩🇪', + '🇩🇬', + '🇩🇯', + '🇩🇰', + '🇩🇲', + '🇩🇴', + '🇩🇿', + '🇪🇦', + '🇪🇨', + '🇪🇪', + '🇪🇬', + '🇪🇭', + '🇪🇷', + '🇪🇸', + '🇪🇹', + '🇪🇺', + '🇫🇮', + '🇫🇯', + '🇫🇰', + '🇫🇲', + '🇫🇴', + '🇫🇷', + '🇬🇦', + '🇬🇧', + '🇬🇩', + '🇬🇪', + '🇬🇫', + '🇬🇬', + '🇬🇭', + '🇬🇮', + '🇬🇱', + '🇬🇲', + '🇬🇳', + '🇬🇵', + '🇬🇶', + '🇬🇷', + '🇬🇸', + '🇬🇹', + '🇬🇺', + '🇬🇼', + '🇬🇾', + '🇭🇰', + '🇭🇲', + '🇭🇳', + '🇭🇷', + '🇭🇹', + '🇭🇺', + '🇮🇨', + '🇮🇩', + '🇮🇪', + '🇮🇱', + '🇮🇲', + '🇮🇳', + '🇮🇴', + '🇮🇶', + '🇮🇷', + '🇮🇸', + '🇮🇹', + '🇯🇪', + '🇯🇲', + '🇯🇴', + '🇯🇵', + '🇰🇪', + '🇰🇬', + '🇰🇭', + '🇰🇮', + '🇰🇲', + '🇰🇳', + '🇰🇵', + '🇰🇷', + '🇰🇼', + '🇰🇾', + '🇰🇿', + '🇱🇦', + '🇱🇧', + '🇱🇨', + '🇱🇮', + '🇱🇰', + '🇱🇷', + '🇱🇸', + '🇱🇹', + '🇱🇺', + '🇱🇻', + '🇱🇾', + '🇲🇦', + '🇲🇨', + '🇲🇩', + '🇲🇪', + '🇲🇫', + '🇲🇬', + '🇲🇭', + '🇲🇰', + '🇲🇱', + '🇲🇲', + '🇲🇳', + '🇲🇴', + '🇲🇵', + '🇲🇶', + '🇲🇷', + '🇲🇸', + '🇲🇹', + '🇲🇺', + '🇲🇻', + '🇲🇼', + '🇲🇽', + '🇲🇾', + '🇲🇿', + '🇳🇦', + '🇳🇨', + '🇳🇪', + '🇳🇫', + '🇳🇬', + '🇳🇮', + '🇳🇱', + '🇳🇴', + '🇳🇵', + '🇳🇷', + '🇳🇺', + '🇳🇿', + '🇴🇲', + '🇵🇦', + '🇵🇪', + '🇵🇫', + '🇵🇬', + '🇵🇭', + '🇵🇰', + '🇵🇱', + '🇵🇲', + '🇵🇳', + '🇵🇷', + '🇵🇸', + '🇵🇹', + '🇵🇼', + '🇵🇾', + '🇶🇦', + '🇷🇪', + '🇷🇴', + '🇷🇸', + '🇷🇺', + '🇷🇼', + '🇸🇦', + '🇸🇧', + '🇸🇨', + '🇸🇩', + '🇸🇪', + '🇸🇬', + '🇸🇭', + '🇸🇮', + '🇸🇯', + '🇸🇰', + '🇸🇱', + '🇸🇲', + '🇸🇳', + '🇸🇴', + '🇸🇷', + '🇸🇸', + '🇸🇹', + '🇸🇻', + '🇸🇽', + '🇸🇾', + '🇸🇿', + '🇹🇦', + '🇹🇨', + '🇹🇩', + '🇹🇫', + '🇹🇬', + '🇹🇭', + '🇹🇯', + '🇹🇰', + '🇹🇱', + '🇹🇲', + '🇹🇳', + '🇹🇴', + '🇹🇷', + '🇹🇹', + '🇹🇻', + '🇹🇼', + '🇹🇿', + '🇺🇦', + '🇺🇬', + '🇺🇲', + '🇺🇳', + '🇺🇸', + '🇺🇾', + '🇺🇿', + '🇻🇦', + '🇻🇨', + '🇻🇪', + '🇻🇬', + '🇻🇮', + '🇻🇳', + '🇻🇺', + '🇼🇫', + '🇼🇸', + '🇽🇰', + '🇾🇪', + '🇾🇹', + '🇿🇦', + '🇿🇲', + '🇿🇼', + '🏴󠁧󠁢󠁥󠁮󠁧󠁿', + '🏴󠁧󠁢󠁳󠁣󠁴󠁿', + '🏴󠁧󠁢󠁷󠁬󠁳󠁿' + ] + } +] + +export const FULL_LIST = GROUPS.map(({emojis}) => emojis).reduce((acc, v) => acc.concat(v), []) diff --git a/app/userland/app-stdlib/img/beaker-logo.png b/app/userland/app-stdlib/img/beaker-logo.png new file mode 100644 index 0000000000..e8abf4c8e8 Binary files /dev/null and b/app/userland/app-stdlib/img/beaker-logo.png differ diff --git a/app/userland/app-stdlib/img/icons/bookmarks.png b/app/userland/app-stdlib/img/icons/bookmarks.png new file mode 100644 index 0000000000..adc413b751 Binary files /dev/null and b/app/userland/app-stdlib/img/icons/bookmarks.png differ diff --git a/app/userland/app-stdlib/img/icons/library.png b/app/userland/app-stdlib/img/icons/library.png new file mode 100644 index 0000000000..cbae18d101 Binary files /dev/null and b/app/userland/app-stdlib/img/icons/library.png differ diff --git a/app/assets/img/favicons/timeline.png b/app/userland/app-stdlib/img/icons/newsfeed.png similarity index 100% rename from app/assets/img/favicons/timeline.png rename to app/userland/app-stdlib/img/icons/newsfeed.png diff --git a/app/userland/app-stdlib/img/icons/search.png b/app/userland/app-stdlib/img/icons/search.png new file mode 100644 index 0000000000..525ee64323 Binary files /dev/null and b/app/userland/app-stdlib/img/icons/search.png differ diff --git a/app/userland/app-stdlib/img/icons/start.png b/app/userland/app-stdlib/img/icons/start.png new file mode 100644 index 0000000000..938d9ae0ef Binary files /dev/null and b/app/userland/app-stdlib/img/icons/start.png differ diff --git a/app/userland/app-stdlib/js/clipboard.js b/app/userland/app-stdlib/js/clipboard.js new file mode 100644 index 0000000000..a1a140d666 --- /dev/null +++ b/app/userland/app-stdlib/js/clipboard.js @@ -0,0 +1,8 @@ +export function writeToClipboard (str) { + var textarea = document.createElement('textarea') + textarea.textContent = str + document.body.appendChild(textarea) + textarea.select() + document.execCommand('copy') + document.body.removeChild(textarea) +} \ No newline at end of file diff --git a/app/userland/app-stdlib/js/com/app-menu.js b/app/userland/app-stdlib/js/com/app-menu.js new file mode 100644 index 0000000000..fb3c677113 --- /dev/null +++ b/app/userland/app-stdlib/js/com/app-menu.js @@ -0,0 +1,45 @@ +import {html} from '../../vendor/lit-element/lit-element.js' +import * as contextMenu from './context-menu.js' + +export function create ({x, y}) { + return contextMenu.create({ + x, + y, + noBorders: true, + render () { + return html` + + + ` + } + }) +} \ No newline at end of file diff --git a/app/userland/app-stdlib/js/com/context-menu.js b/app/userland/app-stdlib/js/com/context-menu.js new file mode 100644 index 0000000000..8b825a9040 --- /dev/null +++ b/app/userland/app-stdlib/js/com/context-menu.js @@ -0,0 +1,234 @@ +import {LitElement, html, css} from '../../vendor/lit-element/lit-element.js' +import {classMap} from '../../vendor/lit-element/lit-html/directives/class-map.js' +import {ifDefined} from '../../vendor/lit-element/lit-html/directives/if-defined.js' +import {findParent} from '../dom.js' +import dropdownCSS from '../../css/com/dropdown.css.js' + +// globals +// = + +var resolve + +// exported api +// = + +// create a new context menu +// - returns a promise that will resolve to undefined when the menu goes away +// - example usage: +/* +create({ + // where to put the menu + x: e.clientX, + y: e.clientY, + + // align edge to right instead of left + right: true, + + // use triangle + withTriangle: true, + + // roomy style + roomy: true, + + // no borders on items + noBorders: false, + + // additional styles on dropdown-items + style: 'font-size: 14px', + + // parent element to append to + parent: document.body, + + // url to fontawesome css + fontAwesomeCSSUrl: 'beaker://assets/font-awesome.css', + + // menu items + items: [ + // icon from font-awesome + {icon: 'fa fa-link', label: 'Copy link', click: () => writeToClipboard('...')} + ] + + // instead of items, can give render() + render () { + return html` + + ` + } +} +*/ +export function create (opts) { + // destroy any existing + destroy() + + // extract attrs + var parent = opts.parent || document.body + + // render interface + parent.appendChild(new BeakerContextMenu(opts)) + document.addEventListener('keyup', onKeyUp) + document.addEventListener('click', onClickAnywhere) + + // return promise + return new Promise(_resolve => { + resolve = _resolve + }) +} + +export function destroy (value) { + const el = document.querySelector('beaker-context-menu') + if (el) { + el.parentNode.removeChild(el) + document.removeEventListener('keyup', onKeyUp) + document.removeEventListener('click', onClickAnywhere) + resolve(value) + } +} + +// global event handlers +// = + +function onKeyUp (e) { + e.preventDefault() + e.stopPropagation() + + if (e.keyCode === 27) { + destroy() + } +} + +function onClickAnywhere (e) { + if (!findParent(e.target, el => el.tagName === 'BEAKER-CONTEXT-MENU')) { + // click is outside the context-menu, destroy + destroy() + } +} + +// internal +// = + +export class BeakerContextMenu extends LitElement { + constructor ({x, y, right, center, top, withTriangle, roomy, noBorders, style, items, fontAwesomeCSSUrl, render}) { + super() + this.x = x + this.y = y + this.right = right || false + this.center = center || false + this.top = top || false + this.withTriangle = withTriangle || false + this.roomy = roomy || false + this.noBorders = noBorders || false + this.customStyle = style || undefined + this.items = items + this.fontAwesomeCSSUrl = fontAwesomeCSSUrl + this.customRender = render + } + + // calls the global destroy + // (this function exists so that custom renderers can destroy with this.destroy) + destroy () { + destroy() + } + + // rendering + // = + + render () { + const cls = classMap({ + 'dropdown-items': true, + right: this.right, + center: this.center, + left: !this.right, + top: this.top, + 'with-triangle': this.withTriangle, + roomy: this.roomy, + 'no-border': this.noBorders + }) + var style = '' + if (this.x) style += `left: ${this.x}px; ` + if (this.y) style += `top: ${this.y}px; ` + return html` + ${this.fontAwesomeCSSUrl ? html`` : ''} + ` + } +} + +BeakerContextMenu.styles = css` +${dropdownCSS} + +.context-menu { + position: fixed; + z-index: 10000; +} + +.dropdown-items { + width: auto; + white-space: nowrap; +} + +a.dropdown-item { + color: inherit; + text-decoration: none; +} + +.dropdown-item, +.dropdown-items.roomy .dropdown-item { + padding-right: 30px; /* add a little cushion to the right */ +} + +/* custom icon css */ +.fa-long-arrow-alt-right.custom-link-icon { + position: relative; + transform: rotate(-45deg); + left: 1px; +} +.fa-custom-path-icon:after { + content: './'; + letter-spacing: -1px; + font-family: var(--code-font); +} +` + +customElements.define('beaker-context-menu', BeakerContextMenu) \ No newline at end of file diff --git a/app/userland/app-stdlib/js/com/files-explorer.js b/app/userland/app-stdlib/js/com/files-explorer.js new file mode 100644 index 0000000000..d0b81df68c --- /dev/null +++ b/app/userland/app-stdlib/js/com/files-explorer.js @@ -0,0 +1,499 @@ +import { LitElement, html } from '../../vendor/lit-element/lit-element.js' +import { repeat } from '../../vendor/lit-element/lit-html/directives/repeat.js' +import { format as formatBytes } from '../../vendor/bytes/index.js' +import * as contextMenu from './context-menu.js' +import * as toast from './toast.js' +import { joinPath } from '../strings.js' +import { emit } from '../dom.js' +import { writeToClipboard } from '../clipboard.js' +import sidebarFilesViewCSS from '../../css/com/files-explorer.css.js' + +class FilesExplorer extends LitElement { + static get properties () { + return { + url: {type: String, reflect: true}, + isRoot: {type: Boolean, attribute: 'is-root'}, + isLoading: {type: Boolean}, + readOnly: {type: Boolean}, + items: {type: Array} + } + } + + static get styles () { + return [sidebarFilesViewCSS] + } + + get isDrive () { + return this.url && this.url.startsWith('hyper:') + } + + get drive () { + return beaker.hyperdrive.drive(this.url) + } + + get origin () { + let urlp = new URL(this.url) + return urlp.origin + '/' + } + + get viewedDatVersion () { + let urlp = new URL(this.url) + let parts = urlp.hostname.split('+') + if (parts.length === 2) return parts[1] + return 'latest' + } + + get pathname () { + let urlp = new URL(this.url) + return urlp.pathname + } + + constructor () { + super() + this.url = '' + this.isRoot = false + this.isLoading = true + this.readOnly = true + this.folderPath = '' + this.currentFolder = null + this.items = [] + this.load() + } + + attributeChangedCallback (name, oldval, newval) { + super.attributeChangedCallback(name, oldval, newval) + if (name === 'url') { + this.load() + } + } + + async load () { + this.isLoading = true + + var items = [] + if (this.isDrive) { + let drive = this.drive + + let info = await drive.getInfo() + this.readOnly = !info.writable + + let st + let folderPath = this.pathname + while (!st && folderPath !== '/') { + try { st = await drive.stat(folderPath) } + catch (e) { /* ignore */ } + if (!st || !st.isDirectory()) { + folderPath = (folderPath.split('/').slice(0, -1).filter(Boolean).join('/')) || '/' + } + } + this.folderPath = folderPath + + items = await drive.readdir(folderPath, {includeStats: true}) + items.sort((a, b) => { + if (a.stat.isDirectory() && !b.stat.isDirectory()) return -1 + if (!a.stat.isDirectory() && b.stat.isDirectory()) return 1 + return a.name.localeCompare(b.name) + }) + + this.currentFolder = await drive.stat(folderPath) + this.currentFolder.path = folderPath + this.currentFolder.name = folderPath.split('/').pop() || '/' + } + + this.items = items + this.isLoading = false + } + + // rendering + // = + + render () { + if (this.isLoading) { + return html` +
    +
    Loading...
    +
    + ` + } + if (!this.isDrive) { + return html` + +
    +
    This site doesn't support file listings
    +
    + ` + } + const icon = item => { + if (item.stat.mount) return html`` + if (item.stat.isDirectory()) return html`` + return html`` + } + return html` + +
    + ${this.readOnly ? html` +
    This site is read-only
    + ` : html` + + + + `} + ${this.isRoot ? html` + + + This is your private filesystem + + ` : ''} +
    + +
    + ${this.folderPath !== '/' ? html` +
    + + .. +
    + ` : ''} + ${repeat(this.items, item => html` +
    this.onClickItem(e, item)} @contextmenu=${e => this.onContextmenuItem(e, item)}> + ${icon(item)} + + ${item.name} + + + ${item.stat.size ? formatBytes(item.stat.size) : ''} + +
    + `)} +
    + ` + } + + // events + // = + + onContextmenuListing (e) { + e.preventDefault() + e.stopPropagation() + + contextMenu.create({ + x: e.clientX, + y: e.clientY, + fontAwesomeCSSUrl: 'beaker://assets/font-awesome.css', + noBorders: true, + roomy: true, + items: [ + { + icon: 'far fa-fw fa-folder', + label: 'New folder', + click: () => this.onClickNewFolder() + }, + { + icon: 'far fa-fw fa-file', + label: 'New file', + click: () => this.onClickNewFile() + }, + { + icon: 'fas fa-fw fa-upload', + label: 'Import files', + click: () => this.onClickImportFiles() + }, + { + icon: 'fas fa-fw fa-external-link-square-alt', + label: 'Mount', + click: () => this.onClickMount() + } + ] + }) + } + + onContextmenuItem (e, item) { + e.preventDefault() + e.stopPropagation() + + var url = joinPath(this.origin, this.folderPath || '', item.name || '') + var items = [] + items.push({ + icon: 'fas fa-fw fa-external-link-alt', + label: `Open in new tab`, + click: () => { + beaker.browser.openUrl(url, { + setActive: true, + sidebarPanels: ['editor-app'] + }) + } + }) + if (item.stat.isFile()) { + items.push({ + icon: 'fas fa-fw fa-edit', + label: `Edit file`, + click: () => { + beaker.browser.gotoUrl(url) + beaker.browser.executeSidebarCommand('show-panel', 'editor-app') + beaker.browser.executeSidebarCommand('set-context', 'editor-app', url) + } + }) + } + items.push({ + icon: 'fas fa-fw fa-link', + label: `Copy URL`, + click () { + writeToClipboard(url) + toast.create('Copied to your clipboard') + } + }) + items.push({ + icon: 'fa fa-fw fa-i-cursor', + label: 'Rename', + click: async () => { + var newname = prompt(`Enter the new name for this ${item.stat.isDirectory() ? 'folder' : 'file'}`, item.name) + if (!newname) return + var oldpath = joinPath(this.folderPath, item.name) + var newpath = joinPath(this.folderPath, newname) + await this.drive.rename(oldpath, newpath) + if (oldpath === this.pathname) { + beaker.browser.gotoUrl(joinPath(this.origin, newpath)) + } else { + this.load() + } + } + }) + if (item.stat.mount) { + items = items.concat([ + '-', + { + icon: 'fas fa-fw fa-external-link-alt', + label: `Open mount in new tab`, + click: () => { + beaker.browser.openUrl(`hyper://${item.stat.mount.key}/`, { + setActive: true, + sidebarPanels: ['editor-app'] + }) + } + }, + { + icon: 'fas fa-fw fa-link', + label: `Copy Mount URL`, + click () { + writeToClipboard(`hyper://${item.stat.mount.key}/`) + toast.create('Copied to your clipboard') + } + }, + { + icon: 'fas fa-fw fa-trash', + label: `Unmount`, + click: async () => { + if (confirm(`Are you sure you want to unmount ${item.name}?`)) { + let path = joinPath(this.folderPath, item.name) + await this.drive.unmount(path) + this.load() + } + } + } + ]) + } else { + items.push({ + icon: 'fa fa-fw fa-trash', + label: 'Delete', + click: async () => { + if (confirm(`Are you sure you want to delete ${item.name}?`)) { + let path = joinPath(this.folderPath, item.name) + if (item.stat.isDirectory()) { + await this.drive.rmdir(path, {recursive: true}) + } else { + await this.drive.unlink(path) + } + this.load() + } + } + }) + } + + contextMenu.create({ + x: e.clientX, + y: e.clientY, + fontAwesomeCSSUrl: 'beaker://assets/font-awesome.css', + noBorders: true, + roomy: true, + items + }) + } + + onClickAdditionalActions (e) { + e.preventDefault() + e.stopPropagation() + + var rect = e.currentTarget.getClientRects()[0] + contextMenu.create({ + x: rect.left - 3, + y: rect.bottom + 1, + fontAwesomeCSSUrl: 'beaker://assets/font-awesome.css', + noBorders: true, + roomy: true, + withTriangle: true, + items: [ + { + icon: 'fas fa-fw fa-external-link-square-alt', + label: 'Mount', + click: () => this.onClickMount() + }, + { + icon: 'fas fa-fw fa-upload', + label: 'Import files', + click: () => this.onClickImportFiles() + }, + { + icon: 'fas fa-fw fa-download', + label: 'Export files', + click: () => this.onClickExportFiles() + } + ] + }) + } + + onClickUpdog (e) { + var upPath = this.folderPath.split('/').filter(Boolean).slice(0, -1).join('/') + this.url = joinPath(this.origin, upPath) + } + + onClickItem (e, item) { + if (item.stat.isFile()) { + // open the file + let url = joinPath(this.origin, this.folderPath, item.name) + emit(this, 'open', {bubbles: true, composed: true, detail: {url}}) + } else { + // navigate in-UI to the folder + this.url = joinPath(this.origin, this.folderPath, item.name) + } + } + + onContextmenuCurrentFolder (e) { + e.preventDefault() + e.stopPropagation() + + var url = joinPath(this.origin, this.currentFolder.path) + var items = [] + items.push({ + icon: 'fas fa-fw fa-arrow-right', + label: `Go to this folder`, + click: () => { + beaker.browser.gotoUrl(url) + } + }) + items.push({ + icon: 'fas fa-fw fa-external-link-alt', + label: `Open in new tab`, + click: () => { + beaker.browser.openUrl(url, { + setActive: true, + sidebarPanels: ['editor-app'] + }) + } + }) + items.push({ + icon: 'fas fa-fw fa-link', + label: `Copy URL`, + click () { + writeToClipboard(url) + toast.create('Copied to your clipboard') + } + }) + if (this.currentFolder.mount) { + items = items.concat([ + '-', + { + icon: 'fas fa-fw fa-external-link-alt', + label: `Open mount in new tab`, + click: () => { + beaker.browser.openUrl(`hyper://${this.currentFolder.mount.key}/`, { + setActive: true, + sidebarPanels: ['editor-app'] + }) + } + }, + { + icon: 'fas fa-fw fa-link', + label: `Copy Mount URL`, + click () { + writeToClipboard(`hyper://${this.currentFolder.mount.key}/`) + toast.create('Copied to your clipboard') + } + } + ]) + } + + contextMenu.create({ + x: e.clientX, + y: e.clientY, + fontAwesomeCSSUrl: 'beaker://assets/font-awesome.css', + noBorders: true, + roomy: true, + items + }) + } + + async onClickNewFolder (e) { + if (this.readOnly) return + var name = prompt('Enter the new folder name') + if (name) { + let path = joinPath(this.folderPath, name) + await this.drive.mkdir(path) + this.load() + } + } + + async onClickNewFile (e) { + if (this.readOnly) return + var name = prompt('Enter the new file name') + if (name) { + let path = joinPath(this.folderPath, name) + await this.drive.writeFile(path, '') + this.load() + } + } + + async onClickImportFiles (e) { + if (this.readOnly) return + + let browserInfo = await beaker.browser.getInfo() + var osCanImportFoldersAndFiles = browserInfo.platform === 'darwin' + + var files = await beaker.browser.showOpenDialog({ + title: 'Import files', + buttonLabel: 'Import', + properties: ['openFile', osCanImportFoldersAndFiles ? 'openDirectory' : false, 'multiSelections', 'createDirectory'].filter(Boolean) + }) + if (files) { + for (let src of files) { + await beaker.hyperdrive.importFromFilesystem({ + src, + dst: joinPath(this.origin, this.folderPath), + ignore: ['index.json'], + inplaceImport: false + }) + } + this.load() + } + } + + async onClickExportFiles (e) { + beaker.browser.downloadURL(`${this.origin}?download_as=zip`) + } + + async onClickMount (e) { + if (this.readOnly) return + + var url = await beaker.shell.selectDriveDialog() + if (!url) return + var name = await prompt('Enter the mount name') + if (!name) return + await this.drive.mount(name, url) + this.load() + } +} + +customElements.define('files-explorer', FilesExplorer) diff --git a/app/userland/app-stdlib/js/com/history-autocomplete.js b/app/userland/app-stdlib/js/com/history-autocomplete.js new file mode 100644 index 0000000000..278c239626 --- /dev/null +++ b/app/userland/app-stdlib/js/com/history-autocomplete.js @@ -0,0 +1,175 @@ +import {LitElement, html} from '../../vendor/lit-element/lit-element.js' +import {repeat} from '../../vendor/lit-element/lit-html/directives/repeat.js' +import {classMap} from '../../vendor/lit-element/lit-html/directives/class-map.js' +import {toDomain, highlightSearchResult} from '../strings.js' +import historyAutocompleteCSS from '../../css/com/history-autocomplete.css.js' + +export class HistoryAutocomplete extends LitElement { + static get properties () { + return { + fontawesomeSrc: {type: String, attribute: 'fontawesome-src'}, + placeholder: {type: String}, + isFocused: {type: Boolean}, + query: {type: String}, + results: {type: Array}, + highlighted: {type: Number}, + includeVerbatim: {type: Boolean, attribute: 'include-verbatim'} + } + } + + constructor () { + super() + this.fontawesomeSrc = '' + this.placeholder = '' + this.isFocused = false + this.query = '' + this.results = null + this.highlighted = 0 + this.includeVerbatim = false + + this.$onClickDocument = this.onClickDocument.bind(this) + } + + async runQuery () { + var queryAtTimeOfRun = this.query + var res = this.query ? await beaker.history.search(this.query) : [] + console.log(res) + + if (queryAtTimeOfRun !== this.query) { + // user changed query while we were running, discard + console.log('Discarding results from outdated query') + return + } + + if (this.includeVerbatim) { + res = [{url: this.query, title: ''}].concat(res) + } + + this.highlighted = 0 + this.results = res + } + + get value () { + return this.query + } + + // rendering + // = + + render () { + return html` +
    + + ${this.renderResults()} +
    + ` + } + + renderResults () { + if (!this.results || !this.isFocused || this.results.length === 0) { + return '' + } + return html` +
    + ${repeat(this.results, (res, i) => this.renderResult(res, i))} +
    + ` + } + + renderResult (res, i) { + const cls = classMap({ + 'autocomplete-result': true, + 'search-result': true, + active: i === this.highlighted + }) + return html` + + + ${res.title} + ${res.url} + + ` + } + + // events + // = + + select (url, title) { + this.shadowRoot.querySelector('input').value = this.query = url + this.unfocus() + this.dispatchEvent(new CustomEvent('selection-changed', {detail: {title}})) + } + + unfocus () { + this.isFocused = false + + var input = this.shadowRoot.querySelector('input') + if (input.matches(':focus')) { + input.blur() + } + + document.removeEventListener('click', this.$onClickDocument) + } + + onClickResult (e) { + e.preventDefault() + this.select(e.currentTarget.getAttribute('href'), e.currentTarget.getAttribute('title')) + } + + onKeydownInput (e) { + if (e.key === 'Enter') { + e.preventDefault() + e.stopPropagation() + + let res = this.results[this.highlighted] + if (res) { + this.select(res.url, res.title) + } + return + } + if (e.key === 'Escape') { + return this.unfocus() + } + if (e.key === 'ArrowUp') { + e.preventDefault() + this.highlighted = Math.max(this.highlighted - 1, 0) + } + if (e.key === 'ArrowDown') { + e.preventDefault() + this.highlighted = Math.min(this.highlighted + 1, this.results.length) + } + } + + onKeyupInput (e) { + if (this.query !== e.currentTarget.value) { + this.query = e.currentTarget.value + this.runQuery() + } + } + + onFocusInput (e) { + this.isFocused = true + document.addEventListener('click', this.$onClickDocument) + } + + onClickDocument (e) { + // is the click inside us? + for (let el of e.path) { + if (el === this) return + } + // no, unfocus + this.unfocus() + } +} +HistoryAutocomplete.styles = historyAutocompleteCSS + +customElements.define('beaker-history-autocomplete', HistoryAutocomplete) \ No newline at end of file diff --git a/app/userland/app-stdlib/js/com/hover-menu.js b/app/userland/app-stdlib/js/com/hover-menu.js new file mode 100644 index 0000000000..c0687e8e7c --- /dev/null +++ b/app/userland/app-stdlib/js/com/hover-menu.js @@ -0,0 +1,104 @@ +import { LitElement, html } from '../../vendor/lit-element/lit-element.js' +import { repeat } from '../../vendor/lit-element/lit-html/directives/repeat.js' +import { unsafeHTML } from '../../vendor/lit-element/lit-html/directives/unsafe-html.js' +import { emit } from '../dom.js' +import hoverMenuCSS from '../../css/com/hover-menu.css.js' + +// NOTE +// We use the globalOpenCounter to let multiple side-by-side hover-menus share "open" state +// This is to create a menubar behavior. +// +// Each time a hover-menu is opened, the counter increments +// 500ms after a hover-menu closes, the counter decrements +// If the counter > 0 on hover, we open the menu +// (only relevant when require-click attr is set) +// -prf +var globalOpenCounter = 0 + +class HoverMenu extends LitElement { + static get properties () { + return { + icon: {type: 'String'}, + current: {type: 'String'}, + options: {type: 'Object'}, + isOpen: {type: Boolean} + } + } + + constructor () { + super() + this.icon = '' + this.current = '' + this.options = {} + this.isOpen = false + } + + // rendering + // = + + render () { + var items = Array.isArray(this.options) ? this.options : Object.entries(this.options).map(([id, label]) => ({id, label})) + + const item = ({id, label, divider, disabled, heading}) => { + if (heading) return html`
    ${heading}
    ` + if (divider) return html`
    ` + if (disabled) return html`${label}` + return html` + this.onClickItem(e, id)}> + ${label} + + ` + } + return html` + + + ` + } + + // events + // = + + onClickItem (e, id) { + e.preventDefault() + e.stopPropagation() + emit(this, 'change', {bubbles: true, detail: {id}}) + } + + onMouseOver (e) { + if (!this.hasAttribute('require-click')) { + this.isOpen = true + globalOpenCounter++ + } else if (!this.isOpen && globalOpenCounter > 0) { + this.isOpen = true + globalOpenCounter++ + } + } + + onClick (e) { + if (!this.isOpen) { + this.isOpen = true + globalOpenCounter++ + } + } + + onMouseLeave (e) { + if (this.isOpen) { + setTimeout(() => { globalOpenCounter-- }, 500) + this.isOpen = false + } + } +} +HoverMenu.styles = hoverMenuCSS +customElements.define('hover-menu', HoverMenu) \ No newline at end of file diff --git a/app/userland/app-stdlib/js/com/hoverable.js b/app/userland/app-stdlib/js/com/hoverable.js new file mode 100644 index 0000000000..f2ada9958f --- /dev/null +++ b/app/userland/app-stdlib/js/com/hoverable.js @@ -0,0 +1,50 @@ +import {LitElement, html} from '../../vendor/lit-element/lit-element.js' + +/* +Usage: + + + + + +*/ + +export class Hoverable extends LitElement { + static get properties () { + return { + isHovered: {type: Boolean} + } + } + + constructor () { + super() + this.isHovered = false + } + + render () { + if (this.isHovered) { + return html`` + } + return html`` + } + + onMouseenter () { + this.isHovered = true + + // HACK + // sometimes, if the mouse cursor leaves too quickly, 'mouseleave' doesn't get fired + // after a few ms, double check that it's still hovered + // -prf + setTimeout(() => { + if (!this.querySelector(':hover')) { + this.isHovered = false + } + }, 50) + } + + onMouseleave () { + this.isHovered = false + } +} + +customElements.define('beaker-hoverable', Hoverable) \ No newline at end of file diff --git a/app/userland/app-stdlib/js/com/img-fallbacks.js b/app/userland/app-stdlib/js/com/img-fallbacks.js new file mode 100644 index 0000000000..5033d5e367 --- /dev/null +++ b/app/userland/app-stdlib/js/com/img-fallbacks.js @@ -0,0 +1,39 @@ +import {LitElement, html} from '../../vendor/lit-element/lit-element.js' + +/* +Usage: + + + + + + +*/ + +export class ImgFallbacks extends LitElement { + static get properties () { + return { + currentImage: {type: Number} + } + } + + constructor () { + super() + this.currentImage = 1 + } + + render () { + return html`` + } + + onSlotChange (e) { + var img = this.shadowRoot.querySelector('slot').assignedElements()[0] + if (img) img.addEventListener('error', this.onError.bind(this)) + } + + onError (e) { + this.currentImage = this.currentImage + 1 + } +} + +customElements.define('beaker-img-fallbacks', ImgFallbacks) diff --git a/app/userland/app-stdlib/js/com/new-drive-dropdown.js b/app/userland/app-stdlib/js/com/new-drive-dropdown.js new file mode 100644 index 0000000000..16a7546932 --- /dev/null +++ b/app/userland/app-stdlib/js/com/new-drive-dropdown.js @@ -0,0 +1,135 @@ +import { html } from '../../vendor/lit-element/lit-element.js' +import { HELP } from '../const.js' +import * as contextMenu from './context-menu.js' +import * as toast from './toast.js' + +export async function create ({x, y}) { + return contextMenu.create({ + x, + y, + render: () => { + return html` + + + + ` + } + }) +} + +async function onCreateDrive (type) { + contextMenu.destroy() + var drive = await beaker.hyperdrive.createDrive({type}) + window.location = drive.url +} + +async function onCreateDriveFromFolder () { + contextMenu.destroy() + var folder = await beaker.browser.showOpenDialog({ + title: 'Select folder', + buttonLabel: 'Use folder', + properties: ['openDirectory'] + }) + if (!folder || !folder.length) return + + var drive = await beaker.hyperdrive.createDrive({ + title: folder[0].split('/').pop(), + prompt: false + }) + toast.create('Importing...') + await beaker.hyperdrive.importFromFilesystem({src: folder[0], dst: drive.url}) + window.location = drive.url +} \ No newline at end of file diff --git a/app/userland/app-stdlib/js/com/popups/base.js b/app/userland/app-stdlib/js/com/popups/base.js new file mode 100644 index 0000000000..4c5adf3f19 --- /dev/null +++ b/app/userland/app-stdlib/js/com/popups/base.js @@ -0,0 +1,106 @@ +import {LitElement, html} from '../../../vendor/lit-element/lit-element.js' +import popupsCSS from '../../../css/com/popups.css.js' + +// exported api +// = + +export class BasePopup extends LitElement { + constructor () { + super() + + const onGlobalKeyUp = e => { + // listen for the escape key + if (e.keyCode === 27) { + this.onReject() + } + } + document.addEventListener('keyup', onGlobalKeyUp) + + // cleanup function called on cancel + this.cleanup = () => { + document.removeEventListener('keyup', onGlobalKeyUp) + } + } + + get shouldCloseOnOuterClick () { + return true + } + + // management + // + + static async coreCreate (parentEl, Class, ...args) { + var popupEl = new Class(...args) + parentEl.appendChild(popupEl) + + const cleanup = () => { + popupEl.cleanup() + popupEl.remove() + } + + // return a promise that resolves with resolve/reject events + return new Promise((resolve, reject) => { + popupEl.addEventListener('resolve', e => { + resolve(e.detail) + cleanup() + }) + + popupEl.addEventListener('reject', e => { + reject() + cleanup() + }) + }) + } + + static async create (Class, ...args) { + return BasePopup.coreCreate(document.body, Class, ...args) + } + + static destroy (tagName) { + var popup = document.querySelector(tagName) + if (popup) popup.onReject() + } + + // rendering + // = + + render () { + return html` + + ` + } + + renderTitle () { + // should be overridden by subclasses + } + + renderBody () { + // should be overridden by subclasses + } + + // events + // = + + onClickWrapper (e) { + if (e.target.classList.contains('popup-wrapper') && this.shouldCloseOnOuterClick) { + this.onReject() + } + } + + onReject (e) { + if (e) e.preventDefault() + this.dispatchEvent(new CustomEvent('reject')) + } +} + +BasePopup.styles = [popupsCSS] \ No newline at end of file diff --git a/app/userland/app-stdlib/js/com/popups/edit-cover-photo.js b/app/userland/app-stdlib/js/com/popups/edit-cover-photo.js new file mode 100644 index 0000000000..a135124f14 --- /dev/null +++ b/app/userland/app-stdlib/js/com/popups/edit-cover-photo.js @@ -0,0 +1,151 @@ +import { html, css } from '../../../vendor/lit-element/lit-element.js' +import { BasePopup } from './base.js' +import popupsCSS from '../../../css/com/popups.css.js' + +const CANVAS_WIDTH = 600 +const CANVAS_HEIGHT = 200 + +// exported api +// = + +export class BeakerEditCoverPhoto extends BasePopup { + static get properties () { + return { + currentImgUrl: {type: String} + } + } + + constructor (siteUrl, existingCoverPath) { + super() + this.siteUrl = siteUrl + this.loadedImg = null + this.currentImgUrl = '' + if (existingCoverPath) { + this.currentImgUrl = `${siteUrl}${existingCoverPath}` + } + } + + // management + // + + static async create (siteUrl, existingCoverPath) { + return BasePopup.create(BeakerEditCoverPhoto, siteUrl, existingCoverPath) + } + + static async runFlow (profiles) { + var profile = await profiles.me() + var drive = beaker.hyperdrive.drive(profile.url) + + // find the existing cover + var existingCoverPath = null + const test = async (path) => { + if (existingCoverPath) return + var res = await drive.stat(path).catch(e => undefined) + if (res) existingCoverPath = path + } + await test('/cover.jpg') + await test('/cover.jpeg') + await test('/cover.png') + + // run the modal + var img = await BeakerEditCoverPhoto.create(profile.url, existingCoverPath) + if (!img) return + + // replace any existing cover + await drive.unlink('/cover.jpg').catch(e => undefined) + await drive.unlink('/cover.jpeg').catch(e => undefined) + await drive.unlink('/cover.png').catch(e => undefined) + await drive.writeFile(`/cover.${img.ext}`, img.base64buf, 'base64') + } + + static destroy () { + return BasePopup.destroy('beaker-edit-cover-photo') + } + + // rendering + // = + + renderTitle () { + return `Update your cover photo` + } + + renderBody () { + return html` +
    +
    + +
    + + +
    +
    + +
    + + +
    +
    + ` + } + + // events + // = + + async onClickThumb (e) { + e.preventDefault() + this.shadowRoot.querySelector('input[type="file"]').click() + } + + onChooseFile (e) { + var file = e.currentTarget.files[0] + if (!file) return + var fr = new FileReader() + fr.onload = () => { + var ext = file.name.split('.').pop() + this.currentImgUrl = fr.result + var base64buf = fr.result.split(',').pop() + this.loadedImg = {ext, base64buf} + } + fr.readAsDataURL(file) + } + + onSubmit (e) { + e.preventDefault() + e.stopPropagation() + this.dispatchEvent(new CustomEvent('resolve', {detail: this.loadedImg})) + } +} +BeakerEditCoverPhoto.styles = [popupsCSS, css` +img { + display: block; + width: 600px; + height: 200px; + cursor: pointer; + margin-bottom: 10px; + object-fit: cover; +} + +img:hover { + opacity: 0.9; +} + +.controls { + display: flex; + flex-direction: column; + align-items: center; +} + +.popup-inner { + width: 630px; +} + +.popup-inner .actions { + justify-content: space-between; +} + +input[type="file"] { + display: none; +} +`] + +customElements.define('beaker-edit-cover-photo', BeakerEditCoverPhoto) \ No newline at end of file diff --git a/app/userland/app-stdlib/js/com/popups/edit-profile.js b/app/userland/app-stdlib/js/com/popups/edit-profile.js new file mode 100644 index 0000000000..fd439de6af --- /dev/null +++ b/app/userland/app-stdlib/js/com/popups/edit-profile.js @@ -0,0 +1,108 @@ +import { html, css } from '../../../vendor/lit-element/lit-element.js' +import { BasePopup } from './base.js' +import popupsCSS from '../../../css/com/popups.css.js' + +// exported api +// = + +export class BeakerEditProfile extends BasePopup { + constructor (profile) { + super() + this.profile = profile + this.isCreate = !profile.title && !profile.description + } + + // management + // + + static async create (profile) { + return BasePopup.create(BeakerEditProfile, profile) + } + + static async runFlow (profiles) { + var profile = await profiles.me() + var newValues = await BeakerEditProfile.create(profile) + await beaker.hyperdrive.drive(profile.url).configure(newValues) + return profiles.me() + } + + static destroy () { + return BasePopup.destroy('beaker-edit-profile') + } + + // rendering + // = + + renderTitle () { + return `${this.isCreate ? 'Create' : 'Edit'} your profile` + } + + renderBody () { + return html` +
    +
    + + + + + + + +
    + +
    + + +
    +
    + ` + } + + // events + // = + + onSubmit (e) { + e.preventDefault() + e.stopPropagation() + this.dispatchEvent(new CustomEvent('resolve', { + detail: { + title: e.target.title.value, + description: e.target.description.value + } + })) + } +} +BeakerEditProfile.styles = [popupsCSS, css` +img { + display: block; + margin: 10px auto; + border-radius: 50%; + height: 130px; + width: 130px; + object-fit: cover; +} + +.controls { + max-width: 460px; + margin: 20px auto 40px; +} + +.popup-inner { + width: 560px; +} + +.popup-inner textarea, +.popup-inner input { + margin-bottom: 20px; +} + +.popup-inner .actions { + justify-content: space-between; +} + +.hidden { + visibility: hidden; +} +`] + +customElements.define('beaker-edit-profile', BeakerEditProfile) \ No newline at end of file diff --git a/app/userland/app-stdlib/js/com/popups/edit-thumb.js b/app/userland/app-stdlib/js/com/popups/edit-thumb.js new file mode 100644 index 0000000000..eaa728c3d0 --- /dev/null +++ b/app/userland/app-stdlib/js/com/popups/edit-thumb.js @@ -0,0 +1,171 @@ +import { html, css } from '../../../vendor/lit-element/lit-element.js' +import { BasePopup } from './base.js' +import popupsCSS from '../../../css/com/popups.css.js' + +const CANVAS_SIZE = 125 + +// exported api +// = + +export class BeakerEditThumb extends BasePopup { + constructor (siteUrl, existingThumbPath) { + super() + this.siteUrl = siteUrl + this.loadedImg = null + if (existingThumbPath) { + this.loadImg(`${siteUrl}${existingThumbPath}`) + } + } + + // management + // + + static async create (siteUrl, existingThumbPath) { + return BasePopup.create(BeakerEditThumb, siteUrl, existingThumbPath) + } + + static async runFlow (profiles) { + var profile = await profiles.me() + var drive = beaker.hyperdrive.drive(profile.url) + + // find the existing thumb + var existingThumbPath = null + const test = async (path) => { + if (existingThumbPath) return + var res = await drive.stat(path).catch(e => undefined) + if (res) existingThumbPath = path + } + await test('/thumb.jpg') + await test('/thumb.jpeg') + await test('/thumb.png') + + // run the modal + var img = await BeakerEditThumb.create(profile.url, existingThumbPath) + if (!img) return + + // replace any existing thumb + await drive.unlink('/thumb.jpg').catch(e => undefined) + await drive.unlink('/thumb.jpeg').catch(e => undefined) + await drive.unlink('/thumb.png').catch(e => undefined) + await drive.writeFile(`/thumb.${img.ext}`, img.base64buf, 'base64') + } + + static destroy () { + return BasePopup.destroy('beaker-edit-thumb') + } + + // rendering + // = + + renderTitle () { + return `Update your profile photo` + } + + renderBody () { + return html` +
    +
    + +
    + + +
    +
    + +
    + + +
    +
    + ` + } + + // canvas handling + // = + + loadImg (url) { + this.zoom = 1 + this.img = document.createElement('img') + this.img.src = url + this.img.onload = () => { + var smallest = (this.img.width < this.img.height) ? this.img.width : this.img.height + this.zoom = CANVAS_SIZE / smallest + this.updateCanvas() + } + } + + updateCanvas () { + var canvas = this.shadowRoot.getElementById('thumb-canvas') + if (canvas) { + var ctx = canvas.getContext('2d') + ctx.globalCompositeOperation = 'source-over' + ctx.fillStyle = '#fff' + ctx.fillRect(0, 0, CANVAS_SIZE, CANVAS_SIZE) + ctx.save() + ctx.scale(this.zoom, this.zoom) + ctx.drawImage(this.img, 0, 0, this.img.width, this.img.height) + ctx.restore() + } + } + + // events + // = + + async onClickThumb (e) { + e.preventDefault() + this.shadowRoot.querySelector('input[type="file"]').click() + } + + onChooseFile (e) { + var file = e.currentTarget.files[0] + if (!file) return + var fr = new FileReader() + fr.onload = () => { + var ext = file.name.split('.').pop() + this.loadImg(fr.result) + var base64buf = fr.result.split(',').pop() + this.loadedImg = {ext, base64buf} + } + fr.readAsDataURL(file) + } + + onSubmit (e) { + e.preventDefault() + e.stopPropagation() + this.dispatchEvent(new CustomEvent('resolve', {detail: this.loadedImg})) + } +} +BeakerEditThumb.styles = [popupsCSS, css` +canvas { + display: block; + margin: 0 30px 0 10px; + width: 125px; + height: 125px; + border-radius: 50%; + cursor: pointer; +} + +canvas:hover { + opacity: 0.5; +} + +.controls { + display: flex; + margin: 20px 20px 30px; + align-items: center; +} + +.popup-inner { + width: 360px; +} + +.popup-inner .actions { + justify-content: space-between; +} + +input[type="file"] { + display: none; +} +`] + +customElements.define('beaker-edit-thumb', BeakerEditThumb) \ No newline at end of file diff --git a/app/userland/app-stdlib/js/com/table.js b/app/userland/app-stdlib/js/com/table.js new file mode 100644 index 0000000000..8029384aa0 --- /dev/null +++ b/app/userland/app-stdlib/js/com/table.js @@ -0,0 +1,191 @@ +import {LitElement, html} from '../../vendor/lit-element/lit-element.js' +import {classMap} from '../../vendor/lit-element/lit-html/directives/class-map.js' +import {styleMap} from '../../vendor/lit-element/lit-html/directives/style-map.js' +import {repeat} from '../../vendor/lit-element/lit-html/directives/repeat.js' +import tableCSS from '../../css/com/table.css.js' + +/** + * @typedef {Object} ColumnDef + * @property {string} id + * @property {string=} label + * @property {number=} flex + * @property {number=} width + * @property {string=} renderer + */ + +export class Table extends LitElement { + static get properties () { + return { + rows: {type: Array} + } + } + + /** + * @type ColumnDef[] + */ + get columns () { + // this should be overridden by subclasses + return [ + {id: 'example', label: 'Example', flex: 1}, + {id: 'column2', label: 'Column2', width: 150} + ] + } + + get hasHeadingLabels () { + return !!this.columns.find(col => !!col.label) + } + + getRowKey (row) { + // this can be overridden by subclasses + return row + } + + getRowHref (row) { + // this can be overridden by subclasses + // if a string is returned, the row will become a link + return false + } + + isRowSelected (row) { + // this can be overridden by subclasses + return false + } + + sort () { + // this can be overridden by subclasses + } + + constructor (opts = {}) { + super() + this.rows = [] + this.sortColumn = this.columns[0] ? this.columns[0].id : '' + this.sortDirection = 'asc' + + if (opts.fontAwesomeCSSUrl) { + this.fontAwesomeCSSUrl = opts.fontAwesomeCSSUrl + } + } + + // rendering + // = + + render () { + return html` + + ${this.hasHeadingLabels + ? html` +
    + ${repeat(this.columns, col => this.renderHeadingColumn(col))} +
    + ` : ''} +
    + ${repeat(this.rows, row => this.getRowKey(row), row => this.renderRow(row))} + ${this.rows.length === 0 ? this.renderEmpty() : ''} +
    + ` + } + + getColumnClasses (column) { + return classMap({ + col: true, + [column.id]: true, + stretch: column.stretch + }) + } + + getColumnStyles (column) { + const styles = {} + if (column.width) { + styles.width = `${column.width}px` + } + if (column.flex) { + styles.flex = column.flex + } + return styleMap(styles) + } + + renderHeadingColumn (column) { + const cls = this.getColumnClasses(column) + const styles = this.getColumnStyles(column) + return html` +
    + this.onClickHeadingColumn(e, column)}>${column.label} + ${this.renderSortIcon(column)} +
    + ` + } + + renderRow (row) { + const cls = classMap({ + row: true, + selected: this.isRowSelected(row) + }) + const columns = repeat(this.columns, col => this.renderRowColumn(col, row)) + const href = this.getRowHref(row) + if (href) { + return html` this.onClickRow(e, row)} @contextmenu=${e => this.onContextmenuRow(e, row)}>${columns}` + } + return html` +
    this.onClickRow(e, row)} + @dblclick=${e => this.onDblclickRow(e, row)} + @contextmenu=${e => this.onContextmenuRow(e, row)} + >${columns}
    ` + } + + renderRowColumn (column, row) { + const cls = this.getColumnClasses(column) + const styles = this.getColumnStyles(column) + var content = (column.renderer) ? this[column.renderer](row) : row[column.id] + return html` +
    + ${content} +
    + ` + } + + renderSortIcon (column) { + if (column.id !== this.sortColumn) { + return '' + } + return html` + + ` + } + + renderEmpty () { + // this can be overridden by subclasses + return '' + } + + // events + // = + + onClickHeadingColumn (e, column) { + if (this.sortColumn === column.id) { + this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc' + } else { + this.sortColumn = column.id + this.sortDirection = 'asc' + } + this.sort() + } + + onClickRows (e, row) { + // this can be overridden by subclasses + } + + onClickRow (e, row) { + // this can be overridden by subclasses + } + + onDblclickRow (e, row) { + // this can be overridden by subclasses + } + + onContextmenuRow (e, row) { + // this can be overridden by subclasses + } +} +Table.styles = [tableCSS] diff --git a/app/userland/app-stdlib/js/com/tabs-nav.js b/app/userland/app-stdlib/js/com/tabs-nav.js new file mode 100644 index 0000000000..b7ba377eb2 --- /dev/null +++ b/app/userland/app-stdlib/js/com/tabs-nav.js @@ -0,0 +1,45 @@ +import {LitElement, html} from '../../vendor/lit-element/lit-element.js' +import {classMap} from '../../vendor/lit-element/lit-html/directives/class-map.js' +import {repeat} from '../../vendor/lit-element/lit-html/directives/repeat.js' +import tabsCSS from '../../css/com/tabs-nav.css.js' + +export class TabsNav extends LitElement { + static get properties () { + return { + currentTab: {attribute: 'current-tab', reflect: true} + } + } + + get tabs () { + // this should be overridden by subclasses + return [ + {id: 'tab1', label: 'Tab 1'}, + {id: 'tab2', label: 'Tab 2'}, + {id: 'tab3', label: 'Tab 3'} + ] + } + + constructor () { + super() + this.currentTab = this.tabs[0].id + } + + render () { + return html`${repeat(this.tabs, tab => this.renderTab(tab))}` + } + + renderTab (tab) { + if (tab.spacer) return html`` + if (tab.type === 'html') return tab + + var {id, label, onClick} = tab + const cls = classMap({active: this.currentTab === id}) + return html` this.onClickTab(e, id)}>${label}` + } + + onClickTab (e, id) { + this.currentTab = id + this.dispatchEvent(new CustomEvent('change-tab', {detail: {tab: id}})) + } +} +TabsNav.styles = [tabsCSS] diff --git a/app/userland/app-stdlib/js/com/toast.js b/app/userland/app-stdlib/js/com/toast.js new file mode 100644 index 0000000000..9fd1b92242 --- /dev/null +++ b/app/userland/app-stdlib/js/com/toast.js @@ -0,0 +1,48 @@ +import {LitElement, html} from '../../vendor/lit-element/lit-element.js' +import toastCSS from '../../css/com/toast.css.js' + +// exported api +// = + +export function create (message, type = '', time = 5000, button = null) { + // destroy existing + destroy() + + // render toast + document.body.appendChild(new BeakerToast({message, type, button})) + setTimeout(destroy, time) +} + +export function destroy () { + var toast = document.querySelector('beaker-toast') + + if (toast) { + // fadeout before removing element + toast.shadowRoot.querySelector('.toast-wrapper').classList.add('hidden') + setTimeout(() => toast.remove(), 500) + } +} + +// internal +// = + +class BeakerToast extends LitElement { + constructor ({message, type, button}) { + super() + this.message = message + this.type = type + this.button = button + } + + render () { + const onButtonClick = this.button ? (e) => { destroy(); this.button.click(e) } : undefined + return html` +
    +

    ${this.message} ${this.button ? html`${this.button.label}` : ''}

    +
    + ` + } +} +BeakerToast.styles = toastCSS + +customElements.define('beaker-toast', BeakerToast) diff --git a/app/userland/app-stdlib/js/com/top-right-controls.js b/app/userland/app-stdlib/js/com/top-right-controls.js new file mode 100644 index 0000000000..04bfc06598 --- /dev/null +++ b/app/userland/app-stdlib/js/com/top-right-controls.js @@ -0,0 +1,169 @@ +import { LitElement, html, css } from '/vendor/beaker-app-stdlib/vendor/lit-element/lit-element.js' +import * as appMenu from '/vendor/beaker-app-stdlib/js/com/app-menu.js' +import * as contextMenu from '/vendor/beaker-app-stdlib/js/com/context-menu.js' +import * as toast from '/vendor/beaker-app-stdlib/js/com/toast.js' +import _debounce from '../../vendor/lodash.debounce.js' + +const WIKI_KEY = '9d9bc457f39c987cb775e638d1623d894860947509a4143d035305d4d468587b' + +const createContextMenu = (el, items) => contextMenu.create({ + x: el.getBoundingClientRect().right, + y: el.getBoundingClientRect().bottom, + right: true, + withTriangle: true, + noBorders: true, + style: 'padding: 4px 0; min-width: 200px; font-size: 14px; color: #000', + fontAwesomeCSSUrl: 'beaker://assets/font-awesome.css', + items +}) + +class TopRightControls extends LitElement { + static get properties () { + return { + user: {type: Object}, + cacheBuster: {type: Number} + } + } + + constructor () { + super() + this.user = null + this.cacheBuster = 0 + window.addEventListener('focus', _debounce(() => { + // load latest when we're opened, to make sure we stay in sync + this.cacheBuster = Date.now() + }, 1e3, {leading: true})) + } + + get userName () { + return this.user && this.user.title || 'Anonymous' + } + + get userUrl () { + return this.user ? this.user.url : '' + } + + get userImg () { + return this.user ? html`` : '' + } + + render () { + return html` + + ` + } + + onClickAppMenu (e) { + e.preventDefault() + e.stopPropagation() + appMenu.create({ + x: e.currentTarget.getBoundingClientRect().right, + y: e.currentTarget.getBoundingClientRect().bottom + }) + } + + onClickNewMenu (e) { + e.preventDefault() + e.stopPropagation() + + const goto = (url) => { window.location = url } + async function create (templateUrl, title, description, urlModifyFn) { + toast.create('Loading...', '', 10e3) + setTimeout(() => toast.create('Still loading...', '', 10e3), 10e3) + setTimeout(() => toast.create('Still loading, must be having trouble downloading the template...', '', 10e3), 20e3) + setTimeout(() => toast.create('Okay wow...', '', 10e3), 30e3) + setTimeout(() => toast.create('Still loading, is your Internet connected?...', '', 10e3), 40e3) + setTimeout(() => toast.create('Lets give it 10 more seconds...', '', 10e3), 50e3) + try { + var newSite = await beaker.hyperdrive.forkDrive(templateUrl, {title, description, prompt: false}) + window.location = urlModifyFn ? urlModifyFn(newSite.url) : newSite.url + } catch (e) { + console.error(e) + if (e.name === 'TimeoutError') { + toast.create('Beaker was unable to download the template for your new site. Please check your Internet connection and try again!', 'error') + } else { + toast.create('Unexpected error: ' + e.message, 'error') + } + } + } + const items = [ + html`
    Projects
    `, + {icon: false, label: 'Blank website', click: () => goto('beaker://library/?view=new-website')}, + '-', + html`
    Templates
    `, + {icon: false, label: 'Wiki', click: () => create(WIKI_KEY, 'Untitled Wiki', ' ', url => url + '?edit')} + ] + createContextMenu(e.currentTarget, items) + } + + onClickProfileMenu (e) { + e.preventDefault() + e.stopPropagation() + + const goto = (url) => { window.location = url } + const items = [ + html`
    Identity
    `, + {icon: false, label: 'Your personal site', click: () => goto(this.userUrl)}, + '-', + html`
    Apps
    `, + {icon: false, label: 'Beaker.Social profile', click: () => goto(`intent:unwalled.garden/view-feed?url=${encodeURIComponent(this.userUrl)}`)}, + '-', + html`
    Personal data
    `, + {icon: false, label: 'Your address book', click: () => goto('beaker://library/?view=addressbook')}, + {icon: false, label: 'Your bookmarks', click: () => goto('beaker://library/?view=bookmarks')}, + {icon: false, label: 'Your websites', click: () => goto('beaker://library/?view=websites')}, + '-', + {icon: false, label: 'Settings', click: () => goto('beaker://settings/')} + ] + createContextMenu(e.currentTarget, items) + } +} + +TopRightControls.styles = css` +div { + display: flex; + align-items: center; + position: fixed; + top: 8px; + right: 10px; + font-size: 16px; +} + +a { + color: gray; + padding: 10px; + cursor: pointer; +} + +a:hover { + color: #555; +} + +.profile { + display: inline-flex; + align-items: center; + font-size: 13px; + border-radius: 2px; + padding: 3px 6px; + margin-left: 5px; +} + +.profile:hover { + color: #333; + background: #eee; +} + +.profile img { + width: 32px; + height: 32px; + object-fit: cover; + border-radius: 50%; + margin-right: 5px; +} +` + +customElements.define('beaker-top-right-controls', TopRightControls) diff --git a/app/userland/app-stdlib/js/const.js b/app/userland/app-stdlib/js/const.js new file mode 100644 index 0000000000..5b3e599d70 --- /dev/null +++ b/app/userland/app-stdlib/js/const.js @@ -0,0 +1,9 @@ +import { html } from 'beaker://app-stdlib/vendor/lit-element/lit-element.js' + +export const HELP = { + files: () => html`

    Files drives are shareable folders.

    `, + websites: () => html`

    Websites contain web pages and applications.

    `, + groups: () => html`

    User Groups host other users who can share content together.

    `, + modules: () => html`

    Modules contain code. They can be imported by other drives to provide reusable components.

    `, + frontends: () => html`

    Frontends are swappable user-interfaces for drives.

    ` +} \ No newline at end of file diff --git a/app/userland/app-stdlib/js/dom.js b/app/userland/app-stdlib/js/dom.js new file mode 100644 index 0000000000..bf1b37b4ae --- /dev/null +++ b/app/userland/app-stdlib/js/dom.js @@ -0,0 +1,49 @@ +export function findParent (node, test) { + if (typeof test === 'string') { + // classname default + var cls = test + test = el => el.classList && el.classList.contains(cls) + } + + while (node) { + if (test(node)) { + return node + } + node = node.parentNode + } +} + +export function on (el, event, fn, opts) { + el.addEventListener(event, fn, opts) +} + +export function once (el, event, fn, opts) { + opts = opts || {} + opts.once = true + el.addEventListener(event, fn, opts) +} + +export function emit (el, evt, opts = {}) { + opts.bubbles = ('bubbles' in opts) ? opts.bubbles : true + opts.composed = ('composed' in opts) ? opts.composed : true + el.dispatchEvent(new CustomEvent(evt, opts)) +} + +/*! + * Dynamically changing favicons with JavaScript + * Works in all A-grade browsers except Safari and Internet Explorer + * Demo: http://mathiasbynens.be/demo/dynamic-favicons + */ + +var _head = document.head || document.getElementsByTagName('head')[0] // https://stackoverflow.com/a/2995536 +export function changeFavicon (src) { + var link = document.createElement('link') + var oldLink = document.getElementById('dynamic-favicon') + link.id = 'dynamic-favicon' + link.rel = 'shortcut icon' + link.href = src + if (oldLink) { + _head.removeChild(oldLink) + } + _head.appendChild(link) +} \ No newline at end of file diff --git a/app/userland/app-stdlib/js/emoji.js b/app/userland/app-stdlib/js/emoji.js new file mode 100644 index 0000000000..c2be479a23 --- /dev/null +++ b/app/userland/app-stdlib/js/emoji.js @@ -0,0 +1,24 @@ +import { FULL_LIST } from '../data/emoji-list.js' +import * as skinTone from '../vendor/emoji-skin-tone/index.js' + +const EMOJI_VARIANT = `\uFE0F` // this codepoint forces emoji rendering rather than symbolic + +export function setSkinTone (emoji, tone) { + return skinTone.set(emoji, tone) +} + +export function render (emoji, tone = false) { + emoji = emoji.replace('\uFE0F', '').replace('\uFE0E', '') + return (tone === false ? emoji : skinTone.set(emoji, tone)) + EMOJI_VARIANT +} + +export function renderSafe (emoji, tone = false) { + // if (!isSupported(emoji)) return '' TODO needed? + return render(emoji, tone) +} + +export function isSupported (emoji) { + if (!emoji || typeof emoji !== 'string') return false + emoji = emoji.replace('\uFE0F', '').replace('\uFE0E', '') + return FULL_LIST.indexOf(skinTone.set(emoji, skinTone.NONE)) !== -1 +} \ No newline at end of file diff --git a/app/userland/app-stdlib/js/fs.js b/app/userland/app-stdlib/js/fs.js new file mode 100644 index 0000000000..ca53973e00 --- /dev/null +++ b/app/userland/app-stdlib/js/fs.js @@ -0,0 +1,182 @@ +import { isFilenameBinary } from './is-ext-binary.js' +import { urlToKey, joinPath, slugify } from './strings.js' + +// typedefs +// = + +/** + * @typedef {Object} FSQueryOpts + * @prop {string|string[]} path + * @prop {string} [type] + * @prop {string} [mount] + * @prop {Object} [meta] + * @prop {string} [sort] - 'name', 'ctime', 'mtime' + * @prop {boolean} [reverse] + * @prop {number} [limit] + * @prop {number} [offset] + * + * @typedef {Object} Stat + * @prop {number} mode + * @prop {number} size + * @prop {number} offset + * @prop {number} blocks + * @prop {Date} atime + * @prop {Date} mtime + * @prop {Date} ctime + * @prop {Object} [mount] + * @prop {string} [mount.key] + * @prop {string} linkname + * + * @typedef {Object} FSQueryResult + * @prop {string} type + * @prop {string} path + * @prop {string} url + * @prop {Stat} stat + * @prop {string} drive + * @prop {string} [mount] + * @prop {any} [content] + */ + +// exported +// = + +/** + * @param {Hyperdrive} fs + * @param {FSQueryOpts} query + * @returns {Promise} + */ +export async function queryRead (fs, query) { + var files = await fs.query(query) + for (let file of files) { + if (isFilenameBinary(file.path)) continue + file.content = await fs.readFile(file.path, 'utf8').catch(err => undefined) + if (file.path.endsWith('.json')) { + try { + file.content = JSON.parse(file.content) + } catch (e) { + // ignore + } + } + } + return files +} + +/** + * @param {Hyperdrive} fs + * @param {FSQueryOpts} query + * @returns {Promise} + */ +export async function queryHas (fs, query) { + var files = await fs.query(query) + return files.length > 0 +} + +/** + * @param {Hyperdrive} fs + * @param {string} path + */ +export async function ensureDir (fs, path) { + try { + let st = await fs.stat(path).catch(e => null) + if (!st) { + await fs.mkdir(path) + } else if (!st.isDirectory()) { + console.error('Warning! Filesystem expects a folder but an unexpected file exists at this location.', {path}) + } + } catch (e) { + console.error('Filesystem failed to make directory', {path, error: e}) + } +} + +/** + * @param {Hyperdrive} fs + * @param {string} path + */ +export async function ensureParentDir (fs, path) { + return ensureDir(fs, path.split('/').slice(0, -1).join('/')) +} + +/** + * @param {Hyperdrive} fs + * @param {string} path + * @param {string} url + * @return {Promise} + */ +export async function ensureMount (fs, path, url) { + try { + let st = await fs.stat(path).catch(e => null) + let key = urlToKey(url) + if (!st) { + // add mount + await fs.mount(path, key) + } else if (st.mount) { + if (st.mount.key !== key) { + // change mount + await fs.unmount(path) + await fs.mount(path, key) + } + } else { + console.error('Warning! Filesystem expects a mount but an unexpected file exists at this location.', {path}) + } + } catch (e) { + console.error('Filesystem failed to mount drive', {path, url, error: e}) + } +} + +/** + * @param {Hyperdrive} fs + * @param {string} path + * @return {Promise} + */ +export async function ensureUnmount (fs, path) { + try { + let st = await fs.stat(path).catch(e => null) + if (st && st.mount) { + // remove mount + await fs.unmount(path) + } + } catch (e) { + console.error('Filesystem failed to unmount drive', {path, error: e}) + } +} + +/** + * @param {string} pathSelector + * @param {string} url + * @param {Object} drive + * @return {Promise} + */ +export async function ensureUnmountByUrl (pathSelector, url, drive) { + try { + let mounts = await drive.query({ + path: pathSelector, + mount: url + }) + if (mounts[0]) { + // remove mount + await drive.unmount(mounts[0].path) + } else { + throw "Mount not found" + } + } catch (e) { + console.error('Filesystem failed to unmount drive', {pathSelector, url, error: e}) + } +} + +/** + * @param {string} containingPath + * @param {string} title + * @param {Object} fs + * @param {string} ext + * @returns {Promise} + */ +export async function getAvailableName (containingPath, title, fs, ext = '') { + var basename = slugify((title || '').trim() || 'untitled').toLowerCase() + for (let i = 1; i < 1e9; i++) { + let name = ((i === 1) ? basename : `${basename}-${i}`) + (ext ? `.${ext}` : '') + let st = await fs.stat(joinPath(containingPath, name), fs).catch(e => null) + if (!st) return name + } + // yikes if this happens + throw new Error('Unable to find an available name for ' + title) +} diff --git a/app/userland/app-stdlib/js/is-ext-binary.js b/app/userland/app-stdlib/js/is-ext-binary.js new file mode 100644 index 0000000000..8949b5a30e --- /dev/null +++ b/app/userland/app-stdlib/js/is-ext-binary.js @@ -0,0 +1,256 @@ +const BIN_EXTS = [ + '3dm', + '3ds', + '3g2', + '3gp', + '7z', + 'a', + 'aac', + 'adp', + 'ai', + 'aif', + 'aiff', + 'alz', + 'ape', + 'apk', + 'ar', + 'arj', + 'asf', + 'au', + 'avi', + 'bak', + 'baml', + 'bh', + 'bin', + 'bk', + 'bmp', + 'btif', + 'bz2', + 'bzip2', + 'cab', + 'caf', + 'cgm', + 'class', + 'cmx', + 'cpio', + 'cr2', + 'cur', + 'dat', + 'dcm', + 'deb', + 'dex', + 'djvu', + 'dll', + 'dmg', + 'dng', + 'doc', + 'docm', + 'docx', + 'dot', + 'dotm', + 'dra', + 'DS_Store', + 'dsk', + 'dts', + 'dtshd', + 'dvb', + 'dwg', + 'dxf', + 'ecelp4800', + 'ecelp7470', + 'ecelp9600', + 'egg', + 'eol', + 'eot', + 'epub', + 'exe', + 'f4v', + 'fbs', + 'fh', + 'fla', + 'flac', + 'fli', + 'flv', + 'fpx', + 'fst', + 'fvt', + 'g3', + 'gh', + 'gif', + 'graffle', + 'gz', + 'gzip', + 'h261', + 'h263', + 'h264', + 'icns', + 'ico', + 'ief', + 'img', + 'ipa', + 'iso', + 'jar', + 'jpeg', + 'jpg', + 'jpgv', + 'jpm', + 'jxr', + 'key', + 'ktx', + 'lha', + 'lib', + 'lvp', + 'lz', + 'lzh', + 'lzma', + 'lzo', + 'm3u', + 'm4a', + 'm4v', + 'mar', + 'mdi', + 'mht', + 'mid', + 'midi', + 'mj2', + 'mka', + 'mkv', + 'mmr', + 'mng', + 'mobi', + 'mov', + 'movie', + 'mp3', + 'mp4', + 'mp4a', + 'mpeg', + 'mpg', + 'mpga', + 'mxu', + 'nef', + 'npx', + 'numbers', + 'nupkg', + 'o', + 'oga', + 'ogg', + 'ogv', + 'otf', + 'pages', + 'pbm', + 'pcx', + 'pdb', + 'pdf', + 'pea', + 'pgm', + 'pic', + 'png', + 'pnm', + 'pot', + 'potm', + 'potx', + 'ppa', + 'ppam', + 'ppm', + 'pps', + 'ppsm', + 'ppsx', + 'ppt', + 'pptm', + 'pptx', + 'psd', + 'pya', + 'pyc', + 'pyo', + 'pyv', + 'qt', + 'rar', + 'ras', + 'raw', + 'resources', + 'rgb', + 'rip', + 'rlc', + 'rmf', + 'rmvb', + 'rtf', + 'rz', + 's3m', + 's7z', + 'scpt', + 'sgi', + 'shar', + 'sil', + 'sketch', + 'slk', + 'smv', + 'snk', + 'so', + 'stl', + 'suo', + 'sub', + 'swf', + 'tar', + 'tbz', + 'tbz2', + 'tga', + 'tgz', + 'thmx', + 'tif', + 'tiff', + 'tlz', + 'ttc', + 'ttf', + 'txz', + 'udf', + 'uvh', + 'uvi', + 'uvm', + 'uvp', + 'uvs', + 'uvu', + 'viv', + 'vob', + 'war', + 'wav', + 'wax', + 'wbmp', + 'wdp', + 'weba', + 'webm', + 'webp', + 'whl', + 'wim', + 'wm', + 'wma', + 'wmv', + 'wmx', + 'woff', + 'woff2', + 'wrm', + 'wvx', + 'xbm', + 'xif', + 'xla', + 'xlam', + 'xls', + 'xlsb', + 'xlsm', + 'xlsx', + 'xlt', + 'xltm', + 'xltx', + 'xm', + 'xmind', + 'xpi', + 'xpm', + 'xwd', + 'xz', + 'z', + 'zip', + 'zipx' +] + +export function isFilenameBinary (str = '') { + return BIN_EXTS.includes(str.split('.').pop()) +} \ No newline at end of file diff --git a/app/userland/app-stdlib/js/query-params.js b/app/userland/app-stdlib/js/query-params.js new file mode 100644 index 0000000000..c6112c0cd8 --- /dev/null +++ b/app/userland/app-stdlib/js/query-params.js @@ -0,0 +1,20 @@ +export function setParams (kv, clear = false, replaceState = false) { + var url = (new URL(window.location)) + if (clear) url.search = '' + for (var k in kv) { + if (kv[k]) { + url.searchParams.set(k, kv[k]) + } else { + url.searchParams.delete(k) + } + } + if (replaceState) { + window.history.replaceState({}, null, url) + } else { + window.history.pushState({}, null, url) + } +} + +export function getParam (k, fallback = '') { + return (new URL(window.location)).searchParams.get(k) || fallback +} \ No newline at end of file diff --git a/app/userland/app-stdlib/js/strings.js b/app/userland/app-stdlib/js/strings.js new file mode 100644 index 0000000000..1370f52328 --- /dev/null +++ b/app/userland/app-stdlib/js/strings.js @@ -0,0 +1,171 @@ +export const DRIVE_KEY_REGEX = /[0-9a-f]{64}/i + +export function urlToKey (str) { + try { + return DRIVE_KEY_REGEX.exec(str)[0] + } catch (e) { + return '' + } +} + +export function ucfirst (str) { + if (!str) str = '' + if (typeof str !== 'string') str = '' + str + return str.charAt(0).toUpperCase() + str.slice(1) +} + +export function pluralize (num, base, suffix = 's') { + if (num === 1) { return base } + return base + suffix +} + +export function shorten (str, n = 6) { + if (str.length > (n + 3)) { + return str.slice(0, n) + '...' + } + return str +} + +export function joinPath (...args) { + var str = args[0] + for (let v of args.slice(1)) { + v = v && typeof v === 'string' ? v : '' + let left = str.endsWith('/') + let right = v.startsWith('/') + if (left !== right) str += v + else if (left) str += v.slice(1) + else str += '/' + v + } + return str +} + +export function shortenAllKeys (str = '') { + return str.replace(/[0-9a-f]{64}/ig, (key) => `${key.slice(0, 4)}..${key.slice(-2)}`) +} + +export function toDomain (str) { + if (!str) return '' + try { + var urlParsed = new URL(str) + return urlParsed.hostname + } catch (e) { + // ignore, not a url + } + return str +} + +export function toNiceDomain (str, len=4) { + var domain = toDomain(str) + if (DRIVE_KEY_REGEX.test(domain)) { + domain = `${domain.slice(0, len)}..${domain.slice(-2)}` + } + return domain +} + +export function toNiceUrl (str) { + if (!str) return '' + try { + var urlParsed = new URL(str) + if (DRIVE_KEY_REGEX.test(urlParsed.hostname)) { + urlParsed.hostname = `${urlParsed.hostname.slice(0, 4)}..${urlParsed.hostname.slice(-2)}` + } + return urlParsed.toString() + } catch (e) { + // ignore, not a url + } + return str +} + +export function makeSafe (str = '') { + return str.replace(//g, '>').replace(/&/g, '&').replace(/"/g, '"') +} + +// search results are returned from beaker's search APIs with nonces wrapping the highlighted sections +// e.g. a search for "test" might return "the {500}test{/500} result" +// this enables us to safely escape the HTML, then replace the nonces with tags +export function highlightSearchResult (str = '', nonce = 0) { + var start = new RegExp(`\\{${nonce}\\}`, 'g') // eg {500} + var end = new RegExp(`\\{/${nonce}\\}`, 'g') // eg {/500} + return makeSafe(str).replace(start, '').replace(end, '') +} + +export function slugifyUrl (str = '') { + try { + let url = new URL(str) + str = url.protocol + url.hostname + url.pathname + url.search + url.hash + } catch (e) { + // ignore + } + return slugify(str) +} + +const reservedChars = /[ <>:"/\\|?*\x00-\x1F]/g +const endingDashes = /([-]+$)/g +export function slugify (str = '') { + return str.replace(reservedChars, '-').replace(endingDashes, '') +} + +export function normalizeUrl (str = '') { + try { + let url = new URL(str) + let res = url.protocol + '//' + url.hostname + if (url.port) res += ':' + url.port + res += url.pathname.replace(/(\/)$/, '') || '/' + if (url.search && url.search !== '?') res += url.search + if (url.hash && url.hash !== '#') res += url.hash + return res + } catch (e) { + return str + } +} + +export function changeURLScheme (url = '', scheme = '') { + try { + let urlp = new URL(url) + urlp.protocol = scheme + return urlp.toString() + } catch (e) { + return url + } +} + +export function toNiceDriveType (type = '') { + if (!type) return 'files drive' + if (type === 'webterm.sh/cmd-pkg') return 'webterm command' + return type +} + +export function getDriveTypeIcon (type = '') { + switch (type) { + case 'user': return 'fas fa-user' + case 'group': return 'fas fa-users' + case 'webterm.sh/cmd-pkg': return 'fas fa-terminal' + case 'module': return 'fas fa-cube' + case 'website': return 'fas fa-desktop' + default: return 'far fa-folder-open' + } +} + +/** + * Calculate a 32 bit FNV-1a hash + * Found here: https://gist.github.com/vaiorabbit/5657561 + * Ref.: http://isthe.com/chongo/tech/comp/fnv/ + * + * @param {string} str the input value + * @param {boolean} [asString=false] set to true to return the hash value as 8-digit hex string instead of an integer + * @param {number} [seed] optionally pass the hash of the previous chunk + * @returns {number | string} + */ +export function hashFnv32a (str, asString, seed) { + var i, l, hval = (seed === undefined) ? 0x811c9dc5 : seed + + for (i = 0, l = str.length; i < l; i++) { + hval ^= str.charCodeAt(i) + hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24) + } + if (asString) { + // Convert to 8 digit hex string + return ("0000000" + (hval >>> 0).toString(16)).substr(-8) + } + return hval >>> 0 +} \ No newline at end of file diff --git a/app/userland/app-stdlib/js/time.js b/app/userland/app-stdlib/js/time.js new file mode 100644 index 0000000000..c66cfb13fa --- /dev/null +++ b/app/userland/app-stdlib/js/time.js @@ -0,0 +1,54 @@ +import {pluralize} from './strings.js' + +const shortFormatter = new Intl.DateTimeFormat('en-US', { + month: 'short', + day: 'numeric' +}) +const longFormatter = new Intl.DateTimeFormat('en-US', { + month: 'short', + year: 'numeric', + day: 'numeric' +}) +const yearFormatter = new Intl.DateTimeFormat('en-US', {year: 'numeric'}) +const CURRENT_YEAR = yearFormatter.format(new Date()) + +export function shortDate (ts) { + ts = new Date(ts) + var year = yearFormatter.format(ts) + var formatter = (year === CURRENT_YEAR) ? shortFormatter : longFormatter + return formatter.format(ts) +} + +// simple timediff fn +// replace this with Intl.RelativeTimeFormat when it lands in Beaker +// https://stackoverflow.com/questions/6108819/javascript-timestamp-to-relative-time-eg-2-seconds-ago-one-week-ago-etc-best +const msPerMinute = 60 * 1000 +const msPerHour = msPerMinute * 60 +const msPerDay = msPerHour * 24 +const msPerMonth = msPerDay * 30 +const msPerYear = msPerDay * 365 +const now = Date.now() +export function timeDifference (ts, short = false, postfix = 'ago') { + ts = Number(new Date(ts)) + var elapsed = now - ts + if (elapsed < 1) elapsed = 1 // let's avoid 0 and negative values + if (elapsed < msPerMinute) { + let n = Math.round(elapsed/1000) + return `${n}${short ? 's' : pluralize(n, ' second')} ${postfix}` + } else if (elapsed < msPerHour) { + let n = Math.round(elapsed/msPerMinute) + return `${n}${short ? 'm' : pluralize(n, ' minute')} ${postfix}` + } else if (elapsed < msPerDay) { + let n = Math.round(elapsed/msPerHour) + return `${n}${short ? 'h' : pluralize(n, ' hour')} ${postfix}` + } else if (elapsed < msPerMonth) { + let n = Math.round(elapsed/msPerDay) + return `${n}${short ? 'd' : pluralize(n, ' day')} ${postfix}` + } else if (elapsed < msPerYear) { + let n = Math.round(elapsed/msPerMonth) + return `${n}${short ? 'mo' : pluralize(n, ' month')} ${postfix}` + } else { + let n = Math.round(elapsed/msPerYear) + return `${n}${short ? 'yr' : pluralize(n, ' year')} ${postfix}` + } +} \ No newline at end of file diff --git a/app/userland/app-stdlib/readme.md b/app/userland/app-stdlib/readme.md new file mode 100644 index 0000000000..e66d3784d5 --- /dev/null +++ b/app/userland/app-stdlib/readme.md @@ -0,0 +1,13 @@ +# Beaker Applications Standard Library + +A collection of JS and Web Components which are used across Beaker's default applications. + +## Scripts + +### Build *.css.js files + +To build the *.css.js files from the original css, run: + +``` +node scripts/generate-css-js.js +``` \ No newline at end of file diff --git a/app/userland/app-stdlib/scripts/css-watcher.js b/app/userland/app-stdlib/scripts/css-watcher.js new file mode 100644 index 0000000000..ea621a8a6c --- /dev/null +++ b/app/userland/app-stdlib/scripts/css-watcher.js @@ -0,0 +1,27 @@ +const path = require('path') +const fs = require('fs') +const exec = require('child_process').exec + +fs.watch(path.join(__dirname, '..', 'css'), {recursive: true}, function (eventType, filename) { + if (filename.endsWith('.css')) { + hit() + } +}) + +function run () { + console.log('Change detected, generating...') + exec(`node ${path.join(__dirname, 'generate-css-js.js')}`, (error, stdout, stderr) => { + if (error) { + console.error(`exec error: ${error}`) + return + } + console.log(`stdout: ${stdout}`) + console.log(`stderr: ${stderr}`) + }) +} + +var to = 0 +function hit () { + clearTimeout(to) + to = setTimeout(run, 500) +} \ No newline at end of file diff --git a/app/userland/app-stdlib/scripts/emoji-data.txt b/app/userland/app-stdlib/scripts/emoji-data.txt new file mode 100644 index 0000000000..2ad239f251 --- /dev/null +++ b/app/userland/app-stdlib/scripts/emoji-data.txt @@ -0,0 +1,3303 @@ +# Captured April 17 2019 +# Stripped out all entires which are not fully-qualified +# Commented out all items not yet supported by Beaker +# -prf + + +# emoji-test.txt +# Date: 2019-01-27, 15:43:01 GMT +# © 2019 Unicode®, Inc. +# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. +# For terms of use, see http://www.unicode.org/terms_of_use.html +# +# Emoji Keyboard/Display Test Data for UTS #51 +# Version: 12.0 +# +# For documentation and usage, see http://www.unicode.org/reports/tr51 +# +# This file provides data for testing which emoji forms should be in keyboards and which should also be displayed/processed. +# Format: code points; status # emoji name +# Code points — list of one or more hex code points, separated by spaces +# Status +# component — an Emoji_Component, +# excluding Regional_Indicators, ASCII, and non-Emoji. +# fully-qualified — a fully-qualified emoji (see ED-18 in UTS #51), +# excluding Emoji_Component +# Notes: +# • This includes the emoji components that need emoji presentation (skin tone and hair) +# when isolated, but omits the components that need not have an emoji +# presentation when isolated. +# • The RGI set is covered by the listed fully-qualified emoji. +# element of the RGI set is missing one or more emoji presentation selectors. +# • The file is in CLDR order, not codepoint order. This is recommended (but not required!) for keyboard palettes. +# • The groups and subgroups are illustrative. See the Emoji Order chart for more information. + + +# group: Smileys & Emotion + +# subgroup: face-smiling +1F600 ; fully-qualified # 😀 grinning face +1F603 ; fully-qualified # 😃 grinning face with big eyes +1F604 ; fully-qualified # 😄 grinning face with smiling eyes +1F601 ; fully-qualified # 😁 beaming face with smiling eyes +1F606 ; fully-qualified # 😆 grinning squinting face +1F605 ; fully-qualified # 😅 grinning face with sweat +1F923 ; fully-qualified # 🤣 rolling on the floor laughing +1F602 ; fully-qualified # 😂 face with tears of joy +1F642 ; fully-qualified # 🙂 slightly smiling face +1F643 ; fully-qualified # 🙃 upside-down face +1F609 ; fully-qualified # 😉 winking face +1F60A ; fully-qualified # 😊 smiling face with smiling eyes +1F607 ; fully-qualified # 😇 smiling face with halo + +# subgroup: face-affection +1F970 ; fully-qualified # 🥰 smiling face with hearts +1F60D ; fully-qualified # 😍 smiling face with heart-eyes +1F929 ; fully-qualified # 🤩 star-struck +1F618 ; fully-qualified # 😘 face blowing a kiss +1F617 ; fully-qualified # 😗 kissing face +#SUPPORT(prf) 263A FE0F ; fully-qualified # ☺️ smiling face +1F61A ; fully-qualified # 😚 kissing face with closed eyes +1F619 ; fully-qualified # 😙 kissing face with smiling eyes + +# subgroup: face-tongue +1F60B ; fully-qualified # 😋 face savoring food +1F61B ; fully-qualified # 😛 face with tongue +1F61C ; fully-qualified # 😜 winking face with tongue +1F92A ; fully-qualified # 🤪 zany face +1F61D ; fully-qualified # 😝 squinting face with tongue +1F911 ; fully-qualified # 🤑 money-mouth face + +# subgroup: face-hand +1F917 ; fully-qualified # 🤗 hugging face +1F92D ; fully-qualified # 🤭 face with hand over mouth +1F92B ; fully-qualified # 🤫 shushing face +1F914 ; fully-qualified # 🤔 thinking face + +# subgroup: face-neutral-skeptical +1F910 ; fully-qualified # 🤐 zipper-mouth face +1F928 ; fully-qualified # 🤨 face with raised eyebrow +1F610 ; fully-qualified # 😐 neutral face +1F611 ; fully-qualified # 😑 expressionless face +1F636 ; fully-qualified # 😶 face without mouth +1F60F ; fully-qualified # 😏 smirking face +1F612 ; fully-qualified # 😒 unamused face +1F644 ; fully-qualified # 🙄 face with rolling eyes +1F62C ; fully-qualified # 😬 grimacing face +1F925 ; fully-qualified # 🤥 lying face + +# subgroup: face-sleepy +1F60C ; fully-qualified # 😌 relieved face +1F614 ; fully-qualified # 😔 pensive face +1F62A ; fully-qualified # 😪 sleepy face +1F924 ; fully-qualified # 🤤 drooling face +1F634 ; fully-qualified # 😴 sleeping face + +# subgroup: face-unwell +1F637 ; fully-qualified # 😷 face with medical mask +1F912 ; fully-qualified # 🤒 face with thermometer +1F915 ; fully-qualified # 🤕 face with head-bandage +1F922 ; fully-qualified # 🤢 nauseated face +1F92E ; fully-qualified # 🤮 face vomiting +1F927 ; fully-qualified # 🤧 sneezing face +1F975 ; fully-qualified # 🥵 hot face +1F976 ; fully-qualified # 🥶 cold face +1F974 ; fully-qualified # 🥴 woozy face +1F635 ; fully-qualified # 😵 dizzy face +1F92F ; fully-qualified # 🤯 exploding head + +# subgroup: face-hat +1F920 ; fully-qualified # 🤠 cowboy hat face +1F973 ; fully-qualified # 🥳 partying face + +# subgroup: face-glasses +1F60E ; fully-qualified # 😎 smiling face with sunglasses +1F913 ; fully-qualified # 🤓 nerd face +1F9D0 ; fully-qualified # 🧐 face with monocle + +# subgroup: face-concerned +1F615 ; fully-qualified # 😕 confused face +1F61F ; fully-qualified # 😟 worried face +1F641 ; fully-qualified # 🙁 slightly frowning face +2639 FE0F ; fully-qualified # ☹️ frowning face +1F62E ; fully-qualified # 😮 face with open mouth +1F62F ; fully-qualified # 😯 hushed face +1F632 ; fully-qualified # 😲 astonished face +1F633 ; fully-qualified # 😳 flushed face +1F97A ; fully-qualified # 🥺 pleading face +1F626 ; fully-qualified # 😦 frowning face with open mouth +1F627 ; fully-qualified # 😧 anguished face +1F628 ; fully-qualified # 😨 fearful face +1F630 ; fully-qualified # 😰 anxious face with sweat +1F625 ; fully-qualified # 😥 sad but relieved face +1F622 ; fully-qualified # 😢 crying face +1F62D ; fully-qualified # 😭 loudly crying face +1F631 ; fully-qualified # 😱 face screaming in fear +1F616 ; fully-qualified # 😖 confounded face +1F623 ; fully-qualified # 😣 persevering face +1F61E ; fully-qualified # 😞 disappointed face +1F613 ; fully-qualified # 😓 downcast face with sweat +1F629 ; fully-qualified # 😩 weary face +1F62B ; fully-qualified # 😫 tired face +#SUPPORT(prf) 1F971 ; fully-qualified # 🥱 yawning face + +# subgroup: face-negative +1F624 ; fully-qualified # 😤 face with steam from nose +1F621 ; fully-qualified # 😡 pouting face +1F620 ; fully-qualified # 😠 angry face +1F92C ; fully-qualified # 🤬 face with symbols on mouth +1F608 ; fully-qualified # 😈 smiling face with horns +1F47F ; fully-qualified # 👿 angry face with horns +1F480 ; fully-qualified # 💀 skull +2620 FE0F ; fully-qualified # ☠️ skull and crossbones + +# subgroup: face-costume +1F4A9 ; fully-qualified # 💩 pile of poo +1F921 ; fully-qualified # 🤡 clown face +1F479 ; fully-qualified # 👹 ogre +1F47A ; fully-qualified # 👺 goblin +1F47B ; fully-qualified # 👻 ghost +1F47D ; fully-qualified # 👽 alien +1F47E ; fully-qualified # 👾 alien monster +1F916 ; fully-qualified # 🤖 robot + +# subgroup: cat-face +1F63A ; fully-qualified # 😺 grinning cat +1F638 ; fully-qualified # 😸 grinning cat with smiling eyes +1F639 ; fully-qualified # 😹 cat with tears of joy +1F63B ; fully-qualified # 😻 smiling cat with heart-eyes +1F63C ; fully-qualified # 😼 cat with wry smile +1F63D ; fully-qualified # 😽 kissing cat +1F640 ; fully-qualified # 🙀 weary cat +1F63F ; fully-qualified # 😿 crying cat +1F63E ; fully-qualified # 😾 pouting cat + +# subgroup: monkey-face +1F648 ; fully-qualified # 🙈 see-no-evil monkey +1F649 ; fully-qualified # 🙉 hear-no-evil monkey +1F64A ; fully-qualified # 🙊 speak-no-evil monkey + +# subgroup: emotion +1F48B ; fully-qualified # 💋 kiss mark +1F48C ; fully-qualified # 💌 love letter +1F498 ; fully-qualified # 💘 heart with arrow +1F49D ; fully-qualified # 💝 heart with ribbon +1F496 ; fully-qualified # 💖 sparkling heart +1F497 ; fully-qualified # 💗 growing heart +1F493 ; fully-qualified # 💓 beating heart +1F49E ; fully-qualified # 💞 revolving hearts +1F495 ; fully-qualified # 💕 two hearts +1F49F ; fully-qualified # 💟 heart decoration +2763 FE0F ; fully-qualified # ❣️ heart exclamation +1F494 ; fully-qualified # 💔 broken heart +2764 FE0F ; fully-qualified # ❤️ red heart +1F9E1 ; fully-qualified # 🧡 orange heart +1F49B ; fully-qualified # 💛 yellow heart +1F49A ; fully-qualified # 💚 green heart +1F499 ; fully-qualified # 💙 blue heart +1F49C ; fully-qualified # 💜 purple heart +#SUPPORT(prf) 1F90E ; fully-qualified # 🤎 brown heart +1F5A4 ; fully-qualified # 🖤 black heart +#SUPPORT(prf) 1F90D ; fully-qualified # 🤍 white heart +1F4AF ; fully-qualified # 💯 hundred points +1F4A2 ; fully-qualified # 💢 anger symbol +1F4A5 ; fully-qualified # 💥 collision +1F4AB ; fully-qualified # 💫 dizzy +1F4A6 ; fully-qualified # 💦 sweat droplets +1F4A8 ; fully-qualified # 💨 dashing away +1F573 FE0F ; fully-qualified # 🕳️ hole +1F4A3 ; fully-qualified # 💣 bomb +1F4AC ; fully-qualified # 💬 speech balloon +1F441 FE0F 200D 1F5E8 FE0F ; fully-qualified # 👁️‍🗨️ eye in speech bubble +1F5E8 FE0F ; fully-qualified # 🗨️ left speech bubble +1F5EF FE0F ; fully-qualified # 🗯️ right anger bubble +1F4AD ; fully-qualified # 💭 thought balloon +1F4A4 ; fully-qualified # 💤 zzz + +# Smileys & Emotion subtotal: 160 +# Smileys & Emotion subtotal: 160 w/o modifiers + +# group: People & Body + +# subgroup: hand-fingers-open +1F44B ; fully-qualified # 👋 waving hand +1F44B 1F3FB ; fully-qualified # 👋🏻 waving hand: light skin tone +1F44B 1F3FC ; fully-qualified # 👋🏼 waving hand: medium-light skin tone +1F44B 1F3FD ; fully-qualified # 👋🏽 waving hand: medium skin tone +1F44B 1F3FE ; fully-qualified # 👋🏾 waving hand: medium-dark skin tone +1F44B 1F3FF ; fully-qualified # 👋🏿 waving hand: dark skin tone +1F91A ; fully-qualified # 🤚 raised back of hand +1F91A 1F3FB ; fully-qualified # 🤚🏻 raised back of hand: light skin tone +1F91A 1F3FC ; fully-qualified # 🤚🏼 raised back of hand: medium-light skin tone +1F91A 1F3FD ; fully-qualified # 🤚🏽 raised back of hand: medium skin tone +1F91A 1F3FE ; fully-qualified # 🤚🏾 raised back of hand: medium-dark skin tone +1F91A 1F3FF ; fully-qualified # 🤚🏿 raised back of hand: dark skin tone +1F590 FE0F ; fully-qualified # 🖐️ hand with fingers splayed +1F590 1F3FB ; fully-qualified # 🖐🏻 hand with fingers splayed: light skin tone +1F590 1F3FC ; fully-qualified # 🖐🏼 hand with fingers splayed: medium-light skin tone +1F590 1F3FD ; fully-qualified # 🖐🏽 hand with fingers splayed: medium skin tone +1F590 1F3FE ; fully-qualified # 🖐🏾 hand with fingers splayed: medium-dark skin tone +1F590 1F3FF ; fully-qualified # 🖐🏿 hand with fingers splayed: dark skin tone +270B ; fully-qualified # ✋ raised hand +270B 1F3FB ; fully-qualified # ✋🏻 raised hand: light skin tone +270B 1F3FC ; fully-qualified # ✋🏼 raised hand: medium-light skin tone +270B 1F3FD ; fully-qualified # ✋🏽 raised hand: medium skin tone +270B 1F3FE ; fully-qualified # ✋🏾 raised hand: medium-dark skin tone +270B 1F3FF ; fully-qualified # ✋🏿 raised hand: dark skin tone +1F596 ; fully-qualified # 🖖 vulcan salute +1F596 1F3FB ; fully-qualified # 🖖🏻 vulcan salute: light skin tone +1F596 1F3FC ; fully-qualified # 🖖🏼 vulcan salute: medium-light skin tone +1F596 1F3FD ; fully-qualified # 🖖🏽 vulcan salute: medium skin tone +1F596 1F3FE ; fully-qualified # 🖖🏾 vulcan salute: medium-dark skin tone +1F596 1F3FF ; fully-qualified # 🖖🏿 vulcan salute: dark skin tone + +# subgroup: hand-fingers-partial +1F44C ; fully-qualified # 👌 OK hand +1F44C 1F3FB ; fully-qualified # 👌🏻 OK hand: light skin tone +1F44C 1F3FC ; fully-qualified # 👌🏼 OK hand: medium-light skin tone +1F44C 1F3FD ; fully-qualified # 👌🏽 OK hand: medium skin tone +1F44C 1F3FE ; fully-qualified # 👌🏾 OK hand: medium-dark skin tone +1F44C 1F3FF ; fully-qualified # 👌🏿 OK hand: dark skin tone +#SUPPORT(prf) 1F90F ; fully-qualified # 🤏 pinching hand +#SUPPORT(prf) 1F90F 1F3FB ; fully-qualified # 🤏🏻 pinching hand: light skin tone +#SUPPORT(prf) 1F90F 1F3FC ; fully-qualified # 🤏🏼 pinching hand: medium-light skin tone +#SUPPORT(prf) 1F90F 1F3FD ; fully-qualified # 🤏🏽 pinching hand: medium skin tone +#SUPPORT(prf) 1F90F 1F3FE ; fully-qualified # 🤏🏾 pinching hand: medium-dark skin tone +#SUPPORT(prf) 1F90F 1F3FF ; fully-qualified # 🤏🏿 pinching hand: dark skin tone +270C FE0F ; fully-qualified # ✌️ victory hand +270C 1F3FB ; fully-qualified # ✌🏻 victory hand: light skin tone +270C 1F3FC ; fully-qualified # ✌🏼 victory hand: medium-light skin tone +270C 1F3FD ; fully-qualified # ✌🏽 victory hand: medium skin tone +270C 1F3FE ; fully-qualified # ✌🏾 victory hand: medium-dark skin tone +270C 1F3FF ; fully-qualified # ✌🏿 victory hand: dark skin tone +1F91E ; fully-qualified # 🤞 crossed fingers +1F91E 1F3FB ; fully-qualified # 🤞🏻 crossed fingers: light skin tone +1F91E 1F3FC ; fully-qualified # 🤞🏼 crossed fingers: medium-light skin tone +1F91E 1F3FD ; fully-qualified # 🤞🏽 crossed fingers: medium skin tone +1F91E 1F3FE ; fully-qualified # 🤞🏾 crossed fingers: medium-dark skin tone +1F91E 1F3FF ; fully-qualified # 🤞🏿 crossed fingers: dark skin tone +1F91F ; fully-qualified # 🤟 love-you gesture +1F91F 1F3FB ; fully-qualified # 🤟🏻 love-you gesture: light skin tone +1F91F 1F3FC ; fully-qualified # 🤟🏼 love-you gesture: medium-light skin tone +1F91F 1F3FD ; fully-qualified # 🤟🏽 love-you gesture: medium skin tone +1F91F 1F3FE ; fully-qualified # 🤟🏾 love-you gesture: medium-dark skin tone +1F91F 1F3FF ; fully-qualified # 🤟🏿 love-you gesture: dark skin tone +1F918 ; fully-qualified # 🤘 sign of the horns +1F918 1F3FB ; fully-qualified # 🤘🏻 sign of the horns: light skin tone +1F918 1F3FC ; fully-qualified # 🤘🏼 sign of the horns: medium-light skin tone +1F918 1F3FD ; fully-qualified # 🤘🏽 sign of the horns: medium skin tone +1F918 1F3FE ; fully-qualified # 🤘🏾 sign of the horns: medium-dark skin tone +1F918 1F3FF ; fully-qualified # 🤘🏿 sign of the horns: dark skin tone +1F919 ; fully-qualified # 🤙 call me hand +1F919 1F3FB ; fully-qualified # 🤙🏻 call me hand: light skin tone +1F919 1F3FC ; fully-qualified # 🤙🏼 call me hand: medium-light skin tone +1F919 1F3FD ; fully-qualified # 🤙🏽 call me hand: medium skin tone +1F919 1F3FE ; fully-qualified # 🤙🏾 call me hand: medium-dark skin tone +1F919 1F3FF ; fully-qualified # 🤙🏿 call me hand: dark skin tone + +# subgroup: hand-single-finger +1F448 ; fully-qualified # 👈 backhand index pointing left +1F448 1F3FB ; fully-qualified # 👈🏻 backhand index pointing left: light skin tone +1F448 1F3FC ; fully-qualified # 👈🏼 backhand index pointing left: medium-light skin tone +1F448 1F3FD ; fully-qualified # 👈🏽 backhand index pointing left: medium skin tone +1F448 1F3FE ; fully-qualified # 👈🏾 backhand index pointing left: medium-dark skin tone +1F448 1F3FF ; fully-qualified # 👈🏿 backhand index pointing left: dark skin tone +1F449 ; fully-qualified # 👉 backhand index pointing right +1F449 1F3FB ; fully-qualified # 👉🏻 backhand index pointing right: light skin tone +1F449 1F3FC ; fully-qualified # 👉🏼 backhand index pointing right: medium-light skin tone +1F449 1F3FD ; fully-qualified # 👉🏽 backhand index pointing right: medium skin tone +1F449 1F3FE ; fully-qualified # 👉🏾 backhand index pointing right: medium-dark skin tone +1F449 1F3FF ; fully-qualified # 👉🏿 backhand index pointing right: dark skin tone +1F446 ; fully-qualified # 👆 backhand index pointing up +1F446 1F3FB ; fully-qualified # 👆🏻 backhand index pointing up: light skin tone +1F446 1F3FC ; fully-qualified # 👆🏼 backhand index pointing up: medium-light skin tone +1F446 1F3FD ; fully-qualified # 👆🏽 backhand index pointing up: medium skin tone +1F446 1F3FE ; fully-qualified # 👆🏾 backhand index pointing up: medium-dark skin tone +1F446 1F3FF ; fully-qualified # 👆🏿 backhand index pointing up: dark skin tone +1F595 ; fully-qualified # 🖕 middle finger +1F595 1F3FB ; fully-qualified # 🖕🏻 middle finger: light skin tone +1F595 1F3FC ; fully-qualified # 🖕🏼 middle finger: medium-light skin tone +1F595 1F3FD ; fully-qualified # 🖕🏽 middle finger: medium skin tone +1F595 1F3FE ; fully-qualified # 🖕🏾 middle finger: medium-dark skin tone +1F595 1F3FF ; fully-qualified # 🖕🏿 middle finger: dark skin tone +1F447 ; fully-qualified # 👇 backhand index pointing down +1F447 1F3FB ; fully-qualified # 👇🏻 backhand index pointing down: light skin tone +1F447 1F3FC ; fully-qualified # 👇🏼 backhand index pointing down: medium-light skin tone +1F447 1F3FD ; fully-qualified # 👇🏽 backhand index pointing down: medium skin tone +1F447 1F3FE ; fully-qualified # 👇🏾 backhand index pointing down: medium-dark skin tone +1F447 1F3FF ; fully-qualified # 👇🏿 backhand index pointing down: dark skin tone +261D FE0F ; fully-qualified # ☝️ index pointing up +261D 1F3FB ; fully-qualified # ☝🏻 index pointing up: light skin tone +261D 1F3FC ; fully-qualified # ☝🏼 index pointing up: medium-light skin tone +261D 1F3FD ; fully-qualified # ☝🏽 index pointing up: medium skin tone +261D 1F3FE ; fully-qualified # ☝🏾 index pointing up: medium-dark skin tone +261D 1F3FF ; fully-qualified # ☝🏿 index pointing up: dark skin tone + +# subgroup: hand-fingers-closed +1F44D ; fully-qualified # 👍 thumbs up +1F44D 1F3FB ; fully-qualified # 👍🏻 thumbs up: light skin tone +1F44D 1F3FC ; fully-qualified # 👍🏼 thumbs up: medium-light skin tone +1F44D 1F3FD ; fully-qualified # 👍🏽 thumbs up: medium skin tone +1F44D 1F3FE ; fully-qualified # 👍🏾 thumbs up: medium-dark skin tone +1F44D 1F3FF ; fully-qualified # 👍🏿 thumbs up: dark skin tone +1F44E ; fully-qualified # 👎 thumbs down +1F44E 1F3FB ; fully-qualified # 👎🏻 thumbs down: light skin tone +1F44E 1F3FC ; fully-qualified # 👎🏼 thumbs down: medium-light skin tone +1F44E 1F3FD ; fully-qualified # 👎🏽 thumbs down: medium skin tone +1F44E 1F3FE ; fully-qualified # 👎🏾 thumbs down: medium-dark skin tone +1F44E 1F3FF ; fully-qualified # 👎🏿 thumbs down: dark skin tone +270A ; fully-qualified # ✊ raised fist +270A 1F3FB ; fully-qualified # ✊🏻 raised fist: light skin tone +270A 1F3FC ; fully-qualified # ✊🏼 raised fist: medium-light skin tone +270A 1F3FD ; fully-qualified # ✊🏽 raised fist: medium skin tone +270A 1F3FE ; fully-qualified # ✊🏾 raised fist: medium-dark skin tone +270A 1F3FF ; fully-qualified # ✊🏿 raised fist: dark skin tone +1F44A ; fully-qualified # 👊 oncoming fist +1F44A 1F3FB ; fully-qualified # 👊🏻 oncoming fist: light skin tone +1F44A 1F3FC ; fully-qualified # 👊🏼 oncoming fist: medium-light skin tone +1F44A 1F3FD ; fully-qualified # 👊🏽 oncoming fist: medium skin tone +1F44A 1F3FE ; fully-qualified # 👊🏾 oncoming fist: medium-dark skin tone +1F44A 1F3FF ; fully-qualified # 👊🏿 oncoming fist: dark skin tone +1F91B ; fully-qualified # 🤛 left-facing fist +1F91B 1F3FB ; fully-qualified # 🤛🏻 left-facing fist: light skin tone +1F91B 1F3FC ; fully-qualified # 🤛🏼 left-facing fist: medium-light skin tone +1F91B 1F3FD ; fully-qualified # 🤛🏽 left-facing fist: medium skin tone +1F91B 1F3FE ; fully-qualified # 🤛🏾 left-facing fist: medium-dark skin tone +1F91B 1F3FF ; fully-qualified # 🤛🏿 left-facing fist: dark skin tone +1F91C ; fully-qualified # 🤜 right-facing fist +1F91C 1F3FB ; fully-qualified # 🤜🏻 right-facing fist: light skin tone +1F91C 1F3FC ; fully-qualified # 🤜🏼 right-facing fist: medium-light skin tone +1F91C 1F3FD ; fully-qualified # 🤜🏽 right-facing fist: medium skin tone +1F91C 1F3FE ; fully-qualified # 🤜🏾 right-facing fist: medium-dark skin tone +1F91C 1F3FF ; fully-qualified # 🤜🏿 right-facing fist: dark skin tone + +# subgroup: hands +1F44F ; fully-qualified # 👏 clapping hands +1F44F 1F3FB ; fully-qualified # 👏🏻 clapping hands: light skin tone +1F44F 1F3FC ; fully-qualified # 👏🏼 clapping hands: medium-light skin tone +1F44F 1F3FD ; fully-qualified # 👏🏽 clapping hands: medium skin tone +1F44F 1F3FE ; fully-qualified # 👏🏾 clapping hands: medium-dark skin tone +1F44F 1F3FF ; fully-qualified # 👏🏿 clapping hands: dark skin tone +1F64C ; fully-qualified # 🙌 raising hands +1F64C 1F3FB ; fully-qualified # 🙌🏻 raising hands: light skin tone +1F64C 1F3FC ; fully-qualified # 🙌🏼 raising hands: medium-light skin tone +1F64C 1F3FD ; fully-qualified # 🙌🏽 raising hands: medium skin tone +1F64C 1F3FE ; fully-qualified # 🙌🏾 raising hands: medium-dark skin tone +1F64C 1F3FF ; fully-qualified # 🙌🏿 raising hands: dark skin tone +1F450 ; fully-qualified # 👐 open hands +1F450 1F3FB ; fully-qualified # 👐🏻 open hands: light skin tone +1F450 1F3FC ; fully-qualified # 👐🏼 open hands: medium-light skin tone +1F450 1F3FD ; fully-qualified # 👐🏽 open hands: medium skin tone +1F450 1F3FE ; fully-qualified # 👐🏾 open hands: medium-dark skin tone +1F450 1F3FF ; fully-qualified # 👐🏿 open hands: dark skin tone +1F932 ; fully-qualified # 🤲 palms up together +1F932 1F3FB ; fully-qualified # 🤲🏻 palms up together: light skin tone +1F932 1F3FC ; fully-qualified # 🤲🏼 palms up together: medium-light skin tone +1F932 1F3FD ; fully-qualified # 🤲🏽 palms up together: medium skin tone +1F932 1F3FE ; fully-qualified # 🤲🏾 palms up together: medium-dark skin tone +1F932 1F3FF ; fully-qualified # 🤲🏿 palms up together: dark skin tone +1F91D ; fully-qualified # 🤝 handshake +1F64F ; fully-qualified # 🙏 folded hands +1F64F 1F3FB ; fully-qualified # 🙏🏻 folded hands: light skin tone +1F64F 1F3FC ; fully-qualified # 🙏🏼 folded hands: medium-light skin tone +1F64F 1F3FD ; fully-qualified # 🙏🏽 folded hands: medium skin tone +1F64F 1F3FE ; fully-qualified # 🙏🏾 folded hands: medium-dark skin tone +1F64F 1F3FF ; fully-qualified # 🙏🏿 folded hands: dark skin tone + +# subgroup: hand-prop +270D FE0F ; fully-qualified # ✍️ writing hand +270D 1F3FB ; fully-qualified # ✍🏻 writing hand: light skin tone +270D 1F3FC ; fully-qualified # ✍🏼 writing hand: medium-light skin tone +270D 1F3FD ; fully-qualified # ✍🏽 writing hand: medium skin tone +270D 1F3FE ; fully-qualified # ✍🏾 writing hand: medium-dark skin tone +270D 1F3FF ; fully-qualified # ✍🏿 writing hand: dark skin tone +1F485 ; fully-qualified # 💅 nail polish +1F485 1F3FB ; fully-qualified # 💅🏻 nail polish: light skin tone +1F485 1F3FC ; fully-qualified # 💅🏼 nail polish: medium-light skin tone +1F485 1F3FD ; fully-qualified # 💅🏽 nail polish: medium skin tone +1F485 1F3FE ; fully-qualified # 💅🏾 nail polish: medium-dark skin tone +1F485 1F3FF ; fully-qualified # 💅🏿 nail polish: dark skin tone +1F933 ; fully-qualified # 🤳 selfie +1F933 1F3FB ; fully-qualified # 🤳🏻 selfie: light skin tone +1F933 1F3FC ; fully-qualified # 🤳🏼 selfie: medium-light skin tone +1F933 1F3FD ; fully-qualified # 🤳🏽 selfie: medium skin tone +1F933 1F3FE ; fully-qualified # 🤳🏾 selfie: medium-dark skin tone +1F933 1F3FF ; fully-qualified # 🤳🏿 selfie: dark skin tone + +# subgroup: body-parts +1F4AA ; fully-qualified # 💪 flexed biceps +1F4AA 1F3FB ; fully-qualified # 💪🏻 flexed biceps: light skin tone +1F4AA 1F3FC ; fully-qualified # 💪🏼 flexed biceps: medium-light skin tone +1F4AA 1F3FD ; fully-qualified # 💪🏽 flexed biceps: medium skin tone +1F4AA 1F3FE ; fully-qualified # 💪🏾 flexed biceps: medium-dark skin tone +1F4AA 1F3FF ; fully-qualified # 💪🏿 flexed biceps: dark skin tone +#SUPPORT(prf) 1F9BE ; fully-qualified # 🦾 mechanical arm +#SUPPORT(prf) 1F9BF ; fully-qualified # 🦿 mechanical leg +1F9B5 ; fully-qualified # 🦵 leg +1F9B5 1F3FB ; fully-qualified # 🦵🏻 leg: light skin tone +1F9B5 1F3FC ; fully-qualified # 🦵🏼 leg: medium-light skin tone +1F9B5 1F3FD ; fully-qualified # 🦵🏽 leg: medium skin tone +1F9B5 1F3FE ; fully-qualified # 🦵🏾 leg: medium-dark skin tone +1F9B5 1F3FF ; fully-qualified # 🦵🏿 leg: dark skin tone +1F9B6 ; fully-qualified # 🦶 foot +1F9B6 1F3FB ; fully-qualified # 🦶🏻 foot: light skin tone +1F9B6 1F3FC ; fully-qualified # 🦶🏼 foot: medium-light skin tone +1F9B6 1F3FD ; fully-qualified # 🦶🏽 foot: medium skin tone +1F9B6 1F3FE ; fully-qualified # 🦶🏾 foot: medium-dark skin tone +1F9B6 1F3FF ; fully-qualified # 🦶🏿 foot: dark skin tone +1F442 ; fully-qualified # 👂 ear +1F442 1F3FB ; fully-qualified # 👂🏻 ear: light skin tone +1F442 1F3FC ; fully-qualified # 👂🏼 ear: medium-light skin tone +1F442 1F3FD ; fully-qualified # 👂🏽 ear: medium skin tone +1F442 1F3FE ; fully-qualified # 👂🏾 ear: medium-dark skin tone +1F442 1F3FF ; fully-qualified # 👂🏿 ear: dark skin tone +#SUPPORT(prf) 1F9BB ; fully-qualified # 🦻 ear with hearing aid +#SUPPORT(prf) 1F9BB 1F3FB ; fully-qualified # 🦻🏻 ear with hearing aid: light skin tone +#SUPPORT(prf) 1F9BB 1F3FC ; fully-qualified # 🦻🏼 ear with hearing aid: medium-light skin tone +#SUPPORT(prf) 1F9BB 1F3FD ; fully-qualified # 🦻🏽 ear with hearing aid: medium skin tone +#SUPPORT(prf) 1F9BB 1F3FE ; fully-qualified # 🦻🏾 ear with hearing aid: medium-dark skin tone +#SUPPORT(prf) 1F9BB 1F3FF ; fully-qualified # 🦻🏿 ear with hearing aid: dark skin tone +1F443 ; fully-qualified # 👃 nose +1F443 1F3FB ; fully-qualified # 👃🏻 nose: light skin tone +1F443 1F3FC ; fully-qualified # 👃🏼 nose: medium-light skin tone +1F443 1F3FD ; fully-qualified # 👃🏽 nose: medium skin tone +1F443 1F3FE ; fully-qualified # 👃🏾 nose: medium-dark skin tone +1F443 1F3FF ; fully-qualified # 👃🏿 nose: dark skin tone +1F9E0 ; fully-qualified # 🧠 brain +1F9B7 ; fully-qualified # 🦷 tooth +1F9B4 ; fully-qualified # 🦴 bone +1F440 ; fully-qualified # 👀 eyes +1F441 FE0F ; fully-qualified # 👁️ eye +1F445 ; fully-qualified # 👅 tongue +1F444 ; fully-qualified # 👄 mouth + +# subgroup: person +1F476 ; fully-qualified # 👶 baby +1F476 1F3FB ; fully-qualified # 👶🏻 baby: light skin tone +1F476 1F3FC ; fully-qualified # 👶🏼 baby: medium-light skin tone +1F476 1F3FD ; fully-qualified # 👶🏽 baby: medium skin tone +1F476 1F3FE ; fully-qualified # 👶🏾 baby: medium-dark skin tone +1F476 1F3FF ; fully-qualified # 👶🏿 baby: dark skin tone +1F9D2 ; fully-qualified # 🧒 child +1F9D2 1F3FB ; fully-qualified # 🧒🏻 child: light skin tone +1F9D2 1F3FC ; fully-qualified # 🧒🏼 child: medium-light skin tone +1F9D2 1F3FD ; fully-qualified # 🧒🏽 child: medium skin tone +1F9D2 1F3FE ; fully-qualified # 🧒🏾 child: medium-dark skin tone +1F9D2 1F3FF ; fully-qualified # 🧒🏿 child: dark skin tone +1F466 ; fully-qualified # 👦 boy +1F466 1F3FB ; fully-qualified # 👦🏻 boy: light skin tone +1F466 1F3FC ; fully-qualified # 👦🏼 boy: medium-light skin tone +1F466 1F3FD ; fully-qualified # 👦🏽 boy: medium skin tone +1F466 1F3FE ; fully-qualified # 👦🏾 boy: medium-dark skin tone +1F466 1F3FF ; fully-qualified # 👦🏿 boy: dark skin tone +1F467 ; fully-qualified # 👧 girl +1F467 1F3FB ; fully-qualified # 👧🏻 girl: light skin tone +1F467 1F3FC ; fully-qualified # 👧🏼 girl: medium-light skin tone +1F467 1F3FD ; fully-qualified # 👧🏽 girl: medium skin tone +1F467 1F3FE ; fully-qualified # 👧🏾 girl: medium-dark skin tone +1F467 1F3FF ; fully-qualified # 👧🏿 girl: dark skin tone +1F9D1 ; fully-qualified # 🧑 person +1F9D1 1F3FB ; fully-qualified # 🧑🏻 person: light skin tone +1F9D1 1F3FC ; fully-qualified # 🧑🏼 person: medium-light skin tone +1F9D1 1F3FD ; fully-qualified # 🧑🏽 person: medium skin tone +1F9D1 1F3FE ; fully-qualified # 🧑🏾 person: medium-dark skin tone +1F9D1 1F3FF ; fully-qualified # 🧑🏿 person: dark skin tone +1F471 ; fully-qualified # 👱 person: blond hair +1F471 1F3FB ; fully-qualified # 👱🏻 person: light skin tone, blond hair +1F471 1F3FC ; fully-qualified # 👱🏼 person: medium-light skin tone, blond hair +1F471 1F3FD ; fully-qualified # 👱🏽 person: medium skin tone, blond hair +1F471 1F3FE ; fully-qualified # 👱🏾 person: medium-dark skin tone, blond hair +1F471 1F3FF ; fully-qualified # 👱🏿 person: dark skin tone, blond hair +1F468 ; fully-qualified # 👨 man +1F468 1F3FB ; fully-qualified # 👨🏻 man: light skin tone +1F468 1F3FC ; fully-qualified # 👨🏼 man: medium-light skin tone +1F468 1F3FD ; fully-qualified # 👨🏽 man: medium skin tone +1F468 1F3FE ; fully-qualified # 👨🏾 man: medium-dark skin tone +1F468 1F3FF ; fully-qualified # 👨🏿 man: dark skin tone +1F9D4 ; fully-qualified # 🧔 man: beard +1F9D4 1F3FB ; fully-qualified # 🧔🏻 man: light skin tone, beard +1F9D4 1F3FC ; fully-qualified # 🧔🏼 man: medium-light skin tone, beard +1F9D4 1F3FD ; fully-qualified # 🧔🏽 man: medium skin tone, beard +1F9D4 1F3FE ; fully-qualified # 🧔🏾 man: medium-dark skin tone, beard +1F9D4 1F3FF ; fully-qualified # 🧔🏿 man: dark skin tone, beard +#SUPPORT(prf) 1F471 200D 2642 FE0F ; fully-qualified # 👱‍♂️ man: blond hair +#SUPPORT(prf) 1F471 1F3FB 200D 2642 FE0F ; fully-qualified # 👱🏻‍♂️ man: light skin tone, blond hair +#SUPPORT(prf) 1F471 1F3FC 200D 2642 FE0F ; fully-qualified # 👱🏼‍♂️ man: medium-light skin tone, blond hair +#SUPPORT(prf) 1F471 1F3FD 200D 2642 FE0F ; fully-qualified # 👱🏽‍♂️ man: medium skin tone, blond hair +#SUPPORT(prf) 1F471 1F3FE 200D 2642 FE0F ; fully-qualified # 👱🏾‍♂️ man: medium-dark skin tone, blond hair +#SUPPORT(prf) 1F471 1F3FF 200D 2642 FE0F ; fully-qualified # 👱🏿‍♂️ man: dark skin tone, blond hair +#SUPPORT(prf) 1F468 200D 1F9B0 ; fully-qualified # 👨‍🦰 man: red hair +#SUPPORT(prf) 1F468 1F3FB 200D 1F9B0 ; fully-qualified # 👨🏻‍🦰 man: light skin tone, red hair +#SUPPORT(prf) 1F468 1F3FC 200D 1F9B0 ; fully-qualified # 👨🏼‍🦰 man: medium-light skin tone, red hair +#SUPPORT(prf) 1F468 1F3FD 200D 1F9B0 ; fully-qualified # 👨🏽‍🦰 man: medium skin tone, red hair +#SUPPORT(prf) 1F468 1F3FE 200D 1F9B0 ; fully-qualified # 👨🏾‍🦰 man: medium-dark skin tone, red hair +#SUPPORT(prf) 1F468 1F3FF 200D 1F9B0 ; fully-qualified # 👨🏿‍🦰 man: dark skin tone, red hair +#SUPPORT(prf) 1F468 200D 1F9B1 ; fully-qualified # 👨‍🦱 man: curly hair +#SUPPORT(prf) 1F468 1F3FB 200D 1F9B1 ; fully-qualified # 👨🏻‍🦱 man: light skin tone, curly hair +#SUPPORT(prf) 1F468 1F3FC 200D 1F9B1 ; fully-qualified # 👨🏼‍🦱 man: medium-light skin tone, curly hair +#SUPPORT(prf) 1F468 1F3FD 200D 1F9B1 ; fully-qualified # 👨🏽‍🦱 man: medium skin tone, curly hair +#SUPPORT(prf) 1F468 1F3FE 200D 1F9B1 ; fully-qualified # 👨🏾‍🦱 man: medium-dark skin tone, curly hair +#SUPPORT(prf) 1F468 1F3FF 200D 1F9B1 ; fully-qualified # 👨🏿‍🦱 man: dark skin tone, curly hair +#SUPPORT(prf) 1F468 200D 1F9B3 ; fully-qualified # 👨‍🦳 man: white hair +#SUPPORT(prf) 1F468 1F3FB 200D 1F9B3 ; fully-qualified # 👨🏻‍🦳 man: light skin tone, white hair +#SUPPORT(prf) 1F468 1F3FC 200D 1F9B3 ; fully-qualified # 👨🏼‍🦳 man: medium-light skin tone, white hair +#SUPPORT(prf) 1F468 1F3FD 200D 1F9B3 ; fully-qualified # 👨🏽‍🦳 man: medium skin tone, white hair +#SUPPORT(prf) 1F468 1F3FE 200D 1F9B3 ; fully-qualified # 👨🏾‍🦳 man: medium-dark skin tone, white hair +#SUPPORT(prf) 1F468 1F3FF 200D 1F9B3 ; fully-qualified # 👨🏿‍🦳 man: dark skin tone, white hair +#SUPPORT(prf) 1F468 200D 1F9B2 ; fully-qualified # 👨‍🦲 man: bald +#SUPPORT(prf) 1F468 1F3FB 200D 1F9B2 ; fully-qualified # 👨🏻‍🦲 man: light skin tone, bald +#SUPPORT(prf) 1F468 1F3FC 200D 1F9B2 ; fully-qualified # 👨🏼‍🦲 man: medium-light skin tone, bald +#SUPPORT(prf) 1F468 1F3FD 200D 1F9B2 ; fully-qualified # 👨🏽‍🦲 man: medium skin tone, bald +#SUPPORT(prf) 1F468 1F3FE 200D 1F9B2 ; fully-qualified # 👨🏾‍🦲 man: medium-dark skin tone, bald +#SUPPORT(prf) 1F468 1F3FF 200D 1F9B2 ; fully-qualified # 👨🏿‍🦲 man: dark skin tone, bald +1F469 ; fully-qualified # 👩 woman +1F469 1F3FB ; fully-qualified # 👩🏻 woman: light skin tone +1F469 1F3FC ; fully-qualified # 👩🏼 woman: medium-light skin tone +1F469 1F3FD ; fully-qualified # 👩🏽 woman: medium skin tone +1F469 1F3FE ; fully-qualified # 👩🏾 woman: medium-dark skin tone +1F469 1F3FF ; fully-qualified # 👩🏿 woman: dark skin tone +#SUPPORT(prf) 1F471 200D 2640 FE0F ; fully-qualified # 👱‍♀️ woman: blond hair +#SUPPORT(prf) 1F471 1F3FB 200D 2640 FE0F ; fully-qualified # 👱🏻‍♀️ woman: light skin tone, blond hair +#SUPPORT(prf) 1F471 1F3FC 200D 2640 FE0F ; fully-qualified # 👱🏼‍♀️ woman: medium-light skin tone, blond hair +#SUPPORT(prf) 1F471 1F3FD 200D 2640 FE0F ; fully-qualified # 👱🏽‍♀️ woman: medium skin tone, blond hair +#SUPPORT(prf) 1F471 1F3FE 200D 2640 FE0F ; fully-qualified # 👱🏾‍♀️ woman: medium-dark skin tone, blond hair +#SUPPORT(prf) 1F471 1F3FF 200D 2640 FE0F ; fully-qualified # 👱🏿‍♀️ woman: dark skin tone, blond hair +#SUPPORT(prf) 1F469 200D 1F9B0 ; fully-qualified # 👩‍🦰 woman: red hair +#SUPPORT(prf) 1F469 1F3FB 200D 1F9B0 ; fully-qualified # 👩🏻‍🦰 woman: light skin tone, red hair +#SUPPORT(prf) 1F469 1F3FC 200D 1F9B0 ; fully-qualified # 👩🏼‍🦰 woman: medium-light skin tone, red hair +#SUPPORT(prf) 1F469 1F3FD 200D 1F9B0 ; fully-qualified # 👩🏽‍🦰 woman: medium skin tone, red hair +#SUPPORT(prf) 1F469 1F3FE 200D 1F9B0 ; fully-qualified # 👩🏾‍🦰 woman: medium-dark skin tone, red hair +#SUPPORT(prf) 1F469 1F3FF 200D 1F9B0 ; fully-qualified # 👩🏿‍🦰 woman: dark skin tone, red hair +#SUPPORT(prf) 1F469 200D 1F9B1 ; fully-qualified # 👩‍🦱 woman: curly hair +#SUPPORT(prf) 1F469 1F3FB 200D 1F9B1 ; fully-qualified # 👩🏻‍🦱 woman: light skin tone, curly hair +#SUPPORT(prf) 1F469 1F3FC 200D 1F9B1 ; fully-qualified # 👩🏼‍🦱 woman: medium-light skin tone, curly hair +#SUPPORT(prf) 1F469 1F3FD 200D 1F9B1 ; fully-qualified # 👩🏽‍🦱 woman: medium skin tone, curly hair +#SUPPORT(prf) 1F469 1F3FE 200D 1F9B1 ; fully-qualified # 👩🏾‍🦱 woman: medium-dark skin tone, curly hair +#SUPPORT(prf) 1F469 1F3FF 200D 1F9B1 ; fully-qualified # 👩🏿‍🦱 woman: dark skin tone, curly hair +#SUPPORT(prf) 1F469 200D 1F9B3 ; fully-qualified # 👩‍🦳 woman: white hair +#SUPPORT(prf) 1F469 1F3FB 200D 1F9B3 ; fully-qualified # 👩🏻‍🦳 woman: light skin tone, white hair +#SUPPORT(prf) 1F469 1F3FC 200D 1F9B3 ; fully-qualified # 👩🏼‍🦳 woman: medium-light skin tone, white hair +#SUPPORT(prf) 1F469 1F3FD 200D 1F9B3 ; fully-qualified # 👩🏽‍🦳 woman: medium skin tone, white hair +#SUPPORT(prf) 1F469 1F3FE 200D 1F9B3 ; fully-qualified # 👩🏾‍🦳 woman: medium-dark skin tone, white hair +#SUPPORT(prf) 1F469 1F3FF 200D 1F9B3 ; fully-qualified # 👩🏿‍🦳 woman: dark skin tone, white hair +#SUPPORT(prf) 1F469 200D 1F9B2 ; fully-qualified # 👩‍🦲 woman: bald +#SUPPORT(prf) 1F469 1F3FB 200D 1F9B2 ; fully-qualified # 👩🏻‍🦲 woman: light skin tone, bald +#SUPPORT(prf) 1F469 1F3FC 200D 1F9B2 ; fully-qualified # 👩🏼‍🦲 woman: medium-light skin tone, bald +#SUPPORT(prf) 1F469 1F3FD 200D 1F9B2 ; fully-qualified # 👩🏽‍🦲 woman: medium skin tone, bald +#SUPPORT(prf) 1F469 1F3FE 200D 1F9B2 ; fully-qualified # 👩🏾‍🦲 woman: medium-dark skin tone, bald +#SUPPORT(prf) 1F469 1F3FF 200D 1F9B2 ; fully-qualified # 👩🏿‍🦲 woman: dark skin tone, bald +1F9D3 ; fully-qualified # 🧓 older person +1F9D3 1F3FB ; fully-qualified # 🧓🏻 older person: light skin tone +1F9D3 1F3FC ; fully-qualified # 🧓🏼 older person: medium-light skin tone +1F9D3 1F3FD ; fully-qualified # 🧓🏽 older person: medium skin tone +1F9D3 1F3FE ; fully-qualified # 🧓🏾 older person: medium-dark skin tone +1F9D3 1F3FF ; fully-qualified # 🧓🏿 older person: dark skin tone +1F474 ; fully-qualified # 👴 old man +1F474 1F3FB ; fully-qualified # 👴🏻 old man: light skin tone +1F474 1F3FC ; fully-qualified # 👴🏼 old man: medium-light skin tone +1F474 1F3FD ; fully-qualified # 👴🏽 old man: medium skin tone +1F474 1F3FE ; fully-qualified # 👴🏾 old man: medium-dark skin tone +1F474 1F3FF ; fully-qualified # 👴🏿 old man: dark skin tone +1F475 ; fully-qualified # 👵 old woman +1F475 1F3FB ; fully-qualified # 👵🏻 old woman: light skin tone +1F475 1F3FC ; fully-qualified # 👵🏼 old woman: medium-light skin tone +1F475 1F3FD ; fully-qualified # 👵🏽 old woman: medium skin tone +1F475 1F3FE ; fully-qualified # 👵🏾 old woman: medium-dark skin tone +1F475 1F3FF ; fully-qualified # 👵🏿 old woman: dark skin tone + +# subgroup: person-gesture +1F64D ; fully-qualified # 🙍 person frowning +1F64D 1F3FB ; fully-qualified # 🙍🏻 person frowning: light skin tone +1F64D 1F3FC ; fully-qualified # 🙍🏼 person frowning: medium-light skin tone +1F64D 1F3FD ; fully-qualified # 🙍🏽 person frowning: medium skin tone +1F64D 1F3FE ; fully-qualified # 🙍🏾 person frowning: medium-dark skin tone +1F64D 1F3FF ; fully-qualified # 🙍🏿 person frowning: dark skin tone +#SUPPORT(prf) 1F64D 200D 2642 FE0F ; fully-qualified # 🙍‍♂️ man frowning +#SUPPORT(prf) 1F64D 1F3FB 200D 2642 FE0F ; fully-qualified # 🙍🏻‍♂️ man frowning: light skin tone +#SUPPORT(prf) 1F64D 1F3FC 200D 2642 FE0F ; fully-qualified # 🙍🏼‍♂️ man frowning: medium-light skin tone +#SUPPORT(prf) 1F64D 1F3FD 200D 2642 FE0F ; fully-qualified # 🙍🏽‍♂️ man frowning: medium skin tone +#SUPPORT(prf) 1F64D 1F3FE 200D 2642 FE0F ; fully-qualified # 🙍🏾‍♂️ man frowning: medium-dark skin tone +#SUPPORT(prf) 1F64D 1F3FF 200D 2642 FE0F ; fully-qualified # 🙍🏿‍♂️ man frowning: dark skin tone +#SUPPORT(prf) 1F64D 200D 2640 FE0F ; fully-qualified # 🙍‍♀️ woman frowning +#SUPPORT(prf) 1F64D 1F3FB 200D 2640 FE0F ; fully-qualified # 🙍🏻‍♀️ woman frowning: light skin tone +#SUPPORT(prf) 1F64D 1F3FC 200D 2640 FE0F ; fully-qualified # 🙍🏼‍♀️ woman frowning: medium-light skin tone +#SUPPORT(prf) 1F64D 1F3FD 200D 2640 FE0F ; fully-qualified # 🙍🏽‍♀️ woman frowning: medium skin tone +#SUPPORT(prf) 1F64D 1F3FE 200D 2640 FE0F ; fully-qualified # 🙍🏾‍♀️ woman frowning: medium-dark skin tone +#SUPPORT(prf) 1F64D 1F3FF 200D 2640 FE0F ; fully-qualified # 🙍🏿‍♀️ woman frowning: dark skin tone +1F64E ; fully-qualified # 🙎 person pouting +1F64E 1F3FB ; fully-qualified # 🙎🏻 person pouting: light skin tone +1F64E 1F3FC ; fully-qualified # 🙎🏼 person pouting: medium-light skin tone +1F64E 1F3FD ; fully-qualified # 🙎🏽 person pouting: medium skin tone +1F64E 1F3FE ; fully-qualified # 🙎🏾 person pouting: medium-dark skin tone +1F64E 1F3FF ; fully-qualified # 🙎🏿 person pouting: dark skin tone +#SUPPORT(prf) 1F64E 200D 2642 FE0F ; fully-qualified # 🙎‍♂️ man pouting +#SUPPORT(prf) 1F64E 1F3FB 200D 2642 FE0F ; fully-qualified # 🙎🏻‍♂️ man pouting: light skin tone +#SUPPORT(prf) 1F64E 1F3FC 200D 2642 FE0F ; fully-qualified # 🙎🏼‍♂️ man pouting: medium-light skin tone +#SUPPORT(prf) 1F64E 1F3FD 200D 2642 FE0F ; fully-qualified # 🙎🏽‍♂️ man pouting: medium skin tone +#SUPPORT(prf) 1F64E 1F3FE 200D 2642 FE0F ; fully-qualified # 🙎🏾‍♂️ man pouting: medium-dark skin tone +#SUPPORT(prf) 1F64E 1F3FF 200D 2642 FE0F ; fully-qualified # 🙎🏿‍♂️ man pouting: dark skin tone +#SUPPORT(prf) 1F64E 200D 2640 FE0F ; fully-qualified # 🙎‍♀️ woman pouting +#SUPPORT(prf) 1F64E 1F3FB 200D 2640 FE0F ; fully-qualified # 🙎🏻‍♀️ woman pouting: light skin tone +#SUPPORT(prf) 1F64E 1F3FC 200D 2640 FE0F ; fully-qualified # 🙎🏼‍♀️ woman pouting: medium-light skin tone +#SUPPORT(prf) 1F64E 1F3FD 200D 2640 FE0F ; fully-qualified # 🙎🏽‍♀️ woman pouting: medium skin tone +#SUPPORT(prf) 1F64E 1F3FE 200D 2640 FE0F ; fully-qualified # 🙎🏾‍♀️ woman pouting: medium-dark skin tone +#SUPPORT(prf) 1F64E 1F3FF 200D 2640 FE0F ; fully-qualified # 🙎🏿‍♀️ woman pouting: dark skin tone +1F645 ; fully-qualified # 🙅 person gesturing NO +1F645 1F3FB ; fully-qualified # 🙅🏻 person gesturing NO: light skin tone +1F645 1F3FC ; fully-qualified # 🙅🏼 person gesturing NO: medium-light skin tone +1F645 1F3FD ; fully-qualified # 🙅🏽 person gesturing NO: medium skin tone +1F645 1F3FE ; fully-qualified # 🙅🏾 person gesturing NO: medium-dark skin tone +1F645 1F3FF ; fully-qualified # 🙅🏿 person gesturing NO: dark skin tone +#SUPPORT(prf) 1F645 200D 2642 FE0F ; fully-qualified # 🙅‍♂️ man gesturing NO +#SUPPORT(prf) 1F645 1F3FB 200D 2642 FE0F ; fully-qualified # 🙅🏻‍♂️ man gesturing NO: light skin tone +#SUPPORT(prf) 1F645 1F3FC 200D 2642 FE0F ; fully-qualified # 🙅🏼‍♂️ man gesturing NO: medium-light skin tone +#SUPPORT(prf) 1F645 1F3FD 200D 2642 FE0F ; fully-qualified # 🙅🏽‍♂️ man gesturing NO: medium skin tone +#SUPPORT(prf) 1F645 1F3FE 200D 2642 FE0F ; fully-qualified # 🙅🏾‍♂️ man gesturing NO: medium-dark skin tone +#SUPPORT(prf) 1F645 1F3FF 200D 2642 FE0F ; fully-qualified # 🙅🏿‍♂️ man gesturing NO: dark skin tone +#SUPPORT(prf) 1F645 200D 2640 FE0F ; fully-qualified # 🙅‍♀️ woman gesturing NO +#SUPPORT(prf) 1F645 1F3FB 200D 2640 FE0F ; fully-qualified # 🙅🏻‍♀️ woman gesturing NO: light skin tone +#SUPPORT(prf) 1F645 1F3FC 200D 2640 FE0F ; fully-qualified # 🙅🏼‍♀️ woman gesturing NO: medium-light skin tone +#SUPPORT(prf) 1F645 1F3FD 200D 2640 FE0F ; fully-qualified # 🙅🏽‍♀️ woman gesturing NO: medium skin tone +#SUPPORT(prf) 1F645 1F3FE 200D 2640 FE0F ; fully-qualified # 🙅🏾‍♀️ woman gesturing NO: medium-dark skin tone +#SUPPORT(prf) 1F645 1F3FF 200D 2640 FE0F ; fully-qualified # 🙅🏿‍♀️ woman gesturing NO: dark skin tone +1F646 ; fully-qualified # 🙆 person gesturing OK +1F646 1F3FB ; fully-qualified # 🙆🏻 person gesturing OK: light skin tone +1F646 1F3FC ; fully-qualified # 🙆🏼 person gesturing OK: medium-light skin tone +1F646 1F3FD ; fully-qualified # 🙆🏽 person gesturing OK: medium skin tone +1F646 1F3FE ; fully-qualified # 🙆🏾 person gesturing OK: medium-dark skin tone +1F646 1F3FF ; fully-qualified # 🙆🏿 person gesturing OK: dark skin tone +#SUPPORT(prf) 1F646 200D 2642 FE0F ; fully-qualified # 🙆‍♂️ man gesturing OK +#SUPPORT(prf) 1F646 1F3FB 200D 2642 FE0F ; fully-qualified # 🙆🏻‍♂️ man gesturing OK: light skin tone +#SUPPORT(prf) 1F646 1F3FC 200D 2642 FE0F ; fully-qualified # 🙆🏼‍♂️ man gesturing OK: medium-light skin tone +#SUPPORT(prf) 1F646 1F3FD 200D 2642 FE0F ; fully-qualified # 🙆🏽‍♂️ man gesturing OK: medium skin tone +#SUPPORT(prf) 1F646 1F3FE 200D 2642 FE0F ; fully-qualified # 🙆🏾‍♂️ man gesturing OK: medium-dark skin tone +#SUPPORT(prf) 1F646 1F3FF 200D 2642 FE0F ; fully-qualified # 🙆🏿‍♂️ man gesturing OK: dark skin tone +#SUPPORT(prf) 1F646 200D 2640 FE0F ; fully-qualified # 🙆‍♀️ woman gesturing OK +#SUPPORT(prf) 1F646 1F3FB 200D 2640 FE0F ; fully-qualified # 🙆🏻‍♀️ woman gesturing OK: light skin tone +#SUPPORT(prf) 1F646 1F3FC 200D 2640 FE0F ; fully-qualified # 🙆🏼‍♀️ woman gesturing OK: medium-light skin tone +#SUPPORT(prf) 1F646 1F3FD 200D 2640 FE0F ; fully-qualified # 🙆🏽‍♀️ woman gesturing OK: medium skin tone +#SUPPORT(prf) 1F646 1F3FE 200D 2640 FE0F ; fully-qualified # 🙆🏾‍♀️ woman gesturing OK: medium-dark skin tone +#SUPPORT(prf) 1F646 1F3FF 200D 2640 FE0F ; fully-qualified # 🙆🏿‍♀️ woman gesturing OK: dark skin tone +1F481 ; fully-qualified # 💁 person tipping hand +1F481 1F3FB ; fully-qualified # 💁🏻 person tipping hand: light skin tone +1F481 1F3FC ; fully-qualified # 💁🏼 person tipping hand: medium-light skin tone +1F481 1F3FD ; fully-qualified # 💁🏽 person tipping hand: medium skin tone +1F481 1F3FE ; fully-qualified # 💁🏾 person tipping hand: medium-dark skin tone +1F481 1F3FF ; fully-qualified # 💁🏿 person tipping hand: dark skin tone +#SUPPORT(prf) 1F481 200D 2642 FE0F ; fully-qualified # 💁‍♂️ man tipping hand +#SUPPORT(prf) 1F481 1F3FB 200D 2642 FE0F ; fully-qualified # 💁🏻‍♂️ man tipping hand: light skin tone +#SUPPORT(prf) 1F481 1F3FC 200D 2642 FE0F ; fully-qualified # 💁🏼‍♂️ man tipping hand: medium-light skin tone +#SUPPORT(prf) 1F481 1F3FD 200D 2642 FE0F ; fully-qualified # 💁🏽‍♂️ man tipping hand: medium skin tone +#SUPPORT(prf) 1F481 1F3FE 200D 2642 FE0F ; fully-qualified # 💁🏾‍♂️ man tipping hand: medium-dark skin tone +#SUPPORT(prf) 1F481 1F3FF 200D 2642 FE0F ; fully-qualified # 💁🏿‍♂️ man tipping hand: dark skin tone +#SUPPORT(prf) 1F481 200D 2640 FE0F ; fully-qualified # 💁‍♀️ woman tipping hand +#SUPPORT(prf) 1F481 1F3FB 200D 2640 FE0F ; fully-qualified # 💁🏻‍♀️ woman tipping hand: light skin tone +#SUPPORT(prf) 1F481 1F3FC 200D 2640 FE0F ; fully-qualified # 💁🏼‍♀️ woman tipping hand: medium-light skin tone +#SUPPORT(prf) 1F481 1F3FD 200D 2640 FE0F ; fully-qualified # 💁🏽‍♀️ woman tipping hand: medium skin tone +#SUPPORT(prf) 1F481 1F3FE 200D 2640 FE0F ; fully-qualified # 💁🏾‍♀️ woman tipping hand: medium-dark skin tone +#SUPPORT(prf) 1F481 1F3FF 200D 2640 FE0F ; fully-qualified # 💁🏿‍♀️ woman tipping hand: dark skin tone +1F64B ; fully-qualified # 🙋 person raising hand +1F64B 1F3FB ; fully-qualified # 🙋🏻 person raising hand: light skin tone +1F64B 1F3FC ; fully-qualified # 🙋🏼 person raising hand: medium-light skin tone +1F64B 1F3FD ; fully-qualified # 🙋🏽 person raising hand: medium skin tone +1F64B 1F3FE ; fully-qualified # 🙋🏾 person raising hand: medium-dark skin tone +1F64B 1F3FF ; fully-qualified # 🙋🏿 person raising hand: dark skin tone +#SUPPORT(prf) 1F64B 200D 2642 FE0F ; fully-qualified # 🙋‍♂️ man raising hand +#SUPPORT(prf) 1F64B 1F3FB 200D 2642 FE0F ; fully-qualified # 🙋🏻‍♂️ man raising hand: light skin tone +#SUPPORT(prf) 1F64B 1F3FC 200D 2642 FE0F ; fully-qualified # 🙋🏼‍♂️ man raising hand: medium-light skin tone +#SUPPORT(prf) 1F64B 1F3FD 200D 2642 FE0F ; fully-qualified # 🙋🏽‍♂️ man raising hand: medium skin tone +#SUPPORT(prf) 1F64B 1F3FE 200D 2642 FE0F ; fully-qualified # 🙋🏾‍♂️ man raising hand: medium-dark skin tone +#SUPPORT(prf) 1F64B 1F3FF 200D 2642 FE0F ; fully-qualified # 🙋🏿‍♂️ man raising hand: dark skin tone +#SUPPORT(prf) 1F64B 200D 2640 FE0F ; fully-qualified # 🙋‍♀️ woman raising hand +#SUPPORT(prf) 1F64B 1F3FB 200D 2640 FE0F ; fully-qualified # 🙋🏻‍♀️ woman raising hand: light skin tone +#SUPPORT(prf) 1F64B 1F3FC 200D 2640 FE0F ; fully-qualified # 🙋🏼‍♀️ woman raising hand: medium-light skin tone +#SUPPORT(prf) 1F64B 1F3FD 200D 2640 FE0F ; fully-qualified # 🙋🏽‍♀️ woman raising hand: medium skin tone +#SUPPORT(prf) 1F64B 1F3FE 200D 2640 FE0F ; fully-qualified # 🙋🏾‍♀️ woman raising hand: medium-dark skin tone +#SUPPORT(prf) 1F64B 1F3FF 200D 2640 FE0F ; fully-qualified # 🙋🏿‍♀️ woman raising hand: dark skin tone +#SUPPORT(prf) 1F9CF ; fully-qualified # 🧏 deaf person +#SUPPORT(prf) 1F9CF 1F3FB ; fully-qualified # 🧏🏻 deaf person: light skin tone +#SUPPORT(prf) 1F9CF 1F3FC ; fully-qualified # 🧏🏼 deaf person: medium-light skin tone +#SUPPORT(prf) 1F9CF 1F3FD ; fully-qualified # 🧏🏽 deaf person: medium skin tone +#SUPPORT(prf) 1F9CF 1F3FE ; fully-qualified # 🧏🏾 deaf person: medium-dark skin tone +#SUPPORT(prf) 1F9CF 1F3FF ; fully-qualified # 🧏🏿 deaf person: dark skin tone +#SUPPORT(prf) 1F9CF 200D 2642 FE0F ; fully-qualified # 🧏‍♂️ deaf man +#SUPPORT(prf) 1F9CF 1F3FB 200D 2642 FE0F ; fully-qualified # 🧏🏻‍♂️ deaf man: light skin tone +#SUPPORT(prf) 1F9CF 1F3FC 200D 2642 FE0F ; fully-qualified # 🧏🏼‍♂️ deaf man: medium-light skin tone +#SUPPORT(prf) 1F9CF 1F3FD 200D 2642 FE0F ; fully-qualified # 🧏🏽‍♂️ deaf man: medium skin tone +#SUPPORT(prf) 1F9CF 1F3FE 200D 2642 FE0F ; fully-qualified # 🧏🏾‍♂️ deaf man: medium-dark skin tone +#SUPPORT(prf) 1F9CF 1F3FF 200D 2642 FE0F ; fully-qualified # 🧏🏿‍♂️ deaf man: dark skin tone +#SUPPORT(prf) 1F9CF 200D 2640 FE0F ; fully-qualified # 🧏‍♀️ deaf woman +#SUPPORT(prf) 1F9CF 1F3FB 200D 2640 FE0F ; fully-qualified # 🧏🏻‍♀️ deaf woman: light skin tone +#SUPPORT(prf) 1F9CF 1F3FC 200D 2640 FE0F ; fully-qualified # 🧏🏼‍♀️ deaf woman: medium-light skin tone +#SUPPORT(prf) 1F9CF 1F3FD 200D 2640 FE0F ; fully-qualified # 🧏🏽‍♀️ deaf woman: medium skin tone +#SUPPORT(prf) 1F9CF 1F3FE 200D 2640 FE0F ; fully-qualified # 🧏🏾‍♀️ deaf woman: medium-dark skin tone +#SUPPORT(prf) 1F9CF 1F3FF 200D 2640 FE0F ; fully-qualified # 🧏🏿‍♀️ deaf woman: dark skin tone +1F647 ; fully-qualified # 🙇 person bowing +1F647 1F3FB ; fully-qualified # 🙇🏻 person bowing: light skin tone +1F647 1F3FC ; fully-qualified # 🙇🏼 person bowing: medium-light skin tone +1F647 1F3FD ; fully-qualified # 🙇🏽 person bowing: medium skin tone +1F647 1F3FE ; fully-qualified # 🙇🏾 person bowing: medium-dark skin tone +1F647 1F3FF ; fully-qualified # 🙇🏿 person bowing: dark skin tone +#SUPPORT(prf) 1F647 200D 2642 FE0F ; fully-qualified # 🙇‍♂️ man bowing +#SUPPORT(prf) 1F647 1F3FB 200D 2642 FE0F ; fully-qualified # 🙇🏻‍♂️ man bowing: light skin tone +#SUPPORT(prf) 1F647 1F3FC 200D 2642 FE0F ; fully-qualified # 🙇🏼‍♂️ man bowing: medium-light skin tone +#SUPPORT(prf) 1F647 1F3FD 200D 2642 FE0F ; fully-qualified # 🙇🏽‍♂️ man bowing: medium skin tone +#SUPPORT(prf) 1F647 1F3FE 200D 2642 FE0F ; fully-qualified # 🙇🏾‍♂️ man bowing: medium-dark skin tone +#SUPPORT(prf) 1F647 1F3FF 200D 2642 FE0F ; fully-qualified # 🙇🏿‍♂️ man bowing: dark skin tone +#SUPPORT(prf) 1F647 200D 2640 FE0F ; fully-qualified # 🙇‍♀️ woman bowing +#SUPPORT(prf) 1F647 1F3FB 200D 2640 FE0F ; fully-qualified # 🙇🏻‍♀️ woman bowing: light skin tone +#SUPPORT(prf) 1F647 1F3FC 200D 2640 FE0F ; fully-qualified # 🙇🏼‍♀️ woman bowing: medium-light skin tone +#SUPPORT(prf) 1F647 1F3FD 200D 2640 FE0F ; fully-qualified # 🙇🏽‍♀️ woman bowing: medium skin tone +#SUPPORT(prf) 1F647 1F3FE 200D 2640 FE0F ; fully-qualified # 🙇🏾‍♀️ woman bowing: medium-dark skin tone +#SUPPORT(prf) 1F647 1F3FF 200D 2640 FE0F ; fully-qualified # 🙇🏿‍♀️ woman bowing: dark skin tone +1F926 ; fully-qualified # 🤦 person facepalming +1F926 1F3FB ; fully-qualified # 🤦🏻 person facepalming: light skin tone +1F926 1F3FC ; fully-qualified # 🤦🏼 person facepalming: medium-light skin tone +1F926 1F3FD ; fully-qualified # 🤦🏽 person facepalming: medium skin tone +1F926 1F3FE ; fully-qualified # 🤦🏾 person facepalming: medium-dark skin tone +1F926 1F3FF ; fully-qualified # 🤦🏿 person facepalming: dark skin tone +#SUPPORT(prf) 1F926 200D 2642 FE0F ; fully-qualified # 🤦‍♂️ man facepalming +#SUPPORT(prf) 1F926 1F3FB 200D 2642 FE0F ; fully-qualified # 🤦🏻‍♂️ man facepalming: light skin tone +#SUPPORT(prf) 1F926 1F3FC 200D 2642 FE0F ; fully-qualified # 🤦🏼‍♂️ man facepalming: medium-light skin tone +#SUPPORT(prf) 1F926 1F3FD 200D 2642 FE0F ; fully-qualified # 🤦🏽‍♂️ man facepalming: medium skin tone +#SUPPORT(prf) 1F926 1F3FE 200D 2642 FE0F ; fully-qualified # 🤦🏾‍♂️ man facepalming: medium-dark skin tone +#SUPPORT(prf) 1F926 1F3FF 200D 2642 FE0F ; fully-qualified # 🤦🏿‍♂️ man facepalming: dark skin tone +#SUPPORT(prf) 1F926 200D 2640 FE0F ; fully-qualified # 🤦‍♀️ woman facepalming +#SUPPORT(prf) 1F926 1F3FB 200D 2640 FE0F ; fully-qualified # 🤦🏻‍♀️ woman facepalming: light skin tone +#SUPPORT(prf) 1F926 1F3FC 200D 2640 FE0F ; fully-qualified # 🤦🏼‍♀️ woman facepalming: medium-light skin tone +#SUPPORT(prf) 1F926 1F3FD 200D 2640 FE0F ; fully-qualified # 🤦🏽‍♀️ woman facepalming: medium skin tone +#SUPPORT(prf) 1F926 1F3FE 200D 2640 FE0F ; fully-qualified # 🤦🏾‍♀️ woman facepalming: medium-dark skin tone +#SUPPORT(prf) 1F926 1F3FF 200D 2640 FE0F ; fully-qualified # 🤦🏿‍♀️ woman facepalming: dark skin tone +1F937 ; fully-qualified # 🤷 person shrugging +1F937 1F3FB ; fully-qualified # 🤷🏻 person shrugging: light skin tone +1F937 1F3FC ; fully-qualified # 🤷🏼 person shrugging: medium-light skin tone +1F937 1F3FD ; fully-qualified # 🤷🏽 person shrugging: medium skin tone +1F937 1F3FE ; fully-qualified # 🤷🏾 person shrugging: medium-dark skin tone +1F937 1F3FF ; fully-qualified # 🤷🏿 person shrugging: dark skin tone +#SUPPORT(prf) 1F937 200D 2642 FE0F ; fully-qualified # 🤷‍♂️ man shrugging +#SUPPORT(prf) 1F937 1F3FB 200D 2642 FE0F ; fully-qualified # 🤷🏻‍♂️ man shrugging: light skin tone +#SUPPORT(prf) 1F937 1F3FC 200D 2642 FE0F ; fully-qualified # 🤷🏼‍♂️ man shrugging: medium-light skin tone +#SUPPORT(prf) 1F937 1F3FD 200D 2642 FE0F ; fully-qualified # 🤷🏽‍♂️ man shrugging: medium skin tone +#SUPPORT(prf) 1F937 1F3FE 200D 2642 FE0F ; fully-qualified # 🤷🏾‍♂️ man shrugging: medium-dark skin tone +#SUPPORT(prf) 1F937 1F3FF 200D 2642 FE0F ; fully-qualified # 🤷🏿‍♂️ man shrugging: dark skin tone +#SUPPORT(prf) 1F937 200D 2640 FE0F ; fully-qualified # 🤷‍♀️ woman shrugging +#SUPPORT(prf) 1F937 1F3FB 200D 2640 FE0F ; fully-qualified # 🤷🏻‍♀️ woman shrugging: light skin tone +#SUPPORT(prf) 1F937 1F3FC 200D 2640 FE0F ; fully-qualified # 🤷🏼‍♀️ woman shrugging: medium-light skin tone +#SUPPORT(prf) 1F937 1F3FD 200D 2640 FE0F ; fully-qualified # 🤷🏽‍♀️ woman shrugging: medium skin tone +#SUPPORT(prf) 1F937 1F3FE 200D 2640 FE0F ; fully-qualified # 🤷🏾‍♀️ woman shrugging: medium-dark skin tone +#SUPPORT(prf) 1F937 1F3FF 200D 2640 FE0F ; fully-qualified # 🤷🏿‍♀️ woman shrugging: dark skin tone + +# subgroup: person-role +1F468 200D 2695 FE0F ; fully-qualified # 👨‍⚕️ man health worker +1F468 1F3FB 200D 2695 FE0F ; fully-qualified # 👨🏻‍⚕️ man health worker: light skin tone +1F468 1F3FC 200D 2695 FE0F ; fully-qualified # 👨🏼‍⚕️ man health worker: medium-light skin tone +1F468 1F3FD 200D 2695 FE0F ; fully-qualified # 👨🏽‍⚕️ man health worker: medium skin tone +1F468 1F3FE 200D 2695 FE0F ; fully-qualified # 👨🏾‍⚕️ man health worker: medium-dark skin tone +1F468 1F3FF 200D 2695 FE0F ; fully-qualified # 👨🏿‍⚕️ man health worker: dark skin tone +1F469 200D 2695 FE0F ; fully-qualified # 👩‍⚕️ woman health worker +1F469 1F3FB 200D 2695 FE0F ; fully-qualified # 👩🏻‍⚕️ woman health worker: light skin tone +1F469 1F3FC 200D 2695 FE0F ; fully-qualified # 👩🏼‍⚕️ woman health worker: medium-light skin tone +1F469 1F3FD 200D 2695 FE0F ; fully-qualified # 👩🏽‍⚕️ woman health worker: medium skin tone +1F469 1F3FE 200D 2695 FE0F ; fully-qualified # 👩🏾‍⚕️ woman health worker: medium-dark skin tone +1F469 1F3FF 200D 2695 FE0F ; fully-qualified # 👩🏿‍⚕️ woman health worker: dark skin tone +1F468 200D 1F393 ; fully-qualified # 👨‍🎓 man student +1F468 1F3FB 200D 1F393 ; fully-qualified # 👨🏻‍🎓 man student: light skin tone +1F468 1F3FC 200D 1F393 ; fully-qualified # 👨🏼‍🎓 man student: medium-light skin tone +1F468 1F3FD 200D 1F393 ; fully-qualified # 👨🏽‍🎓 man student: medium skin tone +1F468 1F3FE 200D 1F393 ; fully-qualified # 👨🏾‍🎓 man student: medium-dark skin tone +1F468 1F3FF 200D 1F393 ; fully-qualified # 👨🏿‍🎓 man student: dark skin tone +1F469 200D 1F393 ; fully-qualified # 👩‍🎓 woman student +1F469 1F3FB 200D 1F393 ; fully-qualified # 👩🏻‍🎓 woman student: light skin tone +1F469 1F3FC 200D 1F393 ; fully-qualified # 👩🏼‍🎓 woman student: medium-light skin tone +1F469 1F3FD 200D 1F393 ; fully-qualified # 👩🏽‍🎓 woman student: medium skin tone +1F469 1F3FE 200D 1F393 ; fully-qualified # 👩🏾‍🎓 woman student: medium-dark skin tone +1F469 1F3FF 200D 1F393 ; fully-qualified # 👩🏿‍🎓 woman student: dark skin tone +1F468 200D 1F3EB ; fully-qualified # 👨‍🏫 man teacher +1F468 1F3FB 200D 1F3EB ; fully-qualified # 👨🏻‍🏫 man teacher: light skin tone +1F468 1F3FC 200D 1F3EB ; fully-qualified # 👨🏼‍🏫 man teacher: medium-light skin tone +1F468 1F3FD 200D 1F3EB ; fully-qualified # 👨🏽‍🏫 man teacher: medium skin tone +1F468 1F3FE 200D 1F3EB ; fully-qualified # 👨🏾‍🏫 man teacher: medium-dark skin tone +1F468 1F3FF 200D 1F3EB ; fully-qualified # 👨🏿‍🏫 man teacher: dark skin tone +1F469 200D 1F3EB ; fully-qualified # 👩‍🏫 woman teacher +1F469 1F3FB 200D 1F3EB ; fully-qualified # 👩🏻‍🏫 woman teacher: light skin tone +1F469 1F3FC 200D 1F3EB ; fully-qualified # 👩🏼‍🏫 woman teacher: medium-light skin tone +1F469 1F3FD 200D 1F3EB ; fully-qualified # 👩🏽‍🏫 woman teacher: medium skin tone +1F469 1F3FE 200D 1F3EB ; fully-qualified # 👩🏾‍🏫 woman teacher: medium-dark skin tone +1F469 1F3FF 200D 1F3EB ; fully-qualified # 👩🏿‍🏫 woman teacher: dark skin tone +1F468 200D 2696 FE0F ; fully-qualified # 👨‍⚖️ man judge +1F468 1F3FB 200D 2696 FE0F ; fully-qualified # 👨🏻‍⚖️ man judge: light skin tone +1F468 1F3FC 200D 2696 FE0F ; fully-qualified # 👨🏼‍⚖️ man judge: medium-light skin tone +1F468 1F3FD 200D 2696 FE0F ; fully-qualified # 👨🏽‍⚖️ man judge: medium skin tone +1F468 1F3FE 200D 2696 FE0F ; fully-qualified # 👨🏾‍⚖️ man judge: medium-dark skin tone +1F468 1F3FF 200D 2696 FE0F ; fully-qualified # 👨🏿‍⚖️ man judge: dark skin tone +1F469 200D 2696 FE0F ; fully-qualified # 👩‍⚖️ woman judge +1F469 1F3FB 200D 2696 FE0F ; fully-qualified # 👩🏻‍⚖️ woman judge: light skin tone +1F469 1F3FC 200D 2696 FE0F ; fully-qualified # 👩🏼‍⚖️ woman judge: medium-light skin tone +1F469 1F3FD 200D 2696 FE0F ; fully-qualified # 👩🏽‍⚖️ woman judge: medium skin tone +1F469 1F3FE 200D 2696 FE0F ; fully-qualified # 👩🏾‍⚖️ woman judge: medium-dark skin tone +1F469 1F3FF 200D 2696 FE0F ; fully-qualified # 👩🏿‍⚖️ woman judge: dark skin tone +1F468 200D 1F33E ; fully-qualified # 👨‍🌾 man farmer +1F468 1F3FB 200D 1F33E ; fully-qualified # 👨🏻‍🌾 man farmer: light skin tone +1F468 1F3FC 200D 1F33E ; fully-qualified # 👨🏼‍🌾 man farmer: medium-light skin tone +1F468 1F3FD 200D 1F33E ; fully-qualified # 👨🏽‍🌾 man farmer: medium skin tone +1F468 1F3FE 200D 1F33E ; fully-qualified # 👨🏾‍🌾 man farmer: medium-dark skin tone +1F468 1F3FF 200D 1F33E ; fully-qualified # 👨🏿‍🌾 man farmer: dark skin tone +1F469 200D 1F33E ; fully-qualified # 👩‍🌾 woman farmer +1F469 1F3FB 200D 1F33E ; fully-qualified # 👩🏻‍🌾 woman farmer: light skin tone +1F469 1F3FC 200D 1F33E ; fully-qualified # 👩🏼‍🌾 woman farmer: medium-light skin tone +1F469 1F3FD 200D 1F33E ; fully-qualified # 👩🏽‍🌾 woman farmer: medium skin tone +1F469 1F3FE 200D 1F33E ; fully-qualified # 👩🏾‍🌾 woman farmer: medium-dark skin tone +1F469 1F3FF 200D 1F33E ; fully-qualified # 👩🏿‍🌾 woman farmer: dark skin tone +1F468 200D 1F373 ; fully-qualified # 👨‍🍳 man cook +1F468 1F3FB 200D 1F373 ; fully-qualified # 👨🏻‍🍳 man cook: light skin tone +1F468 1F3FC 200D 1F373 ; fully-qualified # 👨🏼‍🍳 man cook: medium-light skin tone +1F468 1F3FD 200D 1F373 ; fully-qualified # 👨🏽‍🍳 man cook: medium skin tone +1F468 1F3FE 200D 1F373 ; fully-qualified # 👨🏾‍🍳 man cook: medium-dark skin tone +1F468 1F3FF 200D 1F373 ; fully-qualified # 👨🏿‍🍳 man cook: dark skin tone +1F469 200D 1F373 ; fully-qualified # 👩‍🍳 woman cook +1F469 1F3FB 200D 1F373 ; fully-qualified # 👩🏻‍🍳 woman cook: light skin tone +1F469 1F3FC 200D 1F373 ; fully-qualified # 👩🏼‍🍳 woman cook: medium-light skin tone +1F469 1F3FD 200D 1F373 ; fully-qualified # 👩🏽‍🍳 woman cook: medium skin tone +1F469 1F3FE 200D 1F373 ; fully-qualified # 👩🏾‍🍳 woman cook: medium-dark skin tone +1F469 1F3FF 200D 1F373 ; fully-qualified # 👩🏿‍🍳 woman cook: dark skin tone +1F468 200D 1F527 ; fully-qualified # 👨‍🔧 man mechanic +1F468 1F3FB 200D 1F527 ; fully-qualified # 👨🏻‍🔧 man mechanic: light skin tone +1F468 1F3FC 200D 1F527 ; fully-qualified # 👨🏼‍🔧 man mechanic: medium-light skin tone +1F468 1F3FD 200D 1F527 ; fully-qualified # 👨🏽‍🔧 man mechanic: medium skin tone +1F468 1F3FE 200D 1F527 ; fully-qualified # 👨🏾‍🔧 man mechanic: medium-dark skin tone +1F468 1F3FF 200D 1F527 ; fully-qualified # 👨🏿‍🔧 man mechanic: dark skin tone +1F469 200D 1F527 ; fully-qualified # 👩‍🔧 woman mechanic +1F469 1F3FB 200D 1F527 ; fully-qualified # 👩🏻‍🔧 woman mechanic: light skin tone +1F469 1F3FC 200D 1F527 ; fully-qualified # 👩🏼‍🔧 woman mechanic: medium-light skin tone +1F469 1F3FD 200D 1F527 ; fully-qualified # 👩🏽‍🔧 woman mechanic: medium skin tone +1F469 1F3FE 200D 1F527 ; fully-qualified # 👩🏾‍🔧 woman mechanic: medium-dark skin tone +1F469 1F3FF 200D 1F527 ; fully-qualified # 👩🏿‍🔧 woman mechanic: dark skin tone +1F468 200D 1F3ED ; fully-qualified # 👨‍🏭 man factory worker +1F468 1F3FB 200D 1F3ED ; fully-qualified # 👨🏻‍🏭 man factory worker: light skin tone +1F468 1F3FC 200D 1F3ED ; fully-qualified # 👨🏼‍🏭 man factory worker: medium-light skin tone +1F468 1F3FD 200D 1F3ED ; fully-qualified # 👨🏽‍🏭 man factory worker: medium skin tone +1F468 1F3FE 200D 1F3ED ; fully-qualified # 👨🏾‍🏭 man factory worker: medium-dark skin tone +1F468 1F3FF 200D 1F3ED ; fully-qualified # 👨🏿‍🏭 man factory worker: dark skin tone +1F469 200D 1F3ED ; fully-qualified # 👩‍🏭 woman factory worker +1F469 1F3FB 200D 1F3ED ; fully-qualified # 👩🏻‍🏭 woman factory worker: light skin tone +1F469 1F3FC 200D 1F3ED ; fully-qualified # 👩🏼‍🏭 woman factory worker: medium-light skin tone +1F469 1F3FD 200D 1F3ED ; fully-qualified # 👩🏽‍🏭 woman factory worker: medium skin tone +1F469 1F3FE 200D 1F3ED ; fully-qualified # 👩🏾‍🏭 woman factory worker: medium-dark skin tone +1F469 1F3FF 200D 1F3ED ; fully-qualified # 👩🏿‍🏭 woman factory worker: dark skin tone +1F468 200D 1F4BC ; fully-qualified # 👨‍💼 man office worker +1F468 1F3FB 200D 1F4BC ; fully-qualified # 👨🏻‍💼 man office worker: light skin tone +1F468 1F3FC 200D 1F4BC ; fully-qualified # 👨🏼‍💼 man office worker: medium-light skin tone +1F468 1F3FD 200D 1F4BC ; fully-qualified # 👨🏽‍💼 man office worker: medium skin tone +1F468 1F3FE 200D 1F4BC ; fully-qualified # 👨🏾‍💼 man office worker: medium-dark skin tone +1F468 1F3FF 200D 1F4BC ; fully-qualified # 👨🏿‍💼 man office worker: dark skin tone +1F469 200D 1F4BC ; fully-qualified # 👩‍💼 woman office worker +1F469 1F3FB 200D 1F4BC ; fully-qualified # 👩🏻‍💼 woman office worker: light skin tone +1F469 1F3FC 200D 1F4BC ; fully-qualified # 👩🏼‍💼 woman office worker: medium-light skin tone +1F469 1F3FD 200D 1F4BC ; fully-qualified # 👩🏽‍💼 woman office worker: medium skin tone +1F469 1F3FE 200D 1F4BC ; fully-qualified # 👩🏾‍💼 woman office worker: medium-dark skin tone +1F469 1F3FF 200D 1F4BC ; fully-qualified # 👩🏿‍💼 woman office worker: dark skin tone +1F468 200D 1F52C ; fully-qualified # 👨‍🔬 man scientist +1F468 1F3FB 200D 1F52C ; fully-qualified # 👨🏻‍🔬 man scientist: light skin tone +1F468 1F3FC 200D 1F52C ; fully-qualified # 👨🏼‍🔬 man scientist: medium-light skin tone +1F468 1F3FD 200D 1F52C ; fully-qualified # 👨🏽‍🔬 man scientist: medium skin tone +1F468 1F3FE 200D 1F52C ; fully-qualified # 👨🏾‍🔬 man scientist: medium-dark skin tone +1F468 1F3FF 200D 1F52C ; fully-qualified # 👨🏿‍🔬 man scientist: dark skin tone +1F469 200D 1F52C ; fully-qualified # 👩‍🔬 woman scientist +1F469 1F3FB 200D 1F52C ; fully-qualified # 👩🏻‍🔬 woman scientist: light skin tone +1F469 1F3FC 200D 1F52C ; fully-qualified # 👩🏼‍🔬 woman scientist: medium-light skin tone +1F469 1F3FD 200D 1F52C ; fully-qualified # 👩🏽‍🔬 woman scientist: medium skin tone +1F469 1F3FE 200D 1F52C ; fully-qualified # 👩🏾‍🔬 woman scientist: medium-dark skin tone +1F469 1F3FF 200D 1F52C ; fully-qualified # 👩🏿‍🔬 woman scientist: dark skin tone +1F468 200D 1F4BB ; fully-qualified # 👨‍💻 man technologist +1F468 1F3FB 200D 1F4BB ; fully-qualified # 👨🏻‍💻 man technologist: light skin tone +1F468 1F3FC 200D 1F4BB ; fully-qualified # 👨🏼‍💻 man technologist: medium-light skin tone +1F468 1F3FD 200D 1F4BB ; fully-qualified # 👨🏽‍💻 man technologist: medium skin tone +1F468 1F3FE 200D 1F4BB ; fully-qualified # 👨🏾‍💻 man technologist: medium-dark skin tone +1F468 1F3FF 200D 1F4BB ; fully-qualified # 👨🏿‍💻 man technologist: dark skin tone +1F469 200D 1F4BB ; fully-qualified # 👩‍💻 woman technologist +1F469 1F3FB 200D 1F4BB ; fully-qualified # 👩🏻‍💻 woman technologist: light skin tone +1F469 1F3FC 200D 1F4BB ; fully-qualified # 👩🏼‍💻 woman technologist: medium-light skin tone +1F469 1F3FD 200D 1F4BB ; fully-qualified # 👩🏽‍💻 woman technologist: medium skin tone +1F469 1F3FE 200D 1F4BB ; fully-qualified # 👩🏾‍💻 woman technologist: medium-dark skin tone +1F469 1F3FF 200D 1F4BB ; fully-qualified # 👩🏿‍💻 woman technologist: dark skin tone +1F468 200D 1F3A4 ; fully-qualified # 👨‍🎤 man singer +1F468 1F3FB 200D 1F3A4 ; fully-qualified # 👨🏻‍🎤 man singer: light skin tone +1F468 1F3FC 200D 1F3A4 ; fully-qualified # 👨🏼‍🎤 man singer: medium-light skin tone +1F468 1F3FD 200D 1F3A4 ; fully-qualified # 👨🏽‍🎤 man singer: medium skin tone +1F468 1F3FE 200D 1F3A4 ; fully-qualified # 👨🏾‍🎤 man singer: medium-dark skin tone +1F468 1F3FF 200D 1F3A4 ; fully-qualified # 👨🏿‍🎤 man singer: dark skin tone +1F469 200D 1F3A4 ; fully-qualified # 👩‍🎤 woman singer +1F469 1F3FB 200D 1F3A4 ; fully-qualified # 👩🏻‍🎤 woman singer: light skin tone +1F469 1F3FC 200D 1F3A4 ; fully-qualified # 👩🏼‍🎤 woman singer: medium-light skin tone +1F469 1F3FD 200D 1F3A4 ; fully-qualified # 👩🏽‍🎤 woman singer: medium skin tone +1F469 1F3FE 200D 1F3A4 ; fully-qualified # 👩🏾‍🎤 woman singer: medium-dark skin tone +1F469 1F3FF 200D 1F3A4 ; fully-qualified # 👩🏿‍🎤 woman singer: dark skin tone +1F468 200D 1F3A8 ; fully-qualified # 👨‍🎨 man artist +1F468 1F3FB 200D 1F3A8 ; fully-qualified # 👨🏻‍🎨 man artist: light skin tone +1F468 1F3FC 200D 1F3A8 ; fully-qualified # 👨🏼‍🎨 man artist: medium-light skin tone +1F468 1F3FD 200D 1F3A8 ; fully-qualified # 👨🏽‍🎨 man artist: medium skin tone +1F468 1F3FE 200D 1F3A8 ; fully-qualified # 👨🏾‍🎨 man artist: medium-dark skin tone +1F468 1F3FF 200D 1F3A8 ; fully-qualified # 👨🏿‍🎨 man artist: dark skin tone +1F469 200D 1F3A8 ; fully-qualified # 👩‍🎨 woman artist +1F469 1F3FB 200D 1F3A8 ; fully-qualified # 👩🏻‍🎨 woman artist: light skin tone +1F469 1F3FC 200D 1F3A8 ; fully-qualified # 👩🏼‍🎨 woman artist: medium-light skin tone +1F469 1F3FD 200D 1F3A8 ; fully-qualified # 👩🏽‍🎨 woman artist: medium skin tone +1F469 1F3FE 200D 1F3A8 ; fully-qualified # 👩🏾‍🎨 woman artist: medium-dark skin tone +1F469 1F3FF 200D 1F3A8 ; fully-qualified # 👩🏿‍🎨 woman artist: dark skin tone +1F468 200D 2708 FE0F ; fully-qualified # 👨‍✈️ man pilot +1F468 1F3FB 200D 2708 FE0F ; fully-qualified # 👨🏻‍✈️ man pilot: light skin tone +1F468 1F3FC 200D 2708 FE0F ; fully-qualified # 👨🏼‍✈️ man pilot: medium-light skin tone +1F468 1F3FD 200D 2708 FE0F ; fully-qualified # 👨🏽‍✈️ man pilot: medium skin tone +1F468 1F3FE 200D 2708 FE0F ; fully-qualified # 👨🏾‍✈️ man pilot: medium-dark skin tone +1F468 1F3FF 200D 2708 FE0F ; fully-qualified # 👨🏿‍✈️ man pilot: dark skin tone +1F469 200D 2708 FE0F ; fully-qualified # 👩‍✈️ woman pilot +1F469 1F3FB 200D 2708 FE0F ; fully-qualified # 👩🏻‍✈️ woman pilot: light skin tone +1F469 1F3FC 200D 2708 FE0F ; fully-qualified # 👩🏼‍✈️ woman pilot: medium-light skin tone +1F469 1F3FD 200D 2708 FE0F ; fully-qualified # 👩🏽‍✈️ woman pilot: medium skin tone +1F469 1F3FE 200D 2708 FE0F ; fully-qualified # 👩🏾‍✈️ woman pilot: medium-dark skin tone +1F469 1F3FF 200D 2708 FE0F ; fully-qualified # 👩🏿‍✈️ woman pilot: dark skin tone +1F468 200D 1F680 ; fully-qualified # 👨‍🚀 man astronaut +1F468 1F3FB 200D 1F680 ; fully-qualified # 👨🏻‍🚀 man astronaut: light skin tone +1F468 1F3FC 200D 1F680 ; fully-qualified # 👨🏼‍🚀 man astronaut: medium-light skin tone +1F468 1F3FD 200D 1F680 ; fully-qualified # 👨🏽‍🚀 man astronaut: medium skin tone +1F468 1F3FE 200D 1F680 ; fully-qualified # 👨🏾‍🚀 man astronaut: medium-dark skin tone +1F468 1F3FF 200D 1F680 ; fully-qualified # 👨🏿‍🚀 man astronaut: dark skin tone +1F469 200D 1F680 ; fully-qualified # 👩‍🚀 woman astronaut +1F469 1F3FB 200D 1F680 ; fully-qualified # 👩🏻‍🚀 woman astronaut: light skin tone +1F469 1F3FC 200D 1F680 ; fully-qualified # 👩🏼‍🚀 woman astronaut: medium-light skin tone +1F469 1F3FD 200D 1F680 ; fully-qualified # 👩🏽‍🚀 woman astronaut: medium skin tone +1F469 1F3FE 200D 1F680 ; fully-qualified # 👩🏾‍🚀 woman astronaut: medium-dark skin tone +1F469 1F3FF 200D 1F680 ; fully-qualified # 👩🏿‍🚀 woman astronaut: dark skin tone +1F468 200D 1F692 ; fully-qualified # 👨‍🚒 man firefighter +1F468 1F3FB 200D 1F692 ; fully-qualified # 👨🏻‍🚒 man firefighter: light skin tone +1F468 1F3FC 200D 1F692 ; fully-qualified # 👨🏼‍🚒 man firefighter: medium-light skin tone +1F468 1F3FD 200D 1F692 ; fully-qualified # 👨🏽‍🚒 man firefighter: medium skin tone +1F468 1F3FE 200D 1F692 ; fully-qualified # 👨🏾‍🚒 man firefighter: medium-dark skin tone +1F468 1F3FF 200D 1F692 ; fully-qualified # 👨🏿‍🚒 man firefighter: dark skin tone +1F469 200D 1F692 ; fully-qualified # 👩‍🚒 woman firefighter +1F469 1F3FB 200D 1F692 ; fully-qualified # 👩🏻‍🚒 woman firefighter: light skin tone +1F469 1F3FC 200D 1F692 ; fully-qualified # 👩🏼‍🚒 woman firefighter: medium-light skin tone +1F469 1F3FD 200D 1F692 ; fully-qualified # 👩🏽‍🚒 woman firefighter: medium skin tone +1F469 1F3FE 200D 1F692 ; fully-qualified # 👩🏾‍🚒 woman firefighter: medium-dark skin tone +1F469 1F3FF 200D 1F692 ; fully-qualified # 👩🏿‍🚒 woman firefighter: dark skin tone +1F46E ; fully-qualified # 👮 police officer +1F46E 1F3FB ; fully-qualified # 👮🏻 police officer: light skin tone +1F46E 1F3FC ; fully-qualified # 👮🏼 police officer: medium-light skin tone +1F46E 1F3FD ; fully-qualified # 👮🏽 police officer: medium skin tone +1F46E 1F3FE ; fully-qualified # 👮🏾 police officer: medium-dark skin tone +1F46E 1F3FF ; fully-qualified # 👮🏿 police officer: dark skin tone +#SUPPORT(prf) 1F46E 200D 2642 FE0F ; fully-qualified # 👮‍♂️ man police officer +#SUPPORT(prf) 1F46E 1F3FB 200D 2642 FE0F ; fully-qualified # 👮🏻‍♂️ man police officer: light skin tone +#SUPPORT(prf) 1F46E 1F3FC 200D 2642 FE0F ; fully-qualified # 👮🏼‍♂️ man police officer: medium-light skin tone +#SUPPORT(prf) 1F46E 1F3FD 200D 2642 FE0F ; fully-qualified # 👮🏽‍♂️ man police officer: medium skin tone +#SUPPORT(prf) 1F46E 1F3FE 200D 2642 FE0F ; fully-qualified # 👮🏾‍♂️ man police officer: medium-dark skin tone +#SUPPORT(prf) 1F46E 1F3FF 200D 2642 FE0F ; fully-qualified # 👮🏿‍♂️ man police officer: dark skin tone +#SUPPORT(prf) 1F46E 200D 2640 FE0F ; fully-qualified # 👮‍♀️ woman police officer +#SUPPORT(prf) 1F46E 1F3FB 200D 2640 FE0F ; fully-qualified # 👮🏻‍♀️ woman police officer: light skin tone +#SUPPORT(prf) 1F46E 1F3FC 200D 2640 FE0F ; fully-qualified # 👮🏼‍♀️ woman police officer: medium-light skin tone +#SUPPORT(prf) 1F46E 1F3FD 200D 2640 FE0F ; fully-qualified # 👮🏽‍♀️ woman police officer: medium skin tone +#SUPPORT(prf) 1F46E 1F3FE 200D 2640 FE0F ; fully-qualified # 👮🏾‍♀️ woman police officer: medium-dark skin tone +#SUPPORT(prf) 1F46E 1F3FF 200D 2640 FE0F ; fully-qualified # 👮🏿‍♀️ woman police officer: dark skin tone +1F575 FE0F ; fully-qualified # 🕵️ detective +1F575 1F3FB ; fully-qualified # 🕵🏻 detective: light skin tone +1F575 1F3FC ; fully-qualified # 🕵🏼 detective: medium-light skin tone +1F575 1F3FD ; fully-qualified # 🕵🏽 detective: medium skin tone +1F575 1F3FE ; fully-qualified # 🕵🏾 detective: medium-dark skin tone +1F575 1F3FF ; fully-qualified # 🕵🏿 detective: dark skin tone +#SUPPORT(prf) 1F575 FE0F 200D 2642 FE0F ; fully-qualified # 🕵️‍♂️ man detective +#SUPPORT(prf) 1F575 1F3FB 200D 2642 FE0F ; fully-qualified # 🕵🏻‍♂️ man detective: light skin tone +#SUPPORT(prf) 1F575 1F3FC 200D 2642 FE0F ; fully-qualified # 🕵🏼‍♂️ man detective: medium-light skin tone +#SUPPORT(prf) 1F575 1F3FD 200D 2642 FE0F ; fully-qualified # 🕵🏽‍♂️ man detective: medium skin tone +#SUPPORT(prf) 1F575 1F3FE 200D 2642 FE0F ; fully-qualified # 🕵🏾‍♂️ man detective: medium-dark skin tone +#SUPPORT(prf) 1F575 1F3FF 200D 2642 FE0F ; fully-qualified # 🕵🏿‍♂️ man detective: dark skin tone +#SUPPORT(prf) 1F575 FE0F 200D 2640 FE0F ; fully-qualified # 🕵️‍♀️ woman detective +#SUPPORT(prf) 1F575 1F3FB 200D 2640 FE0F ; fully-qualified # 🕵🏻‍♀️ woman detective: light skin tone +#SUPPORT(prf) 1F575 1F3FC 200D 2640 FE0F ; fully-qualified # 🕵🏼‍♀️ woman detective: medium-light skin tone +#SUPPORT(prf) 1F575 1F3FD 200D 2640 FE0F ; fully-qualified # 🕵🏽‍♀️ woman detective: medium skin tone +#SUPPORT(prf) 1F575 1F3FE 200D 2640 FE0F ; fully-qualified # 🕵🏾‍♀️ woman detective: medium-dark skin tone +#SUPPORT(prf) 1F575 1F3FF 200D 2640 FE0F ; fully-qualified # 🕵🏿‍♀️ woman detective: dark skin tone +1F482 ; fully-qualified # 💂 guard +1F482 1F3FB ; fully-qualified # 💂🏻 guard: light skin tone +1F482 1F3FC ; fully-qualified # 💂🏼 guard: medium-light skin tone +1F482 1F3FD ; fully-qualified # 💂🏽 guard: medium skin tone +1F482 1F3FE ; fully-qualified # 💂🏾 guard: medium-dark skin tone +1F482 1F3FF ; fully-qualified # 💂🏿 guard: dark skin tone +#SUPPORT(prf) 1F482 200D 2642 FE0F ; fully-qualified # 💂‍♂️ man guard +#SUPPORT(prf) 1F482 1F3FB 200D 2642 FE0F ; fully-qualified # 💂🏻‍♂️ man guard: light skin tone +#SUPPORT(prf) 1F482 1F3FC 200D 2642 FE0F ; fully-qualified # 💂🏼‍♂️ man guard: medium-light skin tone +#SUPPORT(prf) 1F482 1F3FD 200D 2642 FE0F ; fully-qualified # 💂🏽‍♂️ man guard: medium skin tone +#SUPPORT(prf) 1F482 1F3FE 200D 2642 FE0F ; fully-qualified # 💂🏾‍♂️ man guard: medium-dark skin tone +#SUPPORT(prf) 1F482 1F3FF 200D 2642 FE0F ; fully-qualified # 💂🏿‍♂️ man guard: dark skin tone +#SUPPORT(prf) 1F482 200D 2640 FE0F ; fully-qualified # 💂‍♀️ woman guard +#SUPPORT(prf) 1F482 1F3FB 200D 2640 FE0F ; fully-qualified # 💂🏻‍♀️ woman guard: light skin tone +#SUPPORT(prf) 1F482 1F3FC 200D 2640 FE0F ; fully-qualified # 💂🏼‍♀️ woman guard: medium-light skin tone +#SUPPORT(prf) 1F482 1F3FD 200D 2640 FE0F ; fully-qualified # 💂🏽‍♀️ woman guard: medium skin tone +#SUPPORT(prf) 1F482 1F3FE 200D 2640 FE0F ; fully-qualified # 💂🏾‍♀️ woman guard: medium-dark skin tone +#SUPPORT(prf) 1F482 1F3FF 200D 2640 FE0F ; fully-qualified # 💂🏿‍♀️ woman guard: dark skin tone +1F477 ; fully-qualified # 👷 construction worker +1F477 1F3FB ; fully-qualified # 👷🏻 construction worker: light skin tone +1F477 1F3FC ; fully-qualified # 👷🏼 construction worker: medium-light skin tone +1F477 1F3FD ; fully-qualified # 👷🏽 construction worker: medium skin tone +1F477 1F3FE ; fully-qualified # 👷🏾 construction worker: medium-dark skin tone +1F477 1F3FF ; fully-qualified # 👷🏿 construction worker: dark skin tone +#SUPPORT(prf) 1F477 200D 2642 FE0F ; fully-qualified # 👷‍♂️ man construction worker +#SUPPORT(prf) 1F477 1F3FB 200D 2642 FE0F ; fully-qualified # 👷🏻‍♂️ man construction worker: light skin tone +#SUPPORT(prf) 1F477 1F3FC 200D 2642 FE0F ; fully-qualified # 👷🏼‍♂️ man construction worker: medium-light skin tone +#SUPPORT(prf) 1F477 1F3FD 200D 2642 FE0F ; fully-qualified # 👷🏽‍♂️ man construction worker: medium skin tone +#SUPPORT(prf) 1F477 1F3FE 200D 2642 FE0F ; fully-qualified # 👷🏾‍♂️ man construction worker: medium-dark skin tone +#SUPPORT(prf) 1F477 1F3FF 200D 2642 FE0F ; fully-qualified # 👷🏿‍♂️ man construction worker: dark skin tone +#SUPPORT(prf) 1F477 200D 2640 FE0F ; fully-qualified # 👷‍♀️ woman construction worker +#SUPPORT(prf) 1F477 1F3FB 200D 2640 FE0F ; fully-qualified # 👷🏻‍♀️ woman construction worker: light skin tone +#SUPPORT(prf) 1F477 1F3FC 200D 2640 FE0F ; fully-qualified # 👷🏼‍♀️ woman construction worker: medium-light skin tone +#SUPPORT(prf) 1F477 1F3FD 200D 2640 FE0F ; fully-qualified # 👷🏽‍♀️ woman construction worker: medium skin tone +#SUPPORT(prf) 1F477 1F3FE 200D 2640 FE0F ; fully-qualified # 👷🏾‍♀️ woman construction worker: medium-dark skin tone +#SUPPORT(prf) 1F477 1F3FF 200D 2640 FE0F ; fully-qualified # 👷🏿‍♀️ woman construction worker: dark skin tone +1F934 ; fully-qualified # 🤴 prince +1F934 1F3FB ; fully-qualified # 🤴🏻 prince: light skin tone +1F934 1F3FC ; fully-qualified # 🤴🏼 prince: medium-light skin tone +1F934 1F3FD ; fully-qualified # 🤴🏽 prince: medium skin tone +1F934 1F3FE ; fully-qualified # 🤴🏾 prince: medium-dark skin tone +1F934 1F3FF ; fully-qualified # 🤴🏿 prince: dark skin tone +1F478 ; fully-qualified # 👸 princess +1F478 1F3FB ; fully-qualified # 👸🏻 princess: light skin tone +1F478 1F3FC ; fully-qualified # 👸🏼 princess: medium-light skin tone +1F478 1F3FD ; fully-qualified # 👸🏽 princess: medium skin tone +1F478 1F3FE ; fully-qualified # 👸🏾 princess: medium-dark skin tone +1F478 1F3FF ; fully-qualified # 👸🏿 princess: dark skin tone +1F473 ; fully-qualified # 👳 person wearing turban +1F473 1F3FB ; fully-qualified # 👳🏻 person wearing turban: light skin tone +1F473 1F3FC ; fully-qualified # 👳🏼 person wearing turban: medium-light skin tone +1F473 1F3FD ; fully-qualified # 👳🏽 person wearing turban: medium skin tone +1F473 1F3FE ; fully-qualified # 👳🏾 person wearing turban: medium-dark skin tone +1F473 1F3FF ; fully-qualified # 👳🏿 person wearing turban: dark skin tone +#SUPPORT(prf) 1F473 200D 2642 FE0F ; fully-qualified # 👳‍♂️ man wearing turban +#SUPPORT(prf) 1F473 1F3FB 200D 2642 FE0F ; fully-qualified # 👳🏻‍♂️ man wearing turban: light skin tone +#SUPPORT(prf) 1F473 1F3FC 200D 2642 FE0F ; fully-qualified # 👳🏼‍♂️ man wearing turban: medium-light skin tone +#SUPPORT(prf) 1F473 1F3FD 200D 2642 FE0F ; fully-qualified # 👳🏽‍♂️ man wearing turban: medium skin tone +#SUPPORT(prf) 1F473 1F3FE 200D 2642 FE0F ; fully-qualified # 👳🏾‍♂️ man wearing turban: medium-dark skin tone +#SUPPORT(prf) 1F473 1F3FF 200D 2642 FE0F ; fully-qualified # 👳🏿‍♂️ man wearing turban: dark skin tone +#SUPPORT(prf) 1F473 200D 2640 FE0F ; fully-qualified # 👳‍♀️ woman wearing turban +#SUPPORT(prf) 1F473 1F3FB 200D 2640 FE0F ; fully-qualified # 👳🏻‍♀️ woman wearing turban: light skin tone +#SUPPORT(prf) 1F473 1F3FC 200D 2640 FE0F ; fully-qualified # 👳🏼‍♀️ woman wearing turban: medium-light skin tone +#SUPPORT(prf) 1F473 1F3FD 200D 2640 FE0F ; fully-qualified # 👳🏽‍♀️ woman wearing turban: medium skin tone +#SUPPORT(prf) 1F473 1F3FE 200D 2640 FE0F ; fully-qualified # 👳🏾‍♀️ woman wearing turban: medium-dark skin tone +#SUPPORT(prf) 1F473 1F3FF 200D 2640 FE0F ; fully-qualified # 👳🏿‍♀️ woman wearing turban: dark skin tone +1F472 ; fully-qualified # 👲 man with Chinese cap +1F472 1F3FB ; fully-qualified # 👲🏻 man with Chinese cap: light skin tone +1F472 1F3FC ; fully-qualified # 👲🏼 man with Chinese cap: medium-light skin tone +1F472 1F3FD ; fully-qualified # 👲🏽 man with Chinese cap: medium skin tone +1F472 1F3FE ; fully-qualified # 👲🏾 man with Chinese cap: medium-dark skin tone +1F472 1F3FF ; fully-qualified # 👲🏿 man with Chinese cap: dark skin tone +1F9D5 ; fully-qualified # 🧕 woman with headscarf +1F9D5 1F3FB ; fully-qualified # 🧕🏻 woman with headscarf: light skin tone +1F9D5 1F3FC ; fully-qualified # 🧕🏼 woman with headscarf: medium-light skin tone +1F9D5 1F3FD ; fully-qualified # 🧕🏽 woman with headscarf: medium skin tone +1F9D5 1F3FE ; fully-qualified # 🧕🏾 woman with headscarf: medium-dark skin tone +1F9D5 1F3FF ; fully-qualified # 🧕🏿 woman with headscarf: dark skin tone +1F935 ; fully-qualified # 🤵 man in tuxedo +1F935 1F3FB ; fully-qualified # 🤵🏻 man in tuxedo: light skin tone +1F935 1F3FC ; fully-qualified # 🤵🏼 man in tuxedo: medium-light skin tone +1F935 1F3FD ; fully-qualified # 🤵🏽 man in tuxedo: medium skin tone +1F935 1F3FE ; fully-qualified # 🤵🏾 man in tuxedo: medium-dark skin tone +1F935 1F3FF ; fully-qualified # 🤵🏿 man in tuxedo: dark skin tone +1F470 ; fully-qualified # 👰 bride with veil +1F470 1F3FB ; fully-qualified # 👰🏻 bride with veil: light skin tone +1F470 1F3FC ; fully-qualified # 👰🏼 bride with veil: medium-light skin tone +1F470 1F3FD ; fully-qualified # 👰🏽 bride with veil: medium skin tone +1F470 1F3FE ; fully-qualified # 👰🏾 bride with veil: medium-dark skin tone +1F470 1F3FF ; fully-qualified # 👰🏿 bride with veil: dark skin tone +1F930 ; fully-qualified # 🤰 pregnant woman +1F930 1F3FB ; fully-qualified # 🤰🏻 pregnant woman: light skin tone +1F930 1F3FC ; fully-qualified # 🤰🏼 pregnant woman: medium-light skin tone +1F930 1F3FD ; fully-qualified # 🤰🏽 pregnant woman: medium skin tone +1F930 1F3FE ; fully-qualified # 🤰🏾 pregnant woman: medium-dark skin tone +1F930 1F3FF ; fully-qualified # 🤰🏿 pregnant woman: dark skin tone +1F931 ; fully-qualified # 🤱 breast-feeding +1F931 1F3FB ; fully-qualified # 🤱🏻 breast-feeding: light skin tone +1F931 1F3FC ; fully-qualified # 🤱🏼 breast-feeding: medium-light skin tone +1F931 1F3FD ; fully-qualified # 🤱🏽 breast-feeding: medium skin tone +1F931 1F3FE ; fully-qualified # 🤱🏾 breast-feeding: medium-dark skin tone +1F931 1F3FF ; fully-qualified # 🤱🏿 breast-feeding: dark skin tone + +# subgroup: person-fantasy +1F47C ; fully-qualified # 👼 baby angel +1F47C 1F3FB ; fully-qualified # 👼🏻 baby angel: light skin tone +1F47C 1F3FC ; fully-qualified # 👼🏼 baby angel: medium-light skin tone +1F47C 1F3FD ; fully-qualified # 👼🏽 baby angel: medium skin tone +1F47C 1F3FE ; fully-qualified # 👼🏾 baby angel: medium-dark skin tone +1F47C 1F3FF ; fully-qualified # 👼🏿 baby angel: dark skin tone +1F385 ; fully-qualified # 🎅 Santa Claus +1F385 1F3FB ; fully-qualified # 🎅🏻 Santa Claus: light skin tone +1F385 1F3FC ; fully-qualified # 🎅🏼 Santa Claus: medium-light skin tone +1F385 1F3FD ; fully-qualified # 🎅🏽 Santa Claus: medium skin tone +1F385 1F3FE ; fully-qualified # 🎅🏾 Santa Claus: medium-dark skin tone +1F385 1F3FF ; fully-qualified # 🎅🏿 Santa Claus: dark skin tone +1F936 ; fully-qualified # 🤶 Mrs. Claus +1F936 1F3FB ; fully-qualified # 🤶🏻 Mrs. Claus: light skin tone +1F936 1F3FC ; fully-qualified # 🤶🏼 Mrs. Claus: medium-light skin tone +1F936 1F3FD ; fully-qualified # 🤶🏽 Mrs. Claus: medium skin tone +1F936 1F3FE ; fully-qualified # 🤶🏾 Mrs. Claus: medium-dark skin tone +1F936 1F3FF ; fully-qualified # 🤶🏿 Mrs. Claus: dark skin tone +#SUPPORT(prf) 1F9B8 ; fully-qualified # 🦸 superhero +#SUPPORT(prf) 1F9B8 1F3FB ; fully-qualified # 🦸🏻 superhero: light skin tone +#SUPPORT(prf) 1F9B8 1F3FC ; fully-qualified # 🦸🏼 superhero: medium-light skin tone +#SUPPORT(prf) 1F9B8 1F3FD ; fully-qualified # 🦸🏽 superhero: medium skin tone +#SUPPORT(prf) 1F9B8 1F3FE ; fully-qualified # 🦸🏾 superhero: medium-dark skin tone +#SUPPORT(prf) 1F9B8 1F3FF ; fully-qualified # 🦸🏿 superhero: dark skin tone +#SUPPORT(prf) 1F9B8 200D 2642 FE0F ; fully-qualified # 🦸‍♂️ man superhero +#SUPPORT(prf) 1F9B8 1F3FB 200D 2642 FE0F ; fully-qualified # 🦸🏻‍♂️ man superhero: light skin tone +#SUPPORT(prf) 1F9B8 1F3FC 200D 2642 FE0F ; fully-qualified # 🦸🏼‍♂️ man superhero: medium-light skin tone +#SUPPORT(prf) 1F9B8 1F3FD 200D 2642 FE0F ; fully-qualified # 🦸🏽‍♂️ man superhero: medium skin tone +#SUPPORT(prf) 1F9B8 1F3FE 200D 2642 FE0F ; fully-qualified # 🦸🏾‍♂️ man superhero: medium-dark skin tone +#SUPPORT(prf) 1F9B8 1F3FF 200D 2642 FE0F ; fully-qualified # 🦸🏿‍♂️ man superhero: dark skin tone +#SUPPORT(prf) 1F9B8 200D 2640 FE0F ; fully-qualified # 🦸‍♀️ woman superhero +#SUPPORT(prf) 1F9B8 1F3FB 200D 2640 FE0F ; fully-qualified # 🦸🏻‍♀️ woman superhero: light skin tone +#SUPPORT(prf) 1F9B8 1F3FC 200D 2640 FE0F ; fully-qualified # 🦸🏼‍♀️ woman superhero: medium-light skin tone +#SUPPORT(prf) 1F9B8 1F3FD 200D 2640 FE0F ; fully-qualified # 🦸🏽‍♀️ woman superhero: medium skin tone +#SUPPORT(prf) 1F9B8 1F3FE 200D 2640 FE0F ; fully-qualified # 🦸🏾‍♀️ woman superhero: medium-dark skin tone +#SUPPORT(prf) 1F9B8 1F3FF 200D 2640 FE0F ; fully-qualified # 🦸🏿‍♀️ woman superhero: dark skin tone +#SUPPORT(prf) 1F9B9 ; fully-qualified # 🦹 supervillain +#SUPPORT(prf) 1F9B9 1F3FB ; fully-qualified # 🦹🏻 supervillain: light skin tone +#SUPPORT(prf) 1F9B9 1F3FC ; fully-qualified # 🦹🏼 supervillain: medium-light skin tone +#SUPPORT(prf) 1F9B9 1F3FD ; fully-qualified # 🦹🏽 supervillain: medium skin tone +#SUPPORT(prf) 1F9B9 1F3FE ; fully-qualified # 🦹🏾 supervillain: medium-dark skin tone +#SUPPORT(prf) 1F9B9 1F3FF ; fully-qualified # 🦹🏿 supervillain: dark skin tone +#SUPPORT(prf) 1F9B9 200D 2642 FE0F ; fully-qualified # 🦹‍♂️ man supervillain +#SUPPORT(prf) 1F9B9 1F3FB 200D 2642 FE0F ; fully-qualified # 🦹🏻‍♂️ man supervillain: light skin tone +#SUPPORT(prf) 1F9B9 1F3FC 200D 2642 FE0F ; fully-qualified # 🦹🏼‍♂️ man supervillain: medium-light skin tone +#SUPPORT(prf) 1F9B9 1F3FD 200D 2642 FE0F ; fully-qualified # 🦹🏽‍♂️ man supervillain: medium skin tone +#SUPPORT(prf) 1F9B9 1F3FE 200D 2642 FE0F ; fully-qualified # 🦹🏾‍♂️ man supervillain: medium-dark skin tone +#SUPPORT(prf) 1F9B9 1F3FF 200D 2642 FE0F ; fully-qualified # 🦹🏿‍♂️ man supervillain: dark skin tone +#SUPPORT(prf) 1F9B9 200D 2640 FE0F ; fully-qualified # 🦹‍♀️ woman supervillain +#SUPPORT(prf) 1F9B9 1F3FB 200D 2640 FE0F ; fully-qualified # 🦹🏻‍♀️ woman supervillain: light skin tone +#SUPPORT(prf) 1F9B9 1F3FC 200D 2640 FE0F ; fully-qualified # 🦹🏼‍♀️ woman supervillain: medium-light skin tone +#SUPPORT(prf) 1F9B9 1F3FD 200D 2640 FE0F ; fully-qualified # 🦹🏽‍♀️ woman supervillain: medium skin tone +#SUPPORT(prf) 1F9B9 1F3FE 200D 2640 FE0F ; fully-qualified # 🦹🏾‍♀️ woman supervillain: medium-dark skin tone +#SUPPORT(prf) 1F9B9 1F3FF 200D 2640 FE0F ; fully-qualified # 🦹🏿‍♀️ woman supervillain: dark skin tone +1F9D9 ; fully-qualified # 🧙 mage +1F9D9 1F3FB ; fully-qualified # 🧙🏻 mage: light skin tone +1F9D9 1F3FC ; fully-qualified # 🧙🏼 mage: medium-light skin tone +1F9D9 1F3FD ; fully-qualified # 🧙🏽 mage: medium skin tone +1F9D9 1F3FE ; fully-qualified # 🧙🏾 mage: medium-dark skin tone +1F9D9 1F3FF ; fully-qualified # 🧙🏿 mage: dark skin tone +#SUPPORT(prf) 1F9D9 200D 2642 FE0F ; fully-qualified # 🧙‍♂️ man mage +#SUPPORT(prf) 1F9D9 1F3FB 200D 2642 FE0F ; fully-qualified # 🧙🏻‍♂️ man mage: light skin tone +#SUPPORT(prf) 1F9D9 1F3FC 200D 2642 FE0F ; fully-qualified # 🧙🏼‍♂️ man mage: medium-light skin tone +#SUPPORT(prf) 1F9D9 1F3FD 200D 2642 FE0F ; fully-qualified # 🧙🏽‍♂️ man mage: medium skin tone +#SUPPORT(prf) 1F9D9 1F3FE 200D 2642 FE0F ; fully-qualified # 🧙🏾‍♂️ man mage: medium-dark skin tone +#SUPPORT(prf) 1F9D9 1F3FF 200D 2642 FE0F ; fully-qualified # 🧙🏿‍♂️ man mage: dark skin tone +#SUPPORT(prf) 1F9D9 200D 2640 FE0F ; fully-qualified # 🧙‍♀️ woman mage +#SUPPORT(prf) 1F9D9 1F3FB 200D 2640 FE0F ; fully-qualified # 🧙🏻‍♀️ woman mage: light skin tone +#SUPPORT(prf) 1F9D9 1F3FC 200D 2640 FE0F ; fully-qualified # 🧙🏼‍♀️ woman mage: medium-light skin tone +#SUPPORT(prf) 1F9D9 1F3FD 200D 2640 FE0F ; fully-qualified # 🧙🏽‍♀️ woman mage: medium skin tone +#SUPPORT(prf) 1F9D9 1F3FE 200D 2640 FE0F ; fully-qualified # 🧙🏾‍♀️ woman mage: medium-dark skin tone +#SUPPORT(prf) 1F9D9 1F3FF 200D 2640 FE0F ; fully-qualified # 🧙🏿‍♀️ woman mage: dark skin tone +1F9DA ; fully-qualified # 🧚 fairy +1F9DA 1F3FB ; fully-qualified # 🧚🏻 fairy: light skin tone +1F9DA 1F3FC ; fully-qualified # 🧚🏼 fairy: medium-light skin tone +1F9DA 1F3FD ; fully-qualified # 🧚🏽 fairy: medium skin tone +1F9DA 1F3FE ; fully-qualified # 🧚🏾 fairy: medium-dark skin tone +1F9DA 1F3FF ; fully-qualified # 🧚🏿 fairy: dark skin tone +#SUPPORT(prf) 1F9DA 200D 2642 FE0F ; fully-qualified # 🧚‍♂️ man fairy +#SUPPORT(prf) 1F9DA 1F3FB 200D 2642 FE0F ; fully-qualified # 🧚🏻‍♂️ man fairy: light skin tone +#SUPPORT(prf) 1F9DA 1F3FC 200D 2642 FE0F ; fully-qualified # 🧚🏼‍♂️ man fairy: medium-light skin tone +#SUPPORT(prf) 1F9DA 1F3FD 200D 2642 FE0F ; fully-qualified # 🧚🏽‍♂️ man fairy: medium skin tone +#SUPPORT(prf) 1F9DA 1F3FE 200D 2642 FE0F ; fully-qualified # 🧚🏾‍♂️ man fairy: medium-dark skin tone +#SUPPORT(prf) 1F9DA 1F3FF 200D 2642 FE0F ; fully-qualified # 🧚🏿‍♂️ man fairy: dark skin tone +#SUPPORT(prf) 1F9DA 200D 2640 FE0F ; fully-qualified # 🧚‍♀️ woman fairy +#SUPPORT(prf) 1F9DA 1F3FB 200D 2640 FE0F ; fully-qualified # 🧚🏻‍♀️ woman fairy: light skin tone +#SUPPORT(prf) 1F9DA 1F3FC 200D 2640 FE0F ; fully-qualified # 🧚🏼‍♀️ woman fairy: medium-light skin tone +#SUPPORT(prf) 1F9DA 1F3FD 200D 2640 FE0F ; fully-qualified # 🧚🏽‍♀️ woman fairy: medium skin tone +#SUPPORT(prf) 1F9DA 1F3FE 200D 2640 FE0F ; fully-qualified # 🧚🏾‍♀️ woman fairy: medium-dark skin tone +#SUPPORT(prf) 1F9DA 1F3FF 200D 2640 FE0F ; fully-qualified # 🧚🏿‍♀️ woman fairy: dark skin tone +1F9DB ; fully-qualified # 🧛 vampire +1F9DB 1F3FB ; fully-qualified # 🧛🏻 vampire: light skin tone +1F9DB 1F3FC ; fully-qualified # 🧛🏼 vampire: medium-light skin tone +1F9DB 1F3FD ; fully-qualified # 🧛🏽 vampire: medium skin tone +1F9DB 1F3FE ; fully-qualified # 🧛🏾 vampire: medium-dark skin tone +1F9DB 1F3FF ; fully-qualified # 🧛🏿 vampire: dark skin tone +#SUPPORT(prf) 1F9DB 200D 2642 FE0F ; fully-qualified # 🧛‍♂️ man vampire +#SUPPORT(prf) 1F9DB 1F3FB 200D 2642 FE0F ; fully-qualified # 🧛🏻‍♂️ man vampire: light skin tone +#SUPPORT(prf) 1F9DB 1F3FC 200D 2642 FE0F ; fully-qualified # 🧛🏼‍♂️ man vampire: medium-light skin tone +#SUPPORT(prf) 1F9DB 1F3FD 200D 2642 FE0F ; fully-qualified # 🧛🏽‍♂️ man vampire: medium skin tone +#SUPPORT(prf) 1F9DB 1F3FE 200D 2642 FE0F ; fully-qualified # 🧛🏾‍♂️ man vampire: medium-dark skin tone +#SUPPORT(prf) 1F9DB 1F3FF 200D 2642 FE0F ; fully-qualified # 🧛🏿‍♂️ man vampire: dark skin tone +#SUPPORT(prf) 1F9DB 200D 2640 FE0F ; fully-qualified # 🧛‍♀️ woman vampire +#SUPPORT(prf) 1F9DB 1F3FB 200D 2640 FE0F ; fully-qualified # 🧛🏻‍♀️ woman vampire: light skin tone +#SUPPORT(prf) 1F9DB 1F3FC 200D 2640 FE0F ; fully-qualified # 🧛🏼‍♀️ woman vampire: medium-light skin tone +#SUPPORT(prf) 1F9DB 1F3FD 200D 2640 FE0F ; fully-qualified # 🧛🏽‍♀️ woman vampire: medium skin tone +#SUPPORT(prf) 1F9DB 1F3FE 200D 2640 FE0F ; fully-qualified # 🧛🏾‍♀️ woman vampire: medium-dark skin tone +#SUPPORT(prf) 1F9DB 1F3FF 200D 2640 FE0F ; fully-qualified # 🧛🏿‍♀️ woman vampire: dark skin tone +1F9DC ; fully-qualified # 🧜 merperson +1F9DC 1F3FB ; fully-qualified # 🧜🏻 merperson: light skin tone +1F9DC 1F3FC ; fully-qualified # 🧜🏼 merperson: medium-light skin tone +1F9DC 1F3FD ; fully-qualified # 🧜🏽 merperson: medium skin tone +1F9DC 1F3FE ; fully-qualified # 🧜🏾 merperson: medium-dark skin tone +1F9DC 1F3FF ; fully-qualified # 🧜🏿 merperson: dark skin tone +#SUPPORT(prf) 1F9DC 200D 2642 FE0F ; fully-qualified # 🧜‍♂️ merman +#SUPPORT(prf) 1F9DC 1F3FB 200D 2642 FE0F ; fully-qualified # 🧜🏻‍♂️ merman: light skin tone +#SUPPORT(prf) 1F9DC 1F3FC 200D 2642 FE0F ; fully-qualified # 🧜🏼‍♂️ merman: medium-light skin tone +#SUPPORT(prf) 1F9DC 1F3FD 200D 2642 FE0F ; fully-qualified # 🧜🏽‍♂️ merman: medium skin tone +#SUPPORT(prf) 1F9DC 1F3FE 200D 2642 FE0F ; fully-qualified # 🧜🏾‍♂️ merman: medium-dark skin tone +#SUPPORT(prf) 1F9DC 1F3FF 200D 2642 FE0F ; fully-qualified # 🧜🏿‍♂️ merman: dark skin tone +#SUPPORT(prf) 1F9DC 200D 2640 FE0F ; fully-qualified # 🧜‍♀️ mermaid +#SUPPORT(prf) 1F9DC 1F3FB 200D 2640 FE0F ; fully-qualified # 🧜🏻‍♀️ mermaid: light skin tone +#SUPPORT(prf) 1F9DC 1F3FC 200D 2640 FE0F ; fully-qualified # 🧜🏼‍♀️ mermaid: medium-light skin tone +#SUPPORT(prf) 1F9DC 1F3FD 200D 2640 FE0F ; fully-qualified # 🧜🏽‍♀️ mermaid: medium skin tone +#SUPPORT(prf) 1F9DC 1F3FE 200D 2640 FE0F ; fully-qualified # 🧜🏾‍♀️ mermaid: medium-dark skin tone +#SUPPORT(prf) 1F9DC 1F3FF 200D 2640 FE0F ; fully-qualified # 🧜🏿‍♀️ mermaid: dark skin tone +1F9DD ; fully-qualified # 🧝 elf +1F9DD 1F3FB ; fully-qualified # 🧝🏻 elf: light skin tone +1F9DD 1F3FC ; fully-qualified # 🧝🏼 elf: medium-light skin tone +1F9DD 1F3FD ; fully-qualified # 🧝🏽 elf: medium skin tone +1F9DD 1F3FE ; fully-qualified # 🧝🏾 elf: medium-dark skin tone +1F9DD 1F3FF ; fully-qualified # 🧝🏿 elf: dark skin tone +#SUPPORT(prf) 1F9DD 200D 2642 FE0F ; fully-qualified # 🧝‍♂️ man elf +#SUPPORT(prf) 1F9DD 1F3FB 200D 2642 FE0F ; fully-qualified # 🧝🏻‍♂️ man elf: light skin tone +#SUPPORT(prf) 1F9DD 1F3FC 200D 2642 FE0F ; fully-qualified # 🧝🏼‍♂️ man elf: medium-light skin tone +#SUPPORT(prf) 1F9DD 1F3FD 200D 2642 FE0F ; fully-qualified # 🧝🏽‍♂️ man elf: medium skin tone +#SUPPORT(prf) 1F9DD 1F3FE 200D 2642 FE0F ; fully-qualified # 🧝🏾‍♂️ man elf: medium-dark skin tone +#SUPPORT(prf) 1F9DD 1F3FF 200D 2642 FE0F ; fully-qualified # 🧝🏿‍♂️ man elf: dark skin tone +#SUPPORT(prf) 1F9DD 200D 2640 FE0F ; fully-qualified # 🧝‍♀️ woman elf +#SUPPORT(prf) 1F9DD 1F3FB 200D 2640 FE0F ; fully-qualified # 🧝🏻‍♀️ woman elf: light skin tone +#SUPPORT(prf) 1F9DD 1F3FC 200D 2640 FE0F ; fully-qualified # 🧝🏼‍♀️ woman elf: medium-light skin tone +#SUPPORT(prf) 1F9DD 1F3FD 200D 2640 FE0F ; fully-qualified # 🧝🏽‍♀️ woman elf: medium skin tone +#SUPPORT(prf) 1F9DD 1F3FE 200D 2640 FE0F ; fully-qualified # 🧝🏾‍♀️ woman elf: medium-dark skin tone +#SUPPORT(prf) 1F9DD 1F3FF 200D 2640 FE0F ; fully-qualified # 🧝🏿‍♀️ woman elf: dark skin tone +1F9DE ; fully-qualified # 🧞 genie +#SUPPORT(prf) 1F9DE 200D 2642 FE0F ; fully-qualified # 🧞‍♂️ man genie +#SUPPORT(prf) 1F9DE 200D 2640 FE0F ; fully-qualified # 🧞‍♀️ woman genie +1F9DF ; fully-qualified # 🧟 zombie +#SUPPORT(prf) 1F9DF 200D 2642 FE0F ; fully-qualified # 🧟‍♂️ man zombie +#SUPPORT(prf) 1F9DF 200D 2640 FE0F ; fully-qualified # 🧟‍♀️ woman zombie + +# subgroup: person-activity +1F486 ; fully-qualified # 💆 person getting massage +1F486 1F3FB ; fully-qualified # 💆🏻 person getting massage: light skin tone +1F486 1F3FC ; fully-qualified # 💆🏼 person getting massage: medium-light skin tone +1F486 1F3FD ; fully-qualified # 💆🏽 person getting massage: medium skin tone +1F486 1F3FE ; fully-qualified # 💆🏾 person getting massage: medium-dark skin tone +1F486 1F3FF ; fully-qualified # 💆🏿 person getting massage: dark skin tone +#SUPPORT(prf) 1F486 200D 2642 FE0F ; fully-qualified # 💆‍♂️ man getting massage +#SUPPORT(prf) 1F486 1F3FB 200D 2642 FE0F ; fully-qualified # 💆🏻‍♂️ man getting massage: light skin tone +#SUPPORT(prf) 1F486 1F3FC 200D 2642 FE0F ; fully-qualified # 💆🏼‍♂️ man getting massage: medium-light skin tone +#SUPPORT(prf) 1F486 1F3FD 200D 2642 FE0F ; fully-qualified # 💆🏽‍♂️ man getting massage: medium skin tone +#SUPPORT(prf) 1F486 1F3FE 200D 2642 FE0F ; fully-qualified # 💆🏾‍♂️ man getting massage: medium-dark skin tone +#SUPPORT(prf) 1F486 1F3FF 200D 2642 FE0F ; fully-qualified # 💆🏿‍♂️ man getting massage: dark skin tone +#SUPPORT(prf) 1F486 200D 2640 FE0F ; fully-qualified # 💆‍♀️ woman getting massage +#SUPPORT(prf) 1F486 1F3FB 200D 2640 FE0F ; fully-qualified # 💆🏻‍♀️ woman getting massage: light skin tone +#SUPPORT(prf) 1F486 1F3FC 200D 2640 FE0F ; fully-qualified # 💆🏼‍♀️ woman getting massage: medium-light skin tone +#SUPPORT(prf) 1F486 1F3FD 200D 2640 FE0F ; fully-qualified # 💆🏽‍♀️ woman getting massage: medium skin tone +#SUPPORT(prf) 1F486 1F3FE 200D 2640 FE0F ; fully-qualified # 💆🏾‍♀️ woman getting massage: medium-dark skin tone +#SUPPORT(prf) 1F486 1F3FF 200D 2640 FE0F ; fully-qualified # 💆🏿‍♀️ woman getting massage: dark skin tone +1F487 ; fully-qualified # 💇 person getting haircut +1F487 1F3FB ; fully-qualified # 💇🏻 person getting haircut: light skin tone +1F487 1F3FC ; fully-qualified # 💇🏼 person getting haircut: medium-light skin tone +1F487 1F3FD ; fully-qualified # 💇🏽 person getting haircut: medium skin tone +1F487 1F3FE ; fully-qualified # 💇🏾 person getting haircut: medium-dark skin tone +1F487 1F3FF ; fully-qualified # 💇🏿 person getting haircut: dark skin tone +#SUPPORT(prf) 1F487 200D 2642 FE0F ; fully-qualified # 💇‍♂️ man getting haircut +#SUPPORT(prf) 1F487 1F3FB 200D 2642 FE0F ; fully-qualified # 💇🏻‍♂️ man getting haircut: light skin tone +#SUPPORT(prf) 1F487 1F3FC 200D 2642 FE0F ; fully-qualified # 💇🏼‍♂️ man getting haircut: medium-light skin tone +#SUPPORT(prf) 1F487 1F3FD 200D 2642 FE0F ; fully-qualified # 💇🏽‍♂️ man getting haircut: medium skin tone +#SUPPORT(prf) 1F487 1F3FE 200D 2642 FE0F ; fully-qualified # 💇🏾‍♂️ man getting haircut: medium-dark skin tone +#SUPPORT(prf) 1F487 1F3FF 200D 2642 FE0F ; fully-qualified # 💇🏿‍♂️ man getting haircut: dark skin tone +#SUPPORT(prf) 1F487 200D 2640 FE0F ; fully-qualified # 💇‍♀️ woman getting haircut +#SUPPORT(prf) 1F487 1F3FB 200D 2640 FE0F ; fully-qualified # 💇🏻‍♀️ woman getting haircut: light skin tone +#SUPPORT(prf) 1F487 1F3FC 200D 2640 FE0F ; fully-qualified # 💇🏼‍♀️ woman getting haircut: medium-light skin tone +#SUPPORT(prf) 1F487 1F3FD 200D 2640 FE0F ; fully-qualified # 💇🏽‍♀️ woman getting haircut: medium skin tone +#SUPPORT(prf) 1F487 1F3FE 200D 2640 FE0F ; fully-qualified # 💇🏾‍♀️ woman getting haircut: medium-dark skin tone +#SUPPORT(prf) 1F487 1F3FF 200D 2640 FE0F ; fully-qualified # 💇🏿‍♀️ woman getting haircut: dark skin tone +1F6B6 ; fully-qualified # 🚶 person walking +1F6B6 1F3FB ; fully-qualified # 🚶🏻 person walking: light skin tone +1F6B6 1F3FC ; fully-qualified # 🚶🏼 person walking: medium-light skin tone +1F6B6 1F3FD ; fully-qualified # 🚶🏽 person walking: medium skin tone +1F6B6 1F3FE ; fully-qualified # 🚶🏾 person walking: medium-dark skin tone +1F6B6 1F3FF ; fully-qualified # 🚶🏿 person walking: dark skin tone +#SUPPORT(prf) 1F6B6 200D 2642 FE0F ; fully-qualified # 🚶‍♂️ man walking +#SUPPORT(prf) 1F6B6 1F3FB 200D 2642 FE0F ; fully-qualified # 🚶🏻‍♂️ man walking: light skin tone +#SUPPORT(prf) 1F6B6 1F3FC 200D 2642 FE0F ; fully-qualified # 🚶🏼‍♂️ man walking: medium-light skin tone +#SUPPORT(prf) 1F6B6 1F3FD 200D 2642 FE0F ; fully-qualified # 🚶🏽‍♂️ man walking: medium skin tone +#SUPPORT(prf) 1F6B6 1F3FE 200D 2642 FE0F ; fully-qualified # 🚶🏾‍♂️ man walking: medium-dark skin tone +#SUPPORT(prf) 1F6B6 1F3FF 200D 2642 FE0F ; fully-qualified # 🚶🏿‍♂️ man walking: dark skin tone +#SUPPORT(prf) 1F6B6 200D 2640 FE0F ; fully-qualified # 🚶‍♀️ woman walking +#SUPPORT(prf) 1F6B6 1F3FB 200D 2640 FE0F ; fully-qualified # 🚶🏻‍♀️ woman walking: light skin tone +#SUPPORT(prf) 1F6B6 1F3FC 200D 2640 FE0F ; fully-qualified # 🚶🏼‍♀️ woman walking: medium-light skin tone +#SUPPORT(prf) 1F6B6 1F3FD 200D 2640 FE0F ; fully-qualified # 🚶🏽‍♀️ woman walking: medium skin tone +#SUPPORT(prf) 1F6B6 1F3FE 200D 2640 FE0F ; fully-qualified # 🚶🏾‍♀️ woman walking: medium-dark skin tone +#SUPPORT(prf) 1F6B6 1F3FF 200D 2640 FE0F ; fully-qualified # 🚶🏿‍♀️ woman walking: dark skin tone +#SUPPORT(prf) 1F9CD ; fully-qualified # 🧍 person standing +#SUPPORT(prf) 1F9CD 1F3FB ; fully-qualified # 🧍🏻 person standing: light skin tone +#SUPPORT(prf) 1F9CD 1F3FC ; fully-qualified # 🧍🏼 person standing: medium-light skin tone +#SUPPORT(prf) 1F9CD 1F3FD ; fully-qualified # 🧍🏽 person standing: medium skin tone +#SUPPORT(prf) 1F9CD 1F3FE ; fully-qualified # 🧍🏾 person standing: medium-dark skin tone +#SUPPORT(prf) 1F9CD 1F3FF ; fully-qualified # 🧍🏿 person standing: dark skin tone +#SUPPORT(prf) 1F9CD 200D 2642 FE0F ; fully-qualified # 🧍‍♂️ man standing +#SUPPORT(prf) 1F9CD 1F3FB 200D 2642 FE0F ; fully-qualified # 🧍🏻‍♂️ man standing: light skin tone +#SUPPORT(prf) 1F9CD 1F3FC 200D 2642 FE0F ; fully-qualified # 🧍🏼‍♂️ man standing: medium-light skin tone +#SUPPORT(prf) 1F9CD 1F3FD 200D 2642 FE0F ; fully-qualified # 🧍🏽‍♂️ man standing: medium skin tone +#SUPPORT(prf) 1F9CD 1F3FE 200D 2642 FE0F ; fully-qualified # 🧍🏾‍♂️ man standing: medium-dark skin tone +#SUPPORT(prf) 1F9CD 1F3FF 200D 2642 FE0F ; fully-qualified # 🧍🏿‍♂️ man standing: dark skin tone +#SUPPORT(prf) 1F9CD 200D 2640 FE0F ; fully-qualified # 🧍‍♀️ woman standing +#SUPPORT(prf) 1F9CD 1F3FB 200D 2640 FE0F ; fully-qualified # 🧍🏻‍♀️ woman standing: light skin tone +#SUPPORT(prf) 1F9CD 1F3FC 200D 2640 FE0F ; fully-qualified # 🧍🏼‍♀️ woman standing: medium-light skin tone +#SUPPORT(prf) 1F9CD 1F3FD 200D 2640 FE0F ; fully-qualified # 🧍🏽‍♀️ woman standing: medium skin tone +#SUPPORT(prf) 1F9CD 1F3FE 200D 2640 FE0F ; fully-qualified # 🧍🏾‍♀️ woman standing: medium-dark skin tone +#SUPPORT(prf) 1F9CD 1F3FF 200D 2640 FE0F ; fully-qualified # 🧍🏿‍♀️ woman standing: dark skin tone +#SUPPORT(prf) 1F9CE ; fully-qualified # 🧎 person kneeling +#SUPPORT(prf) 1F9CE 1F3FB ; fully-qualified # 🧎🏻 person kneeling: light skin tone +#SUPPORT(prf) 1F9CE 1F3FC ; fully-qualified # 🧎🏼 person kneeling: medium-light skin tone +#SUPPORT(prf) 1F9CE 1F3FD ; fully-qualified # 🧎🏽 person kneeling: medium skin tone +#SUPPORT(prf) 1F9CE 1F3FE ; fully-qualified # 🧎🏾 person kneeling: medium-dark skin tone +#SUPPORT(prf) 1F9CE 1F3FF ; fully-qualified # 🧎🏿 person kneeling: dark skin tone +#SUPPORT(prf) 1F9CE 200D 2642 FE0F ; fully-qualified # 🧎‍♂️ man kneeling +#SUPPORT(prf) 1F9CE 1F3FB 200D 2642 FE0F ; fully-qualified # 🧎🏻‍♂️ man kneeling: light skin tone +#SUPPORT(prf) 1F9CE 1F3FC 200D 2642 FE0F ; fully-qualified # 🧎🏼‍♂️ man kneeling: medium-light skin tone +#SUPPORT(prf) 1F9CE 1F3FD 200D 2642 FE0F ; fully-qualified # 🧎🏽‍♂️ man kneeling: medium skin tone +#SUPPORT(prf) 1F9CE 1F3FE 200D 2642 FE0F ; fully-qualified # 🧎🏾‍♂️ man kneeling: medium-dark skin tone +#SUPPORT(prf) 1F9CE 1F3FF 200D 2642 FE0F ; fully-qualified # 🧎🏿‍♂️ man kneeling: dark skin tone +#SUPPORT(prf) 1F9CE 200D 2640 FE0F ; fully-qualified # 🧎‍♀️ woman kneeling +#SUPPORT(prf) 1F9CE 1F3FB 200D 2640 FE0F ; fully-qualified # 🧎🏻‍♀️ woman kneeling: light skin tone +#SUPPORT(prf) 1F9CE 1F3FC 200D 2640 FE0F ; fully-qualified # 🧎🏼‍♀️ woman kneeling: medium-light skin tone +#SUPPORT(prf) 1F9CE 1F3FD 200D 2640 FE0F ; fully-qualified # 🧎🏽‍♀️ woman kneeling: medium skin tone +#SUPPORT(prf) 1F9CE 1F3FE 200D 2640 FE0F ; fully-qualified # 🧎🏾‍♀️ woman kneeling: medium-dark skin tone +#SUPPORT(prf) 1F9CE 1F3FF 200D 2640 FE0F ; fully-qualified # 🧎🏿‍♀️ woman kneeling: dark skin tone +#SUPPORT(prf) 1F468 200D 1F9AF ; fully-qualified # 👨‍🦯 man with probing cane +#SUPPORT(prf) 1F468 1F3FB 200D 1F9AF ; fully-qualified # 👨🏻‍🦯 man with probing cane: light skin tone +#SUPPORT(prf) 1F468 1F3FC 200D 1F9AF ; fully-qualified # 👨🏼‍🦯 man with probing cane: medium-light skin tone +#SUPPORT(prf) 1F468 1F3FD 200D 1F9AF ; fully-qualified # 👨🏽‍🦯 man with probing cane: medium skin tone +#SUPPORT(prf) 1F468 1F3FE 200D 1F9AF ; fully-qualified # 👨🏾‍🦯 man with probing cane: medium-dark skin tone +#SUPPORT(prf) 1F468 1F3FF 200D 1F9AF ; fully-qualified # 👨🏿‍🦯 man with probing cane: dark skin tone +#SUPPORT(prf) 1F469 200D 1F9AF ; fully-qualified # 👩‍🦯 woman with probing cane +#SUPPORT(prf) 1F469 1F3FB 200D 1F9AF ; fully-qualified # 👩🏻‍🦯 woman with probing cane: light skin tone +#SUPPORT(prf) 1F469 1F3FC 200D 1F9AF ; fully-qualified # 👩🏼‍🦯 woman with probing cane: medium-light skin tone +#SUPPORT(prf) 1F469 1F3FD 200D 1F9AF ; fully-qualified # 👩🏽‍🦯 woman with probing cane: medium skin tone +#SUPPORT(prf) 1F469 1F3FE 200D 1F9AF ; fully-qualified # 👩🏾‍🦯 woman with probing cane: medium-dark skin tone +#SUPPORT(prf) 1F469 1F3FF 200D 1F9AF ; fully-qualified # 👩🏿‍🦯 woman with probing cane: dark skin tone +#SUPPORT(prf) 1F468 200D 1F9BC ; fully-qualified # 👨‍🦼 man in motorized wheelchair +#SUPPORT(prf) 1F468 1F3FB 200D 1F9BC ; fully-qualified # 👨🏻‍🦼 man in motorized wheelchair: light skin tone +#SUPPORT(prf) 1F468 1F3FC 200D 1F9BC ; fully-qualified # 👨🏼‍🦼 man in motorized wheelchair: medium-light skin tone +#SUPPORT(prf) 1F468 1F3FD 200D 1F9BC ; fully-qualified # 👨🏽‍🦼 man in motorized wheelchair: medium skin tone +#SUPPORT(prf) 1F468 1F3FE 200D 1F9BC ; fully-qualified # 👨🏾‍🦼 man in motorized wheelchair: medium-dark skin tone +#SUPPORT(prf) 1F468 1F3FF 200D 1F9BC ; fully-qualified # 👨🏿‍🦼 man in motorized wheelchair: dark skin tone +#SUPPORT(prf) 1F469 200D 1F9BC ; fully-qualified # 👩‍🦼 woman in motorized wheelchair +#SUPPORT(prf) 1F469 1F3FB 200D 1F9BC ; fully-qualified # 👩🏻‍🦼 woman in motorized wheelchair: light skin tone +#SUPPORT(prf) 1F469 1F3FC 200D 1F9BC ; fully-qualified # 👩🏼‍🦼 woman in motorized wheelchair: medium-light skin tone +#SUPPORT(prf) 1F469 1F3FD 200D 1F9BC ; fully-qualified # 👩🏽‍🦼 woman in motorized wheelchair: medium skin tone +#SUPPORT(prf) 1F469 1F3FE 200D 1F9BC ; fully-qualified # 👩🏾‍🦼 woman in motorized wheelchair: medium-dark skin tone +#SUPPORT(prf) 1F469 1F3FF 200D 1F9BC ; fully-qualified # 👩🏿‍🦼 woman in motorized wheelchair: dark skin tone +#SUPPORT(prf) 1F468 200D 1F9BD ; fully-qualified # 👨‍🦽 man in manual wheelchair +#SUPPORT(prf) 1F468 1F3FB 200D 1F9BD ; fully-qualified # 👨🏻‍🦽 man in manual wheelchair: light skin tone +#SUPPORT(prf) 1F468 1F3FC 200D 1F9BD ; fully-qualified # 👨🏼‍🦽 man in manual wheelchair: medium-light skin tone +#SUPPORT(prf) 1F468 1F3FD 200D 1F9BD ; fully-qualified # 👨🏽‍🦽 man in manual wheelchair: medium skin tone +#SUPPORT(prf) 1F468 1F3FE 200D 1F9BD ; fully-qualified # 👨🏾‍🦽 man in manual wheelchair: medium-dark skin tone +#SUPPORT(prf) 1F468 1F3FF 200D 1F9BD ; fully-qualified # 👨🏿‍🦽 man in manual wheelchair: dark skin tone +#SUPPORT(prf) 1F469 200D 1F9BD ; fully-qualified # 👩‍🦽 woman in manual wheelchair +#SUPPORT(prf) 1F469 1F3FB 200D 1F9BD ; fully-qualified # 👩🏻‍🦽 woman in manual wheelchair: light skin tone +#SUPPORT(prf) 1F469 1F3FC 200D 1F9BD ; fully-qualified # 👩🏼‍🦽 woman in manual wheelchair: medium-light skin tone +#SUPPORT(prf) 1F469 1F3FD 200D 1F9BD ; fully-qualified # 👩🏽‍🦽 woman in manual wheelchair: medium skin tone +#SUPPORT(prf) 1F469 1F3FE 200D 1F9BD ; fully-qualified # 👩🏾‍🦽 woman in manual wheelchair: medium-dark skin tone +#SUPPORT(prf) 1F469 1F3FF 200D 1F9BD ; fully-qualified # 👩🏿‍🦽 woman in manual wheelchair: dark skin tone +1F3C3 ; fully-qualified # 🏃 person running +1F3C3 1F3FB ; fully-qualified # 🏃🏻 person running: light skin tone +1F3C3 1F3FC ; fully-qualified # 🏃🏼 person running: medium-light skin tone +1F3C3 1F3FD ; fully-qualified # 🏃🏽 person running: medium skin tone +1F3C3 1F3FE ; fully-qualified # 🏃🏾 person running: medium-dark skin tone +1F3C3 1F3FF ; fully-qualified # 🏃🏿 person running: dark skin tone +#SUPPORT(prf) 1F3C3 200D 2642 FE0F ; fully-qualified # 🏃‍♂️ man running +#SUPPORT(prf) 1F3C3 1F3FB 200D 2642 FE0F ; fully-qualified # 🏃🏻‍♂️ man running: light skin tone +#SUPPORT(prf) 1F3C3 1F3FC 200D 2642 FE0F ; fully-qualified # 🏃🏼‍♂️ man running: medium-light skin tone +#SUPPORT(prf) 1F3C3 1F3FD 200D 2642 FE0F ; fully-qualified # 🏃🏽‍♂️ man running: medium skin tone +#SUPPORT(prf) 1F3C3 1F3FE 200D 2642 FE0F ; fully-qualified # 🏃🏾‍♂️ man running: medium-dark skin tone +#SUPPORT(prf) 1F3C3 1F3FF 200D 2642 FE0F ; fully-qualified # 🏃🏿‍♂️ man running: dark skin tone +#SUPPORT(prf) 1F3C3 200D 2640 FE0F ; fully-qualified # 🏃‍♀️ woman running +#SUPPORT(prf) 1F3C3 1F3FB 200D 2640 FE0F ; fully-qualified # 🏃🏻‍♀️ woman running: light skin tone +#SUPPORT(prf) 1F3C3 1F3FC 200D 2640 FE0F ; fully-qualified # 🏃🏼‍♀️ woman running: medium-light skin tone +#SUPPORT(prf) 1F3C3 1F3FD 200D 2640 FE0F ; fully-qualified # 🏃🏽‍♀️ woman running: medium skin tone +#SUPPORT(prf) 1F3C3 1F3FE 200D 2640 FE0F ; fully-qualified # 🏃🏾‍♀️ woman running: medium-dark skin tone +#SUPPORT(prf) 1F3C3 1F3FF 200D 2640 FE0F ; fully-qualified # 🏃🏿‍♀️ woman running: dark skin tone +1F483 ; fully-qualified # 💃 woman dancing +1F483 1F3FB ; fully-qualified # 💃🏻 woman dancing: light skin tone +1F483 1F3FC ; fully-qualified # 💃🏼 woman dancing: medium-light skin tone +1F483 1F3FD ; fully-qualified # 💃🏽 woman dancing: medium skin tone +1F483 1F3FE ; fully-qualified # 💃🏾 woman dancing: medium-dark skin tone +1F483 1F3FF ; fully-qualified # 💃🏿 woman dancing: dark skin tone +1F57A ; fully-qualified # 🕺 man dancing +1F57A 1F3FB ; fully-qualified # 🕺🏻 man dancing: light skin tone +1F57A 1F3FC ; fully-qualified # 🕺🏼 man dancing: medium-light skin tone +1F57A 1F3FD ; fully-qualified # 🕺🏽 man dancing: medium skin tone +1F57A 1F3FE ; fully-qualified # 🕺🏾 man dancing: medium-dark skin tone +1F57A 1F3FF ; fully-qualified # 🕺🏿 man dancing: dark skin tone +1F574 FE0F ; fully-qualified # 🕴️ man in suit levitating +1F574 1F3FB ; fully-qualified # 🕴🏻 man in suit levitating: light skin tone +1F574 1F3FC ; fully-qualified # 🕴🏼 man in suit levitating: medium-light skin tone +1F574 1F3FD ; fully-qualified # 🕴🏽 man in suit levitating: medium skin tone +1F574 1F3FE ; fully-qualified # 🕴🏾 man in suit levitating: medium-dark skin tone +1F574 1F3FF ; fully-qualified # 🕴🏿 man in suit levitating: dark skin tone +1F46F ; fully-qualified # 👯 people with bunny ears +#SUPPORT(prf) 1F46F 200D 2642 FE0F ; fully-qualified # 👯‍♂️ men with bunny ears +#SUPPORT(prf) 1F46F 200D 2640 FE0F ; fully-qualified # 👯‍♀️ women with bunny ears +1F9D6 ; fully-qualified # 🧖 person in steamy room +1F9D6 1F3FB ; fully-qualified # 🧖🏻 person in steamy room: light skin tone +1F9D6 1F3FC ; fully-qualified # 🧖🏼 person in steamy room: medium-light skin tone +1F9D6 1F3FD ; fully-qualified # 🧖🏽 person in steamy room: medium skin tone +1F9D6 1F3FE ; fully-qualified # 🧖🏾 person in steamy room: medium-dark skin tone +1F9D6 1F3FF ; fully-qualified # 🧖🏿 person in steamy room: dark skin tone +#SUPPORT(prf) 1F9D6 200D 2642 FE0F ; fully-qualified # 🧖‍♂️ man in steamy room +#SUPPORT(prf) 1F9D6 1F3FB 200D 2642 FE0F ; fully-qualified # 🧖🏻‍♂️ man in steamy room: light skin tone +#SUPPORT(prf) 1F9D6 1F3FC 200D 2642 FE0F ; fully-qualified # 🧖🏼‍♂️ man in steamy room: medium-light skin tone +#SUPPORT(prf) 1F9D6 1F3FD 200D 2642 FE0F ; fully-qualified # 🧖🏽‍♂️ man in steamy room: medium skin tone +#SUPPORT(prf) 1F9D6 1F3FE 200D 2642 FE0F ; fully-qualified # 🧖🏾‍♂️ man in steamy room: medium-dark skin tone +#SUPPORT(prf) 1F9D6 1F3FF 200D 2642 FE0F ; fully-qualified # 🧖🏿‍♂️ man in steamy room: dark skin tone +#SUPPORT(prf) 1F9D6 200D 2640 FE0F ; fully-qualified # 🧖‍♀️ woman in steamy room +#SUPPORT(prf) 1F9D6 1F3FB 200D 2640 FE0F ; fully-qualified # 🧖🏻‍♀️ woman in steamy room: light skin tone +#SUPPORT(prf) 1F9D6 1F3FC 200D 2640 FE0F ; fully-qualified # 🧖🏼‍♀️ woman in steamy room: medium-light skin tone +#SUPPORT(prf) 1F9D6 1F3FD 200D 2640 FE0F ; fully-qualified # 🧖🏽‍♀️ woman in steamy room: medium skin tone +#SUPPORT(prf) 1F9D6 1F3FE 200D 2640 FE0F ; fully-qualified # 🧖🏾‍♀️ woman in steamy room: medium-dark skin tone +#SUPPORT(prf) 1F9D6 1F3FF 200D 2640 FE0F ; fully-qualified # 🧖🏿‍♀️ woman in steamy room: dark skin tone +1F9D7 ; fully-qualified # 🧗 person climbing +1F9D7 1F3FB ; fully-qualified # 🧗🏻 person climbing: light skin tone +1F9D7 1F3FC ; fully-qualified # 🧗🏼 person climbing: medium-light skin tone +1F9D7 1F3FD ; fully-qualified # 🧗🏽 person climbing: medium skin tone +1F9D7 1F3FE ; fully-qualified # 🧗🏾 person climbing: medium-dark skin tone +1F9D7 1F3FF ; fully-qualified # 🧗🏿 person climbing: dark skin tone +#SUPPORT(prf) 1F9D7 200D 2642 FE0F ; fully-qualified # 🧗‍♂️ man climbing +#SUPPORT(prf) 1F9D7 1F3FB 200D 2642 FE0F ; fully-qualified # 🧗🏻‍♂️ man climbing: light skin tone +#SUPPORT(prf) 1F9D7 1F3FC 200D 2642 FE0F ; fully-qualified # 🧗🏼‍♂️ man climbing: medium-light skin tone +#SUPPORT(prf) 1F9D7 1F3FD 200D 2642 FE0F ; fully-qualified # 🧗🏽‍♂️ man climbing: medium skin tone +#SUPPORT(prf) 1F9D7 1F3FE 200D 2642 FE0F ; fully-qualified # 🧗🏾‍♂️ man climbing: medium-dark skin tone +#SUPPORT(prf) 1F9D7 1F3FF 200D 2642 FE0F ; fully-qualified # 🧗🏿‍♂️ man climbing: dark skin tone +#SUPPORT(prf) 1F9D7 200D 2640 FE0F ; fully-qualified # 🧗‍♀️ woman climbing +#SUPPORT(prf) 1F9D7 1F3FB 200D 2640 FE0F ; fully-qualified # 🧗🏻‍♀️ woman climbing: light skin tone +#SUPPORT(prf) 1F9D7 1F3FC 200D 2640 FE0F ; fully-qualified # 🧗🏼‍♀️ woman climbing: medium-light skin tone +#SUPPORT(prf) 1F9D7 1F3FD 200D 2640 FE0F ; fully-qualified # 🧗🏽‍♀️ woman climbing: medium skin tone +#SUPPORT(prf) 1F9D7 1F3FE 200D 2640 FE0F ; fully-qualified # 🧗🏾‍♀️ woman climbing: medium-dark skin tone +#SUPPORT(prf) 1F9D7 1F3FF 200D 2640 FE0F ; fully-qualified # 🧗🏿‍♀️ woman climbing: dark skin tone + +# subgroup: person-sport +1F93A ; fully-qualified # 🤺 person fencing +1F3C7 ; fully-qualified # 🏇 horse racing +1F3C7 1F3FB ; fully-qualified # 🏇🏻 horse racing: light skin tone +1F3C7 1F3FC ; fully-qualified # 🏇🏼 horse racing: medium-light skin tone +1F3C7 1F3FD ; fully-qualified # 🏇🏽 horse racing: medium skin tone +1F3C7 1F3FE ; fully-qualified # 🏇🏾 horse racing: medium-dark skin tone +1F3C7 1F3FF ; fully-qualified # 🏇🏿 horse racing: dark skin tone +26F7 FE0F ; fully-qualified # ⛷️ skier +1F3C2 ; fully-qualified # 🏂 snowboarder +1F3C2 1F3FB ; fully-qualified # 🏂🏻 snowboarder: light skin tone +1F3C2 1F3FC ; fully-qualified # 🏂🏼 snowboarder: medium-light skin tone +1F3C2 1F3FD ; fully-qualified # 🏂🏽 snowboarder: medium skin tone +1F3C2 1F3FE ; fully-qualified # 🏂🏾 snowboarder: medium-dark skin tone +1F3C2 1F3FF ; fully-qualified # 🏂🏿 snowboarder: dark skin tone +1F3CC FE0F ; fully-qualified # 🏌️ person golfing +1F3CC 1F3FB ; fully-qualified # 🏌🏻 person golfing: light skin tone +1F3CC 1F3FC ; fully-qualified # 🏌🏼 person golfing: medium-light skin tone +1F3CC 1F3FD ; fully-qualified # 🏌🏽 person golfing: medium skin tone +1F3CC 1F3FE ; fully-qualified # 🏌🏾 person golfing: medium-dark skin tone +1F3CC 1F3FF ; fully-qualified # 🏌🏿 person golfing: dark skin tone +#SUPPORT(prf) 1F3CC FE0F 200D 2642 FE0F ; fully-qualified # 🏌️‍♂️ man golfing +#SUPPORT(prf) 1F3CC 1F3FB 200D 2642 FE0F ; fully-qualified # 🏌🏻‍♂️ man golfing: light skin tone +#SUPPORT(prf) 1F3CC 1F3FC 200D 2642 FE0F ; fully-qualified # 🏌🏼‍♂️ man golfing: medium-light skin tone +#SUPPORT(prf) 1F3CC 1F3FD 200D 2642 FE0F ; fully-qualified # 🏌🏽‍♂️ man golfing: medium skin tone +#SUPPORT(prf) 1F3CC 1F3FE 200D 2642 FE0F ; fully-qualified # 🏌🏾‍♂️ man golfing: medium-dark skin tone +#SUPPORT(prf) 1F3CC 1F3FF 200D 2642 FE0F ; fully-qualified # 🏌🏿‍♂️ man golfing: dark skin tone +#SUPPORT(prf) 1F3CC FE0F 200D 2640 FE0F ; fully-qualified # 🏌️‍♀️ woman golfing +#SUPPORT(prf) 1F3CC 1F3FB 200D 2640 FE0F ; fully-qualified # 🏌🏻‍♀️ woman golfing: light skin tone +#SUPPORT(prf) 1F3CC 1F3FC 200D 2640 FE0F ; fully-qualified # 🏌🏼‍♀️ woman golfing: medium-light skin tone +#SUPPORT(prf) 1F3CC 1F3FD 200D 2640 FE0F ; fully-qualified # 🏌🏽‍♀️ woman golfing: medium skin tone +#SUPPORT(prf) 1F3CC 1F3FE 200D 2640 FE0F ; fully-qualified # 🏌🏾‍♀️ woman golfing: medium-dark skin tone +#SUPPORT(prf) 1F3CC 1F3FF 200D 2640 FE0F ; fully-qualified # 🏌🏿‍♀️ woman golfing: dark skin tone +1F3C4 ; fully-qualified # 🏄 person surfing +1F3C4 1F3FB ; fully-qualified # 🏄🏻 person surfing: light skin tone +1F3C4 1F3FC ; fully-qualified # 🏄🏼 person surfing: medium-light skin tone +1F3C4 1F3FD ; fully-qualified # 🏄🏽 person surfing: medium skin tone +1F3C4 1F3FE ; fully-qualified # 🏄🏾 person surfing: medium-dark skin tone +1F3C4 1F3FF ; fully-qualified # 🏄🏿 person surfing: dark skin tone +#SUPPORT(prf) 1F3C4 200D 2642 FE0F ; fully-qualified # 🏄‍♂️ man surfing +#SUPPORT(prf) 1F3C4 1F3FB 200D 2642 FE0F ; fully-qualified # 🏄🏻‍♂️ man surfing: light skin tone +#SUPPORT(prf) 1F3C4 1F3FC 200D 2642 FE0F ; fully-qualified # 🏄🏼‍♂️ man surfing: medium-light skin tone +#SUPPORT(prf) 1F3C4 1F3FD 200D 2642 FE0F ; fully-qualified # 🏄🏽‍♂️ man surfing: medium skin tone +#SUPPORT(prf) 1F3C4 1F3FE 200D 2642 FE0F ; fully-qualified # 🏄🏾‍♂️ man surfing: medium-dark skin tone +#SUPPORT(prf) 1F3C4 1F3FF 200D 2642 FE0F ; fully-qualified # 🏄🏿‍♂️ man surfing: dark skin tone +#SUPPORT(prf) 1F3C4 200D 2640 FE0F ; fully-qualified # 🏄‍♀️ woman surfing +#SUPPORT(prf) 1F3C4 1F3FB 200D 2640 FE0F ; fully-qualified # 🏄🏻‍♀️ woman surfing: light skin tone +#SUPPORT(prf) 1F3C4 1F3FC 200D 2640 FE0F ; fully-qualified # 🏄🏼‍♀️ woman surfing: medium-light skin tone +#SUPPORT(prf) 1F3C4 1F3FD 200D 2640 FE0F ; fully-qualified # 🏄🏽‍♀️ woman surfing: medium skin tone +#SUPPORT(prf) 1F3C4 1F3FE 200D 2640 FE0F ; fully-qualified # 🏄🏾‍♀️ woman surfing: medium-dark skin tone +#SUPPORT(prf) 1F3C4 1F3FF 200D 2640 FE0F ; fully-qualified # 🏄🏿‍♀️ woman surfing: dark skin tone +1F6A3 ; fully-qualified # 🚣 person rowing boat +1F6A3 1F3FB ; fully-qualified # 🚣🏻 person rowing boat: light skin tone +1F6A3 1F3FC ; fully-qualified # 🚣🏼 person rowing boat: medium-light skin tone +1F6A3 1F3FD ; fully-qualified # 🚣🏽 person rowing boat: medium skin tone +1F6A3 1F3FE ; fully-qualified # 🚣🏾 person rowing boat: medium-dark skin tone +1F6A3 1F3FF ; fully-qualified # 🚣🏿 person rowing boat: dark skin tone +#SUPPORT(prf) 1F6A3 200D 2642 FE0F ; fully-qualified # 🚣‍♂️ man rowing boat +#SUPPORT(prf) 1F6A3 1F3FB 200D 2642 FE0F ; fully-qualified # 🚣🏻‍♂️ man rowing boat: light skin tone +#SUPPORT(prf) 1F6A3 1F3FC 200D 2642 FE0F ; fully-qualified # 🚣🏼‍♂️ man rowing boat: medium-light skin tone +#SUPPORT(prf) 1F6A3 1F3FD 200D 2642 FE0F ; fully-qualified # 🚣🏽‍♂️ man rowing boat: medium skin tone +#SUPPORT(prf) 1F6A3 1F3FE 200D 2642 FE0F ; fully-qualified # 🚣🏾‍♂️ man rowing boat: medium-dark skin tone +#SUPPORT(prf) 1F6A3 1F3FF 200D 2642 FE0F ; fully-qualified # 🚣🏿‍♂️ man rowing boat: dark skin tone +#SUPPORT(prf) 1F6A3 200D 2640 FE0F ; fully-qualified # 🚣‍♀️ woman rowing boat +#SUPPORT(prf) 1F6A3 1F3FB 200D 2640 FE0F ; fully-qualified # 🚣🏻‍♀️ woman rowing boat: light skin tone +#SUPPORT(prf) 1F6A3 1F3FC 200D 2640 FE0F ; fully-qualified # 🚣🏼‍♀️ woman rowing boat: medium-light skin tone +#SUPPORT(prf) 1F6A3 1F3FD 200D 2640 FE0F ; fully-qualified # 🚣🏽‍♀️ woman rowing boat: medium skin tone +#SUPPORT(prf) 1F6A3 1F3FE 200D 2640 FE0F ; fully-qualified # 🚣🏾‍♀️ woman rowing boat: medium-dark skin tone +#SUPPORT(prf) 1F6A3 1F3FF 200D 2640 FE0F ; fully-qualified # 🚣🏿‍♀️ woman rowing boat: dark skin tone +1F3CA ; fully-qualified # 🏊 person swimming +1F3CA 1F3FB ; fully-qualified # 🏊🏻 person swimming: light skin tone +1F3CA 1F3FC ; fully-qualified # 🏊🏼 person swimming: medium-light skin tone +1F3CA 1F3FD ; fully-qualified # 🏊🏽 person swimming: medium skin tone +1F3CA 1F3FE ; fully-qualified # 🏊🏾 person swimming: medium-dark skin tone +1F3CA 1F3FF ; fully-qualified # 🏊🏿 person swimming: dark skin tone +#SUPPORT(prf) 1F3CA 200D 2642 FE0F ; fully-qualified # 🏊‍♂️ man swimming +#SUPPORT(prf) 1F3CA 1F3FB 200D 2642 FE0F ; fully-qualified # 🏊🏻‍♂️ man swimming: light skin tone +#SUPPORT(prf) 1F3CA 1F3FC 200D 2642 FE0F ; fully-qualified # 🏊🏼‍♂️ man swimming: medium-light skin tone +#SUPPORT(prf) 1F3CA 1F3FD 200D 2642 FE0F ; fully-qualified # 🏊🏽‍♂️ man swimming: medium skin tone +#SUPPORT(prf) 1F3CA 1F3FE 200D 2642 FE0F ; fully-qualified # 🏊🏾‍♂️ man swimming: medium-dark skin tone +#SUPPORT(prf) 1F3CA 1F3FF 200D 2642 FE0F ; fully-qualified # 🏊🏿‍♂️ man swimming: dark skin tone +#SUPPORT(prf) 1F3CA 200D 2640 FE0F ; fully-qualified # 🏊‍♀️ woman swimming +#SUPPORT(prf) 1F3CA 1F3FB 200D 2640 FE0F ; fully-qualified # 🏊🏻‍♀️ woman swimming: light skin tone +#SUPPORT(prf) 1F3CA 1F3FC 200D 2640 FE0F ; fully-qualified # 🏊🏼‍♀️ woman swimming: medium-light skin tone +#SUPPORT(prf) 1F3CA 1F3FD 200D 2640 FE0F ; fully-qualified # 🏊🏽‍♀️ woman swimming: medium skin tone +#SUPPORT(prf) 1F3CA 1F3FE 200D 2640 FE0F ; fully-qualified # 🏊🏾‍♀️ woman swimming: medium-dark skin tone +#SUPPORT(prf) 1F3CA 1F3FF 200D 2640 FE0F ; fully-qualified # 🏊🏿‍♀️ woman swimming: dark skin tone +26F9 FE0F ; fully-qualified # ⛹️ person bouncing ball +26F9 1F3FB ; fully-qualified # ⛹🏻 person bouncing ball: light skin tone +26F9 1F3FC ; fully-qualified # ⛹🏼 person bouncing ball: medium-light skin tone +26F9 1F3FD ; fully-qualified # ⛹🏽 person bouncing ball: medium skin tone +26F9 1F3FE ; fully-qualified # ⛹🏾 person bouncing ball: medium-dark skin tone +26F9 1F3FF ; fully-qualified # ⛹🏿 person bouncing ball: dark skin tone +#SUPPORT(prf) 26F9 FE0F 200D 2642 FE0F ; fully-qualified # ⛹️‍♂️ man bouncing ball +#SUPPORT(prf) 26F9 1F3FB 200D 2642 FE0F ; fully-qualified # ⛹🏻‍♂️ man bouncing ball: light skin tone +#SUPPORT(prf) 26F9 1F3FC 200D 2642 FE0F ; fully-qualified # ⛹🏼‍♂️ man bouncing ball: medium-light skin tone +#SUPPORT(prf) 26F9 1F3FD 200D 2642 FE0F ; fully-qualified # ⛹🏽‍♂️ man bouncing ball: medium skin tone +#SUPPORT(prf) 26F9 1F3FE 200D 2642 FE0F ; fully-qualified # ⛹🏾‍♂️ man bouncing ball: medium-dark skin tone +#SUPPORT(prf) 26F9 1F3FF 200D 2642 FE0F ; fully-qualified # ⛹🏿‍♂️ man bouncing ball: dark skin tone +#SUPPORT(prf) 26F9 FE0F 200D 2640 FE0F ; fully-qualified # ⛹️‍♀️ woman bouncing ball +#SUPPORT(prf) 26F9 1F3FB 200D 2640 FE0F ; fully-qualified # ⛹🏻‍♀️ woman bouncing ball: light skin tone +#SUPPORT(prf) 26F9 1F3FC 200D 2640 FE0F ; fully-qualified # ⛹🏼‍♀️ woman bouncing ball: medium-light skin tone +#SUPPORT(prf) 26F9 1F3FD 200D 2640 FE0F ; fully-qualified # ⛹🏽‍♀️ woman bouncing ball: medium skin tone +#SUPPORT(prf) 26F9 1F3FE 200D 2640 FE0F ; fully-qualified # ⛹🏾‍♀️ woman bouncing ball: medium-dark skin tone +#SUPPORT(prf) 26F9 1F3FF 200D 2640 FE0F ; fully-qualified # ⛹🏿‍♀️ woman bouncing ball: dark skin tone +1F3CB FE0F ; fully-qualified # 🏋️ person lifting weights +1F3CB 1F3FB ; fully-qualified # 🏋🏻 person lifting weights: light skin tone +1F3CB 1F3FC ; fully-qualified # 🏋🏼 person lifting weights: medium-light skin tone +1F3CB 1F3FD ; fully-qualified # 🏋🏽 person lifting weights: medium skin tone +1F3CB 1F3FE ; fully-qualified # 🏋🏾 person lifting weights: medium-dark skin tone +1F3CB 1F3FF ; fully-qualified # 🏋🏿 person lifting weights: dark skin tone +#SUPPORT(prf) 1F3CB FE0F 200D 2642 FE0F ; fully-qualified # 🏋️‍♂️ man lifting weights +#SUPPORT(prf) 1F3CB 1F3FB 200D 2642 FE0F ; fully-qualified # 🏋🏻‍♂️ man lifting weights: light skin tone +#SUPPORT(prf) 1F3CB 1F3FC 200D 2642 FE0F ; fully-qualified # 🏋🏼‍♂️ man lifting weights: medium-light skin tone +#SUPPORT(prf) 1F3CB 1F3FD 200D 2642 FE0F ; fully-qualified # 🏋🏽‍♂️ man lifting weights: medium skin tone +#SUPPORT(prf) 1F3CB 1F3FE 200D 2642 FE0F ; fully-qualified # 🏋🏾‍♂️ man lifting weights: medium-dark skin tone +#SUPPORT(prf) 1F3CB 1F3FF 200D 2642 FE0F ; fully-qualified # 🏋🏿‍♂️ man lifting weights: dark skin tone +#SUPPORT(prf) 1F3CB FE0F 200D 2640 FE0F ; fully-qualified # 🏋️‍♀️ woman lifting weights +#SUPPORT(prf) 1F3CB 1F3FB 200D 2640 FE0F ; fully-qualified # 🏋🏻‍♀️ woman lifting weights: light skin tone +#SUPPORT(prf) 1F3CB 1F3FC 200D 2640 FE0F ; fully-qualified # 🏋🏼‍♀️ woman lifting weights: medium-light skin tone +#SUPPORT(prf) 1F3CB 1F3FD 200D 2640 FE0F ; fully-qualified # 🏋🏽‍♀️ woman lifting weights: medium skin tone +#SUPPORT(prf) 1F3CB 1F3FE 200D 2640 FE0F ; fully-qualified # 🏋🏾‍♀️ woman lifting weights: medium-dark skin tone +#SUPPORT(prf) 1F3CB 1F3FF 200D 2640 FE0F ; fully-qualified # 🏋🏿‍♀️ woman lifting weights: dark skin tone +1F6B4 ; fully-qualified # 🚴 person biking +1F6B4 1F3FB ; fully-qualified # 🚴🏻 person biking: light skin tone +1F6B4 1F3FC ; fully-qualified # 🚴🏼 person biking: medium-light skin tone +1F6B4 1F3FD ; fully-qualified # 🚴🏽 person biking: medium skin tone +1F6B4 1F3FE ; fully-qualified # 🚴🏾 person biking: medium-dark skin tone +1F6B4 1F3FF ; fully-qualified # 🚴🏿 person biking: dark skin tone +#SUPPORT(prf) 1F6B4 200D 2642 FE0F ; fully-qualified # 🚴‍♂️ man biking +#SUPPORT(prf) 1F6B4 1F3FB 200D 2642 FE0F ; fully-qualified # 🚴🏻‍♂️ man biking: light skin tone +#SUPPORT(prf) 1F6B4 1F3FC 200D 2642 FE0F ; fully-qualified # 🚴🏼‍♂️ man biking: medium-light skin tone +#SUPPORT(prf) 1F6B4 1F3FD 200D 2642 FE0F ; fully-qualified # 🚴🏽‍♂️ man biking: medium skin tone +#SUPPORT(prf) 1F6B4 1F3FE 200D 2642 FE0F ; fully-qualified # 🚴🏾‍♂️ man biking: medium-dark skin tone +#SUPPORT(prf) 1F6B4 1F3FF 200D 2642 FE0F ; fully-qualified # 🚴🏿‍♂️ man biking: dark skin tone +#SUPPORT(prf) 1F6B4 200D 2640 FE0F ; fully-qualified # 🚴‍♀️ woman biking +#SUPPORT(prf) 1F6B4 1F3FB 200D 2640 FE0F ; fully-qualified # 🚴🏻‍♀️ woman biking: light skin tone +#SUPPORT(prf) 1F6B4 1F3FC 200D 2640 FE0F ; fully-qualified # 🚴🏼‍♀️ woman biking: medium-light skin tone +#SUPPORT(prf) 1F6B4 1F3FD 200D 2640 FE0F ; fully-qualified # 🚴🏽‍♀️ woman biking: medium skin tone +#SUPPORT(prf) 1F6B4 1F3FE 200D 2640 FE0F ; fully-qualified # 🚴🏾‍♀️ woman biking: medium-dark skin tone +#SUPPORT(prf) 1F6B4 1F3FF 200D 2640 FE0F ; fully-qualified # 🚴🏿‍♀️ woman biking: dark skin tone +1F6B5 ; fully-qualified # 🚵 person mountain biking +1F6B5 1F3FB ; fully-qualified # 🚵🏻 person mountain biking: light skin tone +1F6B5 1F3FC ; fully-qualified # 🚵🏼 person mountain biking: medium-light skin tone +1F6B5 1F3FD ; fully-qualified # 🚵🏽 person mountain biking: medium skin tone +1F6B5 1F3FE ; fully-qualified # 🚵🏾 person mountain biking: medium-dark skin tone +1F6B5 1F3FF ; fully-qualified # 🚵🏿 person mountain biking: dark skin tone +#SUPPORT(prf) 1F6B5 200D 2642 FE0F ; fully-qualified # 🚵‍♂️ man mountain biking +#SUPPORT(prf) 1F6B5 1F3FB 200D 2642 FE0F ; fully-qualified # 🚵🏻‍♂️ man mountain biking: light skin tone +#SUPPORT(prf) 1F6B5 1F3FC 200D 2642 FE0F ; fully-qualified # 🚵🏼‍♂️ man mountain biking: medium-light skin tone +#SUPPORT(prf) 1F6B5 1F3FD 200D 2642 FE0F ; fully-qualified # 🚵🏽‍♂️ man mountain biking: medium skin tone +#SUPPORT(prf) 1F6B5 1F3FE 200D 2642 FE0F ; fully-qualified # 🚵🏾‍♂️ man mountain biking: medium-dark skin tone +#SUPPORT(prf) 1F6B5 1F3FF 200D 2642 FE0F ; fully-qualified # 🚵🏿‍♂️ man mountain biking: dark skin tone +#SUPPORT(prf) 1F6B5 200D 2640 FE0F ; fully-qualified # 🚵‍♀️ woman mountain biking +#SUPPORT(prf) 1F6B5 1F3FB 200D 2640 FE0F ; fully-qualified # 🚵🏻‍♀️ woman mountain biking: light skin tone +#SUPPORT(prf) 1F6B5 1F3FC 200D 2640 FE0F ; fully-qualified # 🚵🏼‍♀️ woman mountain biking: medium-light skin tone +#SUPPORT(prf) 1F6B5 1F3FD 200D 2640 FE0F ; fully-qualified # 🚵🏽‍♀️ woman mountain biking: medium skin tone +#SUPPORT(prf) 1F6B5 1F3FE 200D 2640 FE0F ; fully-qualified # 🚵🏾‍♀️ woman mountain biking: medium-dark skin tone +#SUPPORT(prf) 1F6B5 1F3FF 200D 2640 FE0F ; fully-qualified # 🚵🏿‍♀️ woman mountain biking: dark skin tone +1F938 ; fully-qualified # 🤸 person cartwheeling +1F938 1F3FB ; fully-qualified # 🤸🏻 person cartwheeling: light skin tone +1F938 1F3FC ; fully-qualified # 🤸🏼 person cartwheeling: medium-light skin tone +1F938 1F3FD ; fully-qualified # 🤸🏽 person cartwheeling: medium skin tone +1F938 1F3FE ; fully-qualified # 🤸🏾 person cartwheeling: medium-dark skin tone +1F938 1F3FF ; fully-qualified # 🤸🏿 person cartwheeling: dark skin tone +#SUPPORT(prf) 1F938 200D 2642 FE0F ; fully-qualified # 🤸‍♂️ man cartwheeling +#SUPPORT(prf) 1F938 1F3FB 200D 2642 FE0F ; fully-qualified # 🤸🏻‍♂️ man cartwheeling: light skin tone +#SUPPORT(prf) 1F938 1F3FC 200D 2642 FE0F ; fully-qualified # 🤸🏼‍♂️ man cartwheeling: medium-light skin tone +#SUPPORT(prf) 1F938 1F3FD 200D 2642 FE0F ; fully-qualified # 🤸🏽‍♂️ man cartwheeling: medium skin tone +#SUPPORT(prf) 1F938 1F3FE 200D 2642 FE0F ; fully-qualified # 🤸🏾‍♂️ man cartwheeling: medium-dark skin tone +#SUPPORT(prf) 1F938 1F3FF 200D 2642 FE0F ; fully-qualified # 🤸🏿‍♂️ man cartwheeling: dark skin tone +#SUPPORT(prf) 1F938 200D 2640 FE0F ; fully-qualified # 🤸‍♀️ woman cartwheeling +#SUPPORT(prf) 1F938 1F3FB 200D 2640 FE0F ; fully-qualified # 🤸🏻‍♀️ woman cartwheeling: light skin tone +#SUPPORT(prf) 1F938 1F3FC 200D 2640 FE0F ; fully-qualified # 🤸🏼‍♀️ woman cartwheeling: medium-light skin tone +#SUPPORT(prf) 1F938 1F3FD 200D 2640 FE0F ; fully-qualified # 🤸🏽‍♀️ woman cartwheeling: medium skin tone +#SUPPORT(prf) 1F938 1F3FE 200D 2640 FE0F ; fully-qualified # 🤸🏾‍♀️ woman cartwheeling: medium-dark skin tone +#SUPPORT(prf) 1F938 1F3FF 200D 2640 FE0F ; fully-qualified # 🤸🏿‍♀️ woman cartwheeling: dark skin tone +1F93C ; fully-qualified # 🤼 people wrestling +#SUPPORT(prf) 1F93C 200D 2642 FE0F ; fully-qualified # 🤼‍♂️ men wrestling +#SUPPORT(prf) 1F93C 200D 2640 FE0F ; fully-qualified # 🤼‍♀️ women wrestling +1F93D ; fully-qualified # 🤽 person playing water polo +1F93D 1F3FB ; fully-qualified # 🤽🏻 person playing water polo: light skin tone +1F93D 1F3FC ; fully-qualified # 🤽🏼 person playing water polo: medium-light skin tone +1F93D 1F3FD ; fully-qualified # 🤽🏽 person playing water polo: medium skin tone +1F93D 1F3FE ; fully-qualified # 🤽🏾 person playing water polo: medium-dark skin tone +1F93D 1F3FF ; fully-qualified # 🤽🏿 person playing water polo: dark skin tone +#SUPPORT(prf) 1F93D 200D 2642 FE0F ; fully-qualified # 🤽‍♂️ man playing water polo +#SUPPORT(prf) 1F93D 1F3FB 200D 2642 FE0F ; fully-qualified # 🤽🏻‍♂️ man playing water polo: light skin tone +#SUPPORT(prf) 1F93D 1F3FC 200D 2642 FE0F ; fully-qualified # 🤽🏼‍♂️ man playing water polo: medium-light skin tone +#SUPPORT(prf) 1F93D 1F3FD 200D 2642 FE0F ; fully-qualified # 🤽🏽‍♂️ man playing water polo: medium skin tone +#SUPPORT(prf) 1F93D 1F3FE 200D 2642 FE0F ; fully-qualified # 🤽🏾‍♂️ man playing water polo: medium-dark skin tone +#SUPPORT(prf) 1F93D 1F3FF 200D 2642 FE0F ; fully-qualified # 🤽🏿‍♂️ man playing water polo: dark skin tone +#SUPPORT(prf) 1F93D 200D 2640 FE0F ; fully-qualified # 🤽‍♀️ woman playing water polo +#SUPPORT(prf) 1F93D 1F3FB 200D 2640 FE0F ; fully-qualified # 🤽🏻‍♀️ woman playing water polo: light skin tone +#SUPPORT(prf) 1F93D 1F3FC 200D 2640 FE0F ; fully-qualified # 🤽🏼‍♀️ woman playing water polo: medium-light skin tone +#SUPPORT(prf) 1F93D 1F3FD 200D 2640 FE0F ; fully-qualified # 🤽🏽‍♀️ woman playing water polo: medium skin tone +#SUPPORT(prf) 1F93D 1F3FE 200D 2640 FE0F ; fully-qualified # 🤽🏾‍♀️ woman playing water polo: medium-dark skin tone +#SUPPORT(prf) 1F93D 1F3FF 200D 2640 FE0F ; fully-qualified # 🤽🏿‍♀️ woman playing water polo: dark skin tone +1F93E ; fully-qualified # 🤾 person playing handball +1F93E 1F3FB ; fully-qualified # 🤾🏻 person playing handball: light skin tone +1F93E 1F3FC ; fully-qualified # 🤾🏼 person playing handball: medium-light skin tone +1F93E 1F3FD ; fully-qualified # 🤾🏽 person playing handball: medium skin tone +1F93E 1F3FE ; fully-qualified # 🤾🏾 person playing handball: medium-dark skin tone +1F93E 1F3FF ; fully-qualified # 🤾🏿 person playing handball: dark skin tone +#SUPPORT(prf) 1F93E 200D 2642 FE0F ; fully-qualified # 🤾‍♂️ man playing handball +#SUPPORT(prf) 1F93E 1F3FB 200D 2642 FE0F ; fully-qualified # 🤾🏻‍♂️ man playing handball: light skin tone +#SUPPORT(prf) 1F93E 1F3FC 200D 2642 FE0F ; fully-qualified # 🤾🏼‍♂️ man playing handball: medium-light skin tone +#SUPPORT(prf) 1F93E 1F3FD 200D 2642 FE0F ; fully-qualified # 🤾🏽‍♂️ man playing handball: medium skin tone +#SUPPORT(prf) 1F93E 1F3FE 200D 2642 FE0F ; fully-qualified # 🤾🏾‍♂️ man playing handball: medium-dark skin tone +#SUPPORT(prf) 1F93E 1F3FF 200D 2642 FE0F ; fully-qualified # 🤾🏿‍♂️ man playing handball: dark skin tone +#SUPPORT(prf) 1F93E 200D 2640 FE0F ; fully-qualified # 🤾‍♀️ woman playing handball +#SUPPORT(prf) 1F93E 1F3FB 200D 2640 FE0F ; fully-qualified # 🤾🏻‍♀️ woman playing handball: light skin tone +#SUPPORT(prf) 1F93E 1F3FC 200D 2640 FE0F ; fully-qualified # 🤾🏼‍♀️ woman playing handball: medium-light skin tone +#SUPPORT(prf) 1F93E 1F3FD 200D 2640 FE0F ; fully-qualified # 🤾🏽‍♀️ woman playing handball: medium skin tone +#SUPPORT(prf) 1F93E 1F3FE 200D 2640 FE0F ; fully-qualified # 🤾🏾‍♀️ woman playing handball: medium-dark skin tone +#SUPPORT(prf) 1F93E 1F3FF 200D 2640 FE0F ; fully-qualified # 🤾🏿‍♀️ woman playing handball: dark skin tone +1F939 ; fully-qualified # 🤹 person juggling +1F939 1F3FB ; fully-qualified # 🤹🏻 person juggling: light skin tone +1F939 1F3FC ; fully-qualified # 🤹🏼 person juggling: medium-light skin tone +1F939 1F3FD ; fully-qualified # 🤹🏽 person juggling: medium skin tone +1F939 1F3FE ; fully-qualified # 🤹🏾 person juggling: medium-dark skin tone +1F939 1F3FF ; fully-qualified # 🤹🏿 person juggling: dark skin tone +#SUPPORT(prf) 1F939 200D 2642 FE0F ; fully-qualified # 🤹‍♂️ man juggling +#SUPPORT(prf) 1F939 1F3FB 200D 2642 FE0F ; fully-qualified # 🤹🏻‍♂️ man juggling: light skin tone +#SUPPORT(prf) 1F939 1F3FC 200D 2642 FE0F ; fully-qualified # 🤹🏼‍♂️ man juggling: medium-light skin tone +#SUPPORT(prf) 1F939 1F3FD 200D 2642 FE0F ; fully-qualified # 🤹🏽‍♂️ man juggling: medium skin tone +#SUPPORT(prf) 1F939 1F3FE 200D 2642 FE0F ; fully-qualified # 🤹🏾‍♂️ man juggling: medium-dark skin tone +#SUPPORT(prf) 1F939 1F3FF 200D 2642 FE0F ; fully-qualified # 🤹🏿‍♂️ man juggling: dark skin tone +#SUPPORT(prf) 1F939 200D 2640 FE0F ; fully-qualified # 🤹‍♀️ woman juggling +#SUPPORT(prf) 1F939 1F3FB 200D 2640 FE0F ; fully-qualified # 🤹🏻‍♀️ woman juggling: light skin tone +#SUPPORT(prf) 1F939 1F3FC 200D 2640 FE0F ; fully-qualified # 🤹🏼‍♀️ woman juggling: medium-light skin tone +#SUPPORT(prf) 1F939 1F3FD 200D 2640 FE0F ; fully-qualified # 🤹🏽‍♀️ woman juggling: medium skin tone +#SUPPORT(prf) 1F939 1F3FE 200D 2640 FE0F ; fully-qualified # 🤹🏾‍♀️ woman juggling: medium-dark skin tone +#SUPPORT(prf) 1F939 1F3FF 200D 2640 FE0F ; fully-qualified # 🤹🏿‍♀️ woman juggling: dark skin tone + +# subgroup: person-resting +1F9D8 ; fully-qualified # 🧘 person in lotus position +1F9D8 1F3FB ; fully-qualified # 🧘🏻 person in lotus position: light skin tone +1F9D8 1F3FC ; fully-qualified # 🧘🏼 person in lotus position: medium-light skin tone +1F9D8 1F3FD ; fully-qualified # 🧘🏽 person in lotus position: medium skin tone +1F9D8 1F3FE ; fully-qualified # 🧘🏾 person in lotus position: medium-dark skin tone +1F9D8 1F3FF ; fully-qualified # 🧘🏿 person in lotus position: dark skin tone +#SUPPORT(prf) 1F9D8 200D 2642 FE0F ; fully-qualified # 🧘‍♂️ man in lotus position +#SUPPORT(prf) 1F9D8 1F3FB 200D 2642 FE0F ; fully-qualified # 🧘🏻‍♂️ man in lotus position: light skin tone +#SUPPORT(prf) 1F9D8 1F3FC 200D 2642 FE0F ; fully-qualified # 🧘🏼‍♂️ man in lotus position: medium-light skin tone +#SUPPORT(prf) 1F9D8 1F3FD 200D 2642 FE0F ; fully-qualified # 🧘🏽‍♂️ man in lotus position: medium skin tone +#SUPPORT(prf) 1F9D8 1F3FE 200D 2642 FE0F ; fully-qualified # 🧘🏾‍♂️ man in lotus position: medium-dark skin tone +#SUPPORT(prf) 1F9D8 1F3FF 200D 2642 FE0F ; fully-qualified # 🧘🏿‍♂️ man in lotus position: dark skin tone +#SUPPORT(prf) 1F9D8 200D 2640 FE0F ; fully-qualified # 🧘‍♀️ woman in lotus position +#SUPPORT(prf) 1F9D8 1F3FB 200D 2640 FE0F ; fully-qualified # 🧘🏻‍♀️ woman in lotus position: light skin tone +#SUPPORT(prf) 1F9D8 1F3FC 200D 2640 FE0F ; fully-qualified # 🧘🏼‍♀️ woman in lotus position: medium-light skin tone +#SUPPORT(prf) 1F9D8 1F3FD 200D 2640 FE0F ; fully-qualified # 🧘🏽‍♀️ woman in lotus position: medium skin tone +#SUPPORT(prf) 1F9D8 1F3FE 200D 2640 FE0F ; fully-qualified # 🧘🏾‍♀️ woman in lotus position: medium-dark skin tone +#SUPPORT(prf) 1F9D8 1F3FF 200D 2640 FE0F ; fully-qualified # 🧘🏿‍♀️ woman in lotus position: dark skin tone +1F6C0 ; fully-qualified # 🛀 person taking bath +1F6C0 1F3FB ; fully-qualified # 🛀🏻 person taking bath: light skin tone +1F6C0 1F3FC ; fully-qualified # 🛀🏼 person taking bath: medium-light skin tone +1F6C0 1F3FD ; fully-qualified # 🛀🏽 person taking bath: medium skin tone +1F6C0 1F3FE ; fully-qualified # 🛀🏾 person taking bath: medium-dark skin tone +1F6C0 1F3FF ; fully-qualified # 🛀🏿 person taking bath: dark skin tone +1F6CC ; fully-qualified # 🛌 person in bed +1F6CC 1F3FB ; fully-qualified # 🛌🏻 person in bed: light skin tone +1F6CC 1F3FC ; fully-qualified # 🛌🏼 person in bed: medium-light skin tone +1F6CC 1F3FD ; fully-qualified # 🛌🏽 person in bed: medium skin tone +1F6CC 1F3FE ; fully-qualified # 🛌🏾 person in bed: medium-dark skin tone +1F6CC 1F3FF ; fully-qualified # 🛌🏿 person in bed: dark skin tone + +# subgroup: family +#SUPPORT(prf) 1F9D1 200D 1F91D 200D 1F9D1 ; fully-qualified # 🧑‍🤝‍🧑 people holding hands +#SUPPORT(prf) 1F9D1 1F3FB 200D 1F91D 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏻‍🤝‍🧑🏻 people holding hands: light skin tone +#SUPPORT(prf) 1F9D1 1F3FC 200D 1F91D 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏼‍🤝‍🧑🏻 people holding hands: medium-light skin tone, light skin tone +#SUPPORT(prf) 1F9D1 1F3FC 200D 1F91D 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏼‍🤝‍🧑🏼 people holding hands: medium-light skin tone +#SUPPORT(prf) 1F9D1 1F3FD 200D 1F91D 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏽‍🤝‍🧑🏻 people holding hands: medium skin tone, light skin tone +#SUPPORT(prf) 1F9D1 1F3FD 200D 1F91D 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏽‍🤝‍🧑🏼 people holding hands: medium skin tone, medium-light skin tone +#SUPPORT(prf) 1F9D1 1F3FD 200D 1F91D 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏽‍🤝‍🧑🏽 people holding hands: medium skin tone +#SUPPORT(prf) 1F9D1 1F3FE 200D 1F91D 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏾‍🤝‍🧑🏻 people holding hands: medium-dark skin tone, light skin tone +#SUPPORT(prf) 1F9D1 1F3FE 200D 1F91D 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏾‍🤝‍🧑🏼 people holding hands: medium-dark skin tone, medium-light skin tone +#SUPPORT(prf) 1F9D1 1F3FE 200D 1F91D 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏾‍🤝‍🧑🏽 people holding hands: medium-dark skin tone, medium skin tone +#SUPPORT(prf) 1F9D1 1F3FE 200D 1F91D 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏾‍🤝‍🧑🏾 people holding hands: medium-dark skin tone +#SUPPORT(prf) 1F9D1 1F3FF 200D 1F91D 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏿‍🤝‍🧑🏻 people holding hands: dark skin tone, light skin tone +#SUPPORT(prf) 1F9D1 1F3FF 200D 1F91D 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏿‍🤝‍🧑🏼 people holding hands: dark skin tone, medium-light skin tone +#SUPPORT(prf) 1F9D1 1F3FF 200D 1F91D 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏿‍🤝‍🧑🏽 people holding hands: dark skin tone, medium skin tone +#SUPPORT(prf) 1F9D1 1F3FF 200D 1F91D 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏿‍🤝‍🧑🏾 people holding hands: dark skin tone, medium-dark skin tone +#SUPPORT(prf) 1F9D1 1F3FF 200D 1F91D 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏿‍🤝‍🧑🏿 people holding hands: dark skin tone +1F46D ; fully-qualified # 👭 women holding hands +1F46D 1F3FB ; fully-qualified # 👭🏻 women holding hands: light skin tone +#SUPPORT(prf) 1F469 1F3FC 200D 1F91D 200D 1F469 1F3FB ; fully-qualified # 👩🏼‍🤝‍👩🏻 women holding hands: medium-light skin tone, light skin tone +1F46D 1F3FC ; fully-qualified # 👭🏼 women holding hands: medium-light skin tone +#SUPPORT(prf) 1F469 1F3FD 200D 1F91D 200D 1F469 1F3FB ; fully-qualified # 👩🏽‍🤝‍👩🏻 women holding hands: medium skin tone, light skin tone +#SUPPORT(prf) 1F469 1F3FD 200D 1F91D 200D 1F469 1F3FC ; fully-qualified # 👩🏽‍🤝‍👩🏼 women holding hands: medium skin tone, medium-light skin tone +1F46D 1F3FD ; fully-qualified # 👭🏽 women holding hands: medium skin tone +#SUPPORT(prf) 1F469 1F3FE 200D 1F91D 200D 1F469 1F3FB ; fully-qualified # 👩🏾‍🤝‍👩🏻 women holding hands: medium-dark skin tone, light skin tone +#SUPPORT(prf) 1F469 1F3FE 200D 1F91D 200D 1F469 1F3FC ; fully-qualified # 👩🏾‍🤝‍👩🏼 women holding hands: medium-dark skin tone, medium-light skin tone +#SUPPORT(prf) 1F469 1F3FE 200D 1F91D 200D 1F469 1F3FD ; fully-qualified # 👩🏾‍🤝‍👩🏽 women holding hands: medium-dark skin tone, medium skin tone +1F46D 1F3FE ; fully-qualified # 👭🏾 women holding hands: medium-dark skin tone +#SUPPORT(prf) 1F469 1F3FF 200D 1F91D 200D 1F469 1F3FB ; fully-qualified # 👩🏿‍🤝‍👩🏻 women holding hands: dark skin tone, light skin tone +#SUPPORT(prf) 1F469 1F3FF 200D 1F91D 200D 1F469 1F3FC ; fully-qualified # 👩🏿‍🤝‍👩🏼 women holding hands: dark skin tone, medium-light skin tone +#SUPPORT(prf) 1F469 1F3FF 200D 1F91D 200D 1F469 1F3FD ; fully-qualified # 👩🏿‍🤝‍👩🏽 women holding hands: dark skin tone, medium skin tone +#SUPPORT(prf) 1F469 1F3FF 200D 1F91D 200D 1F469 1F3FE ; fully-qualified # 👩🏿‍🤝‍👩🏾 women holding hands: dark skin tone, medium-dark skin tone +1F46D 1F3FF ; fully-qualified # 👭🏿 women holding hands: dark skin tone +1F46B ; fully-qualified # 👫 woman and man holding hands +1F46B 1F3FB ; fully-qualified # 👫🏻 woman and man holding hands: light skin tone +#SUPPORT(prf) 1F469 1F3FB 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👩🏻‍🤝‍👨🏼 woman and man holding hands: light skin tone, medium-light skin tone +#SUPPORT(prf) 1F469 1F3FB 200D 1F91D 200D 1F468 1F3FD ; fully-qualified # 👩🏻‍🤝‍👨🏽 woman and man holding hands: light skin tone, medium skin tone +#SUPPORT(prf) 1F469 1F3FB 200D 1F91D 200D 1F468 1F3FE ; fully-qualified # 👩🏻‍🤝‍👨🏾 woman and man holding hands: light skin tone, medium-dark skin tone +#SUPPORT(prf) 1F469 1F3FB 200D 1F91D 200D 1F468 1F3FF ; fully-qualified # 👩🏻‍🤝‍👨🏿 woman and man holding hands: light skin tone, dark skin tone +#SUPPORT(prf) 1F469 1F3FC 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👩🏼‍🤝‍👨🏻 woman and man holding hands: medium-light skin tone, light skin tone +1F46B 1F3FC ; fully-qualified # 👫🏼 woman and man holding hands: medium-light skin tone +#SUPPORT(prf) 1F469 1F3FC 200D 1F91D 200D 1F468 1F3FD ; fully-qualified # 👩🏼‍🤝‍👨🏽 woman and man holding hands: medium-light skin tone, medium skin tone +#SUPPORT(prf) 1F469 1F3FC 200D 1F91D 200D 1F468 1F3FE ; fully-qualified # 👩🏼‍🤝‍👨🏾 woman and man holding hands: medium-light skin tone, medium-dark skin tone +#SUPPORT(prf) 1F469 1F3FC 200D 1F91D 200D 1F468 1F3FF ; fully-qualified # 👩🏼‍🤝‍👨🏿 woman and man holding hands: medium-light skin tone, dark skin tone +#SUPPORT(prf) 1F469 1F3FD 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👩🏽‍🤝‍👨🏻 woman and man holding hands: medium skin tone, light skin tone +#SUPPORT(prf) 1F469 1F3FD 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👩🏽‍🤝‍👨🏼 woman and man holding hands: medium skin tone, medium-light skin tone +1F46B 1F3FD ; fully-qualified # 👫🏽 woman and man holding hands: medium skin tone +#SUPPORT(prf) 1F469 1F3FD 200D 1F91D 200D 1F468 1F3FE ; fully-qualified # 👩🏽‍🤝‍👨🏾 woman and man holding hands: medium skin tone, medium-dark skin tone +#SUPPORT(prf) 1F469 1F3FD 200D 1F91D 200D 1F468 1F3FF ; fully-qualified # 👩🏽‍🤝‍👨🏿 woman and man holding hands: medium skin tone, dark skin tone +#SUPPORT(prf) 1F469 1F3FE 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👩🏾‍🤝‍👨🏻 woman and man holding hands: medium-dark skin tone, light skin tone +#SUPPORT(prf) 1F469 1F3FE 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👩🏾‍🤝‍👨🏼 woman and man holding hands: medium-dark skin tone, medium-light skin tone +#SUPPORT(prf) 1F469 1F3FE 200D 1F91D 200D 1F468 1F3FD ; fully-qualified # 👩🏾‍🤝‍👨🏽 woman and man holding hands: medium-dark skin tone, medium skin tone +1F46B 1F3FE ; fully-qualified # 👫🏾 woman and man holding hands: medium-dark skin tone +#SUPPORT(prf) 1F469 1F3FE 200D 1F91D 200D 1F468 1F3FF ; fully-qualified # 👩🏾‍🤝‍👨🏿 woman and man holding hands: medium-dark skin tone, dark skin tone +#SUPPORT(prf) 1F469 1F3FF 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👩🏿‍🤝‍👨🏻 woman and man holding hands: dark skin tone, light skin tone +#SUPPORT(prf) 1F469 1F3FF 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👩🏿‍🤝‍👨🏼 woman and man holding hands: dark skin tone, medium-light skin tone +#SUPPORT(prf) 1F469 1F3FF 200D 1F91D 200D 1F468 1F3FD ; fully-qualified # 👩🏿‍🤝‍👨🏽 woman and man holding hands: dark skin tone, medium skin tone +#SUPPORT(prf) 1F469 1F3FF 200D 1F91D 200D 1F468 1F3FE ; fully-qualified # 👩🏿‍🤝‍👨🏾 woman and man holding hands: dark skin tone, medium-dark skin tone +1F46B 1F3FF ; fully-qualified # 👫🏿 woman and man holding hands: dark skin tone +1F46C ; fully-qualified # 👬 men holding hands +1F46C 1F3FB ; fully-qualified # 👬🏻 men holding hands: light skin tone +#SUPPORT(prf) 1F468 1F3FC 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👨🏼‍🤝‍👨🏻 men holding hands: medium-light skin tone, light skin tone +1F46C 1F3FC ; fully-qualified # 👬🏼 men holding hands: medium-light skin tone +#SUPPORT(prf) 1F468 1F3FD 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👨🏽‍🤝‍👨🏻 men holding hands: medium skin tone, light skin tone +#SUPPORT(prf) 1F468 1F3FD 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👨🏽‍🤝‍👨🏼 men holding hands: medium skin tone, medium-light skin tone +1F46C 1F3FD ; fully-qualified # 👬🏽 men holding hands: medium skin tone +#SUPPORT(prf) 1F468 1F3FE 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👨🏾‍🤝‍👨🏻 men holding hands: medium-dark skin tone, light skin tone +#SUPPORT(prf) 1F468 1F3FE 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👨🏾‍🤝‍👨🏼 men holding hands: medium-dark skin tone, medium-light skin tone +#SUPPORT(prf) 1F468 1F3FE 200D 1F91D 200D 1F468 1F3FD ; fully-qualified # 👨🏾‍🤝‍👨🏽 men holding hands: medium-dark skin tone, medium skin tone +1F46C 1F3FE ; fully-qualified # 👬🏾 men holding hands: medium-dark skin tone +#SUPPORT(prf) 1F468 1F3FF 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👨🏿‍🤝‍👨🏻 men holding hands: dark skin tone, light skin tone +#SUPPORT(prf) 1F468 1F3FF 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👨🏿‍🤝‍👨🏼 men holding hands: dark skin tone, medium-light skin tone +#SUPPORT(prf) 1F468 1F3FF 200D 1F91D 200D 1F468 1F3FD ; fully-qualified # 👨🏿‍🤝‍👨🏽 men holding hands: dark skin tone, medium skin tone +#SUPPORT(prf) 1F468 1F3FF 200D 1F91D 200D 1F468 1F3FE ; fully-qualified # 👨🏿‍🤝‍👨🏾 men holding hands: dark skin tone, medium-dark skin tone +1F46C 1F3FF ; fully-qualified # 👬🏿 men holding hands: dark skin tone +1F48F ; fully-qualified # 💏 kiss +#SUPPORT(prf) 1F469 200D 2764 FE0F 200D 1F48B 200D 1F468 ; fully-qualified # 👩‍❤️‍💋‍👨 kiss: woman, man +#SUPPORT(prf) 1F468 200D 2764 FE0F 200D 1F48B 200D 1F468 ; fully-qualified # 👨‍❤️‍💋‍👨 kiss: man, man +#SUPPORT(prf) 1F469 200D 2764 FE0F 200D 1F48B 200D 1F469 ; fully-qualified # 👩‍❤️‍💋‍👩 kiss: woman, woman +#SUPPORT(prf) 1F491 ; fully-qualified # 💑 couple with heart +#SUPPORT(prf) 1F469 200D 2764 FE0F 200D 1F468 ; fully-qualified # 👩‍❤️‍👨 couple with heart: woman, man +#SUPPORT(prf) 1F468 200D 2764 FE0F 200D 1F468 ; fully-qualified # 👨‍❤️‍👨 couple with heart: man, man +#SUPPORT(prf) 1F469 200D 2764 FE0F 200D 1F469 ; fully-qualified # 👩‍❤️‍👩 couple with heart: woman, woman +1F46A ; fully-qualified # 👪 family +1F468 200D 1F469 200D 1F466 ; fully-qualified # 👨‍👩‍👦 family: man, woman, boy +1F468 200D 1F469 200D 1F467 ; fully-qualified # 👨‍👩‍👧 family: man, woman, girl +1F468 200D 1F469 200D 1F467 200D 1F466 ; fully-qualified # 👨‍👩‍👧‍👦 family: man, woman, girl, boy +1F468 200D 1F469 200D 1F466 200D 1F466 ; fully-qualified # 👨‍👩‍👦‍👦 family: man, woman, boy, boy +1F468 200D 1F469 200D 1F467 200D 1F467 ; fully-qualified # 👨‍👩‍👧‍👧 family: man, woman, girl, girl +1F468 200D 1F468 200D 1F466 ; fully-qualified # 👨‍👨‍👦 family: man, man, boy +1F468 200D 1F468 200D 1F467 ; fully-qualified # 👨‍👨‍👧 family: man, man, girl +1F468 200D 1F468 200D 1F467 200D 1F466 ; fully-qualified # 👨‍👨‍👧‍👦 family: man, man, girl, boy +1F468 200D 1F468 200D 1F466 200D 1F466 ; fully-qualified # 👨‍👨‍👦‍👦 family: man, man, boy, boy +1F468 200D 1F468 200D 1F467 200D 1F467 ; fully-qualified # 👨‍👨‍👧‍👧 family: man, man, girl, girl +1F469 200D 1F469 200D 1F466 ; fully-qualified # 👩‍👩‍👦 family: woman, woman, boy +1F469 200D 1F469 200D 1F467 ; fully-qualified # 👩‍👩‍👧 family: woman, woman, girl +1F469 200D 1F469 200D 1F467 200D 1F466 ; fully-qualified # 👩‍👩‍👧‍👦 family: woman, woman, girl, boy +1F469 200D 1F469 200D 1F466 200D 1F466 ; fully-qualified # 👩‍👩‍👦‍👦 family: woman, woman, boy, boy +1F469 200D 1F469 200D 1F467 200D 1F467 ; fully-qualified # 👩‍👩‍👧‍👧 family: woman, woman, girl, girl +1F468 200D 1F466 ; fully-qualified # 👨‍👦 family: man, boy +1F468 200D 1F466 200D 1F466 ; fully-qualified # 👨‍👦‍👦 family: man, boy, boy +1F468 200D 1F467 ; fully-qualified # 👨‍👧 family: man, girl +1F468 200D 1F467 200D 1F466 ; fully-qualified # 👨‍👧‍👦 family: man, girl, boy +1F468 200D 1F467 200D 1F467 ; fully-qualified # 👨‍👧‍👧 family: man, girl, girl +1F469 200D 1F466 ; fully-qualified # 👩‍👦 family: woman, boy +1F469 200D 1F466 200D 1F466 ; fully-qualified # 👩‍👦‍👦 family: woman, boy, boy +1F469 200D 1F467 ; fully-qualified # 👩‍👧 family: woman, girl +1F469 200D 1F467 200D 1F466 ; fully-qualified # 👩‍👧‍👦 family: woman, girl, boy +1F469 200D 1F467 200D 1F467 ; fully-qualified # 👩‍👧‍👧 family: woman, girl, girl + +# subgroup: person-symbol +1F5E3 FE0F ; fully-qualified # 🗣️ speaking head +1F464 ; fully-qualified # 👤 bust in silhouette +1F465 ; fully-qualified # 👥 busts in silhouette +1F463 ; fully-qualified # 👣 footprints + +# People & Body subtotal: 2212 +# People & Body subtotal: 447 w/o modifiers + +# group: Component + +# subgroup: skin-tone +1F3FB ; component # 🏻 light skin tone +1F3FC ; component # 🏼 medium-light skin tone +1F3FD ; component # 🏽 medium skin tone +1F3FE ; component # 🏾 medium-dark skin tone +1F3FF ; component # 🏿 dark skin tone + +# subgroup: hair-style +1F9B0 ; component # 🦰 red hair +1F9B1 ; component # 🦱 curly hair +1F9B3 ; component # 🦳 white hair +1F9B2 ; component # 🦲 bald + +# Component subtotal: 9 +# Component subtotal: 4 w/o modifiers + +# group: Animals & Nature + +# subgroup: animal-mammal +1F435 ; fully-qualified # 🐵 monkey face +1F412 ; fully-qualified # 🐒 monkey +1F98D ; fully-qualified # 🦍 gorilla +#SUPPORT(prf) 1F9A7 ; fully-qualified # 🦧 orangutan +1F436 ; fully-qualified # 🐶 dog face +1F415 ; fully-qualified # 🐕 dog +#SUPPORT(prf) 1F9AE ; fully-qualified # 🦮 guide dog +#SUPPORT(prf) 1F415 200D 1F9BA ; fully-qualified # 🐕‍🦺 service dog +1F429 ; fully-qualified # 🐩 poodle +1F43A ; fully-qualified # 🐺 wolf +1F98A ; fully-qualified # 🦊 fox +1F99D ; fully-qualified # 🦝 raccoon +1F431 ; fully-qualified # 🐱 cat face +1F408 ; fully-qualified # 🐈 cat +1F981 ; fully-qualified # 🦁 lion +1F42F ; fully-qualified # 🐯 tiger face +1F405 ; fully-qualified # 🐅 tiger +1F406 ; fully-qualified # 🐆 leopard +1F434 ; fully-qualified # 🐴 horse face +1F40E ; fully-qualified # 🐎 horse +1F984 ; fully-qualified # 🦄 unicorn +1F993 ; fully-qualified # 🦓 zebra +1F98C ; fully-qualified # 🦌 deer +1F42E ; fully-qualified # 🐮 cow face +1F402 ; fully-qualified # 🐂 ox +1F403 ; fully-qualified # 🐃 water buffalo +1F404 ; fully-qualified # 🐄 cow +1F437 ; fully-qualified # 🐷 pig face +1F416 ; fully-qualified # 🐖 pig +1F417 ; fully-qualified # 🐗 boar +1F43D ; fully-qualified # 🐽 pig nose +1F40F ; fully-qualified # 🐏 ram +1F411 ; fully-qualified # 🐑 ewe +1F410 ; fully-qualified # 🐐 goat +1F42A ; fully-qualified # 🐪 camel +1F42B ; fully-qualified # 🐫 two-hump camel +1F999 ; fully-qualified # 🦙 llama +1F992 ; fully-qualified # 🦒 giraffe +1F418 ; fully-qualified # 🐘 elephant +1F98F ; fully-qualified # 🦏 rhinoceros +1F99B ; fully-qualified # 🦛 hippopotamus +1F42D ; fully-qualified # 🐭 mouse face +1F401 ; fully-qualified # 🐁 mouse +1F400 ; fully-qualified # 🐀 rat +1F439 ; fully-qualified # 🐹 hamster +1F430 ; fully-qualified # 🐰 rabbit face +1F407 ; fully-qualified # 🐇 rabbit +1F43F FE0F ; fully-qualified # 🐿️ chipmunk +1F994 ; fully-qualified # 🦔 hedgehog +1F987 ; fully-qualified # 🦇 bat +1F43B ; fully-qualified # 🐻 bear +1F428 ; fully-qualified # 🐨 koala +1F43C ; fully-qualified # 🐼 panda +#SUPPORT(prf) 1F9A5 ; fully-qualified # 🦥 sloth +#SUPPORT(prf) 1F9A6 ; fully-qualified # 🦦 otter +#SUPPORT(prf) 1F9A8 ; fully-qualified # 🦨 skunk +1F998 ; fully-qualified # 🦘 kangaroo +1F9A1 ; fully-qualified # 🦡 badger +1F43E ; fully-qualified # 🐾 paw prints + +# subgroup: animal-bird +1F983 ; fully-qualified # 🦃 turkey +1F414 ; fully-qualified # 🐔 chicken +1F413 ; fully-qualified # 🐓 rooster +1F423 ; fully-qualified # 🐣 hatching chick +1F424 ; fully-qualified # 🐤 baby chick +1F425 ; fully-qualified # 🐥 front-facing baby chick +1F426 ; fully-qualified # 🐦 bird +1F427 ; fully-qualified # 🐧 penguin +1F54A FE0F ; fully-qualified # 🕊️ dove +1F985 ; fully-qualified # 🦅 eagle +1F986 ; fully-qualified # 🦆 duck +1F9A2 ; fully-qualified # 🦢 swan +1F989 ; fully-qualified # 🦉 owl +#SUPPORT(prf) 1F9A9 ; fully-qualified # 🦩 flamingo +1F99A ; fully-qualified # 🦚 peacock +1F99C ; fully-qualified # 🦜 parrot + +# subgroup: animal-amphibian +1F438 ; fully-qualified # 🐸 frog + +# subgroup: animal-reptile +1F40A ; fully-qualified # 🐊 crocodile +1F422 ; fully-qualified # 🐢 turtle +1F98E ; fully-qualified # 🦎 lizard +1F40D ; fully-qualified # 🐍 snake +1F432 ; fully-qualified # 🐲 dragon face +1F409 ; fully-qualified # 🐉 dragon +1F995 ; fully-qualified # 🦕 sauropod +1F996 ; fully-qualified # 🦖 T-Rex + +# subgroup: animal-marine +1F433 ; fully-qualified # 🐳 spouting whale +1F40B ; fully-qualified # 🐋 whale +1F42C ; fully-qualified # 🐬 dolphin +1F41F ; fully-qualified # 🐟 fish +1F420 ; fully-qualified # 🐠 tropical fish +1F421 ; fully-qualified # 🐡 blowfish +1F988 ; fully-qualified # 🦈 shark +1F419 ; fully-qualified # 🐙 octopus +1F41A ; fully-qualified # 🐚 spiral shell + +# subgroup: animal-bug +1F40C ; fully-qualified # 🐌 snail +1F98B ; fully-qualified # 🦋 butterfly +1F41B ; fully-qualified # 🐛 bug +1F41C ; fully-qualified # 🐜 ant +1F41D ; fully-qualified # 🐝 honeybee +1F41E ; fully-qualified # 🐞 lady beetle +1F997 ; fully-qualified # 🦗 cricket +1F577 FE0F ; fully-qualified # 🕷️ spider +1F578 FE0F ; fully-qualified # 🕸️ spider web +1F982 ; fully-qualified # 🦂 scorpion +1F99F ; fully-qualified # 🦟 mosquito +1F9A0 ; fully-qualified # 🦠 microbe + +# subgroup: plant-flower +1F490 ; fully-qualified # 💐 bouquet +1F338 ; fully-qualified # 🌸 cherry blossom +1F4AE ; fully-qualified # 💮 white flower +1F3F5 FE0F ; fully-qualified # 🏵️ rosette +1F339 ; fully-qualified # 🌹 rose +1F940 ; fully-qualified # 🥀 wilted flower +1F33A ; fully-qualified # 🌺 hibiscus +1F33B ; fully-qualified # 🌻 sunflower +1F33C ; fully-qualified # 🌼 blossom +1F337 ; fully-qualified # 🌷 tulip + +# subgroup: plant-other +1F331 ; fully-qualified # 🌱 seedling +1F332 ; fully-qualified # 🌲 evergreen tree +1F333 ; fully-qualified # 🌳 deciduous tree +1F334 ; fully-qualified # 🌴 palm tree +1F335 ; fully-qualified # 🌵 cactus +1F33E ; fully-qualified # 🌾 sheaf of rice +1F33F ; fully-qualified # 🌿 herb +2618 FE0F ; fully-qualified # ☘️ shamrock +1F340 ; fully-qualified # 🍀 four leaf clover +1F341 ; fully-qualified # 🍁 maple leaf +1F342 ; fully-qualified # 🍂 fallen leaf +1F343 ; fully-qualified # 🍃 leaf fluttering in wind + +# Animals & Nature subtotal: 133 +# Animals & Nature subtotal: 133 w/o modifiers + +# group: Food & Drink + +# subgroup: food-fruit +1F347 ; fully-qualified # 🍇 grapes +1F348 ; fully-qualified # 🍈 melon +1F349 ; fully-qualified # 🍉 watermelon +1F34A ; fully-qualified # 🍊 tangerine +1F34B ; fully-qualified # 🍋 lemon +1F34C ; fully-qualified # 🍌 banana +1F34D ; fully-qualified # 🍍 pineapple +1F96D ; fully-qualified # 🥭 mango +1F34E ; fully-qualified # 🍎 red apple +1F34F ; fully-qualified # 🍏 green apple +1F350 ; fully-qualified # 🍐 pear +1F351 ; fully-qualified # 🍑 peach +1F352 ; fully-qualified # 🍒 cherries +1F353 ; fully-qualified # 🍓 strawberry +1F95D ; fully-qualified # 🥝 kiwi fruit +1F345 ; fully-qualified # 🍅 tomato +1F965 ; fully-qualified # 🥥 coconut + +# subgroup: food-vegetable +1F951 ; fully-qualified # 🥑 avocado +1F346 ; fully-qualified # 🍆 eggplant +1F954 ; fully-qualified # 🥔 potato +1F955 ; fully-qualified # 🥕 carrot +1F33D ; fully-qualified # 🌽 ear of corn +1F336 FE0F ; fully-qualified # 🌶️ hot pepper +1F952 ; fully-qualified # 🥒 cucumber +1F96C ; fully-qualified # 🥬 leafy green +1F966 ; fully-qualified # 🥦 broccoli +#SUPPORT(prf) 1F9C4 ; fully-qualified # 🧄 garlic +#SUPPORT(prf) 1F9C5 ; fully-qualified # 🧅 onion +1F344 ; fully-qualified # 🍄 mushroom +1F95C ; fully-qualified # 🥜 peanuts +1F330 ; fully-qualified # 🌰 chestnut + +# subgroup: food-prepared +1F35E ; fully-qualified # 🍞 bread +1F950 ; fully-qualified # 🥐 croissant +1F956 ; fully-qualified # 🥖 baguette bread +1F968 ; fully-qualified # 🥨 pretzel +1F96F ; fully-qualified # 🥯 bagel +1F95E ; fully-qualified # 🥞 pancakes +#SUPPORT(prf) 1F9C7 ; fully-qualified # 🧇 waffle +1F9C0 ; fully-qualified # 🧀 cheese wedge +1F356 ; fully-qualified # 🍖 meat on bone +1F357 ; fully-qualified # 🍗 poultry leg +1F969 ; fully-qualified # 🥩 cut of meat +1F953 ; fully-qualified # 🥓 bacon +1F354 ; fully-qualified # 🍔 hamburger +1F35F ; fully-qualified # 🍟 french fries +1F355 ; fully-qualified # 🍕 pizza +1F32D ; fully-qualified # 🌭 hot dog +1F96A ; fully-qualified # 🥪 sandwich +1F32E ; fully-qualified # 🌮 taco +1F32F ; fully-qualified # 🌯 burrito +1F959 ; fully-qualified # 🥙 stuffed flatbread +#SUPPORT(prf) 1F9C6 ; fully-qualified # 🧆 falafel +1F95A ; fully-qualified # 🥚 egg +1F373 ; fully-qualified # 🍳 cooking +1F958 ; fully-qualified # 🥘 shallow pan of food +1F372 ; fully-qualified # 🍲 pot of food +1F963 ; fully-qualified # 🥣 bowl with spoon +1F957 ; fully-qualified # 🥗 green salad +1F37F ; fully-qualified # 🍿 popcorn +#SUPPORT(prf) 1F9C8 ; fully-qualified # 🧈 butter +1F9C2 ; fully-qualified # 🧂 salt +1F96B ; fully-qualified # 🥫 canned food + +# subgroup: food-asian +1F371 ; fully-qualified # 🍱 bento box +1F358 ; fully-qualified # 🍘 rice cracker +1F359 ; fully-qualified # 🍙 rice ball +1F35A ; fully-qualified # 🍚 cooked rice +1F35B ; fully-qualified # 🍛 curry rice +1F35C ; fully-qualified # 🍜 steaming bowl +1F35D ; fully-qualified # 🍝 spaghetti +1F360 ; fully-qualified # 🍠 roasted sweet potato +1F362 ; fully-qualified # 🍢 oden +1F363 ; fully-qualified # 🍣 sushi +1F364 ; fully-qualified # 🍤 fried shrimp +1F365 ; fully-qualified # 🍥 fish cake with swirl +1F96E ; fully-qualified # 🥮 moon cake +1F361 ; fully-qualified # 🍡 dango +1F95F ; fully-qualified # 🥟 dumpling +1F960 ; fully-qualified # 🥠 fortune cookie +1F961 ; fully-qualified # 🥡 takeout box + +# subgroup: food-marine +1F980 ; fully-qualified # 🦀 crab +1F99E ; fully-qualified # 🦞 lobster +1F990 ; fully-qualified # 🦐 shrimp +1F991 ; fully-qualified # 🦑 squid +#SUPPORT(prf) 1F9AA ; fully-qualified # 🦪 oyster + +# subgroup: food-sweet +1F366 ; fully-qualified # 🍦 soft ice cream +1F367 ; fully-qualified # 🍧 shaved ice +1F368 ; fully-qualified # 🍨 ice cream +1F369 ; fully-qualified # 🍩 doughnut +1F36A ; fully-qualified # 🍪 cookie +1F382 ; fully-qualified # 🎂 birthday cake +1F370 ; fully-qualified # 🍰 shortcake +1F9C1 ; fully-qualified # 🧁 cupcake +1F967 ; fully-qualified # 🥧 pie +1F36B ; fully-qualified # 🍫 chocolate bar +1F36C ; fully-qualified # 🍬 candy +1F36D ; fully-qualified # 🍭 lollipop +1F36E ; fully-qualified # 🍮 custard +1F36F ; fully-qualified # 🍯 honey pot + +# subgroup: drink +1F37C ; fully-qualified # 🍼 baby bottle +1F95B ; fully-qualified # 🥛 glass of milk +2615 ; fully-qualified # ☕ hot beverage +1F375 ; fully-qualified # 🍵 teacup without handle +1F376 ; fully-qualified # 🍶 sake +1F37E ; fully-qualified # 🍾 bottle with popping cork +1F377 ; fully-qualified # 🍷 wine glass +1F378 ; fully-qualified # 🍸 cocktail glass +1F379 ; fully-qualified # 🍹 tropical drink +1F37A ; fully-qualified # 🍺 beer mug +1F37B ; fully-qualified # 🍻 clinking beer mugs +1F942 ; fully-qualified # 🥂 clinking glasses +1F943 ; fully-qualified # 🥃 tumbler glass +1F964 ; fully-qualified # 🥤 cup with straw +#SUPPORT(prf) 1F9C3 ; fully-qualified # 🧃 beverage box +#SUPPORT(prf) 1F9C9 ; fully-qualified # 🧉 mate +#SUPPORT(prf) 1F9CA ; fully-qualified # 🧊 ice cube + +# subgroup: dishware +1F962 ; fully-qualified # 🥢 chopsticks +1F37D FE0F ; fully-qualified # 🍽️ fork and knife with plate +1F374 ; fully-qualified # 🍴 fork and knife +1F944 ; fully-qualified # 🥄 spoon +1F52A ; fully-qualified # 🔪 kitchen knife +1F3FA ; fully-qualified # 🏺 amphora + +# Food & Drink subtotal: 123 +# Food & Drink subtotal: 123 w/o modifiers + +# group: Travel & Places + +# subgroup: place-map +1F30D ; fully-qualified # 🌍 globe showing Europe-Africa +1F30E ; fully-qualified # 🌎 globe showing Americas +1F30F ; fully-qualified # 🌏 globe showing Asia-Australia +1F310 ; fully-qualified # 🌐 globe with meridians +1F5FA FE0F ; fully-qualified # 🗺️ world map +1F5FE ; fully-qualified # 🗾 map of Japan +1F9ED ; fully-qualified # 🧭 compass + +# subgroup: place-geographic +1F3D4 FE0F ; fully-qualified # 🏔️ snow-capped mountain +26F0 FE0F ; fully-qualified # ⛰️ mountain +1F30B ; fully-qualified # 🌋 volcano +1F5FB ; fully-qualified # 🗻 mount fuji +1F3D5 FE0F ; fully-qualified # 🏕️ camping +1F3D6 FE0F ; fully-qualified # 🏖️ beach with umbrella +1F3DC FE0F ; fully-qualified # 🏜️ desert +1F3DD FE0F ; fully-qualified # 🏝️ desert island +1F3DE FE0F ; fully-qualified # 🏞️ national park + +# subgroup: place-building +1F3DF FE0F ; fully-qualified # 🏟️ stadium +1F3DB FE0F ; fully-qualified # 🏛️ classical building +1F3D7 FE0F ; fully-qualified # 🏗️ building construction +1F9F1 ; fully-qualified # 🧱 brick +1F3D8 FE0F ; fully-qualified # 🏘️ houses +1F3DA FE0F ; fully-qualified # 🏚️ derelict house +1F3E0 ; fully-qualified # 🏠 house +1F3E1 ; fully-qualified # 🏡 house with garden +1F3E2 ; fully-qualified # 🏢 office building +1F3E3 ; fully-qualified # 🏣 Japanese post office +1F3E4 ; fully-qualified # 🏤 post office +1F3E5 ; fully-qualified # 🏥 hospital +1F3E6 ; fully-qualified # 🏦 bank +1F3E8 ; fully-qualified # 🏨 hotel +1F3E9 ; fully-qualified # 🏩 love hotel +1F3EA ; fully-qualified # 🏪 convenience store +1F3EB ; fully-qualified # 🏫 school +1F3EC ; fully-qualified # 🏬 department store +1F3ED ; fully-qualified # 🏭 factory +1F3EF ; fully-qualified # 🏯 Japanese castle +1F3F0 ; fully-qualified # 🏰 castle +1F492 ; fully-qualified # 💒 wedding +1F5FC ; fully-qualified # 🗼 Tokyo tower +1F5FD ; fully-qualified # 🗽 Statue of Liberty + +# subgroup: place-religious +26EA ; fully-qualified # ⛪ church +1F54C ; fully-qualified # 🕌 mosque +#SUPPORT(prf) 1F6D5 ; fully-qualified # 🛕 hindu temple +1F54D ; fully-qualified # 🕍 synagogue +26E9 FE0F ; fully-qualified # ⛩️ shinto shrine +1F54B ; fully-qualified # 🕋 kaaba + +# subgroup: place-other +26F2 ; fully-qualified # ⛲ fountain +26FA ; fully-qualified # ⛺ tent +1F301 ; fully-qualified # 🌁 foggy +1F303 ; fully-qualified # 🌃 night with stars +1F3D9 FE0F ; fully-qualified # 🏙️ cityscape +1F304 ; fully-qualified # 🌄 sunrise over mountains +1F305 ; fully-qualified # 🌅 sunrise +1F306 ; fully-qualified # 🌆 cityscape at dusk +1F307 ; fully-qualified # 🌇 sunset +1F309 ; fully-qualified # 🌉 bridge at night +2668 FE0F ; fully-qualified # ♨️ hot springs +1F3A0 ; fully-qualified # 🎠 carousel horse +1F3A1 ; fully-qualified # 🎡 ferris wheel +1F3A2 ; fully-qualified # 🎢 roller coaster +1F488 ; fully-qualified # 💈 barber pole +1F3AA ; fully-qualified # 🎪 circus tent + +# subgroup: transport-ground +1F682 ; fully-qualified # 🚂 locomotive +1F683 ; fully-qualified # 🚃 railway car +1F684 ; fully-qualified # 🚄 high-speed train +1F685 ; fully-qualified # 🚅 bullet train +1F686 ; fully-qualified # 🚆 train +1F687 ; fully-qualified # 🚇 metro +1F688 ; fully-qualified # 🚈 light rail +1F689 ; fully-qualified # 🚉 station +1F68A ; fully-qualified # 🚊 tram +1F69D ; fully-qualified # 🚝 monorail +1F69E ; fully-qualified # 🚞 mountain railway +1F68B ; fully-qualified # 🚋 tram car +1F68C ; fully-qualified # 🚌 bus +1F68D ; fully-qualified # 🚍 oncoming bus +1F68E ; fully-qualified # 🚎 trolleybus +1F690 ; fully-qualified # 🚐 minibus +1F691 ; fully-qualified # 🚑 ambulance +1F692 ; fully-qualified # 🚒 fire engine +1F693 ; fully-qualified # 🚓 police car +1F694 ; fully-qualified # 🚔 oncoming police car +1F695 ; fully-qualified # 🚕 taxi +1F696 ; fully-qualified # 🚖 oncoming taxi +1F697 ; fully-qualified # 🚗 automobile +1F698 ; fully-qualified # 🚘 oncoming automobile +1F699 ; fully-qualified # 🚙 sport utility vehicle +1F69A ; fully-qualified # 🚚 delivery truck +1F69B ; fully-qualified # 🚛 articulated lorry +1F69C ; fully-qualified # 🚜 tractor +1F3CE FE0F ; fully-qualified # 🏎️ racing car +1F3CD FE0F ; fully-qualified # 🏍️ motorcycle +1F6F5 ; fully-qualified # 🛵 motor scooter +#SUPPORT(prf) 1F9BD ; fully-qualified # 🦽 manual wheelchair +#SUPPORT(prf) 1F9BC ; fully-qualified # 🦼 motorized wheelchair +#SUPPORT(prf) 1F6FA ; fully-qualified # 🛺 auto rickshaw +1F6B2 ; fully-qualified # 🚲 bicycle +1F6F4 ; fully-qualified # 🛴 kick scooter +1F6F9 ; fully-qualified # 🛹 skateboard +1F68F ; fully-qualified # 🚏 bus stop +1F6E3 FE0F ; fully-qualified # 🛣️ motorway +1F6E4 FE0F ; fully-qualified # 🛤️ railway track +1F6E2 FE0F ; fully-qualified # 🛢️ oil drum +26FD ; fully-qualified # ⛽ fuel pump +1F6A8 ; fully-qualified # 🚨 police car light +1F6A5 ; fully-qualified # 🚥 horizontal traffic light +1F6A6 ; fully-qualified # 🚦 vertical traffic light +1F6D1 ; fully-qualified # 🛑 stop sign +1F6A7 ; fully-qualified # 🚧 construction + +# subgroup: transport-water +2693 ; fully-qualified # ⚓ anchor +26F5 ; fully-qualified # ⛵ sailboat +1F6F6 ; fully-qualified # 🛶 canoe +1F6A4 ; fully-qualified # 🚤 speedboat +1F6F3 FE0F ; fully-qualified # 🛳️ passenger ship +26F4 FE0F ; fully-qualified # ⛴️ ferry +1F6E5 FE0F ; fully-qualified # 🛥️ motor boat +1F6A2 ; fully-qualified # 🚢 ship + +# subgroup: transport-air +2708 FE0F ; fully-qualified # ✈️ airplane +1F6E9 FE0F ; fully-qualified # 🛩️ small airplane +1F6EB ; fully-qualified # 🛫 airplane departure +1F6EC ; fully-qualified # 🛬 airplane arrival +#SUPPORT(prf) 1FA82 ; fully-qualified # 🪂 parachute +1F4BA ; fully-qualified # 💺 seat +1F681 ; fully-qualified # 🚁 helicopter +1F69F ; fully-qualified # 🚟 suspension railway +1F6A0 ; fully-qualified # 🚠 mountain cableway +1F6A1 ; fully-qualified # 🚡 aerial tramway +1F6F0 FE0F ; fully-qualified # 🛰️ satellite +1F680 ; fully-qualified # 🚀 rocket +1F6F8 ; fully-qualified # 🛸 flying saucer + +# subgroup: hotel +1F6CE FE0F ; fully-qualified # 🛎️ bellhop bell +1F9F3 ; fully-qualified # 🧳 luggage + +# subgroup: time +231B ; fully-qualified # ⌛ hourglass done +23F3 ; fully-qualified # ⏳ hourglass not done +231A ; fully-qualified # ⌚ watch +23F0 ; fully-qualified # ⏰ alarm clock +23F1 FE0F ; fully-qualified # ⏱️ stopwatch +23F2 FE0F ; fully-qualified # ⏲️ timer clock +1F570 FE0F ; fully-qualified # 🕰️ mantelpiece clock +1F55B ; fully-qualified # 🕛 twelve o’clock +1F567 ; fully-qualified # 🕧 twelve-thirty +1F550 ; fully-qualified # 🕐 one o’clock +1F55C ; fully-qualified # 🕜 one-thirty +1F551 ; fully-qualified # 🕑 two o’clock +1F55D ; fully-qualified # 🕝 two-thirty +1F552 ; fully-qualified # 🕒 three o’clock +1F55E ; fully-qualified # 🕞 three-thirty +1F553 ; fully-qualified # 🕓 four o’clock +1F55F ; fully-qualified # 🕟 four-thirty +1F554 ; fully-qualified # 🕔 five o’clock +1F560 ; fully-qualified # 🕠 five-thirty +1F555 ; fully-qualified # 🕕 six o’clock +1F561 ; fully-qualified # 🕡 six-thirty +1F556 ; fully-qualified # 🕖 seven o’clock +1F562 ; fully-qualified # 🕢 seven-thirty +1F557 ; fully-qualified # 🕗 eight o’clock +1F563 ; fully-qualified # 🕣 eight-thirty +1F558 ; fully-qualified # 🕘 nine o’clock +1F564 ; fully-qualified # 🕤 nine-thirty +1F559 ; fully-qualified # 🕙 ten o’clock +1F565 ; fully-qualified # 🕥 ten-thirty +1F55A ; fully-qualified # 🕚 eleven o’clock +1F566 ; fully-qualified # 🕦 eleven-thirty + +# subgroup: sky & weather +1F311 ; fully-qualified # 🌑 new moon +1F312 ; fully-qualified # 🌒 waxing crescent moon +1F313 ; fully-qualified # 🌓 first quarter moon +1F314 ; fully-qualified # 🌔 waxing gibbous moon +1F315 ; fully-qualified # 🌕 full moon +1F316 ; fully-qualified # 🌖 waning gibbous moon +1F317 ; fully-qualified # 🌗 last quarter moon +1F318 ; fully-qualified # 🌘 waning crescent moon +1F319 ; fully-qualified # 🌙 crescent moon +1F31A ; fully-qualified # 🌚 new moon face +1F31B ; fully-qualified # 🌛 first quarter moon face +1F31C ; fully-qualified # 🌜 last quarter moon face +1F321 FE0F ; fully-qualified # 🌡️ thermometer +2600 FE0F ; fully-qualified # ☀️ sun +1F31D ; fully-qualified # 🌝 full moon face +1F31E ; fully-qualified # 🌞 sun with face +#SUPPORT(prf) 1FA90 ; fully-qualified # 🪐 ringed planet +2B50 ; fully-qualified # ⭐ star +1F31F ; fully-qualified # 🌟 glowing star +1F320 ; fully-qualified # 🌠 shooting star +1F30C ; fully-qualified # 🌌 milky way +2601 FE0F ; fully-qualified # ☁️ cloud +26C5 ; fully-qualified # ⛅ sun behind cloud +26C8 FE0F ; fully-qualified # ⛈️ cloud with lightning and rain +1F324 FE0F ; fully-qualified # 🌤️ sun behind small cloud +1F325 FE0F ; fully-qualified # 🌥️ sun behind large cloud +1F326 FE0F ; fully-qualified # 🌦️ sun behind rain cloud +1F327 FE0F ; fully-qualified # 🌧️ cloud with rain +1F328 FE0F ; fully-qualified # 🌨️ cloud with snow +1F329 FE0F ; fully-qualified # 🌩️ cloud with lightning +1F32A FE0F ; fully-qualified # 🌪️ tornado +1F32B FE0F ; fully-qualified # 🌫️ fog +1F32C FE0F ; fully-qualified # 🌬️ wind face +1F300 ; fully-qualified # 🌀 cyclone +1F308 ; fully-qualified # 🌈 rainbow +1F302 ; fully-qualified # 🌂 closed umbrella +2602 FE0F ; fully-qualified # ☂️ umbrella +2614 ; fully-qualified # ☔ umbrella with rain drops +26F1 FE0F ; fully-qualified # ⛱️ umbrella on ground +26A1 ; fully-qualified # ⚡ high voltage +2744 FE0F ; fully-qualified # ❄️ snowflake +2603 FE0F ; fully-qualified # ☃️ snowman +26C4 ; fully-qualified # ⛄ snowman without snow +2604 FE0F ; fully-qualified # ☄️ comet +1F525 ; fully-qualified # 🔥 fire +1F4A7 ; fully-qualified # 💧 droplet +1F30A ; fully-qualified # 🌊 water wave + +# Travel & Places subtotal: 259 +# Travel & Places subtotal: 259 w/o modifiers + +# group: Activities + +# subgroup: event +1F383 ; fully-qualified # 🎃 jack-o-lantern +1F384 ; fully-qualified # 🎄 Christmas tree +1F386 ; fully-qualified # 🎆 fireworks +1F387 ; fully-qualified # 🎇 sparkler +1F9E8 ; fully-qualified # 🧨 firecracker +2728 ; fully-qualified # ✨ sparkles +1F388 ; fully-qualified # 🎈 balloon +1F389 ; fully-qualified # 🎉 party popper +1F38A ; fully-qualified # 🎊 confetti ball +1F38B ; fully-qualified # 🎋 tanabata tree +1F38D ; fully-qualified # 🎍 pine decoration +1F38E ; fully-qualified # 🎎 Japanese dolls +1F38F ; fully-qualified # 🎏 carp streamer +1F390 ; fully-qualified # 🎐 wind chime +1F391 ; fully-qualified # 🎑 moon viewing ceremony +1F9E7 ; fully-qualified # 🧧 red envelope +1F380 ; fully-qualified # 🎀 ribbon +1F381 ; fully-qualified # 🎁 wrapped gift +1F397 FE0F ; fully-qualified # 🎗️ reminder ribbon +1F39F FE0F ; fully-qualified # 🎟️ admission tickets +1F3AB ; fully-qualified # 🎫 ticket + +# subgroup: award-medal +1F396 FE0F ; fully-qualified # 🎖️ military medal +1F3C6 ; fully-qualified # 🏆 trophy +1F3C5 ; fully-qualified # 🏅 sports medal +1F947 ; fully-qualified # 🥇 1st place medal +1F948 ; fully-qualified # 🥈 2nd place medal +1F949 ; fully-qualified # 🥉 3rd place medal + +# subgroup: sport +26BD ; fully-qualified # ⚽ soccer ball +26BE ; fully-qualified # ⚾ baseball +1F94E ; fully-qualified # 🥎 softball +1F3C0 ; fully-qualified # 🏀 basketball +1F3D0 ; fully-qualified # 🏐 volleyball +1F3C8 ; fully-qualified # 🏈 american football +1F3C9 ; fully-qualified # 🏉 rugby football +1F3BE ; fully-qualified # 🎾 tennis +1F94F ; fully-qualified # 🥏 flying disc +1F3B3 ; fully-qualified # 🎳 bowling +1F3CF ; fully-qualified # 🏏 cricket game +1F3D1 ; fully-qualified # 🏑 field hockey +1F3D2 ; fully-qualified # 🏒 ice hockey +1F94D ; fully-qualified # 🥍 lacrosse +1F3D3 ; fully-qualified # 🏓 ping pong +1F3F8 ; fully-qualified # 🏸 badminton +1F94A ; fully-qualified # 🥊 boxing glove +1F94B ; fully-qualified # 🥋 martial arts uniform +1F945 ; fully-qualified # 🥅 goal net +26F3 ; fully-qualified # ⛳ flag in hole +26F8 FE0F ; fully-qualified # ⛸️ ice skate +1F3A3 ; fully-qualified # 🎣 fishing pole +#SUPPORT(prf) 1F93F ; fully-qualified # 🤿 diving mask +1F3BD ; fully-qualified # 🎽 running shirt +1F3BF ; fully-qualified # 🎿 skis +1F6F7 ; fully-qualified # 🛷 sled +1F94C ; fully-qualified # 🥌 curling stone + +# subgroup: game +1F3AF ; fully-qualified # 🎯 direct hit +#SUPPORT(prf) 1FA80 ; fully-qualified # 🪀 yo-yo +#SUPPORT(prf) 1FA81 ; fully-qualified # 🪁 kite +1F3B1 ; fully-qualified # 🎱 pool 8 ball +1F52E ; fully-qualified # 🔮 crystal ball +1F9FF ; fully-qualified # 🧿 nazar amulet +1F3AE ; fully-qualified # 🎮 video game +1F579 FE0F ; fully-qualified # 🕹️ joystick +1F3B0 ; fully-qualified # 🎰 slot machine +1F3B2 ; fully-qualified # 🎲 game die +1F9E9 ; fully-qualified # 🧩 puzzle piece +1F9F8 ; fully-qualified # 🧸 teddy bear +2660 FE0F ; fully-qualified # ♠️ spade suit +2665 FE0F ; fully-qualified # ♥️ heart suit +2666 FE0F ; fully-qualified # ♦️ diamond suit +2663 FE0F ; fully-qualified # ♣️ club suit +265F FE0F ; fully-qualified # ♟️ chess pawn +1F0CF ; fully-qualified # 🃏 joker +1F004 ; fully-qualified # 🀄 mahjong red dragon +1F3B4 ; fully-qualified # 🎴 flower playing cards + +# subgroup: arts & crafts +1F3AD ; fully-qualified # 🎭 performing arts +1F5BC FE0F ; fully-qualified # 🖼️ framed picture +1F3A8 ; fully-qualified # 🎨 artist palette +1F9F5 ; fully-qualified # 🧵 thread +1F9F6 ; fully-qualified # 🧶 yarn + +# Activities subtotal: 90 +# Activities subtotal: 90 w/o modifiers + +# group: Objects + +# subgroup: clothing +1F453 ; fully-qualified # 👓 glasses +1F576 FE0F ; fully-qualified # 🕶️ sunglasses +1F97D ; fully-qualified # 🥽 goggles +1F97C ; fully-qualified # 🥼 lab coat +#SUPPORT(prf) 1F9BA ; fully-qualified # 🦺 safety vest +1F454 ; fully-qualified # 👔 necktie +1F455 ; fully-qualified # 👕 t-shirt +1F456 ; fully-qualified # 👖 jeans +1F9E3 ; fully-qualified # 🧣 scarf +1F9E4 ; fully-qualified # 🧤 gloves +1F9E5 ; fully-qualified # 🧥 coat +1F9E6 ; fully-qualified # 🧦 socks +1F457 ; fully-qualified # 👗 dress +1F458 ; fully-qualified # 👘 kimono +#SUPPORT(prf) 1F97B ; fully-qualified # 🥻 sari +#SUPPORT(prf) 1FA71 ; fully-qualified # 🩱 one-piece swimsuit +#SUPPORT(prf) 1FA72 ; fully-qualified # 🩲 swim brief +#SUPPORT(prf) 1FA73 ; fully-qualified # 🩳 shorts +1F459 ; fully-qualified # 👙 bikini +1F45A ; fully-qualified # 👚 woman’s clothes +1F45B ; fully-qualified # 👛 purse +1F45C ; fully-qualified # 👜 handbag +1F45D ; fully-qualified # 👝 clutch bag +1F6CD FE0F ; fully-qualified # 🛍️ shopping bags +1F392 ; fully-qualified # 🎒 backpack +1F45E ; fully-qualified # 👞 man’s shoe +1F45F ; fully-qualified # 👟 running shoe +1F97E ; fully-qualified # 🥾 hiking boot +1F97F ; fully-qualified # 🥿 flat shoe +1F460 ; fully-qualified # 👠 high-heeled shoe +1F461 ; fully-qualified # 👡 woman’s sandal +#SUPPORT(prf) 1FA70 ; fully-qualified # 🩰 ballet shoes +1F462 ; fully-qualified # 👢 woman’s boot +1F451 ; fully-qualified # 👑 crown +1F452 ; fully-qualified # 👒 woman’s hat +1F3A9 ; fully-qualified # 🎩 top hat +1F393 ; fully-qualified # 🎓 graduation cap +1F9E2 ; fully-qualified # 🧢 billed cap +26D1 FE0F ; fully-qualified # ⛑️ rescue worker’s helmet +1F4FF ; fully-qualified # 📿 prayer beads +1F484 ; fully-qualified # 💄 lipstick +1F48D ; fully-qualified # 💍 ring +1F48E ; fully-qualified # 💎 gem stone + +# subgroup: sound +1F507 ; fully-qualified # 🔇 muted speaker +1F508 ; fully-qualified # 🔈 speaker low volume +1F509 ; fully-qualified # 🔉 speaker medium volume +1F50A ; fully-qualified # 🔊 speaker high volume +1F4E2 ; fully-qualified # 📢 loudspeaker +1F4E3 ; fully-qualified # 📣 megaphone +1F4EF ; fully-qualified # 📯 postal horn +1F514 ; fully-qualified # 🔔 bell +1F515 ; fully-qualified # 🔕 bell with slash + +# subgroup: music +1F3BC ; fully-qualified # 🎼 musical score +1F3B5 ; fully-qualified # 🎵 musical note +1F3B6 ; fully-qualified # 🎶 musical notes +1F399 FE0F ; fully-qualified # 🎙️ studio microphone +1F39A FE0F ; fully-qualified # 🎚️ level slider +1F39B FE0F ; fully-qualified # 🎛️ control knobs +1F3A4 ; fully-qualified # 🎤 microphone +1F3A7 ; fully-qualified # 🎧 headphone +1F4FB ; fully-qualified # 📻 radio + +# subgroup: musical-instrument +1F3B7 ; fully-qualified # 🎷 saxophone +1F3B8 ; fully-qualified # 🎸 guitar +1F3B9 ; fully-qualified # 🎹 musical keyboard +1F3BA ; fully-qualified # 🎺 trumpet +1F3BB ; fully-qualified # 🎻 violin +#SUPPORT(prf) 1FA95 ; fully-qualified # 🪕 banjo +1F941 ; fully-qualified # 🥁 drum + +# subgroup: phone +1F4F1 ; fully-qualified # 📱 mobile phone +1F4F2 ; fully-qualified # 📲 mobile phone with arrow +260E FE0F ; fully-qualified # ☎️ telephone +1F4DE ; fully-qualified # 📞 telephone receiver +1F4DF ; fully-qualified # 📟 pager +1F4E0 ; fully-qualified # 📠 fax machine + +# subgroup: computer +1F50B ; fully-qualified # 🔋 battery +1F50C ; fully-qualified # 🔌 electric plug +1F4BB ; fully-qualified # 💻 laptop computer +1F5A5 FE0F ; fully-qualified # 🖥️ desktop computer +1F5A8 FE0F ; fully-qualified # 🖨️ printer +2328 FE0F ; fully-qualified # ⌨️ keyboard +1F5B1 FE0F ; fully-qualified # 🖱️ computer mouse +1F5B2 FE0F ; fully-qualified # 🖲️ trackball +1F4BD ; fully-qualified # 💽 computer disk +1F4BE ; fully-qualified # 💾 floppy disk +1F4BF ; fully-qualified # 💿 optical disk +1F4C0 ; fully-qualified # 📀 dvd +1F9EE ; fully-qualified # 🧮 abacus + +# subgroup: light & video +1F3A5 ; fully-qualified # 🎥 movie camera +1F39E FE0F ; fully-qualified # 🎞️ film frames +1F4FD FE0F ; fully-qualified # 📽️ film projector +1F3AC ; fully-qualified # 🎬 clapper board +1F4FA ; fully-qualified # 📺 television +1F4F7 ; fully-qualified # 📷 camera +1F4F8 ; fully-qualified # 📸 camera with flash +1F4F9 ; fully-qualified # 📹 video camera +1F4FC ; fully-qualified # 📼 videocassette +1F50D ; fully-qualified # 🔍 magnifying glass tilted left +1F50E ; fully-qualified # 🔎 magnifying glass tilted right +1F56F FE0F ; fully-qualified # 🕯️ candle +1F4A1 ; fully-qualified # 💡 light bulb +1F526 ; fully-qualified # 🔦 flashlight +1F3EE ; fully-qualified # 🏮 red paper lantern +#SUPPORT(prf) 1FA94 ; fully-qualified # 🪔 diya lamp + +# subgroup: book-paper +1F4D4 ; fully-qualified # 📔 notebook with decorative cover +1F4D5 ; fully-qualified # 📕 closed book +1F4D6 ; fully-qualified # 📖 open book +1F4D7 ; fully-qualified # 📗 green book +1F4D8 ; fully-qualified # 📘 blue book +1F4D9 ; fully-qualified # 📙 orange book +1F4DA ; fully-qualified # 📚 books +1F4D3 ; fully-qualified # 📓 notebook +1F4D2 ; fully-qualified # 📒 ledger +1F4C3 ; fully-qualified # 📃 page with curl +1F4DC ; fully-qualified # 📜 scroll +1F4C4 ; fully-qualified # 📄 page facing up +1F4F0 ; fully-qualified # 📰 newspaper +1F5DE FE0F ; fully-qualified # 🗞️ rolled-up newspaper +1F4D1 ; fully-qualified # 📑 bookmark tabs +1F516 ; fully-qualified # 🔖 bookmark +1F3F7 FE0F ; fully-qualified # 🏷️ label + +# subgroup: money +1F4B0 ; fully-qualified # 💰 money bag +1F4B4 ; fully-qualified # 💴 yen banknote +1F4B5 ; fully-qualified # 💵 dollar banknote +1F4B6 ; fully-qualified # 💶 euro banknote +1F4B7 ; fully-qualified # 💷 pound banknote +1F4B8 ; fully-qualified # 💸 money with wings +1F4B3 ; fully-qualified # 💳 credit card +1F9FE ; fully-qualified # 🧾 receipt +1F4B9 ; fully-qualified # 💹 chart increasing with yen +1F4B1 ; fully-qualified # 💱 currency exchange +1F4B2 ; fully-qualified # 💲 heavy dollar sign + +# subgroup: mail +2709 FE0F ; fully-qualified # ✉️ envelope +1F4E7 ; fully-qualified # 📧 e-mail +1F4E8 ; fully-qualified # 📨 incoming envelope +1F4E9 ; fully-qualified # 📩 envelope with arrow +1F4E4 ; fully-qualified # 📤 outbox tray +1F4E5 ; fully-qualified # 📥 inbox tray +1F4E6 ; fully-qualified # 📦 package +1F4EB ; fully-qualified # 📫 closed mailbox with raised flag +1F4EA ; fully-qualified # 📪 closed mailbox with lowered flag +1F4EC ; fully-qualified # 📬 open mailbox with raised flag +1F4ED ; fully-qualified # 📭 open mailbox with lowered flag +1F4EE ; fully-qualified # 📮 postbox +1F5F3 FE0F ; fully-qualified # 🗳️ ballot box with ballot + +# subgroup: writing +270F FE0F ; fully-qualified # ✏️ pencil +2712 FE0F ; fully-qualified # ✒️ black nib +1F58B FE0F ; fully-qualified # 🖋️ fountain pen +1F58A FE0F ; fully-qualified # 🖊️ pen +1F58C FE0F ; fully-qualified # 🖌️ paintbrush +1F58D FE0F ; fully-qualified # 🖍️ crayon +1F4DD ; fully-qualified # 📝 memo + +# subgroup: office +1F4BC ; fully-qualified # 💼 briefcase +1F4C1 ; fully-qualified # 📁 file folder +1F4C2 ; fully-qualified # 📂 open file folder +1F5C2 FE0F ; fully-qualified # 🗂️ card index dividers +1F4C5 ; fully-qualified # 📅 calendar +1F4C6 ; fully-qualified # 📆 tear-off calendar +1F5D2 FE0F ; fully-qualified # 🗒️ spiral notepad +1F5D3 FE0F ; fully-qualified # 🗓️ spiral calendar +1F4C7 ; fully-qualified # 📇 card index +1F4C8 ; fully-qualified # 📈 chart increasing +1F4C9 ; fully-qualified # 📉 chart decreasing +1F4CA ; fully-qualified # 📊 bar chart +1F4CB ; fully-qualified # 📋 clipboard +1F4CC ; fully-qualified # 📌 pushpin +1F4CD ; fully-qualified # 📍 round pushpin +1F4CE ; fully-qualified # 📎 paperclip +1F587 FE0F ; fully-qualified # 🖇️ linked paperclips +1F4CF ; fully-qualified # 📏 straight ruler +1F4D0 ; fully-qualified # 📐 triangular ruler +2702 FE0F ; fully-qualified # ✂️ scissors +1F5C3 FE0F ; fully-qualified # 🗃️ card file box +1F5C4 FE0F ; fully-qualified # 🗄️ file cabinet +1F5D1 FE0F ; fully-qualified # 🗑️ wastebasket + +# subgroup: lock +1F512 ; fully-qualified # 🔒 locked +1F513 ; fully-qualified # 🔓 unlocked +1F50F ; fully-qualified # 🔏 locked with pen +1F510 ; fully-qualified # 🔐 locked with key +1F511 ; fully-qualified # 🔑 key +1F5DD FE0F ; fully-qualified # 🗝️ old key + +# subgroup: tool +1F528 ; fully-qualified # 🔨 hammer +#SUPPORT(prf) 1FA93 ; fully-qualified # 🪓 axe +26CF FE0F ; fully-qualified # ⛏️ pick +2692 FE0F ; fully-qualified # ⚒️ hammer and pick +1F6E0 FE0F ; fully-qualified # 🛠️ hammer and wrench +1F5E1 FE0F ; fully-qualified # 🗡️ dagger +2694 FE0F ; fully-qualified # ⚔️ crossed swords +1F52B ; fully-qualified # 🔫 pistol +1F3F9 ; fully-qualified # 🏹 bow and arrow +1F6E1 FE0F ; fully-qualified # 🛡️ shield +1F527 ; fully-qualified # 🔧 wrench +1F529 ; fully-qualified # 🔩 nut and bolt +2699 FE0F ; fully-qualified # ⚙️ gear +1F5DC FE0F ; fully-qualified # 🗜️ clamp +2696 FE0F ; fully-qualified # ⚖️ balance scale +#SUPPORT(prf) 1F9AF ; fully-qualified # 🦯 probing cane +1F517 ; fully-qualified # 🔗 link +26D3 FE0F ; fully-qualified # ⛓️ chains +1F9F0 ; fully-qualified # 🧰 toolbox +1F9F2 ; fully-qualified # 🧲 magnet + +# subgroup: science +2697 FE0F ; fully-qualified # ⚗️ alembic +1F9EA ; fully-qualified # 🧪 test tube +1F9EB ; fully-qualified # 🧫 petri dish +1F9EC ; fully-qualified # 🧬 dna +1F52C ; fully-qualified # 🔬 microscope +1F52D ; fully-qualified # 🔭 telescope +1F4E1 ; fully-qualified # 📡 satellite antenna + +# subgroup: medical +1F489 ; fully-qualified # 💉 syringe +#SUPPORT(prf) 1FA78 ; fully-qualified # 🩸 drop of blood +1F48A ; fully-qualified # 💊 pill +#SUPPORT(prf) 1FA79 ; fully-qualified # 🩹 adhesive bandage +#SUPPORT(prf) 1FA7A ; fully-qualified # 🩺 stethoscope + +# subgroup: household +1F6AA ; fully-qualified # 🚪 door +1F6CF FE0F ; fully-qualified # 🛏️ bed +1F6CB FE0F ; fully-qualified # 🛋️ couch and lamp +#SUPPORT(prf) 1FA91 ; fully-qualified # 🪑 chair +1F6BD ; fully-qualified # 🚽 toilet +1F6BF ; fully-qualified # 🚿 shower +1F6C1 ; fully-qualified # 🛁 bathtub +#SUPPORT(prf) 1FA92 ; fully-qualified # 🪒 razor +1F9F4 ; fully-qualified # 🧴 lotion bottle +1F9F7 ; fully-qualified # 🧷 safety pin +1F9F9 ; fully-qualified # 🧹 broom +1F9FA ; fully-qualified # 🧺 basket +1F9FB ; fully-qualified # 🧻 roll of paper +1F9FC ; fully-qualified # 🧼 soap +1F9FD ; fully-qualified # 🧽 sponge +1F9EF ; fully-qualified # 🧯 fire extinguisher +1F6D2 ; fully-qualified # 🛒 shopping cart + +# subgroup: other-object +1F6AC ; fully-qualified # 🚬 cigarette +26B0 FE0F ; fully-qualified # ⚰️ coffin +26B1 FE0F ; fully-qualified # ⚱️ funeral urn +1F5FF ; fully-qualified # 🗿 moai + +# Objects subtotal: 282 +# Objects subtotal: 282 w/o modifiers + +# group: Symbols + +# subgroup: transport-sign +1F3E7 ; fully-qualified # 🏧 ATM sign +1F6AE ; fully-qualified # 🚮 litter in bin sign +1F6B0 ; fully-qualified # 🚰 potable water +267F ; fully-qualified # ♿ wheelchair symbol +1F6B9 ; fully-qualified # 🚹 men’s room +1F6BA ; fully-qualified # 🚺 women’s room +1F6BB ; fully-qualified # 🚻 restroom +1F6BC ; fully-qualified # 🚼 baby symbol +1F6BE ; fully-qualified # 🚾 water closet +1F6C2 ; fully-qualified # 🛂 passport control +1F6C3 ; fully-qualified # 🛃 customs +1F6C4 ; fully-qualified # 🛄 baggage claim +1F6C5 ; fully-qualified # 🛅 left luggage + +# subgroup: warning +26A0 FE0F ; fully-qualified # ⚠️ warning +1F6B8 ; fully-qualified # 🚸 children crossing +26D4 ; fully-qualified # ⛔ no entry +1F6AB ; fully-qualified # 🚫 prohibited +1F6B3 ; fully-qualified # 🚳 no bicycles +1F6AD ; fully-qualified # 🚭 no smoking +1F6AF ; fully-qualified # 🚯 no littering +1F6B1 ; fully-qualified # 🚱 non-potable water +1F6B7 ; fully-qualified # 🚷 no pedestrians +1F4F5 ; fully-qualified # 📵 no mobile phones +1F51E ; fully-qualified # 🔞 no one under eighteen +2622 FE0F ; fully-qualified # ☢️ radioactive +2623 FE0F ; fully-qualified # ☣️ biohazard + +# subgroup: arrow +2B06 FE0F ; fully-qualified # ⬆️ up arrow +2197 FE0F ; fully-qualified # ↗️ up-right arrow +27A1 FE0F ; fully-qualified # ➡️ right arrow +2198 FE0F ; fully-qualified # ↘️ down-right arrow +2B07 FE0F ; fully-qualified # ⬇️ down arrow +2199 FE0F ; fully-qualified # ↙️ down-left arrow +2B05 FE0F ; fully-qualified # ⬅️ left arrow +2196 FE0F ; fully-qualified # ↖️ up-left arrow +2195 FE0F ; fully-qualified # ↕️ up-down arrow +2194 FE0F ; fully-qualified # ↔️ left-right arrow +21A9 FE0F ; fully-qualified # ↩️ right arrow curving left +21AA FE0F ; fully-qualified # ↪️ left arrow curving right +2934 FE0F ; fully-qualified # ⤴️ right arrow curving up +2935 FE0F ; fully-qualified # ⤵️ right arrow curving down +1F503 ; fully-qualified # 🔃 clockwise vertical arrows +1F504 ; fully-qualified # 🔄 counterclockwise arrows button +1F519 ; fully-qualified # 🔙 BACK arrow +1F51A ; fully-qualified # 🔚 END arrow +1F51B ; fully-qualified # 🔛 ON! arrow +1F51C ; fully-qualified # 🔜 SOON arrow +1F51D ; fully-qualified # 🔝 TOP arrow + +# subgroup: religion +1F6D0 ; fully-qualified # 🛐 place of worship +269B FE0F ; fully-qualified # ⚛️ atom symbol +1F549 FE0F ; fully-qualified # 🕉️ om +2721 FE0F ; fully-qualified # ✡️ star of David +2638 FE0F ; fully-qualified # ☸️ wheel of dharma +262F FE0F ; fully-qualified # ☯️ yin yang +271D FE0F ; fully-qualified # ✝️ latin cross +2626 FE0F ; fully-qualified # ☦️ orthodox cross +262A FE0F ; fully-qualified # ☪️ star and crescent +262E FE0F ; fully-qualified # ☮️ peace symbol +1F54E ; fully-qualified # 🕎 menorah +1F52F ; fully-qualified # 🔯 dotted six-pointed star + +# subgroup: zodiac +2648 ; fully-qualified # ♈ Aries +2649 ; fully-qualified # ♉ Taurus +264A ; fully-qualified # ♊ Gemini +264B ; fully-qualified # ♋ Cancer +264C ; fully-qualified # ♌ Leo +264D ; fully-qualified # ♍ Virgo +264E ; fully-qualified # ♎ Libra +264F ; fully-qualified # ♏ Scorpio +2650 ; fully-qualified # ♐ Sagittarius +2651 ; fully-qualified # ♑ Capricorn +2652 ; fully-qualified # ♒ Aquarius +2653 ; fully-qualified # ♓ Pisces +26CE ; fully-qualified # ⛎ Ophiuchus + +# subgroup: av-symbol +1F500 ; fully-qualified # 🔀 shuffle tracks button +1F501 ; fully-qualified # 🔁 repeat button +1F502 ; fully-qualified # 🔂 repeat single button +25B6 FE0F ; fully-qualified # ▶️ play button +23E9 ; fully-qualified # ⏩ fast-forward button +23ED FE0F ; fully-qualified # ⏭️ next track button +23EF FE0F ; fully-qualified # ⏯️ play or pause button +25C0 FE0F ; fully-qualified # ◀️ reverse button +23EA ; fully-qualified # ⏪ fast reverse button +23EE FE0F ; fully-qualified # ⏮️ last track button +1F53C ; fully-qualified # 🔼 upwards button +23EB ; fully-qualified # ⏫ fast up button +1F53D ; fully-qualified # 🔽 downwards button +23EC ; fully-qualified # ⏬ fast down button +23F8 FE0F ; fully-qualified # ⏸️ pause button +23F9 FE0F ; fully-qualified # ⏹️ stop button +23FA FE0F ; fully-qualified # ⏺️ record button +23CF FE0F ; fully-qualified # ⏏️ eject button +1F3A6 ; fully-qualified # 🎦 cinema +1F505 ; fully-qualified # 🔅 dim button +1F506 ; fully-qualified # 🔆 bright button +1F4F6 ; fully-qualified # 📶 antenna bars +1F4F3 ; fully-qualified # 📳 vibration mode +1F4F4 ; fully-qualified # 📴 mobile phone off + +# subgroup: gender +2640 FE0F ; fully-qualified # ♀️ female sign +2642 FE0F ; fully-qualified # ♂️ male sign + +# subgroup: other-symbol +2695 FE0F ; fully-qualified # ⚕️ medical symbol +267E FE0F ; fully-qualified # ♾️ infinity +267B FE0F ; fully-qualified # ♻️ recycling symbol +269C FE0F ; fully-qualified # ⚜️ fleur-de-lis +1F531 ; fully-qualified # 🔱 trident emblem +1F4DB ; fully-qualified # 📛 name badge +1F530 ; fully-qualified # 🔰 Japanese symbol for beginner +2B55 ; fully-qualified # ⭕ hollow red circle +2705 ; fully-qualified # ✅ check mark button +2611 FE0F ; fully-qualified # ☑️ check box with check +2714 FE0F ; fully-qualified # ✔️ check mark +2716 FE0F ; fully-qualified # ✖️ multiplication sign +274C ; fully-qualified # ❌ cross mark +274E ; fully-qualified # ❎ cross mark button +2795 ; fully-qualified # ➕ plus sign +2796 ; fully-qualified # ➖ minus sign +2797 ; fully-qualified # ➗ division sign +27B0 ; fully-qualified # ➰ curly loop +27BF ; fully-qualified # ➿ double curly loop +303D FE0F ; fully-qualified # 〽️ part alternation mark +2733 FE0F ; fully-qualified # ✳️ eight-spoked asterisk +2734 FE0F ; fully-qualified # ✴️ eight-pointed star +2747 FE0F ; fully-qualified # ❇️ sparkle +203C FE0F ; fully-qualified # ‼️ double exclamation mark +2049 FE0F ; fully-qualified # ⁉️ exclamation question mark +2753 ; fully-qualified # ❓ question mark +2754 ; fully-qualified # ❔ white question mark +2755 ; fully-qualified # ❕ white exclamation mark +2757 ; fully-qualified # ❗ exclamation mark +3030 FE0F ; fully-qualified # 〰️ wavy dash +00A9 FE0F ; fully-qualified # ©️ copyright +00AE FE0F ; fully-qualified # ®️ registered +2122 FE0F ; fully-qualified # ™️ trade mark + +# subgroup: keycap +0023 FE0F 20E3 ; fully-qualified # #️⃣ keycap: # +002A FE0F 20E3 ; fully-qualified # *️⃣ keycap: * +0030 FE0F 20E3 ; fully-qualified # 0️⃣ keycap: 0 +0031 FE0F 20E3 ; fully-qualified # 1️⃣ keycap: 1 +0032 FE0F 20E3 ; fully-qualified # 2️⃣ keycap: 2 +0033 FE0F 20E3 ; fully-qualified # 3️⃣ keycap: 3 +0034 FE0F 20E3 ; fully-qualified # 4️⃣ keycap: 4 +0035 FE0F 20E3 ; fully-qualified # 5️⃣ keycap: 5 +0036 FE0F 20E3 ; fully-qualified # 6️⃣ keycap: 6 +0037 FE0F 20E3 ; fully-qualified # 7️⃣ keycap: 7 +0038 FE0F 20E3 ; fully-qualified # 8️⃣ keycap: 8 +0039 FE0F 20E3 ; fully-qualified # 9️⃣ keycap: 9 +1F51F ; fully-qualified # 🔟 keycap: 10 + +# subgroup: alphanum +1F520 ; fully-qualified # 🔠 input latin uppercase +1F521 ; fully-qualified # 🔡 input latin lowercase +1F522 ; fully-qualified # 🔢 input numbers +1F523 ; fully-qualified # 🔣 input symbols +1F524 ; fully-qualified # 🔤 input latin letters +1F170 FE0F ; fully-qualified # 🅰️ A button (blood type) +1F18E ; fully-qualified # 🆎 AB button (blood type) +1F171 FE0F ; fully-qualified # 🅱️ B button (blood type) +1F191 ; fully-qualified # 🆑 CL button +1F192 ; fully-qualified # 🆒 COOL button +1F193 ; fully-qualified # 🆓 FREE button +2139 FE0F ; fully-qualified # ℹ️ information +1F194 ; fully-qualified # 🆔 ID button +24C2 FE0F ; fully-qualified # Ⓜ️ circled M +1F195 ; fully-qualified # 🆕 NEW button +1F196 ; fully-qualified # 🆖 NG button +1F17E FE0F ; fully-qualified # 🅾️ O button (blood type) +1F197 ; fully-qualified # 🆗 OK button +1F17F FE0F ; fully-qualified # 🅿️ P button +1F198 ; fully-qualified # 🆘 SOS button +1F199 ; fully-qualified # 🆙 UP! button +1F19A ; fully-qualified # 🆚 VS button +1F201 ; fully-qualified # 🈁 Japanese “here” button +1F202 FE0F ; fully-qualified # 🈂️ Japanese “service charge” button +1F237 FE0F ; fully-qualified # 🈷️ Japanese “monthly amount” button +1F236 ; fully-qualified # 🈶 Japanese “not free of charge” button +1F22F ; fully-qualified # 🈯 Japanese “reserved” button +1F250 ; fully-qualified # 🉐 Japanese “bargain” button +1F239 ; fully-qualified # 🈹 Japanese “discount” button +1F21A ; fully-qualified # 🈚 Japanese “free of charge” button +1F232 ; fully-qualified # 🈲 Japanese “prohibited” button +1F251 ; fully-qualified # 🉑 Japanese “acceptable” button +1F238 ; fully-qualified # 🈸 Japanese “application” button +1F234 ; fully-qualified # 🈴 Japanese “passing grade” button +1F233 ; fully-qualified # 🈳 Japanese “vacancy” button +3297 FE0F ; fully-qualified # ㊗️ Japanese “congratulations” button +3299 FE0F ; fully-qualified # ㊙️ Japanese “secret” button +1F23A ; fully-qualified # 🈺 Japanese “open for business” button +1F235 ; fully-qualified # 🈵 Japanese “no vacancy” button + +# subgroup: geometric +1F534 ; fully-qualified # 🔴 red circle +#SUPPORT(prf) 1F7E0 ; fully-qualified # 🟠 orange circle +#SUPPORT(prf) 1F7E1 ; fully-qualified # 🟡 yellow circle +#SUPPORT(prf) 1F7E2 ; fully-qualified # 🟢 green circle +1F535 ; fully-qualified # 🔵 blue circle +#SUPPORT(prf) 1F7E3 ; fully-qualified # 🟣 purple circle +#SUPPORT(prf) 1F7E4 ; fully-qualified # 🟤 brown circle +26AB ; fully-qualified # ⚫ black circle +26AA ; fully-qualified # ⚪ white circle +#SUPPORT(prf) 1F7E5 ; fully-qualified # 🟥 red square +#SUPPORT(prf) 1F7E7 ; fully-qualified # 🟧 orange square +#SUPPORT(prf) 1F7E8 ; fully-qualified # 🟨 yellow square +#SUPPORT(prf) 1F7E9 ; fully-qualified # 🟩 green square +#SUPPORT(prf) 1F7E6 ; fully-qualified # 🟦 blue square +#SUPPORT(prf) 1F7EA ; fully-qualified # 🟪 purple square +#SUPPORT(prf) 1F7EB ; fully-qualified # 🟫 brown square +2B1B ; fully-qualified # ⬛ black large square +2B1C ; fully-qualified # ⬜ white large square +25FC FE0F ; fully-qualified # ◼️ black medium square +25FB FE0F ; fully-qualified # ◻️ white medium square +25FE ; fully-qualified # ◾ black medium-small square +25FD ; fully-qualified # ◽ white medium-small square +25AA FE0F ; fully-qualified # ▪️ black small square +25AB FE0F ; fully-qualified # ▫️ white small square +1F536 ; fully-qualified # 🔶 large orange diamond +1F537 ; fully-qualified # 🔷 large blue diamond +1F538 ; fully-qualified # 🔸 small orange diamond +1F539 ; fully-qualified # 🔹 small blue diamond +1F53A ; fully-qualified # 🔺 red triangle pointed up +1F53B ; fully-qualified # 🔻 red triangle pointed down +1F4A0 ; fully-qualified # 💠 diamond with a dot +1F518 ; fully-qualified # 🔘 radio button +1F533 ; fully-qualified # 🔳 white square button +1F532 ; fully-qualified # 🔲 black square button + +# Symbols subtotal: 297 +# Symbols subtotal: 297 w/o modifiers + +# group: Flags + +# subgroup: flag +1F3C1 ; fully-qualified # 🏁 chequered flag +1F6A9 ; fully-qualified # 🚩 triangular flag +1F38C ; fully-qualified # 🎌 crossed flags +1F3F4 ; fully-qualified # 🏴 black flag +1F3F3 FE0F ; fully-qualified # 🏳️ white flag +1F3F3 FE0F 200D 1F308 ; fully-qualified # 🏳️‍🌈 rainbow flag +#SUPPORT(prf) 1F3F4 200D 2620 FE0F ; fully-qualified # 🏴‍☠️ pirate flag + +# subgroup: country-flag +1F1E6 1F1E8 ; fully-qualified # 🇦🇨 flag: Ascension Island +1F1E6 1F1E9 ; fully-qualified # 🇦🇩 flag: Andorra +1F1E6 1F1EA ; fully-qualified # 🇦🇪 flag: United Arab Emirates +1F1E6 1F1EB ; fully-qualified # 🇦🇫 flag: Afghanistan +1F1E6 1F1EC ; fully-qualified # 🇦🇬 flag: Antigua & Barbuda +1F1E6 1F1EE ; fully-qualified # 🇦🇮 flag: Anguilla +1F1E6 1F1F1 ; fully-qualified # 🇦🇱 flag: Albania +1F1E6 1F1F2 ; fully-qualified # 🇦🇲 flag: Armenia +1F1E6 1F1F4 ; fully-qualified # 🇦🇴 flag: Angola +1F1E6 1F1F6 ; fully-qualified # 🇦🇶 flag: Antarctica +1F1E6 1F1F7 ; fully-qualified # 🇦🇷 flag: Argentina +1F1E6 1F1F8 ; fully-qualified # 🇦🇸 flag: American Samoa +1F1E6 1F1F9 ; fully-qualified # 🇦🇹 flag: Austria +1F1E6 1F1FA ; fully-qualified # 🇦🇺 flag: Australia +1F1E6 1F1FC ; fully-qualified # 🇦🇼 flag: Aruba +1F1E6 1F1FD ; fully-qualified # 🇦🇽 flag: Åland Islands +1F1E6 1F1FF ; fully-qualified # 🇦🇿 flag: Azerbaijan +1F1E7 1F1E6 ; fully-qualified # 🇧🇦 flag: Bosnia & Herzegovina +1F1E7 1F1E7 ; fully-qualified # 🇧🇧 flag: Barbados +1F1E7 1F1E9 ; fully-qualified # 🇧🇩 flag: Bangladesh +1F1E7 1F1EA ; fully-qualified # 🇧🇪 flag: Belgium +1F1E7 1F1EB ; fully-qualified # 🇧🇫 flag: Burkina Faso +1F1E7 1F1EC ; fully-qualified # 🇧🇬 flag: Bulgaria +1F1E7 1F1ED ; fully-qualified # 🇧🇭 flag: Bahrain +1F1E7 1F1EE ; fully-qualified # 🇧🇮 flag: Burundi +1F1E7 1F1EF ; fully-qualified # 🇧🇯 flag: Benin +1F1E7 1F1F1 ; fully-qualified # 🇧🇱 flag: St. Barthélemy +1F1E7 1F1F2 ; fully-qualified # 🇧🇲 flag: Bermuda +1F1E7 1F1F3 ; fully-qualified # 🇧🇳 flag: Brunei +1F1E7 1F1F4 ; fully-qualified # 🇧🇴 flag: Bolivia +1F1E7 1F1F6 ; fully-qualified # 🇧🇶 flag: Caribbean Netherlands +1F1E7 1F1F7 ; fully-qualified # 🇧🇷 flag: Brazil +1F1E7 1F1F8 ; fully-qualified # 🇧🇸 flag: Bahamas +1F1E7 1F1F9 ; fully-qualified # 🇧🇹 flag: Bhutan +1F1E7 1F1FB ; fully-qualified # 🇧🇻 flag: Bouvet Island +1F1E7 1F1FC ; fully-qualified # 🇧🇼 flag: Botswana +1F1E7 1F1FE ; fully-qualified # 🇧🇾 flag: Belarus +1F1E7 1F1FF ; fully-qualified # 🇧🇿 flag: Belize +1F1E8 1F1E6 ; fully-qualified # 🇨🇦 flag: Canada +1F1E8 1F1E8 ; fully-qualified # 🇨🇨 flag: Cocos (Keeling) Islands +1F1E8 1F1E9 ; fully-qualified # 🇨🇩 flag: Congo - Kinshasa +1F1E8 1F1EB ; fully-qualified # 🇨🇫 flag: Central African Republic +1F1E8 1F1EC ; fully-qualified # 🇨🇬 flag: Congo - Brazzaville +1F1E8 1F1ED ; fully-qualified # 🇨🇭 flag: Switzerland +1F1E8 1F1EE ; fully-qualified # 🇨🇮 flag: Côte d’Ivoire +1F1E8 1F1F0 ; fully-qualified # 🇨🇰 flag: Cook Islands +1F1E8 1F1F1 ; fully-qualified # 🇨🇱 flag: Chile +1F1E8 1F1F2 ; fully-qualified # 🇨🇲 flag: Cameroon +1F1E8 1F1F3 ; fully-qualified # 🇨🇳 flag: China +1F1E8 1F1F4 ; fully-qualified # 🇨🇴 flag: Colombia +1F1E8 1F1F5 ; fully-qualified # 🇨🇵 flag: Clipperton Island +1F1E8 1F1F7 ; fully-qualified # 🇨🇷 flag: Costa Rica +1F1E8 1F1FA ; fully-qualified # 🇨🇺 flag: Cuba +1F1E8 1F1FB ; fully-qualified # 🇨🇻 flag: Cape Verde +1F1E8 1F1FC ; fully-qualified # 🇨🇼 flag: Curaçao +1F1E8 1F1FD ; fully-qualified # 🇨🇽 flag: Christmas Island +1F1E8 1F1FE ; fully-qualified # 🇨🇾 flag: Cyprus +1F1E8 1F1FF ; fully-qualified # 🇨🇿 flag: Czechia +1F1E9 1F1EA ; fully-qualified # 🇩🇪 flag: Germany +1F1E9 1F1EC ; fully-qualified # 🇩🇬 flag: Diego Garcia +1F1E9 1F1EF ; fully-qualified # 🇩🇯 flag: Djibouti +1F1E9 1F1F0 ; fully-qualified # 🇩🇰 flag: Denmark +1F1E9 1F1F2 ; fully-qualified # 🇩🇲 flag: Dominica +1F1E9 1F1F4 ; fully-qualified # 🇩🇴 flag: Dominican Republic +1F1E9 1F1FF ; fully-qualified # 🇩🇿 flag: Algeria +1F1EA 1F1E6 ; fully-qualified # 🇪🇦 flag: Ceuta & Melilla +1F1EA 1F1E8 ; fully-qualified # 🇪🇨 flag: Ecuador +1F1EA 1F1EA ; fully-qualified # 🇪🇪 flag: Estonia +1F1EA 1F1EC ; fully-qualified # 🇪🇬 flag: Egypt +1F1EA 1F1ED ; fully-qualified # 🇪🇭 flag: Western Sahara +1F1EA 1F1F7 ; fully-qualified # 🇪🇷 flag: Eritrea +1F1EA 1F1F8 ; fully-qualified # 🇪🇸 flag: Spain +1F1EA 1F1F9 ; fully-qualified # 🇪🇹 flag: Ethiopia +1F1EA 1F1FA ; fully-qualified # 🇪🇺 flag: European Union +1F1EB 1F1EE ; fully-qualified # 🇫🇮 flag: Finland +1F1EB 1F1EF ; fully-qualified # 🇫🇯 flag: Fiji +1F1EB 1F1F0 ; fully-qualified # 🇫🇰 flag: Falkland Islands +1F1EB 1F1F2 ; fully-qualified # 🇫🇲 flag: Micronesia +1F1EB 1F1F4 ; fully-qualified # 🇫🇴 flag: Faroe Islands +1F1EB 1F1F7 ; fully-qualified # 🇫🇷 flag: France +1F1EC 1F1E6 ; fully-qualified # 🇬🇦 flag: Gabon +1F1EC 1F1E7 ; fully-qualified # 🇬🇧 flag: United Kingdom +1F1EC 1F1E9 ; fully-qualified # 🇬🇩 flag: Grenada +1F1EC 1F1EA ; fully-qualified # 🇬🇪 flag: Georgia +1F1EC 1F1EB ; fully-qualified # 🇬🇫 flag: French Guiana +1F1EC 1F1EC ; fully-qualified # 🇬🇬 flag: Guernsey +1F1EC 1F1ED ; fully-qualified # 🇬🇭 flag: Ghana +1F1EC 1F1EE ; fully-qualified # 🇬🇮 flag: Gibraltar +1F1EC 1F1F1 ; fully-qualified # 🇬🇱 flag: Greenland +1F1EC 1F1F2 ; fully-qualified # 🇬🇲 flag: Gambia +1F1EC 1F1F3 ; fully-qualified # 🇬🇳 flag: Guinea +1F1EC 1F1F5 ; fully-qualified # 🇬🇵 flag: Guadeloupe +1F1EC 1F1F6 ; fully-qualified # 🇬🇶 flag: Equatorial Guinea +1F1EC 1F1F7 ; fully-qualified # 🇬🇷 flag: Greece +1F1EC 1F1F8 ; fully-qualified # 🇬🇸 flag: South Georgia & South Sandwich Islands +1F1EC 1F1F9 ; fully-qualified # 🇬🇹 flag: Guatemala +1F1EC 1F1FA ; fully-qualified # 🇬🇺 flag: Guam +1F1EC 1F1FC ; fully-qualified # 🇬🇼 flag: Guinea-Bissau +1F1EC 1F1FE ; fully-qualified # 🇬🇾 flag: Guyana +1F1ED 1F1F0 ; fully-qualified # 🇭🇰 flag: Hong Kong SAR China +1F1ED 1F1F2 ; fully-qualified # 🇭🇲 flag: Heard & McDonald Islands +1F1ED 1F1F3 ; fully-qualified # 🇭🇳 flag: Honduras +1F1ED 1F1F7 ; fully-qualified # 🇭🇷 flag: Croatia +1F1ED 1F1F9 ; fully-qualified # 🇭🇹 flag: Haiti +1F1ED 1F1FA ; fully-qualified # 🇭🇺 flag: Hungary +1F1EE 1F1E8 ; fully-qualified # 🇮🇨 flag: Canary Islands +1F1EE 1F1E9 ; fully-qualified # 🇮🇩 flag: Indonesia +1F1EE 1F1EA ; fully-qualified # 🇮🇪 flag: Ireland +1F1EE 1F1F1 ; fully-qualified # 🇮🇱 flag: Israel +1F1EE 1F1F2 ; fully-qualified # 🇮🇲 flag: Isle of Man +1F1EE 1F1F3 ; fully-qualified # 🇮🇳 flag: India +1F1EE 1F1F4 ; fully-qualified # 🇮🇴 flag: British Indian Ocean Territory +1F1EE 1F1F6 ; fully-qualified # 🇮🇶 flag: Iraq +1F1EE 1F1F7 ; fully-qualified # 🇮🇷 flag: Iran +1F1EE 1F1F8 ; fully-qualified # 🇮🇸 flag: Iceland +1F1EE 1F1F9 ; fully-qualified # 🇮🇹 flag: Italy +1F1EF 1F1EA ; fully-qualified # 🇯🇪 flag: Jersey +1F1EF 1F1F2 ; fully-qualified # 🇯🇲 flag: Jamaica +1F1EF 1F1F4 ; fully-qualified # 🇯🇴 flag: Jordan +1F1EF 1F1F5 ; fully-qualified # 🇯🇵 flag: Japan +1F1F0 1F1EA ; fully-qualified # 🇰🇪 flag: Kenya +1F1F0 1F1EC ; fully-qualified # 🇰🇬 flag: Kyrgyzstan +1F1F0 1F1ED ; fully-qualified # 🇰🇭 flag: Cambodia +1F1F0 1F1EE ; fully-qualified # 🇰🇮 flag: Kiribati +1F1F0 1F1F2 ; fully-qualified # 🇰🇲 flag: Comoros +1F1F0 1F1F3 ; fully-qualified # 🇰🇳 flag: St. Kitts & Nevis +1F1F0 1F1F5 ; fully-qualified # 🇰🇵 flag: North Korea +1F1F0 1F1F7 ; fully-qualified # 🇰🇷 flag: South Korea +1F1F0 1F1FC ; fully-qualified # 🇰🇼 flag: Kuwait +1F1F0 1F1FE ; fully-qualified # 🇰🇾 flag: Cayman Islands +1F1F0 1F1FF ; fully-qualified # 🇰🇿 flag: Kazakhstan +1F1F1 1F1E6 ; fully-qualified # 🇱🇦 flag: Laos +1F1F1 1F1E7 ; fully-qualified # 🇱🇧 flag: Lebanon +1F1F1 1F1E8 ; fully-qualified # 🇱🇨 flag: St. Lucia +1F1F1 1F1EE ; fully-qualified # 🇱🇮 flag: Liechtenstein +1F1F1 1F1F0 ; fully-qualified # 🇱🇰 flag: Sri Lanka +1F1F1 1F1F7 ; fully-qualified # 🇱🇷 flag: Liberia +1F1F1 1F1F8 ; fully-qualified # 🇱🇸 flag: Lesotho +1F1F1 1F1F9 ; fully-qualified # 🇱🇹 flag: Lithuania +1F1F1 1F1FA ; fully-qualified # 🇱🇺 flag: Luxembourg +1F1F1 1F1FB ; fully-qualified # 🇱🇻 flag: Latvia +1F1F1 1F1FE ; fully-qualified # 🇱🇾 flag: Libya +1F1F2 1F1E6 ; fully-qualified # 🇲🇦 flag: Morocco +1F1F2 1F1E8 ; fully-qualified # 🇲🇨 flag: Monaco +1F1F2 1F1E9 ; fully-qualified # 🇲🇩 flag: Moldova +1F1F2 1F1EA ; fully-qualified # 🇲🇪 flag: Montenegro +1F1F2 1F1EB ; fully-qualified # 🇲🇫 flag: St. Martin +1F1F2 1F1EC ; fully-qualified # 🇲🇬 flag: Madagascar +1F1F2 1F1ED ; fully-qualified # 🇲🇭 flag: Marshall Islands +1F1F2 1F1F0 ; fully-qualified # 🇲🇰 flag: Macedonia +1F1F2 1F1F1 ; fully-qualified # 🇲🇱 flag: Mali +1F1F2 1F1F2 ; fully-qualified # 🇲🇲 flag: Myanmar (Burma) +1F1F2 1F1F3 ; fully-qualified # 🇲🇳 flag: Mongolia +1F1F2 1F1F4 ; fully-qualified # 🇲🇴 flag: Macao SAR China +1F1F2 1F1F5 ; fully-qualified # 🇲🇵 flag: Northern Mariana Islands +1F1F2 1F1F6 ; fully-qualified # 🇲🇶 flag: Martinique +1F1F2 1F1F7 ; fully-qualified # 🇲🇷 flag: Mauritania +1F1F2 1F1F8 ; fully-qualified # 🇲🇸 flag: Montserrat +1F1F2 1F1F9 ; fully-qualified # 🇲🇹 flag: Malta +1F1F2 1F1FA ; fully-qualified # 🇲🇺 flag: Mauritius +1F1F2 1F1FB ; fully-qualified # 🇲🇻 flag: Maldives +1F1F2 1F1FC ; fully-qualified # 🇲🇼 flag: Malawi +1F1F2 1F1FD ; fully-qualified # 🇲🇽 flag: Mexico +1F1F2 1F1FE ; fully-qualified # 🇲🇾 flag: Malaysia +1F1F2 1F1FF ; fully-qualified # 🇲🇿 flag: Mozambique +1F1F3 1F1E6 ; fully-qualified # 🇳🇦 flag: Namibia +1F1F3 1F1E8 ; fully-qualified # 🇳🇨 flag: New Caledonia +1F1F3 1F1EA ; fully-qualified # 🇳🇪 flag: Niger +1F1F3 1F1EB ; fully-qualified # 🇳🇫 flag: Norfolk Island +1F1F3 1F1EC ; fully-qualified # 🇳🇬 flag: Nigeria +1F1F3 1F1EE ; fully-qualified # 🇳🇮 flag: Nicaragua +1F1F3 1F1F1 ; fully-qualified # 🇳🇱 flag: Netherlands +1F1F3 1F1F4 ; fully-qualified # 🇳🇴 flag: Norway +1F1F3 1F1F5 ; fully-qualified # 🇳🇵 flag: Nepal +1F1F3 1F1F7 ; fully-qualified # 🇳🇷 flag: Nauru +1F1F3 1F1FA ; fully-qualified # 🇳🇺 flag: Niue +1F1F3 1F1FF ; fully-qualified # 🇳🇿 flag: New Zealand +1F1F4 1F1F2 ; fully-qualified # 🇴🇲 flag: Oman +1F1F5 1F1E6 ; fully-qualified # 🇵🇦 flag: Panama +1F1F5 1F1EA ; fully-qualified # 🇵🇪 flag: Peru +1F1F5 1F1EB ; fully-qualified # 🇵🇫 flag: French Polynesia +1F1F5 1F1EC ; fully-qualified # 🇵🇬 flag: Papua New Guinea +1F1F5 1F1ED ; fully-qualified # 🇵🇭 flag: Philippines +1F1F5 1F1F0 ; fully-qualified # 🇵🇰 flag: Pakistan +1F1F5 1F1F1 ; fully-qualified # 🇵🇱 flag: Poland +1F1F5 1F1F2 ; fully-qualified # 🇵🇲 flag: St. Pierre & Miquelon +1F1F5 1F1F3 ; fully-qualified # 🇵🇳 flag: Pitcairn Islands +1F1F5 1F1F7 ; fully-qualified # 🇵🇷 flag: Puerto Rico +1F1F5 1F1F8 ; fully-qualified # 🇵🇸 flag: Palestinian Territories +1F1F5 1F1F9 ; fully-qualified # 🇵🇹 flag: Portugal +1F1F5 1F1FC ; fully-qualified # 🇵🇼 flag: Palau +1F1F5 1F1FE ; fully-qualified # 🇵🇾 flag: Paraguay +1F1F6 1F1E6 ; fully-qualified # 🇶🇦 flag: Qatar +1F1F7 1F1EA ; fully-qualified # 🇷🇪 flag: Réunion +1F1F7 1F1F4 ; fully-qualified # 🇷🇴 flag: Romania +1F1F7 1F1F8 ; fully-qualified # 🇷🇸 flag: Serbia +1F1F7 1F1FA ; fully-qualified # 🇷🇺 flag: Russia +1F1F7 1F1FC ; fully-qualified # 🇷🇼 flag: Rwanda +1F1F8 1F1E6 ; fully-qualified # 🇸🇦 flag: Saudi Arabia +1F1F8 1F1E7 ; fully-qualified # 🇸🇧 flag: Solomon Islands +1F1F8 1F1E8 ; fully-qualified # 🇸🇨 flag: Seychelles +1F1F8 1F1E9 ; fully-qualified # 🇸🇩 flag: Sudan +1F1F8 1F1EA ; fully-qualified # 🇸🇪 flag: Sweden +1F1F8 1F1EC ; fully-qualified # 🇸🇬 flag: Singapore +1F1F8 1F1ED ; fully-qualified # 🇸🇭 flag: St. Helena +1F1F8 1F1EE ; fully-qualified # 🇸🇮 flag: Slovenia +1F1F8 1F1EF ; fully-qualified # 🇸🇯 flag: Svalbard & Jan Mayen +1F1F8 1F1F0 ; fully-qualified # 🇸🇰 flag: Slovakia +1F1F8 1F1F1 ; fully-qualified # 🇸🇱 flag: Sierra Leone +1F1F8 1F1F2 ; fully-qualified # 🇸🇲 flag: San Marino +1F1F8 1F1F3 ; fully-qualified # 🇸🇳 flag: Senegal +1F1F8 1F1F4 ; fully-qualified # 🇸🇴 flag: Somalia +1F1F8 1F1F7 ; fully-qualified # 🇸🇷 flag: Suriname +1F1F8 1F1F8 ; fully-qualified # 🇸🇸 flag: South Sudan +1F1F8 1F1F9 ; fully-qualified # 🇸🇹 flag: São Tomé & Príncipe +1F1F8 1F1FB ; fully-qualified # 🇸🇻 flag: El Salvador +1F1F8 1F1FD ; fully-qualified # 🇸🇽 flag: Sint Maarten +1F1F8 1F1FE ; fully-qualified # 🇸🇾 flag: Syria +1F1F8 1F1FF ; fully-qualified # 🇸🇿 flag: Eswatini +1F1F9 1F1E6 ; fully-qualified # 🇹🇦 flag: Tristan da Cunha +1F1F9 1F1E8 ; fully-qualified # 🇹🇨 flag: Turks & Caicos Islands +1F1F9 1F1E9 ; fully-qualified # 🇹🇩 flag: Chad +1F1F9 1F1EB ; fully-qualified # 🇹🇫 flag: French Southern Territories +1F1F9 1F1EC ; fully-qualified # 🇹🇬 flag: Togo +1F1F9 1F1ED ; fully-qualified # 🇹🇭 flag: Thailand +1F1F9 1F1EF ; fully-qualified # 🇹🇯 flag: Tajikistan +1F1F9 1F1F0 ; fully-qualified # 🇹🇰 flag: Tokelau +1F1F9 1F1F1 ; fully-qualified # 🇹🇱 flag: Timor-Leste +1F1F9 1F1F2 ; fully-qualified # 🇹🇲 flag: Turkmenistan +1F1F9 1F1F3 ; fully-qualified # 🇹🇳 flag: Tunisia +1F1F9 1F1F4 ; fully-qualified # 🇹🇴 flag: Tonga +1F1F9 1F1F7 ; fully-qualified # 🇹🇷 flag: Turkey +1F1F9 1F1F9 ; fully-qualified # 🇹🇹 flag: Trinidad & Tobago +1F1F9 1F1FB ; fully-qualified # 🇹🇻 flag: Tuvalu +1F1F9 1F1FC ; fully-qualified # 🇹🇼 flag: Taiwan +1F1F9 1F1FF ; fully-qualified # 🇹🇿 flag: Tanzania +1F1FA 1F1E6 ; fully-qualified # 🇺🇦 flag: Ukraine +1F1FA 1F1EC ; fully-qualified # 🇺🇬 flag: Uganda +1F1FA 1F1F2 ; fully-qualified # 🇺🇲 flag: U.S. Outlying Islands +1F1FA 1F1F3 ; fully-qualified # 🇺🇳 flag: United Nations +1F1FA 1F1F8 ; fully-qualified # 🇺🇸 flag: United States +1F1FA 1F1FE ; fully-qualified # 🇺🇾 flag: Uruguay +1F1FA 1F1FF ; fully-qualified # 🇺🇿 flag: Uzbekistan +1F1FB 1F1E6 ; fully-qualified # 🇻🇦 flag: Vatican City +1F1FB 1F1E8 ; fully-qualified # 🇻🇨 flag: St. Vincent & Grenadines +1F1FB 1F1EA ; fully-qualified # 🇻🇪 flag: Venezuela +1F1FB 1F1EC ; fully-qualified # 🇻🇬 flag: British Virgin Islands +1F1FB 1F1EE ; fully-qualified # 🇻🇮 flag: U.S. Virgin Islands +1F1FB 1F1F3 ; fully-qualified # 🇻🇳 flag: Vietnam +1F1FB 1F1FA ; fully-qualified # 🇻🇺 flag: Vanuatu +1F1FC 1F1EB ; fully-qualified # 🇼🇫 flag: Wallis & Futuna +1F1FC 1F1F8 ; fully-qualified # 🇼🇸 flag: Samoa +1F1FD 1F1F0 ; fully-qualified # 🇽🇰 flag: Kosovo +1F1FE 1F1EA ; fully-qualified # 🇾🇪 flag: Yemen +1F1FE 1F1F9 ; fully-qualified # 🇾🇹 flag: Mayotte +1F1FF 1F1E6 ; fully-qualified # 🇿🇦 flag: South Africa +1F1FF 1F1F2 ; fully-qualified # 🇿🇲 flag: Zambia +1F1FF 1F1FC ; fully-qualified # 🇿🇼 flag: Zimbabwe + +# subgroup: subdivision-flag +1F3F4 E0067 E0062 E0065 E006E E0067 E007F ; fully-qualified # 🏴󠁧󠁢󠁥󠁮󠁧󠁿 flag: England +1F3F4 E0067 E0062 E0073 E0063 E0074 E007F ; fully-qualified # 🏴󠁧󠁢󠁳󠁣󠁴󠁿 flag: Scotland +1F3F4 E0067 E0062 E0077 E006C E0073 E007F ; fully-qualified # 🏴󠁧󠁢󠁷󠁬󠁳󠁿 flag: Wales + +# Flags subtotal: 271 +# Flags subtotal: 271 w/o modifiers + +# Status Counts +# fully-qualified : 3010 +# component : 9 + +#EOF diff --git a/app/userland/app-stdlib/scripts/generate-css-js.js b/app/userland/app-stdlib/scripts/generate-css-js.js new file mode 100644 index 0000000000..ec4698c603 --- /dev/null +++ b/app/userland/app-stdlib/scripts/generate-css-js.js @@ -0,0 +1,63 @@ +const fs = require('fs') +const path = require('path') + +const litElementPath = path.join(__dirname, '..', 'vendor', 'lit-element', 'lit-element.js') +console.log('Path:', litElementPath) +const cssdir = path.join(__dirname, '..', 'css') +handleFolder(cssdir) + +function handleFolder (dirpath) { + console.log('->', dirpath) + for (let name of fs.readdirSync(dirpath)) { + let itempath = path.join(dirpath, name) + let stat = fs.statSync(itempath) + if (stat.isDirectory()) { + handleFolder(itempath) + } else if (itempath.endsWith('.css')) { + handleCSSFile(itempath) + } + } +} + +// generates the css-js files +function handleCSSFile (cssPath) { + const cssJsPath = cssPathToJsPath(cssPath) + console.log('Generating', cssJsPath) + + // read the css + const css = fs.readFileSync(cssPath, 'utf8') + + // replace the css imports with js imports + const [newCss, imports] = extractAndReplaceImports(css) + + // write the css-js file + fs.writeFileSync(cssJsPath, `import {css} from '${path.relative(path.dirname(cssPath), litElementPath)}' +${imports} +const cssStr = css\` +${newCss} +\` +export default cssStr +`) +} + +// converts a css path to a css-js path +// eg reset.css -> reset.css.js +function cssPathToJsPath (cssPath) { + return cssPath.slice(0, cssPath.length - '.css'.length) + '.css.js' +} + +// finds all css imports and converts them into css-js module imports +// eg @import "./reset.less" -> import resetcss from './reset.css.js' +function extractAndReplaceImports (css) { + var imports = [] + var newCss = css.replace(/^@import "([^"]*)";$/gm, (line, path) => { + const importObj = { + path: cssPathToJsPath(path), + varname: path.split('/').pop().replace(/\./g, '').replace(/-/g, '') + } + imports.push(importObj) + return `\${${importObj.varname}}` + }) + var importsStr = imports.map(i => `import ${i.varname} from '${i.path}'`).join('\n') + return [newCss, importsStr] +} \ No newline at end of file diff --git a/app/userland/app-stdlib/scripts/generate-emoji-list.js b/app/userland/app-stdlib/scripts/generate-emoji-list.js new file mode 100644 index 0000000000..5f45239485 --- /dev/null +++ b/app/userland/app-stdlib/scripts/generate-emoji-list.js @@ -0,0 +1,70 @@ +const fs = require('fs') + +const DISALLOWED = new Set([ + '🔫', + '🔪', + '🖕', + '🗡️' +]) + +var emojiDataStr = fs.readFileSync(require('path').join(__dirname, 'emoji-data.txt'), 'utf8') + +var groups = [] +for (let groupStr of emojiDataStr.split('# group: ').slice(1)) { + let name = (/(.*)\n/.exec(groupStr))[1] + let emojis = new Set() + + if (name === 'Component') { + continue // skip + } + + let re = /$([0-9A-F\.\s]+);/gim + let match + while ((match = re.exec(groupStr))) { + let emoji = match[1].trim().split(' ').map(v => String.fromCodePoint(parseInt(v, 16))).join('') // parse out emoji + emoji = emoji.replace(/🏻|🏼|🏽|🏾|🏿/g, '') // strip skin tones + if (DISALLOWED.has(emoji)) continue // skip disallowed emojis + emojis.add(emoji) + } + + groups.push({name, emojis: Array.from(emojis)}) +} + +fs.writeFileSync(require('path').join(__dirname, 'emoji-list.js'), ` +export const SUGGESTED = [ + "❤", + "👀", + "🔥", + "🎉", + "✨", + "🆒", + '🙂', + '😂', + "😅", + '😢', + "😐", + "😮", + '😡', + "😤", + "🤭", + "🤔", + "🤨", + "🤯", + '👍', + "👎", + "👆", + "👏", + "🙌", + "🙏", + "👋", + "💪", + "💅", + "✊", + "👌", + "🤘", +] + +export const GROUPS = ${JSON.stringify(groups, null, 2)} + +export const FULL_LIST = GROUPS.map(({emojis}) => emojis).reduce((acc, v) => acc.concat(v), []) +`) \ No newline at end of file diff --git a/app/userland/app-stdlib/vendor/bytes/.editorconfig b/app/userland/app-stdlib/vendor/bytes/.editorconfig new file mode 100755 index 0000000000..cdb36c1b46 --- /dev/null +++ b/app/userland/app-stdlib/vendor/bytes/.editorconfig @@ -0,0 +1,11 @@ +# http://editorconfig.org +root = true + +[*] +charset = utf-8 +insert_final_newline = true +trim_trailing_whitespace = true + +[{*.js,*.json,*.yml}] +indent_size = 2 +indent_style = space diff --git a/app/userland/app-stdlib/vendor/bytes/.eslintignore b/app/userland/app-stdlib/vendor/bytes/.eslintignore new file mode 100755 index 0000000000..76b1021d09 --- /dev/null +++ b/app/userland/app-stdlib/vendor/bytes/.eslintignore @@ -0,0 +1,3 @@ +.nyc_output +coverage +node_modules diff --git a/app/userland/app-stdlib/vendor/bytes/.eslintrc.yml b/app/userland/app-stdlib/vendor/bytes/.eslintrc.yml new file mode 100755 index 0000000000..c1475b20ba --- /dev/null +++ b/app/userland/app-stdlib/vendor/bytes/.eslintrc.yml @@ -0,0 +1,9 @@ +root: true +extends: eslint:recommended +env: + node: true +rules: + eol-last: error + indent: ["error", 2, { "SwitchCase": 1 }] + no-mixed-spaces-and-tabs: error + no-trailing-spaces: error diff --git a/app/userland/app-stdlib/vendor/bytes/.gitignore b/app/userland/app-stdlib/vendor/bytes/.gitignore new file mode 100755 index 0000000000..f15b98e249 --- /dev/null +++ b/app/userland/app-stdlib/vendor/bytes/.gitignore @@ -0,0 +1,5 @@ +.nyc_output/ +coverage/ +node_modules/ +npm-debug.log +package-lock.json diff --git a/app/userland/app-stdlib/vendor/bytes/.travis.yml b/app/userland/app-stdlib/vendor/bytes/.travis.yml new file mode 100755 index 0000000000..5c5e0edd88 --- /dev/null +++ b/app/userland/app-stdlib/vendor/bytes/.travis.yml @@ -0,0 +1,77 @@ +language: node_js +node_js: + - "0.8" + - "0.10" + - "0.12" + - "1.8" + - "2.5" + - "3.3" + - "4.9" + - "5.12" + - "6.16" + - "7.10" + - "8.15" + - "9.11" + - "10.15" + - "11.7" +sudo: false +cache: + directories: + - node_modules +before_install: + # Configure npm + - | + # Skip updating shrinkwrap / lock + npm config set shrinkwrap false + # Setup Node.js version-specific dependencies + - | + # mocha for testing + # - use 2.x for Node.js < 0.10 + # - use 3.x for Node.js < 6 + if [[ "$(cut -d. -f1 <<< "$TRAVIS_NODE_VERSION")" -eq 0 && "$(cut -d. -f2 <<< "$TRAVIS_NODE_VERSION")" -lt 10 ]]; then + npm install --save-dev mocha@2.5.3 + elif [[ "$(cut -d. -f1 <<< "$TRAVIS_NODE_VERSION")" -lt 6 ]]; then + npm install --save-dev mocha@3.5.3 + fi + - | + # nyc for coverage + # - remove on Node.js < 6 + if [[ "$(cut -d. -f1 <<< "$TRAVIS_NODE_VERSION")" -lt 6 ]]; then + npm rm --save-dev nyc + fi + - | + # eslint for linting + # - remove on Node.js < 6 + if [[ "$(cut -d. -f1 <<< "$TRAVIS_NODE_VERSION")" -lt 6 ]]; then + node -pe 'Object.keys(require("./package").devDependencies).join("\n")' | \ + grep -E '^eslint(-|$)' | \ + xargs npm rm --save-dev + fi + # Update Node.js modules + - | + # Prune & rebuild node_modules + if [[ -d node_modules ]]; then + npm prune + npm rebuild + fi + +script: + - | + # Run test script, depending on nyc install + if [[ -n "$(npm -ps ls nyc)" ]]; then + npm run-script test-ci + else + npm test + fi + - | + # Run linting, depending on eslint install + if [[ -n "$(npm -ps ls eslint)" ]]; then + npm run-script lint + fi +after_script: + - | + # Upload coverage to coveralls, if exists + if [[ -d .nyc_output ]]; then + npm install --save-dev coveralls@2 + nyc report --reporter=text-lcov | coveralls + fi diff --git a/app/userland/app-stdlib/vendor/bytes/History.md b/app/userland/app-stdlib/vendor/bytes/History.md new file mode 100755 index 0000000000..cf6a5bb9cf --- /dev/null +++ b/app/userland/app-stdlib/vendor/bytes/History.md @@ -0,0 +1,87 @@ +3.1.0 / 2019-01-22 +================== + + * Add petabyte (`pb`) support + +3.0.0 / 2017-08-31 +================== + + * Change "kB" to "KB" in format output + * Remove support for Node.js 0.6 + * Remove support for ComponentJS + +2.5.0 / 2017-03-24 +================== + + * Add option "unit" + +2.4.0 / 2016-06-01 +================== + + * Add option "unitSeparator" + +2.3.0 / 2016-02-15 +================== + + * Drop partial bytes on all parsed units + * Fix non-finite numbers to `.format` to return `null` + * Fix parsing byte string that looks like hex + * perf: hoist regular expressions + +2.2.0 / 2015-11-13 +================== + + * add option "decimalPlaces" + * add option "fixedDecimals" + +2.1.0 / 2015-05-21 +================== + + * add `.format` export + * add `.parse` export + +2.0.2 / 2015-05-20 +================== + + * remove map recreation + * remove unnecessary object construction + +2.0.1 / 2015-05-07 +================== + + * fix browserify require + * remove node.extend dependency + +2.0.0 / 2015-04-12 +================== + + * add option "case" + * add option "thousandsSeparator" + * return "null" on invalid parse input + * support proper round-trip: bytes(bytes(num)) === num + * units no longer case sensitive when parsing + +1.0.0 / 2014-05-05 +================== + + * add negative support. fixes #6 + +0.3.0 / 2014-03-19 +================== + + * added terabyte support + +0.2.1 / 2013-04-01 +================== + + * add .component + +0.2.0 / 2012-10-28 +================== + + * bytes(200).should.eql('200b') + +0.1.0 / 2012-07-04 +================== + + * add bytes to string conversion [yields] diff --git a/app/userland/app-stdlib/vendor/bytes/LICENSE b/app/userland/app-stdlib/vendor/bytes/LICENSE new file mode 100755 index 0000000000..63e95a9633 --- /dev/null +++ b/app/userland/app-stdlib/vendor/bytes/LICENSE @@ -0,0 +1,23 @@ +(The MIT License) + +Copyright (c) 2012-2014 TJ Holowaychuk +Copyright (c) 2015 Jed Watson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/app/userland/app-stdlib/vendor/bytes/Readme.md b/app/userland/app-stdlib/vendor/bytes/Readme.md new file mode 100755 index 0000000000..6ad1ec6e2a --- /dev/null +++ b/app/userland/app-stdlib/vendor/bytes/Readme.md @@ -0,0 +1,126 @@ +# Bytes utility + +[![NPM Version][npm-image]][npm-url] +[![NPM Downloads][downloads-image]][downloads-url] +[![Build Status][travis-image]][travis-url] +[![Test Coverage][coveralls-image]][coveralls-url] + +Utility to parse a string bytes (ex: `1TB`) to bytes (`1099511627776`) and vice-versa. + +## Installation + +This is a [Node.js](https://nodejs.org/en/) module available through the +[npm registry](https://www.npmjs.com/). Installation is done using the +[`npm install` command](https://docs.npmjs.com/getting-started/installing-npm-packages-locally): + +```bash +$ npm install bytes +``` + +## Usage + +```js +var bytes = require('bytes'); +``` + +#### bytes.format(number value, [options]): string|null + +Format the given value in bytes into a string. If the value is negative, it is kept as such. If it is a float, it is + rounded. + +**Arguments** + +| Name | Type | Description | +|---------|----------|--------------------| +| value | `number` | Value in bytes | +| options | `Object` | Conversion options | + +**Options** + +| Property | Type | Description | +|-------------------|--------|-----------------------------------------------------------------------------------------| +| decimalPlaces | `number`|`null` | Maximum number of decimal places to include in output. Default value to `2`. | +| fixedDecimals | `boolean`|`null` | Whether to always display the maximum number of decimal places. Default value to `false` | +| thousandsSeparator | `string`|`null` | Example of values: `' '`, `','` and `.`... Default value to `''`. | +| unit | `string`|`null` | The unit in which the result will be returned (B/KB/MB/GB/TB). Default value to `''` (which means auto detect). | +| unitSeparator | `string`|`null` | Separator to use between number and unit. Default value to `''`. | + +**Returns** + +| Name | Type | Description | +|---------|------------------|-------------------------------------------------| +| results | `string`|`null` | Return null upon error. String value otherwise. | + +**Example** + +```js +bytes(1024); +// output: '1KB' + +bytes(1000); +// output: '1000B' + +bytes(1000, {thousandsSeparator: ' '}); +// output: '1 000B' + +bytes(1024 * 1.7, {decimalPlaces: 0}); +// output: '2KB' + +bytes(1024, {unitSeparator: ' '}); +// output: '1 KB' + +``` + +#### bytes.parse(string|number value): number|null + +Parse the string value into an integer in bytes. If no unit is given, or `value` +is a number, it is assumed the value is in bytes. + +Supported units and abbreviations are as follows and are case-insensitive: + + * `b` for bytes + * `kb` for kilobytes + * `mb` for megabytes + * `gb` for gigabytes + * `tb` for terabytes + * `pb` for petabytes + +The units are in powers of two, not ten. This means 1kb = 1024b according to this parser. + +**Arguments** + +| Name | Type | Description | +|---------------|--------|--------------------| +| value | `string`|`number` | String to parse, or number in bytes. | + +**Returns** + +| Name | Type | Description | +|---------|-------------|-------------------------| +| results | `number`|`null` | Return null upon error. Value in bytes otherwise. | + +**Example** + +```js +bytes('1KB'); +// output: 1024 + +bytes('1024'); +// output: 1024 + +bytes(1024); +// output: 1KB +``` + +## License + +[MIT](LICENSE) + +[coveralls-image]: https://badgen.net/coveralls/c/github/visionmedia/bytes.js/master +[coveralls-url]: https://coveralls.io/r/visionmedia/bytes.js?branch=master +[downloads-image]: https://badgen.net/npm/dm/bytes +[downloads-url]: https://npmjs.org/package/bytes +[npm-image]: https://badgen.net/npm/node/bytes +[npm-url]: https://npmjs.org/package/bytes +[travis-image]: https://badgen.net/travis/visionmedia/bytes.js/master +[travis-url]: https://travis-ci.org/visionmedia/bytes.js diff --git a/app/userland/app-stdlib/vendor/bytes/index.js b/app/userland/app-stdlib/vendor/bytes/index.js new file mode 100755 index 0000000000..0e83a8fb89 --- /dev/null +++ b/app/userland/app-stdlib/vendor/bytes/index.js @@ -0,0 +1,153 @@ +/*! + * bytes + * Copyright(c) 2012-2014 TJ Holowaychuk + * Copyright(c) 2015 Jed Watson + * MIT Licensed + */ + +'use strict'; + +/** + * Module variables. + * @private + */ + +var formatThousandsRegExp = /\B(?=(\d{3})+(?!\d))/g; + +var formatDecimalsRegExp = /(?:\.0*|(\.[^0]+)0+)$/; + +var map = { + b: 1, + kb: 1 << 10, + mb: 1 << 20, + gb: 1 << 30, + tb: Math.pow(1024, 4), + pb: Math.pow(1024, 5), +}; + +var parseRegExp = /^((-|\+)?(\d+(?:\.\d+)?)) *(kb|mb|gb|tb|pb)$/i; + +/** + * Convert the given value in bytes into a string or parse to string to an integer in bytes. + * + * @param {string|number} value + * @param {{ + * case: [string], + * decimalPlaces: [number] + * fixedDecimals: [boolean] + * thousandsSeparator: [string] + * unitSeparator: [string] + * }} [options] bytes options. + * + * @returns {string|number|null} + */ + +export default function bytes(value, options) { + if (typeof value === 'string') { + return parse(value); + } + + if (typeof value === 'number') { + return format(value, options); + } + + return null; +} + +/** + * Format the given value in bytes into a string. + * + * If the value is negative, it is kept as such. If it is a float, + * it is rounded. + * + * @param {number} value + * @param {object} [options] + * @param {number} [options.decimalPlaces=2] + * @param {number} [options.fixedDecimals=false] + * @param {string} [options.thousandsSeparator=] + * @param {string} [options.unit=] + * @param {string} [options.unitSeparator=] + * + * @returns {string|null} + * @public + */ + +export function format(value, options) { + if (!Number.isFinite(value)) { + return null; + } + + var mag = Math.abs(value); + var thousandsSeparator = (options && options.thousandsSeparator) || ''; + var unitSeparator = (options && options.unitSeparator) || ''; + var decimalPlaces = (options && options.decimalPlaces !== undefined) ? options.decimalPlaces : 2; + var fixedDecimals = Boolean(options && options.fixedDecimals); + var unit = (options && options.unit) || ''; + + if (!unit || !map[unit.toLowerCase()]) { + if (mag >= map.pb) { + unit = 'PB'; + } else if (mag >= map.tb) { + unit = 'TB'; + } else if (mag >= map.gb) { + unit = 'GB'; + } else if (mag >= map.mb) { + unit = 'MB'; + } else if (mag >= map.kb) { + unit = 'KB'; + } else { + unit = 'B'; + } + } + + var val = value / map[unit.toLowerCase()]; + var str = val.toFixed(decimalPlaces); + + if (!fixedDecimals) { + str = str.replace(formatDecimalsRegExp, '$1'); + } + + if (thousandsSeparator) { + str = str.replace(formatThousandsRegExp, thousandsSeparator); + } + + return str + unitSeparator + unit; +} + +/** + * Parse the string value into an integer in bytes. + * + * If no unit is given, it is assumed the value is in bytes. + * + * @param {number|string} val + * + * @returns {number|null} + * @public + */ + +export function parse(val) { + if (typeof val === 'number' && !isNaN(val)) { + return val; + } + + if (typeof val !== 'string') { + return null; + } + + // Test if the string passed is valid + var results = parseRegExp.exec(val); + var floatValue; + var unit = 'b'; + + if (!results) { + // Nothing could be extracted from the given string + floatValue = parseInt(val, 10); + unit = 'b' + } else { + // Retrieve the value and the unit + floatValue = parseFloat(results[1]); + unit = results[4].toLowerCase(); + } + + return Math.floor(map[unit] * floatValue); +} diff --git a/app/userland/app-stdlib/vendor/emoji-skin-tone/README.md b/app/userland/app-stdlib/vendor/emoji-skin-tone/README.md new file mode 100644 index 0000000000..bfe1bb2549 --- /dev/null +++ b/app/userland/app-stdlib/vendor/emoji-skin-tone/README.md @@ -0,0 +1,65 @@ +# skin-tone [![Build Status](https://travis-ci.org/sindresorhus/skin-tone.svg?branch=master)](https://travis-ci.org/sindresorhus/skin-tone) + +> Change the skin tone of an emoji 👌👌🏻👌🏼👌🏽👌🏾👌🏿 + +The [Fitzpatrick scale](https://en.wikipedia.org/wiki/Fitzpatrick_scale#Unicode) is used to specify skin tones for emoji characters which represent humans. + + +## Install + +``` +$ npm install --save skin-tone +``` + + +## Usage + +```js +const skinTone = require('skin-tone'); + +skinTone('👍', skinTone.BROWN); +//=> '👍🏾' + +// or by using the constant value directly +skinTone('👍', 4); +//=> '👍🏾 + +skinTone('👍', skinTone.WHITE); +//=> '👍🏻' + +// can also remove skin tone +skinTone('👍🏾', skinTone.NONE); +//=> '👍' + +// just passes it through when not supported +skinTone('🦄', skinTone.DARK_BROWN); +//=> '🦄' +``` + + +## API + +### skinTone(emoji, type) + +#### emoji + +Type: `string` + +Emoji to modify. + +#### type + +Type: `number`
    +Values: + +- `skinTone.NONE` | `0`: *(Removes skin tone)* +- `skinTone.WHITE` | `1`: 🏻 *(Fitzpatrick Type-1–2)* +- `skinTone.CREAM_WHITE` | `2`: 🏼 *(Fitzpatrick Type-3)* +- `skinTone.LIGHT_BROWN` | `3`: 🏽 *(Fitzpatrick Type-4)* +- `skinTone.BROWN` | `4`: 🏾 *(Fitzpatrick Type-5)* +- `skinTone.DARK_BROWN` | `5`: 🏿 *(Fitzpatrick Type-6)* + + +## License + +MIT © [Sindre Sorhus](https://sindresorhus.com) diff --git a/app/userland/app-stdlib/vendor/emoji-skin-tone/index.js b/app/userland/app-stdlib/vendor/emoji-skin-tone/index.js new file mode 100644 index 0000000000..97373c0e50 --- /dev/null +++ b/app/userland/app-stdlib/vendor/emoji-skin-tone/index.js @@ -0,0 +1,162 @@ +/* +The MIT License (MIT) + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + */ + +const emojiModifierBase = new Set([ + 0x261D, + 0x26F9, + 0x270A, + 0x270B, + 0x270C, + 0x270D, + 0x1F385, + 0x1F3C3, + 0x1F3C4, + 0x1F3CA, + 0x1F3CB, + 0x1F442, + 0x1F443, + 0x1F446, + 0x1F447, + 0x1F448, + 0x1F449, + 0x1F44A, + 0x1F44B, + 0x1F44C, + 0x1F44D, + 0x1F44E, + 0x1F44F, + 0x1F450, + 0x1F466, + 0x1F467, + // 0x1F468, SUPPORT (prf) + // 0x1F469, SUPPORT (prf) + 0x1F46E, + 0x1F470, + 0x1F471, + 0x1F472, + 0x1F473, + 0x1F474, + 0x1F475, + 0x1F476, + 0x1F477, + 0x1F478, + 0x1F47C, + 0x1F481, + 0x1F482, + 0x1F483, + 0x1F485, + 0x1F486, + 0x1F487, + 0x1F4AA, + 0x1F575, + 0x1F57A, + 0x1F590, + 0x1F595, + 0x1F596, + 0x1F645, + 0x1F646, + 0x1F647, + 0x1F64B, + 0x1F64C, + 0x1F64D, + 0x1F64E, + 0x1F64F, + 0x1F6A3, + 0x1F6B4, + 0x1F6B5, + 0x1F6B6, + 0x1F6C0, + 0x1F918, + 0x1F919, + 0x1F91A, + 0x1F91B, + 0x1F91C, + // 0x1F91D, SUPPORT (prf) + 0x1F91E, + 0x1F926, + 0x1F930, + 0x1F933, + 0x1F934, + 0x1F935, + 0x1F936, + 0x1F937, + 0x1F938, + 0x1F939, + // 0x1F93C, SUPPORT (prf) + 0x1F93D, + 0x1F93E +]); + + +const skinTones = [ + { + name: 'NONE', + color: '' + }, + { + name: 'WHITE', + color: '🏻' + }, + { + name: 'CREAM_WHITE', + color: '🏼' + }, + { + name: 'LIGHT_BROWN', + color: '🏽' + }, + { + name: 'BROWN', + color: '🏾' + }, + { + name: 'DARK_BROWN', + color: '🏿' + } +]; + +export const NONE = 0 +export const WHITE = 1 +export const CREAM_WHITE = 2 +export const LIGHT_BROWN = 3 +export const BROWN = 4 +export const DARK_BROWN = 5 + +export function set (emoji, type) { + if (type > 5 || type < 0) { + throw new TypeError(`Expected \`type\` to be a number between 0 and 5, got ${type}`); + } + + // TODO: Use this instead when targeting Node.js 6 + // emoji = emoji.replace(/[\u{1f3fb}-\u{1f3ff}]/u, ''); + skinTones.forEach(x => { + emoji = emoji.replace(x.color, ''); + }); + + if (emojiModifierBase.has(emoji.codePointAt(0)) && type !== 0) { + emoji += skinTones[type].color; + } + + return emoji; +} diff --git a/app/userland/app-stdlib/vendor/lit-element-router/README.md b/app/userland/app-stdlib/vendor/lit-element-router/README.md new file mode 100644 index 0000000000..a102734fb7 --- /dev/null +++ b/app/userland/app-stdlib/vendor/lit-element-router/README.md @@ -0,0 +1,207 @@ +# LitElement Router +A simple and lightweight LitElement Router. + +[![Coverage Status](https://coveralls.io/repos/github/hamedasemi/lit-element-router/badge.svg?branch=mainline)](https://coveralls.io/github/hamedasemi/lit-element-router?branch=mainline) +[![npm version](https://badge.fury.io/js/lit-element-router.svg)](https://badge.fury.io/js/lit-element-router) +[![Published on webcomponents.org](https://img.shields.io/badge/webcomponents.org-published-blue.svg)](https://www.webcomponents.org/element/lit-element-router) +[![Known Vulnerabilities](https://snyk.io/test/github/hamedasemi/lit-element-router/badge.svg?targetFile=package.json)](https://snyk.io/test/github/hamedasemi/lit-element-router?targetFile=package.json) +[![CircleCI](https://circleci.com/gh/hamedasemi/lit-element-router/tree/mainline.svg?style=svg)](https://circleci.com/gh/hamedasemi/lit-element-router/tree/mainline) + + +## Installation + +```sh +npm install lit-element-router --save +``` + +## Usage + +### Working example +You can find a working project on StackBlitz https://stackblitz.com/edit/lit-element-router + +### Minimal +```js +import { LitElement, html } from 'lit-element'; +import { routerMixin } from 'lit-element-router'; + +class MyApp extends routerMixin(LitElement) { + + static get routes() { + return [{ + name: 'home', + pattern: '', + data: { title: 'Home' } + }, { + name: 'info', + pattern: 'info' + }, { + name: 'user', + pattern: 'user/:id' + }, { + name: 'not-found', + pattern: '*' + }]; + } + + onRoute(route, params, query, data) { + console.log(route, params, query, data) + } +} + +customElements.define('my-app', MyApp); +``` + + + +# Complete Example Using JavaScript Mixins in Details + +## Dont like mixins check out other examples +Don't want to use mixins interface you cane use a simple version in this tutorial: https://github.com/hamedasemi/lit-element-router/blob/mainline/README_NOT_MIXIN.md + +## Make any arbitary components or elements to a router using router mixins method +```javascript +import { LitElement, html } from 'lit-element'; +import { routerMixin } from 'lit-element-router'; + +class MyApp extends routerMixin(LitElement) { + +} + +customElements.define('my-app', MyApp); +``` + +## Register routes and the onRoute function +```javascript +import { LitElement, html } from 'lit-element'; +import { routerMixin } from 'lit-element-router'; + +class MyApp extends routerMixin(LitElement) { + static get routes() { + return [{ + name: 'home', + pattern: '' + }, { + name: 'info', + pattern: 'info' + }, { + name: 'user', + pattern: 'user/:id' + }, { + name: 'not-found', + pattern: '*' + }]; + } + + onRoute(route, params, query, data) { + this.route = route; + this.params = params; + } +} + +customElements.define('my-app', MyApp); +``` + + +## Make any arbitary components or elements to a router outlet using router outlet mixins method +```javascript +import { LitElement, html } from 'lit-element'; +import { routerOutletMixin } from 'lit-element-router'; + +export class AnyArbitaryLitElement extends routerOutletMixin(LitElement) { + +} + +customElements.define('any-arbitary-lit-element', AnyArbitaryLitElement); +``` + +## Put the components under router outlet +```javascript +import { LitElement, html } from 'lit-element'; +import { routerMixin } from 'lit-element-router'; + +class MyApp extends routerMixin(LitElement) { + static get routes() { + return [{ + name: 'home', + pattern: '' + }, { + name: 'info', + pattern: 'info' + }, { + name: 'user', + pattern: 'user/:id' + }, { + name: 'not-found', + pattern: '*' + }]; + } + + onRoute(route, params, query, data) { + this.route = route; + this.params = params; + } + + render() { + return html` + +
    Home any-arbitary-lit-element
    +
    mY Info any-arbitary-lit-element
    +
    User ${this.params.id} any-arbitary-lit-element
    +
    Not Authorized any-arbitary-lit-element
    +
    Not Found any-arbitary-lit-element
    +
    + `; +} + +customElements.define('my-app', MyApp); +``` + + +## Make any arbitary components or elements to a router link using router link mixins method +```javascript +import { LitElement, html } from 'lit-element'; +import { routerLinkMixin } from 'lit-element-router'; + +export class AnArbitaryLitElement extends routerLinkMixin(LitElement) { + +} + +customElements.define('an-arbitary-lit-element', AnArbitaryLitElement); +``` + +## Navigate using the router navigate method +```javascript +import { LitElement, html } from 'lit-element'; +import { routerLinkMixin } from 'lit-element-router'; + +export class AnArbitaryLitElement extends routerLinkMixin(LitElement) { + constructor() { + super() + this.href = '' + } + static get properties() { + return { + href: { type: String } + } + } + render() { + return html` + + ` + } + linkClick(event) { + event.preventDefault(); + this.navigate(this.href); + } +} + +customElements.define('an-arbitary-lit-element', AnArbitaryLitElement); +``` + + +## Browsers support + +| [IE / Edge](http://godban.github.io/browsers-support-badges/)
    IE / Edge | [Firefox](http://godban.github.io/browsers-support-badges/)
    Firefox | [Chrome](http://godban.github.io/browsers-support-badges/)
    Chrome | [Safari](http://godban.github.io/browsers-support-badges/)
    Safari | [iOS Safari](http://godban.github.io/browsers-support-badges/)
    iOS Safari | [Samsung](http://godban.github.io/browsers-support-badges/)
    Samsung | [Opera](http://godban.github.io/browsers-support-badges/)
    Opera | +| --------- | --------- | --------- | --------- | --------- | --------- | --------- | +| IE11, Edge| last 2 versions| last 2 versions| last 2 versions| last 2 versions| last 2 versions| last 2 versions + diff --git a/app/userland/app-stdlib/vendor/lit-element-router/index.js b/app/userland/app-stdlib/vendor/lit-element-router/index.js new file mode 100644 index 0000000000..c6a31f1ac9 --- /dev/null +++ b/app/userland/app-stdlib/vendor/lit-element-router/index.js @@ -0,0 +1,108 @@ +import { parseParams, parseQuery, testRoute } from './utility/router-utility.js'; + +export let routerMixin = (superclass) => class extends superclass { + static get properties() { + return { + route: { type: String, reflect: true, attribute: 'route' }, + canceled: { type: Boolean } + } + } + + firstUpdated() { + + this.router(this.constructor.routes, (...args) => this.onRoute(...args)); + window.addEventListener('route', () => { + this.router(this.constructor.routes, (...args) => this.onRoute(...args)); + }) + + window.onpopstate = () => { + window.dispatchEvent(new CustomEvent('route')); + } + if (super.firstUpdated) super.firstUpdated(); + } + + router(routes, callback) { + this.canceled = true; + + const uri = decodeURI(window.location.pathname); + const querystring = decodeURI(window.location.search); + + let notFoundRoute = routes.filter(route => route.pattern === '*')[0]; + + routes = routes.filter(route => route.pattern !== '*' && testRoute(uri, route.pattern)); + + if (routes.length) { + let route = routes[0]; + route.params = parseParams(route.pattern, uri); + route.query = parseQuery(querystring); + + if (route.guard && typeof route.guard === 'function') { + + this.canceled = false + Promise.resolve(route.guard()) + .then((allowed) => { + if (!this.canceled) { + if (allowed) { + route.callback && route.callback(route.name, route.params, route.query, route.data) + callback(route.name, route.params, route.query, route.data); + } else { + route.callback && route.callback('not-authorized', route.params, route.query, route.data) + callback('not-authorized', {}, {}, {}); + } + } + }) + } else { + route.callback && route.callback(route.name, route.params, route.query, route.data) + callback(route.name, route.params, route.query, route.data); + } + } else if (notFoundRoute) { + notFoundRoute.callback && notFoundRoute.callback(notFoundRoute.name, {}, {}, {}) + callback(notFoundRoute.name, {}, {}, {}); + } else { + callback('not-found', {}, {}, {}); + } + + if (super.router) super.router(); + } +}; + +export let routerLinkMixin = (superclass) => class extends superclass { + + navigate(href) { + window.history.pushState({}, null, href + window.location.search); + window.dispatchEvent(new CustomEvent('route')); + + if (super.navigate) super.navigate(); + } +}; + +export let routerOutletMixin = (superclass) => class extends superclass { + + static get properties() { + return { + currentRoute: { type: String, reflect: true, attribute: 'current-route' } + } + } + + updated(updatedProperties) { + updatedProperties.has('currentRoute') && this.routerOutlet(); + if (super.updated) super.updated(); + } + + firstUpdated() { + this.routerOutlet(); + } + + routerOutlet() { + Array.from(this.shadowRoot.querySelectorAll(`[route]`)).map((selected) => { + this.appendChild(selected); + }); + if (this.currentRoute) { + Array.from(this.querySelectorAll(`[route~=${this.currentRoute}]`)).map((selected) => { + this.shadowRoot.appendChild(selected) + }); + } + + if (super.routerOutlet) super.routerOutlet(); + } +}; \ No newline at end of file diff --git a/app/userland/app-stdlib/vendor/lit-element-router/package.json b/app/userland/app-stdlib/vendor/lit-element-router/package.json new file mode 100644 index 0000000000..868c06cdde --- /dev/null +++ b/app/userland/app-stdlib/vendor/lit-element-router/package.json @@ -0,0 +1,50 @@ +{ + "name": "lit-element-router", + "version": "1.2.3", + "main": "lit-element-router.js", + "scripts": { + "demo": "polymer serve", + "sync": "browser-sync start --proxy localhost:8081 --watch --files ./**/*.* --ignore node_modules --logLevel debug", + "test": "nyc mocha --require @babel/register './{,!(node_modules)/**/}*.test.js' --exit", + "report": "npm test && nyc report --reporter=text-lcov | COVERALLS_REPO_TOKEN=$COVERALLS_TOKEN coveralls", + "ver": "npm version prerelease --preid=beta", + "risk": "snyk monitor" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/hamedasemi/lit-element-router.git" + }, + "keywords": [ + "lit", + "element", + "router", + "lit-element", + "lit-element-router", + "regexp" + ], + "author": "hamedabolghasemi", + "license": "ISC", + "bugs": { + "url": "https://github.com/hamedasemi/lit-element-router/issues" + }, + "homepage": "https://github.com/hamedasemi/lit-element-router#readme", + "devDependencies": { + "@babel/core": "^7.2.2", + "@babel/preset-env": "^7.3.1", + "@babel/register": "^7.0.0", + "@webcomponents/webcomponentsjs": "^2.2.7", + "asserts": "^4.0.2", + "chai": "^4.2.0", + "coveralls": "^3.0.2", + "lit-element": "^2.0.1", + "mocha": "^5.2.0", + "nyc": "^13.2.0", + "snyk": "^1.126.0" + }, + "dependencies": { + "lit-element": "^2.0.1" + }, + "nyc": { + "temp-dir": "./node_modules/.cache/alternative-tmp" + } +} diff --git a/app/userland/app-stdlib/vendor/lit-element-router/utility/router-utility.js b/app/userland/app-stdlib/vendor/lit-element-router/utility/router-utility.js new file mode 100644 index 0000000000..74d6334d43 --- /dev/null +++ b/app/userland/app-stdlib/vendor/lit-element-router/utility/router-utility.js @@ -0,0 +1,56 @@ +/** + * + * @param {String} str - The uri that has extra slashes + */ +export function stripExtraTrailingSlash(str) { + while (str.length !== 1 && str.substr(-1) === '/') { + str = str.substr(0, str.length - 1); + } + return str; +} + +/** +* +* @param {String} querystring - The author of the book. +*/ +export function parseQuery(querystring) { + return querystring ? JSON.parse('{"' + querystring.substring(1).replace(/&/g, '","').replace(/=/g, '":"') + '"}') : {} +} + +/** +* Desc +* @param {String} pattern - The pattern +* @param {String} uri - The current uri +* @return {Object} - The uri params object +*/ +export function parseParams(pattern, uri) { + let params = {} + + const patternArray = pattern.split('/').filter((path) => { return path != '' }) + const uriArray = uri.split('/').filter((path) => { return path != '' }) + + patternArray.map((pattern, i) => { + if (/^:/.test(pattern)) { + params[pattern.substring(1)] = uriArray[i] + } + }) + return params +} + +/** +* À-ÖØ-öø-ÿ +* @param {*} pattern +*/ +export function patternToRegExp(pattern) { + if (pattern) { + return new RegExp(pattern.replace(/:[^\s/]+/g, '([\\w\u00C0-\u00D6\u00D8-\u00f6\u00f8-\u00ff-]+)') + '(|/)$'); + } else { + return new RegExp('(^$|^/$)'); + } +} + +export function testRoute(uri, pattern) { + if (patternToRegExp(pattern).test(uri)) { + return true; + } +} \ No newline at end of file diff --git a/app/userland/app-stdlib/vendor/lit-element/CHANGELOG.md b/app/userland/app-stdlib/vendor/lit-element/CHANGELOG.md new file mode 100755 index 0000000000..5ec5020707 --- /dev/null +++ b/app/userland/app-stdlib/vendor/lit-element/CHANGELOG.md @@ -0,0 +1,150 @@ +# Change Log + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/) +and this project adheres to [Semantic Versioning](http://semver.org/). + + + + + + +## [2.0.1] - 2019-02-05 +### Fixed +* Use `lit-html` 1.0 ([#543](https://github.com/Polymer/lit-element/pull/543)). + +## [2.0.0] - 2019-02-05 +### Added +* Add `toString()` function to `CSSResult` ([#508](https://github.com/Polymer/lit-element/pull/508)) +* Add a global version to `window` ([#536](https://github.com/Polymer/lit-element/pull/536)) + +### Changed +* [Breaking] Renamed `unsafeCss` to `unsafeCSS` for consistency with lit-html's `unsafeHTML` ([#524](https://github.com/Polymer/lit-element/pull/524)) +* Remove all uses of `any` outside of tests ([#457](https://github.com/Polymer/lit-element/pull/457)) + +### Fixed +* A bunch of docs fixes ([#464](https://github.com/Polymer/lit-element/pull/464)), ([#458](https://github.com/Polymer/lit-element/pull/458)), ([#493](https://github.com/Polymer/lit-element/pull/493)), ([#504](https://github.com/Polymer/lit-element/pull/504)), ([#505](https://github.com/Polymer/lit-element/pull/505)), ([#501](https://github.com/Polymer/lit-element/pull/501)), ([#494](https://github.com/Polymer/lit-element/pull/494)), ([#491](https://github.com/Polymer/lit-element/pull/491)), ([#509](https://github.com/Polymer/lit-element/pull/509)), ([#513](https://github.com/Polymer/lit-element/pull/513)), ([#515](https://github.com/Polymer/lit-element/pull/515)), ([#512](https://github.com/Polymer/lit-element/pull/512)), ([#503](https://github.com/Polymer/lit-element/pull/503)), ([#460](https://github.com/Polymer/lit-element/pull/460)), ([#413](https://github.com/Polymer/lit-element/pull/413)), ([#426](https://github.com/Polymer/lit-element/pull/426)), ([#516](https://github.com/Polymer/lit-element/pull/516)), ([#537](https://github.com/Polymer/lit-element/pull/537)), ([#535](https://github.com/Polymer/lit-element/pull/535)), ([#539](https://github.com/Polymer/lit-element/pull/539)), ([#540](https://github.com/Polymer/lit-element/pull/540)) +* Build on checkout ([#423](https://github.com/Polymer/lit-element/pull/423)) + +### Fixed +* Adds a check to ensure `CSSStyleSheet` is constructable ([#527](https://github.com/Polymer/lit-element/pull/527)). + +## [2.0.0-rc.5] - 2019-01-24 +### Fixed +* Fixed a bug causing duplicate styles when an array was returned from `static get styles` ([#480](https://github.com/Polymer/lit-element/issues/480)). + +## [2.0.0-rc.4] - 2019-01-24 +### Added +* [Maintenance] Added script to publish dev releases automatically ([#476](https://github.com/Polymer/lit-element/pull/476)). +* Adds `unsafeCss` for composing "unsafe" values into `css`. Note, `CSSResult` is no longer constructable. ([#451](https://github.com/Polymer/lit-element/issues/451) and [#471](https://github.com/Polymer/lit-element/issues/471)). + +### Fixed +* Fixed a bug where we broke compatibility with closure compiler's property renaming optimizations. JSCompiler_renameProperty can't be a module export ([#465](https://github.com/Polymer/lit-element/pull/465)). +* Fixed an issue with inheriting from `styles` property when extending a superclass that is never instanced. ([#470](https://github.com/Polymer/lit-element/pull/470)). +* Fixed an issue with Closure Compiler and ([#470](https://github.com/Polymer/lit-element/pull/470)) ([#476](https://github.com/Polymer/lit-element/pull/476)). + +## [2.0.0-rc.3] - 2019-01-18 +### Fixed +* README: Fixed jsfiddle reference ([#435](https://github.com/Polymer/lit-element/pull/435)). +* Compile with Closure Compiler cleanly ([#436](https://github.com/Polymer/lit-element/pull/436)). +* Opt `@property` decorators out of Closure Compiler renaming ([#448](https://github.com/Polymer/lit-element/pull/448)). + +### Changed +* [Breaking] Property accessors are no longer wrapped when they already exist. Instead the `noAccessor` flag should be set when a user-defined accessor exists on the prototype (and in this case, user-defined accessors must call `requestUpdate` themselves). ([#454](https://github.com/Polymer/lit-element/pull/454)). +* Class fields can now be used to define styles, e.g. `static styles = css` and `styles` correctly compose when elements are extended ([#456](https://github.com/Polymer/lit-element/pull/456)). +* Styles returned via `static styles` are automatically flattend ([#437](https://github.com/Polymer/lit-element/pull/437)). +* Replace use of for/of loops over Maps with forEach ([#455](https://github.com/Polymer/lit-element/pull/455)) + +## [2.0.0-rc.2] - 2019-01-11 +### Fixed +* Fix references to `@polymer/lit-element` in README and docs ([#427](https://github.com/Polymer/lit-element/pull/427)). +* Fix decorator types causing compiler errors for TypeScript users. ([#431](https://github.com/Polymer/lit-element/pull/431)). + +## [2.0.0-rc.1] - 2019-01-10 +### Changed +* [Breaking] Changed NPM package name to `lit-element` + +## [0.7.0] - 2019-01-10 +### Added +* Updated decorator implementations to support TC39 decorator API proposal (supported by Babel 7.1+) in addition to the legacy decorator API (supported by older Babel and TypeScript) ([#156](https://github.com/Polymer/lit-element/issues/156)). +* Added `static get styles()` to allow defining element styling separate from `render` method. +This takes advantage of [`adoptedStyleSheets`](https://wicg.github.io/construct-stylesheets/#using-constructed-stylesheets) when possible ([#391](https://github.com/Polymer/lit-element/issues/391)). +* Added the `performUpdate` method to allow control of update timing ([#290](https://github.com/Polymer/lit-element/issues/290)). +* Updates deferred until first connection ([#258](https://github.com/Polymer/lit-element/issues/258)). +* Export `TemplateResult` and `SVGTemplateResult` ([#415](https://github.com/Polymer/lit-element/pull/415)). +### Changed +* [Breaking] The `createRenderRoot` method has moved from `UpdatingElement` to `LitElement`. Therefore, `UpdatingElement` no longer creates a `shadowRoot` by default ([#391](https://github.com/Polymer/lit-element/issues/391)). +* [Breaking] Changes property options to add `converter`. This option works the same as the previous `type` option except that the `converter` methods now also get `type` as the second argument. This effectively changes `type` to be a hint for the `converter`. A default `converter` is used if none is provided and it now supports `Boolean`, `String`, `Number`, `Object`, and `Array` ([#264](https://github.com/Polymer/lit-element/issues/264)). +* [Breaking] Numbers and strings now become null if their reflected attribute is removed (https://github.com/Polymer/lit-element/issues/264)). +* [Breaking] Previously, when an attribute changed as a result of a reflecting property changing, the property was prevented from mutating again as can happen when a custom +`converter` is used. Now, the oppose is also true. When a property changes as a result of an attribute changing, the attribute is prevented from mutating again (https://github.com/Polymer/lit-element/issues/264)) +### Fixed +* [Breaking] User defined accessors are now wrapped to enable better composition ([#286](https://github.com/Polymer/lit-element/issues/286)) +* Type for `eventOptions` decorator now properly includes `passive` and `once` options ([#325](https://github.com/Polymer/lit-element/issues/325)) + +## [0.6.5] - 2018-12-13 +### Changed: +* Use lit-html 1.0 release candidate. + +### Fixed +* Types for the `property` and `customElement` decorators updated ([#288](https://github.com/Polymer/lit-element/issues/288) and [#291](https://github.com/Polymer/lit-element/issues/291)). +* Docs updated. + +## [0.6.4] - 2018-11-30 +### Changed +* Update lit-html dependency to ^0.14.0 ([#324](https://github.com/Polymer/lit-element/pull/324)). + +## [0.6.3] - 2018-11-08 +### Changed +* Update lit-html dependency to ^0.13.0 ([#298](https://github.com/Polymer/lit-element/pull/298)). + +## [0.6.2] - 2018-10-05 + +### Changed +* LitElement changed to a non-abstract class to be more compatible with the JavaScript mixin pattern +([#227](https://github.com/Polymer/lit-element/issues/227)). +* Update lit-html dependency to ^0.12.0 ([#244](https://github.com/Polymer/lit-element/pull/244)). +* Passes the component's `this` reference to lit-html as the `eventContext`, allowing unbound event listener methods ([#244](https://github.com/Polymer/lit-element/pull/244)). +### Added +* A `disconnectedCallback()` method was added to UpdatingElement ([#213](https://github.com/Polymer/lit-element/pull/213)). +* Added `@eventOptions()` decorator for setting event listener options on methods ([#244](https://github.com/Polymer/lit-element/pull/244)). + +## [0.6.1] - 2018-09-17 + +### Fixed +* Fixes part rendering and css custom properties issues introduced with lit-html 0.11.3 by updating to 0.11.4 (https://github.com/Polymer/lit-element/issues/202). + +### Removed +* Removed custom_typings for Polymer as they are no longer needed +(https://github.com/Polymer/lit-element/issues/186). + +## [0.6.0] - 2018-09-13 + +### Added +* Added `@query()`, `@queryAll()`, and `@customElement` decorators ([#159](https://github.com/Polymer/lit-element/pull/159)) + +### Changed +* Significantly changed update/render lifecycle and property API. Render lifecycle +is now `requestUpdate`, `shouldUpdate`, `update`, `render`, `firstUpdated` +(first time only), `updated`, `updateComplete`. Property options are now +`{attribute, reflect, type, hasChanged}`. Properties may be defined in a +`static get properties` or using the `@property` decorator. +(https://github.com/Polymer/lit-element/pull/132). + + +### Removed +* Removed render helpers `classString` and `styleString`. Similar directives +(`classMap` and `styleMap`) have been added to lit-html and should be used instead +(https://github.com/Polymer/lit-element/pull/165 and +https://github.com/Polymer/lit-html/pull/486). + +### Fixed +* The `npm run checksize` command should now return the correct minified size +(https://github.com/Polymer/lit-element/pull/153). +* The `firstUpdated` method should now always be called the first time the element +updates, even if `shouldUpdate` initially returned `false` +(https://github.com/Polymer/lit-element/pull/173). diff --git a/app/userland/app-stdlib/vendor/lit-element/CONTRIBUTING.md b/app/userland/app-stdlib/vendor/lit-element/CONTRIBUTING.md new file mode 100755 index 0000000000..c582ad7d33 --- /dev/null +++ b/app/userland/app-stdlib/vendor/lit-element/CONTRIBUTING.md @@ -0,0 +1,143 @@ +# Contributing to Polymer + +There are many ways to contribute to the Polymer project! We welcome and truly appreciate contribution in all forms - issues and pull requests to the [main library](https://github.com/polymer/polymer), issues and pull requests to the [elements the Polymer team maintains](https://github.com/polymerelements), issues and pull requests to one of our many [Polymer-related tools](https://github.com/polymer), and of course we love to hear about any Polymer elements that you build to share with the community! + +## Logistics + +### Communicating with the Polymer team + +Beyond GitHub, we try to have a variety of different lines of communication open: + +* [Blog](https://blog.polymer-project.org/) +* [Twitter](https://twitter.com/polymer) +* [Google+ Community](https://plus.sandbox.google.com/u/0/communities/115626364525706131031?cfem=1) +* [Mailing list](https://groups.google.com/forum/#!forum/polymer-dev) +* [Slack channel](https://bit.ly/polymerslack) + +### The Polymer Repositories + +Because of the component-based nature of the Polymer project, we tend to have lots of different repositories. Our main repository for the Polymer library itself is at [github.com/Polymer/polymer](https://github.com/polymer/polymer). File any issues or pull requests that have to do with the core library on that repository, and we'll take a look ASAP. + +We keep all of the element "product lines" that the Polymer team maintains and distributes in the [PolymerElements](https://github.com/polymerelements) organization. For any element-specific issues or pull requests, file directly on the element's repository, such as the `paper-button` repository at [github.com/polymerelements/paper-button](https://github.com/polymerelements/paper-button). Of course, the elements built by the Polymer team are just a tiny fraction of all the Polymer-based elements out there - catalogs of other web components include [https://www.webcomponents.org/](https://github.com/webcomponents/webcomponents.org) and [component.kitchen](https://component.kitchen). + +The GoogleWebComponents element product line is maintained by teams all across Google, and so is kept in a separate organization: the [GoogleWebComponents](https://github.com/googlewebcomponents) org. Feel free to file issues and PR's on those elements directly in that organization. + +We also track each element product line overall in "meta-repos", named as `$PRODUCTLINE-elements`. These include [paper-elements](https://github.com/polymerelements/paper-elements), [iron-elements](https://github.com/polymerelements/iron-elements), [gold-elements](https://github.com/polymerelements/gold-elements), and more. Feel free to file issues for element requests on those meta-repos, and the README in each repo tracks a roadmap for the product line. + +### Contributor License Agreement + +You might notice our friendly CLA-bot commenting on a pull request you open if you haven't yet signed our CLA. We use the same CLA for all open-source Google projects, so you only have to sign it once. Once you complete the CLA, all your pull-requests will automatically get the `cla: yes` tag. + +If you've already signed a CLA but are still getting bothered by the awfully insistent CLA bot, it's possible we don't have your GitHub username or you're using a different email address. Check the [information on your CLA](https://cla.developers.google.com/clas) or see this help article on [setting the email on your git commits](https://help.github.com/articles/setting-your-email-in-git/). + +[Complete the CLA](https://cla.developers.google.com/clas) + +## Contributing + +### Contributing documentation + +Docs source is in the `docs` folder. To build the site yourself, see the instructions in [docs/README.md](docs/README.md). + +### Filing bugs + +The Polymer team heavily uses (and loves!) GitHub for all of our software management. We use GitHub issues to track all bugs and features. + +If you find an issue, please do file it on the repository. The [Polymer/polymer issues](https://github.com/polymer/polymer/issues) should be used only for issues on the Polymer library itself - bugs somewhere in the core codebase. + +For issues with elements the team maintains, please file directly on the element's repository. If you're not sure if a bug stems from the element or the library, air toward filing it on the element and we'll move the issue if necessary. + +Please file issues using the issue template provided, filling out as many fields as possible. We love examples for addressing issues - issues with a jsBin, Plunkr, jsFiddle, or glitch.me repro will be much easier for us to work on quickly. You can start with [this StackBlitz](https://stackblitz.com/edit/lit-element-example?file=index.js) which sets up the basics to demonstrate a lit-element. If you need your repro to run in IE11, you can start from [this glitch](https://glitch.com/edit/#!/hello-lit-element?path=index.html:1:0), which serves the source via polyserve for automatic transpilation, although you must sign up for a glitch.me account to ensure your code persists for more than 5 days (note the glitch.me _editing environment_ is not compatible with IE11, however the "live" view link of the running code should work). + +Occasionally we'll close issues if they appear stale or are too vague - please don't take this personally! Please feel free to re-open issues we've closed if there's something we've missed and they still need to be addressed. + +### Contributing Pull Requests + +PR's are even better than issues. We gladly accept community pull requests. In general across the core library and all of the elements, there are a few necessary steps before we can accept a pull request: + +- Open an issue describing the problem that you are looking to solve in your PR (if one is not already open), and your approach to solving it. This makes it easier to have a conversation around the best general approach for solving your problem, outside of the code itself. +- Sign the [CLA](https://cla.developers.google.com/clas), as described above. +- Fork the repo you're making the fix on to your own GitHub account. +- Code! +- Ideally, squash your commits into a single commit with a clear message of what the PR does. If it absolutely makes sense to keep multiple commits, that's OK - or perhaps consider making two separate PR's. +- **Include tests that test the range of behavior that changes with your PR.** If you PR fixes a bug, make sure your tests capture that bug. If your PR adds new behavior, make sure that behavior is fully tested. Every PR *must* include associated tests. (See [Unit tests](#unit-tests) for more.) +- Submit your PR, making sure it references the issue you created. +- If your PR fixes a bug, make sure the issue includes clear steps to reproduce the bug so we can test your fix. + +If you've completed all of these steps the core team will do its best to respond to the PR as soon as possible. + +#### Contributing Code to Elements + +Though the aim of the Polymer library is to allow lots of flexibility and not get in your way, we work to standardize our elements to make them as toolable and easy to maintain as possible. + +All elements should follow the [Polymer element style guide](https://www.polymer-project.org/3.0/docs/tools/documentation), which defines how to specify properties, documentation, and more. It's a great guide to follow when building your own elements as well, for maximum standardization and toolability. For instance, structuring elements following the style guide will ensure that they work with the [`iron-component-page`](https://github.com/polymerelements/iron-component-page) element, an incredibly easy way to turn any raw element directly into a documentation page. + +#### Contributing Code to the Polymer library + +We follow the most common JavaScript and HTML style guidelines for how we structure our code - in general, look at the code and you'll know how to contribute! If you'd like a bit more structure, the [Google JavaScript Styleguide](https://google.github.io/styleguide/javascriptguide.xml) is a good place to start. + +Polymer also participates in Google's [Patch Rewards Program](https://www.google.com/about/appsecurity/patch-rewards/), where you can earn cold, hard cash for qualifying security patches to the Polymer library. Visit the [patch rewards page](https://www.google.com/about/appsecurity/patch-rewards/) to find out more. + +## Unit tests + +All Polymer projects use [`polymer-cli`](https://github.com/Polymer/tools/tree/master/packages/cli) for unit tests. + +For maximum flexibility, install `polymer-cli` locally: + + npm install -g polymer-cli + +### Running the lit-element unit tests + +To run the lit-element unit tests: + +1. Clone the [lit-element repo](https://github.com/polymer/lit-element). + +2. Install the dependencies: + + npm install + +3. Run the tests: + + npm test + + Or if you have `polymer-cli` installed locally: + + polymer test --npm + +To run individual test suites: + +npm test path/to/suite + +Or: + +polymer test --npm path/to/suite + +For example: + + polymer test --npm test/index.html + +You can also run tests in the browser: + + polymer serve --npm + +Navigate to: + +[`http://localhost:8080/components/@polymer/lit-element/test/index.html`](http://localhost:8080/components/@polymer/lit-element/test/index.html) + +### Configuring `web-component-tester` + +By default, `polymer test` runs tests on all installed browsers. You can configure it +to run tests on a subset of available browsers, or to run tests remotely using Sauce Labs. + +See the [`web-component-tester` README](https://github.com/Polymer/tools/tree/master/packages/web-component-tester) for +information on configuring the tool using by `polymer-cli` to run the tests. + +### Viewing the source documentation locally + +You can view the updates you make to the source documentation locally with the following steps. +Make sure to rerun step 1 after every change you make. + +1. Run `polymer analyze > analysis.json` + +1. Run `polymer serve` + +1. Open `http://127.0.0.1:PORT/components/polymer/` to view the documentation diff --git a/app/userland/app-stdlib/vendor/lit-element/LICENSE b/app/userland/app-stdlib/vendor/lit-element/LICENSE new file mode 100755 index 0000000000..39cfe44304 --- /dev/null +++ b/app/userland/app-stdlib/vendor/lit-element/LICENSE @@ -0,0 +1,28 @@ +BSD 3-Clause License + +Copyright (c) 2017, The Polymer Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/app/userland/app-stdlib/vendor/lit-element/README.md b/app/userland/app-stdlib/vendor/lit-element/README.md new file mode 100755 index 0000000000..681cb5c3e1 --- /dev/null +++ b/app/userland/app-stdlib/vendor/lit-element/README.md @@ -0,0 +1,85 @@ +# LitElement +A simple base class for creating fast, lightweight web components with [lit-html](https://lit-html.polymer-project.org/). + +[![Build Status](https://travis-ci.org/Polymer/lit-element.svg?branch=master)](https://travis-ci.org/Polymer/lit-element) +[![Published on npm](https://img.shields.io/npm/v/lit-element.svg)](https://www.npmjs.com/package/lit-element) +[![Published on webcomponents.org](https://img.shields.io/badge/webcomponents.org-published-blue.svg)](https://www.webcomponents.org/element/lit-element) +[![Mentioned in Awesome lit-html](https://awesome.re/mentioned-badge.svg)](https://github.com/web-padawan/awesome-lit-html) + +## Documentation + +Full documentation is available at [lit-element.polymer-project.org](https://lit-element.polymer-project.org). + +## Overview + +LitElement uses [lit-html](https://lit-html.polymer-project.org/) to render into the +element's [Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM) +and adds API to help manage element properties and attributes. LitElement reacts to changes in properties +and renders declaratively using `lit-html`. See the [lit-html guide](https://lit-html.polymer-project.org/guide) +for additional information on how to create templates for lit-element. + +```ts + import {LitElement, html, css, customElement, property} from 'lit-element'; + + // This decorator defines the element. + @customElement('my-element'); + export class MyElement extends LitElement { + + // This decorator creates a property accessor that triggers rendering and + // an observed attribute. + @property() + mood = 'great'; + + static styles = css` + span { + color: green; + }`; + + // Render element DOM by returning a `lit-html` template. + render() { + return html`Web Components are ${this.mood}!`; + } + + } +``` + +```html + +``` + +Note, this example uses decorators to create properties. Decorators are a proposed +standard currently available in [TypeScript](https://www.typescriptlang.org/) or [Babel](https://babeljs.io/docs/en/babel-plugin-proposal-decorators). LitElement also supports a [vanilla JavaScript method](https://lit-element.polymer-project.org/guide/properties#declare) of declaring reactive properties. + +## Examples + + * Runs in all [supported](#supported-browsers) browsers: [Glitch](https://glitch.com/edit/#!/hello-lit-element?path=index.html) + + * Runs in browsers with [JavaScript Modules](https://caniuse.com/#search=modules): [Stackblitz](https://stackblitz.com/edit/lit-element-demo?file=src%2Fmy-element.js), [JSFiddle](https://jsfiddle.net/sorvell1/801f9cdu/), [JSBin](http://jsbin.com/vecuyan/edit?html,output), +[CodePen](https://codepen.io/sorvell/pen/RYQyoe?editors=1000). + + * You can also copy [this HTML file](https://gist.githubusercontent.com/sorvell/48f4b7be35c8748e8f6db5c66d36ee29/raw/67346e4e8bc4c81d5a7968d18f0a6a8bc00d792e/index.html) into a local file and run it in any browser that supports [JavaScript Modules]((https://caniuse.com/#search=modules)). + +## Installation + +From inside your project folder, run: + +```bash +$ npm install lit-element +``` + +To install the web components polyfills needed for older browsers: + +```bash +$ npm i -D @webcomponents/webcomponentsjs +``` + +## Supported Browsers + +The last 2 versions of all modern browsers are supported, including +Chrome, Safari, Opera, Firefox, Edge. In addition, Internet Explorer 11 is also supported. + +Edge and Internet Explorer 11 require the web components polyfills. + +## Contributing + +Please see [CONTRIBUTING.md](./CONTRIBUTING.md). \ No newline at end of file diff --git a/app/userland/app-stdlib/vendor/lit-element/lib/css-tag.js b/app/userland/app-stdlib/vendor/lit-element/lib/css-tag.js new file mode 100644 index 0000000000..19b8f0d29a --- /dev/null +++ b/app/userland/app-stdlib/vendor/lit-element/lib/css-tag.js @@ -0,0 +1,70 @@ +/** +@license +Copyright (c) 2019 The Polymer Project Authors. All rights reserved. +This code may only be used under the BSD style license found at +http://polymer.github.io/LICENSE.txt The complete set of authors may be found at +http://polymer.github.io/AUTHORS.txt The complete set of contributors may be +found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as +part of the polymer project is also subject to an additional IP rights grant +found at http://polymer.github.io/PATENTS.txt +*/ +export const supportsAdoptingStyleSheets = ('adoptedStyleSheets' in Document.prototype) && + ('replace' in CSSStyleSheet.prototype); +const constructionToken = Symbol(); +export class CSSResult { + constructor(cssText, safeToken) { + if (safeToken !== constructionToken) { + throw new Error('CSSResult is not constructable. Use `unsafeCSS` or `css` instead.'); + } + this.cssText = cssText; + } + // Note, this is a getter so that it's lazy. In practice, this means + // stylesheets are not created until the first element instance is made. + get styleSheet() { + if (this._styleSheet === undefined) { + // Note, if `adoptedStyleSheets` is supported then we assume CSSStyleSheet + // is constructable. + if (supportsAdoptingStyleSheets) { + this._styleSheet = new CSSStyleSheet(); + this._styleSheet.replaceSync(this.cssText); + } + else { + this._styleSheet = null; + } + } + return this._styleSheet; + } + toString() { + return this.cssText; + } +} +/** + * Wrap a value for interpolation in a css tagged template literal. + * + * This is unsafe because untrusted CSS text can be used to phone home + * or exfiltrate data to an attacker controlled site. Take care to only use + * this with trusted input. + */ +export const unsafeCSS = (value) => { + return new CSSResult(String(value), constructionToken); +}; +const textFromCSSResult = (value) => { + if (value instanceof CSSResult) { + return value.cssText; + } + else { + throw new Error(`Value passed to 'css' function must be a 'css' function result: ${value}. Use 'unsafeCSS' to pass non-literal values, but + take care to ensure page security.`); + } +}; +/** + * Template tag which which can be used with LitElement's `style` property to + * set element styles. For security reasons, only literal string values may be + * used. To incorporate non-literal values `unsafeCSS` may be used inside a + * template string part. + */ +export const css = (strings, ...values) => { + const cssText = values.reduce((acc, v, idx) => acc + textFromCSSResult(v) + strings[idx + 1], strings[0]); + return new CSSResult(cssText, constructionToken); +}; +//# =css-tag.js.map \ No newline at end of file diff --git a/app/userland/app-stdlib/vendor/lit-element/lib/css-tag.js.map b/app/userland/app-stdlib/vendor/lit-element/lib/css-tag.js.map new file mode 100644 index 0000000000..e82db9480d --- /dev/null +++ b/app/userland/app-stdlib/vendor/lit-element/lib/css-tag.js.map @@ -0,0 +1 @@ +{"version":3,"file":"css-tag.js","sourceRoot":"","sources":["../src/lib/css-tag.ts"],"names":[],"mappings":"AAAA;;;;;;;;;EASE;AAEF,MAAM,CAAC,MAAM,2BAA2B,GACpC,CAAC,oBAAoB,IAAI,QAAQ,CAAC,SAAS,CAAC;IAC5C,CAAC,SAAS,IAAI,aAAa,CAAC,SAAS,CAAC,CAAC;AAE3C,MAAM,iBAAiB,GAAG,MAAM,EAAE,CAAC;AAEnC,MAAM,OAAO,SAAS;IAKpB,YAAY,OAAe,EAAE,SAAiB;QAC5C,IAAI,SAAS,KAAK,iBAAiB,EAAE;YACnC,MAAM,IAAI,KAAK,CACX,mEAAmE,CAAC,CAAC;SAC1E;QACD,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED,oEAAoE;IACpE,wEAAwE;IACxE,IAAI,UAAU;QACZ,IAAI,IAAI,CAAC,WAAW,KAAK,SAAS,EAAE;YAClC,0EAA0E;YAC1E,oBAAoB;YACpB,IAAI,2BAA2B,EAAE;gBAC/B,IAAI,CAAC,WAAW,GAAG,IAAI,aAAa,EAAE,CAAC;gBACvC,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;aAC5C;iBAAM;gBACL,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;aACzB;SACF;QACD,OAAO,IAAI,CAAC,WAAW,CAAC;IAC1B,CAAC;IAED,QAAQ;QACN,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;CACF;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,KAAc,EAAE,EAAE;IAC1C,OAAO,IAAI,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,iBAAiB,CAAC,CAAC;AACzD,CAAC,CAAC;AAEF,MAAM,iBAAiB,GAAG,CAAC,KAAgB,EAAE,EAAE;IAC7C,IAAI,KAAK,YAAY,SAAS,EAAE;QAC9B,OAAO,KAAK,CAAC,OAAO,CAAC;KACtB;SAAM;QACL,MAAM,IAAI,KAAK,CACX,mEACI,KAAK;+CAC8B,CAAC,CAAC;KAC9C;AACH,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,GAAG,GAAG,CAAC,OAA6B,EAAE,GAAG,MAAmB,EAAE,EAAE;IAC3E,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CACzB,CAAC,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,GAAG,iBAAiB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,GAAG,GAAG,CAAC,CAAC,EAC9D,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IAChB,OAAO,IAAI,SAAS,CAAC,OAAO,EAAE,iBAAiB,CAAC,CAAC;AACnD,CAAC,CAAC","sourcesContent":["/**\n@license\nCopyright (c) 2019 The Polymer Project Authors. All rights reserved.\nThis code may only be used under the BSD style license found at\nhttp://polymer.github.io/LICENSE.txt The complete set of authors may be found at\nhttp://polymer.github.io/AUTHORS.txt The complete set of contributors may be\nfound at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as\npart of the polymer project is also subject to an additional IP rights grant\nfound at http://polymer.github.io/PATENTS.txt\n*/\n\nexport const supportsAdoptingStyleSheets =\n ('adoptedStyleSheets' in Document.prototype) &&\n ('replace' in CSSStyleSheet.prototype);\n\nconst constructionToken = Symbol();\n\nexport class CSSResult {\n _styleSheet?: CSSStyleSheet|null;\n\n readonly cssText: string;\n\n constructor(cssText: string, safeToken: symbol) {\n if (safeToken !== constructionToken) {\n throw new Error(\n 'CSSResult is not constructable. Use `unsafeCSS` or `css` instead.');\n }\n this.cssText = cssText;\n }\n\n // Note, this is a getter so that it's lazy. In practice, this means\n // stylesheets are not created until the first element instance is made.\n get styleSheet(): CSSStyleSheet|null {\n if (this._styleSheet === undefined) {\n // Note, if `adoptedStyleSheets` is supported then we assume CSSStyleSheet\n // is constructable.\n if (supportsAdoptingStyleSheets) {\n this._styleSheet = new CSSStyleSheet();\n this._styleSheet.replaceSync(this.cssText);\n } else {\n this._styleSheet = null;\n }\n }\n return this._styleSheet;\n }\n\n toString(): String {\n return this.cssText;\n }\n}\n\n/**\n * Wrap a value for interpolation in a css tagged template literal.\n *\n * This is unsafe because untrusted CSS text can be used to phone home\n * or exfiltrate data to an attacker controlled site. Take care to only use\n * this with trusted input.\n */\nexport const unsafeCSS = (value: unknown) => {\n return new CSSResult(String(value), constructionToken);\n};\n\nconst textFromCSSResult = (value: CSSResult) => {\n if (value instanceof CSSResult) {\n return value.cssText;\n } else {\n throw new Error(\n `Value passed to 'css' function must be a 'css' function result: ${\n value}. Use 'unsafeCSS' to pass non-literal values, but\n take care to ensure page security.`);\n }\n};\n\n/**\n * Template tag which which can be used with LitElement's `style` property to\n * set element styles. For security reasons, only literal string values may be\n * used. To incorporate non-literal values `unsafeCSS` may be used inside a\n * template string part.\n */\nexport const css = (strings: TemplateStringsArray, ...values: CSSResult[]) => {\n const cssText = values.reduce(\n (acc, v, idx) => acc + textFromCSSResult(v) + strings[idx + 1],\n strings[0]);\n return new CSSResult(cssText, constructionToken);\n};\n"]} \ No newline at end of file diff --git a/app/userland/app-stdlib/vendor/lit-element/lib/decorators.js b/app/userland/app-stdlib/vendor/lit-element/lib/decorators.js new file mode 100644 index 0000000000..014c68e9df --- /dev/null +++ b/app/userland/app-stdlib/vendor/lit-element/lib/decorators.js @@ -0,0 +1,188 @@ +/** + * @license + * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */ +const legacyCustomElement = (tagName, clazz) => { + window.customElements.define(tagName, clazz); + // Cast as any because TS doesn't recognize the return type as being a + // subtype of the decorated class when clazz is typed as + // `Constructor` for some reason. + // `Constructor` is helpful to make sure the decorator is + // applied to elements however. + // tslint:disable-next-line:no-any + return clazz; +}; +const standardCustomElement = (tagName, descriptor) => { + const { kind, elements } = descriptor; + return { + kind, + elements, + // This callback is called once the class is otherwise fully defined + finisher(clazz) { + window.customElements.define(tagName, clazz); + } + }; +}; +/** + * Class decorator factory that defines the decorated class as a custom element. + * + * @param tagName the name of the custom element to define + */ +export const customElement = (tagName) => (classOrDescriptor) => (typeof classOrDescriptor === 'function') ? + legacyCustomElement(tagName, classOrDescriptor) : + standardCustomElement(tagName, classOrDescriptor); +const standardProperty = (options, element) => { + // When decorating an accessor, pass it through and add property metadata. + // Note, the `hasOwnProperty` check in `createProperty` ensures we don't + // stomp over the user's accessor. + if (element.kind === 'method' && element.descriptor && + !('value' in element.descriptor)) { + return Object.assign({}, element, { finisher(clazz) { + clazz.createProperty(element.key, options); + } }); + } + else { + // createProperty() takes care of defining the property, but we still + // must return some kind of descriptor, so return a descriptor for an + // unused prototype field. The finisher calls createProperty(). + return { + kind: 'field', + key: Symbol(), + placement: 'own', + descriptor: {}, + // When @babel/plugin-proposal-decorators implements initializers, + // do this instead of the initializer below. See: + // https://github.com/babel/babel/issues/9260 extras: [ + // { + // kind: 'initializer', + // placement: 'own', + // initializer: descriptor.initializer, + // } + // ], + // tslint:disable-next-line:no-any decorator + initializer() { + if (typeof element.initializer === 'function') { + this[element.key] = element.initializer.call(this); + } + }, + finisher(clazz) { + clazz.createProperty(element.key, options); + } + }; + } +}; +const legacyProperty = (options, proto, name) => { + proto.constructor + .createProperty(name, options); +}; +/** + * A property decorator which creates a LitElement property which reflects a + * corresponding attribute value. A `PropertyDeclaration` may optionally be + * supplied to configure property features. + * + * @ExportDecoratedItems + */ +export function property(options) { + // tslint:disable-next-line:no-any decorator + return (protoOrDescriptor, name) => (name !== undefined) ? + legacyProperty(options, protoOrDescriptor, name) : + standardProperty(options, protoOrDescriptor); +} +/** + * A property decorator that converts a class property into a getter that + * executes a querySelector on the element's renderRoot. + */ +export const query = _query((target, selector) => target.querySelector(selector)); +/** + * A property decorator that converts a class property into a getter + * that executes a querySelectorAll on the element's renderRoot. + */ +export const queryAll = _query((target, selector) => target.querySelectorAll(selector)); +const legacyQuery = (descriptor, proto, name) => { + Object.defineProperty(proto, name, descriptor); +}; +const standardQuery = (descriptor, element) => ({ + kind: 'method', + placement: 'prototype', + key: element.key, + descriptor, +}); +/** + * Base-implementation of `@query` and `@queryAll` decorators. + * + * @param queryFn exectute a `selector` (ie, querySelector or querySelectorAll) + * against `target`. + * @suppress {visibility} The descriptor accesses an internal field on the + * element. + */ +function _query(queryFn) { + return (selector) => (protoOrDescriptor, + // tslint:disable-next-line:no-any decorator + name) => { + const descriptor = { + get() { + return queryFn(this.renderRoot, selector); + }, + enumerable: true, + configurable: true, + }; + return (name !== undefined) ? + legacyQuery(descriptor, protoOrDescriptor, name) : + standardQuery(descriptor, protoOrDescriptor); + }; +} +const standardEventOptions = (options, element) => { + return Object.assign({}, element, { finisher(clazz) { + Object.assign(clazz.prototype[element.key], options); + } }); +}; +const legacyEventOptions = +// tslint:disable-next-line:no-any legacy decorator +(options, proto, name) => { + Object.assign(proto[name], options); +}; +/** + * Adds event listener options to a method used as an event listener in a + * lit-html template. + * + * @param options An object that specifis event listener options as accepted by + * `EventTarget#addEventListener` and `EventTarget#removeEventListener`. + * + * Current browsers support the `capture`, `passive`, and `once` options. See: + * https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Parameters + * + * @example + * + * class MyElement { + * + * clicked = false; + * + * render() { + * return html`
    `; + * } + * + * @eventOptions({capture: true}) + * _onClick(e) { + * this.clicked = true; + * } + * } + */ +export const eventOptions = (options) => +// Return value typed as any to prevent TypeScript from complaining that +// standard decorator function signature does not match TypeScript decorator +// signature +// TODO(kschaaf): unclear why it was only failing on this decorator and not +// the others +((protoOrDescriptor, name) => (name !== undefined) ? + legacyEventOptions(options, protoOrDescriptor, name) : + standardEventOptions(options, protoOrDescriptor)); +//# =decorators.js.map \ No newline at end of file diff --git a/app/userland/app-stdlib/vendor/lit-element/lib/decorators.js.map b/app/userland/app-stdlib/vendor/lit-element/lib/decorators.js.map new file mode 100644 index 0000000000..74531c6a6c --- /dev/null +++ b/app/userland/app-stdlib/vendor/lit-element/lib/decorators.js.map @@ -0,0 +1 @@ +{"version":3,"file":"decorators.js","sourceRoot":"","sources":["../src/lib/decorators.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;GAYG;AA4BH,MAAM,mBAAmB,GACrB,CAAC,OAAe,EAAE,KAA+B,EAAE,EAAE;IACnD,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAC7C,sEAAsE;IACtE,wDAAwD;IACxD,8CAA8C;IAC9C,sEAAsE;IACtE,+BAA+B;IAC/B,kCAAkC;IAClC,OAAO,KAAY,CAAC;AACtB,CAAC,CAAC;AAEN,MAAM,qBAAqB,GACvB,CAAC,OAAe,EAAE,UAA2B,EAAE,EAAE;IAC/C,MAAM,EAAC,IAAI,EAAE,QAAQ,EAAC,GAAG,UAAU,CAAC;IACpC,OAAO;QACL,IAAI;QACJ,QAAQ;QACR,oEAAoE;QACpE,QAAQ,CAAC,KAA+B;YACtC,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QAC/C,CAAC;KACF,CAAC;AACJ,CAAC,CAAC;AAEN;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,OAAe,EAAE,EAAE,CAC7C,CAAC,iBAA2D,EAAE,EAAE,CAC5D,CAAC,OAAO,iBAAiB,KAAK,UAAU,CAAC,CAAC,CAAC;IAC/C,mBAAmB,CACf,OAAO,EAAE,iBAA6C,CAAC,CAAC,CAAC;IAC7D,qBAAqB,CAAC,OAAO,EAAE,iBAAoC,CAAC,CAAC;AAEzE,MAAM,gBAAgB,GAClB,CAAC,OAA4B,EAAE,OAAqB,EAAE,EAAE;IACtD,0EAA0E;IAC1E,wEAAwE;IACxE,kCAAkC;IAClC,IAAI,OAAO,CAAC,IAAI,KAAK,QAAQ,IAAI,OAAO,CAAC,UAAU;QAC/C,CAAC,CAAC,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC,EAAE;QACpC,yBACK,OAAO,IACV,QAAQ,CAAC,KAA6B;gBACpC,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;YAC7C,CAAC,IACD;KACH;SAAM;QACL,qEAAqE;QACrE,qEAAqE;QACrE,+DAA+D;QAC/D,OAAO;YACL,IAAI,EAAE,OAAO;YACb,GAAG,EAAE,MAAM,EAAE;YACb,SAAS,EAAE,KAAK;YAChB,UAAU,EAAE,EAAE;YACd,kEAAkE;YAClE,iDAAiD;YACjD,uDAAuD;YACvD,MAAM;YACN,2BAA2B;YAC3B,wBAAwB;YACxB,2CAA2C;YAC3C,MAAM;YACN,KAAK;YACL,4CAA4C;YAC5C,WAAW;gBACT,IAAI,OAAO,OAAO,CAAC,WAAW,KAAK,UAAU,EAAE;oBAC7C,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,WAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;iBACrD;YACH,CAAC;YACD,QAAQ,CAAC,KAA6B;gBACpC,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;YAC7C,CAAC;SACF,CAAC;KACH;AACH,CAAC,CAAC;AAEN,MAAM,cAAc,GAChB,CAAC,OAA4B,EAAE,KAAa,EAAE,IAAiB,EAAE,EAAE;IAChE,KAAK,CAAC,WAAsC;SACxC,cAAc,CAAC,IAAK,EAAE,OAAO,CAAC,CAAC;AACtC,CAAC,CAAC;AAEN;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CAAC,OAA6B;IACpD,4CAA4C;IAC5C,OAAO,CAAC,iBAAsC,EAAE,IAAkB,EAAO,EAAE,CAChE,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC;QAC7B,cAAc,CAAC,OAAQ,EAAE,iBAA2B,EAAE,IAAI,CAAC,CAAC,CAAC;QAC7D,gBAAgB,CAAC,OAAQ,EAAE,iBAAiC,CAAC,CAAC;AACpE,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,MAAM,CACvB,CAAC,MAAoB,EAAE,QAAgB,EAAE,EAAE,CAAC,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC;AAEhF;;;GAGG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAC1B,CAAC,MAAoB,EAAE,QAAgB,EAAE,EAAE,CACvC,MAAM,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC,CAAC;AAE3C,MAAM,WAAW,GACb,CAAC,UAA8B,EAAE,KAAa,EAAE,IAAiB,EAAE,EAAE;IACnE,MAAM,CAAC,cAAc,CAAC,KAAK,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;AACjD,CAAC,CAAC;AAEN,MAAM,aAAa,GAAG,CAAC,UAA8B,EAAE,OAAqB,EAAE,EAAE,CAC5E,CAAC;IACC,IAAI,EAAE,QAAQ;IACd,SAAS,EAAE,WAAW;IACtB,GAAG,EAAE,OAAO,CAAC,GAAG;IAChB,UAAU;CACX,CAAC,CAAC;AAEP;;;;;;;GAOG;AACH,SAAS,MAAM,CAAI,OAAsD;IACvE,OAAO,CAAC,QAAgB,EAAE,EAAE,CACjB,CAAC,iBAAsC;IACtC,4CAA4C;IAC5C,IAAkB,EAAO,EAAE;QAC1B,MAAM,UAAU,GAAG;YACjB,GAAG;gBACD,OAAO,OAAO,CAAC,IAAI,CAAC,UAAW,EAAE,QAAQ,CAAC,CAAC;YAC7C,CAAC;YACD,UAAU,EAAE,IAAI;YAChB,YAAY,EAAE,IAAI;SACnB,CAAC;QACF,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC;YACzB,WAAW,CAAC,UAAU,EAAE,iBAA2B,EAAE,IAAI,CAAC,CAAC,CAAC;YAC5D,aAAa,CAAC,UAAU,EAAE,iBAAiC,CAAC,CAAC;IACnE,CAAC,CAAC;AACf,CAAC;AAED,MAAM,oBAAoB,GACtB,CAAC,OAAgC,EAAE,OAAqB,EAAE,EAAE;IAC1D,yBACK,OAAO,IACV,QAAQ,CAAC,KAA6B;YACpC,MAAM,CAAC,MAAM,CACT,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,GAA4B,CAAC,EAAE,OAAO,CAAC,CAAC;QACtE,CAAC,IACD;AACJ,CAAC,CAAC;AAEN,MAAM,kBAAkB;AACpB,mDAAmD;AACnD,CAAC,OAAgC,EAAE,KAAU,EAAE,IAAiB,EAAE,EAAE;IAClE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;AACtC,CAAC,CAAC;AAEN;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,OAAgC,EAAE,EAAE;AAC7D,wEAAwE;AACxE,4EAA4E;AAC5E,YAAY;AACZ,2EAA2E;AAC3E,aAAa;AACb,CAAC,CAAC,iBAAsC,EAAE,IAAa,EAAE,EAAE,CACtD,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC;IACtB,kBAAkB,CAAC,OAAO,EAAE,iBAA2B,EAAE,IAAI,CAAC,CAAC,CAAC;IAChE,oBAAoB,CAAC,OAAO,EAAE,iBAAiC,CAAC,CAE9D,CAAC","sourcesContent":["\n/**\n * @license\n * Copyright (c) 2017 The Polymer Project Authors. All rights reserved.\n * This code may only be used under the BSD style license found at\n * http://polymer.github.io/LICENSE.txt\n * The complete set of authors may be found at\n * http://polymer.github.io/AUTHORS.txt\n * The complete set of contributors may be found at\n * http://polymer.github.io/CONTRIBUTORS.txt\n * Code distributed by Google as part of the polymer project is also\n * subject to an additional IP rights grant found at\n * http://polymer.github.io/PATENTS.txt\n */\n\nimport {LitElement} from '../lit-element.js';\n\nimport {PropertyDeclaration, UpdatingElement} from './updating-element.js';\n\nexport type Constructor = {\n new (...args: unknown[]): T\n};\n\n// From the TC39 Decorators proposal\ninterface ClassDescriptor {\n kind: 'class';\n elements: ClassElement[];\n finisher?: (clazz: Constructor) => undefined | Constructor;\n}\n\n// From the TC39 Decorators proposal\ninterface ClassElement {\n kind: 'field'|'method';\n key: PropertyKey;\n placement: 'static'|'prototype'|'own';\n initializer?: Function;\n extras?: ClassElement[];\n finisher?: (clazz: Constructor) => undefined | Constructor;\n descriptor?: PropertyDescriptor;\n}\n\nconst legacyCustomElement =\n (tagName: string, clazz: Constructor) => {\n window.customElements.define(tagName, clazz);\n // Cast as any because TS doesn't recognize the return type as being a\n // subtype of the decorated class when clazz is typed as\n // `Constructor` for some reason.\n // `Constructor` is helpful to make sure the decorator is\n // applied to elements however.\n // tslint:disable-next-line:no-any\n return clazz as any;\n };\n\nconst standardCustomElement =\n (tagName: string, descriptor: ClassDescriptor) => {\n const {kind, elements} = descriptor;\n return {\n kind,\n elements,\n // This callback is called once the class is otherwise fully defined\n finisher(clazz: Constructor) {\n window.customElements.define(tagName, clazz);\n }\n };\n };\n\n/**\n * Class decorator factory that defines the decorated class as a custom element.\n *\n * @param tagName the name of the custom element to define\n */\nexport const customElement = (tagName: string) =>\n (classOrDescriptor: Constructor|ClassDescriptor) =>\n (typeof classOrDescriptor === 'function') ?\n legacyCustomElement(\n tagName, classOrDescriptor as Constructor) :\n standardCustomElement(tagName, classOrDescriptor as ClassDescriptor);\n\nconst standardProperty =\n (options: PropertyDeclaration, element: ClassElement) => {\n // When decorating an accessor, pass it through and add property metadata.\n // Note, the `hasOwnProperty` check in `createProperty` ensures we don't\n // stomp over the user's accessor.\n if (element.kind === 'method' && element.descriptor &&\n !('value' in element.descriptor)) {\n return {\n ...element,\n finisher(clazz: typeof UpdatingElement) {\n clazz.createProperty(element.key, options);\n }\n };\n } else {\n // createProperty() takes care of defining the property, but we still\n // must return some kind of descriptor, so return a descriptor for an\n // unused prototype field. The finisher calls createProperty().\n return {\n kind: 'field',\n key: Symbol(),\n placement: 'own',\n descriptor: {},\n // When @babel/plugin-proposal-decorators implements initializers,\n // do this instead of the initializer below. See:\n // https://github.com/babel/babel/issues/9260 extras: [\n // {\n // kind: 'initializer',\n // placement: 'own',\n // initializer: descriptor.initializer,\n // }\n // ],\n // tslint:disable-next-line:no-any decorator\n initializer(this: any) {\n if (typeof element.initializer === 'function') {\n this[element.key] = element.initializer!.call(this);\n }\n },\n finisher(clazz: typeof UpdatingElement) {\n clazz.createProperty(element.key, options);\n }\n };\n }\n };\n\nconst legacyProperty =\n (options: PropertyDeclaration, proto: Object, name: PropertyKey) => {\n (proto.constructor as typeof UpdatingElement)\n .createProperty(name!, options);\n };\n\n/**\n * A property decorator which creates a LitElement property which reflects a\n * corresponding attribute value. A `PropertyDeclaration` may optionally be\n * supplied to configure property features.\n *\n * @ExportDecoratedItems\n */\nexport function property(options?: PropertyDeclaration) {\n // tslint:disable-next-line:no-any decorator\n return (protoOrDescriptor: Object|ClassElement, name?: PropertyKey): any =>\n (name !== undefined) ?\n legacyProperty(options!, protoOrDescriptor as Object, name) :\n standardProperty(options!, protoOrDescriptor as ClassElement);\n}\n\n/**\n * A property decorator that converts a class property into a getter that\n * executes a querySelector on the element's renderRoot.\n */\nexport const query = _query(\n (target: NodeSelector, selector: string) => target.querySelector(selector));\n\n/**\n * A property decorator that converts a class property into a getter\n * that executes a querySelectorAll on the element's renderRoot.\n */\nexport const queryAll = _query(\n (target: NodeSelector, selector: string) =>\n target.querySelectorAll(selector));\n\nconst legacyQuery =\n (descriptor: PropertyDescriptor, proto: Object, name: PropertyKey) => {\n Object.defineProperty(proto, name, descriptor);\n };\n\nconst standardQuery = (descriptor: PropertyDescriptor, element: ClassElement) =>\n ({\n kind: 'method',\n placement: 'prototype',\n key: element.key,\n descriptor,\n });\n\n/**\n * Base-implementation of `@query` and `@queryAll` decorators.\n *\n * @param queryFn exectute a `selector` (ie, querySelector or querySelectorAll)\n * against `target`.\n * @suppress {visibility} The descriptor accesses an internal field on the\n * element.\n */\nfunction _query(queryFn: (target: NodeSelector, selector: string) => T) {\n return (selector: string) =>\n (protoOrDescriptor: Object|ClassElement,\n // tslint:disable-next-line:no-any decorator\n name?: PropertyKey): any => {\n const descriptor = {\n get(this: LitElement) {\n return queryFn(this.renderRoot!, selector);\n },\n enumerable: true,\n configurable: true,\n };\n return (name !== undefined) ?\n legacyQuery(descriptor, protoOrDescriptor as Object, name) :\n standardQuery(descriptor, protoOrDescriptor as ClassElement);\n };\n}\n\nconst standardEventOptions =\n (options: AddEventListenerOptions, element: ClassElement) => {\n return {\n ...element,\n finisher(clazz: typeof UpdatingElement) {\n Object.assign(\n clazz.prototype[element.key as keyof UpdatingElement], options);\n }\n };\n };\n\nconst legacyEventOptions =\n // tslint:disable-next-line:no-any legacy decorator\n (options: AddEventListenerOptions, proto: any, name: PropertyKey) => {\n Object.assign(proto[name], options);\n };\n\n/**\n * Adds event listener options to a method used as an event listener in a\n * lit-html template.\n *\n * @param options An object that specifis event listener options as accepted by\n * `EventTarget#addEventListener` and `EventTarget#removeEventListener`.\n *\n * Current browsers support the `capture`, `passive`, and `once` options. See:\n * https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Parameters\n *\n * @example\n *\n * class MyElement {\n *\n * clicked = false;\n *\n * render() {\n * return html`
    `;\n * }\n *\n * @eventOptions({capture: true})\n * _onClick(e) {\n * this.clicked = true;\n * }\n * }\n */\nexport const eventOptions = (options: AddEventListenerOptions) =>\n // Return value typed as any to prevent TypeScript from complaining that\n // standard decorator function signature does not match TypeScript decorator\n // signature\n // TODO(kschaaf): unclear why it was only failing on this decorator and not\n // the others\n ((protoOrDescriptor: Object|ClassElement, name?: string) =>\n (name !== undefined) ?\n legacyEventOptions(options, protoOrDescriptor as Object, name) :\n standardEventOptions(options, protoOrDescriptor as ClassElement)) as\n // tslint:disable-next-line:no-any decorator\n any;\n"]} \ No newline at end of file diff --git a/app/userland/app-stdlib/vendor/lit-element/lib/updating-element.js b/app/userland/app-stdlib/vendor/lit-element/lib/updating-element.js new file mode 100644 index 0000000000..b2e5fbf4f5 --- /dev/null +++ b/app/userland/app-stdlib/vendor/lit-element/lib/updating-element.js @@ -0,0 +1,560 @@ +/** + * @license + * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */ +/** + * When using Closure Compiler, JSCompiler_renameProperty(property, object) is + * replaced at compile time by the munged name for object[property]. We cannot + * alias this function, so we have to use a small shim that has the same + * behavior when not compiling. + */ +window.JSCompiler_renameProperty = + (prop, _obj) => prop; +export const defaultConverter = { + toAttribute(value, type) { + switch (type) { + case Boolean: + return value ? '' : null; + case Object: + case Array: + // if the value is `null` or `undefined` pass this through + // to allow removing/no change behavior. + return value == null ? value : JSON.stringify(value); + } + return value; + }, + fromAttribute(value, type) { + switch (type) { + case Boolean: + return value !== null; + case Number: + return value === null ? null : Number(value); + case Object: + case Array: + return JSON.parse(value); + } + return value; + } +}; +/** + * Change function that returns true if `value` is different from `oldValue`. + * This method is used as the default for a property's `hasChanged` function. + */ +export const notEqual = (value, old) => { + // This ensures (old==NaN, value==NaN) always returns false + return old !== value && (old === old || value === value); +}; +const defaultPropertyDeclaration = { + attribute: true, + type: String, + converter: defaultConverter, + reflect: false, + hasChanged: notEqual +}; +const microtaskPromise = Promise.resolve(true); +const STATE_HAS_UPDATED = 1; +const STATE_UPDATE_REQUESTED = 1 << 2; +const STATE_IS_REFLECTING_TO_ATTRIBUTE = 1 << 3; +const STATE_IS_REFLECTING_TO_PROPERTY = 1 << 4; +const STATE_HAS_CONNECTED = 1 << 5; +/** + * Base element class which manages element properties and attributes. When + * properties change, the `update` method is asynchronously called. This method + * should be supplied by subclassers to render updates as desired. + */ +export class UpdatingElement extends HTMLElement { + constructor() { + super(); + this._updateState = 0; + this._instanceProperties = undefined; + this._updatePromise = microtaskPromise; + this._hasConnectedResolver = undefined; + /** + * Map with keys for any properties that have changed since the last + * update cycle with previous values. + */ + this._changedProperties = new Map(); + /** + * Map with keys of properties that should be reflected when updated. + */ + this._reflectingProperties = undefined; + this.initialize(); + } + /** + * Returns a list of attributes corresponding to the registered properties. + * @nocollapse + */ + static get observedAttributes() { + // note: piggy backing on this to ensure we're finalized. + this.finalize(); + const attributes = []; + // Use forEach so this works even if for/of loops are compiled to for loops + // expecting arrays + this._classProperties.forEach((v, p) => { + const attr = this._attributeNameForProperty(p, v); + if (attr !== undefined) { + this._attributeToPropertyMap.set(attr, p); + attributes.push(attr); + } + }); + return attributes; + } + /** + * Ensures the private `_classProperties` property metadata is created. + * In addition to `finalize` this is also called in `createProperty` to + * ensure the `@property` decorator can add property metadata. + */ + /** @nocollapse */ + static _ensureClassProperties() { + // ensure private storage for property declarations. + if (!this.hasOwnProperty(JSCompiler_renameProperty('_classProperties', this))) { + this._classProperties = new Map(); + // NOTE: Workaround IE11 not supporting Map constructor argument. + const superProperties = Object.getPrototypeOf(this)._classProperties; + if (superProperties !== undefined) { + superProperties.forEach((v, k) => this._classProperties.set(k, v)); + } + } + } + /** + * Creates a property accessor on the element prototype if one does not exist. + * The property setter calls the property's `hasChanged` property option + * or uses a strict identity check to determine whether or not to request + * an update. + * @nocollapse + */ + static createProperty(name, options = defaultPropertyDeclaration) { + // Note, since this can be called by the `@property` decorator which + // is called before `finalize`, we ensure storage exists for property + // metadata. + this._ensureClassProperties(); + this._classProperties.set(name, options); + // Do not generate an accessor if the prototype already has one, since + // it would be lost otherwise and that would never be the user's intention; + // Instead, we expect users to call `requestUpdate` themselves from + // user-defined accessors. Note that if the super has an accessor we will + // still overwrite it + if (options.noAccessor || this.prototype.hasOwnProperty(name)) { + return; + } + const key = typeof name === 'symbol' ? Symbol() : `__${name}`; + Object.defineProperty(this.prototype, name, { + // tslint:disable-next-line:no-any no symbol in index + get() { + // tslint:disable-next-line:no-any no symbol in index + return this[key]; + }, + set(value) { + // tslint:disable-next-line:no-any no symbol in index + const oldValue = this[name]; + // tslint:disable-next-line:no-any no symbol in index + this[key] = value; + this.requestUpdate(name, oldValue); + }, + configurable: true, + enumerable: true + }); + } + /** + * Creates property accessors for registered properties and ensures + * any superclasses are also finalized. + * @nocollapse + */ + static finalize() { + if (this.hasOwnProperty(JSCompiler_renameProperty('finalized', this)) && + this.finalized) { + return; + } + // finalize any superclasses + const superCtor = Object.getPrototypeOf(this); + if (typeof superCtor.finalize === 'function') { + superCtor.finalize(); + } + this.finalized = true; + this._ensureClassProperties(); + // initialize Map populated in observedAttributes + this._attributeToPropertyMap = new Map(); + // make any properties + // Note, only process "own" properties since this element will inherit + // any properties defined on the superClass, and finalization ensures + // the entire prototype chain is finalized. + if (this.hasOwnProperty(JSCompiler_renameProperty('properties', this))) { + const props = this.properties; + // support symbols in properties (IE11 does not support this) + const propKeys = [ + ...Object.getOwnPropertyNames(props), + ...(typeof Object.getOwnPropertySymbols === 'function') ? + Object.getOwnPropertySymbols(props) : + [] + ]; + // This for/of is ok because propKeys is an array + for (const p of propKeys) { + // note, use of `any` is due to TypeSript lack of support for symbol in + // index types + // tslint:disable-next-line:no-any no symbol in index + this.createProperty(p, props[p]); + } + } + } + /** + * Returns the property name for the given attribute `name`. + * @nocollapse + */ + static _attributeNameForProperty(name, options) { + const attribute = options.attribute; + return attribute === false ? + undefined : + (typeof attribute === 'string' ? + attribute : + (typeof name === 'string' ? name.toLowerCase() : undefined)); + } + /** + * Returns true if a property should request an update. + * Called when a property value is set and uses the `hasChanged` + * option for the property if present or a strict identity check. + * @nocollapse + */ + static _valueHasChanged(value, old, hasChanged = notEqual) { + return hasChanged(value, old); + } + /** + * Returns the property value for the given attribute value. + * Called via the `attributeChangedCallback` and uses the property's + * `converter` or `converter.fromAttribute` property option. + * @nocollapse + */ + static _propertyValueFromAttribute(value, options) { + const type = options.type; + const converter = options.converter || defaultConverter; + const fromAttribute = (typeof converter === 'function' ? converter : converter.fromAttribute); + return fromAttribute ? fromAttribute(value, type) : value; + } + /** + * Returns the attribute value for the given property value. If this + * returns undefined, the property will *not* be reflected to an attribute. + * If this returns null, the attribute will be removed, otherwise the + * attribute will be set to the value. + * This uses the property's `reflect` and `type.toAttribute` property options. + * @nocollapse + */ + static _propertyValueToAttribute(value, options) { + if (options.reflect === undefined) { + return; + } + const type = options.type; + const converter = options.converter; + const toAttribute = converter && converter.toAttribute || + defaultConverter.toAttribute; + return toAttribute(value, type); + } + /** + * Performs element initialization. By default captures any pre-set values for + * registered properties. + */ + initialize() { + this._saveInstanceProperties(); + } + /** + * Fixes any properties set on the instance before upgrade time. + * Otherwise these would shadow the accessor and break these properties. + * The properties are stored in a Map which is played back after the + * constructor runs. Note, on very old versions of Safari (<=9) or Chrome + * (<=41), properties created for native platform properties like (`id` or + * `name`) may not have default values set in the element constructor. On + * these browsers native properties appear on instances and therefore their + * default value will overwrite any element default (e.g. if the element sets + * this.id = 'id' in the constructor, the 'id' will become '' since this is + * the native platform default). + */ + _saveInstanceProperties() { + // Use forEach so this works even if for/of loops are compiled to for loops + // expecting arrays + this.constructor + ._classProperties.forEach((_v, p) => { + if (this.hasOwnProperty(p)) { + const value = this[p]; + delete this[p]; + if (!this._instanceProperties) { + this._instanceProperties = new Map(); + } + this._instanceProperties.set(p, value); + } + }); + } + /** + * Applies previously saved instance properties. + */ + _applyInstanceProperties() { + // Use forEach so this works even if for/of loops are compiled to for loops + // expecting arrays + // tslint:disable-next-line:no-any + this._instanceProperties.forEach((v, p) => this[p] = v); + this._instanceProperties = undefined; + } + connectedCallback() { + this._updateState = this._updateState | STATE_HAS_CONNECTED; + // Ensure connection triggers an update. Updates cannot complete before + // connection and if one is pending connection the `_hasConnectionResolver` + // will exist. If so, resolve it to complete the update, otherwise + // requestUpdate. + if (this._hasConnectedResolver) { + this._hasConnectedResolver(); + this._hasConnectedResolver = undefined; + } + else { + this.requestUpdate(); + } + } + /** + * Allows for `super.disconnectedCallback()` in extensions while + * reserving the possibility of making non-breaking feature additions + * when disconnecting at some point in the future. + */ + disconnectedCallback() { + } + /** + * Synchronizes property values when attributes change. + */ + attributeChangedCallback(name, old, value) { + if (old !== value) { + this._attributeToProperty(name, value); + } + } + _propertyToAttribute(name, value, options = defaultPropertyDeclaration) { + const ctor = this.constructor; + const attr = ctor._attributeNameForProperty(name, options); + if (attr !== undefined) { + const attrValue = ctor._propertyValueToAttribute(value, options); + // an undefined value does not change the attribute. + if (attrValue === undefined) { + return; + } + // Track if the property is being reflected to avoid + // setting the property again via `attributeChangedCallback`. Note: + // 1. this takes advantage of the fact that the callback is synchronous. + // 2. will behave incorrectly if multiple attributes are in the reaction + // stack at time of calling. However, since we process attributes + // in `update` this should not be possible (or an extreme corner case + // that we'd like to discover). + // mark state reflecting + this._updateState = this._updateState | STATE_IS_REFLECTING_TO_ATTRIBUTE; + if (attrValue == null) { + this.removeAttribute(attr); + } + else { + this.setAttribute(attr, attrValue); + } + // mark state not reflecting + this._updateState = this._updateState & ~STATE_IS_REFLECTING_TO_ATTRIBUTE; + } + } + _attributeToProperty(name, value) { + // Use tracking info to avoid deserializing attribute value if it was + // just set from a property setter. + if (this._updateState & STATE_IS_REFLECTING_TO_ATTRIBUTE) { + return; + } + const ctor = this.constructor; + const propName = ctor._attributeToPropertyMap.get(name); + if (propName !== undefined) { + const options = ctor._classProperties.get(propName) || defaultPropertyDeclaration; + // mark state reflecting + this._updateState = this._updateState | STATE_IS_REFLECTING_TO_PROPERTY; + this[propName] = + // tslint:disable-next-line:no-any + ctor._propertyValueFromAttribute(value, options); + // mark state not reflecting + this._updateState = this._updateState & ~STATE_IS_REFLECTING_TO_PROPERTY; + } + } + /** + * Requests an update which is processed asynchronously. This should + * be called when an element should update based on some state not triggered + * by setting a property. In this case, pass no arguments. It should also be + * called when manually implementing a property setter. In this case, pass the + * property `name` and `oldValue` to ensure that any configured property + * options are honored. Returns the `updateComplete` Promise which is resolved + * when the update completes. + * + * @param name {PropertyKey} (optional) name of requesting property + * @param oldValue {any} (optional) old value of requesting property + * @returns {Promise} A Promise that is resolved when the update completes. + */ + requestUpdate(name, oldValue) { + let shouldRequestUpdate = true; + // if we have a property key, perform property update steps. + if (name !== undefined && !this._changedProperties.has(name)) { + const ctor = this.constructor; + const options = ctor._classProperties.get(name) || defaultPropertyDeclaration; + if (ctor._valueHasChanged(this[name], oldValue, options.hasChanged)) { + // track old value when changing. + this._changedProperties.set(name, oldValue); + // add to reflecting properties set + if (options.reflect === true && + !(this._updateState & STATE_IS_REFLECTING_TO_PROPERTY)) { + if (this._reflectingProperties === undefined) { + this._reflectingProperties = new Map(); + } + this._reflectingProperties.set(name, options); + } + // abort the request if the property should not be considered changed. + } + else { + shouldRequestUpdate = false; + } + } + if (!this._hasRequestedUpdate && shouldRequestUpdate) { + this._enqueueUpdate(); + } + return this.updateComplete; + } + /** + * Sets up the element to asynchronously update. + */ + async _enqueueUpdate() { + // Mark state updating... + this._updateState = this._updateState | STATE_UPDATE_REQUESTED; + let resolve; + const previousUpdatePromise = this._updatePromise; + this._updatePromise = new Promise((res) => resolve = res); + // Ensure any previous update has resolved before updating. + // This `await` also ensures that property changes are batched. + await previousUpdatePromise; + // Make sure the element has connected before updating. + if (!this._hasConnected) { + await new Promise((res) => this._hasConnectedResolver = res); + } + // Allow `performUpdate` to be asynchronous to enable scheduling of updates. + const result = this.performUpdate(); + // Note, this is to avoid delaying an additional microtask unless we need + // to. + if (result != null && + typeof result.then === 'function') { + await result; + } + resolve(!this._hasRequestedUpdate); + } + get _hasConnected() { + return (this._updateState & STATE_HAS_CONNECTED); + } + get _hasRequestedUpdate() { + return (this._updateState & STATE_UPDATE_REQUESTED); + } + get hasUpdated() { + return (this._updateState & STATE_HAS_UPDATED); + } + /** + * Performs an element update. + * + * You can override this method to change the timing of updates. For instance, + * to schedule updates to occur just before the next frame: + * + * ``` + * protected async performUpdate(): Promise { + * await new Promise((resolve) => requestAnimationFrame(() => resolve())); + * super.performUpdate(); + * } + * ``` + */ + performUpdate() { + // Mixin instance properties once, if they exist. + if (this._instanceProperties) { + this._applyInstanceProperties(); + } + if (this.shouldUpdate(this._changedProperties)) { + const changedProperties = this._changedProperties; + this.update(changedProperties); + this._markUpdated(); + if (!(this._updateState & STATE_HAS_UPDATED)) { + this._updateState = this._updateState | STATE_HAS_UPDATED; + this.firstUpdated(changedProperties); + } + this.updated(changedProperties); + } + else { + this._markUpdated(); + } + } + _markUpdated() { + this._changedProperties = new Map(); + this._updateState = this._updateState & ~STATE_UPDATE_REQUESTED; + } + /** + * Returns a Promise that resolves when the element has completed updating. + * The Promise value is a boolean that is `true` if the element completed the + * update without triggering another update. The Promise result is `false` if + * a property was set inside `updated()`. This getter can be implemented to + * await additional state. For example, it is sometimes useful to await a + * rendered element before fulfilling this Promise. To do this, first await + * `super.updateComplete` then any subsequent state. + * + * @returns {Promise} The Promise returns a boolean that indicates if the + * update resolved without triggering another update. + */ + get updateComplete() { + return this._updatePromise; + } + /** + * Controls whether or not `update` should be called when the element requests + * an update. By default, this method always returns `true`, but this can be + * customized to control when to update. + * + * * @param _changedProperties Map of changed properties with old values + */ + shouldUpdate(_changedProperties) { + return true; + } + /** + * Updates the element. This method reflects property values to attributes. + * It can be overridden to render and keep updated element DOM. + * Setting properties inside this method will *not* trigger + * another update. + * + * * @param _changedProperties Map of changed properties with old values + */ + update(_changedProperties) { + if (this._reflectingProperties !== undefined && + this._reflectingProperties.size > 0) { + // Use forEach so this works even if for/of loops are compiled to for + // loops expecting arrays + this._reflectingProperties.forEach((v, k) => this._propertyToAttribute(k, this[k], v)); + this._reflectingProperties = undefined; + } + } + /** + * Invoked whenever the element is updated. Implement to perform + * post-updating tasks via DOM APIs, for example, focusing an element. + * + * Setting properties inside this method will trigger the element to update + * again after this update cycle completes. + * + * * @param _changedProperties Map of changed properties with old values + */ + updated(_changedProperties) { + } + /** + * Invoked when the element is first updated. Implement to perform one time + * work on the element after update. + * + * Setting properties inside this method will trigger the element to update + * again after this update cycle completes. + * + * * @param _changedProperties Map of changed properties with old values + */ + firstUpdated(_changedProperties) { + } +} +/** + * Marks class as having finished creating properties. + */ +UpdatingElement.finalized = true; +//# =updating-element.js.map \ No newline at end of file diff --git a/app/userland/app-stdlib/vendor/lit-element/lib/updating-element.js.map b/app/userland/app-stdlib/vendor/lit-element/lib/updating-element.js.map new file mode 100644 index 0000000000..063d3156c8 --- /dev/null +++ b/app/userland/app-stdlib/vendor/lit-element/lib/updating-element.js.map @@ -0,0 +1 @@ +{"version":3,"file":"updating-element.js","sourceRoot":"","sources":["../src/lib/updating-element.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH;;;;;GAKG;AACH,MAAM,CAAC,yBAAyB;IAC5B,CAAwB,IAAO,EAAE,IAAa,EAAK,EAAE,CAAC,IAAI,CAAC;AA8G/D,MAAM,CAAC,MAAM,gBAAgB,GAA8B;IAEzD,WAAW,CAAC,KAAc,EAAE,IAAc;QACxC,QAAQ,IAAI,EAAE;YACZ,KAAK,OAAO;gBACV,OAAO,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;YAC3B,KAAK,MAAM,CAAC;YACZ,KAAK,KAAK;gBACR,0DAA0D;gBAC1D,wCAAwC;gBACxC,OAAO,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;SACxD;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,aAAa,CAAC,KAAkB,EAAE,IAAc;QAC9C,QAAQ,IAAI,EAAE;YACZ,KAAK,OAAO;gBACV,OAAO,KAAK,KAAK,IAAI,CAAC;YACxB,KAAK,MAAM;gBACT,OAAO,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC/C,KAAK,MAAM,CAAC;YACZ,KAAK,KAAK;gBACR,OAAO,IAAI,CAAC,KAAK,CAAC,KAAM,CAAC,CAAC;SAC7B;QACD,OAAO,KAAK,CAAC;IACf,CAAC;CAEF,CAAC;AAMF;;;GAGG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAe,CAAC,KAAc,EAAE,GAAY,EAAW,EAAE;IAC5E,2DAA2D;IAC3D,OAAO,GAAG,KAAK,KAAK,IAAI,CAAC,GAAG,KAAK,GAAG,IAAI,KAAK,KAAK,KAAK,CAAC,CAAC;AAC3D,CAAC,CAAC;AAEF,MAAM,0BAA0B,GAAwB;IACtD,SAAS,EAAE,IAAI;IACf,IAAI,EAAE,MAAM;IACZ,SAAS,EAAE,gBAAgB;IAC3B,OAAO,EAAE,KAAK;IACd,UAAU,EAAE,QAAQ;CACrB,CAAC;AAEF,MAAM,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;AAE/C,MAAM,iBAAiB,GAAG,CAAC,CAAC;AAC5B,MAAM,sBAAsB,GAAG,CAAC,IAAI,CAAC,CAAC;AACtC,MAAM,gCAAgC,GAAG,CAAC,IAAI,CAAC,CAAC;AAChD,MAAM,+BAA+B,GAAG,CAAC,IAAI,CAAC,CAAC;AAC/C,MAAM,mBAAmB,GAAG,CAAC,IAAI,CAAC,CAAC;AAKnC;;;;GAIG;AACH,MAAM,OAAgB,eAAgB,SAAQ,WAAW;IA2OvD;QACE,KAAK,EAAE,CAAC;QAlBF,iBAAY,GAAgB,CAAC,CAAC;QAC9B,wBAAmB,GAA6B,SAAS,CAAC;QAC1D,mBAAc,GAAqB,gBAAgB,CAAC;QACpD,0BAAqB,GAA2B,SAAS,CAAC;QAElE;;;WAGG;QACK,uBAAkB,GAAmB,IAAI,GAAG,EAAE,CAAC;QAEvD;;WAEG;QACK,0BAAqB,GACb,SAAS,CAAC;QAIxB,IAAI,CAAC,UAAU,EAAE,CAAC;IACpB,CAAC;IA/MD;;;OAGG;IACH,MAAM,KAAK,kBAAkB;QAC3B,yDAAyD;QACzD,IAAI,CAAC,QAAQ,EAAE,CAAC;QAChB,MAAM,UAAU,GAAa,EAAE,CAAC;QAChC,2EAA2E;QAC3E,mBAAmB;QACnB,IAAI,CAAC,gBAAiB,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;YACtC,MAAM,IAAI,GAAG,IAAI,CAAC,yBAAyB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YAClD,IAAI,IAAI,KAAK,SAAS,EAAE;gBACtB,IAAI,CAAC,uBAAuB,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;gBAC1C,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;aACvB;QACH,CAAC,CAAC,CAAC;QACH,OAAO,UAAU,CAAC;IACpB,CAAC;IAED;;;;OAIG;IACH,kBAAkB;IACV,MAAM,CAAC,sBAAsB;QACnC,oDAAoD;QACpD,IAAI,CAAC,IAAI,CAAC,cAAc,CAChB,yBAAyB,CAAC,kBAAkB,EAAE,IAAI,CAAC,CAAC,EAAE;YAC5D,IAAI,CAAC,gBAAgB,GAAG,IAAI,GAAG,EAAE,CAAC;YAClC,iEAAiE;YACjE,MAAM,eAAe,GACjB,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,gBAAgB,CAAC;YACjD,IAAI,eAAe,KAAK,SAAS,EAAE;gBACjC,eAAe,CAAC,OAAO,CACnB,CAAC,CAAsB,EAAE,CAAc,EAAE,EAAE,CACvC,IAAI,CAAC,gBAAiB,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;aAC3C;SACF;IACH,CAAC;IAED;;;;;;OAMG;IACH,MAAM,CAAC,cAAc,CACjB,IAAiB,EACjB,UAA+B,0BAA0B;QAC3D,oEAAoE;QACpE,qEAAqE;QACrE,YAAY;QACZ,IAAI,CAAC,sBAAsB,EAAE,CAAC;QAC9B,IAAI,CAAC,gBAAiB,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC1C,sEAAsE;QACtE,2EAA2E;QAC3E,mEAAmE;QACnE,yEAAyE;QACzE,qBAAqB;QACrB,IAAI,OAAO,CAAC,UAAU,IAAI,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE;YAC7D,OAAO;SACR;QACD,MAAM,GAAG,GAAG,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC9D,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE;YAC1C,qDAAqD;YACrD,GAAG;gBACD,qDAAqD;gBACrD,OAAQ,IAAY,CAAC,GAAG,CAAC,CAAC;YAC5B,CAAC;YACD,GAAG,CAAwB,KAAc;gBACvC,qDAAqD;gBACrD,MAAM,QAAQ,GAAI,IAAY,CAAC,IAAI,CAAC,CAAC;gBACrC,qDAAqD;gBACpD,IAAY,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;gBAC3B,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;YACrC,CAAC;YACD,YAAY,EAAE,IAAI;YAClB,UAAU,EAAE,IAAI;SACjB,CAAC,CAAC;IACL,CAAC;IAED;;;;OAIG;IACO,MAAM,CAAC,QAAQ;QACvB,IAAI,IAAI,CAAC,cAAc,CAAC,yBAAyB,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;YACjE,IAAI,CAAC,SAAS,EAAE;YAClB,OAAO;SACR;QACD,4BAA4B;QAC5B,MAAM,SAAS,GAAG,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;QAC9C,IAAI,OAAO,SAAS,CAAC,QAAQ,KAAK,UAAU,EAAE;YAC5C,SAAS,CAAC,QAAQ,EAAE,CAAC;SACtB;QACD,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACtB,IAAI,CAAC,sBAAsB,EAAE,CAAC;QAC9B,iDAAiD;QACjD,IAAI,CAAC,uBAAuB,GAAG,IAAI,GAAG,EAAE,CAAC;QACzC,sBAAsB;QACtB,sEAAsE;QACtE,qEAAqE;QACrE,2CAA2C;QAC3C,IAAI,IAAI,CAAC,cAAc,CAAC,yBAAyB,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC,EAAE;YACtE,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC;YAC9B,6DAA6D;YAC7D,MAAM,QAAQ,GAAG;gBACf,GAAG,MAAM,CAAC,mBAAmB,CAAC,KAAK,CAAC;gBACpC,GAAG,CAAC,OAAO,MAAM,CAAC,qBAAqB,KAAK,UAAU,CAAC,CAAC,CAAC;oBACrD,MAAM,CAAC,qBAAqB,CAAC,KAAK,CAAC,CAAC,CAAC;oBACrC,EAAE;aACP,CAAC;YACF,iDAAiD;YACjD,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE;gBACxB,uEAAuE;gBACvE,cAAc;gBACd,qDAAqD;gBACrD,IAAI,CAAC,cAAc,CAAC,CAAC,EAAG,KAAa,CAAC,CAAC,CAAC,CAAC,CAAC;aAC3C;SACF;IACH,CAAC;IAED;;;OAGG;IACK,MAAM,CAAC,yBAAyB,CACpC,IAAiB,EAAE,OAA4B;QACjD,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;QACpC,OAAO,SAAS,KAAK,KAAK,CAAC,CAAC;YACxB,SAAS,CAAC,CAAC;YACX,CAAC,OAAO,SAAS,KAAK,QAAQ,CAAC,CAAC;gBAC3B,SAAS,CAAC,CAAC;gBACX,CAAC,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;IACxE,CAAC;IAED;;;;;OAKG;IACK,MAAM,CAAC,gBAAgB,CAC3B,KAAc,EAAE,GAAY,EAAE,aAAyB,QAAQ;QACjE,OAAO,UAAU,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IAChC,CAAC;IAED;;;;;OAKG;IACK,MAAM,CAAC,2BAA2B,CACtC,KAAkB,EAAE,OAA4B;QAClD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;QAC1B,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,gBAAgB,CAAC;QACxD,MAAM,aAAa,GACf,CAAC,OAAO,SAAS,KAAK,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;QAC5E,OAAO,aAAa,CAAC,CAAC,CAAC,aAAa,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IAC5D,CAAC;IAED;;;;;;;OAOG;IACK,MAAM,CAAC,yBAAyB,CACpC,KAAc,EAAE,OAA4B;QAC9C,IAAI,OAAO,CAAC,OAAO,KAAK,SAAS,EAAE;YACjC,OAAO;SACR;QACD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;QAC1B,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;QACpC,MAAM,WAAW,GACb,SAAS,IAAK,SAAuC,CAAC,WAAW;YACjE,gBAAgB,CAAC,WAAW,CAAC;QACjC,OAAO,WAAY,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACnC,CAAC;IAwBD;;;OAGG;IACO,UAAU;QAClB,IAAI,CAAC,uBAAuB,EAAE,CAAC;IACjC,CAAC;IAED;;;;;;;;;;;OAWG;IACK,uBAAuB;QAC7B,2EAA2E;QAC3E,mBAAmB;QAClB,IAAI,CAAC,WAAsC;aACvC,gBAAiB,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE;YACnC,IAAI,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE;gBAC1B,MAAM,KAAK,GAAG,IAAI,CAAC,CAAe,CAAC,CAAC;gBACpC,OAAO,IAAI,CAAC,CAAe,CAAC,CAAC;gBAC7B,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE;oBAC7B,IAAI,CAAC,mBAAmB,GAAG,IAAI,GAAG,EAAE,CAAC;iBACtC;gBACD,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;aACxC;QACH,CAAC,CAAC,CAAC;IACT,CAAC;IAED;;OAEG;IACK,wBAAwB;QAC9B,2EAA2E;QAC3E,mBAAmB;QACnB,kCAAkC;QAClC,IAAI,CAAC,mBAAoB,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAE,IAAY,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAClE,IAAI,CAAC,mBAAmB,GAAG,SAAS,CAAC;IACvC,CAAC;IAED,iBAAiB;QACf,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,GAAG,mBAAmB,CAAC;QAC5D,uEAAuE;QACvE,2EAA2E;QAC3E,kEAAkE;QAClE,iBAAiB;QACjB,IAAI,IAAI,CAAC,qBAAqB,EAAE;YAC9B,IAAI,CAAC,qBAAqB,EAAE,CAAC;YAC7B,IAAI,CAAC,qBAAqB,GAAG,SAAS,CAAC;SACxC;aAAM;YACL,IAAI,CAAC,aAAa,EAAE,CAAC;SACtB;IACH,CAAC;IAED;;;;OAIG;IACH,oBAAoB;IACpB,CAAC;IAED;;OAEG;IACH,wBAAwB,CAAC,IAAY,EAAE,GAAgB,EAAE,KAAkB;QACzE,IAAI,GAAG,KAAK,KAAK,EAAE;YACjB,IAAI,CAAC,oBAAoB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;SACxC;IACH,CAAC;IAEO,oBAAoB,CACxB,IAAiB,EAAE,KAAc,EACjC,UAA+B,0BAA0B;QAC3D,MAAM,IAAI,GAAI,IAAI,CAAC,WAAsC,CAAC;QAC1D,MAAM,IAAI,GAAG,IAAI,CAAC,yBAAyB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC3D,IAAI,IAAI,KAAK,SAAS,EAAE;YACtB,MAAM,SAAS,GAAG,IAAI,CAAC,yBAAyB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;YACjE,oDAAoD;YACpD,IAAI,SAAS,KAAK,SAAS,EAAE;gBAC3B,OAAO;aACR;YACD,oDAAoD;YACpD,mEAAmE;YACnE,wEAAwE;YACxE,wEAAwE;YACxE,iEAAiE;YACjE,qEAAqE;YACrE,+BAA+B;YAC/B,wBAAwB;YACxB,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,GAAG,gCAAgC,CAAC;YACzE,IAAI,SAAS,IAAI,IAAI,EAAE;gBACrB,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;aAC5B;iBAAM;gBACL,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,SAAmB,CAAC,CAAC;aAC9C;YACD,4BAA4B;YAC5B,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,GAAG,CAAC,gCAAgC,CAAC;SAC3E;IACH,CAAC;IAEO,oBAAoB,CAAC,IAAY,EAAE,KAAkB;QAC3D,qEAAqE;QACrE,mCAAmC;QACnC,IAAI,IAAI,CAAC,YAAY,GAAG,gCAAgC,EAAE;YACxD,OAAO;SACR;QACD,MAAM,IAAI,GAAI,IAAI,CAAC,WAAsC,CAAC;QAC1D,MAAM,QAAQ,GAAG,IAAI,CAAC,uBAAuB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACxD,IAAI,QAAQ,KAAK,SAAS,EAAE;YAC1B,MAAM,OAAO,GACT,IAAI,CAAC,gBAAiB,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,0BAA0B,CAAC;YACvE,wBAAwB;YACxB,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,GAAG,+BAA+B,CAAC;YACxE,IAAI,CAAC,QAAsB,CAAC;gBACxB,kCAAkC;gBAClC,IAAI,CAAC,2BAA2B,CAAC,KAAK,EAAE,OAAO,CAAQ,CAAC;YAC5D,4BAA4B;YAC5B,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,GAAG,CAAC,+BAA+B,CAAC;SAC1E;IACH,CAAC;IAED;;;;;;;;;;;;OAYG;IACH,aAAa,CAAC,IAAkB,EAAE,QAAkB;QAClD,IAAI,mBAAmB,GAAG,IAAI,CAAC;QAC/B,4DAA4D;QAC5D,IAAI,IAAI,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE;YAC5D,MAAM,IAAI,GAAG,IAAI,CAAC,WAAqC,CAAC;YACxD,MAAM,OAAO,GACT,IAAI,CAAC,gBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,0BAA0B,CAAC;YACnE,IAAI,IAAI,CAAC,gBAAgB,CACjB,IAAI,CAAC,IAAkB,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,UAAU,CAAC,EAAE;gBAC/D,iCAAiC;gBACjC,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;gBAC5C,mCAAmC;gBACnC,IAAI,OAAO,CAAC,OAAO,KAAK,IAAI;oBACxB,CAAC,CAAC,IAAI,CAAC,YAAY,GAAG,+BAA+B,CAAC,EAAE;oBAC1D,IAAI,IAAI,CAAC,qBAAqB,KAAK,SAAS,EAAE;wBAC5C,IAAI,CAAC,qBAAqB,GAAG,IAAI,GAAG,EAAE,CAAC;qBACxC;oBACD,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;iBAC/C;gBACD,sEAAsE;aACvE;iBAAM;gBACL,mBAAmB,GAAG,KAAK,CAAC;aAC7B;SACF;QACD,IAAI,CAAC,IAAI,CAAC,mBAAmB,IAAI,mBAAmB,EAAE;YACpD,IAAI,CAAC,cAAc,EAAE,CAAC;SACvB;QACD,OAAO,IAAI,CAAC,cAAc,CAAC;IAC7B,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,cAAc;QAC1B,yBAAyB;QACzB,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,GAAG,sBAAsB,CAAC;QAC/D,IAAI,OAA6B,CAAC;QAClC,MAAM,qBAAqB,GAAG,IAAI,CAAC,cAAc,CAAC;QAClD,IAAI,CAAC,cAAc,GAAG,IAAI,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,GAAG,GAAG,CAAC,CAAC;QAC1D,2DAA2D;QAC3D,+DAA+D;QAC/D,MAAM,qBAAqB,CAAC;QAC5B,uDAAuD;QACvD,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE;YACvB,MAAM,IAAI,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,qBAAqB,GAAG,GAAG,CAAC,CAAC;SAC9D;QACD,4EAA4E;QAC5E,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;QACpC,yEAAyE;QACzE,MAAM;QACN,IAAI,MAAM,IAAI,IAAI;YACd,OAAQ,MAA+B,CAAC,IAAI,KAAK,UAAU,EAAE;YAC/D,MAAM,MAAM,CAAC;SACd;QACD,OAAQ,CAAC,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;IACtC,CAAC;IAED,IAAY,aAAa;QACvB,OAAO,CAAC,IAAI,CAAC,YAAY,GAAG,mBAAmB,CAAC,CAAC;IACnD,CAAC;IAED,IAAY,mBAAmB;QAC7B,OAAO,CAAC,IAAI,CAAC,YAAY,GAAG,sBAAsB,CAAC,CAAC;IACtD,CAAC;IAED,IAAc,UAAU;QACtB,OAAO,CAAC,IAAI,CAAC,YAAY,GAAG,iBAAiB,CAAC,CAAC;IACjD,CAAC;IAED;;;;;;;;;;;;OAYG;IACO,aAAa;QACrB,iDAAiD;QACjD,IAAI,IAAI,CAAC,mBAAmB,EAAE;YAC5B,IAAI,CAAC,wBAAwB,EAAE,CAAC;SACjC;QACD,IAAI,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,kBAAkB,CAAC,EAAE;YAC9C,MAAM,iBAAiB,GAAG,IAAI,CAAC,kBAAkB,CAAC;YAClD,IAAI,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC;YAC/B,IAAI,CAAC,YAAY,EAAE,CAAC;YACpB,IAAI,CAAC,CAAC,IAAI,CAAC,YAAY,GAAG,iBAAiB,CAAC,EAAE;gBAC5C,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,GAAG,iBAAiB,CAAC;gBAC1D,IAAI,CAAC,YAAY,CAAC,iBAAiB,CAAC,CAAC;aACtC;YACD,IAAI,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;SACjC;aAAM;YACL,IAAI,CAAC,YAAY,EAAE,CAAC;SACrB;IACH,CAAC;IAEO,YAAY;QAClB,IAAI,CAAC,kBAAkB,GAAG,IAAI,GAAG,EAAE,CAAC;QACpC,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,GAAG,CAAC,sBAAsB,CAAC;IAClE,CAAC;IAED;;;;;;;;;;;OAWG;IACH,IAAI,cAAc;QAChB,OAAO,IAAI,CAAC,cAAc,CAAC;IAC7B,CAAC;IAED;;;;;;OAMG;IACO,YAAY,CAAC,kBAAkC;QACvD,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;;;;OAOG;IACO,MAAM,CAAC,kBAAkC;QACjD,IAAI,IAAI,CAAC,qBAAqB,KAAK,SAAS;YACxC,IAAI,CAAC,qBAAqB,CAAC,IAAI,GAAG,CAAC,EAAE;YACvC,qEAAqE;YACrE,yBAAyB;YACzB,IAAI,CAAC,qBAAqB,CAAC,OAAO,CAC9B,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC,EAAE,IAAI,CAAC,CAAe,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YACtE,IAAI,CAAC,qBAAqB,GAAG,SAAS,CAAC;SACxC;IACH,CAAC;IAED;;;;;;;;OAQG;IACO,OAAO,CAAC,kBAAkC;IACpD,CAAC;IAED;;;;;;;;OAQG;IACO,YAAY,CAAC,kBAAkC;IACzD,CAAC;;AA9hBD;;GAEG;AACc,yBAAS,GAAG,IAAI,CAAC","sourcesContent":["/**\n * @license\n * Copyright (c) 2017 The Polymer Project Authors. All rights reserved.\n * This code may only be used under the BSD style license found at\n * http://polymer.github.io/LICENSE.txt\n * The complete set of authors may be found at\n * http://polymer.github.io/AUTHORS.txt\n * The complete set of contributors may be found at\n * http://polymer.github.io/CONTRIBUTORS.txt\n * Code distributed by Google as part of the polymer project is also\n * subject to an additional IP rights grant found at\n * http://polymer.github.io/PATENTS.txt\n */\n\n/**\n * When using Closure Compiler, JSCompiler_renameProperty(property, object) is\n * replaced at compile time by the munged name for object[property]. We cannot\n * alias this function, so we have to use a small shim that has the same\n * behavior when not compiling.\n */\nwindow.JSCompiler_renameProperty =\n

    (prop: P, _obj: unknown): P => prop;\n\ndeclare global {\n var JSCompiler_renameProperty:

    (\n prop: P, _obj: unknown) => P;\n\n interface Window {\n JSCompiler_renameProperty: typeof JSCompiler_renameProperty;\n }\n}\n\n/**\n * Converts property values to and from attribute values.\n */\nexport interface ComplexAttributeConverter {\n /**\n * Function called to convert an attribute value to a property\n * value.\n */\n fromAttribute?(value: string|null, type?: TypeHint): Type;\n\n /**\n * Function called to convert a property value to an attribute\n * value.\n *\n * It returns unknown instead of string, to be compatible with\n * https://github.com/WICG/trusted-types (and similar efforts).\n */\n toAttribute?(value: Type, type?: TypeHint): unknown;\n}\n\ntype AttributeConverter =\n ComplexAttributeConverter|((value: string, type?: TypeHint) => Type);\n\n/**\n * Defines options for a property accessor.\n */\nexport interface PropertyDeclaration {\n /**\n * Indicates how and whether the property becomes an observed attribute.\n * If the value is `false`, the property is not added to `observedAttributes`.\n * If true or absent, the lowercased property name is observed (e.g. `fooBar`\n * becomes `foobar`). If a string, the string value is observed (e.g\n * `attribute: 'foo-bar'`).\n */\n readonly attribute?: boolean|string;\n\n /**\n * Indicates the type of the property. This is used only as a hint for the\n * `converter` to determine how to convert the attribute\n * to/from a property.\n */\n readonly type?: TypeHint;\n\n /**\n * Indicates how to convert the attribute to/from a property. If this value\n * is a function, it is used to convert the attribute value a the property\n * value. If it's an object, it can have keys for `fromAttribute` and\n * `toAttribute`. If no `toAttribute` function is provided and\n * `reflect` is set to `true`, the property value is set directly to the\n * attribute. A default `converter` is used if none is provided; it supports\n * `Boolean`, `String`, `Number`, `Object`, and `Array`. Note,\n * when a property changes and the converter is used to update the attribute,\n * the property is never updated again as a result of the attribute changing,\n * and vice versa.\n */\n readonly converter?: AttributeConverter;\n\n /**\n * Indicates if the property should reflect to an attribute.\n * If `true`, when the property is set, the attribute is set using the\n * attribute name determined according to the rules for the `attribute`\n * property option and the value of the property converted using the rules\n * from the `converter` property option.\n */\n readonly reflect?: boolean;\n\n /**\n * A function that indicates if a property should be considered changed when\n * it is set. The function should take the `newValue` and `oldValue` and\n * return `true` if an update should be requested.\n */\n hasChanged?(value: Type, oldValue: Type): boolean;\n\n /**\n * Indicates whether an accessor will be created for this property. By\n * default, an accessor will be generated for this property that requests an\n * update when set. If this flag is `true`, no accessor will be created, and\n * it will be the user's responsibility to call\n * `this.requestUpdate(propertyName, oldValue)` to request an update when\n * the property changes.\n */\n readonly noAccessor?: boolean;\n}\n\n/**\n * Map of properties to PropertyDeclaration options. For each property an\n * accessor is made, and the property is processed according to the\n * PropertyDeclaration options.\n */\nexport interface PropertyDeclarations {\n readonly [key: string]: PropertyDeclaration;\n}\n\ntype PropertyDeclarationMap = Map;\n\ntype AttributeMap = Map;\n\nexport type PropertyValues = Map;\n\nexport const defaultConverter: ComplexAttributeConverter = {\n\n toAttribute(value: unknown, type?: unknown): unknown {\n switch (type) {\n case Boolean:\n return value ? '' : null;\n case Object:\n case Array:\n // if the value is `null` or `undefined` pass this through\n // to allow removing/no change behavior.\n return value == null ? value : JSON.stringify(value);\n }\n return value;\n },\n\n fromAttribute(value: string|null, type?: unknown) {\n switch (type) {\n case Boolean:\n return value !== null;\n case Number:\n return value === null ? null : Number(value);\n case Object:\n case Array:\n return JSON.parse(value!);\n }\n return value;\n }\n\n};\n\nexport interface HasChanged {\n (value: unknown, old: unknown): boolean;\n}\n\n/**\n * Change function that returns true if `value` is different from `oldValue`.\n * This method is used as the default for a property's `hasChanged` function.\n */\nexport const notEqual: HasChanged = (value: unknown, old: unknown): boolean => {\n // This ensures (old==NaN, value==NaN) always returns false\n return old !== value && (old === old || value === value);\n};\n\nconst defaultPropertyDeclaration: PropertyDeclaration = {\n attribute: true,\n type: String,\n converter: defaultConverter,\n reflect: false,\n hasChanged: notEqual\n};\n\nconst microtaskPromise = Promise.resolve(true);\n\nconst STATE_HAS_UPDATED = 1;\nconst STATE_UPDATE_REQUESTED = 1 << 2;\nconst STATE_IS_REFLECTING_TO_ATTRIBUTE = 1 << 3;\nconst STATE_IS_REFLECTING_TO_PROPERTY = 1 << 4;\nconst STATE_HAS_CONNECTED = 1 << 5;\ntype UpdateState = typeof STATE_HAS_UPDATED|typeof STATE_UPDATE_REQUESTED|\n typeof STATE_IS_REFLECTING_TO_ATTRIBUTE|\n typeof STATE_IS_REFLECTING_TO_PROPERTY|typeof STATE_HAS_CONNECTED;\n\n/**\n * Base element class which manages element properties and attributes. When\n * properties change, the `update` method is asynchronously called. This method\n * should be supplied by subclassers to render updates as desired.\n */\nexport abstract class UpdatingElement extends HTMLElement {\n /*\n * Due to closure compiler ES6 compilation bugs, @nocollapse is required on\n * all static methods and properties with initializers. Reference:\n * - https://github.com/google/closure-compiler/issues/1776\n */\n\n /**\n * Maps attribute names to properties; for example `foobar` attribute to\n * `fooBar` property. Created lazily on user subclasses when finalizing the\n * class.\n */\n private static _attributeToPropertyMap: AttributeMap;\n\n /**\n * Marks class as having finished creating properties.\n */\n protected static finalized = true;\n\n /**\n * Memoized list of all class properties, including any superclass properties.\n * Created lazily on user subclasses when finalizing the class.\n */\n private static _classProperties?: PropertyDeclarationMap;\n\n /**\n * User-supplied object that maps property names to `PropertyDeclaration`\n * objects containing options for configuring the property.\n */\n static properties: PropertyDeclarations;\n\n /**\n * Returns a list of attributes corresponding to the registered properties.\n * @nocollapse\n */\n static get observedAttributes() {\n // note: piggy backing on this to ensure we're finalized.\n this.finalize();\n const attributes: string[] = [];\n // Use forEach so this works even if for/of loops are compiled to for loops\n // expecting arrays\n this._classProperties!.forEach((v, p) => {\n const attr = this._attributeNameForProperty(p, v);\n if (attr !== undefined) {\n this._attributeToPropertyMap.set(attr, p);\n attributes.push(attr);\n }\n });\n return attributes;\n }\n\n /**\n * Ensures the private `_classProperties` property metadata is created.\n * In addition to `finalize` this is also called in `createProperty` to\n * ensure the `@property` decorator can add property metadata.\n */\n /** @nocollapse */\n private static _ensureClassProperties() {\n // ensure private storage for property declarations.\n if (!this.hasOwnProperty(\n JSCompiler_renameProperty('_classProperties', this))) {\n this._classProperties = new Map();\n // NOTE: Workaround IE11 not supporting Map constructor argument.\n const superProperties: PropertyDeclarationMap =\n Object.getPrototypeOf(this)._classProperties;\n if (superProperties !== undefined) {\n superProperties.forEach(\n (v: PropertyDeclaration, k: PropertyKey) =>\n this._classProperties!.set(k, v));\n }\n }\n }\n\n /**\n * Creates a property accessor on the element prototype if one does not exist.\n * The property setter calls the property's `hasChanged` property option\n * or uses a strict identity check to determine whether or not to request\n * an update.\n * @nocollapse\n */\n static createProperty(\n name: PropertyKey,\n options: PropertyDeclaration = defaultPropertyDeclaration) {\n // Note, since this can be called by the `@property` decorator which\n // is called before `finalize`, we ensure storage exists for property\n // metadata.\n this._ensureClassProperties();\n this._classProperties!.set(name, options);\n // Do not generate an accessor if the prototype already has one, since\n // it would be lost otherwise and that would never be the user's intention;\n // Instead, we expect users to call `requestUpdate` themselves from\n // user-defined accessors. Note that if the super has an accessor we will\n // still overwrite it\n if (options.noAccessor || this.prototype.hasOwnProperty(name)) {\n return;\n }\n const key = typeof name === 'symbol' ? Symbol() : `__${name}`;\n Object.defineProperty(this.prototype, name, {\n // tslint:disable-next-line:no-any no symbol in index\n get(): any {\n // tslint:disable-next-line:no-any no symbol in index\n return (this as any)[key];\n },\n set(this: UpdatingElement, value: unknown) {\n // tslint:disable-next-line:no-any no symbol in index\n const oldValue = (this as any)[name];\n // tslint:disable-next-line:no-any no symbol in index\n (this as any)[key] = value;\n this.requestUpdate(name, oldValue);\n },\n configurable: true,\n enumerable: true\n });\n }\n\n /**\n * Creates property accessors for registered properties and ensures\n * any superclasses are also finalized.\n * @nocollapse\n */\n protected static finalize() {\n if (this.hasOwnProperty(JSCompiler_renameProperty('finalized', this)) &&\n this.finalized) {\n return;\n }\n // finalize any superclasses\n const superCtor = Object.getPrototypeOf(this);\n if (typeof superCtor.finalize === 'function') {\n superCtor.finalize();\n }\n this.finalized = true;\n this._ensureClassProperties();\n // initialize Map populated in observedAttributes\n this._attributeToPropertyMap = new Map();\n // make any properties\n // Note, only process \"own\" properties since this element will inherit\n // any properties defined on the superClass, and finalization ensures\n // the entire prototype chain is finalized.\n if (this.hasOwnProperty(JSCompiler_renameProperty('properties', this))) {\n const props = this.properties;\n // support symbols in properties (IE11 does not support this)\n const propKeys = [\n ...Object.getOwnPropertyNames(props),\n ...(typeof Object.getOwnPropertySymbols === 'function') ?\n Object.getOwnPropertySymbols(props) :\n []\n ];\n // This for/of is ok because propKeys is an array\n for (const p of propKeys) {\n // note, use of `any` is due to TypeSript lack of support for symbol in\n // index types\n // tslint:disable-next-line:no-any no symbol in index\n this.createProperty(p, (props as any)[p]);\n }\n }\n }\n\n /**\n * Returns the property name for the given attribute `name`.\n * @nocollapse\n */\n private static _attributeNameForProperty(\n name: PropertyKey, options: PropertyDeclaration) {\n const attribute = options.attribute;\n return attribute === false ?\n undefined :\n (typeof attribute === 'string' ?\n attribute :\n (typeof name === 'string' ? name.toLowerCase() : undefined));\n }\n\n /**\n * Returns true if a property should request an update.\n * Called when a property value is set and uses the `hasChanged`\n * option for the property if present or a strict identity check.\n * @nocollapse\n */\n private static _valueHasChanged(\n value: unknown, old: unknown, hasChanged: HasChanged = notEqual) {\n return hasChanged(value, old);\n }\n\n /**\n * Returns the property value for the given attribute value.\n * Called via the `attributeChangedCallback` and uses the property's\n * `converter` or `converter.fromAttribute` property option.\n * @nocollapse\n */\n private static _propertyValueFromAttribute(\n value: string|null, options: PropertyDeclaration) {\n const type = options.type;\n const converter = options.converter || defaultConverter;\n const fromAttribute =\n (typeof converter === 'function' ? converter : converter.fromAttribute);\n return fromAttribute ? fromAttribute(value, type) : value;\n }\n\n /**\n * Returns the attribute value for the given property value. If this\n * returns undefined, the property will *not* be reflected to an attribute.\n * If this returns null, the attribute will be removed, otherwise the\n * attribute will be set to the value.\n * This uses the property's `reflect` and `type.toAttribute` property options.\n * @nocollapse\n */\n private static _propertyValueToAttribute(\n value: unknown, options: PropertyDeclaration) {\n if (options.reflect === undefined) {\n return;\n }\n const type = options.type;\n const converter = options.converter;\n const toAttribute =\n converter && (converter as ComplexAttributeConverter).toAttribute ||\n defaultConverter.toAttribute;\n return toAttribute!(value, type);\n }\n\n private _updateState: UpdateState = 0;\n private _instanceProperties: PropertyValues|undefined = undefined;\n private _updatePromise: Promise = microtaskPromise;\n private _hasConnectedResolver: (() => void)|undefined = undefined;\n\n /**\n * Map with keys for any properties that have changed since the last\n * update cycle with previous values.\n */\n private _changedProperties: PropertyValues = new Map();\n\n /**\n * Map with keys of properties that should be reflected when updated.\n */\n private _reflectingProperties: Map|\n undefined = undefined;\n\n constructor() {\n super();\n this.initialize();\n }\n\n /**\n * Performs element initialization. By default captures any pre-set values for\n * registered properties.\n */\n protected initialize() {\n this._saveInstanceProperties();\n }\n\n /**\n * Fixes any properties set on the instance before upgrade time.\n * Otherwise these would shadow the accessor and break these properties.\n * The properties are stored in a Map which is played back after the\n * constructor runs. Note, on very old versions of Safari (<=9) or Chrome\n * (<=41), properties created for native platform properties like (`id` or\n * `name`) may not have default values set in the element constructor. On\n * these browsers native properties appear on instances and therefore their\n * default value will overwrite any element default (e.g. if the element sets\n * this.id = 'id' in the constructor, the 'id' will become '' since this is\n * the native platform default).\n */\n private _saveInstanceProperties() {\n // Use forEach so this works even if for/of loops are compiled to for loops\n // expecting arrays\n (this.constructor as typeof UpdatingElement)\n ._classProperties!.forEach((_v, p) => {\n if (this.hasOwnProperty(p)) {\n const value = this[p as keyof this];\n delete this[p as keyof this];\n if (!this._instanceProperties) {\n this._instanceProperties = new Map();\n }\n this._instanceProperties.set(p, value);\n }\n });\n }\n\n /**\n * Applies previously saved instance properties.\n */\n private _applyInstanceProperties() {\n // Use forEach so this works even if for/of loops are compiled to for loops\n // expecting arrays\n // tslint:disable-next-line:no-any\n this._instanceProperties!.forEach((v, p) => (this as any)[p] = v);\n this._instanceProperties = undefined;\n }\n\n connectedCallback() {\n this._updateState = this._updateState | STATE_HAS_CONNECTED;\n // Ensure connection triggers an update. Updates cannot complete before\n // connection and if one is pending connection the `_hasConnectionResolver`\n // will exist. If so, resolve it to complete the update, otherwise\n // requestUpdate.\n if (this._hasConnectedResolver) {\n this._hasConnectedResolver();\n this._hasConnectedResolver = undefined;\n } else {\n this.requestUpdate();\n }\n }\n\n /**\n * Allows for `super.disconnectedCallback()` in extensions while\n * reserving the possibility of making non-breaking feature additions\n * when disconnecting at some point in the future.\n */\n disconnectedCallback() {\n }\n\n /**\n * Synchronizes property values when attributes change.\n */\n attributeChangedCallback(name: string, old: string|null, value: string|null) {\n if (old !== value) {\n this._attributeToProperty(name, value);\n }\n }\n\n private _propertyToAttribute(\n name: PropertyKey, value: unknown,\n options: PropertyDeclaration = defaultPropertyDeclaration) {\n const ctor = (this.constructor as typeof UpdatingElement);\n const attr = ctor._attributeNameForProperty(name, options);\n if (attr !== undefined) {\n const attrValue = ctor._propertyValueToAttribute(value, options);\n // an undefined value does not change the attribute.\n if (attrValue === undefined) {\n return;\n }\n // Track if the property is being reflected to avoid\n // setting the property again via `attributeChangedCallback`. Note:\n // 1. this takes advantage of the fact that the callback is synchronous.\n // 2. will behave incorrectly if multiple attributes are in the reaction\n // stack at time of calling. However, since we process attributes\n // in `update` this should not be possible (or an extreme corner case\n // that we'd like to discover).\n // mark state reflecting\n this._updateState = this._updateState | STATE_IS_REFLECTING_TO_ATTRIBUTE;\n if (attrValue == null) {\n this.removeAttribute(attr);\n } else {\n this.setAttribute(attr, attrValue as string);\n }\n // mark state not reflecting\n this._updateState = this._updateState & ~STATE_IS_REFLECTING_TO_ATTRIBUTE;\n }\n }\n\n private _attributeToProperty(name: string, value: string|null) {\n // Use tracking info to avoid deserializing attribute value if it was\n // just set from a property setter.\n if (this._updateState & STATE_IS_REFLECTING_TO_ATTRIBUTE) {\n return;\n }\n const ctor = (this.constructor as typeof UpdatingElement);\n const propName = ctor._attributeToPropertyMap.get(name);\n if (propName !== undefined) {\n const options =\n ctor._classProperties!.get(propName) || defaultPropertyDeclaration;\n // mark state reflecting\n this._updateState = this._updateState | STATE_IS_REFLECTING_TO_PROPERTY;\n this[propName as keyof this] =\n // tslint:disable-next-line:no-any\n ctor._propertyValueFromAttribute(value, options) as any;\n // mark state not reflecting\n this._updateState = this._updateState & ~STATE_IS_REFLECTING_TO_PROPERTY;\n }\n }\n\n /**\n * Requests an update which is processed asynchronously. This should\n * be called when an element should update based on some state not triggered\n * by setting a property. In this case, pass no arguments. It should also be\n * called when manually implementing a property setter. In this case, pass the\n * property `name` and `oldValue` to ensure that any configured property\n * options are honored. Returns the `updateComplete` Promise which is resolved\n * when the update completes.\n *\n * @param name {PropertyKey} (optional) name of requesting property\n * @param oldValue {any} (optional) old value of requesting property\n * @returns {Promise} A Promise that is resolved when the update completes.\n */\n requestUpdate(name?: PropertyKey, oldValue?: unknown) {\n let shouldRequestUpdate = true;\n // if we have a property key, perform property update steps.\n if (name !== undefined && !this._changedProperties.has(name)) {\n const ctor = this.constructor as typeof UpdatingElement;\n const options =\n ctor._classProperties!.get(name) || defaultPropertyDeclaration;\n if (ctor._valueHasChanged(\n this[name as keyof this], oldValue, options.hasChanged)) {\n // track old value when changing.\n this._changedProperties.set(name, oldValue);\n // add to reflecting properties set\n if (options.reflect === true &&\n !(this._updateState & STATE_IS_REFLECTING_TO_PROPERTY)) {\n if (this._reflectingProperties === undefined) {\n this._reflectingProperties = new Map();\n }\n this._reflectingProperties.set(name, options);\n }\n // abort the request if the property should not be considered changed.\n } else {\n shouldRequestUpdate = false;\n }\n }\n if (!this._hasRequestedUpdate && shouldRequestUpdate) {\n this._enqueueUpdate();\n }\n return this.updateComplete;\n }\n\n /**\n * Sets up the element to asynchronously update.\n */\n private async _enqueueUpdate() {\n // Mark state updating...\n this._updateState = this._updateState | STATE_UPDATE_REQUESTED;\n let resolve: (r: boolean) => void;\n const previousUpdatePromise = this._updatePromise;\n this._updatePromise = new Promise((res) => resolve = res);\n // Ensure any previous update has resolved before updating.\n // This `await` also ensures that property changes are batched.\n await previousUpdatePromise;\n // Make sure the element has connected before updating.\n if (!this._hasConnected) {\n await new Promise((res) => this._hasConnectedResolver = res);\n }\n // Allow `performUpdate` to be asynchronous to enable scheduling of updates.\n const result = this.performUpdate();\n // Note, this is to avoid delaying an additional microtask unless we need\n // to.\n if (result != null &&\n typeof (result as PromiseLike).then === 'function') {\n await result;\n }\n resolve!(!this._hasRequestedUpdate);\n }\n\n private get _hasConnected() {\n return (this._updateState & STATE_HAS_CONNECTED);\n }\n\n private get _hasRequestedUpdate() {\n return (this._updateState & STATE_UPDATE_REQUESTED);\n }\n\n protected get hasUpdated() {\n return (this._updateState & STATE_HAS_UPDATED);\n }\n\n /**\n * Performs an element update.\n *\n * You can override this method to change the timing of updates. For instance,\n * to schedule updates to occur just before the next frame:\n *\n * ```\n * protected async performUpdate(): Promise {\n * await new Promise((resolve) => requestAnimationFrame(() => resolve()));\n * super.performUpdate();\n * }\n * ```\n */\n protected performUpdate(): void|Promise {\n // Mixin instance properties once, if they exist.\n if (this._instanceProperties) {\n this._applyInstanceProperties();\n }\n if (this.shouldUpdate(this._changedProperties)) {\n const changedProperties = this._changedProperties;\n this.update(changedProperties);\n this._markUpdated();\n if (!(this._updateState & STATE_HAS_UPDATED)) {\n this._updateState = this._updateState | STATE_HAS_UPDATED;\n this.firstUpdated(changedProperties);\n }\n this.updated(changedProperties);\n } else {\n this._markUpdated();\n }\n }\n\n private _markUpdated() {\n this._changedProperties = new Map();\n this._updateState = this._updateState & ~STATE_UPDATE_REQUESTED;\n }\n\n /**\n * Returns a Promise that resolves when the element has completed updating.\n * The Promise value is a boolean that is `true` if the element completed the\n * update without triggering another update. The Promise result is `false` if\n * a property was set inside `updated()`. This getter can be implemented to\n * await additional state. For example, it is sometimes useful to await a\n * rendered element before fulfilling this Promise. To do this, first await\n * `super.updateComplete` then any subsequent state.\n *\n * @returns {Promise} The Promise returns a boolean that indicates if the\n * update resolved without triggering another update.\n */\n get updateComplete() {\n return this._updatePromise;\n }\n\n /**\n * Controls whether or not `update` should be called when the element requests\n * an update. By default, this method always returns `true`, but this can be\n * customized to control when to update.\n *\n * * @param _changedProperties Map of changed properties with old values\n */\n protected shouldUpdate(_changedProperties: PropertyValues): boolean {\n return true;\n }\n\n /**\n * Updates the element. This method reflects property values to attributes.\n * It can be overridden to render and keep updated element DOM.\n * Setting properties inside this method will *not* trigger\n * another update.\n *\n * * @param _changedProperties Map of changed properties with old values\n */\n protected update(_changedProperties: PropertyValues) {\n if (this._reflectingProperties !== undefined &&\n this._reflectingProperties.size > 0) {\n // Use forEach so this works even if for/of loops are compiled to for\n // loops expecting arrays\n this._reflectingProperties.forEach(\n (v, k) => this._propertyToAttribute(k, this[k as keyof this], v));\n this._reflectingProperties = undefined;\n }\n }\n\n /**\n * Invoked whenever the element is updated. Implement to perform\n * post-updating tasks via DOM APIs, for example, focusing an element.\n *\n * Setting properties inside this method will trigger the element to update\n * again after this update cycle completes.\n *\n * * @param _changedProperties Map of changed properties with old values\n */\n protected updated(_changedProperties: PropertyValues) {\n }\n\n /**\n * Invoked when the element is first updated. Implement to perform one time\n * work on the element after update.\n *\n * Setting properties inside this method will trigger the element to update\n * again after this update cycle completes.\n *\n * * @param _changedProperties Map of changed properties with old values\n */\n protected firstUpdated(_changedProperties: PropertyValues) {\n }\n}\n"]} \ No newline at end of file diff --git a/app/userland/app-stdlib/vendor/lit-element/lit-element.js b/app/userland/app-stdlib/vendor/lit-element/lit-element.js new file mode 100644 index 0000000000..dcd31ac87f --- /dev/null +++ b/app/userland/app-stdlib/vendor/lit-element/lit-element.js @@ -0,0 +1,198 @@ +/** + * @license + * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */ +import { TemplateResult } from './lit-html/lit-html.js'; +import { render } from './lit-html/lib/shady-render.js'; +import { UpdatingElement } from './lib/updating-element.js'; +export * from './lib/updating-element.js'; +export * from './lib/decorators.js'; +export { html, svg, TemplateResult, SVGTemplateResult } from './lit-html/lit-html.js'; +import { supportsAdoptingStyleSheets } from './lib/css-tag.js'; +export * from './lib/css-tag.js'; +// IMPORTANT: do not change the property name or the assignment expression. +// This line will be used in regexes to search for LitElement usage. +// TODO(justinfagnani): inject version number at build time +(window['litElementVersions'] || (window['litElementVersions'] = [])) + .push('2.0.1'); +/** + * Minimal implementation of Array.prototype.flat + * @param arr the array to flatten + * @param result the accumlated result + */ +function arrayFlat(styles, result = []) { + for (let i = 0, length = styles.length; i < length; i++) { + const value = styles[i]; + if (Array.isArray(value)) { + arrayFlat(value, result); + } + else { + result.push(value); + } + } + return result; +} +/** Deeply flattens styles array. Uses native flat if available. */ +const flattenStyles = (styles) => styles.flat ? styles.flat(Infinity) : arrayFlat(styles); +export class LitElement extends UpdatingElement { + /** @nocollapse */ + static finalize() { + super.finalize(); + // Prepare styling that is stamped at first render time. Styling + // is built from user provided `styles` or is inherited from the superclass. + this._styles = + this.hasOwnProperty(JSCompiler_renameProperty('styles', this)) ? + this._getUniqueStyles() : + this._styles || []; + } + /** @nocollapse */ + static _getUniqueStyles() { + // Take care not to call `this.styles` multiple times since this generates + // new CSSResults each time. + // TODO(sorvell): Since we do not cache CSSResults by input, any + // shared styles will generate new stylesheet objects, which is wasteful. + // This should be addressed when a browser ships constructable + // stylesheets. + const userStyles = this.styles; + const styles = []; + if (Array.isArray(userStyles)) { + const flatStyles = flattenStyles(userStyles); + // As a performance optimization to avoid duplicated styling that can + // occur especially when composing via subclassing, de-duplicate styles + // preserving the last item in the list. The last item is kept to + // try to preserve cascade order with the assumption that it's most + // important that last added styles override previous styles. + const styleSet = flatStyles.reduceRight((set, s) => { + set.add(s); + // on IE set.add does not return the set. + return set; + }, new Set()); + // Array.from does not work on Set in IE + styleSet.forEach((v) => styles.unshift(v)); + } + else if (userStyles) { + styles.push(userStyles); + } + return styles; + } + /** + * Performs element initialization. By default this calls `createRenderRoot` + * to create the element `renderRoot` node and captures any pre-set values for + * registered properties. + */ + initialize() { + super.initialize(); + this.renderRoot = this.createRenderRoot(); + // Note, if renderRoot is not a shadowRoot, styles would/could apply to the + // element's getRootNode(). While this could be done, we're choosing not to + // support this now since it would require different logic around de-duping. + if (window.ShadowRoot && this.renderRoot instanceof window.ShadowRoot) { + this.adoptStyles(); + } + } + /** + * Returns the node into which the element should render and by default + * creates and returns an open shadowRoot. Implement to customize where the + * element's DOM is rendered. For example, to render into the element's + * childNodes, return `this`. + * @returns {Element|DocumentFragment} Returns a node into which to render. + */ + createRenderRoot() { + return this.attachShadow({ mode: 'open' }); + } + /** + * Applies styling to the element shadowRoot using the `static get styles` + * property. Styling will apply using `shadowRoot.adoptedStyleSheets` where + * available and will fallback otherwise. When Shadow DOM is polyfilled, + * ShadyCSS scopes styles and adds them to the document. When Shadow DOM + * is available but `adoptedStyleSheets` is not, styles are appended to the + * end of the `shadowRoot` to [mimic spec + * behavior](https://wicg.github.io/construct-stylesheets/#using-constructed-stylesheets). + */ + adoptStyles() { + const styles = this.constructor._styles; + if (styles.length === 0) { + return; + } + // There are three separate cases here based on Shadow DOM support. + // (1) shadowRoot polyfilled: use ShadyCSS + // (2) shadowRoot.adoptedStyleSheets available: use it. + // (3) shadowRoot.adoptedStyleSheets polyfilled: append styles after + // rendering + if (window.ShadyCSS !== undefined && !window.ShadyCSS.nativeShadow) { + window.ShadyCSS.ScopingShim.prepareAdoptedCssText(styles.map((s) => s.cssText), this.localName); + } + else if (supportsAdoptingStyleSheets) { + this.renderRoot.adoptedStyleSheets = + styles.map((s) => s.styleSheet); + } + else { + // This must be done after rendering so the actual style insertion is done + // in `update`. + this._needsShimAdoptedStyleSheets = true; + } + } + connectedCallback() { + super.connectedCallback(); + // Note, first update/render handles styleElement so we only call this if + // connected after first update. + if (this.hasUpdated && window.ShadyCSS !== undefined) { + window.ShadyCSS.styleElement(this); + } + } + /** + * Updates the element. This method reflects property values to attributes + * and calls `render` to render DOM via lit-html. Setting properties inside + * this method will *not* trigger another update. + * * @param _changedProperties Map of changed properties with old values + */ + update(changedProperties) { + super.update(changedProperties); + const templateResult = this.render(); + if (templateResult instanceof TemplateResult) { + this.constructor + .render(templateResult, this.renderRoot, { scopeName: this.localName, eventContext: this }); + } + // When native Shadow DOM is used but adoptedStyles are not supported, + // insert styling after rendering to ensure adoptedStyles have highest + // priority. + if (this._needsShimAdoptedStyleSheets) { + this._needsShimAdoptedStyleSheets = false; + this.constructor._styles.forEach((s) => { + const style = document.createElement('style'); + style.textContent = s.cssText; + this.renderRoot.appendChild(style); + }); + } + } + /** + * Invoked on each update to perform rendering tasks. This method must return + * a lit-html TemplateResult. Setting properties inside this method will *not* + * trigger the element to update. + */ + render() { + } +} +/** + * Ensure this class is marked as `finalized` as an optimization ensuring + * it will not needlessly try to `finalize`. + */ +LitElement.finalized = true; +/** + * Render method used to render the lit-html TemplateResult to the element's + * DOM. + * @param {TemplateResult} Template to render. + * @param {Element|DocumentFragment} Node into which to render. + * @param {String} Element name. + * @nocollapse + */ +LitElement.render = render; diff --git a/app/userland/app-stdlib/vendor/lit-element/lit-element.js.map b/app/userland/app-stdlib/vendor/lit-element/lit-element.js.map new file mode 100644 index 0000000000..e24fb86641 --- /dev/null +++ b/app/userland/app-stdlib/vendor/lit-element/lit-element.js.map @@ -0,0 +1 @@ +{"version":3,"file":"lit-element.js","sourceRoot":"","sources":["src/lit-element.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAC,cAAc,EAAC,MAAM,UAAU,CAAC;AACxC,OAAO,EAAC,MAAM,EAAC,MAAM,2BAA2B,CAAC;AAEjD,OAAO,EAAiB,eAAe,EAAC,MAAM,2BAA2B,CAAC;AAE1E,cAAc,2BAA2B,CAAC;AAC1C,cAAc,qBAAqB,CAAC;AACpC,OAAO,EAAC,IAAI,EAAE,GAAG,EAAE,cAAc,EAAE,iBAAiB,EAAC,MAAM,mBAAmB,CAAC;AAC/E,OAAO,EAAC,2BAA2B,EAAY,MAAM,kBAAkB,CAAC;AACxE,cAAc,kBAAkB,CAAC;AAQjC,2EAA2E;AAC3E,oEAAoE;AACpE,2DAA2D;AAC3D,CAAC,MAAM,CAAC,oBAAoB,CAAC,IAAI,CAAC,MAAM,CAAC,oBAAoB,CAAC,GAAG,EAAE,CAAC,CAAC;KAChE,IAAI,CAAC,OAAO,CAAC,CAAC;AAInB;;;;GAIG;AACH,SAAS,SAAS,CACd,MAAsB,EAAE,SAAsB,EAAE;IAClD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE,EAAE;QACvD,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QACxB,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;YACxB,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;SAC1B;aAAM;YACL,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;SACpB;KACF;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,mEAAmE;AACnE,MAAM,aAAa,GAAG,CAAC,MAAsB,EAAe,EAAE,CAC1D,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;AAE5D,MAAM,OAAO,UAAW,SAAQ,eAAe;IAyB7C,kBAAkB;IACR,MAAM,CAAC,QAAQ;QACvB,KAAK,CAAC,QAAQ,EAAE,CAAC;QACjB,gEAAgE;QAChE,4EAA4E;QAC5E,IAAI,CAAC,OAAO;YACR,IAAI,CAAC,cAAc,CAAC,yBAAyB,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;gBAChE,IAAI,CAAC,gBAAgB,EAAE,CAAC,CAAC;gBACzB,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC;IACzB,CAAC;IAED,kBAAkB;IACV,MAAM,CAAC,gBAAgB;QAC7B,0EAA0E;QAC1E,4BAA4B;QAC5B,gEAAgE;QAChE,yEAAyE;QACzE,8DAA8D;QAC9D,eAAe;QACf,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC;QAC/B,MAAM,MAAM,GAAgB,EAAE,CAAC;QAC/B,IAAI,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE;YAC7B,MAAM,UAAU,GAAG,aAAa,CAAC,UAAU,CAAC,CAAC;YAC7C,qEAAqE;YACrE,uEAAuE;YACvE,iEAAiE;YACjE,mEAAmE;YACnE,6DAA6D;YAC7D,MAAM,QAAQ,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE;gBACjD,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;gBACX,yCAAyC;gBACzC,OAAO,GAAG,CAAC;YACb,CAAC,EAAE,IAAI,GAAG,EAAa,CAAC,CAAC;YACzB,wCAAwC;YACxC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;SAC7C;aAAM,IAAI,UAAU,EAAE;YACrB,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;SACzB;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAUD;;;;OAIG;IACO,UAAU;QAClB,KAAK,CAAC,UAAU,EAAE,CAAC;QACnB,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC1C,2EAA2E;QAC3E,2EAA2E;QAC3E,4EAA4E;QAC5E,IAAI,MAAM,CAAC,UAAU,IAAI,IAAI,CAAC,UAAU,YAAY,MAAM,CAAC,UAAU,EAAE;YACrE,IAAI,CAAC,WAAW,EAAE,CAAC;SACpB;IACH,CAAC;IAED;;;;;;OAMG;IACO,gBAAgB;QACxB,OAAO,IAAI,CAAC,YAAY,CAAC,EAAC,IAAI,EAAE,MAAM,EAAC,CAAC,CAAC;IAC3C,CAAC;IAED;;;;;;;;OAQG;IACO,WAAW;QACnB,MAAM,MAAM,GAAI,IAAI,CAAC,WAAiC,CAAC,OAAQ,CAAC;QAChE,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE;YACvB,OAAO;SACR;QACD,mEAAmE;QACnE,0CAA0C;QAC1C,uDAAuD;QACvD,oEAAoE;QACpE,YAAY;QACZ,IAAI,MAAM,CAAC,QAAQ,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,EAAE;YAClE,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,qBAAqB,CAC7C,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;SACnD;aAAM,IAAI,2BAA2B,EAAE;YACrC,IAAI,CAAC,UAAyB,CAAC,kBAAkB;gBAC9C,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAW,CAAC,CAAC;SACtC;aAAM;YACL,0EAA0E;YAC1E,eAAe;YACf,IAAI,CAAC,4BAA4B,GAAG,IAAI,CAAC;SAC1C;IACH,CAAC;IAED,iBAAiB;QACf,KAAK,CAAC,iBAAiB,EAAE,CAAC;QAC1B,yEAAyE;QACzE,gCAAgC;QAChC,IAAI,IAAI,CAAC,UAAU,IAAI,MAAM,CAAC,QAAQ,KAAK,SAAS,EAAE;YACpD,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;SACpC;IACH,CAAC;IAED;;;;;OAKG;IACO,MAAM,CAAC,iBAAiC;QAChD,KAAK,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC;QAChC,MAAM,cAAc,GAAG,IAAI,CAAC,MAAM,EAAa,CAAC;QAChD,IAAI,cAAc,YAAY,cAAc,EAAE;YAC3C,IAAI,CAAC,WAAiC;iBAClC,MAAM,CACH,cAAc,EACd,IAAI,CAAC,UAAW,EAChB,EAAC,SAAS,EAAE,IAAI,CAAC,SAAU,EAAE,YAAY,EAAE,IAAI,EAAC,CAAC,CAAC;SAC3D;QACD,sEAAsE;QACtE,sEAAsE;QACtE,YAAY;QACZ,IAAI,IAAI,CAAC,4BAA4B,EAAE;YACrC,IAAI,CAAC,4BAA4B,GAAG,KAAK,CAAC;YACzC,IAAI,CAAC,WAAiC,CAAC,OAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE;gBAC7D,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;gBAC9C,KAAK,CAAC,WAAW,GAAG,CAAC,CAAC,OAAO,CAAC;gBAC9B,IAAI,CAAC,UAAW,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;YACtC,CAAC,CAAC,CAAC;SACJ;IACH,CAAC;IAED;;;;OAIG;IACO,MAAM;IAChB,CAAC;;AAhLD;;;GAGG;AACc,oBAAS,GAAG,IAAI,CAAC;AAElC;;;;;;;GAOG;AACI,iBAAM,GAAG,MAAM,CAAC","sourcesContent":["/**\n * @license\n * Copyright (c) 2017 The Polymer Project Authors. All rights reserved.\n * This code may only be used under the BSD style license found at\n * http://polymer.github.io/LICENSE.txt\n * The complete set of authors may be found at\n * http://polymer.github.io/AUTHORS.txt\n * The complete set of contributors may be found at\n * http://polymer.github.io/CONTRIBUTORS.txt\n * Code distributed by Google as part of the polymer project is also\n * subject to an additional IP rights grant found at\n * http://polymer.github.io/PATENTS.txt\n */\nimport {TemplateResult} from 'lit-html';\nimport {render} from 'lit-html/lib/shady-render';\n\nimport {PropertyValues, UpdatingElement} from './lib/updating-element.js';\n\nexport * from './lib/updating-element.js';\nexport * from './lib/decorators.js';\nexport {html, svg, TemplateResult, SVGTemplateResult} from 'lit-html/lit-html';\nimport {supportsAdoptingStyleSheets, CSSResult} from './lib/css-tag.js';\nexport * from './lib/css-tag.js';\n\ndeclare global {\n interface Window {\n litElementVersions: string[];\n }\n}\n\n// IMPORTANT: do not change the property name or the assignment expression.\n// This line will be used in regexes to search for LitElement usage.\n// TODO(justinfagnani): inject version number at build time\n(window['litElementVersions'] || (window['litElementVersions'] = []))\n .push('2.0.1');\n\nexport interface CSSResultArray extends Array {}\n\n/**\n * Minimal implementation of Array.prototype.flat\n * @param arr the array to flatten\n * @param result the accumlated result\n */\nfunction arrayFlat(\n styles: CSSResultArray, result: CSSResult[] = []): CSSResult[] {\n for (let i = 0, length = styles.length; i < length; i++) {\n const value = styles[i];\n if (Array.isArray(value)) {\n arrayFlat(value, result);\n } else {\n result.push(value);\n }\n }\n return result;\n}\n\n/** Deeply flattens styles array. Uses native flat if available. */\nconst flattenStyles = (styles: CSSResultArray): CSSResult[] =>\n styles.flat ? styles.flat(Infinity) : arrayFlat(styles);\n\nexport class LitElement extends UpdatingElement {\n /**\n * Ensure this class is marked as `finalized` as an optimization ensuring\n * it will not needlessly try to `finalize`.\n */\n protected static finalized = true;\n\n /**\n * Render method used to render the lit-html TemplateResult to the element's\n * DOM.\n * @param {TemplateResult} Template to render.\n * @param {Element|DocumentFragment} Node into which to render.\n * @param {String} Element name.\n * @nocollapse\n */\n static render = render;\n\n /**\n * Array of styles to apply to the element. The styles should be defined\n * using the `css` tag function.\n */\n static styles?: CSSResult|CSSResultArray;\n\n private static _styles: CSSResult[]|undefined;\n\n /** @nocollapse */\n protected static finalize() {\n super.finalize();\n // Prepare styling that is stamped at first render time. Styling\n // is built from user provided `styles` or is inherited from the superclass.\n this._styles =\n this.hasOwnProperty(JSCompiler_renameProperty('styles', this)) ?\n this._getUniqueStyles() :\n this._styles || [];\n }\n\n /** @nocollapse */\n private static _getUniqueStyles(): CSSResult[] {\n // Take care not to call `this.styles` multiple times since this generates\n // new CSSResults each time.\n // TODO(sorvell): Since we do not cache CSSResults by input, any\n // shared styles will generate new stylesheet objects, which is wasteful.\n // This should be addressed when a browser ships constructable\n // stylesheets.\n const userStyles = this.styles;\n const styles: CSSResult[] = [];\n if (Array.isArray(userStyles)) {\n const flatStyles = flattenStyles(userStyles);\n // As a performance optimization to avoid duplicated styling that can\n // occur especially when composing via subclassing, de-duplicate styles\n // preserving the last item in the list. The last item is kept to\n // try to preserve cascade order with the assumption that it's most\n // important that last added styles override previous styles.\n const styleSet = flatStyles.reduceRight((set, s) => {\n set.add(s);\n // on IE set.add does not return the set.\n return set;\n }, new Set());\n // Array.from does not work on Set in IE\n styleSet.forEach((v) => styles!.unshift(v));\n } else if (userStyles) {\n styles.push(userStyles);\n }\n return styles;\n }\n\n private _needsShimAdoptedStyleSheets?: boolean;\n\n /**\n * Node or ShadowRoot into which element DOM should be rendered. Defaults\n * to an open shadowRoot.\n */\n protected renderRoot?: Element|DocumentFragment;\n\n /**\n * Performs element initialization. By default this calls `createRenderRoot`\n * to create the element `renderRoot` node and captures any pre-set values for\n * registered properties.\n */\n protected initialize() {\n super.initialize();\n this.renderRoot = this.createRenderRoot();\n // Note, if renderRoot is not a shadowRoot, styles would/could apply to the\n // element's getRootNode(). While this could be done, we're choosing not to\n // support this now since it would require different logic around de-duping.\n if (window.ShadowRoot && this.renderRoot instanceof window.ShadowRoot) {\n this.adoptStyles();\n }\n }\n\n /**\n * Returns the node into which the element should render and by default\n * creates and returns an open shadowRoot. Implement to customize where the\n * element's DOM is rendered. For example, to render into the element's\n * childNodes, return `this`.\n * @returns {Element|DocumentFragment} Returns a node into which to render.\n */\n protected createRenderRoot(): Element|ShadowRoot {\n return this.attachShadow({mode: 'open'});\n }\n\n /**\n * Applies styling to the element shadowRoot using the `static get styles`\n * property. Styling will apply using `shadowRoot.adoptedStyleSheets` where\n * available and will fallback otherwise. When Shadow DOM is polyfilled,\n * ShadyCSS scopes styles and adds them to the document. When Shadow DOM\n * is available but `adoptedStyleSheets` is not, styles are appended to the\n * end of the `shadowRoot` to [mimic spec\n * behavior](https://wicg.github.io/construct-stylesheets/#using-constructed-stylesheets).\n */\n protected adoptStyles() {\n const styles = (this.constructor as typeof LitElement)._styles!;\n if (styles.length === 0) {\n return;\n }\n // There are three separate cases here based on Shadow DOM support.\n // (1) shadowRoot polyfilled: use ShadyCSS\n // (2) shadowRoot.adoptedStyleSheets available: use it.\n // (3) shadowRoot.adoptedStyleSheets polyfilled: append styles after\n // rendering\n if (window.ShadyCSS !== undefined && !window.ShadyCSS.nativeShadow) {\n window.ShadyCSS.ScopingShim.prepareAdoptedCssText(\n styles.map((s) => s.cssText), this.localName);\n } else if (supportsAdoptingStyleSheets) {\n (this.renderRoot as ShadowRoot).adoptedStyleSheets =\n styles.map((s) => s.styleSheet!);\n } else {\n // This must be done after rendering so the actual style insertion is done\n // in `update`.\n this._needsShimAdoptedStyleSheets = true;\n }\n }\n\n connectedCallback() {\n super.connectedCallback();\n // Note, first update/render handles styleElement so we only call this if\n // connected after first update.\n if (this.hasUpdated && window.ShadyCSS !== undefined) {\n window.ShadyCSS.styleElement(this);\n }\n }\n\n /**\n * Updates the element. This method reflects property values to attributes\n * and calls `render` to render DOM via lit-html. Setting properties inside\n * this method will *not* trigger another update.\n * * @param _changedProperties Map of changed properties with old values\n */\n protected update(changedProperties: PropertyValues) {\n super.update(changedProperties);\n const templateResult = this.render() as unknown;\n if (templateResult instanceof TemplateResult) {\n (this.constructor as typeof LitElement)\n .render(\n templateResult,\n this.renderRoot!,\n {scopeName: this.localName!, eventContext: this});\n }\n // When native Shadow DOM is used but adoptedStyles are not supported,\n // insert styling after rendering to ensure adoptedStyles have highest\n // priority.\n if (this._needsShimAdoptedStyleSheets) {\n this._needsShimAdoptedStyleSheets = false;\n (this.constructor as typeof LitElement)._styles!.forEach((s) => {\n const style = document.createElement('style');\n style.textContent = s.cssText;\n this.renderRoot!.appendChild(style);\n });\n }\n }\n\n /**\n * Invoked on each update to perform rendering tasks. This method must return\n * a lit-html TemplateResult. Setting properties inside this method will *not*\n * trigger the element to update.\n */\n protected render(): TemplateResult|void {\n }\n}\n"]} \ No newline at end of file diff --git a/app/userland/app-stdlib/vendor/lit-element/lit-html/CHANGELOG.md b/app/userland/app-stdlib/vendor/lit-element/lit-html/CHANGELOG.md new file mode 100755 index 0000000000..6499c444c6 --- /dev/null +++ b/app/userland/app-stdlib/vendor/lit-element/lit-html/CHANGELOG.md @@ -0,0 +1,194 @@ +# Change Log + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/) +and this project adheres to [Semantic Versioning](http://semver.org/). + + + + + + + + + +## [1.0.0] - 2019-02-05 +### Changed +* Tons of docs updates ([#746](https://github.com/Polymer/lit-html/pull/746)), ([#675](https://github.com/Polymer/lit-html/pull/675)), ([#724](https://github.com/Polymer/lit-html/pull/724)), ([#753](https://github.com/Polymer/lit-html/pull/753)), ([#764](https://github.com/Polymer/lit-html/pull/764)), ([#763](https://github.com/Polymer/lit-html/pull/763)), ([#765](https://github.com/Polymer/lit-html/pull/765)), ([#767](https://github.com/Polymer/lit-html/pull/767)), ([#768](https://github.com/Polymer/lit-html/pull/768)), ([#734](https://github.com/Polymer/lit-html/pull/734)), ([#771](https://github.com/Polymer/lit-html/pull/771)), ([#766](https://github.com/Polymer/lit-html/pull/766)), ([#773](https://github.com/Polymer/lit-html/pull/773)), ([#770](https://github.com/Polymer/lit-html/pull/770)), ([#769](https://github.com/Polymer/lit-html/pull/769)), ([#777](https://github.com/Polymer/lit-html/pull/777)), ([#776](https://github.com/Polymer/lit-html/pull/776)), ([#754](https://github.com/Polymer/lit-html/pull/754)), ([#779](https://github.com/Polymer/lit-html/pull/779)) +### Added +* Global version of `lit-html` on window ([#790](https://github.com/Polymer/lit-html/pull/790)). +### Fixed +* Removed use of `any` outside of test code ([#741](https://github.com/Polymer/lit-html/pull/741)). + +## [1.0.0-rc.2] - 2019-01-09 +### Changed +* Performance improvements to template processing. ([#690](https://github.com/Polymer/lit-html/pull/690)) +### Added +* Added the `nothing` sentinel value which can be used to clear a part. ([#673](https://github.com/Polymer/lit-html/pull/673)) +### Fixed +* Fixed #702: a bug with the `unsafeHTML` directive when changing between unsafe and other values. ([#703](https://github.com/Polymer/lit-html/pull/703)) +* Fixed #708: a bug with the `until` directive where placeholders could overwrite resolved Promises. ([#721](https://github.com/Polymer/lit-html/pull/721)) + + +## [1.0.0-rc.1] - 2018-12-13 +### Fixed +* Documentation updates. +* Fixed typing for template_polyfill `createElement` call. + +## [0.14.0] - 2018-11-30 +### Changed +* `until()` can now take any number of sync or async arguments. ([#555](https://github.com/Polymer/lit-html/pull/555)) +* [Breaking] `guard()` supports multiple dependencies. If the first argument to `guard()` is an array, the array items are checked for equality to previous values. ([#666](https://github.com/Polymer/lit-html/pull/666)) +* [Breaking] Renamed `classMap.js` and `styleMap.js` files to kebab-case. ([#644](https://github.com/Polymer/lit-html/pull/644)) +### Added +* Added `cache()` directive. ([#646](https://github.com/Polymer/lit-html/pull/646)) +* Removed Promise as a supposed node-position value type. ([#555](https://github.com/Polymer/lit-html/pull/555)) +* Added a minimal `