shoelace-rails 0.4.1 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +11 -61
- data/.gitignore +1 -6
- data/Appraisals +6 -0
- data/CHANGELOG.md +22 -5
- data/Gemfile +0 -3
- data/Rakefile +2 -17
- data/app/helpers/shoelace/form_helper.rb +63 -14
- data/gemfiles/rails_60.gemfile +0 -1
- data/gemfiles/rails_61.gemfile +0 -1
- data/gemfiles/rails_70.gemfile +0 -1
- data/gemfiles/rails_71.gemfile +10 -0
- data/gemfiles/rails_edge.gemfile +0 -1
- data/lib/shoelace/rails/version.rb +1 -1
- data/shoelace-rails.gemspec +1 -1
- data/test/helpers/form_helper_test.rb +40 -11
- data/test/helpers/translation_test.rb +160 -0
- metadata +7 -58
- data/dist/.keep +0 -0
- data/dist/types/.keep +0 -0
- data/package.json +0 -50
- data/rollup.config.js +0 -49
- data/src/index.ts +0 -2
- data/src/turbo/index.ts +0 -6
- data/src/turbo/polyfills/formdata-event.js +0 -27
- data/src/turbo/sl-turbo-form.ts +0 -110
- data/src/turbolinks/features/confirm.ts +0 -42
- data/src/turbolinks/features/disable.ts +0 -94
- data/src/turbolinks/features/remote.ts +0 -107
- data/src/turbolinks/index.ts +0 -6
- data/src/turbolinks/selectors.ts +0 -38
- data/src/turbolinks/start.ts +0 -38
- data/src/turbolinks/turbolinks.ts +0 -78
- data/src/turbolinks/utils/ajax.ts +0 -146
- data/src/turbolinks/utils/csp.ts +0 -20
- data/src/turbolinks/utils/csrf.ts +0 -33
- data/src/turbolinks/utils/dom.ts +0 -40
- data/src/turbolinks/utils/event.ts +0 -57
- data/src/turbolinks/utils/form.ts +0 -58
- data/test/dummy_app/Gemfile +0 -19
- data/test/dummy_app/Rakefile +0 -6
- data/test/dummy_app/app/controllers/hotwire_forms_controller.rb +0 -46
- data/test/dummy_app/app/controllers/turbolinks_forms_controller.rb +0 -37
- data/test/dummy_app/app/models/user.rb +0 -16
- data/test/dummy_app/app/packs/entrypoints/hotwire.js +0 -1
- data/test/dummy_app/app/packs/entrypoints/turbolinks.js +0 -5
- data/test/dummy_app/app/views/hotwire_forms/form.html.erb +0 -45
- data/test/dummy_app/app/views/hotwire_forms/show.html.erb +0 -5
- data/test/dummy_app/app/views/layouts/application.html.erb +0 -39
- data/test/dummy_app/app/views/turbolinks_forms/form.html.erb +0 -44
- data/test/dummy_app/app/views/turbolinks_forms/show.html.erb +0 -5
- data/test/dummy_app/bin/rails +0 -5
- data/test/dummy_app/bin/webpack +0 -18
- data/test/dummy_app/bin/yarn +0 -18
- data/test/dummy_app/config/application.rb +0 -16
- data/test/dummy_app/config/boot.rb +0 -4
- data/test/dummy_app/config/environment.rb +0 -2
- data/test/dummy_app/config/environments/development.rb +0 -10
- data/test/dummy_app/config/environments/test.rb +0 -18
- data/test/dummy_app/config/routes.rb +0 -4
- data/test/dummy_app/config/webpack/development.js +0 -5
- data/test/dummy_app/config/webpack/production.js +0 -1
- data/test/dummy_app/config/webpack/test.js +0 -5
- data/test/dummy_app/config/webpacker.yml +0 -33
- data/test/dummy_app/config.ru +0 -6
- data/test/dummy_app/package.json +0 -24
- data/test/dummy_app/test/system/hotwire_form_test.rb +0 -63
- data/test/dummy_app/test/system/turbolinks_form_test.rb +0 -38
- data/test/dummy_app/test/test_helper.rb +0 -68
- data/tsconfig.json +0 -19
- data/yarn.lock +0 -249
@@ -1,94 +0,0 @@
|
|
1
|
-
// This code was heavily inspired by the rails-ujs project.
|
2
|
-
// Copyright (c) 2007-2021 Rails Core team.
|
3
|
-
import { buttonDisableSelector, formDisableSelector, formEnableSelector, formSubmitSelector } from "../selectors"
|
4
|
-
import { getData, matches, setData } from "../utils/dom"
|
5
|
-
import { stopEverything } from "../utils/event"
|
6
|
-
import { formElements } from "../utils/form"
|
7
|
-
|
8
|
-
export const handleDisabledElement = (event) => {
|
9
|
-
const element: HTMLInputElement | HTMLFormElement = event.target
|
10
|
-
|
11
|
-
if (element.disabled) {
|
12
|
-
return stopEverything(event)
|
13
|
-
}
|
14
|
-
}
|
15
|
-
|
16
|
-
// Unified function to enable an element (link, button and form)
|
17
|
-
export const enableElement = function (e) {
|
18
|
-
let element
|
19
|
-
|
20
|
-
if (e instanceof Event) {
|
21
|
-
if (isXhrRedirect(e)) {
|
22
|
-
return
|
23
|
-
}
|
24
|
-
element = e.target
|
25
|
-
} else {
|
26
|
-
element = e
|
27
|
-
}
|
28
|
-
|
29
|
-
if (matches(element, buttonDisableSelector) || matches(element, formEnableSelector)) {
|
30
|
-
return enableFormElement(element)
|
31
|
-
} else if (matches(element, formSubmitSelector)) {
|
32
|
-
return enableFormElements(element)
|
33
|
-
}
|
34
|
-
}
|
35
|
-
|
36
|
-
// Unified function to disable an element (link, button and form)
|
37
|
-
export const disableElement = function (e) {
|
38
|
-
const element = e instanceof Event ? e.target : e
|
39
|
-
|
40
|
-
if (matches(element, buttonDisableSelector) || matches(element, formDisableSelector)) {
|
41
|
-
return disableFormElement(element)
|
42
|
-
} else if (matches(element, formSubmitSelector)) {
|
43
|
-
return disableFormElements(element)
|
44
|
-
}
|
45
|
-
}
|
46
|
-
|
47
|
-
// Disables form elements:
|
48
|
-
// - Caches element value in 'ujs:enable-with' data store
|
49
|
-
// - Replaces element text with value of 'data-disable-with' attribute
|
50
|
-
// - Sets disabled property to true
|
51
|
-
const disableFormElements = (form) => formElements(form, formDisableSelector).forEach(disableFormElement)
|
52
|
-
|
53
|
-
const disableFormElement = function (element) {
|
54
|
-
if (getData(element, "ujs:disabled")) {
|
55
|
-
return
|
56
|
-
}
|
57
|
-
|
58
|
-
const replacement = element.getAttribute("data-disable-with")
|
59
|
-
if (replacement != null) {
|
60
|
-
if (matches(element, "button")) {
|
61
|
-
setData(element, "ujs:enable-with", element.innerHTML)
|
62
|
-
element.innerHTML = replacement
|
63
|
-
} else {
|
64
|
-
setData(element, "ujs:enable-with", element.value)
|
65
|
-
element.value = replacement
|
66
|
-
}
|
67
|
-
}
|
68
|
-
element.disabled = true
|
69
|
-
return setData(element, "ujs:disabled", true)
|
70
|
-
}
|
71
|
-
|
72
|
-
// Re-enables disabled form elements:
|
73
|
-
// - Replaces element text with cached value from 'ujs:enable-with' data store (created in `disableFormElements`)
|
74
|
-
// - Sets disabled property to false
|
75
|
-
const enableFormElements = (form) => formElements(form, formEnableSelector).forEach(enableFormElement)
|
76
|
-
|
77
|
-
const enableFormElement = function (element) {
|
78
|
-
const originalText = getData(element, "ujs:enable-with")
|
79
|
-
if (originalText != null) {
|
80
|
-
if (matches(element, "button")) {
|
81
|
-
element.innerHTML = originalText
|
82
|
-
} else {
|
83
|
-
element.value = originalText
|
84
|
-
}
|
85
|
-
setData(element, "ujs:enable-with", null) // clean up cache
|
86
|
-
}
|
87
|
-
element.disabled = false
|
88
|
-
return setData(element, "ujs:disabled", null)
|
89
|
-
}
|
90
|
-
|
91
|
-
const isXhrRedirect = function (event) {
|
92
|
-
const xhr = event.detail != null ? event.detail[0] : undefined
|
93
|
-
return (xhr != null ? xhr.getResponseHeader("X-Xhr-Redirect") : undefined) != null
|
94
|
-
}
|
@@ -1,107 +0,0 @@
|
|
1
|
-
// This code was heavily inspired by the rails-ujs project.
|
2
|
-
// Copyright (c) 2007-2021 Rails Core team.
|
3
|
-
import { formSubmitSelector } from "../selectors"
|
4
|
-
import { ajax, href, isCrossDomain } from "../utils/ajax"
|
5
|
-
import { getData, matches, setData } from "../utils/dom"
|
6
|
-
import { fire, stopEverything } from "../utils/event"
|
7
|
-
|
8
|
-
const isRemote = function (element) {
|
9
|
-
const value = element.getAttribute("data-remote")
|
10
|
-
return value != null && value !== "false"
|
11
|
-
}
|
12
|
-
|
13
|
-
export const handleRemote = function (event: CustomEvent<{ formData: FormData; formControls: HTMLElement[] }>) {
|
14
|
-
const element = this
|
15
|
-
|
16
|
-
if (!isRemote(element)) {
|
17
|
-
return true
|
18
|
-
}
|
19
|
-
|
20
|
-
if (!fire(element, "ajax:before")) {
|
21
|
-
fire(element, "ajax:stopped")
|
22
|
-
return false
|
23
|
-
}
|
24
|
-
|
25
|
-
let method, url, data
|
26
|
-
|
27
|
-
const withCredentials = element.getAttribute("data-with-credentials")
|
28
|
-
const dataType = element.getAttribute("data-type") || "script"
|
29
|
-
|
30
|
-
if (matches(element, formSubmitSelector)) {
|
31
|
-
// memoized value from clicked submit button
|
32
|
-
method = getData(element, "ujs:submit-button-formmethod") || element.getAttribute("method") || "GET"
|
33
|
-
url = getData(element, "ujs:submit-button-formaction") || element.getAttribute("action") || location.href
|
34
|
-
|
35
|
-
// strip query string if it's a GET request
|
36
|
-
if (method.toUpperCase() === "GET") {
|
37
|
-
url = url.replace(/\?.*$/, "")
|
38
|
-
data = Array.from<Array<string>, string>(event.detail.formData as any, (e) =>
|
39
|
-
e.map(encodeURIComponent).join("=")
|
40
|
-
).join("&")
|
41
|
-
} else {
|
42
|
-
data = event.detail.formData
|
43
|
-
}
|
44
|
-
|
45
|
-
setData(element, "ujs:submit-button", null)
|
46
|
-
setData(element, "ujs:submit-button-formmethod", null)
|
47
|
-
setData(element, "ujs:submit-button-formaction", null)
|
48
|
-
} else {
|
49
|
-
method = element.getAttribute("data-method")
|
50
|
-
url = href(element)
|
51
|
-
data = element.getAttribute("data-params")
|
52
|
-
}
|
53
|
-
|
54
|
-
ajax({
|
55
|
-
type: method,
|
56
|
-
url,
|
57
|
-
data,
|
58
|
-
dataType,
|
59
|
-
// stopping the "ajax:beforeSend" event will cancel the ajax request
|
60
|
-
beforeSend: (xhr, options) => {
|
61
|
-
if (fire(element, "ajax:beforeSend", [xhr, options])) {
|
62
|
-
return fire(element, "ajax:send", [xhr])
|
63
|
-
} else {
|
64
|
-
fire(element, "ajax:stopped")
|
65
|
-
return false
|
66
|
-
}
|
67
|
-
},
|
68
|
-
success: (...args) => fire(element, "ajax:success", args),
|
69
|
-
error: (...args) => fire(element, "ajax:error", args),
|
70
|
-
complete: (...args) => fire(element, "ajax:complete", args),
|
71
|
-
crossDomain: isCrossDomain(url),
|
72
|
-
withCredentials: withCredentials != null && withCredentials !== "false",
|
73
|
-
})
|
74
|
-
|
75
|
-
return stopEverything(event)
|
76
|
-
}
|
77
|
-
|
78
|
-
export const formSubmitButtonClick = (event) => {
|
79
|
-
const button: HTMLButtonElement = event.target
|
80
|
-
const { form } = button
|
81
|
-
|
82
|
-
if (!form) {
|
83
|
-
return
|
84
|
-
}
|
85
|
-
|
86
|
-
if (button.name) {
|
87
|
-
setData(form, "ujs:submit-button", { name: button.name, value: button.value })
|
88
|
-
}
|
89
|
-
|
90
|
-
setData(form, "ujs:formnovalidate-button", button.formNoValidate)
|
91
|
-
setData(form, "ujs:submit-button-formaction", button.getAttribute("formaction"))
|
92
|
-
return setData(form, "ujs:submit-button-formmethod", button.getAttribute("formmethod"))
|
93
|
-
}
|
94
|
-
|
95
|
-
export const preventInsignificantClick = (event) => {
|
96
|
-
const link: HTMLElement = event.target
|
97
|
-
|
98
|
-
const method = (link.getAttribute("data-method") || "GET").toUpperCase()
|
99
|
-
const data = link.getAttribute("data-params")
|
100
|
-
const metaClick = event.metaKey || event.ctrlKey
|
101
|
-
const insignificantMetaClick = metaClick && method === "GET" && !data
|
102
|
-
const nonPrimaryMouseClick = event.button != null && event.button !== 0
|
103
|
-
|
104
|
-
if (nonPrimaryMouseClick || insignificantMetaClick) {
|
105
|
-
return event.stopImmediatePropagation()
|
106
|
-
}
|
107
|
-
}
|
data/src/turbolinks/index.ts
DELETED
data/src/turbolinks/selectors.ts
DELETED
@@ -1,38 +0,0 @@
|
|
1
|
-
// This code was heavily inspired by the rails-ujs project.
|
2
|
-
// Copyright (c) 2007-2021 Rails Core team.
|
3
|
-
export const buttonClickSelector = {
|
4
|
-
selector: "sl-button[data-remote]:not([form]), sl-button[data-confirm]:not([form])",
|
5
|
-
exclude: "sl-form sl-button",
|
6
|
-
}
|
7
|
-
|
8
|
-
export const formSubmitSelector = "sl-form[data-remote]"
|
9
|
-
|
10
|
-
export const formInputClickSelector = [
|
11
|
-
"sl-form sl-button[submit]",
|
12
|
-
"sl-form sl-button:not([type])",
|
13
|
-
"sl-button[submit][sl-form]",
|
14
|
-
"sl-button[sl-form]:not([type])",
|
15
|
-
].join(", ")
|
16
|
-
|
17
|
-
export const formDisableSelector = [
|
18
|
-
"sl-input[data-disable-with]:not([disabled])",
|
19
|
-
"sl-button[data-disable-with]:not([disabled])",
|
20
|
-
"sl-textarea[data-disable-with]:not([disabled])",
|
21
|
-
"sl-input[data-disable]:not([disabled])",
|
22
|
-
"sl-button[data-disable]:not([disabled])",
|
23
|
-
"sl-textarea[data-disable]:not([disabled])",
|
24
|
-
].join(", ")
|
25
|
-
|
26
|
-
export const formEnableSelector = [
|
27
|
-
"sl-input[data-disable-with][disabled]",
|
28
|
-
"sl-button[data-disable-with][disabled]",
|
29
|
-
"sl-textarea[data-disable-with][disabled]",
|
30
|
-
"sl-input[data-disable][disabled]",
|
31
|
-
"sl-button[data-disable][disabled]",
|
32
|
-
"sl-textarea[data-disable][disabled]",
|
33
|
-
].join(", ")
|
34
|
-
|
35
|
-
export const buttonDisableSelector = [
|
36
|
-
"sl-button[data-remote][data-disable-with]",
|
37
|
-
"sl-button[data-remote][data-disable]",
|
38
|
-
].join(", ")
|
data/src/turbolinks/start.ts
DELETED
@@ -1,38 +0,0 @@
|
|
1
|
-
// This code was heavily inspired by the rails-ujs project.
|
2
|
-
// Copyright (c) 2007-2021 Rails Core team.
|
3
|
-
import { handleConfirm } from "./features/confirm"
|
4
|
-
import { disableElement, enableElement, handleDisabledElement } from "./features/disable"
|
5
|
-
import { formSubmitButtonClick, handleRemote, preventInsignificantClick } from "./features/remote"
|
6
|
-
import { buttonClickSelector, buttonDisableSelector, formInputClickSelector, formSubmitSelector } from "./selectors"
|
7
|
-
|
8
|
-
import { delegate } from "./utils/event"
|
9
|
-
|
10
|
-
export const getDefaultAssetPath = () => {
|
11
|
-
const rootUrl = (document.currentScript as any).src.replace(/\/packs.*$/, "")
|
12
|
-
|
13
|
-
return `${rootUrl}/packs/js/`
|
14
|
-
}
|
15
|
-
|
16
|
-
export const startUjs = () => {
|
17
|
-
delegate(document, buttonDisableSelector, "ajax:complete", enableElement)
|
18
|
-
delegate(document, buttonDisableSelector, "ajax:stopped", enableElement)
|
19
|
-
|
20
|
-
delegate(document, buttonClickSelector, "click", preventInsignificantClick)
|
21
|
-
delegate(document, buttonClickSelector, "click", handleDisabledElement)
|
22
|
-
delegate(document, buttonClickSelector, "click", handleConfirm)
|
23
|
-
delegate(document, buttonClickSelector, "click", disableElement)
|
24
|
-
delegate(document, buttonClickSelector, "click", handleRemote)
|
25
|
-
|
26
|
-
delegate(document, formSubmitSelector, "sl-submit", handleDisabledElement)
|
27
|
-
delegate(document, formSubmitSelector, "sl-submit", handleConfirm)
|
28
|
-
delegate(document, formSubmitSelector, "sl-submit", handleRemote)
|
29
|
-
|
30
|
-
// simulates a normal form submit:
|
31
|
-
delegate(document, formSubmitSelector, "ajax:send", disableElement)
|
32
|
-
delegate(document, formSubmitSelector, "ajax:complete", enableElement)
|
33
|
-
|
34
|
-
delegate(document, formInputClickSelector, "click", preventInsignificantClick)
|
35
|
-
delegate(document, formInputClickSelector, "click", handleDisabledElement)
|
36
|
-
// delegate(document, formInputClickSelector, "click", handleConfirm)
|
37
|
-
delegate(document, formInputClickSelector, "click", formSubmitButtonClick)
|
38
|
-
}
|
@@ -1,78 +0,0 @@
|
|
1
|
-
import { Snapshot, SnapshotRenderer, ErrorRenderer, uuid } from "turbolinks"
|
2
|
-
import { formSubmitSelector } from "./selectors"
|
3
|
-
import { matches } from "./utils/dom"
|
4
|
-
import { delegate } from "./utils/event"
|
5
|
-
|
6
|
-
const nullCallback = function () {}
|
7
|
-
const nullDelegate = {
|
8
|
-
viewInvalidated: nullCallback,
|
9
|
-
viewWillRender: nullCallback,
|
10
|
-
viewRendered: nullCallback,
|
11
|
-
}
|
12
|
-
|
13
|
-
const renderWithTurbolinks = (htmlContent) => {
|
14
|
-
const currentSnapshot = Snapshot.fromHTMLElement(document.documentElement)
|
15
|
-
const newSnapshot = Snapshot.fromHTMLString(htmlContent)
|
16
|
-
let renderer = new SnapshotRenderer(currentSnapshot, newSnapshot, false)
|
17
|
-
|
18
|
-
if (!renderer.shouldRender()) {
|
19
|
-
renderer = new ErrorRenderer(htmlContent)
|
20
|
-
}
|
21
|
-
|
22
|
-
renderer.delegate = nullDelegate
|
23
|
-
renderer.render(nullCallback)
|
24
|
-
}
|
25
|
-
|
26
|
-
const findActiveElement = (shadowRoot: ShadowRoot) => {
|
27
|
-
let el = shadowRoot.activeElement
|
28
|
-
|
29
|
-
while (el && el.shadowRoot && el.shadowRoot.activeElement) {
|
30
|
-
el = el.shadowRoot.activeElement
|
31
|
-
}
|
32
|
-
|
33
|
-
return el
|
34
|
-
}
|
35
|
-
|
36
|
-
export const addShadowDomSupportToTurbolinks = (turbolinksController) => {
|
37
|
-
// From https://github.com/turbolinks/turbolinks/blob/71b7a7d0546a573735af99113b622180e8a813c2/src/util.ts#L9
|
38
|
-
// © 2019 Basecamp, LLC.
|
39
|
-
const originalClosest: typeof turbolinksController.closest = turbolinksController.closest
|
40
|
-
|
41
|
-
turbolinksController.closest = (node, selector) => {
|
42
|
-
if (!!node.shadowRoot) {
|
43
|
-
const rootActiveElement = findActiveElement(node.shadowRoot) || node
|
44
|
-
|
45
|
-
if (matches(rootActiveElement, selector)) {
|
46
|
-
return rootActiveElement
|
47
|
-
}
|
48
|
-
} else {
|
49
|
-
return originalClosest(node, selector)
|
50
|
-
}
|
51
|
-
}
|
52
|
-
}
|
53
|
-
|
54
|
-
// Turbolinks does not automatically handle form responses. This creates a handler that does it.
|
55
|
-
// From https://github.com/turbolinks/turbolinks/issues/85#issuecomment-299617076
|
56
|
-
export const handleResponse = (turbolinksInstance) => {
|
57
|
-
return (event: CustomEvent<[XMLHttpRequest, string]>) => {
|
58
|
-
const [xhr, _status] = event.detail
|
59
|
-
|
60
|
-
if (xhr.getResponseHeader("Content-Type").startsWith("text/html")) {
|
61
|
-
turbolinksInstance.restorationIdentifier = uuid()
|
62
|
-
turbolinksInstance.clearCache()
|
63
|
-
turbolinksInstance.dispatch("turbolinks:before-cache")
|
64
|
-
turbolinksInstance.controller.pushHistoryWithLocationAndRestorationIdentifier(
|
65
|
-
xhr.responseURL,
|
66
|
-
turbolinksInstance.restorationIdentifier
|
67
|
-
)
|
68
|
-
renderWithTurbolinks(xhr.responseText)
|
69
|
-
window.scroll(0, 0)
|
70
|
-
turbolinksInstance.dispatch("turbolinks:load")
|
71
|
-
}
|
72
|
-
}
|
73
|
-
}
|
74
|
-
|
75
|
-
export const startTurbolinks = (turbolinksInstance) => {
|
76
|
-
delegate(document, formSubmitSelector, "ajax:complete", handleResponse(turbolinksInstance))
|
77
|
-
addShadowDomSupportToTurbolinks(turbolinksInstance)
|
78
|
-
}
|
@@ -1,146 +0,0 @@
|
|
1
|
-
// This code was heavily inspired by the rails-ujs project.
|
2
|
-
// Copyright (c) 2007-2021 Rails Core team.
|
3
|
-
import { cspNonce } from "./csp"
|
4
|
-
import { CSRFProtection } from "./csrf"
|
5
|
-
|
6
|
-
const AcceptHeaders = {
|
7
|
-
"*": "*/*",
|
8
|
-
text: "text/plain",
|
9
|
-
html: "text/html",
|
10
|
-
xml: "application/xml, text/xml",
|
11
|
-
json: "application/json, text/javascript",
|
12
|
-
script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript",
|
13
|
-
}
|
14
|
-
|
15
|
-
export const ajax = (options) => {
|
16
|
-
options = prepareOptions(options)
|
17
|
-
var xhr = createXHR(options, function () {
|
18
|
-
const response = processResponse(
|
19
|
-
xhr.response != null ? xhr.response : xhr.responseText,
|
20
|
-
xhr.getResponseHeader("Content-Type")
|
21
|
-
)
|
22
|
-
|
23
|
-
if (Math.floor(xhr.status / 100) === 2) {
|
24
|
-
if (typeof options.success === "function") {
|
25
|
-
options.success(response, xhr.statusText, xhr)
|
26
|
-
}
|
27
|
-
} else {
|
28
|
-
if (typeof options.error === "function") {
|
29
|
-
options.error(response, xhr.statusText, xhr)
|
30
|
-
}
|
31
|
-
}
|
32
|
-
return typeof options.complete === "function" ? options.complete(xhr, xhr.statusText) : undefined
|
33
|
-
})
|
34
|
-
|
35
|
-
if (options.beforeSend != null && !options.beforeSend(xhr, options)) {
|
36
|
-
return false
|
37
|
-
}
|
38
|
-
|
39
|
-
if (xhr.readyState === XMLHttpRequest.OPENED) {
|
40
|
-
return xhr.send(options.data)
|
41
|
-
}
|
42
|
-
}
|
43
|
-
|
44
|
-
const prepareOptions = (options) => {
|
45
|
-
options.url = options.url || location.href
|
46
|
-
options.type = options.type.toUpperCase()
|
47
|
-
// append data to url if it's a GET request
|
48
|
-
if (options.type === "GET" && options.data) {
|
49
|
-
if (options.url.indexOf("?") < 0) {
|
50
|
-
options.url += "?" + options.data
|
51
|
-
} else {
|
52
|
-
options.url += "&" + options.data
|
53
|
-
}
|
54
|
-
}
|
55
|
-
|
56
|
-
// Use "*" as default dataType
|
57
|
-
if (AcceptHeaders[options.dataType] == null) {
|
58
|
-
options.dataType = "*"
|
59
|
-
}
|
60
|
-
|
61
|
-
options.accept = AcceptHeaders[options.dataType]
|
62
|
-
if (options.dataType !== "*") {
|
63
|
-
options.accept += ", */*; q=0.01"
|
64
|
-
}
|
65
|
-
|
66
|
-
return options
|
67
|
-
}
|
68
|
-
|
69
|
-
const createXHR = (options, done) => {
|
70
|
-
const xhr = new XMLHttpRequest()
|
71
|
-
|
72
|
-
// Open and set up xhr
|
73
|
-
xhr.open(options.type, options.url, true)
|
74
|
-
xhr.setRequestHeader("Accept", options.accept)
|
75
|
-
|
76
|
-
// Set Content-Type only when sending a string
|
77
|
-
// Sending FormData will automatically set Content-Type to multipart/form-data
|
78
|
-
if (typeof options.data === "string") {
|
79
|
-
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
|
80
|
-
}
|
81
|
-
|
82
|
-
if (!options.crossDomain) {
|
83
|
-
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest")
|
84
|
-
CSRFProtection(xhr)
|
85
|
-
}
|
86
|
-
|
87
|
-
xhr.withCredentials = !!options.withCredentials
|
88
|
-
xhr.onreadystatechange = function () {
|
89
|
-
if (xhr.readyState === XMLHttpRequest.DONE) {
|
90
|
-
return done(xhr)
|
91
|
-
}
|
92
|
-
}
|
93
|
-
return xhr
|
94
|
-
}
|
95
|
-
|
96
|
-
const processResponse = (response, type) => {
|
97
|
-
if (typeof response === "string" && typeof type === "string") {
|
98
|
-
if (type.match(/\bjson\b/)) {
|
99
|
-
try {
|
100
|
-
response = JSON.parse(response)
|
101
|
-
} catch (error) {
|
102
|
-
// no-op...
|
103
|
-
}
|
104
|
-
} else if (type.match(/\b(?:java|ecma)script\b/)) {
|
105
|
-
const script = document.createElement("script")
|
106
|
-
script.setAttribute("nonce", cspNonce())
|
107
|
-
script.text = response
|
108
|
-
document.head.appendChild(script).parentNode.removeChild(script)
|
109
|
-
} else if (type.match(/\b(xml|html|svg)\b/)) {
|
110
|
-
const parser = new DOMParser()
|
111
|
-
type = type.replace(/;.+/, "") // remove something like ';charset=utf-8'
|
112
|
-
|
113
|
-
try {
|
114
|
-
response = parser.parseFromString(response, type)
|
115
|
-
} catch (error1) {}
|
116
|
-
}
|
117
|
-
}
|
118
|
-
|
119
|
-
return response
|
120
|
-
}
|
121
|
-
|
122
|
-
// Default way to get an element's href. May be overridden at Rails.href.
|
123
|
-
export const href = (element) => element.href
|
124
|
-
|
125
|
-
// Determines if the request is a cross domain request.
|
126
|
-
export const isCrossDomain = (url) => {
|
127
|
-
const originAnchor = document.createElement("a")
|
128
|
-
originAnchor.href = location.href
|
129
|
-
const urlAnchor = document.createElement("a")
|
130
|
-
|
131
|
-
try {
|
132
|
-
urlAnchor.href = url
|
133
|
-
// If URL protocol is false or is a string containing a single colon
|
134
|
-
// *and* host are false, assume it is not a cross-domain request
|
135
|
-
// (should only be the case for IE7 and IE compatibility mode).
|
136
|
-
// Otherwise, evaluate protocol and host of the URL against the origin
|
137
|
-
// protocol and host.
|
138
|
-
return !(
|
139
|
-
((!urlAnchor.protocol || urlAnchor.protocol === ":") && !urlAnchor.host) ||
|
140
|
-
originAnchor.protocol + "//" + originAnchor.host === urlAnchor.protocol + "//" + urlAnchor.host
|
141
|
-
)
|
142
|
-
} catch (e) {
|
143
|
-
// If there is an error parsing the URL, assume it is crossDomain.
|
144
|
-
return true
|
145
|
-
}
|
146
|
-
}
|
data/src/turbolinks/utils/csp.ts
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
// This code was heavily inspired by the rails-ujs project.
|
2
|
-
// Copyright (c) 2007-2021 Rails Core team.
|
3
|
-
let nonce = null
|
4
|
-
|
5
|
-
const loadCSPNonce = () => {
|
6
|
-
if (nonce) {
|
7
|
-
return nonce
|
8
|
-
}
|
9
|
-
|
10
|
-
const cspMetaTag: HTMLMetaElement = document.querySelector("meta[name=csp-nonce]")
|
11
|
-
|
12
|
-
if (cspMetaTag) {
|
13
|
-
nonce = cspMetaTag.content
|
14
|
-
}
|
15
|
-
|
16
|
-
return nonce
|
17
|
-
}
|
18
|
-
|
19
|
-
// Returns the Content-Security-Policy nonce for inline scripts.
|
20
|
-
export const cspNonce = () => (nonce != null ? nonce : loadCSPNonce())
|
@@ -1,33 +0,0 @@
|
|
1
|
-
// This code was heavily inspired by the rails-ujs project.
|
2
|
-
// Copyright (c) 2007-2021 Rails Core team.
|
3
|
-
const $ = (selector) => Array.prototype.slice.call(document.querySelectorAll(selector))
|
4
|
-
|
5
|
-
// Up-to-date Cross-Site Request Forgery token
|
6
|
-
export const csrfToken = () => {
|
7
|
-
const meta: HTMLMetaElement = document.querySelector("meta[name=csrf-token]")
|
8
|
-
return meta && meta.content
|
9
|
-
}
|
10
|
-
|
11
|
-
// URL param that must contain the CSRF token
|
12
|
-
export const csrfParam = () => {
|
13
|
-
const meta: HTMLMetaElement = document.querySelector("meta[name=csrf-param]")
|
14
|
-
return meta && meta.content
|
15
|
-
}
|
16
|
-
|
17
|
-
// Make sure that every Ajax request sends the CSRF token
|
18
|
-
export const CSRFProtection = (xhr) => {
|
19
|
-
const token = csrfToken()
|
20
|
-
if (token != null) {
|
21
|
-
return xhr.setRequestHeader("X-CSRF-Token", token)
|
22
|
-
}
|
23
|
-
}
|
24
|
-
|
25
|
-
// Make sure that all forms have actual up-to-date tokens (cached forms contain old ones)
|
26
|
-
export const refreshCSRFTokens = () => {
|
27
|
-
const token = csrfToken()
|
28
|
-
const param = csrfParam()
|
29
|
-
|
30
|
-
if (token != null && param != null) {
|
31
|
-
return $('sl-form input[name="' + param + '"]').forEach((input) => (input.value = token))
|
32
|
-
}
|
33
|
-
}
|
data/src/turbolinks/utils/dom.ts
DELETED
@@ -1,40 +0,0 @@
|
|
1
|
-
// This code was heavily inspired by the rails-ujs project.
|
2
|
-
// Copyright (c) 2007-2021 Rails Core team.
|
3
|
-
const elementPrototype = Element.prototype as any
|
4
|
-
|
5
|
-
const m: (this: Element, selector: string) => boolean =
|
6
|
-
elementPrototype.matches ||
|
7
|
-
elementPrototype.matchesSelector ||
|
8
|
-
elementPrototype.mozMatchesSelector ||
|
9
|
-
elementPrototype.msMatchesSelector ||
|
10
|
-
elementPrototype.oMatchesSelector ||
|
11
|
-
elementPrototype.webkitMatchesSelector
|
12
|
-
|
13
|
-
// Checks if the given native dom element matches the selector
|
14
|
-
// element::
|
15
|
-
// native DOM element
|
16
|
-
// selector::
|
17
|
-
// CSS selector string or
|
18
|
-
// a JavaScript object with `selector` and `exclude` properties
|
19
|
-
// Examples: "form", { selector: "form", exclude: "form[data-remote='true']"}
|
20
|
-
export const matches = (element, selector) => {
|
21
|
-
if (selector.exclude != null) {
|
22
|
-
return m.call(element, selector.selector) && !m.call(element, selector.exclude)
|
23
|
-
} else {
|
24
|
-
return m.call(element, selector)
|
25
|
-
}
|
26
|
-
}
|
27
|
-
|
28
|
-
// get and set data on a given element using "expando properties"
|
29
|
-
// See: https://developer.mozilla.org/en-US/docs/Glossary/Expando
|
30
|
-
const expando = "_ujsData"
|
31
|
-
|
32
|
-
export const getData = (element, key) => (element[expando] != null ? element[expando][key] : undefined)
|
33
|
-
|
34
|
-
export const setData = (element, key, value) => {
|
35
|
-
if (element[expando] == null) {
|
36
|
-
element[expando] = {}
|
37
|
-
}
|
38
|
-
|
39
|
-
return (element[expando][key] = value)
|
40
|
-
}
|
@@ -1,57 +0,0 @@
|
|
1
|
-
// This code was heavily inspired by the rails-ujs project.
|
2
|
-
// Copyright (c) 2007-2021 Rails Core team.
|
3
|
-
import { matches } from "./dom"
|
4
|
-
|
5
|
-
// Triggers a custom event on an element and returns false if the event result is false
|
6
|
-
// obj::
|
7
|
-
// a native DOM element
|
8
|
-
// name::
|
9
|
-
// string that corresponds to the event you want to trigger
|
10
|
-
// e.g. 'click', 'submit'
|
11
|
-
// data::
|
12
|
-
// data you want to pass when you dispatch an event
|
13
|
-
export const fire = (obj, name, data = {}) => {
|
14
|
-
const event = new CustomEvent(name, {
|
15
|
-
bubbles: true,
|
16
|
-
cancelable: true,
|
17
|
-
detail: data,
|
18
|
-
})
|
19
|
-
|
20
|
-
obj.dispatchEvent(event)
|
21
|
-
return !event.defaultPrevented
|
22
|
-
}
|
23
|
-
|
24
|
-
// Helper function, needed to provide consistent behavior in IE
|
25
|
-
export const stopEverything = (event) => {
|
26
|
-
fire(event.target, "ujs:everythingStopped")
|
27
|
-
|
28
|
-
event.preventDefault()
|
29
|
-
event.stopPropagation()
|
30
|
-
|
31
|
-
return event.stopImmediatePropagation()
|
32
|
-
}
|
33
|
-
|
34
|
-
// Delegates events
|
35
|
-
// to a specified parent `element`, which fires event `handler`
|
36
|
-
// for the specified `selector` when an event of `eventType` is triggered
|
37
|
-
// element::
|
38
|
-
// parent element that will listen for events e.g. document
|
39
|
-
// selector::
|
40
|
-
// CSS selector; or an object that has `selector` and `exclude` properties (see: Rails.matches)
|
41
|
-
// eventType::
|
42
|
-
// string representing the event e.g. 'submit', 'click'
|
43
|
-
// handler::
|
44
|
-
// the event handler to be called
|
45
|
-
export const delegate = (element, selector, eventType, handler) =>
|
46
|
-
element.addEventListener(eventType, function (event) {
|
47
|
-
let { target } = event
|
48
|
-
|
49
|
-
while (!!(target instanceof Element) && !matches(target, selector)) {
|
50
|
-
target = target.parentNode
|
51
|
-
}
|
52
|
-
|
53
|
-
if (target instanceof Element && handler.call(target, event) === false) {
|
54
|
-
event.preventDefault()
|
55
|
-
return event.stopPropagation()
|
56
|
-
}
|
57
|
-
})
|