From 7631d63714f4c7b49600b60f1338ac511de6d86d Mon Sep 17 00:00:00 2001 From: Hartley McGuire Date: Wed, 6 Jul 2022 21:07:53 -0400 Subject: [PATCH 1/4] Move and rename rails-ujs files For consistency with Action Cable and Active Storage, the source files are moved to app/javascript. The files are also renamed in this commit (pre-conversion to javascript) so that git does a better job maintaining the history of the files. Ref 7b0b372 --- actionview/app/{assets/javascripts => javascript}/MIT-LICENSE | 0 actionview/app/{assets/javascripts => javascript}/README.md | 0 .../javascripts/rails-ujs.coffee => javascript/rails-ujs.js} | 0 .../app/{assets/javascripts => javascript}/rails-ujs/BANNER.js | 0 .../confirm.coffee => javascript/rails-ujs/features/confirm.js} | 0 .../disable.coffee => javascript/rails-ujs/features/disable.js} | 0 .../method.coffee => javascript/rails-ujs/features/method.js} | 0 .../remote.coffee => javascript/rails-ujs/features/remote.js} | 0 .../rails-ujs/start.coffee => javascript/rails-ujs/start.js} | 0 .../utils/ajax.coffee => javascript/rails-ujs/utils/ajax.js} | 0 .../utils/csp.coffee => javascript/rails-ujs/utils/csp.js} | 0 .../utils/csrf.coffee => javascript/rails-ujs/utils/csrf.js} | 0 .../utils/dom.coffee => javascript/rails-ujs/utils/dom.js} | 0 .../utils/event.coffee => javascript/rails-ujs/utils/event.js} | 0 .../utils/form.coffee => javascript/rails-ujs/utils/form.js} | 0 15 files changed, 0 insertions(+), 0 deletions(-) rename actionview/app/{assets/javascripts => javascript}/MIT-LICENSE (100%) rename actionview/app/{assets/javascripts => javascript}/README.md (100%) rename actionview/app/{assets/javascripts/rails-ujs.coffee => javascript/rails-ujs.js} (100%) rename actionview/app/{assets/javascripts => javascript}/rails-ujs/BANNER.js (100%) rename actionview/app/{assets/javascripts/rails-ujs/features/confirm.coffee => javascript/rails-ujs/features/confirm.js} (100%) rename actionview/app/{assets/javascripts/rails-ujs/features/disable.coffee => javascript/rails-ujs/features/disable.js} (100%) rename actionview/app/{assets/javascripts/rails-ujs/features/method.coffee => javascript/rails-ujs/features/method.js} (100%) rename actionview/app/{assets/javascripts/rails-ujs/features/remote.coffee => javascript/rails-ujs/features/remote.js} (100%) rename actionview/app/{assets/javascripts/rails-ujs/start.coffee => javascript/rails-ujs/start.js} (100%) rename actionview/app/{assets/javascripts/rails-ujs/utils/ajax.coffee => javascript/rails-ujs/utils/ajax.js} (100%) rename actionview/app/{assets/javascripts/rails-ujs/utils/csp.coffee => javascript/rails-ujs/utils/csp.js} (100%) rename actionview/app/{assets/javascripts/rails-ujs/utils/csrf.coffee => javascript/rails-ujs/utils/csrf.js} (100%) rename actionview/app/{assets/javascripts/rails-ujs/utils/dom.coffee => javascript/rails-ujs/utils/dom.js} (100%) rename actionview/app/{assets/javascripts/rails-ujs/utils/event.coffee => javascript/rails-ujs/utils/event.js} (100%) rename actionview/app/{assets/javascripts/rails-ujs/utils/form.coffee => javascript/rails-ujs/utils/form.js} (100%) diff --git a/actionview/app/assets/javascripts/MIT-LICENSE b/actionview/app/javascript/MIT-LICENSE similarity index 100% rename from actionview/app/assets/javascripts/MIT-LICENSE rename to actionview/app/javascript/MIT-LICENSE diff --git a/actionview/app/assets/javascripts/README.md b/actionview/app/javascript/README.md similarity index 100% rename from actionview/app/assets/javascripts/README.md rename to actionview/app/javascript/README.md diff --git a/actionview/app/assets/javascripts/rails-ujs.coffee b/actionview/app/javascript/rails-ujs.js similarity index 100% rename from actionview/app/assets/javascripts/rails-ujs.coffee rename to actionview/app/javascript/rails-ujs.js diff --git a/actionview/app/assets/javascripts/rails-ujs/BANNER.js b/actionview/app/javascript/rails-ujs/BANNER.js similarity index 100% rename from actionview/app/assets/javascripts/rails-ujs/BANNER.js rename to actionview/app/javascript/rails-ujs/BANNER.js diff --git a/actionview/app/assets/javascripts/rails-ujs/features/confirm.coffee b/actionview/app/javascript/rails-ujs/features/confirm.js similarity index 100% rename from actionview/app/assets/javascripts/rails-ujs/features/confirm.coffee rename to actionview/app/javascript/rails-ujs/features/confirm.js diff --git a/actionview/app/assets/javascripts/rails-ujs/features/disable.coffee b/actionview/app/javascript/rails-ujs/features/disable.js similarity index 100% rename from actionview/app/assets/javascripts/rails-ujs/features/disable.coffee rename to actionview/app/javascript/rails-ujs/features/disable.js diff --git a/actionview/app/assets/javascripts/rails-ujs/features/method.coffee b/actionview/app/javascript/rails-ujs/features/method.js similarity index 100% rename from actionview/app/assets/javascripts/rails-ujs/features/method.coffee rename to actionview/app/javascript/rails-ujs/features/method.js diff --git a/actionview/app/assets/javascripts/rails-ujs/features/remote.coffee b/actionview/app/javascript/rails-ujs/features/remote.js similarity index 100% rename from actionview/app/assets/javascripts/rails-ujs/features/remote.coffee rename to actionview/app/javascript/rails-ujs/features/remote.js diff --git a/actionview/app/assets/javascripts/rails-ujs/start.coffee b/actionview/app/javascript/rails-ujs/start.js similarity index 100% rename from actionview/app/assets/javascripts/rails-ujs/start.coffee rename to actionview/app/javascript/rails-ujs/start.js diff --git a/actionview/app/assets/javascripts/rails-ujs/utils/ajax.coffee b/actionview/app/javascript/rails-ujs/utils/ajax.js similarity index 100% rename from actionview/app/assets/javascripts/rails-ujs/utils/ajax.coffee rename to actionview/app/javascript/rails-ujs/utils/ajax.js diff --git a/actionview/app/assets/javascripts/rails-ujs/utils/csp.coffee b/actionview/app/javascript/rails-ujs/utils/csp.js similarity index 100% rename from actionview/app/assets/javascripts/rails-ujs/utils/csp.coffee rename to actionview/app/javascript/rails-ujs/utils/csp.js diff --git a/actionview/app/assets/javascripts/rails-ujs/utils/csrf.coffee b/actionview/app/javascript/rails-ujs/utils/csrf.js similarity index 100% rename from actionview/app/assets/javascripts/rails-ujs/utils/csrf.coffee rename to actionview/app/javascript/rails-ujs/utils/csrf.js diff --git a/actionview/app/assets/javascripts/rails-ujs/utils/dom.coffee b/actionview/app/javascript/rails-ujs/utils/dom.js similarity index 100% rename from actionview/app/assets/javascripts/rails-ujs/utils/dom.coffee rename to actionview/app/javascript/rails-ujs/utils/dom.js diff --git a/actionview/app/assets/javascripts/rails-ujs/utils/event.coffee b/actionview/app/javascript/rails-ujs/utils/event.js similarity index 100% rename from actionview/app/assets/javascripts/rails-ujs/utils/event.coffee rename to actionview/app/javascript/rails-ujs/utils/event.js diff --git a/actionview/app/assets/javascripts/rails-ujs/utils/form.coffee b/actionview/app/javascript/rails-ujs/utils/form.js similarity index 100% rename from actionview/app/assets/javascripts/rails-ujs/utils/form.coffee rename to actionview/app/javascript/rails-ujs/utils/form.js From 9c13efac3d3b948a202735d7d1085dc2a7ae380c Mon Sep 17 00:00:00 2001 From: Hartley McGuire Date: Wed, 6 Jul 2022 21:48:40 -0400 Subject: [PATCH 2/4] Run decaffeinate on rails-ujs/*.js Also run `eslint --fix app/javascript` with eslintrc from Active Storage --- actionview/app/javascript/rails-ujs.js | 65 ++++--- .../javascript/rails-ujs/features/confirm.js | 56 +++--- .../javascript/rails-ujs/features/disable.js | 166 +++++++++------- .../javascript/rails-ujs/features/method.js | 52 ++--- .../javascript/rails-ujs/features/remote.js | 164 +++++++++------- actionview/app/javascript/rails-ujs/start.js | 131 +++++++------ .../app/javascript/rails-ujs/utils/ajax.js | 182 ++++++++++-------- .../app/javascript/rails-ujs/utils/csp.js | 21 +- .../app/javascript/rails-ujs/utils/csrf.js | 51 +++-- .../app/javascript/rails-ujs/utils/dom.js | 65 ++++--- .../app/javascript/rails-ujs/utils/event.js | 122 +++++++----- .../app/javascript/rails-ujs/utils/form.js | 72 ++++--- 12 files changed, 654 insertions(+), 493 deletions(-) diff --git a/actionview/app/javascript/rails-ujs.js b/actionview/app/javascript/rails-ujs.js index 8b665280ce9..4f881c0b628 100644 --- a/actionview/app/javascript/rails-ujs.js +++ b/actionview/app/javascript/rails-ujs.js @@ -1,39 +1,46 @@ -#= require ./rails-ujs/BANNER -#= export Rails -#= require_self -#= require_tree ./rails-ujs/utils -#= require_tree ./rails-ujs/features -#= require ./rails-ujs/start +/* + * decaffeinate suggestions: + * DS208: Avoid top-level this + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md + */ +//= require ./rails-ujs/BANNER +//= export Rails +//= require_self +//= require_tree ./rails-ujs/utils +//= require_tree ./rails-ujs/features +//= require ./rails-ujs/start -@Rails = - # Link elements bound by rails-ujs - linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote]:not([disabled]), a[data-disable-with], a[data-disable]' +this.Rails = { + // Link elements bound by rails-ujs + linkClickSelector: "a[data-confirm], a[data-method], a[data-remote]:not([disabled]), a[data-disable-with], a[data-disable]", - # Button elements bound by rails-ujs - buttonClickSelector: - selector: 'button[data-remote]:not([form]), button[data-confirm]:not([form])' - exclude: 'form button' + // Button elements bound by rails-ujs + buttonClickSelector: { + selector: "button[data-remote]:not([form]), button[data-confirm]:not([form])", + exclude: "form button" + }, - # Select elements bound by rails-ujs - inputChangeSelector: 'select[data-remote], input[data-remote], textarea[data-remote]' + // Select elements bound by rails-ujs + inputChangeSelector: "select[data-remote], input[data-remote], textarea[data-remote]", - # Form elements bound by rails-ujs - formSubmitSelector: 'form:not([data-turbo=true])', + // Form elements bound by rails-ujs + formSubmitSelector: "form:not([data-turbo=true])", - # Form input elements bound by rails-ujs - 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])', + // Form input elements bound by rails-ujs + 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])", - # Form input elements disabled during form submission - 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' + // Form input elements disabled during form submission + 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", - # Form input elements re-enabled after form submission - 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' + // Form input elements re-enabled after form submission + 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", - # Form file input elements - fileInputSelector: 'input[name][type=file]:not([disabled])' + // Form file input elements + fileInputSelector: "input[name][type=file]:not([disabled])", - # Link onClick disable selector with possible re-enable after remote submission - linkDisableSelector: 'a[data-disable-with], a[data-disable]' + // Link onClick disable selector with possible re-enable after remote submission + linkDisableSelector: "a[data-disable-with], a[data-disable]", - # Button onClick disable selector with possible re-enable after remote submission - buttonDisableSelector: 'button[data-remote][data-disable-with], button[data-remote][data-disable]' + // Button onClick disable selector with possible re-enable after remote submission + buttonDisableSelector: "button[data-remote][data-disable-with], button[data-remote][data-disable]" +} diff --git a/actionview/app/javascript/rails-ujs/features/confirm.js b/actionview/app/javascript/rails-ujs/features/confirm.js index 0738ffcdc9a..f23e9e15aee 100644 --- a/actionview/app/javascript/rails-ujs/features/confirm.js +++ b/actionview/app/javascript/rails-ujs/features/confirm.js @@ -1,30 +1,38 @@ -#= require_tree ../utils +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md + */ +//= require_tree ../utils -{ fire, stopEverything } = Rails +const { fire, stopEverything } = Rails -Rails.handleConfirm = (e) -> - stopEverything(e) unless allowAction(this) +Rails.handleConfirm = function(e) { + if (!allowAction(this)) { return stopEverything(e) } +} -# Default confirm dialog, may be overridden with custom confirm dialog in Rails.confirm -Rails.confirm = (message, element) -> - confirm(message) +// Default confirm dialog, may be overridden with custom confirm dialog in Rails.confirm +Rails.confirm = (message, element) => confirm(message) -# For 'data-confirm' attribute: -# - Fires `confirm` event -# - Shows the confirmation dialog -# - Fires the `confirm:complete` event -# -# Returns `true` if no function stops the chain and user chose yes `false` otherwise. -# Attaching a handler to the element's `confirm` event that returns a `falsy` value cancels the confirmation dialog. -# Attaching a handler to the element's `confirm:complete` event that returns a `falsy` value makes this function -# return false. The `confirm:complete` event is fired whether or not the user answered true or false to the dialog. -allowAction = (element) -> - message = element.getAttribute('data-confirm') - return true unless message +// For 'data-confirm' attribute: +// - Fires `confirm` event +// - Shows the confirmation dialog +// - Fires the `confirm:complete` event +// +// Returns `true` if no function stops the chain and user chose yes `false` otherwise. +// Attaching a handler to the element's `confirm` event that returns a `falsy` value cancels the confirmation dialog. +// Attaching a handler to the element's `confirm:complete` event that returns a `falsy` value makes this function +// return false. The `confirm:complete` event is fired whether or not the user answered true or false to the dialog. +var allowAction = function(element) { + let callback + const message = element.getAttribute("data-confirm") + if (!message) { return true } - answer = false - if fire(element, 'confirm') - try answer = Rails.confirm(message, element) - callback = fire(element, 'confirm:complete', [answer]) + let answer = false + if (fire(element, "confirm")) { + try { answer = Rails.confirm(message, element) } catch (error) {} + callback = fire(element, "confirm:complete", [answer]) + } - answer and callback + return answer && callback +} diff --git a/actionview/app/javascript/rails-ujs/features/disable.js b/actionview/app/javascript/rails-ujs/features/disable.js index 4cfaead0788..28571d5cbaa 100644 --- a/actionview/app/javascript/rails-ujs/features/disable.js +++ b/actionview/app/javascript/rails-ujs/features/disable.js @@ -1,93 +1,115 @@ -#= require_tree ../utils +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md + */ +//= require_tree ../utils -{ matches, getData, setData, stopEverything, formElements } = Rails +const { matches, getData, setData, stopEverything, formElements } = Rails -Rails.handleDisabledElement = (e) -> - element = this - stopEverything(e) if element.disabled +Rails.handleDisabledElement = function(e) { + const element = this + if (element.disabled) { return stopEverything(e) } +} -# Unified function to enable an element (link, button and form) -Rails.enableElement = (e) -> - if e instanceof Event - return if isXhrRedirect(e) +// Unified function to enable an element (link, button and form) +Rails.enableElement = function(e) { + let element + if (e instanceof Event) { + if (isXhrRedirect(e)) { return } element = e.target - else + } else { element = e + } - if matches(element, Rails.linkDisableSelector) - enableLinkElement(element) - else if matches(element, Rails.buttonDisableSelector) or matches(element, Rails.formEnableSelector) - enableFormElement(element) - else if matches(element, Rails.formSubmitSelector) - enableFormElements(element) + if (matches(element, Rails.linkDisableSelector)) { + return enableLinkElement(element) + } else if (matches(element, Rails.buttonDisableSelector) || matches(element, Rails.formEnableSelector)) { + return enableFormElement(element) + } else if (matches(element, Rails.formSubmitSelector)) { + return enableFormElements(element) + } +} -# Unified function to disable an element (link, button and form) -Rails.disableElement = (e) -> - element = if e instanceof Event then e.target else e - if matches(element, Rails.linkDisableSelector) - disableLinkElement(element) - else if matches(element, Rails.buttonDisableSelector) or matches(element, Rails.formDisableSelector) - disableFormElement(element) - else if matches(element, Rails.formSubmitSelector) - disableFormElements(element) +// Unified function to disable an element (link, button and form) +Rails.disableElement = function(e) { + const element = e instanceof Event ? e.target : e + if (matches(element, Rails.linkDisableSelector)) { + return disableLinkElement(element) + } else if (matches(element, Rails.buttonDisableSelector) || matches(element, Rails.formDisableSelector)) { + return disableFormElement(element) + } else if (matches(element, Rails.formSubmitSelector)) { + return disableFormElements(element) + } +} -# Replace element's html with the 'data-disable-with' after storing original html -# and prevent clicking on it -disableLinkElement = (element) -> - return if getData(element, 'ujs:disabled') - replacement = element.getAttribute('data-disable-with') - if replacement? - setData(element, 'ujs:enable-with', element.innerHTML) # store enabled state +// Replace element's html with the 'data-disable-with' after storing original html +// and prevent clicking on it +var disableLinkElement = function(element) { + if (getData(element, "ujs:disabled")) { return } + const replacement = element.getAttribute("data-disable-with") + if (replacement != null) { + setData(element, "ujs:enable-with", element.innerHTML) // store enabled state element.innerHTML = replacement - element.addEventListener('click', stopEverything) # prevent further clicking - setData(element, 'ujs:disabled', true) + } + element.addEventListener("click", stopEverything) // prevent further clicking + return setData(element, "ujs:disabled", true) +} -# Restore element to its original state which was disabled by 'disableLinkElement' above -enableLinkElement = (element) -> - originalText = getData(element, 'ujs:enable-with') - if originalText? - element.innerHTML = originalText # set to old enabled state - setData(element, 'ujs:enable-with', null) # clean up cache - element.removeEventListener('click', stopEverything) # enable element - setData(element, 'ujs:disabled', null) +// Restore element to its original state which was disabled by 'disableLinkElement' above +var enableLinkElement = function(element) { + const originalText = getData(element, "ujs:enable-with") + if (originalText != null) { + element.innerHTML = originalText // set to old enabled state + setData(element, "ujs:enable-with", null) // clean up cache + } + element.removeEventListener("click", stopEverything) // enable element + return setData(element, "ujs:disabled", null) +} -# Disables form elements: -# - Caches element value in 'ujs:enable-with' data store -# - Replaces element text with value of 'data-disable-with' attribute -# - Sets disabled property to true -disableFormElements = (form) -> - formElements(form, Rails.formDisableSelector).forEach(disableFormElement) +// Disables form elements: +// - Caches element value in 'ujs:enable-with' data store +// - Replaces element text with value of 'data-disable-with' attribute +// - Sets disabled property to true +var disableFormElements = form => formElements(form, Rails.formDisableSelector).forEach(disableFormElement) -disableFormElement = (element) -> - return if getData(element, 'ujs:disabled') - replacement = element.getAttribute('data-disable-with') - if replacement? - if matches(element, 'button') - setData(element, 'ujs:enable-with', element.innerHTML) +var disableFormElement = function(element) { + if (getData(element, "ujs:disabled")) { return } + const replacement = element.getAttribute("data-disable-with") + if (replacement != null) { + if (matches(element, "button")) { + setData(element, "ujs:enable-with", element.innerHTML) element.innerHTML = replacement - else - setData(element, 'ujs:enable-with', element.value) + } else { + setData(element, "ujs:enable-with", element.value) element.value = replacement + } + } element.disabled = true - setData(element, 'ujs:disabled', true) + return setData(element, "ujs:disabled", true) +} -# Re-enables disabled form elements: -# - Replaces element text with cached value from 'ujs:enable-with' data store (created in `disableFormElements`) -# - Sets disabled property to false -enableFormElements = (form) -> - formElements(form, Rails.formEnableSelector).forEach(enableFormElement) +// Re-enables disabled form elements: +// - Replaces element text with cached value from 'ujs:enable-with' data store (created in `disableFormElements`) +// - Sets disabled property to false +var enableFormElements = form => formElements(form, Rails.formEnableSelector).forEach(enableFormElement) -enableFormElement = (element) -> - originalText = getData(element, 'ujs:enable-with') - if originalText? - if matches(element, 'button') +var enableFormElement = function(element) { + const originalText = getData(element, "ujs:enable-with") + if (originalText != null) { + if (matches(element, "button")) { element.innerHTML = originalText - else + } else { element.value = originalText - setData(element, 'ujs:enable-with', null) # clean up cache + } + setData(element, "ujs:enable-with", null) // clean up cache + } element.disabled = false - setData(element, 'ujs:disabled', null) + return setData(element, "ujs:disabled", null) +} -isXhrRedirect = (event) -> - xhr = event.detail?[0] - xhr?.getResponseHeader("X-Xhr-Redirect")? +var isXhrRedirect = function(event) { + const xhr = event.detail != null ? event.detail[0] : undefined + return ((xhr != null ? xhr.getResponseHeader("X-Xhr-Redirect") : undefined) != null) +} diff --git a/actionview/app/javascript/rails-ujs/features/method.js b/actionview/app/javascript/rails-ujs/features/method.js index d04d9414dda..5ed21a80d90 100644 --- a/actionview/app/javascript/rails-ujs/features/method.js +++ b/actionview/app/javascript/rails-ujs/features/method.js @@ -1,34 +1,42 @@ -#= require_tree ../utils +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md + */ +//= require_tree ../utils -{ stopEverything } = Rails +const { stopEverything } = Rails -# Handles "data-method" on links such as: -# Delete -Rails.handleMethod = (e) -> - link = this - method = link.getAttribute('data-method') - return unless method +// Handles "data-method" on links such as: +// Delete +Rails.handleMethod = function(e) { + const link = this + const method = link.getAttribute("data-method") + if (!method) { return } - href = Rails.href(link) - csrfToken = Rails.csrfToken() - csrfParam = Rails.csrfParam() - form = document.createElement('form') - formContent = "" + const href = Rails.href(link) + const csrfToken = Rails.csrfToken() + const csrfParam = Rails.csrfParam() + const form = document.createElement("form") + let formContent = `` - if csrfParam? and csrfToken? and not Rails.isCrossDomain(href) - formContent += "" + if ((csrfParam != null) && (csrfToken != null) && !Rails.isCrossDomain(href)) { + formContent += `` + } - # Must trigger submit by click on a button, else "submit" event handler won't work! - # https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/submit - formContent += '' + // Must trigger submit by click on a button, else "submit" event handler won't work! + // https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/submit + formContent += "" - form.method = 'post' + form.method = "post" form.action = href form.target = link.target form.innerHTML = formContent - form.style.display = 'none' + form.style.display = "none" document.body.appendChild(form) - form.querySelector('[type="submit"]').click() + form.querySelector("[type=\"submit\"]").click() - stopEverything(e) + return stopEverything(e) +} diff --git a/actionview/app/javascript/rails-ujs/features/remote.js b/actionview/app/javascript/rails-ujs/features/remote.js index b0f64cdee6f..599696fb520 100644 --- a/actionview/app/javascript/rails-ujs/features/remote.js +++ b/actionview/app/javascript/rails-ujs/features/remote.js @@ -1,93 +1,111 @@ -#= require_tree ../utils +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md + */ +//= require_tree ../utils -{ - matches, getData, setData - fire, stopEverything - ajax, isCrossDomain +const { + matches, getData, setData, + fire, stopEverything, + ajax, isCrossDomain, serializeElement } = Rails -# Checks "data-remote" if true to handle the request through a XHR request. -isRemote = (element) -> - value = element.getAttribute('data-remote') - value? and value isnt 'false' +// Checks "data-remote" if true to handle the request through a XHR request. +const isRemote = function(element) { + const value = element.getAttribute("data-remote") + return (value != null) && (value !== "false") +} -# Submits "remote" forms and links with ajax -Rails.handleRemote = (e) -> - element = this +// Submits "remote" forms and links with ajax +Rails.handleRemote = function(e) { + let data, method, url + const element = this - return true unless isRemote(element) - unless fire(element, 'ajax:before') - fire(element, 'ajax:stopped') + if (!isRemote(element)) { return true } + if (!fire(element, "ajax:before")) { + fire(element, "ajax:stopped") return false + } - withCredentials = element.getAttribute('data-with-credentials') - dataType = element.getAttribute('data-type') or 'script' + const withCredentials = element.getAttribute("data-with-credentials") + const dataType = element.getAttribute("data-type") || "script" - if matches(element, Rails.formSubmitSelector) - # memoized value from clicked submit button - button = getData(element, 'ujs:submit-button') - method = getData(element, 'ujs:submit-button-formmethod') or element.getAttribute('method') or "get" - url = getData(element, 'ujs:submit-button-formaction') or element.getAttribute('action') or location.href + if (matches(element, Rails.formSubmitSelector)) { + // memoized value from clicked submit button + const button = getData(element, "ujs:submit-button") + method = getData(element, "ujs:submit-button-formmethod") || element.getAttribute("method") || "get" + url = getData(element, "ujs:submit-button-formaction") || element.getAttribute("action") || location.href - # strip query string if it's a GET request - url = url.replace(/\?.*$/, '') if method.toUpperCase() is 'GET' + // strip query string if it's a GET request + if (method.toUpperCase() === "GET") { url = url.replace(/\?.*$/, "") } - if element.enctype is 'multipart/form-data' + if (element.enctype === "multipart/form-data") { data = new FormData(element) - data.append(button.name, button.value) if button? - else + if (button != null) { data.append(button.name, button.value) } + } else { data = serializeElement(element, button) + } - setData(element, 'ujs:submit-button', null) - setData(element, 'ujs:submit-button-formmethod', null) - setData(element, 'ujs:submit-button-formaction', null) - else if matches(element, Rails.buttonClickSelector) or matches(element, Rails.inputChangeSelector) - method = element.getAttribute('data-method') - url = element.getAttribute('data-url') - data = serializeElement(element, element.getAttribute('data-params')) - else - method = element.getAttribute('data-method') + setData(element, "ujs:submit-button", null) + setData(element, "ujs:submit-button-formmethod", null) + setData(element, "ujs:submit-button-formaction", null) + } else if (matches(element, Rails.buttonClickSelector) || matches(element, Rails.inputChangeSelector)) { + method = element.getAttribute("data-method") + url = element.getAttribute("data-url") + data = serializeElement(element, element.getAttribute("data-params")) + } else { + method = element.getAttribute("data-method") url = Rails.href(element) - data = element.getAttribute('data-params') + data = element.getAttribute("data-params") + } - ajax( - type: method or 'GET' - url: url - data: data - dataType: dataType - # stopping the "ajax:beforeSend" event will cancel the ajax request - beforeSend: (xhr, options) -> - if fire(element, 'ajax:beforeSend', [xhr, options]) - fire(element, 'ajax:send', [xhr]) - else - fire(element, 'ajax:stopped') + ajax({ + type: method || "GET", + url, + data, + dataType, + // stopping the "ajax:beforeSend" event will cancel the ajax request + beforeSend(xhr, options) { + if (fire(element, "ajax:beforeSend", [xhr, options])) { + return fire(element, "ajax:send", [xhr]) + } else { + fire(element, "ajax:stopped") return false - success: (args...) -> fire(element, 'ajax:success', args) - error: (args...) -> fire(element, 'ajax:error', args) - complete: (args...) -> fire(element, 'ajax:complete', args) - crossDomain: isCrossDomain(url) - withCredentials: withCredentials? and withCredentials isnt 'false' - ) - stopEverything(e) + } + }, + success(...args) { return fire(element, "ajax:success", args) }, + error(...args) { return fire(element, "ajax:error", args) }, + complete(...args) { return fire(element, "ajax:complete", args) }, + crossDomain: isCrossDomain(url), + withCredentials: (withCredentials != null) && (withCredentials !== "false") + }) + return stopEverything(e) +} -Rails.formSubmitButtonClick = (e) -> - button = this - form = button.form - return unless form - # Register the pressed submit button - setData(form, 'ujs:submit-button', name: button.name, value: button.value) if button.name - # Save attributes from button - setData(form, 'ujs:formnovalidate-button', button.formNoValidate) - setData(form, 'ujs:submit-button-formaction', button.getAttribute('formaction')) - setData(form, 'ujs:submit-button-formmethod', button.getAttribute('formmethod')) +Rails.formSubmitButtonClick = function(e) { + const button = this + const { + form + } = button + if (!form) { return } + // Register the pressed submit button + if (button.name) { setData(form, "ujs:submit-button", {name: button.name, value: button.value}) } + // Save attributes from button + setData(form, "ujs:formnovalidate-button", button.formNoValidate) + setData(form, "ujs:submit-button-formaction", button.getAttribute("formaction")) + return setData(form, "ujs:submit-button-formmethod", button.getAttribute("formmethod")) +} -Rails.preventInsignificantClick = (e) -> - link = this - method = (link.getAttribute('data-method') or 'GET').toUpperCase() - data = link.getAttribute('data-params') - metaClick = e.metaKey or e.ctrlKey - insignificantMetaClick = metaClick and method is 'GET' and not data - nonPrimaryMouseClick = e.button? and e.button isnt 0 - e.stopImmediatePropagation() if nonPrimaryMouseClick or insignificantMetaClick +Rails.preventInsignificantClick = function(e) { + const link = this + const method = (link.getAttribute("data-method") || "GET").toUpperCase() + const data = link.getAttribute("data-params") + const metaClick = e.metaKey || e.ctrlKey + const insignificantMetaClick = metaClick && (method === "GET") && !data + const nonPrimaryMouseClick = (e.button != null) && (e.button !== 0) + if (nonPrimaryMouseClick || insignificantMetaClick) { return e.stopImmediatePropagation() } +} diff --git a/actionview/app/javascript/rails-ujs/start.js b/actionview/app/javascript/rails-ujs/start.js index 03470581955..967fd570768 100644 --- a/actionview/app/javascript/rails-ujs/start.js +++ b/actionview/app/javascript/rails-ujs/start.js @@ -1,75 +1,88 @@ -{ - fire, delegate - getData, $ - refreshCSRFTokens, CSRFProtection - loadCSPNonce - enableElement, disableElement, handleDisabledElement - handleConfirm, preventInsignificantClick +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md + */ +const { + fire, delegate, + getData, $, + refreshCSRFTokens, CSRFProtection, + loadCSPNonce, + enableElement, disableElement, handleDisabledElement, + handleConfirm, preventInsignificantClick, handleRemote, formSubmitButtonClick, handleMethod } = Rails -# For backward compatibility -if jQuery? and jQuery.ajax? - throw new Error('If you load both jquery_ujs and rails-ujs, use rails-ujs only.') if jQuery.rails +// For backward compatibility +if ((typeof jQuery !== "undefined" && jQuery !== null) && (jQuery.ajax != null)) { + if (jQuery.rails) { throw new Error("If you load both jquery_ujs and rails-ujs, use rails-ujs only.") } jQuery.rails = Rails - jQuery.ajaxPrefilter (options, originalOptions, xhr) -> - CSRFProtection(xhr) unless options.crossDomain + jQuery.ajaxPrefilter(function(options, originalOptions, xhr) { + if (!options.crossDomain) { return CSRFProtection(xhr) } + }) +} -Rails.start = -> - # Cut down on the number of issues from people inadvertently including - # rails-ujs twice by detecting and raising an error when it happens. - throw new Error('rails-ujs has already been loaded!') if window._rails_loaded +Rails.start = function() { + // Cut down on the number of issues from people inadvertently including + // rails-ujs twice by detecting and raising an error when it happens. + if (window._rails_loaded) { throw new Error("rails-ujs has already been loaded!") } - # This event works the same as the load event, except that it fires every - # time the page is loaded. - # See https://github.com/rails/jquery-ujs/issues/357 - # See https://developer.mozilla.org/en-US/docs/Using_Firefox_1.5_caching - window.addEventListener 'pageshow', -> - $(Rails.formEnableSelector).forEach (el) -> - enableElement(el) if getData(el, 'ujs:disabled') - $(Rails.linkDisableSelector).forEach (el) -> - enableElement(el) if getData(el, 'ujs:disabled') + // This event works the same as the load event, except that it fires every + // time the page is loaded. + // See https://github.com/rails/jquery-ujs/issues/357 + // See https://developer.mozilla.org/en-US/docs/Using_Firefox_1.5_caching + window.addEventListener("pageshow", function() { + $(Rails.formEnableSelector).forEach(function(el) { + if (getData(el, "ujs:disabled")) { return enableElement(el) } + }) + return $(Rails.linkDisableSelector).forEach(function(el) { + if (getData(el, "ujs:disabled")) { return enableElement(el) } + }) + }) - delegate document, Rails.linkDisableSelector, 'ajax:complete', enableElement - delegate document, Rails.linkDisableSelector, 'ajax:stopped', enableElement - delegate document, Rails.buttonDisableSelector, 'ajax:complete', enableElement - delegate document, Rails.buttonDisableSelector, 'ajax:stopped', enableElement + delegate(document, Rails.linkDisableSelector, "ajax:complete", enableElement) + delegate(document, Rails.linkDisableSelector, "ajax:stopped", enableElement) + delegate(document, Rails.buttonDisableSelector, "ajax:complete", enableElement) + delegate(document, Rails.buttonDisableSelector, "ajax:stopped", enableElement) - delegate document, Rails.linkClickSelector, 'click', preventInsignificantClick - delegate document, Rails.linkClickSelector, 'click', handleDisabledElement - delegate document, Rails.linkClickSelector, 'click', handleConfirm - delegate document, Rails.linkClickSelector, 'click', disableElement - delegate document, Rails.linkClickSelector, 'click', handleRemote - delegate document, Rails.linkClickSelector, 'click', handleMethod + delegate(document, Rails.linkClickSelector, "click", preventInsignificantClick) + delegate(document, Rails.linkClickSelector, "click", handleDisabledElement) + delegate(document, Rails.linkClickSelector, "click", handleConfirm) + delegate(document, Rails.linkClickSelector, "click", disableElement) + delegate(document, Rails.linkClickSelector, "click", handleRemote) + delegate(document, Rails.linkClickSelector, "click", handleMethod) - delegate document, Rails.buttonClickSelector, 'click', preventInsignificantClick - delegate document, Rails.buttonClickSelector, 'click', handleDisabledElement - delegate document, Rails.buttonClickSelector, 'click', handleConfirm - delegate document, Rails.buttonClickSelector, 'click', disableElement - delegate document, Rails.buttonClickSelector, 'click', handleRemote + delegate(document, Rails.buttonClickSelector, "click", preventInsignificantClick) + delegate(document, Rails.buttonClickSelector, "click", handleDisabledElement) + delegate(document, Rails.buttonClickSelector, "click", handleConfirm) + delegate(document, Rails.buttonClickSelector, "click", disableElement) + delegate(document, Rails.buttonClickSelector, "click", handleRemote) - delegate document, Rails.inputChangeSelector, 'change', handleDisabledElement - delegate document, Rails.inputChangeSelector, 'change', handleConfirm - delegate document, Rails.inputChangeSelector, 'change', handleRemote + delegate(document, Rails.inputChangeSelector, "change", handleDisabledElement) + delegate(document, Rails.inputChangeSelector, "change", handleConfirm) + delegate(document, Rails.inputChangeSelector, "change", handleRemote) - delegate document, Rails.formSubmitSelector, 'submit', handleDisabledElement - delegate document, Rails.formSubmitSelector, 'submit', handleConfirm - delegate document, Rails.formSubmitSelector, 'submit', handleRemote - # Normal mode submit - # Slight timeout so that the submit button gets properly serialized - delegate document, Rails.formSubmitSelector, 'submit', (e) -> setTimeout((-> disableElement(e)), 13) - delegate document, Rails.formSubmitSelector, 'ajax:send', disableElement - delegate document, Rails.formSubmitSelector, 'ajax:complete', enableElement + delegate(document, Rails.formSubmitSelector, "submit", handleDisabledElement) + delegate(document, Rails.formSubmitSelector, "submit", handleConfirm) + delegate(document, Rails.formSubmitSelector, "submit", handleRemote) + // Normal mode submit + // Slight timeout so that the submit button gets properly serialized + delegate(document, Rails.formSubmitSelector, "submit", e => setTimeout((() => disableElement(e)), 13)) + delegate(document, Rails.formSubmitSelector, "ajax:send", disableElement) + delegate(document, Rails.formSubmitSelector, "ajax:complete", enableElement) - delegate document, Rails.formInputClickSelector, 'click', preventInsignificantClick - delegate document, Rails.formInputClickSelector, 'click', handleDisabledElement - delegate document, Rails.formInputClickSelector, 'click', handleConfirm - delegate document, Rails.formInputClickSelector, 'click', formSubmitButtonClick + delegate(document, Rails.formInputClickSelector, "click", preventInsignificantClick) + delegate(document, Rails.formInputClickSelector, "click", handleDisabledElement) + delegate(document, Rails.formInputClickSelector, "click", handleConfirm) + delegate(document, Rails.formInputClickSelector, "click", formSubmitButtonClick) - document.addEventListener('DOMContentLoaded', refreshCSRFTokens) - document.addEventListener('DOMContentLoaded', loadCSPNonce) - window._rails_loaded = true + document.addEventListener("DOMContentLoaded", refreshCSRFTokens) + document.addEventListener("DOMContentLoaded", loadCSPNonce) + return window._rails_loaded = true +} -if window.Rails is Rails and fire(document, 'rails:attachBindings') +if ((window.Rails === Rails) && fire(document, "rails:attachBindings")) { Rails.start() +} diff --git a/actionview/app/javascript/rails-ujs/utils/ajax.js b/actionview/app/javascript/rails-ujs/utils/ajax.js index 645144022c0..048d43ece53 100644 --- a/actionview/app/javascript/rails-ujs/utils/ajax.js +++ b/actionview/app/javascript/rails-ujs/utils/ajax.js @@ -1,98 +1,126 @@ -#= require ./csp -#= require ./csrf -#= require ./event +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md + */ +//= require ./csp +//= require ./csrf +//= require ./event -{ cspNonce, CSRFProtection, fire } = Rails +const { cspNonce, CSRFProtection, fire } = Rails -AcceptHeaders = - '*': '*/*' - text: 'text/plain' - html: 'text/html' - xml: 'application/xml, text/xml' - json: 'application/json, text/javascript' - script: 'text/javascript, application/javascript, application/ecmascript, application/x-ecmascript' +const AcceptHeaders = { + "*": "*/*", + text: "text/plain", + html: "text/html", + xml: "application/xml, text/xml", + json: "application/json, text/javascript", + script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript" +} -Rails.ajax = (options) -> +Rails.ajax = function(options) { options = prepareOptions(options) - xhr = createXHR options, -> - response = processResponse(xhr.response ? xhr.responseText, xhr.getResponseHeader('Content-Type')) - if xhr.status // 100 == 2 - options.success?(response, xhr.statusText, xhr) - else - options.error?(response, xhr.statusText, xhr) - options.complete?(xhr, xhr.statusText) + var xhr = createXHR(options, function() { + const response = processResponse(xhr.response != null ? xhr.response : xhr.responseText, xhr.getResponseHeader("Content-Type")) + if (Math.floor(xhr.status / 100) === 2) { + if (typeof options.success === "function") { + options.success(response, xhr.statusText, xhr) + } + } else { + if (typeof options.error === "function") { + options.error(response, xhr.statusText, xhr) + } + } + return (typeof options.complete === "function" ? options.complete(xhr, xhr.statusText) : undefined) + }) - if options.beforeSend? && !options.beforeSend(xhr, options) + if ((options.beforeSend != null) && !options.beforeSend(xhr, options)) { return false + } - if xhr.readyState is XMLHttpRequest.OPENED - xhr.send(options.data) + if (xhr.readyState === XMLHttpRequest.OPENED) { + return xhr.send(options.data) + } +} -prepareOptions = (options) -> - options.url = options.url or location.href +var prepareOptions = function(options) { + options.url = options.url || location.href options.type = options.type.toUpperCase() - # append data to url if it's a GET request - if options.type is 'GET' and options.data - if options.url.indexOf('?') < 0 - options.url += '?' + options.data - else - options.url += '&' + options.data - # Use "*" as default dataType - options.dataType = '*' unless AcceptHeaders[options.dataType]? + // append data to url if it's a GET request + if ((options.type === "GET") && options.data) { + if (options.url.indexOf("?") < 0) { + options.url += "?" + options.data + } else { + options.url += "&" + options.data + } + } + // Use "*" as default dataType + if (AcceptHeaders[options.dataType] == null) { options.dataType = "*" } options.accept = AcceptHeaders[options.dataType] - options.accept += ', */*; q=0.01' if options.dataType isnt '*' - options + if (options.dataType !== "*") { options.accept += ", */*; q=0.01" } + return options +} -createXHR = (options, done) -> - xhr = new XMLHttpRequest() - # Open and set up xhr +var createXHR = function(options, done) { + const xhr = new XMLHttpRequest() + // Open and set up xhr xhr.open(options.type, options.url, true) - xhr.setRequestHeader('Accept', options.accept) - # Set Content-Type only when sending a string - # Sending FormData will automatically set Content-Type to multipart/form-data - if typeof options.data is 'string' - xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8') - unless options.crossDomain - xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest') - # Add X-CSRF-Token + xhr.setRequestHeader("Accept", options.accept) + // Set Content-Type only when sending a string + // Sending FormData will automatically set Content-Type to multipart/form-data + if (typeof options.data === "string") { + xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8") + } + if (!options.crossDomain) { + xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest") + // Add X-CSRF-Token CSRFProtection(xhr) + } xhr.withCredentials = !!options.withCredentials - xhr.onreadystatechange = -> - done(xhr) if xhr.readyState is XMLHttpRequest.DONE - xhr + xhr.onreadystatechange = function() { + if (xhr.readyState === XMLHttpRequest.DONE) { return done(xhr) } + } + return xhr +} -processResponse = (response, type) -> - if typeof response is 'string' and typeof type is 'string' - if type.match(/\bjson\b/) - try response = JSON.parse(response) - else if type.match(/\b(?:java|ecma)script\b/) - script = document.createElement('script') - script.setAttribute('nonce', cspNonce()) +var processResponse = function(response, type) { + if ((typeof response === "string") && (typeof type === "string")) { + if (type.match(/\bjson\b/)) { + try { response = JSON.parse(response) } catch (error) {} + } else if (type.match(/\b(?:java|ecma)script\b/)) { + const script = document.createElement("script") + script.setAttribute("nonce", cspNonce()) script.text = response document.head.appendChild(script).parentNode.removeChild(script) - else if type.match(/\b(xml|html|svg)\b/) - parser = new DOMParser() - type = type.replace(/;.+/, '') # remove something like ';charset=utf-8' - try response = parser.parseFromString(response, type) - response + } else if (type.match(/\b(xml|html|svg)\b/)) { + const parser = new DOMParser() + type = type.replace(/;.+/, "") // remove something like ';charset=utf-8' + try { response = parser.parseFromString(response, type) } catch (error1) {} + } + } + return response +} -# Default way to get an element's href. May be overridden at Rails.href. -Rails.href = (element) -> element.href +// Default way to get an element's href. May be overridden at Rails.href. +Rails.href = element => element.href -# Determines if the request is a cross domain request. -Rails.isCrossDomain = (url) -> - originAnchor = document.createElement('a') +// Determines if the request is a cross domain request. +Rails.isCrossDomain = function(url) { + const originAnchor = document.createElement("a") originAnchor.href = location.href - urlAnchor = document.createElement('a') - try + const urlAnchor = document.createElement("a") + try { urlAnchor.href = url - # If URL protocol is false or is a string containing a single colon - # *and* host are false, assume it is not a cross-domain request - # (should only be the case for IE7 and IE compatibility mode). - # Otherwise, evaluate protocol and host of the URL against the origin - # protocol and host. - !(((!urlAnchor.protocol || urlAnchor.protocol == ':') && !urlAnchor.host) || - (originAnchor.protocol + '//' + originAnchor.host == urlAnchor.protocol + '//' + urlAnchor.host)) - catch e - # If there is an error parsing the URL, assume it is crossDomain. - true + // If URL protocol is false or is a string containing a single colon + // *and* host are false, assume it is not a cross-domain request + // (should only be the case for IE7 and IE compatibility mode). + // Otherwise, evaluate protocol and host of the URL against the origin + // protocol and host. + return !(((!urlAnchor.protocol || (urlAnchor.protocol === ":")) && !urlAnchor.host) || + ((originAnchor.protocol + "//" + originAnchor.host) === (urlAnchor.protocol + "//" + urlAnchor.host))) + } catch (e) { + // If there is an error parsing the URL, assume it is crossDomain. + return true + } +} diff --git a/actionview/app/javascript/rails-ujs/utils/csp.js b/actionview/app/javascript/rails-ujs/utils/csp.js index a33f531375d..5b0a353fa98 100644 --- a/actionview/app/javascript/rails-ujs/utils/csp.js +++ b/actionview/app/javascript/rails-ujs/utils/csp.js @@ -1,8 +1,17 @@ -nonce = null +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS103: Rewrite code to no longer use __guard__, or convert again using --optional-chaining + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md + */ +let nonce = null -Rails.loadCSPNonce = -> - nonce = document.querySelector("meta[name=csp-nonce]")?.content +Rails.loadCSPNonce = () => nonce = __guard__(document.querySelector("meta[name=csp-nonce]"), x => x.content) -# Returns the Content-Security-Policy nonce for inline scripts. -Rails.cspNonce = -> - nonce ? Rails.loadCSPNonce() +// Returns the Content-Security-Policy nonce for inline scripts. +Rails.cspNonce = () => nonce != null ? nonce : Rails.loadCSPNonce() + +function __guard__(value, transform) { + return (typeof value !== "undefined" && value !== null) ? transform(value) : undefined +} \ No newline at end of file diff --git a/actionview/app/javascript/rails-ujs/utils/csrf.js b/actionview/app/javascript/rails-ujs/utils/csrf.js index 4eb5ebb414b..33d15467df6 100644 --- a/actionview/app/javascript/rails-ujs/utils/csrf.js +++ b/actionview/app/javascript/rails-ujs/utils/csrf.js @@ -1,25 +1,36 @@ -#= require ./dom +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md + */ +//= require ./dom -{ $ } = Rails +const { $ } = Rails -# Up-to-date Cross-Site Request Forgery token -csrfToken = Rails.csrfToken = -> - meta = document.querySelector('meta[name=csrf-token]') - meta and meta.content +// Up-to-date Cross-Site Request Forgery token +const csrfToken = (Rails.csrfToken = function() { + const meta = document.querySelector("meta[name=csrf-token]") + return meta && meta.content +}) -# URL param that must contain the CSRF token -csrfParam = Rails.csrfParam = -> - meta = document.querySelector('meta[name=csrf-param]') - meta and meta.content +// URL param that must contain the CSRF token +const csrfParam = (Rails.csrfParam = function() { + const meta = document.querySelector("meta[name=csrf-param]") + return meta && meta.content +}) -# Make sure that every Ajax request sends the CSRF token -Rails.CSRFProtection = (xhr) -> - token = csrfToken() - xhr.setRequestHeader('X-CSRF-Token', token) if token? +// Make sure that every Ajax request sends the CSRF token +Rails.CSRFProtection = function(xhr) { + const token = csrfToken() + if (token != null) { return xhr.setRequestHeader("X-CSRF-Token", token) } +} -# Make sure that all forms have actual up-to-date tokens (cached forms contain old ones) -Rails.refreshCSRFTokens = -> - token = csrfToken() - param = csrfParam() - if token? and param? - $('form input[name="' + param + '"]').forEach (input) -> input.value = token +// Make sure that all forms have actual up-to-date tokens (cached forms contain old ones) +Rails.refreshCSRFTokens = function() { + const token = csrfToken() + const param = csrfParam() + if ((token != null) && (param != null)) { + return $("form input[name=\"" + param + "\"]").forEach(input => input.value = token) + } +} diff --git a/actionview/app/javascript/rails-ujs/utils/dom.js b/actionview/app/javascript/rails-ujs/utils/dom.js index a760f272997..a4c320b3e95 100644 --- a/actionview/app/javascript/rails-ujs/utils/dom.js +++ b/actionview/app/javascript/rails-ujs/utils/dom.js @@ -1,35 +1,42 @@ -m = Element.prototype.matches or - Element.prototype.matchesSelector or - Element.prototype.mozMatchesSelector or - Element.prototype.msMatchesSelector or - Element.prototype.oMatchesSelector or +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md + */ +const m = Element.prototype.matches || + Element.prototype.matchesSelector || + Element.prototype.mozMatchesSelector || + Element.prototype.msMatchesSelector || + Element.prototype.oMatchesSelector || Element.prototype.webkitMatchesSelector -# Checks if the given native dom element matches the selector -# element:: -# native DOM element -# selector:: -# CSS selector string or -# a JavaScript object with `selector` and `exclude` properties -# Examples: "form", { selector: "form", exclude: "form[data-remote='true']"} -Rails.matches = (element, selector) -> - if selector.exclude? - m.call(element, selector.selector) and not m.call(element, selector.exclude) - else - m.call(element, selector) +// Checks if the given native dom element matches the selector +// element:: +// native DOM element +// selector:: +// CSS selector string or +// a JavaScript object with `selector` and `exclude` properties +// Examples: "form", { selector: "form", exclude: "form[data-remote='true']"} +Rails.matches = function(element, selector) { + if (selector.exclude != null) { + return m.call(element, selector.selector) && !m.call(element, selector.exclude) + } else { + return m.call(element, selector) + } +} -# get and set data on a given element using "expando properties" -# See: https://developer.mozilla.org/en-US/docs/Glossary/Expando -expando = '_ujsData' +// get and set data on a given element using "expando properties" +// See: https://developer.mozilla.org/en-US/docs/Glossary/Expando +const expando = "_ujsData" -Rails.getData = (element, key) -> - element[expando]?[key] +Rails.getData = (element, key) => element[expando] != null ? element[expando][key] : undefined -Rails.setData = (element, key, value) -> - element[expando] ?= {} - element[expando][key] = value +Rails.setData = function(element, key, value) { + if (element[expando] == null) { element[expando] = {} } + return element[expando][key] = value +} -# a wrapper for document.querySelectorAll -# returns an Array -Rails.$ = (selector) -> - Array.prototype.slice.call(document.querySelectorAll(selector)) +// a wrapper for document.querySelectorAll +// returns an Array +Rails.$ = selector => Array.prototype.slice.call(document.querySelectorAll(selector)) diff --git a/actionview/app/javascript/rails-ujs/utils/event.js b/actionview/app/javascript/rails-ujs/utils/event.js index 60c2c49c3e7..a6962430dc1 100644 --- a/actionview/app/javascript/rails-ujs/utils/event.js +++ b/actionview/app/javascript/rails-ujs/utils/event.js @@ -1,68 +1,86 @@ -#= require ./dom +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md + */ +//= require ./dom -{ matches } = Rails +let preventDefault +const { matches } = Rails -# Polyfill for CustomEvent in IE9+ -# https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#Polyfill -CustomEvent = window.CustomEvent +// Polyfill for CustomEvent in IE9+ +// https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#Polyfill +let { + CustomEvent +} = window -if typeof CustomEvent isnt 'function' - CustomEvent = (event, params) -> - evt = document.createEvent('CustomEvent') +if (typeof CustomEvent !== "function") { + CustomEvent = function(event, params) { + const evt = document.createEvent("CustomEvent") evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail) - evt + return evt + } - CustomEvent.prototype = window.Event.prototype + CustomEvent.prototype = window.Event.prototype; - # Fix setting `defaultPrevented` when `preventDefault()` is called - # http://stackoverflow.com/questions/23349191/event-preventdefault-is-not-working-in-ie-11-for-custom-events - { preventDefault } = CustomEvent.prototype - CustomEvent.prototype.preventDefault = -> - result = preventDefault.call(this) - if @cancelable and not @defaultPrevented - Object.defineProperty(this, 'defaultPrevented', get: -> true) - result + // Fix setting `defaultPrevented` when `preventDefault()` is called + // http://stackoverflow.com/questions/23349191/event-preventdefault-is-not-working-in-ie-11-for-custom-events + ({ preventDefault } = CustomEvent.prototype) + CustomEvent.prototype.preventDefault = function() { + const result = preventDefault.call(this) + if (this.cancelable && !this.defaultPrevented) { + Object.defineProperty(this, "defaultPrevented", {get() { return true }}) + } + return result + } +} -# Triggers a custom event on an element and returns false if the event result is false -# obj:: -# a native DOM element -# name:: -# string that corresponds to the event you want to trigger -# e.g. 'click', 'submit' -# data:: -# data you want to pass when you dispatch an event -fire = Rails.fire = (obj, name, data) -> - event = new CustomEvent( - name, +// Triggers a custom event on an element and returns false if the event result is false +// obj:: +// a native DOM element +// name:: +// string that corresponds to the event you want to trigger +// e.g. 'click', 'submit' +// data:: +// data you want to pass when you dispatch an event +const fire = (Rails.fire = function(obj, name, data) { + const event = new CustomEvent( + name, { bubbles: true, cancelable: true, detail: data, + } ) obj.dispatchEvent(event) - !event.defaultPrevented + return !event.defaultPrevented +}) -# Helper function, needed to provide consistent behavior in IE -Rails.stopEverything = (e) -> - fire(e.target, 'ujs:everythingStopped') +// Helper function, needed to provide consistent behavior in IE +Rails.stopEverything = function(e) { + fire(e.target, "ujs:everythingStopped") e.preventDefault() e.stopPropagation() - e.stopImmediatePropagation() + return e.stopImmediatePropagation() +} -# Delegates events -# to a specified parent `element`, which fires event `handler` -# for the specified `selector` when an event of `eventType` is triggered -# element:: -# parent element that will listen for events e.g. document -# selector:: -# CSS selector; or an object that has `selector` and `exclude` properties (see: Rails.matches) -# eventType:: -# string representing the event e.g. 'submit', 'click' -# handler:: -# the event handler to be called -Rails.delegate = (element, selector, eventType, handler) -> - element.addEventListener eventType, (e) -> - target = e.target - target = target.parentNode until not (target instanceof Element) or matches(target, selector) - if target instanceof Element and handler.call(target, e) == false - e.preventDefault() - e.stopPropagation() +// Delegates events +// to a specified parent `element`, which fires event `handler` +// for the specified `selector` when an event of `eventType` is triggered +// element:: +// parent element that will listen for events e.g. document +// selector:: +// CSS selector; or an object that has `selector` and `exclude` properties (see: Rails.matches) +// eventType:: +// string representing the event e.g. 'submit', 'click' +// handler:: +// the event handler to be called +Rails.delegate = (element, selector, eventType, handler) => element.addEventListener(eventType, function(e) { + let { + target + } = e + while (!!(target instanceof Element) && !matches(target, selector)) { target = target.parentNode } + if (target instanceof Element && (handler.call(target, e) === false)) { + e.preventDefault() + return e.stopPropagation() + } +}) diff --git a/actionview/app/javascript/rails-ujs/utils/form.js b/actionview/app/javascript/rails-ujs/utils/form.js index 0c706842080..9b49a47f6c2 100644 --- a/actionview/app/javascript/rails-ujs/utils/form.js +++ b/actionview/app/javascript/rails-ujs/utils/form.js @@ -1,37 +1,49 @@ -#= require ./dom +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md + */ +//= require ./dom -{ matches } = Rails +const { matches } = Rails -toArray = (e) -> Array.prototype.slice.call(e) +const toArray = e => Array.prototype.slice.call(e) -Rails.serializeElement = (element, additionalParam) -> - inputs = [element] - inputs = toArray(element.elements) if matches(element, 'form') - params = [] +Rails.serializeElement = function(element, additionalParam) { + let inputs = [element] + if (matches(element, "form")) { inputs = toArray(element.elements) } + const params = [] - inputs.forEach (input) -> - return if !input.name || input.disabled - return if matches(input, 'fieldset[disabled] *') - if matches(input, 'select') - toArray(input.options).forEach (option) -> - params.push(name: input.name, value: option.value) if option.selected - else if input.checked or ['radio', 'checkbox', 'submit'].indexOf(input.type) == -1 - params.push(name: input.name, value: input.value) + inputs.forEach(function(input) { + if (!input.name || input.disabled) { return } + if (matches(input, "fieldset[disabled] *")) { return } + if (matches(input, "select")) { + return toArray(input.options).forEach(function(option) { + if (option.selected) { return params.push({name: input.name, value: option.value}) } + }) + } else if (input.checked || (["radio", "checkbox", "submit"].indexOf(input.type) === -1)) { + return params.push({name: input.name, value: input.value}) + } + }) - params.push(additionalParam) if additionalParam + if (additionalParam) { params.push(additionalParam) } - params.map (param) -> - if param.name? - "#{encodeURIComponent(param.name)}=#{encodeURIComponent(param.value)}" - else - param - .join('&') + return params.map(function(param) { + if (param.name != null) { + return `${encodeURIComponent(param.name)}=${encodeURIComponent(param.value)}` + } else { + return param + }}).join("&") +} -# Helper function that returns form elements that match the specified CSS selector -# If form is actually a "form" element this will return associated elements outside the from that have -# the html form attribute set -Rails.formElements = (form, selector) -> - if matches(form, 'form') - toArray(form.elements).filter (el) -> matches(el, selector) - else - toArray(form.querySelectorAll(selector)) +// Helper function that returns form elements that match the specified CSS selector +// If form is actually a "form" element this will return associated elements outside the from that have +// the html form attribute set +Rails.formElements = function(form, selector) { + if (matches(form, "form")) { + return toArray(form.elements).filter(el => matches(el, selector)) + } else { + return toArray(form.querySelectorAll(selector)) + } +} From 66353fd929c6dc1a28a4b908c462b393cb7cf4ad Mon Sep 17 00:00:00 2001 From: Hartley McGuire Date: Wed, 6 Jul 2022 22:01:38 -0400 Subject: [PATCH 3/4] Address decaffeinate warnings rails-ujs/start.js - Remove some implicit returns - Replace null checks with shorter variations rails-ujs/features/confirm.js - Remove an implicit return - Add comment to empty catch block to fix eslint rails-ujs/features/disable.js - Replace null checks with shorter variations - Remove an implicit return rails-ujs/features/method.js - Replace null check with shorter version - Remove an implicit return rails-ujs/features/remote.js - Remove some implicit returns rails-ujs/utils/ajax.js - Rewrite null checks with shorter variations - Add comment to empty catch blocks to fix eslint rails-ujs/utils/csp.js - Replace null checks with shorter variations - Rewrite code to not use __guard__ rails-ujs/utils/csrf.js - Rewrite null checks with shorter variations rails-ujs/utils/dom.js - Replace null checks with shorter variations rails-ujs/utils/events.js - Remove some implicit returns rails-ujs/utils/form.js - Remove some implicit returns - Rewrite null check with shorter variation --- actionview/app/javascript/rails-ujs.js | 5 ----- .../javascript/rails-ujs/features/confirm.js | 9 ++------- .../javascript/rails-ujs/features/disable.js | 12 +++--------- .../javascript/rails-ujs/features/method.js | 10 ++-------- .../javascript/rails-ujs/features/remote.js | 10 ++-------- actionview/app/javascript/rails-ujs/start.js | 18 ++++++++---------- .../app/javascript/rails-ujs/utils/ajax.js | 14 ++++---------- .../app/javascript/rails-ujs/utils/csp.js | 18 +++++------------- .../app/javascript/rails-ujs/utils/csrf.js | 10 ++-------- .../app/javascript/rails-ujs/utils/dom.js | 12 +++--------- .../app/javascript/rails-ujs/utils/event.js | 9 ++------- .../app/javascript/rails-ujs/utils/form.js | 14 ++++---------- 12 files changed, 37 insertions(+), 104 deletions(-) diff --git a/actionview/app/javascript/rails-ujs.js b/actionview/app/javascript/rails-ujs.js index 4f881c0b628..cd139f92c9f 100644 --- a/actionview/app/javascript/rails-ujs.js +++ b/actionview/app/javascript/rails-ujs.js @@ -1,8 +1,3 @@ -/* - * decaffeinate suggestions: - * DS208: Avoid top-level this - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md - */ //= require ./rails-ujs/BANNER //= export Rails //= require_self diff --git a/actionview/app/javascript/rails-ujs/features/confirm.js b/actionview/app/javascript/rails-ujs/features/confirm.js index f23e9e15aee..caed10e2cef 100644 --- a/actionview/app/javascript/rails-ujs/features/confirm.js +++ b/actionview/app/javascript/rails-ujs/features/confirm.js @@ -1,14 +1,9 @@ -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md - */ //= require_tree ../utils const { fire, stopEverything } = Rails Rails.handleConfirm = function(e) { - if (!allowAction(this)) { return stopEverything(e) } + if (!allowAction(this)) { stopEverything(e) } } // Default confirm dialog, may be overridden with custom confirm dialog in Rails.confirm @@ -30,7 +25,7 @@ var allowAction = function(element) { let answer = false if (fire(element, "confirm")) { - try { answer = Rails.confirm(message, element) } catch (error) {} + try { answer = Rails.confirm(message, element) } catch (error) { /* do nothing */ } callback = fire(element, "confirm:complete", [answer]) } diff --git a/actionview/app/javascript/rails-ujs/features/disable.js b/actionview/app/javascript/rails-ujs/features/disable.js index 28571d5cbaa..d5b17def070 100644 --- a/actionview/app/javascript/rails-ujs/features/disable.js +++ b/actionview/app/javascript/rails-ujs/features/disable.js @@ -1,16 +1,10 @@ -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * DS207: Consider shorter variations of null checks - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md - */ //= require_tree ../utils const { matches, getData, setData, stopEverything, formElements } = Rails Rails.handleDisabledElement = function(e) { const element = this - if (element.disabled) { return stopEverything(e) } + if (element.disabled) { stopEverything(e) } } // Unified function to enable an element (link, button and form) @@ -110,6 +104,6 @@ var enableFormElement = function(element) { } var isXhrRedirect = function(event) { - const xhr = event.detail != null ? event.detail[0] : undefined - return ((xhr != null ? xhr.getResponseHeader("X-Xhr-Redirect") : undefined) != null) + const xhr = event.detail ? event.detail[0] : undefined + return xhr && xhr.getResponseHeader("X-Xhr-Redirect") } diff --git a/actionview/app/javascript/rails-ujs/features/method.js b/actionview/app/javascript/rails-ujs/features/method.js index 5ed21a80d90..1acb220b016 100644 --- a/actionview/app/javascript/rails-ujs/features/method.js +++ b/actionview/app/javascript/rails-ujs/features/method.js @@ -1,9 +1,3 @@ -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * DS207: Consider shorter variations of null checks - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md - */ //= require_tree ../utils const { stopEverything } = Rails @@ -21,7 +15,7 @@ Rails.handleMethod = function(e) { const form = document.createElement("form") let formContent = `` - if ((csrfParam != null) && (csrfToken != null) && !Rails.isCrossDomain(href)) { + if (csrfParam && csrfToken && !Rails.isCrossDomain(href)) { formContent += `` } @@ -38,5 +32,5 @@ Rails.handleMethod = function(e) { document.body.appendChild(form) form.querySelector("[type=\"submit\"]").click() - return stopEverything(e) + stopEverything(e) } diff --git a/actionview/app/javascript/rails-ujs/features/remote.js b/actionview/app/javascript/rails-ujs/features/remote.js index 599696fb520..3551d5cae79 100644 --- a/actionview/app/javascript/rails-ujs/features/remote.js +++ b/actionview/app/javascript/rails-ujs/features/remote.js @@ -1,9 +1,3 @@ -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * DS207: Consider shorter variations of null checks - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md - */ //= require_tree ../utils const { @@ -82,7 +76,7 @@ Rails.handleRemote = function(e) { crossDomain: isCrossDomain(url), withCredentials: (withCredentials != null) && (withCredentials !== "false") }) - return stopEverything(e) + stopEverything(e) } Rails.formSubmitButtonClick = function(e) { @@ -106,6 +100,6 @@ Rails.preventInsignificantClick = function(e) { const metaClick = e.metaKey || e.ctrlKey const insignificantMetaClick = metaClick && (method === "GET") && !data const nonPrimaryMouseClick = (e.button != null) && (e.button !== 0) - if (nonPrimaryMouseClick || insignificantMetaClick) { return e.stopImmediatePropagation() } + if (nonPrimaryMouseClick || insignificantMetaClick) { e.stopImmediatePropagation() } } diff --git a/actionview/app/javascript/rails-ujs/start.js b/actionview/app/javascript/rails-ujs/start.js index 967fd570768..79182d7f3e7 100644 --- a/actionview/app/javascript/rails-ujs/start.js +++ b/actionview/app/javascript/rails-ujs/start.js @@ -1,9 +1,3 @@ -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * DS207: Consider shorter variations of null checks - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md - */ const { fire, delegate, getData, $, @@ -16,7 +10,7 @@ const { } = Rails // For backward compatibility -if ((typeof jQuery !== "undefined" && jQuery !== null) && (jQuery.ajax != null)) { +if (typeof jQuery !== "undefined" && jQuery && jQuery.ajax) { if (jQuery.rails) { throw new Error("If you load both jquery_ujs and rails-ujs, use rails-ujs only.") } jQuery.rails = Rails jQuery.ajaxPrefilter(function(options, originalOptions, xhr) { @@ -35,10 +29,14 @@ Rails.start = function() { // See https://developer.mozilla.org/en-US/docs/Using_Firefox_1.5_caching window.addEventListener("pageshow", function() { $(Rails.formEnableSelector).forEach(function(el) { - if (getData(el, "ujs:disabled")) { return enableElement(el) } + if (getData(el, "ujs:disabled")) { + enableElement(el) + } }) - return $(Rails.linkDisableSelector).forEach(function(el) { - if (getData(el, "ujs:disabled")) { return enableElement(el) } + $(Rails.linkDisableSelector).forEach(function(el) { + if (getData(el, "ujs:disabled")) { + enableElement(el) + } }) }) diff --git a/actionview/app/javascript/rails-ujs/utils/ajax.js b/actionview/app/javascript/rails-ujs/utils/ajax.js index 048d43ece53..a366e1ee118 100644 --- a/actionview/app/javascript/rails-ujs/utils/ajax.js +++ b/actionview/app/javascript/rails-ujs/utils/ajax.js @@ -1,9 +1,3 @@ -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * DS207: Consider shorter variations of null checks - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md - */ //= require ./csp //= require ./csrf //= require ./event @@ -35,7 +29,7 @@ Rails.ajax = function(options) { return (typeof options.complete === "function" ? options.complete(xhr, xhr.statusText) : undefined) }) - if ((options.beforeSend != null) && !options.beforeSend(xhr, options)) { + if (options.beforeSend && !options.beforeSend(xhr, options)) { return false } @@ -56,7 +50,7 @@ var prepareOptions = function(options) { } } // Use "*" as default dataType - if (AcceptHeaders[options.dataType] == null) { options.dataType = "*" } + if (!(options.dataType in AcceptHeaders)) { options.dataType = "*" } options.accept = AcceptHeaders[options.dataType] if (options.dataType !== "*") { options.accept += ", */*; q=0.01" } return options @@ -87,7 +81,7 @@ var createXHR = function(options, done) { var processResponse = function(response, type) { if ((typeof response === "string") && (typeof type === "string")) { if (type.match(/\bjson\b/)) { - try { response = JSON.parse(response) } catch (error) {} + try { response = JSON.parse(response) } catch (error) { /* do nothing */ } } else if (type.match(/\b(?:java|ecma)script\b/)) { const script = document.createElement("script") script.setAttribute("nonce", cspNonce()) @@ -96,7 +90,7 @@ var processResponse = function(response, type) { } else if (type.match(/\b(xml|html|svg)\b/)) { const parser = new DOMParser() type = type.replace(/;.+/, "") // remove something like ';charset=utf-8' - try { response = parser.parseFromString(response, type) } catch (error1) {} + try { response = parser.parseFromString(response, type) } catch (error1) { /* do nothing */ } } } return response diff --git a/actionview/app/javascript/rails-ujs/utils/csp.js b/actionview/app/javascript/rails-ujs/utils/csp.js index 5b0a353fa98..4ddd76f1805 100644 --- a/actionview/app/javascript/rails-ujs/utils/csp.js +++ b/actionview/app/javascript/rails-ujs/utils/csp.js @@ -1,17 +1,9 @@ -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * DS103: Rewrite code to no longer use __guard__, or convert again using --optional-chaining - * DS207: Consider shorter variations of null checks - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md - */ let nonce = null -Rails.loadCSPNonce = () => nonce = __guard__(document.querySelector("meta[name=csp-nonce]"), x => x.content) +Rails.loadCSPNonce = () => { + const metaTag = document.querySelector("meta[name=csp-nonce]") + return nonce = metaTag && metaTag.content +} // Returns the Content-Security-Policy nonce for inline scripts. -Rails.cspNonce = () => nonce != null ? nonce : Rails.loadCSPNonce() - -function __guard__(value, transform) { - return (typeof value !== "undefined" && value !== null) ? transform(value) : undefined -} \ No newline at end of file +Rails.cspNonce = () => nonce || Rails.loadCSPNonce() diff --git a/actionview/app/javascript/rails-ujs/utils/csrf.js b/actionview/app/javascript/rails-ujs/utils/csrf.js index 33d15467df6..96ac78293b2 100644 --- a/actionview/app/javascript/rails-ujs/utils/csrf.js +++ b/actionview/app/javascript/rails-ujs/utils/csrf.js @@ -1,9 +1,3 @@ -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * DS207: Consider shorter variations of null checks - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md - */ //= require ./dom const { $ } = Rails @@ -23,14 +17,14 @@ const csrfParam = (Rails.csrfParam = function() { // Make sure that every Ajax request sends the CSRF token Rails.CSRFProtection = function(xhr) { const token = csrfToken() - if (token != null) { return xhr.setRequestHeader("X-CSRF-Token", token) } + if (token) { return xhr.setRequestHeader("X-CSRF-Token", token) } } // Make sure that all forms have actual up-to-date tokens (cached forms contain old ones) Rails.refreshCSRFTokens = function() { const token = csrfToken() const param = csrfParam() - if ((token != null) && (param != null)) { + if (token && param) { return $("form input[name=\"" + param + "\"]").forEach(input => input.value = token) } } diff --git a/actionview/app/javascript/rails-ujs/utils/dom.js b/actionview/app/javascript/rails-ujs/utils/dom.js index a4c320b3e95..dbf26ad7381 100644 --- a/actionview/app/javascript/rails-ujs/utils/dom.js +++ b/actionview/app/javascript/rails-ujs/utils/dom.js @@ -1,9 +1,3 @@ -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * DS207: Consider shorter variations of null checks - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md - */ const m = Element.prototype.matches || Element.prototype.matchesSelector || Element.prototype.mozMatchesSelector || @@ -19,7 +13,7 @@ const m = Element.prototype.matches || // a JavaScript object with `selector` and `exclude` properties // Examples: "form", { selector: "form", exclude: "form[data-remote='true']"} Rails.matches = function(element, selector) { - if (selector.exclude != null) { + if (selector.exclude) { return m.call(element, selector.selector) && !m.call(element, selector.exclude) } else { return m.call(element, selector) @@ -30,10 +24,10 @@ Rails.matches = function(element, selector) { // See: https://developer.mozilla.org/en-US/docs/Glossary/Expando const expando = "_ujsData" -Rails.getData = (element, key) => element[expando] != null ? element[expando][key] : undefined +Rails.getData = (element, key) => element[expando] ? element[expando][key] : undefined Rails.setData = function(element, key, value) { - if (element[expando] == null) { element[expando] = {} } + if (!element[expando]) { element[expando] = {} } return element[expando][key] = value } diff --git a/actionview/app/javascript/rails-ujs/utils/event.js b/actionview/app/javascript/rails-ujs/utils/event.js index a6962430dc1..15b8720633c 100644 --- a/actionview/app/javascript/rails-ujs/utils/event.js +++ b/actionview/app/javascript/rails-ujs/utils/event.js @@ -1,8 +1,3 @@ -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md - */ //= require ./dom let preventDefault @@ -60,7 +55,7 @@ Rails.stopEverything = function(e) { fire(e.target, "ujs:everythingStopped") e.preventDefault() e.stopPropagation() - return e.stopImmediatePropagation() + e.stopImmediatePropagation() } // Delegates events @@ -81,6 +76,6 @@ Rails.delegate = (element, selector, eventType, handler) => element.addEventList while (!!(target instanceof Element) && !matches(target, selector)) { target = target.parentNode } if (target instanceof Element && (handler.call(target, e) === false)) { e.preventDefault() - return e.stopPropagation() + e.stopPropagation() } }) diff --git a/actionview/app/javascript/rails-ujs/utils/form.js b/actionview/app/javascript/rails-ujs/utils/form.js index 9b49a47f6c2..a31c4a10913 100644 --- a/actionview/app/javascript/rails-ujs/utils/form.js +++ b/actionview/app/javascript/rails-ujs/utils/form.js @@ -1,9 +1,3 @@ -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * DS207: Consider shorter variations of null checks - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md - */ //= require ./dom const { matches } = Rails @@ -19,18 +13,18 @@ Rails.serializeElement = function(element, additionalParam) { if (!input.name || input.disabled) { return } if (matches(input, "fieldset[disabled] *")) { return } if (matches(input, "select")) { - return toArray(input.options).forEach(function(option) { - if (option.selected) { return params.push({name: input.name, value: option.value}) } + toArray(input.options).forEach(function(option) { + if (option.selected) { params.push({name: input.name, value: option.value}) } }) } else if (input.checked || (["radio", "checkbox", "submit"].indexOf(input.type) === -1)) { - return params.push({name: input.name, value: input.value}) + params.push({name: input.name, value: input.value}) } }) if (additionalParam) { params.push(additionalParam) } return params.map(function(param) { - if (param.name != null) { + if (param.name) { return `${encodeURIComponent(param.name)}=${encodeURIComponent(param.value)}` } else { return param From 7d116c93cf6cf2470600860c4c17417df7768c34 Mon Sep 17 00:00:00 2001 From: Hartley McGuire Date: Thu, 7 Jul 2022 02:50:11 -0400 Subject: [PATCH 4/4] Convert rails-ujs to ES2015 modules Building and linting are setup similar to other packages Most of the changes are related to converting from sprockets requires to ESM imports/export. However, there are a few notable changes as well: - A few methods have been refactored to store the Rails object in a closure so that properties on it can be overriden by applications (as documented and tested). This also resulted in the "start" module getting inlined so that it can use the resulting functions. - The logic for running Rails.start() automatically had to change because Rollup uses a slightly different module format than the previous coffeescript bundle. The Rollup bundle does not set window.Rails until the end up the bundle, so the condition had to be updated and window.Rails had to be set manually to ensure backwards compatability with scripts listening to the rails:attachBindings event --- actionview/.eslintrc | 20 + actionview/Rakefile | 48 +- actionview/actionview.gemspec | 2 +- .../app/assets/javascripts/rails-ujs.esm.js | 668 ++++++++++++++++++ .../app/assets/javascripts/rails-ujs.js | 606 ++++++++++++++++ actionview/app/javascript/rails-ujs.js | 41 -- actionview/app/javascript/rails-ujs/BANNER.js | 5 - .../javascript/rails-ujs/features/confirm.js | 16 +- .../javascript/rails-ujs/features/disable.js | 37 +- .../javascript/rails-ujs/features/method.js | 18 +- .../javascript/rails-ujs/features/remote.js | 26 +- actionview/app/javascript/rails-ujs/index.js | 164 +++++ actionview/app/javascript/rails-ujs/start.js | 86 --- .../app/javascript/rails-ujs/utils/ajax.js | 15 +- .../javascript/rails-ujs/utils/constants.js | 45 ++ .../app/javascript/rails-ujs/utils/csp.js | 6 +- .../app/javascript/rails-ujs/utils/csrf.js | 18 +- .../app/javascript/rails-ujs/utils/dom.js | 16 +- .../app/javascript/rails-ujs/utils/event.js | 15 +- .../app/javascript/rails-ujs/utils/form.js | 10 +- actionview/blade.yml | 6 - actionview/package.json | 15 +- actionview/rollup.config.js | 51 ++ railties/test/isolation/abstract_unit.rb | 6 - yarn.lock | 422 +---------- 25 files changed, 1678 insertions(+), 684 deletions(-) create mode 100644 actionview/.eslintrc create mode 100644 actionview/app/assets/javascripts/rails-ujs.esm.js create mode 100644 actionview/app/assets/javascripts/rails-ujs.js delete mode 100644 actionview/app/javascript/rails-ujs.js delete mode 100644 actionview/app/javascript/rails-ujs/BANNER.js create mode 100644 actionview/app/javascript/rails-ujs/index.js delete mode 100644 actionview/app/javascript/rails-ujs/start.js create mode 100644 actionview/app/javascript/rails-ujs/utils/constants.js create mode 100644 actionview/rollup.config.js diff --git a/actionview/.eslintrc b/actionview/.eslintrc new file mode 100644 index 00000000000..c3734b90519 --- /dev/null +++ b/actionview/.eslintrc @@ -0,0 +1,20 @@ +{ + "extends": "eslint:recommended", + "rules": { + "semi": ["error", "never"], + "quotes": ["error", "double"], + "no-unused-vars": ["error", { "vars": "all", "args": "none" }] + }, + "plugins": [ + "import" + ], + "env": { + "browser": true, + "es6": true, + "jquery": true + }, + "parserOptions": { + "ecmaVersion": 6, + "sourceType": "module" + } +} diff --git a/actionview/Rakefile b/actionview/Rakefile index 73be0002315..d24f067c8de 100644 --- a/actionview/Rakefile +++ b/actionview/Rakefile @@ -7,7 +7,7 @@ require "open3" desc "Default Task" task default: :test -task package: %w( assets:compile assets:verify ) +task :package # Run the unit tests @@ -105,52 +105,6 @@ namespace :ujs do end end -namespace :assets do - desc "Compile Action View assets" - task :compile do - require "blade" - require "sprockets" - require "sprockets/export" - Blade.build - end - - desc "Verify compiled Action View assets" - task :verify do - file = "lib/assets/compiled/rails-ujs.js" - pathname = Pathname.new("#{__dir__}/#{file}") - - print "[verify] #{file} exists " - if pathname.exist? - puts "[OK]" - else - $stderr.puts "[FAIL]" - fail - end - - print "[verify] #{file} is a UMD module " - if /module\.exports.*define\.amd/m.match?(pathname.read) - puts "[OK]" - else - $stderr.puts "[FAIL]" - fail - end - - print "[verify] #{__dir__} can be required as a module " - js = <<-JS - window = { Event: class {} } - class Element {} - require('#{__dir__}') - JS - _, stderr, status = Open3.capture3("node", "--print", js) - if status.success? - puts "[OK]" - else - $stderr.puts "[FAIL]\n#{stderr}" - fail - end - end -end - task :lines do load File.expand_path("../tools/line_statistics", __dir__) files = FileList["lib/**/*.rb"] diff --git a/actionview/actionview.gemspec b/actionview/actionview.gemspec index 45691aac0c8..295dd82e965 100644 --- a/actionview/actionview.gemspec +++ b/actionview/actionview.gemspec @@ -17,7 +17,7 @@ Gem::Specification.new do |s| s.email = "david@loudthinking.com" s.homepage = "https://rubyonrails.org" - s.files = Dir["CHANGELOG.md", "README.rdoc", "MIT-LICENSE", "lib/**/*"] + s.files = Dir["CHANGELOG.md", "README.rdoc", "MIT-LICENSE", "lib/**/*", "app/assets/javascripts/*.js"] s.require_path = "lib" s.requirements << "none" diff --git a/actionview/app/assets/javascripts/rails-ujs.esm.js b/actionview/app/assets/javascripts/rails-ujs.esm.js new file mode 100644 index 00000000000..89a40617e95 --- /dev/null +++ b/actionview/app/assets/javascripts/rails-ujs.esm.js @@ -0,0 +1,668 @@ +/* +Unobtrusive JavaScript +https://github.com/rails/rails/blob/main/actionview/app/javascript +Released under the MIT license + */ +const linkClickSelector = "a[data-confirm], a[data-method], a[data-remote]:not([disabled]), a[data-disable-with], a[data-disable]"; + +const buttonClickSelector = { + selector: "button[data-remote]:not([form]), button[data-confirm]:not([form])", + exclude: "form button" +}; + +const inputChangeSelector = "select[data-remote], input[data-remote], textarea[data-remote]"; + +const formSubmitSelector = "form:not([data-turbo=true])"; + +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])"; + +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"; + +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"; + +const fileInputSelector = "input[name][type=file]:not([disabled])"; + +const linkDisableSelector = "a[data-disable-with], a[data-disable]"; + +const buttonDisableSelector = "button[data-remote][data-disable-with], button[data-remote][data-disable]"; + +let nonce = null; + +const loadCSPNonce = () => { + const metaTag = document.querySelector("meta[name=csp-nonce]"); + return nonce = metaTag && metaTag.content; +}; + +const cspNonce = () => nonce || loadCSPNonce(); + +const m = Element.prototype.matches || Element.prototype.matchesSelector || Element.prototype.mozMatchesSelector || Element.prototype.msMatchesSelector || Element.prototype.oMatchesSelector || Element.prototype.webkitMatchesSelector; + +const matches = function(element, selector) { + if (selector.exclude) { + return m.call(element, selector.selector) && !m.call(element, selector.exclude); + } else { + return m.call(element, selector); + } +}; + +const EXPANDO = "_ujsData"; + +const getData = (element, key) => element[EXPANDO] ? element[EXPANDO][key] : undefined; + +const setData = function(element, key, value) { + if (!element[EXPANDO]) { + element[EXPANDO] = {}; + } + return element[EXPANDO][key] = value; +}; + +const $ = selector => Array.prototype.slice.call(document.querySelectorAll(selector)); + +const csrfToken = () => { + const meta = document.querySelector("meta[name=csrf-token]"); + return meta && meta.content; +}; + +const csrfParam = () => { + const meta = document.querySelector("meta[name=csrf-param]"); + return meta && meta.content; +}; + +const CSRFProtection = xhr => { + const token = csrfToken(); + if (token) { + return xhr.setRequestHeader("X-CSRF-Token", token); + } +}; + +const refreshCSRFTokens = () => { + const token = csrfToken(); + const param = csrfParam(); + if (token && param) { + return $('form input[name="' + param + '"]').forEach((input => input.value = token)); + } +}; + +const AcceptHeaders = { + "*": "*/*", + text: "text/plain", + html: "text/html", + xml: "application/xml, text/xml", + json: "application/json, text/javascript", + script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript" +}; + +const ajax = options => { + options = prepareOptions(options); + var xhr = createXHR(options, (function() { + const response = processResponse(xhr.response != null ? xhr.response : xhr.responseText, xhr.getResponseHeader("Content-Type")); + if (Math.floor(xhr.status / 100) === 2) { + if (typeof options.success === "function") { + options.success(response, xhr.statusText, xhr); + } + } else { + if (typeof options.error === "function") { + options.error(response, xhr.statusText, xhr); + } + } + return typeof options.complete === "function" ? options.complete(xhr, xhr.statusText) : undefined; + })); + if (options.beforeSend && !options.beforeSend(xhr, options)) { + return false; + } + if (xhr.readyState === XMLHttpRequest.OPENED) { + return xhr.send(options.data); + } +}; + +var prepareOptions = function(options) { + options.url = options.url || location.href; + options.type = options.type.toUpperCase(); + if (options.type === "GET" && options.data) { + if (options.url.indexOf("?") < 0) { + options.url += "?" + options.data; + } else { + options.url += "&" + options.data; + } + } + if (!(options.dataType in AcceptHeaders)) { + options.dataType = "*"; + } + options.accept = AcceptHeaders[options.dataType]; + if (options.dataType !== "*") { + options.accept += ", */*; q=0.01"; + } + return options; +}; + +var createXHR = function(options, done) { + const xhr = new XMLHttpRequest; + xhr.open(options.type, options.url, true); + xhr.setRequestHeader("Accept", options.accept); + if (typeof options.data === "string") { + xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); + } + if (!options.crossDomain) { + xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); + CSRFProtection(xhr); + } + xhr.withCredentials = !!options.withCredentials; + xhr.onreadystatechange = function() { + if (xhr.readyState === XMLHttpRequest.DONE) { + return done(xhr); + } + }; + return xhr; +}; + +var processResponse = function(response, type) { + if (typeof response === "string" && typeof type === "string") { + if (type.match(/\bjson\b/)) { + try { + response = JSON.parse(response); + } catch (error) {} + } else if (type.match(/\b(?:java|ecma)script\b/)) { + const script = document.createElement("script"); + script.setAttribute("nonce", cspNonce()); + script.text = response; + document.head.appendChild(script).parentNode.removeChild(script); + } else if (type.match(/\b(xml|html|svg)\b/)) { + const parser = new DOMParser; + type = type.replace(/;.+/, ""); + try { + response = parser.parseFromString(response, type); + } catch (error1) {} + } + } + return response; +}; + +const href = element => element.href; + +const isCrossDomain = function(url) { + const originAnchor = document.createElement("a"); + originAnchor.href = location.href; + const urlAnchor = document.createElement("a"); + try { + urlAnchor.href = url; + return !((!urlAnchor.protocol || urlAnchor.protocol === ":") && !urlAnchor.host || originAnchor.protocol + "//" + originAnchor.host === urlAnchor.protocol + "//" + urlAnchor.host); + } catch (e) { + return true; + } +}; + +let preventDefault; + +let {CustomEvent: CustomEvent} = window; + +if (typeof CustomEvent !== "function") { + CustomEvent = function(event, params) { + const evt = document.createEvent("CustomEvent"); + evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail); + return evt; + }; + CustomEvent.prototype = window.Event.prototype; + ({preventDefault: preventDefault} = CustomEvent.prototype); + CustomEvent.prototype.preventDefault = function() { + const result = preventDefault.call(this); + if (this.cancelable && !this.defaultPrevented) { + Object.defineProperty(this, "defaultPrevented", { + get() { + return true; + } + }); + } + return result; + }; +} + +const fire = (obj, name, data) => { + const event = new CustomEvent(name, { + bubbles: true, + cancelable: true, + detail: data + }); + obj.dispatchEvent(event); + return !event.defaultPrevented; +}; + +const stopEverything = e => { + fire(e.target, "ujs:everythingStopped"); + e.preventDefault(); + e.stopPropagation(); + e.stopImmediatePropagation(); +}; + +const delegate = (element, selector, eventType, handler) => element.addEventListener(eventType, (function(e) { + let {target: target} = e; + while (!!(target instanceof Element) && !matches(target, selector)) { + target = target.parentNode; + } + if (target instanceof Element && handler.call(target, e) === false) { + e.preventDefault(); + e.stopPropagation(); + } +})); + +const toArray = e => Array.prototype.slice.call(e); + +const serializeElement = (element, additionalParam) => { + let inputs = [ element ]; + if (matches(element, "form")) { + inputs = toArray(element.elements); + } + const params = []; + inputs.forEach((function(input) { + if (!input.name || input.disabled) { + return; + } + if (matches(input, "fieldset[disabled] *")) { + return; + } + if (matches(input, "select")) { + toArray(input.options).forEach((function(option) { + if (option.selected) { + params.push({ + name: input.name, + value: option.value + }); + } + })); + } else if (input.checked || [ "radio", "checkbox", "submit" ].indexOf(input.type) === -1) { + params.push({ + name: input.name, + value: input.value + }); + } + })); + if (additionalParam) { + params.push(additionalParam); + } + return params.map((function(param) { + if (param.name) { + return `${encodeURIComponent(param.name)}=${encodeURIComponent(param.value)}`; + } else { + return param; + } + })).join("&"); +}; + +const formElements = (form, selector) => { + if (matches(form, "form")) { + return toArray(form.elements).filter((el => matches(el, selector))); + } else { + return toArray(form.querySelectorAll(selector)); + } +}; + +const handleConfirmWithRails = rails => function(e) { + if (!allowAction(this, rails)) { + stopEverything(e); + } +}; + +const confirm = (message, element) => window.confirm(message); + +var allowAction = function(element, rails) { + let callback; + const message = element.getAttribute("data-confirm"); + if (!message) { + return true; + } + let answer = false; + if (fire(element, "confirm")) { + try { + answer = rails.confirm(message, element); + } catch (error) {} + callback = fire(element, "confirm:complete", [ answer ]); + } + return answer && callback; +}; + +const handleDisabledElement = function(e) { + const element = this; + if (element.disabled) { + stopEverything(e); + } +}; + +const enableElement = e => { + let element; + if (e instanceof Event) { + if (isXhrRedirect(e)) { + return; + } + element = e.target; + } else { + element = e; + } + if (matches(element, linkDisableSelector)) { + return enableLinkElement(element); + } else if (matches(element, buttonDisableSelector) || matches(element, formEnableSelector)) { + return enableFormElement(element); + } else if (matches(element, formSubmitSelector)) { + return enableFormElements(element); + } +}; + +const disableElement = e => { + const element = e instanceof Event ? e.target : e; + if (matches(element, linkDisableSelector)) { + return disableLinkElement(element); + } else if (matches(element, buttonDisableSelector) || matches(element, formDisableSelector)) { + return disableFormElement(element); + } else if (matches(element, formSubmitSelector)) { + return disableFormElements(element); + } +}; + +var disableLinkElement = function(element) { + if (getData(element, "ujs:disabled")) { + return; + } + const replacement = element.getAttribute("data-disable-with"); + if (replacement != null) { + setData(element, "ujs:enable-with", element.innerHTML); + element.innerHTML = replacement; + } + element.addEventListener("click", stopEverything); + return setData(element, "ujs:disabled", true); +}; + +var enableLinkElement = function(element) { + const originalText = getData(element, "ujs:enable-with"); + if (originalText != null) { + element.innerHTML = originalText; + setData(element, "ujs:enable-with", null); + } + element.removeEventListener("click", stopEverything); + return setData(element, "ujs:disabled", null); +}; + +var disableFormElements = form => formElements(form, formDisableSelector).forEach(disableFormElement); + +var disableFormElement = function(element) { + if (getData(element, "ujs:disabled")) { + return; + } + const replacement = element.getAttribute("data-disable-with"); + if (replacement != null) { + if (matches(element, "button")) { + setData(element, "ujs:enable-with", element.innerHTML); + element.innerHTML = replacement; + } else { + setData(element, "ujs:enable-with", element.value); + element.value = replacement; + } + } + element.disabled = true; + return setData(element, "ujs:disabled", true); +}; + +var enableFormElements = form => formElements(form, formEnableSelector).forEach((element => enableFormElement(element))); + +var enableFormElement = function(element) { + const originalText = getData(element, "ujs:enable-with"); + if (originalText != null) { + if (matches(element, "button")) { + element.innerHTML = originalText; + } else { + element.value = originalText; + } + setData(element, "ujs:enable-with", null); + } + element.disabled = false; + return setData(element, "ujs:disabled", null); +}; + +var isXhrRedirect = function(event) { + const xhr = event.detail ? event.detail[0] : undefined; + return xhr && xhr.getResponseHeader("X-Xhr-Redirect"); +}; + +const handleMethodWithRails = rails => function(e) { + const link = this; + const method = link.getAttribute("data-method"); + if (!method) { + return; + } + const href = rails.href(link); + const csrfToken$1 = csrfToken(); + const csrfParam$1 = csrfParam(); + const form = document.createElement("form"); + let formContent = ``; + if (csrfParam$1 && csrfToken$1 && !isCrossDomain(href)) { + formContent += ``; + } + formContent += ''; + form.method = "post"; + form.action = href; + form.target = link.target; + form.innerHTML = formContent; + form.style.display = "none"; + document.body.appendChild(form); + form.querySelector('[type="submit"]').click(); + stopEverything(e); +}; + +const isRemote = function(element) { + const value = element.getAttribute("data-remote"); + return value != null && value !== "false"; +}; + +const handleRemoteWithRails = rails => function(e) { + let data, method, url; + const element = this; + if (!isRemote(element)) { + return true; + } + if (!fire(element, "ajax:before")) { + fire(element, "ajax:stopped"); + return false; + } + const withCredentials = element.getAttribute("data-with-credentials"); + const dataType = element.getAttribute("data-type") || "script"; + if (matches(element, formSubmitSelector)) { + const button = getData(element, "ujs:submit-button"); + method = getData(element, "ujs:submit-button-formmethod") || element.getAttribute("method") || "get"; + url = getData(element, "ujs:submit-button-formaction") || element.getAttribute("action") || location.href; + if (method.toUpperCase() === "GET") { + url = url.replace(/\?.*$/, ""); + } + if (element.enctype === "multipart/form-data") { + data = new FormData(element); + if (button != null) { + data.append(button.name, button.value); + } + } else { + data = serializeElement(element, button); + } + setData(element, "ujs:submit-button", null); + setData(element, "ujs:submit-button-formmethod", null); + setData(element, "ujs:submit-button-formaction", null); + } else if (matches(element, buttonClickSelector) || matches(element, inputChangeSelector)) { + method = element.getAttribute("data-method"); + url = element.getAttribute("data-url"); + data = serializeElement(element, element.getAttribute("data-params")); + } else { + method = element.getAttribute("data-method"); + url = rails.href(element); + data = element.getAttribute("data-params"); + } + ajax({ + type: method || "GET", + url: url, + data: data, + dataType: dataType, + beforeSend(xhr, options) { + if (fire(element, "ajax:beforeSend", [ xhr, options ])) { + return fire(element, "ajax:send", [ xhr ]); + } else { + fire(element, "ajax:stopped"); + return false; + } + }, + success(...args) { + return fire(element, "ajax:success", args); + }, + error(...args) { + return fire(element, "ajax:error", args); + }, + complete(...args) { + return fire(element, "ajax:complete", args); + }, + crossDomain: isCrossDomain(url), + withCredentials: withCredentials != null && withCredentials !== "false" + }); + stopEverything(e); +}; + +const formSubmitButtonClick = function(e) { + const button = this; + const {form: form} = button; + if (!form) { + return; + } + if (button.name) { + setData(form, "ujs:submit-button", { + name: button.name, + value: button.value + }); + } + setData(form, "ujs:formnovalidate-button", button.formNoValidate); + setData(form, "ujs:submit-button-formaction", button.getAttribute("formaction")); + return setData(form, "ujs:submit-button-formmethod", button.getAttribute("formmethod")); +}; + +const preventInsignificantClick = function(e) { + const link = this; + const method = (link.getAttribute("data-method") || "GET").toUpperCase(); + const data = link.getAttribute("data-params"); + const metaClick = e.metaKey || e.ctrlKey; + const insignificantMetaClick = metaClick && method === "GET" && !data; + const nonPrimaryMouseClick = e.button != null && e.button !== 0; + if (nonPrimaryMouseClick || insignificantMetaClick) { + e.stopImmediatePropagation(); + } +}; + +const Rails = { + $: $, + ajax: ajax, + buttonClickSelector: buttonClickSelector, + buttonDisableSelector: buttonDisableSelector, + confirm: confirm, + cspNonce: cspNonce, + csrfToken: csrfToken, + csrfParam: csrfParam, + CSRFProtection: CSRFProtection, + delegate: delegate, + disableElement: disableElement, + enableElement: enableElement, + fileInputSelector: fileInputSelector, + fire: fire, + formElements: formElements, + formEnableSelector: formEnableSelector, + formDisableSelector: formDisableSelector, + formInputClickSelector: formInputClickSelector, + formSubmitButtonClick: formSubmitButtonClick, + formSubmitSelector: formSubmitSelector, + getData: getData, + handleDisabledElement: handleDisabledElement, + href: href, + inputChangeSelector: inputChangeSelector, + isCrossDomain: isCrossDomain, + linkClickSelector: linkClickSelector, + linkDisableSelector: linkDisableSelector, + loadCSPNonce: loadCSPNonce, + matches: matches, + preventInsignificantClick: preventInsignificantClick, + refreshCSRFTokens: refreshCSRFTokens, + serializeElement: serializeElement, + setData: setData, + stopEverything: stopEverything +}; + +const handleConfirm = handleConfirmWithRails(Rails); + +Rails.handleConfirm = handleConfirm; + +const handleMethod = handleMethodWithRails(Rails); + +Rails.handleMethod = handleMethod; + +const handleRemote = handleRemoteWithRails(Rails); + +Rails.handleRemote = handleRemote; + +const start = function() { + if (window._rails_loaded) { + throw new Error("rails-ujs has already been loaded!"); + } + window.addEventListener("pageshow", (function() { + $(formEnableSelector).forEach((function(el) { + if (getData(el, "ujs:disabled")) { + enableElement(el); + } + })); + $(linkDisableSelector).forEach((function(el) { + if (getData(el, "ujs:disabled")) { + enableElement(el); + } + })); + })); + delegate(document, linkDisableSelector, "ajax:complete", enableElement); + delegate(document, linkDisableSelector, "ajax:stopped", enableElement); + delegate(document, buttonDisableSelector, "ajax:complete", enableElement); + delegate(document, buttonDisableSelector, "ajax:stopped", enableElement); + delegate(document, linkClickSelector, "click", preventInsignificantClick); + delegate(document, linkClickSelector, "click", handleDisabledElement); + delegate(document, linkClickSelector, "click", handleConfirm); + delegate(document, linkClickSelector, "click", disableElement); + delegate(document, linkClickSelector, "click", handleRemote); + delegate(document, linkClickSelector, "click", handleMethod); + delegate(document, buttonClickSelector, "click", preventInsignificantClick); + delegate(document, buttonClickSelector, "click", handleDisabledElement); + delegate(document, buttonClickSelector, "click", handleConfirm); + delegate(document, buttonClickSelector, "click", disableElement); + delegate(document, buttonClickSelector, "click", handleRemote); + delegate(document, inputChangeSelector, "change", handleDisabledElement); + delegate(document, inputChangeSelector, "change", handleConfirm); + delegate(document, inputChangeSelector, "change", handleRemote); + delegate(document, formSubmitSelector, "submit", handleDisabledElement); + delegate(document, formSubmitSelector, "submit", handleConfirm); + delegate(document, formSubmitSelector, "submit", handleRemote); + delegate(document, formSubmitSelector, "submit", (e => setTimeout((() => disableElement(e)), 13))); + delegate(document, formSubmitSelector, "ajax:send", disableElement); + delegate(document, formSubmitSelector, "ajax:complete", enableElement); + delegate(document, formInputClickSelector, "click", preventInsignificantClick); + delegate(document, formInputClickSelector, "click", handleDisabledElement); + delegate(document, formInputClickSelector, "click", handleConfirm); + delegate(document, formInputClickSelector, "click", formSubmitButtonClick); + document.addEventListener("DOMContentLoaded", refreshCSRFTokens); + document.addEventListener("DOMContentLoaded", loadCSPNonce); + return window._rails_loaded = true; +}; + +Rails.start = start; + +if (typeof jQuery !== "undefined" && jQuery && jQuery.ajax) { + if (jQuery.rails) { + throw new Error("If you load both jquery_ujs and rails-ujs, use rails-ujs only."); + } + jQuery.rails = Rails; + jQuery.ajaxPrefilter((function(options, originalOptions, xhr) { + if (!options.crossDomain) { + return CSRFProtection(xhr); + } + })); +} + +if (typeof exports !== "object" && typeof module === "undefined") { + window.Rails = Rails; + if (fire(document, "rails:attachBindings")) { + start(); + } +} + +export default Rails; diff --git a/actionview/app/assets/javascripts/rails-ujs.js b/actionview/app/assets/javascripts/rails-ujs.js new file mode 100644 index 00000000000..9a23af41ce6 --- /dev/null +++ b/actionview/app/assets/javascripts/rails-ujs.js @@ -0,0 +1,606 @@ +/* +Unobtrusive JavaScript +https://github.com/rails/rails/blob/main/actionview/app/javascript +Released under the MIT license + */ +(function(global, factory) { + typeof exports === "object" && typeof module !== "undefined" ? module.exports = factory() : typeof define === "function" && define.amd ? define(factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, + global.Rails = factory()); +})(this, (function() { + "use strict"; + const linkClickSelector = "a[data-confirm], a[data-method], a[data-remote]:not([disabled]), a[data-disable-with], a[data-disable]"; + const buttonClickSelector = { + selector: "button[data-remote]:not([form]), button[data-confirm]:not([form])", + exclude: "form button" + }; + const inputChangeSelector = "select[data-remote], input[data-remote], textarea[data-remote]"; + const formSubmitSelector = "form:not([data-turbo=true])"; + 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])"; + 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"; + 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"; + const fileInputSelector = "input[name][type=file]:not([disabled])"; + const linkDisableSelector = "a[data-disable-with], a[data-disable]"; + const buttonDisableSelector = "button[data-remote][data-disable-with], button[data-remote][data-disable]"; + let nonce = null; + const loadCSPNonce = () => { + const metaTag = document.querySelector("meta[name=csp-nonce]"); + return nonce = metaTag && metaTag.content; + }; + const cspNonce = () => nonce || loadCSPNonce(); + const m = Element.prototype.matches || Element.prototype.matchesSelector || Element.prototype.mozMatchesSelector || Element.prototype.msMatchesSelector || Element.prototype.oMatchesSelector || Element.prototype.webkitMatchesSelector; + const matches = function(element, selector) { + if (selector.exclude) { + return m.call(element, selector.selector) && !m.call(element, selector.exclude); + } else { + return m.call(element, selector); + } + }; + const EXPANDO = "_ujsData"; + const getData = (element, key) => element[EXPANDO] ? element[EXPANDO][key] : undefined; + const setData = function(element, key, value) { + if (!element[EXPANDO]) { + element[EXPANDO] = {}; + } + return element[EXPANDO][key] = value; + }; + const $ = selector => Array.prototype.slice.call(document.querySelectorAll(selector)); + const csrfToken = () => { + const meta = document.querySelector("meta[name=csrf-token]"); + return meta && meta.content; + }; + const csrfParam = () => { + const meta = document.querySelector("meta[name=csrf-param]"); + return meta && meta.content; + }; + const CSRFProtection = xhr => { + const token = csrfToken(); + if (token) { + return xhr.setRequestHeader("X-CSRF-Token", token); + } + }; + const refreshCSRFTokens = () => { + const token = csrfToken(); + const param = csrfParam(); + if (token && param) { + return $('form input[name="' + param + '"]').forEach((input => input.value = token)); + } + }; + const AcceptHeaders = { + "*": "*/*", + text: "text/plain", + html: "text/html", + xml: "application/xml, text/xml", + json: "application/json, text/javascript", + script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript" + }; + const ajax = options => { + options = prepareOptions(options); + var xhr = createXHR(options, (function() { + const response = processResponse(xhr.response != null ? xhr.response : xhr.responseText, xhr.getResponseHeader("Content-Type")); + if (Math.floor(xhr.status / 100) === 2) { + if (typeof options.success === "function") { + options.success(response, xhr.statusText, xhr); + } + } else { + if (typeof options.error === "function") { + options.error(response, xhr.statusText, xhr); + } + } + return typeof options.complete === "function" ? options.complete(xhr, xhr.statusText) : undefined; + })); + if (options.beforeSend && !options.beforeSend(xhr, options)) { + return false; + } + if (xhr.readyState === XMLHttpRequest.OPENED) { + return xhr.send(options.data); + } + }; + var prepareOptions = function(options) { + options.url = options.url || location.href; + options.type = options.type.toUpperCase(); + if (options.type === "GET" && options.data) { + if (options.url.indexOf("?") < 0) { + options.url += "?" + options.data; + } else { + options.url += "&" + options.data; + } + } + if (!(options.dataType in AcceptHeaders)) { + options.dataType = "*"; + } + options.accept = AcceptHeaders[options.dataType]; + if (options.dataType !== "*") { + options.accept += ", */*; q=0.01"; + } + return options; + }; + var createXHR = function(options, done) { + const xhr = new XMLHttpRequest; + xhr.open(options.type, options.url, true); + xhr.setRequestHeader("Accept", options.accept); + if (typeof options.data === "string") { + xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); + } + if (!options.crossDomain) { + xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); + CSRFProtection(xhr); + } + xhr.withCredentials = !!options.withCredentials; + xhr.onreadystatechange = function() { + if (xhr.readyState === XMLHttpRequest.DONE) { + return done(xhr); + } + }; + return xhr; + }; + var processResponse = function(response, type) { + if (typeof response === "string" && typeof type === "string") { + if (type.match(/\bjson\b/)) { + try { + response = JSON.parse(response); + } catch (error) {} + } else if (type.match(/\b(?:java|ecma)script\b/)) { + const script = document.createElement("script"); + script.setAttribute("nonce", cspNonce()); + script.text = response; + document.head.appendChild(script).parentNode.removeChild(script); + } else if (type.match(/\b(xml|html|svg)\b/)) { + const parser = new DOMParser; + type = type.replace(/;.+/, ""); + try { + response = parser.parseFromString(response, type); + } catch (error1) {} + } + } + return response; + }; + const href = element => element.href; + const isCrossDomain = function(url) { + const originAnchor = document.createElement("a"); + originAnchor.href = location.href; + const urlAnchor = document.createElement("a"); + try { + urlAnchor.href = url; + return !((!urlAnchor.protocol || urlAnchor.protocol === ":") && !urlAnchor.host || originAnchor.protocol + "//" + originAnchor.host === urlAnchor.protocol + "//" + urlAnchor.host); + } catch (e) { + return true; + } + }; + let preventDefault; + let {CustomEvent: CustomEvent} = window; + if (typeof CustomEvent !== "function") { + CustomEvent = function(event, params) { + const evt = document.createEvent("CustomEvent"); + evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail); + return evt; + }; + CustomEvent.prototype = window.Event.prototype; + ({preventDefault: preventDefault} = CustomEvent.prototype); + CustomEvent.prototype.preventDefault = function() { + const result = preventDefault.call(this); + if (this.cancelable && !this.defaultPrevented) { + Object.defineProperty(this, "defaultPrevented", { + get() { + return true; + } + }); + } + return result; + }; + } + const fire = (obj, name, data) => { + const event = new CustomEvent(name, { + bubbles: true, + cancelable: true, + detail: data + }); + obj.dispatchEvent(event); + return !event.defaultPrevented; + }; + const stopEverything = e => { + fire(e.target, "ujs:everythingStopped"); + e.preventDefault(); + e.stopPropagation(); + e.stopImmediatePropagation(); + }; + const delegate = (element, selector, eventType, handler) => element.addEventListener(eventType, (function(e) { + let {target: target} = e; + while (!!(target instanceof Element) && !matches(target, selector)) { + target = target.parentNode; + } + if (target instanceof Element && handler.call(target, e) === false) { + e.preventDefault(); + e.stopPropagation(); + } + })); + const toArray = e => Array.prototype.slice.call(e); + const serializeElement = (element, additionalParam) => { + let inputs = [ element ]; + if (matches(element, "form")) { + inputs = toArray(element.elements); + } + const params = []; + inputs.forEach((function(input) { + if (!input.name || input.disabled) { + return; + } + if (matches(input, "fieldset[disabled] *")) { + return; + } + if (matches(input, "select")) { + toArray(input.options).forEach((function(option) { + if (option.selected) { + params.push({ + name: input.name, + value: option.value + }); + } + })); + } else if (input.checked || [ "radio", "checkbox", "submit" ].indexOf(input.type) === -1) { + params.push({ + name: input.name, + value: input.value + }); + } + })); + if (additionalParam) { + params.push(additionalParam); + } + return params.map((function(param) { + if (param.name) { + return `${encodeURIComponent(param.name)}=${encodeURIComponent(param.value)}`; + } else { + return param; + } + })).join("&"); + }; + const formElements = (form, selector) => { + if (matches(form, "form")) { + return toArray(form.elements).filter((el => matches(el, selector))); + } else { + return toArray(form.querySelectorAll(selector)); + } + }; + const handleConfirmWithRails = rails => function(e) { + if (!allowAction(this, rails)) { + stopEverything(e); + } + }; + const confirm = (message, element) => window.confirm(message); + var allowAction = function(element, rails) { + let callback; + const message = element.getAttribute("data-confirm"); + if (!message) { + return true; + } + let answer = false; + if (fire(element, "confirm")) { + try { + answer = rails.confirm(message, element); + } catch (error) {} + callback = fire(element, "confirm:complete", [ answer ]); + } + return answer && callback; + }; + const handleDisabledElement = function(e) { + const element = this; + if (element.disabled) { + stopEverything(e); + } + }; + const enableElement = e => { + let element; + if (e instanceof Event) { + if (isXhrRedirect(e)) { + return; + } + element = e.target; + } else { + element = e; + } + if (matches(element, linkDisableSelector)) { + return enableLinkElement(element); + } else if (matches(element, buttonDisableSelector) || matches(element, formEnableSelector)) { + return enableFormElement(element); + } else if (matches(element, formSubmitSelector)) { + return enableFormElements(element); + } + }; + const disableElement = e => { + const element = e instanceof Event ? e.target : e; + if (matches(element, linkDisableSelector)) { + return disableLinkElement(element); + } else if (matches(element, buttonDisableSelector) || matches(element, formDisableSelector)) { + return disableFormElement(element); + } else if (matches(element, formSubmitSelector)) { + return disableFormElements(element); + } + }; + var disableLinkElement = function(element) { + if (getData(element, "ujs:disabled")) { + return; + } + const replacement = element.getAttribute("data-disable-with"); + if (replacement != null) { + setData(element, "ujs:enable-with", element.innerHTML); + element.innerHTML = replacement; + } + element.addEventListener("click", stopEverything); + return setData(element, "ujs:disabled", true); + }; + var enableLinkElement = function(element) { + const originalText = getData(element, "ujs:enable-with"); + if (originalText != null) { + element.innerHTML = originalText; + setData(element, "ujs:enable-with", null); + } + element.removeEventListener("click", stopEverything); + return setData(element, "ujs:disabled", null); + }; + var disableFormElements = form => formElements(form, formDisableSelector).forEach(disableFormElement); + var disableFormElement = function(element) { + if (getData(element, "ujs:disabled")) { + return; + } + const replacement = element.getAttribute("data-disable-with"); + if (replacement != null) { + if (matches(element, "button")) { + setData(element, "ujs:enable-with", element.innerHTML); + element.innerHTML = replacement; + } else { + setData(element, "ujs:enable-with", element.value); + element.value = replacement; + } + } + element.disabled = true; + return setData(element, "ujs:disabled", true); + }; + var enableFormElements = form => formElements(form, formEnableSelector).forEach((element => enableFormElement(element))); + var enableFormElement = function(element) { + const originalText = getData(element, "ujs:enable-with"); + if (originalText != null) { + if (matches(element, "button")) { + element.innerHTML = originalText; + } else { + element.value = originalText; + } + setData(element, "ujs:enable-with", null); + } + element.disabled = false; + return setData(element, "ujs:disabled", null); + }; + var isXhrRedirect = function(event) { + const xhr = event.detail ? event.detail[0] : undefined; + return xhr && xhr.getResponseHeader("X-Xhr-Redirect"); + }; + const handleMethodWithRails = rails => function(e) { + const link = this; + const method = link.getAttribute("data-method"); + if (!method) { + return; + } + const href = rails.href(link); + const csrfToken$1 = csrfToken(); + const csrfParam$1 = csrfParam(); + const form = document.createElement("form"); + let formContent = ``; + if (csrfParam$1 && csrfToken$1 && !isCrossDomain(href)) { + formContent += ``; + } + formContent += ''; + form.method = "post"; + form.action = href; + form.target = link.target; + form.innerHTML = formContent; + form.style.display = "none"; + document.body.appendChild(form); + form.querySelector('[type="submit"]').click(); + stopEverything(e); + }; + const isRemote = function(element) { + const value = element.getAttribute("data-remote"); + return value != null && value !== "false"; + }; + const handleRemoteWithRails = rails => function(e) { + let data, method, url; + const element = this; + if (!isRemote(element)) { + return true; + } + if (!fire(element, "ajax:before")) { + fire(element, "ajax:stopped"); + return false; + } + const withCredentials = element.getAttribute("data-with-credentials"); + const dataType = element.getAttribute("data-type") || "script"; + if (matches(element, formSubmitSelector)) { + const button = getData(element, "ujs:submit-button"); + method = getData(element, "ujs:submit-button-formmethod") || element.getAttribute("method") || "get"; + url = getData(element, "ujs:submit-button-formaction") || element.getAttribute("action") || location.href; + if (method.toUpperCase() === "GET") { + url = url.replace(/\?.*$/, ""); + } + if (element.enctype === "multipart/form-data") { + data = new FormData(element); + if (button != null) { + data.append(button.name, button.value); + } + } else { + data = serializeElement(element, button); + } + setData(element, "ujs:submit-button", null); + setData(element, "ujs:submit-button-formmethod", null); + setData(element, "ujs:submit-button-formaction", null); + } else if (matches(element, buttonClickSelector) || matches(element, inputChangeSelector)) { + method = element.getAttribute("data-method"); + url = element.getAttribute("data-url"); + data = serializeElement(element, element.getAttribute("data-params")); + } else { + method = element.getAttribute("data-method"); + url = rails.href(element); + data = element.getAttribute("data-params"); + } + ajax({ + type: method || "GET", + url: url, + data: data, + dataType: dataType, + beforeSend(xhr, options) { + if (fire(element, "ajax:beforeSend", [ xhr, options ])) { + return fire(element, "ajax:send", [ xhr ]); + } else { + fire(element, "ajax:stopped"); + return false; + } + }, + success(...args) { + return fire(element, "ajax:success", args); + }, + error(...args) { + return fire(element, "ajax:error", args); + }, + complete(...args) { + return fire(element, "ajax:complete", args); + }, + crossDomain: isCrossDomain(url), + withCredentials: withCredentials != null && withCredentials !== "false" + }); + stopEverything(e); + }; + const formSubmitButtonClick = function(e) { + const button = this; + const {form: form} = button; + if (!form) { + return; + } + if (button.name) { + setData(form, "ujs:submit-button", { + name: button.name, + value: button.value + }); + } + setData(form, "ujs:formnovalidate-button", button.formNoValidate); + setData(form, "ujs:submit-button-formaction", button.getAttribute("formaction")); + return setData(form, "ujs:submit-button-formmethod", button.getAttribute("formmethod")); + }; + const preventInsignificantClick = function(e) { + const link = this; + const method = (link.getAttribute("data-method") || "GET").toUpperCase(); + const data = link.getAttribute("data-params"); + const metaClick = e.metaKey || e.ctrlKey; + const insignificantMetaClick = metaClick && method === "GET" && !data; + const nonPrimaryMouseClick = e.button != null && e.button !== 0; + if (nonPrimaryMouseClick || insignificantMetaClick) { + e.stopImmediatePropagation(); + } + }; + const Rails = { + $: $, + ajax: ajax, + buttonClickSelector: buttonClickSelector, + buttonDisableSelector: buttonDisableSelector, + confirm: confirm, + cspNonce: cspNonce, + csrfToken: csrfToken, + csrfParam: csrfParam, + CSRFProtection: CSRFProtection, + delegate: delegate, + disableElement: disableElement, + enableElement: enableElement, + fileInputSelector: fileInputSelector, + fire: fire, + formElements: formElements, + formEnableSelector: formEnableSelector, + formDisableSelector: formDisableSelector, + formInputClickSelector: formInputClickSelector, + formSubmitButtonClick: formSubmitButtonClick, + formSubmitSelector: formSubmitSelector, + getData: getData, + handleDisabledElement: handleDisabledElement, + href: href, + inputChangeSelector: inputChangeSelector, + isCrossDomain: isCrossDomain, + linkClickSelector: linkClickSelector, + linkDisableSelector: linkDisableSelector, + loadCSPNonce: loadCSPNonce, + matches: matches, + preventInsignificantClick: preventInsignificantClick, + refreshCSRFTokens: refreshCSRFTokens, + serializeElement: serializeElement, + setData: setData, + stopEverything: stopEverything + }; + const handleConfirm = handleConfirmWithRails(Rails); + Rails.handleConfirm = handleConfirm; + const handleMethod = handleMethodWithRails(Rails); + Rails.handleMethod = handleMethod; + const handleRemote = handleRemoteWithRails(Rails); + Rails.handleRemote = handleRemote; + const start = function() { + if (window._rails_loaded) { + throw new Error("rails-ujs has already been loaded!"); + } + window.addEventListener("pageshow", (function() { + $(formEnableSelector).forEach((function(el) { + if (getData(el, "ujs:disabled")) { + enableElement(el); + } + })); + $(linkDisableSelector).forEach((function(el) { + if (getData(el, "ujs:disabled")) { + enableElement(el); + } + })); + })); + delegate(document, linkDisableSelector, "ajax:complete", enableElement); + delegate(document, linkDisableSelector, "ajax:stopped", enableElement); + delegate(document, buttonDisableSelector, "ajax:complete", enableElement); + delegate(document, buttonDisableSelector, "ajax:stopped", enableElement); + delegate(document, linkClickSelector, "click", preventInsignificantClick); + delegate(document, linkClickSelector, "click", handleDisabledElement); + delegate(document, linkClickSelector, "click", handleConfirm); + delegate(document, linkClickSelector, "click", disableElement); + delegate(document, linkClickSelector, "click", handleRemote); + delegate(document, linkClickSelector, "click", handleMethod); + delegate(document, buttonClickSelector, "click", preventInsignificantClick); + delegate(document, buttonClickSelector, "click", handleDisabledElement); + delegate(document, buttonClickSelector, "click", handleConfirm); + delegate(document, buttonClickSelector, "click", disableElement); + delegate(document, buttonClickSelector, "click", handleRemote); + delegate(document, inputChangeSelector, "change", handleDisabledElement); + delegate(document, inputChangeSelector, "change", handleConfirm); + delegate(document, inputChangeSelector, "change", handleRemote); + delegate(document, formSubmitSelector, "submit", handleDisabledElement); + delegate(document, formSubmitSelector, "submit", handleConfirm); + delegate(document, formSubmitSelector, "submit", handleRemote); + delegate(document, formSubmitSelector, "submit", (e => setTimeout((() => disableElement(e)), 13))); + delegate(document, formSubmitSelector, "ajax:send", disableElement); + delegate(document, formSubmitSelector, "ajax:complete", enableElement); + delegate(document, formInputClickSelector, "click", preventInsignificantClick); + delegate(document, formInputClickSelector, "click", handleDisabledElement); + delegate(document, formInputClickSelector, "click", handleConfirm); + delegate(document, formInputClickSelector, "click", formSubmitButtonClick); + document.addEventListener("DOMContentLoaded", refreshCSRFTokens); + document.addEventListener("DOMContentLoaded", loadCSPNonce); + return window._rails_loaded = true; + }; + Rails.start = start; + if (typeof jQuery !== "undefined" && jQuery && jQuery.ajax) { + if (jQuery.rails) { + throw new Error("If you load both jquery_ujs and rails-ujs, use rails-ujs only."); + } + jQuery.rails = Rails; + jQuery.ajaxPrefilter((function(options, originalOptions, xhr) { + if (!options.crossDomain) { + return CSRFProtection(xhr); + } + })); + } + if (typeof exports !== "object" && typeof module === "undefined") { + window.Rails = Rails; + if (fire(document, "rails:attachBindings")) { + start(); + } + } + return Rails; +})); diff --git a/actionview/app/javascript/rails-ujs.js b/actionview/app/javascript/rails-ujs.js deleted file mode 100644 index cd139f92c9f..00000000000 --- a/actionview/app/javascript/rails-ujs.js +++ /dev/null @@ -1,41 +0,0 @@ -//= require ./rails-ujs/BANNER -//= export Rails -//= require_self -//= require_tree ./rails-ujs/utils -//= require_tree ./rails-ujs/features -//= require ./rails-ujs/start - -this.Rails = { - // Link elements bound by rails-ujs - linkClickSelector: "a[data-confirm], a[data-method], a[data-remote]:not([disabled]), a[data-disable-with], a[data-disable]", - - // Button elements bound by rails-ujs - buttonClickSelector: { - selector: "button[data-remote]:not([form]), button[data-confirm]:not([form])", - exclude: "form button" - }, - - // Select elements bound by rails-ujs - inputChangeSelector: "select[data-remote], input[data-remote], textarea[data-remote]", - - // Form elements bound by rails-ujs - formSubmitSelector: "form:not([data-turbo=true])", - - // Form input elements bound by rails-ujs - 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])", - - // Form input elements disabled during form submission - 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", - - // Form input elements re-enabled after form submission - 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", - - // Form file input elements - fileInputSelector: "input[name][type=file]:not([disabled])", - - // Link onClick disable selector with possible re-enable after remote submission - linkDisableSelector: "a[data-disable-with], a[data-disable]", - - // Button onClick disable selector with possible re-enable after remote submission - buttonDisableSelector: "button[data-remote][data-disable-with], button[data-remote][data-disable]" -} diff --git a/actionview/app/javascript/rails-ujs/BANNER.js b/actionview/app/javascript/rails-ujs/BANNER.js deleted file mode 100644 index 6a48891886d..00000000000 --- a/actionview/app/javascript/rails-ujs/BANNER.js +++ /dev/null @@ -1,5 +0,0 @@ -/* -Unobtrusive JavaScript -https://github.com/rails/rails/blob/main/actionview/app/assets/javascripts -Released under the MIT license - */ diff --git a/actionview/app/javascript/rails-ujs/features/confirm.js b/actionview/app/javascript/rails-ujs/features/confirm.js index caed10e2cef..33ef4cd99ca 100644 --- a/actionview/app/javascript/rails-ujs/features/confirm.js +++ b/actionview/app/javascript/rails-ujs/features/confirm.js @@ -1,13 +1,11 @@ -//= require_tree ../utils +import { fire, stopEverything } from "../utils/event" -const { fire, stopEverything } = Rails - -Rails.handleConfirm = function(e) { - if (!allowAction(this)) { stopEverything(e) } +const handleConfirmWithRails = (rails) => function(e) { + if (!allowAction(this, rails)) { stopEverything(e) } } // Default confirm dialog, may be overridden with custom confirm dialog in Rails.confirm -Rails.confirm = (message, element) => confirm(message) +const confirm = (message, element) => window.confirm(message) // For 'data-confirm' attribute: // - Fires `confirm` event @@ -18,16 +16,18 @@ Rails.confirm = (message, element) => confirm(message) // Attaching a handler to the element's `confirm` event that returns a `falsy` value cancels the confirmation dialog. // Attaching a handler to the element's `confirm:complete` event that returns a `falsy` value makes this function // return false. The `confirm:complete` event is fired whether or not the user answered true or false to the dialog. -var allowAction = function(element) { +var allowAction = function(element, rails) { let callback const message = element.getAttribute("data-confirm") if (!message) { return true } let answer = false if (fire(element, "confirm")) { - try { answer = Rails.confirm(message, element) } catch (error) { /* do nothing */ } + try { answer = rails.confirm(message, element) } catch(error) { /* do nothing */ } callback = fire(element, "confirm:complete", [answer]) } return answer && callback } + +export { handleConfirmWithRails, confirm } diff --git a/actionview/app/javascript/rails-ujs/features/disable.js b/actionview/app/javascript/rails-ujs/features/disable.js index d5b17def070..3a2101b74b7 100644 --- a/actionview/app/javascript/rails-ujs/features/disable.js +++ b/actionview/app/javascript/rails-ujs/features/disable.js @@ -1,14 +1,21 @@ -//= require_tree ../utils +import { + linkDisableSelector, + buttonDisableSelector, + formDisableSelector, + formEnableSelector, + formSubmitSelector +} from "../utils/constants" +import { matches, getData, setData } from "../utils/dom" +import { stopEverything } from "../utils/event" +import { formElements } from "../utils/form" -const { matches, getData, setData, stopEverything, formElements } = Rails - -Rails.handleDisabledElement = function(e) { +const handleDisabledElement = function(e) { const element = this if (element.disabled) { stopEverything(e) } } // Unified function to enable an element (link, button and form) -Rails.enableElement = function(e) { +const enableElement = (e) => { let element if (e instanceof Event) { if (isXhrRedirect(e)) { return } @@ -17,23 +24,23 @@ Rails.enableElement = function(e) { element = e } - if (matches(element, Rails.linkDisableSelector)) { + if (matches(element, linkDisableSelector)) { return enableLinkElement(element) - } else if (matches(element, Rails.buttonDisableSelector) || matches(element, Rails.formEnableSelector)) { + } else if (matches(element, buttonDisableSelector) || matches(element, formEnableSelector)) { return enableFormElement(element) - } else if (matches(element, Rails.formSubmitSelector)) { + } else if (matches(element, formSubmitSelector)) { return enableFormElements(element) } } // Unified function to disable an element (link, button and form) -Rails.disableElement = function(e) { +const disableElement = (e) => { const element = e instanceof Event ? e.target : e - if (matches(element, Rails.linkDisableSelector)) { + if (matches(element, linkDisableSelector)) { return disableLinkElement(element) - } else if (matches(element, Rails.buttonDisableSelector) || matches(element, Rails.formDisableSelector)) { + } else if (matches(element, buttonDisableSelector) || matches(element, formDisableSelector)) { return disableFormElement(element) - } else if (matches(element, Rails.formSubmitSelector)) { + } else if (matches(element, formSubmitSelector)) { return disableFormElements(element) } } @@ -66,7 +73,7 @@ var enableLinkElement = function(element) { // - Caches element value in 'ujs:enable-with' data store // - Replaces element text with value of 'data-disable-with' attribute // - Sets disabled property to true -var disableFormElements = form => formElements(form, Rails.formDisableSelector).forEach(disableFormElement) +var disableFormElements = form => formElements(form, formDisableSelector).forEach(disableFormElement) var disableFormElement = function(element) { if (getData(element, "ujs:disabled")) { return } @@ -87,7 +94,7 @@ var disableFormElement = function(element) { // Re-enables disabled form elements: // - Replaces element text with cached value from 'ujs:enable-with' data store (created in `disableFormElements`) // - Sets disabled property to false -var enableFormElements = form => formElements(form, Rails.formEnableSelector).forEach(enableFormElement) +var enableFormElements = form => formElements(form, formEnableSelector).forEach(element => enableFormElement(element)) var enableFormElement = function(element) { const originalText = getData(element, "ujs:enable-with") @@ -107,3 +114,5 @@ var isXhrRedirect = function(event) { const xhr = event.detail ? event.detail[0] : undefined return xhr && xhr.getResponseHeader("X-Xhr-Redirect") } + +export { handleDisabledElement, enableElement, disableElement } diff --git a/actionview/app/javascript/rails-ujs/features/method.js b/actionview/app/javascript/rails-ujs/features/method.js index 1acb220b016..0bf8b071a9a 100644 --- a/actionview/app/javascript/rails-ujs/features/method.js +++ b/actionview/app/javascript/rails-ujs/features/method.js @@ -1,21 +1,21 @@ -//= require_tree ../utils - -const { stopEverything } = Rails +import { isCrossDomain } from "../utils/ajax" +import * as csrf from "../utils/csrf" +import { stopEverything } from "../utils/event" // Handles "data-method" on links such as: // Delete -Rails.handleMethod = function(e) { +const handleMethodWithRails = (rails) => function(e) { const link = this const method = link.getAttribute("data-method") if (!method) { return } - const href = Rails.href(link) - const csrfToken = Rails.csrfToken() - const csrfParam = Rails.csrfParam() + const href = rails.href(link) + const csrfToken = csrf.csrfToken() + const csrfParam = csrf.csrfParam() const form = document.createElement("form") let formContent = `` - if (csrfParam && csrfToken && !Rails.isCrossDomain(href)) { + if (csrfParam && csrfToken && !isCrossDomain(href)) { formContent += `` } @@ -34,3 +34,5 @@ Rails.handleMethod = function(e) { stopEverything(e) } + +export { handleMethodWithRails } diff --git a/actionview/app/javascript/rails-ujs/features/remote.js b/actionview/app/javascript/rails-ujs/features/remote.js index 3551d5cae79..7f225e80dd7 100644 --- a/actionview/app/javascript/rails-ujs/features/remote.js +++ b/actionview/app/javascript/rails-ujs/features/remote.js @@ -1,11 +1,8 @@ -//= require_tree ../utils - -const { - matches, getData, setData, - fire, stopEverything, - ajax, isCrossDomain, - serializeElement -} = Rails +import { formSubmitSelector, buttonClickSelector, inputChangeSelector } from "../utils/constants" +import { ajax, isCrossDomain } from "../utils/ajax" +import { matches, getData, setData } from "../utils/dom" +import { fire, stopEverything } from "../utils/event" +import { serializeElement } from "../utils/form" // Checks "data-remote" if true to handle the request through a XHR request. const isRemote = function(element) { @@ -14,7 +11,7 @@ const isRemote = function(element) { } // Submits "remote" forms and links with ajax -Rails.handleRemote = function(e) { +const handleRemoteWithRails = (rails) => function(e) { let data, method, url const element = this @@ -27,7 +24,7 @@ Rails.handleRemote = function(e) { const withCredentials = element.getAttribute("data-with-credentials") const dataType = element.getAttribute("data-type") || "script" - if (matches(element, Rails.formSubmitSelector)) { + if (matches(element, formSubmitSelector)) { // memoized value from clicked submit button const button = getData(element, "ujs:submit-button") method = getData(element, "ujs:submit-button-formmethod") || element.getAttribute("method") || "get" @@ -46,13 +43,13 @@ Rails.handleRemote = function(e) { setData(element, "ujs:submit-button", null) setData(element, "ujs:submit-button-formmethod", null) setData(element, "ujs:submit-button-formaction", null) - } else if (matches(element, Rails.buttonClickSelector) || matches(element, Rails.inputChangeSelector)) { + } else if (matches(element, buttonClickSelector) || matches(element, inputChangeSelector)) { method = element.getAttribute("data-method") url = element.getAttribute("data-url") data = serializeElement(element, element.getAttribute("data-params")) } else { method = element.getAttribute("data-method") - url = Rails.href(element) + url = rails.href(element) data = element.getAttribute("data-params") } @@ -79,7 +76,7 @@ Rails.handleRemote = function(e) { stopEverything(e) } -Rails.formSubmitButtonClick = function(e) { +const formSubmitButtonClick = function(e) { const button = this const { form @@ -93,7 +90,7 @@ Rails.formSubmitButtonClick = function(e) { return setData(form, "ujs:submit-button-formmethod", button.getAttribute("formmethod")) } -Rails.preventInsignificantClick = function(e) { +const preventInsignificantClick = function(e) { const link = this const method = (link.getAttribute("data-method") || "GET").toUpperCase() const data = link.getAttribute("data-params") @@ -103,3 +100,4 @@ Rails.preventInsignificantClick = function(e) { if (nonPrimaryMouseClick || insignificantMetaClick) { e.stopImmediatePropagation() } } +export { handleRemoteWithRails, formSubmitButtonClick, preventInsignificantClick } diff --git a/actionview/app/javascript/rails-ujs/index.js b/actionview/app/javascript/rails-ujs/index.js new file mode 100644 index 00000000000..89607e7de09 --- /dev/null +++ b/actionview/app/javascript/rails-ujs/index.js @@ -0,0 +1,164 @@ +import { + linkClickSelector, + buttonClickSelector, + inputChangeSelector, + formSubmitSelector, + formInputClickSelector, + formDisableSelector, + formEnableSelector, + fileInputSelector, + linkDisableSelector, + buttonDisableSelector +} from "./utils/constants" + +import { ajax, href, isCrossDomain } from "./utils/ajax" +import { cspNonce, loadCSPNonce } from "./utils/csp" +import { csrfToken, csrfParam, CSRFProtection, refreshCSRFTokens } from "./utils/csrf" +import { matches, getData, setData, $ } from "./utils/dom" +import { fire, stopEverything, delegate } from "./utils/event" +import { serializeElement, formElements } from "./utils/form" + +import { confirm, handleConfirmWithRails } from "./features/confirm" +import { handleDisabledElement, enableElement, disableElement } from "./features/disable" +import { handleMethodWithRails } from "./features/method" +import { handleRemoteWithRails, formSubmitButtonClick, preventInsignificantClick } from "./features/remote" + +const Rails = { + $, + ajax, + buttonClickSelector, + buttonDisableSelector, + confirm, + cspNonce, + csrfToken, + csrfParam, + CSRFProtection, + delegate, + disableElement, + enableElement, + fileInputSelector, + fire, + formElements, + formEnableSelector, + formDisableSelector, + formInputClickSelector, + formSubmitButtonClick, + formSubmitSelector, + getData, + handleDisabledElement, + href, + inputChangeSelector, + isCrossDomain, + linkClickSelector, + linkDisableSelector, + loadCSPNonce, + matches, + preventInsignificantClick, + refreshCSRFTokens, + serializeElement, + setData, + stopEverything +} + +// needs to be able to call Rails.confirm in case its overridden +const handleConfirm = handleConfirmWithRails(Rails) +Rails.handleConfirm = handleConfirm + +// needs to be able to call Rails.href in case its overridden +const handleMethod = handleMethodWithRails(Rails) +Rails.handleMethod = handleMethod + +// needs to be able to call Rails.href in case its overridden +const handleRemote = handleRemoteWithRails(Rails) +Rails.handleRemote = handleRemote + +const start = function() { + // Cut down on the number of issues from people inadvertently including + // rails-ujs twice by detecting and raising an error when it happens. + if (window._rails_loaded) { throw new Error("rails-ujs has already been loaded!") } + + // This event works the same as the load event, except that it fires every + // time the page is loaded. + // See https://github.com/rails/jquery-ujs/issues/357 + // See https://developer.mozilla.org/en-US/docs/Using_Firefox_1.5_caching + window.addEventListener("pageshow", function() { + $(formEnableSelector).forEach(function(el) { + if (getData(el, "ujs:disabled")) { + enableElement(el) + } + }) + $(linkDisableSelector).forEach(function(el) { + if (getData(el, "ujs:disabled")) { + enableElement(el) + } + }) + }) + + delegate(document, linkDisableSelector, "ajax:complete", enableElement) + delegate(document, linkDisableSelector, "ajax:stopped", enableElement) + delegate(document, buttonDisableSelector, "ajax:complete", enableElement) + delegate(document, buttonDisableSelector, "ajax:stopped", enableElement) + + delegate(document, linkClickSelector, "click", preventInsignificantClick) + delegate(document, linkClickSelector, "click", handleDisabledElement) + delegate(document, linkClickSelector, "click", handleConfirm) + delegate(document, linkClickSelector, "click", disableElement) + delegate(document, linkClickSelector, "click", handleRemote) + delegate(document, linkClickSelector, "click", handleMethod) + + delegate(document, buttonClickSelector, "click", preventInsignificantClick) + delegate(document, buttonClickSelector, "click", handleDisabledElement) + delegate(document, buttonClickSelector, "click", handleConfirm) + delegate(document, buttonClickSelector, "click", disableElement) + delegate(document, buttonClickSelector, "click", handleRemote) + + delegate(document, inputChangeSelector, "change", handleDisabledElement) + delegate(document, inputChangeSelector, "change", handleConfirm) + delegate(document, inputChangeSelector, "change", handleRemote) + + delegate(document, formSubmitSelector, "submit", handleDisabledElement) + delegate(document, formSubmitSelector, "submit", handleConfirm) + delegate(document, formSubmitSelector, "submit", handleRemote) + // Normal mode submit + // Slight timeout so that the submit button gets properly serialized + delegate(document, formSubmitSelector, "submit", e => setTimeout((() => disableElement(e)), 13)) + delegate(document, formSubmitSelector, "ajax:send", disableElement) + delegate(document, formSubmitSelector, "ajax:complete", enableElement) + + delegate(document, formInputClickSelector, "click", preventInsignificantClick) + delegate(document, formInputClickSelector, "click", handleDisabledElement) + delegate(document, formInputClickSelector, "click", handleConfirm) + delegate(document, formInputClickSelector, "click", formSubmitButtonClick) + + document.addEventListener("DOMContentLoaded", refreshCSRFTokens) + document.addEventListener("DOMContentLoaded", loadCSPNonce) + return window._rails_loaded = true +} +Rails.start = start + +// For backward compatibility +if (typeof jQuery !== "undefined" && jQuery && jQuery.ajax) { + if (jQuery.rails) { throw new Error("If you load both jquery_ujs and rails-ujs, use rails-ujs only.") } + jQuery.rails = Rails + jQuery.ajaxPrefilter(function(options, originalOptions, xhr) { + if (!options.crossDomain) { return CSRFProtection(xhr) } + }) +} + +// This block is to maintain backwards compatibility with the existing +// difference between what happens in a bundler and what happens using a +// sprockets compiler. In the sprockets case, Rails.start() is called +// automatically, but it is not in the ESModule case. +if (typeof exports !== "object" && typeof module === "undefined") { + // The coffeescript bundle would set this at the very top. The Rollup bundle + // doesn't set this until the entire bundle has finished running, so we need + // to make sure its set before firing the rails:attachBindings event for + // backwards compatibility. + window.Rails = Rails + + if (fire(document, "rails:attachBindings")) { + start() + } +} + +export default Rails diff --git a/actionview/app/javascript/rails-ujs/start.js b/actionview/app/javascript/rails-ujs/start.js deleted file mode 100644 index 79182d7f3e7..00000000000 --- a/actionview/app/javascript/rails-ujs/start.js +++ /dev/null @@ -1,86 +0,0 @@ -const { - fire, delegate, - getData, $, - refreshCSRFTokens, CSRFProtection, - loadCSPNonce, - enableElement, disableElement, handleDisabledElement, - handleConfirm, preventInsignificantClick, - handleRemote, formSubmitButtonClick, - handleMethod -} = Rails - -// For backward compatibility -if (typeof jQuery !== "undefined" && jQuery && jQuery.ajax) { - if (jQuery.rails) { throw new Error("If you load both jquery_ujs and rails-ujs, use rails-ujs only.") } - jQuery.rails = Rails - jQuery.ajaxPrefilter(function(options, originalOptions, xhr) { - if (!options.crossDomain) { return CSRFProtection(xhr) } - }) -} - -Rails.start = function() { - // Cut down on the number of issues from people inadvertently including - // rails-ujs twice by detecting and raising an error when it happens. - if (window._rails_loaded) { throw new Error("rails-ujs has already been loaded!") } - - // This event works the same as the load event, except that it fires every - // time the page is loaded. - // See https://github.com/rails/jquery-ujs/issues/357 - // See https://developer.mozilla.org/en-US/docs/Using_Firefox_1.5_caching - window.addEventListener("pageshow", function() { - $(Rails.formEnableSelector).forEach(function(el) { - if (getData(el, "ujs:disabled")) { - enableElement(el) - } - }) - $(Rails.linkDisableSelector).forEach(function(el) { - if (getData(el, "ujs:disabled")) { - enableElement(el) - } - }) - }) - - delegate(document, Rails.linkDisableSelector, "ajax:complete", enableElement) - delegate(document, Rails.linkDisableSelector, "ajax:stopped", enableElement) - delegate(document, Rails.buttonDisableSelector, "ajax:complete", enableElement) - delegate(document, Rails.buttonDisableSelector, "ajax:stopped", enableElement) - - delegate(document, Rails.linkClickSelector, "click", preventInsignificantClick) - delegate(document, Rails.linkClickSelector, "click", handleDisabledElement) - delegate(document, Rails.linkClickSelector, "click", handleConfirm) - delegate(document, Rails.linkClickSelector, "click", disableElement) - delegate(document, Rails.linkClickSelector, "click", handleRemote) - delegate(document, Rails.linkClickSelector, "click", handleMethod) - - delegate(document, Rails.buttonClickSelector, "click", preventInsignificantClick) - delegate(document, Rails.buttonClickSelector, "click", handleDisabledElement) - delegate(document, Rails.buttonClickSelector, "click", handleConfirm) - delegate(document, Rails.buttonClickSelector, "click", disableElement) - delegate(document, Rails.buttonClickSelector, "click", handleRemote) - - delegate(document, Rails.inputChangeSelector, "change", handleDisabledElement) - delegate(document, Rails.inputChangeSelector, "change", handleConfirm) - delegate(document, Rails.inputChangeSelector, "change", handleRemote) - - delegate(document, Rails.formSubmitSelector, "submit", handleDisabledElement) - delegate(document, Rails.formSubmitSelector, "submit", handleConfirm) - delegate(document, Rails.formSubmitSelector, "submit", handleRemote) - // Normal mode submit - // Slight timeout so that the submit button gets properly serialized - delegate(document, Rails.formSubmitSelector, "submit", e => setTimeout((() => disableElement(e)), 13)) - delegate(document, Rails.formSubmitSelector, "ajax:send", disableElement) - delegate(document, Rails.formSubmitSelector, "ajax:complete", enableElement) - - delegate(document, Rails.formInputClickSelector, "click", preventInsignificantClick) - delegate(document, Rails.formInputClickSelector, "click", handleDisabledElement) - delegate(document, Rails.formInputClickSelector, "click", handleConfirm) - delegate(document, Rails.formInputClickSelector, "click", formSubmitButtonClick) - - document.addEventListener("DOMContentLoaded", refreshCSRFTokens) - document.addEventListener("DOMContentLoaded", loadCSPNonce) - return window._rails_loaded = true -} - -if ((window.Rails === Rails) && fire(document, "rails:attachBindings")) { - Rails.start() -} diff --git a/actionview/app/javascript/rails-ujs/utils/ajax.js b/actionview/app/javascript/rails-ujs/utils/ajax.js index a366e1ee118..2c2125e0e86 100644 --- a/actionview/app/javascript/rails-ujs/utils/ajax.js +++ b/actionview/app/javascript/rails-ujs/utils/ajax.js @@ -1,8 +1,5 @@ -//= require ./csp -//= require ./csrf -//= require ./event - -const { cspNonce, CSRFProtection, fire } = Rails +import { cspNonce } from "./csp" +import { CSRFProtection } from "./csrf" const AcceptHeaders = { "*": "*/*", @@ -13,7 +10,7 @@ const AcceptHeaders = { script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript" } -Rails.ajax = function(options) { +const ajax = (options) => { options = prepareOptions(options) var xhr = createXHR(options, function() { const response = processResponse(xhr.response != null ? xhr.response : xhr.responseText, xhr.getResponseHeader("Content-Type")) @@ -97,10 +94,10 @@ var processResponse = function(response, type) { } // Default way to get an element's href. May be overridden at Rails.href. -Rails.href = element => element.href +const href = element => element.href // Determines if the request is a cross domain request. -Rails.isCrossDomain = function(url) { +const isCrossDomain = function(url) { const originAnchor = document.createElement("a") originAnchor.href = location.href const urlAnchor = document.createElement("a") @@ -118,3 +115,5 @@ Rails.isCrossDomain = function(url) { return true } } + +export { ajax, href, isCrossDomain } diff --git a/actionview/app/javascript/rails-ujs/utils/constants.js b/actionview/app/javascript/rails-ujs/utils/constants.js new file mode 100644 index 00000000000..1973d28eb44 --- /dev/null +++ b/actionview/app/javascript/rails-ujs/utils/constants.js @@ -0,0 +1,45 @@ +// Link elements bound by rails-ujs +const linkClickSelector = "a[data-confirm], a[data-method], a[data-remote]:not([disabled]), a[data-disable-with], a[data-disable]" + +// Button elements bound by rails-ujs +const buttonClickSelector = { + selector: "button[data-remote]:not([form]), button[data-confirm]:not([form])", + exclude: "form button" +} + +// Select elements bound by rails-ujs +const inputChangeSelector = "select[data-remote], input[data-remote], textarea[data-remote]" + +// Form elements bound by rails-ujs +const formSubmitSelector = "form:not([data-turbo=true])" + +// Form input elements bound by rails-ujs +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])" + +// Form input elements disabled during form submission +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" + +// Form input elements re-enabled after form submission +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" + +// Form file input elements +const fileInputSelector = "input[name][type=file]:not([disabled])" + +// Link onClick disable selector with possible re-enable after remote submission +const linkDisableSelector = "a[data-disable-with], a[data-disable]" + +// Button onClick disable selector with possible re-enable after remote submission +const buttonDisableSelector = "button[data-remote][data-disable-with], button[data-remote][data-disable]" + +export { + linkClickSelector, + buttonClickSelector, + inputChangeSelector, + formSubmitSelector, + formInputClickSelector, + formDisableSelector, + formEnableSelector, + fileInputSelector, + linkDisableSelector, + buttonDisableSelector +} diff --git a/actionview/app/javascript/rails-ujs/utils/csp.js b/actionview/app/javascript/rails-ujs/utils/csp.js index 4ddd76f1805..ab71796c7b2 100644 --- a/actionview/app/javascript/rails-ujs/utils/csp.js +++ b/actionview/app/javascript/rails-ujs/utils/csp.js @@ -1,9 +1,11 @@ let nonce = null -Rails.loadCSPNonce = () => { +const loadCSPNonce = () => { const metaTag = document.querySelector("meta[name=csp-nonce]") return nonce = metaTag && metaTag.content } // Returns the Content-Security-Policy nonce for inline scripts. -Rails.cspNonce = () => nonce || Rails.loadCSPNonce() +const cspNonce = () => nonce || loadCSPNonce() + +export { cspNonce, loadCSPNonce } diff --git a/actionview/app/javascript/rails-ujs/utils/csrf.js b/actionview/app/javascript/rails-ujs/utils/csrf.js index 96ac78293b2..da7a69d5a82 100644 --- a/actionview/app/javascript/rails-ujs/utils/csrf.js +++ b/actionview/app/javascript/rails-ujs/utils/csrf.js @@ -1,30 +1,30 @@ -//= require ./dom - -const { $ } = Rails +import { $ } from "./dom" // Up-to-date Cross-Site Request Forgery token -const csrfToken = (Rails.csrfToken = function() { +const csrfToken = () => { const meta = document.querySelector("meta[name=csrf-token]") return meta && meta.content -}) +} // URL param that must contain the CSRF token -const csrfParam = (Rails.csrfParam = function() { +const csrfParam = () => { const meta = document.querySelector("meta[name=csrf-param]") return meta && meta.content -}) +} // Make sure that every Ajax request sends the CSRF token -Rails.CSRFProtection = function(xhr) { +const CSRFProtection = (xhr) => { const token = csrfToken() if (token) { return xhr.setRequestHeader("X-CSRF-Token", token) } } // Make sure that all forms have actual up-to-date tokens (cached forms contain old ones) -Rails.refreshCSRFTokens = function() { +const refreshCSRFTokens = () => { const token = csrfToken() const param = csrfParam() if (token && param) { return $("form input[name=\"" + param + "\"]").forEach(input => input.value = token) } } + +export { csrfToken, csrfParam, CSRFProtection, refreshCSRFTokens } diff --git a/actionview/app/javascript/rails-ujs/utils/dom.js b/actionview/app/javascript/rails-ujs/utils/dom.js index dbf26ad7381..39e2f1a4d02 100644 --- a/actionview/app/javascript/rails-ujs/utils/dom.js +++ b/actionview/app/javascript/rails-ujs/utils/dom.js @@ -12,7 +12,7 @@ const m = Element.prototype.matches || // CSS selector string or // a JavaScript object with `selector` and `exclude` properties // Examples: "form", { selector: "form", exclude: "form[data-remote='true']"} -Rails.matches = function(element, selector) { +const matches = function(element, selector) { if (selector.exclude) { return m.call(element, selector.selector) && !m.call(element, selector.exclude) } else { @@ -22,15 +22,17 @@ Rails.matches = function(element, selector) { // get and set data on a given element using "expando properties" // See: https://developer.mozilla.org/en-US/docs/Glossary/Expando -const expando = "_ujsData" +const EXPANDO = "_ujsData" -Rails.getData = (element, key) => element[expando] ? element[expando][key] : undefined +const getData = (element, key) => element[EXPANDO] ? element[EXPANDO][key] : undefined -Rails.setData = function(element, key, value) { - if (!element[expando]) { element[expando] = {} } - return element[expando][key] = value +const setData = function(element, key, value) { + if (!element[EXPANDO]) { element[EXPANDO] = {} } + return element[EXPANDO][key] = value } // a wrapper for document.querySelectorAll // returns an Array -Rails.$ = selector => Array.prototype.slice.call(document.querySelectorAll(selector)) +const $ = selector => Array.prototype.slice.call(document.querySelectorAll(selector)) + +export { matches, getData, setData, $ } diff --git a/actionview/app/javascript/rails-ujs/utils/event.js b/actionview/app/javascript/rails-ujs/utils/event.js index 15b8720633c..be526b8aff6 100644 --- a/actionview/app/javascript/rails-ujs/utils/event.js +++ b/actionview/app/javascript/rails-ujs/utils/event.js @@ -1,7 +1,6 @@ -//= require ./dom +import { matches } from "./dom" let preventDefault -const { matches } = Rails // Polyfill for CustomEvent in IE9+ // https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#Polyfill @@ -38,20 +37,20 @@ if (typeof CustomEvent !== "function") { // e.g. 'click', 'submit' // data:: // data you want to pass when you dispatch an event -const fire = (Rails.fire = function(obj, name, data) { +const fire = (obj, name, data) => { const event = new CustomEvent( name, { bubbles: true, cancelable: true, - detail: data, + detail: data } ) obj.dispatchEvent(event) return !event.defaultPrevented -}) +} // Helper function, needed to provide consistent behavior in IE -Rails.stopEverything = function(e) { +const stopEverything = (e) => { fire(e.target, "ujs:everythingStopped") e.preventDefault() e.stopPropagation() @@ -69,7 +68,7 @@ Rails.stopEverything = function(e) { // string representing the event e.g. 'submit', 'click' // handler:: // the event handler to be called -Rails.delegate = (element, selector, eventType, handler) => element.addEventListener(eventType, function(e) { +const delegate = (element, selector, eventType, handler) => element.addEventListener(eventType, function(e) { let { target } = e @@ -79,3 +78,5 @@ Rails.delegate = (element, selector, eventType, handler) => element.addEventList e.stopPropagation() } }) + +export { fire, stopEverything, delegate } diff --git a/actionview/app/javascript/rails-ujs/utils/form.js b/actionview/app/javascript/rails-ujs/utils/form.js index a31c4a10913..8f0d38accda 100644 --- a/actionview/app/javascript/rails-ujs/utils/form.js +++ b/actionview/app/javascript/rails-ujs/utils/form.js @@ -1,10 +1,8 @@ -//= require ./dom - -const { matches } = Rails +import { matches } from "./dom" const toArray = e => Array.prototype.slice.call(e) -Rails.serializeElement = function(element, additionalParam) { +const serializeElement = (element, additionalParam) => { let inputs = [element] if (matches(element, "form")) { inputs = toArray(element.elements) } const params = [] @@ -34,10 +32,12 @@ Rails.serializeElement = function(element, additionalParam) { // Helper function that returns form elements that match the specified CSS selector // If form is actually a "form" element this will return associated elements outside the from that have // the html form attribute set -Rails.formElements = function(form, selector) { +const formElements = (form, selector) => { if (matches(form, "form")) { return toArray(form.elements).filter(el => matches(el, selector)) } else { return toArray(form.querySelectorAll(selector)) } } + +export { serializeElement, formElements } diff --git a/actionview/blade.yml b/actionview/blade.yml index 9e5eb953a42..12f60ab3784 100644 --- a/actionview/blade.yml +++ b/actionview/blade.yml @@ -3,9 +3,3 @@ load_paths: logical_paths: - rails-ujs.js - -build: - logical_paths: - - rails-ujs.js - path: lib/assets/compiled - clean: true diff --git a/actionview/package.json b/actionview/package.json index 288fe6958b1..5dbbc640257 100644 --- a/actionview/package.json +++ b/actionview/package.json @@ -2,17 +2,18 @@ "name": "@rails/ujs", "version": "7.1.0-alpha", "description": "Ruby on Rails unobtrusive scripting adapter", - "main": "lib/assets/compiled/rails-ujs.js", + "main": "app/assets/javascripts/rails-ujs.js", + "module": "app/assets/javascripts/rails-ujs.esm.js", "files": [ - "lib/assets/compiled/*.js" + "app/assets/javascripts/*.js" ], "directories": { "test": "test" }, "scripts": { - "build": "bundle exec blade build", + "build": "rollup --config rollup.config.js", "test": "echo \"See the README: https://github.com/rails/rails/blob/main/actionview/app/assets/javascripts#how-to-run-tests\" && exit 1", - "lint": "coffeelint app/assets/javascripts && eslint test/ujs/public/test" + "lint": "eslint app/javascript" }, "repository": { "type": "git", @@ -30,7 +31,9 @@ }, "homepage": "https://rubyonrails.org/", "devDependencies": { - "coffeelint": "^2.1.0", - "eslint": "^2.13.1" + "eslint": "^4.19.1", + "eslint-plugin-import": "^2.23.4", + "rollup": "^2.53.3", + "rollup-plugin-terser": "^7.0.2" } } diff --git a/actionview/rollup.config.js b/actionview/rollup.config.js new file mode 100644 index 00000000000..7f9d4164e62 --- /dev/null +++ b/actionview/rollup.config.js @@ -0,0 +1,51 @@ +import { terser } from "rollup-plugin-terser" + +const banner = ` +/* +Unobtrusive JavaScript +https://github.com/rails/rails/blob/main/actionview/app/javascript +Released under the MIT license + */ +` + +const terserOptions = { + mangle: false, + compress: false, + format: { + beautify: true, + indent_level: 2, + comments: function (node, comment) { + if (comment.type == "comment2") { + // multiline comment + return comment.value.includes("Released under the MIT license") + } + } + } +} + +export default [ + { + input: "app/javascript/rails-ujs/index.js", + output: { + file: "app/assets/javascripts/rails-ujs.js", + format: "umd", + name: "Rails", + banner, + }, + plugins: [ + terser(terserOptions) + ] + }, + + { + input: "app/javascript/rails-ujs/index.js", + output: { + file: "app/assets/javascripts/rails-ujs.esm.js", + format: "es", + banner, + }, + plugins: [ + terser(terserOptions) + ] + } +] diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index 8e8c36bc306..d6c98493455 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -534,12 +534,6 @@ Module.new do f.puts 'require "rails/all"' end - unless File.exist?("#{RAILS_FRAMEWORK_ROOT}/actionview/lib/assets/compiled/rails-ujs.js") - Dir.chdir("#{RAILS_FRAMEWORK_ROOT}/actionview") do - sh "yarn build" - end - end - assets_path = "#{RAILS_FRAMEWORK_ROOT}/railties/test/isolation/assets" unless Dir.exist?("#{assets_path}/node_modules") Dir.chdir(assets_path) do diff --git a/yarn.lock b/yarn.lock index f3a78b515aa..d8d9be31d1e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -293,11 +293,6 @@ ajv-errors@^1.0.0: resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.0.tgz#ecf021fa108fd17dfb5e6b383f2dd233e31ffc59" integrity sha1-7PAh+hCP0X37Xms4Py3SM+Mf/Fk= -ajv-keywords@^1.0.0: - version "1.5.1" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c" - integrity sha1-MU3QpLM2j609/NxU7eYXG4htrzw= - ajv-keywords@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.1.tgz#617997fc5f60576894c435f940d819e135b80762" @@ -308,14 +303,6 @@ ajv-keywords@^3.1.0: resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.2.0.tgz#e86b819c602cf8821ad637413698f1dec021847a" integrity sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo= -ajv@^4.7.0: - version "4.11.8" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" - integrity sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY= - dependencies: - co "^4.6.0" - json-stable-stringify "^1.0.1" - ajv@^5.2.3, ajv@^5.3.0: version "5.5.2" resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" @@ -336,11 +323,6 @@ ajv@^6.1.0, ajv@^6.5.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ansi-escapes@^1.1.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e" - integrity sha1-06ioOzGapneTZisT52HHkRQiMG4= - ansi-escapes@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.1.0.tgz#f73207bb81207d75fd6c83f125af26eea378ca30" @@ -907,7 +889,7 @@ caseless@~0.12.0: resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= -chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: +chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= @@ -992,13 +974,6 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" -cli-cursor@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987" - integrity sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc= - dependencies: - restore-cursor "^1.0.1" - cli-cursor@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" @@ -1016,28 +991,6 @@ co@^4.6.0: resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= -code-point-at@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" - integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= - -coffeelint@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/coffeelint/-/coffeelint-2.1.0.tgz#af65df3634e999d9ac01480736c36d3cd2f5dad8" - integrity sha1-r2XfNjTpmdmsAUgHNsNtPNL12tg= - dependencies: - coffeescript "^2.1.0" - glob "^7.0.6" - ignore "^3.0.9" - optimist "^0.6.1" - resolve "^0.6.3" - strip-json-comments "^1.0.2" - -coffeescript@^2.1.0: - version "2.3.2" - resolved "https://registry.yarnpkg.com/coffeescript/-/coffeescript-2.3.2.tgz#e854a7020dfe47b7cf4dd412042e32ef1e269810" - integrity sha512-YObiFDoukx7qPBi/K0kUKyntEZDfBQiqs/DbrR1xzASKOBjGT7auD85/DiPeRr9k++lRj7l3uA9TNMLfyfcD/Q== - collection-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" @@ -1127,7 +1080,7 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -concat-stream@^1.4.6, concat-stream@^1.5.0, concat-stream@^1.6.0: +concat-stream@^1.5.0, concat-stream@^1.6.0: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== @@ -1294,13 +1247,6 @@ cyclist@~0.2.2: resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640" integrity sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA= -d@1: - version "1.0.0" - resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f" - integrity sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8= - dependencies: - es5-ext "^0.10.9" - dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" @@ -1318,7 +1264,7 @@ date-now@^0.1.4: resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" integrity sha1-6vQ5/U1ISK105cx9vvIAZyueNFs= -debug@2.6.9, debug@^2.1.1, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9: +debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== @@ -1427,7 +1373,7 @@ diffie-hellman@^5.0.0: miller-rabin "^4.0.0" randombytes "^2.0.0" -doctrine@1.5.0, doctrine@^1.2.2: +doctrine@1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" integrity sha1-N53Ocw9hZvds76TmcHoVmwLFpvo= @@ -1615,36 +1561,6 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" -es5-ext@^0.10.14, es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14: - version "0.10.46" - resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.46.tgz#efd99f67c5a7ec789baa3daa7f79870388f7f572" - integrity sha512-24XxRvJXNFwEMpJb3nOkiRJKRoupmjYmOPVlI65Qy2SrtxwOTB+g6ODjBKOtwEHbYrhWRty9xxOWLNdClT2djw== - dependencies: - es6-iterator "~2.0.3" - es6-symbol "~3.1.1" - next-tick "1" - -es6-iterator@^2.0.1, es6-iterator@~2.0.1, es6-iterator@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" - integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c= - dependencies: - d "1" - es5-ext "^0.10.35" - es6-symbol "^3.1.1" - -es6-map@^0.1.3: - version "0.1.5" - resolved "https://registry.yarnpkg.com/es6-map/-/es6-map-0.1.5.tgz#9136e0503dcc06a301690f0bb14ff4e364e949f0" - integrity sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA= - dependencies: - d "1" - es5-ext "~0.10.14" - es6-iterator "~2.0.1" - es6-set "~0.1.5" - es6-symbol "~3.1.1" - event-emitter "~0.3.5" - es6-promise@^4.0.3: version "4.2.5" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.5.tgz#da6d0d5692efb461e082c14817fe2427d8f5d054" @@ -1657,35 +1573,6 @@ es6-promisify@^5.0.0: dependencies: es6-promise "^4.0.3" -es6-set@~0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.5.tgz#d2b3ec5d4d800ced818db538d28974db0a73ccb1" - integrity sha1-0rPsXU2ADO2BjbU40ol02wpzzLE= - dependencies: - d "1" - es5-ext "~0.10.14" - es6-iterator "~2.0.1" - es6-symbol "3.1.1" - event-emitter "~0.3.5" - -es6-symbol@3.1.1, es6-symbol@^3.1.1, es6-symbol@~3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77" - integrity sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc= - dependencies: - d "1" - es5-ext "~0.10.14" - -es6-weak-map@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.2.tgz#5e3ab32251ffd1538a1f8e5ffa1357772f92d96f" - integrity sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8= - dependencies: - d "1" - es5-ext "^0.10.14" - es6-iterator "^2.0.1" - es6-symbol "^3.1.1" - escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" @@ -1696,16 +1583,6 @@ escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= -escope@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/escope/-/escope-3.6.0.tgz#e01975e812781a163a6dadfdd80398dc64c889c3" - integrity sha1-4Bl16BJ4GhY6ba392AOY3GTIicM= - dependencies: - es6-map "^0.1.3" - es6-weak-map "^2.0.1" - esrecurse "^4.1.0" - estraverse "^4.1.1" - eslint-import-resolver-node@^0.3.1: version "0.3.2" resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz#58f15fb839b8d0576ca980413476aab2472db66a" @@ -1796,46 +1673,7 @@ eslint-visitor-keys@^1.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d" integrity sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ== -eslint@^2.13.1: - version "2.13.1" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-2.13.1.tgz#e4cc8fa0f009fb829aaae23855a29360be1f6c11" - integrity sha1-5MyPoPAJ+4KaquI4VaKTYL4fbBE= - dependencies: - chalk "^1.1.3" - concat-stream "^1.4.6" - debug "^2.1.1" - doctrine "^1.2.2" - es6-map "^0.1.3" - escope "^3.6.0" - espree "^3.1.6" - estraverse "^4.2.0" - esutils "^2.0.2" - file-entry-cache "^1.1.1" - glob "^7.0.3" - globals "^9.2.0" - ignore "^3.1.2" - imurmurhash "^0.1.4" - inquirer "^0.12.0" - is-my-json-valid "^2.10.0" - is-resolvable "^1.0.0" - js-yaml "^3.5.1" - json-stable-stringify "^1.0.0" - levn "^0.3.0" - lodash "^4.0.0" - mkdirp "^0.5.0" - optionator "^0.8.1" - path-is-absolute "^1.0.0" - path-is-inside "^1.0.1" - pluralize "^1.2.1" - progress "^1.1.8" - require-uncached "^1.0.2" - shelljs "^0.6.0" - strip-json-comments "~1.0.1" - table "^3.7.8" - text-table "~0.2.0" - user-home "^2.0.0" - -eslint@^4.3.0: +eslint@^4.19.1, eslint@^4.3.0: version "4.19.1" resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.19.1.tgz#32d1d653e1d90408854bfb296f076ec7e186a300" integrity sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ== @@ -1879,7 +1717,7 @@ eslint@^4.3.0: table "4.0.2" text-table "~0.2.0" -espree@^3.1.6, espree@^3.5.4: +espree@^3.5.4: version "3.5.4" resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.4.tgz#b0f447187c8a8bed944b815a660bddf5deb5d1a7" integrity sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A== @@ -1906,7 +1744,7 @@ esrecurse@^4.1.0: dependencies: estraverse "^4.1.0" -estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: +estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1: version "4.2.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" integrity sha1-De4/7TH81GlhjOc0IJn8GvoL2xM= @@ -1926,14 +1764,6 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" integrity sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs= -event-emitter@~0.3.5: - version "0.3.5" - resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" - integrity sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk= - dependencies: - d "1" - es5-ext "~0.10.14" - eventemitter3@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.0.tgz#090b4d6cdbd645ed10bf750d4b5407942d7ba163" @@ -1982,11 +1812,6 @@ exists-stat@1.0.0: resolved "https://registry.yarnpkg.com/exists-stat/-/exists-stat-1.0.0.tgz#0660e3525a2e89d9e446129440c272edfa24b529" integrity sha1-BmDjUlouidnkRhKUQMJy7foktSk= -exit-hook@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8" - integrity sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g= - expand-braces@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/expand-braces/-/expand-braces-0.1.2.tgz#488b1d1d2451cb3d3a6b192cfc030f44c5855fea" @@ -2109,14 +1934,6 @@ figgy-pudding@^3.1.0, figgy-pudding@^3.5.1: resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790" integrity sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w== -figures@^1.3.5: - version "1.7.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e" - integrity sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4= - dependencies: - escape-string-regexp "^1.0.5" - object-assign "^4.1.0" - figures@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" @@ -2124,14 +1941,6 @@ figures@^2.0.0: dependencies: escape-string-regexp "^1.0.5" -file-entry-cache@^1.1.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-1.3.1.tgz#44c61ea607ae4be9c1402f41f44270cbfe334ff8" - integrity sha1-RMYepgeuS+nBQC9B9EJwy/4zT/g= - dependencies: - flat-cache "^1.2.1" - object-assign "^4.0.1" - file-entry-cache@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361" @@ -2323,20 +2132,6 @@ functional-red-black-tree@^1.0.1: resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= -generate-function@^2.0.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.3.1.tgz#f069617690c10c868e73b8465746764f97c3479f" - integrity sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ== - dependencies: - is-property "^1.0.2" - -generate-object-property@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0" - integrity sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA= - dependencies: - is-property "^1.0.0" - get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" @@ -2373,7 +2168,7 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" -glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.0.6, glob@^7.1.1, glob@^7.1.2: +glob@^7.0.0, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2: version "7.1.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== @@ -2422,11 +2217,6 @@ globals@^11.0.1: resolved "https://registry.yarnpkg.com/globals/-/globals-11.9.0.tgz#bde236808e987f290768a93d065060d78e6ab249" integrity sha512-5cJVtyXWH8PiJPVLZzzoIizXx944O4OmRro5MWKx5fT4MgcN7OfaMutPeaTdJCCURwbWdhhcCWcKIffPnmTzBg== -globals@^9.2.0: - version "9.18.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" - integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ== - graceful-fs@^4.1.0, graceful-fs@^4.1.11, graceful-fs@^4.1.2: version "4.1.15" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" @@ -2624,7 +2414,7 @@ iferr@^0.1.5: resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE= -ignore@^3.0.9, ignore@^3.1.2, ignore@^3.3.3: +ignore@^3.3.3: version "3.3.10" resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043" integrity sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug== @@ -2662,25 +2452,6 @@ ini@^1.3.4: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== -inquirer@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.12.0.tgz#1ef2bfd63504df0bc75785fff8c2c41df12f077e" - integrity sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34= - dependencies: - ansi-escapes "^1.1.0" - ansi-regex "^2.0.0" - chalk "^1.0.0" - cli-cursor "^1.0.1" - cli-width "^2.0.0" - figures "^1.3.5" - lodash "^4.3.0" - readline2 "^1.0.1" - run-async "^0.1.0" - rx-lite "^3.1.2" - string-width "^1.0.1" - strip-ansi "^3.0.0" - through "^2.3.6" - inquirer@^3.0.6: version "3.3.0" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9" @@ -2826,13 +2597,6 @@ is-extglob@^2.1.0, is-extglob@^2.1.1: resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= -is-fullwidth-code-point@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" - integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= - dependencies: - number-is-nan "^1.0.0" - is-fullwidth-code-point@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" @@ -2857,22 +2621,6 @@ is-module@^1.0.0: resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE= -is-my-ip-valid@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz#7b351b8e8edd4d3995d4d066680e664d94696824" - integrity sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ== - -is-my-json-valid@^2.10.0: - version "2.19.0" - resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.19.0.tgz#8fd6e40363cd06b963fa877d444bfb5eddc62175" - integrity sha512-mG0f/unGX1HZ5ep4uhRaPOS8EkAY8/j6mDRMJrutq4CqhoJWYp7qAlonIPy3TV7p3ju4TK9fo/PbnoksWmsp5Q== - dependencies: - generate-function "^2.0.0" - generate-object-property "^1.1.0" - is-my-ip-valid "^1.0.0" - jsonpointer "^4.0.0" - xtend "^4.0.0" - is-negative-zero@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" @@ -2907,11 +2655,6 @@ is-promise@^2.1.0: resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= -is-property@^1.0.0, is-property@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" - integrity sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ= - is-reference@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.2.1.tgz#8b2dac0b371f4bc994fdeaba9eb542d03002d0b7" @@ -3022,7 +2765,7 @@ js-tokens@^4.0.0: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@^3.5.1, js-yaml@^3.9.1: +js-yaml@^3.9.1: version "3.12.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.0.tgz#eaed656ec8344f10f527c6bfa1b6e2244de167d1" integrity sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A== @@ -3060,13 +2803,6 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= -json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" - integrity sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8= - dependencies: - jsonify "~0.0.0" - json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" @@ -3084,16 +2820,6 @@ json5@^2.2.0: dependencies: minimist "^1.2.5" -jsonify@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" - integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= - -jsonpointer@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" - integrity sha1-T9kss04OnbPInIYi7PUfm5eMbLk= - jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -3255,7 +2981,7 @@ lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= -lodash@4.17.11, lodash@^4.0.0, lodash@^4.16.6, lodash@^4.17.10, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0, lodash@^4.5.0, lodash@^4.8.0: +lodash@4.17.11, lodash@^4.16.6, lodash@^4.17.10, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0, lodash@^4.5.0, lodash@^4.8.0: version "4.17.11" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== @@ -3461,7 +3187,7 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" -mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0: +mkdirp@^0.5.1, mkdirp@~0.5.0: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= @@ -3497,11 +3223,6 @@ ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== -mute-stream@0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0" - integrity sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA= - mute-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" @@ -3544,11 +3265,6 @@ neo-async@^2.5.0: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.0.tgz#b9d15e4d71c6762908654b5183ed38b753340835" integrity sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA== -next-tick@1: - version "1.0.0" - resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" - integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= - nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" @@ -3617,17 +3333,12 @@ null-check@^1.0.0: resolved "https://registry.yarnpkg.com/null-check/-/null-check-1.0.0.tgz#977dffd7176012b9ec30d2a39db5cf72a0439edd" integrity sha1-l33/1xdgErnsMNKjnbXPcqBDnt0= -number-is-nan@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" - integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= - oauth-sign@~0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== -object-assign@^4.0.1, object-assign@^4.1.0: +object-assign@^4.0.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= @@ -3703,11 +3414,6 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" -onetime@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789" - integrity sha1-ofeDj4MUxRbwXs78vEzP4EtO14k= - onetime@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" @@ -3723,7 +3429,7 @@ optimist@^0.6.1: minimist "~0.0.1" wordwrap "~0.0.2" -optionator@^0.8.1, optionator@^0.8.2: +optionator@^0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" integrity sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q= @@ -3740,11 +3446,6 @@ os-browserify@^0.3.0: resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= -os-homedir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= - os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" @@ -3889,7 +3590,7 @@ path-is-absolute@^1.0.0: resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= -path-is-inside@^1.0.1, path-is-inside@^1.0.2: +path-is-inside@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= @@ -3994,11 +3695,6 @@ pkg-up@^2.0.0: dependencies: find-up "^2.1.0" -pluralize@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45" - integrity sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU= - pluralize@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777" @@ -4024,11 +3720,6 @@ process@^0.11.10: resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= -progress@^1.1.8: - version "1.1.8" - resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" - integrity sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74= - progress@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.1.tgz#c9242169342b1c29d275889c95734621b1952e31" @@ -4242,15 +3933,6 @@ readdirp@^2.0.0: micromatch "^3.1.10" readable-stream "^2.0.2" -readline2@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/readline2/-/readline2-1.0.1.tgz#41059608ffc154757b715d9989d199ffbf372e35" - integrity sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU= - dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - mute-stream "0.0.5" - regex-not@^1.0.0, regex-not@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" @@ -4310,7 +3992,7 @@ request@2.88.0: tunnel-agent "^0.6.0" uuid "^3.3.2" -require-uncached@^1.0.2, require-uncached@^1.0.3: +require-uncached@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3" integrity sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM= @@ -4348,11 +4030,6 @@ resolve@1.5.0: dependencies: path-parse "^1.0.5" -resolve@^0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-0.6.3.tgz#dd957982e7e736debdf53b58a4dd91754575dd46" - integrity sha1-3ZV5gufnNt699TtYpN2RdUV13UY= - resolve@^1.13.1, resolve@^1.17.0, resolve@^1.19.0, resolve@^1.20.0: version "1.20.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" @@ -4368,14 +4045,6 @@ resolve@^1.5.0, resolve@^1.6.0: dependencies: path-parse "^1.0.5" -restore-cursor@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541" - integrity sha1-NGYfRohjJ/7SmRR5FSJS35LapUE= - dependencies: - exit-hook "^1.0.0" - onetime "^1.0.0" - restore-cursor@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" @@ -4419,7 +4088,7 @@ rollup-plugin-terser@^7.0.2: serialize-javascript "^4.0.0" terser "^5.0.0" -rollup@^2.35.1: +rollup@^2.35.1, rollup@^2.53.3: version "2.53.3" resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.53.3.tgz#14b0e57f0874d4ad23bdbb13050cf70bcd1eabf7" integrity sha512-79QIGP5DXz5ZHYnCPi3tLz+elOQi6gudp9YINdaJdjG0Yddubo6JRFUM//qCZ0Bap/GJrsUoEBVdSOc4AkMlRA== @@ -4431,13 +4100,6 @@ rsvp@^3.3.3: resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-3.6.2.tgz#2e96491599a96cde1b515d5674a8f7a91452926a" integrity sha512-OfWGQTb9vnwRjwtA2QwpG2ICclHC3pgXZO5xt8H2EfgDquO0qVdSb5T88L4qJVAEugbS56pAuV4XZM58UX8ulw== -run-async@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-0.1.0.tgz#c8ad4a5e110661e402a7d21b530e009f25f8e389" - integrity sha1-yK1KXhEGYeQCp9IbUw4AnyX444k= - dependencies: - once "^1.3.0" - run-async@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" @@ -4464,11 +4126,6 @@ rx-lite@*, rx-lite@^4.0.8: resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444" integrity sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ= -rx-lite@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102" - integrity sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI= - safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" @@ -4603,11 +4260,6 @@ shebang-regex@^1.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= -shelljs@^0.6.0: - version "0.6.1" - resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.6.1.tgz#ec6211bed1920442088fe0f70b2837232ed2c8a8" - integrity sha1-7GIRvtGSBEIIj+D3Cyg3Iy7SyKg= - side-channel@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" @@ -4622,11 +4274,6 @@ signal-exit@^3.0.0, signal-exit@^3.0.2: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= -slice-ansi@0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" - integrity sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU= - slice-ansi@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-1.0.0.tgz#044f1a49d8842ff307aad6b505ed178bd950134d" @@ -4892,16 +4539,7 @@ streamroller@0.7.0: mkdirp "^0.5.1" readable-stream "^2.3.0" -string-width@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" - integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= - dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - strip-ansi "^3.0.0" - -string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1: +string-width@^2.1.0, string-width@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== @@ -4963,11 +4601,6 @@ strip-eof@^1.0.0: resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= -strip-json-comments@^1.0.2, strip-json-comments@~1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91" - integrity sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E= - strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" @@ -5004,18 +4637,6 @@ table@4.0.2: slice-ansi "1.0.0" string-width "^2.1.1" -table@^3.7.8: - version "3.8.3" - resolved "https://registry.yarnpkg.com/table/-/table-3.8.3.tgz#2bbc542f0fda9861a755d3947fefd8b3f513855f" - integrity sha1-K7xULw/amGGnVdOUf+/Ys/UThV8= - dependencies: - ajv "^4.7.0" - ajv-keywords "^1.0.0" - chalk "^1.1.1" - lodash "^4.0.0" - slice-ansi "0.0.4" - string-width "^2.0.0" - tapable@^1.0.0, tapable@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.1.tgz#4d297923c5a72a42360de2ab52dadfaaec00018e" @@ -5294,13 +4915,6 @@ use@^3.1.0: resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== -user-home@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/user-home/-/user-home-2.0.0.tgz#9c70bfd8169bc1dcbf48604e0f04b8b49cde9e9f" - integrity sha1-nHC/2Babwdy/SGBODwS4tJzenp8= - dependencies: - os-homedir "^1.0.0" - useragent@2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/useragent/-/useragent-2.3.0.tgz#217f943ad540cb2128658ab23fc960f6a88c9972"