actionview 7.0.8.6 → 7.1.0.beta1
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/CHANGELOG.md +235 -387
- data/MIT-LICENSE +1 -1
- data/README.rdoc +1 -1
- data/app/assets/javascripts/rails-ujs.esm.js +668 -0
- data/app/assets/javascripts/rails-ujs.js +606 -0
- data/lib/action_view/base.rb +28 -7
- data/lib/action_view/buffers.rb +106 -8
- data/lib/action_view/cache_expiry.rb +40 -43
- data/lib/action_view/context.rb +1 -1
- data/lib/action_view/deprecator.rb +7 -0
- data/lib/action_view/digestor.rb +1 -1
- data/lib/action_view/gem_version.rb +4 -4
- data/lib/action_view/helpers/active_model_helper.rb +1 -1
- data/lib/action_view/helpers/asset_tag_helper.rb +130 -46
- data/lib/action_view/helpers/asset_url_helper.rb +6 -5
- data/lib/action_view/helpers/atom_feed_helper.rb +5 -5
- data/lib/action_view/helpers/cache_helper.rb +3 -9
- data/lib/action_view/helpers/capture_helper.rb +24 -10
- data/lib/action_view/helpers/content_exfiltration_prevention_helper.rb +70 -0
- data/lib/action_view/helpers/controller_helper.rb +6 -0
- data/lib/action_view/helpers/csp_helper.rb +2 -2
- data/lib/action_view/helpers/csrf_helper.rb +2 -2
- data/lib/action_view/helpers/date_helper.rb +17 -19
- data/lib/action_view/helpers/debug_helper.rb +3 -3
- data/lib/action_view/helpers/form_helper.rb +43 -18
- data/lib/action_view/helpers/form_options_helper.rb +2 -1
- data/lib/action_view/helpers/form_tag_helper.rb +43 -9
- data/lib/action_view/helpers/javascript_helper.rb +1 -0
- data/lib/action_view/helpers/number_helper.rb +2 -1
- data/lib/action_view/helpers/output_safety_helper.rb +2 -2
- data/lib/action_view/helpers/rendering_helper.rb +1 -1
- data/lib/action_view/helpers/sanitize_helper.rb +33 -14
- data/lib/action_view/helpers/tag_helper.rb +5 -27
- data/lib/action_view/helpers/tags/base.rb +11 -52
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +1 -0
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +1 -0
- data/lib/action_view/helpers/tags/collection_select.rb +3 -0
- data/lib/action_view/helpers/tags/date_field.rb +1 -1
- data/lib/action_view/helpers/tags/date_select.rb +2 -0
- data/lib/action_view/helpers/tags/datetime_field.rb +14 -6
- data/lib/action_view/helpers/tags/datetime_local_field.rb +11 -2
- data/lib/action_view/helpers/tags/grouped_collection_select.rb +3 -0
- data/lib/action_view/helpers/tags/month_field.rb +1 -1
- data/lib/action_view/helpers/tags/select.rb +3 -0
- data/lib/action_view/helpers/tags/select_renderer.rb +56 -0
- data/lib/action_view/helpers/tags/time_field.rb +1 -1
- data/lib/action_view/helpers/tags/time_zone_select.rb +3 -0
- data/lib/action_view/helpers/tags/week_field.rb +1 -1
- data/lib/action_view/helpers/tags/weekday_select.rb +3 -0
- data/lib/action_view/helpers/tags.rb +2 -0
- data/lib/action_view/helpers/text_helper.rb +32 -16
- data/lib/action_view/helpers/translation_helper.rb +3 -3
- data/lib/action_view/helpers/url_helper.rb +41 -14
- data/lib/action_view/helpers.rb +2 -0
- data/lib/action_view/layouts.rb +4 -2
- data/lib/action_view/log_subscriber.rb +49 -32
- data/lib/action_view/lookup_context.rb +29 -13
- data/lib/action_view/path_registry.rb +57 -0
- data/lib/action_view/path_set.rb +13 -14
- data/lib/action_view/railtie.rb +26 -3
- data/lib/action_view/record_identifier.rb +15 -8
- data/lib/action_view/renderer/abstract_renderer.rb +1 -1
- data/lib/action_view/renderer/collection_renderer.rb +9 -1
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +2 -1
- data/lib/action_view/renderer/partial_renderer.rb +2 -1
- data/lib/action_view/renderer/renderer.rb +2 -0
- data/lib/action_view/renderer/streaming_template_renderer.rb +3 -2
- data/lib/action_view/renderer/template_renderer.rb +3 -2
- data/lib/action_view/rendering.rb +22 -4
- data/lib/action_view/ripper_ast_parser.rb +6 -6
- data/lib/action_view/template/error.rb +14 -1
- data/lib/action_view/template/handlers/builder.rb +4 -4
- data/lib/action_view/template/handlers/erb/erubi.rb +23 -27
- data/lib/action_view/template/handlers/erb.rb +73 -1
- data/lib/action_view/template/handlers.rb +1 -1
- data/lib/action_view/template/html.rb +1 -1
- data/lib/action_view/template/raw_file.rb +1 -1
- data/lib/action_view/template/renderable.rb +1 -1
- data/lib/action_view/template/resolver.rb +10 -2
- data/lib/action_view/template/text.rb +1 -1
- data/lib/action_view/template/types.rb +25 -34
- data/lib/action_view/template.rb +179 -52
- data/lib/action_view/template_path.rb +2 -0
- data/lib/action_view/test_case.rb +8 -5
- data/lib/action_view/unbound_template.rb +15 -5
- data/lib/action_view/version.rb +1 -1
- data/lib/action_view/view_paths.rb +15 -24
- data/lib/action_view.rb +4 -1
- metadata +29 -29
@@ -0,0 +1,606 @@
|
|
1
|
+
/*
|
2
|
+
Unobtrusive JavaScript
|
3
|
+
https://github.com/rails/rails/blob/main/actionview/app/javascript
|
4
|
+
Released under the MIT license
|
5
|
+
*/
|
6
|
+
(function(global, factory) {
|
7
|
+
typeof exports === "object" && typeof module !== "undefined" ? module.exports = factory() : typeof define === "function" && define.amd ? define(factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self,
|
8
|
+
global.Rails = factory());
|
9
|
+
})(this, (function() {
|
10
|
+
"use strict";
|
11
|
+
const linkClickSelector = "a[data-confirm], a[data-method], a[data-remote]:not([disabled]), a[data-disable-with], a[data-disable]";
|
12
|
+
const buttonClickSelector = {
|
13
|
+
selector: "button[data-remote]:not([form]), button[data-confirm]:not([form])",
|
14
|
+
exclude: "form button"
|
15
|
+
};
|
16
|
+
const inputChangeSelector = "select[data-remote], input[data-remote], textarea[data-remote]";
|
17
|
+
const formSubmitSelector = "form:not([data-turbo=true])";
|
18
|
+
const formInputClickSelector = "form:not([data-turbo=true]) input[type=submit], form:not([data-turbo=true]) input[type=image], form:not([data-turbo=true]) button[type=submit], form:not([data-turbo=true]) button:not([type]), input[type=submit][form], input[type=image][form], button[type=submit][form], button[form]:not([type])";
|
19
|
+
const formDisableSelector = "input[data-disable-with]:enabled, button[data-disable-with]:enabled, textarea[data-disable-with]:enabled, input[data-disable]:enabled, button[data-disable]:enabled, textarea[data-disable]:enabled";
|
20
|
+
const formEnableSelector = "input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled, input[data-disable]:disabled, button[data-disable]:disabled, textarea[data-disable]:disabled";
|
21
|
+
const fileInputSelector = "input[name][type=file]:not([disabled])";
|
22
|
+
const linkDisableSelector = "a[data-disable-with], a[data-disable]";
|
23
|
+
const buttonDisableSelector = "button[data-remote][data-disable-with], button[data-remote][data-disable]";
|
24
|
+
let nonce = null;
|
25
|
+
const loadCSPNonce = () => {
|
26
|
+
const metaTag = document.querySelector("meta[name=csp-nonce]");
|
27
|
+
return nonce = metaTag && metaTag.content;
|
28
|
+
};
|
29
|
+
const cspNonce = () => nonce || loadCSPNonce();
|
30
|
+
const m = Element.prototype.matches || Element.prototype.matchesSelector || Element.prototype.mozMatchesSelector || Element.prototype.msMatchesSelector || Element.prototype.oMatchesSelector || Element.prototype.webkitMatchesSelector;
|
31
|
+
const matches = function(element, selector) {
|
32
|
+
if (selector.exclude) {
|
33
|
+
return m.call(element, selector.selector) && !m.call(element, selector.exclude);
|
34
|
+
} else {
|
35
|
+
return m.call(element, selector);
|
36
|
+
}
|
37
|
+
};
|
38
|
+
const EXPANDO = "_ujsData";
|
39
|
+
const getData = (element, key) => element[EXPANDO] ? element[EXPANDO][key] : undefined;
|
40
|
+
const setData = function(element, key, value) {
|
41
|
+
if (!element[EXPANDO]) {
|
42
|
+
element[EXPANDO] = {};
|
43
|
+
}
|
44
|
+
return element[EXPANDO][key] = value;
|
45
|
+
};
|
46
|
+
const $ = selector => Array.prototype.slice.call(document.querySelectorAll(selector));
|
47
|
+
const csrfToken = () => {
|
48
|
+
const meta = document.querySelector("meta[name=csrf-token]");
|
49
|
+
return meta && meta.content;
|
50
|
+
};
|
51
|
+
const csrfParam = () => {
|
52
|
+
const meta = document.querySelector("meta[name=csrf-param]");
|
53
|
+
return meta && meta.content;
|
54
|
+
};
|
55
|
+
const CSRFProtection = xhr => {
|
56
|
+
const token = csrfToken();
|
57
|
+
if (token) {
|
58
|
+
return xhr.setRequestHeader("X-CSRF-Token", token);
|
59
|
+
}
|
60
|
+
};
|
61
|
+
const refreshCSRFTokens = () => {
|
62
|
+
const token = csrfToken();
|
63
|
+
const param = csrfParam();
|
64
|
+
if (token && param) {
|
65
|
+
return $('form input[name="' + param + '"]').forEach((input => input.value = token));
|
66
|
+
}
|
67
|
+
};
|
68
|
+
const AcceptHeaders = {
|
69
|
+
"*": "*/*",
|
70
|
+
text: "text/plain",
|
71
|
+
html: "text/html",
|
72
|
+
xml: "application/xml, text/xml",
|
73
|
+
json: "application/json, text/javascript",
|
74
|
+
script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
|
75
|
+
};
|
76
|
+
const ajax = options => {
|
77
|
+
options = prepareOptions(options);
|
78
|
+
var xhr = createXHR(options, (function() {
|
79
|
+
const response = processResponse(xhr.response != null ? xhr.response : xhr.responseText, xhr.getResponseHeader("Content-Type"));
|
80
|
+
if (Math.floor(xhr.status / 100) === 2) {
|
81
|
+
if (typeof options.success === "function") {
|
82
|
+
options.success(response, xhr.statusText, xhr);
|
83
|
+
}
|
84
|
+
} else {
|
85
|
+
if (typeof options.error === "function") {
|
86
|
+
options.error(response, xhr.statusText, xhr);
|
87
|
+
}
|
88
|
+
}
|
89
|
+
return typeof options.complete === "function" ? options.complete(xhr, xhr.statusText) : undefined;
|
90
|
+
}));
|
91
|
+
if (options.beforeSend && !options.beforeSend(xhr, options)) {
|
92
|
+
return false;
|
93
|
+
}
|
94
|
+
if (xhr.readyState === XMLHttpRequest.OPENED) {
|
95
|
+
return xhr.send(options.data);
|
96
|
+
}
|
97
|
+
};
|
98
|
+
var prepareOptions = function(options) {
|
99
|
+
options.url = options.url || location.href;
|
100
|
+
options.type = options.type.toUpperCase();
|
101
|
+
if (options.type === "GET" && options.data) {
|
102
|
+
if (options.url.indexOf("?") < 0) {
|
103
|
+
options.url += "?" + options.data;
|
104
|
+
} else {
|
105
|
+
options.url += "&" + options.data;
|
106
|
+
}
|
107
|
+
}
|
108
|
+
if (!(options.dataType in AcceptHeaders)) {
|
109
|
+
options.dataType = "*";
|
110
|
+
}
|
111
|
+
options.accept = AcceptHeaders[options.dataType];
|
112
|
+
if (options.dataType !== "*") {
|
113
|
+
options.accept += ", */*; q=0.01";
|
114
|
+
}
|
115
|
+
return options;
|
116
|
+
};
|
117
|
+
var createXHR = function(options, done) {
|
118
|
+
const xhr = new XMLHttpRequest;
|
119
|
+
xhr.open(options.type, options.url, true);
|
120
|
+
xhr.setRequestHeader("Accept", options.accept);
|
121
|
+
if (typeof options.data === "string") {
|
122
|
+
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
|
123
|
+
}
|
124
|
+
if (!options.crossDomain) {
|
125
|
+
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
|
126
|
+
CSRFProtection(xhr);
|
127
|
+
}
|
128
|
+
xhr.withCredentials = !!options.withCredentials;
|
129
|
+
xhr.onreadystatechange = function() {
|
130
|
+
if (xhr.readyState === XMLHttpRequest.DONE) {
|
131
|
+
return done(xhr);
|
132
|
+
}
|
133
|
+
};
|
134
|
+
return xhr;
|
135
|
+
};
|
136
|
+
var processResponse = function(response, type) {
|
137
|
+
if (typeof response === "string" && typeof type === "string") {
|
138
|
+
if (type.match(/\bjson\b/)) {
|
139
|
+
try {
|
140
|
+
response = JSON.parse(response);
|
141
|
+
} catch (error) {}
|
142
|
+
} else if (type.match(/\b(?:java|ecma)script\b/)) {
|
143
|
+
const script = document.createElement("script");
|
144
|
+
script.setAttribute("nonce", cspNonce());
|
145
|
+
script.text = response;
|
146
|
+
document.head.appendChild(script).parentNode.removeChild(script);
|
147
|
+
} else if (type.match(/\b(xml|html|svg)\b/)) {
|
148
|
+
const parser = new DOMParser;
|
149
|
+
type = type.replace(/;.+/, "");
|
150
|
+
try {
|
151
|
+
response = parser.parseFromString(response, type);
|
152
|
+
} catch (error1) {}
|
153
|
+
}
|
154
|
+
}
|
155
|
+
return response;
|
156
|
+
};
|
157
|
+
const href = element => element.href;
|
158
|
+
const isCrossDomain = function(url) {
|
159
|
+
const originAnchor = document.createElement("a");
|
160
|
+
originAnchor.href = location.href;
|
161
|
+
const urlAnchor = document.createElement("a");
|
162
|
+
try {
|
163
|
+
urlAnchor.href = url;
|
164
|
+
return !((!urlAnchor.protocol || urlAnchor.protocol === ":") && !urlAnchor.host || originAnchor.protocol + "//" + originAnchor.host === urlAnchor.protocol + "//" + urlAnchor.host);
|
165
|
+
} catch (e) {
|
166
|
+
return true;
|
167
|
+
}
|
168
|
+
};
|
169
|
+
let preventDefault;
|
170
|
+
let {CustomEvent: CustomEvent} = window;
|
171
|
+
if (typeof CustomEvent !== "function") {
|
172
|
+
CustomEvent = function(event, params) {
|
173
|
+
const evt = document.createEvent("CustomEvent");
|
174
|
+
evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
|
175
|
+
return evt;
|
176
|
+
};
|
177
|
+
CustomEvent.prototype = window.Event.prototype;
|
178
|
+
({preventDefault: preventDefault} = CustomEvent.prototype);
|
179
|
+
CustomEvent.prototype.preventDefault = function() {
|
180
|
+
const result = preventDefault.call(this);
|
181
|
+
if (this.cancelable && !this.defaultPrevented) {
|
182
|
+
Object.defineProperty(this, "defaultPrevented", {
|
183
|
+
get() {
|
184
|
+
return true;
|
185
|
+
}
|
186
|
+
});
|
187
|
+
}
|
188
|
+
return result;
|
189
|
+
};
|
190
|
+
}
|
191
|
+
const fire = (obj, name, data) => {
|
192
|
+
const event = new CustomEvent(name, {
|
193
|
+
bubbles: true,
|
194
|
+
cancelable: true,
|
195
|
+
detail: data
|
196
|
+
});
|
197
|
+
obj.dispatchEvent(event);
|
198
|
+
return !event.defaultPrevented;
|
199
|
+
};
|
200
|
+
const stopEverything = e => {
|
201
|
+
fire(e.target, "ujs:everythingStopped");
|
202
|
+
e.preventDefault();
|
203
|
+
e.stopPropagation();
|
204
|
+
e.stopImmediatePropagation();
|
205
|
+
};
|
206
|
+
const delegate = (element, selector, eventType, handler) => element.addEventListener(eventType, (function(e) {
|
207
|
+
let {target: target} = e;
|
208
|
+
while (!!(target instanceof Element) && !matches(target, selector)) {
|
209
|
+
target = target.parentNode;
|
210
|
+
}
|
211
|
+
if (target instanceof Element && handler.call(target, e) === false) {
|
212
|
+
e.preventDefault();
|
213
|
+
e.stopPropagation();
|
214
|
+
}
|
215
|
+
}));
|
216
|
+
const toArray = e => Array.prototype.slice.call(e);
|
217
|
+
const serializeElement = (element, additionalParam) => {
|
218
|
+
let inputs = [ element ];
|
219
|
+
if (matches(element, "form")) {
|
220
|
+
inputs = toArray(element.elements);
|
221
|
+
}
|
222
|
+
const params = [];
|
223
|
+
inputs.forEach((function(input) {
|
224
|
+
if (!input.name || input.disabled) {
|
225
|
+
return;
|
226
|
+
}
|
227
|
+
if (matches(input, "fieldset[disabled] *")) {
|
228
|
+
return;
|
229
|
+
}
|
230
|
+
if (matches(input, "select")) {
|
231
|
+
toArray(input.options).forEach((function(option) {
|
232
|
+
if (option.selected) {
|
233
|
+
params.push({
|
234
|
+
name: input.name,
|
235
|
+
value: option.value
|
236
|
+
});
|
237
|
+
}
|
238
|
+
}));
|
239
|
+
} else if (input.checked || [ "radio", "checkbox", "submit" ].indexOf(input.type) === -1) {
|
240
|
+
params.push({
|
241
|
+
name: input.name,
|
242
|
+
value: input.value
|
243
|
+
});
|
244
|
+
}
|
245
|
+
}));
|
246
|
+
if (additionalParam) {
|
247
|
+
params.push(additionalParam);
|
248
|
+
}
|
249
|
+
return params.map((function(param) {
|
250
|
+
if (param.name) {
|
251
|
+
return `${encodeURIComponent(param.name)}=${encodeURIComponent(param.value)}`;
|
252
|
+
} else {
|
253
|
+
return param;
|
254
|
+
}
|
255
|
+
})).join("&");
|
256
|
+
};
|
257
|
+
const formElements = (form, selector) => {
|
258
|
+
if (matches(form, "form")) {
|
259
|
+
return toArray(form.elements).filter((el => matches(el, selector)));
|
260
|
+
} else {
|
261
|
+
return toArray(form.querySelectorAll(selector));
|
262
|
+
}
|
263
|
+
};
|
264
|
+
const handleConfirmWithRails = rails => function(e) {
|
265
|
+
if (!allowAction(this, rails)) {
|
266
|
+
stopEverything(e);
|
267
|
+
}
|
268
|
+
};
|
269
|
+
const confirm = (message, element) => window.confirm(message);
|
270
|
+
var allowAction = function(element, rails) {
|
271
|
+
let callback;
|
272
|
+
const message = element.getAttribute("data-confirm");
|
273
|
+
if (!message) {
|
274
|
+
return true;
|
275
|
+
}
|
276
|
+
let answer = false;
|
277
|
+
if (fire(element, "confirm")) {
|
278
|
+
try {
|
279
|
+
answer = rails.confirm(message, element);
|
280
|
+
} catch (error) {}
|
281
|
+
callback = fire(element, "confirm:complete", [ answer ]);
|
282
|
+
}
|
283
|
+
return answer && callback;
|
284
|
+
};
|
285
|
+
const handleDisabledElement = function(e) {
|
286
|
+
const element = this;
|
287
|
+
if (element.disabled) {
|
288
|
+
stopEverything(e);
|
289
|
+
}
|
290
|
+
};
|
291
|
+
const enableElement = e => {
|
292
|
+
let element;
|
293
|
+
if (e instanceof Event) {
|
294
|
+
if (isXhrRedirect(e)) {
|
295
|
+
return;
|
296
|
+
}
|
297
|
+
element = e.target;
|
298
|
+
} else {
|
299
|
+
element = e;
|
300
|
+
}
|
301
|
+
if (matches(element, linkDisableSelector)) {
|
302
|
+
return enableLinkElement(element);
|
303
|
+
} else if (matches(element, buttonDisableSelector) || matches(element, formEnableSelector)) {
|
304
|
+
return enableFormElement(element);
|
305
|
+
} else if (matches(element, formSubmitSelector)) {
|
306
|
+
return enableFormElements(element);
|
307
|
+
}
|
308
|
+
};
|
309
|
+
const disableElement = e => {
|
310
|
+
const element = e instanceof Event ? e.target : e;
|
311
|
+
if (matches(element, linkDisableSelector)) {
|
312
|
+
return disableLinkElement(element);
|
313
|
+
} else if (matches(element, buttonDisableSelector) || matches(element, formDisableSelector)) {
|
314
|
+
return disableFormElement(element);
|
315
|
+
} else if (matches(element, formSubmitSelector)) {
|
316
|
+
return disableFormElements(element);
|
317
|
+
}
|
318
|
+
};
|
319
|
+
var disableLinkElement = function(element) {
|
320
|
+
if (getData(element, "ujs:disabled")) {
|
321
|
+
return;
|
322
|
+
}
|
323
|
+
const replacement = element.getAttribute("data-disable-with");
|
324
|
+
if (replacement != null) {
|
325
|
+
setData(element, "ujs:enable-with", element.innerHTML);
|
326
|
+
element.innerHTML = replacement;
|
327
|
+
}
|
328
|
+
element.addEventListener("click", stopEverything);
|
329
|
+
return setData(element, "ujs:disabled", true);
|
330
|
+
};
|
331
|
+
var enableLinkElement = function(element) {
|
332
|
+
const originalText = getData(element, "ujs:enable-with");
|
333
|
+
if (originalText != null) {
|
334
|
+
element.innerHTML = originalText;
|
335
|
+
setData(element, "ujs:enable-with", null);
|
336
|
+
}
|
337
|
+
element.removeEventListener("click", stopEverything);
|
338
|
+
return setData(element, "ujs:disabled", null);
|
339
|
+
};
|
340
|
+
var disableFormElements = form => formElements(form, formDisableSelector).forEach(disableFormElement);
|
341
|
+
var disableFormElement = function(element) {
|
342
|
+
if (getData(element, "ujs:disabled")) {
|
343
|
+
return;
|
344
|
+
}
|
345
|
+
const replacement = element.getAttribute("data-disable-with");
|
346
|
+
if (replacement != null) {
|
347
|
+
if (matches(element, "button")) {
|
348
|
+
setData(element, "ujs:enable-with", element.innerHTML);
|
349
|
+
element.innerHTML = replacement;
|
350
|
+
} else {
|
351
|
+
setData(element, "ujs:enable-with", element.value);
|
352
|
+
element.value = replacement;
|
353
|
+
}
|
354
|
+
}
|
355
|
+
element.disabled = true;
|
356
|
+
return setData(element, "ujs:disabled", true);
|
357
|
+
};
|
358
|
+
var enableFormElements = form => formElements(form, formEnableSelector).forEach((element => enableFormElement(element)));
|
359
|
+
var enableFormElement = function(element) {
|
360
|
+
const originalText = getData(element, "ujs:enable-with");
|
361
|
+
if (originalText != null) {
|
362
|
+
if (matches(element, "button")) {
|
363
|
+
element.innerHTML = originalText;
|
364
|
+
} else {
|
365
|
+
element.value = originalText;
|
366
|
+
}
|
367
|
+
setData(element, "ujs:enable-with", null);
|
368
|
+
}
|
369
|
+
element.disabled = false;
|
370
|
+
return setData(element, "ujs:disabled", null);
|
371
|
+
};
|
372
|
+
var isXhrRedirect = function(event) {
|
373
|
+
const xhr = event.detail ? event.detail[0] : undefined;
|
374
|
+
return xhr && xhr.getResponseHeader("X-Xhr-Redirect");
|
375
|
+
};
|
376
|
+
const handleMethodWithRails = rails => function(e) {
|
377
|
+
const link = this;
|
378
|
+
const method = link.getAttribute("data-method");
|
379
|
+
if (!method) {
|
380
|
+
return;
|
381
|
+
}
|
382
|
+
const href = rails.href(link);
|
383
|
+
const csrfToken$1 = csrfToken();
|
384
|
+
const csrfParam$1 = csrfParam();
|
385
|
+
const form = document.createElement("form");
|
386
|
+
let formContent = `<input name='_method' value='${method}' type='hidden' />`;
|
387
|
+
if (csrfParam$1 && csrfToken$1 && !isCrossDomain(href)) {
|
388
|
+
formContent += `<input name='${csrfParam$1}' value='${csrfToken$1}' type='hidden' />`;
|
389
|
+
}
|
390
|
+
formContent += '<input type="submit" />';
|
391
|
+
form.method = "post";
|
392
|
+
form.action = href;
|
393
|
+
form.target = link.target;
|
394
|
+
form.innerHTML = formContent;
|
395
|
+
form.style.display = "none";
|
396
|
+
document.body.appendChild(form);
|
397
|
+
form.querySelector('[type="submit"]').click();
|
398
|
+
stopEverything(e);
|
399
|
+
};
|
400
|
+
const isRemote = function(element) {
|
401
|
+
const value = element.getAttribute("data-remote");
|
402
|
+
return value != null && value !== "false";
|
403
|
+
};
|
404
|
+
const handleRemoteWithRails = rails => function(e) {
|
405
|
+
let data, method, url;
|
406
|
+
const element = this;
|
407
|
+
if (!isRemote(element)) {
|
408
|
+
return true;
|
409
|
+
}
|
410
|
+
if (!fire(element, "ajax:before")) {
|
411
|
+
fire(element, "ajax:stopped");
|
412
|
+
return false;
|
413
|
+
}
|
414
|
+
const withCredentials = element.getAttribute("data-with-credentials");
|
415
|
+
const dataType = element.getAttribute("data-type") || "script";
|
416
|
+
if (matches(element, formSubmitSelector)) {
|
417
|
+
const button = getData(element, "ujs:submit-button");
|
418
|
+
method = getData(element, "ujs:submit-button-formmethod") || element.getAttribute("method") || "get";
|
419
|
+
url = getData(element, "ujs:submit-button-formaction") || element.getAttribute("action") || location.href;
|
420
|
+
if (method.toUpperCase() === "GET") {
|
421
|
+
url = url.replace(/\?.*$/, "");
|
422
|
+
}
|
423
|
+
if (element.enctype === "multipart/form-data") {
|
424
|
+
data = new FormData(element);
|
425
|
+
if (button != null) {
|
426
|
+
data.append(button.name, button.value);
|
427
|
+
}
|
428
|
+
} else {
|
429
|
+
data = serializeElement(element, button);
|
430
|
+
}
|
431
|
+
setData(element, "ujs:submit-button", null);
|
432
|
+
setData(element, "ujs:submit-button-formmethod", null);
|
433
|
+
setData(element, "ujs:submit-button-formaction", null);
|
434
|
+
} else if (matches(element, buttonClickSelector) || matches(element, inputChangeSelector)) {
|
435
|
+
method = element.getAttribute("data-method");
|
436
|
+
url = element.getAttribute("data-url");
|
437
|
+
data = serializeElement(element, element.getAttribute("data-params"));
|
438
|
+
} else {
|
439
|
+
method = element.getAttribute("data-method");
|
440
|
+
url = rails.href(element);
|
441
|
+
data = element.getAttribute("data-params");
|
442
|
+
}
|
443
|
+
ajax({
|
444
|
+
type: method || "GET",
|
445
|
+
url: url,
|
446
|
+
data: data,
|
447
|
+
dataType: dataType,
|
448
|
+
beforeSend(xhr, options) {
|
449
|
+
if (fire(element, "ajax:beforeSend", [ xhr, options ])) {
|
450
|
+
return fire(element, "ajax:send", [ xhr ]);
|
451
|
+
} else {
|
452
|
+
fire(element, "ajax:stopped");
|
453
|
+
return false;
|
454
|
+
}
|
455
|
+
},
|
456
|
+
success(...args) {
|
457
|
+
return fire(element, "ajax:success", args);
|
458
|
+
},
|
459
|
+
error(...args) {
|
460
|
+
return fire(element, "ajax:error", args);
|
461
|
+
},
|
462
|
+
complete(...args) {
|
463
|
+
return fire(element, "ajax:complete", args);
|
464
|
+
},
|
465
|
+
crossDomain: isCrossDomain(url),
|
466
|
+
withCredentials: withCredentials != null && withCredentials !== "false"
|
467
|
+
});
|
468
|
+
stopEverything(e);
|
469
|
+
};
|
470
|
+
const formSubmitButtonClick = function(e) {
|
471
|
+
const button = this;
|
472
|
+
const {form: form} = button;
|
473
|
+
if (!form) {
|
474
|
+
return;
|
475
|
+
}
|
476
|
+
if (button.name) {
|
477
|
+
setData(form, "ujs:submit-button", {
|
478
|
+
name: button.name,
|
479
|
+
value: button.value
|
480
|
+
});
|
481
|
+
}
|
482
|
+
setData(form, "ujs:formnovalidate-button", button.formNoValidate);
|
483
|
+
setData(form, "ujs:submit-button-formaction", button.getAttribute("formaction"));
|
484
|
+
return setData(form, "ujs:submit-button-formmethod", button.getAttribute("formmethod"));
|
485
|
+
};
|
486
|
+
const preventInsignificantClick = function(e) {
|
487
|
+
const link = this;
|
488
|
+
const method = (link.getAttribute("data-method") || "GET").toUpperCase();
|
489
|
+
const data = link.getAttribute("data-params");
|
490
|
+
const metaClick = e.metaKey || e.ctrlKey;
|
491
|
+
const insignificantMetaClick = metaClick && method === "GET" && !data;
|
492
|
+
const nonPrimaryMouseClick = e.button != null && e.button !== 0;
|
493
|
+
if (nonPrimaryMouseClick || insignificantMetaClick) {
|
494
|
+
e.stopImmediatePropagation();
|
495
|
+
}
|
496
|
+
};
|
497
|
+
const Rails = {
|
498
|
+
$: $,
|
499
|
+
ajax: ajax,
|
500
|
+
buttonClickSelector: buttonClickSelector,
|
501
|
+
buttonDisableSelector: buttonDisableSelector,
|
502
|
+
confirm: confirm,
|
503
|
+
cspNonce: cspNonce,
|
504
|
+
csrfToken: csrfToken,
|
505
|
+
csrfParam: csrfParam,
|
506
|
+
CSRFProtection: CSRFProtection,
|
507
|
+
delegate: delegate,
|
508
|
+
disableElement: disableElement,
|
509
|
+
enableElement: enableElement,
|
510
|
+
fileInputSelector: fileInputSelector,
|
511
|
+
fire: fire,
|
512
|
+
formElements: formElements,
|
513
|
+
formEnableSelector: formEnableSelector,
|
514
|
+
formDisableSelector: formDisableSelector,
|
515
|
+
formInputClickSelector: formInputClickSelector,
|
516
|
+
formSubmitButtonClick: formSubmitButtonClick,
|
517
|
+
formSubmitSelector: formSubmitSelector,
|
518
|
+
getData: getData,
|
519
|
+
handleDisabledElement: handleDisabledElement,
|
520
|
+
href: href,
|
521
|
+
inputChangeSelector: inputChangeSelector,
|
522
|
+
isCrossDomain: isCrossDomain,
|
523
|
+
linkClickSelector: linkClickSelector,
|
524
|
+
linkDisableSelector: linkDisableSelector,
|
525
|
+
loadCSPNonce: loadCSPNonce,
|
526
|
+
matches: matches,
|
527
|
+
preventInsignificantClick: preventInsignificantClick,
|
528
|
+
refreshCSRFTokens: refreshCSRFTokens,
|
529
|
+
serializeElement: serializeElement,
|
530
|
+
setData: setData,
|
531
|
+
stopEverything: stopEverything
|
532
|
+
};
|
533
|
+
const handleConfirm = handleConfirmWithRails(Rails);
|
534
|
+
Rails.handleConfirm = handleConfirm;
|
535
|
+
const handleMethod = handleMethodWithRails(Rails);
|
536
|
+
Rails.handleMethod = handleMethod;
|
537
|
+
const handleRemote = handleRemoteWithRails(Rails);
|
538
|
+
Rails.handleRemote = handleRemote;
|
539
|
+
const start = function() {
|
540
|
+
if (window._rails_loaded) {
|
541
|
+
throw new Error("rails-ujs has already been loaded!");
|
542
|
+
}
|
543
|
+
window.addEventListener("pageshow", (function() {
|
544
|
+
$(formEnableSelector).forEach((function(el) {
|
545
|
+
if (getData(el, "ujs:disabled")) {
|
546
|
+
enableElement(el);
|
547
|
+
}
|
548
|
+
}));
|
549
|
+
$(linkDisableSelector).forEach((function(el) {
|
550
|
+
if (getData(el, "ujs:disabled")) {
|
551
|
+
enableElement(el);
|
552
|
+
}
|
553
|
+
}));
|
554
|
+
}));
|
555
|
+
delegate(document, linkDisableSelector, "ajax:complete", enableElement);
|
556
|
+
delegate(document, linkDisableSelector, "ajax:stopped", enableElement);
|
557
|
+
delegate(document, buttonDisableSelector, "ajax:complete", enableElement);
|
558
|
+
delegate(document, buttonDisableSelector, "ajax:stopped", enableElement);
|
559
|
+
delegate(document, linkClickSelector, "click", preventInsignificantClick);
|
560
|
+
delegate(document, linkClickSelector, "click", handleDisabledElement);
|
561
|
+
delegate(document, linkClickSelector, "click", handleConfirm);
|
562
|
+
delegate(document, linkClickSelector, "click", disableElement);
|
563
|
+
delegate(document, linkClickSelector, "click", handleRemote);
|
564
|
+
delegate(document, linkClickSelector, "click", handleMethod);
|
565
|
+
delegate(document, buttonClickSelector, "click", preventInsignificantClick);
|
566
|
+
delegate(document, buttonClickSelector, "click", handleDisabledElement);
|
567
|
+
delegate(document, buttonClickSelector, "click", handleConfirm);
|
568
|
+
delegate(document, buttonClickSelector, "click", disableElement);
|
569
|
+
delegate(document, buttonClickSelector, "click", handleRemote);
|
570
|
+
delegate(document, inputChangeSelector, "change", handleDisabledElement);
|
571
|
+
delegate(document, inputChangeSelector, "change", handleConfirm);
|
572
|
+
delegate(document, inputChangeSelector, "change", handleRemote);
|
573
|
+
delegate(document, formSubmitSelector, "submit", handleDisabledElement);
|
574
|
+
delegate(document, formSubmitSelector, "submit", handleConfirm);
|
575
|
+
delegate(document, formSubmitSelector, "submit", handleRemote);
|
576
|
+
delegate(document, formSubmitSelector, "submit", (e => setTimeout((() => disableElement(e)), 13)));
|
577
|
+
delegate(document, formSubmitSelector, "ajax:send", disableElement);
|
578
|
+
delegate(document, formSubmitSelector, "ajax:complete", enableElement);
|
579
|
+
delegate(document, formInputClickSelector, "click", preventInsignificantClick);
|
580
|
+
delegate(document, formInputClickSelector, "click", handleDisabledElement);
|
581
|
+
delegate(document, formInputClickSelector, "click", handleConfirm);
|
582
|
+
delegate(document, formInputClickSelector, "click", formSubmitButtonClick);
|
583
|
+
document.addEventListener("DOMContentLoaded", refreshCSRFTokens);
|
584
|
+
document.addEventListener("DOMContentLoaded", loadCSPNonce);
|
585
|
+
return window._rails_loaded = true;
|
586
|
+
};
|
587
|
+
Rails.start = start;
|
588
|
+
if (typeof jQuery !== "undefined" && jQuery && jQuery.ajax) {
|
589
|
+
if (jQuery.rails) {
|
590
|
+
throw new Error("If you load both jquery_ujs and rails-ujs, use rails-ujs only.");
|
591
|
+
}
|
592
|
+
jQuery.rails = Rails;
|
593
|
+
jQuery.ajaxPrefilter((function(options, originalOptions, xhr) {
|
594
|
+
if (!options.crossDomain) {
|
595
|
+
return CSRFProtection(xhr);
|
596
|
+
}
|
597
|
+
}));
|
598
|
+
}
|
599
|
+
if (typeof exports !== "object" && typeof module === "undefined") {
|
600
|
+
window.Rails = Rails;
|
601
|
+
if (fire(document, "rails:attachBindings")) {
|
602
|
+
start();
|
603
|
+
}
|
604
|
+
}
|
605
|
+
return Rails;
|
606
|
+
}));
|
data/lib/action_view/base.rb
CHANGED
@@ -10,7 +10,7 @@ require "action_view/template"
|
|
10
10
|
require "action_view/lookup_context"
|
11
11
|
|
12
12
|
module ActionView # :nodoc:
|
13
|
-
# = Action View Base
|
13
|
+
# = Action View \Base
|
14
14
|
#
|
15
15
|
# Action View templates can be written in several ways.
|
16
16
|
# If the template file has a <tt>.erb</tt> extension, then it uses the erubi[https://rubygems.org/gems/erubi]
|
@@ -82,8 +82,8 @@ module ActionView # :nodoc:
|
|
82
82
|
#
|
83
83
|
# === Template caching
|
84
84
|
#
|
85
|
-
# By default, Rails will compile each template to a method in order to render it. When you alter a template,
|
86
|
-
# Rails will check the file's modification time and recompile it in development mode.
|
85
|
+
# By default, \Rails will compile each template to a method in order to render it. When you alter a template,
|
86
|
+
# \Rails will check the file's modification time and recompile it in development mode.
|
87
87
|
#
|
88
88
|
# == Builder
|
89
89
|
#
|
@@ -205,7 +205,8 @@ module ActionView # :nodoc:
|
|
205
205
|
delegate :formats, :formats=, :locale, :locale=, :view_paths, :view_paths=, to: :lookup_context
|
206
206
|
|
207
207
|
def assign(new_assigns) # :nodoc:
|
208
|
-
@_assigns = new_assigns
|
208
|
+
@_assigns = new_assigns
|
209
|
+
new_assigns.each { |key, value| instance_variable_set("@#{key}", value) }
|
209
210
|
end
|
210
211
|
|
211
212
|
# :stopdoc:
|
@@ -232,16 +233,36 @@ module ActionView # :nodoc:
|
|
232
233
|
@view_renderer = ActionView::Renderer.new @lookup_context
|
233
234
|
@current_template = nil
|
234
235
|
|
235
|
-
assign(assigns)
|
236
236
|
assign_controller(controller)
|
237
237
|
_prepare_context
|
238
|
+
|
239
|
+
super()
|
240
|
+
|
241
|
+
# Assigns must be called last to minimize the number of shapes
|
242
|
+
assign(assigns)
|
238
243
|
end
|
239
244
|
|
240
|
-
def _run(method, template, locals, buffer, add_to_stack: true, &block)
|
245
|
+
def _run(method, template, locals, buffer, add_to_stack: true, has_strict_locals: false, &block)
|
241
246
|
_old_output_buffer, _old_virtual_path, _old_template = @output_buffer, @virtual_path, @current_template
|
242
247
|
@current_template = template if add_to_stack
|
243
248
|
@output_buffer = buffer
|
244
|
-
|
249
|
+
|
250
|
+
if has_strict_locals
|
251
|
+
begin
|
252
|
+
public_send(method, buffer, **locals, &block)
|
253
|
+
rescue ArgumentError => argument_error
|
254
|
+
raise(
|
255
|
+
ArgumentError,
|
256
|
+
argument_error.
|
257
|
+
message.
|
258
|
+
gsub("unknown keyword:", "unknown local:").
|
259
|
+
gsub("missing keyword:", "missing local:").
|
260
|
+
gsub("no keywords accepted", "no locals accepted")
|
261
|
+
)
|
262
|
+
end
|
263
|
+
else
|
264
|
+
public_send(method, locals, buffer, &block)
|
265
|
+
end
|
245
266
|
ensure
|
246
267
|
@output_buffer, @virtual_path, @current_template = _old_output_buffer, _old_virtual_path, _old_template
|
247
268
|
end
|