From 40798cba4114369ad0f352a2fb8bc33af0c203c3 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Fri, 14 Feb 2020 20:19:35 +0100 Subject: Fix alt+enter changing visibility setting without sending toot when toot is empty --- .../glitch/features/compose/components/compose_form.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/flavours/glitch/features/compose/components/compose_form.js b/app/javascript/flavours/glitch/features/compose/components/compose_form.js index 6e07998ec..daacbb73f 100644 --- a/app/javascript/flavours/glitch/features/compose/components/compose_form.js +++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.js @@ -93,7 +93,7 @@ class ComposeForm extends ImmutablePureComponent { } } - handleSubmit = () => { + handleSubmit = (overriddenVisibility = null) => { const { textarea: { value }, uploadForm } = this; const { onChange, @@ -106,6 +106,7 @@ class ComposeForm extends ImmutablePureComponent { text, mediaDescriptionConfirmation, onMediaDescriptionConfirm, + onChangeVisibility, } = this.props; // If something changes inside the textarea, then we update the @@ -124,6 +125,9 @@ class ComposeForm extends ImmutablePureComponent { const firstWithoutDescription = media.find(item => !item.get('description')); onMediaDescriptionConfirm(this.context.router ? this.context.router.history : null, firstWithoutDescription.get('id')); } else if (onSubmit) { + if (onChangeVisibility && overriddenVisibility) { + onChangeVisibility(overriddenVisibility); + } onSubmit(this.context.router ? this.context.router.history : null); } } @@ -152,13 +156,9 @@ class ComposeForm extends ImmutablePureComponent { // Handles the secondary submit button. handleSecondarySubmit = () => { const { - onChangeVisibility, sideArm, } = this.props; - if (sideArm !== 'none' && onChangeVisibility) { - onChangeVisibility(sideArm); - } - this.handleSubmit(); + this.handleSubmit(sideArm === 'none' ? null : sideArm); } // Selects a suggestion from the autofill. -- cgit From 955f838f9df9a6af4ba2ab51716def6093698c28 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Sat, 15 Feb 2020 23:51:24 +0100 Subject: Fix clicking on the “TOOT” button send bogus visibility parameter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../flavours/glitch/features/compose/components/publisher.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/flavours/glitch/features/compose/components/publisher.js b/app/javascript/flavours/glitch/features/compose/components/publisher.js index b8d9d98bf..97890f40d 100644 --- a/app/javascript/flavours/glitch/features/compose/components/publisher.js +++ b/app/javascript/flavours/glitch/features/compose/components/publisher.js @@ -38,8 +38,12 @@ class Publisher extends ImmutablePureComponent { sideArm: PropTypes.oneOf(['none', 'direct', 'private', 'unlisted', 'public']), }; + handleSubmit = () => { + this.props.onSubmit(); + }; + render () { - const { countText, disabled, intl, onSecondarySubmit, onSubmit, privacy, sideArm } = this.props; + const { countText, disabled, intl, onSecondarySubmit, privacy, sideArm } = this.props; const diff = maxChars - length(countText || ''); const computedClass = classNames('composer--publisher', { @@ -105,7 +109,7 @@ class Publisher extends ImmutablePureComponent { } }()} title={`${intl.formatMessage(messages.publish)}: ${intl.formatMessage({ id: `privacy.${privacy}.short` })}`} - onClick={onSubmit} + onClick={this.handleSubmit} disabled={disabled || diff < 0} /> -- cgit From 1314bba68a5f2d271312bad08f108e1ff56c2c00 Mon Sep 17 00:00:00 2001 From: ThibG Date: Tue, 18 Feb 2020 17:22:44 +0100 Subject: Fix old browsers crashing because of missing `finally` polyfill in web UI (#13115) Fix #13015 --- app/javascript/mastodon/base_polyfills.js | 3 +++ app/javascript/mastodon/load_polyfills.js | 3 ++- package.json | 3 ++- yarn.lock | 26 ++++++++++++++++++++++++++ 4 files changed, 33 insertions(+), 2 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/base_polyfills.js b/app/javascript/mastodon/base_polyfills.js index 997813a04..12096d902 100644 --- a/app/javascript/mastodon/base_polyfills.js +++ b/app/javascript/mastodon/base_polyfills.js @@ -6,6 +6,7 @@ import assign from 'object-assign'; import values from 'object.values'; import isNaN from 'is-nan'; import { decode as decodeBase64 } from './utils/base64'; +import promiseFinally from 'promise.prototype.finally'; if (!Array.prototype.includes) { includes.shim(); @@ -23,6 +24,8 @@ if (!Number.isNaN) { Number.isNaN = isNaN; } +promiseFinally.shim(); + if (!HTMLCanvasElement.prototype.toBlob) { const BASE64_MARKER = ';base64,'; diff --git a/app/javascript/mastodon/load_polyfills.js b/app/javascript/mastodon/load_polyfills.js index 8cb81c1a6..73eedc9dc 100644 --- a/app/javascript/mastodon/load_polyfills.js +++ b/app/javascript/mastodon/load_polyfills.js @@ -18,7 +18,8 @@ function loadPolyfills() { Number.isNaN && Object.assign && Object.values && - window.Symbol + window.Symbol && + Promise.prototype.finally ); // Latest version of Firefox and Safari do not have IntersectionObserver. diff --git a/package.json b/package.json index 8483c8a89..d856051c1 100644 --- a/package.json +++ b/package.json @@ -67,8 +67,8 @@ "@babel/preset-env": "^7.8.3", "@babel/preset-react": "^7.8.3", "@babel/runtime": "^7.8.3", - "@gamestdio/websocket": "^0.3.2", "@clusterws/cws": "^0.17.3", + "@gamestdio/websocket": "^0.3.2", "array-includes": "^3.1.1", "arrow-key-navigation": "^1.1.0", "autoprefixer": "^9.7.4", @@ -120,6 +120,7 @@ "pg": "^6.4.0", "postcss-loader": "^3.0.0", "postcss-object-fit-images": "^1.1.2", + "promise.prototype.finally": "^3.1.2", "prop-types": "^15.5.10", "punycode": "^2.1.0", "rails-ujs": "^5.2.4", diff --git a/yarn.lock b/yarn.lock index 2c5d7440c..98c146ab8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3711,6 +3711,23 @@ es-abstract@^1.13.0, es-abstract@^1.15.0, es-abstract@^1.17.0, es-abstract@^1.17 string.prototype.trimleft "^2.1.1" string.prototype.trimright "^2.1.1" +es-abstract@^1.17.0-next.0: + version "1.17.4" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.4.tgz#e3aedf19706b20e7c2594c35fc0d57605a79e184" + integrity sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ== + dependencies: + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.1.5" + is-regex "^1.0.5" + object-inspect "^1.7.0" + object-keys "^1.1.1" + object.assign "^4.1.0" + string.prototype.trimleft "^2.1.1" + string.prototype.trimright "^2.1.1" + es-to-primitive@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" @@ -8347,6 +8364,15 @@ promise-inflight@^1.0.1: resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= +promise.prototype.finally@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/promise.prototype.finally/-/promise.prototype.finally-3.1.2.tgz#b8af89160c9c673cefe3b4c4435b53cfd0287067" + integrity sha512-A2HuJWl2opDH0EafgdjwEw7HysI8ff/n4lW4QEVBCUXFk9QeGecBWv0Deph0UmLe3tTNYegz8MOjsVuE6SMoJA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.0" + function-bind "^1.1.1" + prompts@^2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.0.3.tgz#c5ccb324010b2e8f74752aadceeb57134c1d2522" -- cgit From ff3a11d01d262c0a37cc1e33db2013b19729fb9d Mon Sep 17 00:00:00 2001 From: ThibG Date: Wed, 19 Feb 2020 22:36:52 +0100 Subject: Add source-mapped stacktrace to error message in web UI (#13082) * Add source-mapped stack trace to copyable text in error boundary * Add the error message to the copied report, not only the stack trace --- .../mastodon/components/error_boundary.js | 25 +++++++++++-- package.json | 1 + yarn.lock | 41 ++++++++++++++++++++++ 3 files changed, 64 insertions(+), 3 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/components/error_boundary.js b/app/javascript/mastodon/components/error_boundary.js index 4e1c882e2..ca3012276 100644 --- a/app/javascript/mastodon/components/error_boundary.js +++ b/app/javascript/mastodon/components/error_boundary.js @@ -2,6 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; import { version, source_url } from 'mastodon/initial_state'; +import StackTrace from 'stacktrace-js'; export default class ErrorBoundary extends React.PureComponent { @@ -11,24 +12,42 @@ export default class ErrorBoundary extends React.PureComponent { state = { hasError: false, + errorMessage: undefined, stackTrace: undefined, + mappedStackTrace: undefined, componentStack: undefined, }; componentDidCatch (error, info) { this.setState({ hasError: true, + errorMessage: error.toString(), stackTrace: error.stack, componentStack: info && info.componentStack, - copied: false, + mappedStackTrace: undefined, + }); + + StackTrace.fromError(error).then((stackframes) => { + this.setState({ + mappedStackTrace: stackframes.map((sf) => sf.toString()).join('\n'), + }); + }).catch(() => { + this.setState({ + mappedStackTrace: undefined, + }); }); } handleCopyStackTrace = () => { - const { stackTrace } = this.state; + const { errorMessage, stackTrace, mappedStackTrace } = this.state; const textarea = document.createElement('textarea'); - textarea.textContent = stackTrace; + let contents = [errorMessage, stackTrace]; + if (mappedStackTrace) { + contents.push(mappedStackTrace); + } + + textarea.textContent = contents.join('\n\n\n'); textarea.style.position = 'fixed'; document.body.appendChild(textarea); diff --git a/package.json b/package.json index d856051c1..04c05961f 100644 --- a/package.json +++ b/package.json @@ -153,6 +153,7 @@ "rimraf": "^3.0.2", "sass": "^1.25.0", "sass-loader": "^8.0.2", + "stacktrace-js": "^2.0.2", "stringz": "^2.0.0", "substring-trie": "^1.0.2", "terser-webpack-plugin": "^2.3.5", diff --git a/yarn.lock b/yarn.lock index 98c146ab8..92495c5ed 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3694,6 +3694,13 @@ error-ex@^1.2.0, error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" +error-stack-parser@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-2.0.6.tgz#5a99a707bd7a4c58a797902d48d82803ede6aad8" + integrity sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ== + dependencies: + stackframe "^1.1.1" + es-abstract@^1.13.0, es-abstract@^1.15.0, es-abstract@^1.17.0, es-abstract@^1.17.0-next.1, es-abstract@^1.5.1: version "1.17.0" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.0.tgz#f42a517d0036a5591dbb2c463591dc8bb50309b1" @@ -9723,6 +9730,11 @@ source-list-map@^2.0.0: resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== +source-map@0.5.6: + version "0.5.6" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" + integrity sha1-dc449SvwczxafwwRjYEzSiu19BI= + source-map-resolve@^0.5.0: version "0.5.2" resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259" @@ -9868,11 +9880,40 @@ stable@~0.1.6: resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== +stack-generator@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/stack-generator/-/stack-generator-2.0.5.tgz#fb00e5b4ee97de603e0773ea78ce944d81596c36" + integrity sha512-/t1ebrbHkrLrDuNMdeAcsvynWgoH/i4o8EGGfX7dEYDoTXOYVAkEpFdtshlvabzc6JlJ8Kf9YdFEoz7JkzGN9Q== + dependencies: + stackframe "^1.1.1" + stack-utils@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.2.tgz#33eba3897788558bebfc2db059dc158ec36cebb8" integrity sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA== +stackframe@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.1.1.tgz#ffef0a3318b1b60c3b58564989aca5660729ec71" + integrity sha512-0PlYhdKh6AfFxRyK/v+6/k+/mMfyiEBbTM5L94D0ZytQnJ166wuwoTYLHFWGbs2dpA8Rgq763KGWmN1EQEYHRQ== + +stacktrace-gps@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/stacktrace-gps/-/stacktrace-gps-3.0.4.tgz#7688dc2fc09ffb3a13165ebe0dbcaf41bcf0c69a" + integrity sha512-qIr8x41yZVSldqdqe6jciXEaSCKw1U8XTXpjDuy0ki/apyTn/r3w9hDAAQOhZdxvsC93H+WwwEu5cq5VemzYeg== + dependencies: + source-map "0.5.6" + stackframe "^1.1.1" + +stacktrace-js@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/stacktrace-js/-/stacktrace-js-2.0.2.tgz#4ca93ea9f494752d55709a081d400fdaebee897b" + integrity sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg== + dependencies: + error-stack-parser "^2.0.6" + stack-generator "^2.0.5" + stacktrace-gps "^3.0.4" + static-extend@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" -- cgit From c9166a594318557ab682ba285dc952e9a7da503f Mon Sep 17 00:00:00 2001 From: ThibG Date: Tue, 18 Feb 2020 17:22:44 +0100 Subject: [Glitch] Fix old browsers crashing because of missing `finally` polyfill in web UI Port 1314bba68a5f2d271312bad08f108e1ff56c2c00 to glitch-soc Signed-off-by: Thibaut Girka --- app/javascript/flavours/glitch/util/base_polyfills.js | 3 +++ app/javascript/flavours/glitch/util/load_polyfills.js | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) (limited to 'app/javascript') diff --git a/app/javascript/flavours/glitch/util/base_polyfills.js b/app/javascript/flavours/glitch/util/base_polyfills.js index ad023eb73..4b8123dba 100644 --- a/app/javascript/flavours/glitch/util/base_polyfills.js +++ b/app/javascript/flavours/glitch/util/base_polyfills.js @@ -6,6 +6,7 @@ import assign from 'object-assign'; import values from 'object.values'; import isNaN from 'is-nan'; import { decode as decodeBase64 } from './base64'; +import promiseFinally from 'promise.prototype.finally'; if (!Array.prototype.includes) { includes.shim(); @@ -23,6 +24,8 @@ if (!Number.isNaN) { Number.isNaN = isNaN; } +promiseFinally.shim(); + if (!HTMLCanvasElement.prototype.toBlob) { const BASE64_MARKER = ';base64,'; diff --git a/app/javascript/flavours/glitch/util/load_polyfills.js b/app/javascript/flavours/glitch/util/load_polyfills.js index 8cb81c1a6..73eedc9dc 100644 --- a/app/javascript/flavours/glitch/util/load_polyfills.js +++ b/app/javascript/flavours/glitch/util/load_polyfills.js @@ -18,7 +18,8 @@ function loadPolyfills() { Number.isNaN && Object.assign && Object.values && - window.Symbol + window.Symbol && + Promise.prototype.finally ); // Latest version of Firefox and Safari do not have IntersectionObserver. -- cgit From bc4de2f66117b5eac8ebe4a389ad9915c471b0c5 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Wed, 19 Feb 2020 23:16:29 +0100 Subject: [Glitch] Add source-mapped stacktrace to error message in web UI Port ff3a11d01d262c0a37cc1e33db2013b19729fb9d to glitch-soc --- .../flavours/glitch/components/error_boundary.js | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/flavours/glitch/components/error_boundary.js b/app/javascript/flavours/glitch/components/error_boundary.js index 62950a7d3..8998802b1 100644 --- a/app/javascript/flavours/glitch/components/error_boundary.js +++ b/app/javascript/flavours/glitch/components/error_boundary.js @@ -2,6 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; import { preferencesLink } from 'flavours/glitch/util/backend_links'; +import StackTrace from 'stacktrace-js'; export default class ErrorBoundary extends React.PureComponent { @@ -11,15 +12,29 @@ export default class ErrorBoundary extends React.PureComponent { state = { hasError: false, + errorMessage: undefined, stackTrace: undefined, + mappedStackTrace: undefined, componentStack: undefined, } componentDidCatch(error, info) { this.setState({ hasError: true, + errorMessage: error.toString(), stackTrace: error.stack, componentStack: info && info.componentStack, + mappedStackTrace: undefined, + }); + + StackTrace.fromError(error).then((stackframes) => { + this.setState({ + mappedStackTrace: stackframes.map((sf) => sf.toString()).join('\n'), + }); + }).catch(() => { + this.setState({ + mappedStackTrace: undefined, + }); }); } @@ -29,13 +44,16 @@ export default class ErrorBoundary extends React.PureComponent { } render() { - const { hasError, stackTrace, componentStack } = this.state; + const { hasError, errorMessage, stackTrace, mappedStackTrace, componentStack } = this.state; if (!hasError) return this.props.children; let debugInfo = ''; if (stackTrace) { - debugInfo += 'Stack trace\n-----------\n\n```\n' + stackTrace.toString() + '\n```'; + debugInfo += 'Stack trace\n-----------\n\n```\n' + errorMessage + '\n' + stackTrace.toString() + '\n```'; + } + if (mappedStackTrace) { + debugInfo += 'Mapped stack trace\n-----------\n\n```\n' + errorMessage + '\n' + mappedStackTrace.toString() + '\n```'; } if (componentStack) { if (debugInfo) { -- cgit From 0f07218e53bf581127cdcf5fbf12d9c207ace8d7 Mon Sep 17 00:00:00 2001 From: ThibG Date: Sat, 22 Feb 2020 01:27:34 +0100 Subject: Fix `/web` redirecting to `/web/web` in web UI (#13128) Fixes #13127 --- app/javascript/mastodon/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/main.js b/app/javascript/mastodon/main.js index 5d73caa10..da4884fd3 100644 --- a/app/javascript/mastodon/main.js +++ b/app/javascript/mastodon/main.js @@ -12,7 +12,7 @@ function main() { if (window.history && history.replaceState) { const { pathname, search, hash } = window.location; const path = pathname + search + hash; - if (!(/^\/web[$/]/).test(path)) { + if (!(/^\/web($|\/)/).test(path)) { history.replaceState(null, document.title, `/web${path}`); } } -- cgit From 0d41fef50618e3d58255b4f4082326e83ff06982 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Fri, 21 Feb 2020 12:25:23 +0100 Subject: Document alt+enter --- app/javascript/flavours/glitch/features/keyboard_shortcuts/index.js | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'app/javascript') diff --git a/app/javascript/flavours/glitch/features/keyboard_shortcuts/index.js b/app/javascript/flavours/glitch/features/keyboard_shortcuts/index.js index bc7571200..0bb71e872 100644 --- a/app/javascript/flavours/glitch/features/keyboard_shortcuts/index.js +++ b/app/javascript/flavours/glitch/features/keyboard_shortcuts/index.js @@ -113,6 +113,10 @@ class KeyboardShortcuts extends ImmutablePureComponent { s + + alt+enter + + esc -- cgit From 2f2b48b227304d0f0a28dd4867e84af10b8097ad Mon Sep 17 00:00:00 2001 From: ThibG Date: Sat, 22 Feb 2020 01:27:34 +0100 Subject: [Glitch] Fix `/web` redirecting to `/web/web` in web UI Port 0f07218e53bf581127cdcf5fbf12d9c207ace8d7 to glitch-soc Signed-off-by: Thibaut Girka --- app/javascript/flavours/glitch/util/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/javascript') diff --git a/app/javascript/flavours/glitch/util/main.js b/app/javascript/flavours/glitch/util/main.js index b76826481..1fdb9ff2b 100644 --- a/app/javascript/flavours/glitch/util/main.js +++ b/app/javascript/flavours/glitch/util/main.js @@ -12,7 +12,7 @@ function main() { if (window.history && history.replaceState) { const { pathname, search, hash } = window.location; const path = pathname + search + hash; - if (!(/^\/web[$/]/).test(path)) { + if (!(/^\/web($|\/)/).test(path)) { history.replaceState(null, document.title, `/web${path}`); } } -- cgit