turbo-rails 2.0.6 → 2.0.10
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.
- checksums.yaml +4 -4
- data/README.md +69 -1
- data/app/assets/javascripts/turbo.js +1991 -1914
- data/app/assets/javascripts/turbo.min.js +6 -6
- data/app/assets/javascripts/turbo.min.js.map +1 -1
- data/app/channels/turbo/streams/broadcasts.rb +17 -6
- data/app/helpers/turbo/streams_helper.rb +6 -0
- data/app/javascript/turbo/cable_stream_source_element.js +10 -0
- data/app/models/concerns/turbo/broadcastable.rb +32 -22
- data/app/models/turbo/streams/tag_builder.rb +26 -34
- data/config/routes.rb +3 -3
- data/lib/tasks/turbo_tasks.rake +0 -22
- data/lib/turbo/engine.rb +48 -3
- data/lib/turbo/system_test_helper.rb +128 -0
- data/lib/turbo/version.rb +1 -1
- metadata +4 -18
- data/lib/install/turbo_needs_redis.rb +0 -20
@@ -1,5 +1,5 @@
|
|
1
1
|
/*!
|
2
|
-
Turbo 8.0.
|
2
|
+
Turbo 8.0.6
|
3
3
|
Copyright © 2024 37signals LLC
|
4
4
|
*/
|
5
5
|
(function(prototype) {
|
@@ -116,6 +116,9 @@ class FrameElement extends HTMLElement {
|
|
116
116
|
this.removeAttribute("refresh");
|
117
117
|
}
|
118
118
|
}
|
119
|
+
get shouldReloadWithMorph() {
|
120
|
+
return this.src && this.refresh === "morph";
|
121
|
+
}
|
119
122
|
get loading() {
|
120
123
|
return frameLoadingStyleFromString(this.getAttribute("loading") || "");
|
121
124
|
}
|
@@ -167,115 +170,11 @@ function frameLoadingStyleFromString(style) {
|
|
167
170
|
}
|
168
171
|
}
|
169
172
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
let anchorMatch;
|
176
|
-
if (url.hash) {
|
177
|
-
return url.hash.slice(1);
|
178
|
-
} else if (anchorMatch = url.href.match(/#(.*)$/)) {
|
179
|
-
return anchorMatch[1];
|
180
|
-
}
|
181
|
-
}
|
182
|
-
|
183
|
-
function getAction$1(form, submitter) {
|
184
|
-
const action = submitter?.getAttribute("formaction") || form.getAttribute("action") || form.action;
|
185
|
-
return expandURL(action);
|
186
|
-
}
|
187
|
-
|
188
|
-
function getExtension(url) {
|
189
|
-
return (getLastPathComponent(url).match(/\.[^.]*$/) || [])[0] || "";
|
190
|
-
}
|
191
|
-
|
192
|
-
function isHTML(url) {
|
193
|
-
return !!getExtension(url).match(/^(?:|\.(?:htm|html|xhtml|php))$/);
|
194
|
-
}
|
195
|
-
|
196
|
-
function isPrefixedBy(baseURL, url) {
|
197
|
-
const prefix = getPrefix(url);
|
198
|
-
return baseURL.href === expandURL(prefix).href || baseURL.href.startsWith(prefix);
|
199
|
-
}
|
200
|
-
|
201
|
-
function locationIsVisitable(location, rootLocation) {
|
202
|
-
return isPrefixedBy(location, rootLocation) && isHTML(location);
|
203
|
-
}
|
204
|
-
|
205
|
-
function getRequestURL(url) {
|
206
|
-
const anchor = getAnchor(url);
|
207
|
-
return anchor != null ? url.href.slice(0, -(anchor.length + 1)) : url.href;
|
208
|
-
}
|
209
|
-
|
210
|
-
function toCacheKey(url) {
|
211
|
-
return getRequestURL(url);
|
212
|
-
}
|
213
|
-
|
214
|
-
function urlsAreEqual(left, right) {
|
215
|
-
return expandURL(left).href == expandURL(right).href;
|
216
|
-
}
|
217
|
-
|
218
|
-
function getPathComponents(url) {
|
219
|
-
return url.pathname.split("/").slice(1);
|
220
|
-
}
|
221
|
-
|
222
|
-
function getLastPathComponent(url) {
|
223
|
-
return getPathComponents(url).slice(-1)[0];
|
224
|
-
}
|
225
|
-
|
226
|
-
function getPrefix(url) {
|
227
|
-
return addTrailingSlash(url.origin + url.pathname);
|
228
|
-
}
|
229
|
-
|
230
|
-
function addTrailingSlash(value) {
|
231
|
-
return value.endsWith("/") ? value : value + "/";
|
232
|
-
}
|
233
|
-
|
234
|
-
class FetchResponse {
|
235
|
-
constructor(response) {
|
236
|
-
this.response = response;
|
237
|
-
}
|
238
|
-
get succeeded() {
|
239
|
-
return this.response.ok;
|
240
|
-
}
|
241
|
-
get failed() {
|
242
|
-
return !this.succeeded;
|
243
|
-
}
|
244
|
-
get clientError() {
|
245
|
-
return this.statusCode >= 400 && this.statusCode <= 499;
|
246
|
-
}
|
247
|
-
get serverError() {
|
248
|
-
return this.statusCode >= 500 && this.statusCode <= 599;
|
249
|
-
}
|
250
|
-
get redirected() {
|
251
|
-
return this.response.redirected;
|
252
|
-
}
|
253
|
-
get location() {
|
254
|
-
return expandURL(this.response.url);
|
255
|
-
}
|
256
|
-
get isHTML() {
|
257
|
-
return this.contentType && this.contentType.match(/^(?:text\/([^\s;,]+\b)?html|application\/xhtml\+xml)\b/);
|
258
|
-
}
|
259
|
-
get statusCode() {
|
260
|
-
return this.response.status;
|
261
|
-
}
|
262
|
-
get contentType() {
|
263
|
-
return this.header("Content-Type");
|
264
|
-
}
|
265
|
-
get responseText() {
|
266
|
-
return this.response.clone().text();
|
267
|
-
}
|
268
|
-
get responseHTML() {
|
269
|
-
if (this.isHTML) {
|
270
|
-
return this.response.clone().text();
|
271
|
-
} else {
|
272
|
-
return Promise.resolve(undefined);
|
273
|
-
}
|
274
|
-
}
|
275
|
-
header(name) {
|
276
|
-
return this.response.headers.get(name);
|
277
|
-
}
|
278
|
-
}
|
173
|
+
const drive = {
|
174
|
+
enabled: true,
|
175
|
+
progressBarDelay: 500,
|
176
|
+
unvisitableExtensions: new Set([ ".7z", ".aac", ".apk", ".avi", ".bmp", ".bz2", ".css", ".csv", ".deb", ".dmg", ".doc", ".docx", ".exe", ".gif", ".gz", ".heic", ".heif", ".ico", ".iso", ".jpeg", ".jpg", ".js", ".json", ".m4a", ".mkv", ".mov", ".mp3", ".mp4", ".mpeg", ".mpg", ".msi", ".ogg", ".ogv", ".pdf", ".pkg", ".png", ".ppt", ".pptx", ".rar", ".rtf", ".svg", ".tar", ".tif", ".tiff", ".txt", ".wav", ".webm", ".webp", ".wma", ".wmv", ".xls", ".xlsx", ".xml", ".zip" ])
|
177
|
+
};
|
279
178
|
|
280
179
|
function activateScriptElement(element) {
|
281
180
|
if (element.getAttribute("data-turbo-eval") == "false") {
|
@@ -320,6 +219,11 @@ function dispatch(eventName, {target: target, cancelable: cancelable, detail: de
|
|
320
219
|
return event;
|
321
220
|
}
|
322
221
|
|
222
|
+
function cancelEvent(event) {
|
223
|
+
event.preventDefault();
|
224
|
+
event.stopImmediatePropagation();
|
225
|
+
}
|
226
|
+
|
323
227
|
function nextRepaint() {
|
324
228
|
if (document.visibilityState === "hidden") {
|
325
229
|
return nextEventLoopTick();
|
@@ -513,6 +417,152 @@ function debounce(fn, delay) {
|
|
513
417
|
};
|
514
418
|
}
|
515
419
|
|
420
|
+
const submitter = {
|
421
|
+
"aria-disabled": {
|
422
|
+
beforeSubmit: submitter => {
|
423
|
+
submitter.setAttribute("aria-disabled", "true");
|
424
|
+
submitter.addEventListener("click", cancelEvent);
|
425
|
+
},
|
426
|
+
afterSubmit: submitter => {
|
427
|
+
submitter.removeAttribute("aria-disabled");
|
428
|
+
submitter.removeEventListener("click", cancelEvent);
|
429
|
+
}
|
430
|
+
},
|
431
|
+
disabled: {
|
432
|
+
beforeSubmit: submitter => submitter.disabled = true,
|
433
|
+
afterSubmit: submitter => submitter.disabled = false
|
434
|
+
}
|
435
|
+
};
|
436
|
+
|
437
|
+
class Config {
|
438
|
+
#submitter=null;
|
439
|
+
constructor(config) {
|
440
|
+
Object.assign(this, config);
|
441
|
+
}
|
442
|
+
get submitter() {
|
443
|
+
return this.#submitter;
|
444
|
+
}
|
445
|
+
set submitter(value) {
|
446
|
+
this.#submitter = submitter[value] || value;
|
447
|
+
}
|
448
|
+
}
|
449
|
+
|
450
|
+
const forms = new Config({
|
451
|
+
mode: "on",
|
452
|
+
submitter: "disabled"
|
453
|
+
});
|
454
|
+
|
455
|
+
const config = {
|
456
|
+
drive: drive,
|
457
|
+
forms: forms
|
458
|
+
};
|
459
|
+
|
460
|
+
function expandURL(locatable) {
|
461
|
+
return new URL(locatable.toString(), document.baseURI);
|
462
|
+
}
|
463
|
+
|
464
|
+
function getAnchor(url) {
|
465
|
+
let anchorMatch;
|
466
|
+
if (url.hash) {
|
467
|
+
return url.hash.slice(1);
|
468
|
+
} else if (anchorMatch = url.href.match(/#(.*)$/)) {
|
469
|
+
return anchorMatch[1];
|
470
|
+
}
|
471
|
+
}
|
472
|
+
|
473
|
+
function getAction$1(form, submitter) {
|
474
|
+
const action = submitter?.getAttribute("formaction") || form.getAttribute("action") || form.action;
|
475
|
+
return expandURL(action);
|
476
|
+
}
|
477
|
+
|
478
|
+
function getExtension(url) {
|
479
|
+
return (getLastPathComponent(url).match(/\.[^.]*$/) || [])[0] || "";
|
480
|
+
}
|
481
|
+
|
482
|
+
function isPrefixedBy(baseURL, url) {
|
483
|
+
const prefix = getPrefix(url);
|
484
|
+
return baseURL.href === expandURL(prefix).href || baseURL.href.startsWith(prefix);
|
485
|
+
}
|
486
|
+
|
487
|
+
function locationIsVisitable(location, rootLocation) {
|
488
|
+
return isPrefixedBy(location, rootLocation) && !config.drive.unvisitableExtensions.has(getExtension(location));
|
489
|
+
}
|
490
|
+
|
491
|
+
function getRequestURL(url) {
|
492
|
+
const anchor = getAnchor(url);
|
493
|
+
return anchor != null ? url.href.slice(0, -(anchor.length + 1)) : url.href;
|
494
|
+
}
|
495
|
+
|
496
|
+
function toCacheKey(url) {
|
497
|
+
return getRequestURL(url);
|
498
|
+
}
|
499
|
+
|
500
|
+
function urlsAreEqual(left, right) {
|
501
|
+
return expandURL(left).href == expandURL(right).href;
|
502
|
+
}
|
503
|
+
|
504
|
+
function getPathComponents(url) {
|
505
|
+
return url.pathname.split("/").slice(1);
|
506
|
+
}
|
507
|
+
|
508
|
+
function getLastPathComponent(url) {
|
509
|
+
return getPathComponents(url).slice(-1)[0];
|
510
|
+
}
|
511
|
+
|
512
|
+
function getPrefix(url) {
|
513
|
+
return addTrailingSlash(url.origin + url.pathname);
|
514
|
+
}
|
515
|
+
|
516
|
+
function addTrailingSlash(value) {
|
517
|
+
return value.endsWith("/") ? value : value + "/";
|
518
|
+
}
|
519
|
+
|
520
|
+
class FetchResponse {
|
521
|
+
constructor(response) {
|
522
|
+
this.response = response;
|
523
|
+
}
|
524
|
+
get succeeded() {
|
525
|
+
return this.response.ok;
|
526
|
+
}
|
527
|
+
get failed() {
|
528
|
+
return !this.succeeded;
|
529
|
+
}
|
530
|
+
get clientError() {
|
531
|
+
return this.statusCode >= 400 && this.statusCode <= 499;
|
532
|
+
}
|
533
|
+
get serverError() {
|
534
|
+
return this.statusCode >= 500 && this.statusCode <= 599;
|
535
|
+
}
|
536
|
+
get redirected() {
|
537
|
+
return this.response.redirected;
|
538
|
+
}
|
539
|
+
get location() {
|
540
|
+
return expandURL(this.response.url);
|
541
|
+
}
|
542
|
+
get isHTML() {
|
543
|
+
return this.contentType && this.contentType.match(/^(?:text\/([^\s;,]+\b)?html|application\/xhtml\+xml)\b/);
|
544
|
+
}
|
545
|
+
get statusCode() {
|
546
|
+
return this.response.status;
|
547
|
+
}
|
548
|
+
get contentType() {
|
549
|
+
return this.header("Content-Type");
|
550
|
+
}
|
551
|
+
get responseText() {
|
552
|
+
return this.response.clone().text();
|
553
|
+
}
|
554
|
+
get responseHTML() {
|
555
|
+
if (this.isHTML) {
|
556
|
+
return this.response.clone().text();
|
557
|
+
} else {
|
558
|
+
return Promise.resolve(undefined);
|
559
|
+
}
|
560
|
+
}
|
561
|
+
header(name) {
|
562
|
+
return this.response.headers.get(name);
|
563
|
+
}
|
564
|
+
}
|
565
|
+
|
516
566
|
class LimitedSet extends Set {
|
517
567
|
constructor(maxSize) {
|
518
568
|
super();
|
@@ -861,7 +911,7 @@ const FormSubmissionState = {
|
|
861
911
|
|
862
912
|
class FormSubmission {
|
863
913
|
state=FormSubmissionState.initialized;
|
864
|
-
static confirmMethod(message
|
914
|
+
static confirmMethod(message) {
|
865
915
|
return Promise.resolve(confirm(message));
|
866
916
|
}
|
867
917
|
constructor(delegate, formElement, submitter, mustRedirect = false) {
|
@@ -903,7 +953,8 @@ class FormSubmission {
|
|
903
953
|
const {initialized: initialized, requesting: requesting} = FormSubmissionState;
|
904
954
|
const confirmationMessage = getAttribute("data-turbo-confirm", this.submitter, this.formElement);
|
905
955
|
if (typeof confirmationMessage === "string") {
|
906
|
-
const
|
956
|
+
const confirmMethod = typeof config.forms.confirm === "function" ? config.forms.confirm : FormSubmission.confirmMethod;
|
957
|
+
const answer = await confirmMethod(confirmationMessage, this.formElement, this.submitter);
|
907
958
|
if (!answer) {
|
908
959
|
return;
|
909
960
|
}
|
@@ -934,7 +985,7 @@ class FormSubmission {
|
|
934
985
|
}
|
935
986
|
requestStarted(_request) {
|
936
987
|
this.state = FormSubmissionState.waiting;
|
937
|
-
this.submitter
|
988
|
+
if (this.submitter) config.forms.submitter.beforeSubmit(this.submitter);
|
938
989
|
this.setSubmitsWith();
|
939
990
|
markAsBusy(this.formElement);
|
940
991
|
dispatch("turbo:submit-start", {
|
@@ -986,7 +1037,7 @@ class FormSubmission {
|
|
986
1037
|
}
|
987
1038
|
requestFinished(_request) {
|
988
1039
|
this.state = FormSubmissionState.stopped;
|
989
|
-
this.submitter
|
1040
|
+
if (this.submitter) config.forms.submitter.afterSubmit(this.submitter);
|
990
1041
|
this.resetSubmitterText();
|
991
1042
|
clearBusyState(this.formElement);
|
992
1043
|
dispatch("turbo:submit-end", {
|
@@ -1626,2083 +1677,2085 @@ function readScrollBehavior(value, defaultValue) {
|
|
1626
1677
|
}
|
1627
1678
|
}
|
1628
1679
|
|
1629
|
-
|
1630
|
-
|
1631
|
-
|
1632
|
-
|
1633
|
-
|
1634
|
-
|
1635
|
-
|
1636
|
-
|
1637
|
-
|
1638
|
-
|
1639
|
-
|
1640
|
-
|
1641
|
-
|
1642
|
-
|
1643
|
-
|
1644
|
-
|
1645
|
-
|
1646
|
-
|
1647
|
-
|
1648
|
-
|
1649
|
-
|
1650
|
-
|
1651
|
-
|
1652
|
-
this.stylesheetElement = this.createStylesheetElement();
|
1653
|
-
this.progressElement = this.createProgressElement();
|
1654
|
-
this.installStylesheetElement();
|
1655
|
-
this.setValue(0);
|
1656
|
-
}
|
1657
|
-
show() {
|
1658
|
-
if (!this.visible) {
|
1659
|
-
this.visible = true;
|
1660
|
-
this.installProgressElement();
|
1661
|
-
this.startTrickling();
|
1680
|
+
var Idiomorph = function() {
|
1681
|
+
let EMPTY_SET = new Set;
|
1682
|
+
let defaults = {
|
1683
|
+
morphStyle: "outerHTML",
|
1684
|
+
callbacks: {
|
1685
|
+
beforeNodeAdded: noOp,
|
1686
|
+
afterNodeAdded: noOp,
|
1687
|
+
beforeNodeMorphed: noOp,
|
1688
|
+
afterNodeMorphed: noOp,
|
1689
|
+
beforeNodeRemoved: noOp,
|
1690
|
+
afterNodeRemoved: noOp,
|
1691
|
+
beforeAttributeUpdated: noOp
|
1692
|
+
},
|
1693
|
+
head: {
|
1694
|
+
style: "merge",
|
1695
|
+
shouldPreserve: function(elt) {
|
1696
|
+
return elt.getAttribute("im-preserve") === "true";
|
1697
|
+
},
|
1698
|
+
shouldReAppend: function(elt) {
|
1699
|
+
return elt.getAttribute("im-re-append") === "true";
|
1700
|
+
},
|
1701
|
+
shouldRemove: noOp,
|
1702
|
+
afterHeadMorphed: noOp
|
1662
1703
|
}
|
1663
|
-
}
|
1664
|
-
|
1665
|
-
if (
|
1666
|
-
|
1667
|
-
this.fadeProgressElement((() => {
|
1668
|
-
this.uninstallProgressElement();
|
1669
|
-
this.stopTrickling();
|
1670
|
-
this.visible = false;
|
1671
|
-
this.hiding = false;
|
1672
|
-
}));
|
1704
|
+
};
|
1705
|
+
function morph(oldNode, newContent, config = {}) {
|
1706
|
+
if (oldNode instanceof Document) {
|
1707
|
+
oldNode = oldNode.documentElement;
|
1673
1708
|
}
|
1674
|
-
|
1675
|
-
|
1676
|
-
this.value = value;
|
1677
|
-
this.refresh();
|
1678
|
-
}
|
1679
|
-
installStylesheetElement() {
|
1680
|
-
document.head.insertBefore(this.stylesheetElement, document.head.firstChild);
|
1681
|
-
}
|
1682
|
-
installProgressElement() {
|
1683
|
-
this.progressElement.style.width = "0";
|
1684
|
-
this.progressElement.style.opacity = "1";
|
1685
|
-
document.documentElement.insertBefore(this.progressElement, document.body);
|
1686
|
-
this.refresh();
|
1687
|
-
}
|
1688
|
-
fadeProgressElement(callback) {
|
1689
|
-
this.progressElement.style.opacity = "0";
|
1690
|
-
setTimeout(callback, ProgressBar.animationDuration * 1.5);
|
1691
|
-
}
|
1692
|
-
uninstallProgressElement() {
|
1693
|
-
if (this.progressElement.parentNode) {
|
1694
|
-
document.documentElement.removeChild(this.progressElement);
|
1709
|
+
if (typeof newContent === "string") {
|
1710
|
+
newContent = parseContent(newContent);
|
1695
1711
|
}
|
1712
|
+
let normalizedContent = normalizeContent(newContent);
|
1713
|
+
let ctx = createMorphContext(oldNode, normalizedContent, config);
|
1714
|
+
return morphNormalizedContent(oldNode, normalizedContent, ctx);
|
1696
1715
|
}
|
1697
|
-
|
1698
|
-
if (
|
1699
|
-
|
1716
|
+
function morphNormalizedContent(oldNode, normalizedNewContent, ctx) {
|
1717
|
+
if (ctx.head.block) {
|
1718
|
+
let oldHead = oldNode.querySelector("head");
|
1719
|
+
let newHead = normalizedNewContent.querySelector("head");
|
1720
|
+
if (oldHead && newHead) {
|
1721
|
+
let promises = handleHeadElement(newHead, oldHead, ctx);
|
1722
|
+
Promise.all(promises).then((function() {
|
1723
|
+
morphNormalizedContent(oldNode, normalizedNewContent, Object.assign(ctx, {
|
1724
|
+
head: {
|
1725
|
+
block: false,
|
1726
|
+
ignore: true
|
1727
|
+
}
|
1728
|
+
}));
|
1729
|
+
}));
|
1730
|
+
return;
|
1731
|
+
}
|
1700
1732
|
}
|
1701
|
-
|
1702
|
-
|
1703
|
-
|
1704
|
-
|
1705
|
-
|
1706
|
-
|
1707
|
-
|
1708
|
-
|
1709
|
-
|
1710
|
-
|
1711
|
-
|
1712
|
-
|
1713
|
-
|
1714
|
-
|
1715
|
-
|
1716
|
-
element.type = "text/css";
|
1717
|
-
element.textContent = ProgressBar.defaultCSS;
|
1718
|
-
if (this.cspNonce) {
|
1719
|
-
element.nonce = this.cspNonce;
|
1733
|
+
if (ctx.morphStyle === "innerHTML") {
|
1734
|
+
morphChildren(normalizedNewContent, oldNode, ctx);
|
1735
|
+
return oldNode.children;
|
1736
|
+
} else if (ctx.morphStyle === "outerHTML" || ctx.morphStyle == null) {
|
1737
|
+
let bestMatch = findBestNodeMatch(normalizedNewContent, oldNode, ctx);
|
1738
|
+
let previousSibling = bestMatch?.previousSibling;
|
1739
|
+
let nextSibling = bestMatch?.nextSibling;
|
1740
|
+
let morphedNode = morphOldNodeTo(oldNode, bestMatch, ctx);
|
1741
|
+
if (bestMatch) {
|
1742
|
+
return insertSiblings(previousSibling, morphedNode, nextSibling);
|
1743
|
+
} else {
|
1744
|
+
return [];
|
1745
|
+
}
|
1746
|
+
} else {
|
1747
|
+
throw "Do not understand how to morph style " + ctx.morphStyle;
|
1720
1748
|
}
|
1721
|
-
return element;
|
1722
1749
|
}
|
1723
|
-
|
1724
|
-
|
1725
|
-
element.className = "turbo-progress-bar";
|
1726
|
-
return element;
|
1727
|
-
}
|
1728
|
-
get cspNonce() {
|
1729
|
-
return getMetaContent("csp-nonce");
|
1750
|
+
function ignoreValueOfActiveElement(possibleActiveElement, ctx) {
|
1751
|
+
return ctx.ignoreActiveValue && possibleActiveElement === document.activeElement && possibleActiveElement !== document.body;
|
1730
1752
|
}
|
1731
|
-
|
1732
|
-
|
1733
|
-
|
1734
|
-
|
1735
|
-
|
1736
|
-
|
1737
|
-
|
1738
|
-
|
1739
|
-
|
1740
|
-
|
1741
|
-
|
1742
|
-
|
1743
|
-
|
1744
|
-
|
1745
|
-
|
1753
|
+
function morphOldNodeTo(oldNode, newContent, ctx) {
|
1754
|
+
if (ctx.ignoreActive && oldNode === document.activeElement) ; else if (newContent == null) {
|
1755
|
+
if (ctx.callbacks.beforeNodeRemoved(oldNode) === false) return oldNode;
|
1756
|
+
oldNode.remove();
|
1757
|
+
ctx.callbacks.afterNodeRemoved(oldNode);
|
1758
|
+
return null;
|
1759
|
+
} else if (!isSoftMatch(oldNode, newContent)) {
|
1760
|
+
if (ctx.callbacks.beforeNodeRemoved(oldNode) === false) return oldNode;
|
1761
|
+
if (ctx.callbacks.beforeNodeAdded(newContent) === false) return oldNode;
|
1762
|
+
oldNode.parentElement.replaceChild(newContent, oldNode);
|
1763
|
+
ctx.callbacks.afterNodeAdded(newContent);
|
1764
|
+
ctx.callbacks.afterNodeRemoved(oldNode);
|
1765
|
+
return newContent;
|
1766
|
+
} else {
|
1767
|
+
if (ctx.callbacks.beforeNodeMorphed(oldNode, newContent) === false) return oldNode;
|
1768
|
+
if (oldNode instanceof HTMLHeadElement && ctx.head.ignore) ; else if (oldNode instanceof HTMLHeadElement && ctx.head.style !== "morph") {
|
1769
|
+
handleHeadElement(newContent, oldNode, ctx);
|
1770
|
+
} else {
|
1771
|
+
syncNodeFrom(newContent, oldNode, ctx);
|
1772
|
+
if (!ignoreValueOfActiveElement(oldNode, ctx)) {
|
1773
|
+
morphChildren(newContent, oldNode, ctx);
|
1774
|
+
}
|
1746
1775
|
}
|
1747
|
-
|
1748
|
-
|
1749
|
-
|
1750
|
-
return Object.keys(this.detailsByOuterHTML).filter((outerHTML => this.detailsByOuterHTML[outerHTML].tracked)).join("");
|
1776
|
+
ctx.callbacks.afterNodeMorphed(oldNode, newContent);
|
1777
|
+
return oldNode;
|
1778
|
+
}
|
1751
1779
|
}
|
1752
|
-
|
1753
|
-
|
1780
|
+
function morphChildren(newParent, oldParent, ctx) {
|
1781
|
+
let nextNewChild = newParent.firstChild;
|
1782
|
+
let insertionPoint = oldParent.firstChild;
|
1783
|
+
let newChild;
|
1784
|
+
while (nextNewChild) {
|
1785
|
+
newChild = nextNewChild;
|
1786
|
+
nextNewChild = newChild.nextSibling;
|
1787
|
+
if (insertionPoint == null) {
|
1788
|
+
if (ctx.callbacks.beforeNodeAdded(newChild) === false) return;
|
1789
|
+
oldParent.appendChild(newChild);
|
1790
|
+
ctx.callbacks.afterNodeAdded(newChild);
|
1791
|
+
removeIdsFromConsideration(ctx, newChild);
|
1792
|
+
continue;
|
1793
|
+
}
|
1794
|
+
if (isIdSetMatch(newChild, insertionPoint, ctx)) {
|
1795
|
+
morphOldNodeTo(insertionPoint, newChild, ctx);
|
1796
|
+
insertionPoint = insertionPoint.nextSibling;
|
1797
|
+
removeIdsFromConsideration(ctx, newChild);
|
1798
|
+
continue;
|
1799
|
+
}
|
1800
|
+
let idSetMatch = findIdSetMatch(newParent, oldParent, newChild, insertionPoint, ctx);
|
1801
|
+
if (idSetMatch) {
|
1802
|
+
insertionPoint = removeNodesBetween(insertionPoint, idSetMatch, ctx);
|
1803
|
+
morphOldNodeTo(idSetMatch, newChild, ctx);
|
1804
|
+
removeIdsFromConsideration(ctx, newChild);
|
1805
|
+
continue;
|
1806
|
+
}
|
1807
|
+
let softMatch = findSoftMatch(newParent, oldParent, newChild, insertionPoint, ctx);
|
1808
|
+
if (softMatch) {
|
1809
|
+
insertionPoint = removeNodesBetween(insertionPoint, softMatch, ctx);
|
1810
|
+
morphOldNodeTo(softMatch, newChild, ctx);
|
1811
|
+
removeIdsFromConsideration(ctx, newChild);
|
1812
|
+
continue;
|
1813
|
+
}
|
1814
|
+
if (ctx.callbacks.beforeNodeAdded(newChild) === false) return;
|
1815
|
+
oldParent.insertBefore(newChild, insertionPoint);
|
1816
|
+
ctx.callbacks.afterNodeAdded(newChild);
|
1817
|
+
removeIdsFromConsideration(ctx, newChild);
|
1818
|
+
}
|
1819
|
+
while (insertionPoint !== null) {
|
1820
|
+
let tempNode = insertionPoint;
|
1821
|
+
insertionPoint = insertionPoint.nextSibling;
|
1822
|
+
removeNode(tempNode, ctx);
|
1823
|
+
}
|
1754
1824
|
}
|
1755
|
-
|
1756
|
-
|
1825
|
+
function ignoreAttribute(attr, to, updateType, ctx) {
|
1826
|
+
if (attr === "value" && ctx.ignoreActiveValue && to === document.activeElement) {
|
1827
|
+
return true;
|
1828
|
+
}
|
1829
|
+
return ctx.callbacks.beforeAttributeUpdated(attr, to, updateType) === false;
|
1757
1830
|
}
|
1758
|
-
|
1759
|
-
|
1831
|
+
function syncNodeFrom(from, to, ctx) {
|
1832
|
+
let type = from.nodeType;
|
1833
|
+
if (type === 1) {
|
1834
|
+
const fromAttributes = from.attributes;
|
1835
|
+
const toAttributes = to.attributes;
|
1836
|
+
for (const fromAttribute of fromAttributes) {
|
1837
|
+
if (ignoreAttribute(fromAttribute.name, to, "update", ctx)) {
|
1838
|
+
continue;
|
1839
|
+
}
|
1840
|
+
if (to.getAttribute(fromAttribute.name) !== fromAttribute.value) {
|
1841
|
+
to.setAttribute(fromAttribute.name, fromAttribute.value);
|
1842
|
+
}
|
1843
|
+
}
|
1844
|
+
for (let i = toAttributes.length - 1; 0 <= i; i--) {
|
1845
|
+
const toAttribute = toAttributes[i];
|
1846
|
+
if (ignoreAttribute(toAttribute.name, to, "remove", ctx)) {
|
1847
|
+
continue;
|
1848
|
+
}
|
1849
|
+
if (!from.hasAttribute(toAttribute.name)) {
|
1850
|
+
to.removeAttribute(toAttribute.name);
|
1851
|
+
}
|
1852
|
+
}
|
1853
|
+
}
|
1854
|
+
if (type === 8 || type === 3) {
|
1855
|
+
if (to.nodeValue !== from.nodeValue) {
|
1856
|
+
to.nodeValue = from.nodeValue;
|
1857
|
+
}
|
1858
|
+
}
|
1859
|
+
if (!ignoreValueOfActiveElement(to, ctx)) {
|
1860
|
+
syncInputValue(from, to, ctx);
|
1861
|
+
}
|
1760
1862
|
}
|
1761
|
-
|
1762
|
-
|
1763
|
-
|
1764
|
-
if (
|
1765
|
-
|
1766
|
-
}
|
1767
|
-
|
1863
|
+
function syncBooleanAttribute(from, to, attributeName, ctx) {
|
1864
|
+
if (from[attributeName] !== to[attributeName]) {
|
1865
|
+
let ignoreUpdate = ignoreAttribute(attributeName, to, "update", ctx);
|
1866
|
+
if (!ignoreUpdate) {
|
1867
|
+
to[attributeName] = from[attributeName];
|
1868
|
+
}
|
1869
|
+
if (from[attributeName]) {
|
1870
|
+
if (!ignoreUpdate) {
|
1871
|
+
to.setAttribute(attributeName, from[attributeName]);
|
1872
|
+
}
|
1768
1873
|
} else {
|
1769
|
-
|
1874
|
+
if (!ignoreAttribute(attributeName, to, "remove", ctx)) {
|
1875
|
+
to.removeAttribute(attributeName);
|
1876
|
+
}
|
1770
1877
|
}
|
1771
|
-
}
|
1772
|
-
}
|
1773
|
-
getMetaValue(name) {
|
1774
|
-
const element = this.findMetaElementByName(name);
|
1775
|
-
return element ? element.getAttribute("content") : null;
|
1776
|
-
}
|
1777
|
-
findMetaElementByName(name) {
|
1778
|
-
return Object.keys(this.detailsByOuterHTML).reduce(((result, outerHTML) => {
|
1779
|
-
const {elements: [element]} = this.detailsByOuterHTML[outerHTML];
|
1780
|
-
return elementIsMetaElementWithName(element, name) ? element : result;
|
1781
|
-
}), undefined | undefined);
|
1782
|
-
}
|
1783
|
-
}
|
1784
|
-
|
1785
|
-
function elementType(element) {
|
1786
|
-
if (elementIsScript(element)) {
|
1787
|
-
return "script";
|
1788
|
-
} else if (elementIsStylesheet(element)) {
|
1789
|
-
return "stylesheet";
|
1790
|
-
}
|
1791
|
-
}
|
1792
|
-
|
1793
|
-
function elementIsTracked(element) {
|
1794
|
-
return element.getAttribute("data-turbo-track") == "reload";
|
1795
|
-
}
|
1796
|
-
|
1797
|
-
function elementIsScript(element) {
|
1798
|
-
const tagName = element.localName;
|
1799
|
-
return tagName == "script";
|
1800
|
-
}
|
1801
|
-
|
1802
|
-
function elementIsNoscript(element) {
|
1803
|
-
const tagName = element.localName;
|
1804
|
-
return tagName == "noscript";
|
1805
|
-
}
|
1806
|
-
|
1807
|
-
function elementIsStylesheet(element) {
|
1808
|
-
const tagName = element.localName;
|
1809
|
-
return tagName == "style" || tagName == "link" && element.getAttribute("rel") == "stylesheet";
|
1810
|
-
}
|
1811
|
-
|
1812
|
-
function elementIsMetaElementWithName(element, name) {
|
1813
|
-
const tagName = element.localName;
|
1814
|
-
return tagName == "meta" && element.getAttribute("name") == name;
|
1815
|
-
}
|
1816
|
-
|
1817
|
-
function elementWithoutNonce(element) {
|
1818
|
-
if (element.hasAttribute("nonce")) {
|
1819
|
-
element.setAttribute("nonce", "");
|
1878
|
+
}
|
1820
1879
|
}
|
1821
|
-
|
1822
|
-
|
1823
|
-
|
1824
|
-
|
1825
|
-
|
1826
|
-
|
1880
|
+
function syncInputValue(from, to, ctx) {
|
1881
|
+
if (from instanceof HTMLInputElement && to instanceof HTMLInputElement && from.type !== "file") {
|
1882
|
+
let fromValue = from.value;
|
1883
|
+
let toValue = to.value;
|
1884
|
+
syncBooleanAttribute(from, to, "checked", ctx);
|
1885
|
+
syncBooleanAttribute(from, to, "disabled", ctx);
|
1886
|
+
if (!from.hasAttribute("value")) {
|
1887
|
+
if (!ignoreAttribute("value", to, "remove", ctx)) {
|
1888
|
+
to.value = "";
|
1889
|
+
to.removeAttribute("value");
|
1890
|
+
}
|
1891
|
+
} else if (fromValue !== toValue) {
|
1892
|
+
if (!ignoreAttribute("value", to, "update", ctx)) {
|
1893
|
+
to.setAttribute("value", fromValue);
|
1894
|
+
to.value = fromValue;
|
1895
|
+
}
|
1896
|
+
}
|
1897
|
+
} else if (from instanceof HTMLOptionElement) {
|
1898
|
+
syncBooleanAttribute(from, to, "selected", ctx);
|
1899
|
+
} else if (from instanceof HTMLTextAreaElement && to instanceof HTMLTextAreaElement) {
|
1900
|
+
let fromValue = from.value;
|
1901
|
+
let toValue = to.value;
|
1902
|
+
if (ignoreAttribute("value", to, "update", ctx)) {
|
1903
|
+
return;
|
1904
|
+
}
|
1905
|
+
if (fromValue !== toValue) {
|
1906
|
+
to.value = fromValue;
|
1907
|
+
}
|
1908
|
+
if (to.firstChild && to.firstChild.nodeValue !== fromValue) {
|
1909
|
+
to.firstChild.nodeValue = fromValue;
|
1910
|
+
}
|
1911
|
+
}
|
1827
1912
|
}
|
1828
|
-
|
1829
|
-
|
1913
|
+
function handleHeadElement(newHeadTag, currentHead, ctx) {
|
1914
|
+
let added = [];
|
1915
|
+
let removed = [];
|
1916
|
+
let preserved = [];
|
1917
|
+
let nodesToAppend = [];
|
1918
|
+
let headMergeStyle = ctx.head.style;
|
1919
|
+
let srcToNewHeadNodes = new Map;
|
1920
|
+
for (const newHeadChild of newHeadTag.children) {
|
1921
|
+
srcToNewHeadNodes.set(newHeadChild.outerHTML, newHeadChild);
|
1922
|
+
}
|
1923
|
+
for (const currentHeadElt of currentHead.children) {
|
1924
|
+
let inNewContent = srcToNewHeadNodes.has(currentHeadElt.outerHTML);
|
1925
|
+
let isReAppended = ctx.head.shouldReAppend(currentHeadElt);
|
1926
|
+
let isPreserved = ctx.head.shouldPreserve(currentHeadElt);
|
1927
|
+
if (inNewContent || isPreserved) {
|
1928
|
+
if (isReAppended) {
|
1929
|
+
removed.push(currentHeadElt);
|
1930
|
+
} else {
|
1931
|
+
srcToNewHeadNodes.delete(currentHeadElt.outerHTML);
|
1932
|
+
preserved.push(currentHeadElt);
|
1933
|
+
}
|
1934
|
+
} else {
|
1935
|
+
if (headMergeStyle === "append") {
|
1936
|
+
if (isReAppended) {
|
1937
|
+
removed.push(currentHeadElt);
|
1938
|
+
nodesToAppend.push(currentHeadElt);
|
1939
|
+
}
|
1940
|
+
} else {
|
1941
|
+
if (ctx.head.shouldRemove(currentHeadElt) !== false) {
|
1942
|
+
removed.push(currentHeadElt);
|
1943
|
+
}
|
1944
|
+
}
|
1945
|
+
}
|
1946
|
+
}
|
1947
|
+
nodesToAppend.push(...srcToNewHeadNodes.values());
|
1948
|
+
let promises = [];
|
1949
|
+
for (const newNode of nodesToAppend) {
|
1950
|
+
let newElt = document.createRange().createContextualFragment(newNode.outerHTML).firstChild;
|
1951
|
+
if (ctx.callbacks.beforeNodeAdded(newElt) !== false) {
|
1952
|
+
if (newElt.href || newElt.src) {
|
1953
|
+
let resolve = null;
|
1954
|
+
let promise = new Promise((function(_resolve) {
|
1955
|
+
resolve = _resolve;
|
1956
|
+
}));
|
1957
|
+
newElt.addEventListener("load", (function() {
|
1958
|
+
resolve();
|
1959
|
+
}));
|
1960
|
+
promises.push(promise);
|
1961
|
+
}
|
1962
|
+
currentHead.appendChild(newElt);
|
1963
|
+
ctx.callbacks.afterNodeAdded(newElt);
|
1964
|
+
added.push(newElt);
|
1965
|
+
}
|
1966
|
+
}
|
1967
|
+
for (const removedElement of removed) {
|
1968
|
+
if (ctx.callbacks.beforeNodeRemoved(removedElement) !== false) {
|
1969
|
+
currentHead.removeChild(removedElement);
|
1970
|
+
ctx.callbacks.afterNodeRemoved(removedElement);
|
1971
|
+
}
|
1972
|
+
}
|
1973
|
+
ctx.head.afterHeadMorphed(currentHead, {
|
1974
|
+
added: added,
|
1975
|
+
kept: preserved,
|
1976
|
+
removed: removed
|
1977
|
+
});
|
1978
|
+
return promises;
|
1830
1979
|
}
|
1831
|
-
|
1832
|
-
|
1980
|
+
function noOp() {}
|
1981
|
+
function mergeDefaults(config) {
|
1982
|
+
let finalConfig = {};
|
1983
|
+
Object.assign(finalConfig, defaults);
|
1984
|
+
Object.assign(finalConfig, config);
|
1985
|
+
finalConfig.callbacks = {};
|
1986
|
+
Object.assign(finalConfig.callbacks, defaults.callbacks);
|
1987
|
+
Object.assign(finalConfig.callbacks, config.callbacks);
|
1988
|
+
finalConfig.head = {};
|
1989
|
+
Object.assign(finalConfig.head, defaults.head);
|
1990
|
+
Object.assign(finalConfig.head, config.head);
|
1991
|
+
return finalConfig;
|
1833
1992
|
}
|
1834
|
-
|
1835
|
-
|
1836
|
-
|
1837
|
-
|
1993
|
+
function createMorphContext(oldNode, newContent, config) {
|
1994
|
+
config = mergeDefaults(config);
|
1995
|
+
return {
|
1996
|
+
target: oldNode,
|
1997
|
+
newContent: newContent,
|
1998
|
+
config: config,
|
1999
|
+
morphStyle: config.morphStyle,
|
2000
|
+
ignoreActive: config.ignoreActive,
|
2001
|
+
ignoreActiveValue: config.ignoreActiveValue,
|
2002
|
+
idMap: createIdMap(oldNode, newContent),
|
2003
|
+
deadIds: new Set,
|
2004
|
+
callbacks: config.callbacks,
|
2005
|
+
head: config.head
|
2006
|
+
};
|
1838
2007
|
}
|
1839
|
-
|
1840
|
-
|
1841
|
-
|
1842
|
-
const clonedSelectElements = clonedElement.querySelectorAll("select");
|
1843
|
-
for (const [index, source] of selectElements.entries()) {
|
1844
|
-
const clone = clonedSelectElements[index];
|
1845
|
-
for (const option of clone.selectedOptions) option.selected = false;
|
1846
|
-
for (const option of source.selectedOptions) clone.options[option.index].selected = true;
|
2008
|
+
function isIdSetMatch(node1, node2, ctx) {
|
2009
|
+
if (node1 == null || node2 == null) {
|
2010
|
+
return false;
|
1847
2011
|
}
|
1848
|
-
|
1849
|
-
|
2012
|
+
if (node1.nodeType === node2.nodeType && node1.tagName === node2.tagName) {
|
2013
|
+
if (node1.id !== "" && node1.id === node2.id) {
|
2014
|
+
return true;
|
2015
|
+
} else {
|
2016
|
+
return getIdIntersectionCount(ctx, node1, node2) > 0;
|
2017
|
+
}
|
1850
2018
|
}
|
1851
|
-
return
|
1852
|
-
}
|
1853
|
-
get lang() {
|
1854
|
-
return this.documentElement.getAttribute("lang");
|
1855
|
-
}
|
1856
|
-
get headElement() {
|
1857
|
-
return this.headSnapshot.element;
|
1858
|
-
}
|
1859
|
-
get rootLocation() {
|
1860
|
-
const root = this.getSetting("root") ?? "/";
|
1861
|
-
return expandURL(root);
|
1862
|
-
}
|
1863
|
-
get cacheControlValue() {
|
1864
|
-
return this.getSetting("cache-control");
|
1865
|
-
}
|
1866
|
-
get isPreviewable() {
|
1867
|
-
return this.cacheControlValue != "no-preview";
|
1868
|
-
}
|
1869
|
-
get isCacheable() {
|
1870
|
-
return this.cacheControlValue != "no-cache";
|
1871
|
-
}
|
1872
|
-
get isVisitable() {
|
1873
|
-
return this.getSetting("visit-control") != "reload";
|
2019
|
+
return false;
|
1874
2020
|
}
|
1875
|
-
|
1876
|
-
|
2021
|
+
function isSoftMatch(node1, node2) {
|
2022
|
+
if (node1 == null || node2 == null) {
|
2023
|
+
return false;
|
2024
|
+
}
|
2025
|
+
return node1.nodeType === node2.nodeType && node1.tagName === node2.tagName;
|
1877
2026
|
}
|
1878
|
-
|
1879
|
-
|
2027
|
+
function removeNodesBetween(startInclusive, endExclusive, ctx) {
|
2028
|
+
while (startInclusive !== endExclusive) {
|
2029
|
+
let tempNode = startInclusive;
|
2030
|
+
startInclusive = startInclusive.nextSibling;
|
2031
|
+
removeNode(tempNode, ctx);
|
2032
|
+
}
|
2033
|
+
removeIdsFromConsideration(ctx, endExclusive);
|
2034
|
+
return endExclusive.nextSibling;
|
1880
2035
|
}
|
1881
|
-
|
1882
|
-
|
2036
|
+
function findIdSetMatch(newContent, oldParent, newChild, insertionPoint, ctx) {
|
2037
|
+
let newChildPotentialIdCount = getIdIntersectionCount(ctx, newChild, oldParent);
|
2038
|
+
let potentialMatch = null;
|
2039
|
+
if (newChildPotentialIdCount > 0) {
|
2040
|
+
let potentialMatch = insertionPoint;
|
2041
|
+
let otherMatchCount = 0;
|
2042
|
+
while (potentialMatch != null) {
|
2043
|
+
if (isIdSetMatch(newChild, potentialMatch, ctx)) {
|
2044
|
+
return potentialMatch;
|
2045
|
+
}
|
2046
|
+
otherMatchCount += getIdIntersectionCount(ctx, potentialMatch, newContent);
|
2047
|
+
if (otherMatchCount > newChildPotentialIdCount) {
|
2048
|
+
return null;
|
2049
|
+
}
|
2050
|
+
potentialMatch = potentialMatch.nextSibling;
|
2051
|
+
}
|
2052
|
+
}
|
2053
|
+
return potentialMatch;
|
1883
2054
|
}
|
1884
|
-
|
1885
|
-
|
2055
|
+
function findSoftMatch(newContent, oldParent, newChild, insertionPoint, ctx) {
|
2056
|
+
let potentialSoftMatch = insertionPoint;
|
2057
|
+
let nextSibling = newChild.nextSibling;
|
2058
|
+
let siblingSoftMatchCount = 0;
|
2059
|
+
while (potentialSoftMatch != null) {
|
2060
|
+
if (getIdIntersectionCount(ctx, potentialSoftMatch, newContent) > 0) {
|
2061
|
+
return null;
|
2062
|
+
}
|
2063
|
+
if (isSoftMatch(newChild, potentialSoftMatch)) {
|
2064
|
+
return potentialSoftMatch;
|
2065
|
+
}
|
2066
|
+
if (isSoftMatch(nextSibling, potentialSoftMatch)) {
|
2067
|
+
siblingSoftMatchCount++;
|
2068
|
+
nextSibling = nextSibling.nextSibling;
|
2069
|
+
if (siblingSoftMatchCount >= 2) {
|
2070
|
+
return null;
|
2071
|
+
}
|
2072
|
+
}
|
2073
|
+
potentialSoftMatch = potentialSoftMatch.nextSibling;
|
2074
|
+
}
|
2075
|
+
return potentialSoftMatch;
|
1886
2076
|
}
|
1887
|
-
|
1888
|
-
|
1889
|
-
|
1890
|
-
|
1891
|
-
|
1892
|
-
|
1893
|
-
|
1894
|
-
|
1895
|
-
|
1896
|
-
|
1897
|
-
|
2077
|
+
function parseContent(newContent) {
|
2078
|
+
let parser = new DOMParser;
|
2079
|
+
let contentWithSvgsRemoved = newContent.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim, "");
|
2080
|
+
if (contentWithSvgsRemoved.match(/<\/html>/) || contentWithSvgsRemoved.match(/<\/head>/) || contentWithSvgsRemoved.match(/<\/body>/)) {
|
2081
|
+
let content = parser.parseFromString(newContent, "text/html");
|
2082
|
+
if (contentWithSvgsRemoved.match(/<\/html>/)) {
|
2083
|
+
content.generatedByIdiomorph = true;
|
2084
|
+
return content;
|
2085
|
+
} else {
|
2086
|
+
let htmlElement = content.firstChild;
|
2087
|
+
if (htmlElement) {
|
2088
|
+
htmlElement.generatedByIdiomorph = true;
|
2089
|
+
return htmlElement;
|
2090
|
+
} else {
|
2091
|
+
return null;
|
2092
|
+
}
|
2093
|
+
}
|
1898
2094
|
} else {
|
1899
|
-
|
2095
|
+
let responseDoc = parser.parseFromString("<body><template>" + newContent + "</template></body>", "text/html");
|
2096
|
+
let content = responseDoc.body.querySelector("template").content;
|
2097
|
+
content.generatedByIdiomorph = true;
|
2098
|
+
return content;
|
1900
2099
|
}
|
1901
|
-
return this.#lastOperation;
|
1902
2100
|
}
|
1903
|
-
|
1904
|
-
|
2101
|
+
function normalizeContent(newContent) {
|
2102
|
+
if (newContent == null) {
|
2103
|
+
const dummyParent = document.createElement("div");
|
2104
|
+
return dummyParent;
|
2105
|
+
} else if (newContent.generatedByIdiomorph) {
|
2106
|
+
return newContent;
|
2107
|
+
} else if (newContent instanceof Node) {
|
2108
|
+
const dummyParent = document.createElement("div");
|
2109
|
+
dummyParent.append(newContent);
|
2110
|
+
return dummyParent;
|
2111
|
+
} else {
|
2112
|
+
const dummyParent = document.createElement("div");
|
2113
|
+
for (const elt of [ ...newContent ]) {
|
2114
|
+
dummyParent.append(elt);
|
2115
|
+
}
|
2116
|
+
return dummyParent;
|
2117
|
+
}
|
1905
2118
|
}
|
1906
|
-
|
1907
|
-
|
1908
|
-
|
1909
|
-
|
1910
|
-
|
1911
|
-
|
1912
|
-
|
1913
|
-
|
1914
|
-
|
1915
|
-
|
1916
|
-
|
1917
|
-
|
1918
|
-
|
1919
|
-
|
1920
|
-
|
1921
|
-
|
1922
|
-
|
1923
|
-
}
|
1924
|
-
|
1925
|
-
|
1926
|
-
|
1927
|
-
|
1928
|
-
canceled: "canceled",
|
1929
|
-
failed: "failed",
|
1930
|
-
completed: "completed"
|
1931
|
-
};
|
1932
|
-
|
1933
|
-
const SystemStatusCode = {
|
1934
|
-
networkFailure: 0,
|
1935
|
-
timeoutFailure: -1,
|
1936
|
-
contentTypeMismatch: -2
|
1937
|
-
};
|
1938
|
-
|
1939
|
-
const Direction = {
|
1940
|
-
advance: "forward",
|
1941
|
-
restore: "back",
|
1942
|
-
replace: "none"
|
1943
|
-
};
|
1944
|
-
|
1945
|
-
class Visit {
|
1946
|
-
identifier=uuid();
|
1947
|
-
timingMetrics={};
|
1948
|
-
followedRedirect=false;
|
1949
|
-
historyChanged=false;
|
1950
|
-
scrolled=false;
|
1951
|
-
shouldCacheSnapshot=true;
|
1952
|
-
acceptsStreamResponse=false;
|
1953
|
-
snapshotCached=false;
|
1954
|
-
state=VisitState.initialized;
|
1955
|
-
viewTransitioner=new ViewTransitioner;
|
1956
|
-
constructor(delegate, location, restorationIdentifier, options = {}) {
|
1957
|
-
this.delegate = delegate;
|
1958
|
-
this.location = location;
|
1959
|
-
this.restorationIdentifier = restorationIdentifier || uuid();
|
1960
|
-
const {action: action, historyChanged: historyChanged, referrer: referrer, snapshot: snapshot, snapshotHTML: snapshotHTML, response: response, visitCachedSnapshot: visitCachedSnapshot, willRender: willRender, updateHistory: updateHistory, shouldCacheSnapshot: shouldCacheSnapshot, acceptsStreamResponse: acceptsStreamResponse, direction: direction} = {
|
1961
|
-
...defaultOptions,
|
1962
|
-
...options
|
1963
|
-
};
|
1964
|
-
this.action = action;
|
1965
|
-
this.historyChanged = historyChanged;
|
1966
|
-
this.referrer = referrer;
|
1967
|
-
this.snapshot = snapshot;
|
1968
|
-
this.snapshotHTML = snapshotHTML;
|
1969
|
-
this.response = response;
|
1970
|
-
this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action);
|
1971
|
-
this.isPageRefresh = this.view.isPageRefresh(this);
|
1972
|
-
this.visitCachedSnapshot = visitCachedSnapshot;
|
1973
|
-
this.willRender = willRender;
|
1974
|
-
this.updateHistory = updateHistory;
|
1975
|
-
this.scrolled = !willRender;
|
1976
|
-
this.shouldCacheSnapshot = shouldCacheSnapshot;
|
1977
|
-
this.acceptsStreamResponse = acceptsStreamResponse;
|
1978
|
-
this.direction = direction || Direction[action];
|
2119
|
+
function insertSiblings(previousSibling, morphedNode, nextSibling) {
|
2120
|
+
let stack = [];
|
2121
|
+
let added = [];
|
2122
|
+
while (previousSibling != null) {
|
2123
|
+
stack.push(previousSibling);
|
2124
|
+
previousSibling = previousSibling.previousSibling;
|
2125
|
+
}
|
2126
|
+
while (stack.length > 0) {
|
2127
|
+
let node = stack.pop();
|
2128
|
+
added.push(node);
|
2129
|
+
morphedNode.parentElement.insertBefore(node, morphedNode);
|
2130
|
+
}
|
2131
|
+
added.push(morphedNode);
|
2132
|
+
while (nextSibling != null) {
|
2133
|
+
stack.push(nextSibling);
|
2134
|
+
added.push(nextSibling);
|
2135
|
+
nextSibling = nextSibling.nextSibling;
|
2136
|
+
}
|
2137
|
+
while (stack.length > 0) {
|
2138
|
+
morphedNode.parentElement.insertBefore(stack.pop(), morphedNode.nextSibling);
|
2139
|
+
}
|
2140
|
+
return added;
|
1979
2141
|
}
|
1980
|
-
|
1981
|
-
|
2142
|
+
function findBestNodeMatch(newContent, oldNode, ctx) {
|
2143
|
+
let currentElement;
|
2144
|
+
currentElement = newContent.firstChild;
|
2145
|
+
let bestElement = currentElement;
|
2146
|
+
let score = 0;
|
2147
|
+
while (currentElement) {
|
2148
|
+
let newScore = scoreElement(currentElement, oldNode, ctx);
|
2149
|
+
if (newScore > score) {
|
2150
|
+
bestElement = currentElement;
|
2151
|
+
score = newScore;
|
2152
|
+
}
|
2153
|
+
currentElement = currentElement.nextSibling;
|
2154
|
+
}
|
2155
|
+
return bestElement;
|
1982
2156
|
}
|
1983
|
-
|
1984
|
-
|
2157
|
+
function scoreElement(node1, node2, ctx) {
|
2158
|
+
if (isSoftMatch(node1, node2)) {
|
2159
|
+
return .5 + getIdIntersectionCount(ctx, node1, node2);
|
2160
|
+
}
|
2161
|
+
return 0;
|
1985
2162
|
}
|
1986
|
-
|
1987
|
-
|
2163
|
+
function removeNode(tempNode, ctx) {
|
2164
|
+
removeIdsFromConsideration(ctx, tempNode);
|
2165
|
+
if (ctx.callbacks.beforeNodeRemoved(tempNode) === false) return;
|
2166
|
+
tempNode.remove();
|
2167
|
+
ctx.callbacks.afterNodeRemoved(tempNode);
|
1988
2168
|
}
|
1989
|
-
|
1990
|
-
return
|
2169
|
+
function isIdInConsideration(ctx, id) {
|
2170
|
+
return !ctx.deadIds.has(id);
|
1991
2171
|
}
|
1992
|
-
|
1993
|
-
|
2172
|
+
function idIsWithinNode(ctx, id, targetNode) {
|
2173
|
+
let idSet = ctx.idMap.get(targetNode) || EMPTY_SET;
|
2174
|
+
return idSet.has(id);
|
1994
2175
|
}
|
1995
|
-
|
1996
|
-
|
1997
|
-
|
1998
|
-
|
1999
|
-
this.adapter.visitStarted(this);
|
2000
|
-
this.delegate.visitStarted(this);
|
2176
|
+
function removeIdsFromConsideration(ctx, node) {
|
2177
|
+
let idSet = ctx.idMap.get(node) || EMPTY_SET;
|
2178
|
+
for (const id of idSet) {
|
2179
|
+
ctx.deadIds.add(id);
|
2001
2180
|
}
|
2002
2181
|
}
|
2003
|
-
|
2004
|
-
|
2005
|
-
|
2006
|
-
|
2182
|
+
function getIdIntersectionCount(ctx, node1, node2) {
|
2183
|
+
let sourceSet = ctx.idMap.get(node1) || EMPTY_SET;
|
2184
|
+
let matchCount = 0;
|
2185
|
+
for (const id of sourceSet) {
|
2186
|
+
if (isIdInConsideration(ctx, id) && idIsWithinNode(ctx, id, node2)) {
|
2187
|
+
++matchCount;
|
2007
2188
|
}
|
2008
|
-
this.cancelRender();
|
2009
|
-
this.state = VisitState.canceled;
|
2010
2189
|
}
|
2190
|
+
return matchCount;
|
2011
2191
|
}
|
2012
|
-
|
2013
|
-
|
2014
|
-
|
2015
|
-
|
2016
|
-
|
2017
|
-
|
2018
|
-
|
2019
|
-
|
2192
|
+
function populateIdMapForNode(node, idMap) {
|
2193
|
+
let nodeParent = node.parentElement;
|
2194
|
+
let idElements = node.querySelectorAll("[id]");
|
2195
|
+
for (const elt of idElements) {
|
2196
|
+
let current = elt;
|
2197
|
+
while (current !== nodeParent && current != null) {
|
2198
|
+
let idSet = idMap.get(current);
|
2199
|
+
if (idSet == null) {
|
2200
|
+
idSet = new Set;
|
2201
|
+
idMap.set(current, idSet);
|
2202
|
+
}
|
2203
|
+
idSet.add(elt.id);
|
2204
|
+
current = current.parentElement;
|
2020
2205
|
}
|
2021
2206
|
}
|
2022
2207
|
}
|
2023
|
-
|
2024
|
-
|
2025
|
-
|
2026
|
-
|
2027
|
-
|
2028
|
-
}
|
2208
|
+
function createIdMap(oldContent, newContent) {
|
2209
|
+
let idMap = new Map;
|
2210
|
+
populateIdMapForNode(oldContent, idMap);
|
2211
|
+
populateIdMapForNode(newContent, idMap);
|
2212
|
+
return idMap;
|
2029
2213
|
}
|
2030
|
-
|
2031
|
-
|
2032
|
-
|
2033
|
-
|
2034
|
-
|
2035
|
-
|
2036
|
-
|
2214
|
+
return {
|
2215
|
+
morph: morph,
|
2216
|
+
defaults: defaults
|
2217
|
+
};
|
2218
|
+
}();
|
2219
|
+
|
2220
|
+
function morphElements(currentElement, newElement, {callbacks: callbacks, ...options} = {}) {
|
2221
|
+
Idiomorph.morph(currentElement, newElement, {
|
2222
|
+
...options,
|
2223
|
+
callbacks: new DefaultIdiomorphCallbacks(callbacks)
|
2224
|
+
});
|
2225
|
+
}
|
2226
|
+
|
2227
|
+
function morphChildren(currentElement, newElement) {
|
2228
|
+
morphElements(currentElement, newElement.children, {
|
2229
|
+
morphStyle: "innerHTML"
|
2230
|
+
});
|
2231
|
+
}
|
2232
|
+
|
2233
|
+
class DefaultIdiomorphCallbacks {
|
2234
|
+
#beforeNodeMorphed;
|
2235
|
+
constructor({beforeNodeMorphed: beforeNodeMorphed} = {}) {
|
2236
|
+
this.#beforeNodeMorphed = beforeNodeMorphed || (() => true);
|
2037
2237
|
}
|
2038
|
-
|
2039
|
-
|
2040
|
-
|
2041
|
-
|
2042
|
-
|
2043
|
-
|
2238
|
+
beforeNodeAdded=node => !(node.id && node.hasAttribute("data-turbo-permanent") && document.getElementById(node.id));
|
2239
|
+
beforeNodeMorphed=(currentElement, newElement) => {
|
2240
|
+
if (currentElement instanceof Element) {
|
2241
|
+
if (!currentElement.hasAttribute("data-turbo-permanent") && this.#beforeNodeMorphed(currentElement, newElement)) {
|
2242
|
+
const event = dispatch("turbo:before-morph-element", {
|
2243
|
+
cancelable: true,
|
2244
|
+
target: currentElement,
|
2245
|
+
detail: {
|
2246
|
+
currentElement: currentElement,
|
2247
|
+
newElement: newElement
|
2248
|
+
}
|
2249
|
+
});
|
2250
|
+
return !event.defaultPrevented;
|
2251
|
+
} else {
|
2252
|
+
return false;
|
2253
|
+
}
|
2044
2254
|
}
|
2045
|
-
}
|
2046
|
-
|
2047
|
-
|
2048
|
-
|
2049
|
-
|
2050
|
-
|
2255
|
+
};
|
2256
|
+
beforeAttributeUpdated=(attributeName, target, mutationType) => {
|
2257
|
+
const event = dispatch("turbo:before-morph-attribute", {
|
2258
|
+
cancelable: true,
|
2259
|
+
target: target,
|
2260
|
+
detail: {
|
2261
|
+
attributeName: attributeName,
|
2262
|
+
mutationType: mutationType
|
2263
|
+
}
|
2264
|
+
});
|
2265
|
+
return !event.defaultPrevented;
|
2266
|
+
};
|
2267
|
+
beforeNodeRemoved=node => this.beforeNodeMorphed(node);
|
2268
|
+
afterNodeMorphed=(currentElement, newElement) => {
|
2269
|
+
if (currentElement instanceof Element) {
|
2270
|
+
dispatch("turbo:morph-element", {
|
2271
|
+
target: currentElement,
|
2272
|
+
detail: {
|
2273
|
+
currentElement: currentElement,
|
2274
|
+
newElement: newElement
|
2275
|
+
}
|
2276
|
+
});
|
2051
2277
|
}
|
2278
|
+
};
|
2279
|
+
}
|
2280
|
+
|
2281
|
+
class MorphingFrameRenderer extends FrameRenderer {
|
2282
|
+
static renderElement(currentElement, newElement) {
|
2283
|
+
dispatch("turbo:before-frame-morph", {
|
2284
|
+
target: currentElement,
|
2285
|
+
detail: {
|
2286
|
+
currentElement: currentElement,
|
2287
|
+
newElement: newElement
|
2288
|
+
}
|
2289
|
+
});
|
2290
|
+
morphChildren(currentElement, newElement);
|
2052
2291
|
}
|
2053
|
-
|
2054
|
-
|
2055
|
-
|
2056
|
-
|
2057
|
-
|
2058
|
-
|
2059
|
-
|
2060
|
-
|
2061
|
-
|
2062
|
-
|
2063
|
-
|
2064
|
-
|
2292
|
+
}
|
2293
|
+
|
2294
|
+
class ProgressBar {
|
2295
|
+
static animationDuration=300;
|
2296
|
+
static get defaultCSS() {
|
2297
|
+
return unindent`
|
2298
|
+
.turbo-progress-bar {
|
2299
|
+
position: fixed;
|
2300
|
+
display: block;
|
2301
|
+
top: 0;
|
2302
|
+
left: 0;
|
2303
|
+
height: 3px;
|
2304
|
+
background: #0076ff;
|
2305
|
+
z-index: 2147483647;
|
2306
|
+
transition:
|
2307
|
+
width ${ProgressBar.animationDuration}ms ease-out,
|
2308
|
+
opacity ${ProgressBar.animationDuration / 2}ms ${ProgressBar.animationDuration / 2}ms ease-in;
|
2309
|
+
transform: translate3d(0, 0, 0);
|
2065
2310
|
}
|
2066
|
-
|
2311
|
+
`;
|
2067
2312
|
}
|
2068
|
-
|
2069
|
-
|
2070
|
-
|
2313
|
+
hiding=false;
|
2314
|
+
value=0;
|
2315
|
+
visible=false;
|
2316
|
+
constructor() {
|
2317
|
+
this.stylesheetElement = this.createStylesheetElement();
|
2318
|
+
this.progressElement = this.createProgressElement();
|
2319
|
+
this.installStylesheetElement();
|
2320
|
+
this.setValue(0);
|
2071
2321
|
}
|
2072
|
-
|
2073
|
-
if (this.
|
2074
|
-
|
2075
|
-
this.
|
2076
|
-
|
2077
|
-
|
2078
|
-
|
2079
|
-
|
2080
|
-
|
2081
|
-
|
2082
|
-
|
2083
|
-
|
2084
|
-
|
2085
|
-
|
2086
|
-
|
2087
|
-
}
|
2322
|
+
show() {
|
2323
|
+
if (!this.visible) {
|
2324
|
+
this.visible = true;
|
2325
|
+
this.installProgressElement();
|
2326
|
+
this.startTrickling();
|
2327
|
+
}
|
2328
|
+
}
|
2329
|
+
hide() {
|
2330
|
+
if (this.visible && !this.hiding) {
|
2331
|
+
this.hiding = true;
|
2332
|
+
this.fadeProgressElement((() => {
|
2333
|
+
this.uninstallProgressElement();
|
2334
|
+
this.stopTrickling();
|
2335
|
+
this.visible = false;
|
2336
|
+
this.hiding = false;
|
2088
2337
|
}));
|
2089
2338
|
}
|
2090
2339
|
}
|
2091
|
-
|
2092
|
-
|
2093
|
-
|
2094
|
-
if (this.action == "restore" || snapshot.isPreviewable) {
|
2095
|
-
return snapshot;
|
2096
|
-
}
|
2097
|
-
}
|
2340
|
+
setValue(value) {
|
2341
|
+
this.value = value;
|
2342
|
+
this.refresh();
|
2098
2343
|
}
|
2099
|
-
|
2100
|
-
|
2101
|
-
return PageSnapshot.fromHTMLString(this.snapshotHTML);
|
2102
|
-
}
|
2344
|
+
installStylesheetElement() {
|
2345
|
+
document.head.insertBefore(this.stylesheetElement, document.head.firstChild);
|
2103
2346
|
}
|
2104
|
-
|
2105
|
-
|
2347
|
+
installProgressElement() {
|
2348
|
+
this.progressElement.style.width = "0";
|
2349
|
+
this.progressElement.style.opacity = "1";
|
2350
|
+
document.documentElement.insertBefore(this.progressElement, document.body);
|
2351
|
+
this.refresh();
|
2106
2352
|
}
|
2107
|
-
|
2108
|
-
|
2109
|
-
|
2110
|
-
const isPreview = this.shouldIssueRequest();
|
2111
|
-
this.render((async () => {
|
2112
|
-
this.cacheSnapshot();
|
2113
|
-
if (this.isSamePage || this.isPageRefresh) {
|
2114
|
-
this.adapter.visitRendered(this);
|
2115
|
-
} else {
|
2116
|
-
if (this.view.renderPromise) await this.view.renderPromise;
|
2117
|
-
await this.renderPageSnapshot(snapshot, isPreview);
|
2118
|
-
this.adapter.visitRendered(this);
|
2119
|
-
if (!isPreview) {
|
2120
|
-
this.complete();
|
2121
|
-
}
|
2122
|
-
}
|
2123
|
-
}));
|
2124
|
-
}
|
2353
|
+
fadeProgressElement(callback) {
|
2354
|
+
this.progressElement.style.opacity = "0";
|
2355
|
+
setTimeout(callback, ProgressBar.animationDuration * 1.5);
|
2125
2356
|
}
|
2126
|
-
|
2127
|
-
if (this.
|
2128
|
-
|
2129
|
-
action: "replace",
|
2130
|
-
response: this.response,
|
2131
|
-
shouldCacheSnapshot: false,
|
2132
|
-
willRender: false
|
2133
|
-
});
|
2134
|
-
this.followedRedirect = true;
|
2357
|
+
uninstallProgressElement() {
|
2358
|
+
if (this.progressElement.parentNode) {
|
2359
|
+
document.documentElement.removeChild(this.progressElement);
|
2135
2360
|
}
|
2136
2361
|
}
|
2137
|
-
|
2138
|
-
if (this.
|
2139
|
-
this.
|
2140
|
-
this.cacheSnapshot();
|
2141
|
-
this.performScroll();
|
2142
|
-
this.changeHistory();
|
2143
|
-
this.adapter.visitRendered(this);
|
2144
|
-
}));
|
2362
|
+
startTrickling() {
|
2363
|
+
if (!this.trickleInterval) {
|
2364
|
+
this.trickleInterval = window.setInterval(this.trickle, ProgressBar.animationDuration);
|
2145
2365
|
}
|
2146
2366
|
}
|
2147
|
-
|
2148
|
-
|
2149
|
-
|
2150
|
-
}
|
2367
|
+
stopTrickling() {
|
2368
|
+
window.clearInterval(this.trickleInterval);
|
2369
|
+
delete this.trickleInterval;
|
2151
2370
|
}
|
2152
|
-
|
2153
|
-
this.
|
2371
|
+
trickle=() => {
|
2372
|
+
this.setValue(this.value + Math.random() / 100);
|
2373
|
+
};
|
2374
|
+
refresh() {
|
2375
|
+
requestAnimationFrame((() => {
|
2376
|
+
this.progressElement.style.width = `${10 + this.value * 90}%`;
|
2377
|
+
}));
|
2154
2378
|
}
|
2155
|
-
|
2156
|
-
|
2157
|
-
|
2158
|
-
|
2159
|
-
if (
|
2160
|
-
this.
|
2161
|
-
statusCode: SystemStatusCode.contentTypeMismatch,
|
2162
|
-
redirected: redirected
|
2163
|
-
});
|
2164
|
-
} else {
|
2165
|
-
this.redirectedToLocation = response.redirected ? response.location : undefined;
|
2166
|
-
this.recordResponse({
|
2167
|
-
statusCode: statusCode,
|
2168
|
-
responseHTML: responseHTML,
|
2169
|
-
redirected: redirected
|
2170
|
-
});
|
2379
|
+
createStylesheetElement() {
|
2380
|
+
const element = document.createElement("style");
|
2381
|
+
element.type = "text/css";
|
2382
|
+
element.textContent = ProgressBar.defaultCSS;
|
2383
|
+
if (this.cspNonce) {
|
2384
|
+
element.nonce = this.cspNonce;
|
2171
2385
|
}
|
2386
|
+
return element;
|
2172
2387
|
}
|
2173
|
-
|
2174
|
-
const
|
2175
|
-
|
2176
|
-
|
2177
|
-
this.recordResponse({
|
2178
|
-
statusCode: SystemStatusCode.contentTypeMismatch,
|
2179
|
-
redirected: redirected
|
2180
|
-
});
|
2181
|
-
} else {
|
2182
|
-
this.recordResponse({
|
2183
|
-
statusCode: statusCode,
|
2184
|
-
responseHTML: responseHTML,
|
2185
|
-
redirected: redirected
|
2186
|
-
});
|
2187
|
-
}
|
2388
|
+
createProgressElement() {
|
2389
|
+
const element = document.createElement("div");
|
2390
|
+
element.className = "turbo-progress-bar";
|
2391
|
+
return element;
|
2188
2392
|
}
|
2189
|
-
|
2190
|
-
|
2191
|
-
statusCode: SystemStatusCode.networkFailure,
|
2192
|
-
redirected: false
|
2193
|
-
});
|
2393
|
+
get cspNonce() {
|
2394
|
+
return getMetaContent("csp-nonce");
|
2194
2395
|
}
|
2195
|
-
|
2196
|
-
|
2396
|
+
}
|
2397
|
+
|
2398
|
+
class HeadSnapshot extends Snapshot {
|
2399
|
+
detailsByOuterHTML=this.children.filter((element => !elementIsNoscript(element))).map((element => elementWithoutNonce(element))).reduce(((result, element) => {
|
2400
|
+
const {outerHTML: outerHTML} = element;
|
2401
|
+
const details = outerHTML in result ? result[outerHTML] : {
|
2402
|
+
type: elementType(element),
|
2403
|
+
tracked: elementIsTracked(element),
|
2404
|
+
elements: []
|
2405
|
+
};
|
2406
|
+
return {
|
2407
|
+
...result,
|
2408
|
+
[outerHTML]: {
|
2409
|
+
...details,
|
2410
|
+
elements: [ ...details.elements, element ]
|
2411
|
+
}
|
2412
|
+
};
|
2413
|
+
}), {});
|
2414
|
+
get trackedElementSignature() {
|
2415
|
+
return Object.keys(this.detailsByOuterHTML).filter((outerHTML => this.detailsByOuterHTML[outerHTML].tracked)).join("");
|
2197
2416
|
}
|
2198
|
-
|
2199
|
-
|
2200
|
-
|
2201
|
-
|
2417
|
+
getScriptElementsNotInSnapshot(snapshot) {
|
2418
|
+
return this.getElementsMatchingTypeNotInSnapshot("script", snapshot);
|
2419
|
+
}
|
2420
|
+
getStylesheetElementsNotInSnapshot(snapshot) {
|
2421
|
+
return this.getElementsMatchingTypeNotInSnapshot("stylesheet", snapshot);
|
2422
|
+
}
|
2423
|
+
getElementsMatchingTypeNotInSnapshot(matchedType, snapshot) {
|
2424
|
+
return Object.keys(this.detailsByOuterHTML).filter((outerHTML => !(outerHTML in snapshot.detailsByOuterHTML))).map((outerHTML => this.detailsByOuterHTML[outerHTML])).filter((({type: type}) => type == matchedType)).map((({elements: [element]}) => element));
|
2425
|
+
}
|
2426
|
+
get provisionalElements() {
|
2427
|
+
return Object.keys(this.detailsByOuterHTML).reduce(((result, outerHTML) => {
|
2428
|
+
const {type: type, tracked: tracked, elements: elements} = this.detailsByOuterHTML[outerHTML];
|
2429
|
+
if (type == null && !tracked) {
|
2430
|
+
return [ ...result, ...elements ];
|
2431
|
+
} else if (elements.length > 1) {
|
2432
|
+
return [ ...result, ...elements.slice(1) ];
|
2202
2433
|
} else {
|
2203
|
-
|
2204
|
-
}
|
2205
|
-
if (this.isSamePage) {
|
2206
|
-
this.delegate.visitScrolledToSamePageLocation(this.view.lastRenderedLocation, this.location);
|
2434
|
+
return result;
|
2207
2435
|
}
|
2208
|
-
|
2436
|
+
}), []);
|
2437
|
+
}
|
2438
|
+
getMetaValue(name) {
|
2439
|
+
const element = this.findMetaElementByName(name);
|
2440
|
+
return element ? element.getAttribute("content") : null;
|
2441
|
+
}
|
2442
|
+
findMetaElementByName(name) {
|
2443
|
+
return Object.keys(this.detailsByOuterHTML).reduce(((result, outerHTML) => {
|
2444
|
+
const {elements: [element]} = this.detailsByOuterHTML[outerHTML];
|
2445
|
+
return elementIsMetaElementWithName(element, name) ? element : result;
|
2446
|
+
}), undefined | undefined);
|
2447
|
+
}
|
2448
|
+
}
|
2449
|
+
|
2450
|
+
function elementType(element) {
|
2451
|
+
if (elementIsScript(element)) {
|
2452
|
+
return "script";
|
2453
|
+
} else if (elementIsStylesheet(element)) {
|
2454
|
+
return "stylesheet";
|
2455
|
+
}
|
2456
|
+
}
|
2457
|
+
|
2458
|
+
function elementIsTracked(element) {
|
2459
|
+
return element.getAttribute("data-turbo-track") == "reload";
|
2460
|
+
}
|
2461
|
+
|
2462
|
+
function elementIsScript(element) {
|
2463
|
+
const tagName = element.localName;
|
2464
|
+
return tagName == "script";
|
2465
|
+
}
|
2466
|
+
|
2467
|
+
function elementIsNoscript(element) {
|
2468
|
+
const tagName = element.localName;
|
2469
|
+
return tagName == "noscript";
|
2470
|
+
}
|
2471
|
+
|
2472
|
+
function elementIsStylesheet(element) {
|
2473
|
+
const tagName = element.localName;
|
2474
|
+
return tagName == "style" || tagName == "link" && element.getAttribute("rel") == "stylesheet";
|
2475
|
+
}
|
2476
|
+
|
2477
|
+
function elementIsMetaElementWithName(element, name) {
|
2478
|
+
const tagName = element.localName;
|
2479
|
+
return tagName == "meta" && element.getAttribute("name") == name;
|
2480
|
+
}
|
2481
|
+
|
2482
|
+
function elementWithoutNonce(element) {
|
2483
|
+
if (element.hasAttribute("nonce")) {
|
2484
|
+
element.setAttribute("nonce", "");
|
2485
|
+
}
|
2486
|
+
return element;
|
2487
|
+
}
|
2488
|
+
|
2489
|
+
class PageSnapshot extends Snapshot {
|
2490
|
+
static fromHTMLString(html = "") {
|
2491
|
+
return this.fromDocument(parseHTMLDocument(html));
|
2492
|
+
}
|
2493
|
+
static fromElement(element) {
|
2494
|
+
return this.fromDocument(element.ownerDocument);
|
2495
|
+
}
|
2496
|
+
static fromDocument({documentElement: documentElement, body: body, head: head}) {
|
2497
|
+
return new this(documentElement, body, new HeadSnapshot(head));
|
2498
|
+
}
|
2499
|
+
constructor(documentElement, body, headSnapshot) {
|
2500
|
+
super(body);
|
2501
|
+
this.documentElement = documentElement;
|
2502
|
+
this.headSnapshot = headSnapshot;
|
2503
|
+
}
|
2504
|
+
clone() {
|
2505
|
+
const clonedElement = this.element.cloneNode(true);
|
2506
|
+
const selectElements = this.element.querySelectorAll("select");
|
2507
|
+
const clonedSelectElements = clonedElement.querySelectorAll("select");
|
2508
|
+
for (const [index, source] of selectElements.entries()) {
|
2509
|
+
const clone = clonedSelectElements[index];
|
2510
|
+
for (const option of clone.selectedOptions) option.selected = false;
|
2511
|
+
for (const option of source.selectedOptions) clone.options[option.index].selected = true;
|
2209
2512
|
}
|
2210
|
-
|
2211
|
-
|
2212
|
-
const {scrollPosition: scrollPosition} = this.restorationData;
|
2213
|
-
if (scrollPosition) {
|
2214
|
-
this.view.scrollToPosition(scrollPosition);
|
2215
|
-
return true;
|
2513
|
+
for (const clonedPasswordInput of clonedElement.querySelectorAll('input[type="password"]')) {
|
2514
|
+
clonedPasswordInput.value = "";
|
2216
2515
|
}
|
2516
|
+
return new PageSnapshot(this.documentElement, clonedElement, this.headSnapshot);
|
2217
2517
|
}
|
2218
|
-
|
2219
|
-
|
2220
|
-
if (anchor != null) {
|
2221
|
-
this.view.scrollToAnchor(anchor);
|
2222
|
-
return true;
|
2223
|
-
}
|
2518
|
+
get lang() {
|
2519
|
+
return this.documentElement.getAttribute("lang");
|
2224
2520
|
}
|
2225
|
-
|
2226
|
-
this.
|
2521
|
+
get headElement() {
|
2522
|
+
return this.headSnapshot.element;
|
2227
2523
|
}
|
2228
|
-
|
2229
|
-
|
2230
|
-
|
2231
|
-
};
|
2524
|
+
get rootLocation() {
|
2525
|
+
const root = this.getSetting("root") ?? "/";
|
2526
|
+
return expandURL(root);
|
2232
2527
|
}
|
2233
|
-
|
2234
|
-
|
2235
|
-
case "replace":
|
2236
|
-
return history.replaceState;
|
2237
|
-
|
2238
|
-
case "advance":
|
2239
|
-
case "restore":
|
2240
|
-
return history.pushState;
|
2241
|
-
}
|
2528
|
+
get cacheControlValue() {
|
2529
|
+
return this.getSetting("cache-control");
|
2242
2530
|
}
|
2243
|
-
|
2244
|
-
return
|
2531
|
+
get isPreviewable() {
|
2532
|
+
return this.cacheControlValue != "no-preview";
|
2245
2533
|
}
|
2246
|
-
|
2247
|
-
|
2248
|
-
return false;
|
2249
|
-
} else if (this.action == "restore") {
|
2250
|
-
return !this.hasCachedSnapshot();
|
2251
|
-
} else {
|
2252
|
-
return this.willRender;
|
2253
|
-
}
|
2534
|
+
get isCacheable() {
|
2535
|
+
return this.cacheControlValue != "no-cache";
|
2254
2536
|
}
|
2255
|
-
|
2256
|
-
|
2257
|
-
this.view.cacheSnapshot(this.snapshot).then((snapshot => snapshot && this.visitCachedSnapshot(snapshot)));
|
2258
|
-
this.snapshotCached = true;
|
2259
|
-
}
|
2537
|
+
get isVisitable() {
|
2538
|
+
return this.getSetting("visit-control") != "reload";
|
2260
2539
|
}
|
2261
|
-
|
2262
|
-
this.
|
2263
|
-
this.frame = await nextRepaint();
|
2264
|
-
await callback();
|
2265
|
-
delete this.frame;
|
2540
|
+
get prefersViewTransitions() {
|
2541
|
+
return this.headSnapshot.getMetaValue("view-transition") === "same-origin";
|
2266
2542
|
}
|
2267
|
-
|
2268
|
-
|
2269
|
-
await this.view.renderPage(snapshot, isPreview, this.willRender, this);
|
2270
|
-
this.performScroll();
|
2271
|
-
}));
|
2543
|
+
get shouldMorphPage() {
|
2544
|
+
return this.getSetting("refresh-method") === "morph";
|
2272
2545
|
}
|
2273
|
-
|
2274
|
-
|
2275
|
-
|
2276
|
-
|
2277
|
-
}
|
2546
|
+
get shouldPreserveScrollPosition() {
|
2547
|
+
return this.getSetting("refresh-scroll") === "preserve";
|
2548
|
+
}
|
2549
|
+
getSetting(name) {
|
2550
|
+
return this.headSnapshot.getMetaValue(`turbo-${name}`);
|
2278
2551
|
}
|
2279
2552
|
}
|
2280
2553
|
|
2281
|
-
|
2282
|
-
|
2283
|
-
|
2284
|
-
|
2285
|
-
|
2286
|
-
|
2287
|
-
|
2288
|
-
|
2289
|
-
|
2290
|
-
visitProposedToLocation(location, options) {
|
2291
|
-
if (locationIsVisitable(location, this.navigator.rootLocation)) {
|
2292
|
-
this.navigator.startVisit(location, options?.restorationIdentifier || uuid(), options);
|
2293
|
-
} else {
|
2294
|
-
window.location.href = location.toString();
|
2295
|
-
}
|
2296
|
-
}
|
2297
|
-
visitStarted(visit) {
|
2298
|
-
this.location = visit.location;
|
2299
|
-
visit.loadCachedSnapshot();
|
2300
|
-
visit.issueRequest();
|
2301
|
-
visit.goToSamePageAnchor();
|
2302
|
-
}
|
2303
|
-
visitRequestStarted(visit) {
|
2304
|
-
this.progressBar.setValue(0);
|
2305
|
-
if (visit.hasCachedSnapshot() || visit.action != "restore") {
|
2306
|
-
this.showVisitProgressBarAfterDelay();
|
2554
|
+
class ViewTransitioner {
|
2555
|
+
#viewTransitionStarted=false;
|
2556
|
+
#lastOperation=Promise.resolve();
|
2557
|
+
renderChange(useViewTransition, render) {
|
2558
|
+
if (useViewTransition && this.viewTransitionsAvailable && !this.#viewTransitionStarted) {
|
2559
|
+
this.#viewTransitionStarted = true;
|
2560
|
+
this.#lastOperation = this.#lastOperation.then((async () => {
|
2561
|
+
await document.startViewTransition(render).finished;
|
2562
|
+
}));
|
2307
2563
|
} else {
|
2308
|
-
this.
|
2564
|
+
this.#lastOperation = this.#lastOperation.then(render);
|
2309
2565
|
}
|
2566
|
+
return this.#lastOperation;
|
2310
2567
|
}
|
2311
|
-
|
2312
|
-
|
2568
|
+
get viewTransitionsAvailable() {
|
2569
|
+
return document.startViewTransition;
|
2313
2570
|
}
|
2314
|
-
|
2315
|
-
switch (statusCode) {
|
2316
|
-
case SystemStatusCode.networkFailure:
|
2317
|
-
case SystemStatusCode.timeoutFailure:
|
2318
|
-
case SystemStatusCode.contentTypeMismatch:
|
2319
|
-
return this.reload({
|
2320
|
-
reason: "request_failed",
|
2321
|
-
context: {
|
2322
|
-
statusCode: statusCode
|
2323
|
-
}
|
2324
|
-
});
|
2571
|
+
}
|
2325
2572
|
|
2326
|
-
|
2327
|
-
|
2328
|
-
|
2329
|
-
}
|
2330
|
-
|
2331
|
-
|
2332
|
-
|
2333
|
-
|
2334
|
-
|
2335
|
-
|
2336
|
-
|
2337
|
-
|
2338
|
-
|
2339
|
-
|
2340
|
-
|
2341
|
-
|
2342
|
-
|
2343
|
-
|
2344
|
-
|
2345
|
-
|
2346
|
-
|
2347
|
-
|
2348
|
-
|
2349
|
-
|
2350
|
-
|
2351
|
-
|
2352
|
-
|
2573
|
+
const defaultOptions = {
|
2574
|
+
action: "advance",
|
2575
|
+
historyChanged: false,
|
2576
|
+
visitCachedSnapshot: () => {},
|
2577
|
+
willRender: true,
|
2578
|
+
updateHistory: true,
|
2579
|
+
shouldCacheSnapshot: true,
|
2580
|
+
acceptsStreamResponse: false
|
2581
|
+
};
|
2582
|
+
|
2583
|
+
const TimingMetric = {
|
2584
|
+
visitStart: "visitStart",
|
2585
|
+
requestStart: "requestStart",
|
2586
|
+
requestEnd: "requestEnd",
|
2587
|
+
visitEnd: "visitEnd"
|
2588
|
+
};
|
2589
|
+
|
2590
|
+
const VisitState = {
|
2591
|
+
initialized: "initialized",
|
2592
|
+
started: "started",
|
2593
|
+
canceled: "canceled",
|
2594
|
+
failed: "failed",
|
2595
|
+
completed: "completed"
|
2596
|
+
};
|
2597
|
+
|
2598
|
+
const SystemStatusCode = {
|
2599
|
+
networkFailure: 0,
|
2600
|
+
timeoutFailure: -1,
|
2601
|
+
contentTypeMismatch: -2
|
2602
|
+
};
|
2603
|
+
|
2604
|
+
const Direction = {
|
2605
|
+
advance: "forward",
|
2606
|
+
restore: "back",
|
2607
|
+
replace: "none"
|
2608
|
+
};
|
2609
|
+
|
2610
|
+
class Visit {
|
2611
|
+
identifier=uuid();
|
2612
|
+
timingMetrics={};
|
2613
|
+
followedRedirect=false;
|
2614
|
+
historyChanged=false;
|
2615
|
+
scrolled=false;
|
2616
|
+
shouldCacheSnapshot=true;
|
2617
|
+
acceptsStreamResponse=false;
|
2618
|
+
snapshotCached=false;
|
2619
|
+
state=VisitState.initialized;
|
2620
|
+
viewTransitioner=new ViewTransitioner;
|
2621
|
+
constructor(delegate, location, restorationIdentifier, options = {}) {
|
2622
|
+
this.delegate = delegate;
|
2623
|
+
this.location = location;
|
2624
|
+
this.restorationIdentifier = restorationIdentifier || uuid();
|
2625
|
+
const {action: action, historyChanged: historyChanged, referrer: referrer, snapshot: snapshot, snapshotHTML: snapshotHTML, response: response, visitCachedSnapshot: visitCachedSnapshot, willRender: willRender, updateHistory: updateHistory, shouldCacheSnapshot: shouldCacheSnapshot, acceptsStreamResponse: acceptsStreamResponse, direction: direction} = {
|
2626
|
+
...defaultOptions,
|
2627
|
+
...options
|
2628
|
+
};
|
2629
|
+
this.action = action;
|
2630
|
+
this.historyChanged = historyChanged;
|
2631
|
+
this.referrer = referrer;
|
2632
|
+
this.snapshot = snapshot;
|
2633
|
+
this.snapshotHTML = snapshotHTML;
|
2634
|
+
this.response = response;
|
2635
|
+
this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action);
|
2636
|
+
this.isPageRefresh = this.view.isPageRefresh(this);
|
2637
|
+
this.visitCachedSnapshot = visitCachedSnapshot;
|
2638
|
+
this.willRender = willRender;
|
2639
|
+
this.updateHistory = updateHistory;
|
2640
|
+
this.scrolled = !willRender;
|
2641
|
+
this.shouldCacheSnapshot = shouldCacheSnapshot;
|
2642
|
+
this.acceptsStreamResponse = acceptsStreamResponse;
|
2643
|
+
this.direction = direction || Direction[action];
|
2353
2644
|
}
|
2354
|
-
|
2355
|
-
this.
|
2356
|
-
if (this.visitProgressBarTimeout != null) {
|
2357
|
-
window.clearTimeout(this.visitProgressBarTimeout);
|
2358
|
-
delete this.visitProgressBarTimeout;
|
2359
|
-
}
|
2645
|
+
get adapter() {
|
2646
|
+
return this.delegate.adapter;
|
2360
2647
|
}
|
2361
|
-
|
2362
|
-
|
2363
|
-
this.formProgressBarTimeout = window.setTimeout(this.showProgressBar, this.session.progressBarDelay);
|
2364
|
-
}
|
2648
|
+
get view() {
|
2649
|
+
return this.delegate.view;
|
2365
2650
|
}
|
2366
|
-
|
2367
|
-
this.
|
2368
|
-
if (this.formProgressBarTimeout != null) {
|
2369
|
-
window.clearTimeout(this.formProgressBarTimeout);
|
2370
|
-
delete this.formProgressBarTimeout;
|
2371
|
-
}
|
2651
|
+
get history() {
|
2652
|
+
return this.delegate.history;
|
2372
2653
|
}
|
2373
|
-
|
2374
|
-
this.
|
2375
|
-
};
|
2376
|
-
reload(reason) {
|
2377
|
-
dispatch("turbo:reload", {
|
2378
|
-
detail: reason
|
2379
|
-
});
|
2380
|
-
window.location.href = this.location?.toString() || window.location.href;
|
2654
|
+
get restorationData() {
|
2655
|
+
return this.history.getRestorationDataForIdentifier(this.restorationIdentifier);
|
2381
2656
|
}
|
2382
|
-
get
|
2383
|
-
return this.
|
2657
|
+
get silent() {
|
2658
|
+
return this.isSamePage;
|
2384
2659
|
}
|
2385
|
-
}
|
2386
|
-
|
2387
|
-
class CacheObserver {
|
2388
|
-
selector="[data-turbo-temporary]";
|
2389
|
-
deprecatedSelector="[data-turbo-cache=false]";
|
2390
|
-
started=false;
|
2391
2660
|
start() {
|
2392
|
-
if (
|
2393
|
-
this.
|
2394
|
-
|
2661
|
+
if (this.state == VisitState.initialized) {
|
2662
|
+
this.recordTimingMetric(TimingMetric.visitStart);
|
2663
|
+
this.state = VisitState.started;
|
2664
|
+
this.adapter.visitStarted(this);
|
2665
|
+
this.delegate.visitStarted(this);
|
2395
2666
|
}
|
2396
2667
|
}
|
2397
|
-
|
2398
|
-
if (this.started) {
|
2399
|
-
this.
|
2400
|
-
|
2668
|
+
cancel() {
|
2669
|
+
if (this.state == VisitState.started) {
|
2670
|
+
if (this.request) {
|
2671
|
+
this.request.cancel();
|
2672
|
+
}
|
2673
|
+
this.cancelRender();
|
2674
|
+
this.state = VisitState.canceled;
|
2401
2675
|
}
|
2402
2676
|
}
|
2403
|
-
|
2404
|
-
|
2405
|
-
|
2677
|
+
complete() {
|
2678
|
+
if (this.state == VisitState.started) {
|
2679
|
+
this.recordTimingMetric(TimingMetric.visitEnd);
|
2680
|
+
this.adapter.visitCompleted(this);
|
2681
|
+
this.state = VisitState.completed;
|
2682
|
+
this.followRedirect();
|
2683
|
+
if (!this.followedRedirect) {
|
2684
|
+
this.delegate.visitCompleted(this);
|
2685
|
+
}
|
2406
2686
|
}
|
2407
|
-
};
|
2408
|
-
get temporaryElements() {
|
2409
|
-
return [ ...document.querySelectorAll(this.selector), ...this.temporaryElementsWithDeprecation ];
|
2410
2687
|
}
|
2411
|
-
|
2412
|
-
|
2413
|
-
|
2414
|
-
|
2688
|
+
fail() {
|
2689
|
+
if (this.state == VisitState.started) {
|
2690
|
+
this.state = VisitState.failed;
|
2691
|
+
this.adapter.visitFailed(this);
|
2692
|
+
this.delegate.visitCompleted(this);
|
2415
2693
|
}
|
2416
|
-
return [ ...elements ];
|
2417
|
-
}
|
2418
|
-
}
|
2419
|
-
|
2420
|
-
class FrameRedirector {
|
2421
|
-
constructor(session, element) {
|
2422
|
-
this.session = session;
|
2423
|
-
this.element = element;
|
2424
|
-
this.linkInterceptor = new LinkInterceptor(this, element);
|
2425
|
-
this.formSubmitObserver = new FormSubmitObserver(this, element);
|
2426
|
-
}
|
2427
|
-
start() {
|
2428
|
-
this.linkInterceptor.start();
|
2429
|
-
this.formSubmitObserver.start();
|
2430
|
-
}
|
2431
|
-
stop() {
|
2432
|
-
this.linkInterceptor.stop();
|
2433
|
-
this.formSubmitObserver.stop();
|
2434
|
-
}
|
2435
|
-
shouldInterceptLinkClick(element, _location, _event) {
|
2436
|
-
return this.#shouldRedirect(element);
|
2437
2694
|
}
|
2438
|
-
|
2439
|
-
|
2440
|
-
|
2441
|
-
|
2695
|
+
changeHistory() {
|
2696
|
+
if (!this.historyChanged && this.updateHistory) {
|
2697
|
+
const actionForHistory = this.location.href === this.referrer?.href ? "replace" : this.action;
|
2698
|
+
const method = getHistoryMethodForAction(actionForHistory);
|
2699
|
+
this.history.update(method, this.location, this.restorationIdentifier);
|
2700
|
+
this.historyChanged = true;
|
2442
2701
|
}
|
2443
2702
|
}
|
2444
|
-
|
2445
|
-
|
2446
|
-
|
2447
|
-
|
2448
|
-
|
2449
|
-
|
2450
|
-
frame.delegate.formSubmitted(element, submitter);
|
2703
|
+
issueRequest() {
|
2704
|
+
if (this.hasPreloadedResponse()) {
|
2705
|
+
this.simulateRequest();
|
2706
|
+
} else if (this.shouldIssueRequest() && !this.request) {
|
2707
|
+
this.request = new FetchRequest(this, FetchMethod.get, this.location);
|
2708
|
+
this.request.perform();
|
2451
2709
|
}
|
2452
2710
|
}
|
2453
|
-
|
2454
|
-
|
2455
|
-
|
2456
|
-
|
2457
|
-
|
2458
|
-
}
|
2459
|
-
#shouldRedirect(element, submitter) {
|
2460
|
-
const isNavigatable = element instanceof HTMLFormElement ? this.session.submissionIsNavigatable(element, submitter) : this.session.elementIsNavigatable(element);
|
2461
|
-
if (isNavigatable) {
|
2462
|
-
const frame = this.#findFrameElement(element, submitter);
|
2463
|
-
return frame ? frame != element.closest("turbo-frame") : false;
|
2464
|
-
} else {
|
2465
|
-
return false;
|
2711
|
+
simulateRequest() {
|
2712
|
+
if (this.response) {
|
2713
|
+
this.startRequest();
|
2714
|
+
this.recordResponse();
|
2715
|
+
this.finishRequest();
|
2466
2716
|
}
|
2467
2717
|
}
|
2468
|
-
|
2469
|
-
|
2470
|
-
|
2471
|
-
|
2472
|
-
|
2473
|
-
|
2718
|
+
startRequest() {
|
2719
|
+
this.recordTimingMetric(TimingMetric.requestStart);
|
2720
|
+
this.adapter.visitRequestStarted(this);
|
2721
|
+
}
|
2722
|
+
recordResponse(response = this.response) {
|
2723
|
+
this.response = response;
|
2724
|
+
if (response) {
|
2725
|
+
const {statusCode: statusCode} = response;
|
2726
|
+
if (isSuccessful(statusCode)) {
|
2727
|
+
this.adapter.visitRequestCompleted(this);
|
2728
|
+
} else {
|
2729
|
+
this.adapter.visitRequestFailedWithStatusCode(this, statusCode);
|
2474
2730
|
}
|
2475
2731
|
}
|
2476
2732
|
}
|
2477
|
-
|
2478
|
-
|
2479
|
-
|
2480
|
-
location;
|
2481
|
-
restorationIdentifier=uuid();
|
2482
|
-
restorationData={};
|
2483
|
-
started=false;
|
2484
|
-
pageLoaded=false;
|
2485
|
-
currentIndex=0;
|
2486
|
-
constructor(delegate) {
|
2487
|
-
this.delegate = delegate;
|
2733
|
+
finishRequest() {
|
2734
|
+
this.recordTimingMetric(TimingMetric.requestEnd);
|
2735
|
+
this.adapter.visitRequestFinished(this);
|
2488
2736
|
}
|
2489
|
-
|
2490
|
-
if (
|
2491
|
-
|
2492
|
-
|
2493
|
-
|
2494
|
-
|
2495
|
-
|
2737
|
+
loadResponse() {
|
2738
|
+
if (this.response) {
|
2739
|
+
const {statusCode: statusCode, responseHTML: responseHTML} = this.response;
|
2740
|
+
this.render((async () => {
|
2741
|
+
if (this.shouldCacheSnapshot) this.cacheSnapshot();
|
2742
|
+
if (this.view.renderPromise) await this.view.renderPromise;
|
2743
|
+
if (isSuccessful(statusCode) && responseHTML != null) {
|
2744
|
+
const snapshot = PageSnapshot.fromHTMLString(responseHTML);
|
2745
|
+
await this.renderPageSnapshot(snapshot, false);
|
2746
|
+
this.adapter.visitRendered(this);
|
2747
|
+
this.complete();
|
2748
|
+
} else {
|
2749
|
+
await this.view.renderError(PageSnapshot.fromHTMLString(responseHTML), this);
|
2750
|
+
this.adapter.visitRendered(this);
|
2751
|
+
this.fail();
|
2752
|
+
}
|
2753
|
+
}));
|
2496
2754
|
}
|
2497
2755
|
}
|
2498
|
-
|
2499
|
-
|
2500
|
-
|
2501
|
-
|
2502
|
-
|
2756
|
+
getCachedSnapshot() {
|
2757
|
+
const snapshot = this.view.getCachedSnapshotForLocation(this.location) || this.getPreloadedSnapshot();
|
2758
|
+
if (snapshot && (!getAnchor(this.location) || snapshot.hasAnchor(getAnchor(this.location)))) {
|
2759
|
+
if (this.action == "restore" || snapshot.isPreviewable) {
|
2760
|
+
return snapshot;
|
2761
|
+
}
|
2503
2762
|
}
|
2504
2763
|
}
|
2505
|
-
|
2506
|
-
this.
|
2507
|
-
|
2508
|
-
|
2509
|
-
this.update(history.replaceState, location, restorationIdentifier);
|
2510
|
-
}
|
2511
|
-
update(method, location, restorationIdentifier = uuid()) {
|
2512
|
-
if (method === history.pushState) ++this.currentIndex;
|
2513
|
-
const state = {
|
2514
|
-
turbo: {
|
2515
|
-
restorationIdentifier: restorationIdentifier,
|
2516
|
-
restorationIndex: this.currentIndex
|
2517
|
-
}
|
2518
|
-
};
|
2519
|
-
method.call(history, state, "", location.href);
|
2520
|
-
this.location = location;
|
2521
|
-
this.restorationIdentifier = restorationIdentifier;
|
2764
|
+
getPreloadedSnapshot() {
|
2765
|
+
if (this.snapshotHTML) {
|
2766
|
+
return PageSnapshot.fromHTMLString(this.snapshotHTML);
|
2767
|
+
}
|
2522
2768
|
}
|
2523
|
-
|
2524
|
-
return this.
|
2769
|
+
hasCachedSnapshot() {
|
2770
|
+
return this.getCachedSnapshot() != null;
|
2525
2771
|
}
|
2526
|
-
|
2527
|
-
const
|
2528
|
-
|
2529
|
-
|
2530
|
-
|
2531
|
-
|
2532
|
-
|
2772
|
+
loadCachedSnapshot() {
|
2773
|
+
const snapshot = this.getCachedSnapshot();
|
2774
|
+
if (snapshot) {
|
2775
|
+
const isPreview = this.shouldIssueRequest();
|
2776
|
+
this.render((async () => {
|
2777
|
+
this.cacheSnapshot();
|
2778
|
+
if (this.isSamePage || this.isPageRefresh) {
|
2779
|
+
this.adapter.visitRendered(this);
|
2780
|
+
} else {
|
2781
|
+
if (this.view.renderPromise) await this.view.renderPromise;
|
2782
|
+
await this.renderPageSnapshot(snapshot, isPreview);
|
2783
|
+
this.adapter.visitRendered(this);
|
2784
|
+
if (!isPreview) {
|
2785
|
+
this.complete();
|
2786
|
+
}
|
2787
|
+
}
|
2788
|
+
}));
|
2789
|
+
}
|
2533
2790
|
}
|
2534
|
-
|
2535
|
-
if (!this.
|
2536
|
-
this.
|
2537
|
-
|
2791
|
+
followRedirect() {
|
2792
|
+
if (this.redirectedToLocation && !this.followedRedirect && this.response?.redirected) {
|
2793
|
+
this.adapter.visitProposedToLocation(this.redirectedToLocation, {
|
2794
|
+
action: "replace",
|
2795
|
+
response: this.response,
|
2796
|
+
shouldCacheSnapshot: false,
|
2797
|
+
willRender: false
|
2798
|
+
});
|
2799
|
+
this.followedRedirect = true;
|
2538
2800
|
}
|
2539
2801
|
}
|
2540
|
-
|
2541
|
-
if (this.
|
2542
|
-
|
2543
|
-
|
2802
|
+
goToSamePageAnchor() {
|
2803
|
+
if (this.isSamePage) {
|
2804
|
+
this.render((async () => {
|
2805
|
+
this.cacheSnapshot();
|
2806
|
+
this.performScroll();
|
2807
|
+
this.changeHistory();
|
2808
|
+
this.adapter.visitRendered(this);
|
2809
|
+
}));
|
2544
2810
|
}
|
2545
2811
|
}
|
2546
|
-
|
2547
|
-
if (this.
|
2548
|
-
|
2549
|
-
if (turbo) {
|
2550
|
-
this.location = new URL(window.location.href);
|
2551
|
-
const {restorationIdentifier: restorationIdentifier, restorationIndex: restorationIndex} = turbo;
|
2552
|
-
this.restorationIdentifier = restorationIdentifier;
|
2553
|
-
const direction = restorationIndex > this.currentIndex ? "forward" : "back";
|
2554
|
-
this.delegate.historyPoppedToLocationWithRestorationIdentifierAndDirection(this.location, restorationIdentifier, direction);
|
2555
|
-
this.currentIndex = restorationIndex;
|
2556
|
-
}
|
2812
|
+
prepareRequest(request) {
|
2813
|
+
if (this.acceptsStreamResponse) {
|
2814
|
+
request.acceptResponseType(StreamMessage.contentType);
|
2557
2815
|
}
|
2558
|
-
};
|
2559
|
-
onPageLoad=async _event => {
|
2560
|
-
await nextMicrotask();
|
2561
|
-
this.pageLoaded = true;
|
2562
|
-
};
|
2563
|
-
shouldHandlePopState() {
|
2564
|
-
return this.pageIsLoaded();
|
2565
2816
|
}
|
2566
|
-
|
2567
|
-
|
2817
|
+
requestStarted() {
|
2818
|
+
this.startRequest();
|
2568
2819
|
}
|
2569
|
-
}
|
2570
|
-
|
2571
|
-
|
2572
|
-
|
2573
|
-
|
2574
|
-
|
2575
|
-
|
2576
|
-
|
2820
|
+
requestPreventedHandlingResponse(_request, _response) {}
|
2821
|
+
async requestSucceededWithResponse(request, response) {
|
2822
|
+
const responseHTML = await response.responseHTML;
|
2823
|
+
const {redirected: redirected, statusCode: statusCode} = response;
|
2824
|
+
if (responseHTML == undefined) {
|
2825
|
+
this.recordResponse({
|
2826
|
+
statusCode: SystemStatusCode.contentTypeMismatch,
|
2827
|
+
redirected: redirected
|
2828
|
+
});
|
2829
|
+
} else {
|
2830
|
+
this.redirectedToLocation = response.redirected ? response.location : undefined;
|
2831
|
+
this.recordResponse({
|
2832
|
+
statusCode: statusCode,
|
2833
|
+
responseHTML: responseHTML,
|
2834
|
+
redirected: redirected
|
2835
|
+
});
|
2836
|
+
}
|
2577
2837
|
}
|
2578
|
-
|
2579
|
-
|
2580
|
-
|
2581
|
-
|
2582
|
-
|
2838
|
+
async requestFailedWithResponse(request, response) {
|
2839
|
+
const responseHTML = await response.responseHTML;
|
2840
|
+
const {redirected: redirected, statusCode: statusCode} = response;
|
2841
|
+
if (responseHTML == undefined) {
|
2842
|
+
this.recordResponse({
|
2843
|
+
statusCode: SystemStatusCode.contentTypeMismatch,
|
2844
|
+
redirected: redirected
|
2583
2845
|
});
|
2584
2846
|
} else {
|
2585
|
-
this
|
2847
|
+
this.recordResponse({
|
2848
|
+
statusCode: statusCode,
|
2849
|
+
responseHTML: responseHTML,
|
2850
|
+
redirected: redirected
|
2851
|
+
});
|
2586
2852
|
}
|
2587
2853
|
}
|
2588
|
-
|
2589
|
-
|
2590
|
-
|
2591
|
-
|
2592
|
-
passive: true
|
2593
|
-
});
|
2594
|
-
this.eventTarget.removeEventListener("mouseleave", this.#cancelRequestIfObsolete, {
|
2595
|
-
capture: true,
|
2596
|
-
passive: true
|
2854
|
+
requestErrored(_request, _error) {
|
2855
|
+
this.recordResponse({
|
2856
|
+
statusCode: SystemStatusCode.networkFailure,
|
2857
|
+
redirected: false
|
2597
2858
|
});
|
2598
|
-
this.eventTarget.removeEventListener("turbo:before-fetch-request", this.#tryToUsePrefetchedRequest, true);
|
2599
|
-
this.started = false;
|
2600
2859
|
}
|
2601
|
-
|
2602
|
-
this.
|
2603
|
-
|
2604
|
-
|
2605
|
-
|
2606
|
-
|
2607
|
-
|
2608
|
-
|
2609
|
-
|
2610
|
-
this.eventTarget.addEventListener("turbo:before-fetch-request", this.#tryToUsePrefetchedRequest, true);
|
2611
|
-
this.started = true;
|
2612
|
-
};
|
2613
|
-
#tryToPrefetchRequest=event => {
|
2614
|
-
if (getMetaContent("turbo-prefetch") === "false") return;
|
2615
|
-
const target = event.target;
|
2616
|
-
const isLink = target.matches && target.matches("a[href]:not([target^=_]):not([download])");
|
2617
|
-
if (isLink && this.#isPrefetchable(target)) {
|
2618
|
-
const link = target;
|
2619
|
-
const location = getLocationForLink(link);
|
2620
|
-
if (this.delegate.canPrefetchRequestToLocation(link, location)) {
|
2621
|
-
this.#prefetchedLink = link;
|
2622
|
-
const fetchRequest = new FetchRequest(this, FetchMethod.get, location, new URLSearchParams, target);
|
2623
|
-
prefetchCache.setLater(location.toString(), fetchRequest, this.#cacheTtl);
|
2860
|
+
requestFinished() {
|
2861
|
+
this.finishRequest();
|
2862
|
+
}
|
2863
|
+
performScroll() {
|
2864
|
+
if (!this.scrolled && !this.view.forceReloaded && !this.view.shouldPreserveScrollPosition(this)) {
|
2865
|
+
if (this.action == "restore") {
|
2866
|
+
this.scrollToRestoredPosition() || this.scrollToAnchor() || this.view.scrollToTop();
|
2867
|
+
} else {
|
2868
|
+
this.scrollToAnchor() || this.view.scrollToTop();
|
2624
2869
|
}
|
2625
|
-
|
2626
|
-
|
2627
|
-
#cancelRequestIfObsolete=event => {
|
2628
|
-
if (event.target === this.#prefetchedLink) this.#cancelPrefetchRequest();
|
2629
|
-
};
|
2630
|
-
#cancelPrefetchRequest=() => {
|
2631
|
-
prefetchCache.clear();
|
2632
|
-
this.#prefetchedLink = null;
|
2633
|
-
};
|
2634
|
-
#tryToUsePrefetchedRequest=event => {
|
2635
|
-
if (event.target.tagName !== "FORM" && event.detail.fetchOptions.method === "GET") {
|
2636
|
-
const cached = prefetchCache.get(event.detail.url.toString());
|
2637
|
-
if (cached) {
|
2638
|
-
event.detail.fetchRequest = cached;
|
2870
|
+
if (this.isSamePage) {
|
2871
|
+
this.delegate.visitScrolledToSamePageLocation(this.view.lastRenderedLocation, this.location);
|
2639
2872
|
}
|
2640
|
-
|
2641
|
-
}
|
2642
|
-
};
|
2643
|
-
prepareRequest(request) {
|
2644
|
-
const link = request.target;
|
2645
|
-
request.headers["X-Sec-Purpose"] = "prefetch";
|
2646
|
-
const turboFrame = link.closest("turbo-frame");
|
2647
|
-
const turboFrameTarget = link.getAttribute("data-turbo-frame") || turboFrame?.getAttribute("target") || turboFrame?.id;
|
2648
|
-
if (turboFrameTarget && turboFrameTarget !== "_top") {
|
2649
|
-
request.headers["Turbo-Frame"] = turboFrameTarget;
|
2873
|
+
this.scrolled = true;
|
2650
2874
|
}
|
2651
2875
|
}
|
2652
|
-
|
2653
|
-
|
2654
|
-
|
2655
|
-
|
2656
|
-
|
2657
|
-
|
2658
|
-
get #cacheTtl() {
|
2659
|
-
return Number(getMetaContent("turbo-prefetch-cache-time")) || cacheTtl;
|
2660
|
-
}
|
2661
|
-
#isPrefetchable(link) {
|
2662
|
-
const href = link.getAttribute("href");
|
2663
|
-
if (!href) return false;
|
2664
|
-
if (unfetchableLink(link)) return false;
|
2665
|
-
if (linkToTheSamePage(link)) return false;
|
2666
|
-
if (linkOptsOut(link)) return false;
|
2667
|
-
if (nonSafeLink(link)) return false;
|
2668
|
-
if (eventPrevented(link)) return false;
|
2669
|
-
return true;
|
2670
|
-
}
|
2671
|
-
}
|
2672
|
-
|
2673
|
-
const unfetchableLink = link => link.origin !== document.location.origin || ![ "http:", "https:" ].includes(link.protocol) || link.hasAttribute("target");
|
2674
|
-
|
2675
|
-
const linkToTheSamePage = link => link.pathname + link.search === document.location.pathname + document.location.search || link.href.startsWith("#");
|
2676
|
-
|
2677
|
-
const linkOptsOut = link => {
|
2678
|
-
if (link.getAttribute("data-turbo-prefetch") === "false") return true;
|
2679
|
-
if (link.getAttribute("data-turbo") === "false") return true;
|
2680
|
-
const turboPrefetchParent = findClosestRecursively(link, "[data-turbo-prefetch]");
|
2681
|
-
if (turboPrefetchParent && turboPrefetchParent.getAttribute("data-turbo-prefetch") === "false") return true;
|
2682
|
-
return false;
|
2683
|
-
};
|
2684
|
-
|
2685
|
-
const nonSafeLink = link => {
|
2686
|
-
const turboMethod = link.getAttribute("data-turbo-method");
|
2687
|
-
if (turboMethod && turboMethod.toLowerCase() !== "get") return true;
|
2688
|
-
if (isUJS(link)) return true;
|
2689
|
-
if (link.hasAttribute("data-turbo-confirm")) return true;
|
2690
|
-
if (link.hasAttribute("data-turbo-stream")) return true;
|
2691
|
-
return false;
|
2692
|
-
};
|
2693
|
-
|
2694
|
-
const isUJS = link => link.hasAttribute("data-remote") || link.hasAttribute("data-behavior") || link.hasAttribute("data-confirm") || link.hasAttribute("data-method");
|
2695
|
-
|
2696
|
-
const eventPrevented = link => {
|
2697
|
-
const event = dispatch("turbo:before-prefetch", {
|
2698
|
-
target: link,
|
2699
|
-
cancelable: true
|
2700
|
-
});
|
2701
|
-
return event.defaultPrevented;
|
2702
|
-
};
|
2703
|
-
|
2704
|
-
class Navigator {
|
2705
|
-
constructor(delegate) {
|
2706
|
-
this.delegate = delegate;
|
2876
|
+
scrollToRestoredPosition() {
|
2877
|
+
const {scrollPosition: scrollPosition} = this.restorationData;
|
2878
|
+
if (scrollPosition) {
|
2879
|
+
this.view.scrollToPosition(scrollPosition);
|
2880
|
+
return true;
|
2881
|
+
}
|
2707
2882
|
}
|
2708
|
-
|
2709
|
-
|
2710
|
-
|
2883
|
+
scrollToAnchor() {
|
2884
|
+
const anchor = getAnchor(this.location);
|
2885
|
+
if (anchor != null) {
|
2886
|
+
this.view.scrollToAnchor(anchor);
|
2887
|
+
return true;
|
2711
2888
|
}
|
2712
2889
|
}
|
2713
|
-
|
2714
|
-
this.
|
2715
|
-
this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, {
|
2716
|
-
referrer: this.location,
|
2717
|
-
...options
|
2718
|
-
});
|
2719
|
-
this.currentVisit.start();
|
2890
|
+
recordTimingMetric(metric) {
|
2891
|
+
this.timingMetrics[metric] = (new Date).getTime();
|
2720
2892
|
}
|
2721
|
-
|
2722
|
-
|
2723
|
-
|
2724
|
-
|
2893
|
+
getTimingMetrics() {
|
2894
|
+
return {
|
2895
|
+
...this.timingMetrics
|
2896
|
+
};
|
2725
2897
|
}
|
2726
|
-
|
2727
|
-
|
2728
|
-
|
2729
|
-
|
2730
|
-
|
2731
|
-
|
2732
|
-
|
2733
|
-
|
2898
|
+
getHistoryMethodForAction(action) {
|
2899
|
+
switch (action) {
|
2900
|
+
case "replace":
|
2901
|
+
return history.replaceState;
|
2902
|
+
|
2903
|
+
case "advance":
|
2904
|
+
case "restore":
|
2905
|
+
return history.pushState;
|
2734
2906
|
}
|
2735
2907
|
}
|
2736
|
-
|
2737
|
-
return this.
|
2908
|
+
hasPreloadedResponse() {
|
2909
|
+
return typeof this.response == "object";
|
2738
2910
|
}
|
2739
|
-
|
2740
|
-
|
2911
|
+
shouldIssueRequest() {
|
2912
|
+
if (this.isSamePage) {
|
2913
|
+
return false;
|
2914
|
+
} else if (this.action == "restore") {
|
2915
|
+
return !this.hasCachedSnapshot();
|
2916
|
+
} else {
|
2917
|
+
return this.willRender;
|
2918
|
+
}
|
2741
2919
|
}
|
2742
|
-
|
2743
|
-
|
2920
|
+
cacheSnapshot() {
|
2921
|
+
if (!this.snapshotCached) {
|
2922
|
+
this.view.cacheSnapshot(this.snapshot).then((snapshot => snapshot && this.visitCachedSnapshot(snapshot)));
|
2923
|
+
this.snapshotCached = true;
|
2924
|
+
}
|
2744
2925
|
}
|
2745
|
-
|
2746
|
-
|
2926
|
+
async render(callback) {
|
2927
|
+
this.cancelRender();
|
2928
|
+
await new Promise((resolve => {
|
2929
|
+
this.frame = document.visibilityState === "hidden" ? setTimeout((() => resolve()), 0) : requestAnimationFrame((() => resolve()));
|
2930
|
+
}));
|
2931
|
+
await callback();
|
2932
|
+
delete this.frame;
|
2747
2933
|
}
|
2748
|
-
|
2749
|
-
|
2750
|
-
this.
|
2934
|
+
async renderPageSnapshot(snapshot, isPreview) {
|
2935
|
+
await this.viewTransitioner.renderChange(this.view.shouldTransitionTo(snapshot), (async () => {
|
2936
|
+
await this.view.renderPage(snapshot, isPreview, this.willRender, this);
|
2937
|
+
this.performScroll();
|
2938
|
+
}));
|
2939
|
+
}
|
2940
|
+
cancelRender() {
|
2941
|
+
if (this.frame) {
|
2942
|
+
cancelAnimationFrame(this.frame);
|
2943
|
+
delete this.frame;
|
2751
2944
|
}
|
2752
2945
|
}
|
2753
|
-
|
2754
|
-
|
2755
|
-
|
2756
|
-
|
2757
|
-
|
2758
|
-
|
2759
|
-
|
2760
|
-
|
2761
|
-
|
2762
|
-
|
2763
|
-
|
2764
|
-
|
2765
|
-
|
2766
|
-
|
2767
|
-
|
2768
|
-
|
2769
|
-
redirected: redirected
|
2770
|
-
}
|
2771
|
-
};
|
2772
|
-
this.proposeVisit(fetchResponse.location, visitOptions);
|
2773
|
-
}
|
2946
|
+
}
|
2947
|
+
|
2948
|
+
function isSuccessful(statusCode) {
|
2949
|
+
return statusCode >= 200 && statusCode < 300;
|
2950
|
+
}
|
2951
|
+
|
2952
|
+
class BrowserAdapter {
|
2953
|
+
progressBar=new ProgressBar;
|
2954
|
+
constructor(session) {
|
2955
|
+
this.session = session;
|
2956
|
+
}
|
2957
|
+
visitProposedToLocation(location, options) {
|
2958
|
+
if (locationIsVisitable(location, this.navigator.rootLocation)) {
|
2959
|
+
this.navigator.startVisit(location, options?.restorationIdentifier || uuid(), options);
|
2960
|
+
} else {
|
2961
|
+
window.location.href = location.toString();
|
2774
2962
|
}
|
2775
2963
|
}
|
2776
|
-
|
2777
|
-
|
2778
|
-
|
2779
|
-
|
2780
|
-
|
2781
|
-
|
2782
|
-
|
2783
|
-
|
2784
|
-
|
2785
|
-
|
2786
|
-
|
2787
|
-
|
2788
|
-
this.view.clearSnapshotCache();
|
2964
|
+
visitStarted(visit) {
|
2965
|
+
this.location = visit.location;
|
2966
|
+
visit.loadCachedSnapshot();
|
2967
|
+
visit.issueRequest();
|
2968
|
+
visit.goToSamePageAnchor();
|
2969
|
+
}
|
2970
|
+
visitRequestStarted(visit) {
|
2971
|
+
this.progressBar.setValue(0);
|
2972
|
+
if (visit.hasCachedSnapshot() || visit.action != "restore") {
|
2973
|
+
this.showVisitProgressBarAfterDelay();
|
2974
|
+
} else {
|
2975
|
+
this.showProgressBar();
|
2789
2976
|
}
|
2790
2977
|
}
|
2791
|
-
|
2792
|
-
|
2978
|
+
visitRequestCompleted(visit) {
|
2979
|
+
visit.loadResponse();
|
2793
2980
|
}
|
2794
|
-
|
2795
|
-
|
2796
|
-
|
2981
|
+
visitRequestFailedWithStatusCode(visit, statusCode) {
|
2982
|
+
switch (statusCode) {
|
2983
|
+
case SystemStatusCode.networkFailure:
|
2984
|
+
case SystemStatusCode.timeoutFailure:
|
2985
|
+
case SystemStatusCode.contentTypeMismatch:
|
2986
|
+
return this.reload({
|
2987
|
+
reason: "request_failed",
|
2988
|
+
context: {
|
2989
|
+
statusCode: statusCode
|
2990
|
+
}
|
2991
|
+
});
|
2992
|
+
|
2993
|
+
default:
|
2994
|
+
return visit.loadResponse();
|
2797
2995
|
}
|
2798
2996
|
}
|
2799
|
-
|
2800
|
-
|
2997
|
+
visitRequestFinished(_visit) {}
|
2998
|
+
visitCompleted(_visit) {
|
2999
|
+
this.progressBar.setValue(1);
|
3000
|
+
this.hideVisitProgressBar();
|
2801
3001
|
}
|
2802
|
-
|
2803
|
-
this.
|
2804
|
-
delete this.currentVisit;
|
3002
|
+
pageInvalidated(reason) {
|
3003
|
+
this.reload(reason);
|
2805
3004
|
}
|
2806
|
-
|
2807
|
-
|
2808
|
-
|
2809
|
-
const isRestorationToTop = action === "restore" && typeof anchor === "undefined";
|
2810
|
-
return action !== "replace" && getRequestURL(location) === getRequestURL(this.view.lastRenderedLocation) && (isRestorationToTop || anchor != null && anchor !== currentAnchor);
|
3005
|
+
visitFailed(_visit) {
|
3006
|
+
this.progressBar.setValue(1);
|
3007
|
+
this.hideVisitProgressBar();
|
2811
3008
|
}
|
2812
|
-
|
2813
|
-
|
3009
|
+
visitRendered(_visit) {}
|
3010
|
+
formSubmissionStarted(_formSubmission) {
|
3011
|
+
this.progressBar.setValue(0);
|
3012
|
+
this.showFormProgressBarAfterDelay();
|
2814
3013
|
}
|
2815
|
-
|
2816
|
-
|
3014
|
+
formSubmissionFinished(_formSubmission) {
|
3015
|
+
this.progressBar.setValue(1);
|
3016
|
+
this.hideFormProgressBar();
|
2817
3017
|
}
|
2818
|
-
|
2819
|
-
|
3018
|
+
showVisitProgressBarAfterDelay() {
|
3019
|
+
this.visitProgressBarTimeout = window.setTimeout(this.showProgressBar, this.session.progressBarDelay);
|
2820
3020
|
}
|
2821
|
-
|
2822
|
-
|
2823
|
-
|
3021
|
+
hideVisitProgressBar() {
|
3022
|
+
this.progressBar.hide();
|
3023
|
+
if (this.visitProgressBarTimeout != null) {
|
3024
|
+
window.clearTimeout(this.visitProgressBarTimeout);
|
3025
|
+
delete this.visitProgressBarTimeout;
|
3026
|
+
}
|
2824
3027
|
}
|
2825
|
-
|
2826
|
-
|
2827
|
-
|
3028
|
+
showFormProgressBarAfterDelay() {
|
3029
|
+
if (this.formProgressBarTimeout == null) {
|
3030
|
+
this.formProgressBarTimeout = window.setTimeout(this.showProgressBar, this.session.progressBarDelay);
|
3031
|
+
}
|
3032
|
+
}
|
3033
|
+
hideFormProgressBar() {
|
3034
|
+
this.progressBar.hide();
|
3035
|
+
if (this.formProgressBarTimeout != null) {
|
3036
|
+
window.clearTimeout(this.formProgressBarTimeout);
|
3037
|
+
delete this.formProgressBarTimeout;
|
3038
|
+
}
|
3039
|
+
}
|
3040
|
+
showProgressBar=() => {
|
3041
|
+
this.progressBar.show();
|
3042
|
+
};
|
3043
|
+
reload(reason) {
|
3044
|
+
dispatch("turbo:reload", {
|
3045
|
+
detail: reason
|
3046
|
+
});
|
3047
|
+
window.location.href = this.location?.toString() || window.location.href;
|
3048
|
+
}
|
3049
|
+
get navigator() {
|
3050
|
+
return this.session.navigator;
|
2828
3051
|
}
|
2829
3052
|
}
|
2830
3053
|
|
2831
|
-
|
2832
|
-
|
2833
|
-
|
2834
|
-
interactive: 2,
|
2835
|
-
complete: 3
|
2836
|
-
};
|
2837
|
-
|
2838
|
-
class PageObserver {
|
2839
|
-
stage=PageStage.initial;
|
3054
|
+
class CacheObserver {
|
3055
|
+
selector="[data-turbo-temporary]";
|
3056
|
+
deprecatedSelector="[data-turbo-cache=false]";
|
2840
3057
|
started=false;
|
2841
|
-
constructor(delegate) {
|
2842
|
-
this.delegate = delegate;
|
2843
|
-
}
|
2844
3058
|
start() {
|
2845
3059
|
if (!this.started) {
|
2846
|
-
if (this.stage == PageStage.initial) {
|
2847
|
-
this.stage = PageStage.loading;
|
2848
|
-
}
|
2849
|
-
document.addEventListener("readystatechange", this.interpretReadyState, false);
|
2850
|
-
addEventListener("pagehide", this.pageWillUnload, false);
|
2851
3060
|
this.started = true;
|
3061
|
+
addEventListener("turbo:before-cache", this.removeTemporaryElements, false);
|
2852
3062
|
}
|
2853
3063
|
}
|
2854
3064
|
stop() {
|
2855
3065
|
if (this.started) {
|
2856
|
-
document.removeEventListener("readystatechange", this.interpretReadyState, false);
|
2857
|
-
removeEventListener("pagehide", this.pageWillUnload, false);
|
2858
3066
|
this.started = false;
|
3067
|
+
removeEventListener("turbo:before-cache", this.removeTemporaryElements, false);
|
2859
3068
|
}
|
2860
3069
|
}
|
2861
|
-
|
2862
|
-
const
|
2863
|
-
|
2864
|
-
this.pageIsInteractive();
|
2865
|
-
} else if (readyState == "complete") {
|
2866
|
-
this.pageIsComplete();
|
3070
|
+
removeTemporaryElements=_event => {
|
3071
|
+
for (const element of this.temporaryElements) {
|
3072
|
+
element.remove();
|
2867
3073
|
}
|
2868
3074
|
};
|
2869
|
-
|
2870
|
-
|
2871
|
-
this.stage = PageStage.interactive;
|
2872
|
-
this.delegate.pageBecameInteractive();
|
2873
|
-
}
|
3075
|
+
get temporaryElements() {
|
3076
|
+
return [ ...document.querySelectorAll(this.selector), ...this.temporaryElementsWithDeprecation ];
|
2874
3077
|
}
|
2875
|
-
|
2876
|
-
this.
|
2877
|
-
if (
|
2878
|
-
this.
|
2879
|
-
this.delegate.pageLoaded();
|
3078
|
+
get temporaryElementsWithDeprecation() {
|
3079
|
+
const elements = document.querySelectorAll(this.deprecatedSelector);
|
3080
|
+
if (elements.length) {
|
3081
|
+
console.warn(`The ${this.deprecatedSelector} selector is deprecated and will be removed in a future version. Use ${this.selector} instead.`);
|
2880
3082
|
}
|
2881
|
-
|
2882
|
-
pageWillUnload=() => {
|
2883
|
-
this.delegate.pageWillUnload();
|
2884
|
-
};
|
2885
|
-
get readyState() {
|
2886
|
-
return document.readyState;
|
3083
|
+
return [ ...elements ];
|
2887
3084
|
}
|
2888
3085
|
}
|
2889
3086
|
|
2890
|
-
class
|
2891
|
-
|
2892
|
-
|
2893
|
-
this.
|
3087
|
+
class FrameRedirector {
|
3088
|
+
constructor(session, element) {
|
3089
|
+
this.session = session;
|
3090
|
+
this.element = element;
|
3091
|
+
this.linkInterceptor = new LinkInterceptor(this, element);
|
3092
|
+
this.formSubmitObserver = new FormSubmitObserver(this, element);
|
2894
3093
|
}
|
2895
3094
|
start() {
|
2896
|
-
|
2897
|
-
|
2898
|
-
this.onScroll();
|
2899
|
-
this.started = true;
|
2900
|
-
}
|
3095
|
+
this.linkInterceptor.start();
|
3096
|
+
this.formSubmitObserver.start();
|
2901
3097
|
}
|
2902
3098
|
stop() {
|
2903
|
-
|
2904
|
-
|
2905
|
-
this.started = false;
|
2906
|
-
}
|
3099
|
+
this.linkInterceptor.stop();
|
3100
|
+
this.formSubmitObserver.stop();
|
2907
3101
|
}
|
2908
|
-
|
2909
|
-
this
|
2910
|
-
x: window.pageXOffset,
|
2911
|
-
y: window.pageYOffset
|
2912
|
-
});
|
2913
|
-
};
|
2914
|
-
updatePosition(position) {
|
2915
|
-
this.delegate.scrollPositionChanged(position);
|
3102
|
+
shouldInterceptLinkClick(element, _location, _event) {
|
3103
|
+
return this.#shouldRedirect(element);
|
2916
3104
|
}
|
2917
|
-
|
2918
|
-
|
2919
|
-
|
2920
|
-
|
2921
|
-
|
2922
|
-
withAutofocusFromFragment(fragment, (() => {
|
2923
|
-
withPreservedFocus((() => {
|
2924
|
-
document.documentElement.appendChild(fragment);
|
2925
|
-
}));
|
2926
|
-
}));
|
2927
|
-
}));
|
3105
|
+
linkClickIntercepted(element, url, event) {
|
3106
|
+
const frame = this.#findFrameElement(element);
|
3107
|
+
if (frame) {
|
3108
|
+
frame.delegate.linkClickIntercepted(element, url, event);
|
3109
|
+
}
|
2928
3110
|
}
|
2929
|
-
|
2930
|
-
|
3111
|
+
willSubmitForm(element, submitter) {
|
3112
|
+
return element.closest("turbo-frame") == null && this.#shouldSubmit(element, submitter) && this.#shouldRedirect(element, submitter);
|
2931
3113
|
}
|
2932
|
-
|
2933
|
-
|
2934
|
-
|
2935
|
-
|
2936
|
-
const permanentElementsInDocument = queryPermanentElementsAll(document.documentElement);
|
2937
|
-
const permanentElementMap = {};
|
2938
|
-
for (const permanentElementInDocument of permanentElementsInDocument) {
|
2939
|
-
const {id: id} = permanentElementInDocument;
|
2940
|
-
for (const streamElement of fragment.querySelectorAll("turbo-stream")) {
|
2941
|
-
const elementInStream = getPermanentElementById(streamElement.templateElement.content, id);
|
2942
|
-
if (elementInStream) {
|
2943
|
-
permanentElementMap[id] = [ permanentElementInDocument, elementInStream ];
|
2944
|
-
}
|
3114
|
+
formSubmitted(element, submitter) {
|
3115
|
+
const frame = this.#findFrameElement(element, submitter);
|
3116
|
+
if (frame) {
|
3117
|
+
frame.delegate.formSubmitted(element, submitter);
|
2945
3118
|
}
|
2946
3119
|
}
|
2947
|
-
|
2948
|
-
|
2949
|
-
|
2950
|
-
|
2951
|
-
|
2952
|
-
const turboStreams = fragment.querySelectorAll("turbo-stream");
|
2953
|
-
const elementWithAutofocus = firstAutofocusableElementInStreams(turboStreams);
|
2954
|
-
let willAutofocusId = null;
|
2955
|
-
if (elementWithAutofocus) {
|
2956
|
-
if (elementWithAutofocus.id) {
|
2957
|
-
willAutofocusId = elementWithAutofocus.id;
|
2958
|
-
} else {
|
2959
|
-
willAutofocusId = generatedID;
|
2960
|
-
}
|
2961
|
-
elementWithAutofocus.id = willAutofocusId;
|
3120
|
+
#shouldSubmit(form, submitter) {
|
3121
|
+
const action = getAction$1(form, submitter);
|
3122
|
+
const meta = this.element.ownerDocument.querySelector(`meta[name="turbo-root"]`);
|
3123
|
+
const rootLocation = expandURL(meta?.content ?? "/");
|
3124
|
+
return this.#shouldRedirect(form, submitter) && locationIsVisitable(action, rootLocation);
|
2962
3125
|
}
|
2963
|
-
|
2964
|
-
|
2965
|
-
|
2966
|
-
|
2967
|
-
|
2968
|
-
|
2969
|
-
|
2970
|
-
}
|
2971
|
-
if (elementToAutofocus && elementToAutofocus.id == generatedID) {
|
2972
|
-
elementToAutofocus.removeAttribute("id");
|
3126
|
+
#shouldRedirect(element, submitter) {
|
3127
|
+
const isNavigatable = element instanceof HTMLFormElement ? this.session.submissionIsNavigatable(element, submitter) : this.session.elementIsNavigatable(element);
|
3128
|
+
if (isNavigatable) {
|
3129
|
+
const frame = this.#findFrameElement(element, submitter);
|
3130
|
+
return frame ? frame != element.closest("turbo-frame") : false;
|
3131
|
+
} else {
|
3132
|
+
return false;
|
2973
3133
|
}
|
2974
3134
|
}
|
2975
|
-
|
2976
|
-
|
2977
|
-
|
2978
|
-
|
2979
|
-
|
2980
|
-
|
2981
|
-
|
2982
|
-
if (elementIsFocusable(elementToFocus) && elementToFocus != activeElementAfterRender) {
|
2983
|
-
elementToFocus.focus();
|
3135
|
+
#findFrameElement(element, submitter) {
|
3136
|
+
const id = submitter?.getAttribute("data-turbo-frame") || element.getAttribute("data-turbo-frame");
|
3137
|
+
if (id && id != "_top") {
|
3138
|
+
const frame = this.element.querySelector(`#${id}:not([disabled])`);
|
3139
|
+
if (frame instanceof FrameElement) {
|
3140
|
+
return frame;
|
3141
|
+
}
|
2984
3142
|
}
|
2985
3143
|
}
|
2986
3144
|
}
|
2987
3145
|
|
2988
|
-
|
2989
|
-
|
2990
|
-
|
2991
|
-
|
2992
|
-
|
2993
|
-
|
2994
|
-
|
2995
|
-
|
2996
|
-
class StreamObserver {
|
2997
|
-
sources=new Set;
|
2998
|
-
#started=false;
|
3146
|
+
class History {
|
3147
|
+
location;
|
3148
|
+
restorationIdentifier=uuid();
|
3149
|
+
restorationData={};
|
3150
|
+
started=false;
|
3151
|
+
pageLoaded=false;
|
3152
|
+
currentIndex=0;
|
2999
3153
|
constructor(delegate) {
|
3000
3154
|
this.delegate = delegate;
|
3001
3155
|
}
|
3002
3156
|
start() {
|
3003
|
-
if (!this
|
3004
|
-
this
|
3005
|
-
addEventListener("
|
3157
|
+
if (!this.started) {
|
3158
|
+
addEventListener("popstate", this.onPopState, false);
|
3159
|
+
addEventListener("load", this.onPageLoad, false);
|
3160
|
+
this.currentIndex = history.state?.turbo?.restorationIndex || 0;
|
3161
|
+
this.started = true;
|
3162
|
+
this.replace(new URL(window.location.href));
|
3006
3163
|
}
|
3007
3164
|
}
|
3008
3165
|
stop() {
|
3009
|
-
if (this
|
3010
|
-
this
|
3011
|
-
removeEventListener("
|
3012
|
-
|
3013
|
-
}
|
3014
|
-
connectStreamSource(source) {
|
3015
|
-
if (!this.streamSourceIsConnected(source)) {
|
3016
|
-
this.sources.add(source);
|
3017
|
-
source.addEventListener("message", this.receiveMessageEvent, false);
|
3018
|
-
}
|
3019
|
-
}
|
3020
|
-
disconnectStreamSource(source) {
|
3021
|
-
if (this.streamSourceIsConnected(source)) {
|
3022
|
-
this.sources.delete(source);
|
3023
|
-
source.removeEventListener("message", this.receiveMessageEvent, false);
|
3166
|
+
if (this.started) {
|
3167
|
+
removeEventListener("popstate", this.onPopState, false);
|
3168
|
+
removeEventListener("load", this.onPageLoad, false);
|
3169
|
+
this.started = false;
|
3024
3170
|
}
|
3025
3171
|
}
|
3026
|
-
|
3027
|
-
|
3172
|
+
push(location, restorationIdentifier) {
|
3173
|
+
this.update(history.pushState, location, restorationIdentifier);
|
3028
3174
|
}
|
3029
|
-
|
3030
|
-
|
3031
|
-
if (response && fetchResponseIsStream(response)) {
|
3032
|
-
event.preventDefault();
|
3033
|
-
this.receiveMessageResponse(response);
|
3034
|
-
}
|
3035
|
-
};
|
3036
|
-
receiveMessageEvent=event => {
|
3037
|
-
if (this.#started && typeof event.data == "string") {
|
3038
|
-
this.receiveMessageHTML(event.data);
|
3039
|
-
}
|
3040
|
-
};
|
3041
|
-
async receiveMessageResponse(response) {
|
3042
|
-
const html = await response.responseHTML;
|
3043
|
-
if (html) {
|
3044
|
-
this.receiveMessageHTML(html);
|
3045
|
-
}
|
3175
|
+
replace(location, restorationIdentifier) {
|
3176
|
+
this.update(history.replaceState, location, restorationIdentifier);
|
3046
3177
|
}
|
3047
|
-
|
3048
|
-
|
3178
|
+
update(method, location, restorationIdentifier = uuid()) {
|
3179
|
+
if (method === history.pushState) ++this.currentIndex;
|
3180
|
+
const state = {
|
3181
|
+
turbo: {
|
3182
|
+
restorationIdentifier: restorationIdentifier,
|
3183
|
+
restorationIndex: this.currentIndex
|
3184
|
+
}
|
3185
|
+
};
|
3186
|
+
method.call(history, state, "", location.href);
|
3187
|
+
this.location = location;
|
3188
|
+
this.restorationIdentifier = restorationIdentifier;
|
3049
3189
|
}
|
3050
|
-
|
3051
|
-
|
3052
|
-
function fetchResponseFromEvent(event) {
|
3053
|
-
const fetchResponse = event.detail?.fetchResponse;
|
3054
|
-
if (fetchResponse instanceof FetchResponse) {
|
3055
|
-
return fetchResponse;
|
3190
|
+
getRestorationDataForIdentifier(restorationIdentifier) {
|
3191
|
+
return this.restorationData[restorationIdentifier] || {};
|
3056
3192
|
}
|
3057
|
-
|
3058
|
-
|
3059
|
-
|
3060
|
-
|
3061
|
-
|
3062
|
-
|
3063
|
-
|
3064
|
-
class ErrorRenderer extends Renderer {
|
3065
|
-
static renderElement(currentElement, newElement) {
|
3066
|
-
const {documentElement: documentElement, body: body} = document;
|
3067
|
-
documentElement.replaceChild(newElement, body);
|
3193
|
+
updateRestorationData(additionalData) {
|
3194
|
+
const {restorationIdentifier: restorationIdentifier} = this;
|
3195
|
+
const restorationData = this.restorationData[restorationIdentifier];
|
3196
|
+
this.restorationData[restorationIdentifier] = {
|
3197
|
+
...restorationData,
|
3198
|
+
...additionalData
|
3199
|
+
};
|
3068
3200
|
}
|
3069
|
-
|
3070
|
-
this.
|
3071
|
-
|
3201
|
+
assumeControlOfScrollRestoration() {
|
3202
|
+
if (!this.previousScrollRestoration) {
|
3203
|
+
this.previousScrollRestoration = history.scrollRestoration ?? "auto";
|
3204
|
+
history.scrollRestoration = "manual";
|
3205
|
+
}
|
3072
3206
|
}
|
3073
|
-
|
3074
|
-
|
3075
|
-
|
3076
|
-
|
3207
|
+
relinquishControlOfScrollRestoration() {
|
3208
|
+
if (this.previousScrollRestoration) {
|
3209
|
+
history.scrollRestoration = this.previousScrollRestoration;
|
3210
|
+
delete this.previousScrollRestoration;
|
3211
|
+
}
|
3077
3212
|
}
|
3078
|
-
|
3079
|
-
|
3080
|
-
const
|
3081
|
-
if (
|
3082
|
-
|
3083
|
-
|
3213
|
+
onPopState=event => {
|
3214
|
+
if (this.shouldHandlePopState()) {
|
3215
|
+
const {turbo: turbo} = event.state || {};
|
3216
|
+
if (turbo) {
|
3217
|
+
this.location = new URL(window.location.href);
|
3218
|
+
const {restorationIdentifier: restorationIdentifier, restorationIndex: restorationIndex} = turbo;
|
3219
|
+
this.restorationIdentifier = restorationIdentifier;
|
3220
|
+
const direction = restorationIndex > this.currentIndex ? "forward" : "back";
|
3221
|
+
this.delegate.historyPoppedToLocationWithRestorationIdentifierAndDirection(this.location, restorationIdentifier, direction);
|
3222
|
+
this.currentIndex = restorationIndex;
|
3084
3223
|
}
|
3085
3224
|
}
|
3225
|
+
};
|
3226
|
+
onPageLoad=async _event => {
|
3227
|
+
await nextMicrotask();
|
3228
|
+
this.pageLoaded = true;
|
3229
|
+
};
|
3230
|
+
shouldHandlePopState() {
|
3231
|
+
return this.pageIsLoaded();
|
3086
3232
|
}
|
3087
|
-
|
3088
|
-
return this.
|
3089
|
-
}
|
3090
|
-
get scriptElements() {
|
3091
|
-
return document.documentElement.querySelectorAll("script");
|
3233
|
+
pageIsLoaded() {
|
3234
|
+
return this.pageLoaded || document.readyState == "complete";
|
3092
3235
|
}
|
3093
3236
|
}
|
3094
3237
|
|
3095
|
-
|
3096
|
-
|
3097
|
-
|
3098
|
-
|
3099
|
-
|
3100
|
-
|
3101
|
-
afterNodeAdded: noOp,
|
3102
|
-
beforeNodeMorphed: noOp,
|
3103
|
-
afterNodeMorphed: noOp,
|
3104
|
-
beforeNodeRemoved: noOp,
|
3105
|
-
afterNodeRemoved: noOp,
|
3106
|
-
beforeAttributeUpdated: noOp
|
3107
|
-
},
|
3108
|
-
head: {
|
3109
|
-
style: "merge",
|
3110
|
-
shouldPreserve: function(elt) {
|
3111
|
-
return elt.getAttribute("im-preserve") === "true";
|
3112
|
-
},
|
3113
|
-
shouldReAppend: function(elt) {
|
3114
|
-
return elt.getAttribute("im-re-append") === "true";
|
3115
|
-
},
|
3116
|
-
shouldRemove: noOp,
|
3117
|
-
afterHeadMorphed: noOp
|
3118
|
-
}
|
3119
|
-
};
|
3120
|
-
function morph(oldNode, newContent, config = {}) {
|
3121
|
-
if (oldNode instanceof Document) {
|
3122
|
-
oldNode = oldNode.documentElement;
|
3123
|
-
}
|
3124
|
-
if (typeof newContent === "string") {
|
3125
|
-
newContent = parseContent(newContent);
|
3126
|
-
}
|
3127
|
-
let normalizedContent = normalizeContent(newContent);
|
3128
|
-
let ctx = createMorphContext(oldNode, normalizedContent, config);
|
3129
|
-
return morphNormalizedContent(oldNode, normalizedContent, ctx);
|
3238
|
+
class LinkPrefetchObserver {
|
3239
|
+
started=false;
|
3240
|
+
#prefetchedLink=null;
|
3241
|
+
constructor(delegate, eventTarget) {
|
3242
|
+
this.delegate = delegate;
|
3243
|
+
this.eventTarget = eventTarget;
|
3130
3244
|
}
|
3131
|
-
|
3132
|
-
if (
|
3133
|
-
|
3134
|
-
|
3135
|
-
|
3136
|
-
|
3137
|
-
Promise.all(promises).then((function() {
|
3138
|
-
morphNormalizedContent(oldNode, normalizedNewContent, Object.assign(ctx, {
|
3139
|
-
head: {
|
3140
|
-
block: false,
|
3141
|
-
ignore: true
|
3142
|
-
}
|
3143
|
-
}));
|
3144
|
-
}));
|
3145
|
-
return;
|
3146
|
-
}
|
3147
|
-
}
|
3148
|
-
if (ctx.morphStyle === "innerHTML") {
|
3149
|
-
morphChildren(normalizedNewContent, oldNode, ctx);
|
3150
|
-
return oldNode.children;
|
3151
|
-
} else if (ctx.morphStyle === "outerHTML" || ctx.morphStyle == null) {
|
3152
|
-
let bestMatch = findBestNodeMatch(normalizedNewContent, oldNode, ctx);
|
3153
|
-
let previousSibling = bestMatch?.previousSibling;
|
3154
|
-
let nextSibling = bestMatch?.nextSibling;
|
3155
|
-
let morphedNode = morphOldNodeTo(oldNode, bestMatch, ctx);
|
3156
|
-
if (bestMatch) {
|
3157
|
-
return insertSiblings(previousSibling, morphedNode, nextSibling);
|
3158
|
-
} else {
|
3159
|
-
return [];
|
3160
|
-
}
|
3245
|
+
start() {
|
3246
|
+
if (this.started) return;
|
3247
|
+
if (this.eventTarget.readyState === "loading") {
|
3248
|
+
this.eventTarget.addEventListener("DOMContentLoaded", this.#enable, {
|
3249
|
+
once: true
|
3250
|
+
});
|
3161
3251
|
} else {
|
3162
|
-
|
3252
|
+
this.#enable();
|
3163
3253
|
}
|
3164
3254
|
}
|
3165
|
-
|
3166
|
-
|
3255
|
+
stop() {
|
3256
|
+
if (!this.started) return;
|
3257
|
+
this.eventTarget.removeEventListener("mouseenter", this.#tryToPrefetchRequest, {
|
3258
|
+
capture: true,
|
3259
|
+
passive: true
|
3260
|
+
});
|
3261
|
+
this.eventTarget.removeEventListener("mouseleave", this.#cancelRequestIfObsolete, {
|
3262
|
+
capture: true,
|
3263
|
+
passive: true
|
3264
|
+
});
|
3265
|
+
this.eventTarget.removeEventListener("turbo:before-fetch-request", this.#tryToUsePrefetchedRequest, true);
|
3266
|
+
this.started = false;
|
3167
3267
|
}
|
3168
|
-
|
3169
|
-
|
3170
|
-
|
3171
|
-
|
3172
|
-
|
3173
|
-
|
3174
|
-
|
3175
|
-
|
3176
|
-
|
3177
|
-
|
3178
|
-
|
3179
|
-
|
3180
|
-
|
3181
|
-
|
3182
|
-
|
3183
|
-
|
3184
|
-
|
3185
|
-
|
3186
|
-
|
3187
|
-
|
3188
|
-
|
3189
|
-
|
3268
|
+
#enable=() => {
|
3269
|
+
this.eventTarget.addEventListener("mouseenter", this.#tryToPrefetchRequest, {
|
3270
|
+
capture: true,
|
3271
|
+
passive: true
|
3272
|
+
});
|
3273
|
+
this.eventTarget.addEventListener("mouseleave", this.#cancelRequestIfObsolete, {
|
3274
|
+
capture: true,
|
3275
|
+
passive: true
|
3276
|
+
});
|
3277
|
+
this.eventTarget.addEventListener("turbo:before-fetch-request", this.#tryToUsePrefetchedRequest, true);
|
3278
|
+
this.started = true;
|
3279
|
+
};
|
3280
|
+
#tryToPrefetchRequest=event => {
|
3281
|
+
if (getMetaContent("turbo-prefetch") === "false") return;
|
3282
|
+
const target = event.target;
|
3283
|
+
const isLink = target.matches && target.matches("a[href]:not([target^=_]):not([download])");
|
3284
|
+
if (isLink && this.#isPrefetchable(target)) {
|
3285
|
+
const link = target;
|
3286
|
+
const location = getLocationForLink(link);
|
3287
|
+
if (this.delegate.canPrefetchRequestToLocation(link, location)) {
|
3288
|
+
this.#prefetchedLink = link;
|
3289
|
+
const fetchRequest = new FetchRequest(this, FetchMethod.get, location, new URLSearchParams, target);
|
3290
|
+
prefetchCache.setLater(location.toString(), fetchRequest, this.#cacheTtl);
|
3190
3291
|
}
|
3191
|
-
ctx.callbacks.afterNodeMorphed(oldNode, newContent);
|
3192
|
-
return oldNode;
|
3193
3292
|
}
|
3194
|
-
}
|
3195
|
-
|
3196
|
-
|
3197
|
-
|
3198
|
-
|
3199
|
-
|
3200
|
-
|
3201
|
-
|
3202
|
-
|
3203
|
-
|
3204
|
-
|
3205
|
-
|
3206
|
-
|
3207
|
-
continue;
|
3208
|
-
}
|
3209
|
-
if (isIdSetMatch(newChild, insertionPoint, ctx)) {
|
3210
|
-
morphOldNodeTo(insertionPoint, newChild, ctx);
|
3211
|
-
insertionPoint = insertionPoint.nextSibling;
|
3212
|
-
removeIdsFromConsideration(ctx, newChild);
|
3213
|
-
continue;
|
3214
|
-
}
|
3215
|
-
let idSetMatch = findIdSetMatch(newParent, oldParent, newChild, insertionPoint, ctx);
|
3216
|
-
if (idSetMatch) {
|
3217
|
-
insertionPoint = removeNodesBetween(insertionPoint, idSetMatch, ctx);
|
3218
|
-
morphOldNodeTo(idSetMatch, newChild, ctx);
|
3219
|
-
removeIdsFromConsideration(ctx, newChild);
|
3220
|
-
continue;
|
3221
|
-
}
|
3222
|
-
let softMatch = findSoftMatch(newParent, oldParent, newChild, insertionPoint, ctx);
|
3223
|
-
if (softMatch) {
|
3224
|
-
insertionPoint = removeNodesBetween(insertionPoint, softMatch, ctx);
|
3225
|
-
morphOldNodeTo(softMatch, newChild, ctx);
|
3226
|
-
removeIdsFromConsideration(ctx, newChild);
|
3227
|
-
continue;
|
3293
|
+
};
|
3294
|
+
#cancelRequestIfObsolete=event => {
|
3295
|
+
if (event.target === this.#prefetchedLink) this.#cancelPrefetchRequest();
|
3296
|
+
};
|
3297
|
+
#cancelPrefetchRequest=() => {
|
3298
|
+
prefetchCache.clear();
|
3299
|
+
this.#prefetchedLink = null;
|
3300
|
+
};
|
3301
|
+
#tryToUsePrefetchedRequest=event => {
|
3302
|
+
if (event.target.tagName !== "FORM" && event.detail.fetchOptions.method === "GET") {
|
3303
|
+
const cached = prefetchCache.get(event.detail.url.toString());
|
3304
|
+
if (cached) {
|
3305
|
+
event.detail.fetchRequest = cached;
|
3228
3306
|
}
|
3229
|
-
|
3230
|
-
oldParent.insertBefore(newChild, insertionPoint);
|
3231
|
-
ctx.callbacks.afterNodeAdded(newChild);
|
3232
|
-
removeIdsFromConsideration(ctx, newChild);
|
3307
|
+
prefetchCache.clear();
|
3233
3308
|
}
|
3234
|
-
|
3235
|
-
|
3236
|
-
|
3237
|
-
|
3309
|
+
};
|
3310
|
+
prepareRequest(request) {
|
3311
|
+
const link = request.target;
|
3312
|
+
request.headers["X-Sec-Purpose"] = "prefetch";
|
3313
|
+
const turboFrame = link.closest("turbo-frame");
|
3314
|
+
const turboFrameTarget = link.getAttribute("data-turbo-frame") || turboFrame?.getAttribute("target") || turboFrame?.id;
|
3315
|
+
if (turboFrameTarget && turboFrameTarget !== "_top") {
|
3316
|
+
request.headers["Turbo-Frame"] = turboFrameTarget;
|
3238
3317
|
}
|
3239
3318
|
}
|
3240
|
-
|
3241
|
-
|
3242
|
-
|
3319
|
+
requestSucceededWithResponse() {}
|
3320
|
+
requestStarted(fetchRequest) {}
|
3321
|
+
requestErrored(fetchRequest) {}
|
3322
|
+
requestFinished(fetchRequest) {}
|
3323
|
+
requestPreventedHandlingResponse(fetchRequest, fetchResponse) {}
|
3324
|
+
requestFailedWithResponse(fetchRequest, fetchResponse) {}
|
3325
|
+
get #cacheTtl() {
|
3326
|
+
return Number(getMetaContent("turbo-prefetch-cache-time")) || cacheTtl;
|
3327
|
+
}
|
3328
|
+
#isPrefetchable(link) {
|
3329
|
+
const href = link.getAttribute("href");
|
3330
|
+
if (!href) return false;
|
3331
|
+
if (unfetchableLink(link)) return false;
|
3332
|
+
if (linkToTheSamePage(link)) return false;
|
3333
|
+
if (linkOptsOut(link)) return false;
|
3334
|
+
if (nonSafeLink(link)) return false;
|
3335
|
+
if (eventPrevented(link)) return false;
|
3336
|
+
return true;
|
3337
|
+
}
|
3338
|
+
}
|
3339
|
+
|
3340
|
+
const unfetchableLink = link => link.origin !== document.location.origin || ![ "http:", "https:" ].includes(link.protocol) || link.hasAttribute("target");
|
3341
|
+
|
3342
|
+
const linkToTheSamePage = link => link.pathname + link.search === document.location.pathname + document.location.search || link.href.startsWith("#");
|
3343
|
+
|
3344
|
+
const linkOptsOut = link => {
|
3345
|
+
if (link.getAttribute("data-turbo-prefetch") === "false") return true;
|
3346
|
+
if (link.getAttribute("data-turbo") === "false") return true;
|
3347
|
+
const turboPrefetchParent = findClosestRecursively(link, "[data-turbo-prefetch]");
|
3348
|
+
if (turboPrefetchParent && turboPrefetchParent.getAttribute("data-turbo-prefetch") === "false") return true;
|
3349
|
+
return false;
|
3350
|
+
};
|
3351
|
+
|
3352
|
+
const nonSafeLink = link => {
|
3353
|
+
const turboMethod = link.getAttribute("data-turbo-method");
|
3354
|
+
if (turboMethod && turboMethod.toLowerCase() !== "get") return true;
|
3355
|
+
if (isUJS(link)) return true;
|
3356
|
+
if (link.hasAttribute("data-turbo-confirm")) return true;
|
3357
|
+
if (link.hasAttribute("data-turbo-stream")) return true;
|
3358
|
+
return false;
|
3359
|
+
};
|
3360
|
+
|
3361
|
+
const isUJS = link => link.hasAttribute("data-remote") || link.hasAttribute("data-behavior") || link.hasAttribute("data-confirm") || link.hasAttribute("data-method");
|
3362
|
+
|
3363
|
+
const eventPrevented = link => {
|
3364
|
+
const event = dispatch("turbo:before-prefetch", {
|
3365
|
+
target: link,
|
3366
|
+
cancelable: true
|
3367
|
+
});
|
3368
|
+
return event.defaultPrevented;
|
3369
|
+
};
|
3370
|
+
|
3371
|
+
class Navigator {
|
3372
|
+
constructor(delegate) {
|
3373
|
+
this.delegate = delegate;
|
3374
|
+
}
|
3375
|
+
proposeVisit(location, options = {}) {
|
3376
|
+
if (this.delegate.allowsVisitingLocationWithAction(location, options.action)) {
|
3377
|
+
this.delegate.visitProposedToLocation(location, options);
|
3243
3378
|
}
|
3244
|
-
return ctx.callbacks.beforeAttributeUpdated(attr, to, updateType) === false;
|
3245
3379
|
}
|
3246
|
-
|
3247
|
-
|
3248
|
-
|
3249
|
-
|
3250
|
-
|
3251
|
-
|
3252
|
-
|
3253
|
-
|
3254
|
-
|
3255
|
-
|
3256
|
-
|
3257
|
-
|
3258
|
-
|
3259
|
-
|
3260
|
-
|
3261
|
-
|
3262
|
-
|
3263
|
-
}
|
3264
|
-
if (!from.hasAttribute(toAttribute.name)) {
|
3265
|
-
to.removeAttribute(toAttribute.name);
|
3266
|
-
}
|
3267
|
-
}
|
3268
|
-
}
|
3269
|
-
if (type === 8 || type === 3) {
|
3270
|
-
if (to.nodeValue !== from.nodeValue) {
|
3271
|
-
to.nodeValue = from.nodeValue;
|
3272
|
-
}
|
3380
|
+
startVisit(locatable, restorationIdentifier, options = {}) {
|
3381
|
+
this.stop();
|
3382
|
+
this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, {
|
3383
|
+
referrer: this.location,
|
3384
|
+
...options
|
3385
|
+
});
|
3386
|
+
this.currentVisit.start();
|
3387
|
+
}
|
3388
|
+
submitForm(form, submitter) {
|
3389
|
+
this.stop();
|
3390
|
+
this.formSubmission = new FormSubmission(this, form, submitter, true);
|
3391
|
+
this.formSubmission.start();
|
3392
|
+
}
|
3393
|
+
stop() {
|
3394
|
+
if (this.formSubmission) {
|
3395
|
+
this.formSubmission.stop();
|
3396
|
+
delete this.formSubmission;
|
3273
3397
|
}
|
3274
|
-
if (
|
3275
|
-
|
3398
|
+
if (this.currentVisit) {
|
3399
|
+
this.currentVisit.cancel();
|
3400
|
+
delete this.currentVisit;
|
3276
3401
|
}
|
3277
3402
|
}
|
3278
|
-
|
3279
|
-
|
3280
|
-
let ignoreUpdate = ignoreAttribute(attributeName, to, "update", ctx);
|
3281
|
-
if (!ignoreUpdate) {
|
3282
|
-
to[attributeName] = from[attributeName];
|
3283
|
-
}
|
3284
|
-
if (from[attributeName]) {
|
3285
|
-
if (!ignoreUpdate) {
|
3286
|
-
to.setAttribute(attributeName, from[attributeName]);
|
3287
|
-
}
|
3288
|
-
} else {
|
3289
|
-
if (!ignoreAttribute(attributeName, to, "remove", ctx)) {
|
3290
|
-
to.removeAttribute(attributeName);
|
3291
|
-
}
|
3292
|
-
}
|
3293
|
-
}
|
3403
|
+
get adapter() {
|
3404
|
+
return this.delegate.adapter;
|
3294
3405
|
}
|
3295
|
-
|
3296
|
-
|
3297
|
-
let fromValue = from.value;
|
3298
|
-
let toValue = to.value;
|
3299
|
-
syncBooleanAttribute(from, to, "checked", ctx);
|
3300
|
-
syncBooleanAttribute(from, to, "disabled", ctx);
|
3301
|
-
if (!from.hasAttribute("value")) {
|
3302
|
-
if (!ignoreAttribute("value", to, "remove", ctx)) {
|
3303
|
-
to.value = "";
|
3304
|
-
to.removeAttribute("value");
|
3305
|
-
}
|
3306
|
-
} else if (fromValue !== toValue) {
|
3307
|
-
if (!ignoreAttribute("value", to, "update", ctx)) {
|
3308
|
-
to.setAttribute("value", fromValue);
|
3309
|
-
to.value = fromValue;
|
3310
|
-
}
|
3311
|
-
}
|
3312
|
-
} else if (from instanceof HTMLOptionElement) {
|
3313
|
-
syncBooleanAttribute(from, to, "selected", ctx);
|
3314
|
-
} else if (from instanceof HTMLTextAreaElement && to instanceof HTMLTextAreaElement) {
|
3315
|
-
let fromValue = from.value;
|
3316
|
-
let toValue = to.value;
|
3317
|
-
if (ignoreAttribute("value", to, "update", ctx)) {
|
3318
|
-
return;
|
3319
|
-
}
|
3320
|
-
if (fromValue !== toValue) {
|
3321
|
-
to.value = fromValue;
|
3322
|
-
}
|
3323
|
-
if (to.firstChild && to.firstChild.nodeValue !== fromValue) {
|
3324
|
-
to.firstChild.nodeValue = fromValue;
|
3325
|
-
}
|
3326
|
-
}
|
3406
|
+
get view() {
|
3407
|
+
return this.delegate.view;
|
3327
3408
|
}
|
3328
|
-
|
3329
|
-
|
3330
|
-
|
3331
|
-
|
3332
|
-
|
3333
|
-
|
3334
|
-
|
3335
|
-
|
3336
|
-
|
3409
|
+
get rootLocation() {
|
3410
|
+
return this.view.snapshot.rootLocation;
|
3411
|
+
}
|
3412
|
+
get history() {
|
3413
|
+
return this.delegate.history;
|
3414
|
+
}
|
3415
|
+
formSubmissionStarted(formSubmission) {
|
3416
|
+
if (typeof this.adapter.formSubmissionStarted === "function") {
|
3417
|
+
this.adapter.formSubmissionStarted(formSubmission);
|
3337
3418
|
}
|
3338
|
-
|
3339
|
-
|
3340
|
-
|
3341
|
-
|
3342
|
-
if (
|
3343
|
-
|
3344
|
-
|
3345
|
-
|
3346
|
-
srcToNewHeadNodes.delete(currentHeadElt.outerHTML);
|
3347
|
-
preserved.push(currentHeadElt);
|
3419
|
+
}
|
3420
|
+
async formSubmissionSucceededWithResponse(formSubmission, fetchResponse) {
|
3421
|
+
if (formSubmission == this.formSubmission) {
|
3422
|
+
const responseHTML = await fetchResponse.responseHTML;
|
3423
|
+
if (responseHTML) {
|
3424
|
+
const shouldCacheSnapshot = formSubmission.isSafe;
|
3425
|
+
if (!shouldCacheSnapshot) {
|
3426
|
+
this.view.clearSnapshotCache();
|
3348
3427
|
}
|
3349
|
-
|
3350
|
-
|
3351
|
-
|
3352
|
-
|
3353
|
-
|
3354
|
-
|
3355
|
-
|
3356
|
-
|
3357
|
-
|
3428
|
+
const {statusCode: statusCode, redirected: redirected} = fetchResponse;
|
3429
|
+
const action = this.#getActionForFormSubmission(formSubmission, fetchResponse);
|
3430
|
+
const visitOptions = {
|
3431
|
+
action: action,
|
3432
|
+
shouldCacheSnapshot: shouldCacheSnapshot,
|
3433
|
+
response: {
|
3434
|
+
statusCode: statusCode,
|
3435
|
+
responseHTML: responseHTML,
|
3436
|
+
redirected: redirected
|
3358
3437
|
}
|
3359
|
-
}
|
3360
|
-
|
3361
|
-
}
|
3362
|
-
nodesToAppend.push(...srcToNewHeadNodes.values());
|
3363
|
-
let promises = [];
|
3364
|
-
for (const newNode of nodesToAppend) {
|
3365
|
-
let newElt = document.createRange().createContextualFragment(newNode.outerHTML).firstChild;
|
3366
|
-
if (ctx.callbacks.beforeNodeAdded(newElt) !== false) {
|
3367
|
-
if (newElt.href || newElt.src) {
|
3368
|
-
let resolve = null;
|
3369
|
-
let promise = new Promise((function(_resolve) {
|
3370
|
-
resolve = _resolve;
|
3371
|
-
}));
|
3372
|
-
newElt.addEventListener("load", (function() {
|
3373
|
-
resolve();
|
3374
|
-
}));
|
3375
|
-
promises.push(promise);
|
3376
|
-
}
|
3377
|
-
currentHead.appendChild(newElt);
|
3378
|
-
ctx.callbacks.afterNodeAdded(newElt);
|
3379
|
-
added.push(newElt);
|
3380
|
-
}
|
3381
|
-
}
|
3382
|
-
for (const removedElement of removed) {
|
3383
|
-
if (ctx.callbacks.beforeNodeRemoved(removedElement) !== false) {
|
3384
|
-
currentHead.removeChild(removedElement);
|
3385
|
-
ctx.callbacks.afterNodeRemoved(removedElement);
|
3438
|
+
};
|
3439
|
+
this.proposeVisit(fetchResponse.location, visitOptions);
|
3386
3440
|
}
|
3387
3441
|
}
|
3388
|
-
ctx.head.afterHeadMorphed(currentHead, {
|
3389
|
-
added: added,
|
3390
|
-
kept: preserved,
|
3391
|
-
removed: removed
|
3392
|
-
});
|
3393
|
-
return promises;
|
3394
|
-
}
|
3395
|
-
function noOp() {}
|
3396
|
-
function mergeDefaults(config) {
|
3397
|
-
let finalConfig = {};
|
3398
|
-
Object.assign(finalConfig, defaults);
|
3399
|
-
Object.assign(finalConfig, config);
|
3400
|
-
finalConfig.callbacks = {};
|
3401
|
-
Object.assign(finalConfig.callbacks, defaults.callbacks);
|
3402
|
-
Object.assign(finalConfig.callbacks, config.callbacks);
|
3403
|
-
finalConfig.head = {};
|
3404
|
-
Object.assign(finalConfig.head, defaults.head);
|
3405
|
-
Object.assign(finalConfig.head, config.head);
|
3406
|
-
return finalConfig;
|
3407
|
-
}
|
3408
|
-
function createMorphContext(oldNode, newContent, config) {
|
3409
|
-
config = mergeDefaults(config);
|
3410
|
-
return {
|
3411
|
-
target: oldNode,
|
3412
|
-
newContent: newContent,
|
3413
|
-
config: config,
|
3414
|
-
morphStyle: config.morphStyle,
|
3415
|
-
ignoreActive: config.ignoreActive,
|
3416
|
-
ignoreActiveValue: config.ignoreActiveValue,
|
3417
|
-
idMap: createIdMap(oldNode, newContent),
|
3418
|
-
deadIds: new Set,
|
3419
|
-
callbacks: config.callbacks,
|
3420
|
-
head: config.head
|
3421
|
-
};
|
3422
3442
|
}
|
3423
|
-
|
3424
|
-
|
3425
|
-
|
3426
|
-
|
3427
|
-
|
3428
|
-
|
3429
|
-
return true;
|
3443
|
+
async formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
|
3444
|
+
const responseHTML = await fetchResponse.responseHTML;
|
3445
|
+
if (responseHTML) {
|
3446
|
+
const snapshot = PageSnapshot.fromHTMLString(responseHTML);
|
3447
|
+
if (fetchResponse.serverError) {
|
3448
|
+
await this.view.renderError(snapshot, this.currentVisit);
|
3430
3449
|
} else {
|
3431
|
-
|
3450
|
+
await this.view.renderPage(snapshot, false, true, this.currentVisit);
|
3451
|
+
}
|
3452
|
+
if (!snapshot.shouldPreserveScrollPosition) {
|
3453
|
+
this.view.scrollToTop();
|
3432
3454
|
}
|
3455
|
+
this.view.clearSnapshotCache();
|
3433
3456
|
}
|
3434
|
-
return false;
|
3435
3457
|
}
|
3436
|
-
|
3437
|
-
|
3438
|
-
return false;
|
3439
|
-
}
|
3440
|
-
return node1.nodeType === node2.nodeType && node1.tagName === node2.tagName;
|
3458
|
+
formSubmissionErrored(formSubmission, error) {
|
3459
|
+
console.error(error);
|
3441
3460
|
}
|
3442
|
-
|
3443
|
-
|
3444
|
-
|
3445
|
-
startInclusive = startInclusive.nextSibling;
|
3446
|
-
removeNode(tempNode, ctx);
|
3461
|
+
formSubmissionFinished(formSubmission) {
|
3462
|
+
if (typeof this.adapter.formSubmissionFinished === "function") {
|
3463
|
+
this.adapter.formSubmissionFinished(formSubmission);
|
3447
3464
|
}
|
3448
|
-
removeIdsFromConsideration(ctx, endExclusive);
|
3449
|
-
return endExclusive.nextSibling;
|
3450
3465
|
}
|
3451
|
-
|
3452
|
-
|
3453
|
-
|
3454
|
-
|
3455
|
-
|
3456
|
-
|
3457
|
-
|
3458
|
-
|
3459
|
-
|
3460
|
-
|
3461
|
-
|
3462
|
-
|
3463
|
-
|
3464
|
-
|
3465
|
-
|
3466
|
+
visitStarted(visit) {
|
3467
|
+
this.delegate.visitStarted(visit);
|
3468
|
+
}
|
3469
|
+
visitCompleted(visit) {
|
3470
|
+
this.delegate.visitCompleted(visit);
|
3471
|
+
delete this.currentVisit;
|
3472
|
+
}
|
3473
|
+
locationWithActionIsSamePage(location, action) {
|
3474
|
+
const anchor = getAnchor(location);
|
3475
|
+
const currentAnchor = getAnchor(this.view.lastRenderedLocation);
|
3476
|
+
const isRestorationToTop = action === "restore" && typeof anchor === "undefined";
|
3477
|
+
return action !== "replace" && getRequestURL(location) === getRequestURL(this.view.lastRenderedLocation) && (isRestorationToTop || anchor != null && anchor !== currentAnchor);
|
3478
|
+
}
|
3479
|
+
visitScrolledToSamePageLocation(oldURL, newURL) {
|
3480
|
+
this.delegate.visitScrolledToSamePageLocation(oldURL, newURL);
|
3481
|
+
}
|
3482
|
+
get location() {
|
3483
|
+
return this.history.location;
|
3484
|
+
}
|
3485
|
+
get restorationIdentifier() {
|
3486
|
+
return this.history.restorationIdentifier;
|
3487
|
+
}
|
3488
|
+
#getActionForFormSubmission(formSubmission, fetchResponse) {
|
3489
|
+
const {submitter: submitter, formElement: formElement} = formSubmission;
|
3490
|
+
return getVisitAction(submitter, formElement) || this.#getDefaultAction(fetchResponse);
|
3491
|
+
}
|
3492
|
+
#getDefaultAction(fetchResponse) {
|
3493
|
+
const sameLocationRedirect = fetchResponse.redirected && fetchResponse.location.href === this.location?.href;
|
3494
|
+
return sameLocationRedirect ? "replace" : "advance";
|
3495
|
+
}
|
3496
|
+
}
|
3497
|
+
|
3498
|
+
const PageStage = {
|
3499
|
+
initial: 0,
|
3500
|
+
loading: 1,
|
3501
|
+
interactive: 2,
|
3502
|
+
complete: 3
|
3503
|
+
};
|
3504
|
+
|
3505
|
+
class PageObserver {
|
3506
|
+
stage=PageStage.initial;
|
3507
|
+
started=false;
|
3508
|
+
constructor(delegate) {
|
3509
|
+
this.delegate = delegate;
|
3510
|
+
}
|
3511
|
+
start() {
|
3512
|
+
if (!this.started) {
|
3513
|
+
if (this.stage == PageStage.initial) {
|
3514
|
+
this.stage = PageStage.loading;
|
3466
3515
|
}
|
3516
|
+
document.addEventListener("readystatechange", this.interpretReadyState, false);
|
3517
|
+
addEventListener("pagehide", this.pageWillUnload, false);
|
3518
|
+
this.started = true;
|
3467
3519
|
}
|
3468
|
-
return potentialMatch;
|
3469
3520
|
}
|
3470
|
-
|
3471
|
-
|
3472
|
-
|
3473
|
-
|
3474
|
-
|
3475
|
-
if (getIdIntersectionCount(ctx, potentialSoftMatch, newContent) > 0) {
|
3476
|
-
return null;
|
3477
|
-
}
|
3478
|
-
if (isSoftMatch(newChild, potentialSoftMatch)) {
|
3479
|
-
return potentialSoftMatch;
|
3480
|
-
}
|
3481
|
-
if (isSoftMatch(nextSibling, potentialSoftMatch)) {
|
3482
|
-
siblingSoftMatchCount++;
|
3483
|
-
nextSibling = nextSibling.nextSibling;
|
3484
|
-
if (siblingSoftMatchCount >= 2) {
|
3485
|
-
return null;
|
3486
|
-
}
|
3487
|
-
}
|
3488
|
-
potentialSoftMatch = potentialSoftMatch.nextSibling;
|
3521
|
+
stop() {
|
3522
|
+
if (this.started) {
|
3523
|
+
document.removeEventListener("readystatechange", this.interpretReadyState, false);
|
3524
|
+
removeEventListener("pagehide", this.pageWillUnload, false);
|
3525
|
+
this.started = false;
|
3489
3526
|
}
|
3490
|
-
return potentialSoftMatch;
|
3491
3527
|
}
|
3492
|
-
|
3493
|
-
|
3494
|
-
|
3495
|
-
|
3496
|
-
|
3497
|
-
|
3498
|
-
|
3499
|
-
|
3500
|
-
|
3501
|
-
|
3502
|
-
|
3503
|
-
|
3504
|
-
return htmlElement;
|
3505
|
-
} else {
|
3506
|
-
return null;
|
3507
|
-
}
|
3508
|
-
}
|
3509
|
-
} else {
|
3510
|
-
let responseDoc = parser.parseFromString("<body><template>" + newContent + "</template></body>", "text/html");
|
3511
|
-
let content = responseDoc.body.querySelector("template").content;
|
3512
|
-
content.generatedByIdiomorph = true;
|
3513
|
-
return content;
|
3528
|
+
interpretReadyState=() => {
|
3529
|
+
const {readyState: readyState} = this;
|
3530
|
+
if (readyState == "interactive") {
|
3531
|
+
this.pageIsInteractive();
|
3532
|
+
} else if (readyState == "complete") {
|
3533
|
+
this.pageIsComplete();
|
3534
|
+
}
|
3535
|
+
};
|
3536
|
+
pageIsInteractive() {
|
3537
|
+
if (this.stage == PageStage.loading) {
|
3538
|
+
this.stage = PageStage.interactive;
|
3539
|
+
this.delegate.pageBecameInteractive();
|
3514
3540
|
}
|
3515
3541
|
}
|
3516
|
-
|
3517
|
-
|
3518
|
-
|
3519
|
-
|
3520
|
-
|
3521
|
-
return newContent;
|
3522
|
-
} else if (newContent instanceof Node) {
|
3523
|
-
const dummyParent = document.createElement("div");
|
3524
|
-
dummyParent.append(newContent);
|
3525
|
-
return dummyParent;
|
3526
|
-
} else {
|
3527
|
-
const dummyParent = document.createElement("div");
|
3528
|
-
for (const elt of [ ...newContent ]) {
|
3529
|
-
dummyParent.append(elt);
|
3530
|
-
}
|
3531
|
-
return dummyParent;
|
3542
|
+
pageIsComplete() {
|
3543
|
+
this.pageIsInteractive();
|
3544
|
+
if (this.stage == PageStage.interactive) {
|
3545
|
+
this.stage = PageStage.complete;
|
3546
|
+
this.delegate.pageLoaded();
|
3532
3547
|
}
|
3533
3548
|
}
|
3534
|
-
|
3535
|
-
|
3536
|
-
|
3537
|
-
|
3538
|
-
|
3539
|
-
|
3549
|
+
pageWillUnload=() => {
|
3550
|
+
this.delegate.pageWillUnload();
|
3551
|
+
};
|
3552
|
+
get readyState() {
|
3553
|
+
return document.readyState;
|
3554
|
+
}
|
3555
|
+
}
|
3556
|
+
|
3557
|
+
class ScrollObserver {
|
3558
|
+
started=false;
|
3559
|
+
constructor(delegate) {
|
3560
|
+
this.delegate = delegate;
|
3561
|
+
}
|
3562
|
+
start() {
|
3563
|
+
if (!this.started) {
|
3564
|
+
addEventListener("scroll", this.onScroll, false);
|
3565
|
+
this.onScroll();
|
3566
|
+
this.started = true;
|
3540
3567
|
}
|
3541
|
-
|
3542
|
-
|
3543
|
-
|
3544
|
-
|
3568
|
+
}
|
3569
|
+
stop() {
|
3570
|
+
if (this.started) {
|
3571
|
+
removeEventListener("scroll", this.onScroll, false);
|
3572
|
+
this.started = false;
|
3545
3573
|
}
|
3546
|
-
|
3547
|
-
|
3548
|
-
|
3549
|
-
|
3550
|
-
|
3574
|
+
}
|
3575
|
+
onScroll=() => {
|
3576
|
+
this.updatePosition({
|
3577
|
+
x: window.pageXOffset,
|
3578
|
+
y: window.pageYOffset
|
3579
|
+
});
|
3580
|
+
};
|
3581
|
+
updatePosition(position) {
|
3582
|
+
this.delegate.scrollPositionChanged(position);
|
3583
|
+
}
|
3584
|
+
}
|
3585
|
+
|
3586
|
+
class StreamMessageRenderer {
|
3587
|
+
render({fragment: fragment}) {
|
3588
|
+
Bardo.preservingPermanentElements(this, getPermanentElementMapForFragment(fragment), (() => {
|
3589
|
+
withAutofocusFromFragment(fragment, (() => {
|
3590
|
+
withPreservedFocus((() => {
|
3591
|
+
document.documentElement.appendChild(fragment);
|
3592
|
+
}));
|
3593
|
+
}));
|
3594
|
+
}));
|
3595
|
+
}
|
3596
|
+
enteringBardo(currentPermanentElement, newPermanentElement) {
|
3597
|
+
newPermanentElement.replaceWith(currentPermanentElement.cloneNode(true));
|
3598
|
+
}
|
3599
|
+
leavingBardo() {}
|
3600
|
+
}
|
3601
|
+
|
3602
|
+
function getPermanentElementMapForFragment(fragment) {
|
3603
|
+
const permanentElementsInDocument = queryPermanentElementsAll(document.documentElement);
|
3604
|
+
const permanentElementMap = {};
|
3605
|
+
for (const permanentElementInDocument of permanentElementsInDocument) {
|
3606
|
+
const {id: id} = permanentElementInDocument;
|
3607
|
+
for (const streamElement of fragment.querySelectorAll("turbo-stream")) {
|
3608
|
+
const elementInStream = getPermanentElementById(streamElement.templateElement.content, id);
|
3609
|
+
if (elementInStream) {
|
3610
|
+
permanentElementMap[id] = [ permanentElementInDocument, elementInStream ];
|
3611
|
+
}
|
3551
3612
|
}
|
3552
|
-
|
3553
|
-
|
3613
|
+
}
|
3614
|
+
return permanentElementMap;
|
3615
|
+
}
|
3616
|
+
|
3617
|
+
async function withAutofocusFromFragment(fragment, callback) {
|
3618
|
+
const generatedID = `turbo-stream-autofocus-${uuid()}`;
|
3619
|
+
const turboStreams = fragment.querySelectorAll("turbo-stream");
|
3620
|
+
const elementWithAutofocus = firstAutofocusableElementInStreams(turboStreams);
|
3621
|
+
let willAutofocusId = null;
|
3622
|
+
if (elementWithAutofocus) {
|
3623
|
+
if (elementWithAutofocus.id) {
|
3624
|
+
willAutofocusId = elementWithAutofocus.id;
|
3625
|
+
} else {
|
3626
|
+
willAutofocusId = generatedID;
|
3554
3627
|
}
|
3555
|
-
|
3628
|
+
elementWithAutofocus.id = willAutofocusId;
|
3556
3629
|
}
|
3557
|
-
|
3558
|
-
|
3559
|
-
|
3560
|
-
|
3561
|
-
|
3562
|
-
|
3563
|
-
|
3564
|
-
|
3565
|
-
|
3566
|
-
|
3567
|
-
}
|
3568
|
-
currentElement = currentElement.nextSibling;
|
3630
|
+
callback();
|
3631
|
+
await nextRepaint();
|
3632
|
+
const hasNoActiveElement = document.activeElement == null || document.activeElement == document.body;
|
3633
|
+
if (hasNoActiveElement && willAutofocusId) {
|
3634
|
+
const elementToAutofocus = document.getElementById(willAutofocusId);
|
3635
|
+
if (elementIsFocusable(elementToAutofocus)) {
|
3636
|
+
elementToAutofocus.focus();
|
3637
|
+
}
|
3638
|
+
if (elementToAutofocus && elementToAutofocus.id == generatedID) {
|
3639
|
+
elementToAutofocus.removeAttribute("id");
|
3569
3640
|
}
|
3570
|
-
return bestElement;
|
3571
3641
|
}
|
3572
|
-
|
3573
|
-
|
3574
|
-
|
3642
|
+
}
|
3643
|
+
|
3644
|
+
async function withPreservedFocus(callback) {
|
3645
|
+
const [activeElementBeforeRender, activeElementAfterRender] = await around(callback, (() => document.activeElement));
|
3646
|
+
const restoreFocusTo = activeElementBeforeRender && activeElementBeforeRender.id;
|
3647
|
+
if (restoreFocusTo) {
|
3648
|
+
const elementToFocus = document.getElementById(restoreFocusTo);
|
3649
|
+
if (elementIsFocusable(elementToFocus) && elementToFocus != activeElementAfterRender) {
|
3650
|
+
elementToFocus.focus();
|
3575
3651
|
}
|
3576
|
-
return 0;
|
3577
3652
|
}
|
3578
|
-
|
3579
|
-
|
3580
|
-
|
3581
|
-
|
3582
|
-
|
3653
|
+
}
|
3654
|
+
|
3655
|
+
function firstAutofocusableElementInStreams(nodeListOfStreamElements) {
|
3656
|
+
for (const streamElement of nodeListOfStreamElements) {
|
3657
|
+
const elementWithAutofocus = queryAutofocusableElement(streamElement.templateElement.content);
|
3658
|
+
if (elementWithAutofocus) return elementWithAutofocus;
|
3583
3659
|
}
|
3584
|
-
|
3585
|
-
|
3660
|
+
return null;
|
3661
|
+
}
|
3662
|
+
|
3663
|
+
class StreamObserver {
|
3664
|
+
sources=new Set;
|
3665
|
+
#started=false;
|
3666
|
+
constructor(delegate) {
|
3667
|
+
this.delegate = delegate;
|
3586
3668
|
}
|
3587
|
-
|
3588
|
-
|
3589
|
-
|
3669
|
+
start() {
|
3670
|
+
if (!this.#started) {
|
3671
|
+
this.#started = true;
|
3672
|
+
addEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
|
3673
|
+
}
|
3590
3674
|
}
|
3591
|
-
|
3592
|
-
|
3593
|
-
|
3594
|
-
|
3675
|
+
stop() {
|
3676
|
+
if (this.#started) {
|
3677
|
+
this.#started = false;
|
3678
|
+
removeEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
|
3595
3679
|
}
|
3596
3680
|
}
|
3597
|
-
|
3598
|
-
|
3599
|
-
|
3600
|
-
|
3601
|
-
if (isIdInConsideration(ctx, id) && idIsWithinNode(ctx, id, node2)) {
|
3602
|
-
++matchCount;
|
3603
|
-
}
|
3681
|
+
connectStreamSource(source) {
|
3682
|
+
if (!this.streamSourceIsConnected(source)) {
|
3683
|
+
this.sources.add(source);
|
3684
|
+
source.addEventListener("message", this.receiveMessageEvent, false);
|
3604
3685
|
}
|
3605
|
-
return matchCount;
|
3606
3686
|
}
|
3607
|
-
|
3608
|
-
|
3609
|
-
|
3610
|
-
|
3611
|
-
let current = elt;
|
3612
|
-
while (current !== nodeParent && current != null) {
|
3613
|
-
let idSet = idMap.get(current);
|
3614
|
-
if (idSet == null) {
|
3615
|
-
idSet = new Set;
|
3616
|
-
idMap.set(current, idSet);
|
3617
|
-
}
|
3618
|
-
idSet.add(elt.id);
|
3619
|
-
current = current.parentElement;
|
3620
|
-
}
|
3687
|
+
disconnectStreamSource(source) {
|
3688
|
+
if (this.streamSourceIsConnected(source)) {
|
3689
|
+
this.sources.delete(source);
|
3690
|
+
source.removeEventListener("message", this.receiveMessageEvent, false);
|
3621
3691
|
}
|
3622
3692
|
}
|
3623
|
-
|
3624
|
-
|
3625
|
-
populateIdMapForNode(oldContent, idMap);
|
3626
|
-
populateIdMapForNode(newContent, idMap);
|
3627
|
-
return idMap;
|
3693
|
+
streamSourceIsConnected(source) {
|
3694
|
+
return this.sources.has(source);
|
3628
3695
|
}
|
3629
|
-
|
3630
|
-
|
3631
|
-
|
3696
|
+
inspectFetchResponse=event => {
|
3697
|
+
const response = fetchResponseFromEvent(event);
|
3698
|
+
if (response && fetchResponseIsStream(response)) {
|
3699
|
+
event.preventDefault();
|
3700
|
+
this.receiveMessageResponse(response);
|
3701
|
+
}
|
3632
3702
|
};
|
3633
|
-
|
3634
|
-
|
3635
|
-
|
3636
|
-
|
3637
|
-
|
3638
|
-
|
3639
|
-
|
3703
|
+
receiveMessageEvent=event => {
|
3704
|
+
if (this.#started && typeof event.data == "string") {
|
3705
|
+
this.receiveMessageHTML(event.data);
|
3706
|
+
}
|
3707
|
+
};
|
3708
|
+
async receiveMessageResponse(response) {
|
3709
|
+
const html = await response.responseHTML;
|
3710
|
+
if (html) {
|
3711
|
+
this.receiveMessageHTML(html);
|
3712
|
+
}
|
3713
|
+
}
|
3714
|
+
receiveMessageHTML(html) {
|
3715
|
+
this.delegate.receivedMessageFromStream(StreamMessage.wrap(html));
|
3716
|
+
}
|
3640
3717
|
}
|
3641
3718
|
|
3642
|
-
function
|
3643
|
-
|
3644
|
-
|
3645
|
-
|
3719
|
+
function fetchResponseFromEvent(event) {
|
3720
|
+
const fetchResponse = event.detail?.fetchResponse;
|
3721
|
+
if (fetchResponse instanceof FetchResponse) {
|
3722
|
+
return fetchResponse;
|
3723
|
+
}
|
3646
3724
|
}
|
3647
3725
|
|
3648
|
-
|
3649
|
-
|
3650
|
-
|
3651
|
-
this.#beforeNodeMorphed = beforeNodeMorphed || (() => true);
|
3652
|
-
}
|
3653
|
-
beforeNodeAdded=node => !(node.id && node.hasAttribute("data-turbo-permanent") && document.getElementById(node.id));
|
3654
|
-
beforeNodeMorphed=(currentElement, newElement) => {
|
3655
|
-
if (currentElement instanceof Element) {
|
3656
|
-
if (!currentElement.hasAttribute("data-turbo-permanent") && this.#beforeNodeMorphed(currentElement, newElement)) {
|
3657
|
-
const event = dispatch("turbo:before-morph-element", {
|
3658
|
-
cancelable: true,
|
3659
|
-
target: currentElement,
|
3660
|
-
detail: {
|
3661
|
-
currentElement: currentElement,
|
3662
|
-
newElement: newElement
|
3663
|
-
}
|
3664
|
-
});
|
3665
|
-
return !event.defaultPrevented;
|
3666
|
-
} else {
|
3667
|
-
return false;
|
3668
|
-
}
|
3669
|
-
}
|
3670
|
-
};
|
3671
|
-
beforeAttributeUpdated=(attributeName, target, mutationType) => {
|
3672
|
-
const event = dispatch("turbo:before-morph-attribute", {
|
3673
|
-
cancelable: true,
|
3674
|
-
target: target,
|
3675
|
-
detail: {
|
3676
|
-
attributeName: attributeName,
|
3677
|
-
mutationType: mutationType
|
3678
|
-
}
|
3679
|
-
});
|
3680
|
-
return !event.defaultPrevented;
|
3681
|
-
};
|
3682
|
-
beforeNodeRemoved=node => this.beforeNodeMorphed(node);
|
3683
|
-
afterNodeMorphed=(currentElement, newElement) => {
|
3684
|
-
if (currentElement instanceof Element) {
|
3685
|
-
dispatch("turbo:morph-element", {
|
3686
|
-
target: currentElement,
|
3687
|
-
detail: {
|
3688
|
-
currentElement: currentElement,
|
3689
|
-
newElement: newElement
|
3690
|
-
}
|
3691
|
-
});
|
3692
|
-
}
|
3693
|
-
};
|
3726
|
+
function fetchResponseIsStream(response) {
|
3727
|
+
const contentType = response.contentType ?? "";
|
3728
|
+
return contentType.startsWith(StreamMessage.contentType);
|
3694
3729
|
}
|
3695
3730
|
|
3696
|
-
class
|
3731
|
+
class ErrorRenderer extends Renderer {
|
3697
3732
|
static renderElement(currentElement, newElement) {
|
3698
|
-
|
3699
|
-
|
3700
|
-
|
3701
|
-
|
3702
|
-
|
3733
|
+
const {documentElement: documentElement, body: body} = document;
|
3734
|
+
documentElement.replaceChild(newElement, body);
|
3735
|
+
}
|
3736
|
+
async render() {
|
3737
|
+
this.replaceHeadAndBody();
|
3738
|
+
this.activateScriptElements();
|
3739
|
+
}
|
3740
|
+
replaceHeadAndBody() {
|
3741
|
+
const {documentElement: documentElement, head: head} = document;
|
3742
|
+
documentElement.replaceChild(this.newHead, head);
|
3743
|
+
this.renderElement(this.currentElement, this.newElement);
|
3744
|
+
}
|
3745
|
+
activateScriptElements() {
|
3746
|
+
for (const replaceableElement of this.scriptElements) {
|
3747
|
+
const parentNode = replaceableElement.parentNode;
|
3748
|
+
if (parentNode) {
|
3749
|
+
const element = activateScriptElement(replaceableElement);
|
3750
|
+
parentNode.replaceChild(element, replaceableElement);
|
3703
3751
|
}
|
3704
|
-
}
|
3705
|
-
|
3752
|
+
}
|
3753
|
+
}
|
3754
|
+
get newHead() {
|
3755
|
+
return this.newSnapshot.headSnapshot.element;
|
3756
|
+
}
|
3757
|
+
get scriptElements() {
|
3758
|
+
return document.documentElement.querySelectorAll("script");
|
3706
3759
|
}
|
3707
3760
|
}
|
3708
3761
|
|
@@ -3882,7 +3935,7 @@ class MorphingPageRenderer extends PageRenderer {
|
|
3882
3935
|
}
|
3883
3936
|
});
|
3884
3937
|
for (const frame of currentElement.querySelectorAll("turbo-frame")) {
|
3885
|
-
if (canRefreshFrame(frame))
|
3938
|
+
if (canRefreshFrame(frame)) frame.reload();
|
3886
3939
|
}
|
3887
3940
|
dispatch("turbo:morph", {
|
3888
3941
|
detail: {
|
@@ -3906,15 +3959,6 @@ function canRefreshFrame(frame) {
|
|
3906
3959
|
return frame instanceof FrameElement && frame.src && frame.refresh === "morph" && !frame.closest("[data-turbo-permanent]");
|
3907
3960
|
}
|
3908
3961
|
|
3909
|
-
function refreshFrame(frame) {
|
3910
|
-
frame.addEventListener("turbo:before-frame-render", (({detail: detail}) => {
|
3911
|
-
detail.render = MorphingFrameRenderer.renderElement;
|
3912
|
-
}), {
|
3913
|
-
once: true
|
3914
|
-
});
|
3915
|
-
frame.reload();
|
3916
|
-
}
|
3917
|
-
|
3918
3962
|
class SnapshotCache {
|
3919
3963
|
keys=[];
|
3920
3964
|
snapshots={};
|
@@ -4097,11 +4141,8 @@ class Session {
|
|
4097
4141
|
frameRedirector=new FrameRedirector(this, document.documentElement);
|
4098
4142
|
streamMessageRenderer=new StreamMessageRenderer;
|
4099
4143
|
cache=new Cache(this);
|
4100
|
-
drive=true;
|
4101
4144
|
enabled=true;
|
4102
|
-
progressBarDelay=500;
|
4103
4145
|
started=false;
|
4104
|
-
formMode="on";
|
4105
4146
|
#pageRefreshDebouncePeriod=150;
|
4106
4147
|
constructor(recentRequests) {
|
4107
4148
|
this.recentRequests = recentRequests;
|
@@ -4180,10 +4221,26 @@ class Session {
|
|
4180
4221
|
this.view.clearSnapshotCache();
|
4181
4222
|
}
|
4182
4223
|
setProgressBarDelay(delay) {
|
4224
|
+
console.warn("Please replace `session.setProgressBarDelay(delay)` with `session.progressBarDelay = delay`. The function is deprecated and will be removed in a future version of Turbo.`");
|
4183
4225
|
this.progressBarDelay = delay;
|
4184
4226
|
}
|
4185
|
-
|
4186
|
-
|
4227
|
+
set progressBarDelay(delay) {
|
4228
|
+
config.drive.progressBarDelay = delay;
|
4229
|
+
}
|
4230
|
+
get progressBarDelay() {
|
4231
|
+
return config.drive.progressBarDelay;
|
4232
|
+
}
|
4233
|
+
set drive(value) {
|
4234
|
+
config.drive.enabled = value;
|
4235
|
+
}
|
4236
|
+
get drive() {
|
4237
|
+
return config.drive.enabled;
|
4238
|
+
}
|
4239
|
+
set formMode(value) {
|
4240
|
+
config.forms.mode = value;
|
4241
|
+
}
|
4242
|
+
get formMode() {
|
4243
|
+
return config.forms.mode;
|
4187
4244
|
}
|
4188
4245
|
get location() {
|
4189
4246
|
return this.history.location;
|
@@ -4405,11 +4462,11 @@ class Session {
|
|
4405
4462
|
});
|
4406
4463
|
}
|
4407
4464
|
submissionIsNavigatable(form, submitter) {
|
4408
|
-
if (
|
4465
|
+
if (config.forms.mode == "off") {
|
4409
4466
|
return false;
|
4410
4467
|
} else {
|
4411
4468
|
const submitterIsNavigatable = submitter ? this.elementIsNavigatable(submitter) : true;
|
4412
|
-
if (
|
4469
|
+
if (config.forms.mode == "optin") {
|
4413
4470
|
return submitterIsNavigatable && form.closest('[data-turbo="true"]') != null;
|
4414
4471
|
} else {
|
4415
4472
|
return submitterIsNavigatable && this.elementIsNavigatable(form);
|
@@ -4419,7 +4476,7 @@ class Session {
|
|
4419
4476
|
elementIsNavigatable(element) {
|
4420
4477
|
const container = findClosestRecursively(element, "[data-turbo]");
|
4421
4478
|
const withinFrame = findClosestRecursively(element, "turbo-frame");
|
4422
|
-
if (
|
4479
|
+
if (config.drive.enabled || withinFrame) {
|
4423
4480
|
if (container) {
|
4424
4481
|
return container.getAttribute("data-turbo") != "false";
|
4425
4482
|
} else {
|
@@ -4487,15 +4544,18 @@ function clearCache() {
|
|
4487
4544
|
}
|
4488
4545
|
|
4489
4546
|
function setProgressBarDelay(delay) {
|
4490
|
-
|
4547
|
+
console.warn("Please replace `Turbo.setProgressBarDelay(delay)` with `Turbo.config.drive.progressBarDelay = delay`. The top-level function is deprecated and will be removed in a future version of Turbo.`");
|
4548
|
+
config.drive.progressBarDelay = delay;
|
4491
4549
|
}
|
4492
4550
|
|
4493
4551
|
function setConfirmMethod(confirmMethod) {
|
4494
|
-
|
4552
|
+
console.warn("Please replace `Turbo.setConfirmMethod(confirmMethod)` with `Turbo.config.forms.confirm = confirmMethod`. The top-level function is deprecated and will be removed in a future version of Turbo.`");
|
4553
|
+
config.forms.confirm = confirmMethod;
|
4495
4554
|
}
|
4496
4555
|
|
4497
4556
|
function setFormMode(mode) {
|
4498
|
-
|
4557
|
+
console.warn("Please replace `Turbo.setFormMode(mode)` with `Turbo.config.forms.mode = mode`. The top-level function is deprecated and will be removed in a future version of Turbo.`");
|
4558
|
+
config.forms.mode = mode;
|
4499
4559
|
}
|
4500
4560
|
|
4501
4561
|
var Turbo = Object.freeze({
|
@@ -4507,6 +4567,7 @@ var Turbo = Object.freeze({
|
|
4507
4567
|
PageSnapshot: PageSnapshot,
|
4508
4568
|
FrameRenderer: FrameRenderer,
|
4509
4569
|
fetch: fetchWithTurboHeaders,
|
4570
|
+
config: config,
|
4510
4571
|
start: start,
|
4511
4572
|
registerAdapter: registerAdapter,
|
4512
4573
|
visit: visit,
|
@@ -4575,6 +4636,13 @@ class FrameController {
|
|
4575
4636
|
}
|
4576
4637
|
}
|
4577
4638
|
sourceURLReloaded() {
|
4639
|
+
if (this.element.shouldReloadWithMorph) {
|
4640
|
+
this.element.addEventListener("turbo:before-frame-render", (({detail: detail}) => {
|
4641
|
+
detail.render = MorphingFrameRenderer.renderElement;
|
4642
|
+
}), {
|
4643
|
+
once: true
|
4644
|
+
});
|
4645
|
+
}
|
4578
4646
|
const {src: src} = this.element;
|
4579
4647
|
this.element.removeAttribute("complete");
|
4580
4648
|
this.element.src = null;
|
@@ -5200,6 +5268,7 @@ var Turbo$1 = Object.freeze({
|
|
5200
5268
|
StreamSourceElement: StreamSourceElement,
|
5201
5269
|
cache: cache,
|
5202
5270
|
clearCache: clearCache,
|
5271
|
+
config: config,
|
5203
5272
|
connectStreamSource: connectStreamSource,
|
5204
5273
|
disconnectStreamSource: disconnectStreamSource,
|
5205
5274
|
fetch: fetchWithTurboHeaders,
|
@@ -5261,6 +5330,7 @@ function walk(obj) {
|
|
5261
5330
|
}
|
5262
5331
|
|
5263
5332
|
class TurboCableStreamSourceElement extends HTMLElement {
|
5333
|
+
static observedAttributes=[ "channel", "signed-stream-name" ];
|
5264
5334
|
async connectedCallback() {
|
5265
5335
|
connectStreamSource(this);
|
5266
5336
|
this.subscription = await subscribeTo(this.channel, {
|
@@ -5272,6 +5342,13 @@ class TurboCableStreamSourceElement extends HTMLElement {
|
|
5272
5342
|
disconnectedCallback() {
|
5273
5343
|
disconnectStreamSource(this);
|
5274
5344
|
if (this.subscription) this.subscription.unsubscribe();
|
5345
|
+
this.subscriptionDisconnected();
|
5346
|
+
}
|
5347
|
+
attributeChangedCallback() {
|
5348
|
+
if (this.subscription) {
|
5349
|
+
this.disconnectedCallback();
|
5350
|
+
this.connectedCallback();
|
5351
|
+
}
|
5275
5352
|
}
|
5276
5353
|
dispatchMessageEvent(data) {
|
5277
5354
|
const event = new MessageEvent("message", {
|