From 96f66f7bd734028e780825dbf1dced47ca4e5344 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Wed, 12 Jun 2024 23:38:42 -0500 Subject: [PATCH] Add esim-adapter checkout --- Gemfile | 1 + assets/css/loader/loader.scss | 39 + assets/css/tom_select/_dropdown.scss | 104 + assets/css/tom_select/_items.scss | 115 + .../tom_select/plugins/_dropdown_input.scss | 37 + assets/css/tom_select/tom_select.scss | 169 + assets/js/htmx/htmx.js | 2751 ++++++++++ assets/js/section_list/section_list.js | 51 + assets/js/tom_select/tom_select.js | 4806 +++++++++++++++++ config.ru | 280 +- views/braintree.slim | 15 + views/esim_adapter.slim | 160 + views/layout.slim | 1 + views/receipt.slim | 36 + views/total.slim | 17 + 15 files changed, 8535 insertions(+), 47 deletions(-) create mode 100644 assets/css/loader/loader.scss create mode 100644 assets/css/tom_select/_dropdown.scss create mode 100644 assets/css/tom_select/_items.scss create mode 100644 assets/css/tom_select/plugins/_dropdown_input.scss create mode 100644 assets/css/tom_select/tom_select.scss create mode 100644 assets/js/htmx/htmx.js create mode 100644 assets/js/section_list/section_list.js create mode 100644 assets/js/tom_select/tom_select.js create mode 100644 views/braintree.slim create mode 100644 views/esim_adapter.slim create mode 100644 views/receipt.slim create mode 100644 views/total.slim diff --git a/Gemfile b/Gemfile index 27a54190605efab798e8d8b5b2b07fdd62735d62..675f79b943e4207082eb3ee1552f4f4c3fa449a5 100644 --- a/Gemfile +++ b/Gemfile @@ -4,6 +4,7 @@ source "https://rubygems.org" gem "blather" gem "braintree" +gem "countries" gem "dhall", ">= 0.5.3.fixed" gem "em-http-request" gem "em_promise.rb" diff --git a/assets/css/loader/loader.scss b/assets/css/loader/loader.scss new file mode 100644 index 0000000000000000000000000000000000000000..b87c95e26813d8077a0162c008624a370d2cebdc --- /dev/null +++ b/assets/css/loader/loader.scss @@ -0,0 +1,39 @@ +.lds-ring, +.lds-ring div { + box-sizing: border-box; +} +.lds-ring { + display: inline-block; + position: relative; + width: 80px; + height: 80px; +} +.lds-ring div { + box-sizing: border-box; + display: block; + position: absolute; + width: 64px; + height: 64px; + margin: 8px; + border: 8px solid currentColor; + border-radius: 50%; + animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; + border-color: currentColor transparent transparent transparent; +} +.lds-ring div:nth-child(1) { + animation-delay: -0.45s; +} +.lds-ring div:nth-child(2) { + animation-delay: -0.3s; +} +.lds-ring div:nth-child(3) { + animation-delay: -0.15s; +} +@keyframes lds-ring { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} diff --git a/assets/css/tom_select/_dropdown.scss b/assets/css/tom_select/_dropdown.scss new file mode 100644 index 0000000000000000000000000000000000000000..e17119228170d8f9c9a92346e9289fbbd972a4bf --- /dev/null +++ b/assets/css/tom_select/_dropdown.scss @@ -0,0 +1,104 @@ + + +.#{$select-ns}-dropdown { + position: absolute; + top: 100%; + left: 0; + width: 100%; + z-index: 10; + + border: $select-dropdown-border; + background: $select-color-dropdown; + margin: 0.25rem 0 0 0; + border-top: 0 none; + box-sizing: border-box; + box-shadow: 0 1px 3px rgba(0,0,0,0.1); + border-radius: 0 0 $select-border-radius $select-border-radius; + + + [data-selectable] { + cursor: pointer; + overflow: hidden; + .highlight { + background: $select-color-highlight; + border-radius: 1px; + } + } + + .option, + .optgroup-header, + .no-results, + .create { + padding: $select-padding-dropdown-item-y $select-padding-dropdown-item-x; + } + + .option, [data-disabled], [data-disabled] [data-selectable].option { + cursor: inherit; + opacity: 0.5; + } + + [data-selectable].option { + opacity: 1; + cursor: pointer; + } + + .optgroup:first-child .optgroup-header { + border-top: 0 none; + } + + .optgroup-header { + color: $select-color-optgroup-text; + background: $select-color-optgroup; + cursor: default; + } + + .create:hover, + .option:hover, + .active { + background-color: $select-color-dropdown-item-active; + color: $select-color-dropdown-item-active-text; + &.create { + color: $select-color-dropdown-item-create-active-text; + } + } + + .create { + color: $select-color-dropdown-item-create-text; + } + + .spinner{ + display: inline-block; + width: $select-spinner-size; + height: $select-spinner-size; + margin: $select-padding-dropdown-item-y $select-padding-dropdown-item-x; + + + &:after { + content: " "; + display: block; + width: $select-spinner-size * .8; + height: $select-spinner-size * .8; + margin: $select-spinner-size * .1; + border-radius: 50%; + border: $select-spinner-border-size solid $select-spinner-border-color; + border-color: $select-spinner-border-color transparent $select-spinner-border-color transparent; + animation: lds-dual-ring 1.2s linear infinite; + } + @keyframes lds-dual-ring { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } + } + } +} + +.#{$select-ns}-dropdown-content { + overflow-y: auto; + overflow-x: hidden; + max-height: $select-max-height-dropdown; + overflow-scrolling: touch; + scroll-behavior: smooth; +} diff --git a/assets/css/tom_select/_items.scss b/assets/css/tom_select/_items.scss new file mode 100644 index 0000000000000000000000000000000000000000..8a03bda27ac36c5a2fffb7f6258d43e6d28a6281 --- /dev/null +++ b/assets/css/tom_select/_items.scss @@ -0,0 +1,115 @@ + + +.#{$select-ns}-control { + + border: $select-border; + padding: $select-padding-y $select-padding-x; + width: 100%; + overflow: hidden; + position: relative; + z-index: 1; + box-sizing: border-box; + box-shadow: $select-shadow-input; + border-radius: $select-border-radius; + display: flex; + flex-wrap: wrap; + + .#{$select-ns}-wrapper.multi.has-items & { + $padding-x: $select-padding-x; + $padding-top: calc( #{$select-padding-y} - #{$select-padding-item-y} - #{$select-width-item-border}); + $padding-bottom: calc( #{$select-padding-y} - #{$select-padding-item-y} - #{$select-margin-item-y} - #{$select-width-item-border}); + padding: $padding-top $padding-x $padding-bottom; + } + + .full & { + background-color: $select-color-input-full; + } + + .disabled &, + .disabled & * { + cursor: default !important; + } + + .focus & { + box-shadow: $select-shadow-input-focus; + } + + > * { + vertical-align: baseline; + display: inline-block; + } + + .#{$select-ns}-wrapper.multi & > div { + cursor: pointer; + margin: 0 $select-margin-item-x $select-margin-item-y 0; + padding: $select-padding-item-y $select-padding-item-x; + background: $select-color-item; + color: $select-color-item-text; + border: $select-width-item-border solid $select-color-item-border; + + &.active { + background: $select-color-item-active; + color: $select-color-item-active-text; + border: $select-width-item-border solid $select-color-item-active-border; + } + } + + .#{$select-ns}-wrapper.multi.disabled & > div { + &, &.active { + color: lighten(desaturate($select-color-item-text, 100%), $select-lighten-disabled-item-text); + background: lighten(desaturate($select-color-item, 100%), $select-lighten-disabled-item); + border: $select-width-item-border solid lighten(desaturate($select-color-item-border, 100%), $select-lighten-disabled-item-border); + } + } + + > input { + &::-ms-clear { + display: none; + } + + flex: 1 1 auto; + min-width: 7rem; + display: inline-block !important; + padding: 0 !important; + min-height: 0 !important; + max-height: none !important; + max-width: 100% !important; + margin: 0 !important; + text-indent: 0 !important; + border: 0 none !important; + background: none !important; + line-height: inherit !important; + user-select: auto !important; + box-shadow: none !important; + &:focus { outline: none !important; } + } + + .has-items & > input{ + margin: $select-caret-margin !important; + } + + &.rtl { + text-align: right; + &.single .#{$select-ns}-control:after { + left: $select-arrow-offset; + right: auto; + } + .#{$select-ns}-control > input { + margin: $select-caret-margin-rtl !important; + } + } + + .disabled & { + opacity: $select-opacity-disabled; + background-color: $select-color-disabled; + } + + // hide input, while retaining its focus, and maintain layout so users can still click on the space to bring the display back + // visibility:hidden can prevent the input from receiving focus + .input-hidden & > input{ + opacity: 0; + position: absolute; + left: -10000px; + } + +} diff --git a/assets/css/tom_select/plugins/_dropdown_input.scss b/assets/css/tom_select/plugins/_dropdown_input.scss new file mode 100644 index 0000000000000000000000000000000000000000..089f72175a4b6a1cbb124dd81988c9f409ec47e5 --- /dev/null +++ b/assets/css/tom_select/plugins/_dropdown_input.scss @@ -0,0 +1,37 @@ + +.plugin-dropdown_input{ + + &.focus.dropdown-active .#{$select-ns}-control{ + box-shadow: none; + border: $select-border; + @if variable-exists(input-box-shadow) { + box-shadow: $input-box-shadow; + } + } + + .dropdown-input { + border: 1px solid $select-color-border; + border-width: 0 0 1px 0; + display: block; + padding: $select-padding-y $select-padding-x; + box-shadow: $select-shadow-input; + width: 100%; + background: transparent; + } + + &.focus ~ .#{$select-ns}-dropdown .dropdown-input{ + @if variable-exists(input-focus-border-color) { + border-color: $input-focus-border-color; + + outline: 0; + @if $enable-shadows { + box-shadow: $input-box-shadow, $input-focus-box-shadow; + } @else { + box-shadow: $input-focus-box-shadow; + } + + } + + } + +} diff --git a/assets/css/tom_select/tom_select.scss b/assets/css/tom_select/tom_select.scss new file mode 100644 index 0000000000000000000000000000000000000000..a542d650395a231682de094766dfd8fe5374da54 --- /dev/null +++ b/assets/css/tom_select/tom_select.scss @@ -0,0 +1,169 @@ +/** + * tom-select.css (v2.0.0) + * Copyright (c) contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this + * file except in compliance with the License. You may obtain a copy of the License at: + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF + * ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + */ + + +// base styles +$select-ns: 'ts' !default; +$select-font-family: inherit !default; +$select-font-smoothing: inherit !default; +$select-font-size: 1.2em !default; +$select-line-height: 1.1 !default; + +$select-color-text: #303030 !default; +$select-color-border: #d0d0d0 !default; +$select-color-highlight: rgba(125,168,208,0.2) !default; +$select-color-input: #fff !default; +$select-color-input-full: $select-color-input !default; +$select-color-disabled: #fafafa !default; +$select-color-item: #f2f2f2 !default; +$select-color-item-text: $select-color-text !default; +$select-color-item-border: #d0d0d0 !default; +$select-color-item-active: #e8e8e8 !default; +$select-color-item-active-text: $select-color-text !default; +$select-color-item-active-border: #cacaca !default; +$select-color-dropdown: #fff !default; +$select-color-dropdown-border: $select-color-border !default; +$select-color-dropdown-border-top: #f0f0f0 !default; +$select-color-dropdown-item-active: #f5fafd !default; +$select-color-dropdown-item-active-text: #495c68 !default; +$select-color-dropdown-item-create-text: rgba(red($select-color-text), green($select-color-text), blue($select-color-text), 0.5) !default; +$select-color-dropdown-item-create-active-text: $select-color-dropdown-item-active-text !default; +$select-color-optgroup: $select-color-dropdown !default; +$select-color-optgroup-text: $select-color-text !default; +$select-lighten-disabled-item: 30% !default; +$select-lighten-disabled-item-text: 30% !default; +$select-lighten-disabled-item-border: 30% !default; +$select-opacity-disabled: 0.5 !default; + +$select-shadow-input: none !default; +$select-shadow-input-focus: none !default; +$select-border: 1px solid $select-color-border !default; +$select-dropdown-border: 1px solid $select-color-dropdown-border !default; +$select-border-radius: 3px !default; + +$select-width-item-border: 0 !default; +$select-max-height-dropdown: 200px !default; + +$select-padding-x: 8px !default; +$select-padding-y: 8px !default; +$select-padding-item-x: 6px !default; +$select-padding-item-y: 2px !default; +$select-padding-dropdown-item-x: $select-padding-x !default; +$select-padding-dropdown-item-y: 5px !default; +$select-margin-item-x: 3px !default; +$select-margin-item-y: 3px !default; + +$select-arrow-size: 5px !default; +$select-arrow-color: #808080 !default; +$select-arrow-offset: 15px !default; + +$select-caret-margin: 0 4px !default; +$select-caret-margin-rtl: 0 4px 0 -2px !default; + +$select-spinner-size: 30px !default; +$select-spinner-border-size: 5px !default; +$select-spinner-border-color: $select-color-border !default; + +@mixin selectize-vertical-gradient($color-top, $color-bottom) { + background-color: mix($color-top, $color-bottom, 60%); + background-image: linear-gradient(to bottom, $color-top, $color-bottom); + background-repeat: repeat-x; +} + + +@mixin ts-caret(){ + + .#{$select-ns}-wrapper.single{ + + .#{$select-ns}-control { + padding-right: 2rem; + + &, input { + cursor: pointer; + } + + &:after { + content: ' '; + display: block; + position: absolute; + top: 50%; + right: $select-arrow-offset; + margin-top: round((-1 * $select-arrow-size / 2)); + width: 0; + height: 0; + border-style: solid; + border-width: $select-arrow-size $select-arrow-size 0 $select-arrow-size; + border-color: $select-arrow-color transparent transparent transparent; + } + } + + &.dropdown-active .#{$select-ns}-control::after { + margin-top: $select-arrow-size * -0.8; + border-width: 0 $select-arrow-size $select-arrow-size $select-arrow-size; + border-color: transparent transparent $select-arrow-color transparent; + } + + &.input-active .#{$select-ns}-control, + &.input-active .#{$select-ns}-control input { + cursor: text; + } + + } +} + +//@import "./plugins/drag_drop.scss"; +//@import "./plugins/checkbox_options.scss"; +//@import "./plugins/clear_button.scss"; +//@import "./plugins/dropdown_header.scss"; +@import "./plugins/dropdown_input"; +//@import "./plugins/input_autogrow.scss"; +//@import "./plugins/optgroup_columns.scss"; +//@import "./plugins/remove_button.scss"; + + +.#{$select-ns}-wrapper { + position: relative; +} + +.#{$select-ns}-dropdown, +.#{$select-ns}-control, +.#{$select-ns}-control input { + color: $select-color-text; + font-family: $select-font-family; + font-size: $select-font-size; + line-height: $select-line-height; + font-smoothing: $select-font-smoothing; +} + +.#{$select-ns}-control, +.#{$select-ns}-wrapper.single.input-active .#{$select-ns}-control { + background: $select-color-input; + cursor: text; +} + +@import 'items'; +@import 'dropdown'; + +.ts-hidden-accessible{ + border: 0 !important; + clip: rect(0 0 0 0) !important; + clip-path: inset(50%) !important; + height: 1px !important; + overflow: hidden !important; + padding: 0 !important; + position: absolute !important; + width: 1px !important; + white-space: nowrap !important; +} diff --git a/assets/js/htmx/htmx.js b/assets/js/htmx/htmx.js new file mode 100644 index 0000000000000000000000000000000000000000..24e6dc48260f90613c823b4cb2210922f36bc176 --- /dev/null +++ b/assets/js/htmx/htmx.js @@ -0,0 +1,2751 @@ +//AMD insanity +(function (root, factory) { + //@ts-ignore + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + //@ts-ignore + define([], factory); + } else { + // Browser globals + root.htmx = factory(); + } +}(typeof self !== 'undefined' ? self : this, function () { +return (function () { + 'use strict'; + + // Public API + var htmx = { + onLoad: onLoadHelper, + process: processNode, + on: addEventListenerImpl, + off: removeEventListenerImpl, + trigger : triggerEvent, + ajax : ajaxHelper, + find : find, + findAll : findAll, + closest : closest, + values : function(elt, type){ + var inputValues = getInputValues(elt, type || "post"); + return inputValues.values; + }, + remove : removeElement, + addClass : addClassToElement, + removeClass : removeClassFromElement, + toggleClass : toggleClassOnElement, + takeClass : takeClassForElement, + defineExtension : defineExtension, + removeExtension : removeExtension, + logAll : logAll, + logger : null, + config : { + historyEnabled:true, + historyCacheSize:10, + refreshOnHistoryMiss:false, + defaultSwapStyle:'innerHTML', + defaultSwapDelay:0, + defaultSettleDelay:20, + includeIndicatorStyles:true, + indicatorClass:'htmx-indicator', + requestClass:'htmx-request', + addedClass:'htmx-added', + settlingClass:'htmx-settling', + swappingClass:'htmx-swapping', + allowEval:true, + attributesToSettle:["class", "style", "width", "height"], + withCredentials:false, + timeout:0, + wsReconnectDelay: 'full-jitter', + disableSelector: "[hx-disable], [data-hx-disable]", + useTemplateFragments: false, + scrollBehavior: 'smooth', + }, + parseInterval:parseInterval, + _:internalEval, + createEventSource: function(url){ + return new EventSource(url, {withCredentials:true}) + }, + createWebSocket: function(url){ + return new WebSocket(url, []); + }, + version: "1.6.1" + }; + + var VERBS = ['get', 'post', 'put', 'delete', 'patch']; + var VERB_SELECTOR = VERBS.map(function(verb){ + return "[hx-" + verb + "], [data-hx-" + verb + "]" + }).join(", "); + + //==================================================================== + // Utilities + //==================================================================== + + function parseInterval(str) { + if (str == undefined) { + return undefined + } + if (str.slice(-2) == "ms") { + return parseFloat(str.slice(0,-2)) || undefined + } + if (str.slice(-1) == "s") { + return (parseFloat(str.slice(0,-1)) * 1000) || undefined + } + return parseFloat(str) || undefined + } + + function getRawAttribute(elt, name) { + return elt.getAttribute && elt.getAttribute(name); + } + + // resolve with both hx and data-hx prefixes + function hasAttribute(elt, qualifiedName) { + return elt.hasAttribute && (elt.hasAttribute(qualifiedName) || + elt.hasAttribute("data-" + qualifiedName)); + } + + function getAttributeValue(elt, qualifiedName) { + return getRawAttribute(elt, qualifiedName) || getRawAttribute(elt, "data-" + qualifiedName); + } + + function parentElt(elt) { + return elt.parentElement; + } + + function getDocument() { + return document; + } + + function getClosestMatch(elt, condition) { + if (condition(elt)) { + return elt; + } else if (parentElt(elt)) { + return getClosestMatch(parentElt(elt), condition); + } else { + return null; + } + } + + function getClosestAttributeValue(elt, attributeName) { + var closestAttr = null; + getClosestMatch(elt, function (e) { + return closestAttr = getAttributeValue(e, attributeName); + }); + if (closestAttr !== "unset") { + return closestAttr; + } + } + + function matches(elt, selector) { + // noinspection JSUnresolvedVariable + var matchesFunction = elt.matches || + elt.matchesSelector || elt.msMatchesSelector || elt.mozMatchesSelector + || elt.webkitMatchesSelector || elt.oMatchesSelector; + return matchesFunction && matchesFunction.call(elt, selector); + } + + function getStartTag(str) { + var tagMatcher = /<([a-z][^\/\0>\x20\t\r\n\f]*)/i + var match = tagMatcher.exec( str ); + if (match) { + return match[1].toLowerCase(); + } else { + return ""; + } + } + + function parseHTML(resp, depth) { + var parser = new DOMParser(); + var responseDoc = parser.parseFromString(resp, "text/html"); + var responseNode = responseDoc.body; + while (depth > 0) { + depth--; + // @ts-ignore + responseNode = responseNode.firstChild; + } + if (responseNode == null) { + // @ts-ignore + responseNode = getDocument().createDocumentFragment(); + } + return responseNode; + } + + function makeFragment(resp) { + if (htmx.config.useTemplateFragments) { + var documentFragment = parseHTML("", 0); + return documentFragment.querySelector('template').content; + } else { + var startTag = getStartTag(resp); + switch (startTag) { + case "thead": + case "tbody": + case "tfoot": + case "colgroup": + case "caption": + return parseHTML("" + resp + "
", 1); + case "col": + return parseHTML("" + resp + "
", 2); + case "tr": + return parseHTML("" + resp + "
", 2); + case "td": + case "th": + return parseHTML("" + resp + "
", 3); + case "script": + return parseHTML("
" + resp + "
", 1); + default: + return parseHTML(resp, 0); + } + } + } + + function maybeCall(func){ + if(func) { + func(); + } + } + + function isType(o, type) { + return Object.prototype.toString.call(o) === "[object " + type + "]"; + } + + function isFunction(o) { + return isType(o, "Function"); + } + + function isRawObject(o) { + return isType(o, "Object"); + } + + function getInternalData(elt) { + var dataProp = 'htmx-internal-data'; + var data = elt[dataProp]; + if (!data) { + data = elt[dataProp] = {}; + } + return data; + } + + function toArray(arr) { + var returnArr = []; + if (arr) { + for (var i = 0; i < arr.length; i++) { + returnArr.push(arr[i]); + } + } + return returnArr + } + + function forEach(arr, func) { + if (arr) { + for (var i = 0; i < arr.length; i++) { + func(arr[i]); + } + } + } + + function isScrolledIntoView(el) { + var rect = el.getBoundingClientRect(); + var elemTop = rect.top; + var elemBottom = rect.bottom; + return elemTop < window.innerHeight && elemBottom >= 0; + } + + function bodyContains(elt) { + if (elt.getRootNode() instanceof ShadowRoot) { + return getDocument().body.contains(elt.getRootNode().host); + } else { + return getDocument().body.contains(elt); + } + } + + function splitOnWhitespace(trigger) { + return trigger.trim().split(/\s+/); + } + + function mergeObjects(obj1, obj2) { + for (var key in obj2) { + if (obj2.hasOwnProperty(key)) { + obj1[key] = obj2[key]; + } + } + return obj1; + } + + function parseJSON(jString) { + try { + return JSON.parse(jString); + } catch(error) { + logError(error); + return null; + } + } + + //========================================================================================== + // public API + //========================================================================================== + + function internalEval(str){ + return maybeEval(getDocument().body, function () { + return eval(str); + }); + } + + function onLoadHelper(callback) { + var value = htmx.on("htmx:load", function(evt) { + callback(evt.detail.elt); + }); + return value; + } + + function logAll(){ + htmx.logger = function(elt, event, data) { + if(console) { + console.log(event, elt, data); + } + } + } + + function find(eltOrSelector, selector) { + if (selector) { + return eltOrSelector.querySelector(selector); + } else { + return find(getDocument(), eltOrSelector); + } + } + + function findAll(eltOrSelector, selector) { + if (selector) { + return eltOrSelector.querySelectorAll(selector); + } else { + return findAll(getDocument(), eltOrSelector); + } + } + + function removeElement(elt, delay) { + elt = resolveTarget(elt); + if (delay) { + setTimeout(function(){removeElement(elt);}, delay) + } else { + elt.parentElement.removeChild(elt); + } + } + + function addClassToElement(elt, clazz, delay) { + elt = resolveTarget(elt); + if (delay) { + setTimeout(function(){addClassToElement(elt, clazz);}, delay) + } else { + elt.classList && elt.classList.add(clazz); + } + } + + function removeClassFromElement(elt, clazz, delay) { + elt = resolveTarget(elt); + if (delay) { + setTimeout(function(){removeClassFromElement(elt, clazz);}, delay) + } else { + if (elt.classList) { + elt.classList.remove(clazz); + // if there are no classes left, remove the class attribute + if (elt.classList.length === 0) { + elt.removeAttribute("class"); + } + } + } + } + + function toggleClassOnElement(elt, clazz) { + elt = resolveTarget(elt); + elt.classList.toggle(clazz); + } + + function takeClassForElement(elt, clazz) { + elt = resolveTarget(elt); + forEach(elt.parentElement.children, function(child){ + removeClassFromElement(child, clazz); + }) + addClassToElement(elt, clazz); + } + + function closest(elt, selector) { + elt = resolveTarget(elt); + if (elt.closest) { + return elt.closest(selector); + } else { + do{ + if (elt == null || matches(elt, selector)){ + return elt; + } + } + while (elt = elt && parentElt(elt)); + } + } + + function querySelectorAllExt(elt, selector) { + if (selector.indexOf("closest ") === 0) { + return [closest(elt, selector.substr(8))]; + } else if (selector.indexOf("find ") === 0) { + return [find(elt, selector.substr(5))]; + } else if (selector === 'document') { + return [document]; + } else if (selector === 'window') { + return [window]; + } else { + return getDocument().querySelectorAll(selector); + } + } + + function querySelectorExt(eltOrSelector, selector) { + if (selector) { + return querySelectorAllExt(eltOrSelector, selector)[0]; + } else { + return querySelectorAllExt(getDocument().body, eltOrSelector)[0]; + } + } + + function resolveTarget(arg2) { + if (isType(arg2, 'String')) { + return find(arg2); + } else { + return arg2; + } + } + + function processEventArgs(arg1, arg2, arg3) { + if (isFunction(arg2)) { + return { + target: getDocument().body, + event: arg1, + listener: arg2 + } + } else { + return { + target: resolveTarget(arg1), + event: arg2, + listener: arg3 + } + } + + } + + function addEventListenerImpl(arg1, arg2, arg3) { + ready(function(){ + var eventArgs = processEventArgs(arg1, arg2, arg3); + eventArgs.target.addEventListener(eventArgs.event, eventArgs.listener); + }) + var b = isFunction(arg2); + return b ? arg2 : arg3; + } + + function removeEventListenerImpl(arg1, arg2, arg3) { + ready(function(){ + var eventArgs = processEventArgs(arg1, arg2, arg3); + eventArgs.target.removeEventListener(eventArgs.event, eventArgs.listener); + }) + return isFunction(arg2) ? arg2 : arg3; + } + + //==================================================================== + // Node processing + //==================================================================== + + function getTarget(elt) { + var explicitTarget = getClosestMatch(elt, function(e){return getAttributeValue(e,"hx-target") !== null}); + if (explicitTarget) { + var targetStr = getAttributeValue(explicitTarget, "hx-target"); + if (targetStr === "this") { + return explicitTarget; + } else { + return querySelectorExt(elt, targetStr) + } + } else { + var data = getInternalData(elt); + if (data.boosted) { + return getDocument().body; + } else { + return elt; + } + } + } + + function shouldSettleAttribute(name) { + var attributesToSettle = htmx.config.attributesToSettle; + for (var i = 0; i < attributesToSettle.length; i++) { + if (name === attributesToSettle[i]) { + return true; + } + } + return false; + } + + function cloneAttributes(mergeTo, mergeFrom) { + forEach(mergeTo.attributes, function (attr) { + if (!mergeFrom.hasAttribute(attr.name) && shouldSettleAttribute(attr.name)) { + mergeTo.removeAttribute(attr.name) + } + }); + forEach(mergeFrom.attributes, function (attr) { + if (shouldSettleAttribute(attr.name)) { + mergeTo.setAttribute(attr.name, attr.value); + } + }); + } + + function isInlineSwap(swapStyle, target) { + var extensions = getExtensions(target); + for (var i = 0; i < extensions.length; i++) { + var extension = extensions[i]; + try { + if (extension.isInlineSwap(swapStyle)) { + return true; + } + } catch(e) { + logError(e); + } + } + return swapStyle === "outerHTML"; + } + + function oobSwap(oobValue, oobElement, settleInfo) { + var selector = "#" + oobElement.id; + var swapStyle = "outerHTML"; + if (oobValue === "true") { + // do nothing + } else if (oobValue.indexOf(":") > 0) { + swapStyle = oobValue.substr(0, oobValue.indexOf(":")); + selector = oobValue.substr(oobValue.indexOf(":") + 1, oobValue.length); + } else { + swapStyle = oobValue; + } + + var target = getDocument().querySelector(selector); + if (target) { + var fragment; + fragment = getDocument().createDocumentFragment(); + fragment.appendChild(oobElement); // pulls the child out of the existing fragment + if (!isInlineSwap(swapStyle, target)) { + fragment = oobElement; // if this is not an inline swap, we use the content of the node, not the node itself + } + swap(swapStyle, target, target, fragment, settleInfo); + } else { + oobElement.parentNode.removeChild(oobElement); + triggerErrorEvent(getDocument().body, "htmx:oobErrorNoTarget", {content: oobElement}) + } + return oobValue; + } + + function handleOutOfBandSwaps(fragment, settleInfo) { + forEach(findAll(fragment, '[hx-swap-oob], [data-hx-swap-oob]'), function (oobElement) { + var oobValue = getAttributeValue(oobElement, "hx-swap-oob"); + if (oobValue != null) { + oobSwap(oobValue, oobElement, settleInfo); + } + }); + } + + function handlePreservedElements(fragment) { + forEach(findAll(fragment, '[hx-preserve], [data-hx-preserve]'), function (preservedElt) { + var id = getAttributeValue(preservedElt, "id"); + var oldElt = getDocument().getElementById(id); + if (oldElt != null) { + preservedElt.parentNode.replaceChild(oldElt, preservedElt); + } + }); + } + + function handleAttributes(parentNode, fragment, settleInfo) { + forEach(fragment.querySelectorAll("[id]"), function (newNode) { + if (newNode.id && newNode.id.length > 0) { + var oldNode = parentNode.querySelector(newNode.tagName + "[id='" + newNode.id + "']"); + if (oldNode && oldNode !== parentNode) { + var newAttributes = newNode.cloneNode(); + cloneAttributes(newNode, oldNode); + settleInfo.tasks.push(function () { + cloneAttributes(newNode, newAttributes); + }); + } + } + }); + } + + function makeAjaxLoadTask(child) { + return function () { + removeClassFromElement(child, htmx.config.addedClass); + processNode(child); + processScripts(child); + processFocus(child) + triggerEvent(child, 'htmx:load'); + }; + } + + function processFocus(child) { + var autofocus = "[autofocus]"; + var autoFocusedElt = matches(child, autofocus) ? child : child.querySelector(autofocus) + if (autoFocusedElt != null) { + autoFocusedElt.focus(); + } + } + + function insertNodesBefore(parentNode, insertBefore, fragment, settleInfo) { + handleAttributes(parentNode, fragment, settleInfo); + while(fragment.childNodes.length > 0){ + var child = fragment.firstChild; + addClassToElement(child, htmx.config.addedClass); + parentNode.insertBefore(child, insertBefore); + if (child.nodeType !== Node.TEXT_NODE && child.nodeType !== Node.COMMENT_NODE) { + settleInfo.tasks.push(makeAjaxLoadTask(child)); + } + } + } + + function cleanUpElement(element) { + var internalData = getInternalData(element); + if (internalData.webSocket) { + internalData.webSocket.close(); + } + if (internalData.sseEventSource) { + internalData.sseEventSource.close(); + } + if (internalData.listenerInfos) { + forEach(internalData.listenerInfos, function(info) { + if (element !== info.on) { + info.on.removeEventListener(info.trigger, info.listener); + } + }); + } + if (element.children) { // IE + forEach(element.children, function(child) { cleanUpElement(child) }); + } + } + + function swapOuterHTML(target, fragment, settleInfo) { + if (target.tagName === "BODY") { + return swapInnerHTML(target, fragment, settleInfo); + } else { + var eltBeforeNewContent = target.previousSibling; + insertNodesBefore(parentElt(target), target, fragment, settleInfo); + if (eltBeforeNewContent == null) { + var newElt = parentElt(target).firstChild; + } else { + var newElt = eltBeforeNewContent.nextSibling; + } + getInternalData(target).replacedWith = newElt; // tuck away so we can fire events on it later + settleInfo.elts = [] // clear existing elements + while(newElt && newElt !== target) { + if (newElt.nodeType === Node.ELEMENT_NODE) { + settleInfo.elts.push(newElt); + } + newElt = newElt.nextElementSibling; + } + cleanUpElement(target); + parentElt(target).removeChild(target); + } + } + + function swapAfterBegin(target, fragment, settleInfo) { + return insertNodesBefore(target, target.firstChild, fragment, settleInfo); + } + + function swapBeforeBegin(target, fragment, settleInfo) { + return insertNodesBefore(parentElt(target), target, fragment, settleInfo); + } + + function swapBeforeEnd(target, fragment, settleInfo) { + return insertNodesBefore(target, null, fragment, settleInfo); + } + + function swapAfterEnd(target, fragment, settleInfo) { + return insertNodesBefore(parentElt(target), target.nextSibling, fragment, settleInfo); + } + + function swapInnerHTML(target, fragment, settleInfo) { + var firstChild = target.firstChild; + insertNodesBefore(target, firstChild, fragment, settleInfo); + if (firstChild) { + while (firstChild.nextSibling) { + cleanUpElement(firstChild.nextSibling) + target.removeChild(firstChild.nextSibling); + } + cleanUpElement(firstChild) + target.removeChild(firstChild); + } + } + + function maybeSelectFromResponse(elt, fragment) { + var selector = getClosestAttributeValue(elt, "hx-select"); + if (selector) { + var newFragment = getDocument().createDocumentFragment(); + forEach(fragment.querySelectorAll(selector), function (node) { + newFragment.appendChild(node); + }); + fragment = newFragment; + } + return fragment; + } + + function swap(swapStyle, elt, target, fragment, settleInfo) { + switch (swapStyle) { + case "none": + return; + case "outerHTML": + swapOuterHTML(target, fragment, settleInfo); + return; + case "afterbegin": + swapAfterBegin(target, fragment, settleInfo); + return; + case "beforebegin": + swapBeforeBegin(target, fragment, settleInfo); + return; + case "beforeend": + swapBeforeEnd(target, fragment, settleInfo); + return; + case "afterend": + swapAfterEnd(target, fragment, settleInfo); + return; + default: + var extensions = getExtensions(elt); + for (var i = 0; i < extensions.length; i++) { + var ext = extensions[i]; + try { + var newElements = ext.handleSwap(swapStyle, target, fragment, settleInfo); + if (newElements) { + if (typeof newElements.length !== 'undefined') { + // if handleSwap returns an array (like) of elements, we handle them + for (var j = 0; j < newElements.length; j++) { + var child = newElements[j]; + if (child.nodeType !== Node.TEXT_NODE && child.nodeType !== Node.COMMENT_NODE) { + settleInfo.tasks.push(makeAjaxLoadTask(child)); + } + } + } + return; + } + } catch (e) { + logError(e); + } + } + swapInnerHTML(target, fragment, settleInfo); + } + } + + function findTitle(content) { + if (content.indexOf(' -1) { + var contentWithSvgsRemoved = content.replace(/]*>|>)([\s\S]*?)<\/svg>/gim, ''); + var result = contentWithSvgsRemoved.match(/]*>|>)([\s\S]*?)<\/title>/im); + + if (result) { + return result[2]; + } + } + } + + function selectAndSwap(swapStyle, target, elt, responseText, settleInfo) { + var title = findTitle(responseText); + if(title) { + var titleElt = find("title"); + if(titleElt) { + titleElt.innerHTML = title; + } else { + window.document.title = title; + } + } + var fragment = makeFragment(responseText); + if (fragment) { + handleOutOfBandSwaps(fragment, settleInfo); + fragment = maybeSelectFromResponse(elt, fragment); + handlePreservedElements(fragment); + return swap(swapStyle, elt, target, fragment, settleInfo); + } + } + + function handleTrigger(xhr, header, elt) { + var triggerBody = xhr.getResponseHeader(header); + if (triggerBody.indexOf("{") === 0) { + var triggers = parseJSON(triggerBody); + for (var eventName in triggers) { + if (triggers.hasOwnProperty(eventName)) { + var detail = triggers[eventName]; + if (!isRawObject(detail)) { + detail = {"value": detail} + } + triggerEvent(elt, eventName, detail); + } + } + } else { + triggerEvent(elt, triggerBody, []); + } + } + + var WHITESPACE = /\s/; + var WHITESPACE_OR_COMMA = /[\s,]/; + var SYMBOL_START = /[_$a-zA-Z]/; + var SYMBOL_CONT = /[_$a-zA-Z0-9]/; + var STRINGISH_START = ['"', "'", "/"]; + var NOT_WHITESPACE = /[^\s]/; + function tokenizeString(str) { + var tokens = []; + var position = 0; + while (position < str.length) { + if(SYMBOL_START.exec(str.charAt(position))) { + var startPosition = position; + while (SYMBOL_CONT.exec(str.charAt(position + 1))) { + position++; + } + tokens.push(str.substr(startPosition, position - startPosition + 1)); + } else if (STRINGISH_START.indexOf(str.charAt(position)) !== -1) { + var startChar = str.charAt(position); + var startPosition = position; + position++; + while (position < str.length && str.charAt(position) !== startChar ) { + if (str.charAt(position) === "\\") { + position++; + } + position++; + } + tokens.push(str.substr(startPosition, position - startPosition + 1)); + } else { + var symbol = str.charAt(position); + tokens.push(symbol); + } + position++; + } + return tokens; + } + + function isPossibleRelativeReference(token, last, paramName) { + return SYMBOL_START.exec(token.charAt(0)) && + token !== "true" && + token !== "false" && + token !== "this" && + token !== paramName && + last !== "."; + } + + function maybeGenerateConditional(elt, tokens, paramName) { + if (tokens[0] === '[') { + tokens.shift(); + var bracketCount = 1; + var conditionalSource = " return (function(" + paramName + "){ return ("; + var last = null; + while (tokens.length > 0) { + var token = tokens[0]; + if (token === "]") { + bracketCount--; + if (bracketCount === 0) { + if (last === null) { + conditionalSource = conditionalSource + "true"; + } + tokens.shift(); + conditionalSource += ")})"; + try { + var conditionFunction = maybeEval(elt,function () { + return Function(conditionalSource)(); + }, + function(){return true}) + conditionFunction.source = conditionalSource; + return conditionFunction; + } catch (e) { + triggerErrorEvent(getDocument().body, "htmx:syntax:error", {error:e, source:conditionalSource}) + return null; + } + } + } else if (token === "[") { + bracketCount++; + } + if (isPossibleRelativeReference(token, last, paramName)) { + conditionalSource += "((" + paramName + "." + token + ") ? (" + paramName + "." + token + ") : (window." + token + "))"; + } else { + conditionalSource = conditionalSource + token; + } + last = tokens.shift(); + } + } + } + + function consumeUntil(tokens, match) { + var result = ""; + while (tokens.length > 0 && !tokens[0].match(match)) { + result += tokens.shift(); + } + return result; + } + + var INPUT_SELECTOR = 'input, textarea, select'; + function getTriggerSpecs(elt) { + var explicitTrigger = getAttributeValue(elt, 'hx-trigger'); + var triggerSpecs = []; + if (explicitTrigger) { + var tokens = tokenizeString(explicitTrigger); + do { + consumeUntil(tokens, NOT_WHITESPACE); + var initialLength = tokens.length; + var trigger = consumeUntil(tokens, /[,\[\s]/); + if (trigger !== "") { + if (trigger === "every") { + var every = {trigger: 'every'}; + consumeUntil(tokens, NOT_WHITESPACE); + every.pollInterval = parseInterval(consumeUntil(tokens, /[,\[\s]/)); + consumeUntil(tokens, NOT_WHITESPACE); + var eventFilter = maybeGenerateConditional(elt, tokens, "event"); + if (eventFilter) { + every.eventFilter = eventFilter; + } + triggerSpecs.push(every); + } else if (trigger.indexOf("sse:") === 0) { + triggerSpecs.push({trigger: 'sse', sseEvent: trigger.substr(4)}); + } else { + var triggerSpec = {trigger: trigger}; + var eventFilter = maybeGenerateConditional(elt, tokens, "event"); + if (eventFilter) { + triggerSpec.eventFilter = eventFilter; + } + while (tokens.length > 0 && tokens[0] !== ",") { + consumeUntil(tokens, NOT_WHITESPACE) + var token = tokens.shift(); + if (token === "changed") { + triggerSpec.changed = true; + } else if (token === "once") { + triggerSpec.once = true; + } else if (token === "consume") { + triggerSpec.consume = true; + } else if (token === "delay" && tokens[0] === ":") { + tokens.shift(); + triggerSpec.delay = parseInterval(consumeUntil(tokens, WHITESPACE_OR_COMMA)); + } else if (token === "from" && tokens[0] === ":") { + tokens.shift(); + var from_arg = consumeUntil(tokens, WHITESPACE_OR_COMMA); + if (from_arg === "closest" || from_arg === "find") { + tokens.shift(); + from_arg += + " " + + consumeUntil( + tokens, + WHITESPACE_OR_COMMA + ); + } + triggerSpec.from = from_arg; + } else if (token === "target" && tokens[0] === ":") { + tokens.shift(); + triggerSpec.target = consumeUntil(tokens, WHITESPACE_OR_COMMA); + } else if (token === "throttle" && tokens[0] === ":") { + tokens.shift(); + triggerSpec.throttle = parseInterval(consumeUntil(tokens, WHITESPACE_OR_COMMA)); + } else if (token === "queue" && tokens[0] === ":") { + tokens.shift(); + triggerSpec.queue = consumeUntil(tokens, WHITESPACE_OR_COMMA); + } else if ((token === "root" || token === "threshold") && tokens[0] === ":") { + tokens.shift(); + triggerSpec[token] = consumeUntil(tokens, WHITESPACE_OR_COMMA); + } else { + triggerErrorEvent(elt, "htmx:syntax:error", {token:tokens.shift()}); + } + } + triggerSpecs.push(triggerSpec); + } + } + if (tokens.length === initialLength) { + triggerErrorEvent(elt, "htmx:syntax:error", {token:tokens.shift()}); + } + consumeUntil(tokens, NOT_WHITESPACE); + } while (tokens[0] === "," && tokens.shift()) + } + + if (triggerSpecs.length > 0) { + return triggerSpecs; + } else if (matches(elt, 'form')) { + return [{trigger: 'submit'}]; + } else if (matches(elt, INPUT_SELECTOR)) { + return [{trigger: 'change'}]; + } else { + return [{trigger: 'click'}]; + } + } + + function cancelPolling(elt) { + getInternalData(elt).cancelled = true; + } + + function processPolling(elt, verb, path, spec) { + var nodeData = getInternalData(elt); + nodeData.timeout = setTimeout(function () { + if (bodyContains(elt) && nodeData.cancelled !== true) { + if (!maybeFilterEvent(spec, makeEvent('hx:poll:trigger', {triggerSpec:spec}))) { + issueAjaxRequest(verb, path, elt); + } + processPolling(elt, verb, getAttributeValue(elt, "hx-" + verb), spec); + } + }, spec.pollInterval); + } + + function isLocalLink(elt) { + return location.hostname === elt.hostname && + getRawAttribute(elt,'href') && + getRawAttribute(elt,'href').indexOf("#") !== 0; + } + + function boostElement(elt, nodeData, triggerSpecs) { + if ((elt.tagName === "A" && isLocalLink(elt) && elt.target === "") || elt.tagName === "FORM") { + nodeData.boosted = true; + var verb, path; + if (elt.tagName === "A") { + verb = "get"; + path = getRawAttribute(elt, 'href'); + nodeData.pushURL = true; + } else { + var rawAttribute = getRawAttribute(elt, "method"); + verb = rawAttribute ? rawAttribute.toLowerCase() : "get"; + if (verb === "get") { + nodeData.pushURL = true; + } + path = getRawAttribute(elt, 'action'); + } + triggerSpecs.forEach(function(triggerSpec) { + addEventListener(elt, verb, path, nodeData, triggerSpec, true); + }); + } + } + + function shouldCancel(evt, elt) { + if (evt.type === "submit" || evt.type === "click") { + if (elt.tagName === "FORM") { + return true; + } + if (matches(elt, 'input[type="submit"], button') && closest(elt, 'form') !== null) { + return true; + } + if (elt.tagName === "A" && elt.href && + (elt.getAttribute('href') === '#' || elt.getAttribute('href').indexOf("#") !== 0)) { + return true; + } + } + return false; + } + + function ignoreBoostedAnchorCtrlClick(elt, evt) { + return getInternalData(elt).boosted && elt.tagName === "A" && evt.type === "click" && (evt.ctrlKey || evt.metaKey); + } + + function maybeFilterEvent(triggerSpec, evt) { + var eventFilter = triggerSpec.eventFilter; + if(eventFilter){ + try { + return eventFilter(evt) !== true; + } catch(e) { + triggerErrorEvent(getDocument().body, "htmx:eventFilter:error", {error: e, source:eventFilter.source}); + return true; + } + } + return false; + } + + function addEventListener(elt, verb, path, nodeData, triggerSpec, explicitCancel) { + var eltsToListenOn; + if (triggerSpec.from) { + eltsToListenOn = querySelectorAllExt(elt, triggerSpec.from); + } else { + eltsToListenOn = [elt]; + } + forEach(eltsToListenOn, function (eltToListenOn) { + var eventListener = function (evt) { + if (!bodyContains(elt)) { + eltToListenOn.removeEventListener(triggerSpec.trigger, eventListener); + return; + } + if (ignoreBoostedAnchorCtrlClick(elt, evt)) { + return; + } + if (explicitCancel || shouldCancel(evt, elt)) { + evt.preventDefault(); + } + if (maybeFilterEvent(triggerSpec, evt)) { + return; + } + var eventData = getInternalData(evt); + eventData.triggerSpec = triggerSpec; + if (eventData.handledFor == null) { + eventData.handledFor = []; + } + var elementData = getInternalData(elt); + if (eventData.handledFor.indexOf(elt) < 0) { + eventData.handledFor.push(elt); + if (triggerSpec.consume) { + evt.stopPropagation(); + } + if (triggerSpec.target && evt.target) { + if (!matches(evt.target, triggerSpec.target)) { + return; + } + } + if (triggerSpec.once) { + if (elementData.triggeredOnce) { + return; + } else { + elementData.triggeredOnce = true; + } + } + if (triggerSpec.changed) { + if (elementData.lastValue === elt.value) { + return; + } else { + elementData.lastValue = elt.value; + } + } + if (elementData.delayed) { + clearTimeout(elementData.delayed); + } + if (elementData.throttle) { + return; + } + + if (triggerSpec.throttle) { + if (!elementData.throttle) { + issueAjaxRequest(verb, path, elt, evt); + elementData.throttle = setTimeout(function () { + elementData.throttle = null; + }, triggerSpec.throttle); + } + } else if (triggerSpec.delay) { + elementData.delayed = setTimeout(function () { + issueAjaxRequest(verb, path, elt, evt); + }, triggerSpec.delay); + } else { + issueAjaxRequest(verb, path, elt, evt); + } + } + }; + if (nodeData.listenerInfos == null) { + nodeData.listenerInfos = []; + } + nodeData.listenerInfos.push({ + trigger: triggerSpec.trigger, + listener: eventListener, + on: eltToListenOn + }) + eltToListenOn.addEventListener(triggerSpec.trigger, eventListener); + }) + } + + var windowIsScrolling = false // used by initScrollHandler + var scrollHandler = null; + function initScrollHandler() { + if (!scrollHandler) { + scrollHandler = function() { + windowIsScrolling = true + }; + window.addEventListener("scroll", scrollHandler) + setInterval(function() { + if (windowIsScrolling) { + windowIsScrolling = false; + forEach(getDocument().querySelectorAll("[hx-trigger='revealed'],[data-hx-trigger='revealed']"), function (elt) { + maybeReveal(elt); + }) + } + }, 200); + } + } + + function maybeReveal(elt) { + if (!hasAttribute(elt,'data-hx-revealed') && isScrolledIntoView(elt)) { + elt.setAttribute('data-hx-revealed', 'true'); + var nodeData = getInternalData(elt); + if (nodeData.initialized) { + issueAjaxRequest(nodeData.verb, nodeData.path, elt); + } else { + // if the node isn't initialized, wait for it before triggering the request + elt.addEventListener("htmx:afterProcessNode", + function () { + issueAjaxRequest(nodeData.verb, nodeData.path, elt); + }, {once: true}); + } + } + } + + function processWebSocketInfo(elt, nodeData, info) { + var values = splitOnWhitespace(info); + for (var i = 0; i < values.length; i++) { + var value = values[i].split(/:(.+)/); + if (value[0] === "connect") { + ensureWebSocket(elt, value[1], 0); + } + if (value[0] === "send") { + processWebSocketSend(elt); + } + } + } + + function ensureWebSocket(elt, wssSource, retryCount) { + if (!bodyContains(elt)) { + return; // stop ensuring websocket connection when socket bearing element ceases to exist + } + + if (wssSource.indexOf("/") == 0) { // complete absolute paths only + var base_part = location.hostname + (location.port ? ':'+location.port: ''); + if (location.protocol == 'https:') { + wssSource = "wss://" + base_part + wssSource; + } else if (location.protocol == 'http:') { + wssSource = "ws://" + base_part + wssSource; + } + } + var socket = htmx.createWebSocket(wssSource); + socket.onerror = function (e) { + triggerErrorEvent(elt, "htmx:wsError", {error:e, socket:socket}); + maybeCloseWebSocketSource(elt); + }; + + socket.onclose = function (e) { + if ([1006, 1012, 1013].indexOf(e.code) >= 0) { // Abnormal Closure/Service Restart/Try Again Later + var delay = getWebSocketReconnectDelay(retryCount); + setTimeout(function() { + ensureWebSocket(elt, wssSource, retryCount+1); // creates a websocket with a new timeout + }, delay); + } + }; + socket.onopen = function (e) { + retryCount = 0; + } + + getInternalData(elt).webSocket = socket; + socket.addEventListener('message', function (event) { + if (maybeCloseWebSocketSource(elt)) { + return; + } + + var response = event.data; + withExtensions(elt, function(extension){ + response = extension.transformResponse(response, null, elt); + }); + + var settleInfo = makeSettleInfo(elt); + var fragment = makeFragment(response); + var children = toArray(fragment.children); + for (var i = 0; i < children.length; i++) { + var child = children[i]; + oobSwap(getAttributeValue(child, "hx-swap-oob") || "true", child, settleInfo); + } + + settleImmediately(settleInfo.tasks); + }); + } + + function maybeCloseWebSocketSource(elt) { + if (!bodyContains(elt)) { + getInternalData(elt).webSocket.close(); + return true; + } + } + + function processWebSocketSend(elt) { + var webSocketSourceElt = getClosestMatch(elt, function (parent) { + return getInternalData(parent).webSocket != null; + }); + if (webSocketSourceElt) { + elt.addEventListener(getTriggerSpecs(elt)[0].trigger, function (evt) { + var webSocket = getInternalData(webSocketSourceElt).webSocket; + var headers = getHeaders(elt, webSocketSourceElt); + var results = getInputValues(elt, 'post'); + var errors = results.errors; + var rawParameters = results.values; + var expressionVars = getExpressionVars(elt); + var allParameters = mergeObjects(rawParameters, expressionVars); + var filteredParameters = filterValues(allParameters, elt); + filteredParameters['HEADERS'] = headers; + if (errors && errors.length > 0) { + triggerEvent(elt, 'htmx:validation:halted', errors); + return; + } + webSocket.send(JSON.stringify(filteredParameters)); + if(shouldCancel(evt, elt)){ + evt.preventDefault(); + } + }); + } else { + triggerErrorEvent(elt, "htmx:noWebSocketSourceError"); + } + } + + function getWebSocketReconnectDelay(retryCount) { + var delay = htmx.config.wsReconnectDelay; + if (typeof delay === 'function') { + // @ts-ignore + return delay(retryCount); + } + if (delay === 'full-jitter') { + var exp = Math.min(retryCount, 6); + var maxDelay = 1000 * Math.pow(2, exp); + return maxDelay * Math.random(); + } + logError('htmx.config.wsReconnectDelay must either be a function or the string "full-jitter"'); + } + + //==================================================================== + // Server Sent Events + //==================================================================== + + function processSSEInfo(elt, nodeData, info) { + var values = splitOnWhitespace(info); + for (var i = 0; i < values.length; i++) { + var value = values[i].split(/:(.+)/); + if (value[0] === "connect") { + processSSESource(elt, value[1]); + } + + if ((value[0] === "swap")) { + processSSESwap(elt, value[1]) + } + } + } + + function processSSESource(elt, sseSrc) { + var source = htmx.createEventSource(sseSrc); + source.onerror = function (e) { + triggerErrorEvent(elt, "htmx:sseError", {error:e, source:source}); + maybeCloseSSESource(elt); + }; + getInternalData(elt).sseEventSource = source; + } + + function processSSESwap(elt, sseEventName) { + var sseSourceElt = getClosestMatch(elt, hasEventSource); + if (sseSourceElt) { + var sseEventSource = getInternalData(sseSourceElt).sseEventSource; + var sseListener = function (event) { + if (maybeCloseSSESource(sseSourceElt)) { + sseEventSource.removeEventListener(sseEventName, sseListener); + return; + } + + /////////////////////////// + // TODO: merge this code with AJAX and WebSockets code in the future. + + var response = event.data; + withExtensions(elt, function(extension){ + response = extension.transformResponse(response, null, elt); + }); + + var swapSpec = getSwapSpecification(elt) + var target = getTarget(elt) + var settleInfo = makeSettleInfo(elt); + + selectAndSwap(swapSpec.swapStyle, elt, target, response, settleInfo) + settleImmediately(settleInfo.tasks) + triggerEvent(elt, "htmx:sseMessage", event) + }; + + getInternalData(elt).sseListener = sseListener; + sseEventSource.addEventListener(sseEventName, sseListener); + } else { + triggerErrorEvent(elt, "htmx:noSSESourceError"); + } + } + + function processSSETrigger(elt, verb, path, sseEventName) { + var sseSourceElt = getClosestMatch(elt, hasEventSource); + if (sseSourceElt) { + var sseEventSource = getInternalData(sseSourceElt).sseEventSource; + var sseListener = function () { + if (!maybeCloseSSESource(sseSourceElt)) { + if (bodyContains(elt)) { + issueAjaxRequest(verb, path, elt); + } else { + sseEventSource.removeEventListener(sseEventName, sseListener); + } + } + }; + getInternalData(elt).sseListener = sseListener; + sseEventSource.addEventListener(sseEventName, sseListener); + } else { + triggerErrorEvent(elt, "htmx:noSSESourceError"); + } + } + + function maybeCloseSSESource(elt) { + if (!bodyContains(elt)) { + getInternalData(elt).sseEventSource.close(); + return true; + } + } + + function hasEventSource(node) { + return getInternalData(node).sseEventSource != null; + } + + //==================================================================== + + function loadImmediately(elt, verb, path, nodeData, delay) { + var load = function(){ + if (!nodeData.loaded) { + nodeData.loaded = true; + issueAjaxRequest(verb, path, elt); + } + } + if (delay) { + setTimeout(load, delay); + } else { + load(); + } + } + + function processVerbs(elt, nodeData, triggerSpecs) { + var explicitAction = false; + forEach(VERBS, function (verb) { + if (hasAttribute(elt,'hx-' + verb)) { + var path = getAttributeValue(elt, 'hx-' + verb); + explicitAction = true; + nodeData.path = path; + nodeData.verb = verb; + triggerSpecs.forEach(function(triggerSpec) { + if (triggerSpec.sseEvent) { + processSSETrigger(elt, verb, path, triggerSpec.sseEvent); + } else if (triggerSpec.trigger === "revealed") { + initScrollHandler(); + maybeReveal(elt); + } else if (triggerSpec.trigger === "intersect") { + var observerOptions = {}; + if (triggerSpec.root) { + observerOptions.root = querySelectorExt(elt, triggerSpec.root) + } + if (triggerSpec.threshold) { + observerOptions.threshold = parseFloat(triggerSpec.threshold); + } + var observer = new IntersectionObserver(function (entries) { + for (var i = 0; i < entries.length; i++) { + var entry = entries[i]; + if (entry.isIntersecting) { + triggerEvent(elt, "intersect"); + break; + } + } + }, observerOptions); + observer.observe(elt); + addEventListener(elt, verb, path, nodeData, triggerSpec); + } else if (triggerSpec.trigger === "load") { + loadImmediately(elt, verb, path, nodeData, triggerSpec.delay); + } else if (triggerSpec.pollInterval) { + nodeData.polling = true; + processPolling(elt, verb, path, triggerSpec); + } else { + addEventListener(elt, verb, path, nodeData, triggerSpec); + } + }); + } + }); + return explicitAction; + } + + function evalScript(script) { + if (script.type === "text/javascript" || script.type === "module" || script.type === "") { + var newScript = getDocument().createElement("script"); + forEach(script.attributes, function (attr) { + newScript.setAttribute(attr.name, attr.value); + }); + newScript.textContent = script.textContent; + newScript.async = false; + var parent = script.parentElement; + + try { + parent.insertBefore(newScript, script); + } catch (e) { + logError(e); + } finally { + parent.removeChild(script); + } + } + } + + function processScripts(elt) { + if (matches(elt, "script")) { + evalScript(elt); + } + forEach(findAll(elt, "script"), function (script) { + evalScript(script); + }); + } + + function isBoosted() { + return document.querySelector("[hx-boost], [data-hx-boost]"); + } + + function findElementsToProcess(elt) { + if (elt.querySelectorAll) { + var boostedElts = isBoosted() ? ", a, form" : ""; + var results = elt.querySelectorAll(VERB_SELECTOR + boostedElts + ", [hx-sse], [data-hx-sse], [hx-ws]," + + " [data-hx-ws], [hx-ext], [hx-data-ext]"); + return results; + } else { + return []; + } + } + + function initButtonTracking(form){ + var maybeSetLastButtonClicked = function(evt){ + if (matches(evt.target, "button, input[type='submit']")) { + var internalData = getInternalData(form); + internalData.lastButtonClicked = evt.target; + } + }; + + // need to handle both click and focus in: + // focusin - in case someone tabs in to a button and hits the space bar + // click - on OSX buttons do not focus on click see https://bugs.webkit.org/show_bug.cgi?id=13724 + + form.addEventListener('click', maybeSetLastButtonClicked) + form.addEventListener('focusin', maybeSetLastButtonClicked) + form.addEventListener('focusout', function(evt){ + var internalData = getInternalData(form); + internalData.lastButtonClicked = null; + }) + } + + function initNode(elt) { + if (elt.closest && elt.closest(htmx.config.disableSelector)) { + return; + } + var nodeData = getInternalData(elt); + if (!nodeData.initialized) { + nodeData.initialized = true; + triggerEvent(elt, "htmx:beforeProcessNode") + + if (elt.value) { + nodeData.lastValue = elt.value; + } + + var triggerSpecs = getTriggerSpecs(elt); + var explicitAction = processVerbs(elt, nodeData, triggerSpecs); + + if (!explicitAction && getClosestAttributeValue(elt, "hx-boost") === "true") { + boostElement(elt, nodeData, triggerSpecs); + } + + if (elt.tagName === "FORM") { + initButtonTracking(elt); + } + + var sseInfo = getAttributeValue(elt, 'hx-sse'); + if (sseInfo) { + processSSEInfo(elt, nodeData, sseInfo); + } + + var wsInfo = getAttributeValue(elt, 'hx-ws'); + if (wsInfo) { + processWebSocketInfo(elt, nodeData, wsInfo); + } + triggerEvent(elt, "htmx:afterProcessNode"); + } + } + + function processNode(elt) { + elt = resolveTarget(elt); + initNode(elt); + forEach(findElementsToProcess(elt), function(child) { initNode(child) }); + } + + //==================================================================== + // Event/Log Support + //==================================================================== + + function kebabEventName(str) { + return str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase(); + } + + function makeEvent(eventName, detail) { + var evt; + if (window.CustomEvent && typeof window.CustomEvent === 'function') { + evt = new CustomEvent(eventName, {bubbles: true, cancelable: true, detail: detail}); + } else { + evt = getDocument().createEvent('CustomEvent'); + evt.initCustomEvent(eventName, true, true, detail); + } + return evt; + } + + function triggerErrorEvent(elt, eventName, detail) { + triggerEvent(elt, eventName, mergeObjects({error:eventName}, detail)); + } + + function ignoreEventForLogging(eventName) { + return eventName === "htmx:afterProcessNode" + } + + function withExtensions(elt, toDo) { + forEach(getExtensions(elt), function(extension){ + try { + toDo(extension); + } catch (e) { + logError(e); + } + }); + } + + function logError(msg) { + if(console.error) { + console.error(msg); + } else if (console.log) { + console.log("ERROR: ", msg); + } + } + + function triggerEvent(elt, eventName, detail) { + elt = resolveTarget(elt); + if (detail == null) { + detail = {}; + } + detail["elt"] = elt; + var event = makeEvent(eventName, detail); + if (htmx.logger && !ignoreEventForLogging(eventName)) { + htmx.logger(elt, eventName, detail); + } + if (detail.error) { + logError(detail.error); + triggerEvent(elt, "htmx:error", {errorInfo:detail}) + } + var eventResult = elt.dispatchEvent(event); + var kebabName = kebabEventName(eventName); + if (eventResult && kebabName !== eventName) { + var kebabedEvent = makeEvent(kebabName, event.detail); + eventResult = eventResult && elt.dispatchEvent(kebabedEvent) + } + withExtensions(elt, function (extension) { + eventResult = eventResult && (extension.onEvent(eventName, event) !== false) + }); + return eventResult; + } + + //==================================================================== + // History Support + //==================================================================== + var currentPathForHistory = location.pathname+location.search; + + function getHistoryElement() { + var historyElt = getDocument().querySelector('[hx-history-elt],[data-hx-history-elt]'); + return historyElt || getDocument().body; + } + + function saveToHistoryCache(url, content, title, scroll) { + var historyCache = parseJSON(localStorage.getItem("htmx-history-cache")) || []; + for (var i = 0; i < historyCache.length; i++) { + if (historyCache[i].url === url) { + historyCache.splice(i, 1); + break; + } + } + historyCache.push({url:url, content: content, title:title, scroll:scroll}) + while (historyCache.length > htmx.config.historyCacheSize) { + historyCache.shift(); + } + while(historyCache.length > 0){ + try { + localStorage.setItem("htmx-history-cache", JSON.stringify(historyCache)); + break; + } catch (e) { + triggerErrorEvent(getDocument().body, "htmx:historyCacheError", {cause:e, cache: historyCache}) + historyCache.shift(); // shrink the cache and retry + } + } + } + + function getCachedHistory(url) { + var historyCache = parseJSON(localStorage.getItem("htmx-history-cache")) || []; + for (var i = 0; i < historyCache.length; i++) { + if (historyCache[i].url === url) { + return historyCache[i]; + } + } + return null; + } + + function cleanInnerHtmlForHistory(elt) { + var className = htmx.config.requestClass; + var clone = elt.cloneNode(true); + forEach(findAll(clone, "." + className), function(child){ + removeClassFromElement(child, className); + }); + return clone.innerHTML; + } + + function saveHistory() { + var elt = getHistoryElement(); + var path = currentPathForHistory || location.pathname+location.search; + triggerEvent(getDocument().body, "htmx:beforeHistorySave", {path:path, historyElt:elt}); + if(htmx.config.historyEnabled) history.replaceState({htmx:true}, getDocument().title, window.location.href); + saveToHistoryCache(path, cleanInnerHtmlForHistory(elt), getDocument().title, window.scrollY); + } + + function pushUrlIntoHistory(path) { + if(htmx.config.historyEnabled) history.pushState({htmx:true}, "", path); + currentPathForHistory = path; + } + + function settleImmediately(tasks) { + forEach(tasks, function (task) { + task.call(); + }); + } + + function loadHistoryFromServer(path) { + var request = new XMLHttpRequest(); + var details = {path: path, xhr:request}; + triggerEvent(getDocument().body, "htmx:historyCacheMiss", details); + request.open('GET', path, true); + request.setRequestHeader("HX-History-Restore-Request", "true"); + request.onload = function () { + if (this.status >= 200 && this.status < 400) { + triggerEvent(getDocument().body, "htmx:historyCacheMissLoad", details); + var fragment = makeFragment(this.response); + // @ts-ignore + fragment = fragment.querySelector('[hx-history-elt],[data-hx-history-elt]') || fragment; + var historyElement = getHistoryElement(); + var settleInfo = makeSettleInfo(historyElement); + // @ts-ignore + swapInnerHTML(historyElement, fragment, settleInfo) + settleImmediately(settleInfo.tasks); + currentPathForHistory = path; + triggerEvent(getDocument().body, "htmx:historyRestore", {path:path}); + } else { + triggerErrorEvent(getDocument().body, "htmx:historyCacheMissLoadError", details); + } + }; + request.send(); + } + + function restoreHistory(path) { + saveHistory(); + path = path || location.pathname+location.search; + var cached = getCachedHistory(path); + if (cached) { + var fragment = makeFragment(cached.content); + var historyElement = getHistoryElement(); + var settleInfo = makeSettleInfo(historyElement); + swapInnerHTML(historyElement, fragment, settleInfo) + settleImmediately(settleInfo.tasks); + document.title = cached.title; + window.scrollTo(0, cached.scroll); + currentPathForHistory = path; + triggerEvent(getDocument().body, "htmx:historyRestore", {path:path}); + } else { + if (htmx.config.refreshOnHistoryMiss) { + window.location.reload(true); + } else { + loadHistoryFromServer(path); + } + } + } + + function shouldPush(elt) { + var pushUrl = getClosestAttributeValue(elt, "hx-push-url"); + return (pushUrl && pushUrl !== "false") || + (getInternalData(elt).boosted && getInternalData(elt).pushURL); + } + + function getPushUrl(elt) { + var pushUrl = getClosestAttributeValue(elt, "hx-push-url"); + return (pushUrl === "true" || pushUrl === "false") ? null : pushUrl; + } + + function addRequestIndicatorClasses(elt) { + var indicator = getClosestAttributeValue(elt, 'hx-indicator'); + if (indicator) { + var indicators = querySelectorAllExt(elt, indicator); + } else { + indicators = [elt]; + } + forEach(indicators, function (ic) { + ic.classList["add"].call(ic.classList, htmx.config.requestClass); + }); + return indicators; + } + + function removeRequestIndicatorClasses(indicators) { + forEach(indicators, function (ic) { + ic.classList["remove"].call(ic.classList, htmx.config.requestClass); + }); + } + + //==================================================================== + // Input Value Processing + //==================================================================== + + function haveSeenNode(processed, elt) { + for (var i = 0; i < processed.length; i++) { + var node = processed[i]; + if (node.isSameNode(elt)) { + return true; + } + } + return false; + } + + function shouldInclude(elt) { + if(elt.name === "" || elt.name == null || elt.disabled) { + return false; + } + // ignore "submitter" types (see jQuery src/serialize.js) + if (elt.type === "button" || elt.type === "submit" || elt.tagName === "image" || elt.tagName === "reset" || elt.tagName === "file" ) { + return false; + } + if (elt.type === "checkbox" || elt.type === "radio" ) { + return elt.checked; + } + return true; + } + + function processInputValue(processed, values, errors, elt, validate) { + if (elt == null || haveSeenNode(processed, elt)) { + return; + } else { + processed.push(elt); + } + if (shouldInclude(elt)) { + var name = getRawAttribute(elt,"name"); + var value = elt.value; + if (elt.multiple) { + value = toArray(elt.querySelectorAll("option:checked")).map(function (e) { return e.value }); + } + // include file inputs + if (elt.files) { + value = toArray(elt.files); + } + // This is a little ugly because both the current value of the named value in the form + // and the new value could be arrays, so we have to handle all four cases :/ + if (name != null && value != null) { + var current = values[name]; + if(current) { + if (Array.isArray(current)) { + if (Array.isArray(value)) { + values[name] = current.concat(value); + } else { + current.push(value); + } + } else { + if (Array.isArray(value)) { + values[name] = [current].concat(value); + } else { + values[name] = [current, value]; + } + } + } else { + values[name] = value; + } + } + if (validate) { + validateElement(elt, errors); + } + } + if (matches(elt, 'form')) { + var inputs = elt.elements; + forEach(inputs, function(input) { + processInputValue(processed, values, errors, input, validate); + }); + } + } + + function validateElement(element, errors) { + if (element.willValidate) { + triggerEvent(element, "htmx:validation:validate") + if (!element.checkValidity()) { + errors.push({elt: element, message:element.validationMessage, validity:element.validity}); + triggerEvent(element, "htmx:validation:failed", {message:element.validationMessage, validity:element.validity}) + } + } + } + + function getInputValues(elt, verb) { + var processed = []; + var values = {}; + var formValues = {}; + var errors = []; + + // only validate when form is directly submitted and novalidate is not set + var validate = matches(elt, 'form') && elt.noValidate !== true; + + // for a non-GET include the closest form + if (verb !== 'get') { + processInputValue(processed, formValues, errors, closest(elt, 'form'), validate); + } + + // include the element itself + processInputValue(processed, values, errors, elt, validate); + + // if a button or submit was clicked last, include its value + var internalData = getInternalData(elt); + if (internalData.lastButtonClicked) { + var name = getRawAttribute(internalData.lastButtonClicked,"name"); + if (name) { + values[name] = internalData.lastButtonClicked.value; + } + } + + // include any explicit includes + var includes = getClosestAttributeValue(elt, "hx-include"); + if (includes) { + var nodes = querySelectorAllExt(elt, includes); + forEach(nodes, function(node) { + processInputValue(processed, values, errors, node, validate); + // if a non-form is included, include any input values within it + if (!matches(node, 'form')) { + forEach(node.querySelectorAll(INPUT_SELECTOR), function (descendant) { + processInputValue(processed, values, errors, descendant, validate); + }) + } + }); + } + + // form values take precedence, overriding the regular values + values = mergeObjects(values, formValues); + + return {errors:errors, values:values}; + } + + function appendParam(returnStr, name, realValue) { + if (returnStr !== "") { + returnStr += "&"; + } + returnStr += encodeURIComponent(name) + "=" + encodeURIComponent(realValue); + return returnStr; + } + + function urlEncode(values) { + var returnStr = ""; + for (var name in values) { + if (values.hasOwnProperty(name)) { + var value = values[name]; + if (Array.isArray(value)) { + forEach(value, function(v) { + returnStr = appendParam(returnStr, name, v); + }); + } else { + returnStr = appendParam(returnStr, name, value); + } + } + } + return returnStr; + } + + function makeFormData(values) { + var formData = new FormData(); + for (var name in values) { + if (values.hasOwnProperty(name)) { + var value = values[name]; + if (Array.isArray(value)) { + forEach(value, function(v) { + formData.append(name, v); + }); + } else { + formData.append(name, value); + } + } + } + return formData; + } + + //==================================================================== + // Ajax + //==================================================================== + + function getHeaders(elt, target, prompt) { + var headers = { + "HX-Request" : "true", + "HX-Trigger" : getRawAttribute(elt, "id"), + "HX-Trigger-Name" : getRawAttribute(elt, "name"), + "HX-Target" : getAttributeValue(target, "id"), + "HX-Current-URL" : getDocument().location.href, + } + getValuesForElement(elt, "hx-headers", false, headers) + if (prompt !== undefined) { + headers["HX-Prompt"] = prompt; + } + if (getInternalData(elt).boosted) { + headers["HX-Boosted"] = "true"; + } + return headers; + } + + function filterValues(inputValues, elt) { + var paramsValue = getClosestAttributeValue(elt, "hx-params"); + if (paramsValue) { + if (paramsValue === "none") { + return {}; + } else if (paramsValue === "*") { + return inputValues; + } else if(paramsValue.indexOf("not ") === 0) { + forEach(paramsValue.substr(4).split(","), function (name) { + name = name.trim(); + delete inputValues[name]; + }); + return inputValues; + } else { + var newValues = {} + forEach(paramsValue.split(","), function (name) { + name = name.trim(); + newValues[name] = inputValues[name]; + }); + return newValues; + } + } else { + return inputValues; + } + } + + function isAnchorLink(elt) { + return getRawAttribute(elt, 'href') && getRawAttribute(elt, 'href').indexOf("#") >=0 + } + + function getSwapSpecification(elt) { + var swapInfo = getClosestAttributeValue(elt, "hx-swap"); + var swapSpec = { + "swapStyle" : getInternalData(elt).boosted ? 'innerHTML' : htmx.config.defaultSwapStyle, + "swapDelay" : htmx.config.defaultSwapDelay, + "settleDelay" : htmx.config.defaultSettleDelay + } + if (getInternalData(elt).boosted && !isAnchorLink(elt)) { + swapSpec["show"] = "top" + } + if (swapInfo) { + var split = splitOnWhitespace(swapInfo); + if (split.length > 0) { + swapSpec["swapStyle"] = split[0]; + for (var i = 1; i < split.length; i++) { + var modifier = split[i]; + if (modifier.indexOf("swap:") === 0) { + swapSpec["swapDelay"] = parseInterval(modifier.substr(5)); + } + if (modifier.indexOf("settle:") === 0) { + swapSpec["settleDelay"] = parseInterval(modifier.substr(7)); + } + if (modifier.indexOf("scroll:") === 0) { + var scrollSpec = modifier.substr(7); + var splitSpec = scrollSpec.split(":"); + var scrollVal = splitSpec.pop(); + var selectorVal = splitSpec.length > 0 ? splitSpec.join(":") : null; + swapSpec["scroll"] = scrollVal; + swapSpec["scrollTarget"] = selectorVal; + } + if (modifier.indexOf("show:") === 0) { + var showSpec = modifier.substr(5); + var splitSpec = showSpec.split(":"); + var showVal = splitSpec.pop(); + var selectorVal = splitSpec.length > 0 ? splitSpec.join(":") : null; + swapSpec["show"] = showVal; + swapSpec["showTarget"] = selectorVal; + } + } + } + } + return swapSpec; + } + + function encodeParamsForBody(xhr, elt, filteredParameters) { + var encodedParameters = null; + withExtensions(elt, function (extension) { + if (encodedParameters == null) { + encodedParameters = extension.encodeParameters(xhr, filteredParameters, elt); + } + }); + if (encodedParameters != null) { + return encodedParameters; + } else { + if (getClosestAttributeValue(elt, "hx-encoding") === "multipart/form-data" || + (matches(elt, "form") && getRawAttribute(elt, 'enctype') === "multipart/form-data")) { + return makeFormData(filteredParameters); + } else { + return urlEncode(filteredParameters); + } + } + } + + function makeSettleInfo(target) { + return {tasks: [], elts: [target]}; + } + + function updateScrollState(content, swapSpec) { + var first = content[0]; + var last = content[content.length - 1]; + if (swapSpec.scroll) { + var target = null; + if (swapSpec.scrollTarget) { + target = querySelectorExt(first, swapSpec.scrollTarget); + } + if (swapSpec.scroll === "top" && (first || target)) { + target = target || first; + target.scrollTop = 0; + } + if (swapSpec.scroll === "bottom" && (last || target)) { + target = target || last; + target.scrollTop = target.scrollHeight; + } + } + if (swapSpec.show) { + var target = null; + if (swapSpec.showTarget) { + var targetStr = swapSpec.showTarget; + if (swapSpec.showTarget === "window") { + targetStr = "body"; + } + target = querySelectorExt(first, targetStr); + } + if (swapSpec.show === "top" && (first || target)) { + target = target || first; + target.scrollIntoView({block:'start', behavior: htmx.config.scrollBehavior}); + } + if (swapSpec.show === "bottom" && (last || target)) { + target = target || last; + target.scrollIntoView({block:'end', behavior: htmx.config.scrollBehavior}); + } + } + } + + function getValuesForElement(elt, attr, evalAsDefault, values) { + if (values == null) { + values = {}; + } + if (elt == null) { + return values; + } + var attributeValue = getAttributeValue(elt, attr); + if (attributeValue) { + var str = attributeValue.trim(); + var evaluateValue = evalAsDefault; + if (str.indexOf("javascript:") === 0) { + str = str.substr(11); + evaluateValue = true; + } else if (str.indexOf("js:") === 0) { + str = str.substr(3); + evaluateValue = true; + } + if (str.indexOf('{') !== 0) { + str = "{" + str + "}"; + } + var varsValues; + if (evaluateValue) { + varsValues = maybeEval(elt,function () {return Function("return (" + str + ")")();}, {}); + } else { + varsValues = parseJSON(str); + } + for (var key in varsValues) { + if (varsValues.hasOwnProperty(key)) { + if (values[key] == null) { + values[key] = varsValues[key]; + } + } + } + } + return getValuesForElement(parentElt(elt), attr, evalAsDefault, values); + } + + function maybeEval(elt, toEval, defaultVal) { + if (htmx.config.allowEval) { + return toEval(); + } else { + triggerErrorEvent(elt, 'htmx:evalDisallowedError'); + return defaultVal; + } + } + + function getHXVarsForElement(elt, expressionVars) { + return getValuesForElement(elt, "hx-vars", true, expressionVars); + } + + function getHXValsForElement(elt, expressionVars) { + return getValuesForElement(elt, "hx-vals", false, expressionVars); + } + + function getExpressionVars(elt) { + return mergeObjects(getHXVarsForElement(elt), getHXValsForElement(elt)); + } + + function safelySetHeaderValue(xhr, header, headerValue) { + if (headerValue !== null) { + try { + xhr.setRequestHeader(header, headerValue); + } catch (e) { + // On an exception, try to set the header URI encoded instead + xhr.setRequestHeader(header, encodeURIComponent(headerValue)); + xhr.setRequestHeader(header + "-URI-AutoEncoded", "true"); + } + } + } + + function getResponseURL(xhr) { + // NB: IE11 does not support this stuff + if (xhr.responseURL && typeof(URL) !== "undefined") { + try { + var url = new URL(xhr.responseURL); + return url.pathname + url.search; + } catch (e) { + triggerErrorEvent(getDocument().body, "htmx:badResponseUrl", {url: xhr.responseURL}); + } + } + } + + function hasHeader(xhr, regexp) { + return xhr.getAllResponseHeaders().match(regexp); + } + + function ajaxHelper(verb, path, context) { + verb = verb.toLowerCase(); + if (context) { + if (context instanceof Element || isType(context, 'String')) { + return issueAjaxRequest(verb, path, null, null, { + targetOverride: resolveTarget(context), + returnPromise: true + }); + } else { + return issueAjaxRequest(verb, path, resolveTarget(context.source), context.event, + { + handler : context.handler, + headers : context.headers, + values : context.values, + targetOverride: resolveTarget(context.target), + returnPromise: true + }); + } + } else { + return issueAjaxRequest(verb, path, null, null, { + returnPromise: true + }); + } + } + + function hierarchyForElt(elt) { + var arr = []; + while (elt) { + arr.push(elt); + elt = elt.parentElement; + } + return arr; + } + + function issueAjaxRequest(verb, path, elt, event, etc) { + var resolve = null; + var reject = null; + etc = etc != null ? etc : {}; + if(etc.returnPromise && typeof Promise !== "undefined"){ + var promise = new Promise(function (_resolve, _reject) { + resolve = _resolve; + reject = _reject; + }); + } + if(elt == null) { + elt = getDocument().body; + } + var responseHandler = etc.handler || handleAjaxResponse; + + if (!bodyContains(elt)) { + return; // do not issue requests for elements removed from the DOM + } + var target = etc.targetOverride || getTarget(elt); + if (target == null) { + triggerErrorEvent(elt, 'htmx:targetError', {target: getAttributeValue(elt, "hx-target")}); + return; + } + var eltData = getInternalData(elt); + if (eltData.requestInFlight) { + var queueStrategy = 'last'; + if (event) { + var eventData = getInternalData(event); + if (eventData && eventData.triggerSpec && eventData.triggerSpec.queue) { + queueStrategy = eventData.triggerSpec.queue; + } + } + if (eltData.queuedRequests == null) { + eltData.queuedRequests = []; + } + if (queueStrategy === "first" && eltData.queuedRequests.length === 0) { + eltData.queuedRequests.push(function () { + issueAjaxRequest(verb, path, elt, event, etc) + }); + } else if (queueStrategy === "all") { + eltData.queuedRequests.push(function () { + issueAjaxRequest(verb, path, elt, event, etc) + }); + } else if (queueStrategy === "last") { + eltData.queuedRequests = []; // dump existing queue + eltData.queuedRequests.push(function () { + issueAjaxRequest(verb, path, elt, event, etc) + }); + } + return; + } else { + eltData.requestInFlight = true; + } + var endRequestLock = function(){ + eltData.requestInFlight = false + if (eltData.queuedRequests != null && + eltData.queuedRequests.length > 0) { + var queuedRequest = eltData.queuedRequests.shift(); + queuedRequest(); + } + } + var promptQuestion = getClosestAttributeValue(elt, "hx-prompt"); + if (promptQuestion) { + var promptResponse = prompt(promptQuestion); + // prompt returns null if cancelled and empty string if accepted with no entry + if (promptResponse === null || + !triggerEvent(elt, 'htmx:prompt', {prompt: promptResponse, target:target})) { + maybeCall(resolve); + endRequestLock(); + return promise; + } + } + + var confirmQuestion = getClosestAttributeValue(elt, "hx-confirm"); + if (confirmQuestion) { + if(!confirm(confirmQuestion)) { + maybeCall(resolve); + endRequestLock() + return promise; + } + } + + var xhr = new XMLHttpRequest(); + + var headers = getHeaders(elt, target, promptResponse); + if (etc.headers) { + headers = mergeObjects(headers, etc.headers); + } + var results = getInputValues(elt, verb); + var errors = results.errors; + var rawParameters = results.values; + if (etc.values) { + rawParameters = mergeObjects(rawParameters, etc.values); + } + var expressionVars = getExpressionVars(elt); + var allParameters = mergeObjects(rawParameters, expressionVars); + var filteredParameters = filterValues(allParameters, elt); + + if (verb !== 'get' && getClosestAttributeValue(elt, "hx-encoding") == null) { + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } + + // behavior of anchors w/ empty href is to use the current URL + if (path == null || path === "") { + path = getDocument().location.href; + } + + var requestAttrValues = getValuesForElement(elt, 'hx-request'); + + var requestConfig = { + parameters: filteredParameters, + unfilteredParameters: allParameters, + headers:headers, + target:target, + verb:verb, + errors:errors, + withCredentials: etc.credentials || requestAttrValues.credentials || htmx.config.withCredentials, + timeout: etc.timeout || requestAttrValues.timeout || htmx.config.timeout, + path:path, + triggeringEvent:event + }; + + if(!triggerEvent(elt, 'htmx:configRequest', requestConfig)){ + maybeCall(resolve); + endRequestLock(); + return promise; + } + + // copy out in case the object was overwritten + path = requestConfig.path; + verb = requestConfig.verb; + headers = requestConfig.headers; + filteredParameters = requestConfig.parameters; + errors = requestConfig.errors; + + if(errors && errors.length > 0){ + triggerEvent(elt, 'htmx:validation:halted', requestConfig) + maybeCall(resolve); + endRequestLock(); + return promise; + } + + var splitPath = path.split("#"); + var pathNoAnchor = splitPath[0]; + var anchor = splitPath[1]; + if (verb === 'get') { + var finalPathForGet = pathNoAnchor; + var values = Object.keys(filteredParameters).length !== 0; + if (values) { + if (finalPathForGet.indexOf("?") < 0) { + finalPathForGet += "?"; + } else { + finalPathForGet += "&"; + } + finalPathForGet += urlEncode(filteredParameters); + if (anchor) { + finalPathForGet += "#" + anchor; + } + } + xhr.open('GET', finalPathForGet, true); + } else { + xhr.open(verb.toUpperCase(), path, true); + } + + xhr.overrideMimeType("text/html"); + xhr.withCredentials = requestConfig.withCredentials; + xhr.timeout = requestConfig.timeout; + + // request headers + if (requestAttrValues.noHeaders) { + // ignore all headers + } else { + for (var header in headers) { + if (headers.hasOwnProperty(header)) { + var headerValue = headers[header]; + safelySetHeaderValue(xhr, header, headerValue); + } + } + } + + var responseInfo = {xhr: xhr, target: target, requestConfig: requestConfig, pathInfo:{ + path:path, finalPath:finalPathForGet, anchor:anchor + } + }; + + xhr.onload = function () { + try { + var hierarchy = hierarchyForElt(elt); + responseHandler(elt, responseInfo); + removeRequestIndicatorClasses(indicators); + triggerEvent(elt, 'htmx:afterRequest', responseInfo); + triggerEvent(elt, 'htmx:afterOnLoad', responseInfo); + // if the body no longer contains the element, trigger the even on the closest parent + // remaining in the DOM + if (!bodyContains(elt)) { + var secondaryTriggerElt = null; + while (hierarchy.length > 0 && secondaryTriggerElt == null) { + var parentEltInHierarchy = hierarchy.shift(); + if (bodyContains(parentEltInHierarchy)) { + secondaryTriggerElt = parentEltInHierarchy; + } + } + if (secondaryTriggerElt) { + triggerEvent(secondaryTriggerElt, 'htmx:afterRequest', responseInfo); + triggerEvent(secondaryTriggerElt, 'htmx:afterOnLoad', responseInfo); + } + } + maybeCall(resolve); + endRequestLock(); + } catch (e) { + triggerErrorEvent(elt, 'htmx:onLoadError', mergeObjects({error:e}, responseInfo)); + throw e; + } + } + xhr.onerror = function () { + removeRequestIndicatorClasses(indicators); + triggerErrorEvent(elt, 'htmx:afterRequest', responseInfo); + triggerErrorEvent(elt, 'htmx:sendError', responseInfo); + maybeCall(reject); + endRequestLock(); + } + xhr.onabort = function() { + removeRequestIndicatorClasses(indicators); + triggerErrorEvent(elt, 'htmx:afterRequest', responseInfo); + triggerErrorEvent(elt, 'htmx:sendAbort', responseInfo); + maybeCall(reject); + endRequestLock(); + } + xhr.ontimeout = function() { + removeRequestIndicatorClasses(indicators); + triggerErrorEvent(elt, 'htmx:afterRequest', responseInfo); + triggerErrorEvent(elt, 'htmx:timeout', responseInfo); + maybeCall(reject); + endRequestLock(); + } + if(!triggerEvent(elt, 'htmx:beforeRequest', responseInfo)){ + maybeCall(resolve); + endRequestLock() + return promise + } + var indicators = addRequestIndicatorClasses(elt); + + forEach(['loadstart', 'loadend', 'progress', 'abort'], function(eventName) { + forEach([xhr, xhr.upload], function (target) { + target.addEventListener(eventName, function(event){ + triggerEvent(elt, "htmx:xhr:" + eventName, { + lengthComputable:event.lengthComputable, + loaded:event.loaded, + total:event.total + }); + }) + }); + }); + triggerEvent(elt, 'htmx:beforeSend', responseInfo); + xhr.send(verb === 'get' ? null : encodeParamsForBody(xhr, elt, filteredParameters)); + return promise; + } + + function handleAjaxResponse(elt, responseInfo) { + var xhr = responseInfo.xhr; + var target = responseInfo.target; + + if (!triggerEvent(elt, 'htmx:beforeOnLoad', responseInfo)) return; + + if (hasHeader(xhr, /HX-Trigger:/i)) { + handleTrigger(xhr, "HX-Trigger", elt); + } + + if (hasHeader(xhr,/HX-Push:/i)) { + var pushedUrl = xhr.getResponseHeader("HX-Push"); + } + + if (hasHeader(xhr, /HX-Redirect:/i)) { + window.location.href = xhr.getResponseHeader("HX-Redirect"); + return; + } + + if (hasHeader(xhr,/HX-Refresh:/i)) { + if ("true" === xhr.getResponseHeader("HX-Refresh")) { + location.reload(); + return; + } + } + + if (hasHeader(xhr,/HX-Retarget:/i)) { + responseInfo.target = getDocument().querySelector(xhr.getResponseHeader("HX-Retarget")); + } + + var shouldSaveHistory = shouldPush(elt) || pushedUrl; + + // by default htmx only swaps on 200 return codes and does not swap + // on 204 'No Content' + // this can be ovverriden by responding to the htmx:beforeSwap event and + // overriding the detail.shouldSwap property + var shouldSwap = xhr.status >= 200 && xhr.status < 400 && xhr.status !== 204; + var serverResponse = xhr.response; + var isError = xhr.status >= 400; + var beforeSwapDetails = mergeObjects({shouldSwap: shouldSwap, serverResponse:serverResponse, isError:isError}, responseInfo); + if (!triggerEvent(target, 'htmx:beforeSwap', beforeSwapDetails)) return; + + target = beforeSwapDetails.target; // allow re-targeting + serverResponse = beforeSwapDetails.serverResponse; // allow updating content + isError = beforeSwapDetails.isError; // allow updating error + + responseInfo.failed = isError; // Make failed property available to response events + responseInfo.successful = !isError; // Make successful property available to response events + + if (beforeSwapDetails.shouldSwap) { + if (xhr.status === 286) { + cancelPolling(elt); + } + + withExtensions(elt, function (extension) { + serverResponse = extension.transformResponse(serverResponse, xhr, elt); + }); + + // Save current page + if (shouldSaveHistory) { + saveHistory(); + } + + var swapSpec = getSwapSpecification(elt); + + target.classList.add(htmx.config.swappingClass); + var doSwap = function () { + try { + + var activeElt = document.activeElement; + var selectionInfo = {}; + try { + selectionInfo = { + elt: activeElt, + // @ts-ignore + start: activeElt ? activeElt.selectionStart : null, + // @ts-ignore + end: activeElt ? activeElt.selectionEnd : null + }; + } catch (e) { + // safari issue - see https://github.com/microsoft/playwright/issues/5894 + } + + var settleInfo = makeSettleInfo(target); + selectAndSwap(swapSpec.swapStyle, target, elt, serverResponse, settleInfo); + + if (selectionInfo.elt && + !bodyContains(selectionInfo.elt) && + selectionInfo.elt.id) { + var newActiveElt = document.getElementById(selectionInfo.elt.id); + if (newActiveElt) { + // @ts-ignore + if (selectionInfo.start && newActiveElt.setSelectionRange) { + // @ts-ignore + newActiveElt.setSelectionRange(selectionInfo.start, selectionInfo.end); + } + newActiveElt.focus(); + } + } + + target.classList.remove(htmx.config.swappingClass); + forEach(settleInfo.elts, function (elt) { + if (elt.classList) { + elt.classList.add(htmx.config.settlingClass); + } + triggerEvent(elt, 'htmx:afterSwap', responseInfo); + }); + if (responseInfo.pathInfo.anchor) { + location.hash = responseInfo.pathInfo.anchor; + } + + if (hasHeader(xhr, /HX-Trigger-After-Swap:/i)) { + var finalElt = elt; + if (!bodyContains(elt)) { + finalElt = getDocument().body; + } + handleTrigger(xhr, "HX-Trigger-After-Swap", finalElt); + } + + var doSettle = function () { + forEach(settleInfo.tasks, function (task) { + task.call(); + }); + forEach(settleInfo.elts, function (elt) { + if (elt.classList) { + elt.classList.remove(htmx.config.settlingClass); + } + triggerEvent(elt, 'htmx:afterSettle', responseInfo); + }); + // push URL and save new page + if (shouldSaveHistory) { + var pathToPush = pushedUrl || getPushUrl(elt) || getResponseURL(xhr) || responseInfo.pathInfo.finalPath || responseInfo.pathInfo.path; + pushUrlIntoHistory(pathToPush); + triggerEvent(getDocument().body, 'htmx:pushedIntoHistory', {path: pathToPush}); + } + updateScrollState(settleInfo.elts, swapSpec); + + if (hasHeader(xhr, /HX-Trigger-After-Settle:/i)) { + var finalElt = elt; + if (!bodyContains(elt)) { + finalElt = getDocument().body; + } + handleTrigger(xhr, "HX-Trigger-After-Settle", finalElt); + } + } + + if (swapSpec.settleDelay > 0) { + setTimeout(doSettle, swapSpec.settleDelay) + } else { + doSettle(); + } + } catch (e) { + triggerErrorEvent(elt, 'htmx:swapError', responseInfo); + throw e; + } + }; + + if (swapSpec.swapDelay > 0) { + setTimeout(doSwap, swapSpec.swapDelay) + } else { + doSwap(); + } + } + if (isError) { + triggerErrorEvent(elt, 'htmx:responseError', mergeObjects({error: "Response Status Error Code " + xhr.status + " from " + responseInfo.pathInfo.path}, responseInfo)); + } + } + + //==================================================================== + // Extensions API + //==================================================================== + var extensions = {}; + function extensionBase() { + return { + onEvent : function(name, evt) {return true;}, + transformResponse : function(text, xhr, elt) {return text;}, + isInlineSwap : function(swapStyle) {return false;}, + handleSwap : function(swapStyle, target, fragment, settleInfo) {return false;}, + encodeParameters : function(xhr, parameters, elt) {return null;} + } + } + + function defineExtension(name, extension) { + extensions[name] = mergeObjects(extensionBase(), extension); + } + + function removeExtension(name) { + delete extensions[name]; + } + + function getExtensions(elt, extensionsToReturn, extensionsToIgnore) { + if (elt == undefined) { + return extensionsToReturn; + } + if (extensionsToReturn == undefined) { + extensionsToReturn = []; + } + if (extensionsToIgnore == undefined) { + extensionsToIgnore = []; + } + var extensionsForElement = getAttributeValue(elt, "hx-ext"); + if (extensionsForElement) { + forEach(extensionsForElement.split(","), function(extensionName){ + extensionName = extensionName.replace(/ /g, ''); + if (extensionName.slice(0, 7) == "ignore:") { + extensionsToIgnore.push(extensionName.slice(7)); + return; + } + if (extensionsToIgnore.indexOf(extensionName) < 0) { + var extension = extensions[extensionName]; + if (extension && extensionsToReturn.indexOf(extension) < 0) { + extensionsToReturn.push(extension); + } + } + }); + } + return getExtensions(parentElt(elt), extensionsToReturn, extensionsToIgnore); + } + + //==================================================================== + // Initialization + //==================================================================== + + function ready(fn) { + if (getDocument().readyState !== 'loading') { + fn(); + } else { + getDocument().addEventListener('DOMContentLoaded', fn); + } + } + + function insertIndicatorStyles() { + if (htmx.config.includeIndicatorStyles !== false) { + getDocument().head.insertAdjacentHTML("beforeend", + ""); + } + } + + function getMetaConfig() { + var element = getDocument().querySelector('meta[name="htmx-config"]'); + if (element) { + // @ts-ignore + return parseJSON(element.content); + } else { + return null; + } + } + + function mergeMetaConfig() { + var metaConfig = getMetaConfig(); + if (metaConfig) { + htmx.config = mergeObjects(htmx.config , metaConfig) + } + } + + // initialize the document + ready(function () { + mergeMetaConfig(); + insertIndicatorStyles(); + var body = getDocument().body; + processNode(body); + window.onpopstate = function (event) { + if (event.state && event.state.htmx) { + restoreHistory(); + } + }; + setTimeout(function () { + triggerEvent(body, 'htmx:load', {}); // give ready handlers a chance to load up before firing this event + }, 0); + }) + + return htmx; + } +)() +})); diff --git a/assets/js/section_list/section_list.js b/assets/js/section_list/section_list.js new file mode 100644 index 0000000000000000000000000000000000000000..936e131aa9923963108c5caad3f380597b37bb2e --- /dev/null +++ b/assets/js/section_list/section_list.js @@ -0,0 +1,51 @@ +function section_list(sections, selectAfter, def) { + var group = document.createElement("div"); + group.id = "section-list"; + var select = document.createElement("select"); + + var showall = document.createElement("button"); + group.appendChild(showall); + showall.textContent = "all"; + showall.addEventListener("click", function() { + sections.forEach(function(section) { + section.querySelector("h1").style.display = "block"; + section.style.display = "block"; + }); + group.style.display = "none"; + select = null; + }); + + sections.forEach(function(section) { + section.querySelector("h1").style.display = "none"; + select[select.options.length] = new Option( + section.querySelector("h1").textContent, + section.id, + section.id == def, + window.location.hash ? + "#" + section.id == window.location.hash : + section.id == def + ); + }); + + group.appendChild(select); + selectAfter.insertAdjacentElement("afterend", group); + + window.addEventListener("hashchange", function() { + if(!select) return; + + sections.forEach(function(section) { section.style.display = "none"; }); + if(window.location.hash) { + document.querySelector(window.location.hash).style.display = "block"; + } + select.value = window.location.hash.replace(/^#/, ""); + var event = document.createEvent("HTMLEvents"); + event.initEvent("change", false, true); + select.dispatchEvent(event); + }); + + window.location.hash = ""; + window.location.hash = "#" + select.value; + select.addEventListener("change", function() { + window.location.hash = "#" + select.value; + }); +} diff --git a/assets/js/tom_select/tom_select.js b/assets/js/tom_select/tom_select.js new file mode 100644 index 0000000000000000000000000000000000000000..5546bc94c428844f8469e64e177a89917cd362a6 --- /dev/null +++ b/assets/js/tom_select/tom_select.js @@ -0,0 +1,4806 @@ +/** +* Tom Select v2.0.0 +* Licensed under the Apache License, Version 2.0 (the "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.TomSelect = factory()); +}(this, (function () { 'use strict'; + + /** + * MicroEvent - to make any js object an event emitter + * + * - pure javascript - server compatible, browser compatible + * - dont rely on the browser doms + * - super simple - you get it immediatly, no mistery, no magic involved + * + * @author Jerome Etienne (https://github.com/jeromeetienne) + */ + + /** + * Execute callback for each event in space separated list of event names + * + */ + function forEvents(events, callback) { + events.split(/\s+/).forEach(event => { + callback(event); + }); + } + + class MicroEvent { + constructor() { + this._events = {}; + } + + on(events, fct) { + forEvents(events, event => { + this._events[event] = this._events[event] || []; + + this._events[event].push(fct); + }); + } + + off(events, fct) { + var n = arguments.length; + + if (n === 0) { + this._events = {}; + return; + } + + forEvents(events, event => { + if (n === 1) return delete this._events[event]; + if (event in this._events === false) return; + + this._events[event].splice(this._events[event].indexOf(fct), 1); + }); + } + + trigger(events, ...args) { + var self = this; + forEvents(events, event => { + if (event in self._events === false) return; + + for (let fct of self._events[event]) { + fct.apply(self, args); + } + }); + } + + } + + /** + * microplugin.js + * Copyright (c) 2013 Brian Reavis & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this + * file except in compliance with the License. You may obtain a copy of the License at: + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF + * ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + * @author Brian Reavis + */ + function MicroPlugin(Interface) { + Interface.plugins = {}; + return class extends Interface { + constructor(...args) { + super(...args); + this.plugins = { + names: [], + settings: {}, + requested: {}, + loaded: {} + }; + } + + /** + * Registers a plugin. + * + * @param {function} fn + */ + static define(name, fn) { + Interface.plugins[name] = { + 'name': name, + 'fn': fn + }; + } + /** + * Initializes the listed plugins (with options). + * Acceptable formats: + * + * List (without options): + * ['a', 'b', 'c'] + * + * List (with options): + * [{'name': 'a', options: {}}, {'name': 'b', options: {}}] + * + * Hash (with options): + * {'a': { ... }, 'b': { ... }, 'c': { ... }} + * + * @param {array|object} plugins + */ + + + initializePlugins(plugins) { + var key, name; + const self = this; + const queue = []; + + if (Array.isArray(plugins)) { + plugins.forEach(plugin => { + if (typeof plugin === 'string') { + queue.push(plugin); + } else { + self.plugins.settings[plugin.name] = plugin.options; + queue.push(plugin.name); + } + }); + } else if (plugins) { + for (key in plugins) { + if (plugins.hasOwnProperty(key)) { + self.plugins.settings[key] = plugins[key]; + queue.push(key); + } + } + } + + while (name = queue.shift()) { + self.require(name); + } + } + + loadPlugin(name) { + var self = this; + var plugins = self.plugins; + var plugin = Interface.plugins[name]; + + if (!Interface.plugins.hasOwnProperty(name)) { + throw new Error('Unable to find "' + name + '" plugin'); + } + + plugins.requested[name] = true; + plugins.loaded[name] = plugin.fn.apply(self, [self.plugins.settings[name] || {}]); + plugins.names.push(name); + } + /** + * Initializes a plugin. + * + */ + + + require(name) { + var self = this; + var plugins = self.plugins; + + if (!self.plugins.loaded.hasOwnProperty(name)) { + if (plugins.requested[name]) { + throw new Error('Plugin has circular dependency ("' + name + '")'); + } + + self.loadPlugin(name); + } + + return plugins.loaded[name]; + } + + }; + } + + // https://github.com/andrewrk/node-diacritics/blob/master/index.js + var latin_pat; + const accent_pat = '[\u0300-\u036F\u{b7}\u{2be}]'; // \u{2bc} + + const accent_reg = new RegExp(accent_pat, 'g'); + var diacritic_patterns; + const latin_convert = { + 'æ': 'ae', + 'ⱥ': 'a', + 'ø': 'o' + }; + const convert_pat = new RegExp(Object.keys(latin_convert).join('|'), 'g'); + /** + * code points generated from toCodePoints(); + * removed 65339 to 65345 + */ + + const code_points = [[67, 67], [160, 160], [192, 438], [452, 652], [961, 961], [1019, 1019], [1083, 1083], [1281, 1289], [1984, 1984], [5095, 5095], [7429, 7441], [7545, 7549], [7680, 7935], [8580, 8580], [9398, 9449], [11360, 11391], [42792, 42793], [42802, 42851], [42873, 42897], [42912, 42922], [64256, 64260], [65313, 65338], [65345, 65370]]; + /** + * Remove accents + * via https://github.com/krisk/Fuse/issues/133#issuecomment-318692703 + * + */ + + const asciifold = str => { + return str.normalize('NFKD').replace(accent_reg, '').toLowerCase().replace(convert_pat, function (foreignletter) { + return latin_convert[foreignletter]; + }); + }; + /** + * Convert array of strings to a regular expression + * ex ['ab','a'] => (?:ab|a) + * ex ['a','b'] => [ab] + * + */ + + + const arrayToPattern = (chars, glue = '|') => { + if (chars.length == 1) { + return chars[0]; + } + + var longest = 1; + chars.forEach(a => { + longest = Math.max(longest, a.length); + }); + + if (longest == 1) { + return '[' + chars.join('') + ']'; + } + + return '(?:' + chars.join(glue) + ')'; + }; + /** + * Get all possible combinations of substrings that add up to the given string + * https://stackoverflow.com/questions/30169587/find-all-the-combination-of-substrings-that-add-up-to-the-given-string + * + */ + + const allSubstrings = input => { + if (input.length === 1) return [[input]]; + var result = []; + allSubstrings(input.substring(1)).forEach(function (subresult) { + var tmp = subresult.slice(0); + tmp[0] = input.charAt(0) + tmp[0]; + result.push(tmp); + tmp = subresult.slice(0); + tmp.unshift(input.charAt(0)); + result.push(tmp); + }); + return result; + }; + /** + * Generate a list of diacritics from the list of code points + * + */ + + const generateDiacritics = () => { + var diacritics = {}; + code_points.forEach(code_range => { + for (let i = code_range[0]; i <= code_range[1]; i++) { + let diacritic = String.fromCharCode(i); + let latin = asciifold(diacritic); + + if (latin == diacritic.toLowerCase()) { + continue; + } + + if (!(latin in diacritics)) { + diacritics[latin] = [latin]; + } + + var patt = new RegExp(arrayToPattern(diacritics[latin]), 'iu'); + + if (diacritic.match(patt)) { + continue; + } + + diacritics[latin].push(diacritic); + } + }); + var latin_chars = Object.keys(diacritics); // latin character pattern + // match longer substrings first + + latin_chars = latin_chars.sort((a, b) => b.length - a.length); + latin_pat = new RegExp('(' + arrayToPattern(latin_chars) + accent_pat + '*)', 'g'); // build diacritic patterns + // ae needs: + // (?:(?:ae|Æ|Ǽ|Ǣ)|(?:A|Ⓐ|A...)(?:E|ɛ|Ⓔ...)) + + var diacritic_patterns = {}; + latin_chars.sort((a, b) => a.length - b.length).forEach(latin => { + var substrings = allSubstrings(latin); + var pattern = substrings.map(sub_pat => { + sub_pat = sub_pat.map(l => { + if (diacritics.hasOwnProperty(l)) { + return arrayToPattern(diacritics[l]); + } + + return l; + }); + return arrayToPattern(sub_pat, ''); + }); + diacritic_patterns[latin] = arrayToPattern(pattern); + }); + return diacritic_patterns; + }; + /** + * Expand a regular expression pattern to include diacritics + * eg /a/ becomes /aⓐaẚàáâầấẫẩãāăằắẵẳȧǡäǟảåǻǎȁȃạậặḁąⱥɐɑAⒶAÀÁÂẦẤẪẨÃĀĂẰẮẴẲȦǠÄǞẢÅǺǍȀȂẠẬẶḀĄȺⱯ/ + * + */ + + const diacriticRegexPoints = regex => { + if (diacritic_patterns === undefined) { + diacritic_patterns = generateDiacritics(); + } + + const decomposed = regex.normalize('NFKD').toLowerCase(); + return decomposed.split(latin_pat).map(part => { + if (part == '') { + return ''; + } // "ffl" or "ffl" + + + const no_accent = asciifold(part); + + if (diacritic_patterns.hasOwnProperty(no_accent)) { + return diacritic_patterns[no_accent]; + } // 'أهلا' (\u{623}\u{647}\u{644}\u{627}) or 'أهلا' (\u{627}\u{654}\u{647}\u{644}\u{627}) + + + const composed_part = part.normalize('NFC'); + + if (composed_part != part) { + return arrayToPattern([part, composed_part]); + } + + return part; + }).join(''); + }; + + // @ts-ignore TS2691 "An import path cannot end with a '.ts' extension" + + /** + * A property getter resolving dot-notation + * @param {Object} obj The root object to fetch property on + * @param {String} name The optionally dotted property name to fetch + * @return {Object} The resolved property value + */ + const getAttr = (obj, name) => { + if (!obj) return; + return obj[name]; + }; + /** + * A property getter resolving dot-notation + * @param {Object} obj The root object to fetch property on + * @param {String} name The optionally dotted property name to fetch + * @return {Object} The resolved property value + */ + + const getAttrNesting = (obj, name) => { + if (!obj) return; + var part, + names = name.split("."); + + while ((part = names.shift()) && (obj = obj[part])); + + return obj; + }; + /** + * Calculates how close of a match the + * given value is against a search token. + * + */ + + const scoreValue = (value, token, weight) => { + var score, pos; + if (!value) return 0; + value = value + ''; + pos = value.search(token.regex); + if (pos === -1) return 0; + score = token.string.length / value.length; + if (pos === 0) score += 0.5; + return score * weight; + }; + /** + * + * https://stackoverflow.com/questions/63006601/why-does-u-throw-an-invalid-escape-error + */ + + const escape_regex = str => { + return (str + '').replace(/([\$\(-\+\.\?\[-\^\{-\}])/g, '\\$1'); + }; + /** + * Cast object property to an array if it exists and has a value + * + */ + + const propToArray = (obj, key) => { + var value = obj[key]; + if (typeof value == 'function') return value; + + if (value && !Array.isArray(value)) { + obj[key] = [value]; + } + }; + /** + * Iterates over arrays and hashes. + * + * ``` + * iterate(this.items, function(item, id) { + * // invoked for each item + * }); + * ``` + * + */ + + const iterate = (object, callback) => { + if (Array.isArray(object)) { + object.forEach(callback); + } else { + for (var key in object) { + if (object.hasOwnProperty(key)) { + callback(object[key], key); + } + } + } + }; + const cmp = (a, b) => { + if (typeof a === 'number' && typeof b === 'number') { + return a > b ? 1 : a < b ? -1 : 0; + } + + a = asciifold(a + '').toLowerCase(); + b = asciifold(b + '').toLowerCase(); + if (a > b) return 1; + if (b > a) return -1; + return 0; + }; + + /** + * sifter.js + * Copyright (c) 2013–2020 Brian Reavis & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this + * file except in compliance with the License. You may obtain a copy of the License at: + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF + * ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + * @author Brian Reavis + */ + + class Sifter { + // []|{}; + + /** + * Textually searches arrays and hashes of objects + * by property (or multiple properties). Designed + * specifically for autocomplete. + * + */ + constructor(items, settings) { + this.items = items; + this.settings = settings || { + diacritics: true + }; + } + + /** + * Splits a search string into an array of individual + * regexps to be used to match results. + * + */ + tokenize(query, respect_word_boundaries, weights) { + if (!query || !query.length) return []; + const tokens = []; + const words = query.split(/\s+/); + var field_regex; + + if (weights) { + field_regex = new RegExp('^(' + Object.keys(weights).map(escape_regex).join('|') + ')\:(.*)$'); + } + + words.forEach(word => { + let field_match; + let field = null; + let regex = null; // look for "field:query" tokens + + if (field_regex && (field_match = word.match(field_regex))) { + field = field_match[1]; + word = field_match[2]; + } + + if (word.length > 0) { + regex = escape_regex(word); + + if (this.settings.diacritics) { + regex = diacriticRegexPoints(regex); + } + + if (respect_word_boundaries) regex = "\\b" + regex; + } + + tokens.push({ + string: word, + regex: regex ? new RegExp(regex, 'iu') : null, + field: field + }); + }); + return tokens; + } + + /** + * Returns a function to be used to score individual results. + * + * Good matches will have a higher score than poor matches. + * If an item is not a match, 0 will be returned by the function. + * + * @returns {function} + */ + getScoreFunction(query, options) { + var search = this.prepareSearch(query, options); + return this._getScoreFunction(search); + } + + _getScoreFunction(search) { + const tokens = search.tokens, + token_count = tokens.length; + + if (!token_count) { + return function () { + return 0; + }; + } + + const fields = search.options.fields, + weights = search.weights, + field_count = fields.length, + getAttrFn = search.getAttrFn; + + if (!field_count) { + return function () { + return 1; + }; + } + /** + * Calculates the score of an object + * against the search query. + * + */ + + + const scoreObject = function () { + if (field_count === 1) { + return function (token, data) { + const field = fields[0].field; + return scoreValue(getAttrFn(data, field), token, weights[field]); + }; + } + + return function (token, data) { + var sum = 0; // is the token specific to a field? + + if (token.field) { + const value = getAttrFn(data, token.field); + + if (!token.regex && value) { + sum += 1 / field_count; + } else { + sum += scoreValue(value, token, 1); + } + } else { + iterate(weights, (weight, field) => { + sum += scoreValue(getAttrFn(data, field), token, weight); + }); + } + + return sum / field_count; + }; + }(); + + if (token_count === 1) { + return function (data) { + return scoreObject(tokens[0], data); + }; + } + + if (search.options.conjunction === 'and') { + return function (data) { + var i = 0, + score, + sum = 0; + + for (; i < token_count; i++) { + score = scoreObject(tokens[i], data); + if (score <= 0) return 0; + sum += score; + } + + return sum / token_count; + }; + } else { + return function (data) { + var sum = 0; + iterate(tokens, token => { + sum += scoreObject(token, data); + }); + return sum / token_count; + }; + } + } + + /** + * Returns a function that can be used to compare two + * results, for sorting purposes. If no sorting should + * be performed, `null` will be returned. + * + * @return function(a,b) + */ + getSortFunction(query, options) { + var search = this.prepareSearch(query, options); + return this._getSortFunction(search); + } + + _getSortFunction(search) { + var i, n, implicit_score; + const self = this, + options = search.options, + sort = !search.query && options.sort_empty ? options.sort_empty : options.sort, + sort_flds = [], + multipliers = []; + + if (typeof sort == 'function') { + return sort.bind(this); + } + /** + * Fetches the specified sort field value + * from a search result item. + * + */ + + + const get_field = function get_field(name, result) { + if (name === '$score') return result.score; + return search.getAttrFn(self.items[result.id], name); + }; // parse options + + + if (sort) { + for (i = 0, n = sort.length; i < n; i++) { + if (search.query || sort[i].field !== '$score') { + sort_flds.push(sort[i]); + } + } + } // the "$score" field is implied to be the primary + // sort field, unless it's manually specified + + + if (search.query) { + implicit_score = true; + + for (i = 0, n = sort_flds.length; i < n; i++) { + if (sort_flds[i].field === '$score') { + implicit_score = false; + break; + } + } + + if (implicit_score) { + sort_flds.unshift({ + field: '$score', + direction: 'desc' + }); + } + } else { + for (i = 0, n = sort_flds.length; i < n; i++) { + if (sort_flds[i].field === '$score') { + sort_flds.splice(i, 1); + break; + } + } + } + + for (i = 0, n = sort_flds.length; i < n; i++) { + multipliers.push(sort_flds[i].direction === 'desc' ? -1 : 1); + } // build function + + + const sort_flds_count = sort_flds.length; + + if (!sort_flds_count) { + return null; + } else if (sort_flds_count === 1) { + const sort_fld = sort_flds[0].field; + const multiplier = multipliers[0]; + return function (a, b) { + return multiplier * cmp(get_field(sort_fld, a), get_field(sort_fld, b)); + }; + } else { + return function (a, b) { + var i, result, field; + + for (i = 0; i < sort_flds_count; i++) { + field = sort_flds[i].field; + result = multipliers[i] * cmp(get_field(field, a), get_field(field, b)); + if (result) return result; + } + + return 0; + }; + } + } + + /** + * Parses a search query and returns an object + * with tokens and fields ready to be populated + * with results. + * + */ + prepareSearch(query, optsUser) { + const weights = {}; + var options = Object.assign({}, optsUser); + propToArray(options, 'sort'); + propToArray(options, 'sort_empty'); // convert fields to new format + + if (options.fields) { + propToArray(options, 'fields'); + const fields = []; + options.fields.forEach(field => { + if (typeof field == 'string') { + field = { + field: field, + weight: 1 + }; + } + + fields.push(field); + weights[field.field] = 'weight' in field ? field.weight : 1; + }); + options.fields = fields; + } + + return { + options: options, + query: query.toLowerCase().trim(), + tokens: this.tokenize(query, options.respect_word_boundaries, weights), + total: 0, + items: [], + weights: weights, + getAttrFn: options.nesting ? getAttrNesting : getAttr + }; + } + + /** + * Searches through all items and returns a sorted array of matches. + * + */ + search(query, options) { + var self = this, + score, + search; + search = this.prepareSearch(query, options); + options = search.options; + query = search.query; // generate result scoring function + + const fn_score = options.score || self._getScoreFunction(search); // perform search and sort + + + if (query.length) { + iterate(self.items, (item, id) => { + score = fn_score(item); + + if (options.filter === false || score > 0) { + search.items.push({ + 'score': score, + 'id': id + }); + } + }); + } else { + iterate(self.items, (item, id) => { + search.items.push({ + 'score': 1, + 'id': id + }); + }); + } + + const fn_sort = self._getSortFunction(search); + + if (fn_sort) search.items.sort(fn_sort); // apply limits + + search.total = search.items.length; + + if (typeof options.limit === 'number') { + search.items = search.items.slice(0, options.limit); + } + + return search; + } + + } + + /** + * Return a dom element from either a dom query string, jQuery object, a dom element or html string + * https://stackoverflow.com/questions/494143/creating-a-new-dom-element-from-an-html-string-using-built-in-dom-methods-or-pro/35385518#35385518 + * + * param query should be {} + */ + + const getDom = query => { + if (query.jquery) { + return query[0]; + } + + if (query instanceof HTMLElement) { + return query; + } + + if (isHtmlString(query)) { + let div = document.createElement('div'); + div.innerHTML = query.trim(); // Never return a text node of whitespace as the result + + return div.firstChild; + } + + return document.querySelector(query); + }; + const isHtmlString = arg => { + if (typeof arg === 'string' && arg.indexOf('<') > -1) { + return true; + } + + return false; + }; + const escapeQuery = query => { + return query.replace(/['"\\]/g, '\\$&'); + }; + /** + * Dispatch an event + * + */ + + const triggerEvent = (dom_el, event_name) => { + var event = document.createEvent('HTMLEvents'); + event.initEvent(event_name, true, false); + dom_el.dispatchEvent(event); + }; + /** + * Apply CSS rules to a dom element + * + */ + + const applyCSS = (dom_el, css) => { + Object.assign(dom_el.style, css); + }; + /** + * Add css classes + * + */ + + const addClasses = (elmts, ...classes) => { + var norm_classes = classesArray(classes); + elmts = castAsArray(elmts); + elmts.map(el => { + norm_classes.map(cls => { + el.classList.add(cls); + }); + }); + }; + /** + * Remove css classes + * + */ + + const removeClasses = (elmts, ...classes) => { + var norm_classes = classesArray(classes); + elmts = castAsArray(elmts); + elmts.map(el => { + norm_classes.map(cls => { + el.classList.remove(cls); + }); + }); + }; + /** + * Return arguments + * + */ + + const classesArray = args => { + var classes = []; + iterate(args, _classes => { + if (typeof _classes === 'string') { + _classes = _classes.trim().split(/[\11\12\14\15\40]/); + } + + if (Array.isArray(_classes)) { + classes = classes.concat(_classes); + } + }); + return classes.filter(Boolean); + }; + /** + * Create an array from arg if it's not already an array + * + */ + + const castAsArray = arg => { + if (!Array.isArray(arg)) { + arg = [arg]; + } + + return arg; + }; + /** + * Get the closest node to the evt.target matching the selector + * Stops at wrapper + * + */ + + const parentMatch = (target, selector, wrapper) => { + if (wrapper && !wrapper.contains(target)) { + return; + } + + while (target && target.matches) { + if (target.matches(selector)) { + return target; + } + + target = target.parentNode; + } + }; + /** + * Get the first or last item from an array + * + * > 0 - right (last) + * <= 0 - left (first) + * + */ + + const getTail = (list, direction = 0) => { + if (direction > 0) { + return list[list.length - 1]; + } + + return list[0]; + }; + /** + * Return true if an object is empty + * + */ + + const isEmptyObject = obj => { + return Object.keys(obj).length === 0; + }; + /** + * Get the index of an element amongst sibling nodes of the same type + * + */ + + const nodeIndex = (el, amongst) => { + if (!el) return -1; + amongst = amongst || el.nodeName; + var i = 0; + + while (el = el.previousElementSibling) { + if (el.matches(amongst)) { + i++; + } + } + + return i; + }; + /** + * Set attributes of an element + * + */ + + const setAttr = (el, attrs) => { + iterate(attrs, (val, attr) => { + if (val == null) { + el.removeAttribute(attr); + } else { + el.setAttribute(attr, '' + val); + } + }); + }; + /** + * Replace a node + */ + + const replaceNode = (existing, replacement) => { + if (existing.parentNode) existing.parentNode.replaceChild(replacement, existing); + }; + + /** + * highlight v3 | MIT license | Johann Burkard + * Highlights arbitrary terms in a node. + * + * - Modified by Marshal 2011-6-24 (added regex) + * - Modified by Brian Reavis 2012-8-27 (cleanup) + */ + const highlight = (element, regex) => { + if (regex === null) return; // convet string to regex + + if (typeof regex === 'string') { + if (!regex.length) return; + regex = new RegExp(regex, 'i'); + } // Wrap matching part of text node with highlighting , e.g. + // Soccer -> Soccer for regex = /soc/i + + + const highlightText = node => { + var match = node.data.match(regex); + + if (match && node.data.length > 0) { + var spannode = document.createElement('span'); + spannode.className = 'highlight'; + var middlebit = node.splitText(match.index); + middlebit.splitText(match[0].length); + var middleclone = middlebit.cloneNode(true); + spannode.appendChild(middleclone); + replaceNode(middlebit, spannode); + return 1; + } + + return 0; + }; // Recurse element node, looking for child text nodes to highlight, unless element + // is childless,