unpoly-rails 2.3.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of unpoly-rails might be problematic. Click here for more details.

@@ -8,7 +8,7 @@
8
8
  @module up
9
9
  */
10
10
  window.up = {
11
- version: '2.3.0'
11
+ version: '2.4.0'
12
12
  };
13
13
 
14
14
 
@@ -96,27 +96,63 @@ up.util = (function () {
96
96
  }
97
97
  const NORMALIZE_URL_DEFAULTS = {
98
98
  host: 'cross-domain',
99
- stripTrailingSlash: false,
100
- search: true,
101
- hash: false
102
99
  };
103
100
  /*-
104
- Normalizes the given URL or path.
101
+ Returns a normalized version of the given URL string.
102
+
103
+ Two URLs that point to the same resource should normalize to the same string.
104
+
105
+ ### Comparing normalized URLs
106
+
107
+ The main purpose of this function is to normalize two URLs for string comparison:
108
+
109
+ ```js
110
+ up.util.normalizeURL('http://current-host/path') === up.util.normalizeURL('/path') // => true
111
+ ```
112
+
113
+ By default the hostname is only included if it points to a different origin:
114
+
115
+ ```js
116
+ up.util.normalizeURL('http://current-host/path') // => '/path'
117
+ up.util.normalizeURL('http://other-host/path') // => 'http://other-host/path'
118
+ ```
119
+
120
+ Relative paths are normalized to absolute paths:
121
+
122
+ ```js
123
+ up.util.normalizeURL('index.html') // => '/path/index.html'
124
+ ```
125
+
126
+ ### Excluding URL components
127
+
128
+ You may pass options to exclude URL components from the normalized string:
129
+
130
+ ```js
131
+ up.util.normalizeURL('/foo?query=bar', { query: false }) => '/foo'
132
+ up.util.normalizeURL('/bar#hash', { hash: false }) => '/bar'
133
+ ```
134
+
135
+ ### Limitations
136
+
137
+ - Username and password are always omitted from the normalized URL.
138
+ - Only `http` and `https` schemes are supported.
105
139
 
106
140
  @function up.util.normalizeURL
107
141
  @param {boolean} [options.host='cross-domain']
108
142
  Whether to include protocol, hostname and port in the normalized URL.
109
143
 
110
- By default the host is only included if it differ's from the page's hostname.
111
- @param {boolean} [options.hash=false]
112
- Whether to include an `#hash` anchor in the normalized URL
144
+ When set to `'cross-domain'` (the default), the host is only included if it differ's from the page's hostname.
145
+
146
+ The port is omitted if the port is the standard port for the given protocol, e.g. `:443` for `https://`.
147
+ @param {boolean} [options.hash=true]
148
+ Whether to include an `#hash` anchor in the normalized URL.
113
149
  @param {boolean} [options.search=true]
114
- Whether to include a `?query` string in the normalized URL
115
- @param {boolean} [options.stripTrailingSlash=false]
116
- Whether to strip a trailing slash from the pathname
150
+ Whether to include a `?query` string in the normalized URL.
151
+ @param {boolean} [options.trailingSlash=true]
152
+ Whether to include a trailing slash from the pathname.
117
153
  @return {string}
118
154
  The normalized URL.
119
- @internal
155
+ @experimental
120
156
  */
121
157
  function normalizeURL(urlOrAnchor, options) {
122
158
  options = newOptions(options, NORMALIZE_URL_DEFAULTS);
@@ -135,21 +171,18 @@ up.util = (function () {
135
171
  }
136
172
  }
137
173
  let { pathname } = parts;
138
- if (options.stripTrailingSlash) {
174
+ if (options.trailingSlash === false && pathname !== '/') {
139
175
  pathname = pathname.replace(/\/$/, '');
140
176
  }
141
177
  normalized += pathname;
142
- if (options.search) {
178
+ if (options.search !== false) {
143
179
  normalized += parts.search;
144
180
  }
145
- if (options.hash) {
181
+ if (options.hash !== false) {
146
182
  normalized += parts.hash;
147
183
  }
148
184
  return normalized;
149
185
  }
150
- function urlWithoutHost(url) {
151
- return normalizeURL(url, { host: false });
152
- }
153
186
  function matchURLs(leftURL, rightURL) {
154
187
  return normalizeURL(leftURL) === normalizeURL(rightURL);
155
188
  }
@@ -1911,7 +1944,6 @@ up.util = (function () {
1911
1944
  return {
1912
1945
  parseURL,
1913
1946
  normalizeURL,
1914
- urlWithoutHost,
1915
1947
  matchURLs,
1916
1948
  normalizeMethod,
1917
1949
  methodAllowsPayload,
@@ -3858,7 +3890,7 @@ up.Change.Addition = class Addition extends up.Change {
3858
3890
  // (1) Don't set a source if { false } is passed.
3859
3891
  // (2) Don't set a source if the element HTML already has an [up-source] attribute.
3860
3892
  if (source) {
3861
- e.setMissingAttr(newElement, 'up-source', u.normalizeURL(source));
3893
+ e.setMissingAttr(newElement, 'up-source', u.normalizeURL(source, { hash: false }));
3862
3894
  }
3863
3895
  }
3864
3896
  };
@@ -12962,7 +12994,7 @@ up.history = (function () {
12962
12994
  /*-
12963
12995
  Returns a normalized URL for the previous history entry.
12964
12996
 
12965
- Only history entries pushed by Unpoly will be considered.
12997
+ Only history entries added by Unpoly functions will be considered.
12966
12998
 
12967
12999
  @property up.history.previousLocation
12968
13000
  @param {string} previousLocation
@@ -12976,13 +13008,20 @@ up.history = (function () {
12976
13008
  nextPreviousLocation = undefined;
12977
13009
  trackCurrentLocation();
12978
13010
  }
12979
- function normalizeURL(url, normalizeOptions = {}) {
12980
- normalizeOptions.hash = true;
12981
- return u.normalizeURL(url, normalizeOptions);
13011
+ const DEFAULT_NORMALIZE_OPTIONS = { hash: true };
13012
+ function normalizeURL(url, options) {
13013
+ // The reason why we this takes an { options } object is that
13014
+ // isCurrentLocation() ignores a trailing slash. This is used to check whether
13015
+ // we're already at the given URL before pushing a history state.
13016
+ options = u.merge(DEFAULT_NORMALIZE_OPTIONS, options);
13017
+ return u.normalizeURL(url, options);
12982
13018
  }
12983
13019
  /*-
12984
13020
  Returns a normalized URL for the current browser location.
12985
13021
 
13022
+ The returned URL is an absolute pathname like `"/path"` without a hostname or port.
13023
+ It will include a `#hash` fragment and query string, if present.
13024
+
12986
13025
  Note that if the current [layer](/up.layer) does not have [visible history](/up.Layer.prototype.history),
12987
13026
  the browser's address bar will show the location of an ancestor layer.
12988
13027
  To get the location of the current layer, use `up.layer.location`.
@@ -13008,11 +13047,51 @@ up.history = (function () {
13008
13047
  }
13009
13048
  }
13010
13049
  trackCurrentLocation();
13011
- function isCurrentLocation(url) {
13012
- // Some web frameworks care about a trailing slash, some consider it optional.
13013
- // Only for the equality test (is this the current URL) we consider it optional.
13014
- const normalizeOptions = { stripTrailingSlash: true };
13015
- return normalizeURL(url, normalizeOptions) === currentLocation(normalizeOptions);
13050
+ // Some web frameworks care about a trailing slash, some consider it optional.
13051
+ // Only for the equality test ("is this the current URL?") we consider it optional.
13052
+ // Note that we inherit { hash: true } from DEFAULT_NORMALIZE_OPTIONS.
13053
+ const ADDITIONAL_NORMALIZE_OPTIONS_FOR_COMPARISON = { trailingSlash: false };
13054
+ /*-
13055
+ Returns whether the given URL matches the [current browser location](/up.history.location).
13056
+
13057
+ ### Examples
13058
+
13059
+ ```js
13060
+ location.hostname // => '/path'
13061
+
13062
+ up.history.isLocation('/path') // => true
13063
+ up.history.isLocation('/path?query') // => false
13064
+ up.history.isLocation('/path#hash') // => false
13065
+ up.history.isLocation('/other') // => false
13066
+ ```
13067
+
13068
+ The given URL is [normalized](/up.util.normalizeURL), so any URL string pointing to the browser location
13069
+ will match:
13070
+
13071
+ ```js
13072
+ location.hostname // => '/current-host'
13073
+ location.pathname // => '/foo'
13074
+
13075
+ up.history.isLocation('/foo') // => true
13076
+ up.history.isLocation('http://current-host/foo') // => true
13077
+ up.history.isLocation('http://otgher-host/foo') // => false
13078
+ ```
13079
+
13080
+ @function up.history.isLocation
13081
+ @param {string} url
13082
+ The URL to compare against the current browser location.
13083
+
13084
+ This can be a either an absolute pathname (`/path`), a relative filename (`index.html`) or a fully qualified URL (`https://...`).
13085
+ @param {boolean} [options.hash=true]
13086
+ Whether to consider `#hash` fragments in the given or current URLs.
13087
+
13088
+ When set to `false` this function will consider the URLs `/foo#one` and `/foo#two` to be equal.
13089
+ @return {boolean}
13090
+ @experimental
13091
+ */
13092
+ function isLocation(url, options) {
13093
+ options = u.merge(ADDITIONAL_NORMALIZE_OPTIONS_FOR_COMPARISON, options);
13094
+ return normalizeURL(url, options) === currentLocation(options);
13016
13095
  }
13017
13096
  /*-
13018
13097
  Replaces the current history entry and updates the
@@ -13031,8 +13110,9 @@ up.history = (function () {
13031
13110
  @internal
13032
13111
  */
13033
13112
  function replace(url, options = {}) {
13113
+ url = normalizeURL(url);
13034
13114
  if (manipulate('replaceState', url) && (options.event !== false)) {
13035
- emit('up:location:changed', { url, reason: 'replace', log: `Replaced state for ${u.urlWithoutHost(url)}` });
13115
+ emit('up:location:changed', { url, reason: 'replace', log: `Replaced state for ${url}` });
13036
13116
  }
13037
13117
  }
13038
13118
  /*-
@@ -13045,6 +13125,8 @@ up.history = (function () {
13045
13125
  Note that [fragment navigation](/navigation) will automatically update the
13046
13126
  browser's location bar for you.
13047
13127
 
13128
+ Does not add a history entry if the the given URL is already the current browser location.
13129
+
13048
13130
  Emits event `up:location:changed`.
13049
13131
 
13050
13132
  @function up.history.push
@@ -13054,8 +13136,8 @@ up.history = (function () {
13054
13136
  */
13055
13137
  function push(url) {
13056
13138
  url = normalizeURL(url);
13057
- if (!isCurrentLocation(url) && manipulate('pushState', url)) {
13058
- up.emit('up:location:changed', { url, reason: 'push', log: `Advanced to location ${u.urlWithoutHost(url)}` });
13139
+ if (!isLocation(url) && manipulate('pushState', url)) {
13140
+ up.emit('up:location:changed', { url, reason: 'push', log: `Advanced to location ${url}` });
13059
13141
  }
13060
13142
  }
13061
13143
  /*-
@@ -13204,8 +13286,8 @@ up.history = (function () {
13204
13286
  replace,
13205
13287
  get location() { return currentLocation(); },
13206
13288
  get previousLocation() { return previousLocation; },
13207
- isLocation: isCurrentLocation,
13208
- normalizeURL
13289
+ normalizeURL,
13290
+ isLocation
13209
13291
  };
13210
13292
  })();
13211
13293
 
@@ -14957,7 +15039,7 @@ up.fragment = (function () {
14957
15039
  }
14958
15040
  up.on('up:framework:boot', function () {
14959
15041
  const { body } = document;
14960
- body.setAttribute('up-source', up.history.location);
15042
+ body.setAttribute('up-source', u.normalizeURL(location.href, { hash: false }));
14961
15043
  hello(body);
14962
15044
  if (!up.browser.canPushState()) {
14963
15045
  return up.warn('Cannot push history changes. Next fragment update will load in a new page.');
@@ -15585,7 +15667,7 @@ up.viewport = (function () {
15585
15667
  const [viewports, options] = parseOptions(args);
15586
15668
  const url = options.layer.location;
15587
15669
  const scrollTopsForURL = options.layer.lastScrollTops.get(url) || {};
15588
- up.puts('up.viewport.restoreScroll()', 'Restoring scroll positions for URL %s to %o', u.urlWithoutHost(url), scrollTopsForURL);
15670
+ up.puts('up.viewport.restoreScroll()', 'Restoring scroll positions for URL %s to %o', url, scrollTopsForURL);
15589
15671
  return setScrollTops(viewports, scrollTopsForURL);
15590
15672
  }
15591
15673
  function parseOptions(args) {
@@ -17031,20 +17113,11 @@ up.network = (function () {
17031
17113
  function makeRequest(...args) {
17032
17114
  const request = new up.Request(parseRequestOptions(args));
17033
17115
  useCachedRequest(request) || queueRequest(request);
17034
- let solo = request.solo;
17035
- if (solo) {
17036
- // The { solo } option may also contain a function.
17037
- // This way users can excempt some requests from being solo-aborted
17038
- // by configuring up.fragment.config.navigateOptions.
17039
- queue.abortExcept(request, solo);
17040
- }
17116
+ handleSolo(request);
17041
17117
  return request;
17042
17118
  }
17043
17119
  function mimicLocalRequest(options) {
17044
- let solo = options.solo;
17045
- if (solo) {
17046
- abortRequests(solo);
17047
- }
17120
+ handleSolo(options);
17048
17121
  // We cannot consult config.clearCache since there is no up.Request
17049
17122
  // for a local update.
17050
17123
  let clearCache = options.clearCache;
@@ -17052,6 +17125,21 @@ up.network = (function () {
17052
17125
  cache.clear(clearCache);
17053
17126
  }
17054
17127
  }
17128
+ function handleSolo(requestOrOptions) {
17129
+ let solo = requestOrOptions.solo;
17130
+ if (solo && isBusy()) {
17131
+ up.puts('up.request()', 'Change with { solo } option will abort other requests');
17132
+ // The { solo } option may also contain a function.
17133
+ // This way users can excempt some requests from being solo-aborted
17134
+ // by configuring up.fragment.config.navigateOptions.
17135
+ if (requestOrOptions instanceof up.Request) {
17136
+ queue.abortExcept(requestOrOptions, solo);
17137
+ }
17138
+ else {
17139
+ abortRequests(solo);
17140
+ }
17141
+ }
17142
+ }
17055
17143
  function parseRequestOptions(args) {
17056
17144
  const options = u.extractOptions(args);
17057
17145
  if (!options.url) {
@@ -21156,10 +21244,11 @@ up.feedback = (function () {
21156
21244
  */
21157
21245
  const config = new up.Config(() => ({
21158
21246
  currentClasses: ['up-current'],
21159
- navSelectors: ['[up-nav]', 'nav']
21247
+ navSelectors: ['[up-nav]', 'nav'],
21160
21248
  }));
21161
21249
  function reset() {
21162
21250
  config.reset();
21251
+ up.layer.root.feedbackLocation = null;
21163
21252
  }
21164
21253
  const CLASS_ACTIVE = 'up-active';
21165
21254
  const SELECTOR_LINK = 'a, [up-href]';
@@ -21168,7 +21257,7 @@ up.feedback = (function () {
21168
21257
  }
21169
21258
  function normalizeURL(url) {
21170
21259
  if (url) {
21171
- return u.normalizeURL(url, { stripTrailingSlash: true });
21260
+ return u.normalizeURL(url, { trailingSlash: false, hash: false });
21172
21261
  }
21173
21262
  }
21174
21263
  function linkURLs(link) {
@@ -21203,17 +21292,11 @@ up.feedback = (function () {
21203
21292
  const links = u.flatMap(navs, nav => e.subtree(nav, SELECTOR_LINK));
21204
21293
  updateLinks(links, options);
21205
21294
  }
21206
- const getLayerLocation = layer => // We store the last processed location in layer.feedbackLocation,
21207
-
21208
- // so multiple calls for the same layer won't unnecessarily reprocess links.
21209
- // We update the property on up:layer:location:changed.
21210
- //
21211
- // The { feedbackLocation } property may be nil if:
21212
- // (1) The layer was opened without a location, e.g. if it was created from local HTML.
21213
- // (2) The layer is the root layer and the location was never changed.
21214
- // The initial page load does not emit an up:layer:location:changed event for
21215
- // the root layer to be consistent with up:location:changed.
21216
- layer.feedbackLocation || layer.location;
21295
+ function getNormalizedLayerLocation(layer) {
21296
+ // Don't re-use layer.feedbackLocation since the current layer returns
21297
+ // location.href in case someone changed the history using the pushState API.
21298
+ return layer.feedbackLocation || normalizeURL(layer.location);
21299
+ }
21217
21300
  function updateLinks(links, options = {}) {
21218
21301
  if (!links.length) {
21219
21302
  return;
@@ -21221,7 +21304,7 @@ up.feedback = (function () {
21221
21304
  const layer = options.layer || up.layer.get(links[0]);
21222
21305
  // An overlay might not have a { location } property, e.g. if it was created
21223
21306
  // from local { content }. In this case we do not set .up-current.
21224
- let layerLocation = getLayerLocation(layer);
21307
+ let layerLocation = getNormalizedLayerLocation(layer);
21225
21308
  if (layerLocation) {
21226
21309
  for (let link of links) {
21227
21310
  const isCurrent = linkURLs(link).isCurrent(layerLocation);
@@ -21436,6 +21519,8 @@ up.feedback = (function () {
21436
21519
  - the link's `[up-href]` attribute
21437
21520
  - the URL pattern in the link's [`[up-alias]`](/a-up-alias) attribute
21438
21521
 
21522
+ Any `#hash` fragments in the link's or current URLs will be ignored.
21523
+
21439
21524
  @selector [up-nav]
21440
21525
  @stable
21441
21526
  */
@@ -21470,13 +21555,14 @@ up.feedback = (function () {
21470
21555
  */
21471
21556
  function updateLayerIfLocationChanged(layer) {
21472
21557
  const processedLocation = layer.feedbackLocation;
21473
- const currentLocation = normalizeURL(layer.location);
21558
+ const layerLocation = getNormalizedLayerLocation(layer.location);
21474
21559
  // A history change might call this function multiple times,
21475
21560
  // since we listen to both up:location:changed and up:layer:location:changed.
21561
+ // We also don't want to unnecessarily reprocess nav links, which is expensive.
21476
21562
  // For this reason we check whether the current location differs from
21477
21563
  // the last processed location.
21478
- if (!processedLocation || (processedLocation !== currentLocation)) {
21479
- layer.feedbackLocation = currentLocation;
21564
+ if (!processedLocation || (processedLocation !== layerLocation)) {
21565
+ layer.feedbackLocation = layerLocation;
21480
21566
  updateLinksWithinNavs(layer.element, { layer });
21481
21567
  }
21482
21568
  }
@@ -21506,7 +21592,7 @@ up.feedback = (function () {
21506
21592
  stop,
21507
21593
  around,
21508
21594
  aroundForOptions,
21509
- normalizeURL
21595
+ normalizeURL,
21510
21596
  };
21511
21597
  })();
21512
21598