turbo-rails 0.5.5 → 0.5.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/Rakefile +4 -0
- data/app/assets/javascripts/turbo.js +1170 -939
- data/app/channels/turbo/streams/broadcasts.rb +16 -0
- data/app/helpers/turbo/frames_helper.rb +4 -0
- data/app/helpers/turbo/streams/action_helper.rb +2 -7
- data/app/helpers/turbo/streams_helper.rb +5 -3
- data/app/javascript/turbo/cable.js +6 -3
- data/app/jobs/turbo/streams/action_broadcast_job.rb +2 -0
- data/app/jobs/turbo/streams/broadcast_job.rb +2 -0
- data/app/models/concerns/turbo/broadcastable.rb +39 -7
- data/app/models/turbo/streams/tag_builder.rb +29 -2
- data/lib/install/turbo_with_asset_pipeline.rb +7 -6
- data/lib/install/turbo_with_webpacker.rb +5 -4
- data/lib/tasks/turbo_tasks.rake +3 -3
- data/lib/turbo/engine.rb +5 -1
- data/lib/turbo/test_assertions.rb +2 -3
- data/lib/turbo/version.rb +1 -1
- metadata +7 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1abdc38a6d5a735bf6bbd2ff6fb8db49ea74d14e3452afa5c2bda93b23ffb073
|
4
|
+
data.tar.gz: 0bf468488fd8232fec62b975fb9f0ef5449325cf57b932858e03af876d0548e6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 79272181e9d15ab14434ba684ac33a2fa186df4fa15462d2ad96a77caaeb8caaf111beebd9bf70f67b7fee3eae0e35c5af5f6e86a4e2c970f07c7b1c58e983bf
|
7
|
+
data.tar.gz: ed1f850a041f5d66ae7bafa8264ac3ee5679db81eb5f91869e4e8624ef0cfadb92f345e9f1de192ee917a568deb866e1231db248820ecdb0afcafc96f8296dc4
|
data/Rakefile
CHANGED
@@ -2,6 +2,10 @@ require "bundler/setup"
|
|
2
2
|
require "bundler/gem_tasks"
|
3
3
|
require "rake/testtask"
|
4
4
|
|
5
|
+
APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
|
6
|
+
load "rails/tasks/engine.rake"
|
7
|
+
load "rails/tasks/statistics.rake"
|
8
|
+
|
5
9
|
Rake::TestTask.new do |test|
|
6
10
|
test.libs << "test"
|
7
11
|
test.test_files = FileList["test/**/*_test.rb"]
|
@@ -55,7 +55,7 @@ class FrameElement extends HTMLElement {
|
|
55
55
|
this.delegate = new FrameElement.delegateConstructor(this);
|
56
56
|
}
|
57
57
|
static get observedAttributes() {
|
58
|
-
return [ "loading", "src" ];
|
58
|
+
return [ "disabled", "loading", "src" ];
|
59
59
|
}
|
60
60
|
connectedCallback() {
|
61
61
|
this.delegate.connect();
|
@@ -68,6 +68,8 @@ class FrameElement extends HTMLElement {
|
|
68
68
|
this.delegate.loadingStyleChanged();
|
69
69
|
} else if (name == "src") {
|
70
70
|
this.delegate.sourceURLChanged();
|
71
|
+
} else {
|
72
|
+
this.delegate.disabledChanged();
|
71
73
|
}
|
72
74
|
}
|
73
75
|
get src() {
|
@@ -132,82 +134,61 @@ function frameLoadingStyleFromString(style) {
|
|
132
134
|
}
|
133
135
|
}
|
134
136
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
}
|
148
|
-
static get currentLocation() {
|
149
|
-
return this.wrap(window.location.toString());
|
150
|
-
}
|
151
|
-
static wrap(locatable) {
|
152
|
-
if (typeof locatable == "string") {
|
153
|
-
return new this(locatable);
|
154
|
-
} else if (locatable != null) {
|
155
|
-
return locatable;
|
156
|
-
}
|
157
|
-
}
|
158
|
-
getOrigin() {
|
159
|
-
return this.absoluteURL.split("/", 3).join("/");
|
160
|
-
}
|
161
|
-
getPath() {
|
162
|
-
return (this.requestURL.match(/\/\/[^/]*(\/[^?;]*)/) || [])[1] || "/";
|
163
|
-
}
|
164
|
-
getPathComponents() {
|
165
|
-
return this.getPath().split("/").slice(1);
|
166
|
-
}
|
167
|
-
getLastPathComponent() {
|
168
|
-
return this.getPathComponents().slice(-1)[0];
|
169
|
-
}
|
170
|
-
getExtension() {
|
171
|
-
return (this.getLastPathComponent().match(/\.[^.]*$/) || [])[0] || "";
|
172
|
-
}
|
173
|
-
isHTML() {
|
174
|
-
return !!this.getExtension().match(/^(?:|\.(?:htm|html|xhtml))$/);
|
175
|
-
}
|
176
|
-
isPrefixedBy(location) {
|
177
|
-
const prefixURL = getPrefixURL(location);
|
178
|
-
return this.isEqualTo(location) || stringStartsWith(this.absoluteURL, prefixURL);
|
179
|
-
}
|
180
|
-
isEqualTo(location) {
|
181
|
-
return location && this.absoluteURL === location.absoluteURL;
|
182
|
-
}
|
183
|
-
toCacheKey() {
|
184
|
-
return this.requestURL;
|
185
|
-
}
|
186
|
-
toJSON() {
|
187
|
-
return this.absoluteURL;
|
188
|
-
}
|
189
|
-
toString() {
|
190
|
-
return this.absoluteURL;
|
137
|
+
function expandURL(locatable) {
|
138
|
+
return new URL(locatable.toString(), document.baseURI);
|
139
|
+
}
|
140
|
+
|
141
|
+
function getAnchor(url) {
|
142
|
+
let anchorMatch;
|
143
|
+
if (url.hash) {
|
144
|
+
return url.hash.slice(1);
|
145
|
+
} else if (anchorMatch = url.href.match(/#(.*)$/)) {
|
146
|
+
return anchorMatch[1];
|
147
|
+
} else {
|
148
|
+
return "";
|
191
149
|
}
|
192
|
-
|
193
|
-
|
150
|
+
}
|
151
|
+
|
152
|
+
function getExtension(url) {
|
153
|
+
return (getLastPathComponent(url).match(/\.[^.]*$/) || [])[0] || "";
|
154
|
+
}
|
155
|
+
|
156
|
+
function isHTML(url) {
|
157
|
+
return !!getExtension(url).match(/^(?:|\.(?:htm|html|xhtml))$/);
|
158
|
+
}
|
159
|
+
|
160
|
+
function isPrefixedBy(baseURL, url) {
|
161
|
+
const prefix = getPrefix(url);
|
162
|
+
return baseURL.href === expandURL(prefix).href || baseURL.href.startsWith(prefix);
|
163
|
+
}
|
164
|
+
|
165
|
+
function toCacheKey(url) {
|
166
|
+
const anchorLength = url.hash.length;
|
167
|
+
if (anchorLength < 2) {
|
168
|
+
return url.href;
|
169
|
+
} else {
|
170
|
+
return url.href.slice(0, -anchorLength);
|
194
171
|
}
|
195
172
|
}
|
196
173
|
|
197
|
-
function
|
198
|
-
return
|
174
|
+
function urlsAreEqual(left, right) {
|
175
|
+
return expandURL(left).href == expandURL(right).href;
|
176
|
+
}
|
177
|
+
|
178
|
+
function getPathComponents(url) {
|
179
|
+
return url.pathname.split("/").slice(1);
|
199
180
|
}
|
200
181
|
|
201
|
-
function
|
202
|
-
return
|
182
|
+
function getLastPathComponent(url) {
|
183
|
+
return getPathComponents(url).slice(-1)[0];
|
203
184
|
}
|
204
185
|
|
205
|
-
function
|
206
|
-
return
|
186
|
+
function getPrefix(url) {
|
187
|
+
return addTrailingSlash(url.origin + url.pathname);
|
207
188
|
}
|
208
189
|
|
209
|
-
function
|
210
|
-
return
|
190
|
+
function addTrailingSlash(value) {
|
191
|
+
return value.endsWith("/") ? value : value + "/";
|
211
192
|
}
|
212
193
|
|
213
194
|
class FetchResponse {
|
@@ -230,7 +211,7 @@ class FetchResponse {
|
|
230
211
|
return this.response.redirected;
|
231
212
|
}
|
232
213
|
get location() {
|
233
|
-
return
|
214
|
+
return expandURL(this.response.url);
|
234
215
|
}
|
235
216
|
get isHTML() {
|
236
217
|
return this.contentType && this.contentType.match(/^(?:text\/([^\s;,]+\b)?html|application\/xhtml\+xml)\b/);
|
@@ -270,10 +251,18 @@ function nextAnimationFrame() {
|
|
270
251
|
return new Promise((resolve => requestAnimationFrame((() => resolve()))));
|
271
252
|
}
|
272
253
|
|
254
|
+
function nextEventLoopTick() {
|
255
|
+
return new Promise((resolve => setTimeout((() => resolve()), 0)));
|
256
|
+
}
|
257
|
+
|
273
258
|
function nextMicrotask() {
|
274
259
|
return Promise.resolve();
|
275
260
|
}
|
276
261
|
|
262
|
+
function parseHTMLDocument(html = "") {
|
263
|
+
return (new DOMParser).parseFromString(html, "text/html");
|
264
|
+
}
|
265
|
+
|
277
266
|
function unindent(strings, ...values) {
|
278
267
|
const lines = interpolate(strings, values).replace(/^\n/, "").split("\n");
|
279
268
|
const match = lines[0].match(/^\s+/);
|
@@ -334,27 +323,23 @@ function fetchMethodFromString(method) {
|
|
334
323
|
}
|
335
324
|
|
336
325
|
class FetchRequest {
|
337
|
-
constructor(delegate, method, location, body) {
|
326
|
+
constructor(delegate, method, location, body = new URLSearchParams) {
|
338
327
|
this.abortController = new AbortController;
|
339
328
|
this.delegate = delegate;
|
340
329
|
this.method = method;
|
341
|
-
this.
|
342
|
-
this.
|
343
|
-
|
344
|
-
get url() {
|
345
|
-
const url = this.location.absoluteURL;
|
346
|
-
const query = this.params.toString();
|
347
|
-
if (this.isIdempotent && query.length) {
|
348
|
-
return [ url, query ].join(url.includes("?") ? "&" : "?");
|
330
|
+
this.headers = this.defaultHeaders;
|
331
|
+
if (this.isIdempotent) {
|
332
|
+
this.url = mergeFormDataEntries(location, [ ...body.entries() ]);
|
349
333
|
} else {
|
350
|
-
|
334
|
+
this.body = body;
|
335
|
+
this.url = location;
|
351
336
|
}
|
352
337
|
}
|
338
|
+
get location() {
|
339
|
+
return this.url;
|
340
|
+
}
|
353
341
|
get params() {
|
354
|
-
return this.
|
355
|
-
params.append(name, value.toString());
|
356
|
-
return params;
|
357
|
-
}), new URLSearchParams);
|
342
|
+
return this.url.searchParams;
|
358
343
|
}
|
359
344
|
get entries() {
|
360
345
|
return this.body ? Array.from(this.body.entries()) : [];
|
@@ -363,7 +348,9 @@ class FetchRequest {
|
|
363
348
|
this.abortController.abort();
|
364
349
|
}
|
365
350
|
async perform() {
|
351
|
+
var _a, _b;
|
366
352
|
const {fetchOptions: fetchOptions} = this;
|
353
|
+
(_b = (_a = this.delegate).prepareHeadersForRequest) === null || _b === void 0 ? void 0 : _b.call(_a, this.headers, this);
|
367
354
|
dispatch("turbo:before-fetch-request", {
|
368
355
|
detail: {
|
369
356
|
fetchOptions: fetchOptions
|
@@ -371,7 +358,7 @@ class FetchRequest {
|
|
371
358
|
});
|
372
359
|
try {
|
373
360
|
this.delegate.requestStarted(this);
|
374
|
-
const response = await fetch(this.url, fetchOptions);
|
361
|
+
const response = await fetch(this.url.href, fetchOptions);
|
375
362
|
return await this.receive(response);
|
376
363
|
} catch (error) {
|
377
364
|
this.delegate.requestErrored(this, error);
|
@@ -403,28 +390,35 @@ class FetchRequest {
|
|
403
390
|
credentials: "same-origin",
|
404
391
|
headers: this.headers,
|
405
392
|
redirect: "follow",
|
406
|
-
body: this.
|
393
|
+
body: this.body,
|
407
394
|
signal: this.abortSignal
|
408
395
|
};
|
409
396
|
}
|
397
|
+
get defaultHeaders() {
|
398
|
+
return {
|
399
|
+
Accept: "text/html, application/xhtml+xml"
|
400
|
+
};
|
401
|
+
}
|
410
402
|
get isIdempotent() {
|
411
403
|
return this.method == FetchMethod.get;
|
412
404
|
}
|
413
|
-
get
|
414
|
-
return
|
415
|
-
Accept: "text/html, application/xhtml+xml"
|
416
|
-
}, this.additionalHeaders);
|
405
|
+
get abortSignal() {
|
406
|
+
return this.abortController.signal;
|
417
407
|
}
|
418
|
-
|
419
|
-
|
420
|
-
|
408
|
+
}
|
409
|
+
|
410
|
+
function mergeFormDataEntries(url, entries) {
|
411
|
+
const currentSearchParams = new URLSearchParams(url.search);
|
412
|
+
for (const [name, value] of entries) {
|
413
|
+
if (value instanceof File) continue;
|
414
|
+
if (currentSearchParams.has(name)) {
|
415
|
+
currentSearchParams.delete(name);
|
416
|
+
url.searchParams.set(name, value);
|
421
417
|
} else {
|
422
|
-
|
418
|
+
url.searchParams.append(name, value);
|
423
419
|
}
|
424
420
|
}
|
425
|
-
|
426
|
-
return this.abortController.signal;
|
427
|
-
}
|
421
|
+
return url;
|
428
422
|
}
|
429
423
|
|
430
424
|
class AppearanceObserver {
|
@@ -454,6 +448,41 @@ class AppearanceObserver {
|
|
454
448
|
}
|
455
449
|
}
|
456
450
|
|
451
|
+
class StreamMessage {
|
452
|
+
constructor(html) {
|
453
|
+
this.templateElement = document.createElement("template");
|
454
|
+
this.templateElement.innerHTML = html;
|
455
|
+
}
|
456
|
+
static wrap(message) {
|
457
|
+
if (typeof message == "string") {
|
458
|
+
return new this(message);
|
459
|
+
} else {
|
460
|
+
return message;
|
461
|
+
}
|
462
|
+
}
|
463
|
+
get fragment() {
|
464
|
+
const fragment = document.createDocumentFragment();
|
465
|
+
for (const element of this.foreignElements) {
|
466
|
+
fragment.appendChild(document.importNode(element, true));
|
467
|
+
}
|
468
|
+
return fragment;
|
469
|
+
}
|
470
|
+
get foreignElements() {
|
471
|
+
return this.templateChildren.reduce(((streamElements, child) => {
|
472
|
+
if (child.tagName.toLowerCase() == "turbo-stream") {
|
473
|
+
return [ ...streamElements, child ];
|
474
|
+
} else {
|
475
|
+
return streamElements;
|
476
|
+
}
|
477
|
+
}), []);
|
478
|
+
}
|
479
|
+
get templateChildren() {
|
480
|
+
return Array.from(this.templateElement.content.children);
|
481
|
+
}
|
482
|
+
}
|
483
|
+
|
484
|
+
StreamMessage.contentType = "text/vnd.turbo-stream.html";
|
485
|
+
|
457
486
|
var FormSubmissionState;
|
458
487
|
|
459
488
|
(function(FormSubmissionState) {
|
@@ -465,14 +494,35 @@ var FormSubmissionState;
|
|
465
494
|
FormSubmissionState[FormSubmissionState["stopped"] = 5] = "stopped";
|
466
495
|
})(FormSubmissionState || (FormSubmissionState = {}));
|
467
496
|
|
497
|
+
var FormEnctype;
|
498
|
+
|
499
|
+
(function(FormEnctype) {
|
500
|
+
FormEnctype["urlEncoded"] = "application/x-www-form-urlencoded";
|
501
|
+
FormEnctype["multipart"] = "multipart/form-data";
|
502
|
+
FormEnctype["plain"] = "text/plain";
|
503
|
+
})(FormEnctype || (FormEnctype = {}));
|
504
|
+
|
505
|
+
function formEnctypeFromString(encoding) {
|
506
|
+
switch (encoding.toLowerCase()) {
|
507
|
+
case FormEnctype.multipart:
|
508
|
+
return FormEnctype.multipart;
|
509
|
+
|
510
|
+
case FormEnctype.plain:
|
511
|
+
return FormEnctype.plain;
|
512
|
+
|
513
|
+
default:
|
514
|
+
return FormEnctype.urlEncoded;
|
515
|
+
}
|
516
|
+
}
|
517
|
+
|
468
518
|
class FormSubmission {
|
469
519
|
constructor(delegate, formElement, submitter, mustRedirect = false) {
|
470
520
|
this.state = FormSubmissionState.initialized;
|
471
521
|
this.delegate = delegate;
|
472
522
|
this.formElement = formElement;
|
473
|
-
this.formData = buildFormData(formElement, submitter);
|
474
523
|
this.submitter = submitter;
|
475
|
-
this.
|
524
|
+
this.formData = buildFormData(formElement, submitter);
|
525
|
+
this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body);
|
476
526
|
this.mustRedirect = mustRedirect;
|
477
527
|
}
|
478
528
|
get method() {
|
@@ -485,7 +535,24 @@ class FormSubmission {
|
|
485
535
|
return ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formaction")) || this.formElement.action;
|
486
536
|
}
|
487
537
|
get location() {
|
488
|
-
return
|
538
|
+
return expandURL(this.action);
|
539
|
+
}
|
540
|
+
get body() {
|
541
|
+
if (this.enctype == FormEnctype.urlEncoded || this.method == FetchMethod.get) {
|
542
|
+
return new URLSearchParams(this.stringFormData);
|
543
|
+
} else {
|
544
|
+
return this.formData;
|
545
|
+
}
|
546
|
+
}
|
547
|
+
get enctype() {
|
548
|
+
var _a;
|
549
|
+
return formEnctypeFromString(((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formenctype")) || this.formElement.enctype);
|
550
|
+
}
|
551
|
+
get isIdempotent() {
|
552
|
+
return this.fetchRequest.isIdempotent;
|
553
|
+
}
|
554
|
+
get stringFormData() {
|
555
|
+
return [ ...this.formData ].reduce(((entries, [name, value]) => entries.concat(typeof value == "string" ? [ [ name, value ] ] : [])), []);
|
489
556
|
}
|
490
557
|
async start() {
|
491
558
|
const {initialized: initialized, requesting: requesting} = FormSubmissionState;
|
@@ -502,15 +569,14 @@ class FormSubmission {
|
|
502
569
|
return true;
|
503
570
|
}
|
504
571
|
}
|
505
|
-
|
506
|
-
|
507
|
-
if (this.method != FetchMethod.get) {
|
572
|
+
prepareHeadersForRequest(headers, request) {
|
573
|
+
if (!request.isIdempotent) {
|
508
574
|
const token = getCookieValue(getMetaContent("csrf-param")) || getMetaContent("csrf-token");
|
509
575
|
if (token) {
|
510
576
|
headers["X-CSRF-Token"] = token;
|
511
577
|
}
|
578
|
+
headers["Accept"] = [ StreamMessage.contentType, headers["Accept"] ].join(", ");
|
512
579
|
}
|
513
|
-
return headers;
|
514
580
|
}
|
515
581
|
requestStarted(request) {
|
516
582
|
this.state = FormSubmissionState.waiting;
|
@@ -576,8 +642,8 @@ function buildFormData(formElement, submitter) {
|
|
576
642
|
const formData = new FormData(formElement);
|
577
643
|
const name = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("name");
|
578
644
|
const value = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("value");
|
579
|
-
if (name && formData.get(name) != value) {
|
580
|
-
formData.append(name, value
|
645
|
+
if (name && value != null && formData.get(name) != value) {
|
646
|
+
formData.append(name, value);
|
581
647
|
}
|
582
648
|
return formData;
|
583
649
|
}
|
@@ -602,6 +668,48 @@ function responseSucceededWithoutRedirect(response) {
|
|
602
668
|
return response.statusCode == 200 && !response.redirected;
|
603
669
|
}
|
604
670
|
|
671
|
+
class Snapshot {
|
672
|
+
constructor(element) {
|
673
|
+
this.element = element;
|
674
|
+
}
|
675
|
+
get children() {
|
676
|
+
return [ ...this.element.children ];
|
677
|
+
}
|
678
|
+
hasAnchor(anchor) {
|
679
|
+
return this.getElementForAnchor(anchor) != null;
|
680
|
+
}
|
681
|
+
getElementForAnchor(anchor) {
|
682
|
+
try {
|
683
|
+
return this.element.querySelector(`[id='${anchor}'], a[name='${anchor}']`);
|
684
|
+
} catch (_a) {
|
685
|
+
return null;
|
686
|
+
}
|
687
|
+
}
|
688
|
+
get isConnected() {
|
689
|
+
return this.element.isConnected;
|
690
|
+
}
|
691
|
+
get firstAutofocusableElement() {
|
692
|
+
return this.element.querySelector("[autofocus]");
|
693
|
+
}
|
694
|
+
get permanentElements() {
|
695
|
+
return [ ...this.element.querySelectorAll("[id][data-turbo-permanent]") ];
|
696
|
+
}
|
697
|
+
getPermanentElementById(id) {
|
698
|
+
return this.element.querySelector(`#${id}[data-turbo-permanent]`);
|
699
|
+
}
|
700
|
+
getPermanentElementMapForSnapshot(snapshot) {
|
701
|
+
const permanentElementMap = {};
|
702
|
+
for (const currentPermanentElement of this.permanentElements) {
|
703
|
+
const {id: id} = currentPermanentElement;
|
704
|
+
const newPermanentElement = snapshot.getPermanentElementById(id);
|
705
|
+
if (newPermanentElement) {
|
706
|
+
permanentElementMap[id] = [ currentPermanentElement, newPermanentElement ];
|
707
|
+
}
|
708
|
+
}
|
709
|
+
return permanentElementMap;
|
710
|
+
}
|
711
|
+
}
|
712
|
+
|
605
713
|
class FormInterceptor {
|
606
714
|
constructor(delegate, element) {
|
607
715
|
this.submitBubbled = event => {
|
@@ -626,6 +734,82 @@ class FormInterceptor {
|
|
626
734
|
}
|
627
735
|
}
|
628
736
|
|
737
|
+
class View {
|
738
|
+
constructor(delegate, element) {
|
739
|
+
this.delegate = delegate;
|
740
|
+
this.element = element;
|
741
|
+
}
|
742
|
+
scrollToAnchor(anchor) {
|
743
|
+
const element = this.snapshot.getElementForAnchor(anchor);
|
744
|
+
if (element) {
|
745
|
+
this.scrollToElement(element);
|
746
|
+
} else {
|
747
|
+
this.scrollToPosition({
|
748
|
+
x: 0,
|
749
|
+
y: 0
|
750
|
+
});
|
751
|
+
}
|
752
|
+
}
|
753
|
+
scrollToElement(element) {
|
754
|
+
element.scrollIntoView();
|
755
|
+
}
|
756
|
+
scrollToPosition({x: x, y: y}) {
|
757
|
+
this.scrollRoot.scrollTo(x, y);
|
758
|
+
}
|
759
|
+
get scrollRoot() {
|
760
|
+
return window;
|
761
|
+
}
|
762
|
+
async render(renderer) {
|
763
|
+
if (this.renderer) {
|
764
|
+
throw new Error("rendering is already in progress");
|
765
|
+
}
|
766
|
+
const {isPreview: isPreview, shouldRender: shouldRender, newSnapshot: snapshot} = renderer;
|
767
|
+
if (shouldRender) {
|
768
|
+
try {
|
769
|
+
this.renderer = renderer;
|
770
|
+
this.prepareToRenderSnapshot(renderer);
|
771
|
+
this.delegate.viewWillRenderSnapshot(snapshot, isPreview);
|
772
|
+
await this.renderSnapshot(renderer);
|
773
|
+
this.delegate.viewRenderedSnapshot(snapshot, isPreview);
|
774
|
+
this.finishRenderingSnapshot(renderer);
|
775
|
+
} finally {
|
776
|
+
delete this.renderer;
|
777
|
+
}
|
778
|
+
} else {
|
779
|
+
this.invalidate();
|
780
|
+
}
|
781
|
+
}
|
782
|
+
invalidate() {
|
783
|
+
this.delegate.viewInvalidated();
|
784
|
+
}
|
785
|
+
prepareToRenderSnapshot(renderer) {
|
786
|
+
this.markAsPreview(renderer.isPreview);
|
787
|
+
renderer.prepareToRender();
|
788
|
+
}
|
789
|
+
markAsPreview(isPreview) {
|
790
|
+
if (isPreview) {
|
791
|
+
this.element.setAttribute("data-turbo-preview", "");
|
792
|
+
} else {
|
793
|
+
this.element.removeAttribute("data-turbo-preview");
|
794
|
+
}
|
795
|
+
}
|
796
|
+
async renderSnapshot(renderer) {
|
797
|
+
await renderer.render();
|
798
|
+
}
|
799
|
+
finishRenderingSnapshot(renderer) {
|
800
|
+
renderer.finishRendering();
|
801
|
+
}
|
802
|
+
}
|
803
|
+
|
804
|
+
class FrameView extends View {
|
805
|
+
invalidate() {
|
806
|
+
this.element.innerHTML = "";
|
807
|
+
}
|
808
|
+
get snapshot() {
|
809
|
+
return new Snapshot(this.element);
|
810
|
+
}
|
811
|
+
}
|
812
|
+
|
629
813
|
class LinkInterceptor {
|
630
814
|
constructor(delegate, element) {
|
631
815
|
this.clickBubbled = event => {
|
@@ -640,7 +824,7 @@ class LinkInterceptor {
|
|
640
824
|
if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url)) {
|
641
825
|
this.clickEvent.preventDefault();
|
642
826
|
event.preventDefault();
|
643
|
-
this.delegate.linkClickIntercepted(event.target, event.detail.url);
|
827
|
+
this.convertLinkWithMethodClickToFormSubmission(event.target) || this.delegate.linkClickIntercepted(event.target, event.detail.url);
|
644
828
|
}
|
645
829
|
}
|
646
830
|
delete this.clickEvent;
|
@@ -661,183 +845,172 @@ class LinkInterceptor {
|
|
661
845
|
document.removeEventListener("turbo:click", this.linkClicked);
|
662
846
|
document.removeEventListener("turbo:before-visit", this.willVisit);
|
663
847
|
}
|
848
|
+
convertLinkWithMethodClickToFormSubmission(link) {
|
849
|
+
var _a;
|
850
|
+
const linkMethod = link.getAttribute("data-turbo-method") || link.getAttribute("data-method");
|
851
|
+
if (linkMethod) {
|
852
|
+
const form = document.createElement("form");
|
853
|
+
form.method = linkMethod;
|
854
|
+
form.action = link.getAttribute("href") || "undefined";
|
855
|
+
(_a = link.parentNode) === null || _a === void 0 ? void 0 : _a.insertBefore(form, link);
|
856
|
+
return dispatch("submit", {
|
857
|
+
target: form
|
858
|
+
});
|
859
|
+
} else {
|
860
|
+
return false;
|
861
|
+
}
|
862
|
+
}
|
664
863
|
respondsToEventTarget(target) {
|
665
864
|
const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
|
666
865
|
return element && element.closest("turbo-frame, html") == this.element;
|
667
866
|
}
|
668
867
|
}
|
669
868
|
|
670
|
-
class
|
671
|
-
constructor(
|
672
|
-
this.
|
673
|
-
this.element = element;
|
674
|
-
this.appearanceObserver = new AppearanceObserver(this, this.element);
|
675
|
-
this.linkInterceptor = new LinkInterceptor(this, this.element);
|
676
|
-
this.formInterceptor = new FormInterceptor(this, this.element);
|
677
|
-
}
|
678
|
-
connect() {
|
679
|
-
if (this.loadingStyle == FrameLoadingStyle.lazy) {
|
680
|
-
this.appearanceObserver.start();
|
681
|
-
}
|
682
|
-
this.linkInterceptor.start();
|
683
|
-
this.formInterceptor.start();
|
869
|
+
class Bardo {
|
870
|
+
constructor(permanentElementMap) {
|
871
|
+
this.permanentElementMap = permanentElementMap;
|
684
872
|
}
|
685
|
-
|
686
|
-
this
|
687
|
-
|
688
|
-
|
873
|
+
static preservingPermanentElements(permanentElementMap, callback) {
|
874
|
+
const bardo = new this(permanentElementMap);
|
875
|
+
bardo.enter();
|
876
|
+
callback();
|
877
|
+
bardo.leave();
|
689
878
|
}
|
690
|
-
|
691
|
-
|
692
|
-
this.
|
879
|
+
enter() {
|
880
|
+
for (const id in this.permanentElementMap) {
|
881
|
+
const [, newPermanentElement] = this.permanentElementMap[id];
|
882
|
+
this.replaceNewPermanentElementWithPlaceholder(newPermanentElement);
|
693
883
|
}
|
694
884
|
}
|
695
|
-
|
696
|
-
|
697
|
-
this.
|
698
|
-
|
699
|
-
this.
|
700
|
-
this.loadSourceURL();
|
885
|
+
leave() {
|
886
|
+
for (const id in this.permanentElementMap) {
|
887
|
+
const [currentPermanentElement] = this.permanentElementMap[id];
|
888
|
+
this.replaceCurrentPermanentElementWithClone(currentPermanentElement);
|
889
|
+
this.replacePlaceholderWithPermanentElement(currentPermanentElement);
|
701
890
|
}
|
702
891
|
}
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
this.loadingURL = this.sourceURL;
|
707
|
-
this.element.loaded = this.visit(this.sourceURL);
|
708
|
-
this.appearanceObserver.stop();
|
709
|
-
await this.element.loaded;
|
710
|
-
} finally {
|
711
|
-
delete this.loadingURL;
|
712
|
-
}
|
713
|
-
}
|
892
|
+
replaceNewPermanentElementWithPlaceholder(permanentElement) {
|
893
|
+
const placeholder = createPlaceholderForPermanentElement(permanentElement);
|
894
|
+
permanentElement.replaceWith(placeholder);
|
714
895
|
}
|
715
|
-
|
716
|
-
const
|
717
|
-
|
718
|
-
const element = await this.extractForeignFrameElement(fragment);
|
719
|
-
await nextAnimationFrame();
|
720
|
-
this.loadFrameElement(element);
|
721
|
-
this.scrollFrameIntoView(element);
|
722
|
-
await nextAnimationFrame();
|
723
|
-
this.focusFirstAutofocusableElement();
|
724
|
-
}
|
896
|
+
replaceCurrentPermanentElementWithClone(permanentElement) {
|
897
|
+
const clone = permanentElement.cloneNode(true);
|
898
|
+
permanentElement.replaceWith(clone);
|
725
899
|
}
|
726
|
-
|
727
|
-
this.
|
900
|
+
replacePlaceholderWithPermanentElement(permanentElement) {
|
901
|
+
const placeholder = this.getPlaceholderById(permanentElement.id);
|
902
|
+
placeholder === null || placeholder === void 0 ? void 0 : placeholder.replaceWith(permanentElement);
|
728
903
|
}
|
729
|
-
|
730
|
-
return this.
|
904
|
+
getPlaceholderById(id) {
|
905
|
+
return this.placeholders.find((element => element.content == id));
|
731
906
|
}
|
732
|
-
|
733
|
-
|
907
|
+
get placeholders() {
|
908
|
+
return [ ...document.querySelectorAll("meta[name=turbo-permanent-placeholder][content]") ];
|
734
909
|
}
|
735
|
-
|
736
|
-
|
910
|
+
}
|
911
|
+
|
912
|
+
function createPlaceholderForPermanentElement(permanentElement) {
|
913
|
+
const element = document.createElement("meta");
|
914
|
+
element.setAttribute("name", "turbo-permanent-placeholder");
|
915
|
+
element.setAttribute("content", permanentElement.id);
|
916
|
+
return element;
|
917
|
+
}
|
918
|
+
|
919
|
+
class Renderer {
|
920
|
+
constructor(currentSnapshot, newSnapshot, isPreview) {
|
921
|
+
this.currentSnapshot = currentSnapshot;
|
922
|
+
this.newSnapshot = newSnapshot;
|
923
|
+
this.isPreview = isPreview;
|
924
|
+
this.promise = new Promise(((resolve, reject) => this.resolvingFunctions = {
|
925
|
+
resolve: resolve,
|
926
|
+
reject: reject
|
927
|
+
}));
|
737
928
|
}
|
738
|
-
|
739
|
-
|
740
|
-
|
929
|
+
get shouldRender() {
|
930
|
+
return true;
|
931
|
+
}
|
932
|
+
prepareToRender() {
|
933
|
+
return;
|
934
|
+
}
|
935
|
+
finishRendering() {
|
936
|
+
if (this.resolvingFunctions) {
|
937
|
+
this.resolvingFunctions.resolve();
|
938
|
+
delete this.resolvingFunctions;
|
741
939
|
}
|
742
|
-
|
743
|
-
|
744
|
-
|
940
|
+
}
|
941
|
+
createScriptElement(element) {
|
942
|
+
if (element.getAttribute("data-turbo-eval") == "false") {
|
943
|
+
return element;
|
745
944
|
} else {
|
746
|
-
|
945
|
+
const createdScriptElement = document.createElement("script");
|
946
|
+
createdScriptElement.textContent = element.textContent;
|
947
|
+
createdScriptElement.async = false;
|
948
|
+
copyElementAttributes(createdScriptElement, element);
|
949
|
+
return createdScriptElement;
|
747
950
|
}
|
748
951
|
}
|
749
|
-
|
750
|
-
|
751
|
-
"Turbo-Frame": this.id
|
752
|
-
};
|
952
|
+
preservingPermanentElements(callback) {
|
953
|
+
Bardo.preservingPermanentElements(this.permanentElementMap, callback);
|
753
954
|
}
|
754
|
-
|
755
|
-
this.
|
756
|
-
|
757
|
-
|
758
|
-
|
955
|
+
focusFirstAutofocusableElement() {
|
956
|
+
const element = this.connectedSnapshot.firstAutofocusableElement;
|
957
|
+
if (elementIsFocusable(element)) {
|
958
|
+
element.focus();
|
959
|
+
}
|
759
960
|
}
|
760
|
-
|
761
|
-
|
762
|
-
this.resolveVisitPromise();
|
961
|
+
get connectedSnapshot() {
|
962
|
+
return this.newSnapshot.isConnected ? this.newSnapshot : this.currentSnapshot;
|
763
963
|
}
|
764
|
-
|
765
|
-
|
766
|
-
this.resolveVisitPromise();
|
964
|
+
get currentElement() {
|
965
|
+
return this.currentSnapshot.element;
|
767
966
|
}
|
768
|
-
|
769
|
-
|
770
|
-
this.resolveVisitPromise();
|
967
|
+
get newElement() {
|
968
|
+
return this.newSnapshot.element;
|
771
969
|
}
|
772
|
-
|
773
|
-
this.
|
970
|
+
get permanentElementMap() {
|
971
|
+
return this.currentSnapshot.getPermanentElementMapForSnapshot(this.newSnapshot);
|
774
972
|
}
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
973
|
+
}
|
974
|
+
|
975
|
+
function copyElementAttributes(destinationElement, sourceElement) {
|
976
|
+
for (const {name: name, value: value} of [ ...sourceElement.attributes ]) {
|
977
|
+
destinationElement.setAttribute(name, value);
|
779
978
|
}
|
780
|
-
|
781
|
-
|
979
|
+
}
|
980
|
+
|
981
|
+
function elementIsFocusable(element) {
|
982
|
+
return element && typeof element.focus == "function";
|
983
|
+
}
|
984
|
+
|
985
|
+
class FrameRenderer extends Renderer {
|
986
|
+
get shouldRender() {
|
987
|
+
return true;
|
782
988
|
}
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
const request = new FetchRequest(this, FetchMethod.get, location);
|
788
|
-
return new Promise((resolve => {
|
789
|
-
this.resolveVisitPromise = () => {
|
790
|
-
this.resolveVisitPromise = () => {};
|
791
|
-
resolve();
|
792
|
-
};
|
793
|
-
request.perform();
|
989
|
+
async render() {
|
990
|
+
await nextAnimationFrame();
|
991
|
+
this.preservingPermanentElements((() => {
|
992
|
+
this.loadFrameElement();
|
794
993
|
}));
|
994
|
+
this.scrollFrameIntoView();
|
995
|
+
await nextAnimationFrame();
|
996
|
+
this.focusFirstAutofocusableElement();
|
795
997
|
}
|
796
|
-
|
797
|
-
const frame = this.findFrameElement(element);
|
798
|
-
frame.src = url;
|
799
|
-
}
|
800
|
-
findFrameElement(element) {
|
801
|
-
var _a;
|
802
|
-
const id = element.getAttribute("data-turbo-frame");
|
803
|
-
return (_a = getFrameElementById(id)) !== null && _a !== void 0 ? _a : this.element;
|
804
|
-
}
|
805
|
-
async extractForeignFrameElement(container) {
|
806
|
-
let element;
|
807
|
-
const id = CSS.escape(this.id);
|
808
|
-
if (element = activateElement(container.querySelector(`turbo-frame#${id}`))) {
|
809
|
-
return element;
|
810
|
-
}
|
811
|
-
if (element = activateElement(container.querySelector(`turbo-frame[src][recurse~=${id}]`))) {
|
812
|
-
await element.loaded;
|
813
|
-
return await this.extractForeignFrameElement(element);
|
814
|
-
}
|
815
|
-
console.error(`Response has no matching <turbo-frame id="${id}"> element`);
|
816
|
-
return new FrameElement;
|
817
|
-
}
|
818
|
-
loadFrameElement(frameElement) {
|
998
|
+
loadFrameElement() {
|
819
999
|
var _a;
|
820
1000
|
const destinationRange = document.createRange();
|
821
|
-
destinationRange.selectNodeContents(this.
|
1001
|
+
destinationRange.selectNodeContents(this.currentElement);
|
822
1002
|
destinationRange.deleteContents();
|
1003
|
+
const frameElement = this.newElement;
|
823
1004
|
const sourceRange = (_a = frameElement.ownerDocument) === null || _a === void 0 ? void 0 : _a.createRange();
|
824
1005
|
if (sourceRange) {
|
825
1006
|
sourceRange.selectNodeContents(frameElement);
|
826
|
-
this.
|
827
|
-
}
|
828
|
-
}
|
829
|
-
focusFirstAutofocusableElement() {
|
830
|
-
const element = this.firstAutofocusableElement;
|
831
|
-
if (element) {
|
832
|
-
element.focus();
|
833
|
-
return true;
|
1007
|
+
this.currentElement.appendChild(sourceRange.extractContents());
|
834
1008
|
}
|
835
|
-
return false;
|
836
1009
|
}
|
837
|
-
scrollFrameIntoView(
|
838
|
-
if (this.
|
839
|
-
const element = this.
|
840
|
-
const block = readScrollLogicalPosition(this.
|
1010
|
+
scrollFrameIntoView() {
|
1011
|
+
if (this.currentElement.autoscroll || this.newElement.autoscroll) {
|
1012
|
+
const element = this.currentElement.firstElementChild;
|
1013
|
+
const block = readScrollLogicalPosition(this.currentElement.getAttribute("data-autoscroll-block"), "end");
|
841
1014
|
if (element) {
|
842
1015
|
element.scrollIntoView({
|
843
1016
|
block: block
|
@@ -847,50 +1020,6 @@ class FrameController {
|
|
847
1020
|
}
|
848
1021
|
return false;
|
849
1022
|
}
|
850
|
-
shouldInterceptNavigation(element) {
|
851
|
-
const id = element.getAttribute("data-turbo-frame") || this.element.getAttribute("target");
|
852
|
-
if (!this.enabled || id == "_top") {
|
853
|
-
return false;
|
854
|
-
}
|
855
|
-
if (id) {
|
856
|
-
const frameElement = getFrameElementById(id);
|
857
|
-
if (frameElement) {
|
858
|
-
return !frameElement.disabled;
|
859
|
-
}
|
860
|
-
}
|
861
|
-
return true;
|
862
|
-
}
|
863
|
-
get firstAutofocusableElement() {
|
864
|
-
const element = this.element.querySelector("[autofocus]");
|
865
|
-
return element instanceof HTMLElement ? element : null;
|
866
|
-
}
|
867
|
-
get id() {
|
868
|
-
return this.element.id;
|
869
|
-
}
|
870
|
-
get enabled() {
|
871
|
-
return !this.element.disabled;
|
872
|
-
}
|
873
|
-
get sourceURL() {
|
874
|
-
return this.element.src;
|
875
|
-
}
|
876
|
-
get loadingStyle() {
|
877
|
-
return this.element.loading;
|
878
|
-
}
|
879
|
-
get isLoading() {
|
880
|
-
return this.formSubmission !== undefined || this.loadingURL !== undefined;
|
881
|
-
}
|
882
|
-
get isActive() {
|
883
|
-
return this.element.isActive;
|
884
|
-
}
|
885
|
-
}
|
886
|
-
|
887
|
-
function getFrameElementById(id) {
|
888
|
-
if (id != null) {
|
889
|
-
const element = document.getElementById(id);
|
890
|
-
if (element instanceof FrameElement) {
|
891
|
-
return element;
|
892
|
-
}
|
893
|
-
}
|
894
1023
|
}
|
895
1024
|
|
896
1025
|
function readScrollLogicalPosition(value, defaultValue) {
|
@@ -901,144 +1030,6 @@ function readScrollLogicalPosition(value, defaultValue) {
|
|
901
1030
|
}
|
902
1031
|
}
|
903
1032
|
|
904
|
-
function fragmentFromHTML(html) {
|
905
|
-
if (html) {
|
906
|
-
const foreignDocument = document.implementation.createHTMLDocument();
|
907
|
-
return foreignDocument.createRange().createContextualFragment(html);
|
908
|
-
}
|
909
|
-
}
|
910
|
-
|
911
|
-
function activateElement(element) {
|
912
|
-
if (element && element.ownerDocument !== document) {
|
913
|
-
element = document.importNode(element, true);
|
914
|
-
}
|
915
|
-
if (element instanceof FrameElement) {
|
916
|
-
return element;
|
917
|
-
}
|
918
|
-
}
|
919
|
-
|
920
|
-
const StreamActions = {
|
921
|
-
append() {
|
922
|
-
var _a;
|
923
|
-
(_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.append(this.templateContent);
|
924
|
-
},
|
925
|
-
prepend() {
|
926
|
-
var _a;
|
927
|
-
(_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.prepend(this.templateContent);
|
928
|
-
},
|
929
|
-
remove() {
|
930
|
-
var _a;
|
931
|
-
(_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.remove();
|
932
|
-
},
|
933
|
-
replace() {
|
934
|
-
var _a;
|
935
|
-
(_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.replaceWith(this.templateContent);
|
936
|
-
},
|
937
|
-
update() {
|
938
|
-
if (this.targetElement) {
|
939
|
-
this.targetElement.innerHTML = "";
|
940
|
-
this.targetElement.append(this.templateContent);
|
941
|
-
}
|
942
|
-
}
|
943
|
-
};
|
944
|
-
|
945
|
-
class StreamElement extends HTMLElement {
|
946
|
-
async connectedCallback() {
|
947
|
-
try {
|
948
|
-
await this.render();
|
949
|
-
} catch (error) {
|
950
|
-
console.error(error);
|
951
|
-
} finally {
|
952
|
-
this.disconnect();
|
953
|
-
}
|
954
|
-
}
|
955
|
-
async render() {
|
956
|
-
var _a;
|
957
|
-
return (_a = this.renderPromise) !== null && _a !== void 0 ? _a : this.renderPromise = (async () => {
|
958
|
-
if (this.dispatchEvent(this.beforeRenderEvent)) {
|
959
|
-
await nextAnimationFrame();
|
960
|
-
this.performAction();
|
961
|
-
}
|
962
|
-
})();
|
963
|
-
}
|
964
|
-
disconnect() {
|
965
|
-
try {
|
966
|
-
this.remove();
|
967
|
-
} catch (_a) {}
|
968
|
-
}
|
969
|
-
get performAction() {
|
970
|
-
if (this.action) {
|
971
|
-
const actionFunction = StreamActions[this.action];
|
972
|
-
if (actionFunction) {
|
973
|
-
return actionFunction;
|
974
|
-
}
|
975
|
-
this.raise("unknown action");
|
976
|
-
}
|
977
|
-
this.raise("action attribute is missing");
|
978
|
-
}
|
979
|
-
get targetElement() {
|
980
|
-
var _a;
|
981
|
-
if (this.target) {
|
982
|
-
return (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.getElementById(this.target);
|
983
|
-
}
|
984
|
-
this.raise("target attribute is missing");
|
985
|
-
}
|
986
|
-
get templateContent() {
|
987
|
-
return this.templateElement.content;
|
988
|
-
}
|
989
|
-
get templateElement() {
|
990
|
-
if (this.firstElementChild instanceof HTMLTemplateElement) {
|
991
|
-
return this.firstElementChild;
|
992
|
-
}
|
993
|
-
this.raise("first child element must be a <template> element");
|
994
|
-
}
|
995
|
-
get action() {
|
996
|
-
return this.getAttribute("action");
|
997
|
-
}
|
998
|
-
get target() {
|
999
|
-
return this.getAttribute("target");
|
1000
|
-
}
|
1001
|
-
raise(message) {
|
1002
|
-
throw new Error(`${this.description}: ${message}`);
|
1003
|
-
}
|
1004
|
-
get description() {
|
1005
|
-
var _a, _b;
|
1006
|
-
return (_b = ((_a = this.outerHTML.match(/<[^>]+>/)) !== null && _a !== void 0 ? _a : [])[0]) !== null && _b !== void 0 ? _b : "<turbo-stream>";
|
1007
|
-
}
|
1008
|
-
get beforeRenderEvent() {
|
1009
|
-
return new CustomEvent("turbo:before-stream-render", {
|
1010
|
-
bubbles: true,
|
1011
|
-
cancelable: true
|
1012
|
-
});
|
1013
|
-
}
|
1014
|
-
}
|
1015
|
-
|
1016
|
-
FrameElement.delegateConstructor = FrameController;
|
1017
|
-
|
1018
|
-
customElements.define("turbo-frame", FrameElement);
|
1019
|
-
|
1020
|
-
customElements.define("turbo-stream", StreamElement);
|
1021
|
-
|
1022
|
-
(() => {
|
1023
|
-
let element = document.currentScript;
|
1024
|
-
if (!element) return;
|
1025
|
-
if (element.hasAttribute("data-turbo-suppress-warning")) return;
|
1026
|
-
while (element = element.parentElement) {
|
1027
|
-
if (element == document.body) {
|
1028
|
-
return console.warn(unindent`
|
1029
|
-
You are loading Turbo from a <script> element inside the <body> element. This is probably not what you meant to do!
|
1030
|
-
|
1031
|
-
Load your application’s JavaScript bundle inside the <head> element instead. <script> elements in <body> are evaluated with each page change.
|
1032
|
-
|
1033
|
-
For more information, see: https://turbo.hotwire.dev/handbook/building#working-with-script-elements
|
1034
|
-
|
1035
|
-
——
|
1036
|
-
Suppress this warning by adding a "data-turbo-suppress-warning" attribute to: %s
|
1037
|
-
`, element.outerHTML);
|
1038
|
-
}
|
1039
|
-
}
|
1040
|
-
})();
|
1041
|
-
|
1042
1033
|
class ProgressBar {
|
1043
1034
|
constructor() {
|
1044
1035
|
this.hiding = false;
|
@@ -1138,9 +1129,10 @@ class ProgressBar {
|
|
1138
1129
|
|
1139
1130
|
ProgressBar.animationDuration = 300;
|
1140
1131
|
|
1141
|
-
class
|
1142
|
-
constructor(
|
1143
|
-
|
1132
|
+
class HeadSnapshot extends Snapshot {
|
1133
|
+
constructor() {
|
1134
|
+
super(...arguments);
|
1135
|
+
this.detailsByOuterHTML = this.children.reduce(((result, element) => {
|
1144
1136
|
const {outerHTML: outerHTML} = element;
|
1145
1137
|
const details = outerHTML in result ? result[outerHTML] : {
|
1146
1138
|
type: elementType(element),
|
@@ -1154,23 +1146,19 @@ class HeadDetails {
|
|
1154
1146
|
});
|
1155
1147
|
}), {});
|
1156
1148
|
}
|
1157
|
-
|
1158
|
-
const children = headElement ? [ ...headElement.children ] : [];
|
1159
|
-
return new this(children);
|
1160
|
-
}
|
1161
|
-
getTrackedElementSignature() {
|
1149
|
+
get trackedElementSignature() {
|
1162
1150
|
return Object.keys(this.detailsByOuterHTML).filter((outerHTML => this.detailsByOuterHTML[outerHTML].tracked)).join("");
|
1163
1151
|
}
|
1164
|
-
|
1165
|
-
return this.
|
1152
|
+
getScriptElementsNotInSnapshot(snapshot) {
|
1153
|
+
return this.getElementsMatchingTypeNotInSnapshot("script", snapshot);
|
1166
1154
|
}
|
1167
|
-
|
1168
|
-
return this.
|
1155
|
+
getStylesheetElementsNotInSnapshot(snapshot) {
|
1156
|
+
return this.getElementsMatchingTypeNotInSnapshot("stylesheet", snapshot);
|
1169
1157
|
}
|
1170
|
-
|
1171
|
-
return Object.keys(this.detailsByOuterHTML).filter((outerHTML => !(outerHTML in
|
1158
|
+
getElementsMatchingTypeNotInSnapshot(matchedType, snapshot) {
|
1159
|
+
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));
|
1172
1160
|
}
|
1173
|
-
|
1161
|
+
get provisionalElements() {
|
1174
1162
|
return Object.keys(this.detailsByOuterHTML).reduce(((result, outerHTML) => {
|
1175
1163
|
const {type: type, tracked: tracked, elements: elements} = this.detailsByOuterHTML[outerHTML];
|
1176
1164
|
if (type == null && !tracked) {
|
@@ -1221,75 +1209,45 @@ function elementIsMetaElementWithName(element, name) {
|
|
1221
1209
|
return tagName == "meta" && element.getAttribute("name") == name;
|
1222
1210
|
}
|
1223
1211
|
|
1224
|
-
class Snapshot {
|
1225
|
-
constructor(
|
1226
|
-
|
1227
|
-
this.
|
1228
|
-
}
|
1229
|
-
static
|
1230
|
-
|
1231
|
-
return value;
|
1232
|
-
} else if (typeof value == "string") {
|
1233
|
-
return this.fromHTMLString(value);
|
1234
|
-
} else {
|
1235
|
-
return this.fromHTMLElement(value);
|
1236
|
-
}
|
1212
|
+
class PageSnapshot extends Snapshot {
|
1213
|
+
constructor(element, headSnapshot) {
|
1214
|
+
super(element);
|
1215
|
+
this.headSnapshot = headSnapshot;
|
1216
|
+
}
|
1217
|
+
static fromHTMLString(html = "") {
|
1218
|
+
return this.fromDocument(parseHTMLDocument(html));
|
1237
1219
|
}
|
1238
|
-
static
|
1239
|
-
|
1240
|
-
return this.fromHTMLElement(documentElement);
|
1220
|
+
static fromElement(element) {
|
1221
|
+
return this.fromDocument(element.ownerDocument);
|
1241
1222
|
}
|
1242
|
-
static
|
1243
|
-
|
1244
|
-
const bodyElement = htmlElement.querySelector("body") || document.createElement("body");
|
1245
|
-
const headDetails = HeadDetails.fromHeadElement(headElement);
|
1246
|
-
return new this(headDetails, bodyElement);
|
1223
|
+
static fromDocument({head: head, body: body}) {
|
1224
|
+
return new this(body, new HeadSnapshot(head));
|
1247
1225
|
}
|
1248
1226
|
clone() {
|
1249
|
-
|
1250
|
-
return new Snapshot(this.headDetails, bodyElement);
|
1227
|
+
return new PageSnapshot(this.element.cloneNode(true), this.headSnapshot);
|
1251
1228
|
}
|
1252
|
-
|
1253
|
-
|
1254
|
-
return new Location(root);
|
1229
|
+
get headElement() {
|
1230
|
+
return this.headSnapshot.element;
|
1255
1231
|
}
|
1256
|
-
|
1257
|
-
|
1232
|
+
get rootLocation() {
|
1233
|
+
var _a;
|
1234
|
+
const root = (_a = this.getSetting("root")) !== null && _a !== void 0 ? _a : "/";
|
1235
|
+
return expandURL(root);
|
1258
1236
|
}
|
1259
|
-
|
1260
|
-
|
1261
|
-
return this.bodyElement.querySelector(`[id='${anchor}'], a[name='${anchor}']`);
|
1262
|
-
} catch (_a) {
|
1263
|
-
return null;
|
1264
|
-
}
|
1237
|
+
get cacheControlValue() {
|
1238
|
+
return this.getSetting("cache-control");
|
1265
1239
|
}
|
1266
|
-
|
1267
|
-
return
|
1240
|
+
get isPreviewable() {
|
1241
|
+
return this.cacheControlValue != "no-preview";
|
1268
1242
|
}
|
1269
|
-
|
1270
|
-
return this.
|
1243
|
+
get isCacheable() {
|
1244
|
+
return this.cacheControlValue != "no-cache";
|
1271
1245
|
}
|
1272
|
-
|
1273
|
-
return this.
|
1246
|
+
get isVisitable() {
|
1247
|
+
return this.getSetting("visit-control") != "reload";
|
1274
1248
|
}
|
1275
|
-
|
1276
|
-
return this.
|
1277
|
-
}
|
1278
|
-
hasAnchor(anchor) {
|
1279
|
-
return this.getElementForAnchor(anchor) != null;
|
1280
|
-
}
|
1281
|
-
isPreviewable() {
|
1282
|
-
return this.getCacheControlValue() != "no-preview";
|
1283
|
-
}
|
1284
|
-
isCacheable() {
|
1285
|
-
return this.getCacheControlValue() != "no-cache";
|
1286
|
-
}
|
1287
|
-
isVisitable() {
|
1288
|
-
return this.getSetting("visit-control") != "reload";
|
1289
|
-
}
|
1290
|
-
getSetting(name, defaultValue) {
|
1291
|
-
const value = this.headDetails.getMetaValue(`turbo-${name}`);
|
1292
|
-
return value == null ? defaultValue : value;
|
1249
|
+
getSetting(name) {
|
1250
|
+
return this.headSnapshot.getMetaValue(`turbo-${name}`);
|
1293
1251
|
}
|
1294
1252
|
}
|
1295
1253
|
|
@@ -1334,16 +1292,6 @@ class Visit {
|
|
1334
1292
|
this.scrolled = false;
|
1335
1293
|
this.snapshotCached = false;
|
1336
1294
|
this.state = VisitState.initialized;
|
1337
|
-
this.performScroll = () => {
|
1338
|
-
if (!this.scrolled) {
|
1339
|
-
if (this.action == "restore") {
|
1340
|
-
this.scrollToRestoredPosition() || this.scrollToTop();
|
1341
|
-
} else {
|
1342
|
-
this.scrollToAnchor() || this.scrollToTop();
|
1343
|
-
}
|
1344
|
-
this.scrolled = true;
|
1345
|
-
}
|
1346
|
-
};
|
1347
1295
|
this.delegate = delegate;
|
1348
1296
|
this.location = location;
|
1349
1297
|
this.restorationIdentifier = restorationIdentifier || uuid();
|
@@ -1398,8 +1346,9 @@ class Visit {
|
|
1398
1346
|
}
|
1399
1347
|
}
|
1400
1348
|
changeHistory() {
|
1349
|
+
var _a;
|
1401
1350
|
if (!this.historyChanged) {
|
1402
|
-
const actionForHistory = this.location.
|
1351
|
+
const actionForHistory = this.location.href === ((_a = this.referrer) === null || _a === void 0 ? void 0 : _a.href) ? "replace" : this.action;
|
1403
1352
|
const method = this.getHistoryMethodForAction(actionForHistory);
|
1404
1353
|
this.history.update(method, this.location, this.restorationIdentifier);
|
1405
1354
|
this.historyChanged = true;
|
@@ -1442,18 +1391,14 @@ class Visit {
|
|
1442
1391
|
loadResponse() {
|
1443
1392
|
if (this.response) {
|
1444
1393
|
const {statusCode: statusCode, responseHTML: responseHTML} = this.response;
|
1445
|
-
this.render((() => {
|
1394
|
+
this.render((async () => {
|
1446
1395
|
this.cacheSnapshot();
|
1447
1396
|
if (isSuccessful(statusCode) && responseHTML != null) {
|
1448
|
-
this.view.
|
1449
|
-
snapshot: Snapshot.fromHTMLString(responseHTML)
|
1450
|
-
}, this.performScroll);
|
1397
|
+
await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML));
|
1451
1398
|
this.adapter.visitRendered(this);
|
1452
1399
|
this.complete();
|
1453
1400
|
} else {
|
1454
|
-
this.view.
|
1455
|
-
error: responseHTML
|
1456
|
-
}, this.performScroll);
|
1401
|
+
await this.view.renderError(PageSnapshot.fromHTMLString(responseHTML));
|
1457
1402
|
this.adapter.visitRendered(this);
|
1458
1403
|
this.fail();
|
1459
1404
|
}
|
@@ -1462,15 +1407,15 @@ class Visit {
|
|
1462
1407
|
}
|
1463
1408
|
getCachedSnapshot() {
|
1464
1409
|
const snapshot = this.view.getCachedSnapshotForLocation(this.location) || this.getPreloadedSnapshot();
|
1465
|
-
if (snapshot && (!this.location
|
1466
|
-
if (this.action == "restore" || snapshot.isPreviewable
|
1410
|
+
if (snapshot && (!getAnchor(this.location) || snapshot.hasAnchor(getAnchor(this.location)))) {
|
1411
|
+
if (this.action == "restore" || snapshot.isPreviewable) {
|
1467
1412
|
return snapshot;
|
1468
1413
|
}
|
1469
1414
|
}
|
1470
1415
|
}
|
1471
1416
|
getPreloadedSnapshot() {
|
1472
1417
|
if (this.snapshotHTML) {
|
1473
|
-
return
|
1418
|
+
return PageSnapshot.fromHTMLString(this.snapshotHTML);
|
1474
1419
|
}
|
1475
1420
|
}
|
1476
1421
|
hasCachedSnapshot() {
|
@@ -1480,12 +1425,9 @@ class Visit {
|
|
1480
1425
|
const snapshot = this.getCachedSnapshot();
|
1481
1426
|
if (snapshot) {
|
1482
1427
|
const isPreview = this.shouldIssueRequest();
|
1483
|
-
this.render((() => {
|
1428
|
+
this.render((async () => {
|
1484
1429
|
this.cacheSnapshot();
|
1485
|
-
this.view.
|
1486
|
-
snapshot: snapshot,
|
1487
|
-
isPreview: isPreview
|
1488
|
-
}, this.performScroll);
|
1430
|
+
await this.view.renderPage(snapshot, isPreview);
|
1489
1431
|
this.adapter.visitRendered(this);
|
1490
1432
|
if (!isPreview) {
|
1491
1433
|
this.complete();
|
@@ -1539,6 +1481,16 @@ class Visit {
|
|
1539
1481
|
requestFinished() {
|
1540
1482
|
this.finishRequest();
|
1541
1483
|
}
|
1484
|
+
performScroll() {
|
1485
|
+
if (!this.scrolled) {
|
1486
|
+
if (this.action == "restore") {
|
1487
|
+
this.scrollToRestoredPosition() || this.scrollToTop();
|
1488
|
+
} else {
|
1489
|
+
this.scrollToAnchor() || this.scrollToTop();
|
1490
|
+
}
|
1491
|
+
this.scrolled = true;
|
1492
|
+
}
|
1493
|
+
}
|
1542
1494
|
scrollToRestoredPosition() {
|
1543
1495
|
const {scrollPosition: scrollPosition} = this.restorationData;
|
1544
1496
|
if (scrollPosition) {
|
@@ -1547,8 +1499,8 @@ class Visit {
|
|
1547
1499
|
}
|
1548
1500
|
}
|
1549
1501
|
scrollToAnchor() {
|
1550
|
-
if (this.location
|
1551
|
-
this.view.scrollToAnchor(this.location
|
1502
|
+
if (getAnchor(this.location)) {
|
1503
|
+
this.view.scrollToAnchor(getAnchor(this.location));
|
1552
1504
|
return true;
|
1553
1505
|
}
|
1554
1506
|
}
|
@@ -1586,12 +1538,14 @@ class Visit {
|
|
1586
1538
|
this.snapshotCached = true;
|
1587
1539
|
}
|
1588
1540
|
}
|
1589
|
-
render(callback) {
|
1541
|
+
async render(callback) {
|
1590
1542
|
this.cancelRender();
|
1591
|
-
|
1592
|
-
|
1593
|
-
callback.call(this);
|
1543
|
+
await new Promise((resolve => {
|
1544
|
+
this.frame = requestAnimationFrame((() => resolve()));
|
1594
1545
|
}));
|
1546
|
+
callback();
|
1547
|
+
delete this.frame;
|
1548
|
+
this.performScroll();
|
1595
1549
|
}
|
1596
1550
|
cancelRender() {
|
1597
1551
|
if (this.frame) {
|
@@ -1766,11 +1720,10 @@ class History {
|
|
1766
1720
|
if (this.shouldHandlePopState()) {
|
1767
1721
|
const {turbo: turbo} = event.state || {};
|
1768
1722
|
if (turbo) {
|
1769
|
-
|
1770
|
-
this.location = location;
|
1723
|
+
this.location = new URL(window.location.href);
|
1771
1724
|
const {restorationIdentifier: restorationIdentifier} = turbo;
|
1772
1725
|
this.restorationIdentifier = restorationIdentifier;
|
1773
|
-
this.delegate.historyPoppedToLocationWithRestorationIdentifier(location, restorationIdentifier);
|
1726
|
+
this.delegate.historyPoppedToLocationWithRestorationIdentifier(this.location, restorationIdentifier);
|
1774
1727
|
}
|
1775
1728
|
}
|
1776
1729
|
};
|
@@ -1785,7 +1738,7 @@ class History {
|
|
1785
1738
|
addEventListener("popstate", this.onPopState, false);
|
1786
1739
|
addEventListener("load", this.onPageLoad, false);
|
1787
1740
|
this.started = true;
|
1788
|
-
this.replace(
|
1741
|
+
this.replace(new URL(window.location.href));
|
1789
1742
|
}
|
1790
1743
|
}
|
1791
1744
|
stop() {
|
@@ -1807,7 +1760,7 @@ class History {
|
|
1807
1760
|
restorationIdentifier: restorationIdentifier
|
1808
1761
|
}
|
1809
1762
|
};
|
1810
|
-
method.call(history, state, "", location.
|
1763
|
+
method.call(history, state, "", location.href);
|
1811
1764
|
this.location = location;
|
1812
1765
|
this.restorationIdentifier = restorationIdentifier;
|
1813
1766
|
}
|
@@ -1882,10 +1835,14 @@ class LinkClickObserver {
|
|
1882
1835
|
}
|
1883
1836
|
}
|
1884
1837
|
getLocationForLink(link) {
|
1885
|
-
return
|
1838
|
+
return expandURL(link.getAttribute("href") || "");
|
1886
1839
|
}
|
1887
1840
|
}
|
1888
1841
|
|
1842
|
+
function isAction(action) {
|
1843
|
+
return action == "advance" || action == "replace" || action == "restore";
|
1844
|
+
}
|
1845
|
+
|
1889
1846
|
class Navigator {
|
1890
1847
|
constructor(delegate) {
|
1891
1848
|
this.delegate = delegate;
|
@@ -1895,9 +1852,9 @@ class Navigator {
|
|
1895
1852
|
this.delegate.visitProposedToLocation(location, options);
|
1896
1853
|
}
|
1897
1854
|
}
|
1898
|
-
startVisit(
|
1855
|
+
startVisit(locatable, restorationIdentifier, options = {}) {
|
1899
1856
|
this.stop();
|
1900
|
-
this.currentVisit = new Visit(this,
|
1857
|
+
this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, Object.assign({
|
1901
1858
|
referrer: this.location
|
1902
1859
|
}, options));
|
1903
1860
|
this.currentVisit.start();
|
@@ -1905,7 +1862,13 @@ class Navigator {
|
|
1905
1862
|
submitForm(form, submitter) {
|
1906
1863
|
this.stop();
|
1907
1864
|
this.formSubmission = new FormSubmission(this, form, submitter, true);
|
1908
|
-
this.formSubmission.
|
1865
|
+
if (this.formSubmission.isIdempotent) {
|
1866
|
+
this.proposeVisit(this.formSubmission.fetchRequest.url, {
|
1867
|
+
action: this.getActionForFormSubmission(this.formSubmission)
|
1868
|
+
});
|
1869
|
+
} else {
|
1870
|
+
this.formSubmission.start();
|
1871
|
+
}
|
1909
1872
|
}
|
1910
1873
|
stop() {
|
1911
1874
|
if (this.formSubmission) {
|
@@ -1948,14 +1911,14 @@ class Navigator {
|
|
1948
1911
|
async formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
|
1949
1912
|
const responseHTML = await fetchResponse.responseHTML;
|
1950
1913
|
if (responseHTML) {
|
1951
|
-
const snapshot =
|
1952
|
-
this.view.
|
1953
|
-
snapshot: snapshot
|
1954
|
-
}, (() => {}));
|
1914
|
+
const snapshot = PageSnapshot.fromHTMLString(responseHTML);
|
1915
|
+
await this.view.renderPage(snapshot);
|
1955
1916
|
this.view.clearSnapshotCache();
|
1956
1917
|
}
|
1957
1918
|
}
|
1958
|
-
formSubmissionErrored(formSubmission, error) {
|
1919
|
+
formSubmissionErrored(formSubmission, error) {
|
1920
|
+
console.error(error);
|
1921
|
+
}
|
1959
1922
|
formSubmissionFinished(formSubmission) {}
|
1960
1923
|
visitStarted(visit) {
|
1961
1924
|
this.delegate.visitStarted(visit);
|
@@ -1969,6 +1932,11 @@ class Navigator {
|
|
1969
1932
|
get restorationIdentifier() {
|
1970
1933
|
return this.history.restorationIdentifier;
|
1971
1934
|
}
|
1935
|
+
getActionForFormSubmission(formSubmission) {
|
1936
|
+
const {formElement: formElement, submitter: submitter} = formSubmission;
|
1937
|
+
const action = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("data-turbo-action")) || formElement.getAttribute("data-turbo-action");
|
1938
|
+
return isAction(action) ? action : "advance";
|
1939
|
+
}
|
1972
1940
|
}
|
1973
1941
|
|
1974
1942
|
var PageStage;
|
@@ -2061,51 +2029,10 @@ class ScrollObserver {
|
|
2061
2029
|
}
|
2062
2030
|
}
|
2063
2031
|
|
2064
|
-
class StreamMessage {
|
2065
|
-
constructor(html) {
|
2066
|
-
this.templateElement = document.createElement("template");
|
2067
|
-
this.templateElement.innerHTML = html;
|
2068
|
-
}
|
2069
|
-
static wrap(message) {
|
2070
|
-
if (typeof message == "string") {
|
2071
|
-
return new this(message);
|
2072
|
-
} else {
|
2073
|
-
return message;
|
2074
|
-
}
|
2075
|
-
}
|
2076
|
-
get fragment() {
|
2077
|
-
const fragment = document.createDocumentFragment();
|
2078
|
-
for (const element of this.foreignElements) {
|
2079
|
-
fragment.appendChild(document.importNode(element, true));
|
2080
|
-
}
|
2081
|
-
return fragment;
|
2082
|
-
}
|
2083
|
-
get foreignElements() {
|
2084
|
-
return this.templateChildren.reduce(((streamElements, child) => {
|
2085
|
-
if (child.tagName.toLowerCase() == "turbo-stream") {
|
2086
|
-
return [ ...streamElements, child ];
|
2087
|
-
} else {
|
2088
|
-
return streamElements;
|
2089
|
-
}
|
2090
|
-
}), []);
|
2091
|
-
}
|
2092
|
-
get templateChildren() {
|
2093
|
-
return Array.from(this.templateElement.content.children);
|
2094
|
-
}
|
2095
|
-
}
|
2096
|
-
|
2097
2032
|
class StreamObserver {
|
2098
2033
|
constructor(delegate) {
|
2099
2034
|
this.sources = new Set;
|
2100
2035
|
this.started = false;
|
2101
|
-
this.prepareFetchRequest = event => {
|
2102
|
-
var _a;
|
2103
|
-
const fetchOptions = (_a = event.detail) === null || _a === void 0 ? void 0 : _a.fetchOptions;
|
2104
|
-
if (fetchOptions) {
|
2105
|
-
const {headers: headers} = fetchOptions;
|
2106
|
-
headers.Accept = [ "text/vnd.turbo-stream.html", headers.Accept ].join(", ");
|
2107
|
-
}
|
2108
|
-
};
|
2109
2036
|
this.inspectFetchResponse = event => {
|
2110
2037
|
const response = fetchResponseFromEvent(event);
|
2111
2038
|
if (response && fetchResponseIsStream(response)) {
|
@@ -2123,14 +2050,12 @@ class StreamObserver {
|
|
2123
2050
|
start() {
|
2124
2051
|
if (!this.started) {
|
2125
2052
|
this.started = true;
|
2126
|
-
addEventListener("turbo:before-fetch-request", this.prepareFetchRequest, true);
|
2127
2053
|
addEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
|
2128
2054
|
}
|
2129
2055
|
}
|
2130
2056
|
stop() {
|
2131
2057
|
if (this.started) {
|
2132
2058
|
this.started = false;
|
2133
|
-
removeEventListener("turbo:before-fetch-request", this.prepareFetchRequest, true);
|
2134
2059
|
removeEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
|
2135
2060
|
}
|
2136
2061
|
}
|
@@ -2171,70 +2096,21 @@ function fetchResponseFromEvent(event) {
|
|
2171
2096
|
function fetchResponseIsStream(response) {
|
2172
2097
|
var _a;
|
2173
2098
|
const contentType = (_a = response.contentType) !== null && _a !== void 0 ? _a : "";
|
2174
|
-
return
|
2175
|
-
}
|
2176
|
-
|
2177
|
-
function isAction(action) {
|
2178
|
-
return action == "advance" || action == "replace" || action == "restore";
|
2179
|
-
}
|
2180
|
-
|
2181
|
-
class Renderer {
|
2182
|
-
renderView(callback) {
|
2183
|
-
this.delegate.viewWillRender(this.newBody);
|
2184
|
-
callback();
|
2185
|
-
this.delegate.viewRendered(this.newBody);
|
2186
|
-
}
|
2187
|
-
invalidateView() {
|
2188
|
-
this.delegate.viewInvalidated();
|
2189
|
-
}
|
2190
|
-
createScriptElement(element) {
|
2191
|
-
if (element.getAttribute("data-turbo-eval") == "false") {
|
2192
|
-
return element;
|
2193
|
-
} else {
|
2194
|
-
const createdScriptElement = document.createElement("script");
|
2195
|
-
createdScriptElement.textContent = element.textContent;
|
2196
|
-
createdScriptElement.async = false;
|
2197
|
-
copyElementAttributes(createdScriptElement, element);
|
2198
|
-
return createdScriptElement;
|
2199
|
-
}
|
2200
|
-
}
|
2201
|
-
}
|
2202
|
-
|
2203
|
-
function copyElementAttributes(destinationElement, sourceElement) {
|
2204
|
-
for (const {name: name, value: value} of [ ...sourceElement.attributes ]) {
|
2205
|
-
destinationElement.setAttribute(name, value);
|
2206
|
-
}
|
2099
|
+
return contentType.startsWith(StreamMessage.contentType);
|
2207
2100
|
}
|
2208
2101
|
|
2209
2102
|
class ErrorRenderer extends Renderer {
|
2210
|
-
|
2211
|
-
|
2212
|
-
this.
|
2213
|
-
this.htmlElement = (() => {
|
2214
|
-
const htmlElement = document.createElement("html");
|
2215
|
-
htmlElement.innerHTML = html;
|
2216
|
-
return htmlElement;
|
2217
|
-
})();
|
2218
|
-
this.newHead = this.htmlElement.querySelector("head") || document.createElement("head");
|
2219
|
-
this.newBody = this.htmlElement.querySelector("body") || document.createElement("body");
|
2220
|
-
}
|
2221
|
-
static render(delegate, callback, html) {
|
2222
|
-
return new this(delegate, html).render(callback);
|
2223
|
-
}
|
2224
|
-
render(callback) {
|
2225
|
-
this.renderView((() => {
|
2226
|
-
this.replaceHeadAndBody();
|
2227
|
-
this.activateBodyScriptElements();
|
2228
|
-
callback();
|
2229
|
-
}));
|
2103
|
+
async render() {
|
2104
|
+
this.replaceHeadAndBody();
|
2105
|
+
this.activateScriptElements();
|
2230
2106
|
}
|
2231
2107
|
replaceHeadAndBody() {
|
2232
2108
|
const {documentElement: documentElement, head: head, body: body} = document;
|
2233
2109
|
documentElement.replaceChild(this.newHead, head);
|
2234
|
-
documentElement.replaceChild(this.
|
2110
|
+
documentElement.replaceChild(this.newElement, body);
|
2235
2111
|
}
|
2236
|
-
|
2237
|
-
for (const replaceableElement of this.
|
2112
|
+
activateScriptElements() {
|
2113
|
+
for (const replaceableElement of this.scriptElements) {
|
2238
2114
|
const parentNode = replaceableElement.parentNode;
|
2239
2115
|
if (parentNode) {
|
2240
2116
|
const element = this.createScriptElement(replaceableElement);
|
@@ -2242,82 +2118,38 @@ class ErrorRenderer extends Renderer {
|
|
2242
2118
|
}
|
2243
2119
|
}
|
2244
2120
|
}
|
2245
|
-
|
2121
|
+
get newHead() {
|
2122
|
+
return this.newSnapshot.headSnapshot.element;
|
2123
|
+
}
|
2124
|
+
get scriptElements() {
|
2246
2125
|
return [ ...document.documentElement.querySelectorAll("script") ];
|
2247
2126
|
}
|
2248
2127
|
}
|
2249
2128
|
|
2250
|
-
class
|
2251
|
-
|
2252
|
-
this.
|
2253
|
-
this.snapshots = {};
|
2254
|
-
this.size = size;
|
2255
|
-
}
|
2256
|
-
has(location) {
|
2257
|
-
return location.toCacheKey() in this.snapshots;
|
2129
|
+
class PageRenderer extends Renderer {
|
2130
|
+
get shouldRender() {
|
2131
|
+
return this.newSnapshot.isVisitable && this.trackedElementsAreIdentical;
|
2258
2132
|
}
|
2259
|
-
|
2260
|
-
|
2261
|
-
const snapshot = this.read(location);
|
2262
|
-
this.touch(location);
|
2263
|
-
return snapshot;
|
2264
|
-
}
|
2133
|
+
prepareToRender() {
|
2134
|
+
this.mergeHead();
|
2265
2135
|
}
|
2266
|
-
|
2267
|
-
this.
|
2268
|
-
this.touch(location);
|
2269
|
-
return snapshot;
|
2270
|
-
}
|
2271
|
-
clear() {
|
2272
|
-
this.snapshots = {};
|
2273
|
-
}
|
2274
|
-
read(location) {
|
2275
|
-
return this.snapshots[location.toCacheKey()];
|
2276
|
-
}
|
2277
|
-
write(location, snapshot) {
|
2278
|
-
this.snapshots[location.toCacheKey()] = snapshot;
|
2279
|
-
}
|
2280
|
-
touch(location) {
|
2281
|
-
const key = location.toCacheKey();
|
2282
|
-
const index = this.keys.indexOf(key);
|
2283
|
-
if (index > -1) this.keys.splice(index, 1);
|
2284
|
-
this.keys.unshift(key);
|
2285
|
-
this.trim();
|
2136
|
+
async render() {
|
2137
|
+
this.replaceBody();
|
2286
2138
|
}
|
2287
|
-
|
2288
|
-
|
2289
|
-
|
2139
|
+
finishRendering() {
|
2140
|
+
super.finishRendering();
|
2141
|
+
if (!this.isPreview) {
|
2142
|
+
this.focusFirstAutofocusableElement();
|
2290
2143
|
}
|
2291
2144
|
}
|
2292
|
-
|
2293
|
-
|
2294
|
-
class SnapshotRenderer extends Renderer {
|
2295
|
-
constructor(delegate, currentSnapshot, newSnapshot, isPreview) {
|
2296
|
-
super();
|
2297
|
-
this.delegate = delegate;
|
2298
|
-
this.currentSnapshot = currentSnapshot;
|
2299
|
-
this.currentHeadDetails = currentSnapshot.headDetails;
|
2300
|
-
this.newSnapshot = newSnapshot;
|
2301
|
-
this.newHeadDetails = newSnapshot.headDetails;
|
2302
|
-
this.newBody = newSnapshot.bodyElement;
|
2303
|
-
this.isPreview = isPreview;
|
2145
|
+
get currentHeadSnapshot() {
|
2146
|
+
return this.currentSnapshot.headSnapshot;
|
2304
2147
|
}
|
2305
|
-
|
2306
|
-
return
|
2148
|
+
get newHeadSnapshot() {
|
2149
|
+
return this.newSnapshot.headSnapshot;
|
2307
2150
|
}
|
2308
|
-
|
2309
|
-
|
2310
|
-
this.mergeHead();
|
2311
|
-
this.renderView((() => {
|
2312
|
-
this.replaceBody();
|
2313
|
-
if (!this.isPreview) {
|
2314
|
-
this.focusFirstAutofocusableElement();
|
2315
|
-
}
|
2316
|
-
callback();
|
2317
|
-
}));
|
2318
|
-
} else {
|
2319
|
-
this.invalidateView();
|
2320
|
-
}
|
2151
|
+
get newElement() {
|
2152
|
+
return this.newSnapshot.element;
|
2321
2153
|
}
|
2322
2154
|
mergeHead() {
|
2323
2155
|
this.copyNewHeadStylesheetElements();
|
@@ -2326,190 +2158,145 @@ class SnapshotRenderer extends Renderer {
|
|
2326
2158
|
this.copyNewHeadProvisionalElements();
|
2327
2159
|
}
|
2328
2160
|
replaceBody() {
|
2329
|
-
|
2330
|
-
|
2331
|
-
|
2332
|
-
|
2333
|
-
}
|
2334
|
-
shouldRender() {
|
2335
|
-
return this.newSnapshot.isVisitable() && this.trackedElementsAreIdentical();
|
2161
|
+
this.preservingPermanentElements((() => {
|
2162
|
+
this.activateNewBody();
|
2163
|
+
this.assignNewBody();
|
2164
|
+
}));
|
2336
2165
|
}
|
2337
|
-
trackedElementsAreIdentical() {
|
2338
|
-
return this.
|
2166
|
+
get trackedElementsAreIdentical() {
|
2167
|
+
return this.currentHeadSnapshot.trackedElementSignature == this.newHeadSnapshot.trackedElementSignature;
|
2339
2168
|
}
|
2340
2169
|
copyNewHeadStylesheetElements() {
|
2341
|
-
for (const element of this.
|
2170
|
+
for (const element of this.newHeadStylesheetElements) {
|
2342
2171
|
document.head.appendChild(element);
|
2343
2172
|
}
|
2344
2173
|
}
|
2345
2174
|
copyNewHeadScriptElements() {
|
2346
|
-
for (const element of this.
|
2175
|
+
for (const element of this.newHeadScriptElements) {
|
2347
2176
|
document.head.appendChild(this.createScriptElement(element));
|
2348
2177
|
}
|
2349
2178
|
}
|
2350
2179
|
removeCurrentHeadProvisionalElements() {
|
2351
|
-
for (const element of this.
|
2180
|
+
for (const element of this.currentHeadProvisionalElements) {
|
2352
2181
|
document.head.removeChild(element);
|
2353
2182
|
}
|
2354
2183
|
}
|
2355
2184
|
copyNewHeadProvisionalElements() {
|
2356
|
-
for (const element of this.
|
2185
|
+
for (const element of this.newHeadProvisionalElements) {
|
2357
2186
|
document.head.appendChild(element);
|
2358
2187
|
}
|
2359
2188
|
}
|
2360
|
-
relocateCurrentBodyPermanentElements() {
|
2361
|
-
return this.getCurrentBodyPermanentElements().reduce(((placeholders, permanentElement) => {
|
2362
|
-
const newElement = this.newSnapshot.getPermanentElementById(permanentElement.id);
|
2363
|
-
if (newElement) {
|
2364
|
-
const placeholder = createPlaceholderForPermanentElement(permanentElement);
|
2365
|
-
replaceElementWithElement(permanentElement, placeholder.element);
|
2366
|
-
replaceElementWithElement(newElement, permanentElement);
|
2367
|
-
return [ ...placeholders, placeholder ];
|
2368
|
-
} else {
|
2369
|
-
return placeholders;
|
2370
|
-
}
|
2371
|
-
}), []);
|
2372
|
-
}
|
2373
|
-
replacePlaceholderElementsWithClonedPermanentElements(placeholders) {
|
2374
|
-
for (const {element: element, permanentElement: permanentElement} of placeholders) {
|
2375
|
-
const clonedElement = permanentElement.cloneNode(true);
|
2376
|
-
replaceElementWithElement(element, clonedElement);
|
2377
|
-
}
|
2378
|
-
}
|
2379
2189
|
activateNewBody() {
|
2380
|
-
document.adoptNode(this.
|
2190
|
+
document.adoptNode(this.newElement);
|
2381
2191
|
this.activateNewBodyScriptElements();
|
2382
2192
|
}
|
2383
2193
|
activateNewBodyScriptElements() {
|
2384
|
-
for (const inertScriptElement of this.
|
2194
|
+
for (const inertScriptElement of this.newBodyScriptElements) {
|
2385
2195
|
const activatedScriptElement = this.createScriptElement(inertScriptElement);
|
2386
|
-
|
2196
|
+
inertScriptElement.replaceWith(activatedScriptElement);
|
2387
2197
|
}
|
2388
2198
|
}
|
2389
2199
|
assignNewBody() {
|
2390
|
-
if (document.body) {
|
2391
|
-
|
2200
|
+
if (document.body && this.newElement instanceof HTMLBodyElement) {
|
2201
|
+
document.body.replaceWith(this.newElement);
|
2392
2202
|
} else {
|
2393
|
-
document.documentElement.appendChild(this.
|
2394
|
-
}
|
2395
|
-
}
|
2396
|
-
focusFirstAutofocusableElement() {
|
2397
|
-
const element = this.newSnapshot.findFirstAutofocusableElement();
|
2398
|
-
if (elementIsFocusable(element)) {
|
2399
|
-
element.focus();
|
2203
|
+
document.documentElement.appendChild(this.newElement);
|
2400
2204
|
}
|
2401
2205
|
}
|
2402
|
-
|
2403
|
-
return this.
|
2206
|
+
get newHeadStylesheetElements() {
|
2207
|
+
return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot);
|
2404
2208
|
}
|
2405
|
-
|
2406
|
-
return this.
|
2209
|
+
get newHeadScriptElements() {
|
2210
|
+
return this.newHeadSnapshot.getScriptElementsNotInSnapshot(this.currentHeadSnapshot);
|
2407
2211
|
}
|
2408
|
-
|
2409
|
-
return this.
|
2212
|
+
get currentHeadProvisionalElements() {
|
2213
|
+
return this.currentHeadSnapshot.provisionalElements;
|
2410
2214
|
}
|
2411
|
-
|
2412
|
-
return this.
|
2215
|
+
get newHeadProvisionalElements() {
|
2216
|
+
return this.newHeadSnapshot.provisionalElements;
|
2413
2217
|
}
|
2414
|
-
|
2415
|
-
return this.
|
2416
|
-
}
|
2417
|
-
getNewBodyScriptElements() {
|
2418
|
-
return [ ...this.newBody.querySelectorAll("script") ];
|
2218
|
+
get newBodyScriptElements() {
|
2219
|
+
return [ ...this.newElement.querySelectorAll("script") ];
|
2419
2220
|
}
|
2420
2221
|
}
|
2421
2222
|
|
2422
|
-
|
2423
|
-
|
2424
|
-
|
2425
|
-
|
2426
|
-
|
2427
|
-
|
2428
|
-
|
2429
|
-
|
2430
|
-
}
|
2431
|
-
|
2432
|
-
|
2433
|
-
|
2434
|
-
|
2435
|
-
|
2223
|
+
class SnapshotCache {
|
2224
|
+
constructor(size) {
|
2225
|
+
this.keys = [];
|
2226
|
+
this.snapshots = {};
|
2227
|
+
this.size = size;
|
2228
|
+
}
|
2229
|
+
has(location) {
|
2230
|
+
return toCacheKey(location) in this.snapshots;
|
2231
|
+
}
|
2232
|
+
get(location) {
|
2233
|
+
if (this.has(location)) {
|
2234
|
+
const snapshot = this.read(location);
|
2235
|
+
this.touch(location);
|
2236
|
+
return snapshot;
|
2237
|
+
}
|
2238
|
+
}
|
2239
|
+
put(location, snapshot) {
|
2240
|
+
this.write(location, snapshot);
|
2241
|
+
this.touch(location);
|
2242
|
+
return snapshot;
|
2243
|
+
}
|
2244
|
+
clear() {
|
2245
|
+
this.snapshots = {};
|
2246
|
+
}
|
2247
|
+
read(location) {
|
2248
|
+
return this.snapshots[toCacheKey(location)];
|
2249
|
+
}
|
2250
|
+
write(location, snapshot) {
|
2251
|
+
this.snapshots[toCacheKey(location)] = snapshot;
|
2252
|
+
}
|
2253
|
+
touch(location) {
|
2254
|
+
const key = toCacheKey(location);
|
2255
|
+
const index = this.keys.indexOf(key);
|
2256
|
+
if (index > -1) this.keys.splice(index, 1);
|
2257
|
+
this.keys.unshift(key);
|
2258
|
+
this.trim();
|
2259
|
+
}
|
2260
|
+
trim() {
|
2261
|
+
for (const key of this.keys.splice(this.size)) {
|
2262
|
+
delete this.snapshots[key];
|
2263
|
+
}
|
2436
2264
|
}
|
2437
2265
|
}
|
2438
2266
|
|
2439
|
-
|
2440
|
-
|
2441
|
-
|
2442
|
-
|
2443
|
-
class View {
|
2444
|
-
constructor(delegate) {
|
2445
|
-
this.htmlElement = document.documentElement;
|
2267
|
+
class PageView extends View {
|
2268
|
+
constructor() {
|
2269
|
+
super(...arguments);
|
2446
2270
|
this.snapshotCache = new SnapshotCache(10);
|
2447
|
-
this.
|
2448
|
-
}
|
2449
|
-
getRootLocation() {
|
2450
|
-
return this.getSnapshot().getRootLocation();
|
2271
|
+
this.lastRenderedLocation = new URL(location.href);
|
2451
2272
|
}
|
2452
|
-
|
2453
|
-
|
2273
|
+
renderPage(snapshot, isPreview = false) {
|
2274
|
+
const renderer = new PageRenderer(this.snapshot, snapshot, isPreview);
|
2275
|
+
return this.render(renderer);
|
2454
2276
|
}
|
2455
|
-
|
2456
|
-
|
2277
|
+
renderError(snapshot) {
|
2278
|
+
const renderer = new ErrorRenderer(this.snapshot, snapshot, false);
|
2279
|
+
this.render(renderer);
|
2457
2280
|
}
|
2458
2281
|
clearSnapshotCache() {
|
2459
2282
|
this.snapshotCache.clear();
|
2460
2283
|
}
|
2461
|
-
shouldCacheSnapshot() {
|
2462
|
-
return this.getSnapshot().isCacheable();
|
2463
|
-
}
|
2464
2284
|
async cacheSnapshot() {
|
2465
|
-
if (this.shouldCacheSnapshot
|
2285
|
+
if (this.shouldCacheSnapshot) {
|
2466
2286
|
this.delegate.viewWillCacheSnapshot();
|
2467
|
-
const snapshot = this
|
2468
|
-
|
2469
|
-
await nextMicrotask();
|
2287
|
+
const {snapshot: snapshot, lastRenderedLocation: location} = this;
|
2288
|
+
await nextEventLoopTick();
|
2470
2289
|
this.snapshotCache.put(location, snapshot.clone());
|
2471
2290
|
}
|
2472
2291
|
}
|
2473
2292
|
getCachedSnapshotForLocation(location) {
|
2474
2293
|
return this.snapshotCache.get(location);
|
2475
2294
|
}
|
2476
|
-
|
2477
|
-
this.
|
2478
|
-
if (snapshot) {
|
2479
|
-
this.renderSnapshot(snapshot, isPreview, callback);
|
2480
|
-
} else {
|
2481
|
-
this.renderError(error, callback);
|
2482
|
-
}
|
2483
|
-
}
|
2484
|
-
scrollToAnchor(anchor) {
|
2485
|
-
const element = this.getElementForAnchor(anchor);
|
2486
|
-
if (element) {
|
2487
|
-
this.scrollToElement(element);
|
2488
|
-
} else {
|
2489
|
-
this.scrollToPosition({
|
2490
|
-
x: 0,
|
2491
|
-
y: 0
|
2492
|
-
});
|
2493
|
-
}
|
2494
|
-
}
|
2495
|
-
scrollToElement(element) {
|
2496
|
-
element.scrollIntoView();
|
2295
|
+
get snapshot() {
|
2296
|
+
return PageSnapshot.fromElement(this.element);
|
2497
2297
|
}
|
2498
|
-
|
2499
|
-
|
2500
|
-
}
|
2501
|
-
markAsPreview(isPreview) {
|
2502
|
-
if (isPreview) {
|
2503
|
-
this.htmlElement.setAttribute("data-turbo-preview", "");
|
2504
|
-
} else {
|
2505
|
-
this.htmlElement.removeAttribute("data-turbo-preview");
|
2506
|
-
}
|
2507
|
-
}
|
2508
|
-
renderSnapshot(snapshot, isPreview, callback) {
|
2509
|
-
SnapshotRenderer.render(this.delegate, callback, this.getSnapshot(), snapshot, isPreview || false);
|
2510
|
-
}
|
2511
|
-
renderError(error, callback) {
|
2512
|
-
ErrorRenderer.render(this.delegate, callback, error || "");
|
2298
|
+
get shouldCacheSnapshot() {
|
2299
|
+
return this.snapshot.isCacheable;
|
2513
2300
|
}
|
2514
2301
|
}
|
2515
2302
|
|
@@ -2517,7 +2304,7 @@ class Session {
|
|
2517
2304
|
constructor() {
|
2518
2305
|
this.navigator = new Navigator(this);
|
2519
2306
|
this.history = new History(this);
|
2520
|
-
this.view = new
|
2307
|
+
this.view = new PageView(this, document.documentElement);
|
2521
2308
|
this.adapter = new BrowserAdapter(this);
|
2522
2309
|
this.pageObserver = new PageObserver(this);
|
2523
2310
|
this.linkClickObserver = new LinkClickObserver(this);
|
@@ -2561,7 +2348,7 @@ class Session {
|
|
2561
2348
|
this.adapter = adapter;
|
2562
2349
|
}
|
2563
2350
|
visit(location, options = {}) {
|
2564
|
-
this.navigator.proposeVisit(
|
2351
|
+
this.navigator.proposeVisit(expandURL(location), options);
|
2565
2352
|
}
|
2566
2353
|
connectStreamSource(source) {
|
2567
2354
|
this.streamObserver.connectStreamSource(source);
|
@@ -2599,128 +2386,553 @@ class Session {
|
|
2599
2386
|
scrollPosition: position
|
2600
2387
|
});
|
2601
2388
|
}
|
2602
|
-
willFollowLinkToLocation(link, location) {
|
2603
|
-
return
|
2389
|
+
willFollowLinkToLocation(link, location) {
|
2390
|
+
return elementIsNavigable(link) && this.locationIsVisitable(location) && this.applicationAllowsFollowingLinkToLocation(link, location);
|
2391
|
+
}
|
2392
|
+
followedLinkToLocation(link, location) {
|
2393
|
+
const action = this.getActionForLink(link);
|
2394
|
+
this.visit(location.href, {
|
2395
|
+
action: action
|
2396
|
+
});
|
2397
|
+
}
|
2398
|
+
allowsVisitingLocation(location) {
|
2399
|
+
return this.applicationAllowsVisitingLocation(location);
|
2400
|
+
}
|
2401
|
+
visitProposedToLocation(location, options) {
|
2402
|
+
extendURLWithDeprecatedProperties(location);
|
2403
|
+
this.adapter.visitProposedToLocation(location, options);
|
2404
|
+
}
|
2405
|
+
visitStarted(visit) {
|
2406
|
+
extendURLWithDeprecatedProperties(visit.location);
|
2407
|
+
this.notifyApplicationAfterVisitingLocation(visit.location);
|
2408
|
+
}
|
2409
|
+
visitCompleted(visit) {
|
2410
|
+
this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());
|
2411
|
+
}
|
2412
|
+
willSubmitForm(form, submitter) {
|
2413
|
+
return elementIsNavigable(form) && elementIsNavigable(submitter);
|
2414
|
+
}
|
2415
|
+
formSubmitted(form, submitter) {
|
2416
|
+
this.navigator.submitForm(form, submitter);
|
2417
|
+
}
|
2418
|
+
pageBecameInteractive() {
|
2419
|
+
this.view.lastRenderedLocation = this.location;
|
2420
|
+
this.notifyApplicationAfterPageLoad();
|
2421
|
+
}
|
2422
|
+
pageLoaded() {
|
2423
|
+
this.history.assumeControlOfScrollRestoration();
|
2424
|
+
}
|
2425
|
+
pageWillUnload() {
|
2426
|
+
this.history.relinquishControlOfScrollRestoration();
|
2427
|
+
}
|
2428
|
+
receivedMessageFromStream(message) {
|
2429
|
+
this.renderStreamMessage(message);
|
2430
|
+
}
|
2431
|
+
viewWillCacheSnapshot() {
|
2432
|
+
this.notifyApplicationBeforeCachingSnapshot();
|
2433
|
+
}
|
2434
|
+
viewWillRenderSnapshot({element: element}, isPreview) {
|
2435
|
+
this.notifyApplicationBeforeRender(element);
|
2436
|
+
}
|
2437
|
+
viewRenderedSnapshot(snapshot, isPreview) {
|
2438
|
+
this.view.lastRenderedLocation = this.history.location;
|
2439
|
+
this.notifyApplicationAfterRender();
|
2440
|
+
}
|
2441
|
+
viewInvalidated() {
|
2442
|
+
this.adapter.pageInvalidated();
|
2443
|
+
}
|
2444
|
+
applicationAllowsFollowingLinkToLocation(link, location) {
|
2445
|
+
const event = this.notifyApplicationAfterClickingLinkToLocation(link, location);
|
2446
|
+
return !event.defaultPrevented;
|
2447
|
+
}
|
2448
|
+
applicationAllowsVisitingLocation(location) {
|
2449
|
+
const event = this.notifyApplicationBeforeVisitingLocation(location);
|
2450
|
+
return !event.defaultPrevented;
|
2451
|
+
}
|
2452
|
+
notifyApplicationAfterClickingLinkToLocation(link, location) {
|
2453
|
+
return dispatch("turbo:click", {
|
2454
|
+
target: link,
|
2455
|
+
detail: {
|
2456
|
+
url: location.href
|
2457
|
+
},
|
2458
|
+
cancelable: true
|
2459
|
+
});
|
2460
|
+
}
|
2461
|
+
notifyApplicationBeforeVisitingLocation(location) {
|
2462
|
+
return dispatch("turbo:before-visit", {
|
2463
|
+
detail: {
|
2464
|
+
url: location.href
|
2465
|
+
},
|
2466
|
+
cancelable: true
|
2467
|
+
});
|
2468
|
+
}
|
2469
|
+
notifyApplicationAfterVisitingLocation(location) {
|
2470
|
+
return dispatch("turbo:visit", {
|
2471
|
+
detail: {
|
2472
|
+
url: location.href
|
2473
|
+
}
|
2474
|
+
});
|
2475
|
+
}
|
2476
|
+
notifyApplicationBeforeCachingSnapshot() {
|
2477
|
+
return dispatch("turbo:before-cache");
|
2478
|
+
}
|
2479
|
+
notifyApplicationBeforeRender(newBody) {
|
2480
|
+
return dispatch("turbo:before-render", {
|
2481
|
+
detail: {
|
2482
|
+
newBody: newBody
|
2483
|
+
}
|
2484
|
+
});
|
2485
|
+
}
|
2486
|
+
notifyApplicationAfterRender() {
|
2487
|
+
return dispatch("turbo:render");
|
2488
|
+
}
|
2489
|
+
notifyApplicationAfterPageLoad(timing = {}) {
|
2490
|
+
return dispatch("turbo:load", {
|
2491
|
+
detail: {
|
2492
|
+
url: this.location.href,
|
2493
|
+
timing: timing
|
2494
|
+
}
|
2495
|
+
});
|
2496
|
+
}
|
2497
|
+
getActionForLink(link) {
|
2498
|
+
const action = link.getAttribute("data-turbo-action");
|
2499
|
+
return isAction(action) ? action : "advance";
|
2500
|
+
}
|
2501
|
+
locationIsVisitable(location) {
|
2502
|
+
return isPrefixedBy(location, this.snapshot.rootLocation) && isHTML(location);
|
2503
|
+
}
|
2504
|
+
get snapshot() {
|
2505
|
+
return this.view.snapshot;
|
2506
|
+
}
|
2507
|
+
}
|
2508
|
+
|
2509
|
+
function elementIsNavigable(element) {
|
2510
|
+
const container = element === null || element === void 0 ? void 0 : element.closest("[data-turbo]");
|
2511
|
+
if (container) {
|
2512
|
+
return container.getAttribute("data-turbo") != "false";
|
2513
|
+
} else {
|
2514
|
+
return true;
|
2515
|
+
}
|
2516
|
+
}
|
2517
|
+
|
2518
|
+
function extendURLWithDeprecatedProperties(url) {
|
2519
|
+
Object.defineProperties(url, deprecatedLocationPropertyDescriptors);
|
2520
|
+
}
|
2521
|
+
|
2522
|
+
const deprecatedLocationPropertyDescriptors = {
|
2523
|
+
absoluteURL: {
|
2524
|
+
get() {
|
2525
|
+
return this.toString();
|
2526
|
+
}
|
2527
|
+
}
|
2528
|
+
};
|
2529
|
+
|
2530
|
+
class FrameController {
|
2531
|
+
constructor(element) {
|
2532
|
+
this.resolveVisitPromise = () => {};
|
2533
|
+
this.connected = false;
|
2534
|
+
this.hasBeenLoaded = false;
|
2535
|
+
this.settingSourceURL = false;
|
2536
|
+
this.element = element;
|
2537
|
+
this.view = new FrameView(this, this.element);
|
2538
|
+
this.appearanceObserver = new AppearanceObserver(this, this.element);
|
2539
|
+
this.linkInterceptor = new LinkInterceptor(this, this.element);
|
2540
|
+
this.formInterceptor = new FormInterceptor(this, this.element);
|
2541
|
+
}
|
2542
|
+
connect() {
|
2543
|
+
if (!this.connected) {
|
2544
|
+
this.connected = true;
|
2545
|
+
if (this.loadingStyle == FrameLoadingStyle.lazy) {
|
2546
|
+
this.appearanceObserver.start();
|
2547
|
+
}
|
2548
|
+
this.linkInterceptor.start();
|
2549
|
+
this.formInterceptor.start();
|
2550
|
+
this.sourceURLChanged();
|
2551
|
+
}
|
2552
|
+
}
|
2553
|
+
disconnect() {
|
2554
|
+
if (this.connected) {
|
2555
|
+
this.connected = false;
|
2556
|
+
this.appearanceObserver.stop();
|
2557
|
+
this.linkInterceptor.stop();
|
2558
|
+
this.formInterceptor.stop();
|
2559
|
+
}
|
2560
|
+
}
|
2561
|
+
disabledChanged() {
|
2562
|
+
if (this.loadingStyle == FrameLoadingStyle.eager) {
|
2563
|
+
this.loadSourceURL();
|
2564
|
+
}
|
2565
|
+
}
|
2566
|
+
sourceURLChanged() {
|
2567
|
+
if (this.loadingStyle == FrameLoadingStyle.eager || this.hasBeenLoaded) {
|
2568
|
+
this.loadSourceURL();
|
2569
|
+
}
|
2570
|
+
}
|
2571
|
+
loadingStyleChanged() {
|
2572
|
+
if (this.loadingStyle == FrameLoadingStyle.lazy) {
|
2573
|
+
this.appearanceObserver.start();
|
2574
|
+
} else {
|
2575
|
+
this.appearanceObserver.stop();
|
2576
|
+
this.loadSourceURL();
|
2577
|
+
}
|
2578
|
+
}
|
2579
|
+
async loadSourceURL() {
|
2580
|
+
if (!this.settingSourceURL && this.enabled && this.isActive && this.sourceURL != this.currentURL) {
|
2581
|
+
const previousURL = this.currentURL;
|
2582
|
+
this.currentURL = this.sourceURL;
|
2583
|
+
if (this.sourceURL) {
|
2584
|
+
try {
|
2585
|
+
this.element.loaded = this.visit(this.sourceURL);
|
2586
|
+
this.appearanceObserver.stop();
|
2587
|
+
await this.element.loaded;
|
2588
|
+
this.hasBeenLoaded = true;
|
2589
|
+
} catch (error) {
|
2590
|
+
this.currentURL = previousURL;
|
2591
|
+
throw error;
|
2592
|
+
}
|
2593
|
+
}
|
2594
|
+
}
|
2595
|
+
}
|
2596
|
+
async loadResponse(fetchResponse) {
|
2597
|
+
if (fetchResponse.redirected) {
|
2598
|
+
this.sourceURL = fetchResponse.response.url;
|
2599
|
+
}
|
2600
|
+
try {
|
2601
|
+
const html = await fetchResponse.responseHTML;
|
2602
|
+
if (html) {
|
2603
|
+
const {body: body} = parseHTMLDocument(html);
|
2604
|
+
const snapshot = new Snapshot(await this.extractForeignFrameElement(body));
|
2605
|
+
const renderer = new FrameRenderer(this.view.snapshot, snapshot, false);
|
2606
|
+
await this.view.render(renderer);
|
2607
|
+
}
|
2608
|
+
} catch (error) {
|
2609
|
+
console.error(error);
|
2610
|
+
this.view.invalidate();
|
2611
|
+
}
|
2612
|
+
}
|
2613
|
+
elementAppearedInViewport(element) {
|
2614
|
+
this.loadSourceURL();
|
2615
|
+
}
|
2616
|
+
shouldInterceptLinkClick(element, url) {
|
2617
|
+
return this.shouldInterceptNavigation(element);
|
2618
|
+
}
|
2619
|
+
linkClickIntercepted(element, url) {
|
2620
|
+
this.navigateFrame(element, url);
|
2621
|
+
}
|
2622
|
+
shouldInterceptFormSubmission(element, submitter) {
|
2623
|
+
return this.shouldInterceptNavigation(element, submitter);
|
2624
|
+
}
|
2625
|
+
formSubmissionIntercepted(element, submitter) {
|
2626
|
+
if (this.formSubmission) {
|
2627
|
+
this.formSubmission.stop();
|
2628
|
+
}
|
2629
|
+
this.formSubmission = new FormSubmission(this, element, submitter);
|
2630
|
+
if (this.formSubmission.fetchRequest.isIdempotent) {
|
2631
|
+
this.navigateFrame(element, this.formSubmission.fetchRequest.url.href);
|
2632
|
+
} else {
|
2633
|
+
const {fetchRequest: fetchRequest} = this.formSubmission;
|
2634
|
+
this.prepareHeadersForRequest(fetchRequest.headers, fetchRequest);
|
2635
|
+
this.formSubmission.start();
|
2636
|
+
}
|
2637
|
+
}
|
2638
|
+
prepareHeadersForRequest(headers, request) {
|
2639
|
+
headers["Turbo-Frame"] = this.id;
|
2640
|
+
}
|
2641
|
+
requestStarted(request) {
|
2642
|
+
this.element.setAttribute("busy", "");
|
2643
|
+
}
|
2644
|
+
requestPreventedHandlingResponse(request, response) {
|
2645
|
+
this.resolveVisitPromise();
|
2646
|
+
}
|
2647
|
+
async requestSucceededWithResponse(request, response) {
|
2648
|
+
await this.loadResponse(response);
|
2649
|
+
this.resolveVisitPromise();
|
2650
|
+
}
|
2651
|
+
requestFailedWithResponse(request, response) {
|
2652
|
+
console.error(response);
|
2653
|
+
this.resolveVisitPromise();
|
2654
|
+
}
|
2655
|
+
requestErrored(request, error) {
|
2656
|
+
console.error(error);
|
2657
|
+
this.resolveVisitPromise();
|
2658
|
+
}
|
2659
|
+
requestFinished(request) {
|
2660
|
+
this.element.removeAttribute("busy");
|
2661
|
+
}
|
2662
|
+
formSubmissionStarted(formSubmission) {
|
2663
|
+
const frame = this.findFrameElement(formSubmission.formElement);
|
2664
|
+
frame.setAttribute("busy", "");
|
2665
|
+
}
|
2666
|
+
formSubmissionSucceededWithResponse(formSubmission, response) {
|
2667
|
+
const frame = this.findFrameElement(formSubmission.formElement);
|
2668
|
+
frame.delegate.loadResponse(response);
|
2669
|
+
}
|
2670
|
+
formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
|
2671
|
+
this.element.delegate.loadResponse(fetchResponse);
|
2672
|
+
}
|
2673
|
+
formSubmissionErrored(formSubmission, error) {
|
2674
|
+
console.error(error);
|
2675
|
+
}
|
2676
|
+
formSubmissionFinished(formSubmission) {
|
2677
|
+
const frame = this.findFrameElement(formSubmission.formElement);
|
2678
|
+
frame.removeAttribute("busy");
|
2679
|
+
}
|
2680
|
+
viewWillRenderSnapshot(snapshot, isPreview) {}
|
2681
|
+
viewRenderedSnapshot(snapshot, isPreview) {}
|
2682
|
+
viewInvalidated() {}
|
2683
|
+
async visit(url) {
|
2684
|
+
const request = new FetchRequest(this, FetchMethod.get, expandURL(url));
|
2685
|
+
return new Promise((resolve => {
|
2686
|
+
this.resolveVisitPromise = () => {
|
2687
|
+
this.resolveVisitPromise = () => {};
|
2688
|
+
resolve();
|
2689
|
+
};
|
2690
|
+
request.perform();
|
2691
|
+
}));
|
2692
|
+
}
|
2693
|
+
navigateFrame(element, url) {
|
2694
|
+
const frame = this.findFrameElement(element);
|
2695
|
+
frame.src = url;
|
2696
|
+
}
|
2697
|
+
findFrameElement(element) {
|
2698
|
+
var _a;
|
2699
|
+
const id = element.getAttribute("data-turbo-frame") || this.element.getAttribute("target");
|
2700
|
+
return (_a = getFrameElementById(id)) !== null && _a !== void 0 ? _a : this.element;
|
2701
|
+
}
|
2702
|
+
async extractForeignFrameElement(container) {
|
2703
|
+
let element;
|
2704
|
+
const id = CSS.escape(this.id);
|
2705
|
+
try {
|
2706
|
+
if (element = activateElement(container.querySelector(`turbo-frame#${id}`), this.currentURL)) {
|
2707
|
+
return element;
|
2708
|
+
}
|
2709
|
+
if (element = activateElement(container.querySelector(`turbo-frame[src][recurse~=${id}]`), this.currentURL)) {
|
2710
|
+
await element.loaded;
|
2711
|
+
return await this.extractForeignFrameElement(element);
|
2712
|
+
}
|
2713
|
+
console.error(`Response has no matching <turbo-frame id="${id}"> element`);
|
2714
|
+
} catch (error) {
|
2715
|
+
console.error(error);
|
2716
|
+
}
|
2717
|
+
return new FrameElement;
|
2718
|
+
}
|
2719
|
+
shouldInterceptNavigation(element, submitter) {
|
2720
|
+
const id = element.getAttribute("data-turbo-frame") || this.element.getAttribute("target");
|
2721
|
+
if (!this.enabled || id == "_top") {
|
2722
|
+
return false;
|
2723
|
+
}
|
2724
|
+
if (id) {
|
2725
|
+
const frameElement = getFrameElementById(id);
|
2726
|
+
if (frameElement) {
|
2727
|
+
return !frameElement.disabled;
|
2728
|
+
}
|
2729
|
+
}
|
2730
|
+
if (!elementIsNavigable(element)) {
|
2731
|
+
return false;
|
2732
|
+
}
|
2733
|
+
if (submitter && !elementIsNavigable(submitter)) {
|
2734
|
+
return false;
|
2735
|
+
}
|
2736
|
+
return true;
|
2604
2737
|
}
|
2605
|
-
|
2606
|
-
|
2607
|
-
this.visit(location, {
|
2608
|
-
action: action
|
2609
|
-
});
|
2738
|
+
get id() {
|
2739
|
+
return this.element.id;
|
2610
2740
|
}
|
2611
|
-
|
2612
|
-
return this.
|
2741
|
+
get enabled() {
|
2742
|
+
return !this.element.disabled;
|
2613
2743
|
}
|
2614
|
-
|
2615
|
-
this.
|
2744
|
+
get sourceURL() {
|
2745
|
+
if (this.element.src) {
|
2746
|
+
return this.element.src;
|
2747
|
+
}
|
2616
2748
|
}
|
2617
|
-
|
2618
|
-
this.
|
2749
|
+
set sourceURL(sourceURL) {
|
2750
|
+
this.settingSourceURL = true;
|
2751
|
+
this.element.src = sourceURL !== null && sourceURL !== void 0 ? sourceURL : null;
|
2752
|
+
this.settingSourceURL = false;
|
2619
2753
|
}
|
2620
|
-
|
2621
|
-
this.
|
2754
|
+
get loadingStyle() {
|
2755
|
+
return this.element.loading;
|
2622
2756
|
}
|
2623
|
-
|
2624
|
-
return this.
|
2757
|
+
get isLoading() {
|
2758
|
+
return this.formSubmission !== undefined || this.resolveVisitPromise !== undefined;
|
2625
2759
|
}
|
2626
|
-
|
2627
|
-
this.
|
2760
|
+
get isActive() {
|
2761
|
+
return this.element.isActive && this.connected;
|
2628
2762
|
}
|
2629
|
-
|
2630
|
-
|
2631
|
-
|
2763
|
+
}
|
2764
|
+
|
2765
|
+
function getFrameElementById(id) {
|
2766
|
+
if (id != null) {
|
2767
|
+
const element = document.getElementById(id);
|
2768
|
+
if (element instanceof FrameElement) {
|
2769
|
+
return element;
|
2770
|
+
}
|
2632
2771
|
}
|
2633
|
-
|
2634
|
-
|
2772
|
+
}
|
2773
|
+
|
2774
|
+
function activateElement(element, currentURL) {
|
2775
|
+
if (element) {
|
2776
|
+
const src = element.getAttribute("src");
|
2777
|
+
if (src != null && currentURL != null && urlsAreEqual(src, currentURL)) {
|
2778
|
+
throw new Error(`Matching <turbo-frame id="${element.id}"> element has a source URL which references itself`);
|
2779
|
+
}
|
2780
|
+
if (element.ownerDocument !== document) {
|
2781
|
+
element = document.importNode(element, true);
|
2782
|
+
}
|
2783
|
+
if (element instanceof FrameElement) {
|
2784
|
+
element.connectedCallback();
|
2785
|
+
return element;
|
2786
|
+
}
|
2635
2787
|
}
|
2636
|
-
|
2637
|
-
|
2788
|
+
}
|
2789
|
+
|
2790
|
+
const StreamActions = {
|
2791
|
+
after() {
|
2792
|
+
var _a, _b;
|
2793
|
+
(_b = (_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.parentElement) === null || _b === void 0 ? void 0 : _b.insertBefore(this.templateContent, this.targetElement.nextSibling);
|
2794
|
+
},
|
2795
|
+
append() {
|
2796
|
+
var _a;
|
2797
|
+
this.removeDuplicateTargetChildren();
|
2798
|
+
(_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.append(this.templateContent);
|
2799
|
+
},
|
2800
|
+
before() {
|
2801
|
+
var _a, _b;
|
2802
|
+
(_b = (_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.parentElement) === null || _b === void 0 ? void 0 : _b.insertBefore(this.templateContent, this.targetElement);
|
2803
|
+
},
|
2804
|
+
prepend() {
|
2805
|
+
var _a;
|
2806
|
+
this.removeDuplicateTargetChildren();
|
2807
|
+
(_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.prepend(this.templateContent);
|
2808
|
+
},
|
2809
|
+
remove() {
|
2810
|
+
var _a;
|
2811
|
+
(_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.remove();
|
2812
|
+
},
|
2813
|
+
replace() {
|
2814
|
+
var _a;
|
2815
|
+
(_a = this.targetElement) === null || _a === void 0 ? void 0 : _a.replaceWith(this.templateContent);
|
2816
|
+
},
|
2817
|
+
update() {
|
2818
|
+
if (this.targetElement) {
|
2819
|
+
this.targetElement.innerHTML = "";
|
2820
|
+
this.targetElement.append(this.templateContent);
|
2821
|
+
}
|
2638
2822
|
}
|
2639
|
-
|
2640
|
-
|
2823
|
+
};
|
2824
|
+
|
2825
|
+
class StreamElement extends HTMLElement {
|
2826
|
+
async connectedCallback() {
|
2827
|
+
try {
|
2828
|
+
await this.render();
|
2829
|
+
} catch (error) {
|
2830
|
+
console.error(error);
|
2831
|
+
} finally {
|
2832
|
+
this.disconnect();
|
2833
|
+
}
|
2641
2834
|
}
|
2642
|
-
|
2643
|
-
|
2835
|
+
async render() {
|
2836
|
+
var _a;
|
2837
|
+
return (_a = this.renderPromise) !== null && _a !== void 0 ? _a : this.renderPromise = (async () => {
|
2838
|
+
if (this.dispatchEvent(this.beforeRenderEvent)) {
|
2839
|
+
await nextAnimationFrame();
|
2840
|
+
this.performAction();
|
2841
|
+
}
|
2842
|
+
})();
|
2644
2843
|
}
|
2645
|
-
|
2646
|
-
|
2647
|
-
|
2844
|
+
disconnect() {
|
2845
|
+
try {
|
2846
|
+
this.remove();
|
2847
|
+
} catch (_a) {}
|
2648
2848
|
}
|
2649
|
-
|
2650
|
-
this.
|
2849
|
+
removeDuplicateTargetChildren() {
|
2850
|
+
this.duplicateChildren.forEach((({targetChild: targetChild}) => {
|
2851
|
+
targetChild.remove();
|
2852
|
+
}));
|
2651
2853
|
}
|
2652
|
-
|
2653
|
-
|
2854
|
+
get duplicateChildren() {
|
2855
|
+
var _a;
|
2856
|
+
return [ ...(_a = this.templateContent) === null || _a === void 0 ? void 0 : _a.children ].map((templateChild => {
|
2857
|
+
let targetChild = [ ...this.targetElement.children ].filter((c => c.id === templateChild.id))[0];
|
2858
|
+
return {
|
2859
|
+
targetChild: targetChild,
|
2860
|
+
templateChild: templateChild
|
2861
|
+
};
|
2862
|
+
})).filter((({targetChild: targetChild}) => targetChild));
|
2654
2863
|
}
|
2655
|
-
|
2656
|
-
|
2657
|
-
|
2864
|
+
get performAction() {
|
2865
|
+
if (this.action) {
|
2866
|
+
const actionFunction = StreamActions[this.action];
|
2867
|
+
if (actionFunction) {
|
2868
|
+
return actionFunction;
|
2869
|
+
}
|
2870
|
+
this.raise("unknown action");
|
2871
|
+
}
|
2872
|
+
this.raise("action attribute is missing");
|
2658
2873
|
}
|
2659
|
-
|
2660
|
-
|
2661
|
-
|
2874
|
+
get targetElement() {
|
2875
|
+
var _a;
|
2876
|
+
if (this.target) {
|
2877
|
+
return (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.getElementById(this.target);
|
2878
|
+
}
|
2879
|
+
this.raise("target attribute is missing");
|
2662
2880
|
}
|
2663
|
-
|
2664
|
-
return
|
2665
|
-
target: link,
|
2666
|
-
detail: {
|
2667
|
-
url: location.absoluteURL
|
2668
|
-
},
|
2669
|
-
cancelable: true
|
2670
|
-
});
|
2881
|
+
get templateContent() {
|
2882
|
+
return this.templateElement.content;
|
2671
2883
|
}
|
2672
|
-
|
2673
|
-
|
2674
|
-
|
2675
|
-
|
2676
|
-
|
2677
|
-
cancelable: true
|
2678
|
-
});
|
2884
|
+
get templateElement() {
|
2885
|
+
if (this.firstElementChild instanceof HTMLTemplateElement) {
|
2886
|
+
return this.firstElementChild;
|
2887
|
+
}
|
2888
|
+
this.raise("first child element must be a <template> element");
|
2679
2889
|
}
|
2680
|
-
|
2681
|
-
return
|
2682
|
-
detail: {
|
2683
|
-
url: location.absoluteURL
|
2684
|
-
}
|
2685
|
-
});
|
2890
|
+
get action() {
|
2891
|
+
return this.getAttribute("action");
|
2686
2892
|
}
|
2687
|
-
|
2688
|
-
return
|
2893
|
+
get target() {
|
2894
|
+
return this.getAttribute("target");
|
2689
2895
|
}
|
2690
|
-
|
2691
|
-
|
2692
|
-
detail: {
|
2693
|
-
newBody: newBody
|
2694
|
-
}
|
2695
|
-
});
|
2896
|
+
raise(message) {
|
2897
|
+
throw new Error(`${this.description}: ${message}`);
|
2696
2898
|
}
|
2697
|
-
|
2698
|
-
|
2899
|
+
get description() {
|
2900
|
+
var _a, _b;
|
2901
|
+
return (_b = ((_a = this.outerHTML.match(/<[^>]+>/)) !== null && _a !== void 0 ? _a : [])[0]) !== null && _b !== void 0 ? _b : "<turbo-stream>";
|
2699
2902
|
}
|
2700
|
-
|
2701
|
-
return
|
2702
|
-
|
2703
|
-
|
2704
|
-
timing: timing
|
2705
|
-
}
|
2903
|
+
get beforeRenderEvent() {
|
2904
|
+
return new CustomEvent("turbo:before-stream-render", {
|
2905
|
+
bubbles: true,
|
2906
|
+
cancelable: true
|
2706
2907
|
});
|
2707
2908
|
}
|
2708
|
-
|
2709
|
-
|
2710
|
-
|
2711
|
-
|
2712
|
-
|
2713
|
-
|
2714
|
-
|
2715
|
-
|
2716
|
-
|
2717
|
-
|
2909
|
+
}
|
2910
|
+
|
2911
|
+
FrameElement.delegateConstructor = FrameController;
|
2912
|
+
|
2913
|
+
customElements.define("turbo-frame", FrameElement);
|
2914
|
+
|
2915
|
+
customElements.define("turbo-stream", StreamElement);
|
2916
|
+
|
2917
|
+
(() => {
|
2918
|
+
let element = document.currentScript;
|
2919
|
+
if (!element) return;
|
2920
|
+
if (element.hasAttribute("data-turbo-suppress-warning")) return;
|
2921
|
+
while (element = element.parentElement) {
|
2922
|
+
if (element == document.body) {
|
2923
|
+
return console.warn(unindent`
|
2924
|
+
You are loading Turbo from a <script> element inside the <body> element. This is probably not what you meant to do!
|
2925
|
+
|
2926
|
+
Load your application’s JavaScript bundle inside the <head> element instead. <script> elements in <body> are evaluated with each page change.
|
2927
|
+
|
2928
|
+
For more information, see: https://turbo.hotwire.dev/handbook/building#working-with-script-elements
|
2929
|
+
|
2930
|
+
——
|
2931
|
+
Suppress this warning by adding a "data-turbo-suppress-warning" attribute to: %s
|
2932
|
+
`, element.outerHTML);
|
2718
2933
|
}
|
2719
2934
|
}
|
2720
|
-
|
2721
|
-
return location.isPrefixedBy(this.view.getRootLocation()) && location.isHTML();
|
2722
|
-
}
|
2723
|
-
}
|
2935
|
+
})();
|
2724
2936
|
|
2725
2937
|
const session = new Session;
|
2726
2938
|
|
@@ -2758,6 +2970,21 @@ function setProgressBarDelay(delay) {
|
|
2758
2970
|
session.setProgressBarDelay(delay);
|
2759
2971
|
}
|
2760
2972
|
|
2973
|
+
var Turbo = Object.freeze({
|
2974
|
+
__proto__: null,
|
2975
|
+
navigator: navigator,
|
2976
|
+
start: start,
|
2977
|
+
registerAdapter: registerAdapter,
|
2978
|
+
visit: visit,
|
2979
|
+
connectStreamSource: connectStreamSource,
|
2980
|
+
disconnectStreamSource: disconnectStreamSource,
|
2981
|
+
renderStreamMessage: renderStreamMessage,
|
2982
|
+
clearCache: clearCache,
|
2983
|
+
setProgressBarDelay: setProgressBarDelay
|
2984
|
+
});
|
2985
|
+
|
2986
|
+
window.Turbo = Turbo;
|
2987
|
+
|
2761
2988
|
start();
|
2762
2989
|
|
2763
2990
|
var turbo_es2017Esm = Object.freeze({
|
@@ -2776,17 +3003,20 @@ var turbo_es2017Esm = Object.freeze({
|
|
2776
3003
|
let consumer;
|
2777
3004
|
|
2778
3005
|
async function getConsumer() {
|
2779
|
-
|
2780
|
-
const {createConsumer: createConsumer} = await Promise.resolve().then((function() {
|
2781
|
-
return index;
|
2782
|
-
}));
|
2783
|
-
return setConsumer(createConsumer());
|
3006
|
+
return consumer || setConsumer(createConsumer().then(setConsumer));
|
2784
3007
|
}
|
2785
3008
|
|
2786
3009
|
function setConsumer(newConsumer) {
|
2787
3010
|
return consumer = newConsumer;
|
2788
3011
|
}
|
2789
3012
|
|
3013
|
+
async function createConsumer() {
|
3014
|
+
const {createConsumer: createConsumer} = await Promise.resolve().then((function() {
|
3015
|
+
return index;
|
3016
|
+
}));
|
3017
|
+
return createConsumer();
|
3018
|
+
}
|
3019
|
+
|
2790
3020
|
async function subscribeTo(channel, mixin) {
|
2791
3021
|
const {subscriptions: subscriptions} = await getConsumer();
|
2792
3022
|
return subscriptions.create(channel, mixin);
|
@@ -2796,6 +3026,7 @@ var cable = Object.freeze({
|
|
2796
3026
|
__proto__: null,
|
2797
3027
|
getConsumer: getConsumer,
|
2798
3028
|
setConsumer: setConsumer,
|
3029
|
+
createConsumer: createConsumer,
|
2799
3030
|
subscribeTo: subscribeTo
|
2800
3031
|
});
|
2801
3032
|
|
@@ -3259,7 +3490,7 @@ function createWebSocketURL(url) {
|
|
3259
3490
|
}
|
3260
3491
|
}
|
3261
3492
|
|
3262
|
-
function createConsumer(url = getConfig("url") || INTERNAL.default_mount_path) {
|
3493
|
+
function createConsumer$1(url = getConfig("url") || INTERNAL.default_mount_path) {
|
3263
3494
|
return new Consumer(url);
|
3264
3495
|
}
|
3265
3496
|
|
@@ -3281,7 +3512,7 @@ var index = Object.freeze({
|
|
3281
3512
|
adapters: adapters,
|
3282
3513
|
createWebSocketURL: createWebSocketURL,
|
3283
3514
|
logger: logger,
|
3284
|
-
createConsumer: createConsumer,
|
3515
|
+
createConsumer: createConsumer$1,
|
3285
3516
|
getConfig: getConfig
|
3286
3517
|
});
|
3287
3518
|
|