From: Eduardo San Martin Morote Date: Sat, 12 Oct 2019 15:04:16 +0000 (+0200) Subject: refactor: remove old history files X-Git-Tag: v4.0.0-alpha.0~196 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=96f02e64779da4db3c5743942a016e317ebe4d75;p=thirdparty%2Fvuejs%2Frouter.git refactor: remove old history files --- diff --git a/src/history/base.ts b/src/history/base.ts deleted file mode 100644 index 5c878fa9..00000000 --- a/src/history/base.ts +++ /dev/null @@ -1,115 +0,0 @@ -import * as utils from './utils' -import { ListenerRemover } from '../types' - -export type HistoryQuery = Record -// TODO: is it reall worth allowing null to form queries like ?q&b&c -// When parsing using URLSearchParams, `q&c=` yield an empty string for q and c -export type RawHistoryQuery = Record - -export interface HistoryLocation { - // pathname section - path: string - // search string parsed - query?: RawHistoryQuery - // hash with the # - hash?: string -} -export interface HistoryLocationNormalized extends Required { - // full path (like href) - fullPath: string - query: HistoryQuery -} - -// pushState clones the state passed and do not accept everything -// it doesn't accept symbols, nor functions as values. It also ignores Symbols as keys -type HistoryStateValue = - | string - | number - | boolean - | null - | HistoryState - | HistoryStateArray -export interface HistoryState { - [x: number]: HistoryStateValue - [x: string]: HistoryStateValue -} -interface HistoryStateArray extends Array {} -// export type HistoryState = Record jest.fn()) - -type HashChangeHandler = (this: Window, ev: HashChangeEvent) => any - -/** - * TODO: currently, we cannot prevent a hashchange, we could pass a callback to restore previous navigation on the listener. But we will face the same problems as with HTML5: go(-n) can leads to unexpected directions. We could save a copy of the history and the state, pretty much polyfilling the state stack - */ - -interface PauseState { - currentLocation: HistoryLocationNormalized - // location we are going to after pausing - to: HistoryLocationNormalized -} - -export class HashHistory extends BaseHistory { - // private history = window.history - private _hashChangeHandler: HashChangeHandler - private _listeners: NavigationCallback[] = [] - private _teardowns: Array<() => void> = [] - - // TODO: should it be a stack? a Dict. Check if the popstate listener - // can trigger twice - private pauseState: PauseState | null = null - - constructor() { - super() - // const to = this.createCurrentLocation() - // replace current url to ensure leading slash - // this.history.replaceState(buildState(null, to, null), '', to.fullPath) - // we cannot use window.location.hash because some browsers - // predecode it - this.location = this.utils.normalizeLocation( - getFullPath(window.location.href) - ) - this._hashChangeHandler = this.setupHashListener() - } - - // TODO: is this necessary - ensureLocation() {} - - replace(location: HistoryLocation) { - const to = this.utils.normalizeLocation(location) - // this.pauseListeners(to) - const hashIndex = window.location.href.indexOf('#') - // set it before to make sure we can skip the listener with a simple check - this.location = to - window.location.replace( - window.location.href.slice(0, hashIndex < 0 ? 0 : hashIndex) + - '#' + - to.fullPath - ) - } - - push(location: HistoryLocation, data?: HistoryState) { - const to = this.utils.normalizeLocation(location) - // set it before to make sure we can skip the listener with a simple check - this.location = to - window.location.hash = '#' + to.fullPath - } - - back(triggerListeners: boolean = true) { - // TODO: check if we can go back - // const previvousLocation = this.history.state - // .back as HistoryLocationNormalized - if (!triggerListeners) this.pauseListeners(this.location) - window.history.back() - } - - forward(triggerListeners: boolean = true) {} - - listen(callback: NavigationCallback) { - // settup the listener and prepare teardown callbacks - this._listeners.push(callback) - - const teardown = () => { - this._listeners.splice(this._listeners.indexOf(callback), 1) - } - - this._teardowns.push(teardown) - return teardown - } - - /** - * Remove all listeners attached to the history and cleanups the history - * instance - */ - destroy() { - for (const teardown of this._teardowns) teardown() - this._teardowns = [] - if (this._hashChangeHandler) - window.removeEventListener('hashchange', this._hashChangeHandler) - } - - /** - * Setups the popstate event listener. It's important to setup only - * one to ensure the same parameters are passed to every listener - */ - private setupHashListener() { - const handler: HashChangeHandler = ({ oldURL, newURL }) => { - // TODO: assert oldURL === this.location.fullPath - cs.info('hashchange fired', { - location: this.location.fullPath, - oldURL, - newURL, - }) - - // TODO: handle go(-2) and go(2) (skipping entries) - - const from = this.location - - const targetTo = getFullPath(newURL) - - if (from.fullPath === targetTo) { - cs.info('ignored because internal navigation') - return - } - // we have the state from the old entry, not the current one being removed - // TODO: correctly parse pathname - // TODO: ensure newURL value is consistent - // handle encoding - const to = this.utils.normalizeLocation(targetTo) - this.location = to - - if ( - this.pauseState && - this.pauseState.to && - this.pauseState.to.fullPath === to.fullPath - ) { - cs.info('Ignored beacuse paused') - // reset pauseState - this.pauseState = null - return - } - - // call all listeners - const navigationInfo = { - // TODO: should we do an unknown direction? - direction: NavigationDirection.forward, - } - this._listeners.forEach(listener => - listener(this.location, from, navigationInfo) - ) - } - - // settup the listener and prepare teardown callbacks - window.addEventListener('hashchange', handler) - return handler - } - - private pauseListeners(to: HistoryLocationNormalized) { - this.pauseState = { - currentLocation: this.location, - to, - } - } -} - -function getFullPath(href: string): string { - const hashIndex = href.indexOf('#') - // if no hash is present, we normalize it to the version without the hash - const fullPath = hashIndex < 0 ? '' : href.slice(hashIndex + 1) - - // ensure leading slash - return fullPath.indexOf('/') < 0 ? '/' + fullPath : fullPath -} diff --git a/src/history/utils.ts b/src/history/utils.ts deleted file mode 100644 index f62f2aea..00000000 --- a/src/history/utils.ts +++ /dev/null @@ -1,186 +0,0 @@ -import { - HistoryLocationNormalized, - HistoryQuery, - HistoryLocation, - RawHistoryQuery, -} from './base' - -const PERCENT_RE = /%/g - -/** - * Transforms a URL into an object - * @param location location to normalize - * @param currentLocation current location, to reuse params and location - */ -export function parseURL(location: string): HistoryLocationNormalized { - let path = '', - query: HistoryQuery = {}, - searchString = '', - hash = '' - - // Could use URL and URLSearchParams but IE 11 doesn't support it - const searchPos = location.indexOf('?') - const hashPos = location.indexOf('#', searchPos > -1 ? searchPos : 0) - if (searchPos > -1) { - path = location.slice(0, searchPos) - searchString = location.slice( - searchPos + 1, - hashPos > -1 ? hashPos : location.length - ) - - query = normalizeQuery(parseQuery(searchString)) - } - - if (hashPos > -1) { - path = path || location.slice(0, hashPos) - hash = location.slice(hashPos, location.length) - } - - path = path || location - - return { - fullPath: location, - path, - query, - hash, - } -} - -function safeDecodeUriComponent(value: string): string { - try { - value = decodeURIComponent(value) - } catch (err) { - // TODO: handling only URIError? - console.warn( - `[vue-router] error decoding query "${value}". Keeping the original value.` - ) - } - - return value -} - -function safeEncodeUriComponent(value: string): string { - try { - value = encodeURIComponent(value) - } catch (err) { - // TODO: handling only URIError? - console.warn( - `[vue-router] error encoding query "${value}". Keeping the original value.` - ) - } - - return value -} - -/** - * Transform a queryString into a query object. Accept both, a version with the leading `?` and without - * Should work as URLSearchParams - * @param search - */ -export function parseQuery(search: string): HistoryQuery { - const hasLeadingIM = search[0] === '?' - const query: HistoryQuery = {} - // avoid creating an object with an empty key and empty value - // because of split('&') - if (search === '' || search === '?') return query - const searchParams = (hasLeadingIM ? search.slice(1) : search).split('&') - for (let i = 0; i < searchParams.length; ++i) { - let [key, value] = searchParams[i].split('=') - key = safeDecodeUriComponent(key) - value = safeDecodeUriComponent(value) - if (key in query) { - // an extra variable for ts types - let currentValue = query[key] - if (!Array.isArray(currentValue)) { - currentValue = query[key] = [currentValue] - } - currentValue.push(value) - } else { - query[key] = value - } - } - return query -} - -/** - * Stringify a URL object - * @param location - */ -export function stringifyURL(location: HistoryLocation): string { - let url = location.path - let query = location.query ? stringifyQuery(location.query) : '' - - return url + (query && '?' + query) + (location.hash || '') -} - -/** - * Stringify an object query. Works like URLSearchParams. Doesn't prepend a `?` - * @param query - */ -export function stringifyQuery(query: RawHistoryQuery): string { - let search = '' - // TODO: util function? - for (const key in query) { - if (search.length > 1) search += '&' - const value = query[key] - if (value === null) { - // TODO: should we just add the empty string value? - search += key - continue - } - let encodedKey = safeEncodeUriComponent(key) - let values: string[] = Array.isArray(value) ? value : [value] - values = values.map(safeEncodeUriComponent) - - search += `${encodedKey}=${values[0]}` - for (let i = 1; i < values.length; i++) { - search += `&${encodedKey}=${values[i]}` - } - } - - return search -} - -export function normalizeQuery(query: RawHistoryQuery): HistoryQuery { - // TODO: implem - const normalizedQuery: HistoryQuery = {} - for (const key in query) { - const value = query[key] - if (value === null) normalizedQuery[key] = '' - else normalizedQuery[key] = value - } - return normalizedQuery -} - -/** - * Prepare a URI string to be passed to pushState - * @param uri - */ -export function prepareURI(uri: string) { - // encode the % symbol so it also works on IE - return uri.replace(PERCENT_RE, '%25') -} - -// use regular decodeURI -// Use a renamed export instead of global.decodeURI -// to support node and browser at the same time -const originalDecodeURI = decodeURI -export { originalDecodeURI as decodeURI } - -/** - * Normalize a History location into an object that looks like - * the one at window.location - * @param location - */ -export function normalizeLocation( - location: string | HistoryLocation -): HistoryLocationNormalized { - if (typeof location === 'string') return parseURL(location) - else - return { - fullPath: stringifyURL(location), - path: location.path, - query: location.query ? normalizeQuery(location.query) : {}, - hash: location.hash || '', - } -}