@angular-wave/angular.ts 0.0.1
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/.eslintignore +1 -0
- package/.eslintrc.cjs +29 -0
- package/.github/workflows/playwright.yml +27 -0
- package/CHANGELOG.md +17974 -0
- package/CODE_OF_CONDUCT.md +3 -0
- package/CONTRIBUTING.md +246 -0
- package/DEVELOPERS.md +488 -0
- package/LICENSE +22 -0
- package/Makefile +31 -0
- package/README.md +115 -0
- package/RELEASE.md +98 -0
- package/SECURITY.md +16 -0
- package/TRIAGING.md +135 -0
- package/css/angular.css +22 -0
- package/dist/angular-ts.cjs.js +36843 -0
- package/dist/angular-ts.esm.js +36841 -0
- package/dist/angular-ts.umd.js +36848 -0
- package/dist/build/angular-animate.js +4272 -0
- package/dist/build/angular-aria.js +426 -0
- package/dist/build/angular-message-format.js +1072 -0
- package/dist/build/angular-messages.js +829 -0
- package/dist/build/angular-mocks.js +3757 -0
- package/dist/build/angular-parse-ext.js +1275 -0
- package/dist/build/angular-resource.js +911 -0
- package/dist/build/angular-route.js +1266 -0
- package/dist/build/angular-sanitize.js +891 -0
- package/dist/build/angular-touch.js +368 -0
- package/dist/build/angular.js +36600 -0
- package/e2e/unit.spec.ts +15 -0
- package/images/android-chrome-192x192.png +0 -0
- package/images/android-chrome-512x512.png +0 -0
- package/images/apple-touch-icon.png +0 -0
- package/images/favicon-16x16.png +0 -0
- package/images/favicon-32x32.png +0 -0
- package/images/favicon.ico +0 -0
- package/images/site.webmanifest +1 -0
- package/index.html +104 -0
- package/package.json +47 -0
- package/playwright.config.ts +78 -0
- package/public/circle.html +1 -0
- package/public/my_child_directive.html +1 -0
- package/public/my_directive.html +1 -0
- package/public/my_other_directive.html +1 -0
- package/public/test.html +1 -0
- package/rollup.config.js +31 -0
- package/src/animations/animateCache.js +55 -0
- package/src/animations/animateChildrenDirective.js +105 -0
- package/src/animations/animateCss.js +1139 -0
- package/src/animations/animateCssDriver.js +291 -0
- package/src/animations/animateJs.js +367 -0
- package/src/animations/animateJsDriver.js +67 -0
- package/src/animations/animateQueue.js +851 -0
- package/src/animations/animation.js +506 -0
- package/src/animations/module.js +779 -0
- package/src/animations/ngAnimateSwap.js +119 -0
- package/src/animations/rafScheduler.js +50 -0
- package/src/animations/shared.js +378 -0
- package/src/constants.js +20 -0
- package/src/core/animate.js +845 -0
- package/src/core/animateCss.js +73 -0
- package/src/core/animateRunner.js +195 -0
- package/src/core/attributes.js +199 -0
- package/src/core/cache.js +45 -0
- package/src/core/compile.js +4727 -0
- package/src/core/controller.js +225 -0
- package/src/core/exceptionHandler.js +63 -0
- package/src/core/filter.js +146 -0
- package/src/core/interpolate.js +442 -0
- package/src/core/interval.js +188 -0
- package/src/core/intervalFactory.js +57 -0
- package/src/core/location.js +1086 -0
- package/src/core/parser/parse.js +2562 -0
- package/src/core/parser/parse.md +13 -0
- package/src/core/q.js +746 -0
- package/src/core/rootScope.js +1596 -0
- package/src/core/sanitizeUri.js +85 -0
- package/src/core/sce.js +1161 -0
- package/src/core/taskTrackerFactory.js +125 -0
- package/src/core/timeout.js +121 -0
- package/src/core/urlUtils.js +187 -0
- package/src/core/utils.js +1349 -0
- package/src/directive/a.js +37 -0
- package/src/directive/attrs.js +283 -0
- package/src/directive/bind.js +51 -0
- package/src/directive/bind.md +142 -0
- package/src/directive/change.js +12 -0
- package/src/directive/change.md +25 -0
- package/src/directive/cloak.js +12 -0
- package/src/directive/cloak.md +24 -0
- package/src/directive/events.js +75 -0
- package/src/directive/events.md +166 -0
- package/src/directive/form.js +725 -0
- package/src/directive/init.js +15 -0
- package/src/directive/init.md +41 -0
- package/src/directive/input.js +1783 -0
- package/src/directive/list.js +46 -0
- package/src/directive/list.md +22 -0
- package/src/directive/ngClass.js +249 -0
- package/src/directive/ngController.js +64 -0
- package/src/directive/ngCsp.js +82 -0
- package/src/directive/ngIf.js +134 -0
- package/src/directive/ngInclude.js +217 -0
- package/src/directive/ngModel.js +1356 -0
- package/src/directive/ngModelOptions.js +509 -0
- package/src/directive/ngOptions.js +670 -0
- package/src/directive/ngRef.js +90 -0
- package/src/directive/ngRepeat.js +650 -0
- package/src/directive/ngShowHide.js +255 -0
- package/src/directive/ngSwitch.js +178 -0
- package/src/directive/ngTransclude.js +98 -0
- package/src/directive/non-bindable.js +11 -0
- package/src/directive/non-bindable.md +17 -0
- package/src/directive/script.js +30 -0
- package/src/directive/select.js +624 -0
- package/src/directive/style.js +25 -0
- package/src/directive/style.md +23 -0
- package/src/directive/validators.js +329 -0
- package/src/exts/aria.js +544 -0
- package/src/exts/messages.js +852 -0
- package/src/filters/filter.js +207 -0
- package/src/filters/filter.md +69 -0
- package/src/filters/filters.js +239 -0
- package/src/filters/json.md +16 -0
- package/src/filters/limit-to.js +43 -0
- package/src/filters/limit-to.md +19 -0
- package/src/filters/order-by.js +183 -0
- package/src/filters/order-by.md +83 -0
- package/src/index.js +13 -0
- package/src/injector.js +1034 -0
- package/src/jqLite.js +1117 -0
- package/src/loader.js +1320 -0
- package/src/public.js +215 -0
- package/src/routeToRegExp.js +41 -0
- package/src/services/anchorScroll.js +135 -0
- package/src/services/browser.js +321 -0
- package/src/services/cacheFactory.js +398 -0
- package/src/services/cookieReader.js +72 -0
- package/src/services/document.js +64 -0
- package/src/services/http.js +1537 -0
- package/src/services/httpBackend.js +206 -0
- package/src/services/log.js +160 -0
- package/src/services/templateRequest.js +139 -0
- package/test/angular.spec.js +2153 -0
- package/test/aria/aria.spec.js +1245 -0
- package/test/binding.spec.js +504 -0
- package/test/build-test.html +14 -0
- package/test/injector.spec.js +2327 -0
- package/test/jasmine/jasmine-5.1.2/boot0.js +65 -0
- package/test/jasmine/jasmine-5.1.2/boot1.js +133 -0
- package/test/jasmine/jasmine-5.1.2/jasmine-html.js +963 -0
- package/test/jasmine/jasmine-5.1.2/jasmine.css +320 -0
- package/test/jasmine/jasmine-5.1.2/jasmine.js +10824 -0
- package/test/jasmine/jasmine-5.1.2/jasmine_favicon.png +0 -0
- package/test/jasmine/jasmine-browser.json +17 -0
- package/test/jasmine/jasmine.json +9 -0
- package/test/jqlite.spec.js +2133 -0
- package/test/loader.spec.js +219 -0
- package/test/messages/messages.spec.js +1146 -0
- package/test/min-err.spec.js +174 -0
- package/test/mock-test.html +13 -0
- package/test/module-test.html +15 -0
- package/test/ng/anomate.spec.js +606 -0
- package/test/ng/cache-factor.spec.js +334 -0
- package/test/ng/compile.spec.js +17956 -0
- package/test/ng/controller-provider.spec.js +227 -0
- package/test/ng/cookie-reader.spec.js +98 -0
- package/test/ng/directive/a.spec.js +192 -0
- package/test/ng/directive/bind.spec.js +334 -0
- package/test/ng/directive/boolean.spec.js +136 -0
- package/test/ng/directive/change.spec.js +71 -0
- package/test/ng/directive/class.spec.js +858 -0
- package/test/ng/directive/click.spec.js +38 -0
- package/test/ng/directive/cloak.spec.js +44 -0
- package/test/ng/directive/constoller.spec.js +194 -0
- package/test/ng/directive/element-style.spec.js +92 -0
- package/test/ng/directive/event.spec.js +282 -0
- package/test/ng/directive/form.spec.js +1518 -0
- package/test/ng/directive/href.spec.js +143 -0
- package/test/ng/directive/if.spec.js +402 -0
- package/test/ng/directive/include.spec.js +828 -0
- package/test/ng/directive/init.spec.js +68 -0
- package/test/ng/directive/input.spec.js +3810 -0
- package/test/ng/directive/list.spec.js +170 -0
- package/test/ng/directive/model-options.spec.js +1008 -0
- package/test/ng/directive/model.spec.js +1905 -0
- package/test/ng/directive/non-bindable.spec.js +55 -0
- package/test/ng/directive/options.spec.js +3583 -0
- package/test/ng/directive/ref.spec.js +575 -0
- package/test/ng/directive/repeat.spec.js +1675 -0
- package/test/ng/directive/script.spec.js +52 -0
- package/test/ng/directive/scrset.spec.js +67 -0
- package/test/ng/directive/select.spec.js +2541 -0
- package/test/ng/directive/show-hide.spec.js +253 -0
- package/test/ng/directive/src.spec.js +157 -0
- package/test/ng/directive/style.spec.js +178 -0
- package/test/ng/directive/switch.spec.js +647 -0
- package/test/ng/directive/validators.spec.js +717 -0
- package/test/ng/document.spec.js +52 -0
- package/test/ng/filter/filter.spec.js +714 -0
- package/test/ng/filter/filters.spec.js +35 -0
- package/test/ng/filter/limit-to.spec.js +251 -0
- package/test/ng/filter/order-by.spec.js +891 -0
- package/test/ng/filter.spec.js +149 -0
- package/test/ng/http-backend.spec.js +398 -0
- package/test/ng/http.spec.js +4071 -0
- package/test/ng/interpolate.spec.js +642 -0
- package/test/ng/interval.spec.js +343 -0
- package/test/ng/location.spec.js +3488 -0
- package/test/ng/on.spec.js +229 -0
- package/test/ng/parse.spec.js +4655 -0
- package/test/ng/prop.spec.js +805 -0
- package/test/ng/q.spec.js +2904 -0
- package/test/ng/root-element.spec.js +16 -0
- package/test/ng/sanitize-uri.spec.js +249 -0
- package/test/ng/sce.spec.js +660 -0
- package/test/ng/scope.spec.js +3442 -0
- package/test/ng/template-request.spec.js +236 -0
- package/test/ng/timeout.spec.js +351 -0
- package/test/ng/url-utils.spec.js +156 -0
- package/test/ng/utils.spec.js +144 -0
- package/test/original-test.html +21 -0
- package/test/public.spec.js +34 -0
- package/test/sanitize/bing-html.spec.js +36 -0
- package/test/server/express.js +158 -0
- package/test/test-utils.js +11 -0
- package/tsconfig.json +17 -0
- package/types/angular.d.ts +138 -0
- package/types/global.d.ts +9 -0
- package/types/index.d.ts +2357 -0
- package/types/jqlite.d.ts +558 -0
- package/vite.config.js +14 -0
|
@@ -0,0 +1,1086 @@
|
|
|
1
|
+
import { jqLite } from "../jqLite";
|
|
2
|
+
import { urlResolve } from "./urlUtils";
|
|
3
|
+
import {
|
|
4
|
+
encodeUriSegment,
|
|
5
|
+
forEach,
|
|
6
|
+
isBoolean,
|
|
7
|
+
isDefined,
|
|
8
|
+
isNumber,
|
|
9
|
+
isObject,
|
|
10
|
+
isString,
|
|
11
|
+
isUndefined,
|
|
12
|
+
minErr,
|
|
13
|
+
nodeName_,
|
|
14
|
+
parseKeyValue,
|
|
15
|
+
toInt,
|
|
16
|
+
toKeyValue,
|
|
17
|
+
} from "./utils";
|
|
18
|
+
|
|
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
|
+
/**
|
|
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.
|
|
108
|
+
*/
|
|
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
|
+
|
|
129
|
+
/**
|
|
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
|
|
137
|
+
*/
|
|
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
|
+
|
|
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
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
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
|
|
214
|
+
*/
|
|
215
|
+
export function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) {
|
|
216
|
+
parseAbsoluteUrl(appBase, this);
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Parse given hashbang URL into properties
|
|
220
|
+
* @param {string} url Hashbang URL
|
|
221
|
+
* @private
|
|
222
|
+
*/
|
|
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
|
+
}
|
|
250
|
+
|
|
251
|
+
parseAppUrl(withoutHashUrl, this, false);
|
|
252
|
+
|
|
253
|
+
this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase);
|
|
254
|
+
|
|
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.
|
|
267
|
+
*/
|
|
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]:(\/.*)/;
|
|
274
|
+
|
|
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
|
+
}
|
|
326
|
+
|
|
327
|
+
let rewrittenUrl;
|
|
328
|
+
let appUrl;
|
|
329
|
+
|
|
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
|
+
};
|
|
342
|
+
|
|
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
|
+
}
|
|
348
|
+
|
|
349
|
+
const locationPrototype = {
|
|
350
|
+
/**
|
|
351
|
+
* Ensure absolute URL is initialized.
|
|
352
|
+
* @private
|
|
353
|
+
*/
|
|
354
|
+
$$absUrl: "",
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Are we in html5 mode?
|
|
358
|
+
* @private
|
|
359
|
+
*/
|
|
360
|
+
$$html5: false,
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Has any change been replacing?
|
|
364
|
+
* @private
|
|
365
|
+
*/
|
|
366
|
+
$$replace: false,
|
|
367
|
+
|
|
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
|
+
},
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* @ngdoc method
|
|
380
|
+
* @name $location#absUrl
|
|
381
|
+
*
|
|
382
|
+
* @description
|
|
383
|
+
* This method is getter only.
|
|
384
|
+
*
|
|
385
|
+
* Return full URL representation with all segments encoded according to rules specified in
|
|
386
|
+
* [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt).
|
|
387
|
+
*
|
|
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
|
+
* @return {string} full URL
|
|
396
|
+
*/
|
|
397
|
+
absUrl: locationGetter("$$absUrl"),
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* @ngdoc method
|
|
401
|
+
* @name $location#url
|
|
402
|
+
*
|
|
403
|
+
* @description
|
|
404
|
+
* This method is getter / setter.
|
|
405
|
+
*
|
|
406
|
+
* Return URL (e.g. `/path?a=b#hash`) when called without any parameter.
|
|
407
|
+
*
|
|
408
|
+
* Change path, search and hash, when called with parameter and return `$location`.
|
|
409
|
+
*
|
|
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
|
+
* @param {string=} url New URL without base prefix (e.g. `/path?a=b#hash`)
|
|
418
|
+
* @return {string} url
|
|
419
|
+
*/
|
|
420
|
+
url(url) {
|
|
421
|
+
if (isUndefined(url)) {
|
|
422
|
+
return this.$$url;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const match = PATH_MATCH.exec(url);
|
|
426
|
+
if (match[1] || url === "") this.path(decodeURIComponent(match[1]));
|
|
427
|
+
if (match[2] || match[1] || url === "") this.search(match[3] || "");
|
|
428
|
+
this.hash(match[5] || "");
|
|
429
|
+
|
|
430
|
+
return this;
|
|
431
|
+
},
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* @ngdoc method
|
|
435
|
+
* @name $location#protocol
|
|
436
|
+
*
|
|
437
|
+
* @description
|
|
438
|
+
* This method is getter only.
|
|
439
|
+
*
|
|
440
|
+
* 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
|
|
450
|
+
*/
|
|
451
|
+
protocol: locationGetter("$$protocol"),
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* @ngdoc method
|
|
455
|
+
* @name $location#host
|
|
456
|
+
*
|
|
457
|
+
* @description
|
|
458
|
+
* This method is getter only.
|
|
459
|
+
*
|
|
460
|
+
* Return host of current URL.
|
|
461
|
+
*
|
|
462
|
+
* Note: compared to the non-AngularJS version `location.host` which returns `hostname:port`, this returns the `hostname` portion only.
|
|
463
|
+
*
|
|
464
|
+
*
|
|
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
|
+
* @return {string} host of current URL.
|
|
478
|
+
*/
|
|
479
|
+
host: locationGetter("$$host"),
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* @ngdoc method
|
|
483
|
+
* @name $location#port
|
|
484
|
+
*
|
|
485
|
+
* @description
|
|
486
|
+
* This method is getter only.
|
|
487
|
+
*
|
|
488
|
+
* Return port of current URL.
|
|
489
|
+
*
|
|
490
|
+
*
|
|
491
|
+
* ```js
|
|
492
|
+
* // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
|
|
493
|
+
* let port = $location.port();
|
|
494
|
+
* // => 80
|
|
495
|
+
* ```
|
|
496
|
+
*
|
|
497
|
+
* @return {Number} port
|
|
498
|
+
*/
|
|
499
|
+
port: locationGetter("$$port"),
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* @ngdoc method
|
|
503
|
+
* @name $location#path
|
|
504
|
+
*
|
|
505
|
+
* @description
|
|
506
|
+
* This method is getter / setter.
|
|
507
|
+
*
|
|
508
|
+
* Return path of current URL when called without any parameter.
|
|
509
|
+
*
|
|
510
|
+
* Change path when called with parameter and return `$location`.
|
|
511
|
+
*
|
|
512
|
+
* Note: Path should always begin with forward slash (/), this method will add the forward slash
|
|
513
|
+
* if it is missing.
|
|
514
|
+
*
|
|
515
|
+
*
|
|
516
|
+
* ```js
|
|
517
|
+
* // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
|
|
518
|
+
* let path = $location.path();
|
|
519
|
+
* // => "/some/path"
|
|
520
|
+
* ```
|
|
521
|
+
*
|
|
522
|
+
* @param {(string|number)=} path New path
|
|
523
|
+
* @return {(string|object)} path if called with no parameters, or `$location` if called with a parameter
|
|
524
|
+
*/
|
|
525
|
+
path: locationGetterSetter("$$path", (path) => {
|
|
526
|
+
path = path !== null ? path.toString() : "";
|
|
527
|
+
return path.charAt(0) === "/" ? path : `/${path}`;
|
|
528
|
+
}),
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* @ngdoc method
|
|
532
|
+
* @name $location#search
|
|
533
|
+
*
|
|
534
|
+
* @description
|
|
535
|
+
* This method is getter / setter.
|
|
536
|
+
*
|
|
537
|
+
* Return search part (as object) of current URL when called without any parameter.
|
|
538
|
+
*
|
|
539
|
+
* Change search part when called with parameter and return `$location`.
|
|
540
|
+
*
|
|
541
|
+
*
|
|
542
|
+
* ```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'}
|
|
550
|
+
* ```
|
|
551
|
+
*
|
|
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.
|
|
571
|
+
*
|
|
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.
|
|
574
|
+
*/
|
|
575
|
+
search(search, paramValue) {
|
|
576
|
+
switch (arguments.length) {
|
|
577
|
+
case 0:
|
|
578
|
+
return this.$$search;
|
|
579
|
+
case 1:
|
|
580
|
+
if (isString(search) || isNumber(search)) {
|
|
581
|
+
search = search.toString();
|
|
582
|
+
this.$$search = parseKeyValue(search);
|
|
583
|
+
} else if (isObject(search)) {
|
|
584
|
+
search = structuredClone(search, {});
|
|
585
|
+
// remove object undefined or null properties
|
|
586
|
+
forEach(search, (value, key) => {
|
|
587
|
+
if (value == null) delete search[key];
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
this.$$search = search;
|
|
591
|
+
} else {
|
|
592
|
+
throw $locationMinErr(
|
|
593
|
+
"isrcharg",
|
|
594
|
+
"The first argument of the `$location#search()` call must be a string or an object.",
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
break;
|
|
598
|
+
default:
|
|
599
|
+
if (isUndefined(paramValue) || paramValue === null) {
|
|
600
|
+
delete this.$$search[search];
|
|
601
|
+
} else {
|
|
602
|
+
this.$$search[search] = paramValue;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
this.$$compose();
|
|
607
|
+
return this;
|
|
608
|
+
},
|
|
609
|
+
|
|
610
|
+
/**
|
|
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
|
|
630
|
+
*/
|
|
631
|
+
hash: locationGetterSetter("$$hash", (hash) =>
|
|
632
|
+
hash !== null ? hash.toString() : "",
|
|
633
|
+
),
|
|
634
|
+
|
|
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
|
+
};
|
|
648
|
+
|
|
649
|
+
forEach(
|
|
650
|
+
[LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url],
|
|
651
|
+
(Location) => {
|
|
652
|
+
Location.prototype = Object.create(locationPrototype);
|
|
653
|
+
|
|
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.
|
|
669
|
+
*
|
|
670
|
+
* @param {object=} state State object for pushState or replaceState
|
|
671
|
+
* @return {object} state
|
|
672
|
+
*/
|
|
673
|
+
Location.prototype.state = function (state) {
|
|
674
|
+
if (!arguments.length) {
|
|
675
|
+
return this.$$state;
|
|
676
|
+
}
|
|
677
|
+
|
|
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;
|
|
690
|
+
|
|
691
|
+
return this;
|
|
692
|
+
};
|
|
693
|
+
},
|
|
694
|
+
);
|
|
695
|
+
|
|
696
|
+
function locationGetter(property) {
|
|
697
|
+
return function () {
|
|
698
|
+
return this[property];
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
function locationGetterSetter(property, preprocess) {
|
|
703
|
+
return function (value) {
|
|
704
|
+
if (isUndefined(value)) {
|
|
705
|
+
return this[property];
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
this[property] = preprocess(value);
|
|
709
|
+
this.$$compose();
|
|
710
|
+
|
|
711
|
+
return this;
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
|
|
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
|
+
export function $LocationProvider() {
|
|
750
|
+
let hashPrefix = "!";
|
|
751
|
+
const html5Mode = {
|
|
752
|
+
enabled: false,
|
|
753
|
+
requireBase: true,
|
|
754
|
+
rewriteLinks: true,
|
|
755
|
+
};
|
|
756
|
+
|
|
757
|
+
/**
|
|
758
|
+
* @ngdoc method
|
|
759
|
+
* @name $locationProvider#hashPrefix
|
|
760
|
+
* @description
|
|
761
|
+
* The default value for the prefix is `'!'`.
|
|
762
|
+
* @param {string=} prefix Prefix for hash part (containing path and search)
|
|
763
|
+
* @returns {*} current value if used as getter or itself (chaining) if used as setter
|
|
764
|
+
*/
|
|
765
|
+
this.hashPrefix = function (prefix) {
|
|
766
|
+
if (isDefined(prefix)) {
|
|
767
|
+
hashPrefix = prefix;
|
|
768
|
+
return this;
|
|
769
|
+
}
|
|
770
|
+
return hashPrefix;
|
|
771
|
+
};
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
* @ngdoc method
|
|
775
|
+
* @name $locationProvider#html5Mode
|
|
776
|
+
* @description
|
|
777
|
+
* @param {(boolean|Object)=} mode If boolean, sets `html5Mode.enabled` to value.
|
|
778
|
+
* If object, sets `enabled`, `requireBase` and `rewriteLinks` to respective values. Supported
|
|
779
|
+
* properties:
|
|
780
|
+
* - **enabled** – `{boolean}` – (default: false) If true, will rely on `history.pushState` to
|
|
781
|
+
* change urls where supported. Will fall back to hash-prefixed paths in browsers that do not
|
|
782
|
+
* support `pushState`.
|
|
783
|
+
* - **requireBase** - `{boolean}` - (default: `true`) When html5Mode is enabled, specifies
|
|
784
|
+
* whether or not a <base> tag is required to be present. If `enabled` and `requireBase` are
|
|
785
|
+
* true, and a base tag is not present, an error will be thrown when `$location` is injected.
|
|
786
|
+
* See the {@link guide/$location $location guide for more information}
|
|
787
|
+
* - **rewriteLinks** - `{boolean|string}` - (default: `true`) When html5Mode is enabled,
|
|
788
|
+
* enables/disables URL rewriting for relative links. If set to a string, URL rewriting will
|
|
789
|
+
* only happen on links with an attribute that matches the given string. For example, if set
|
|
790
|
+
* to `'internal-link'`, then the URL will only be rewritten for `<a internal-link>` links.
|
|
791
|
+
* Note that [attribute name normalization](guide/directive#normalization) does not apply
|
|
792
|
+
* here, so `'internalLink'` will **not** match `'internal-link'`.
|
|
793
|
+
*
|
|
794
|
+
* @returns {Object} html5Mode object if used as getter or itself (chaining) if used as setter
|
|
795
|
+
*/
|
|
796
|
+
this.html5Mode = function (mode) {
|
|
797
|
+
if (isBoolean(mode)) {
|
|
798
|
+
html5Mode.enabled = mode;
|
|
799
|
+
return this;
|
|
800
|
+
}
|
|
801
|
+
if (isObject(mode)) {
|
|
802
|
+
if (isBoolean(mode.enabled)) {
|
|
803
|
+
html5Mode.enabled = mode.enabled;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
if (isBoolean(mode.requireBase)) {
|
|
807
|
+
html5Mode.requireBase = mode.requireBase;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
if (isBoolean(mode.rewriteLinks) || isString(mode.rewriteLinks)) {
|
|
811
|
+
html5Mode.rewriteLinks = mode.rewriteLinks;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
return this;
|
|
815
|
+
}
|
|
816
|
+
return html5Mode;
|
|
817
|
+
};
|
|
818
|
+
|
|
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
|
+
this.$get = [
|
|
859
|
+
"$rootScope",
|
|
860
|
+
"$browser",
|
|
861
|
+
"$rootElement",
|
|
862
|
+
function ($rootScope, $browser, $rootElement) {
|
|
863
|
+
let $location;
|
|
864
|
+
let LocationMode;
|
|
865
|
+
const baseHref = $browser.baseHref(); // if base[href] is undefined, it defaults to ''
|
|
866
|
+
const initialUrl = $browser.url();
|
|
867
|
+
let appBase;
|
|
868
|
+
|
|
869
|
+
if (html5Mode.enabled) {
|
|
870
|
+
if (!baseHref && html5Mode.requireBase) {
|
|
871
|
+
throw $locationMinErr(
|
|
872
|
+
"nobase",
|
|
873
|
+
"$location in HTML5 mode requires a <base> tag to be present!",
|
|
874
|
+
);
|
|
875
|
+
}
|
|
876
|
+
appBase = serverBase(initialUrl) + (baseHref || "/");
|
|
877
|
+
LocationMode = LocationHtml5Url;
|
|
878
|
+
} else {
|
|
879
|
+
appBase = stripHash(initialUrl);
|
|
880
|
+
LocationMode = LocationHashbangUrl;
|
|
881
|
+
}
|
|
882
|
+
const appBaseNoFile = stripFile(appBase);
|
|
883
|
+
|
|
884
|
+
$location = new LocationMode(appBase, appBaseNoFile, `#${hashPrefix}`);
|
|
885
|
+
$location.$$parseLinkUrl(initialUrl, initialUrl);
|
|
886
|
+
|
|
887
|
+
$location.$$state = $browser.state();
|
|
888
|
+
|
|
889
|
+
const IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i;
|
|
890
|
+
|
|
891
|
+
// Determine if two URLs are equal despite potentially having different encoding/normalizing
|
|
892
|
+
// such as $location.absUrl() vs $browser.url()
|
|
893
|
+
// See https://github.com/angular/angular.js/issues/16592
|
|
894
|
+
function urlsEqual(a, b) {
|
|
895
|
+
return a === b || urlResolve(a).href === urlResolve(b).href;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
function setBrowserUrlWithFallback(url, replace, state) {
|
|
899
|
+
const oldUrl = $location.url();
|
|
900
|
+
const oldState = $location.$$state;
|
|
901
|
+
try {
|
|
902
|
+
$browser.url(url, replace, state);
|
|
903
|
+
|
|
904
|
+
// Make sure $location.state() returns referentially identical (not just deeply equal)
|
|
905
|
+
// state object; this makes possible quick checking if the state changed in the digest
|
|
906
|
+
// loop. Checking deep equality would be too expensive.
|
|
907
|
+
$location.$$state = $browser.state();
|
|
908
|
+
} catch (e) {
|
|
909
|
+
// Restore old values if pushState fails
|
|
910
|
+
$location.url(oldUrl);
|
|
911
|
+
$location.$$state = oldState;
|
|
912
|
+
|
|
913
|
+
throw e;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
$rootElement.on("click", (event) => {
|
|
918
|
+
const { rewriteLinks } = html5Mode;
|
|
919
|
+
// TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
|
|
920
|
+
// currently we open nice url link and redirect then
|
|
921
|
+
|
|
922
|
+
if (
|
|
923
|
+
!rewriteLinks ||
|
|
924
|
+
event.ctrlKey ||
|
|
925
|
+
event.metaKey ||
|
|
926
|
+
event.shiftKey ||
|
|
927
|
+
event.which === 2 ||
|
|
928
|
+
event.button === 2
|
|
929
|
+
)
|
|
930
|
+
return;
|
|
931
|
+
|
|
932
|
+
let elm = jqLite(event.target);
|
|
933
|
+
|
|
934
|
+
// traverse the DOM up to find first A tag
|
|
935
|
+
while (nodeName_(elm[0]) !== "a") {
|
|
936
|
+
// ignore rewriting if no A tag (reached root element, or no parent - removed from document)
|
|
937
|
+
if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
if (isString(rewriteLinks) && isUndefined(elm.attr(rewriteLinks)))
|
|
941
|
+
return;
|
|
942
|
+
|
|
943
|
+
let absHref = elm.prop("href");
|
|
944
|
+
// get the actual href attribute - see
|
|
945
|
+
// http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx
|
|
946
|
+
const relHref = elm.attr("href") || elm.attr("xlink:href");
|
|
947
|
+
|
|
948
|
+
if (
|
|
949
|
+
isObject(absHref) &&
|
|
950
|
+
absHref.toString() === "[object SVGAnimatedString]"
|
|
951
|
+
) {
|
|
952
|
+
// SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during
|
|
953
|
+
// an animation.
|
|
954
|
+
absHref = urlResolve(absHref.animVal).href;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
// Ignore when url is started with javascript: or mailto:
|
|
958
|
+
if (IGNORE_URI_REGEXP.test(absHref)) return;
|
|
959
|
+
|
|
960
|
+
if (absHref && !elm.attr("target") && !event.isDefaultPrevented()) {
|
|
961
|
+
if ($location.$$parseLinkUrl(absHref, relHref)) {
|
|
962
|
+
// We do a preventDefault for all urls that are part of the AngularJS application,
|
|
963
|
+
// in html5mode and also without, so that we are able to abort navigation without
|
|
964
|
+
// getting double entries in the location history.
|
|
965
|
+
event.preventDefault();
|
|
966
|
+
// update location manually
|
|
967
|
+
if ($location.absUrl() !== $browser.url()) {
|
|
968
|
+
$rootScope.$apply();
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
});
|
|
973
|
+
|
|
974
|
+
// rewrite hashbang url <> html5 url
|
|
975
|
+
if ($location.absUrl() !== initialUrl) {
|
|
976
|
+
$browser.url($location.absUrl(), true);
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
let initializing = true;
|
|
980
|
+
|
|
981
|
+
// update $location when $browser url changes
|
|
982
|
+
$browser.onUrlChange((newUrl, newState) => {
|
|
983
|
+
if (!startsWith(newUrl, appBaseNoFile)) {
|
|
984
|
+
// If we are navigating outside of the app then force a reload
|
|
985
|
+
window.location.href = newUrl;
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
$rootScope.$evalAsync(() => {
|
|
990
|
+
const oldUrl = $location.absUrl();
|
|
991
|
+
const oldState = $location.$$state;
|
|
992
|
+
let defaultPrevented;
|
|
993
|
+
$location.$$parse(newUrl);
|
|
994
|
+
$location.$$state = newState;
|
|
995
|
+
|
|
996
|
+
defaultPrevented = $rootScope.$broadcast(
|
|
997
|
+
"$locationChangeStart",
|
|
998
|
+
newUrl,
|
|
999
|
+
oldUrl,
|
|
1000
|
+
newState,
|
|
1001
|
+
oldState,
|
|
1002
|
+
).defaultPrevented;
|
|
1003
|
+
|
|
1004
|
+
// if the location was changed by a `$locationChangeStart` handler then stop
|
|
1005
|
+
// processing this location change
|
|
1006
|
+
if ($location.absUrl() !== newUrl) return;
|
|
1007
|
+
|
|
1008
|
+
if (defaultPrevented) {
|
|
1009
|
+
$location.$$parse(oldUrl);
|
|
1010
|
+
$location.$$state = oldState;
|
|
1011
|
+
setBrowserUrlWithFallback(oldUrl, false, oldState);
|
|
1012
|
+
} else {
|
|
1013
|
+
initializing = false;
|
|
1014
|
+
afterLocationChange(oldUrl, oldState);
|
|
1015
|
+
}
|
|
1016
|
+
});
|
|
1017
|
+
if (!$rootScope.$$phase) $rootScope.$digest();
|
|
1018
|
+
});
|
|
1019
|
+
|
|
1020
|
+
// update browser
|
|
1021
|
+
$rootScope.$watch(() => {
|
|
1022
|
+
if (initializing || $location.$$urlUpdatedByLocation) {
|
|
1023
|
+
$location.$$urlUpdatedByLocation = false;
|
|
1024
|
+
|
|
1025
|
+
const oldUrl = $browser.url();
|
|
1026
|
+
const newUrl = $location.absUrl();
|
|
1027
|
+
const oldState = $browser.state();
|
|
1028
|
+
const currentReplace = $location.$$replace;
|
|
1029
|
+
const urlOrStateChanged =
|
|
1030
|
+
!urlsEqual(oldUrl, newUrl) ||
|
|
1031
|
+
($location.$$html5 && oldState !== $location.$$state);
|
|
1032
|
+
|
|
1033
|
+
if (initializing || urlOrStateChanged) {
|
|
1034
|
+
initializing = false;
|
|
1035
|
+
|
|
1036
|
+
$rootScope.$evalAsync(() => {
|
|
1037
|
+
const newUrl = $location.absUrl();
|
|
1038
|
+
const { defaultPrevented } = $rootScope.$broadcast(
|
|
1039
|
+
"$locationChangeStart",
|
|
1040
|
+
newUrl,
|
|
1041
|
+
oldUrl,
|
|
1042
|
+
$location.$$state,
|
|
1043
|
+
oldState,
|
|
1044
|
+
);
|
|
1045
|
+
|
|
1046
|
+
// if the location was changed by a `$locationChangeStart` handler then stop
|
|
1047
|
+
// processing this location change
|
|
1048
|
+
if ($location.absUrl() !== newUrl) return;
|
|
1049
|
+
|
|
1050
|
+
if (defaultPrevented) {
|
|
1051
|
+
$location.$$parse(oldUrl);
|
|
1052
|
+
$location.$$state = oldState;
|
|
1053
|
+
} else {
|
|
1054
|
+
if (urlOrStateChanged) {
|
|
1055
|
+
setBrowserUrlWithFallback(
|
|
1056
|
+
newUrl,
|
|
1057
|
+
currentReplace,
|
|
1058
|
+
oldState === $location.$$state ? null : $location.$$state,
|
|
1059
|
+
);
|
|
1060
|
+
}
|
|
1061
|
+
afterLocationChange(oldUrl, oldState);
|
|
1062
|
+
}
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
$location.$$replace = false;
|
|
1068
|
+
|
|
1069
|
+
// we don't need to return anything because $evalAsync will make the digest loop dirty when
|
|
1070
|
+
// there is a change
|
|
1071
|
+
});
|
|
1072
|
+
|
|
1073
|
+
return $location;
|
|
1074
|
+
|
|
1075
|
+
function afterLocationChange(oldUrl, oldState) {
|
|
1076
|
+
$rootScope.$broadcast(
|
|
1077
|
+
"$locationChangeSuccess",
|
|
1078
|
+
$location.absUrl(),
|
|
1079
|
+
oldUrl,
|
|
1080
|
+
$location.$$state,
|
|
1081
|
+
oldState,
|
|
1082
|
+
);
|
|
1083
|
+
}
|
|
1084
|
+
},
|
|
1085
|
+
];
|
|
1086
|
+
}
|