@angular-wave/angular.ts 0.7.8 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/@types/core/parse/parse.d.ts +6 -7
- package/@types/directive/bind/bind.d.ts +2 -1
- package/@types/index.d.ts +1 -1
- package/@types/interface.d.ts +3 -1
- package/@types/{public.d.ts → ng.d.ts} +2 -2
- package/@types/router/globals.d.ts +1 -1
- package/@types/router/path/path-utils.d.ts +8 -11
- package/@types/router/state/interface.d.ts +1 -1
- package/@types/router/state/state-object.d.ts +1 -1
- package/@types/router/state/state-service.d.ts +8 -7
- package/@types/router/state-filters.d.ts +24 -2
- package/@types/router/transition/transition.d.ts +12 -15
- package/@types/router/url/url-matcher.d.ts +3 -3
- package/@types/router/url/url-rule.d.ts +1 -0
- package/@types/router/url/url-rules.d.ts +26 -6
- package/@types/router/url/url-service.d.ts +28 -38
- package/@types/services/http/http.d.ts +48 -1
- package/@types/services/http-backend/http-backend.d.ts +48 -35
- package/@types/services/location/interface.d.ts +55 -0
- package/@types/services/location/location.d.ts +225 -252
- package/@types/shared/common.d.ts +0 -2
- package/@types/shared/interface.d.ts +0 -4
- package/@types/{router/common → shared}/queue.d.ts +2 -2
- package/@types/shared/url-utils/interface.d.ts +0 -1
- package/@types/shared/url-utils/url-utils.d.ts +0 -5
- package/@types/shared/utils.d.ts +29 -6
- package/Makefile +6 -3
- package/dist/angular-ts.esm.js +960 -1062
- package/dist/angular-ts.umd.js +960 -1062
- package/dist/angular-ts.umd.min.js +1 -1
- package/docs/assets/scss/index.scss +23 -0
- package/docs/content/_index.md +9 -8
- package/docs/content/docs/_index.md +1 -1
- package/docs/content/docs/directive/app.md +1 -1
- package/docs/content/docs/directive/bind.md +1 -1
- package/docs/content/docs/directive/blur.md +1 -1
- package/docs/content/docs/directive/channel.md +2 -2
- package/docs/content/docs/directive/class-even.md +1 -1
- package/docs/content/docs/directive/class-odd.md +1 -1
- package/docs/content/docs/directive/class.md +1 -1
- package/docs/content/docs/directive/click.md +1 -1
- package/docs/content/docs/directive/copy.md +1 -1
- package/docs/content/docs/directive/cut.md +1 -1
- package/docs/content/docs/directive/dblclick.md +1 -1
- package/docs/content/docs/directive/focus.md +1 -1
- package/docs/content/docs/directive/get.md +3 -3
- package/docs/content/docs/directive/keydown.md +1 -1
- package/docs/content/docs/directive/keyup.md +1 -1
- package/docs/content/docs/directive/load.md +1 -1
- package/docs/content/docs/directive/mousedown.md +1 -1
- package/docs/content/docs/directive/mouseenter.md +1 -1
- package/docs/content/docs/directive/mouseleave.md +1 -1
- package/docs/content/docs/directive/mousemove.md +1 -1
- package/docs/content/docs/directive/mouseout.md +1 -1
- package/docs/content/docs/directive/mouseover.md +1 -1
- package/docs/content/docs/directive/mouseup.md +1 -1
- package/docs/content/docs/directive/non-bindable.md +28 -0
- package/docs/content/docs/provider/locationProvider.md +26 -0
- package/docs/content/docs/provider/templateCacheProvider.md +2 -2
- package/docs/content/docs/service/location.md +57 -0
- package/docs/content/docs/service/url.md +5 -0
- package/docs/layouts/partials/hooks/head-end.html +1 -1
- package/docs/layouts/shortcodes/version.html +1 -0
- package/docs/static/examples/counter/counter-test.html +0 -4
- package/docs/static/examples/eventbus/eventbus-test.html +0 -4
- package/docs/static/examples/ng-non-bindable/ng-non-bindable-test.html +13 -0
- package/docs/static/examples/ng-non-bindable/ng-non-bindable.html +3 -0
- package/docs/static/examples/ng-non-bindable/ng-non-bindable.test.js +11 -0
- package/docs/static/typedoc/assets/highlight.css +6 -6
- package/docs/static/typedoc/assets/navigation.js +1 -1
- package/docs/static/typedoc/assets/search.js +1 -1
- package/docs/static/typedoc/classes/Location.html +55 -0
- package/docs/static/typedoc/classes/LocationProvider.html +20 -0
- package/docs/static/typedoc/index.html +1 -1
- package/docs/static/typedoc/interfaces/DefaultPorts.html +5 -0
- package/docs/static/typedoc/interfaces/Html5Mode.html +23 -0
- package/docs/static/typedoc/interfaces/Provider.html +2 -1
- package/docs/static/typedoc/interfaces/UrlParts.html +9 -0
- package/docs/static/typedoc/types/AnnotatedFactory.html +1 -1
- package/docs/static/typedoc/types/Expression.html +1 -1
- package/docs/static/typedoc/types/UrlChangeListener.html +5 -0
- package/docs/static/version.js +13 -0
- package/docs/test-results/.last-run.json +4 -0
- package/docs/test-results/static-examples-counter-counter-counter-example/error-context.md +50 -0
- package/package.json +1 -1
- package/src/{loader.js → angular.js} +1 -1
- package/src/angular.spec.js +189 -21
- package/src/animations/animate-css.js +17 -18
- package/src/animations/animate.spec.js +1 -1
- package/src/animations/shared.js +2 -3
- package/src/binding.spec.js +1 -1
- package/src/core/compile/compile.js +4 -7
- package/src/core/compile/compile.spec.js +1 -1
- package/src/core/controller/controller.spec.js +1 -1
- package/src/core/controller/controller.test.js +1 -0
- package/src/core/di/injector.js +7 -8
- package/src/core/di/injector.spec.js +2 -2
- package/src/core/di/injector.test.js +2 -2
- package/src/core/di/internal-injector.js +3 -6
- package/src/core/filter/filter.js +1 -1
- package/src/core/filter/filter.spec.js +1 -1
- package/src/core/filter/filter.test.js +1 -0
- package/src/core/interpolate/interpolate.js +4 -6
- package/src/core/interpolate/interpolate.spec.js +1 -1
- package/src/core/interpolate/interpolate.test.js +1 -0
- package/src/core/parse/ast/ast.spec.js +1 -1
- package/src/core/parse/ast/ast.test.js +1 -1
- package/src/core/parse/lexer/lexer.spec.js +1 -1
- package/src/core/parse/parse.js +150 -146
- package/src/core/parse/parse.spec.js +17 -16
- package/src/core/prop.spec.js +1 -1
- package/src/core/root-element.spec.js +1 -1
- package/src/core/scope/scope.js +10 -11
- package/src/core/scope/scope.spec.js +3 -4
- package/src/directive/aria/aria.spec.js +1 -1
- package/src/directive/aria/aria.test.js +1 -0
- package/src/directive/attrs/attrs.spec.js +1 -1
- package/src/directive/attrs/attrs.test.js +1 -0
- package/src/directive/attrs/boolean.spec.js +1 -1
- package/src/directive/attrs/boolean.test.js +1 -0
- package/src/directive/attrs/element-style.spec.js +1 -1
- package/src/directive/attrs/element-style.test.js +1 -0
- package/src/directive/attrs/src.spec.js +1 -1
- package/src/directive/attrs/src.test.js +1 -0
- package/src/directive/bind/bind-html.spec.js +1 -1
- package/src/directive/bind/bind.js +1 -0
- package/src/directive/bind/bind.spec.js +1 -1
- package/src/directive/bind/bind.test.js +1 -0
- package/src/directive/channel/channel.spec.js +1 -1
- package/src/directive/channel/channel.test.js +1 -0
- package/src/directive/class/class.spec.js +1 -1
- package/src/directive/class/class.test.js +1 -0
- package/src/directive/cloak/cloak.spec.js +1 -1
- package/src/directive/cloak/cloak.test.js +1 -0
- package/src/directive/controller/controller.spec.js +1 -1
- package/src/directive/controller/controller.test.js +1 -0
- package/src/directive/events/click.spec.js +1 -1
- package/src/directive/events/event.spec.js +1 -1
- package/src/directive/events/events.test.js +1 -0
- package/src/directive/form/form.js +8 -5
- package/src/directive/form/form.spec.js +1 -1
- package/src/directive/form/form.test.js +1 -0
- package/src/directive/http/delete.spec.js +1 -1
- package/src/directive/http/form-test.html +18 -0
- package/src/directive/http/get.spec.js +1 -1
- package/src/directive/http/http.js +12 -3
- package/src/directive/http/post.spec.js +504 -9
- package/src/directive/http/put.spec.js +1 -1
- package/src/directive/if/if.spec.js +1 -1
- package/src/directive/include/include.spec.js +1 -1
- package/src/directive/init/init.spec.js +1 -1
- package/src/directive/init/init.test.js +1 -0
- package/src/directive/input/input.js +13 -15
- package/src/directive/input/input.spec.js +1 -2
- package/src/directive/input/input.test.js +1 -0
- package/src/directive/messages/messages.spec.js +1 -1
- package/src/directive/messages/messages.test.js +1 -0
- package/src/directive/model/model.js +13 -13
- package/src/directive/model/model.spec.js +1 -1
- package/src/directive/model/model.test.js +1 -0
- package/src/directive/model-options/model-option.test.js +1 -0
- package/src/directive/model-options/model-options.js +1 -1
- package/src/directive/model-options/model-options.spec.js +1 -1
- package/src/directive/non-bindable/non-bindable.spec.js +1 -1
- package/src/directive/non-bindable/non-bindable.test.js +1 -0
- package/src/directive/observe/observe.spec.js +1 -1
- package/src/directive/observe/observe.test.js +1 -0
- package/src/directive/on/on.spec.js +1 -1
- package/src/directive/on/on.test.js +1 -0
- package/src/directive/options/options.spec.js +1 -1
- package/src/directive/options/options.test.js +1 -0
- package/src/directive/ref/href.spec.js +1 -1
- package/src/directive/ref/href.test.js +2 -0
- package/src/directive/ref/ref.spec.js +1 -1
- package/src/directive/repeat/repeat.spec.js +2 -3
- package/src/directive/repeat/repeat.test.js +1 -0
- package/src/directive/script/script.spec.js +1 -1
- package/src/directive/script/script.test.js +1 -0
- package/src/directive/select/select.js +1 -1
- package/src/directive/select/select.spec.js +1 -1
- package/src/directive/select/select.test.js +1 -0
- package/src/directive/setter/setter.spec.js +1 -1
- package/src/directive/setter/setter.test.js +1 -0
- package/src/directive/show-hide/show-hide.spec.js +1 -1
- package/src/directive/show-hide/show-hide.test.js +1 -0
- package/src/directive/style/style.spec.js +1 -1
- package/src/directive/style/style.test.js +1 -0
- package/src/directive/switch/switch.spec.js +1 -1
- package/src/directive/switch/switch.test.js +1 -0
- package/src/directive/validators/validators.js +82 -84
- package/src/directive/validators/validators.spec.js +5 -4
- package/src/directive/validators/validators.test.js +1 -0
- package/src/filters/filter.spec.js +1 -1
- package/src/filters/filters.spec.js +1 -1
- package/src/filters/limit-to.js +2 -3
- package/src/filters/limit-to.spec.js +1 -1
- package/src/filters/order-by.spec.js +1 -1
- package/src/index.js +1 -1
- package/src/injection-tokens.js +5 -1
- package/src/interface.ts +3 -1
- package/src/loader.md +0 -155
- package/src/{public.js → ng.js} +7 -8
- package/src/{public.spec.js → ng.spec.js} +1 -1
- package/src/router/directives/state-directives.spec.js +8 -7
- package/src/router/directives/view-directive.js +2 -8
- package/src/router/directives/view-directive.spec.js +8 -9
- package/src/router/{common/common.html → glob/glob.html} +2 -3
- package/src/router/{common/common.test.js → glob/glob.test.js} +2 -1
- package/src/router/globals.js +1 -1
- package/src/router/path/path-utils.js +5 -0
- package/src/router/router-test-hashbang.html +45 -0
- package/src/router/services.spec.js +5 -6
- package/src/router/state/interface.ts +1 -1
- package/src/router/state/state-builder.js +3 -3
- package/src/router/state/state-builder.spec.js +1 -1
- package/src/router/state/state-object.js +1 -1
- package/src/router/state/state-registry.js +2 -2
- package/src/router/state/state-service.js +13 -10
- package/src/router/state/state.spec.js +23 -22
- package/src/router/state/state.test.js +1 -0
- package/src/router/state/views.js +1 -1
- package/src/router/state-filter.spec.js +1 -1
- package/src/router/state-filters.js +13 -9
- package/src/router/template-factory.js +5 -4
- package/src/router/template-factory.spec.js +1 -1
- package/src/router/transition/hook-registry.js +1 -1
- package/src/router/transition/transition-service.js +6 -5
- package/src/router/transition/transition.js +4 -4
- package/src/router/url/url-matcher.js +3 -3
- package/src/router/url/url-rule.js +1 -0
- package/src/router/url/url-rules.js +8 -5
- package/src/router/url/url-service.js +77 -76
- package/src/router/url/url-service.spec.js +55 -39
- package/src/router/url/url.test.js +1 -0
- package/src/router/view/view.js +4 -5
- package/src/router/view/view.spec.js +10 -12
- package/src/router/view/view.test.js +1 -0
- package/src/router/view-hook.spec.js +1 -1
- package/src/router/view-scroll.spec.js +1 -1
- package/src/services/anchor-scroll.html +2 -2
- package/src/services/anchor-scroll.js +5 -4
- package/src/services/http/http.js +9 -4
- package/src/services/http/http.spec.js +2 -7
- package/src/services/http/template-request.spec.js +1 -1
- package/src/services/http-backend/http-backend.js +51 -77
- package/src/services/http-backend/http-backend.spec.js +1 -2
- package/src/services/http-backend/http-backend.test.js +1 -0
- package/src/services/location/interface.ts +62 -0
- package/src/services/location/location.js +433 -520
- package/src/services/location/location.spec.js +909 -530
- package/src/services/location/location.test.js +2 -2
- package/src/services/log/log.spec.js +1 -1
- package/src/services/log/log.test.js +1 -0
- package/src/services/pubsub/pubsub.spec.js +1 -1
- package/src/services/sce/sce.js +5 -7
- package/src/services/sce/sce.md +2 -2
- package/src/services/sce/sce.spec.js +1 -1
- package/src/services/template-cache/template-cache.spec.js +1 -1
- package/src/services/template-cache/template-cache.test.js +1 -0
- package/src/shared/common.js +0 -5
- package/src/shared/common.spec.js +1 -1
- package/src/shared/interface.ts +0 -4
- package/src/{router/common → shared}/queue.js +7 -7
- package/src/shared/shared.html +1 -0
- package/src/shared/shared.test.js +1 -0
- package/src/shared/url-utils/interface.ts +0 -2
- package/src/shared/url-utils/url-utils.js +6 -30
- package/src/shared/url-utils/url-utils.spec.js +10 -9
- package/src/shared/utils.js +32 -9
- package/src/shared/utils.spec.js +35 -1
- package/src/src.html +1 -2
- package/typedoc.json +0 -1
- package/utils/express.js +27 -1
- package/utils/version.cjs +23 -0
- package/@types/router/state-provider.d.ts +0 -123
- package/src/directive/non-bindable/non-bindable.md +0 -17
- package/src/loader.spec.js +0 -169
- package/src/router/state-provider.js +0 -146
- package/src/services/location/location.md +0 -114
- package/src/shared/url-utils/url-utils.md +0 -46
- /package/@types/{loader.d.ts → angular.d.ts} +0 -0
- /package/@types/router/{common → glob}/glob.d.ts +0 -0
- /package/src/router/{common → glob}/glob.js +0 -0
- /package/src/router/{common → glob}/glob.spec.js +0 -0
- /package/src/{router/common → shared}/queue.spec.js +0 -0
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { trimEmptyHash, urlResolve } from "../../shared/url-utils/url-utils.js";
|
|
2
2
|
import {
|
|
3
3
|
encodeUriSegment,
|
|
4
|
-
isBoolean,
|
|
5
4
|
isDefined,
|
|
6
5
|
isNumber,
|
|
7
6
|
isObject,
|
|
@@ -9,263 +8,161 @@ import {
|
|
|
9
8
|
isUndefined,
|
|
10
9
|
minErr,
|
|
11
10
|
parseKeyValue,
|
|
12
|
-
toInt,
|
|
13
11
|
toKeyValue,
|
|
14
12
|
equals,
|
|
13
|
+
startsWith,
|
|
15
14
|
} from "../../shared/utils.js";
|
|
16
15
|
import { getBaseHref } from "../../shared/dom.js";
|
|
16
|
+
import { $injectTokens as $t } from "../../injection-tokens.js";
|
|
17
|
+
|
|
18
|
+
const PATH_MATCH = /^([^?#]*)(\?([^#]*))?(#(.*))?$/;
|
|
19
|
+
const $locationMinErr = minErr("$location");
|
|
20
|
+
|
|
21
|
+
let urlUpdatedByLocation = false;
|
|
17
22
|
|
|
18
23
|
/**
|
|
19
|
-
* @
|
|
20
|
-
*
|
|
21
|
-
* @
|
|
22
|
-
* @property {number} ftp
|
|
24
|
+
* @ignore
|
|
25
|
+
* The pathname, beginning with "/"
|
|
26
|
+
* @type {string}
|
|
23
27
|
*/
|
|
28
|
+
let $$path;
|
|
24
29
|
|
|
25
30
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* @typedef {Object} Html5Mode
|
|
29
|
-
* @property {boolean} enabled - (default: false) If true, will rely on `history.pushState` to
|
|
30
|
-
* change URLs where supported. Falls back to hash-prefixed paths in browsers that do not
|
|
31
|
-
* support `pushState`.
|
|
32
|
-
* @property {boolean} requireBase - (default: `true`) When html5Mode is enabled, specifies
|
|
33
|
-
* whether or not a `<base>` tag is required to be present. If both `enabled` and `requireBase`
|
|
34
|
-
* are true, and a `<base>` tag is not present, an error will be thrown when `$location` is injected.
|
|
35
|
-
* See the {@link guide/$location $location guide} for more information.
|
|
36
|
-
* @property {boolean|string} rewriteLinks - (default: `true`) When html5Mode is enabled, enables or
|
|
37
|
-
* disables URL rewriting for relative links. If set to a string, URL rewriting will only apply to links
|
|
38
|
-
* with an attribute that matches the given string. For example, if set to `'internal-link'`, URL rewriting
|
|
39
|
-
* will only occur for `<a internal-link>` links. Note that [attribute name normalization](guide/directive#normalization)
|
|
40
|
-
* does not apply here, so `'internalLink'` will **not** match `'internal-link'`.
|
|
31
|
+
* @type {Object.<string,boolean|Array>}
|
|
41
32
|
*/
|
|
42
|
-
|
|
43
|
-
/** @type {DefaultPorts} */
|
|
44
|
-
const DEFAULT_PORTS = { http: 80, https: 443, ftp: 21 };
|
|
45
|
-
const PATH_MATCH = /^([^?#]*)(\?([^#]*))?(#(.*))?$/;
|
|
46
|
-
const $locationMinErr = minErr("$location");
|
|
33
|
+
let $$search;
|
|
47
34
|
|
|
48
35
|
/**
|
|
49
|
-
* @
|
|
36
|
+
* @ignore
|
|
37
|
+
* The hash string, minus the hash symbol
|
|
38
|
+
* @type {string}
|
|
50
39
|
*/
|
|
40
|
+
let $$hash;
|
|
41
|
+
|
|
51
42
|
export class Location {
|
|
52
43
|
/**
|
|
53
44
|
* @param {string} appBase application base URL
|
|
54
45
|
* @param {string} appBaseNoFile application base URL stripped of any filename
|
|
46
|
+
* @param {boolean} [html5] Defaults to true
|
|
47
|
+
* @param {string} [prefix] URL path prefix for html5 mode or hash prefix for hashbang mode
|
|
55
48
|
*/
|
|
56
|
-
constructor(appBase, appBaseNoFile) {
|
|
57
|
-
const parsedUrl = urlResolve(appBase);
|
|
58
|
-
|
|
49
|
+
constructor(appBase, appBaseNoFile, html5 = true, prefix) {
|
|
59
50
|
/** @type {string} */
|
|
60
51
|
this.appBase = appBase;
|
|
61
52
|
|
|
62
53
|
/** @type {string} */
|
|
63
54
|
this.appBaseNoFile = appBaseNoFile;
|
|
64
55
|
|
|
65
|
-
/**
|
|
66
|
-
|
|
67
|
-
* @type {string}
|
|
68
|
-
*/
|
|
69
|
-
this.$$absUrl = "";
|
|
56
|
+
/** @type {boolean} */
|
|
57
|
+
this.html5 = html5;
|
|
70
58
|
|
|
71
|
-
/**
|
|
72
|
-
|
|
73
|
-
* @type {boolean}
|
|
74
|
-
*/
|
|
75
|
-
this.$$html5 = false;
|
|
59
|
+
/** @type {string | undefined} */
|
|
60
|
+
this.basePrefix = html5 ? prefix || "" : undefined;
|
|
76
61
|
|
|
77
|
-
/**
|
|
78
|
-
|
|
79
|
-
* @type {boolean}
|
|
80
|
-
*/
|
|
81
|
-
this.$$replace = false;
|
|
82
|
-
|
|
83
|
-
/** @type {string} */
|
|
84
|
-
this.$$protocol = parsedUrl.protocol;
|
|
85
|
-
|
|
86
|
-
/** @type {string} */
|
|
87
|
-
this.$$host = parsedUrl.hostname;
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* The port, without ":"
|
|
91
|
-
* @type {number}
|
|
92
|
-
*/
|
|
93
|
-
this.$$port =
|
|
94
|
-
toInt(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null;
|
|
62
|
+
/** @type {string | undefined} */
|
|
63
|
+
this.hashPrefix = html5 ? undefined : prefix;
|
|
95
64
|
|
|
96
65
|
/**
|
|
97
|
-
*
|
|
66
|
+
* An absolute URL is the full URL, including protocol (http/https ), the optional subdomain (e.g. www ), domain (example.com), and path (which includes the directory and slug)
|
|
67
|
+
* with all segments encoded according to rules specified in [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt).
|
|
98
68
|
* @type {string}
|
|
99
69
|
*/
|
|
100
|
-
this
|
|
70
|
+
this.absUrl = "";
|
|
101
71
|
|
|
102
72
|
/**
|
|
103
|
-
*
|
|
73
|
+
* @ignore
|
|
74
|
+
* Current url
|
|
104
75
|
* @type {string}
|
|
105
76
|
*/
|
|
106
|
-
this.$$
|
|
77
|
+
this.$$url = undefined;
|
|
107
78
|
|
|
108
79
|
/**
|
|
109
|
-
*
|
|
110
|
-
*
|
|
80
|
+
* @ignore
|
|
81
|
+
* Callback to update browser url
|
|
82
|
+
* @type {Function}
|
|
111
83
|
*/
|
|
112
|
-
this.$$
|
|
84
|
+
this.$$updateBrowser = undefined;
|
|
113
85
|
}
|
|
114
86
|
|
|
115
87
|
/**
|
|
116
|
-
* Return full URL representation with all segments encoded according to rules specified in
|
|
117
|
-
* [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt).
|
|
118
|
-
*
|
|
119
|
-
* @return {string} full URL
|
|
120
|
-
*/
|
|
121
|
-
absUrl() {
|
|
122
|
-
return this.$$absUrl;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* This method is getter / setter.
|
|
127
|
-
*
|
|
128
|
-
* Return URL (e.g. `/path?a=b#hash`) when called without any parameter.
|
|
129
88
|
* Change path, search and hash, when called with parameter and return `$location`.
|
|
130
89
|
*
|
|
131
|
-
* @param {string
|
|
132
|
-
* @return {Location
|
|
90
|
+
* @param {string} url New URL without base prefix (e.g. `/path?a=b#hash`)
|
|
91
|
+
* @return {Location} url
|
|
133
92
|
*/
|
|
134
|
-
|
|
135
|
-
if (isUndefined(url)) {
|
|
136
|
-
return this.$$url;
|
|
137
|
-
}
|
|
138
|
-
|
|
93
|
+
setUrl(url) {
|
|
139
94
|
const match = PATH_MATCH.exec(url);
|
|
140
|
-
if (match[1] || url === "") this.
|
|
141
|
-
if (match[2] || match[1] || url === "") this.
|
|
142
|
-
this.
|
|
95
|
+
if (match[1] || url === "") this.setPath(decodeURIComponent(match[1]));
|
|
96
|
+
if (match[2] || match[1] || url === "") this.setSearch(match[3] || "");
|
|
97
|
+
this.setHash(match[5] || "");
|
|
143
98
|
|
|
144
99
|
return this;
|
|
145
100
|
}
|
|
146
101
|
|
|
147
102
|
/**
|
|
103
|
+
* Return URL (e.g. `/path?a=b#hash`) when called without any parameter.
|
|
148
104
|
*
|
|
149
|
-
*
|
|
150
|
-
* @return {string} protocol of current URL
|
|
151
|
-
*/
|
|
152
|
-
protocol() {
|
|
153
|
-
return this.$$protocol;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* This method is getter only.
|
|
158
|
-
*
|
|
159
|
-
* Return host of current URL.
|
|
160
|
-
*
|
|
161
|
-
* Note: compared to the non-AngularTS version `location.host` which returns `hostname:port`, this returns the `hostname` portion only.
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
* @return {string} host of current URL.
|
|
105
|
+
* @return {string} url
|
|
165
106
|
*/
|
|
166
|
-
|
|
167
|
-
return this.$$
|
|
107
|
+
getUrl() {
|
|
108
|
+
return this.$$url;
|
|
168
109
|
}
|
|
169
110
|
|
|
170
111
|
/**
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
* Return port of current URL.
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
* ```js
|
|
177
|
-
* // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
|
|
178
|
-
* let port = $location.port();
|
|
179
|
-
* // => 80
|
|
180
|
-
* ```
|
|
112
|
+
* Change path parameter and return `$location`.
|
|
181
113
|
*
|
|
182
|
-
* @
|
|
114
|
+
* @param {(string|number)} path New path
|
|
115
|
+
* @return {Location}
|
|
183
116
|
*/
|
|
184
|
-
|
|
185
|
-
|
|
117
|
+
setPath(path) {
|
|
118
|
+
let newPath = path !== null ? path.toString() : "";
|
|
119
|
+
$$path = newPath.charAt(0) === "/" ? newPath : `/${newPath}`;
|
|
120
|
+
this.$$compose();
|
|
121
|
+
return this;
|
|
186
122
|
}
|
|
187
123
|
|
|
188
124
|
/**
|
|
189
|
-
* This method is getter / setter.
|
|
190
|
-
*
|
|
191
|
-
* Return path of current URL when called without any parameter.
|
|
192
125
|
*
|
|
193
|
-
*
|
|
126
|
+
* Return path of current URL
|
|
194
127
|
*
|
|
195
|
-
*
|
|
196
|
-
* if it is missing.
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
* ```js
|
|
200
|
-
* // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
|
|
201
|
-
* let path = $location.path();
|
|
202
|
-
* // => "/some/path"
|
|
203
|
-
* ```
|
|
204
|
-
*
|
|
205
|
-
* @param {(string|number)=} path New path
|
|
206
|
-
* @return {(string|object)} path if called with no parameters, or `$location` if called with a parameter
|
|
128
|
+
* @return {string}
|
|
207
129
|
*/
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
return this.$$path;
|
|
211
|
-
}
|
|
212
|
-
let newPath = path !== null ? path.toString() : "";
|
|
213
|
-
this.$$path = newPath.charAt(0) === "/" ? newPath : `/${newPath}`;
|
|
214
|
-
this.$$compose();
|
|
215
|
-
return this;
|
|
130
|
+
getPath() {
|
|
131
|
+
return $$path;
|
|
216
132
|
}
|
|
217
133
|
|
|
218
134
|
/**
|
|
219
|
-
* This method is getter / setter.
|
|
220
|
-
*
|
|
221
|
-
* Returns the hash fragment when called without any parameters.
|
|
222
|
-
*
|
|
223
135
|
* Changes the hash fragment when called with a parameter and returns `$location`.
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
* ```js
|
|
227
|
-
* // given URL http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue
|
|
228
|
-
* let hash = $location.hash();
|
|
229
|
-
* // => "hashValue"
|
|
230
|
-
* ```
|
|
231
|
-
*
|
|
232
|
-
* @param {(string|number)=} hash New hash fragment
|
|
233
|
-
* @return {string|Location} hash
|
|
136
|
+
* @param {(string|number)} hash New hash fragment
|
|
137
|
+
* @return {Location} hash
|
|
234
138
|
*/
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
return this.$$hash;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
this.$$hash = hash !== null ? hash.toString() : "";
|
|
139
|
+
setHash(hash) {
|
|
140
|
+
$$hash = hash !== null ? hash.toString() : "";
|
|
241
141
|
this.$$compose();
|
|
242
142
|
return this;
|
|
243
143
|
}
|
|
244
144
|
|
|
245
145
|
/**
|
|
246
|
-
*
|
|
247
|
-
*
|
|
146
|
+
* Returns the hash fragment when called without any parameters.
|
|
147
|
+
* @return {string} hash
|
|
248
148
|
*/
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
return this;
|
|
149
|
+
getHash() {
|
|
150
|
+
return $$hash;
|
|
252
151
|
}
|
|
253
152
|
|
|
254
153
|
/**
|
|
255
|
-
*
|
|
154
|
+
* Sets the search part (as object) of current URL
|
|
256
155
|
*
|
|
257
|
-
* @param {string|Object
|
|
156
|
+
* @param {string|Object} search New search params - string or hash object.
|
|
258
157
|
* @param {(string|number|Array<string>|boolean)=} paramValue If search is a string or number, then paramValue will override only a single search property.
|
|
259
|
-
* @returns {Object
|
|
158
|
+
* @returns {Object} Search object or Location object
|
|
260
159
|
*/
|
|
261
|
-
|
|
160
|
+
setSearch(search, paramValue) {
|
|
262
161
|
switch (arguments.length) {
|
|
263
|
-
case 0:
|
|
264
|
-
return this.$$search;
|
|
265
162
|
case 1:
|
|
266
163
|
if (isString(search) || isNumber(search)) {
|
|
267
164
|
search = search.toString();
|
|
268
|
-
|
|
165
|
+
$$search = parseKeyValue(search);
|
|
269
166
|
} else if (isObject(search)) {
|
|
270
167
|
search = structuredClone(search, {});
|
|
271
168
|
// remove object undefined or null properties
|
|
@@ -273,7 +170,7 @@ export class Location {
|
|
|
273
170
|
if (value == null) delete search[key];
|
|
274
171
|
});
|
|
275
172
|
|
|
276
|
-
|
|
173
|
+
$$search = search;
|
|
277
174
|
} else {
|
|
278
175
|
throw $locationMinErr(
|
|
279
176
|
"isrcharg",
|
|
@@ -283,9 +180,10 @@ export class Location {
|
|
|
283
180
|
break;
|
|
284
181
|
default:
|
|
285
182
|
if (isUndefined(paramValue) || paramValue === null) {
|
|
286
|
-
delete
|
|
183
|
+
delete $$search[search];
|
|
287
184
|
} else {
|
|
288
|
-
|
|
185
|
+
// @ts-ignore
|
|
186
|
+
$$search[search] = paramValue;
|
|
289
187
|
}
|
|
290
188
|
}
|
|
291
189
|
|
|
@@ -294,28 +192,28 @@ export class Location {
|
|
|
294
192
|
}
|
|
295
193
|
|
|
296
194
|
/**
|
|
297
|
-
*
|
|
298
|
-
*
|
|
195
|
+
* Returns the search part (as object) of current URL
|
|
196
|
+
*
|
|
197
|
+
* @returns {Object} Search object or Location object
|
|
299
198
|
*/
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
this.$$absUrl = this.$$normalizeUrl(this.$$url);
|
|
303
|
-
this.$$urlUpdatedByLocation = true;
|
|
199
|
+
getSearch() {
|
|
200
|
+
return $$search;
|
|
304
201
|
}
|
|
305
202
|
|
|
306
203
|
/**
|
|
307
|
-
* @
|
|
308
|
-
*
|
|
204
|
+
* @private
|
|
205
|
+
* Compose url and update `url` and `absUrl` property
|
|
309
206
|
*/
|
|
310
|
-
$$
|
|
311
|
-
|
|
207
|
+
$$compose() {
|
|
208
|
+
this.$$url = normalizePath($$path, $$search, $$hash);
|
|
209
|
+
this.absUrl = this.html5
|
|
210
|
+
? this.appBaseNoFile + this.$$url.substring(1)
|
|
211
|
+
: this.appBase + (this.$$url ? this.hashPrefix + this.$$url : "");
|
|
212
|
+
urlUpdatedByLocation = true;
|
|
213
|
+
setTimeout(() => this.$$updateBrowser && this.$$updateBrowser());
|
|
312
214
|
}
|
|
313
215
|
|
|
314
216
|
/**
|
|
315
|
-
* This method is getter / setter.
|
|
316
|
-
*
|
|
317
|
-
* Return the history state object when called without any parameter.
|
|
318
|
-
*
|
|
319
217
|
* Change the history state object when called with one parameter and return `$location`.
|
|
320
218
|
* The state object is later passed to `pushState` or `replaceState`.
|
|
321
219
|
* See {@link https://developer.mozilla.org/en-US/docs/Web/API/History/pushState#state|History.state}
|
|
@@ -323,85 +221,30 @@ export class Location {
|
|
|
323
221
|
* NOTE: This method is supported only in HTML5 mode and only in browsers supporting
|
|
324
222
|
* the HTML5 History API (i.e. methods `pushState` and `replaceState`). If you need to support
|
|
325
223
|
* older browsers (like IE9 or Android < 4.0), don't use this method.
|
|
326
|
-
*
|
|
327
|
-
* @
|
|
328
|
-
* @return {any} state
|
|
224
|
+
* @param {any} state
|
|
225
|
+
* @returns {Location}
|
|
329
226
|
*/
|
|
330
|
-
|
|
331
|
-
if (!
|
|
332
|
-
return this.$$state;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
if (!(this instanceof LocationHtml5Url) || !this.$$html5) {
|
|
227
|
+
setState(state) {
|
|
228
|
+
if (!this.html5) {
|
|
336
229
|
throw $locationMinErr(
|
|
337
230
|
"nostate",
|
|
338
|
-
"History API state support is available only "
|
|
339
|
-
"in HTML5 mode and only in browsers supporting HTML5 History API",
|
|
231
|
+
"History API state support is available only in HTML5 mode",
|
|
340
232
|
);
|
|
341
233
|
}
|
|
342
|
-
// The user might modify `stateObject` after invoking `$location.
|
|
234
|
+
// The user might modify `stateObject` after invoking `$location.setState(stateObject)`
|
|
343
235
|
// but we're changing the $$state reference to $browser.state() during the $digest
|
|
344
236
|
// so the modification window is narrow.
|
|
345
237
|
this.$$state = isUndefined(state) ? null : state;
|
|
346
|
-
|
|
238
|
+
urlUpdatedByLocation = true;
|
|
347
239
|
return this;
|
|
348
240
|
}
|
|
349
241
|
|
|
350
242
|
/**
|
|
351
|
-
*
|
|
352
|
-
* @
|
|
353
|
-
* @returns {boolean}
|
|
354
|
-
*/
|
|
355
|
-
$$parseLinkUrl(_url, _url2) {
|
|
356
|
-
throw new Error(`Method not implemented ${_url} ${_url2}`);
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
$$parse(_url) {
|
|
360
|
-
throw new Error(`Method not implemented ${_url}`);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
/**
|
|
365
|
-
* This object is exposed as $location service when HTML5 mode is enabled and supported
|
|
366
|
-
*/
|
|
367
|
-
export class LocationHtml5Url extends Location {
|
|
368
|
-
/**
|
|
369
|
-
* @param {string} appBase application base URL
|
|
370
|
-
* @param {string} appBaseNoFile application base URL stripped of any filename
|
|
371
|
-
* @param {string} basePrefix URL path prefix
|
|
372
|
-
*/
|
|
373
|
-
constructor(appBase, appBaseNoFile, basePrefix) {
|
|
374
|
-
super(appBase, appBaseNoFile);
|
|
375
|
-
this.$$html5 = true;
|
|
376
|
-
this.basePrefix = basePrefix || "";
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
/**
|
|
380
|
-
* Parse given HTML5 (regular) URL string into properties
|
|
381
|
-
* @param {string} url HTML5 URL
|
|
243
|
+
* Return the history state object
|
|
244
|
+
* @returns {any}
|
|
382
245
|
*/
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
if (!isString(pathUrl)) {
|
|
386
|
-
throw $locationMinErr(
|
|
387
|
-
"ipthprfx",
|
|
388
|
-
'Invalid url "{0}", missing path prefix "{1}".',
|
|
389
|
-
url,
|
|
390
|
-
this.appBaseNoFile,
|
|
391
|
-
);
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
parseAppUrl(pathUrl, this, true);
|
|
395
|
-
|
|
396
|
-
if (!this.$$path) {
|
|
397
|
-
this.$$path = "/";
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
this.$$compose();
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
$$normalizeUrl(url) {
|
|
404
|
-
return this.appBaseNoFile + url.substring(1); // first char is always '/'
|
|
246
|
+
getState() {
|
|
247
|
+
return this.$$state;
|
|
405
248
|
}
|
|
406
249
|
|
|
407
250
|
/**
|
|
@@ -409,149 +252,139 @@ export class LocationHtml5Url extends Location {
|
|
|
409
252
|
* @param {string} relHref
|
|
410
253
|
* @returns {boolean}
|
|
411
254
|
*/
|
|
412
|
-
|
|
413
|
-
if (
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
let appUrl;
|
|
420
|
-
let prevAppUrl;
|
|
421
|
-
let rewrittenUrl;
|
|
422
|
-
|
|
423
|
-
if (isDefined((appUrl = stripBaseUrl(this.appBase, url)))) {
|
|
424
|
-
prevAppUrl = appUrl;
|
|
425
|
-
if (
|
|
426
|
-
this.basePrefix &&
|
|
427
|
-
isDefined((appUrl = stripBaseUrl(this.basePrefix, appUrl)))
|
|
428
|
-
) {
|
|
429
|
-
rewrittenUrl =
|
|
430
|
-
this.appBaseNoFile + (stripBaseUrl("/", appUrl) || appUrl);
|
|
431
|
-
} else {
|
|
432
|
-
rewrittenUrl = this.appBase + prevAppUrl;
|
|
255
|
+
parseLinkUrl(url, relHref) {
|
|
256
|
+
if (this.html5) {
|
|
257
|
+
if (relHref && relHref[0] === "#") {
|
|
258
|
+
// special case for links to hash fragments:
|
|
259
|
+
// keep the old url and only replace the hash fragment
|
|
260
|
+
this.setHash(relHref.slice(1));
|
|
261
|
+
return true;
|
|
433
262
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
263
|
+
let appUrl;
|
|
264
|
+
let prevAppUrl;
|
|
265
|
+
let rewrittenUrl;
|
|
266
|
+
|
|
267
|
+
if (isDefined((appUrl = stripBaseUrl(this.appBase, url)))) {
|
|
268
|
+
prevAppUrl = appUrl;
|
|
269
|
+
if (
|
|
270
|
+
this.basePrefix &&
|
|
271
|
+
isDefined((appUrl = stripBaseUrl(this.basePrefix, appUrl)))
|
|
272
|
+
) {
|
|
273
|
+
rewrittenUrl =
|
|
274
|
+
this.appBaseNoFile + (stripBaseUrl("/", appUrl) || appUrl);
|
|
275
|
+
} else {
|
|
276
|
+
rewrittenUrl = this.appBase + prevAppUrl;
|
|
277
|
+
}
|
|
278
|
+
} else if (isDefined((appUrl = stripBaseUrl(this.appBaseNoFile, url)))) {
|
|
279
|
+
rewrittenUrl = this.appBaseNoFile + appUrl;
|
|
280
|
+
} else if (this.appBaseNoFile === `${url}/`) {
|
|
281
|
+
rewrittenUrl = this.appBaseNoFile;
|
|
282
|
+
}
|
|
283
|
+
if (rewrittenUrl) {
|
|
284
|
+
this.parse(rewrittenUrl);
|
|
285
|
+
}
|
|
286
|
+
return !!rewrittenUrl;
|
|
287
|
+
} else {
|
|
288
|
+
if (stripHash(this.appBase) === stripHash(url)) {
|
|
289
|
+
this.parse(url);
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
292
|
+
return false;
|
|
441
293
|
}
|
|
442
|
-
return !!rewrittenUrl;
|
|
443
294
|
}
|
|
444
|
-
}
|
|
445
295
|
|
|
446
|
-
/**
|
|
447
|
-
* LocationHashbangUrl represents URL
|
|
448
|
-
* This object is exposed as $location service when developer doesn't opt into html5 mode.
|
|
449
|
-
* It also serves as the base class for html5 mode fallback on legacy browsers.
|
|
450
|
-
*
|
|
451
|
-
*/
|
|
452
|
-
export class LocationHashbangUrl extends Location {
|
|
453
296
|
/**
|
|
454
|
-
*
|
|
455
|
-
* @param {string}
|
|
456
|
-
* @param {string} hashPrefix hashbang prefix
|
|
297
|
+
* Parse given HTML5 (regular) URL string into properties
|
|
298
|
+
* @param {string} url HTML5 URL
|
|
457
299
|
*/
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
300
|
+
parse(url) {
|
|
301
|
+
if (this.html5) {
|
|
302
|
+
const pathUrl = stripBaseUrl(this.appBaseNoFile, url);
|
|
303
|
+
if (!isString(pathUrl)) {
|
|
304
|
+
throw $locationMinErr(
|
|
305
|
+
"ipthprfx",
|
|
306
|
+
'Invalid url "{0}", missing path prefix "{1}".',
|
|
307
|
+
url,
|
|
308
|
+
this.appBaseNoFile,
|
|
309
|
+
);
|
|
310
|
+
}
|
|
462
311
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
$$parse(url) {
|
|
468
|
-
const withoutBaseUrl =
|
|
469
|
-
stripBaseUrl(this.appBase, url) || stripBaseUrl(this.appBaseNoFile, url);
|
|
470
|
-
let withoutHashUrl;
|
|
471
|
-
|
|
472
|
-
if (!isUndefined(withoutBaseUrl) && withoutBaseUrl.charAt(0) === "#") {
|
|
473
|
-
// The rest of the URL starts with a hash so we have
|
|
474
|
-
// got either a hashbang path or a plain hash fragment
|
|
475
|
-
withoutHashUrl = stripBaseUrl(this.hashPrefix, withoutBaseUrl);
|
|
476
|
-
if (isUndefined(withoutHashUrl)) {
|
|
477
|
-
// There was no hashbang prefix so we just have a hash fragment
|
|
478
|
-
withoutHashUrl = withoutBaseUrl;
|
|
312
|
+
parseAppUrl(pathUrl, true);
|
|
313
|
+
|
|
314
|
+
if (!$$path) {
|
|
315
|
+
$$path = "/";
|
|
479
316
|
}
|
|
317
|
+
|
|
318
|
+
this.$$compose();
|
|
480
319
|
} else {
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
320
|
+
const withoutBaseUrl =
|
|
321
|
+
stripBaseUrl(this.appBase, url) ||
|
|
322
|
+
stripBaseUrl(this.appBaseNoFile, url);
|
|
323
|
+
let withoutHashUrl;
|
|
324
|
+
|
|
325
|
+
if (!isUndefined(withoutBaseUrl) && withoutBaseUrl.charAt(0) === "#") {
|
|
326
|
+
// The rest of the URL starts with a hash so we have
|
|
327
|
+
// got either a hashbang path or a plain hash fragment
|
|
328
|
+
withoutHashUrl = stripBaseUrl(this.hashPrefix, withoutBaseUrl);
|
|
329
|
+
if (isUndefined(withoutHashUrl)) {
|
|
330
|
+
// There was no hashbang prefix so we just have a hash fragment
|
|
331
|
+
withoutHashUrl = withoutBaseUrl;
|
|
332
|
+
}
|
|
486
333
|
} else {
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
334
|
+
// There was no hashbang path nor hash fragment:
|
|
335
|
+
// If we are in HTML5 mode we use what is left as the path;
|
|
336
|
+
// Otherwise we ignore what is left
|
|
337
|
+
if (this.html5) {
|
|
338
|
+
withoutHashUrl = withoutBaseUrl;
|
|
339
|
+
} else {
|
|
340
|
+
withoutHashUrl = "";
|
|
341
|
+
if (isUndefined(withoutBaseUrl)) {
|
|
342
|
+
this.appBase = url;
|
|
343
|
+
}
|
|
491
344
|
}
|
|
492
345
|
}
|
|
493
|
-
}
|
|
494
346
|
|
|
495
|
-
|
|
347
|
+
parseAppUrl(withoutHashUrl, false);
|
|
496
348
|
|
|
497
|
-
|
|
498
|
-
this.$$path,
|
|
499
|
-
withoutHashUrl,
|
|
500
|
-
this.appBase,
|
|
501
|
-
);
|
|
349
|
+
$$path = removeWindowsDriveName($$path, withoutHashUrl, this.appBase);
|
|
502
350
|
|
|
503
|
-
|
|
351
|
+
this.$$compose();
|
|
504
352
|
|
|
505
|
-
/*
|
|
506
|
-
* In Windows, on an anchor node on documents loaded from
|
|
507
|
-
* the filesystem, the browser will return a pathname
|
|
508
|
-
* prefixed with the drive name ('/C:/path') when a
|
|
509
|
-
* pathname without a drive is set:
|
|
510
|
-
* * a.setAttribute('href', '/foo')
|
|
511
|
-
* * a.pathname === '/C:/foo' //true
|
|
512
|
-
*
|
|
513
|
-
* Inside of AngularTS, we're always using pathnames that
|
|
514
|
-
* do not include drive names for routing.
|
|
515
|
-
*/
|
|
516
|
-
function removeWindowsDriveName(path, url, base) {
|
|
517
353
|
/*
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
354
|
+
* In Windows, on an anchor node on documents loaded from
|
|
355
|
+
* the filesystem, the browser will return a pathname
|
|
356
|
+
* prefixed with the drive name ('/C:/path') when a
|
|
357
|
+
* pathname without a drive is set:
|
|
358
|
+
* * a.setAttribute('href', '/foo')
|
|
359
|
+
* * a.pathname === '/C:/foo' //true
|
|
360
|
+
*
|
|
361
|
+
* Inside of AngularTS, we're always using pathnames that
|
|
362
|
+
* do not include drive names for routing.
|
|
363
|
+
*/
|
|
364
|
+
function removeWindowsDriveName(path, url, base) {
|
|
365
|
+
/*
|
|
366
|
+
Matches paths for file protocol on windows,
|
|
367
|
+
such as /C:/foo/bar, and captures only /foo/bar.
|
|
368
|
+
*/
|
|
369
|
+
const windowsFilePathExp = /^\/[A-Z]:(\/.*)/;
|
|
370
|
+
|
|
371
|
+
let firstPathSegmentMatch;
|
|
372
|
+
|
|
373
|
+
// Get the relative path from the input URL.
|
|
374
|
+
if (startsWith(url, base)) {
|
|
375
|
+
url = url.replace(base, "");
|
|
376
|
+
}
|
|
524
377
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
378
|
+
// The input URL intentionally contains a first path segment that ends with a colon.
|
|
379
|
+
if (windowsFilePathExp.exec(url)) {
|
|
380
|
+
return path;
|
|
381
|
+
}
|
|
529
382
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
return path;
|
|
383
|
+
firstPathSegmentMatch = windowsFilePathExp.exec(path);
|
|
384
|
+
return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path;
|
|
533
385
|
}
|
|
534
|
-
|
|
535
|
-
firstPathSegmentMatch = windowsFilePathExp.exec(path);
|
|
536
|
-
return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path;
|
|
537
386
|
}
|
|
538
387
|
}
|
|
539
|
-
|
|
540
|
-
$$normalizeUrl(url) {
|
|
541
|
-
return this.appBase + (url ? this.hashPrefix + url : "");
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
/**
|
|
545
|
-
* @param {string} url
|
|
546
|
-
* @returns {boolean}
|
|
547
|
-
*/
|
|
548
|
-
$$parseLinkUrl(url) {
|
|
549
|
-
if (stripHash(this.appBase) === stripHash(url)) {
|
|
550
|
-
this.$$parse(url);
|
|
551
|
-
return true;
|
|
552
|
-
}
|
|
553
|
-
return false;
|
|
554
|
-
}
|
|
555
388
|
}
|
|
556
389
|
|
|
557
390
|
export class LocationProvider {
|
|
@@ -559,20 +392,20 @@ export class LocationProvider {
|
|
|
559
392
|
/** @type {string} */
|
|
560
393
|
this.hashPrefixConf = "!";
|
|
561
394
|
|
|
562
|
-
/** @type {Html5Mode} */
|
|
395
|
+
/** @type {import("./interface.ts").Html5Mode} */
|
|
563
396
|
this.html5ModeConf = {
|
|
564
|
-
enabled:
|
|
565
|
-
requireBase:
|
|
397
|
+
enabled: true,
|
|
398
|
+
requireBase: false,
|
|
566
399
|
rewriteLinks: true,
|
|
567
400
|
};
|
|
568
401
|
|
|
569
|
-
/** @type {Array<import("./interface.
|
|
402
|
+
/** @type {Array<import("./interface.ts").UrlChangeListener>} */
|
|
570
403
|
this.urlChangeListeners = [];
|
|
571
404
|
this.urlChangeInit = false;
|
|
572
405
|
|
|
573
406
|
/** @type {History['state']} */
|
|
574
407
|
this.cachedState = null;
|
|
575
|
-
/** @
|
|
408
|
+
/** @type {History['state']} */
|
|
576
409
|
this.lastHistoryState = null;
|
|
577
410
|
/** @type {string} */
|
|
578
411
|
this.lastBrowserUrl = window.location.href;
|
|
@@ -583,14 +416,19 @@ export class LocationProvider {
|
|
|
583
416
|
// URL API
|
|
584
417
|
/// ///////////////////////////////////////////////////////////
|
|
585
418
|
|
|
419
|
+
/**
|
|
420
|
+
* Updates the browser's current URL and history state.
|
|
421
|
+
*
|
|
422
|
+
* @param {string|undefined} url - The target URL to navigate to.
|
|
423
|
+
* @param {*} [state=null] - Optional history state object to associate with the new URL.
|
|
424
|
+
* @returns {LocationProvider}
|
|
425
|
+
*/
|
|
586
426
|
setUrl(url, state) {
|
|
587
427
|
if (state === undefined) {
|
|
588
428
|
state = null;
|
|
589
429
|
}
|
|
590
|
-
|
|
591
|
-
// setter
|
|
592
430
|
if (url) {
|
|
593
|
-
url =
|
|
431
|
+
url = new URL(url).href;
|
|
594
432
|
|
|
595
433
|
if (this.lastBrowserUrl === url && this.lastHistoryState === state) {
|
|
596
434
|
return this;
|
|
@@ -607,7 +445,7 @@ export class LocationProvider {
|
|
|
607
445
|
* Returns the current URL with any empty hash (`#`) removed.
|
|
608
446
|
* @return {string}
|
|
609
447
|
*/
|
|
610
|
-
|
|
448
|
+
getBrowserUrl() {
|
|
611
449
|
return trimEmptyHash(window.location.href);
|
|
612
450
|
}
|
|
613
451
|
|
|
@@ -635,19 +473,17 @@ export class LocationProvider {
|
|
|
635
473
|
|
|
636
474
|
/**
|
|
637
475
|
* Fires the state or URL change event.
|
|
638
|
-
*
|
|
639
|
-
* @private
|
|
640
476
|
*/
|
|
641
|
-
fireStateOrUrlChange() {
|
|
477
|
+
#fireStateOrUrlChange() {
|
|
642
478
|
const prevLastHistoryState = this.lastHistoryState;
|
|
643
479
|
this.cacheState();
|
|
644
480
|
if (
|
|
645
|
-
this.lastBrowserUrl === this.
|
|
481
|
+
this.lastBrowserUrl === this.getBrowserUrl() &&
|
|
646
482
|
prevLastHistoryState === this.cachedState
|
|
647
483
|
) {
|
|
648
484
|
return;
|
|
649
485
|
}
|
|
650
|
-
this.lastBrowserUrl = this.
|
|
486
|
+
this.lastBrowserUrl = this.getBrowserUrl();
|
|
651
487
|
this.lastHistoryState = this.cachedState;
|
|
652
488
|
this.urlChangeListeners.forEach((listener) => {
|
|
653
489
|
listener(trimEmptyHash(window.location.href), this.cachedState);
|
|
@@ -660,131 +496,75 @@ export class LocationProvider {
|
|
|
660
496
|
* @param {import("./interface.js").UrlChangeListener} callback - The callback function to register.
|
|
661
497
|
* @returns void
|
|
662
498
|
*/
|
|
663
|
-
onUrlChange(callback) {
|
|
499
|
+
#onUrlChange(callback) {
|
|
664
500
|
if (!this.urlChangeInit) {
|
|
665
|
-
window.addEventListener(
|
|
501
|
+
window.addEventListener(
|
|
502
|
+
"popstate",
|
|
503
|
+
this.#fireStateOrUrlChange.bind(this),
|
|
504
|
+
);
|
|
666
505
|
window.addEventListener(
|
|
667
506
|
"hashchange",
|
|
668
|
-
this
|
|
507
|
+
this.#fireStateOrUrlChange.bind(this),
|
|
669
508
|
);
|
|
670
509
|
this.urlChangeInit = true;
|
|
671
510
|
}
|
|
672
511
|
this.urlChangeListeners.push(callback);
|
|
673
512
|
}
|
|
674
513
|
|
|
675
|
-
/**
|
|
676
|
-
* The default value for the prefix is `'!'`.
|
|
677
|
-
* @param {string=} prefix Prefix for hash part (containing path and search)
|
|
678
|
-
* @returns {void}
|
|
679
|
-
*/
|
|
680
|
-
setHashPrefix(prefix) {
|
|
681
|
-
this.hashPrefixConf = prefix;
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
/**
|
|
685
|
-
* Current hash prefix
|
|
686
|
-
* @returns {string}
|
|
687
|
-
*/
|
|
688
|
-
getHashPrefix() {
|
|
689
|
-
return this.hashPrefixConf;
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
/**
|
|
693
|
-
* Configures html5 mode
|
|
694
|
-
* @param {(boolean|Html5Mode)=} mode If boolean, sets `html5Mode.enabled` to value. Otherwise, accepts html5Mode object
|
|
695
|
-
*
|
|
696
|
-
* @returns {void}
|
|
697
|
-
*/
|
|
698
|
-
setHtml5Mode(mode) {
|
|
699
|
-
if (isBoolean(mode)) {
|
|
700
|
-
this.html5ModeConf.enabled = /** @type {boolean} */ (mode);
|
|
701
|
-
}
|
|
702
|
-
if (isObject(mode)) {
|
|
703
|
-
const html5Mode = /** @type {Html5Mode} */ (mode);
|
|
704
|
-
if (isDefined(html5Mode.enabled) && isBoolean(html5Mode.enabled)) {
|
|
705
|
-
this.html5ModeConf.enabled = html5Mode.enabled;
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
if (
|
|
709
|
-
isDefined(html5Mode.requireBase) &&
|
|
710
|
-
isBoolean(html5Mode.requireBase)
|
|
711
|
-
) {
|
|
712
|
-
this.html5ModeConf.requireBase = html5Mode.requireBase;
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
if (
|
|
716
|
-
isDefined(html5Mode.rewriteLinks) &&
|
|
717
|
-
(isBoolean(html5Mode.rewriteLinks) || isString(html5Mode.rewriteLinks))
|
|
718
|
-
) {
|
|
719
|
-
this.html5ModeConf.rewriteLinks = html5Mode.rewriteLinks;
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
/**
|
|
725
|
-
* Returns html5 mode cofiguration
|
|
726
|
-
* @returns {Html5Mode}
|
|
727
|
-
*/
|
|
728
|
-
getHtml5Mode() {
|
|
729
|
-
return this.html5ModeConf;
|
|
730
|
-
}
|
|
731
|
-
|
|
732
514
|
$get = [
|
|
733
|
-
|
|
734
|
-
|
|
515
|
+
$t.$rootScope,
|
|
516
|
+
$t.$rootElement,
|
|
735
517
|
/**
|
|
736
518
|
*
|
|
737
519
|
* @param {import('../../core/scope/scope.js').Scope} $rootScope
|
|
738
520
|
* @param {Element} $rootElement
|
|
739
|
-
* @returns
|
|
521
|
+
* @returns {Location}
|
|
740
522
|
*/
|
|
741
523
|
($rootScope, $rootElement) => {
|
|
742
524
|
/** @type {Location} */
|
|
743
525
|
let $location;
|
|
744
|
-
let LocationMode;
|
|
745
526
|
const baseHref = getBaseHref(); // if base[href] is undefined, it defaults to ''
|
|
746
527
|
const initialUrl = trimEmptyHash(window.location.href);
|
|
747
528
|
let appBase;
|
|
748
529
|
|
|
749
|
-
if (this.
|
|
750
|
-
if (!baseHref && this.
|
|
530
|
+
if (this.html5ModeConf.enabled) {
|
|
531
|
+
if (!baseHref && this.html5ModeConf.requireBase) {
|
|
751
532
|
throw $locationMinErr(
|
|
752
533
|
"nobase",
|
|
753
534
|
"$location in HTML5 mode requires a <base> tag to be present!",
|
|
754
535
|
);
|
|
755
536
|
}
|
|
756
537
|
appBase = serverBase(initialUrl) + (baseHref || "/");
|
|
757
|
-
LocationMode = LocationHtml5Url;
|
|
758
538
|
} else {
|
|
759
539
|
appBase = stripHash(initialUrl);
|
|
760
|
-
LocationMode = LocationHashbangUrl;
|
|
761
540
|
}
|
|
762
541
|
const appBaseNoFile = stripFile(appBase);
|
|
763
542
|
|
|
764
|
-
$location = new
|
|
543
|
+
$location = new Location(
|
|
765
544
|
appBase,
|
|
766
545
|
appBaseNoFile,
|
|
767
|
-
|
|
546
|
+
this.html5ModeConf.enabled,
|
|
547
|
+
`#${this.hashPrefixConf}`,
|
|
768
548
|
);
|
|
769
|
-
$location
|
|
549
|
+
$location.parseLinkUrl(initialUrl, initialUrl);
|
|
770
550
|
|
|
771
551
|
$location.$$state = this.state();
|
|
772
552
|
|
|
773
553
|
const IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i;
|
|
774
554
|
|
|
775
555
|
const setBrowserUrlWithFallback = (url, state) => {
|
|
776
|
-
const oldUrl = $location.
|
|
556
|
+
const oldUrl = $location.getUrl();
|
|
777
557
|
const oldState = $location.$$state;
|
|
778
558
|
try {
|
|
779
559
|
this.setUrl(url, state);
|
|
780
560
|
|
|
781
|
-
// Make sure $location.
|
|
561
|
+
// Make sure $location.getState() returns referentially identical (not just deeply equal)
|
|
782
562
|
// state object; this makes possible quick checking if the state changed in the digest
|
|
783
563
|
// loop. Checking deep equality would be too expensive.
|
|
784
564
|
$location.$$state = this.state();
|
|
785
565
|
} catch (e) {
|
|
786
566
|
// Restore old values if pushState fails
|
|
787
|
-
$location.
|
|
567
|
+
$location.setUrl(/** @type {string} */ (oldUrl));
|
|
788
568
|
$location.$$state = oldState;
|
|
789
569
|
|
|
790
570
|
throw e;
|
|
@@ -795,7 +575,7 @@ export class LocationProvider {
|
|
|
795
575
|
"click",
|
|
796
576
|
/** @param {MouseEvent} event */
|
|
797
577
|
(event) => {
|
|
798
|
-
const rewriteLinks = this.
|
|
578
|
+
const rewriteLinks = this.html5ModeConf.rewriteLinks;
|
|
799
579
|
// TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
|
|
800
580
|
// currently we open nice url link and redirect then
|
|
801
581
|
|
|
@@ -804,7 +584,6 @@ export class LocationProvider {
|
|
|
804
584
|
event.ctrlKey ||
|
|
805
585
|
event.metaKey ||
|
|
806
586
|
event.shiftKey ||
|
|
807
|
-
event.which === 2 ||
|
|
808
587
|
event.button === 2
|
|
809
588
|
) {
|
|
810
589
|
return;
|
|
@@ -839,7 +618,7 @@ export class LocationProvider {
|
|
|
839
618
|
// an animation.
|
|
840
619
|
|
|
841
620
|
const scvAnimatedString = /** @type {unknown} */ (absHref);
|
|
842
|
-
absHref =
|
|
621
|
+
absHref = new URL(
|
|
843
622
|
/** @type {SVGAnimatedString } */ (scvAnimatedString).animVal,
|
|
844
623
|
).href;
|
|
845
624
|
}
|
|
@@ -852,7 +631,7 @@ export class LocationProvider {
|
|
|
852
631
|
!elm.getAttribute("target") &&
|
|
853
632
|
!event.defaultPrevented
|
|
854
633
|
) {
|
|
855
|
-
if ($location
|
|
634
|
+
if ($location.parseLinkUrl(absHref, relHref)) {
|
|
856
635
|
// We do a preventDefault for all urls that are part of the AngularTS application,
|
|
857
636
|
// in html5mode and also without, so that we are able to abort navigation without
|
|
858
637
|
// getting double entries in the location history.
|
|
@@ -863,14 +642,14 @@ export class LocationProvider {
|
|
|
863
642
|
);
|
|
864
643
|
|
|
865
644
|
// rewrite hashbang url <> html5 url
|
|
866
|
-
if ($location.absUrl
|
|
867
|
-
this.setUrl($location.absUrl
|
|
645
|
+
if ($location.absUrl !== initialUrl) {
|
|
646
|
+
this.setUrl($location.absUrl, true);
|
|
868
647
|
}
|
|
869
648
|
|
|
870
649
|
let initializing = true;
|
|
871
650
|
|
|
872
651
|
// update $location when $browser url changes
|
|
873
|
-
this
|
|
652
|
+
this.#onUrlChange((newUrl, newState) => {
|
|
874
653
|
if (!startsWith(newUrl, appBaseNoFile)) {
|
|
875
654
|
// If we are navigating outside of the app then force a reload
|
|
876
655
|
window.location.href = newUrl;
|
|
@@ -878,10 +657,10 @@ export class LocationProvider {
|
|
|
878
657
|
}
|
|
879
658
|
|
|
880
659
|
Promise.resolve().then(() => {
|
|
881
|
-
const oldUrl = $location.absUrl
|
|
660
|
+
const oldUrl = $location.absUrl;
|
|
882
661
|
const oldState = $location.$$state;
|
|
883
662
|
let defaultPrevented;
|
|
884
|
-
$location
|
|
663
|
+
$location.parse(newUrl);
|
|
885
664
|
$location.$$state = newState;
|
|
886
665
|
|
|
887
666
|
defaultPrevented = $rootScope.$broadcast(
|
|
@@ -894,10 +673,10 @@ export class LocationProvider {
|
|
|
894
673
|
|
|
895
674
|
// if the location was changed by a `$locationChangeStart` handler then stop
|
|
896
675
|
// processing this location change
|
|
897
|
-
if ($location.absUrl
|
|
676
|
+
if ($location.absUrl !== newUrl) return;
|
|
898
677
|
|
|
899
678
|
if (defaultPrevented) {
|
|
900
|
-
$location
|
|
679
|
+
$location.parse(oldUrl);
|
|
901
680
|
$location.$$state = oldState;
|
|
902
681
|
setBrowserUrlWithFallback(oldUrl, oldState);
|
|
903
682
|
} else {
|
|
@@ -909,21 +688,21 @@ export class LocationProvider {
|
|
|
909
688
|
|
|
910
689
|
// update browser
|
|
911
690
|
const updateBrowser = () => {
|
|
912
|
-
if (initializing ||
|
|
913
|
-
|
|
691
|
+
if (initializing || urlUpdatedByLocation) {
|
|
692
|
+
urlUpdatedByLocation = false;
|
|
914
693
|
|
|
915
|
-
const oldUrl = /** @type {string} */ (this.
|
|
916
|
-
const newUrl = $location.absUrl
|
|
694
|
+
const oldUrl = /** @type {string} */ (this.getBrowserUrl());
|
|
695
|
+
const newUrl = $location.absUrl;
|
|
917
696
|
const oldState = this.state();
|
|
918
697
|
const urlOrStateChanged =
|
|
919
698
|
!urlsEqual(oldUrl, newUrl) ||
|
|
920
|
-
($location
|
|
699
|
+
($location.html5 && oldState !== $location.$$state);
|
|
921
700
|
|
|
922
701
|
if (initializing || urlOrStateChanged) {
|
|
923
702
|
initializing = false;
|
|
924
703
|
|
|
925
704
|
setTimeout(() => {
|
|
926
|
-
const newUrl = $location.absUrl
|
|
705
|
+
const newUrl = $location.absUrl;
|
|
927
706
|
const { defaultPrevented } = $rootScope.$broadcast(
|
|
928
707
|
"$locationChangeStart",
|
|
929
708
|
newUrl,
|
|
@@ -934,10 +713,10 @@ export class LocationProvider {
|
|
|
934
713
|
|
|
935
714
|
// if the location was changed by a `$locationChangeStart` handler then stop
|
|
936
715
|
// processing this location change
|
|
937
|
-
if ($location.absUrl
|
|
716
|
+
if ($location.absUrl !== newUrl) return;
|
|
938
717
|
|
|
939
718
|
if (defaultPrevented) {
|
|
940
|
-
$location
|
|
719
|
+
$location.parse(oldUrl);
|
|
941
720
|
$location.$$state = oldState;
|
|
942
721
|
} else {
|
|
943
722
|
if (urlOrStateChanged) {
|
|
@@ -951,13 +730,8 @@ export class LocationProvider {
|
|
|
951
730
|
});
|
|
952
731
|
}
|
|
953
732
|
}
|
|
954
|
-
|
|
955
|
-
$location.$$replace = false;
|
|
956
|
-
|
|
957
|
-
// we don't need to return anything because $evalAsync will make the digest loop dirty when
|
|
958
|
-
// there is a change
|
|
959
733
|
};
|
|
960
|
-
|
|
734
|
+
$location.$$updateBrowser = updateBrowser;
|
|
961
735
|
updateBrowser();
|
|
962
736
|
$rootScope.$on("$updateBrowser", updateBrowser);
|
|
963
737
|
|
|
@@ -966,7 +740,7 @@ export class LocationProvider {
|
|
|
966
740
|
function afterLocationChange(oldUrl, oldState) {
|
|
967
741
|
$rootScope.$broadcast(
|
|
968
742
|
"$locationChangeSuccess",
|
|
969
|
-
$location.absUrl
|
|
743
|
+
$location.absUrl,
|
|
970
744
|
oldUrl,
|
|
971
745
|
$location.$$state,
|
|
972
746
|
oldState,
|
|
@@ -978,29 +752,64 @@ export class LocationProvider {
|
|
|
978
752
|
|
|
979
753
|
/**
|
|
980
754
|
* ///////////////////////////
|
|
981
|
-
*
|
|
755
|
+
* PRIVATE HELPERS
|
|
982
756
|
* ///////////////////////////
|
|
983
757
|
*/
|
|
984
758
|
|
|
985
759
|
/**
|
|
986
|
-
*
|
|
760
|
+
* @ignore
|
|
761
|
+
* Encodes a URL path by encoding each path segment individually using `encodeUriSegment`,
|
|
762
|
+
* while preserving forward slashes (`/`) as segment separators.
|
|
987
763
|
*
|
|
988
|
-
*
|
|
989
|
-
*
|
|
764
|
+
* This function first decodes any existing percent-encodings (such as `%20` or `%2F`)
|
|
765
|
+
* in each segment to prevent double encoding, except for encoded forward slashes (`%2F`),
|
|
766
|
+
* which are replaced with literal slashes before decoding to keep path boundaries intact.
|
|
767
|
+
*
|
|
768
|
+
* After decoding, each segment is re-encoded with `encodeUriSegment` according to RFC 3986,
|
|
769
|
+
* encoding only characters that must be encoded in a path segment.
|
|
770
|
+
*
|
|
771
|
+
* The encoded segments are then rejoined with `/` to form the encoded path.
|
|
772
|
+
*
|
|
773
|
+
* @param {string} path - The URL path string to encode. May contain multiple segments separated by `/`.
|
|
774
|
+
* @returns {string} The encoded path, where each segment is encoded, but forward slashes are preserved.
|
|
775
|
+
*
|
|
776
|
+
* @example
|
|
777
|
+
* encodePath("user profile/images/pic 1.jpg")
|
|
778
|
+
* // returns "user%20profile/images/pic%201.jpg"
|
|
779
|
+
*
|
|
780
|
+
* @example
|
|
781
|
+
* encodePath("folder1%2Fsub/folder2")
|
|
782
|
+
* // returns "folder1%2Fsub/folder2"
|
|
990
783
|
*/
|
|
991
|
-
function encodePath(path) {
|
|
784
|
+
export function encodePath(path) {
|
|
992
785
|
const segments = path.split("/");
|
|
993
786
|
let i = segments.length;
|
|
994
787
|
|
|
995
788
|
while (i--) {
|
|
996
|
-
//
|
|
997
|
-
|
|
789
|
+
// Decode any existing encodings (e.g. %20, %2F) to prevent double-encoding
|
|
790
|
+
// But keep slashes intact (they were split on)
|
|
791
|
+
const decodedSegment = decodeURIComponent(
|
|
792
|
+
segments[i].replace(/%2F/gi, "/"),
|
|
793
|
+
);
|
|
794
|
+
segments[i] = encodeUriSegment(decodedSegment);
|
|
998
795
|
}
|
|
999
796
|
|
|
1000
797
|
return segments.join("/");
|
|
1001
798
|
}
|
|
1002
799
|
|
|
1003
|
-
|
|
800
|
+
/**
|
|
801
|
+
* @ignore
|
|
802
|
+
* Decodes each segment of a URL path.
|
|
803
|
+
*
|
|
804
|
+
* Splits the input path by "/", decodes each segment using decodeURIComponent,
|
|
805
|
+
* and if html5Mode is enabled, re-encodes any forward slashes inside segments
|
|
806
|
+
* as "%2F" to avoid confusion with path separators.
|
|
807
|
+
*
|
|
808
|
+
* @param {string} path - The URL path to decode.
|
|
809
|
+
* @param {boolean} html5Mode - If true, encodes forward slashes in segments as "%2F".
|
|
810
|
+
* @returns {string} The decoded path with segments optionally encoding slashes.
|
|
811
|
+
*/
|
|
812
|
+
export function decodePath(path, html5Mode) {
|
|
1004
813
|
const segments = path.split("/");
|
|
1005
814
|
let i = segments.length;
|
|
1006
815
|
|
|
@@ -1015,7 +824,34 @@ function decodePath(path, html5Mode) {
|
|
|
1015
824
|
return segments.join("/");
|
|
1016
825
|
}
|
|
1017
826
|
|
|
1018
|
-
|
|
827
|
+
/**
|
|
828
|
+
* @ignore
|
|
829
|
+
* Normalizes a URL path by encoding the path segments, query parameters, and hash fragment.
|
|
830
|
+
*
|
|
831
|
+
* - Path segments are encoded using `encodePath`, which encodes each segment individually.
|
|
832
|
+
* - Query parameters (`searchValue`) are converted to a query string using `toKeyValue`.
|
|
833
|
+
* - Hash fragment (`hashValue`) is encoded using `encodeUriSegment` and prefixed with `#`.
|
|
834
|
+
*
|
|
835
|
+
* This function returns a fully constructed URL path with optional query and hash components.
|
|
836
|
+
*
|
|
837
|
+
* @param {string} pathValue - The base URL path (e.g., "folder/item name").
|
|
838
|
+
* @param {Object.<string, any> | string | null} searchValue - An object or string representing query parameters.
|
|
839
|
+
* - If an object, it can contain strings, numbers, booleans, or arrays of values.
|
|
840
|
+
* - If a string, it is assumed to be a raw query string.
|
|
841
|
+
* - If null or undefined, no query string is added.
|
|
842
|
+
* @param {string | null} hashValue - The URL fragment (everything after `#`). If null or undefined, no hash is added.
|
|
843
|
+
*
|
|
844
|
+
* @returns {string} The normalized URL path including encoded path, optional query string, and optional hash.
|
|
845
|
+
*
|
|
846
|
+
* @example
|
|
847
|
+
* normalizePath("products/list", { category: "books", page: 2 }, "section1")
|
|
848
|
+
* // returns "products/list?category=books&page=2#section1"
|
|
849
|
+
*
|
|
850
|
+
* @example
|
|
851
|
+
* normalizePath("user profile/images", null, null)
|
|
852
|
+
* // returns "user%20profile/images"
|
|
853
|
+
*/
|
|
854
|
+
export function normalizePath(pathValue, searchValue, hashValue) {
|
|
1019
855
|
const search = toKeyValue(searchValue);
|
|
1020
856
|
const hash = hashValue ? `#${encodeUriSegment(hashValue)}` : "";
|
|
1021
857
|
const path = encodePath(pathValue);
|
|
@@ -1023,7 +859,15 @@ function normalizePath(pathValue, searchValue, hashValue) {
|
|
|
1023
859
|
return path + (search ? `?${search}` : "") + hash;
|
|
1024
860
|
}
|
|
1025
861
|
|
|
1026
|
-
|
|
862
|
+
/**
|
|
863
|
+
* @ignore
|
|
864
|
+
* Parses the application URL and updates the location object with path, search, and hash.
|
|
865
|
+
*
|
|
866
|
+
* @param {string} url - The URL string to parse.
|
|
867
|
+
* @param {boolean} html5Mode - Whether HTML5 mode is enabled (affects decoding).
|
|
868
|
+
* @throws Will throw an error if the URL starts with invalid slashes.
|
|
869
|
+
*/
|
|
870
|
+
export function parseAppUrl(url, html5Mode) {
|
|
1027
871
|
if (/^\s*[\\/]{2,}/.test(url)) {
|
|
1028
872
|
throw $locationMinErr("badpath", 'Invalid url "{0}".', url);
|
|
1029
873
|
}
|
|
@@ -1037,22 +881,20 @@ function parseAppUrl(url, locationObj, html5Mode) {
|
|
|
1037
881
|
prefixed && match.pathname.charAt(0) === "/"
|
|
1038
882
|
? match.pathname.substring(1)
|
|
1039
883
|
: match.pathname;
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
884
|
+
$$path = decodePath(path, html5Mode);
|
|
885
|
+
$$search = parseKeyValue(match.search);
|
|
886
|
+
$$hash = decodeURIComponent(match.hash);
|
|
1043
887
|
|
|
1044
888
|
// make sure path starts with '/';
|
|
1045
|
-
if (
|
|
1046
|
-
|
|
889
|
+
if ($$path && $$path.charAt(0) !== "/") {
|
|
890
|
+
$$path = `/${$$path}`;
|
|
1047
891
|
}
|
|
1048
892
|
}
|
|
1049
893
|
|
|
1050
|
-
function startsWith(str, search) {
|
|
1051
|
-
return str.slice(0, search.length) === search;
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
894
|
/**
|
|
1055
|
-
*
|
|
895
|
+
* @ignore
|
|
896
|
+
* Returns the substring of `url` after the `base` string if `url` starts with `base`.
|
|
897
|
+
* Returns `undefined` if `url` does not start with `base`.
|
|
1056
898
|
* @param {string} base
|
|
1057
899
|
* @param {string} url
|
|
1058
900
|
* @returns {string} returns text from `url` after `base` or `undefined` if it does not begin with
|
|
@@ -1064,23 +906,94 @@ export function stripBaseUrl(base, url) {
|
|
|
1064
906
|
}
|
|
1065
907
|
}
|
|
1066
908
|
|
|
909
|
+
/**
|
|
910
|
+
* @ignore
|
|
911
|
+
* Removes the hash fragment (including the '#') from the given URL string.
|
|
912
|
+
*
|
|
913
|
+
* @param {string} url - The URL string to process.
|
|
914
|
+
* @returns {string} The URL without the hash fragment.
|
|
915
|
+
*/
|
|
1067
916
|
export function stripHash(url) {
|
|
1068
917
|
const index = url.indexOf("#");
|
|
1069
918
|
return index === -1 ? url : url.substring(0, index);
|
|
1070
919
|
}
|
|
1071
920
|
|
|
921
|
+
/**
|
|
922
|
+
* @ignore
|
|
923
|
+
* Removes the file name (and any hash) from a URL, returning the base directory path.
|
|
924
|
+
*
|
|
925
|
+
* For example:
|
|
926
|
+
* - Input: "https://example.com/path/to/file.js"
|
|
927
|
+
* Output: "https://example.com/path/to/"
|
|
928
|
+
*
|
|
929
|
+
* - Input: "https://example.com/path/to/file.js#section"
|
|
930
|
+
* Output: "https://example.com/path/to/"
|
|
931
|
+
*
|
|
932
|
+
* @param {string} url - The URL from which to strip the file name and hash.
|
|
933
|
+
* @returns {string} The base path of the URL, ending with a slash.
|
|
934
|
+
*/
|
|
1072
935
|
export function stripFile(url) {
|
|
1073
936
|
return url.substring(0, stripHash(url).lastIndexOf("/") + 1);
|
|
1074
937
|
}
|
|
1075
938
|
|
|
1076
|
-
|
|
939
|
+
/**
|
|
940
|
+
* @ignore
|
|
941
|
+
* Extracts the base server URL (scheme, host, and optional port) from a full URL.
|
|
942
|
+
*
|
|
943
|
+
* If no path is present, returns the full URL.
|
|
944
|
+
*
|
|
945
|
+
* For example:
|
|
946
|
+
* - Input: "https://example.com/path/to/file"
|
|
947
|
+
* Output: "https://example.com"
|
|
948
|
+
*
|
|
949
|
+
* - Input: "http://localhost:3000/api/data"
|
|
950
|
+
* Output: "http://localhost:3000"
|
|
951
|
+
*
|
|
952
|
+
* @param {string} url - The full URL to extract the server base from.
|
|
953
|
+
* @returns {string} The server base, including scheme and host (and port if present).
|
|
954
|
+
*/
|
|
1077
955
|
export function serverBase(url) {
|
|
1078
|
-
|
|
956
|
+
const start = url.indexOf("//") + 2;
|
|
957
|
+
const slashIndex = url.indexOf("/", start);
|
|
958
|
+
return slashIndex === -1 ? url : url.substring(0, slashIndex);
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
/**
|
|
962
|
+
* @ignore
|
|
963
|
+
* Determine if two URLs are equal despite potential differences in encoding,
|
|
964
|
+
* trailing slashes, or empty hash fragments, such as between $location.absUrl() and $browser.url().
|
|
965
|
+
*
|
|
966
|
+
* @param {string} a - First URL to compare.
|
|
967
|
+
* @param {string} b - Second URL to compare.
|
|
968
|
+
* @returns {boolean} True if URLs are equivalent after normalization.
|
|
969
|
+
*/
|
|
970
|
+
export function urlsEqual(a, b) {
|
|
971
|
+
return normalizeUrl(a) === normalizeUrl(b);
|
|
1079
972
|
}
|
|
1080
973
|
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
974
|
+
/**
|
|
975
|
+
* @ignore
|
|
976
|
+
* Normalize a URL by resolving it via a DOM anchor element,
|
|
977
|
+
* removing trailing slashes (except root), and trimming empty hashes.
|
|
978
|
+
*
|
|
979
|
+
* @param {string} url - URL to normalize.
|
|
980
|
+
* @returns {string} Normalized URL string.
|
|
981
|
+
*/
|
|
982
|
+
function normalizeUrl(url) {
|
|
983
|
+
const anchor = document.createElement("a");
|
|
984
|
+
anchor.href = url;
|
|
985
|
+
|
|
986
|
+
let normalized = anchor.href;
|
|
987
|
+
|
|
988
|
+
// Remove trailing slash unless it's root (e.g., https://example.com/)
|
|
989
|
+
if (normalized.endsWith("/") && !/^https?:\/\/[^/]+\/$/.test(normalized)) {
|
|
990
|
+
normalized = normalized.slice(0, -1);
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
// Remove empty hash (e.g., https://example.com/foo# -> https://example.com/foo)
|
|
994
|
+
if (normalized.endsWith("#")) {
|
|
995
|
+
normalized = normalized.slice(0, -1);
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
return normalized;
|
|
1086
999
|
}
|