diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index dd48e28bb..93932cf4d 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -28,11 +28,6 @@ jobs:
ruby-version: ${{ matrix.ruby-version }}
bundler-cache: true
- - uses: actions/setup-node@v4
- with:
- node-version: "lts/*"
- cache: "yarn"
-
- name: Set up database
run: bundle exec rails db:setup
diff --git a/.gitignore b/.gitignore
index 7148c54c9..3662f57ad 100755
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,3 @@
-config/database.yml
.env
# Ignore bundler config.
@@ -72,3 +71,6 @@ yarn-debug.log*
/config/master.key
/config/credentials/production.key
/config/credentials/google-drive.json
+
+/app/assets/builds/*
+!/app/assets/builds/.keep
diff --git a/Dockerfile b/Dockerfile
index ef00e7232..fba552080 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -9,11 +9,6 @@ ENV GIT_COMMIT_COUNT=$GIT_COMMIT_COUNT
RUN apt-get update -qq && apt-get install -y cron curl build-essential shared-mime-info sqlite3 tar \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
-# Install NodeJS 16 for webpacker and precompile assets
-RUN curl -sL https://deb.nodesource.com/setup_16.x | bash -
-RUN apt-get install -y nodejs
-RUN corepack enable
-
RUN mkdir /webapp
WORKDIR /webapp
diff --git a/Gemfile b/Gemfile
index 3ba3b91f5..e9f1c8803 100644
--- a/Gemfile
+++ b/Gemfile
@@ -4,7 +4,6 @@ gem "abbrev"
gem 'active_storage_validations'
gem 'binding_of_caller'
gem 'bullet'
-gem 'coffee-rails'
gem 'devise'
gem 'devise_invitable'
gem 'devise-two-factor'
@@ -17,16 +16,19 @@ gem 'gutentag'
gem 'google-apis-drive_v3'
gem 'icalendar'
gem 'image_processing'
+gem 'importmap-rails'
gem "kamal"
gem 'loaf'
gem 'lockbox'
gem 'minidusen'
gem 'mini_magick'
+gem 'mission_control-jobs'
gem 'net-http-persistent'
gem 'pagy'
gem 'paperclip'
gem 'paper_trail'
gem 'paranoia'
+gem 'propshaft'
gem 'psych', '< 4' # https://stackoverflow.com/questions/71191685/visit-psych-nodes-alias-unknown-alias-default-psychbadalias
gem 'rack-cors', require: 'rack/cors'
gem 'rails', '~> 8.0.0'
@@ -36,15 +38,13 @@ gem 'ransack'
gem 'rb-readline'
gem 'rqrcode'
gem 'secure_headers', '~> 6.3'
-gem 'sprockets'
-gem 'sprockets-rails'
+gem 'solid_queue'
gem 'sqlite3'
gem 'stackprof'
-gem 'tailwindcss-rails-webpacker', '~> 0.2.1'
+gem 'tailwindcss-rails', "3.3.2"
+gem "tailwindcss-ruby", "3.4.17"
gem 'turbo-rails'
gem 'tzinfo'
-gem 'uglifier'
-gem 'webpacker'
group :development, :test do
gem 'memory_profiler', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index d6eb24489..630c893c3 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -111,13 +111,6 @@ GEM
chunky_png (1.4.0)
climate_control (0.2.0)
coderay (1.1.3)
- coffee-rails (5.0.0)
- coffee-script (>= 2.2.0)
- railties (>= 5.2.0)
- coffee-script (2.4.1)
- coffee-script-source
- execjs
- coffee-script-source (1.12.2)
concurrent-ruby (1.3.5)
connection_pool (2.5.3)
crass (1.0.6)
@@ -151,7 +144,8 @@ GEM
edge_rider (2.4.0)
activerecord (>= 3.2)
erubi (1.13.1)
- execjs (2.10.0)
+ et-orbi (1.3.0)
+ tzinfo
faraday (2.13.1)
faraday-net_http (>= 2.0, < 3.5)
json
@@ -161,6 +155,9 @@ GEM
ffi (1.17.2-arm64-darwin)
ffi (1.17.2-x86_64-linux-gnu)
flamegraph (0.9.5)
+ fugit (1.11.2)
+ et-orbi (~> 1, >= 1.2.11)
+ raabro (~> 1.4)
globalid (1.2.1)
activesupport (>= 6.1)
google-apis-core (0.17.0)
@@ -201,6 +198,10 @@ GEM
image_processing (1.14.0)
mini_magick (>= 4.9.5, < 6)
ruby-vips (>= 2.0.17, < 3)
+ importmap-rails (2.2.2)
+ actionpack (>= 6.0.0)
+ activesupport (>= 6.0.0)
+ railties (>= 6.0.0)
io-console (0.8.0)
irb (1.15.2)
pp (>= 0.6.0)
@@ -254,6 +255,16 @@ GEM
activesupport (>= 3.2)
edge_rider (>= 0.2.5)
minitest (5.25.5)
+ mission_control-jobs (1.1.0)
+ actioncable (>= 7.1)
+ actionpack (>= 7.1)
+ activejob (>= 7.1)
+ activerecord (>= 7.1)
+ importmap-rails (>= 1.2.1)
+ irb (~> 1.13)
+ railties (>= 7.1)
+ stimulus-rails
+ turbo-rails
multi_json (1.15.0)
mutex_m (0.3.0)
net-http (0.6.0)
@@ -302,6 +313,10 @@ GEM
prettyprint
prettyprint (0.2.0)
prism (1.4.0)
+ propshaft (1.2.1)
+ actionpack (>= 7.0.0)
+ activesupport (>= 7.0.0)
+ rack
pry (0.15.2)
coderay (~> 1.1)
method_source (~> 1.0)
@@ -309,14 +324,13 @@ GEM
public_suffix (6.0.2)
puma (6.6.0)
nio4r (~> 2.0)
+ raabro (1.4.0)
racc (1.8.1)
rack (3.1.16)
rack-cors (2.0.2)
rack (>= 2.0.0)
rack-mini-profiler (3.3.1)
rack (>= 1.2.0)
- rack-proxy (0.7.7)
- rack
rack-session (2.1.1)
base64 (>= 0.1.0)
rack (>= 3.0.0)
@@ -425,21 +439,19 @@ GEM
rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0)
websocket (~> 1.0)
- semantic_range (3.1.0)
shoulda-context (2.0.0)
signet (0.20.0)
addressable (~> 2.8)
faraday (>= 0.17.5, < 3.a)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
- sprockets (4.2.2)
- concurrent-ruby (~> 1.0)
- logger
- rack (>= 2.2.4, < 4)
- sprockets-rails (3.5.2)
- actionpack (>= 6.1)
- activesupport (>= 6.1)
- sprockets (>= 3.0.0)
+ solid_queue (1.2.1)
+ activejob (>= 7.1)
+ activerecord (>= 7.1)
+ concurrent-ruby (>= 1.3.1)
+ fugit (~> 1.11.0)
+ railties (>= 7.1)
+ thor (>= 1.3.1)
sqlite3 (2.6.0-arm64-darwin)
sqlite3 (2.6.0-x86_64-linux-gnu)
sshkit (1.24.0)
@@ -450,8 +462,13 @@ GEM
net-ssh (>= 2.8.0)
ostruct
stackprof (0.2.27)
- tailwindcss-rails-webpacker (0.2.1)
- rails (>= 6.0.0)
+ stimulus-rails (1.3.4)
+ railties (>= 6.0.0)
+ tailwindcss-rails (3.3.2)
+ railties (>= 7.0.0)
+ tailwindcss-ruby (~> 3.0)
+ tailwindcss-ruby (3.4.17-arm64-darwin)
+ tailwindcss-ruby (3.4.17-x86_64-linux)
terrapin (0.6.0)
climate_control (>= 0.0.3, < 1.0)
thor (1.4.0)
@@ -463,8 +480,6 @@ GEM
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
uber (0.1.0)
- uglifier (4.2.1)
- execjs (>= 0.3.0, < 3)
unicode-display_width (3.1.4)
unicode-emoji (~> 4.0, >= 4.0.4)
unicode-emoji (4.0.4)
@@ -482,11 +497,6 @@ GEM
nokogiri (~> 1.6)
rubyzip (>= 1.3.0)
selenium-webdriver (~> 4.0, < 4.11)
- webpacker (5.4.4)
- activesupport (>= 5.2)
- rack-proxy (>= 0.6.1)
- railties (>= 5.2)
- semantic_range (>= 2.3.0)
websocket (1.2.11)
websocket-driver (0.7.7)
base64
@@ -510,7 +520,6 @@ DEPENDENCIES
brakeman
bullet
capybara
- coffee-rails
devise
devise-two-factor
devise_invitable
@@ -524,6 +533,7 @@ DEPENDENCIES
honeybadger (~> 4.0)
icalendar
image_processing
+ importmap-rails
kamal
loaf
lockbox
@@ -531,6 +541,7 @@ DEPENDENCIES
memory_profiler
mini_magick
minidusen
+ mission_control-jobs
net-http-persistent
net-imap
net-pop
@@ -539,6 +550,7 @@ DEPENDENCIES
paper_trail
paperclip
paranoia
+ propshaft
pry
psych (< 4)
puma
@@ -557,17 +569,15 @@ DEPENDENCIES
rubocop-rails
secure_headers (~> 6.3)
shoulda-context
- sprockets
- sprockets-rails
+ solid_queue
sqlite3
stackprof
- tailwindcss-rails-webpacker (~> 0.2.1)
+ tailwindcss-rails (= 3.3.2)
+ tailwindcss-ruby (= 3.4.17)
turbo-rails
tzinfo
- uglifier
web-console
webdrivers
- webpacker
whenever
BUNDLED WITH
diff --git a/Procfile.dev b/Procfile.dev
new file mode 100644
index 000000000..da151fee9
--- /dev/null
+++ b/Procfile.dev
@@ -0,0 +1,2 @@
+web: bin/rails server
+css: bin/rails tailwindcss:watch
diff --git a/app/assets/builds/.keep b/app/assets/builds/.keep
new file mode 100644
index 000000000..e69de29bb
diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js
deleted file mode 100644
index 15bddc09c..000000000
--- a/app/assets/config/manifest.js
+++ /dev/null
@@ -1 +0,0 @@
- //= link_tree ../images
diff --git a/app/javascript/stylesheets/application.css b/app/assets/stylesheets/application.css
similarity index 88%
rename from app/javascript/stylesheets/application.css
rename to app/assets/stylesheets/application.css
index dba2f7ba8..a46f57592 100644
--- a/app/javascript/stylesheets/application.css
+++ b/app/assets/stylesheets/application.css
@@ -1,10 +1,5 @@
/*@import '~flatpickr';*/
/*@import '~tablesort';*/
-@import 'tailwindcss/base';
-@import 'tailwindcss/components';
-@import 'tailwindcss/utilities';
-@import 'trix/dist/trix';
-@import 'flatpickr/dist/flatpickr';
trix-editor.form-control {
min-height: 300px;
diff --git a/app/assets/stylesheets/application.tailwind.css b/app/assets/stylesheets/application.tailwind.css
new file mode 100644
index 000000000..afbb6b677
--- /dev/null
+++ b/app/assets/stylesheets/application.tailwind.css
@@ -0,0 +1,14 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+@config '../../../tailwind.config.js';
+
+@layer base {
+ *,
+ ::after,
+ ::before,
+ ::backdrop,
+ ::file-selector-button {
+ border-color: var(--color-gray-200, currentColor);
+ }
+}
diff --git a/app/controllers/mission_controller.rb b/app/controllers/mission_controller.rb
new file mode 100644
index 000000000..15417b20d
--- /dev/null
+++ b/app/controllers/mission_controller.rb
@@ -0,0 +1,4 @@
+class MissionController < ApplicationController
+ before_action :lid?
+ before_action -> { redirect_to root_path unless current_user.dev? }
+end
diff --git a/app/javascript/packs/application.js b/app/javascript/application.js
similarity index 55%
rename from app/javascript/packs/application.js
rename to app/javascript/application.js
index 3d5413eea..d5685f491 100644
--- a/app/javascript/packs/application.js
+++ b/app/javascript/application.js
@@ -1,20 +1,5 @@
-/* eslint no-console:0 */
-// This file is automatically compiled by Webpack, along with any other files
-// present in this directory. You're encouraged to place your actual application logic in
-// a relevant structure within app/javascript and only use these pack files to reference
-// that code so it'll be compiled.
-//
-// To reference this file, add <%= javascript_pack_tag 'application' %> to the appropriate
-// layout file, like app/views/layouts/application.html.erb
-
-
-// Uncomment to copy all static images under ../images to the output folder and reference
-// them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>)
-// or the `imagePath` JavaScript helper below.
-//
-// const images = require.context('../images', true)
-// const imagePath = (name) => images(name, true)
-
+// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
+import "controllers"
import Rails from '@rails/ujs';
import 'core-js/stable'
import 'regenerator-runtime/runtime'
diff --git a/app/jobs/database_backup.rb b/app/jobs/database_backup_job.rb
similarity index 97%
rename from app/jobs/database_backup.rb
rename to app/jobs/database_backup_job.rb
index bf7c42b34..91cb585af 100644
--- a/app/jobs/database_backup.rb
+++ b/app/jobs/database_backup_job.rb
@@ -2,7 +2,7 @@
require 'googleauth'
require 'stringio'
-class DatabaseBackup < ApplicationJob
+class DatabaseBackupJob < ApplicationJob
queue_as :default
def perform
diff --git a/app/jobs/storage_backup.rb b/app/jobs/storage_backup_job.rb
similarity index 97%
rename from app/jobs/storage_backup.rb
rename to app/jobs/storage_backup_job.rb
index f86c2d3c0..b0a80e10e 100644
--- a/app/jobs/storage_backup.rb
+++ b/app/jobs/storage_backup_job.rb
@@ -2,7 +2,7 @@
require 'googleauth'
require 'stringio'
-class StorageBackup < ApplicationJob
+class StorageBackupJob < ApplicationJob
queue_as :default
def perform
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index f45a15918..e2ff1dd25 100755
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -10,11 +10,11 @@
-
- <%= stylesheet_pack_tag 'application', media: 'all', 'data-turbo-track': 'reload' %>
- <%= javascript_pack_tag 'application', 'data-turbo-track': 'reload' %>
+ <%= stylesheet_link_tag "tailwind", "inter-font", "data-turbo-track": "reload" %>
+ <%= stylesheet_link_tag 'application', media: 'all', 'data-turbo-track': 'reload' %>
<%= csrf_meta_tags %>
+ <%= javascript_importmap_tags %>
diff --git a/app/views/layouts/application_public.html.erb b/app/views/layouts/application_public.html.erb
index 9b4ed3ff5..1e9c57d63 100644
--- a/app/views/layouts/application_public.html.erb
+++ b/app/views/layouts/application_public.html.erb
@@ -3,8 +3,9 @@
Hamers zonder Sikkel
- <%= stylesheet_pack_tag 'application', media: 'all', 'data-turbo-track': 'reload' %>
- <%= javascript_pack_tag 'application', 'data-turbo-track': 'reload' %>
+ <%= stylesheet_link_tag 'application', media: 'all', 'data-turbo-track': 'reload' %>
+ <%= stylesheet_link_tag 'tailwind', media: 'all', 'data-turbo-track': 'reload' %>
+ <%= javascript_importmap_tags %>
<%= csrf_meta_tags %>
diff --git a/babel.config.js b/babel.config.js
deleted file mode 100644
index a5c35ccd5..000000000
--- a/babel.config.js
+++ /dev/null
@@ -1,52 +0,0 @@
-module.exports = function(api) {
- var validEnv = ['development', 'test', 'production']
- var currentEnv = api.env()
- var isDevelopmentEnv = api.env('development')
- var isProductionEnv = api.env('production')
- var isTestEnv = api.env('test')
-
- if (!validEnv.includes(currentEnv)) {
- throw new Error(
- 'Please specify a valid `NODE_ENV` or ' +
- '`BABEL_ENV` environment variables. Valid values are "development", ' +
- '"test", and "production". Instead, received: ' +
- JSON.stringify(currentEnv) +
- '.'
- )
- }
-
- return {
- presets: [
- isTestEnv && [
- '@babel/preset-env',
- {
- targets: {
- node: 'current'
- }
- }
- ],
- (isProductionEnv || isDevelopmentEnv) && [
- '@babel/preset-env',
- {
- forceAllTransforms: true,
- useBuiltIns: 'usage',
- corejs: 3,
- modules: false,
- exclude: ['transform-typeof-symbol']
- }
- ]
- ].filter(Boolean),
- plugins: [
- 'babel-plugin-macros',
- '@babel/plugin-syntax-dynamic-import',
- isTestEnv && 'babel-plugin-dynamic-import-node',
- '@babel/plugin-transform-destructuring',
- '@babel/plugin-proposal-optional-chaining',
- '@babel/plugin-proposal-nullish-coalescing-operator',
- ['@babel/plugin-proposal-class-properties', { loose: true }],
- ['@babel/plugin-proposal-object-rest-spread', { useBuiltIns: true }],
- ['@babel/plugin-transform-runtime', { helpers: false }],
- ['@babel/plugin-transform-regenerator', { async: false}]
- ].filter(Boolean)
- }
-}
diff --git a/bin/dev b/bin/dev
new file mode 100755
index 000000000..ad72c7d53
--- /dev/null
+++ b/bin/dev
@@ -0,0 +1,16 @@
+#!/usr/bin/env sh
+
+if ! gem list foreman -i --silent; then
+ echo "Installing foreman..."
+ gem install foreman
+fi
+
+# Default to port 3000 if not specified
+export PORT="${PORT:-3000}"
+
+# Let the debug gem allow remote connections,
+# but avoid loading until `debugger` is called
+export RUBY_DEBUG_OPEN="true"
+export RUBY_DEBUG_LAZY="true"
+
+exec foreman start -f Procfile.dev "$@"
diff --git a/bin/importmap b/bin/importmap
new file mode 100755
index 000000000..36502ab16
--- /dev/null
+++ b/bin/importmap
@@ -0,0 +1,4 @@
+#!/usr/bin/env ruby
+
+require_relative "../config/application"
+require "importmap/commands"
diff --git a/bin/jobs b/bin/jobs
new file mode 100755
index 000000000..dcf59f309
--- /dev/null
+++ b/bin/jobs
@@ -0,0 +1,6 @@
+#!/usr/bin/env ruby
+
+require_relative "../config/environment"
+require "solid_queue/cli"
+
+SolidQueue::Cli.start(ARGV)
diff --git a/config/application.rb b/config/application.rb
index 274064508..ee0d94a67 100755
--- a/config/application.rb
+++ b/config/application.rb
@@ -1,7 +1,6 @@
require_relative 'boot'
require 'rails/all'
-require 'sprockets/railtie'
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
@@ -14,6 +13,7 @@ class Application < Rails::Application
# -- all .rb files in that directory are automatically loaded.
config.time_zone = 'Amsterdam'
config.autoload_paths += Dir[Rails.root.join('app', '*')]
+ config.autoload_lib(ignore: %w[apps gems rails_ext assets tasks])
config.i18n.default_locale = :nl
config.to_prepare do
Doorkeeper::ApplicationsController.layout 'application'
@@ -29,6 +29,8 @@ class Application < Rails::Application
end
Diffy::Diff.default_format = :html
+ config.mission_control.jobs.base_controller_class = "MissionController"
+ config.mission_control.jobs.http_basic_auth_enabled = false
config.exception_handler = {
# Turn on in development as needed:
diff --git a/config/database.yml b/config/database.yml
index f3a07eac8..16be5247e 100644
--- a/config/database.yml
+++ b/config/database.yml
@@ -4,13 +4,23 @@ default: &default
timeout: 5000
development:
- <<: *default
- database: db/development.sqlite3
+ primary:
+ <<: *default
+ database: db/development.sqlite3
+ queue:
+ <<: *default
+ database: db/development_queue.sqlite3
+ migrations_paths: db/queue_migrate
test:
<<: *default
database: db/test.sqlite3
production:
- <<: *default
- database: db/production.sqlite3
+ primary:
+ <<: *default
+ database: db/production.sqlite3
+ queue:
+ <<: *default
+ database: db/production_queue.sqlite3
+ migrations_paths: db/queue_migrate
diff --git a/config/deploy.yml b/config/deploy.yml
index 90cda5c1b..c3269f105 100644
--- a/config/deploy.yml
+++ b/config/deploy.yml
@@ -41,6 +41,7 @@ env:
RAILS_ENV: production
RAILS_LOG_TO_STDOUT: true
MAINTENANCE_MODE: false
+ SOLID_QUEUE_IN_PUMA: true
secret:
- RAILS_MASTER_KEY
diff --git a/config/environments/development.rb b/config/environments/development.rb
index 78b23b993..e99b10331 100755
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -41,14 +41,6 @@
# Raise an error on page load if there are pending migrations.
config.active_record.migration_error = :page_load
- # Debug mode disables concatenation and preprocessing of assets.
- # This option may cause significant delays in view rendering with a large
- # number of complex assets.
- config.assets.debug = true
-
- # Suppress logger output for asset requests.
- config.assets.quiet = true
-
# Raises error for missing translations
# config.i18n.raise_on_missing_translations = true
diff --git a/config/environments/production.rb b/config/environments/production.rb
index 187cc340a..c00ae5823 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -28,7 +28,6 @@
config.public_file_server.enabled = true
# Compress JavaScripts and CSS.
- config.assets.js_compressor = Uglifier.new(harmony: true)
# config.assets.css_compressor = :sass
# Do not fallback to assets pipeline if a precompiled asset is missed.
@@ -65,7 +64,10 @@
# config.cache_store = :redis_cache_store, { url: Rails.application.credentials.redis_host }
# Use a real queuing backend for Active Job (and separate queues per environment)
- # config.active_job.queue_adapter = :resque
+ config.active_job.queue_adapter = :solid_queue
+ config.solid_queue.connects_to = { database: { writing: :queue } }
+ MissionControl::Jobs.base_controller_class = "MissionController"
+
# config.active_job.queue_name_prefix = "hamers_#{Rails.env}"
config.action_mailer.perform_caching = false
config.action_mailer.default_url_options = { host: 'zondersikkel.nl' }
diff --git a/config/importmap.rb b/config/importmap.rb
new file mode 100644
index 000000000..3fbf5fd7f
--- /dev/null
+++ b/config/importmap.rb
@@ -0,0 +1,21 @@
+# Pin npm packages by running ./bin/importmap
+
+pin "application"
+pin "flatpickr" # @4.6.13
+pin "@hotwired/turbo-rails", to: "@hotwired--turbo-rails.js" # @8.0.16
+pin "@hotwired/turbo", to: "@hotwired--turbo.js" # @8.0.13
+pin "@rails/actioncable/src", to: "@rails--actioncable--src.js" # @8.0.201
+pin "trix" # @2.1.15
+pin "@hotwired/stimulus", to: "@hotwired--stimulus.js" # @3.2.2
+pin "@rails/request.js", to: "@rails--request.js.js" # @0.0.12
+pin "@tailwindcss/forms", to: "@tailwindcss--forms.js" # @0.5.10
+pin "mini-svg-data-uri" # @1.4.4
+pin "tailwindcss/colors", to: "tailwindcss--colors.js" # @4.1.12
+pin "tailwindcss/defaultTheme", to: "tailwindcss--defaultTheme.js" # @4.1.12
+pin "tailwindcss/plugin", to: "tailwindcss--plugin.js" # @4.1.12
+pin "stimulus-flatpickr" # @1.4.0
+pin "stimulus" # @3.2.2
+pin "popper.js" # @1.16.1
+pin "tailwindcss-stimulus-components" # @6.1.3
+
+pin_all_from "app/javascript/controllers", under: "controllers"
diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb
index 34895d374..c7b3de554 100644
--- a/config/initializers/assets.rb
+++ b/config/initializers/assets.rb
@@ -1,12 +1,10 @@
+# frozen_string_literal: true
+
# Be sure to restart your server when you modify this file.
# Version of your assets, change this if you want to expire all your assets.
-Rails.application.config.assets.version = '1.0'
-
-# Add additional assets to the asset load path
-# Rails.application.config.assets.paths << Emoji.images_path
+Rails.application.config.assets.version = "1.0"
-# Precompile additional assets.
-# application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
-# Rails.application.config.assets.precompile += %w( search.js )
-Rails.application.config.assets.paths << Rails.root.join('node_modules')
+# Add additional assets to the asset load path.
+Rails.application.config.assets.paths << Rails.root.join("app/assets/sounds")
+Rails.application.config.assets.precompile += %w[tailwind.css]
diff --git a/config/puma.rb b/config/puma.rb
index c7f311f81..799cb139e 100644
--- a/config/puma.rb
+++ b/config/puma.rb
@@ -45,3 +45,4 @@
# Allow puma to be restarted by `rails restart` command.
plugin :tmp_restart
+plugin :solid_queue if ENV["SOLID_QUEUE_IN_PUMA"]
diff --git a/config/queue.yml b/config/queue.yml
new file mode 100644
index 000000000..9eace59c4
--- /dev/null
+++ b/config/queue.yml
@@ -0,0 +1,18 @@
+default: &default
+ dispatchers:
+ - polling_interval: 1
+ batch_size: 500
+ workers:
+ - queues: "*"
+ threads: 3
+ processes: <%= ENV.fetch("JOB_CONCURRENCY", 1) %>
+ polling_interval: 0.1
+
+development:
+ <<: *default
+
+test:
+ <<: *default
+
+production:
+ <<: *default
diff --git a/config/recurring.yml b/config/recurring.yml
new file mode 100644
index 000000000..353a0f833
--- /dev/null
+++ b/config/recurring.yml
@@ -0,0 +1,30 @@
+# examples:
+# periodic_cleanup:
+# class: CleanSoftDeletedRecordsJob
+# queue: background
+# args: [ 1000, { batch_size: 500 } ]
+# schedule: every hour
+# periodic_cleanup_with_command:
+# command: "SoftDeletedRecord.due.delete_all"
+# priority: 2
+# schedule: at 5am every day
+
+production:
+ clear_solid_queue_finished_jobs:
+ command: "SolidQueue::Job.clear_finished_in_batches(sleep_between_batches: 0.3)"
+ schedule: every hour at minute 12
+ create_drink:
+ command: "UtilHelper.create_drink"
+ schedule: every wednesday at noon
+ remind_drink:
+ command: "UtilHelper.remind_drink"
+ schedule: every monday at noon
+ cleanup:
+ command: "UtilHelper.cleanup"
+ schedule: every sunday at 6am
+ backup_database:
+ class: DatabaseBackupJob
+ schedule: every day at 3am
+ backup_storage:
+ class: StorageBackupJob
+ schedule: every day at 3:10am
diff --git a/config/routes.rb b/config/routes.rb
index 00c70e787..1183b2a18 100755
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -2,6 +2,7 @@
use_doorkeeper
get 'privacy' => 'static_pages#privacy'
get 'activate_account' => 'static_pages#activate_account', as: "activate_account"
+ mount MissionControl::Jobs::Engine, at: "/jobs"
resources :stickers
resources :recipes do
diff --git a/config/tailwind.config.js b/config/tailwind.config.js
new file mode 100644
index 000000000..c3deef144
--- /dev/null
+++ b/config/tailwind.config.js
@@ -0,0 +1,22 @@
+const defaultTheme = require('tailwindcss/defaultTheme')
+
+module.exports = {
+ content: [
+ './public/*.html',
+ './app/helpers/**/*.rb',
+ './app/javascript/**/*.js',
+ './app/views/**/*.{erb,haml,html,slim}'
+ ],
+ theme: {
+ extend: {
+ fontFamily: {
+ sans: ['Inter var', ...defaultTheme.fontFamily.sans],
+ },
+ },
+ },
+ plugins: [
+ // require('@tailwindcss/forms'),
+ // require('@tailwindcss/typography'),
+ // require('@tailwindcss/container-queries'),
+ ]
+}
diff --git a/config/webpacker.yml b/config/webpacker.yml
deleted file mode 100644
index caed1d701..000000000
--- a/config/webpacker.yml
+++ /dev/null
@@ -1,92 +0,0 @@
-# Note: You must restart bin/webpack-dev-server for changes to take effect
-
-default: &default
- source_path: app/javascript
- source_entry_path: packs
- public_root_path: public
- public_output_path: packs
- cache_path: tmp/cache/webpacker
- webpack_compile_output: true
-
- # Additional paths webpack should lookup modules
- # ['app/assets', 'engine/foo/app/assets']
- additional_paths: []
-
- # Reload manifest.json on all requests so we reload latest compiled packs
- cache_manifest: false
-
- # Extract and emit a css file
- extract_css: true
-
- static_assets_extensions:
- - .jpg
- - .jpeg
- - .png
- - .gif
- - .tiff
- - .ico
- - .svg
- - .eot
- - .otf
- - .ttf
- - .woff
- - .woff2
-
- extensions:
- - .mjs
- - .js
- - .sass
- - .scss
- - .css
- - .module.sass
- - .module.scss
- - .module.css
- - .png
- - .svg
- - .gif
- - .jpeg
- - .jpg
-
-development:
- <<: *default
- compile: true
-
- # Reference: https://webpack.js.org/configuration/dev-server/
- dev_server:
- https: false
- host: localhost
- port: 3035
- public: localhost:3035
- hmr: false
- # Inline should be set to true if using HMR
- inline: true
- overlay: true
- compress: true
- disable_host_check: true
- use_local_ip: false
- quiet: false
- pretty: false
- headers:
- 'Access-Control-Allow-Origin': '*'
- watch_options:
- ignored: '**/node_modules/**'
-
-
-test:
- <<: *default
- compile: true
-
- # Compile test packs to a separate directory
- public_output_path: packs-test
-
-production:
- <<: *default
-
- # Production depends on precompilation of packs prior to booting for performance.
- compile: false
-
- # Extract and emit a css file
- extract_css: true
-
- # Cache manifest.json for performance
- cache_manifest: true
diff --git a/cron-executor.sh b/cron-executor.sh
deleted file mode 100644
index d02daa6bc..000000000
--- a/cron-executor.sh
+++ /dev/null
@@ -1,5 +0,0 @@
-#!/bin/bash -e
-PATH=$PATH:/usr/local/bin
-cd /webapp
-echo "cron-executor.sh -> running: $@"
-exec "$@" >/proc/1/fd/1 2>/proc/1/fd/2
diff --git a/lib/tasks/yarn.rake b/lib/tasks/yarn.rake
deleted file mode 100644
index 8f6389dca..000000000
--- a/lib/tasks/yarn.rake
+++ /dev/null
@@ -1 +0,0 @@
-Rake::Task["assets:precompile"].enhance ["yarn:install"]
\ No newline at end of file
diff --git a/package.json b/package.json
deleted file mode 100644
index 1c3f1004f..000000000
--- a/package.json
+++ /dev/null
@@ -1,34 +0,0 @@
-{
- "dependencies": {
- "@fortawesome/fontawesome-free": "^5.15.4",
- "@hotwired/stimulus": "^3.2.2",
- "@hotwired/stimulus-webpack-helpers": "^1.0.1",
- "@hotwired/turbo-rails": "^7.0.1",
- "@rails/actiontext": "^6.1.4-1",
- "@rails/ujs": "^6.1.4-1",
- "@rails/webpacker": "5.4.3",
- "@tailwindcss/forms": "^0.3.3",
- "autoprefixer": "^9",
- "core-js": "^3.8.0",
- "flatpickr": "^4.6.9",
- "jquery": "^3",
- "nprogress": "^0.2.0",
- "popper.js": "^1.16.1",
- "postcss": "^8",
- "regenerator-runtime": "^0.13.7",
- "stimulus-flatpickr": "^3.0.0-0",
- "tablesort": "^5.3",
- "tailwind-colors": "^1.1.0",
- "tailwindcss": "npm:@tailwindcss/postcss7-compat",
- "tailwindcss-stimulus-components": "^6.1.3",
- "trix": "^2.1.15",
- "webpack": "^4.46.0",
- "webpack-cli": "^3.3.12"
- },
- "devDependencies": {
- "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6",
- "@babel/plugin-proposal-optional-chaining": "^7.21.0",
- "resolve-url-loader": "^3.1.2",
- "webpack-dev-server": "^3"
- }
-}
diff --git a/postcss.config.js b/postcss.config.js
deleted file mode 100644
index b6a0e421b..000000000
--- a/postcss.config.js
+++ /dev/null
@@ -1,13 +0,0 @@
-module.exports = {
- plugins: [
- require('postcss-import'),
- require('tailwindcss'),
- require('postcss-flexbugs-fixes'),
- require('postcss-preset-env')({
- autoprefixer: {
- flexbox: 'no-2009'
- },
- stage: 3
- })
- ]
-}
diff --git a/vendor/javascript/.keep b/vendor/javascript/.keep
new file mode 100644
index 000000000..e69de29bb
diff --git a/vendor/javascript/@hotwired--stimulus.js b/vendor/javascript/@hotwired--stimulus.js
new file mode 100644
index 000000000..07dd4a6cc
--- /dev/null
+++ b/vendor/javascript/@hotwired--stimulus.js
@@ -0,0 +1,4 @@
+// @hotwired/stimulus@3.2.2 downloaded from https://ga.jspm.io/npm:@hotwired/stimulus@3.2.2/dist/stimulus.js
+
+class EventListener{constructor(e,t,r){this.eventTarget=e;this.eventName=t;this.eventOptions=r;this.unorderedBindings=new Set}connect(){this.eventTarget.addEventListener(this.eventName,this,this.eventOptions)}disconnect(){this.eventTarget.removeEventListener(this.eventName,this,this.eventOptions)}bindingConnected(e){this.unorderedBindings.add(e)}bindingDisconnected(e){this.unorderedBindings.delete(e)}handleEvent(e){const t=extendEvent(e);for(const e of this.bindings){if(t.immediatePropagationStopped)break;e.handleEvent(t)}}hasBindings(){return this.unorderedBindings.size>0}get bindings(){return Array.from(this.unorderedBindings).sort(((e,t)=>{const r=e.index,s=t.index;return rs?1:0}))}}function extendEvent(e){if("immediatePropagationStopped"in e)return e;{const{stopImmediatePropagation:t}=e;return Object.assign(e,{immediatePropagationStopped:false,stopImmediatePropagation(){this.immediatePropagationStopped=true;t.call(this)}})}}class Dispatcher{constructor(e){this.application=e;this.eventListenerMaps=new Map;this.started=false}start(){if(!this.started){this.started=true;this.eventListeners.forEach((e=>e.connect()))}}stop(){if(this.started){this.started=false;this.eventListeners.forEach((e=>e.disconnect()))}}get eventListeners(){return Array.from(this.eventListenerMaps.values()).reduce(((e,t)=>e.concat(Array.from(t.values()))),[])}bindingConnected(e){this.fetchEventListenerForBinding(e).bindingConnected(e)}bindingDisconnected(e,t=false){this.fetchEventListenerForBinding(e).bindingDisconnected(e);t&&this.clearEventListenersForBinding(e)}handleError(e,t,r={}){this.application.handleError(e,`Error ${t}`,r)}clearEventListenersForBinding(e){const t=this.fetchEventListenerForBinding(e);if(!t.hasBindings()){t.disconnect();this.removeMappedEventListenerFor(e)}}removeMappedEventListenerFor(e){const{eventTarget:t,eventName:r,eventOptions:s}=e;const n=this.fetchEventListenerMapForEventTarget(t);const i=this.cacheKey(r,s);n.delete(i);0==n.size&&this.eventListenerMaps.delete(t)}fetchEventListenerForBinding(e){const{eventTarget:t,eventName:r,eventOptions:s}=e;return this.fetchEventListener(t,r,s)}fetchEventListener(e,t,r){const s=this.fetchEventListenerMapForEventTarget(e);const n=this.cacheKey(t,r);let i=s.get(n);if(!i){i=this.createEventListener(e,t,r);s.set(n,i)}return i}createEventListener(e,t,r){const s=new EventListener(e,t,r);this.started&&s.connect();return s}fetchEventListenerMapForEventTarget(e){let t=this.eventListenerMaps.get(e);if(!t){t=new Map;this.eventListenerMaps.set(e,t)}return t}cacheKey(e,t){const r=[e];Object.keys(t).sort().forEach((e=>{r.push(`${t[e]?"":"!"}${e}`)}));return r.join(":")}}const e={stop({event:e,value:t}){t&&e.stopPropagation();return true},prevent({event:e,value:t}){t&&e.preventDefault();return true},self({event:e,value:t,element:r}){return!t||r===e.target}};const t=/^(?:(?:([^.]+?)\+)?(.+?)(?:\.(.+?))?(?:@(window|document))?->)?(.+?)(?:#([^:]+?))(?::(.+))?$/;function parseActionDescriptorString(e){const r=e.trim();const s=r.match(t)||[];let n=s[2];let i=s[3];if(i&&!["keydown","keyup","keypress"].includes(n)){n+=`.${i}`;i=""}return{eventTarget:parseEventTarget(s[4]),eventName:n,eventOptions:s[7]?parseEventOptions(s[7]):{},identifier:s[5],methodName:s[6],keyFilter:s[1]||i}}function parseEventTarget(e){return"window"==e?window:"document"==e?document:void 0}function parseEventOptions(e){return e.split(":").reduce(((e,t)=>Object.assign(e,{[t.replace(/^!/,"")]:!/^!/.test(t)})),{})}function stringifyEventTarget(e){return e==window?"window":e==document?"document":void 0}function camelize(e){return e.replace(/(?:[_-])([a-z0-9])/g,((e,t)=>t.toUpperCase()))}function namespaceCamelize(e){return camelize(e.replace(/--/g,"-").replace(/__/g,"_"))}function capitalize(e){return e.charAt(0).toUpperCase()+e.slice(1)}function dasherize(e){return e.replace(/([A-Z])/g,((e,t)=>`-${t.toLowerCase()}`))}function tokenize(e){return e.match(/[^\s]+/g)||[]}function isSomething(e){return null!==e&&void 0!==e}function hasProperty(e,t){return Object.prototype.hasOwnProperty.call(e,t)}const r=["meta","ctrl","alt","shift"];class Action{constructor(e,t,r,s){this.element=e;this.index=t;this.eventTarget=r.eventTarget||e;this.eventName=r.eventName||getDefaultEventNameForElement(e)||error("missing event name");this.eventOptions=r.eventOptions||{};this.identifier=r.identifier||error("missing identifier");this.methodName=r.methodName||error("missing method name");this.keyFilter=r.keyFilter||"";this.schema=s}static forToken(e,t){return new this(e.element,e.index,parseActionDescriptorString(e.content),t)}toString(){const e=this.keyFilter?`.${this.keyFilter}`:"";const t=this.eventTargetName?`@${this.eventTargetName}`:"";return`${this.eventName}${e}${t}->${this.identifier}#${this.methodName}`}shouldIgnoreKeyboardEvent(e){if(!this.keyFilter)return false;const t=this.keyFilter.split("+");if(this.keyFilterDissatisfied(e,t))return true;const s=t.filter((e=>!r.includes(e)))[0];if(!s)return false;hasProperty(this.keyMappings,s)||error(`contains unknown key filter: ${this.keyFilter}`);return this.keyMappings[s].toLowerCase()!==e.key.toLowerCase()}shouldIgnoreMouseEvent(e){if(!this.keyFilter)return false;const t=[this.keyFilter];return!!this.keyFilterDissatisfied(e,t)}get params(){const e={};const t=new RegExp(`^data-${this.identifier}-(.+)-param$`,"i");for(const{name:r,value:s}of Array.from(this.element.attributes)){const n=r.match(t);const i=n&&n[1];i&&(e[camelize(i)]=typecast(s))}return e}get eventTargetName(){return stringifyEventTarget(this.eventTarget)}get keyMappings(){return this.schema.keyMappings}keyFilterDissatisfied(e,t){const[s,n,i,o]=r.map((e=>t.includes(e)));return e.metaKey!==s||e.ctrlKey!==n||e.altKey!==i||e.shiftKey!==o}}const s={a:()=>"click",button:()=>"click",form:()=>"submit",details:()=>"toggle",input:e=>"submit"==e.getAttribute("type")?"click":"input",select:()=>"change",textarea:()=>"input"};function getDefaultEventNameForElement(e){const t=e.tagName.toLowerCase();if(t in s)return s[t](e)}function error(e){throw new Error(e)}function typecast(e){try{return JSON.parse(e)}catch(t){return e}}class Binding{constructor(e,t){this.context=e;this.action=t}get index(){return this.action.index}get eventTarget(){return this.action.eventTarget}get eventOptions(){return this.action.eventOptions}get identifier(){return this.context.identifier}handleEvent(e){const t=this.prepareActionEvent(e);this.willBeInvokedByEvent(e)&&this.applyEventModifiers(t)&&this.invokeWithEvent(t)}get eventName(){return this.action.eventName}get method(){const e=this.controller[this.methodName];if("function"==typeof e)return e;throw new Error(`Action "${this.action}" references undefined method "${this.methodName}"`)}applyEventModifiers(e){const{element:t}=this.action;const{actionDescriptorFilters:r}=this.context.application;const{controller:s}=this.context;let n=true;for(const[i,o]of Object.entries(this.eventOptions))if(i in r){const c=r[i];n=n&&c({name:i,value:o,event:e,element:t,controller:s})}return n}prepareActionEvent(e){return Object.assign(e,{params:this.action.params})}invokeWithEvent(e){const{target:t,currentTarget:r}=e;try{this.method.call(this.controller,e);this.context.logDebugActivity(this.methodName,{event:e,target:t,currentTarget:r,action:this.methodName})}catch(t){const{identifier:r,controller:s,element:n,index:i}=this;const o={identifier:r,controller:s,element:n,index:i,event:e};this.context.handleError(t,`invoking action "${this.action}"`,o)}}willBeInvokedByEvent(e){const t=e.target;return!(e instanceof KeyboardEvent&&this.action.shouldIgnoreKeyboardEvent(e))&&(!(e instanceof MouseEvent&&this.action.shouldIgnoreMouseEvent(e))&&(this.element===t||(t instanceof Element&&this.element.contains(t)?this.scope.containsElement(t):this.scope.containsElement(this.action.element))))}get controller(){return this.context.controller}get methodName(){return this.action.methodName}get element(){return this.scope.element}get scope(){return this.context.scope}}class ElementObserver{constructor(e,t){this.mutationObserverInit={attributes:true,childList:true,subtree:true};this.element=e;this.started=false;this.delegate=t;this.elements=new Set;this.mutationObserver=new MutationObserver((e=>this.processMutations(e)))}start(){if(!this.started){this.started=true;this.mutationObserver.observe(this.element,this.mutationObserverInit);this.refresh()}}pause(e){if(this.started){this.mutationObserver.disconnect();this.started=false}e();if(!this.started){this.mutationObserver.observe(this.element,this.mutationObserverInit);this.started=true}}stop(){if(this.started){this.mutationObserver.takeRecords();this.mutationObserver.disconnect();this.started=false}}refresh(){if(this.started){const e=new Set(this.matchElementsInTree());for(const t of Array.from(this.elements))e.has(t)||this.removeElement(t);for(const t of Array.from(e))this.addElement(t)}}processMutations(e){if(this.started)for(const t of e)this.processMutation(t)}processMutation(e){if("attributes"==e.type)this.processAttributeChange(e.target,e.attributeName);else if("childList"==e.type){this.processRemovedNodes(e.removedNodes);this.processAddedNodes(e.addedNodes)}}processAttributeChange(e,t){this.elements.has(e)?this.delegate.elementAttributeChanged&&this.matchElement(e)?this.delegate.elementAttributeChanged(e,t):this.removeElement(e):this.matchElement(e)&&this.addElement(e)}processRemovedNodes(e){for(const t of Array.from(e)){const e=this.elementFromNode(t);e&&this.processTree(e,this.removeElement)}}processAddedNodes(e){for(const t of Array.from(e)){const e=this.elementFromNode(t);e&&this.elementIsActive(e)&&this.processTree(e,this.addElement)}}matchElement(e){return this.delegate.matchElement(e)}matchElementsInTree(e=this.element){return this.delegate.matchElementsInTree(e)}processTree(e,t){for(const r of this.matchElementsInTree(e))t.call(this,r)}elementFromNode(e){if(e.nodeType==Node.ELEMENT_NODE)return e}elementIsActive(e){return e.isConnected==this.element.isConnected&&this.element.contains(e)}addElement(e){if(!this.elements.has(e)&&this.elementIsActive(e)){this.elements.add(e);this.delegate.elementMatched&&this.delegate.elementMatched(e)}}removeElement(e){if(this.elements.has(e)){this.elements.delete(e);this.delegate.elementUnmatched&&this.delegate.elementUnmatched(e)}}}class AttributeObserver{constructor(e,t,r){this.attributeName=t;this.delegate=r;this.elementObserver=new ElementObserver(e,this)}get element(){return this.elementObserver.element}get selector(){return`[${this.attributeName}]`}start(){this.elementObserver.start()}pause(e){this.elementObserver.pause(e)}stop(){this.elementObserver.stop()}refresh(){this.elementObserver.refresh()}get started(){return this.elementObserver.started}matchElement(e){return e.hasAttribute(this.attributeName)}matchElementsInTree(e){const t=this.matchElement(e)?[e]:[];const r=Array.from(e.querySelectorAll(this.selector));return t.concat(r)}elementMatched(e){this.delegate.elementMatchedAttribute&&this.delegate.elementMatchedAttribute(e,this.attributeName)}elementUnmatched(e){this.delegate.elementUnmatchedAttribute&&this.delegate.elementUnmatchedAttribute(e,this.attributeName)}elementAttributeChanged(e,t){this.delegate.elementAttributeValueChanged&&this.attributeName==t&&this.delegate.elementAttributeValueChanged(e,t)}}function add(e,t,r){fetch(e,t).add(r)}function del(e,t,r){fetch(e,t).delete(r);prune(e,t)}function fetch(e,t){let r=e.get(t);if(!r){r=new Set;e.set(t,r)}return r}function prune(e,t){const r=e.get(t);null!=r&&0==r.size&&e.delete(t)}class Multimap{constructor(){this.valuesByKey=new Map}get keys(){return Array.from(this.valuesByKey.keys())}get values(){const e=Array.from(this.valuesByKey.values());return e.reduce(((e,t)=>e.concat(Array.from(t))),[])}get size(){const e=Array.from(this.valuesByKey.values());return e.reduce(((e,t)=>e+t.size),0)}add(e,t){add(this.valuesByKey,e,t)}delete(e,t){del(this.valuesByKey,e,t)}has(e,t){const r=this.valuesByKey.get(e);return null!=r&&r.has(t)}hasKey(e){return this.valuesByKey.has(e)}hasValue(e){const t=Array.from(this.valuesByKey.values());return t.some((t=>t.has(e)))}getValuesForKey(e){const t=this.valuesByKey.get(e);return t?Array.from(t):[]}getKeysForValue(e){return Array.from(this.valuesByKey).filter((([t,r])=>r.has(e))).map((([e,t])=>e))}}class IndexedMultimap extends Multimap{constructor(){super();this.keysByValue=new Map}get values(){return Array.from(this.keysByValue.keys())}add(e,t){super.add(e,t);add(this.keysByValue,t,e)}delete(e,t){super.delete(e,t);del(this.keysByValue,t,e)}hasValue(e){return this.keysByValue.has(e)}getKeysForValue(e){const t=this.keysByValue.get(e);return t?Array.from(t):[]}}class SelectorObserver{constructor(e,t,r,s){this._selector=t;this.details=s;this.elementObserver=new ElementObserver(e,this);this.delegate=r;this.matchesByElement=new Multimap}get started(){return this.elementObserver.started}get selector(){return this._selector}set selector(e){this._selector=e;this.refresh()}start(){this.elementObserver.start()}pause(e){this.elementObserver.pause(e)}stop(){this.elementObserver.stop()}refresh(){this.elementObserver.refresh()}get element(){return this.elementObserver.element}matchElement(e){const{selector:t}=this;if(t){const r=e.matches(t);return this.delegate.selectorMatchElement?r&&this.delegate.selectorMatchElement(e,this.details):r}return false}matchElementsInTree(e){const{selector:t}=this;if(t){const r=this.matchElement(e)?[e]:[];const s=Array.from(e.querySelectorAll(t)).filter((e=>this.matchElement(e)));return r.concat(s)}return[]}elementMatched(e){const{selector:t}=this;t&&this.selectorMatched(e,t)}elementUnmatched(e){const t=this.matchesByElement.getKeysForValue(e);for(const r of t)this.selectorUnmatched(e,r)}elementAttributeChanged(e,t){const{selector:r}=this;if(r){const t=this.matchElement(e);const s=this.matchesByElement.has(r,e);t&&!s?this.selectorMatched(e,r):!t&&s&&this.selectorUnmatched(e,r)}}selectorMatched(e,t){this.delegate.selectorMatched(e,t,this.details);this.matchesByElement.add(t,e)}selectorUnmatched(e,t){this.delegate.selectorUnmatched(e,t,this.details);this.matchesByElement.delete(t,e)}}class StringMapObserver{constructor(e,t){this.element=e;this.delegate=t;this.started=false;this.stringMap=new Map;this.mutationObserver=new MutationObserver((e=>this.processMutations(e)))}start(){if(!this.started){this.started=true;this.mutationObserver.observe(this.element,{attributes:true,attributeOldValue:true});this.refresh()}}stop(){if(this.started){this.mutationObserver.takeRecords();this.mutationObserver.disconnect();this.started=false}}refresh(){if(this.started)for(const e of this.knownAttributeNames)this.refreshAttribute(e,null)}processMutations(e){if(this.started)for(const t of e)this.processMutation(t)}processMutation(e){const t=e.attributeName;t&&this.refreshAttribute(t,e.oldValue)}refreshAttribute(e,t){const r=this.delegate.getStringMapKeyForAttribute(e);if(null!=r){this.stringMap.has(e)||this.stringMapKeyAdded(r,e);const s=this.element.getAttribute(e);this.stringMap.get(e)!=s&&this.stringMapValueChanged(s,r,t);if(null==s){const t=this.stringMap.get(e);this.stringMap.delete(e);t&&this.stringMapKeyRemoved(r,e,t)}else this.stringMap.set(e,s)}}stringMapKeyAdded(e,t){this.delegate.stringMapKeyAdded&&this.delegate.stringMapKeyAdded(e,t)}stringMapValueChanged(e,t,r){this.delegate.stringMapValueChanged&&this.delegate.stringMapValueChanged(e,t,r)}stringMapKeyRemoved(e,t,r){this.delegate.stringMapKeyRemoved&&this.delegate.stringMapKeyRemoved(e,t,r)}get knownAttributeNames(){return Array.from(new Set(this.currentAttributeNames.concat(this.recordedAttributeNames)))}get currentAttributeNames(){return Array.from(this.element.attributes).map((e=>e.name))}get recordedAttributeNames(){return Array.from(this.stringMap.keys())}}class TokenListObserver{constructor(e,t,r){this.attributeObserver=new AttributeObserver(e,t,this);this.delegate=r;this.tokensByElement=new Multimap}get started(){return this.attributeObserver.started}start(){this.attributeObserver.start()}pause(e){this.attributeObserver.pause(e)}stop(){this.attributeObserver.stop()}refresh(){this.attributeObserver.refresh()}get element(){return this.attributeObserver.element}get attributeName(){return this.attributeObserver.attributeName}elementMatchedAttribute(e){this.tokensMatched(this.readTokensForElement(e))}elementAttributeValueChanged(e){const[t,r]=this.refreshTokensForElement(e);this.tokensUnmatched(t);this.tokensMatched(r)}elementUnmatchedAttribute(e){this.tokensUnmatched(this.tokensByElement.getValuesForKey(e))}tokensMatched(e){e.forEach((e=>this.tokenMatched(e)))}tokensUnmatched(e){e.forEach((e=>this.tokenUnmatched(e)))}tokenMatched(e){this.delegate.tokenMatched(e);this.tokensByElement.add(e.element,e)}tokenUnmatched(e){this.delegate.tokenUnmatched(e);this.tokensByElement.delete(e.element,e)}refreshTokensForElement(e){const t=this.tokensByElement.getValuesForKey(e);const r=this.readTokensForElement(e);const s=zip(t,r).findIndex((([e,t])=>!tokensAreEqual(e,t)));return-1==s?[[],[]]:[t.slice(s),r.slice(s)]}readTokensForElement(e){const t=this.attributeName;const r=e.getAttribute(t)||"";return parseTokenString(r,e,t)}}function parseTokenString(e,t,r){return e.trim().split(/\s+/).filter((e=>e.length)).map(((e,s)=>({element:t,attributeName:r,content:e,index:s})))}function zip(e,t){const r=Math.max(e.length,t.length);return Array.from({length:r},((r,s)=>[e[s],t[s]]))}function tokensAreEqual(e,t){return e&&t&&e.index==t.index&&e.content==t.content}class ValueListObserver{constructor(e,t,r){this.tokenListObserver=new TokenListObserver(e,t,this);this.delegate=r;this.parseResultsByToken=new WeakMap;this.valuesByTokenByElement=new WeakMap}get started(){return this.tokenListObserver.started}start(){this.tokenListObserver.start()}stop(){this.tokenListObserver.stop()}refresh(){this.tokenListObserver.refresh()}get element(){return this.tokenListObserver.element}get attributeName(){return this.tokenListObserver.attributeName}tokenMatched(e){const{element:t}=e;const{value:r}=this.fetchParseResultForToken(e);if(r){this.fetchValuesByTokenForElement(t).set(e,r);this.delegate.elementMatchedValue(t,r)}}tokenUnmatched(e){const{element:t}=e;const{value:r}=this.fetchParseResultForToken(e);if(r){this.fetchValuesByTokenForElement(t).delete(e);this.delegate.elementUnmatchedValue(t,r)}}fetchParseResultForToken(e){let t=this.parseResultsByToken.get(e);if(!t){t=this.parseToken(e);this.parseResultsByToken.set(e,t)}return t}fetchValuesByTokenForElement(e){let t=this.valuesByTokenByElement.get(e);if(!t){t=new Map;this.valuesByTokenByElement.set(e,t)}return t}parseToken(e){try{const t=this.delegate.parseValueForToken(e);return{value:t}}catch(e){return{error:e}}}}class BindingObserver{constructor(e,t){this.context=e;this.delegate=t;this.bindingsByAction=new Map}start(){if(!this.valueListObserver){this.valueListObserver=new ValueListObserver(this.element,this.actionAttribute,this);this.valueListObserver.start()}}stop(){if(this.valueListObserver){this.valueListObserver.stop();delete this.valueListObserver;this.disconnectAllActions()}}get element(){return this.context.element}get identifier(){return this.context.identifier}get actionAttribute(){return this.schema.actionAttribute}get schema(){return this.context.schema}get bindings(){return Array.from(this.bindingsByAction.values())}connectAction(e){const t=new Binding(this.context,e);this.bindingsByAction.set(e,t);this.delegate.bindingConnected(t)}disconnectAction(e){const t=this.bindingsByAction.get(e);if(t){this.bindingsByAction.delete(e);this.delegate.bindingDisconnected(t)}}disconnectAllActions(){this.bindings.forEach((e=>this.delegate.bindingDisconnected(e,true)));this.bindingsByAction.clear()}parseValueForToken(e){const t=Action.forToken(e,this.schema);if(t.identifier==this.identifier)return t}elementMatchedValue(e,t){this.connectAction(t)}elementUnmatchedValue(e,t){this.disconnectAction(t)}}class ValueObserver{constructor(e,t){this.context=e;this.receiver=t;this.stringMapObserver=new StringMapObserver(this.element,this);this.valueDescriptorMap=this.controller.valueDescriptorMap}start(){this.stringMapObserver.start();this.invokeChangedCallbacksForDefaultValues()}stop(){this.stringMapObserver.stop()}get element(){return this.context.element}get controller(){return this.context.controller}getStringMapKeyForAttribute(e){if(e in this.valueDescriptorMap)return this.valueDescriptorMap[e].name}stringMapKeyAdded(e,t){const r=this.valueDescriptorMap[t];this.hasValue(e)||this.invokeChangedCallback(e,r.writer(this.receiver[e]),r.writer(r.defaultValue))}stringMapValueChanged(e,t,r){const s=this.valueDescriptorNameMap[t];if(null!==e){null===r&&(r=s.writer(s.defaultValue));this.invokeChangedCallback(t,e,r)}}stringMapKeyRemoved(e,t,r){const s=this.valueDescriptorNameMap[e];this.hasValue(e)?this.invokeChangedCallback(e,s.writer(this.receiver[e]),r):this.invokeChangedCallback(e,s.writer(s.defaultValue),r)}invokeChangedCallbacksForDefaultValues(){for(const{key:e,name:t,defaultValue:r,writer:s}of this.valueDescriptors)void 0==r||this.controller.data.has(e)||this.invokeChangedCallback(t,s(r),void 0)}invokeChangedCallback(e,t,r){const s=`${e}Changed`;const n=this.receiver[s];if("function"==typeof n){const s=this.valueDescriptorNameMap[e];try{const e=s.reader(t);let i=r;r&&(i=s.reader(r));n.call(this.receiver,e,i)}catch(e){e instanceof TypeError&&(e.message=`Stimulus Value "${this.context.identifier}.${s.name}" - ${e.message}`);throw e}}}get valueDescriptors(){const{valueDescriptorMap:e}=this;return Object.keys(e).map((t=>e[t]))}get valueDescriptorNameMap(){const e={};Object.keys(this.valueDescriptorMap).forEach((t=>{const r=this.valueDescriptorMap[t];e[r.name]=r}));return e}hasValue(e){const t=this.valueDescriptorNameMap[e];const r=`has${capitalize(t.name)}`;return this.receiver[r]}}class TargetObserver{constructor(e,t){this.context=e;this.delegate=t;this.targetsByName=new Multimap}start(){if(!this.tokenListObserver){this.tokenListObserver=new TokenListObserver(this.element,this.attributeName,this);this.tokenListObserver.start()}}stop(){if(this.tokenListObserver){this.disconnectAllTargets();this.tokenListObserver.stop();delete this.tokenListObserver}}tokenMatched({element:e,content:t}){this.scope.containsElement(e)&&this.connectTarget(e,t)}tokenUnmatched({element:e,content:t}){this.disconnectTarget(e,t)}connectTarget(e,t){var r;if(!this.targetsByName.has(t,e)){this.targetsByName.add(t,e);null===(r=this.tokenListObserver)||void 0===r?void 0:r.pause((()=>this.delegate.targetConnected(e,t)))}}disconnectTarget(e,t){var r;if(this.targetsByName.has(t,e)){this.targetsByName.delete(t,e);null===(r=this.tokenListObserver)||void 0===r?void 0:r.pause((()=>this.delegate.targetDisconnected(e,t)))}}disconnectAllTargets(){for(const e of this.targetsByName.keys)for(const t of this.targetsByName.getValuesForKey(e))this.disconnectTarget(t,e)}get attributeName(){return`data-${this.context.identifier}-target`}get element(){return this.context.element}get scope(){return this.context.scope}}function readInheritableStaticArrayValues(e,t){const r=getAncestorsForConstructor(e);return Array.from(r.reduce(((e,r)=>{getOwnStaticArrayValues(r,t).forEach((t=>e.add(t)));return e}),new Set))}function readInheritableStaticObjectPairs(e,t){const r=getAncestorsForConstructor(e);return r.reduce(((e,r)=>{e.push(...getOwnStaticObjectPairs(r,t));return e}),[])}function getAncestorsForConstructor(e){const t=[];while(e){t.push(e);e=Object.getPrototypeOf(e)}return t.reverse()}function getOwnStaticArrayValues(e,t){const r=e[t];return Array.isArray(r)?r:[]}function getOwnStaticObjectPairs(e,t){const r=e[t];return r?Object.keys(r).map((e=>[e,r[e]])):[]}class OutletObserver{constructor(e,t){this.started=false;this.context=e;this.delegate=t;this.outletsByName=new Multimap;this.outletElementsByName=new Multimap;this.selectorObserverMap=new Map;this.attributeObserverMap=new Map}start(){if(!this.started){this.outletDefinitions.forEach((e=>{this.setupSelectorObserverForOutlet(e);this.setupAttributeObserverForOutlet(e)}));this.started=true;this.dependentContexts.forEach((e=>e.refresh()))}}refresh(){this.selectorObserverMap.forEach((e=>e.refresh()));this.attributeObserverMap.forEach((e=>e.refresh()))}stop(){if(this.started){this.started=false;this.disconnectAllOutlets();this.stopSelectorObservers();this.stopAttributeObservers()}}stopSelectorObservers(){if(this.selectorObserverMap.size>0){this.selectorObserverMap.forEach((e=>e.stop()));this.selectorObserverMap.clear()}}stopAttributeObservers(){if(this.attributeObserverMap.size>0){this.attributeObserverMap.forEach((e=>e.stop()));this.attributeObserverMap.clear()}}selectorMatched(e,t,{outletName:r}){const s=this.getOutlet(e,r);s&&this.connectOutlet(s,e,r)}selectorUnmatched(e,t,{outletName:r}){const s=this.getOutletFromMap(e,r);s&&this.disconnectOutlet(s,e,r)}selectorMatchElement(e,{outletName:t}){const r=this.selector(t);const s=this.hasOutlet(e,t);const n=e.matches(`[${this.schema.controllerAttribute}~=${t}]`);return!!r&&(s&&n&&e.matches(r))}elementMatchedAttribute(e,t){const r=this.getOutletNameFromOutletAttributeName(t);r&&this.updateSelectorObserverForOutlet(r)}elementAttributeValueChanged(e,t){const r=this.getOutletNameFromOutletAttributeName(t);r&&this.updateSelectorObserverForOutlet(r)}elementUnmatchedAttribute(e,t){const r=this.getOutletNameFromOutletAttributeName(t);r&&this.updateSelectorObserverForOutlet(r)}connectOutlet(e,t,r){var s;if(!this.outletElementsByName.has(r,t)){this.outletsByName.add(r,e);this.outletElementsByName.add(r,t);null===(s=this.selectorObserverMap.get(r))||void 0===s?void 0:s.pause((()=>this.delegate.outletConnected(e,t,r)))}}disconnectOutlet(e,t,r){var s;if(this.outletElementsByName.has(r,t)){this.outletsByName.delete(r,e);this.outletElementsByName.delete(r,t);null===(s=this.selectorObserverMap.get(r))||void 0===s?void 0:s.pause((()=>this.delegate.outletDisconnected(e,t,r)))}}disconnectAllOutlets(){for(const e of this.outletElementsByName.keys)for(const t of this.outletElementsByName.getValuesForKey(e))for(const r of this.outletsByName.getValuesForKey(e))this.disconnectOutlet(r,t,e)}updateSelectorObserverForOutlet(e){const t=this.selectorObserverMap.get(e);t&&(t.selector=this.selector(e))}setupSelectorObserverForOutlet(e){const t=this.selector(e);const r=new SelectorObserver(document.body,t,this,{outletName:e});this.selectorObserverMap.set(e,r);r.start()}setupAttributeObserverForOutlet(e){const t=this.attributeNameForOutletName(e);const r=new AttributeObserver(this.scope.element,t,this);this.attributeObserverMap.set(e,r);r.start()}selector(e){return this.scope.outlets.getSelectorForOutletName(e)}attributeNameForOutletName(e){return this.scope.schema.outletAttributeForScope(this.identifier,e)}getOutletNameFromOutletAttributeName(e){return this.outletDefinitions.find((t=>this.attributeNameForOutletName(t)===e))}get outletDependencies(){const e=new Multimap;this.router.modules.forEach((t=>{const r=t.definition.controllerConstructor;const s=readInheritableStaticArrayValues(r,"outlets");s.forEach((r=>e.add(r,t.identifier)))}));return e}get outletDefinitions(){return this.outletDependencies.getKeysForValue(this.identifier)}get dependentControllerIdentifiers(){return this.outletDependencies.getValuesForKey(this.identifier)}get dependentContexts(){const e=this.dependentControllerIdentifiers;return this.router.contexts.filter((t=>e.includes(t.identifier)))}hasOutlet(e,t){return!!this.getOutlet(e,t)||!!this.getOutletFromMap(e,t)}getOutlet(e,t){return this.application.getControllerForElementAndIdentifier(e,t)}getOutletFromMap(e,t){return this.outletsByName.getValuesForKey(t).find((t=>t.element===e))}get scope(){return this.context.scope}get schema(){return this.context.schema}get identifier(){return this.context.identifier}get application(){return this.context.application}get router(){return this.application.router}}class Context{constructor(e,t){this.logDebugActivity=(e,t={})=>{const{identifier:r,controller:s,element:n}=this;t=Object.assign({identifier:r,controller:s,element:n},t);this.application.logDebugActivity(this.identifier,e,t)};this.module=e;this.scope=t;this.controller=new e.controllerConstructor(this);this.bindingObserver=new BindingObserver(this,this.dispatcher);this.valueObserver=new ValueObserver(this,this.controller);this.targetObserver=new TargetObserver(this,this);this.outletObserver=new OutletObserver(this,this);try{this.controller.initialize();this.logDebugActivity("initialize")}catch(e){this.handleError(e,"initializing controller")}}connect(){this.bindingObserver.start();this.valueObserver.start();this.targetObserver.start();this.outletObserver.start();try{this.controller.connect();this.logDebugActivity("connect")}catch(e){this.handleError(e,"connecting controller")}}refresh(){this.outletObserver.refresh()}disconnect(){try{this.controller.disconnect();this.logDebugActivity("disconnect")}catch(e){this.handleError(e,"disconnecting controller")}this.outletObserver.stop();this.targetObserver.stop();this.valueObserver.stop();this.bindingObserver.stop()}get application(){return this.module.application}get identifier(){return this.module.identifier}get schema(){return this.application.schema}get dispatcher(){return this.application.dispatcher}get element(){return this.scope.element}get parentElement(){return this.element.parentElement}handleError(e,t,r={}){const{identifier:s,controller:n,element:i}=this;r=Object.assign({identifier:s,controller:n,element:i},r);this.application.handleError(e,`Error ${t}`,r)}targetConnected(e,t){this.invokeControllerMethod(`${t}TargetConnected`,e)}targetDisconnected(e,t){this.invokeControllerMethod(`${t}TargetDisconnected`,e)}outletConnected(e,t,r){this.invokeControllerMethod(`${namespaceCamelize(r)}OutletConnected`,e,t)}outletDisconnected(e,t,r){this.invokeControllerMethod(`${namespaceCamelize(r)}OutletDisconnected`,e,t)}invokeControllerMethod(e,...t){const r=this.controller;"function"==typeof r[e]&&r[e](...t)}}function bless(e){return shadow(e,getBlessedProperties(e))}function shadow(e,t){const r=i(e);const s=getShadowProperties(e.prototype,t);Object.defineProperties(r.prototype,s);return r}function getBlessedProperties(e){const t=readInheritableStaticArrayValues(e,"blessings");return t.reduce(((t,r)=>{const s=r(e);for(const e in s){const r=t[e]||{};t[e]=Object.assign(r,s[e])}return t}),{})}function getShadowProperties(e,t){return n(t).reduce(((r,s)=>{const n=getShadowedDescriptor(e,t,s);n&&Object.assign(r,{[s]:n});return r}),{})}function getShadowedDescriptor(e,t,r){const s=Object.getOwnPropertyDescriptor(e,r);const n=s&&"value"in s;if(!n){const e=Object.getOwnPropertyDescriptor(t,r).value;if(s){e.get=s.get||e.get;e.set=s.set||e.set}return e}}const n=(()=>"function"==typeof Object.getOwnPropertySymbols?e=>[...Object.getOwnPropertyNames(e),...Object.getOwnPropertySymbols(e)]:Object.getOwnPropertyNames)();const i=(()=>{function extendWithReflect(e){function extended(){return Reflect.construct(e,arguments,new.target)}extended.prototype=Object.create(e.prototype,{constructor:{value:extended}});Reflect.setPrototypeOf(extended,e);return extended}function testReflectExtension(){const a=function(){this.a.call(this)};const e=extendWithReflect(a);e.prototype.a=function(){};return new e}try{testReflectExtension();return extendWithReflect}catch(e){return e=>class extended extends e{}}})();function blessDefinition(e){return{identifier:e.identifier,controllerConstructor:bless(e.controllerConstructor)}}class Module{constructor(e,t){this.application=e;this.definition=blessDefinition(t);this.contextsByScope=new WeakMap;this.connectedContexts=new Set}get identifier(){return this.definition.identifier}get controllerConstructor(){return this.definition.controllerConstructor}get contexts(){return Array.from(this.connectedContexts)}connectContextForScope(e){const t=this.fetchContextForScope(e);this.connectedContexts.add(t);t.connect()}disconnectContextForScope(e){const t=this.contextsByScope.get(e);if(t){this.connectedContexts.delete(t);t.disconnect()}}fetchContextForScope(e){let t=this.contextsByScope.get(e);if(!t){t=new Context(this,e);this.contextsByScope.set(e,t)}return t}}class ClassMap{constructor(e){this.scope=e}has(e){return this.data.has(this.getDataKey(e))}get(e){return this.getAll(e)[0]}getAll(e){const t=this.data.get(this.getDataKey(e))||"";return tokenize(t)}getAttributeName(e){return this.data.getAttributeNameForKey(this.getDataKey(e))}getDataKey(e){return`${e}-class`}get data(){return this.scope.data}}class DataMap{constructor(e){this.scope=e}get element(){return this.scope.element}get identifier(){return this.scope.identifier}get(e){const t=this.getAttributeNameForKey(e);return this.element.getAttribute(t)}set(e,t){const r=this.getAttributeNameForKey(e);this.element.setAttribute(r,t);return this.get(e)}has(e){const t=this.getAttributeNameForKey(e);return this.element.hasAttribute(t)}delete(e){if(this.has(e)){const t=this.getAttributeNameForKey(e);this.element.removeAttribute(t);return true}return false}getAttributeNameForKey(e){return`data-${this.identifier}-${dasherize(e)}`}}class Guide{constructor(e){this.warnedKeysByObject=new WeakMap;this.logger=e}warn(e,t,r){let s=this.warnedKeysByObject.get(e);if(!s){s=new Set;this.warnedKeysByObject.set(e,s)}if(!s.has(t)){s.add(t);this.logger.warn(r,e)}}}function attributeValueContainsToken(e,t){return`[${e}~="${t}"]`}class TargetSet{constructor(e){this.scope=e}get element(){return this.scope.element}get identifier(){return this.scope.identifier}get schema(){return this.scope.schema}has(e){return null!=this.find(e)}find(...e){return e.reduce(((e,t)=>e||this.findTarget(t)||this.findLegacyTarget(t)),void 0)}findAll(...e){return e.reduce(((e,t)=>[...e,...this.findAllTargets(t),...this.findAllLegacyTargets(t)]),[])}findTarget(e){const t=this.getSelectorForTargetName(e);return this.scope.findElement(t)}findAllTargets(e){const t=this.getSelectorForTargetName(e);return this.scope.findAllElements(t)}getSelectorForTargetName(e){const t=this.schema.targetAttributeForScope(this.identifier);return attributeValueContainsToken(t,e)}findLegacyTarget(e){const t=this.getLegacySelectorForTargetName(e);return this.deprecate(this.scope.findElement(t),e)}findAllLegacyTargets(e){const t=this.getLegacySelectorForTargetName(e);return this.scope.findAllElements(t).map((t=>this.deprecate(t,e)))}getLegacySelectorForTargetName(e){const t=`${this.identifier}.${e}`;return attributeValueContainsToken(this.schema.targetAttribute,t)}deprecate(e,t){if(e){const{identifier:r}=this;const s=this.schema.targetAttribute;const n=this.schema.targetAttributeForScope(r);this.guide.warn(e,`target:${t}`,`Please replace ${s}="${r}.${t}" with ${n}="${t}". The ${s} attribute is deprecated and will be removed in a future version of Stimulus.`)}return e}get guide(){return this.scope.guide}}class OutletSet{constructor(e,t){this.scope=e;this.controllerElement=t}get element(){return this.scope.element}get identifier(){return this.scope.identifier}get schema(){return this.scope.schema}has(e){return null!=this.find(e)}find(...e){return e.reduce(((e,t)=>e||this.findOutlet(t)),void 0)}findAll(...e){return e.reduce(((e,t)=>[...e,...this.findAllOutlets(t)]),[])}getSelectorForOutletName(e){const t=this.schema.outletAttributeForScope(this.identifier,e);return this.controllerElement.getAttribute(t)}findOutlet(e){const t=this.getSelectorForOutletName(e);if(t)return this.findElement(t,e)}findAllOutlets(e){const t=this.getSelectorForOutletName(e);return t?this.findAllElements(t,e):[]}findElement(e,t){const r=this.scope.queryElements(e);return r.filter((r=>this.matchesElement(r,e,t)))[0]}findAllElements(e,t){const r=this.scope.queryElements(e);return r.filter((r=>this.matchesElement(r,e,t)))}matchesElement(e,t,r){const s=e.getAttribute(this.scope.schema.controllerAttribute)||"";return e.matches(t)&&s.split(" ").includes(r)}}class Scope{constructor(e,t,r,s){this.targets=new TargetSet(this);this.classes=new ClassMap(this);this.data=new DataMap(this);this.containsElement=e=>e.closest(this.controllerSelector)===this.element;this.schema=e;this.element=t;this.identifier=r;this.guide=new Guide(s);this.outlets=new OutletSet(this.documentScope,t)}findElement(e){return this.element.matches(e)?this.element:this.queryElements(e).find(this.containsElement)}findAllElements(e){return[...this.element.matches(e)?[this.element]:[],...this.queryElements(e).filter(this.containsElement)]}queryElements(e){return Array.from(this.element.querySelectorAll(e))}get controllerSelector(){return attributeValueContainsToken(this.schema.controllerAttribute,this.identifier)}get isDocumentScope(){return this.element===document.documentElement}get documentScope(){return this.isDocumentScope?this:new Scope(this.schema,document.documentElement,this.identifier,this.guide.logger)}}class ScopeObserver{constructor(e,t,r){this.element=e;this.schema=t;this.delegate=r;this.valueListObserver=new ValueListObserver(this.element,this.controllerAttribute,this);this.scopesByIdentifierByElement=new WeakMap;this.scopeReferenceCounts=new WeakMap}start(){this.valueListObserver.start()}stop(){this.valueListObserver.stop()}get controllerAttribute(){return this.schema.controllerAttribute}parseValueForToken(e){const{element:t,content:r}=e;return this.parseValueForElementAndIdentifier(t,r)}parseValueForElementAndIdentifier(e,t){const r=this.fetchScopesByIdentifierForElement(e);let s=r.get(t);if(!s){s=this.delegate.createScopeForElementAndIdentifier(e,t);r.set(t,s)}return s}elementMatchedValue(e,t){const r=(this.scopeReferenceCounts.get(t)||0)+1;this.scopeReferenceCounts.set(t,r);1==r&&this.delegate.scopeConnected(t)}elementUnmatchedValue(e,t){const r=this.scopeReferenceCounts.get(t);if(r){this.scopeReferenceCounts.set(t,r-1);1==r&&this.delegate.scopeDisconnected(t)}}fetchScopesByIdentifierForElement(e){let t=this.scopesByIdentifierByElement.get(e);if(!t){t=new Map;this.scopesByIdentifierByElement.set(e,t)}return t}}class Router{constructor(e){this.application=e;this.scopeObserver=new ScopeObserver(this.element,this.schema,this);this.scopesByIdentifier=new Multimap;this.modulesByIdentifier=new Map}get element(){return this.application.element}get schema(){return this.application.schema}get logger(){return this.application.logger}get controllerAttribute(){return this.schema.controllerAttribute}get modules(){return Array.from(this.modulesByIdentifier.values())}get contexts(){return this.modules.reduce(((e,t)=>e.concat(t.contexts)),[])}start(){this.scopeObserver.start()}stop(){this.scopeObserver.stop()}loadDefinition(e){this.unloadIdentifier(e.identifier);const t=new Module(this.application,e);this.connectModule(t);const r=e.controllerConstructor.afterLoad;r&&r.call(e.controllerConstructor,e.identifier,this.application)}unloadIdentifier(e){const t=this.modulesByIdentifier.get(e);t&&this.disconnectModule(t)}getContextForElementAndIdentifier(e,t){const r=this.modulesByIdentifier.get(t);if(r)return r.contexts.find((t=>t.element==e))}proposeToConnectScopeForElementAndIdentifier(e,t){const r=this.scopeObserver.parseValueForElementAndIdentifier(e,t);r?this.scopeObserver.elementMatchedValue(r.element,r):console.error(`Couldn't find or create scope for identifier: "${t}" and element:`,e)}handleError(e,t,r){this.application.handleError(e,t,r)}createScopeForElementAndIdentifier(e,t){return new Scope(this.schema,e,t,this.logger)}scopeConnected(e){this.scopesByIdentifier.add(e.identifier,e);const t=this.modulesByIdentifier.get(e.identifier);t&&t.connectContextForScope(e)}scopeDisconnected(e){this.scopesByIdentifier.delete(e.identifier,e);const t=this.modulesByIdentifier.get(e.identifier);t&&t.disconnectContextForScope(e)}connectModule(e){this.modulesByIdentifier.set(e.identifier,e);const t=this.scopesByIdentifier.getValuesForKey(e.identifier);t.forEach((t=>e.connectContextForScope(t)))}disconnectModule(e){this.modulesByIdentifier.delete(e.identifier);const t=this.scopesByIdentifier.getValuesForKey(e.identifier);t.forEach((t=>e.disconnectContextForScope(t)))}}const o={controllerAttribute:"data-controller",actionAttribute:"data-action",targetAttribute:"data-target",targetAttributeForScope:e=>`data-${e}-target`,outletAttributeForScope:(e,t)=>`data-${e}-${t}-outlet`,keyMappings:Object.assign(Object.assign({enter:"Enter",tab:"Tab",esc:"Escape",space:" ",up:"ArrowUp",down:"ArrowDown",left:"ArrowLeft",right:"ArrowRight",home:"Home",end:"End",page_up:"PageUp",page_down:"PageDown"},objectFromEntries("abcdefghijklmnopqrstuvwxyz".split("").map((e=>[e,e])))),objectFromEntries("0123456789".split("").map((e=>[e,e]))))};function objectFromEntries(e){return e.reduce(((e,[t,r])=>Object.assign(Object.assign({},e),{[t]:r})),{})}class Application{constructor(t=document.documentElement,r=o){this.logger=console;this.debug=false;this.logDebugActivity=(e,t,r={})=>{this.debug&&this.logFormattedMessage(e,t,r)};this.element=t;this.schema=r;this.dispatcher=new Dispatcher(this);this.router=new Router(this);this.actionDescriptorFilters=Object.assign({},e)}static start(e,t){const r=new this(e,t);r.start();return r}async start(){await domReady();this.logDebugActivity("application","starting");this.dispatcher.start();this.router.start();this.logDebugActivity("application","start")}stop(){this.logDebugActivity("application","stopping");this.dispatcher.stop();this.router.stop();this.logDebugActivity("application","stop")}register(e,t){this.load({identifier:e,controllerConstructor:t})}registerActionOption(e,t){this.actionDescriptorFilters[e]=t}load(e,...t){const r=Array.isArray(e)?e:[e,...t];r.forEach((e=>{e.controllerConstructor.shouldLoad&&this.router.loadDefinition(e)}))}unload(e,...t){const r=Array.isArray(e)?e:[e,...t];r.forEach((e=>this.router.unloadIdentifier(e)))}get controllers(){return this.router.contexts.map((e=>e.controller))}getControllerForElementAndIdentifier(e,t){const r=this.router.getContextForElementAndIdentifier(e,t);return r?r.controller:null}handleError(e,t,r){var s;this.logger.error("%s\n\n%o\n\n%o",t,e,r);null===(s=window.onerror)||void 0===s?void 0:s.call(window,t,"",0,0,e)}logFormattedMessage(e,t,r={}){r=Object.assign({application:this},r);this.logger.groupCollapsed(`${e} #${t}`);this.logger.log("details:",Object.assign({},r));this.logger.groupEnd()}}function domReady(){return new Promise((e=>{"loading"==document.readyState?document.addEventListener("DOMContentLoaded",(()=>e())):e()}))}function ClassPropertiesBlessing(e){const t=readInheritableStaticArrayValues(e,"classes");return t.reduce(((e,t)=>Object.assign(e,propertiesForClassDefinition(t))),{})}function propertiesForClassDefinition(e){return{[`${e}Class`]:{get(){const{classes:t}=this;if(t.has(e))return t.get(e);{const r=t.getAttributeName(e);throw new Error(`Missing attribute "${r}"`)}}},[`${e}Classes`]:{get(){return this.classes.getAll(e)}},[`has${capitalize(e)}Class`]:{get(){return this.classes.has(e)}}}}function OutletPropertiesBlessing(e){const t=readInheritableStaticArrayValues(e,"outlets");return t.reduce(((e,t)=>Object.assign(e,propertiesForOutletDefinition(t))),{})}function getOutletController(e,t,r){return e.application.getControllerForElementAndIdentifier(t,r)}function getControllerAndEnsureConnectedScope(e,t,r){let s=getOutletController(e,t,r);if(s)return s;e.application.router.proposeToConnectScopeForElementAndIdentifier(t,r);s=getOutletController(e,t,r);return s||void 0}function propertiesForOutletDefinition(e){const t=namespaceCamelize(e);return{[`${t}Outlet`]:{get(){const t=this.outlets.find(e);const r=this.outlets.getSelectorForOutletName(e);if(t){const r=getControllerAndEnsureConnectedScope(this,t,e);if(r)return r;throw new Error(`The provided outlet element is missing an outlet controller "${e}" instance for host controller "${this.identifier}"`)}throw new Error(`Missing outlet element "${e}" for host controller "${this.identifier}". Stimulus couldn't find a matching outlet element using selector "${r}".`)}},[`${t}Outlets`]:{get(){const t=this.outlets.findAll(e);return t.length>0?t.map((t=>{const r=getControllerAndEnsureConnectedScope(this,t,e);if(r)return r;console.warn(`The provided outlet element is missing an outlet controller "${e}" instance for host controller "${this.identifier}"`,t)})).filter((e=>e)):[]}},[`${t}OutletElement`]:{get(){const t=this.outlets.find(e);const r=this.outlets.getSelectorForOutletName(e);if(t)return t;throw new Error(`Missing outlet element "${e}" for host controller "${this.identifier}". Stimulus couldn't find a matching outlet element using selector "${r}".`)}},[`${t}OutletElements`]:{get(){return this.outlets.findAll(e)}},[`has${capitalize(t)}Outlet`]:{get(){return this.outlets.has(e)}}}}function TargetPropertiesBlessing(e){const t=readInheritableStaticArrayValues(e,"targets");return t.reduce(((e,t)=>Object.assign(e,propertiesForTargetDefinition(t))),{})}function propertiesForTargetDefinition(e){return{[`${e}Target`]:{get(){const t=this.targets.find(e);if(t)return t;throw new Error(`Missing target element "${e}" for "${this.identifier}" controller`)}},[`${e}Targets`]:{get(){return this.targets.findAll(e)}},[`has${capitalize(e)}Target`]:{get(){return this.targets.has(e)}}}}function ValuePropertiesBlessing(e){const t=readInheritableStaticObjectPairs(e,"values");const r={valueDescriptorMap:{get(){return t.reduce(((e,t)=>{const r=parseValueDefinitionPair(t,this.identifier);const s=this.data.getAttributeNameForKey(r.key);return Object.assign(e,{[s]:r})}),{})}}};return t.reduce(((e,t)=>Object.assign(e,propertiesForValueDefinitionPair(t))),r)}function propertiesForValueDefinitionPair(e,t){const r=parseValueDefinitionPair(e,t);const{key:s,name:n,reader:i,writer:o}=r;return{[n]:{get(){const e=this.data.get(s);return null!==e?i(e):r.defaultValue},set(e){void 0===e?this.data.delete(s):this.data.set(s,o(e))}},[`has${capitalize(n)}`]:{get(){return this.data.has(s)||r.hasCustomDefaultValue}}}}function parseValueDefinitionPair([e,t],r){return valueDescriptorForTokenAndTypeDefinition({controller:r,token:e,typeDefinition:t})}function parseValueTypeConstant(e){switch(e){case Array:return"array";case Boolean:return"boolean";case Number:return"number";case Object:return"object";case String:return"string"}}function parseValueTypeDefault(e){switch(typeof e){case"boolean":return"boolean";case"number":return"number";case"string":return"string"}return Array.isArray(e)?"array":"[object Object]"===Object.prototype.toString.call(e)?"object":void 0}function parseValueTypeObject(e){const{controller:t,token:r,typeObject:s}=e;const n=isSomething(s.type);const i=isSomething(s.default);const o=n&&i;const c=n&&!i;const l=!n&&i;const h=parseValueTypeConstant(s.type);const u=parseValueTypeDefault(e.typeObject.default);if(c)return h;if(l)return u;if(h!==u){const e=t?`${t}.${r}`:r;throw new Error(`The specified default value for the Stimulus Value "${e}" must match the defined type "${h}". The provided default value of "${s.default}" is of type "${u}".`)}return o?h:void 0}function parseValueTypeDefinition(e){const{controller:t,token:r,typeDefinition:s}=e;const n={controller:t,token:r,typeObject:s};const i=parseValueTypeObject(n);const o=parseValueTypeDefault(s);const c=parseValueTypeConstant(s);const l=i||o||c;if(l)return l;const h=t?`${t}.${s}`:r;throw new Error(`Unknown value type "${h}" for "${r}" value`)}function defaultValueForDefinition(e){const t=parseValueTypeConstant(e);if(t)return c[t];const r=hasProperty(e,"default");const s=hasProperty(e,"type");const n=e;if(r)return n.default;if(s){const{type:e}=n;const t=parseValueTypeConstant(e);if(t)return c[t]}return e}function valueDescriptorForTokenAndTypeDefinition(e){const{token:t,typeDefinition:r}=e;const s=`${dasherize(t)}-value`;const n=parseValueTypeDefinition(e);return{type:n,key:s,name:camelize(s),get defaultValue(){return defaultValueForDefinition(r)},get hasCustomDefaultValue(){return void 0!==parseValueTypeDefault(r)},reader:l[n],writer:h[n]||h.default}}const c={get array(){return[]},boolean:false,number:0,get object(){return{}},string:""};const l={array(e){const t=JSON.parse(e);if(!Array.isArray(t))throw new TypeError(`expected value of type "array" but instead got value "${e}" of type "${parseValueTypeDefault(t)}"`);return t},boolean(e){return!("0"==e||"false"==String(e).toLowerCase())},number(e){return Number(e.replace(/_/g,""))},object(e){const t=JSON.parse(e);if(null===t||"object"!=typeof t||Array.isArray(t))throw new TypeError(`expected value of type "object" but instead got value "${e}" of type "${parseValueTypeDefault(t)}"`);return t},string(e){return e}};const h={default:writeString,array:writeJSON,object:writeJSON};function writeJSON(e){return JSON.stringify(e)}function writeString(e){return`${e}`}class Controller{constructor(e){this.context=e}static get shouldLoad(){return true}static afterLoad(e,t){}get application(){return this.context.application}get scope(){return this.context.scope}get element(){return this.scope.element}get identifier(){return this.scope.identifier}get targets(){return this.scope.targets}get outlets(){return this.scope.outlets}get classes(){return this.scope.classes}get data(){return this.scope.data}initialize(){}connect(){}disconnect(){}dispatch(e,{target:t=this.element,detail:r={},prefix:s=this.identifier,bubbles:n=true,cancelable:i=true}={}){const o=s?`${s}:${e}`:e;const c=new CustomEvent(o,{detail:r,bubbles:n,cancelable:i});t.dispatchEvent(c);return c}}Controller.blessings=[ClassPropertiesBlessing,TargetPropertiesBlessing,ValuePropertiesBlessing,OutletPropertiesBlessing];Controller.targets=[];Controller.outlets=[];Controller.values={};export{Application,AttributeObserver,Context,Controller,ElementObserver,IndexedMultimap,Multimap,SelectorObserver,StringMapObserver,TokenListObserver,ValueListObserver,add,o as defaultSchema,del,fetch,prune};
+
diff --git a/vendor/javascript/@hotwired--turbo-rails.js b/vendor/javascript/@hotwired--turbo-rails.js
new file mode 100644
index 000000000..b28dbeeee
--- /dev/null
+++ b/vendor/javascript/@hotwired--turbo-rails.js
@@ -0,0 +1,4 @@
+// @hotwired/turbo-rails@8.0.16 downloaded from https://ga.jspm.io/npm:@hotwired/turbo-rails@8.0.16/app/javascript/turbo/index.js
+
+import*as t from"@hotwired/turbo";import{connectStreamSource as e,disconnectStreamSource as n}from"@hotwired/turbo";export{t as Turbo};let s;async function o(){return s||i(r().then(i))}function i(t){return s=t}async function r(){const{createConsumer:t}=await import("@rails/actioncable/src");return t()}async function c(t,e){const{subscriptions:n}=await o();return n.create(t,e)}var a=Object.freeze(Object.defineProperty({__proto__:null,createConsumer:r,getConsumer:o,setConsumer:i,subscribeTo:c},Symbol.toStringTag,{value:"Module"}));function u(t){return t&&typeof t==="object"?t instanceof Date||t instanceof RegExp?t:Array.isArray(t)?t.map(u):Object.keys(t).reduce((function(e,n){var s=n[0].toLowerCase()+n.slice(1).replace(/([A-Z]+)/g,(function(t,e){return"_"+e.toLowerCase()}));e[s]=u(t[n]);return e}),{}):t}class TurboCableStreamSourceElement extends HTMLElement{static observedAttributes=["channel","signed-stream-name"];async connectedCallback(){e(this);this.subscription=await c(this.channel,{received:this.dispatchMessageEvent.bind(this),connected:this.subscriptionConnected.bind(this),disconnected:this.subscriptionDisconnected.bind(this)})}disconnectedCallback(){n(this);this.subscription&&this.subscription.unsubscribe();this.subscriptionDisconnected()}attributeChangedCallback(){if(this.subscription){this.disconnectedCallback();this.connectedCallback()}}dispatchMessageEvent(t){const e=new MessageEvent("message",{data:t});return this.dispatchEvent(e)}subscriptionConnected(){this.setAttribute("connected","")}subscriptionDisconnected(){this.removeAttribute("connected")}get channel(){const t=this.getAttribute("channel");const e=this.getAttribute("signed-stream-name");return{channel:t,signed_stream_name:e,...u({...this.dataset})}}}customElements.get("turbo-cable-stream-source")===void 0&&customElements.define("turbo-cable-stream-source",TurboCableStreamSourceElement);function b(t){if(t.target instanceof HTMLFormElement){const{target:e,detail:{fetchOptions:n}}=t;e.addEventListener("turbo:submit-start",(({detail:{formSubmission:{submitter:t}}})=>{const s=h(n.body)?n.body:new URLSearchParams;const o=d(t,s,e);if(!/get/i.test(o)){/post/i.test(o)?s.delete("_method"):s.set("_method",o);n.method="post"}}),{once:true})}}function d(t,e,n){const s=m(t);const o=e.get("_method");const i=n.getAttribute("method")||"get";return typeof s=="string"?s:typeof o=="string"?o:i}function m(t){return t instanceof HTMLButtonElement||t instanceof HTMLInputElement?t.name==="_method"?t.value:t.hasAttribute("formmethod")?t.formMethod:null:null}function h(t){return t instanceof FormData||t instanceof URLSearchParams}window.Turbo=t;addEventListener("turbo:before-fetch-request",b);export{a as cable};
+
diff --git a/vendor/javascript/@hotwired--turbo.js b/vendor/javascript/@hotwired--turbo.js
new file mode 100644
index 000000000..8e5e1eb95
--- /dev/null
+++ b/vendor/javascript/@hotwired--turbo.js
@@ -0,0 +1,458 @@
+// @hotwired/turbo@8.0.13 downloaded from https://ga.jspm.io/npm:@hotwired/turbo@8.0.13/dist/turbo.es2017-esm.js
+
+(function(e){typeof e.requestSubmit!="function"&&(e.requestSubmit=function(e){if(e){validateSubmitter(e,this);e.click()}else{e=document.createElement("input");e.type="submit";e.hidden=true;this.appendChild(e);e.click();this.removeChild(e)}});function validateSubmitter(e,t){e instanceof HTMLElement||raise(TypeError,"parameter 1 is not of type 'HTMLElement'");e.type=="submit"||raise(TypeError,"The specified element is not a submit button");e.form==t||raise(DOMException,"The specified element is not owned by this form element","NotFoundError")}function raise(e,t,r){throw new e("Failed to execute 'requestSubmit' on 'HTMLFormElement': "+t+".",r)}})(HTMLFormElement.prototype);const e=new WeakMap;function findSubmitterFromClickTarget(e){const t=e instanceof Element?e:e instanceof Node?e.parentElement:null;const r=t?t.closest("input, button"):null;return r?.type=="submit"?r:null}function clickCaptured(t){const r=findSubmitterFromClickTarget(t.target);r&&r.form&&e.set(r.form,r)}(function(){if("submitter"in Event.prototype)return;let t=window.Event.prototype;if("SubmitEvent"in window){const e=window.SubmitEvent.prototype;if(!/Apple Computer/.test(navigator.vendor)||"submitter"in e)return;t=e}addEventListener("click",clickCaptured,true);Object.defineProperty(t,"submitter",{get(){if(this.type=="submit"&&this.target instanceof HTMLFormElement)return e.get(this.target)}})})();const t={eager:"eager",lazy:"lazy"};class FrameElement extends HTMLElement{static delegateConstructor=void 0;loaded=Promise.resolve();static get observedAttributes(){return["disabled","loading","src"]}constructor(){super();this.delegate=new FrameElement.delegateConstructor(this)}connectedCallback(){this.delegate.connect()}disconnectedCallback(){this.delegate.disconnect()}reload(){return this.delegate.sourceURLReloaded()}attributeChangedCallback(e){e=="loading"?this.delegate.loadingStyleChanged():e=="src"?this.delegate.sourceURLChanged():e=="disabled"&&this.delegate.disabledChanged()}get src(){return this.getAttribute("src")}set src(e){e?this.setAttribute("src",e):this.removeAttribute("src")}get refresh(){return this.getAttribute("refresh")}set refresh(e){e?this.setAttribute("refresh",e):this.removeAttribute("refresh")}get shouldReloadWithMorph(){return this.src&&this.refresh==="morph"}get loading(){return frameLoadingStyleFromString(this.getAttribute("loading")||"")}set loading(e){e?this.setAttribute("loading",e):this.removeAttribute("loading")}get disabled(){return this.hasAttribute("disabled")}set disabled(e){e?this.setAttribute("disabled",""):this.removeAttribute("disabled")}get autoscroll(){return this.hasAttribute("autoscroll")}set autoscroll(e){e?this.setAttribute("autoscroll",""):this.removeAttribute("autoscroll")}get complete(){return!this.delegate.isLoading}get isActive(){return this.ownerDocument===document&&!this.isPreview}get isPreview(){return this.ownerDocument?.documentElement?.hasAttribute("data-turbo-preview")}}function frameLoadingStyleFromString(e){switch(e.toLowerCase()){case"lazy":return t.lazy;default:return t.eager}}const r={enabled:true,progressBarDelay:500,unvisitableExtensions:new Set([".7z",".aac",".apk",".avi",".bmp",".bz2",".css",".csv",".deb",".dmg",".doc",".docx",".exe",".gif",".gz",".heic",".heif",".ico",".iso",".jpeg",".jpg",".js",".json",".m4a",".mkv",".mov",".mp3",".mp4",".mpeg",".mpg",".msi",".ogg",".ogv",".pdf",".pkg",".png",".ppt",".pptx",".rar",".rtf",".svg",".tar",".tif",".tiff",".txt",".wav",".webm",".webp",".wma",".wmv",".xls",".xlsx",".xml",".zip"])};function activateScriptElement(e){if(e.getAttribute("data-turbo-eval")=="false")return e;{const t=document.createElement("script");const r=getCspNonce();r&&(t.nonce=r);t.textContent=e.textContent;t.async=false;copyElementAttributes(t,e);return t}}function copyElementAttributes(e,t){for(const{name:r,value:s}of t.attributes)e.setAttribute(r,s)}function createDocumentFragment(e){const t=document.createElement("template");t.innerHTML=e;return t.content}function dispatch(e,{target:t,cancelable:r,detail:s}={}){const i=new CustomEvent(e,{cancelable:r,bubbles:true,composed:true,detail:s});t&&t.isConnected?t.dispatchEvent(i):document.documentElement.dispatchEvent(i);return i}function cancelEvent(e){e.preventDefault();e.stopImmediatePropagation()}function nextRepaint(){return document.visibilityState==="hidden"?nextEventLoopTick():nextAnimationFrame()}function nextAnimationFrame(){return new Promise((e=>requestAnimationFrame((()=>e()))))}function nextEventLoopTick(){return new Promise((e=>setTimeout((()=>e()),0)))}function nextMicrotask(){return Promise.resolve()}function parseHTMLDocument(e=""){return(new DOMParser).parseFromString(e,"text/html")}function unindent(e,...t){const r=interpolate(e,t).replace(/^\n/,"").split("\n");const s=r[0].match(/^\s+/);const i=s?s[0].length:0;return r.map((e=>e.slice(i))).join("\n")}function interpolate(e,t){return e.reduce(((e,r,s)=>{const i=t[s]==void 0?"":t[s];return e+r+i}),"")}function uuid(){return Array.from({length:36}).map(((e,t)=>t==8||t==13||t==18||t==23?"-":t==14?"4":t==19?(Math.floor(Math.random()*4)+8).toString(16):Math.floor(Math.random()*15).toString(16))).join("")}function getAttribute(e,...t){for(const r of t.map((t=>t?.getAttribute(e))))if(typeof r=="string")return r;return null}function hasAttribute(e,...t){return t.some((t=>t&&t.hasAttribute(e)))}function markAsBusy(...e){for(const t of e){t.localName=="turbo-frame"&&t.setAttribute("busy","");t.setAttribute("aria-busy","true")}}function clearBusyState(...e){for(const t of e){t.localName=="turbo-frame"&&t.removeAttribute("busy");t.removeAttribute("aria-busy")}}function waitForLoad(e,t=2e3){return new Promise((r=>{const onComplete=()=>{e.removeEventListener("error",onComplete);e.removeEventListener("load",onComplete);r()};e.addEventListener("load",onComplete,{once:true});e.addEventListener("error",onComplete,{once:true});setTimeout(r,t)}))}function getHistoryMethodForAction(e){switch(e){case"replace":return history.replaceState;case"advance":case"restore":return history.pushState}}function isAction(e){return e=="advance"||e=="replace"||e=="restore"}function getVisitAction(...e){const t=getAttribute("data-turbo-action",...e);return isAction(t)?t:null}function getMetaElement(e){return document.querySelector(`meta[name="${e}"]`)}function getMetaContent(e){const t=getMetaElement(e);return t&&t.content}function getCspNonce(){const e=getMetaElement("csp-nonce");if(e){const{nonce:t,content:r}=e;return t==""?r:t}}function setMetaContent(e,t){let r=getMetaElement(e);if(!r){r=document.createElement("meta");r.setAttribute("name",e);document.head.appendChild(r)}r.setAttribute("content",t);return r}function findClosestRecursively(e,t){if(e instanceof Element)return e.closest(t)||findClosestRecursively(e.assignedSlot||e.getRootNode()?.host,t)}function elementIsFocusable(e){const t="[inert], :disabled, [hidden], details:not([open]), dialog:not([open])";return!!e&&e.closest(t)==null&&typeof e.focus=="function"}function queryAutofocusableElement(e){return Array.from(e.querySelectorAll("[autofocus]")).find(elementIsFocusable)}async function around(e,t){const r=t();e();await nextAnimationFrame();const s=t();return[r,s]}function doesNotTargetIFrame(e){if(e==="_blank")return false;if(e){for(const t of document.getElementsByName(e))if(t instanceof HTMLIFrameElement)return false;return true}return true}function findLinkFromClickTarget(e){return findClosestRecursively(e,"a[href]:not([target^=_]):not([download])")}function getLocationForLink(e){return expandURL(e.getAttribute("href")||"")}function debounce(e,t){let r=null;return(...s)=>{const callback=()=>e.apply(this,s);clearTimeout(r);r=setTimeout(callback,t)}}const s={"aria-disabled":{beforeSubmit:e=>{e.setAttribute("aria-disabled","true");e.addEventListener("click",cancelEvent)},afterSubmit:e=>{e.removeAttribute("aria-disabled");e.removeEventListener("click",cancelEvent)}},disabled:{beforeSubmit:e=>e.disabled=true,afterSubmit:e=>e.disabled=false}};class Config{#e=null;constructor(e){Object.assign(this,e)}get submitter(){return this.#e}set submitter(e){this.#e=s[e]||e}}const i=new Config({mode:"on",submitter:"disabled"});const n={drive:r,forms:i};function expandURL(e){return new URL(e.toString(),document.baseURI)}function getAnchor(e){let t;return e.hash?e.hash.slice(1):(t=e.href.match(/#(.*)$/))?t[1]:void 0}function getAction$1(e,t){const r=t?.getAttribute("formaction")||e.getAttribute("action")||e.action;return expandURL(r)}function getExtension(e){return(getLastPathComponent(e).match(/\.[^.]*$/)||[])[0]||""}function isPrefixedBy(e,t){const r=getPrefix(t);return e.href===expandURL(r).href||e.href.startsWith(r)}function locationIsVisitable(e,t){return isPrefixedBy(e,t)&&!n.drive.unvisitableExtensions.has(getExtension(e))}function getRequestURL(e){const t=getAnchor(e);return t!=null?e.href.slice(0,-(t.length+1)):e.href}function toCacheKey(e){return getRequestURL(e)}function urlsAreEqual(e,t){return expandURL(e).href==expandURL(t).href}function getPathComponents(e){return e.pathname.split("/").slice(1)}function getLastPathComponent(e){return getPathComponents(e).slice(-1)[0]}function getPrefix(e){return addTrailingSlash(e.origin+e.pathname)}function addTrailingSlash(e){return e.endsWith("/")?e:e+"/"}class FetchResponse{constructor(e){this.response=e}get succeeded(){return this.response.ok}get failed(){return!this.succeeded}get clientError(){return this.statusCode>=400&&this.statusCode<=499}get serverError(){return this.statusCode>=500&&this.statusCode<=599}get redirected(){return this.response.redirected}get location(){return expandURL(this.response.url)}get isHTML(){return this.contentType&&this.contentType.match(/^(?:text\/([^\s;,]+\b)?html|application\/xhtml\+xml)\b/)}get statusCode(){return this.response.status}get contentType(){return this.header("Content-Type")}get responseText(){return this.response.clone().text()}get responseHTML(){return this.isHTML?this.response.clone().text():Promise.resolve(void 0)}header(e){return this.response.headers.get(e)}}class LimitedSet extends Set{constructor(e){super();this.maxSize=e}add(e){if(this.size>=this.maxSize){const e=this.values();const t=e.next().value;this.delete(t)}super.add(e)}}const o=new LimitedSet(20);const a=window.fetch;function fetchWithTurboHeaders(e,t={}){const r=new Headers(t.headers||{});const s=uuid();o.add(s);r.append("X-Turbo-Request-Id",s);return a(e,{...t,headers:r})}function fetchMethodFromString(e){switch(e.toLowerCase()){case"get":return c.get;case"post":return c.post;case"put":return c.put;case"patch":return c.patch;case"delete":return c.delete}}const c={get:"get",post:"post",put:"put",patch:"patch",delete:"delete"};function fetchEnctypeFromString(e){switch(e.toLowerCase()){case l.multipart:return l.multipart;case l.plain:return l.plain;default:return l.urlEncoded}}const l={urlEncoded:"application/x-www-form-urlencoded",multipart:"multipart/form-data",plain:"text/plain"};class FetchRequest{abortController=new AbortController;#t=e=>{};constructor(e,t,r,s=new URLSearchParams,i=null,n=l.urlEncoded){const[o,a]=buildResourceAndBody(expandURL(r),t,s,n);this.delegate=e;this.url=o;this.target=i;this.fetchOptions={credentials:"same-origin",redirect:"follow",method:t.toUpperCase(),headers:{...this.defaultHeaders},body:a,signal:this.abortSignal,referrer:this.delegate.referrer?.href};this.enctype=n}get method(){return this.fetchOptions.method}set method(e){const t=this.isSafe?this.url.searchParams:this.fetchOptions.body||new FormData;const r=fetchMethodFromString(e)||c.get;this.url.search="";const[s,i]=buildResourceAndBody(this.url,r,t,this.enctype);this.url=s;this.fetchOptions.body=i;this.fetchOptions.method=r.toUpperCase()}get headers(){return this.fetchOptions.headers}set headers(e){this.fetchOptions.headers=e}get body(){return this.isSafe?this.url.searchParams:this.fetchOptions.body}set body(e){this.fetchOptions.body=e}get location(){return this.url}get params(){return this.url.searchParams}get entries(){return this.body?Array.from(this.body.entries()):[]}cancel(){this.abortController.abort()}async perform(){const{fetchOptions:e}=this;this.delegate.prepareRequest(this);const t=await this.#r(e);try{this.delegate.requestStarted(this);t.detail.fetchRequest?this.response=t.detail.fetchRequest.response:this.response=fetchWithTurboHeaders(this.url.href,e);const r=await this.response;return await this.receive(r)}catch(e){if(e.name!=="AbortError"){this.#s(e)&&this.delegate.requestErrored(this,e);throw e}}finally{this.delegate.requestFinished(this)}}async receive(e){const t=new FetchResponse(e);const r=dispatch("turbo:before-fetch-response",{cancelable:true,detail:{fetchResponse:t},target:this.target});r.defaultPrevented?this.delegate.requestPreventedHandlingResponse(this,t):t.succeeded?this.delegate.requestSucceededWithResponse(this,t):this.delegate.requestFailedWithResponse(this,t);return t}get defaultHeaders(){return{Accept:"text/html, application/xhtml+xml"}}get isSafe(){return isSafe(this.method)}get abortSignal(){return this.abortController.signal}acceptResponseType(e){this.headers.Accept=[e,this.headers.Accept].join(", ")}async#r(e){const t=new Promise((e=>this.#t=e));const r=dispatch("turbo:before-fetch-request",{cancelable:true,detail:{fetchOptions:e,url:this.url,resume:this.#t},target:this.target});this.url=r.detail.url;r.defaultPrevented&&await t;return r}#s(e){const t=dispatch("turbo:fetch-request-error",{target:this.target,cancelable:true,detail:{request:this,error:e}});return!t.defaultPrevented}}function isSafe(e){return fetchMethodFromString(e)==c.get}function buildResourceAndBody(e,t,r,s){const i=Array.from(r).length>0?new URLSearchParams(entriesExcludingFiles(r)):e.searchParams;return isSafe(t)?[mergeIntoURLSearchParams(e,i),null]:s==l.urlEncoded?[e,i]:[e,r]}function entriesExcludingFiles(e){const t=[];for(const[r,s]of e)s instanceof File||t.push([r,s]);return t}function mergeIntoURLSearchParams(e,t){const r=new URLSearchParams(entriesExcludingFiles(t));e.search=r.toString();return e}class AppearanceObserver{started=false;constructor(e,t){this.delegate=e;this.element=t;this.intersectionObserver=new IntersectionObserver(this.intersect)}start(){if(!this.started){this.started=true;this.intersectionObserver.observe(this.element)}}stop(){if(this.started){this.started=false;this.intersectionObserver.unobserve(this.element)}}intersect=e=>{const t=e.slice(-1)[0];t?.isIntersecting&&this.delegate.elementAppearedInViewport(this.element)}}class StreamMessage{static contentType="text/vnd.turbo-stream.html";static wrap(e){return typeof e=="string"?new this(createDocumentFragment(e)):e}constructor(e){this.fragment=importStreamElements(e)}}function importStreamElements(e){for(const t of e.querySelectorAll("turbo-stream")){const e=document.importNode(t,true);for(const t of e.templateElement.content.querySelectorAll("script"))t.replaceWith(activateScriptElement(t));t.replaceWith(e)}return e}const h=100;class PrefetchCache{#i=null;#n=null;get(e){if(this.#n&&this.#n.url===e&&this.#n.expire>Date.now())return this.#n.request}setLater(e,t,r){this.clear();this.#i=setTimeout((()=>{t.perform();this.set(e,t,r);this.#i=null}),h)}set(e,t,r){this.#n={url:e,request:t,expire:new Date((new Date).getTime()+r)}}clear(){this.#i&&clearTimeout(this.#i);this.#n=null}}const d=1e4;const u=new PrefetchCache;const m={initialized:"initialized",requesting:"requesting",waiting:"waiting",receiving:"receiving",stopping:"stopping",stopped:"stopped"};class FormSubmission{state=m.initialized;static confirmMethod(e){return Promise.resolve(confirm(e))}constructor(e,t,r,s=false){const i=getMethod(t,r);const n=getAction(getFormAction(t,r),i);const o=buildFormData(t,r);const a=getEnctype(t,r);this.delegate=e;this.formElement=t;this.submitter=r;this.fetchRequest=new FetchRequest(this,i,n,o,t,a);this.mustRedirect=s}get method(){return this.fetchRequest.method}set method(e){this.fetchRequest.method=e}get action(){return this.fetchRequest.url.toString()}set action(e){this.fetchRequest.url=expandURL(e)}get body(){return this.fetchRequest.body}get enctype(){return this.fetchRequest.enctype}get isSafe(){return this.fetchRequest.isSafe}get location(){return this.fetchRequest.url}async start(){const{initialized:e,requesting:t}=m;const r=getAttribute("data-turbo-confirm",this.submitter,this.formElement);if(typeof r==="string"){const e=typeof n.forms.confirm==="function"?n.forms.confirm:FormSubmission.confirmMethod;const t=await e(r,this.formElement,this.submitter);if(!t)return}if(this.state==e){this.state=t;return this.fetchRequest.perform()}}stop(){const{stopping:e,stopped:t}=m;if(this.state!=e&&this.state!=t){this.state=e;this.fetchRequest.cancel();return true}}prepareRequest(e){if(!e.isSafe){const t=getCookieValue(getMetaContent("csrf-param"))||getMetaContent("csrf-token");t&&(e.headers["X-CSRF-Token"]=t)}this.requestAcceptsTurboStreamResponse(e)&&e.acceptResponseType(StreamMessage.contentType)}requestStarted(e){this.state=m.waiting;this.submitter&&n.forms.submitter.beforeSubmit(this.submitter);this.setSubmitsWith();markAsBusy(this.formElement);dispatch("turbo:submit-start",{target:this.formElement,detail:{formSubmission:this}});this.delegate.formSubmissionStarted(this)}requestPreventedHandlingResponse(e,t){u.clear();this.result={success:t.succeeded,fetchResponse:t}}requestSucceededWithResponse(e,t){if(t.clientError||t.serverError)this.delegate.formSubmissionFailedWithResponse(this,t);else{u.clear();if(this.requestMustRedirect(e)&&responseSucceededWithoutRedirect(t)){const e=new Error("Form responses must redirect to another location");this.delegate.formSubmissionErrored(this,e)}else{this.state=m.receiving;this.result={success:true,fetchResponse:t};this.delegate.formSubmissionSucceededWithResponse(this,t)}}}requestFailedWithResponse(e,t){this.result={success:false,fetchResponse:t};this.delegate.formSubmissionFailedWithResponse(this,t)}requestErrored(e,t){this.result={success:false,error:t};this.delegate.formSubmissionErrored(this,t)}requestFinished(e){this.state=m.stopped;this.submitter&&n.forms.submitter.afterSubmit(this.submitter);this.resetSubmitterText();clearBusyState(this.formElement);dispatch("turbo:submit-end",{target:this.formElement,detail:{formSubmission:this,...this.result}});this.delegate.formSubmissionFinished(this)}setSubmitsWith(){if(this.submitter&&this.submitsWith)if(this.submitter.matches("button")){this.originalSubmitText=this.submitter.innerHTML;this.submitter.innerHTML=this.submitsWith}else if(this.submitter.matches("input")){const e=this.submitter;this.originalSubmitText=e.value;e.value=this.submitsWith}}resetSubmitterText(){if(this.submitter&&this.originalSubmitText)if(this.submitter.matches("button"))this.submitter.innerHTML=this.originalSubmitText;else if(this.submitter.matches("input")){const e=this.submitter;e.value=this.originalSubmitText}}requestMustRedirect(e){return!e.isSafe&&this.mustRedirect}requestAcceptsTurboStreamResponse(e){return!e.isSafe||hasAttribute("data-turbo-stream",this.submitter,this.formElement)}get submitsWith(){return this.submitter?.getAttribute("data-turbo-submits-with")}}function buildFormData(e,t){const r=new FormData(e);const s=t?.getAttribute("name");const i=t?.getAttribute("value");s&&r.append(s,i||"");return r}function getCookieValue(e){if(e!=null){const t=document.cookie?document.cookie.split("; "):[];const r=t.find((t=>t.startsWith(e)));if(r){const e=r.split("=").slice(1).join("=");return e?decodeURIComponent(e):void 0}}}function responseSucceededWithoutRedirect(e){return e.statusCode==200&&!e.redirected}function getFormAction(e,t){const r=typeof e.action==="string"?e.action:null;return t?.hasAttribute("formaction")?t.getAttribute("formaction")||"":e.getAttribute("action")||r||""}function getAction(e,t){const r=expandURL(e);isSafe(t)&&(r.search="");return r}function getMethod(e,t){const r=t?.getAttribute("formmethod")||e.getAttribute("method")||"";return fetchMethodFromString(r.toLowerCase())||c.get}function getEnctype(e,t){return fetchEnctypeFromString(t?.getAttribute("formenctype")||e.enctype)}class Snapshot{constructor(e){this.element=e}get activeElement(){return this.element.ownerDocument.activeElement}get children(){return[...this.element.children]}hasAnchor(e){return this.getElementForAnchor(e)!=null}getElementForAnchor(e){return e?this.element.querySelector(`[id='${e}'], a[name='${e}']`):null}get isConnected(){return this.element.isConnected}get firstAutofocusableElement(){return queryAutofocusableElement(this.element)}get permanentElements(){return queryPermanentElementsAll(this.element)}getPermanentElementById(e){return getPermanentElementById(this.element,e)}getPermanentElementMapForSnapshot(e){const t={};for(const r of this.permanentElements){const{id:s}=r;const i=e.getPermanentElementById(s);i&&(t[s]=[r,i])}return t}}function getPermanentElementById(e,t){return e.querySelector(`#${t}[data-turbo-permanent]`)}function queryPermanentElementsAll(e){return e.querySelectorAll("[id][data-turbo-permanent]")}class FormSubmitObserver{started=false;constructor(e,t){this.delegate=e;this.eventTarget=t}start(){if(!this.started){this.eventTarget.addEventListener("submit",this.submitCaptured,true);this.started=true}}stop(){if(this.started){this.eventTarget.removeEventListener("submit",this.submitCaptured,true);this.started=false}}submitCaptured=()=>{this.eventTarget.removeEventListener("submit",this.submitBubbled,false);this.eventTarget.addEventListener("submit",this.submitBubbled,false)};submitBubbled=e=>{if(!e.defaultPrevented){const t=e.target instanceof HTMLFormElement?e.target:void 0;const r=e.submitter||void 0;if(t&&submissionDoesNotDismissDialog(t,r)&&submissionDoesNotTargetIFrame(t,r)&&this.delegate.willSubmitForm(t,r)){e.preventDefault();e.stopImmediatePropagation();this.delegate.formSubmitted(t,r)}}}}function submissionDoesNotDismissDialog(e,t){const r=t?.getAttribute("formmethod")||e.getAttribute("method");return r!="dialog"}function submissionDoesNotTargetIFrame(e,t){const r=t?.getAttribute("formtarget")||e.getAttribute("target");return doesNotTargetIFrame(r)}class View{#o=e=>{};#a=e=>{};constructor(e,t){this.delegate=e;this.element=t}scrollToAnchor(e){const t=this.snapshot.getElementForAnchor(e);if(t){this.scrollToElement(t);this.focusElement(t)}else this.scrollToPosition({x:0,y:0})}scrollToAnchorFromLocation(e){this.scrollToAnchor(getAnchor(e))}scrollToElement(e){e.scrollIntoView()}focusElement(e){if(e instanceof HTMLElement)if(e.hasAttribute("tabindex"))e.focus();else{e.setAttribute("tabindex","-1");e.focus();e.removeAttribute("tabindex")}}scrollToPosition({x:e,y:t}){this.scrollRoot.scrollTo(e,t)}scrollToTop(){this.scrollToPosition({x:0,y:0})}get scrollRoot(){return window}async render(e){const{isPreview:t,shouldRender:r,willRender:s,newSnapshot:i}=e;const n=s;if(r)try{this.renderPromise=new Promise((e=>this.#o=e));this.renderer=e;await this.prepareToRenderSnapshot(e);const r=new Promise((e=>this.#a=e));const s={resume:this.#a,render:this.renderer.renderElement,renderMethod:this.renderer.renderMethod};const n=this.delegate.allowsImmediateRender(i,s);n||await r;await this.renderSnapshot(e);this.delegate.viewRenderedSnapshot(i,t,this.renderer.renderMethod);this.delegate.preloadOnLoadLinksForView(this.element);this.finishRenderingSnapshot(e)}finally{delete this.renderer;this.#o(void 0);delete this.renderPromise}else n&&this.invalidate(e.reloadReason)}invalidate(e){this.delegate.viewInvalidated(e)}async prepareToRenderSnapshot(e){this.markAsPreview(e.isPreview);await e.prepareToRender()}markAsPreview(e){e?this.element.setAttribute("data-turbo-preview",""):this.element.removeAttribute("data-turbo-preview")}markVisitDirection(e){this.element.setAttribute("data-turbo-visit-direction",e)}unmarkVisitDirection(){this.element.removeAttribute("data-turbo-visit-direction")}async renderSnapshot(e){await e.render()}finishRenderingSnapshot(e){e.finishRendering()}}class FrameView extends View{missing(){this.element.innerHTML='Content missing'}get snapshot(){return new Snapshot(this.element)}}class LinkInterceptor{constructor(e,t){this.delegate=e;this.element=t}start(){this.element.addEventListener("click",this.clickBubbled);document.addEventListener("turbo:click",this.linkClicked);document.addEventListener("turbo:before-visit",this.willVisit)}stop(){this.element.removeEventListener("click",this.clickBubbled);document.removeEventListener("turbo:click",this.linkClicked);document.removeEventListener("turbo:before-visit",this.willVisit)}clickBubbled=e=>{this.clickEventIsSignificant(e)?this.clickEvent=e:delete this.clickEvent};linkClicked=e=>{if(this.clickEvent&&this.clickEventIsSignificant(e)&&this.delegate.shouldInterceptLinkClick(e.target,e.detail.url,e.detail.originalEvent)){this.clickEvent.preventDefault();e.preventDefault();this.delegate.linkClickIntercepted(e.target,e.detail.url,e.detail.originalEvent)}delete this.clickEvent};willVisit=e=>{delete this.clickEvent};clickEventIsSignificant(e){const t=e.composed?e.target?.parentElement:e.target;const r=findLinkFromClickTarget(t)||t;return r instanceof Element&&r.closest("turbo-frame, html")==this.element}}class LinkClickObserver{started=false;constructor(e,t){this.delegate=e;this.eventTarget=t}start(){if(!this.started){this.eventTarget.addEventListener("click",this.clickCaptured,true);this.started=true}}stop(){if(this.started){this.eventTarget.removeEventListener("click",this.clickCaptured,true);this.started=false}}clickCaptured=()=>{this.eventTarget.removeEventListener("click",this.clickBubbled,false);this.eventTarget.addEventListener("click",this.clickBubbled,false)};clickBubbled=e=>{if(e instanceof MouseEvent&&this.clickEventIsSignificant(e)){const t=e.composedPath&&e.composedPath()[0]||e.target;const r=findLinkFromClickTarget(t);if(r&&doesNotTargetIFrame(r.target)){const t=getLocationForLink(r);if(this.delegate.willFollowLinkToLocation(r,t,e)){e.preventDefault();this.delegate.followedLinkToLocation(r,t)}}}};clickEventIsSignificant(e){return!(e.target&&e.target.isContentEditable||e.defaultPrevented||e.which>1||e.altKey||e.ctrlKey||e.metaKey||e.shiftKey)}}class FormLinkClickObserver{constructor(e,t){this.delegate=e;this.linkInterceptor=new LinkClickObserver(this,t)}start(){this.linkInterceptor.start()}stop(){this.linkInterceptor.stop()}canPrefetchRequestToLocation(e,t){return false}prefetchAndCacheRequestToLocation(e,t){}willFollowLinkToLocation(e,t,r){return this.delegate.willSubmitFormLinkToLocation(e,t,r)&&(e.hasAttribute("data-turbo-method")||e.hasAttribute("data-turbo-stream"))}followedLinkToLocation(e,t){const r=document.createElement("form");const s="hidden";for(const[e,i]of t.searchParams)r.append(Object.assign(document.createElement("input"),{type:s,name:e,value:i}));const i=Object.assign(t,{search:""});r.setAttribute("data-turbo","true");r.setAttribute("action",i.href);r.setAttribute("hidden","");const n=e.getAttribute("data-turbo-method");n&&r.setAttribute("method",n);const o=e.getAttribute("data-turbo-frame");o&&r.setAttribute("data-turbo-frame",o);const a=getVisitAction(e);a&&r.setAttribute("data-turbo-action",a);const c=e.getAttribute("data-turbo-confirm");c&&r.setAttribute("data-turbo-confirm",c);const l=e.hasAttribute("data-turbo-stream");l&&r.setAttribute("data-turbo-stream","");this.delegate.submittedFormLinkToLocation(e,t,r);document.body.appendChild(r);r.addEventListener("turbo:submit-end",(()=>r.remove()),{once:true});requestAnimationFrame((()=>r.requestSubmit()))}}class Bardo{static async preservingPermanentElements(e,t,r){const s=new this(e,t);s.enter();await r();s.leave()}constructor(e,t){this.delegate=e;this.permanentElementMap=t}enter(){for(const e in this.permanentElementMap){const[t,r]=this.permanentElementMap[e];this.delegate.enteringBardo(t,r);this.replaceNewPermanentElementWithPlaceholder(r)}}leave(){for(const e in this.permanentElementMap){const[t]=this.permanentElementMap[e];this.replaceCurrentPermanentElementWithClone(t);this.replacePlaceholderWithPermanentElement(t);this.delegate.leavingBardo(t)}}replaceNewPermanentElementWithPlaceholder(e){const t=createPlaceholderForPermanentElement(e);e.replaceWith(t)}replaceCurrentPermanentElementWithClone(e){const t=e.cloneNode(true);e.replaceWith(t)}replacePlaceholderWithPermanentElement(e){const t=this.getPlaceholderById(e.id);t?.replaceWith(e)}getPlaceholderById(e){return this.placeholders.find((t=>t.content==e))}get placeholders(){return[...document.querySelectorAll("meta[name=turbo-permanent-placeholder][content]")]}}function createPlaceholderForPermanentElement(e){const t=document.createElement("meta");t.setAttribute("name","turbo-permanent-placeholder");t.setAttribute("content",e.id);return t}class Renderer{#c=null;static renderElement(e,t){}constructor(e,t,r,s=true){this.currentSnapshot=e;this.newSnapshot=t;this.isPreview=r;this.willRender=s;this.renderElement=this.constructor.renderElement;this.promise=new Promise(((e,t)=>this.resolvingFunctions={resolve:e,reject:t}))}get shouldRender(){return true}get shouldAutofocus(){return true}get reloadReason(){}prepareToRender(){}render(){}finishRendering(){if(this.resolvingFunctions){this.resolvingFunctions.resolve();delete this.resolvingFunctions}}async preservingPermanentElements(e){await Bardo.preservingPermanentElements(this,this.permanentElementMap,e)}focusFirstAutofocusableElement(){if(this.shouldAutofocus){const e=this.connectedSnapshot.firstAutofocusableElement;e&&e.focus()}}enteringBardo(e){this.#c||e.contains(this.currentSnapshot.activeElement)&&(this.#c=this.currentSnapshot.activeElement)}leavingBardo(e){if(e.contains(this.#c)&&this.#c instanceof HTMLElement){this.#c.focus();this.#c=null}}get connectedSnapshot(){return this.newSnapshot.isConnected?this.newSnapshot:this.currentSnapshot}get currentElement(){return this.currentSnapshot.element}get newElement(){return this.newSnapshot.element}get permanentElementMap(){return this.currentSnapshot.getPermanentElementMapForSnapshot(this.newSnapshot)}get renderMethod(){return"replace"}}class FrameRenderer extends Renderer{static renderElement(e,t){const r=document.createRange();r.selectNodeContents(e);r.deleteContents();const s=t;const i=s.ownerDocument?.createRange();if(i){i.selectNodeContents(s);e.appendChild(i.extractContents())}}constructor(e,t,r,s,i,n=true){super(t,r,s,i,n);this.delegate=e}get shouldRender(){return true}async render(){await nextRepaint();this.preservingPermanentElements((()=>{this.loadFrameElement()}));this.scrollFrameIntoView();await nextRepaint();this.focusFirstAutofocusableElement();await nextRepaint();this.activateScriptElements()}loadFrameElement(){this.delegate.willRenderFrame(this.currentElement,this.newElement);this.renderElement(this.currentElement,this.newElement)}scrollFrameIntoView(){if(this.currentElement.autoscroll||this.newElement.autoscroll){const e=this.currentElement.firstElementChild;const t=readScrollLogicalPosition(this.currentElement.getAttribute("data-autoscroll-block"),"end");const r=readScrollBehavior(this.currentElement.getAttribute("data-autoscroll-behavior"),"auto");if(e){e.scrollIntoView({block:t,behavior:r});return true}}return false}activateScriptElements(){for(const e of this.newScriptElements){const t=activateScriptElement(e);e.replaceWith(t)}}get newScriptElements(){return this.currentElement.querySelectorAll("script")}}function readScrollLogicalPosition(e,t){return e=="end"||e=="start"||e=="center"||e=="nearest"?e:t}function readScrollBehavior(e,t){return e=="auto"||e=="smooth"?e:t}
+/**
+ * @typedef {object} ConfigHead
+ *
+ * @property {'merge' | 'append' | 'morph' | 'none'} [style]
+ * @property {boolean} [block]
+ * @property {boolean} [ignore]
+ * @property {function(Element): boolean} [shouldPreserve]
+ * @property {function(Element): boolean} [shouldReAppend]
+ * @property {function(Element): boolean} [shouldRemove]
+ * @property {function(Element, {added: Node[], kept: Element[], removed: Element[]}): void} [afterHeadMorphed]
+ */
+/**
+ * @typedef {object} ConfigCallbacks
+ *
+ * @property {function(Node): boolean} [beforeNodeAdded]
+ * @property {function(Node): void} [afterNodeAdded]
+ * @property {function(Element, Node): boolean} [beforeNodeMorphed]
+ * @property {function(Element, Node): void} [afterNodeMorphed]
+ * @property {function(Element): boolean} [beforeNodeRemoved]
+ * @property {function(Element): void} [afterNodeRemoved]
+ * @property {function(string, Element, "update" | "remove"): boolean} [beforeAttributeUpdated]
+ */
+/**
+ * @typedef {object} Config
+ *
+ * @property {'outerHTML' | 'innerHTML'} [morphStyle]
+ * @property {boolean} [ignoreActive]
+ * @property {boolean} [ignoreActiveValue]
+ * @property {boolean} [restoreFocus]
+ * @property {ConfigCallbacks} [callbacks]
+ * @property {ConfigHead} [head]
+ */
+/**
+ * @typedef {function} NoOp
+ *
+ * @returns {void}
+ */
+/**
+ * @typedef {object} ConfigHeadInternal
+ *
+ * @property {'merge' | 'append' | 'morph' | 'none'} style
+ * @property {boolean} [block]
+ * @property {boolean} [ignore]
+ * @property {(function(Element): boolean) | NoOp} shouldPreserve
+ * @property {(function(Element): boolean) | NoOp} shouldReAppend
+ * @property {(function(Element): boolean) | NoOp} shouldRemove
+ * @property {(function(Element, {added: Node[], kept: Element[], removed: Element[]}): void) | NoOp} afterHeadMorphed
+ */
+/**
+ * @typedef {object} ConfigCallbacksInternal
+ *
+ * @property {(function(Node): boolean) | NoOp} beforeNodeAdded
+ * @property {(function(Node): void) | NoOp} afterNodeAdded
+ * @property {(function(Node, Node): boolean) | NoOp} beforeNodeMorphed
+ * @property {(function(Node, Node): void) | NoOp} afterNodeMorphed
+ * @property {(function(Node): boolean) | NoOp} beforeNodeRemoved
+ * @property {(function(Node): void) | NoOp} afterNodeRemoved
+ * @property {(function(string, Element, "update" | "remove"): boolean) | NoOp} beforeAttributeUpdated
+ */
+/**
+ * @typedef {object} ConfigInternal
+ *
+ * @property {'outerHTML' | 'innerHTML'} morphStyle
+ * @property {boolean} [ignoreActive]
+ * @property {boolean} [ignoreActiveValue]
+ * @property {boolean} [restoreFocus]
+ * @property {ConfigCallbacksInternal} callbacks
+ * @property {ConfigHeadInternal} head
+ */
+/**
+ * @typedef {Object} IdSets
+ * @property {Set} persistentIds
+ * @property {Map>} idMap
+ */
+/**
+ * @typedef {Function} Morph
+ *
+ * @param {Element | Document} oldNode
+ * @param {Element | Node | HTMLCollection | Node[] | string | null} newContent
+ * @param {Config} [config]
+ * @returns {undefined | Node[]}
+ */
+/**
+ *
+ * @type {{defaults: ConfigInternal, morph: Morph}}
+ */var p=function(){
+/**
+ * @typedef {object} MorphContext
+ *
+ * @property {Element} target
+ * @property {Element} newContent
+ * @property {ConfigInternal} config
+ * @property {ConfigInternal['morphStyle']} morphStyle
+ * @property {ConfigInternal['ignoreActive']} ignoreActive
+ * @property {ConfigInternal['ignoreActiveValue']} ignoreActiveValue
+ * @property {ConfigInternal['restoreFocus']} restoreFocus
+ * @property {Map>} idMap
+ * @property {Set} persistentIds
+ * @property {ConfigInternal['callbacks']} callbacks
+ * @property {ConfigInternal['head']} head
+ * @property {HTMLDivElement} pantry
+ */
+const noOp=()=>{};
+/**
+ * Default configuration values, updatable by users now
+ * @type {ConfigInternal}
+ */const e={morphStyle:"outerHTML",callbacks:{beforeNodeAdded:noOp,afterNodeAdded:noOp,beforeNodeMorphed:noOp,afterNodeMorphed:noOp,beforeNodeRemoved:noOp,afterNodeRemoved:noOp,beforeAttributeUpdated:noOp},head:{style:"merge",shouldPreserve:e=>e.getAttribute("im-preserve")==="true",shouldReAppend:e=>e.getAttribute("im-re-append")==="true",shouldRemove:noOp,afterHeadMorphed:noOp},restoreFocus:true};
+/**
+ * Core idiomorph function for morphing one DOM tree to another
+ *
+ * @param {Element | Document} oldNode
+ * @param {Element | Node | HTMLCollection | Node[] | string | null} newContent
+ * @param {Config} [config]
+ * @returns {Promise | Node[]}
+ */function morph(e,r,o={}){e=i(e);const a=n(r);const c=s(e,a,o);const l=saveAndRestoreFocus(c,(()=>withHeadBlocking(c,e,a,(
+/** @param {MorphContext} ctx */r=>{if(r.morphStyle==="innerHTML"){t(r,e,a);return Array.from(e.childNodes)}return morphOuterHTML(r,e,a)}))));c.pantry.remove();return l}
+/**
+ * Morph just the outerHTML of the oldNode to the newContent
+ * We have to be careful because the oldNode could have siblings which need to be untouched
+ * @param {MorphContext} ctx
+ * @param {Element} oldNode
+ * @param {Element} newNode
+ * @returns {Node[]}
+ */function morphOuterHTML(e,r,s){const i=n(r);let o=Array.from(i.childNodes);const a=o.indexOf(r);const c=o.length-(a+1);t(e,i,s,r,r.nextSibling);o=Array.from(i.childNodes);return o.slice(a,o.length-c)}
+/**
+ * @param {MorphContext} ctx
+ * @param {Function} fn
+ * @returns {Promise | Node[]}
+ */function saveAndRestoreFocus(e,t){if(!e.config.restoreFocus)return t();let r=
+/** @type {HTMLInputElement|HTMLTextAreaElement|null} */document.activeElement;if(!(r instanceof HTMLInputElement||r instanceof HTMLTextAreaElement))return t();const{id:s,selectionStart:i,selectionEnd:n}=r;const o=t();if(s&&s!==document.activeElement?.id){r=e.target.querySelector(`#${s}`);r?.focus()}r&&!r.selectionEnd&&n&&r.setSelectionRange(i,n);return o}const t=function(){
+/**
+ * This is the core algorithm for matching up children. The idea is to use id sets to try to match up
+ * nodes as faithfully as possible. We greedily match, which allows us to keep the algorithm fast, but
+ * by using id sets, we are able to better match up with content deeper in the DOM.
+ *
+ * Basic algorithm:
+ * - for each node in the new content:
+ * - search self and siblings for an id set match, falling back to a soft match
+ * - if match found
+ * - remove any nodes up to the match:
+ * - pantry persistent nodes
+ * - delete the rest
+ * - morph the match
+ * - elsif no match found, and node is persistent
+ * - find its match by querying the old root (future) and pantry (past)
+ * - move it and its children here
+ * - morph it
+ * - else
+ * - create a new node from scratch as a last result
+ *
+ * @param {MorphContext} ctx the merge context
+ * @param {Element} oldParent the old content that we are merging the new content into
+ * @param {Element} newParent the parent element of the new content
+ * @param {Node|null} [insertionPoint] the point in the DOM we start morphing at (defaults to first child)
+ * @param {Node|null} [endPoint] the point in the DOM we stop morphing at (defaults to after last child)
+ */
+function morphChildren(t,s,i,n=null,o=null){if(s instanceof HTMLTemplateElement&&i instanceof HTMLTemplateElement){s=s.content;i=i.content}n||=s.firstChild;for(const a of i.childNodes){if(n&&n!=o){const s=e(t,a,n,o);if(s){s!==n&&removeNodesBetween(t,n,s);r(s,a,t);n=s.nextSibling;continue}}if(a instanceof Element&&t.persistentIds.has(a.id)){const e=moveBeforeById(s,a.id,n,t);r(e,a,t);n=e.nextSibling;continue}const i=createNode(s,a,n,t);i&&(n=i.nextSibling)}while(n&&n!=o){const e=n;n=n.nextSibling;removeNode(t,e)}}
+/**
+ * This performs the action of inserting a new node while handling situations where the node contains
+ * elements with persistent ids and possible state info we can still preserve by moving in and then morphing
+ *
+ * @param {Element} oldParent
+ * @param {Node} newChild
+ * @param {Node|null} insertionPoint
+ * @param {MorphContext} ctx
+ * @returns {Node|null}
+ */function createNode(e,t,s,i){if(i.callbacks.beforeNodeAdded(t)===false)return null;if(i.idMap.has(t)){const n=document.createElement(
+/** @type {Element} */t.tagName);e.insertBefore(n,s);r(n,t,i);i.callbacks.afterNodeAdded(n);return n}{const r=document.importNode(t,true);e.insertBefore(r,s);i.callbacks.afterNodeAdded(r);return r}}const e=function(){
+/**
+ * Scans forward from the startPoint to the endPoint looking for a match
+ * for the node. It looks for an id set match first, then a soft match.
+ * We abort softmatching if we find two future soft matches, to reduce churn.
+ * @param {Node} node
+ * @param {MorphContext} ctx
+ * @param {Node | null} startPoint
+ * @param {Node | null} endPoint
+ * @returns {Node | null}
+ */
+function findBestMatch(e,t,r,s){let i=null;let n=t.nextSibling;let o=0;let a=r;while(a&&a!=s){if(isSoftMatch(a,t)){if(isIdSetMatch(e,a,t))return a;i===null&&(e.idMap.has(a)||(i=a))}if(i===null&&n&&isSoftMatch(a,n)){o++;n=n.nextSibling;o>=2&&(i=void 0)}if(a.contains(document.activeElement))break;a=a.nextSibling}return i||null}
+/**
+ *
+ * @param {MorphContext} ctx
+ * @param {Node} oldNode
+ * @param {Node} newNode
+ * @returns {boolean}
+ */function isIdSetMatch(e,t,r){let s=e.idMap.get(t);let i=e.idMap.get(r);if(!i||!s)return false;for(const e of s)if(i.has(e))return true;return false}
+/**
+ *
+ * @param {Node} oldNode
+ * @param {Node} newNode
+ * @returns {boolean}
+ */function isSoftMatch(e,t){const r=/** @type {Element} */e;const s=/** @type {Element} */t;return r.nodeType===s.nodeType&&r.tagName===s.tagName&&(!r.id||r.id===s.id)}return findBestMatch}();
+/**
+ * Gets rid of an unwanted DOM node; strategy depends on nature of its reuse:
+ * - Persistent nodes will be moved to the pantry for later reuse
+ * - Other nodes will have their hooks called, and then are removed
+ * @param {MorphContext} ctx
+ * @param {Node} node
+ */function removeNode(e,t){if(e.idMap.has(t))moveBefore(e.pantry,t,null);else{if(e.callbacks.beforeNodeRemoved(t)===false)return;t.parentNode?.removeChild(t);e.callbacks.afterNodeRemoved(t)}}
+/**
+ * Remove nodes between the start and end nodes
+ * @param {MorphContext} ctx
+ * @param {Node} startInclusive
+ * @param {Node} endExclusive
+ * @returns {Node|null}
+ */function removeNodesBetween(e,t,r){
+/** @type {Node | null} */
+let s=t;while(s&&s!==r){let t=/** @type {Node} */s;s=s.nextSibling;removeNode(e,t)}return s}
+/**
+ * Search for an element by id within the document and pantry, and move it using moveBefore.
+ *
+ * @param {Element} parentNode - The parent node to which the element will be moved.
+ * @param {string} id - The ID of the element to be moved.
+ * @param {Node | null} after - The reference node to insert the element before.
+ * If `null`, the element is appended as the last child.
+ * @param {MorphContext} ctx
+ * @returns {Element} The found element
+ */function moveBeforeById(e,t,r,s){const i=
+/** @type {Element} - will always be found */
+s.target.querySelector(`#${t}`)||s.pantry.querySelector(`#${t}`);removeElementFromAncestorsIdMaps(i,s);moveBefore(e,i,r);return i}
+/**
+ * Removes an element from its ancestors' id maps. This is needed when an element is moved from the
+ * "future" via `moveBeforeId`. Otherwise, its erstwhile ancestors could be mistakenly moved to the
+ * pantry rather than being deleted, preventing their removal hooks from being called.
+ *
+ * @param {Element} element - element to remove from its ancestors' id maps
+ * @param {MorphContext} ctx
+ */function removeElementFromAncestorsIdMaps(e,t){const r=e.id;while(e=e.parentNode){let s=t.idMap.get(e);if(s){s.delete(r);s.size||t.idMap.delete(e)}}}
+/**
+ * Moves an element before another element within the same parent.
+ * Uses the proposed `moveBefore` API if available (and working), otherwise falls back to `insertBefore`.
+ * This is essentialy a forward-compat wrapper.
+ *
+ * @param {Element} parentNode - The parent node containing the after element.
+ * @param {Node} element - The element to be moved.
+ * @param {Node | null} after - The reference node to insert `element` before.
+ * If `null`, `element` is appended as the last child.
+ */function moveBefore(e,t,r){if(e.moveBefore)try{e.moveBefore(t,r)}catch(s){e.insertBefore(t,r)}else e.insertBefore(t,r)}return morphChildren}();const r=function(){
+/**
+ * @param {Node} oldNode root node to merge content into
+ * @param {Node} newContent new content to merge
+ * @param {MorphContext} ctx the merge context
+ * @returns {Node | null} the element that ended up in the DOM
+ */
+function morphNode(e,r,s){if(s.ignoreActive&&e===document.activeElement)return null;if(s.callbacks.beforeNodeMorphed(e,r)===false)return e;if(e instanceof HTMLHeadElement&&s.head.ignore);else if(e instanceof HTMLHeadElement&&s.head.style!=="morph")handleHeadElement(e,
+/** @type {HTMLHeadElement} */r,s);else{morphAttributes(e,r,s);ignoreValueOfActiveElement(e,s)||t(s,e,r)}s.callbacks.afterNodeMorphed(e,r);return e}
+/**
+ * syncs the oldNode to the newNode, copying over all attributes and
+ * inner element state from the newNode to the oldNode
+ *
+ * @param {Node} oldNode the node to copy attributes & state to
+ * @param {Node} newNode the node to copy attributes & state from
+ * @param {MorphContext} ctx the merge context
+ */function morphAttributes(e,t,r){let s=t.nodeType;if(s===1){const s=/** @type {Element} */e;const i=/** @type {Element} */t;const n=s.attributes;const o=i.attributes;for(const e of o)ignoreAttribute(e.name,s,"update",r)||s.getAttribute(e.name)!==e.value&&s.setAttribute(e.name,e.value);for(let e=n.length-1;0<=e;e--){const t=n[e];if(t&&!i.hasAttribute(t.name)){if(ignoreAttribute(t.name,s,"remove",r))continue;s.removeAttribute(t.name)}}ignoreValueOfActiveElement(s,r)||syncInputValue(s,i,r)}s!==8&&s!==3||e.nodeValue!==t.nodeValue&&(e.nodeValue=t.nodeValue)}
+/**
+ * NB: many bothans died to bring us information:
+ *
+ * https://github.com/patrick-steele-idem/morphdom/blob/master/src/specialElHandlers.js
+ * https://github.com/choojs/nanomorph/blob/master/lib/morph.jsL113
+ *
+ * @param {Element} oldElement the element to sync the input value to
+ * @param {Element} newElement the element to sync the input value from
+ * @param {MorphContext} ctx the merge context
+ */function syncInputValue(e,t,r){if(e instanceof HTMLInputElement&&t instanceof HTMLInputElement&&t.type!=="file"){let s=t.value;let i=e.value;syncBooleanAttribute(e,t,"checked",r);syncBooleanAttribute(e,t,"disabled",r);if(t.hasAttribute("value")){if(i!==s&&!ignoreAttribute("value",e,"update",r)){e.setAttribute("value",s);e.value=s}}else if(!ignoreAttribute("value",e,"remove",r)){e.value="";e.removeAttribute("value")}}else if(e instanceof HTMLOptionElement&&t instanceof HTMLOptionElement)syncBooleanAttribute(e,t,"selected",r);else if(e instanceof HTMLTextAreaElement&&t instanceof HTMLTextAreaElement){let s=t.value;let i=e.value;if(ignoreAttribute("value",e,"update",r))return;s!==i&&(e.value=s);e.firstChild&&e.firstChild.nodeValue!==s&&(e.firstChild.nodeValue=s)}}
+/**
+ * @param {Element} oldElement element to write the value to
+ * @param {Element} newElement element to read the value from
+ * @param {string} attributeName the attribute name
+ * @param {MorphContext} ctx the merge context
+ */function syncBooleanAttribute(e,t,r,s){const i=t[r],n=e[r];if(i!==n){const n=ignoreAttribute(r,e,"update",s);n||(e[r]=t[r]);i?n||e.setAttribute(r,""):ignoreAttribute(r,e,"remove",s)||e.removeAttribute(r)}}
+/**
+ * @param {string} attr the attribute to be mutated
+ * @param {Element} element the element that is going to be updated
+ * @param {"update" | "remove"} updateType
+ * @param {MorphContext} ctx the merge context
+ * @returns {boolean} true if the attribute should be ignored, false otherwise
+ */function ignoreAttribute(e,t,r,s){return!(e!=="value"||!s.ignoreActiveValue||t!==document.activeElement)||s.callbacks.beforeAttributeUpdated(e,t,r)===false}
+/**
+ * @param {Node} possibleActiveElement
+ * @param {MorphContext} ctx
+ * @returns {boolean}
+ */function ignoreValueOfActiveElement(e,t){return!!t.ignoreActiveValue&&e===document.activeElement&&e!==document.body}return morphNode}();
+/**
+ * @param {MorphContext} ctx
+ * @param {Element} oldNode
+ * @param {Element} newNode
+ * @param {function} callback
+ * @returns {Node[] | Promise}
+ */function withHeadBlocking(e,t,r,s){if(e.head.block){const i=t.querySelector("head");const n=r.querySelector("head");if(i&&n){const t=handleHeadElement(i,n,e);return Promise.all(t).then((()=>{const t=Object.assign(e,{head:{block:false,ignore:true}});return s(t)}))}}return s(e)}
+/**
+ * The HEAD tag can be handled specially, either w/ a 'merge' or 'append' style
+ *
+ * @param {Element} oldHead
+ * @param {Element} newHead
+ * @param {MorphContext} ctx
+ * @returns {Promise[]}
+ */function handleHeadElement(e,t,r){let s=[];let i=[];let n=[];let o=[];let a=new Map;for(const e of t.children)a.set(e.outerHTML,e);for(const t of e.children){let e=a.has(t.outerHTML);let s=r.head.shouldReAppend(t);let c=r.head.shouldPreserve(t);if(e||c)if(s)i.push(t);else{a.delete(t.outerHTML);n.push(t)}else if(r.head.style==="append"){if(s){i.push(t);o.push(t)}}else r.head.shouldRemove(t)!==false&&i.push(t)}o.push(...a.values());let c=[];for(const t of o){let i=/** @type {ChildNode} */document.createRange().createContextualFragment(t.outerHTML).firstChild;if(r.callbacks.beforeNodeAdded(i)!==false){if("href"in i&&i.href||"src"in i&&i.src){
+/** @type {(result?: any) => void} */let e;let t=new Promise((function(t){e=t}));i.addEventListener("load",(function(){e()}));c.push(t)}e.appendChild(i);r.callbacks.afterNodeAdded(i);s.push(i)}}for(const t of i)if(r.callbacks.beforeNodeRemoved(t)!==false){e.removeChild(t);r.callbacks.afterNodeRemoved(t)}r.head.afterHeadMorphed(e,{added:s,kept:n,removed:i});return c}const s=function(){
+/**
+ *
+ * @param {Element} oldNode
+ * @param {Element} newContent
+ * @param {Config} config
+ * @returns {MorphContext}
+ */
+function createMorphContext(e,t,r){const{persistentIds:s,idMap:i}=createIdMaps(e,t);const n=mergeDefaults(r);const o=n.morphStyle||"outerHTML";if(!["innerHTML","outerHTML"].includes(o))throw`Do not understand how to morph style ${o}`;return{target:e,newContent:t,config:n,morphStyle:o,ignoreActive:n.ignoreActive,ignoreActiveValue:n.ignoreActiveValue,restoreFocus:n.restoreFocus,idMap:i,persistentIds:s,pantry:createPantry(),callbacks:n.callbacks,head:n.head}}
+/**
+ * Deep merges the config object and the Idiomorph.defaults object to
+ * produce a final configuration object
+ * @param {Config} config
+ * @returns {ConfigInternal}
+ */function mergeDefaults(t){let r=Object.assign({},e);Object.assign(r,t);r.callbacks=Object.assign({},e.callbacks,t.callbacks);r.head=Object.assign({},e.head,t.head);return r}
+/**
+ * @returns {HTMLDivElement}
+ */function createPantry(){const e=document.createElement("div");e.hidden=true;document.body.insertAdjacentElement("afterend",e);return e}
+/**
+ * Returns all elements with an ID contained within the root element and its descendants
+ *
+ * @param {Element} root
+ * @returns {Element[]}
+ */function findIdElements(e){let t=Array.from(e.querySelectorAll("[id]"));e.id&&t.push(e);return t}
+/**
+ * A bottom-up algorithm that populates a map of Element -> IdSet.
+ * The idSet for a given element is the set of all IDs contained within its subtree.
+ * As an optimzation, we filter these IDs through the given list of persistent IDs,
+ * because we don't need to bother considering IDed elements that won't be in the new content.
+ *
+ * @param {Map>} idMap
+ * @param {Set} persistentIds
+ * @param {Element} root
+ * @param {Element[]} elements
+ */function populateIdMapWithTree(e,t,r,s){for(const i of s)if(t.has(i.id)){
+/** @type {Element|null} */
+let t=i;while(t){let s=e.get(t);if(s==null){s=new Set;e.set(t,s)}s.add(i.id);if(t===r)break;t=t.parentElement}}}
+/**
+ * This function computes a map of nodes to all ids contained within that node (inclusive of the
+ * node). This map can be used to ask if two nodes have intersecting sets of ids, which allows
+ * for a looser definition of "matching" than tradition id matching, and allows child nodes
+ * to contribute to a parent nodes matching.
+ *
+ * @param {Element} oldContent the old content that will be morphed
+ * @param {Element} newContent the new content to morph to
+ * @returns {IdSets}
+ */function createIdMaps(e,t){const r=findIdElements(e);const s=findIdElements(t);const i=createPersistentIds(r,s);
+/** @type {Map>} */let n=new Map;populateIdMapWithTree(n,i,e,r);const o=t.__idiomorphRoot||t;populateIdMapWithTree(n,i,o,s);return{persistentIds:i,idMap:n}}
+/**
+ * This function computes the set of ids that persist between the two contents excluding duplicates
+ *
+ * @param {Element[]} oldIdElements
+ * @param {Element[]} newIdElements
+ * @returns {Set}
+ */function createPersistentIds(e,t){let r=new Set;
+/** @type {Map} */let s=new Map;for(const{id:t,tagName:i}of e)s.has(t)?r.add(t):s.set(t,i);let i=new Set;for(const{id:e,tagName:n}of t)i.has(e)?r.add(e):s.get(e)===n&&i.add(e);for(const e of r)i.delete(e);return i}return createMorphContext}();const{normalizeElement:i,normalizeParent:n}=function(){
+/** @type {WeakSet} */
+const e=new WeakSet;
+/**
+ *
+ * @param {Element | Document} content
+ * @returns {Element}
+ */function normalizeElement(e){return e instanceof Document?e.documentElement:e}
+/**
+ *
+ * @param {null | string | Node | HTMLCollection | Node[] | Document & {generatedByIdiomorph:boolean}} newContent
+ * @returns {Element}
+ */function normalizeParent(t){if(t==null)return document.createElement("div");if(typeof t==="string")return normalizeParent(parseContent(t));if(e.has(/** @type {Element} */t))/** @type {Element} */
+return t;if(t instanceof Node){if(t.parentNode)return createDuckTypedParent(t);{const e=document.createElement("div");e.append(t);return e}}{const e=document.createElement("div");for(const r of[...t])e.append(r);return e}}
+/**
+ * Creates a fake duck-typed parent element to wrap a single node, without actually reparenting it.
+ * "If it walks like a duck, and quacks like a duck, then it must be a duck!" -- James Whitcomb Riley (1849–1916)
+ *
+ * @param {Node} newContent
+ * @returns {Element}
+ */function createDuckTypedParent(e){/** @type {Element} */
+/** @type {unknown} */
+return{childNodes:[e],querySelectorAll:t=>{const r=e.querySelectorAll(t);return e.matches(t)?[e,...r]:r},insertBefore:(t,r)=>e.parentNode.insertBefore(t,r),moveBefore:(t,r)=>e.parentNode.moveBefore(t,r),get __idiomorphRoot(){return e}}}
+/**
+ *
+ * @param {string} newContent
+ * @returns {Node | null | DocumentFragment}
+ */function parseContent(t){let r=new DOMParser;let s=t.replace(/