@angular-wave/angular.ts 0.0.67 → 0.0.68

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.
@@ -2,7 +2,6 @@ import { JQLite } from "../../shared/jqlite/jqlite";
2
2
  import { urlResolve } from "../url-utils/url-utils";
3
3
  import {
4
4
  encodeUriSegment,
5
- forEach,
6
5
  isBoolean,
7
6
  isDefined,
8
7
  isNumber,
@@ -16,406 +15,107 @@ import {
16
15
  } from "../../shared/utils";
17
16
  import { ScopePhase } from "../scope/scope";
18
17
 
19
- export const PATH_MATCH = /^([^?#]*)(\?([^#]*))?(#(.*))?$/;
20
- const DEFAULT_PORTS = { http: 80, https: 443, ftp: 21 };
21
- const $locationMinErr = minErr("$location");
22
-
23
- /**
24
- * Encode path using encodeUriSegment, ignoring forward slashes
25
- *
26
- * @param {string} path Path to encode
27
- * @returns {string}
28
- */
29
- function encodePath(path) {
30
- const segments = path.split("/");
31
- let i = segments.length;
32
-
33
- while (i--) {
34
- // decode forward slashes to prevent them from being double encoded
35
- segments[i] = encodeUriSegment(segments[i].replace(/%2F/g, "/"));
36
- }
37
-
38
- return segments.join("/");
39
- }
40
-
41
- function decodePath(path, html5Mode) {
42
- const segments = path.split("/");
43
- let i = segments.length;
44
-
45
- while (i--) {
46
- segments[i] = decodeURIComponent(segments[i]);
47
- if (html5Mode) {
48
- // encode forward slashes to prevent them from being mistaken for path separators
49
- segments[i] = segments[i].replace(/\//g, "%2F");
50
- }
51
- }
52
-
53
- return segments.join("/");
54
- }
55
-
56
- function normalizePath(pathValue, searchValue, hashValue) {
57
- const search = toKeyValue(searchValue);
58
- const hash = hashValue ? `#${encodeUriSegment(hashValue)}` : "";
59
- const path = encodePath(pathValue);
60
-
61
- return path + (search ? `?${search}` : "") + hash;
62
- }
63
-
64
- function parseAbsoluteUrl(absoluteUrl, locationObj) {
65
- const parsedUrl = urlResolve(absoluteUrl);
66
-
67
- locationObj.$$protocol = parsedUrl.protocol;
68
- locationObj.$$host = parsedUrl.hostname;
69
- locationObj.$$port =
70
- toInt(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null;
71
- }
72
-
73
- const DOUBLE_SLASH_REGEX = /^\s*[\\/]{2,}/;
74
- function parseAppUrl(url, locationObj, html5Mode) {
75
- if (DOUBLE_SLASH_REGEX.test(url)) {
76
- throw $locationMinErr("badpath", 'Invalid url "{0}".', url);
77
- }
78
-
79
- const prefixed = url.charAt(0) !== "/";
80
- if (prefixed) {
81
- url = `/${url}`;
82
- }
83
- const match = urlResolve(url);
84
- const path =
85
- prefixed && match.pathname.charAt(0) === "/"
86
- ? match.pathname.substring(1)
87
- : match.pathname;
88
- locationObj.$$path = decodePath(path, html5Mode);
89
- locationObj.$$search = parseKeyValue(match.search);
90
- locationObj.$$hash = decodeURIComponent(match.hash);
91
-
92
- // make sure path starts with '/';
93
- if (locationObj.$$path && locationObj.$$path.charAt(0) !== "/") {
94
- locationObj.$$path = `/${locationObj.$$path}`;
95
- }
96
- }
97
-
98
- function startsWith(str, search) {
99
- return str.slice(0, search.length) === search;
100
- }
101
-
102
18
  /**
103
- *
104
- * @param {string} base
105
- * @param {string} url
106
- * @returns {string} returns text from `url` after `base` or `undefined` if it does not begin with
107
- * the expected string.
19
+ * @typedef {Object} DefaultPorts
20
+ * @property {number} http
21
+ * @property {number} https
22
+ * @property {number} ftp
108
23
  */
109
- export function stripBaseUrl(base, url) {
110
- if (startsWith(url, base)) {
111
- return url.substr(base.length);
112
- }
113
- }
114
-
115
- export function stripHash(url) {
116
- const index = url.indexOf("#");
117
- return index === -1 ? url : url.substr(0, index);
118
- }
119
-
120
- export function stripFile(url) {
121
- return url.substr(0, stripHash(url).lastIndexOf("/") + 1);
122
- }
123
-
124
- /* return the server only (scheme://host:port) */
125
- export function serverBase(url) {
126
- return url.substring(0, url.indexOf("/", url.indexOf("//") + 2));
127
- }
128
24
 
129
25
  /**
130
- * LocationHtml5Url represents a URL
131
- * This object is exposed as $location service when HTML5 mode is enabled and supported
132
- *
133
- * @constructor
134
- * @param {string} appBase application base URL
135
- * @param {string} appBaseNoFile application base URL stripped of any filename
136
- * @param {string} basePrefix URL path prefix
26
+ * @typedef {Object} Html5Mode
27
+ * @property {boolean} enabled
28
+ * @property {boolean} requireBase
29
+ * @property {boolean|string} rewriteLinks
137
30
  */
138
- export function LocationHtml5Url(appBase, appBaseNoFile, basePrefix) {
139
- this.$$html5 = true;
140
- basePrefix = basePrefix || "";
141
- parseAbsoluteUrl(appBase, this);
142
-
143
- /**
144
- * Parse given HTML5 (regular) URL string into properties
145
- * @param {string} url HTML5 URL
146
- * @private
147
- */
148
- this.$$parse = function (url) {
149
- const pathUrl = stripBaseUrl(appBaseNoFile, url);
150
- if (!isString(pathUrl)) {
151
- throw $locationMinErr(
152
- "ipthprfx",
153
- 'Invalid url "{0}", missing path prefix "{1}".',
154
- url,
155
- appBaseNoFile,
156
- );
157
- }
158
-
159
- parseAppUrl(pathUrl, this, true);
160
-
161
- if (!this.$$path) {
162
- this.$$path = "/";
163
- }
164
-
165
- this.$$compose();
166
- };
167
-
168
- this.$$normalizeUrl = function (url) {
169
- return appBaseNoFile + url.substr(1); // first char is always '/'
170
- };
171
31
 
172
- this.$$parseLinkUrl = function (url, relHref) {
173
- if (relHref && relHref[0] === "#") {
174
- // special case for links to hash fragments:
175
- // keep the old url and only replace the hash fragment
176
- this.hash(relHref.slice(1));
177
- return true;
178
- }
179
- let appUrl;
180
- let prevAppUrl;
181
- let rewrittenUrl;
182
-
183
- if (isDefined((appUrl = stripBaseUrl(appBase, url)))) {
184
- prevAppUrl = appUrl;
185
- if (
186
- basePrefix &&
187
- isDefined((appUrl = stripBaseUrl(basePrefix, appUrl)))
188
- ) {
189
- rewrittenUrl = appBaseNoFile + (stripBaseUrl("/", appUrl) || appUrl);
190
- } else {
191
- rewrittenUrl = appBase + prevAppUrl;
192
- }
193
- } else if (isDefined((appUrl = stripBaseUrl(appBaseNoFile, url)))) {
194
- rewrittenUrl = appBaseNoFile + appUrl;
195
- } else if (appBaseNoFile === `${url}/`) {
196
- rewrittenUrl = appBaseNoFile;
197
- }
198
- if (rewrittenUrl) {
199
- this.$$parse(rewrittenUrl);
200
- }
201
- return !!rewrittenUrl;
202
- };
203
- }
32
+ /** @type {DefaultPorts} */
33
+ const DEFAULT_PORTS = { http: 80, https: 443, ftp: 21 };
34
+ const PATH_MATCH = /^([^?#]*)(\?([^#]*))?(#(.*))?$/;
35
+ const $locationMinErr = minErr("$location");
204
36
 
205
37
  /**
206
- * LocationHashbangUrl represents URL
207
- * This object is exposed as $location service when developer doesn't opt into html5 mode.
208
- * It also serves as the base class for html5 mode fallback on legacy browsers.
209
- *
210
- * @constructor
211
- * @param {string} appBase application base URL
212
- * @param {string} appBaseNoFile application base URL stripped of any filename
213
- * @param {string} hashPrefix hashbang prefix
38
+ * @abstract
214
39
  */
215
- export function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) {
216
- parseAbsoluteUrl(appBase, this);
217
-
40
+ export class Location {
218
41
  /**
219
- * Parse given hashbang URL into properties
220
- * @param {string} url Hashbang URL
221
- * @private
42
+ * @param {string} appBase application base URL
43
+ * @param {string} appBaseNoFile application base URL stripped of any filename
222
44
  */
223
- this.$$parse = function (url) {
224
- const withoutBaseUrl =
225
- stripBaseUrl(appBase, url) || stripBaseUrl(appBaseNoFile, url);
226
- let withoutHashUrl;
227
-
228
- if (!isUndefined(withoutBaseUrl) && withoutBaseUrl.charAt(0) === "#") {
229
- // The rest of the URL starts with a hash so we have
230
- // got either a hashbang path or a plain hash fragment
231
- withoutHashUrl = stripBaseUrl(hashPrefix, withoutBaseUrl);
232
- if (isUndefined(withoutHashUrl)) {
233
- // There was no hashbang prefix so we just have a hash fragment
234
- withoutHashUrl = withoutBaseUrl;
235
- }
236
- } else {
237
- // There was no hashbang path nor hash fragment:
238
- // If we are in HTML5 mode we use what is left as the path;
239
- // Otherwise we ignore what is left
240
- if (this.$$html5) {
241
- withoutHashUrl = withoutBaseUrl;
242
- } else {
243
- withoutHashUrl = "";
244
- if (isUndefined(withoutBaseUrl)) {
245
- appBase = url;
246
- /** @type {?} */ (this).replace();
247
- }
248
- }
249
- }
45
+ constructor(appBase, appBaseNoFile) {
46
+ /** @type {string} */
47
+ this.appBase = appBase;
250
48
 
251
- parseAppUrl(withoutHashUrl, this, false);
252
-
253
- this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase);
49
+ /** @type {string} */
50
+ this.appBaseNoFile = appBaseNoFile;
254
51
 
255
- this.$$compose();
256
-
257
- /*
258
- * In Windows, on an anchor node on documents loaded from
259
- * the filesystem, the browser will return a pathname
260
- * prefixed with the drive name ('/C:/path') when a
261
- * pathname without a drive is set:
262
- * * a.setAttribute('href', '/foo')
263
- * * a.pathname === '/C:/foo' //true
264
- *
265
- * Inside of AngularJS, we're always using pathnames that
266
- * do not include drive names for routing.
52
+ /**
53
+ * 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).
54
+ * @type {string}
267
55
  */
268
- function removeWindowsDriveName(path, url, base) {
269
- /*
270
- Matches paths for file protocol on windows,
271
- such as /C:/foo/bar, and captures only /foo/bar.
272
- */
273
- const windowsFilePathExp = /^\/[A-Z]:(\/.*)/;
56
+ this.$$absUrl = "";
274
57
 
275
- let firstPathSegmentMatch;
276
-
277
- // Get the relative path from the input URL.
278
- if (startsWith(url, base)) {
279
- url = url.replace(base, "");
280
- }
281
-
282
- // The input URL intentionally contains a first path segment that ends with a colon.
283
- if (windowsFilePathExp.exec(url)) {
284
- return path;
285
- }
286
-
287
- firstPathSegmentMatch = windowsFilePathExp.exec(path);
288
- return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path;
289
- }
290
- };
291
-
292
- this.$$normalizeUrl = function (url) {
293
- return appBase + (url ? hashPrefix + url : "");
294
- };
295
-
296
- this.$$parseLinkUrl = function (url) {
297
- if (stripHash(appBase) === stripHash(url)) {
298
- this.$$parse(url);
299
- return true;
300
- }
301
- return false;
302
- };
303
- }
304
-
305
- /**
306
- * LocationHashbangUrl represents URL
307
- * This object is exposed as $location service when html5 history api is enabled but the browser
308
- * does not support it.
309
- *
310
- * @constructor
311
- * @param {string} appBase application base URL
312
- * @param {string} appBaseNoFile application base URL stripped of any filename
313
- * @param {string} hashPrefix hashbang prefix
314
- */
315
- export function LocationHashbangInHtml5Url(appBase, appBaseNoFile, hashPrefix) {
316
- this.$$html5 = true;
317
- LocationHashbangUrl.apply(this, arguments);
318
-
319
- this.$$parseLinkUrl = function (url, relHref) {
320
- if (relHref && relHref[0] === "#") {
321
- // special case for links to hash fragments:
322
- // keep the old url and only replace the hash fragment
323
- this.hash(relHref.slice(1));
324
- return true;
325
- }
58
+ /**
59
+ * If html5 mode is enabled
60
+ * @type {boolean}
61
+ */
62
+ this.$$html5 = false;
326
63
 
327
- let rewrittenUrl;
328
- let appUrl;
64
+ /**
65
+ * Has any change been replacing?
66
+ * @type {boolean}
67
+ */
68
+ this.$$replace = false;
329
69
 
330
- if (appBase === stripHash(url)) {
331
- rewrittenUrl = url;
332
- } else if ((appUrl = stripBaseUrl(appBaseNoFile, url))) {
333
- rewrittenUrl = appBase + hashPrefix + appUrl;
334
- } else if (appBaseNoFile === `${url}/`) {
335
- rewrittenUrl = appBaseNoFile;
336
- }
337
- if (rewrittenUrl) {
338
- this.$$parse(rewrittenUrl);
339
- }
340
- return !!rewrittenUrl;
341
- };
70
+ /** @type {import('../url-utils/url-utils').HttpProtocol} */
71
+ this.$$protocol = undefined;
342
72
 
343
- this.$$normalizeUrl = function (url) {
344
- // include hashPrefix in $$absUrl when $$url is empty so IE9 does not reload page because of removal of '#'
345
- return appBase + hashPrefix + url;
346
- };
347
- }
73
+ /** @type {string} */
74
+ this.$$host = undefined;
348
75
 
349
- const locationPrototype = {
350
- /**
351
- * Ensure absolute URL is initialized.
352
- * @private
353
- */
354
- $$absUrl: "",
76
+ /**
77
+ * The port, without ":"
78
+ * @type {number}
79
+ */
80
+ this.$$port = undefined;
355
81
 
356
- /**
357
- * Are we in html5 mode?
358
- * @private
359
- */
360
- $$html5: false,
82
+ /**
83
+ * The pathname, beginning with "/"
84
+ * @type {string}
85
+ */
86
+ this.$$path = undefined;
361
87
 
362
- /**
363
- * Has any change been replacing?
364
- * @private
365
- */
366
- $$replace: false,
88
+ /**
89
+ * The hash string, minus the hash symbol
90
+ * @type {string}
91
+ */
92
+ this.$$hash = undefined;
367
93
 
368
- /**
369
- * Compose url and update `url` and `absUrl` property
370
- * @private
371
- */
372
- $$compose() {
373
- this.$$url = normalizePath(this.$$path, this.$$search, this.$$hash);
374
- this.$$absUrl = this.$$normalizeUrl(this.$$url);
375
- this.$$urlUpdatedByLocation = true;
376
- },
94
+ /**
95
+ * Helper property for scope watch changes
96
+ * @type {boolean}
97
+ */
98
+ this.$$urlUpdatedByLocation = false;
99
+ }
377
100
 
378
101
  /**
379
- * @ngdoc method
380
- * @name $location#absUrl
381
- *
382
- * @description
383
- * This method is getter only.
384
- *
385
102
  * Return full URL representation with all segments encoded according to rules specified in
386
103
  * [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt).
387
104
  *
388
- *
389
- * ```js
390
- * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
391
- * let absUrl = $location.absUrl();
392
- * // => "http://example.com/#/some/path?foo=bar&baz=xoxo"
393
- * ```
394
- *
395
105
  * @return {string} full URL
396
106
  */
397
- absUrl: locationGetter("$$absUrl"),
107
+ absUrl() {
108
+ return this.$$absUrl;
109
+ }
398
110
 
399
111
  /**
400
- * @ngdoc method
401
- * @name $location#url
402
- *
403
- * @description
404
112
  * This method is getter / setter.
405
113
  *
406
114
  * Return URL (e.g. `/path?a=b#hash`) when called without any parameter.
407
- *
408
115
  * Change path, search and hash, when called with parameter and return `$location`.
409
116
  *
410
- *
411
- * ```js
412
- * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
413
- * let url = $location.url();
414
- * // => "/some/path?foo=bar&baz=xoxo"
415
- * ```
416
- *
417
117
  * @param {string=} url New URL without base prefix (e.g. `/path?a=b#hash`)
418
- * @return {string} url
118
+ * @return {Location|string} url
419
119
  */
420
120
  url(url) {
421
121
  if (isUndefined(url)) {
@@ -428,33 +128,18 @@ const locationPrototype = {
428
128
  this.hash(match[5] || "");
429
129
 
430
130
  return this;
431
- },
131
+ }
432
132
 
433
133
  /**
434
- * @ngdoc method
435
- * @name $location#protocol
436
- *
437
- * @description
438
- * This method is getter only.
439
134
  *
440
135
  * Return protocol of current URL.
441
- *
442
- *
443
- * ```js
444
- * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
445
- * let protocol = $location.protocol();
446
- * // => "http"
447
- * ```
448
- *
449
- * @return {string} protocol of current URL
136
+ * @return {import("../url-utils/url-utils").HttpProtocol} protocol of current URL
450
137
  */
451
- protocol: locationGetter("$$protocol"),
138
+ protocol() {
139
+ return this.$$protocol;
140
+ }
452
141
 
453
142
  /**
454
- * @ngdoc method
455
- * @name $location#host
456
- *
457
- * @description
458
143
  * This method is getter only.
459
144
  *
460
145
  * Return host of current URL.
@@ -462,27 +147,13 @@ const locationPrototype = {
462
147
  * Note: compared to the non-AngularJS version `location.host` which returns `hostname:port`, this returns the `hostname` portion only.
463
148
  *
464
149
  *
465
- * ```js
466
- * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
467
- * let host = $location.host();
468
- * // => "example.com"
469
- *
470
- * // given URL http://user:password@example.com:8080/#/some/path?foo=bar&baz=xoxo
471
- * host = $location.host();
472
- * // => "example.com"
473
- * host = location.host;
474
- * // => "example.com:8080"
475
- * ```
476
- *
477
150
  * @return {string} host of current URL.
478
151
  */
479
- host: locationGetter("$$host"),
152
+ host() {
153
+ return this.$$host;
154
+ }
480
155
 
481
156
  /**
482
- * @ngdoc method
483
- * @name $location#port
484
- *
485
- * @description
486
157
  * This method is getter only.
487
158
  *
488
159
  * Return port of current URL.
@@ -494,15 +165,13 @@ const locationPrototype = {
494
165
  * // => 80
495
166
  * ```
496
167
  *
497
- * @return {Number} port
168
+ * @return {number} port
498
169
  */
499
- port: locationGetter("$$port"),
170
+ port() {
171
+ return this.$$port;
172
+ }
500
173
 
501
174
  /**
502
- * @ngdoc method
503
- * @name $location#path
504
- *
505
- * @description
506
175
  * This method is getter / setter.
507
176
  *
508
177
  * Return path of current URL when called without any parameter.
@@ -522,55 +191,58 @@ const locationPrototype = {
522
191
  * @param {(string|number)=} path New path
523
192
  * @return {(string|object)} path if called with no parameters, or `$location` if called with a parameter
524
193
  */
525
- path: locationGetterSetter("$$path", (path) => {
526
- path = path !== null ? path.toString() : "";
527
- return path.charAt(0) === "/" ? path : `/${path}`;
528
- }),
194
+ path(path) {
195
+ if (isUndefined(path)) {
196
+ return this.$$path;
197
+ }
198
+ let newPath = path !== null ? path.toString() : "";
199
+ this.$$path = newPath.charAt(0) === "/" ? newPath : `/${newPath}`;
200
+ this.$$compose();
201
+ return this;
202
+ }
529
203
 
530
204
  /**
531
- * @ngdoc method
532
- * @name $location#search
533
- *
534
- * @description
535
205
  * This method is getter / setter.
536
206
  *
537
- * Return search part (as object) of current URL when called without any parameter.
207
+ * Returns the hash fragment when called without any parameters.
538
208
  *
539
- * Change search part when called with parameter and return `$location`.
209
+ * Changes the hash fragment when called with a parameter and returns `$location`.
540
210
  *
541
211
  *
542
212
  * ```js
543
- * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
544
- * let searchObject = $location.search();
545
- * // => {foo: 'bar', baz: 'xoxo'}
546
- *
547
- * // set foo to 'yipee'
548
- * $location.search('foo', 'yipee');
549
- * // $location.search() => {foo: 'yipee', baz: 'xoxo'}
213
+ * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue
214
+ * let hash = $location.hash();
215
+ * // => "hashValue"
550
216
  * ```
551
217
  *
552
- * @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or
553
- * hash object.
554
- *
555
- * When called with a single argument the method acts as a setter, setting the `search` component
556
- * of `$location` to the specified value.
557
- *
558
- * If the argument is a hash object containing an array of values, these values will be encoded
559
- * as duplicate search parameters in the URL.
560
- *
561
- * @param {(string|Number|Array<string>|boolean)=} paramValue If `search` is a string or number, then `paramValue`
562
- * will override only a single search property.
563
- *
564
- * If `paramValue` is an array, it will override the property of the `search` component of
565
- * `$location` specified via the first argument.
566
- *
567
- * If `paramValue` is `null`, the property specified via the first argument will be deleted.
568
- *
569
- * If `paramValue` is `true`, the property specified via the first argument will be added with no
570
- * value nor trailing equal sign.
218
+ * @param {(string|number)=} hash New hash fragment
219
+ * @return {string|Location} hash
220
+ */
221
+ hash(hash) {
222
+ if (isUndefined(hash)) {
223
+ return this.$$hash;
224
+ }
225
+
226
+ this.$$hash = hash !== null ? hash.toString() : "";
227
+ this.$$compose();
228
+ return this;
229
+ }
230
+
231
+ /**
232
+ * If called, all changes to $location during the current `$digest` will replace the current history
233
+ * record, instead of adding a new one.
234
+ */
235
+ replace() {
236
+ this.$$replace = true;
237
+ return this;
238
+ }
239
+
240
+ /**
241
+ * Returns or sets the search part (as object) of current URL when called without any parameter
571
242
  *
572
- * @return {Object} If called with no arguments returns the parsed `search` object. If called with
573
- * one or more arguments returns `$location` object itself.
243
+ * @param {string|Object=} search New search params - string or hash object.
244
+ * @param {(string|number|Array<string>|boolean)=} paramValue If search is a string or number, then paramValue will override only a single search property.
245
+ * @returns {Object|Location} Search object or Location object
574
246
  */
575
247
  search(search, paramValue) {
576
248
  switch (arguments.length) {
@@ -603,149 +275,253 @@ const locationPrototype = {
603
275
  }
604
276
  }
605
277
 
606
- this.$$compose();
607
- return this;
608
- },
278
+ this.$$compose();
279
+ return this;
280
+ }
281
+
282
+ /**
283
+ * Compose url and update `url` and `absUrl` property
284
+ * @returns {void}
285
+ */
286
+ $$compose() {
287
+ this.$$url = normalizePath(this.$$path, this.$$search, this.$$hash);
288
+ this.$$absUrl = this.$$normalizeUrl(this.$$url);
289
+ this.$$urlUpdatedByLocation = true;
290
+ }
291
+
292
+ /**
293
+ * @param {string} _url
294
+ * @returns {string}
295
+ */
296
+ $$normalizeUrl(_url) {
297
+ throw new Error(`Method not implemented ${_url}`);
298
+ }
299
+
300
+ /**
301
+ * This method is getter / setter.
302
+ *
303
+ * Return the history state object when called without any parameter.
304
+ *
305
+ * Change the history state object when called with one parameter and return `$location`.
306
+ * The state object is later passed to `pushState` or `replaceState`.
307
+ * See {@link https://developer.mozilla.org/en-US/docs/Web/API/History/pushState#state|History.state}
308
+ *
309
+ * NOTE: This method is supported only in HTML5 mode and only in browsers supporting
310
+ * the HTML5 History API (i.e. methods `pushState` and `replaceState`). If you need to support
311
+ * older browsers (like IE9 or Android < 4.0), don't use this method.
312
+ *
313
+ * @param {any} state State object for pushState or replaceState
314
+ * @return {any} state
315
+ */
316
+ state(state) {
317
+ if (!arguments.length) {
318
+ return this.$$state;
319
+ }
320
+
321
+ if (!(this instanceof LocationHtml5Url) || !this.$$html5) {
322
+ throw $locationMinErr(
323
+ "nostate",
324
+ "History API state support is available only " +
325
+ "in HTML5 mode and only in browsers supporting HTML5 History API",
326
+ );
327
+ }
328
+ // The user might modify `stateObject` after invoking `$location.state(stateObject)`
329
+ // but we're changing the $$state reference to $browser.state() during the $digest
330
+ // so the modification window is narrow.
331
+ this.$$state = isUndefined(state) ? null : state;
332
+ this.$$urlUpdatedByLocation = true;
333
+
334
+ return this;
335
+ }
336
+ }
337
+
338
+ /**
339
+ * This object is exposed as $location service when HTML5 mode is enabled and supported
340
+ */
341
+ export class LocationHtml5Url extends Location {
342
+ /**
343
+ * @param {string} appBase application base URL
344
+ * @param {string} appBaseNoFile application base URL stripped of any filename
345
+ * @param {string} basePrefix URL path prefix
346
+ */
347
+ constructor(appBase, appBaseNoFile, basePrefix) {
348
+ super(appBase, appBaseNoFile);
349
+ this.$$html5 = true;
350
+ this.basePrefix = basePrefix || "";
351
+ parseAbsoluteUrl(appBase, this);
352
+ }
353
+
354
+ /**
355
+ * Parse given HTML5 (regular) URL string into properties
356
+ * @param {string} url HTML5 URL
357
+ */
358
+ $$parse(url) {
359
+ const pathUrl = stripBaseUrl(this.appBaseNoFile, url);
360
+ if (!isString(pathUrl)) {
361
+ throw $locationMinErr(
362
+ "ipthprfx",
363
+ 'Invalid url "{0}", missing path prefix "{1}".',
364
+ url,
365
+ this.appBaseNoFile,
366
+ );
367
+ }
368
+
369
+ parseAppUrl(pathUrl, this, true);
370
+
371
+ if (!this.$$path) {
372
+ this.$$path = "/";
373
+ }
374
+
375
+ this.$$compose();
376
+ }
377
+
378
+ $$normalizeUrl(url) {
379
+ return this.appBaseNoFile + url.substr(1); // first char is always '/'
380
+ }
381
+
382
+ $$parseLinkUrl = function (url, relHref) {
383
+ if (relHref && relHref[0] === "#") {
384
+ // special case for links to hash fragments:
385
+ // keep the old url and only replace the hash fragment
386
+ this.hash(relHref.slice(1));
387
+ return true;
388
+ }
389
+ let appUrl;
390
+ let prevAppUrl;
391
+ let rewrittenUrl;
392
+
393
+ if (isDefined((appUrl = stripBaseUrl(this.appBase, url)))) {
394
+ prevAppUrl = appUrl;
395
+ if (
396
+ this.basePrefix &&
397
+ isDefined((appUrl = stripBaseUrl(this.basePrefix, appUrl)))
398
+ ) {
399
+ rewrittenUrl =
400
+ this.appBaseNoFile + (stripBaseUrl("/", appUrl) || appUrl);
401
+ } else {
402
+ rewrittenUrl = this.appBase + prevAppUrl;
403
+ }
404
+ } else if (isDefined((appUrl = stripBaseUrl(this.appBaseNoFile, url)))) {
405
+ rewrittenUrl = this.appBaseNoFile + appUrl;
406
+ } else if (this.appBaseNoFile === `${url}/`) {
407
+ rewrittenUrl = this.appBaseNoFile;
408
+ }
409
+ if (rewrittenUrl) {
410
+ this.$$parse(rewrittenUrl);
411
+ }
412
+ return !!rewrittenUrl;
413
+ };
414
+ }
415
+
416
+ /**
417
+ * LocationHashbangUrl represents URL
418
+ * This object is exposed as $location service when developer doesn't opt into html5 mode.
419
+ * It also serves as the base class for html5 mode fallback on legacy browsers.
420
+ *
421
+ * @constructor
422
+ * @param {string} appBase application base URL
423
+ * @param {string} appBaseNoFile application base URL stripped of any filename
424
+ * @param {string} hashPrefix hashbang prefix
425
+ */
426
+ export class LocationHashbangUrl extends Location {
427
+ constructor(appBase, appBaseNoFile, hashPrefix) {
428
+ super(appBase, appBaseNoFile);
429
+ this.appBase = appBase;
430
+ this.appBaseNoFile = appBaseNoFile;
431
+ this.hashPrefix = hashPrefix;
432
+ parseAbsoluteUrl(appBase, this);
433
+ }
609
434
 
610
435
  /**
611
- * @ngdoc method
612
- * @name $location#hash
613
- *
614
- * @description
615
- * This method is getter / setter.
616
- *
617
- * Returns the hash fragment when called without any parameters.
618
- *
619
- * Changes the hash fragment when called with a parameter and returns `$location`.
620
- *
621
- *
622
- * ```js
623
- * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue
624
- * let hash = $location.hash();
625
- * // => "hashValue"
626
- * ```
627
- *
628
- * @param {(string|number)=} hash New hash fragment
629
- * @return {string} hash
436
+ * Parse given hashbang URL into properties
437
+ * @param {string} url Hashbang URL
630
438
  */
631
- hash: locationGetterSetter("$$hash", (hash) =>
632
- hash !== null ? hash.toString() : "",
633
- ),
439
+ $$parse(url) {
440
+ const withoutBaseUrl =
441
+ stripBaseUrl(this.appBase, url) || stripBaseUrl(this.appBaseNoFile, url);
442
+ let withoutHashUrl;
634
443
 
635
- /**
636
- * @ngdoc method
637
- * @name $location#replace
638
- *
639
- * @description
640
- * If called, all changes to $location during the current `$digest` will replace the current history
641
- * record, instead of adding a new one.
642
- */
643
- replace() {
644
- this.$$replace = true;
645
- return this;
646
- },
647
- };
444
+ if (!isUndefined(withoutBaseUrl) && withoutBaseUrl.charAt(0) === "#") {
445
+ // The rest of the URL starts with a hash so we have
446
+ // got either a hashbang path or a plain hash fragment
447
+ withoutHashUrl = stripBaseUrl(this.hashPrefix, withoutBaseUrl);
448
+ if (isUndefined(withoutHashUrl)) {
449
+ // There was no hashbang prefix so we just have a hash fragment
450
+ withoutHashUrl = withoutBaseUrl;
451
+ }
452
+ } else {
453
+ // There was no hashbang path nor hash fragment:
454
+ // If we are in HTML5 mode we use what is left as the path;
455
+ // Otherwise we ignore what is left
456
+ if (this.$$html5) {
457
+ withoutHashUrl = withoutBaseUrl;
458
+ } else {
459
+ withoutHashUrl = "";
460
+ if (isUndefined(withoutBaseUrl)) {
461
+ this.appBase = url;
462
+ /** @type {?} */ (this).replace();
463
+ }
464
+ }
465
+ }
648
466
 
649
- forEach(
650
- [LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url],
651
- (Location) => {
652
- Location.prototype = Object.create(locationPrototype);
467
+ parseAppUrl(withoutHashUrl, this, false);
653
468
 
654
- /**
655
- * @ngdoc method
656
- * @name $location#state
657
- *
658
- * @description
659
- * This method is getter / setter.
660
- *
661
- * Return the history state object when called without any parameter.
662
- *
663
- * Change the history state object when called with one parameter and return `$location`.
664
- * The state object is later passed to `pushState` or `replaceState`.
665
- *
666
- * NOTE: This method is supported only in HTML5 mode and only in browsers supporting
667
- * the HTML5 History API (i.e. methods `pushState` and `replaceState`). If you need to support
668
- * older browsers (like IE9 or Android < 4.0), don't use this method.
469
+ this.$$path = removeWindowsDriveName(
470
+ this.$$path,
471
+ withoutHashUrl,
472
+ this.appBase,
473
+ );
474
+
475
+ this.$$compose();
476
+
477
+ /*
478
+ * In Windows, on an anchor node on documents loaded from
479
+ * the filesystem, the browser will return a pathname
480
+ * prefixed with the drive name ('/C:/path') when a
481
+ * pathname without a drive is set:
482
+ * * a.setAttribute('href', '/foo')
483
+ * * a.pathname === '/C:/foo' //true
669
484
  *
670
- * @param {object=} state State object for pushState or replaceState
671
- * @return {object} state
485
+ * Inside of AngularJS, we're always using pathnames that
486
+ * do not include drive names for routing.
672
487
  */
673
- Location.prototype.state = function (state) {
674
- if (!arguments.length) {
675
- return this.$$state;
676
- }
488
+ function removeWindowsDriveName(path, url, base) {
489
+ /*
490
+ Matches paths for file protocol on windows,
491
+ such as /C:/foo/bar, and captures only /foo/bar.
492
+ */
493
+ const windowsFilePathExp = /^\/[A-Z]:(\/.*)/;
677
494
 
678
- if (Location !== LocationHtml5Url || !this.$$html5) {
679
- throw $locationMinErr(
680
- "nostate",
681
- "History API state support is available only " +
682
- "in HTML5 mode and only in browsers supporting HTML5 History API",
683
- );
684
- }
685
- // The user might modify `stateObject` after invoking `$location.state(stateObject)`
686
- // but we're changing the $$state reference to $browser.state() during the $digest
687
- // so the modification window is narrow.
688
- this.$$state = isUndefined(state) ? null : state;
689
- this.$$urlUpdatedByLocation = true;
495
+ let firstPathSegmentMatch;
690
496
 
691
- return this;
692
- };
693
- },
694
- );
497
+ // Get the relative path from the input URL.
498
+ if (startsWith(url, base)) {
499
+ url = url.replace(base, "");
500
+ }
695
501
 
696
- function locationGetter(property) {
697
- return function () {
698
- return this[property];
699
- };
700
- }
502
+ // The input URL intentionally contains a first path segment that ends with a colon.
503
+ if (windowsFilePathExp.exec(url)) {
504
+ return path;
505
+ }
701
506
 
702
- function locationGetterSetter(property, preprocess) {
703
- return function (value) {
704
- if (isUndefined(value)) {
705
- return this[property];
507
+ firstPathSegmentMatch = windowsFilePathExp.exec(path);
508
+ return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path;
706
509
  }
510
+ }
707
511
 
708
- this[property] = preprocess(value);
709
- this.$$compose();
512
+ $$normalizeUrl(url) {
513
+ return this.appBase + (url ? this.hashPrefix + url : "");
514
+ }
710
515
 
711
- return this;
712
- };
516
+ $$parseLinkUrl(url) {
517
+ if (stripHash(this.appBase) === stripHash(url)) {
518
+ this.$$parse(url);
519
+ return true;
520
+ }
521
+ return false;
522
+ }
713
523
  }
714
524
 
715
- /**
716
- * @ngdoc service
717
- * @name $location
718
- *
719
- * @requires $rootElement
720
- *
721
- * @description
722
- * The $location service parses the URL in the browser address bar (based on the
723
- * [window.location](https://developer.mozilla.org/en/window.location)) and makes the URL
724
- * available to your application. Changes to the URL in the address bar are reflected into
725
- * $location service and changes to $location are reflected into the browser address bar.
726
- *
727
- * **The $location service:**
728
- *
729
- * - Exposes the current URL in the browser address bar, so you can
730
- * - Watch and observe the URL.
731
- * - Change the URL.
732
- * - Synchronizes the URL with the browser when the user
733
- * - Changes the address bar.
734
- * - Clicks the back or forward button (or clicks a History link).
735
- * - Clicks on a link.
736
- * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash).
737
- *
738
- * For more information see {@link guide/$location Developer Guide: Using $location}
739
- */
740
-
741
- /**
742
- * @ngdoc provider
743
- * @name $locationProvider
744
- *
745
- *
746
- * @description
747
- * Use the `$locationProvider` to configure how the application deep linking paths are stored.
748
- */
749
525
  export function $LocationProvider() {
750
526
  let hashPrefix = "!";
751
527
  const html5Mode = {
@@ -755,9 +531,6 @@ export function $LocationProvider() {
755
531
  };
756
532
 
757
533
  /**
758
- * @ngdoc method
759
- * @name $locationProvider#hashPrefix
760
- * @description
761
534
  * The default value for the prefix is `'!'`.
762
535
  * @param {string=} prefix Prefix for hash part (containing path and search)
763
536
  * @returns {*} current value if used as getter or itself (chaining) if used as setter
@@ -771,9 +544,6 @@ export function $LocationProvider() {
771
544
  };
772
545
 
773
546
  /**
774
- * @ngdoc method
775
- * @name $locationProvider#html5Mode
776
- * @description
777
547
  * @param {(boolean|Object)=} mode If boolean, sets `html5Mode.enabled` to value.
778
548
  * If object, sets `enabled`, `requireBase` and `rewriteLinks` to respective values. Supported
779
549
  * properties:
@@ -816,57 +586,19 @@ export function $LocationProvider() {
816
586
  return html5Mode;
817
587
  };
818
588
 
819
- /**
820
- * @ngdoc event
821
- * @name $location#$locationChangeStart
822
- * @eventType broadcast on root scope
823
- * @description
824
- * Broadcasted before a URL will change.
825
- *
826
- * This change can be prevented by calling
827
- * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more
828
- * details about event object. Upon successful change
829
- * {@link ng.$location#$locationChangeSuccess $locationChangeSuccess} is fired.
830
- *
831
- * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
832
- * the browser supports the HTML5 History API.
833
- *
834
- * @param {Object} angularEvent Synthetic event object.
835
- * @param {string} newUrl New URL
836
- * @param {string=} oldUrl URL that was before it was changed.
837
- * @param {string=} newState New history state object
838
- * @param {string=} oldState History state object that was before it was changed.
839
- */
840
-
841
- /**
842
- * @ngdoc event
843
- * @name $location#$locationChangeSuccess
844
- * @eventType broadcast on root scope
845
- * @description
846
- * Broadcasted after a URL was changed.
847
- *
848
- * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
849
- * the browser supports the HTML5 History API.
850
- *
851
- * @param {Object} angularEvent Synthetic event object.
852
- * @param {string} newUrl New URL
853
- * @param {string=} oldUrl URL that was before it was changed.
854
- * @param {string=} newState New history state object
855
- * @param {string=} oldState History state object that was before it was changed.
856
- */
857
-
858
589
  this.$get = [
859
590
  "$rootScope",
860
591
  "$browser",
861
592
  "$rootElement",
862
593
  /**
863
594
  *
864
- * @param {*} $rootScope
865
- * @param {*} $browser
595
+ * @param {import('../scope/scope').Scope} $rootScope
596
+ * @param {import('../../services/browser').Browser} $browser
866
597
  * @param {JQLite} $rootElement
867
598
  * @returns
868
599
  */
869
600
  function ($rootScope, $browser, $rootElement) {
601
+ /** @type {Location} */
870
602
  let $location;
871
603
  let LocationMode;
872
604
  const baseHref = $browser.baseHref(); // if base[href] is undefined, it defaults to ''
@@ -895,13 +627,6 @@ export function $LocationProvider() {
895
627
 
896
628
  const IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i;
897
629
 
898
- // Determine if two URLs are equal despite potentially having different encoding/normalizing
899
- // such as $location.absUrl() vs $browser.url()
900
- // See https://github.com/angular/angular.js/issues/16592
901
- function urlsEqual(a, b) {
902
- return a === b || urlResolve(a).href === urlResolve(b).href;
903
- }
904
-
905
630
  function setBrowserUrlWithFallback(url, replace, state) {
906
631
  const oldUrl = $location.url();
907
632
  const oldState = $location.$$state;
@@ -1029,7 +754,7 @@ export function $LocationProvider() {
1029
754
  if (initializing || $location.$$urlUpdatedByLocation) {
1030
755
  $location.$$urlUpdatedByLocation = false;
1031
756
 
1032
- const oldUrl = $browser.url();
757
+ const oldUrl = /** @type {string} */ ($browser.url());
1033
758
  const newUrl = $location.absUrl();
1034
759
  const oldState = $browser.state();
1035
760
  const currentReplace = $location.$$replace;
@@ -1091,3 +816,125 @@ export function $LocationProvider() {
1091
816
  },
1092
817
  ];
1093
818
  }
819
+
820
+ /**
821
+ * ///////////////////////////
822
+ * HELPERS
823
+ * ///////////////////////////
824
+ */
825
+
826
+ /**
827
+ * Encode path using encodeUriSegment, ignoring forward slashes
828
+ *
829
+ * @param {string} path Path to encode
830
+ * @returns {string}
831
+ */
832
+ function encodePath(path) {
833
+ const segments = path.split("/");
834
+ let i = segments.length;
835
+
836
+ while (i--) {
837
+ // decode forward slashes to prevent them from being double encoded
838
+ segments[i] = encodeUriSegment(segments[i].replace(/%2F/g, "/"));
839
+ }
840
+
841
+ return segments.join("/");
842
+ }
843
+
844
+ function decodePath(path, html5Mode) {
845
+ const segments = path.split("/");
846
+ let i = segments.length;
847
+
848
+ while (i--) {
849
+ segments[i] = decodeURIComponent(segments[i]);
850
+ if (html5Mode) {
851
+ // encode forward slashes to prevent them from being mistaken for path separators
852
+ segments[i] = segments[i].replace(/\//g, "%2F");
853
+ }
854
+ }
855
+
856
+ return segments.join("/");
857
+ }
858
+
859
+ function normalizePath(pathValue, searchValue, hashValue) {
860
+ const search = toKeyValue(searchValue);
861
+ const hash = hashValue ? `#${encodeUriSegment(hashValue)}` : "";
862
+ const path = encodePath(pathValue);
863
+
864
+ return path + (search ? `?${search}` : "") + hash;
865
+ }
866
+
867
+ /**
868
+ * @param {string} absoluteUrl
869
+ * @param {Location} locationObj
870
+ */
871
+ function parseAbsoluteUrl(absoluteUrl, locationObj) {
872
+ const parsedUrl = urlResolve(absoluteUrl);
873
+
874
+ locationObj.$$protocol = parsedUrl.protocol;
875
+ locationObj.$$host = parsedUrl.hostname;
876
+ locationObj.$$port =
877
+ toInt(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null;
878
+ }
879
+
880
+ function parseAppUrl(url, locationObj, html5Mode) {
881
+ if (/^\s*[\\/]{2,}/.test(url)) {
882
+ throw $locationMinErr("badpath", 'Invalid url "{0}".', url);
883
+ }
884
+
885
+ const prefixed = url.charAt(0) !== "/";
886
+ if (prefixed) {
887
+ url = `/${url}`;
888
+ }
889
+ const match = urlResolve(url);
890
+ const path =
891
+ prefixed && match.pathname.charAt(0) === "/"
892
+ ? match.pathname.substring(1)
893
+ : match.pathname;
894
+ locationObj.$$path = decodePath(path, html5Mode);
895
+ locationObj.$$search = parseKeyValue(match.search);
896
+ locationObj.$$hash = decodeURIComponent(match.hash);
897
+
898
+ // make sure path starts with '/';
899
+ if (locationObj.$$path && locationObj.$$path.charAt(0) !== "/") {
900
+ locationObj.$$path = `/${locationObj.$$path}`;
901
+ }
902
+ }
903
+
904
+ function startsWith(str, search) {
905
+ return str.slice(0, search.length) === search;
906
+ }
907
+
908
+ /**
909
+ *
910
+ * @param {string} base
911
+ * @param {string} url
912
+ * @returns {string} returns text from `url` after `base` or `undefined` if it does not begin with
913
+ * the expected string.
914
+ */
915
+ export function stripBaseUrl(base, url) {
916
+ if (startsWith(url, base)) {
917
+ return url.substr(base.length);
918
+ }
919
+ }
920
+
921
+ export function stripHash(url) {
922
+ const index = url.indexOf("#");
923
+ return index === -1 ? url : url.substr(0, index);
924
+ }
925
+
926
+ export function stripFile(url) {
927
+ return url.substr(0, stripHash(url).lastIndexOf("/") + 1);
928
+ }
929
+
930
+ /* return the server only (scheme://host:port) */
931
+ export function serverBase(url) {
932
+ return url.substring(0, url.indexOf("/", url.indexOf("//") + 2));
933
+ }
934
+
935
+ // Determine if two URLs are equal despite potentially having different encoding/normalizing
936
+ // such as $location.absUrl() vs $browser.url()
937
+ // See https://github.com/angular/angular.js/issues/16592
938
+ function urlsEqual(a, b) {
939
+ return a === b || urlResolve(a).href === urlResolve(b).href;
940
+ }