shoelace-rails 0.4.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
-
})
|