This repository has been archived on 2023-03-18. You can view files and clone it, but cannot push or open issues or pull requests.
osr-discourse-src/app/assets/javascripts/discourse/lib/discourse-location.js.es6
David Taylor d7d4612b2d
FIX: Subfolder sites rewriting URLs to root domain on initial load (#8932)
The `DiscourseLocation.initState` function was accidently renamed in 0431942f (select-kit-2) to `initOptions`. This means that the ember router does not automatically call the function after the router is initialized.

For a long time, we have been calling the `initState` function in the `init` function of discourse-location, which caused an imperceptible URL change to the the root domain, before switching back to the correct subfolder URL when ember called `initState`. This commit removes that call from the initializer, so `initState` is only called once (by ember).

Relevant ember code: https://github.com/emberjs/ember.js/blob/v3.12.2/packages/@ember/-internals/routing/lib/system/router.ts#L695-L699
2020-02-12 09:36:46 +00:00

224 lines
4.5 KiB
JavaScript

import EmberObject from "@ember/object";
import { defaultHomepage } from "discourse/lib/utilities";
import { guidFor } from "@ember/object/internals";
let popstateFired = false;
const supportsHistoryState = window.history && "state" in window.history;
const popstateCallbacks = [];
/**
`Ember.DiscourseLocation` implements the location API using the browser's
`history.pushState` API.
@class DiscourseLocation
@namespace Discourse
@extends @ember/object
*/
const DiscourseLocation = EmberObject.extend({
init() {
this._super(...arguments);
this.set("location", this.location || window.location);
},
/**
@private
Used to set state on first call to setURL
@method initState
*/
initState() {
const history = this.history || window.history;
if (history && history.scrollRestoration) {
history.scrollRestoration = "manual";
}
this.set("history", history);
let url = this.formatURL(this.getURL());
if (this.location && this.location.hash) {
url += this.location.hash;
}
this.replaceState(url);
},
/**
Will be pre-pended to path upon state change
@property rootURL
@default '/'
*/
rootURL: "/",
/**
@private
Returns the current `location.pathname` without rootURL
@method getURL
*/
getURL() {
let url = this.location.pathname;
url = url.replace(new RegExp(`^${Discourse.BaseUri}`), "");
const search = this.location.search || "";
url += search;
return url;
},
/**
@private
Uses `history.pushState` to update the url without a page reload.
@method setURL
@param path {String}
*/
setURL(path) {
const state = this.getState();
path = this.formatURL(path);
if (state && state.path !== path) {
const paths = [path, state.path];
if (!(paths.includes("/") && paths.includes(`/${defaultHomepage()}`))) {
this.pushState(path);
}
}
},
/**
@private
Uses `history.replaceState` to update the url without a page reload
or history modification.
@method replaceURL
@param path {String}
*/
replaceURL(path) {
const state = this.getState();
path = this.formatURL(path);
if (!state || state.path !== path) {
this.replaceState(path);
}
},
/**
@private
Get the current `history.state`
Polyfill checks for native browser support and falls back to retrieving
from a private _historyState constiable
@method getState
*/
getState() {
return supportsHistoryState ? this.history.state : this._historyState;
},
/**
@private
Pushes a new state
@method pushState
@param path {String}
*/
pushState(path) {
const state = { path };
// store state if browser doesn't support `history.state`
if (!supportsHistoryState) {
this._historyState = state;
} else {
this.history.pushState(state, null, path);
}
// used for webkit workaround
this._previousURL = this.getURL();
},
/**
@private
Replaces the current state
@method replaceState
@param path {String}
*/
replaceState(path) {
const state = { path };
// store state if browser doesn't support `history.state`
if (!supportsHistoryState) {
this._historyState = state;
} else {
this.history.replaceState(state, null, path);
}
// used for webkit workaround
this._previousURL = this.getURL();
},
/**
@private
Register a callback to be invoked whenever the browser
history changes, including using forward and back buttons.
@method onUpdateURL
@param callback {Function}
*/
onUpdateURL(callback) {
const guid = guidFor(this);
$(window).on(`popstate.ember-location-${guid}`, () => {
const url = this.getURL();
// Ignore initial page load popstate event in Chrome
if (!popstateFired) {
popstateFired = true;
if (url === this._previousURL) return;
}
popstateCallbacks.forEach(cb => cb(url));
callback(url);
});
},
/**
@private
Used when using `{{action}}` helper. The url is always appended to the rootURL.
@method formatURL
@param url {String}
*/
formatURL(url) {
let rootURL = this.rootURL;
if (url !== "") {
rootURL = rootURL.replace(/\/$/, "");
if (rootURL.length > 0 && url.indexOf(rootURL + "/") === 0) {
rootURL = "";
}
}
return rootURL + url;
},
willDestroy() {
this._super(...arguments);
const guid = guidFor(this);
$(window).off(`popstate.ember-location-${guid}`);
}
});
export default DiscourseLocation;