From b64108acde69ba98c6d7d376e9383f2585ab212b Mon Sep 17 00:00:00 2001 From: Jose Fernando Davila Date: Tue, 26 Aug 2025 18:37:27 -0600 Subject: [PATCH 1/5] poc turbo offline --- Gemfile | 2 +- Gemfile.lock | 419 +++++++------ app/javascript/application.js | 10 +- app/views/layouts/application.html.erb | 1 + app/views/service_worker/service_worker.js | 97 ---- .../service_worker/service_worker.js.erb | 109 ++++ config/importmap.rb | 3 +- vendor/javascript/turbo-offline-umd.js | 548 ++++++++++++++++++ vendor/javascript/turbo-offline.js | 534 +++++++++++++++++ vendor/javascript/turbo-offline.min.js | 6 + vendor/javascript/turbo-offline.min.js.map | 1 + vendor/javascript/turbo.min.js | 29 + vendor/javascript/turbo.min.js.map | 1 + 13 files changed, 1479 insertions(+), 281 deletions(-) delete mode 100644 app/views/service_worker/service_worker.js create mode 100644 app/views/service_worker/service_worker.js.erb create mode 100644 vendor/javascript/turbo-offline-umd.js create mode 100644 vendor/javascript/turbo-offline.js create mode 100644 vendor/javascript/turbo-offline.min.js create mode 100644 vendor/javascript/turbo-offline.min.js.map create mode 100644 vendor/javascript/turbo.min.js create mode 100644 vendor/javascript/turbo.min.js.map diff --git a/Gemfile b/Gemfile index 433c0415..1ed391ed 100644 --- a/Gemfile +++ b/Gemfile @@ -19,7 +19,7 @@ gem "tailwindcss-rails", "~> 2.6" # Hotwire gem "stimulus-rails" -gem "turbo-rails" +gem "turbo-rails", github: "rosa/turbo-rails", branch: "offline-cache" # Deployment gem "kamal", require: false diff --git a/Gemfile.lock b/Gemfile.lock index b1eed6b4..a8cfdb50 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,12 @@ +GIT + remote: https://github.com/rosa/turbo-rails.git + revision: 25538ece2934237200c36e5ef49cff932463aed9 + branch: offline-cache + specs: + turbo-rails (2.0.16) + actionpack (>= 7.1.0) + railties (>= 7.1.0) + GIT remote: https://github.com/ruby/net-pop.git revision: e8d0afe2773b9eb6a23c39e9e437f6fc0fc7c733 @@ -8,7 +17,7 @@ GIT GEM remote: https://rubygems.org/ specs: - action_policy (0.7.1) + action_policy (0.7.5) ruby-next-core (>= 1.0) actioncable (8.0.2.1) actionpack (= 8.0.2.1) @@ -61,9 +70,9 @@ GEM globalid (>= 0.3.6) activemodel (8.0.2.1) activesupport (= 8.0.2.1) - activemodel-serializers-xml (1.0.2) - activemodel (> 5.x) - activesupport (> 5.x) + activemodel-serializers-xml (1.0.3) + activemodel (>= 5.0.0.a) + activesupport (>= 5.0.0.a) builder (~> 3.1) activerecord (8.0.2.1) activemodel (= 8.0.2.1) @@ -96,10 +105,11 @@ GEM annotate (2.6.5) activerecord (>= 2.3.0) rake (>= 0.8.7) - appsignal (3.13.0) - rack - ast (2.4.2) - avo (3.20.3) + appsignal (4.6.0) + logger + rack (>= 2.0.0) + ast (2.4.3) + avo (3.23.1) actionview (>= 6.1) active_link_to activerecord (>= 6.1) @@ -116,21 +126,24 @@ GEM view_component (>= 3.7.0) zeitwerk (>= 2.6.12) avo-heroicons (0.1.1) - aws-eventstream (1.3.0) - aws-partitions (1.968.0) - aws-sdk-core (3.201.5) + aws-eventstream (1.4.0) + aws-partitions (1.1151.0) + aws-sdk-core (3.231.0) aws-eventstream (~> 1, >= 1.3.0) - aws-partitions (~> 1, >= 1.651.0) + aws-partitions (~> 1, >= 1.992.0) aws-sigv4 (~> 1.9) + base64 + bigdecimal jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.88.0) - aws-sdk-core (~> 3, >= 3.201.0) + logger + aws-sdk-kms (1.111.0) + aws-sdk-core (~> 3, >= 3.231.0) aws-sigv4 (~> 1.5) - aws-sdk-s3 (1.159.0) - aws-sdk-core (~> 3, >= 3.201.0) + aws-sdk-s3 (1.198.0) + aws-sdk-core (~> 3, >= 3.231.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.5) - aws-sigv4 (1.9.1) + aws-sigv4 (1.12.1) aws-eventstream (~> 1, >= 1.0.2) axiom-types (0.1.1) descendants_tracker (~> 0.0.4) @@ -157,17 +170,17 @@ GEM bindex (0.8.1) binding_of_caller (1.0.1) debug_inspector (>= 1.2.0) - bootsnap (1.18.4) + bootsnap (1.18.6) msgpack (~> 1.2) - brakeman (6.2.1) + brakeman (7.1.0) racc builder (3.3.0) bundle-audit (0.1.0) bundler-audit - bundler-audit (0.9.1) + bundler-audit (0.9.2) bundler (>= 1.2.0, < 3) thor (~> 1.0) - byebug (11.1.3) + byebug (12.0.0) capybara (3.40.0) addressable matrix @@ -186,23 +199,23 @@ GEM concurrent-ruby (1.3.5) connection_pool (2.5.3) crass (1.0.6) - csv (3.3.0) - cuprite (0.15.1) + csv (3.3.5) + cuprite (0.17) capybara (~> 3.0) - ferrum (~> 0.15.0) - database_consistency (1.7.24) + ferrum (~> 0.17.0) + database_consistency (2.0.5) activerecord (>= 3.2) date (3.4.1) - debug (1.9.2) + debug (1.11.0) irb (~> 1.10) reline (>= 0.3.8) debug_inspector (1.2.0) descendants_tracker (0.0.4) thread_safe (~> 0.3, >= 0.3.1) - diff-lcs (1.5.1) + diff-lcs (1.6.2) docile (1.4.1) - dotenv (3.1.2) - draper (4.0.2) + dotenv (3.1.8) + draper (4.0.4) actionpack (>= 5.0) activemodel (>= 5.0) activemodel-serializers-xml (>= 1.0) @@ -210,27 +223,29 @@ GEM request_store (>= 1.0) ruby2_keywords drb (2.2.3) - dry-configurable (1.2.0) - dry-core (~> 1.0, < 2) + dry-configurable (1.3.0) + dry-core (~> 1.1) zeitwerk (~> 2.6) - dry-core (1.0.1) + dry-core (1.1.0) concurrent-ruby (~> 1.0) + logger zeitwerk (~> 2.6) - dry-inflector (1.1.0) - dry-initializer (3.1.1) - dry-logic (1.5.0) + dry-inflector (1.2.0) + dry-initializer (3.2.0) + dry-logic (1.6.0) + bigdecimal concurrent-ruby (~> 1.0) - dry-core (~> 1.0, < 2) + dry-core (~> 1.1) zeitwerk (~> 2.6) - dry-schema (1.13.4) + dry-schema (1.14.1) concurrent-ruby (~> 1.0) dry-configurable (~> 1.0, >= 1.0.1) - dry-core (~> 1.0, < 2) - dry-initializer (~> 3.0) - dry-logic (>= 1.4, < 2) - dry-types (>= 1.7, < 2) + dry-core (~> 1.1) + dry-initializer (~> 3.2) + dry-logic (~> 1.5) + dry-types (~> 1.8) zeitwerk (~> 2.6) - dry-types (1.7.2) + dry-types (1.8.3) bigdecimal (~> 3.0) concurrent-ruby (~> 1.0) dry-core (~> 1.0) @@ -238,7 +253,8 @@ GEM dry-logic (~> 1.4) zeitwerk (~> 2.6) ed25519 (1.4.0) - erb_lint (0.6.0) + erb (5.0.2) + erb_lint (0.9.0) activesupport better_html (>= 2.0.1) parser (>= 2.7.1.4) @@ -246,24 +262,29 @@ GEM rubocop (>= 1) smart_properties erubi (1.13.1) - et-orbi (1.2.11) + et-orbi (1.3.0) tzinfo - factory_bot (6.4.6) - activesupport (>= 5.0.0) - factory_bot_rails (6.4.3) - factory_bot (~> 6.4) - railties (>= 5.0.0) - faker (3.4.2) + factory_bot (6.5.5) + activesupport (>= 6.1.0) + factory_bot_rails (6.5.0) + factory_bot (~> 6.5) + railties (>= 6.1.0) + faker (3.5.2) i18n (>= 1.8.11, < 2) - ferrum (0.15) + ferrum (0.17.1) addressable (~> 2.5) + base64 (~> 0.2) concurrent-ruby (~> 1.1) webrick (~> 1.7) websocket-driver (~> 0.7) - ffi (1.17.0-aarch64-linux-gnu) - ffi (1.17.0-arm64-darwin) - ffi (1.17.0-x86_64-darwin) - ffi (1.17.0-x86_64-linux-gnu) + ffi (1.17.2-aarch64-linux-gnu) + ffi (1.17.2-aarch64-linux-musl) + ffi (1.17.2-arm-linux-gnu) + ffi (1.17.2-arm-linux-musl) + ffi (1.17.2-arm64-darwin) + ffi (1.17.2-x86_64-darwin) + ffi (1.17.2-x86_64-linux-gnu) + ffi (1.17.2-x86_64-linux-musl) flay (2.13.3) erubi (~> 1.10) path_expander (~> 1.0) @@ -275,7 +296,7 @@ GEM sexp_processor (~> 4.8) friendly_id (5.5.1) activerecord (>= 4.0.0) - fugit (1.11.1) + fugit (1.11.2) et-orbi (~> 1, >= 1.2.11) raabro (~> 1.4) fuubar (2.5.1) @@ -283,32 +304,33 @@ GEM ruby-progressbar (~> 1.4) globalid (1.2.1) activesupport (>= 6.1) - httparty (0.22.0) + httparty (0.23.1) csv mini_mime (>= 1.0.0) multi_xml (>= 0.5.2) i18n (1.14.7) concurrent-ruby (~> 1.0) ice_nine (0.11.2) - image_processing (1.13.0) - mini_magick (>= 4.9.5, < 5) + image_processing (1.14.0) + mini_magick (>= 4.9.5, < 6) ruby-vips (>= 2.0.17, < 3) - importmap-rails (2.0.1) + importmap-rails (2.2.2) actionpack (>= 6.0.0) activesupport (>= 6.0.0) railties (>= 6.0.0) - inline_svg (1.9.0) + inline_svg (1.10.0) activesupport (>= 3.0) nokogiri (>= 1.6) - io-console (0.7.2) - irb (1.14.0) + io-console (0.8.1) + irb (1.15.2) + pp (>= 0.6.0) rdoc (>= 4.0.0) reline (>= 0.4.2) jmespath (1.6.2) - json (2.7.2) - jwt (2.8.2) + json (2.13.2) + jwt (3.1.2) base64 - kamal (2.6.1) + kamal (2.7.0) activesupport (>= 7.0) base64 (~> 0.2) bcrypt_pbkdf (~> 1.0) @@ -319,10 +341,11 @@ GEM sshkit (>= 1.23.0, < 2.0) thor (~> 1.3) zeitwerk (>= 2.6.18, < 3.0) - language_server-protocol (3.17.0.3) - launchy (3.0.1) + language_server-protocol (3.17.0.5) + launchy (3.1.1) addressable (~> 2.8) childprocess (~> 5.0) + logger (~> 1.6) letter_opener (1.10.0) launchy (>= 2.2, < 4) lint_roller (1.1.0) @@ -353,25 +376,32 @@ GEM net-imap net-pop net-smtp - mailpace-rails (0.3.2) + mailpace-rails (0.4.3) + actionmailbox (>= 6.0.0) actionmailer (>= 6.0.0) + activestorage (>= 6.0.0) httparty (>= 0.18.1) marcel (1.0.4) - matrix (0.4.2) + matrix (0.4.3) meta-tags (2.22.1) actionpack (>= 6.0.0, < 8.1) method_source (1.1.0) - mini_magick (4.13.2) + mini_magick (5.3.1) + logger mini_mime (1.1.5) minitest (5.25.5) - mission_control-jobs (0.3.1) - importmap-rails + 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) - rails (>= 7.1) + railties (>= 7.1) stimulus-rails turbo-rails - msgpack (1.7.2) - multi_xml (0.7.1) + msgpack (1.8.0) + multi_xml (0.7.2) bigdecimal (~> 3.1) net-imap (0.4.22) date @@ -382,59 +412,70 @@ GEM net-ssh (>= 2.6.5, < 8.0.0) net-sftp (4.0.0) net-ssh (>= 5.0.0, < 8.0.0) - net-smtp (0.5.0) + net-smtp (0.5.1) net-protocol net-ssh (7.3.0) - nio4r (2.7.3) + nio4r (2.7.4) nokogiri (1.18.9-aarch64-linux-gnu) racc (~> 1.4) + nokogiri (1.18.9-aarch64-linux-musl) + racc (~> 1.4) + nokogiri (1.18.9-arm-linux-gnu) + racc (~> 1.4) + nokogiri (1.18.9-arm-linux-musl) + racc (~> 1.4) nokogiri (1.18.9-arm64-darwin) racc (~> 1.4) nokogiri (1.18.9-x86_64-darwin) racc (~> 1.4) nokogiri (1.18.9-x86_64-linux-gnu) racc (~> 1.4) - noticed (2.4.1) + nokogiri (1.18.9-x86_64-linux-musl) + racc (~> 1.4) + noticed (2.8.0) rails (>= 6.1.0) - openssl (3.2.0) - ostruct (0.6.1) - pagy (9.3.4) - parallel (1.26.3) - parser (3.3.4.2) + openssl (3.3.0) + ostruct (0.6.3) + pagy (9.4.0) + parallel (1.27.0) + parser (3.3.9.0) ast (~> 2.4.1) racc - path_expander (1.1.2) + path_expander (1.1.3) + pp (0.6.2) + prettyprint + prettyprint (0.2.0) + prism (1.4.0) prop_initializer (0.2.0) zeitwerk (>= 2.6.18) - propshaft (0.9.1) + propshaft (1.2.1) actionpack (>= 7.0.0) activesupport (>= 7.0.0) rack - railties (>= 7.0.0) - pry (0.14.2) + pry (0.15.2) coderay (~> 1.1) method_source (~> 1.0) - pry-byebug (3.10.1) - byebug (~> 11.0) - pry (>= 0.13, < 0.15) - psych (5.1.2) + pry-byebug (3.11.0) + byebug (~> 12.0) + pry (>= 0.13, < 0.16) + psych (5.2.6) + date stringio - public_suffix (6.0.1) - puma (6.4.3) + public_suffix (6.0.2) + puma (6.6.1) nio4r (~> 2.0) raabro (1.4.0) racc (1.8.1) rack (3.2.0) - rack-mini-profiler (3.3.1) + rack-mini-profiler (4.0.1) rack (>= 1.2.0) rack-session (2.1.1) base64 (>= 0.1.0) rack (>= 3.0.0) rack-test (2.2.0) rack (>= 1.3) - rackup (2.1.0) + rackup (2.2.1) rack (>= 3) - webrick (~> 1.8) rails (8.0.2.1) actioncable (= 8.0.2.1) actionmailbox (= 8.0.2.1) @@ -469,123 +510,135 @@ GEM thor (~> 1.0, >= 1.2.2) zeitwerk (~> 2.6) rainbow (3.1.1) - rake (13.2.1) - ransack (4.2.1) + rake (13.3.0) + ransack (4.3.0) activerecord (>= 6.1.5) activesupport (>= 6.1.5) i18n - rdoc (6.7.0) + rdoc (6.14.2) + erb psych (>= 4.0.0) - reek (6.3.0) - dry-schema (~> 1.13.0) + reek (6.5.0) + dry-schema (~> 1.13) + logger (~> 1.6) parser (~> 3.3.0) rainbow (>= 2.0, < 4.0) rexml (~> 3.1) - regexp_parser (2.9.2) - reline (0.5.10) + regexp_parser (2.11.2) + reline (0.6.2) io-console (~> 0.5) request_store (1.7.0) rack (>= 1.4) - rexml (3.4.1) - rouge (4.3.0) + rexml (3.4.2) + rouge (4.6.0) rqrcode (2.2.0) chunky_png (~> 1.0) rqrcode_core (~> 1.0) rqrcode_core (1.2.0) - rspec (3.13.0) + rspec (3.13.1) rspec-core (~> 3.13.0) rspec-expectations (~> 3.13.0) rspec-mocks (~> 3.13.0) - rspec-core (3.13.0) + rspec-core (3.13.5) rspec-support (~> 3.13.0) - rspec-expectations (3.13.2) + rspec-expectations (3.13.5) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) rspec-instafail (1.0.0) rspec - rspec-mocks (3.13.1) + rspec-mocks (3.13.5) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-rails (6.1.4) - actionpack (>= 6.1) - activesupport (>= 6.1) - railties (>= 6.1) + rspec-rails (8.0.2) + actionpack (>= 7.2) + activesupport (>= 7.2) + railties (>= 7.2) rspec-core (~> 3.13) rspec-expectations (~> 3.13) rspec-mocks (~> 3.13) rspec-support (~> 3.13) rspec-retry (0.6.2) rspec-core (> 3.3) - rspec-support (3.13.1) - rubocop (1.65.1) + rspec-support (3.13.5) + rubocop (1.75.8) json (~> 2.3) - language_server-protocol (>= 3.17.0) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) parallel (~> 1.10) parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 2.4, < 3.0) - rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.31.1, < 2.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.44.0, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.32.1) - parser (>= 3.3.1.0) - rubocop-capybara (2.21.0) - rubocop (~> 1.41) - rubocop-performance (1.21.1) - rubocop (>= 1.48.1, < 2.0) - rubocop-ast (>= 1.31.1, < 2.0) - rubocop-rails (2.25.1) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.46.0) + parser (>= 3.3.7.2) + prism (~> 1.4) + rubocop-capybara (2.22.1) + lint_roller (~> 1.1) + rubocop (~> 1.72, >= 1.72.1) + rubocop-performance (1.25.0) + lint_roller (~> 1.1) + rubocop (>= 1.75.0, < 2.0) + rubocop-ast (>= 1.38.0, < 2.0) + rubocop-rails (2.33.3) activesupport (>= 4.2.0) + lint_roller (~> 1.1) rack (>= 1.1) - rubocop (>= 1.33.0, < 2.0) - rubocop-ast (>= 1.31.1, < 2.0) - rubocop-rspec (3.0.4) - rubocop (~> 1.61) - rubocop-rspec_rails (2.30.0) - rubocop (~> 1.61) - rubocop-rspec (~> 3, >= 3.0.1) - ruby-next-core (1.0.3) + rubocop (>= 1.75.0, < 2.0) + rubocop-ast (>= 1.44.0, < 2.0) + rubocop-rspec (3.6.0) + lint_roller (~> 1.1) + rubocop (~> 1.72, >= 1.72.1) + rubocop-rspec_rails (2.31.0) + lint_roller (~> 1.1) + rubocop (~> 1.72, >= 1.72.1) + rubocop-rspec (~> 3.5) + ruby-next-core (1.1.2) ruby-progressbar (1.13.0) - ruby-vips (2.2.2) + ruby-vips (2.2.5) ffi (~> 1.12) logger ruby2_keywords (0.0.5) ruby_parser (3.21.1) racc (~> 1.5) sexp_processor (~> 4.16) - rubycritic (4.9.0) + rubycritic (4.10.0) flay (~> 2.13) flog (~> 4.7) launchy (>= 2.5.2) - parser (>= 3.2.2.1) + parser (>= 3.3.0.5) rainbow (~> 3.1.1) - reek (~> 6.0, < 7.0) + reek (~> 6.5.0, < 7.0) rexml - ruby_parser (~> 3.20) + ruby_parser (~> 3.21) simplecov (>= 0.22.0) tty-which (~> 0.5.0) virtus (~> 2.0) securerandom (0.4.1) - sexp_processor (4.17.2) + sexp_processor (4.17.3) simplecov (0.22.0) docile (~> 1.1) simplecov-html (~> 0.11) simplecov_json_formatter (~> 0.1) - simplecov-html (0.12.3) + simplecov-html (0.13.2) simplecov_json_formatter (0.1.4) smart_properties (1.17.0) - solid_queue (1.1.5) + 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-aarch64-linux-gnu) - sqlite3 (2.6.0-arm64-darwin) - sqlite3 (2.6.0-x86_64-darwin) - sqlite3 (2.6.0-x86_64-linux-gnu) + thor (>= 1.3.1) + sqlite3 (2.7.3-aarch64-linux-gnu) + sqlite3 (2.7.3-aarch64-linux-musl) + sqlite3 (2.7.3-arm-linux-gnu) + sqlite3 (2.7.3-arm-linux-musl) + sqlite3 (2.7.3-arm64-darwin) + sqlite3 (2.7.3-x86_64-darwin) + sqlite3 (2.7.3-x86_64-linux-gnu) + sqlite3 (2.7.3-x86_64-linux-musl) sshkit (1.24.0) base64 logger @@ -593,44 +646,44 @@ GEM net-sftp (>= 2.1.2) net-ssh (>= 2.8.0) ostruct - standard (1.40.0) + standard (1.50.0) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.0) - rubocop (~> 1.65.0) + rubocop (~> 1.75.5) standard-custom (~> 1.0.0) - standard-performance (~> 1.4) + standard-performance (~> 1.8) standard-custom (1.0.2) lint_roller (~> 1.0) rubocop (~> 1.50) - standard-performance (1.4.0) + standard-performance (1.8.0) lint_roller (~> 1.1) - rubocop-performance (~> 1.21.0) + rubocop-performance (~> 1.25.0) stimulus-rails (1.3.4) railties (>= 6.0.0) - stringio (3.1.1) - tailwindcss-rails (2.7.3-aarch64-linux) + stringio (3.1.7) + tailwindcss-rails (2.7.9-aarch64-linux) + railties (>= 7.0.0) + tailwindcss-rails (2.7.9-arm-linux) railties (>= 7.0.0) - tailwindcss-rails (2.7.3-arm64-darwin) + tailwindcss-rails (2.7.9-arm64-darwin) railties (>= 7.0.0) - tailwindcss-rails (2.7.3-x86_64-darwin) + tailwindcss-rails (2.7.9-x86_64-darwin) railties (>= 7.0.0) - tailwindcss-rails (2.7.3-x86_64-linux) + tailwindcss-rails (2.7.9-x86_64-linux) railties (>= 7.0.0) - thor (1.3.2) + thor (1.4.0) thread_safe (0.3.6) timecop (0.9.10) - timeliness (0.5.2) + timeliness (0.5.3) timeout (0.4.3) tty-which (0.5.0) - turbo-rails (2.0.6) - actionpack (>= 6.0.0) - activejob (>= 6.0.0) - railties (>= 6.0.0) turbo_power (0.7.0) turbo-rails (>= 1.3.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - unicode-display_width (2.5.0) + unicode-display_width (3.1.5) + unicode-emoji (~> 4.0, >= 4.0.4) + unicode-emoji (4.0.4) uri (1.0.3) useragent (0.16.11) validate_url (1.0.15) @@ -639,10 +692,9 @@ GEM validates_timeliness (8.0.0) activemodel (>= 8.0.0, < 9) timeliness (>= 0.3.10, < 1) - view_component (3.23.2) - activesupport (>= 5.2.0, < 8.1) + view_component (4.0.2) + activesupport (>= 7.1.0, < 8.1) concurrent-ruby (~> 1) - method_source (~> 1.0) virtus (2.0.0) axiom-types (~> 0.1) coercible (~> 1.0) @@ -652,25 +704,30 @@ GEM activemodel (>= 6.0.0) bindex (>= 0.4.0) railties (>= 6.0.0) - web-push (3.0.1) - jwt (~> 2.0) + web-push (3.0.2) + jwt (~> 3.0) openssl (~> 3.0) - webrick (1.8.2) - websocket-driver (0.7.6) + webrick (1.9.1) + websocket-driver (0.8.0) + base64 websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.6.18) + zeitwerk (2.7.3) PLATFORMS aarch64-linux - arm64-darwin-21 - arm64-darwin-22 - arm64-darwin-23 - arm64-darwin-24 - x86_64-darwin-24 + aarch64-linux-gnu + aarch64-linux-musl + arm-linux + arm-linux-gnu + arm-linux-musl + arm64-darwin + x86_64-darwin x86_64-linux + x86_64-linux-gnu + x86_64-linux-musl DEPENDENCIES action_policy @@ -738,7 +795,7 @@ DEPENDENCIES stimulus-rails tailwindcss-rails (~> 2.6) timecop - turbo-rails + turbo-rails! tzinfo-data validate_url validates_timeliness (~> 8.0) diff --git a/app/javascript/application.js b/app/javascript/application.js index 73337f77..cc8df15b 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -1,6 +1,14 @@ // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails -import '@hotwired/turbo-rails' +// import '@hotwired/turbo-rails' +import { Turbo } from "@hotwired/turbo-rails" import 'controllers' import 'custom/companion' import 'trix' import '@rails/actiontext' + + +Turbo.offline.start("/service-worker.js", { + scope: "/", + type: "classic", + native: true +}) diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 80c321b2..696a659c 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -9,6 +9,7 @@ + "> diff --git a/app/views/service_worker/service_worker.js b/app/views/service_worker/service_worker.js deleted file mode 100644 index e9f43301..00000000 --- a/app/views/service_worker/service_worker.js +++ /dev/null @@ -1,97 +0,0 @@ -/* global self, clients, importScripts, workbox */ - -importScripts( - 'https://storage.googleapis.com/workbox-cdn/releases/6.4.1/workbox-sw.js' -) - -// ------------- Push Notifications ------------- - -self.addEventListener('push', async (event) => { - let data - try { - data = await event.data.json() - } catch (e) { - data = { title: event.data.text(), body: event.data.text(), icon: 'pwa_home_screen_icon.png' } - } - - const title = data.title - const options = { - body: data.body, - icon: data.icon, - data - } - - event.waitUntil( - self.registration.showNotification(title, options) - ) -}) - -self.addEventListener('notificationclick', function (event) { - event.notification.close() - if (!event.notification.data.path) return - - event.waitUntil( - clients.matchAll({ type: 'window' }).then((clientList) => { - for (let i = 0; i < clientList.length; i++) { - const client = clientList[i] - const clientPath = (new URL(client.url)).pathname - - if (clientPath === event.notification.data.path && 'focus' in client) { - return client.focus() - } - } - - if (clients.openWindow) { - return clients.openWindow(event.notification.data.path) - } - }) - ) -}) - -// ------------- Request handling ------------- - -const { CacheFirst, NetworkFirst } = workbox.strategies -const { registerRoute } = workbox.routing - -// For assets (scripts and images), cache first -registerRoute( - ({ request }) => request.destination === 'script' || - request.destination === 'style', - new CacheFirst({ - cacheName: 'assets-styles-and-scripts' - }) -) - -registerRoute( - ({ request }) => request.destination === 'image', - new CacheFirst({ - cacheName: 'assets-images' - }) -) - -// For pages, network first -registerRoute( - ({ request, url }) => request.destination === 'document' || - request.destination === '', - new NetworkFirst() -) - -// Offline fallback - -const { warmStrategyCache } = workbox.recipes -const { setCatchHandler } = workbox.routing -const strategy = new NetworkFirst() -const urls = ['/offline.html'] - -// Warm the runtime cache with a list of asset URLs -warmStrategyCache({ urls, strategy }) - -// Trigger a 'catch' handler when any of the other routes fail to generate a response -setCatchHandler(async ({ event }) => { - switch (event.request.destination) { - case 'document': - return strategy.handle({ event, request: urls[0] }) - default: - return Response.error() - } -}) diff --git a/app/views/service_worker/service_worker.js.erb b/app/views/service_worker/service_worker.js.erb new file mode 100644 index 00000000..59b2d390 --- /dev/null +++ b/app/views/service_worker/service_worker.js.erb @@ -0,0 +1,109 @@ +/* global self, clients, importScripts, workbox */ + +importScripts( + 'https://storage.googleapis.com/workbox-cdn/releases/6.4.1/workbox-sw.js' +) +importScripts("<%= javascript_url('turbo-offline-umd.js') %>") + +// ------------- Push Notifications ------------- +self.addEventListener('push', async (event) => { + let data + try { + data = await event.data.json() + } catch (e) { + data = { title: event.data.text(), body: event.data.text(), icon: 'pwa_home_screen_icon.png' } + } + + const title = data.title + const options = { + body: data.body, + icon: data.icon, + data + } + + event.waitUntil( + self.registration.showNotification(title, options) + ) +}) + +self.addEventListener('notificationclick', function (event) { + event.notification.close() + if (!event.notification.data.path) return + + event.waitUntil( + clients.matchAll({ type: 'window' }).then((clientList) => { + for (let i = 0; i < clientList.length; i++) { + const client = clientList[i] + const clientPath = (new URL(client.url)).pathname + + if (clientPath === event.notification.data.path && 'focus' in client) { + return client.focus() + } + } + + if (clients.openWindow) { + return clients.openWindow(event.notification.data.path) + } + }) + ) +}) + +// ------------- Request handling ------------- + +// const { CacheFirst, NetworkFirst } = workbox.strategies +// const { registerRoute } = workbox.routing + +// // For assets (scripts and images), cache first +// registerRoute( +// ({ request }) => request.destination === 'script' || +// request.destination === 'style', +// new CacheFirst({ +// cacheName: 'assets-styles-and-scripts' +// }) +// ) + +// registerRoute( +// ({ request }) => request.destination === 'image', +// new CacheFirst({ +// cacheName: 'assets-images' +// }) +// ) + +// // For pages, network first +// registerRoute( +// ({ request, url }) => request.destination === 'document' || +// request.destination === '', +// new NetworkFirst() +// ) + +// // Offline fallback + +// const { warmStrategyCache } = workbox.recipes +// const { setCatchHandler } = workbox.routing +// const strategy = new NetworkFirst() +// const urls = ['/offline.html'] + +// // Warm the runtime cache with a list of asset URLs +// warmStrategyCache({ urls, strategy }) + +// // Trigger a 'catch' handler when any of the other routes fail to generate a response +// setCatchHandler(async ({ event }) => { +// switch (event.request.destination) { +// case 'document': +// return strategy.handle({ event, request: urls[0] }) +// default: +// return Response.error() +// } +// }) + + +TurboOffline.addRule({ + match: /.*/, + handler: TurboOffline.handlers.networkFirst({ + cacheName: "general", + maxAge: 60 * 60 * 24 * 7, + networkTimeout: 3 + }) +}) + +TurboOffline.start() \ No newline at end of file diff --git a/config/importmap.rb b/config/importmap.rb index 4d309db2..5d77ac68 100644 --- a/config/importmap.rb +++ b/config/importmap.rb @@ -1,7 +1,8 @@ # Pin npm packages by running ./bin/importmap pin "application" -pin "@hotwired/turbo-rails", to: "turbo.min.js" +pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true +pin "@hotwired/turbo/offline", to: "turbo-offline-umd.js", preload: true pin "@hotwired/stimulus", to: "@hotwired--stimulus.js" # @3.2.2 pin "@hotwired/stimulus-loading", to: "stimulus-loading.js" pin_all_from "app/javascript/controllers", under: "controllers" diff --git a/vendor/javascript/turbo-offline-umd.js b/vendor/javascript/turbo-offline-umd.js new file mode 100644 index 00000000..538e6210 --- /dev/null +++ b/vendor/javascript/turbo-offline-umd.js @@ -0,0 +1,548 @@ +/*! +Turbo 8.0.13 +Copyright © 2025 37signals LLC + */ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : + typeof define === 'function' && define.amd ? define(['exports'], factory) : + (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.TurboOffline = {})); +})(this, (function (exports) { 'use strict'; + + class Rule { + constructor({ handler, match = /.*/ } = {}) { + this.handler = handler; + this.match = match; + } + + matches(request) { + if (typeof this.match === 'function') return this.match(request) + + const regexes = Array.isArray(this.match) ? this.match : [this.match]; + return regexes.some(regex => regex.test(request.url)) + } + + async handle(event) { + const { response, afterHandlePromise } = await this.handler.handle(event.request); + event.waitUntil(afterHandlePromise); + + return response + } + } + + class ServiceWorker { + #started = false + #rules = [] + + addRule(rule) { + this.#rules.push(new Rule(rule)); + } + + start() { + this.#warnIfNoRulesConfigured(); + + if (!this.#started) { + self.addEventListener("install", this.installed); + self.addEventListener("message", this.messageReceived); + self.addEventListener("fetch", this.fetch); + this.#started = true; + } + } + + installed = (event) => { + // Just log for now + console.log("Service worker installed"); + } + + messageReceived = (event) => { + if (this[event.data.action]) { + const actionCall = this[event.data.action](event.data.params); + event.waitUntil(actionCall); + } + } + + fetch = (event) => { + if (this.#canInterceptRequest(event.request)) { + const rule = this.#findMatchingRule(event.request); + if (!rule) return + + const response = rule.handle(event); + event.respondWith(response); + } + } + + #warnIfNoRulesConfigured() { + if (this.#rules.length === 0) { + console.warn("No rules configured for service worker. No requests will be intercepted."); + } + } + + #canInterceptRequest(request) { + const url = new URL(request.url, location.href); + return request.method === "GET" && url.protocol.startsWith('http') + } + + #findMatchingRule(request) { + return this.#rules.find((rule) => rule.matches(request)) + } + } + + const DATABASE_NAME = "turbo-offline-database"; + const DATABASE_VERSION = 1; + const STORE_NAME = "cache-registry"; + + class CacheRegistryDatabase { + get(cacheName, key) { + const getOp = (store) => this.#requestToPromise(store.get(key)); + return this.#performOperation(STORE_NAME, getOp, "readonly") + } + + has(cacheName, key) { + const countOp = (store) => this.#requestToPromise(store.count(key)); + return this.#performOperation(STORE_NAME, countOp, "readonly").then((result) => result === 1) + } + + put(cacheName, key, value) { + const putOp = (store) => { + const item = { key: key, cacheName: cacheName, timestamp: Date.now(), ...value }; + store.put(item); + return this.#requestToPromise(store.transaction) + }; + + return this.#performOperation(STORE_NAME, putOp, "readwrite") + } + + getTimestamp(cacheName, key) { + return this.get(cacheName, key).then((result) => result?.timestamp) + } + + getOlderThan(cacheName, timestamp) { + const getOlderThanOp = (store) => { + const index = store.index("cacheNameAndTimestamp"); + // Use compound key range: [cacheName, timestamp] + const range = IDBKeyRange.bound( + [cacheName, 0], // start of range + [cacheName, timestamp], // end of range + false, // lowerOpen: include lower bound + true // upperOpen: exclude upper bound + ); + const cursorRequest = index.openCursor(range); + + return this.#cursorRequestToPromise(cursorRequest) + }; + return this.#performOperation(STORE_NAME, getOlderThanOp, "readonly") + } + + delete(cacheName, key) { + const deleteOp = (store) => this.#requestToPromise(store.delete(key)); + return this.#performOperation(STORE_NAME, deleteOp, "readwrite") + } + + #performOperation(storeName, operation, mode) { + return this.#openDatabase().then((database) => { + const transaction = database.transaction(storeName, mode); + const store = transaction.objectStore(storeName); + return operation(store) + }) + } + + #openDatabase() { + const request = indexedDB.open(DATABASE_NAME, DATABASE_VERSION); + request.onupgradeneeded = () => { + const cacheMetadataStore = request.result.createObjectStore(STORE_NAME, { keyPath: "key" }); + cacheMetadataStore.createIndex("cacheNameAndTimestamp", [ "cacheName", "timestamp" ]); + }; + + return this.#requestToPromise(request) + } + + #requestToPromise(request) { + return new Promise((resolve, reject) => { + request.oncomplete = request.onsuccess = () => resolve(request.result); + request.onabort = request.onerror = () => reject(request.error); + }) + } + + #cursorRequestToPromise(request) { + return new Promise((resolve, reject) => { + const results = []; + + request.onsuccess = (event) => { + const cursor = event.target.result; + if (cursor) { + results.push(cursor.value); + cursor.continue(); + } else { + resolve(results); + } + }; + + request.onerror = () => reject(request.error); + }) + } + } + + let cacheRegistryDatabase = null; + + function getDatabase() { + if (!cacheRegistryDatabase) { + cacheRegistryDatabase = new CacheRegistryDatabase(); + } + return cacheRegistryDatabase + } + + class CacheRegistry { + constructor(cacheName) { + this.cacheName = cacheName; + this.database = getDatabase(); + } + + get(key) { + return this.database.get(this.cacheName, key) + } + + has(key) { + return this.database.has(this.cacheName, key) + } + + put(key, value = {}) { + return this.database.put(this.cacheName, key, value) + } + + getTimestamp(key) { + return this.database.getTimestamp(this.cacheName, key) + } + + getOlderThan(timestamp) { + return this.database.getOlderThan(this.cacheName, timestamp) + } + + delete(key) { + return this.database.delete(this.cacheName, key) + } + } + + class CacheTrimmer { + #isRunning = false + + constructor(cacheName, cacheRegistry, options = {}) { + this.cacheName = cacheName; + this.cacheRegistry = cacheRegistry; + this.options = options; + } + + async trim() { + if (this.#isRunning) { + return + } + + if (!this.#shouldTrim()) { + return + } + + this.#isRunning = true; + + try { + await this.deleteEntries(); + } finally { + this.#isRunning = false; + } + } + + #shouldTrim() { + // For now, only check maxAge. To be extended for maxEntries, maxStorage, etc. + return this.options.maxAge && this.options.maxAge > 0 + } + + async deleteEntries() { + if (this.options.maxAge) { + await this.deleteEntriesByAge(); + } + // To be extended with other options + } + + async deleteEntriesByAge() { + const maxAgeMs = this.options.maxAge * 1000; + const cutoffTimestamp = Date.now() - maxAgeMs; + + const expiredEntries = await this.cacheRegistry.getOlderThan(cutoffTimestamp); + + if (expiredEntries.length === 0) { + return + } + + console.debug(`Trimming ${expiredEntries.length} expired entries from cache "${this.cacheName}"`); + + const cache = await caches.open(this.cacheName); + + const deletePromises = expiredEntries.map(async (entry) => { + const cacheDeletePromise = cache.delete(entry.key); + const registryDeletePromise = this.cacheRegistry.delete(entry.key); + + return Promise.all([cacheDeletePromise, registryDeletePromise]) + }); + + await Promise.all(deletePromises); + + console.debug(`Successfully trimmed ${expiredEntries.length} entries from cache "${this.cacheName}"`); + } + } + + class Handler { + constructor({ cacheName, networkTimeout, maxAge }) { + this.cacheName = cacheName; + this.networkTimeout = networkTimeout; + + this.cacheRegistry = new CacheRegistry(cacheName); + this.cacheTrimmer = new CacheTrimmer(cacheName, this.cacheRegistry, { maxAge }); + } + + async handle(request) { + // Abstract method + } + + async fetchFromCache(request) { + const cacheKeyUrl = buildCacheKey(request); + let response = await caches.match(cacheKeyUrl, { ignoreVary: true }); + + if (response !== undefined && request.redirect === "manual" && response.redirected) { + // Can't respond with a redirected response to a request + // whose redirect mode is not "follow". Since we can't cache + // the request with manual redirect mode because these might be + // crawled in advance, when we don't know how these will be requested, + // we just return the body here. + response = new Response(response.body, { headers: response.headers, status: response.status, url: response.url }); + } + + return response + } + + async fetchFromNetwork(request) { + // Setting the referrer so that it doesn't appear as the service worker + const referrer = request.referrer; + return await fetch(request, { referrer }) + } + + async saveToCache(request, response) { + if (response && this.canCacheResponse(response)) { + const cacheKeyUrl = buildCacheKey(request, response); + const cache = await caches.open(this.cacheName); + + const cachePromise = cache.put(cacheKeyUrl, response); + const registryPromise = this.cacheRegistry.put(cacheKeyUrl); + const trimPromise = this.cacheTrimmer.trim(); + + return Promise.all([ cachePromise, registryPromise, trimPromise ]) + } + } + + canCacheResponse(response) { + // OK response and opaque responses (due to CORS), that have a 0 status + return response.status === 200 || response.status === 0 + } + } + + function buildCacheKey(requestOrUrl, response) { + // If the response is HTML, cache always based on the final response's request url, so + // that Turbo HTTP location redirects are not masked. Otherwise, cached based on the + // original request url, so redirected images, resources, etc are available offline. + // We don't have a response when responding from the cache because, so in that case, + // we need to use the request's URL always. This is also the case when we want to + // check the URL in our key-value store to see if we have a fresh version already. + const request = new Request(requestOrUrl); + const url = response && isHtmlResponse(response) ? response.url : request.url; + return new URL(url, location.href).href + } + + function isHtmlResponse(response) { + return response.headers.get("content-type")?.includes("text/html") + } + + /** + * Cache-first: try the cache. If it's a hit, return that. If it's a miss, + * fall back to network and then cache the response. + */ + + class CacheFirst extends Handler { + async handle(request) { + let response = await this.fetchFromCache(request); + let afterHandlePromise; + + if (response) { + // Always trim the cache when using a response in cache-first strategy + // because if we are always returning cached content and never saving + // any new content, it will all eventually become older than maxAge + afterHandlePromise = this.cacheTrimmer.trim(); + return { response, afterHandlePromise } + } + + console.debug(`Cache miss for ${request.url}`); + + try { + response = await this.fetchFromNetwork(request); + } catch(error) { + console.warn( + `${error} fetching from network ${request.url}` + ); + } + + if (response) { + afterHandlePromise = this.saveToCache(request, response.clone()); + } + + return { response, afterHandlePromise } + } + + canCacheResponse(response) { + // Only cache OK responses, as we don't want to cache a network error + // by mistake, which is impossible to distinguish from an opaque + // response with status 0 + return response.status === 200 + } + } + + /** + * Network-first: try the network, with an optional time out, and if that fails + * fallback to the cache + */ + + class NetworkFirst extends Handler { + async handle(request) { + let response; + let afterHandlePromise; + let timeoutId; + let cacheAttemptedOnTimeout = false; + + const networkPromise = this.fetchFromNetwork(request); + const promises = [networkPromise]; + + if (this.networkTimeout) { + const timeoutPromise = new Promise((resolve) => { + timeoutId = setTimeout(async () => { + console.debug(`Network timeout after ${this.networkTimeout}s for ${request.url}, trying the cache...`); + + const cachedResponse = await this.fetchFromCache(request); + cacheAttemptedOnTimeout = true; + + resolve(cachedResponse); + }, this.networkTimeout * 1000); + }); + promises.push(timeoutPromise); + } + + try { + // Either the network wins or the timeout wins. The network might win + // with an error, though + response = await Promise.race(promises); + } catch(error) { + console.warn( + `${error} fetching from network ${request.url} with timeout` + ); + } + + if (timeoutId) clearTimeout(timeoutId); + + if (!response && cacheAttemptedOnTimeout) { + // In this case we know we hit the timout and got a cache miss. + // We can wait for the network promise just in case, as we don't have + // anything to lose, knowing that we don't have the cache as fallback + try { + response = await networkPromise; + } catch(error) { + // This might be the same error we got from the promise race + console.warn( + `${error} fetching from network ${request.url}` + ); + } + } else if (!response) { + // If we didn't get a response and didn't try the cache, try it now + // as a fallback + response = await this.fetchFromCache(request); + } + + if (response) { + afterHandlePromise = this.saveToCache(request, response.clone()); + } else { + afterHandlePromise = Promise.resolve(); + } + + return { response, afterHandlePromise } + } + } + + /** + * Stale-while-revalidate: try the cache. If it's a hit, return that and + * go to the network to fetch a new response and store it. If it's a miss, + * just fall back to network and then store the response. + */ + + class StaleWhileRevalidate extends Handler { + async handle(request) { + let response = await this.fetchFromCache(request); + let afterHandlePromise; + + if (response) { + afterHandlePromise = this.revalidateCache(request); + return { response, afterHandlePromise } + } + + console.debug(`Cache miss for ${request.url}`); + + try { + response = await this.fetchFromNetwork(request); + } catch(error) { + console.warn( + `${error} fetching from network ${request.url}` + ); + } + + if (response) { + afterHandlePromise = this.saveToCache(request, response.clone()); + } else { + afterHandlePromise = Promise.resolve(); + } + + return { response, afterHandlePromise } + } + + async revalidateCache(request) { + try { + const response = await this.fetchFromNetwork(request); + if (response) { + await this.saveToCache(request, response.clone()); + } + } catch(error) { + console.debug(`${error} revalidating cache for ${request.url}`); + } + } + } + + const cacheFirst = (config) => new CacheFirst(config); + const networkFirst = (config) => new NetworkFirst(config); + const staleWhileRevalidate = (config) => new StaleWhileRevalidate(config); + + var index = /*#__PURE__*/Object.freeze({ + __proto__: null, + cacheFirst: cacheFirst, + networkFirst: networkFirst, + staleWhileRevalidate: staleWhileRevalidate + }); + + const serviceWorker = new ServiceWorker(); + + function addRule(rule) { + serviceWorker.addRule(rule); + } + + function start() { + serviceWorker.start(); + } + + exports.ServiceWorker = ServiceWorker; + exports.addRule = addRule; + exports.handlers = index; + exports.serviceWorker = serviceWorker; + exports.start = start; + + Object.defineProperty(exports, '__esModule', { value: true }); + +})); diff --git a/vendor/javascript/turbo-offline.js b/vendor/javascript/turbo-offline.js new file mode 100644 index 00000000..da4d2888 --- /dev/null +++ b/vendor/javascript/turbo-offline.js @@ -0,0 +1,534 @@ +/*! +Turbo 8.0.13 +Copyright © 2025 37signals LLC + */ +class Rule { + constructor({ handler, match = /.*/ } = {}) { + this.handler = handler; + this.match = match; + } + + matches(request) { + if (typeof this.match === 'function') return this.match(request) + + const regexes = Array.isArray(this.match) ? this.match : [this.match]; + return regexes.some(regex => regex.test(request.url)) + } + + async handle(event) { + const { response, afterHandlePromise } = await this.handler.handle(event.request); + event.waitUntil(afterHandlePromise); + + return response + } +} + +class ServiceWorker { + #started = false + #rules = [] + + addRule(rule) { + this.#rules.push(new Rule(rule)); + } + + start() { + this.#warnIfNoRulesConfigured(); + + if (!this.#started) { + self.addEventListener("install", this.installed); + self.addEventListener("message", this.messageReceived); + self.addEventListener("fetch", this.fetch); + this.#started = true; + } + } + + installed = (event) => { + // Just log for now + console.log("Service worker installed"); + } + + messageReceived = (event) => { + if (this[event.data.action]) { + const actionCall = this[event.data.action](event.data.params); + event.waitUntil(actionCall); + } + } + + fetch = (event) => { + if (this.#canInterceptRequest(event.request)) { + const rule = this.#findMatchingRule(event.request); + if (!rule) return + + const response = rule.handle(event); + event.respondWith(response); + } + } + + #warnIfNoRulesConfigured() { + if (this.#rules.length === 0) { + console.warn("No rules configured for service worker. No requests will be intercepted."); + } + } + + #canInterceptRequest(request) { + const url = new URL(request.url, location.href); + return request.method === "GET" && url.protocol.startsWith('http') + } + + #findMatchingRule(request) { + return this.#rules.find((rule) => rule.matches(request)) + } +} + +const DATABASE_NAME = "turbo-offline-database"; +const DATABASE_VERSION = 1; +const STORE_NAME = "cache-registry"; + +class CacheRegistryDatabase { + get(cacheName, key) { + const getOp = (store) => this.#requestToPromise(store.get(key)); + return this.#performOperation(STORE_NAME, getOp, "readonly") + } + + has(cacheName, key) { + const countOp = (store) => this.#requestToPromise(store.count(key)); + return this.#performOperation(STORE_NAME, countOp, "readonly").then((result) => result === 1) + } + + put(cacheName, key, value) { + const putOp = (store) => { + const item = { key: key, cacheName: cacheName, timestamp: Date.now(), ...value }; + store.put(item); + return this.#requestToPromise(store.transaction) + }; + + return this.#performOperation(STORE_NAME, putOp, "readwrite") + } + + getTimestamp(cacheName, key) { + return this.get(cacheName, key).then((result) => result?.timestamp) + } + + getOlderThan(cacheName, timestamp) { + const getOlderThanOp = (store) => { + const index = store.index("cacheNameAndTimestamp"); + // Use compound key range: [cacheName, timestamp] + const range = IDBKeyRange.bound( + [cacheName, 0], // start of range + [cacheName, timestamp], // end of range + false, // lowerOpen: include lower bound + true // upperOpen: exclude upper bound + ); + const cursorRequest = index.openCursor(range); + + return this.#cursorRequestToPromise(cursorRequest) + }; + return this.#performOperation(STORE_NAME, getOlderThanOp, "readonly") + } + + delete(cacheName, key) { + const deleteOp = (store) => this.#requestToPromise(store.delete(key)); + return this.#performOperation(STORE_NAME, deleteOp, "readwrite") + } + + #performOperation(storeName, operation, mode) { + return this.#openDatabase().then((database) => { + const transaction = database.transaction(storeName, mode); + const store = transaction.objectStore(storeName); + return operation(store) + }) + } + + #openDatabase() { + const request = indexedDB.open(DATABASE_NAME, DATABASE_VERSION); + request.onupgradeneeded = () => { + const cacheMetadataStore = request.result.createObjectStore(STORE_NAME, { keyPath: "key" }); + cacheMetadataStore.createIndex("cacheNameAndTimestamp", [ "cacheName", "timestamp" ]); + }; + + return this.#requestToPromise(request) + } + + #requestToPromise(request) { + return new Promise((resolve, reject) => { + request.oncomplete = request.onsuccess = () => resolve(request.result); + request.onabort = request.onerror = () => reject(request.error); + }) + } + + #cursorRequestToPromise(request) { + return new Promise((resolve, reject) => { + const results = []; + + request.onsuccess = (event) => { + const cursor = event.target.result; + if (cursor) { + results.push(cursor.value); + cursor.continue(); + } else { + resolve(results); + } + }; + + request.onerror = () => reject(request.error); + }) + } +} + +let cacheRegistryDatabase = null; + +function getDatabase() { + if (!cacheRegistryDatabase) { + cacheRegistryDatabase = new CacheRegistryDatabase(); + } + return cacheRegistryDatabase +} + +class CacheRegistry { + constructor(cacheName) { + this.cacheName = cacheName; + this.database = getDatabase(); + } + + get(key) { + return this.database.get(this.cacheName, key) + } + + has(key) { + return this.database.has(this.cacheName, key) + } + + put(key, value = {}) { + return this.database.put(this.cacheName, key, value) + } + + getTimestamp(key) { + return this.database.getTimestamp(this.cacheName, key) + } + + getOlderThan(timestamp) { + return this.database.getOlderThan(this.cacheName, timestamp) + } + + delete(key) { + return this.database.delete(this.cacheName, key) + } +} + +class CacheTrimmer { + #isRunning = false + + constructor(cacheName, cacheRegistry, options = {}) { + this.cacheName = cacheName; + this.cacheRegistry = cacheRegistry; + this.options = options; + } + + async trim() { + if (this.#isRunning) { + return + } + + if (!this.#shouldTrim()) { + return + } + + this.#isRunning = true; + + try { + await this.deleteEntries(); + } finally { + this.#isRunning = false; + } + } + + #shouldTrim() { + // For now, only check maxAge. To be extended for maxEntries, maxStorage, etc. + return this.options.maxAge && this.options.maxAge > 0 + } + + async deleteEntries() { + if (this.options.maxAge) { + await this.deleteEntriesByAge(); + } + // To be extended with other options + } + + async deleteEntriesByAge() { + const maxAgeMs = this.options.maxAge * 1000; + const cutoffTimestamp = Date.now() - maxAgeMs; + + const expiredEntries = await this.cacheRegistry.getOlderThan(cutoffTimestamp); + + if (expiredEntries.length === 0) { + return + } + + console.debug(`Trimming ${expiredEntries.length} expired entries from cache "${this.cacheName}"`); + + const cache = await caches.open(this.cacheName); + + const deletePromises = expiredEntries.map(async (entry) => { + const cacheDeletePromise = cache.delete(entry.key); + const registryDeletePromise = this.cacheRegistry.delete(entry.key); + + return Promise.all([cacheDeletePromise, registryDeletePromise]) + }); + + await Promise.all(deletePromises); + + console.debug(`Successfully trimmed ${expiredEntries.length} entries from cache "${this.cacheName}"`); + } +} + +class Handler { + constructor({ cacheName, networkTimeout, maxAge }) { + this.cacheName = cacheName; + this.networkTimeout = networkTimeout; + + this.cacheRegistry = new CacheRegistry(cacheName); + this.cacheTrimmer = new CacheTrimmer(cacheName, this.cacheRegistry, { maxAge }); + } + + async handle(request) { + // Abstract method + } + + async fetchFromCache(request) { + const cacheKeyUrl = buildCacheKey(request); + let response = await caches.match(cacheKeyUrl, { ignoreVary: true }); + + if (response !== undefined && request.redirect === "manual" && response.redirected) { + // Can't respond with a redirected response to a request + // whose redirect mode is not "follow". Since we can't cache + // the request with manual redirect mode because these might be + // crawled in advance, when we don't know how these will be requested, + // we just return the body here. + response = new Response(response.body, { headers: response.headers, status: response.status, url: response.url }); + } + + return response + } + + async fetchFromNetwork(request) { + // Setting the referrer so that it doesn't appear as the service worker + const referrer = request.referrer; + return await fetch(request, { referrer }) + } + + async saveToCache(request, response) { + if (response && this.canCacheResponse(response)) { + const cacheKeyUrl = buildCacheKey(request, response); + const cache = await caches.open(this.cacheName); + + const cachePromise = cache.put(cacheKeyUrl, response); + const registryPromise = this.cacheRegistry.put(cacheKeyUrl); + const trimPromise = this.cacheTrimmer.trim(); + + return Promise.all([ cachePromise, registryPromise, trimPromise ]) + } + } + + canCacheResponse(response) { + // OK response and opaque responses (due to CORS), that have a 0 status + return response.status === 200 || response.status === 0 + } +} + +function buildCacheKey(requestOrUrl, response) { + // If the response is HTML, cache always based on the final response's request url, so + // that Turbo HTTP location redirects are not masked. Otherwise, cached based on the + // original request url, so redirected images, resources, etc are available offline. + // We don't have a response when responding from the cache because, so in that case, + // we need to use the request's URL always. This is also the case when we want to + // check the URL in our key-value store to see if we have a fresh version already. + const request = new Request(requestOrUrl); + const url = response && isHtmlResponse(response) ? response.url : request.url; + return new URL(url, location.href).href +} + +function isHtmlResponse(response) { + return response.headers.get("content-type")?.includes("text/html") +} + +/** + * Cache-first: try the cache. If it's a hit, return that. If it's a miss, + * fall back to network and then cache the response. + */ + +class CacheFirst extends Handler { + async handle(request) { + let response = await this.fetchFromCache(request); + let afterHandlePromise; + + if (response) { + // Always trim the cache when using a response in cache-first strategy + // because if we are always returning cached content and never saving + // any new content, it will all eventually become older than maxAge + afterHandlePromise = this.cacheTrimmer.trim(); + return { response, afterHandlePromise } + } + + console.debug(`Cache miss for ${request.url}`); + + try { + response = await this.fetchFromNetwork(request); + } catch(error) { + console.warn( + `${error} fetching from network ${request.url}` + ); + } + + if (response) { + afterHandlePromise = this.saveToCache(request, response.clone()); + } + + return { response, afterHandlePromise } + } + + canCacheResponse(response) { + // Only cache OK responses, as we don't want to cache a network error + // by mistake, which is impossible to distinguish from an opaque + // response with status 0 + return response.status === 200 + } +} + +/** + * Network-first: try the network, with an optional time out, and if that fails + * fallback to the cache + */ + +class NetworkFirst extends Handler { + async handle(request) { + let response; + let afterHandlePromise; + let timeoutId; + let cacheAttemptedOnTimeout = false; + + const networkPromise = this.fetchFromNetwork(request); + const promises = [networkPromise]; + + if (this.networkTimeout) { + const timeoutPromise = new Promise((resolve) => { + timeoutId = setTimeout(async () => { + console.debug(`Network timeout after ${this.networkTimeout}s for ${request.url}, trying the cache...`); + + const cachedResponse = await this.fetchFromCache(request); + cacheAttemptedOnTimeout = true; + + resolve(cachedResponse); + }, this.networkTimeout * 1000); + }); + promises.push(timeoutPromise); + } + + try { + // Either the network wins or the timeout wins. The network might win + // with an error, though + response = await Promise.race(promises); + } catch(error) { + console.warn( + `${error} fetching from network ${request.url} with timeout` + ); + } + + if (timeoutId) clearTimeout(timeoutId); + + if (!response && cacheAttemptedOnTimeout) { + // In this case we know we hit the timout and got a cache miss. + // We can wait for the network promise just in case, as we don't have + // anything to lose, knowing that we don't have the cache as fallback + try { + response = await networkPromise; + } catch(error) { + // This might be the same error we got from the promise race + console.warn( + `${error} fetching from network ${request.url}` + ); + } + } else if (!response) { + // If we didn't get a response and didn't try the cache, try it now + // as a fallback + response = await this.fetchFromCache(request); + } + + if (response) { + afterHandlePromise = this.saveToCache(request, response.clone()); + } else { + afterHandlePromise = Promise.resolve(); + } + + return { response, afterHandlePromise } + } +} + +/** + * Stale-while-revalidate: try the cache. If it's a hit, return that and + * go to the network to fetch a new response and store it. If it's a miss, + * just fall back to network and then store the response. + */ + +class StaleWhileRevalidate extends Handler { + async handle(request) { + let response = await this.fetchFromCache(request); + let afterHandlePromise; + + if (response) { + afterHandlePromise = this.revalidateCache(request); + return { response, afterHandlePromise } + } + + console.debug(`Cache miss for ${request.url}`); + + try { + response = await this.fetchFromNetwork(request); + } catch(error) { + console.warn( + `${error} fetching from network ${request.url}` + ); + } + + if (response) { + afterHandlePromise = this.saveToCache(request, response.clone()); + } else { + afterHandlePromise = Promise.resolve(); + } + + return { response, afterHandlePromise } + } + + async revalidateCache(request) { + try { + const response = await this.fetchFromNetwork(request); + if (response) { + await this.saveToCache(request, response.clone()); + } + } catch(error) { + console.debug(`${error} revalidating cache for ${request.url}`); + } + } +} + +const cacheFirst = (config) => new CacheFirst(config); +const networkFirst = (config) => new NetworkFirst(config); +const staleWhileRevalidate = (config) => new StaleWhileRevalidate(config); + +var index = /*#__PURE__*/Object.freeze({ + __proto__: null, + cacheFirst: cacheFirst, + networkFirst: networkFirst, + staleWhileRevalidate: staleWhileRevalidate +}); + +const serviceWorker = new ServiceWorker(); + +function addRule(rule) { + serviceWorker.addRule(rule); +} + +function start() { + serviceWorker.start(); +} + +export { ServiceWorker, addRule, index as handlers, serviceWorker, start }; diff --git a/vendor/javascript/turbo-offline.min.js b/vendor/javascript/turbo-offline.min.js new file mode 100644 index 00000000..c6d99d51 --- /dev/null +++ b/vendor/javascript/turbo-offline.min.js @@ -0,0 +1,6 @@ +/*! +Turbo 8.0.13 +Copyright © 2025 37signals LLC + */ +class e{constructor({handler:e,match:t=/.*/}={}){this.handler=e,this.match=t}matches(e){if("function"==typeof this.match)return this.match(e);return(Array.isArray(this.match)?this.match:[this.match]).some((t=>t.test(e.url)))}async handle(e){const{response:t,afterHandlePromise:s}=await this.handler.handle(e.request);return e.waitUntil(s),t}}class t{#e=!1;#t=[];addRule(t){this.#t.push(new e(t))}start(){this.#s(),this.#e||(self.addEventListener("install",this.installed),self.addEventListener("message",this.messageReceived),self.addEventListener("fetch",this.fetch),this.#e=!0)}installed=e=>{console.log("Service worker installed")};messageReceived=e=>{if(this[e.data.action]){const t=this[e.data.action](e.data.params);e.waitUntil(t)}};fetch=e=>{if(this.#r(e.request)){const t=this.#a(e.request);if(!t)return;const s=t.handle(e);e.respondWith(s)}};#s(){0===this.#t.length&&console.warn("No rules configured for service worker. No requests will be intercepted.")}#r(e){const t=new URL(e.url,location.href);return"GET"===e.method&&t.protocol.startsWith("http")}#a(e){return this.#t.find((t=>t.matches(e)))}}const s="cache-registry";class r{get(e,t){return this.#n(s,(e=>this.#i(e.get(t))),"readonly")}has(e,t){return this.#n(s,(e=>this.#i(e.count(t))),"readonly").then((e=>1===e))}put(e,t,r){return this.#n(s,(s=>{const a={key:t,cacheName:e,timestamp:Date.now(),...r};return s.put(a),this.#i(s.transaction)}),"readwrite")}getTimestamp(e,t){return this.get(e,t).then((e=>e?.timestamp))}getOlderThan(e,t){return this.#n(s,(s=>{const r=s.index("cacheNameAndTimestamp"),a=IDBKeyRange.bound([e,0],[e,t],!1,!0),n=r.openCursor(a);return this.#o(n)}),"readonly")}delete(e,t){return this.#n(s,(e=>this.#i(e.delete(t))),"readwrite")}#n(e,t,s){return this.#c().then((r=>{const a=r.transaction(e,s).objectStore(e);return t(a)}))}#c(){const e=indexedDB.open("turbo-offline-database",1);return e.onupgradeneeded=()=>{e.result.createObjectStore(s,{keyPath:"key"}).createIndex("cacheNameAndTimestamp",["cacheName","timestamp"])},this.#i(e)}#i(e){return new Promise(((t,s)=>{e.oncomplete=e.onsuccess=()=>t(e.result),e.onabort=e.onerror=()=>s(e.error)}))}#o(e){return new Promise(((t,s)=>{const r=[];e.onsuccess=e=>{const s=e.target.result;s?(r.push(s.value),s.continue()):t(r)},e.onerror=()=>s(e.error)}))}}let a=null;class n{constructor(e){this.cacheName=e,this.database=(a||(a=new r),a)}get(e){return this.database.get(this.cacheName,e)}has(e){return this.database.has(this.cacheName,e)}put(e,t={}){return this.database.put(this.cacheName,e,t)}getTimestamp(e){return this.database.getTimestamp(this.cacheName,e)}getOlderThan(e){return this.database.getOlderThan(this.cacheName,e)}delete(e){return this.database.delete(this.cacheName,e)}}class i{#h=!1;constructor(e,t,s={}){this.cacheName=e,this.cacheRegistry=t,this.options=s}async trim(){if(!this.#h&&this.#l()){this.#h=!0;try{await this.deleteEntries()}finally{this.#h=!1}}}#l(){return this.options.maxAge&&this.options.maxAge>0}async deleteEntries(){this.options.maxAge&&await this.deleteEntriesByAge()}async deleteEntriesByAge(){const e=1e3*this.options.maxAge,t=Date.now()-e,s=await this.cacheRegistry.getOlderThan(t);if(0===s.length)return;console.debug(`Trimming ${s.length} expired entries from cache "${this.cacheName}"`);const r=await caches.open(this.cacheName),a=s.map((async e=>{const t=r.delete(e.key),s=this.cacheRegistry.delete(e.key);return Promise.all([t,s])}));await Promise.all(a),console.debug(`Successfully trimmed ${s.length} entries from cache "${this.cacheName}"`)}}class o{constructor({cacheName:e,networkTimeout:t,maxAge:s}){this.cacheName=e,this.networkTimeout=t,this.cacheRegistry=new n(e),this.cacheTrimmer=new i(e,this.cacheRegistry,{maxAge:s})}async handle(e){}async fetchFromCache(e){const t=c(e);let s=await caches.match(t,{ignoreVary:!0});return void 0!==s&&"manual"===e.redirect&&s.redirected&&(s=new Response(s.body,{headers:s.headers,status:s.status,url:s.url})),s}async fetchFromNetwork(e){const t=e.referrer;return await fetch(e,{referrer:t})}async saveToCache(e,t){if(t&&this.canCacheResponse(t)){const s=c(e,t),r=(await caches.open(this.cacheName)).put(s,t),a=this.cacheRegistry.put(s),n=this.cacheTrimmer.trim();return Promise.all([r,a,n])}}canCacheResponse(e){return 200===e.status||0===e.status}}function c(e,t){const s=new Request(e),r=t&&function(e){return e.headers.get("content-type")?.includes("text/html")}(t)?t.url:s.url;return new URL(r,location.href).href}class h extends o{async handle(e){let t,s=await this.fetchFromCache(e);if(s)return t=this.cacheTrimmer.trim(),{response:s,afterHandlePromise:t};console.debug(`Cache miss for ${e.url}`);try{s=await this.fetchFromNetwork(e)}catch(t){console.warn(`${t} fetching from network ${e.url}`)}return s&&(t=this.saveToCache(e,s.clone())),{response:s,afterHandlePromise:t}}canCacheResponse(e){return 200===e.status}}class l extends o{async handle(e){let t,s,r,a=!1;const n=this.fetchFromNetwork(e),i=[n];if(this.networkTimeout){const t=new Promise((t=>{r=setTimeout((async()=>{console.debug(`Network timeout after ${this.networkTimeout}s for ${e.url}, trying the cache...`);const s=await this.fetchFromCache(e);a=!0,t(s)}),1e3*this.networkTimeout)}));i.push(t)}try{t=await Promise.race(i)}catch(t){console.warn(`${t} fetching from network ${e.url} with timeout`)}if(r&&clearTimeout(r),!t&&a)try{t=await n}catch(t){console.warn(`${t} fetching from network ${e.url}`)}else t||(t=await this.fetchFromCache(e));return s=t?this.saveToCache(e,t.clone()):Promise.resolve(),{response:t,afterHandlePromise:s}}}class u extends o{async handle(e){let t,s=await this.fetchFromCache(e);if(s)return t=this.revalidateCache(e),{response:s,afterHandlePromise:t};console.debug(`Cache miss for ${e.url}`);try{s=await this.fetchFromNetwork(e)}catch(t){console.warn(`${t} fetching from network ${e.url}`)}return t=s?this.saveToCache(e,s.clone()):Promise.resolve(),{response:s,afterHandlePromise:t}}async revalidateCache(e){try{const t=await this.fetchFromNetwork(e);t&&await this.saveToCache(e,t.clone())}catch(t){console.debug(`${t} revalidating cache for ${e.url}`)}}}var m=Object.freeze({__proto__:null,cacheFirst:e=>new h(e),networkFirst:e=>new l(e),staleWhileRevalidate:e=>new u(e)});const d=new t;function f(e){d.addRule(e)}function w(){d.start()}export{t as ServiceWorker,f as addRule,m as handlers,d as serviceWorker,w as start}; +//# sourceMappingURL=turbo-offline.min.js.map diff --git a/vendor/javascript/turbo-offline.min.js.map b/vendor/javascript/turbo-offline.min.js.map new file mode 100644 index 00000000..f98206c8 --- /dev/null +++ b/vendor/javascript/turbo-offline.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"turbo-offline.min.js","sources":["../../../../turbo/dist/turbo-offline.js"],"sourcesContent":["/*!\nTurbo 8.0.13\nCopyright © 2025 37signals LLC\n */\nclass Rule {\n constructor({ handler, match = /.*/ } = {}) {\n this.handler = handler;\n this.match = match;\n }\n\n matches(request) {\n if (typeof this.match === 'function') return this.match(request)\n\n const regexes = Array.isArray(this.match) ? this.match : [this.match];\n return regexes.some(regex => regex.test(request.url))\n }\n\n async handle(event) {\n const { response, afterHandlePromise } = await this.handler.handle(event.request);\n event.waitUntil(afterHandlePromise);\n\n return response\n }\n}\n\nclass ServiceWorker {\n #started = false\n #rules = []\n\n addRule(rule) {\n this.#rules.push(new Rule(rule));\n }\n\n start() {\n this.#warnIfNoRulesConfigured();\n\n if (!this.#started) {\n self.addEventListener(\"install\", this.installed);\n self.addEventListener(\"message\", this.messageReceived);\n self.addEventListener(\"fetch\", this.fetch);\n this.#started = true;\n }\n }\n\n installed = (event) => {\n // Just log for now\n console.log(\"Service worker installed\");\n }\n\n messageReceived = (event) => {\n if (this[event.data.action]) {\n const actionCall = this[event.data.action](event.data.params);\n event.waitUntil(actionCall);\n }\n }\n\n fetch = (event) => {\n if (this.#canInterceptRequest(event.request)) {\n const rule = this.#findMatchingRule(event.request);\n if (!rule) return\n\n const response = rule.handle(event);\n event.respondWith(response);\n }\n }\n\n #warnIfNoRulesConfigured() {\n if (this.#rules.length === 0) {\n console.warn(\"No rules configured for service worker. No requests will be intercepted.\");\n }\n }\n\n #canInterceptRequest(request) {\n const url = new URL(request.url, location.href);\n return request.method === \"GET\" && url.protocol.startsWith('http')\n }\n\n #findMatchingRule(request) {\n return this.#rules.find((rule) => rule.matches(request))\n }\n}\n\nconst DATABASE_NAME = \"turbo-offline-database\";\nconst DATABASE_VERSION = 1;\nconst STORE_NAME = \"cache-registry\";\n\nclass CacheRegistryDatabase {\n get(cacheName, key) {\n const getOp = (store) => this.#requestToPromise(store.get(key));\n return this.#performOperation(STORE_NAME, getOp, \"readonly\")\n }\n\n has(cacheName, key) {\n const countOp = (store) => this.#requestToPromise(store.count(key));\n return this.#performOperation(STORE_NAME, countOp, \"readonly\").then((result) => result === 1)\n }\n\n put(cacheName, key, value) {\n const putOp = (store) => {\n const item = { key: key, cacheName: cacheName, timestamp: Date.now(), ...value };\n store.put(item);\n return this.#requestToPromise(store.transaction)\n };\n\n return this.#performOperation(STORE_NAME, putOp, \"readwrite\")\n }\n\n getTimestamp(cacheName, key) {\n return this.get(cacheName, key).then((result) => result?.timestamp)\n }\n\n getOlderThan(cacheName, timestamp) {\n const getOlderThanOp = (store) => {\n const index = store.index(\"cacheNameAndTimestamp\");\n // Use compound key range: [cacheName, timestamp]\n const range = IDBKeyRange.bound(\n [cacheName, 0], // start of range\n [cacheName, timestamp], // end of range\n false, // lowerOpen: include lower bound\n true // upperOpen: exclude upper bound\n );\n const cursorRequest = index.openCursor(range);\n\n return this.#cursorRequestToPromise(cursorRequest)\n };\n return this.#performOperation(STORE_NAME, getOlderThanOp, \"readonly\")\n }\n\n delete(cacheName, key) {\n const deleteOp = (store) => this.#requestToPromise(store.delete(key));\n return this.#performOperation(STORE_NAME, deleteOp, \"readwrite\")\n }\n\n #performOperation(storeName, operation, mode) {\n return this.#openDatabase().then((database) => {\n const transaction = database.transaction(storeName, mode);\n const store = transaction.objectStore(storeName);\n return operation(store)\n })\n }\n\n #openDatabase() {\n const request = indexedDB.open(DATABASE_NAME, DATABASE_VERSION);\n request.onupgradeneeded = () => {\n const cacheMetadataStore = request.result.createObjectStore(STORE_NAME, { keyPath: \"key\" });\n cacheMetadataStore.createIndex(\"cacheNameAndTimestamp\", [ \"cacheName\", \"timestamp\" ]);\n };\n\n return this.#requestToPromise(request)\n }\n\n #requestToPromise(request) {\n return new Promise((resolve, reject) => {\n request.oncomplete = request.onsuccess = () => resolve(request.result);\n request.onabort = request.onerror = () => reject(request.error);\n })\n }\n\n #cursorRequestToPromise(request) {\n return new Promise((resolve, reject) => {\n const results = [];\n\n request.onsuccess = (event) => {\n const cursor = event.target.result;\n if (cursor) {\n results.push(cursor.value);\n cursor.continue();\n } else {\n resolve(results);\n }\n };\n\n request.onerror = () => reject(request.error);\n })\n }\n}\n\nlet cacheRegistryDatabase = null;\n\nfunction getDatabase() {\n if (!cacheRegistryDatabase) {\n cacheRegistryDatabase = new CacheRegistryDatabase();\n }\n return cacheRegistryDatabase\n}\n\nclass CacheRegistry {\n constructor(cacheName) {\n this.cacheName = cacheName;\n this.database = getDatabase();\n }\n\n get(key) {\n return this.database.get(this.cacheName, key)\n }\n\n has(key) {\n return this.database.has(this.cacheName, key)\n }\n\n put(key, value = {}) {\n return this.database.put(this.cacheName, key, value)\n }\n\n getTimestamp(key) {\n return this.database.getTimestamp(this.cacheName, key)\n }\n\n getOlderThan(timestamp) {\n return this.database.getOlderThan(this.cacheName, timestamp)\n }\n\n delete(key) {\n return this.database.delete(this.cacheName, key)\n }\n}\n\nclass CacheTrimmer {\n #isRunning = false\n\n constructor(cacheName, cacheRegistry, options = {}) {\n this.cacheName = cacheName;\n this.cacheRegistry = cacheRegistry;\n this.options = options;\n }\n\n async trim() {\n if (this.#isRunning) {\n return\n }\n\n if (!this.#shouldTrim()) {\n return\n }\n\n this.#isRunning = true;\n\n try {\n await this.deleteEntries();\n } finally {\n this.#isRunning = false;\n }\n }\n\n #shouldTrim() {\n // For now, only check maxAge. To be extended for maxEntries, maxStorage, etc.\n return this.options.maxAge && this.options.maxAge > 0\n }\n\n async deleteEntries() {\n if (this.options.maxAge) {\n await this.deleteEntriesByAge();\n }\n // To be extended with other options\n }\n\n async deleteEntriesByAge() {\n const maxAgeMs = this.options.maxAge * 1000;\n const cutoffTimestamp = Date.now() - maxAgeMs;\n\n const expiredEntries = await this.cacheRegistry.getOlderThan(cutoffTimestamp);\n\n if (expiredEntries.length === 0) {\n return\n }\n\n console.debug(`Trimming ${expiredEntries.length} expired entries from cache \"${this.cacheName}\"`);\n\n const cache = await caches.open(this.cacheName);\n\n const deletePromises = expiredEntries.map(async (entry) => {\n const cacheDeletePromise = cache.delete(entry.key);\n const registryDeletePromise = this.cacheRegistry.delete(entry.key);\n\n return Promise.all([cacheDeletePromise, registryDeletePromise])\n });\n\n await Promise.all(deletePromises);\n\n console.debug(`Successfully trimmed ${expiredEntries.length} entries from cache \"${this.cacheName}\"`);\n }\n}\n\nclass Handler {\n constructor({ cacheName, networkTimeout, maxAge }) {\n this.cacheName = cacheName;\n this.networkTimeout = networkTimeout;\n\n this.cacheRegistry = new CacheRegistry(cacheName);\n this.cacheTrimmer = new CacheTrimmer(cacheName, this.cacheRegistry, { maxAge });\n }\n\n async handle(request) {\n // Abstract method\n }\n\n async fetchFromCache(request) {\n const cacheKeyUrl = buildCacheKey(request);\n let response = await caches.match(cacheKeyUrl, { ignoreVary: true });\n\n if (response !== undefined && request.redirect === \"manual\" && response.redirected) {\n // Can't respond with a redirected response to a request\n // whose redirect mode is not \"follow\". Since we can't cache\n // the request with manual redirect mode because these might be\n // crawled in advance, when we don't know how these will be requested,\n // we just return the body here.\n response = new Response(response.body, { headers: response.headers, status: response.status, url: response.url });\n }\n\n return response\n }\n\n async fetchFromNetwork(request) {\n // Setting the referrer so that it doesn't appear as the service worker\n const referrer = request.referrer;\n return await fetch(request, { referrer })\n }\n\n async saveToCache(request, response) {\n if (response && this.canCacheResponse(response)) {\n const cacheKeyUrl = buildCacheKey(request, response);\n const cache = await caches.open(this.cacheName);\n\n const cachePromise = cache.put(cacheKeyUrl, response);\n const registryPromise = this.cacheRegistry.put(cacheKeyUrl);\n const trimPromise = this.cacheTrimmer.trim();\n\n return Promise.all([ cachePromise, registryPromise, trimPromise ])\n }\n }\n\n canCacheResponse(response) {\n // OK response and opaque responses (due to CORS), that have a 0 status\n return response.status === 200 || response.status === 0\n }\n}\n\nfunction buildCacheKey(requestOrUrl, response) {\n // If the response is HTML, cache always based on the final response's request url, so\n // that Turbo HTTP location redirects are not masked. Otherwise, cached based on the\n // original request url, so redirected images, resources, etc are available offline.\n // We don't have a response when responding from the cache because, so in that case,\n // we need to use the request's URL always. This is also the case when we want to\n // check the URL in our key-value store to see if we have a fresh version already.\n const request = new Request(requestOrUrl);\n const url = response && isHtmlResponse(response) ? response.url : request.url;\n return new URL(url, location.href).href\n}\n\nfunction isHtmlResponse(response) {\n return response.headers.get(\"content-type\")?.includes(\"text/html\")\n}\n\n/**\n * Cache-first: try the cache. If it's a hit, return that. If it's a miss,\n * fall back to network and then cache the response.\n */\n\nclass CacheFirst extends Handler {\n async handle(request) {\n let response = await this.fetchFromCache(request);\n let afterHandlePromise;\n\n if (response) {\n // Always trim the cache when using a response in cache-first strategy\n // because if we are always returning cached content and never saving\n // any new content, it will all eventually become older than maxAge\n afterHandlePromise = this.cacheTrimmer.trim();\n return { response, afterHandlePromise }\n }\n\n console.debug(`Cache miss for ${request.url}`);\n\n try {\n response = await this.fetchFromNetwork(request);\n } catch(error) {\n console.warn(\n `${error} fetching from network ${request.url}`\n );\n }\n\n if (response) {\n afterHandlePromise = this.saveToCache(request, response.clone());\n }\n\n return { response, afterHandlePromise }\n }\n\n canCacheResponse(response) {\n // Only cache OK responses, as we don't want to cache a network error\n // by mistake, which is impossible to distinguish from an opaque\n // response with status 0\n return response.status === 200\n }\n}\n\n/**\n * Network-first: try the network, with an optional time out, and if that fails\n * fallback to the cache\n */\n\nclass NetworkFirst extends Handler {\n async handle(request) {\n let response;\n let afterHandlePromise;\n let timeoutId;\n let cacheAttemptedOnTimeout = false;\n\n const networkPromise = this.fetchFromNetwork(request);\n const promises = [networkPromise];\n\n if (this.networkTimeout) {\n const timeoutPromise = new Promise((resolve) => {\n timeoutId = setTimeout(async () => {\n console.debug(`Network timeout after ${this.networkTimeout}s for ${request.url}, trying the cache...`);\n\n const cachedResponse = await this.fetchFromCache(request);\n cacheAttemptedOnTimeout = true;\n\n resolve(cachedResponse);\n }, this.networkTimeout * 1000);\n });\n promises.push(timeoutPromise);\n }\n\n try {\n // Either the network wins or the timeout wins. The network might win\n // with an error, though\n response = await Promise.race(promises);\n } catch(error) {\n console.warn(\n `${error} fetching from network ${request.url} with timeout`\n );\n }\n\n if (timeoutId) clearTimeout(timeoutId);\n\n if (!response && cacheAttemptedOnTimeout) {\n // In this case we know we hit the timout and got a cache miss.\n // We can wait for the network promise just in case, as we don't have\n // anything to lose, knowing that we don't have the cache as fallback\n try {\n response = await networkPromise;\n } catch(error) {\n // This might be the same error we got from the promise race\n console.warn(\n `${error} fetching from network ${request.url}`\n );\n }\n } else if (!response) {\n // If we didn't get a response and didn't try the cache, try it now\n // as a fallback\n response = await this.fetchFromCache(request);\n }\n\n if (response) {\n afterHandlePromise = this.saveToCache(request, response.clone());\n } else {\n afterHandlePromise = Promise.resolve();\n }\n\n return { response, afterHandlePromise }\n }\n}\n\n/**\n * Stale-while-revalidate: try the cache. If it's a hit, return that and\n * go to the network to fetch a new response and store it. If it's a miss,\n * just fall back to network and then store the response.\n */\n\nclass StaleWhileRevalidate extends Handler {\n async handle(request) {\n let response = await this.fetchFromCache(request);\n let afterHandlePromise;\n\n if (response) {\n afterHandlePromise = this.revalidateCache(request);\n return { response, afterHandlePromise }\n }\n\n console.debug(`Cache miss for ${request.url}`);\n\n try {\n response = await this.fetchFromNetwork(request);\n } catch(error) {\n console.warn(\n `${error} fetching from network ${request.url}`\n );\n }\n\n if (response) {\n afterHandlePromise = this.saveToCache(request, response.clone());\n } else {\n afterHandlePromise = Promise.resolve();\n }\n\n return { response, afterHandlePromise }\n }\n\n async revalidateCache(request) {\n try {\n const response = await this.fetchFromNetwork(request);\n if (response) {\n await this.saveToCache(request, response.clone());\n }\n } catch(error) {\n console.debug(`${error} revalidating cache for ${request.url}`);\n }\n }\n}\n\nconst cacheFirst = (config) => new CacheFirst(config);\nconst networkFirst = (config) => new NetworkFirst(config);\nconst staleWhileRevalidate = (config) => new StaleWhileRevalidate(config);\n\nvar index = /*#__PURE__*/Object.freeze({\n __proto__: null,\n cacheFirst: cacheFirst,\n networkFirst: networkFirst,\n staleWhileRevalidate: staleWhileRevalidate\n});\n\nconst serviceWorker = new ServiceWorker();\n\nfunction addRule(rule) {\n serviceWorker.addRule(rule);\n}\n\nfunction start() {\n serviceWorker.start();\n}\n\nexport { ServiceWorker, addRule, index as handlers, serviceWorker, start };\n"],"names":["Rule","constructor","handler","match","this","matches","request","Array","isArray","some","regex","test","url","handle","event","response","afterHandlePromise","waitUntil","ServiceWorker","started","rules","addRule","rule","push","start","warnIfNoRulesConfigured","self","addEventListener","installed","messageReceived","fetch","console","log","data","action","actionCall","params","canInterceptRequest","findMatchingRule","respondWith","length","warn","URL","location","href","method","protocol","startsWith","find","STORE_NAME","CacheRegistryDatabase","get","cacheName","key","performOperation","store","requestToPromise","has","count","then","result","put","value","item","timestamp","Date","now","transaction","getTimestamp","getOlderThan","index","range","IDBKeyRange","bound","cursorRequest","openCursor","cursorRequestToPromise","delete","storeName","operation","mode","openDatabase","database","objectStore","indexedDB","open","onupgradeneeded","createObjectStore","keyPath","createIndex","Promise","resolve","reject","oncomplete","onsuccess","onabort","onerror","error","results","cursor","target","continue","cacheRegistryDatabase","CacheRegistry","CacheTrimmer","isRunning","cacheRegistry","options","trim","shouldTrim","deleteEntries","maxAge","deleteEntriesByAge","maxAgeMs","cutoffTimestamp","expiredEntries","debug","cache","caches","deletePromises","map","async","entry","cacheDeletePromise","registryDeletePromise","all","Handler","networkTimeout","cacheTrimmer","fetchFromCache","cacheKeyUrl","buildCacheKey","ignoreVary","undefined","redirect","redirected","Response","body","headers","status","fetchFromNetwork","referrer","saveToCache","canCacheResponse","cachePromise","registryPromise","trimPromise","requestOrUrl","Request","includes","isHtmlResponse","CacheFirst","clone","NetworkFirst","timeoutId","cacheAttemptedOnTimeout","networkPromise","promises","timeoutPromise","setTimeout","cachedResponse","race","clearTimeout","StaleWhileRevalidate","revalidateCache","Object","freeze","__proto__","cacheFirst","config","networkFirst","staleWhileRevalidate","serviceWorker"],"mappings":"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"} \ No newline at end of file diff --git a/vendor/javascript/turbo.min.js b/vendor/javascript/turbo.min.js new file mode 100644 index 00000000..dd1c2bdd --- /dev/null +++ b/vendor/javascript/turbo.min.js @@ -0,0 +1,29 @@ +/*! +Turbo 8.0.13 +Copyright © 2025 37signals LLC + */ +!function(e){function t(e,t,s){throw new e("Failed to execute 'requestSubmit' on 'HTMLFormElement': "+t+".",s)}"function"!=typeof e.requestSubmit&&(e.requestSubmit=function(e){e?(!function(e,s){e instanceof HTMLElement||t(TypeError,"parameter 1 is not of type 'HTMLElement'"),"submit"==e.type||t(TypeError,"The specified element is not a submit button"),e.form==s||t(DOMException,"The specified element is not owned by this form element","NotFoundError")}(e,this),e.click()):((e=document.createElement("input")).type="submit",e.hidden=!0,this.appendChild(e),e.click(),this.removeChild(e))})}(HTMLFormElement.prototype);const e=new WeakMap;function t(t){const s=function(e){const t=e instanceof Element?e:e instanceof Node?e.parentElement:null,s=t?t.closest("input, button"):null;return"submit"==s?.type?s:null}(t.target);s&&s.form&&e.set(s.form,s)}!function(){if("submitter"in Event.prototype)return;let s=window.Event.prototype;if("SubmitEvent"in window){const e=window.SubmitEvent.prototype;if(!/Apple Computer/.test(navigator.vendor)||"submitter"in e)return;s=e}addEventListener("click",t,!0),Object.defineProperty(s,"submitter",{get(){if("submit"==this.type&&this.target instanceof HTMLFormElement)return e.get(this.target)}})}();const s={eager:"eager",lazy:"lazy"};class r extends HTMLElement{static delegateConstructor=void 0;loaded=Promise.resolve();static get observedAttributes(){return["disabled","loading","src"]}constructor(){super(),this.delegate=new r.delegateConstructor(this)}connectedCallback(){this.delegate.connect()}disconnectedCallback(){this.delegate.disconnect()}reload(){return this.delegate.sourceURLReloaded()}attributeChangedCallback(e){"loading"==e?this.delegate.loadingStyleChanged():"src"==e?this.delegate.sourceURLChanged():"disabled"==e&&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&&"morph"===this.refresh}get loading(){return function(e){if("lazy"===e.toLowerCase())return s.lazy;return s.eager}(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")}}const i={enabled:!0,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 n(e){if("false"==e.getAttribute("data-turbo-eval"))return e;{const t=document.createElement("script"),s=E();return s&&(t.nonce=s),t.textContent=e.textContent,t.async=!1,function(e,t){for(const{name:s,value:r}of t.attributes)e.setAttribute(s,r)}(t,e),t}}function o(e,{target:t,cancelable:s,detail:r}={}){const i=new CustomEvent(e,{cancelable:s,bubbles:!0,composed:!0,detail:r});return t&&t.isConnected?t.dispatchEvent(i):document.documentElement.dispatchEvent(i),i}function a(e){e.preventDefault(),e.stopImmediatePropagation()}function c(){return"hidden"===document.visibilityState?h():l()}function l(){return new Promise((e=>requestAnimationFrame((()=>e()))))}function h(){return new Promise((e=>setTimeout((()=>e()),0)))}function d(e=""){return(new DOMParser).parseFromString(e,"text/html")}function u(e,...t){const s=function(e,t){return e.reduce(((e,s,r)=>e+s+(null==t[r]?"":t[r])),"")}(e,t).replace(/^\n/,"").split("\n"),r=s[0].match(/^\s+/),i=r?r[0].length:0;return s.map((e=>e.slice(i))).join("\n")}function m(){return Array.from({length:36}).map(((e,t)=>8==t||13==t||18==t||23==t?"-":14==t?"4":19==t?(Math.floor(4*Math.random())+8).toString(16):Math.floor(15*Math.random()).toString(16))).join("")}function p(e,...t){for(const s of t.map((t=>t?.getAttribute(e))))if("string"==typeof s)return s;return null}function f(...e){for(const t of e)"turbo-frame"==t.localName&&t.setAttribute("busy",""),t.setAttribute("aria-busy","true")}function g(...e){for(const t of e)"turbo-frame"==t.localName&&t.removeAttribute("busy"),t.removeAttribute("aria-busy")}function b(e,t=2e3){return new Promise((s=>{const r=()=>{e.removeEventListener("error",r),e.removeEventListener("load",r),s()};e.addEventListener("load",r,{once:!0}),e.addEventListener("error",r,{once:!0}),setTimeout(s,t)}))}function v(e){switch(e){case"replace":return history.replaceState;case"advance":case"restore":return history.pushState}}function S(...e){const t=p("data-turbo-action",...e);return function(e){return"advance"==e||"replace"==e||"restore"==e}(t)?t:null}function w(e){return document.querySelector(`meta[name="${e}"]`)}function y(e){const t=w(e);return t&&t.content}function E(){const e=w("csp-nonce");if(e){const{nonce:t,content:s}=e;return""==t?s:t}}function A(e,t){if(e instanceof Element)return e.closest(t)||A(e.assignedSlot||e.getRootNode()?.host,t)}function R(e){return!!e&&null==e.closest("[inert], :disabled, [hidden], details:not([open]), dialog:not([open])")&&"function"==typeof e.focus}function L(e){return Array.from(e.querySelectorAll("[autofocus]")).find(R)}function T(e){if("_blank"===e)return!1;if(e){for(const t of document.getElementsByName(e))if(t instanceof HTMLIFrameElement)return!1;return!0}return!0}function C(e){return A(e,"a[href]:not([target^=_]):not([download])")}function P(e){return I(e.getAttribute("href")||"")}const k={"aria-disabled":{beforeSubmit:e=>{e.setAttribute("aria-disabled","true"),e.addEventListener("click",a)},afterSubmit:e=>{e.removeAttribute("aria-disabled"),e.removeEventListener("click",a)}},disabled:{beforeSubmit:e=>e.disabled=!0,afterSubmit:e=>e.disabled=!1}};const M=new class{#e=null;constructor(e){Object.assign(this,e)}get submitter(){return this.#e}set submitter(e){this.#e=k[e]||e}}({mode:"on",submitter:"disabled"}),F={drive:i,forms:M};function I(e){return new URL(e.toString(),document.baseURI)}function q(e){let t;return e.hash?e.hash.slice(1):(t=e.href.match(/#(.*)$/))?t[1]:void 0}function H(e,t){return I(t?.getAttribute("formaction")||e.getAttribute("action")||e.action)}function O(e){return(function(e){return function(e){return e.pathname.split("/").slice(1)}(e).slice(-1)[0]}(e).match(/\.[^.]*$/)||[])[0]||""}function B(e,t){const s=function(e){return t=e.origin+e.pathname,t.endsWith("/")?t:t+"/";var t}(t);return e.href===I(s).href||e.href.startsWith(s)}function N(e,t){return B(e,t)&&!F.drive.unvisitableExtensions.has(O(e))}function D(e){const t=q(e);return null!=t?e.href.slice(0,-(t.length+1)):e.href}function x(e){return D(e)}function V(e,t){return I(e).href==I(t).href}class W{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 I(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 U extends Set{constructor(e){super(),this.maxSize=e}add(e){if(this.size>=this.maxSize){const e=this.values().next().value;this.delete(e)}super.add(e)}}const _=new U(20);function $(e,t={}){const s=new Headers(t.headers||{}),r=m();return _.add(r),s.append("X-Turbo-Request-Id",r),window.fetch(e,{...t,headers:s})}function j(e){switch(e.toLowerCase()){case"get":return z.get;case"post":return z.post;case"put":return z.put;case"patch":return z.patch;case"delete":return z.delete}}const z={get:"get",post:"post",put:"put",patch:"patch",delete:"delete"};function G(e){switch(e.toLowerCase()){case J.multipart:return J.multipart;case J.plain:return J.plain;default:return J.urlEncoded}}const J={urlEncoded:"application/x-www-form-urlencoded",multipart:"multipart/form-data",plain:"text/plain"};class X{abortController=new AbortController;#t=e=>{};constructor(e,t,s,r=new URLSearchParams,i=null,n=J.urlEncoded){const[o,a]=Q(I(s),t,r,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,s=j(e)||z.get;this.url.search="";const[r,i]=Q(this.url,s,t,this.enctype);this.url=r,this.fetchOptions.body=i,this.fetchOptions.method=s.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.#s(e);try{this.delegate.requestStarted(this),t.detail.fetchRequest?this.response=t.detail.fetchRequest.response:this.response=$(this.url.href,e);const s=await this.response;return await this.receive(s)}catch(e){if("AbortError"!==e.name)throw this.#r(e)&&this.delegate.requestErrored(this,e),e}finally{this.delegate.requestFinished(this)}}async receive(e){const t=new W(e);return o("turbo:before-fetch-response",{cancelable:!0,detail:{fetchResponse:t},target:this.target}).defaultPrevented?this.delegate.requestPreventedHandlingResponse(this,t):t.succeeded?this.delegate.requestSucceededWithResponse(this,t):this.delegate.requestFailedWithResponse(this,t),t}get defaultHeaders(){return{Accept:"text/html, application/xhtml+xml"}}get isSafe(){return K(this.method)}get abortSignal(){return this.abortController.signal}acceptResponseType(e){this.headers.Accept=[e,this.headers.Accept].join(", ")}async#s(e){const t=new Promise((e=>this.#t=e)),s=o("turbo:before-fetch-request",{cancelable:!0,detail:{fetchOptions:e,url:this.url,resume:this.#t},target:this.target});return this.url=s.detail.url,s.defaultPrevented&&await t,s}#r(e){return!o("turbo:fetch-request-error",{target:this.target,cancelable:!0,detail:{request:this,error:e}}).defaultPrevented}}function K(e){return j(e)==z.get}function Q(e,t,s,r){const i=Array.from(s).length>0?new URLSearchParams(Y(s)):e.searchParams;return K(t)?[Z(e,i),null]:r==J.urlEncoded?[e,i]:[e,s]}function Y(e){const t=[];for(const[s,r]of e)r instanceof File||t.push([s,r]);return t}function Z(e,t){const s=new URLSearchParams(Y(t));return e.search=s.toString(),e}class ee{started=!1;constructor(e,t){this.delegate=e,this.element=t,this.intersectionObserver=new IntersectionObserver(this.intersect)}start(){this.started||(this.started=!0,this.intersectionObserver.observe(this.element))}stop(){this.started&&(this.started=!1,this.intersectionObserver.unobserve(this.element))}intersect=e=>{const t=e.slice(-1)[0];t?.isIntersecting&&this.delegate.elementAppearedInViewport(this.element)}}class te{static contentType="text/vnd.turbo-stream.html";static wrap(e){return"string"==typeof e?new this(function(e){const t=document.createElement("template");return t.innerHTML=e,t.content}(e)):e}constructor(e){this.fragment=function(e){for(const t of e.querySelectorAll("turbo-stream")){const e=document.importNode(t,!0);for(const t of e.templateElement.content.querySelectorAll("script"))t.replaceWith(n(t));t.replaceWith(e)}return e}(e)}}const se=new class{#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,s){this.clear(),this.#i=setTimeout((()=>{t.perform(),this.set(e,t,s),this.#i=null}),100)}set(e,t,s){this.#n={url:e,request:t,expire:new Date((new Date).getTime()+s)}}clear(){this.#i&&clearTimeout(this.#i),this.#n=null}},re={initialized:"initialized",requesting:"requesting",waiting:"waiting",receiving:"receiving",stopping:"stopping",stopped:"stopped"};class ie{state=re.initialized;static confirmMethod(e){return Promise.resolve(confirm(e))}constructor(e,t,s,r=!1){const i=function(e,t){const s=t?.getAttribute("formmethod")||e.getAttribute("method")||"";return j(s.toLowerCase())||z.get}(t,s),n=function(e,t){const s=I(e);K(t)&&(s.search="");return s}(function(e,t){const s="string"==typeof e.action?e.action:null;return t?.hasAttribute("formaction")?t.getAttribute("formaction")||"":e.getAttribute("action")||s||""}(t,s),i),o=function(e,t){const s=new FormData(e),r=t?.getAttribute("name"),i=t?.getAttribute("value");r&&s.append(r,i||"");return s}(t,s),a=function(e,t){return G(t?.getAttribute("formenctype")||e.enctype)}(t,s);this.delegate=e,this.formElement=t,this.submitter=s,this.fetchRequest=new X(this,i,n,o,t,a),this.mustRedirect=r}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=I(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}=re,s=p("data-turbo-confirm",this.submitter,this.formElement);if("string"==typeof s){const e="function"==typeof F.forms.confirm?F.forms.confirm:ie.confirmMethod;if(!await e(s,this.formElement,this.submitter))return}if(this.state==e)return this.state=t,this.fetchRequest.perform()}stop(){const{stopping:e,stopped:t}=re;if(this.state!=e&&this.state!=t)return this.state=e,this.fetchRequest.cancel(),!0}prepareRequest(e){if(!e.isSafe){const t=function(e){if(null!=e){const t=(document.cookie?document.cookie.split("; "):[]).find((t=>t.startsWith(e)));if(t){const e=t.split("=").slice(1).join("=");return e?decodeURIComponent(e):void 0}}}(y("csrf-param"))||y("csrf-token");t&&(e.headers["X-CSRF-Token"]=t)}this.requestAcceptsTurboStreamResponse(e)&&e.acceptResponseType(te.contentType)}requestStarted(e){this.state=re.waiting,this.submitter&&F.forms.submitter.beforeSubmit(this.submitter),this.setSubmitsWith(),f(this.formElement),o("turbo:submit-start",{target:this.formElement,detail:{formSubmission:this}}),this.delegate.formSubmissionStarted(this)}requestPreventedHandlingResponse(e,t){se.clear(),this.result={success:t.succeeded,fetchResponse:t}}requestSucceededWithResponse(e,t){if(t.clientError||t.serverError)this.delegate.formSubmissionFailedWithResponse(this,t);else if(se.clear(),this.requestMustRedirect(e)&&function(e){return 200==e.statusCode&&!e.redirected}(t)){const e=new Error("Form responses must redirect to another location");this.delegate.formSubmissionErrored(this,e)}else this.state=re.receiving,this.result={success:!0,fetchResponse:t},this.delegate.formSubmissionSucceededWithResponse(this,t)}requestFailedWithResponse(e,t){this.result={success:!1,fetchResponse:t},this.delegate.formSubmissionFailedWithResponse(this,t)}requestErrored(e,t){this.result={success:!1,error:t},this.delegate.formSubmissionErrored(this,t)}requestFinished(e){this.state=re.stopped,this.submitter&&F.forms.submitter.afterSubmit(this.submitter),this.resetSubmitterText(),g(this.formElement),o("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")){this.submitter.value=this.originalSubmitText}}requestMustRedirect(e){return!e.isSafe&&this.mustRedirect}requestAcceptsTurboStreamResponse(e){return!e.isSafe||function(e,...t){return t.some((t=>t&&t.hasAttribute(e)))}("data-turbo-stream",this.submitter,this.formElement)}get submitsWith(){return this.submitter?.getAttribute("data-turbo-submits-with")}}class ne{constructor(e){this.element=e}get activeElement(){return this.element.ownerDocument.activeElement}get children(){return[...this.element.children]}hasAnchor(e){return null!=this.getElementForAnchor(e)}getElementForAnchor(e){return e?this.element.querySelector(`[id='${e}'], a[name='${e}']`):null}get isConnected(){return this.element.isConnected}get firstAutofocusableElement(){return L(this.element)}get permanentElements(){return ae(this.element)}getPermanentElementById(e){return oe(this.element,e)}getPermanentElementMapForSnapshot(e){const t={};for(const s of this.permanentElements){const{id:r}=s,i=e.getPermanentElementById(r);i&&(t[r]=[s,i])}return t}}function oe(e,t){return e.querySelector(`#${t}[data-turbo-permanent]`)}function ae(e){return e.querySelectorAll("[id][data-turbo-permanent]")}class ce{started=!1;constructor(e,t){this.delegate=e,this.eventTarget=t}start(){this.started||(this.eventTarget.addEventListener("submit",this.submitCaptured,!0),this.started=!0)}stop(){this.started&&(this.eventTarget.removeEventListener("submit",this.submitCaptured,!0),this.started=!1)}submitCaptured=()=>{this.eventTarget.removeEventListener("submit",this.submitBubbled,!1),this.eventTarget.addEventListener("submit",this.submitBubbled,!1)};submitBubbled=e=>{if(!e.defaultPrevented){const t=e.target instanceof HTMLFormElement?e.target:void 0,s=e.submitter||void 0;t&&function(e,t){const s=t?.getAttribute("formmethod")||e.getAttribute("method");return"dialog"!=s}(t,s)&&function(e,t){const s=t?.getAttribute("formtarget")||e.getAttribute("target");return T(s)}(t,s)&&this.delegate.willSubmitForm(t,s)&&(e.preventDefault(),e.stopImmediatePropagation(),this.delegate.formSubmitted(t,s))}}}class le{#o=e=>{};#a=e=>{};constructor(e,t){this.delegate=e,this.element=t}scrollToAnchor(e){const t=this.snapshot.getElementForAnchor(e);t?(this.focusElement(t),this.scrollToElement(t)):this.scrollToPosition({x:0,y:0})}scrollToAnchorFromLocation(e){this.scrollToAnchor(q(e))}scrollToElement(e){e.scrollIntoView()}focusElement(e){e instanceof HTMLElement&&(e.hasAttribute("tabindex")?e.focus():(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:s,willRender:r,newSnapshot:i}=e,n=r;if(s)try{this.renderPromise=new Promise((e=>this.#o=e)),this.renderer=e,await this.prepareToRenderSnapshot(e);const s=new Promise((e=>this.#a=e)),r={resume:this.#a,render:this.renderer.renderElement,renderMethod:this.renderer.renderMethod};this.delegate.allowsImmediateRender(i,r)||await s,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 he extends le{missing(){this.element.innerHTML='Content missing'}get snapshot(){return new ne(this.element)}}class de{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=>{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,s=C(t)||t;return s instanceof Element&&s.closest("turbo-frame, html")==this.element}}class ue{started=!1;constructor(e,t){this.delegate=e,this.eventTarget=t}start(){this.started||(this.eventTarget.addEventListener("click",this.clickCaptured,!0),this.started=!0)}stop(){this.started&&(this.eventTarget.removeEventListener("click",this.clickCaptured,!0),this.started=!1)}clickCaptured=()=>{this.eventTarget.removeEventListener("click",this.clickBubbled,!1),this.eventTarget.addEventListener("click",this.clickBubbled,!1)};clickBubbled=e=>{if(e instanceof MouseEvent&&this.clickEventIsSignificant(e)){const t=C(e.composedPath&&e.composedPath()[0]||e.target);if(t&&T(t.target)){const s=P(t);this.delegate.willFollowLinkToLocation(t,s,e)&&(e.preventDefault(),this.delegate.followedLinkToLocation(t,s))}}};clickEventIsSignificant(e){return!(e.target&&e.target.isContentEditable||e.defaultPrevented||e.which>1||e.altKey||e.ctrlKey||e.metaKey||e.shiftKey)}}class me{constructor(e,t){this.delegate=e,this.linkInterceptor=new ue(this,t)}start(){this.linkInterceptor.start()}stop(){this.linkInterceptor.stop()}canPrefetchRequestToLocation(e,t){return!1}prefetchAndCacheRequestToLocation(e,t){}willFollowLinkToLocation(e,t,s){return this.delegate.willSubmitFormLinkToLocation(e,t,s)&&(e.hasAttribute("data-turbo-method")||e.hasAttribute("data-turbo-stream"))}followedLinkToLocation(e,t){const s=document.createElement("form");for(const[e,r]of t.searchParams)s.append(Object.assign(document.createElement("input"),{type:"hidden",name:e,value:r}));const r=Object.assign(t,{search:""});s.setAttribute("data-turbo","true"),s.setAttribute("action",r.href),s.setAttribute("hidden","");const i=e.getAttribute("data-turbo-method");i&&s.setAttribute("method",i);const n=e.getAttribute("data-turbo-frame");n&&s.setAttribute("data-turbo-frame",n);const o=S(e);o&&s.setAttribute("data-turbo-action",o);const a=e.getAttribute("data-turbo-confirm");a&&s.setAttribute("data-turbo-confirm",a);e.hasAttribute("data-turbo-stream")&&s.setAttribute("data-turbo-stream",""),this.delegate.submittedFormLinkToLocation(e,t,s),document.body.appendChild(s),s.addEventListener("turbo:submit-end",(()=>s.remove()),{once:!0}),requestAnimationFrame((()=>s.requestSubmit()))}}class pe{static async preservingPermanentElements(e,t,s){const r=new this(e,t);r.enter(),await s(),r.leave()}constructor(e,t){this.delegate=e,this.permanentElementMap=t}enter(){for(const e in this.permanentElementMap){const[t,s]=this.permanentElementMap[e];this.delegate.enteringBardo(t,s),this.replaceNewPermanentElementWithPlaceholder(s)}}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=function(e){const t=document.createElement("meta");return t.setAttribute("name","turbo-permanent-placeholder"),t.setAttribute("content",e.id),t}(e);e.replaceWith(t)}replaceCurrentPermanentElementWithClone(e){const t=e.cloneNode(!0);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]")]}}class fe{#c=null;static renderElement(e,t){}constructor(e,t,s,r=!0){this.currentSnapshot=e,this.newSnapshot=t,this.isPreview=s,this.willRender=r,this.renderElement=this.constructor.renderElement,this.promise=new Promise(((e,t)=>this.resolvingFunctions={resolve:e,reject:t}))}get shouldRender(){return!0}get shouldAutofocus(){return!0}get reloadReason(){}prepareToRender(){}render(){}finishRendering(){this.resolvingFunctions&&(this.resolvingFunctions.resolve(),delete this.resolvingFunctions)}async preservingPermanentElements(e){await pe.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){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 ge extends fe{static renderElement(e,t){const s=document.createRange();s.selectNodeContents(e),s.deleteContents();const r=t,i=r.ownerDocument?.createRange();i&&(i.selectNodeContents(r),e.appendChild(i.extractContents()))}constructor(e,t,s,r,i,n=!0){super(t,s,r,i,n),this.delegate=e}get shouldRender(){return!0}async render(){await c(),this.preservingPermanentElements((()=>{this.loadFrameElement()})),this.scrollFrameIntoView(),await c(),this.focusFirstAutofocusableElement(),await c(),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 s=this.currentElement.firstElementChild,r=(e=this.currentElement.getAttribute("data-autoscroll-block"),t="end","end"==e||"start"==e||"center"==e||"nearest"==e?e:t),i=function(e,t){return"auto"==e||"smooth"==e?e:t}(this.currentElement.getAttribute("data-autoscroll-behavior"),"auto");if(s)return s.scrollIntoView({block:r,behavior:i}),!0}var e,t;return!1}activateScriptElements(){for(const e of this.newScriptElements){const t=n(e);e.replaceWith(t)}}get newScriptElements(){return this.currentElement.querySelectorAll("script")}}var be=function(){const e=()=>{},t={morphStyle:"outerHTML",callbacks:{beforeNodeAdded:e,afterNodeAdded:e,beforeNodeMorphed:e,afterNodeMorphed:e,beforeNodeRemoved:e,afterNodeRemoved:e,beforeAttributeUpdated:e},head:{style:"merge",shouldPreserve:e=>"true"===e.getAttribute("im-preserve"),shouldReAppend:e=>"true"===e.getAttribute("im-re-append"),shouldRemove:e,afterHeadMorphed:e},restoreFocus:!0};const s=function(){function e(e,t,s,i){if(!1===i.callbacks.beforeNodeAdded(t))return null;if(i.idMap.has(t)){const n=document.createElement(t.tagName);return e.insertBefore(n,s),r(n,t,i),i.callbacks.afterNodeAdded(n),n}{const r=document.importNode(t,!0);return e.insertBefore(r,s),i.callbacks.afterNodeAdded(r),r}}const t=function(){function e(e,t,s){let r=e.idMap.get(t),i=e.idMap.get(s);if(!i||!r)return!1;for(const e of r)if(i.has(e))return!0;return!1}function t(e,t){const s=e,r=t;return s.nodeType===r.nodeType&&s.tagName===r.tagName&&(!s.id||s.id===r.id)}return function(s,r,i,n){let o=null,a=r.nextSibling,c=0,l=i;for(;l&&l!=n;){if(t(l,r)){if(e(s,l,r))return l;null===o&&(s.idMap.has(l)||(o=l))}if(null===o&&a&&t(l,a)&&(c++,a=a.nextSibling,c>=2&&(o=void 0)),l.contains(document.activeElement))break;l=l.nextSibling}return o||null}}();function s(e,t){if(e.idMap.has(t))o(e.pantry,t,null);else{if(!1===e.callbacks.beforeNodeRemoved(t))return;t.parentNode?.removeChild(t),e.callbacks.afterNodeRemoved(t)}}function i(e,t,r){let i=t;for(;i&&i!==r;){let t=i;i=i.nextSibling,s(e,t)}return i}function n(e,t,s,r){const i=r.target.querySelector(`#${t}`)||r.pantry.querySelector(`#${t}`);return function(e,t){const s=e.id;for(;e=e.parentNode;){let r=t.idMap.get(e);r&&(r.delete(s),r.size||t.idMap.delete(e))}}(i,r),o(e,i,s),i}function o(e,t,s){if(e.moveBefore)try{e.moveBefore(t,s)}catch(r){e.insertBefore(t,s)}else e.insertBefore(t,s)}return function(o,a,c,l=null,h=null){a instanceof HTMLTemplateElement&&c instanceof HTMLTemplateElement&&(a=a.content,c=c.content),l||=a.firstChild;for(const s of c.childNodes){if(l&&l!=h){const e=t(o,s,l,h);if(e){e!==l&&i(o,l,e),r(e,s,o),l=e.nextSibling;continue}}if(s instanceof Element&&o.persistentIds.has(s.id)){const e=n(a,s.id,l,o);r(e,s,o),l=e.nextSibling;continue}const c=e(a,s,l,o);c&&(l=c.nextSibling)}for(;l&&l!=h;){const e=l;l=l.nextSibling,s(o,e)}}}(),r=function(){function e(e,s,r,i){const n=s[r];if(n!==e[r]){const o=t(r,e,"update",i);o||(e[r]=s[r]),n?o||e.setAttribute(r,""):t(r,e,"remove",i)||e.removeAttribute(r)}}function t(e,t,s,r){return!("value"!==e||!r.ignoreActiveValue||t!==document.activeElement)||!1===r.callbacks.beforeAttributeUpdated(e,t,s)}function r(e,t){return!!t.ignoreActiveValue&&e===document.activeElement&&e!==document.body}return function(n,o,a){return a.ignoreActive&&n===document.activeElement?null:(!1===a.callbacks.beforeNodeMorphed(n,o)||(n instanceof HTMLHeadElement&&a.head.ignore||(n instanceof HTMLHeadElement&&"morph"!==a.head.style?i(n,o,a):(!function(s,i,n){let o=i.nodeType;if(1===o){const o=s,a=i,c=o.attributes,l=a.attributes;for(const e of l)t(e.name,o,"update",n)||o.getAttribute(e.name)!==e.value&&o.setAttribute(e.name,e.value);for(let e=c.length-1;0<=e;e--){const s=c[e];if(s&&!a.hasAttribute(s.name)){if(t(s.name,o,"remove",n))continue;o.removeAttribute(s.name)}}r(o,n)||function(s,r,i){if(s instanceof HTMLInputElement&&r instanceof HTMLInputElement&&"file"!==r.type){let n=r.value,o=s.value;e(s,r,"checked",i),e(s,r,"disabled",i),r.hasAttribute("value")?o!==n&&(t("value",s,"update",i)||(s.setAttribute("value",n),s.value=n)):t("value",s,"remove",i)||(s.value="",s.removeAttribute("value"))}else if(s instanceof HTMLOptionElement&&r instanceof HTMLOptionElement)e(s,r,"selected",i);else if(s instanceof HTMLTextAreaElement&&r instanceof HTMLTextAreaElement){let e=r.value,n=s.value;if(t("value",s,"update",i))return;e!==n&&(s.value=e),s.firstChild&&s.firstChild.nodeValue!==e&&(s.firstChild.nodeValue=e)}}(o,a,n)}8!==o&&3!==o||s.nodeValue!==i.nodeValue&&(s.nodeValue=i.nodeValue)}(n,o,a),r(n,a)||s(a,n,o))),a.callbacks.afterNodeMorphed(n,o)),n)}}();function i(e,t,s){let r=[],i=[],n=[],o=[],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),r=s.head.shouldReAppend(t),c=s.head.shouldPreserve(t);e||c?r?i.push(t):(a.delete(t.outerHTML),n.push(t)):"append"===s.head.style?r&&(i.push(t),o.push(t)):!1!==s.head.shouldRemove(t)&&i.push(t)}o.push(...a.values());let c=[];for(const t of o){let i=document.createRange().createContextualFragment(t.outerHTML).firstChild;if(!1!==s.callbacks.beforeNodeAdded(i)){if("href"in i&&i.href||"src"in i&&i.src){let e,t=new Promise((function(t){e=t}));i.addEventListener("load",(function(){e()})),c.push(t)}e.appendChild(i),s.callbacks.afterNodeAdded(i),r.push(i)}}for(const t of i)!1!==s.callbacks.beforeNodeRemoved(t)&&(e.removeChild(t),s.callbacks.afterNodeRemoved(t));return s.head.afterHeadMorphed(e,{added:r,kept:n,removed:i}),c}const n=function(){function e(){const e=document.createElement("div");return e.hidden=!0,document.body.insertAdjacentElement("afterend",e),e}function s(e){let t=Array.from(e.querySelectorAll("[id]"));return e.id&&t.push(e),t}function r(e,t,s,r){for(const i of r)if(t.has(i.id)){let t=i;for(;t;){let r=e.get(t);if(null==r&&(r=new Set,e.set(t,r)),r.add(i.id),t===s)break;t=t.parentElement}}}return function(i,n,o){const{persistentIds:a,idMap:c}=function(e,t){const i=s(e),n=s(t),o=function(e,t){let s=new Set,r=new Map;for(const{id:t,tagName:i}of e)r.has(t)?s.add(t):r.set(t,i);let i=new Set;for(const{id:e,tagName:n}of t)i.has(e)?s.add(e):r.get(e)===n&&i.add(e);for(const e of s)i.delete(e);return i}(i,n);let a=new Map;r(a,o,e,i);const c=t.__idiomorphRoot||t;return r(a,o,c,n),{persistentIds:o,idMap:a}}(i,n),l=function(e){let s=Object.assign({},t);return Object.assign(s,e),s.callbacks=Object.assign({},t.callbacks,e.callbacks),s.head=Object.assign({},t.head,e.head),s}(o),h=l.morphStyle||"outerHTML";if(!["innerHTML","outerHTML"].includes(h))throw`Do not understand how to morph style ${h}`;return{target:i,newContent:n,config:l,morphStyle:h,ignoreActive:l.ignoreActive,ignoreActiveValue:l.ignoreActiveValue,restoreFocus:l.restoreFocus,idMap:c,persistentIds:a,pantry:e(),callbacks:l.callbacks,head:l.head}}}(),{normalizeElement:o,normalizeParent:a}=function(){const e=new WeakSet;return{normalizeElement:function(e){return e instanceof Document?e.documentElement:e},normalizeParent:function t(s){if(null==s)return document.createElement("div");if("string"==typeof s)return t(function(t){let s=new DOMParser,r=t.replace(/]*>|>)([\s\S]*?)<\/svg>/gim,"");if(r.match(/<\/html>/)||r.match(/<\/head>/)||r.match(/<\/body>/)){let i=s.parseFromString(t,"text/html");if(r.match(/<\/html>/))return e.add(i),i;{let t=i.firstChild;return t&&e.add(t),t}}{let r=s.parseFromString("","text/html").body.querySelector("template").content;return e.add(r),r}}(s));if(e.has(s))return s;if(s instanceof Node){if(s.parentNode)return function(e){return{childNodes:[e],querySelectorAll:t=>{const s=e.querySelectorAll(t);return e.matches(t)?[e,...s]:s},insertBefore:(t,s)=>e.parentNode.insertBefore(t,s),moveBefore:(t,s)=>e.parentNode.moveBefore(t,s),get __idiomorphRoot(){return e}}}(s);{const e=document.createElement("div");return e.append(s),e}}{const e=document.createElement("div");for(const t of[...s])e.append(t);return e}}}}();return{morph:function(e,t,r={}){e=o(e);const c=a(t),l=n(e,c,r),h=function(e,t){if(!e.config.restoreFocus)return t();let s=document.activeElement;if(!(s instanceof HTMLInputElement||s instanceof HTMLTextAreaElement))return t();const{id:r,selectionStart:i,selectionEnd:n}=s,o=t();r&&r!==document.activeElement?.id&&(s=e.target.querySelector(`#${r}`),s?.focus());s&&!s.selectionEnd&&n&&s.setSelectionRange(i,n);return o}(l,(()=>function(e,t,s,r){if(e.head.block){const n=t.querySelector("head"),o=s.querySelector("head");if(n&&o){const t=i(n,o,e);return Promise.all(t).then((()=>{const t=Object.assign(e,{head:{block:!1,ignore:!0}});return r(t)}))}}return r(e)}(l,e,c,(t=>"innerHTML"===t.morphStyle?(s(t,e,c),Array.from(e.childNodes)):function(e,t,r){const i=a(t);let n=Array.from(i.childNodes);const o=n.indexOf(t),c=n.length-(o+1);return s(e,i,r,t,t.nextSibling),n=Array.from(i.childNodes),n.slice(o,n.length-c)}(t,e,c)))));return l.pantry.remove(),h},defaults:t}}();function ve(e,t,{callbacks:s,...r}={}){be.morph(e,t,{...r,callbacks:new Ee(s)})}function Se(e,t,s={}){ve(e,t.childNodes,{...s,morphStyle:"innerHTML"})}function we(e,t){return e instanceof r&&t instanceof Element&&"TURBO-FRAME"===t.nodeName&&e.shouldReloadWithMorph&&e.id===t.id&&(!t.getAttribute("src")||V(e.src,t.getAttribute("src")))&&!e.closest("[data-turbo-permanent]")}function ye(e){return e.parentElement.closest("turbo-frame[src][refresh=morph]")}class Ee{#l;constructor({beforeNodeMorphed:e}={}){this.#l=e||(()=>!0)}beforeNodeAdded=e=>!(e.id&&e.hasAttribute("data-turbo-permanent")&&document.getElementById(e.id));beforeNodeMorphed=(e,t)=>{if(e instanceof Element){if(!e.hasAttribute("data-turbo-permanent")&&this.#l(e,t)){return!o("turbo:before-morph-element",{cancelable:!0,target:e,detail:{currentElement:e,newElement:t}}).defaultPrevented}return!1}};beforeAttributeUpdated=(e,t,s)=>!o("turbo:before-morph-attribute",{cancelable:!0,target:t,detail:{attributeName:e,mutationType:s}}).defaultPrevented;beforeNodeRemoved=e=>this.beforeNodeMorphed(e);afterNodeMorphed=(e,t)=>{e instanceof Element&&o("turbo:morph-element",{target:e,detail:{currentElement:e,newElement:t}})}}class Ae extends ge{static renderElement(e,t){o("turbo:before-frame-morph",{target:e,detail:{currentElement:e,newElement:t}}),Se(e,t,{callbacks:{beforeNodeMorphed:(t,s)=>!we(t,s)||ye(t)!==e||(t.reload(),!1)}})}async preservingPermanentElements(e){return await e()}}class Re{static animationDuration=300;static get defaultCSS(){return u` + .turbo-progress-bar { + position: fixed; + display: block; + top: 0; + left: 0; + height: 3px; + background: #0076ff; + z-index: 2147483647; + transition: + width ${Re.animationDuration}ms ease-out, + opacity ${Re.animationDuration/2}ms ${Re.animationDuration/2}ms ease-in; + transform: translate3d(0, 0, 0); + } + `}hiding=!1;value=0;visible=!1;constructor(){this.stylesheetElement=this.createStylesheetElement(),this.progressElement=this.createProgressElement(),this.installStylesheetElement(),this.setValue(0)}show(){this.visible||(this.visible=!0,this.installProgressElement(),this.startTrickling())}hide(){this.visible&&!this.hiding&&(this.hiding=!0,this.fadeProgressElement((()=>{this.uninstallProgressElement(),this.stopTrickling(),this.visible=!1,this.hiding=!1})))}setValue(e){this.value=e,this.refresh()}installStylesheetElement(){document.head.insertBefore(this.stylesheetElement,document.head.firstChild)}installProgressElement(){this.progressElement.style.width="0",this.progressElement.style.opacity="1",document.documentElement.insertBefore(this.progressElement,document.body),this.refresh()}fadeProgressElement(e){this.progressElement.style.opacity="0",setTimeout(e,1.5*Re.animationDuration)}uninstallProgressElement(){this.progressElement.parentNode&&document.documentElement.removeChild(this.progressElement)}startTrickling(){this.trickleInterval||(this.trickleInterval=window.setInterval(this.trickle,Re.animationDuration))}stopTrickling(){window.clearInterval(this.trickleInterval),delete this.trickleInterval}trickle=()=>{this.setValue(this.value+Math.random()/100)};refresh(){requestAnimationFrame((()=>{this.progressElement.style.width=10+90*this.value+"%"}))}createStylesheetElement(){const e=document.createElement("style");e.type="text/css",e.textContent=Re.defaultCSS;const t=E();return t&&(e.nonce=t),e}createProgressElement(){const e=document.createElement("div");return e.className="turbo-progress-bar",e}}class Le extends ne{detailsByOuterHTML=this.children.filter((e=>!function(e){const t=e.localName;return"noscript"==t}(e))).map((e=>function(e){e.hasAttribute("nonce")&&e.setAttribute("nonce","");return e}(e))).reduce(((e,t)=>{const{outerHTML:s}=t,r=s in e?e[s]:{type:Te(t),tracked:Ce(t),elements:[]};return{...e,[s]:{...r,elements:[...r.elements,t]}}}),{});get trackedElementSignature(){return Object.keys(this.detailsByOuterHTML).filter((e=>this.detailsByOuterHTML[e].tracked)).join("")}getScriptElementsNotInSnapshot(e){return this.getElementsMatchingTypeNotInSnapshot("script",e)}getStylesheetElementsNotInSnapshot(e){return this.getElementsMatchingTypeNotInSnapshot("stylesheet",e)}getElementsMatchingTypeNotInSnapshot(e,t){return Object.keys(this.detailsByOuterHTML).filter((e=>!(e in t.detailsByOuterHTML))).map((e=>this.detailsByOuterHTML[e])).filter((({type:t})=>t==e)).map((({elements:[e]})=>e))}get provisionalElements(){return Object.keys(this.detailsByOuterHTML).reduce(((e,t)=>{const{type:s,tracked:r,elements:i}=this.detailsByOuterHTML[t];return null!=s||r?i.length>1?[...e,...i.slice(1)]:e:[...e,...i]}),[])}getMetaValue(e){const t=this.findMetaElementByName(e);return t?t.getAttribute("content"):null}findMetaElementByName(e){return Object.keys(this.detailsByOuterHTML).reduce(((t,s)=>{const{elements:[r]}=this.detailsByOuterHTML[s];return function(e,t){const s=e.localName;return"meta"==s&&e.getAttribute("name")==t}(r,e)?r:t}),0)}}function Te(e){return function(e){const t=e.localName;return"script"==t}(e)?"script":function(e){const t=e.localName;return"style"==t||"link"==t&&"stylesheet"==e.getAttribute("rel")}(e)?"stylesheet":void 0}function Ce(e){return"reload"==e.getAttribute("data-turbo-track")}class Pe extends ne{static fromHTMLString(e=""){return this.fromDocument(d(e))}static fromElement(e){return this.fromDocument(e.ownerDocument)}static fromDocument({documentElement:e,body:t,head:s}){return new this(e,t,new Le(s))}constructor(e,t,s){super(t),this.documentElement=e,this.headSnapshot=s}clone(){const e=this.element.cloneNode(!0),t=this.element.querySelectorAll("select"),s=e.querySelectorAll("select");for(const[e,r]of t.entries()){const t=s[e];for(const e of t.selectedOptions)e.selected=!1;for(const e of r.selectedOptions)t.options[e.index].selected=!0}for(const t of e.querySelectorAll('input[type="password"]'))t.value="";return new Pe(this.documentElement,e,this.headSnapshot)}get lang(){return this.documentElement.getAttribute("lang")}get headElement(){return this.headSnapshot.element}get rootLocation(){return I(this.getSetting("root")??"/")}get cacheControlValue(){return this.getSetting("cache-control")}get isPreviewable(){return"no-preview"!=this.cacheControlValue}get isCacheable(){return"no-cache"!=this.cacheControlValue}get isVisitable(){return"reload"!=this.getSetting("visit-control")}get prefersViewTransitions(){return"same-origin"===this.headSnapshot.getMetaValue("view-transition")&&!window.matchMedia("(prefers-reduced-motion: reduce)").matches}get shouldMorphPage(){return"morph"===this.getSetting("refresh-method")}get shouldPreserveScrollPosition(){return"preserve"===this.getSetting("refresh-scroll")}getSetting(e){return this.headSnapshot.getMetaValue(`turbo-${e}`)}}class ke{#h=!1;#d=Promise.resolve();renderChange(e,t){return e&&this.viewTransitionsAvailable&&!this.#h?(this.#h=!0,this.#d=this.#d.then((async()=>{await document.startViewTransition(t).finished}))):this.#d=this.#d.then(t),this.#d}get viewTransitionsAvailable(){return document.startViewTransition}}const Me={action:"advance",historyChanged:!1,visitCachedSnapshot:()=>{},willRender:!0,updateHistory:!0,shouldCacheSnapshot:!0,acceptsStreamResponse:!1},Fe="visitStart",Ie="requestStart",qe="requestEnd",He="visitEnd",Oe="initialized",Be="started",Ne="canceled",De="failed",xe="completed",Ve=0,We=-1,Ue=-2,_e={advance:"forward",restore:"back",replace:"none"};class $e{identifier=m();timingMetrics={};followedRedirect=!1;historyChanged=!1;scrolled=!1;shouldCacheSnapshot=!0;acceptsStreamResponse=!1;snapshotCached=!1;state=Oe;viewTransitioner=new ke;constructor(e,t,s,r={}){this.delegate=e,this.location=t,this.restorationIdentifier=s||m();const{action:i,historyChanged:n,referrer:o,snapshot:a,snapshotHTML:c,response:l,visitCachedSnapshot:h,willRender:d,updateHistory:u,shouldCacheSnapshot:p,acceptsStreamResponse:f,direction:g}={...Me,...r};this.action=i,this.historyChanged=n,this.referrer=o,this.snapshot=a,this.snapshotHTML=c,this.response=l,this.isSamePage=this.delegate.locationWithActionIsSamePage(this.location,this.action),this.isPageRefresh=this.view.isPageRefresh(this),this.visitCachedSnapshot=h,this.willRender=d,this.updateHistory=u,this.scrolled=!d,this.shouldCacheSnapshot=p,this.acceptsStreamResponse=f,this.direction=g||_e[i]}get adapter(){return this.delegate.adapter}get view(){return this.delegate.view}get history(){return this.delegate.history}get restorationData(){return this.history.getRestorationDataForIdentifier(this.restorationIdentifier)}get silent(){return this.isSamePage}start(){this.state==Oe&&(this.recordTimingMetric(Fe),this.state=Be,this.adapter.visitStarted(this),this.delegate.visitStarted(this))}cancel(){this.state==Be&&(this.request&&this.request.cancel(),this.cancelRender(),this.state=Ne)}complete(){this.state==Be&&(this.recordTimingMetric(He),this.adapter.visitCompleted(this),this.state=xe,this.followRedirect(),this.followedRedirect||this.delegate.visitCompleted(this))}fail(){this.state==Be&&(this.state=De,this.adapter.visitFailed(this),this.delegate.visitCompleted(this))}changeHistory(){if(!this.historyChanged&&this.updateHistory){const e=v(this.location.href===this.referrer?.href?"replace":this.action);this.history.update(e,this.location,this.restorationIdentifier),this.historyChanged=!0}}issueRequest(){this.hasPreloadedResponse()?this.simulateRequest():this.shouldIssueRequest()&&!this.request&&(this.request=new X(this,z.get,this.location),this.request.perform())}simulateRequest(){this.response&&(this.startRequest(),this.recordResponse(),this.finishRequest())}startRequest(){this.recordTimingMetric(Ie),this.adapter.visitRequestStarted(this)}recordResponse(e=this.response){if(this.response=e,e){const{statusCode:t}=e;je(t)?this.adapter.visitRequestCompleted(this):this.adapter.visitRequestFailedWithStatusCode(this,t)}}finishRequest(){this.recordTimingMetric(qe),this.adapter.visitRequestFinished(this)}loadResponse(){if(this.response){const{statusCode:e,responseHTML:t}=this.response;this.render((async()=>{if(this.shouldCacheSnapshot&&this.cacheSnapshot(),this.view.renderPromise&&await this.view.renderPromise,je(e)&&null!=t){const e=Pe.fromHTMLString(t);await this.renderPageSnapshot(e,!1),this.adapter.visitRendered(this),this.complete()}else await this.view.renderError(Pe.fromHTMLString(t),this),this.adapter.visitRendered(this),this.fail()}))}}getCachedSnapshot(){const e=this.view.getCachedSnapshotForLocation(this.location)||this.getPreloadedSnapshot();if(e&&(!q(this.location)||e.hasAnchor(q(this.location)))&&("restore"==this.action||e.isPreviewable))return e}getPreloadedSnapshot(){if(this.snapshotHTML)return Pe.fromHTMLString(this.snapshotHTML)}hasCachedSnapshot(){return null!=this.getCachedSnapshot()}loadCachedSnapshot(){const e=this.getCachedSnapshot();if(e){const t=this.shouldIssueRequest();this.render((async()=>{this.cacheSnapshot(),this.isSamePage||this.isPageRefresh?this.adapter.visitRendered(this):(this.view.renderPromise&&await this.view.renderPromise,await this.renderPageSnapshot(e,t),this.adapter.visitRendered(this),t||this.complete())}))}}followRedirect(){this.redirectedToLocation&&!this.followedRedirect&&this.response?.redirected&&(this.adapter.visitProposedToLocation(this.redirectedToLocation,{action:"replace",response:this.response,shouldCacheSnapshot:!1,willRender:!1}),this.followedRedirect=!0)}goToSamePageAnchor(){this.isSamePage&&this.render((async()=>{this.cacheSnapshot(),this.performScroll(),this.changeHistory(),this.adapter.visitRendered(this)}))}prepareRequest(e){this.acceptsStreamResponse&&e.acceptResponseType(te.contentType)}requestStarted(){this.startRequest()}requestPreventedHandlingResponse(e,t){}async requestSucceededWithResponse(e,t){const s=await t.responseHTML,{redirected:r,statusCode:i}=t;null==s?this.recordResponse({statusCode:Ue,redirected:r}):(this.redirectedToLocation=t.redirected?t.location:void 0,this.recordResponse({statusCode:i,responseHTML:s,redirected:r}))}async requestFailedWithResponse(e,t){const s=await t.responseHTML,{redirected:r,statusCode:i}=t;null==s?this.recordResponse({statusCode:Ue,redirected:r}):this.recordResponse({statusCode:i,responseHTML:s,redirected:r})}requestErrored(e,t){this.recordResponse({statusCode:Ve,redirected:!1})}requestFinished(){this.finishRequest()}performScroll(){this.scrolled||this.view.forceReloaded||this.view.shouldPreserveScrollPosition(this)||("restore"==this.action?this.scrollToRestoredPosition()||this.scrollToAnchor()||this.view.scrollToTop():this.scrollToAnchor()||this.view.scrollToTop(),this.isSamePage&&this.delegate.visitScrolledToSamePageLocation(this.view.lastRenderedLocation,this.location),this.scrolled=!0)}scrollToRestoredPosition(){const{scrollPosition:e}=this.restorationData;if(e)return this.view.scrollToPosition(e),!0}scrollToAnchor(){const e=q(this.location);if(null!=e)return this.view.scrollToAnchor(e),!0}recordTimingMetric(e){this.timingMetrics[e]=(new Date).getTime()}getTimingMetrics(){return{...this.timingMetrics}}hasPreloadedResponse(){return"object"==typeof this.response}shouldIssueRequest(){return!this.isSamePage&&("restore"==this.action?!this.hasCachedSnapshot():this.willRender)}cacheSnapshot(){this.snapshotCached||(this.view.cacheSnapshot(this.snapshot).then((e=>e&&this.visitCachedSnapshot(e))),this.snapshotCached=!0)}async render(e){this.cancelRender(),await new Promise((e=>{this.frame="hidden"===document.visibilityState?setTimeout((()=>e()),0):requestAnimationFrame((()=>e()))})),await e(),delete this.frame}async renderPageSnapshot(e,t){await this.viewTransitioner.renderChange(this.view.shouldTransitionTo(e),(async()=>{await this.view.renderPage(e,t,this.willRender,this),this.performScroll()}))}cancelRender(){this.frame&&(cancelAnimationFrame(this.frame),delete this.frame)}}function je(e){return e>=200&&e<300}class ze{progressBar=new Re;constructor(e){this.session=e}visitProposedToLocation(e,t){N(e,this.navigator.rootLocation)?this.navigator.startVisit(e,t?.restorationIdentifier||m(),t):window.location.href=e.toString()}visitStarted(e){this.location=e.location,e.loadCachedSnapshot(),e.issueRequest(),e.goToSamePageAnchor()}visitRequestStarted(e){this.progressBar.setValue(0),e.hasCachedSnapshot()||"restore"!=e.action?this.showVisitProgressBarAfterDelay():this.showProgressBar()}visitRequestCompleted(e){e.loadResponse()}visitRequestFailedWithStatusCode(e,t){switch(t){case Ve:case We:case Ue:return this.reload({reason:"request_failed",context:{statusCode:t}});default:return e.loadResponse()}}visitRequestFinished(e){}visitCompleted(e){this.progressBar.setValue(1),this.hideVisitProgressBar()}pageInvalidated(e){this.reload(e)}visitFailed(e){this.progressBar.setValue(1),this.hideVisitProgressBar()}visitRendered(e){}linkPrefetchingIsEnabledForLocation(e){return!0}formSubmissionStarted(e){this.progressBar.setValue(0),this.showFormProgressBarAfterDelay()}formSubmissionFinished(e){this.progressBar.setValue(1),this.hideFormProgressBar()}showVisitProgressBarAfterDelay(){this.visitProgressBarTimeout=window.setTimeout(this.showProgressBar,this.session.progressBarDelay)}hideVisitProgressBar(){this.progressBar.hide(),null!=this.visitProgressBarTimeout&&(window.clearTimeout(this.visitProgressBarTimeout),delete this.visitProgressBarTimeout)}showFormProgressBarAfterDelay(){null==this.formProgressBarTimeout&&(this.formProgressBarTimeout=window.setTimeout(this.showProgressBar,this.session.progressBarDelay))}hideFormProgressBar(){this.progressBar.hide(),null!=this.formProgressBarTimeout&&(window.clearTimeout(this.formProgressBarTimeout),delete this.formProgressBarTimeout)}showProgressBar=()=>{this.progressBar.show()};reload(e){o("turbo:reload",{detail:e}),window.location.href=this.location?.toString()||window.location.href}get navigator(){return this.session.navigator}}class Ge{selector="[data-turbo-temporary]";deprecatedSelector="[data-turbo-cache=false]";started=!1;start(){this.started||(this.started=!0,addEventListener("turbo:before-cache",this.removeTemporaryElements,!1))}stop(){this.started&&(this.started=!1,removeEventListener("turbo:before-cache",this.removeTemporaryElements,!1))}removeTemporaryElements=e=>{for(const e of this.temporaryElements)e.remove()};get temporaryElements(){return[...document.querySelectorAll(this.selector),...this.temporaryElementsWithDeprecation]}get temporaryElementsWithDeprecation(){const e=document.querySelectorAll(this.deprecatedSelector);return e.length&&console.warn(`The ${this.deprecatedSelector} selector is deprecated and will be removed in a future version. Use ${this.selector} instead.`),[...e]}}class Je{constructor(e,t){this.session=e,this.element=t,this.linkInterceptor=new de(this,t),this.formSubmitObserver=new ce(this,t)}start(){this.linkInterceptor.start(),this.formSubmitObserver.start()}stop(){this.linkInterceptor.stop(),this.formSubmitObserver.stop()}shouldInterceptLinkClick(e,t,s){return this.#u(e)}linkClickIntercepted(e,t,s){const r=this.#m(e);r&&r.delegate.linkClickIntercepted(e,t,s)}willSubmitForm(e,t){return null==e.closest("turbo-frame")&&this.#p(e,t)&&this.#u(e,t)}formSubmitted(e,t){const s=this.#m(e,t);s&&s.delegate.formSubmitted(e,t)}#p(e,t){const s=H(e,t),r=this.element.ownerDocument.querySelector('meta[name="turbo-root"]'),i=I(r?.content??"/");return this.#u(e,t)&&N(s,i)}#u(e,t){if(e instanceof HTMLFormElement?this.session.submissionIsNavigatable(e,t):this.session.elementIsNavigatable(e)){const s=this.#m(e,t);return!!s&&s!=e.closest("turbo-frame")}return!1}#m(e,t){const s=t?.getAttribute("data-turbo-frame")||e.getAttribute("data-turbo-frame");if(s&&"_top"!=s){const e=this.element.querySelector(`#${s}:not([disabled])`);if(e instanceof r)return e}}}class Xe{location;restorationIdentifier=m();restorationData={};started=!1;pageLoaded=!1;currentIndex=0;constructor(e){this.delegate=e}start(){this.started||(addEventListener("popstate",this.onPopState,!1),addEventListener("load",this.onPageLoad,!1),this.currentIndex=history.state?.turbo?.restorationIndex||0,this.started=!0,this.replace(new URL(window.location.href)))}stop(){this.started&&(removeEventListener("popstate",this.onPopState,!1),removeEventListener("load",this.onPageLoad,!1),this.started=!1)}push(e,t){this.update(history.pushState,e,t)}replace(e,t){this.update(history.replaceState,e,t)}update(e,t,s=m()){e===history.pushState&&++this.currentIndex;const r={turbo:{restorationIdentifier:s,restorationIndex:this.currentIndex}};e.call(history,r,"",t.href),this.location=t,this.restorationIdentifier=s}getRestorationDataForIdentifier(e){return this.restorationData[e]||{}}updateRestorationData(e){const{restorationIdentifier:t}=this,s=this.restorationData[t];this.restorationData[t]={...s,...e}}assumeControlOfScrollRestoration(){this.previousScrollRestoration||(this.previousScrollRestoration=history.scrollRestoration??"auto",history.scrollRestoration="manual")}relinquishControlOfScrollRestoration(){this.previousScrollRestoration&&(history.scrollRestoration=this.previousScrollRestoration,delete this.previousScrollRestoration)}onPopState=e=>{if(this.shouldHandlePopState()){const{turbo:t}=e.state||{};if(t){this.location=new URL(window.location.href);const{restorationIdentifier:e,restorationIndex:s}=t;this.restorationIdentifier=e;const r=s>this.currentIndex?"forward":"back";this.delegate.historyPoppedToLocationWithRestorationIdentifierAndDirection(this.location,e,r),this.currentIndex=s}}};onPageLoad=async e=>{await Promise.resolve(),this.pageLoaded=!0};shouldHandlePopState(){return this.pageIsLoaded()}pageIsLoaded(){return this.pageLoaded||"complete"==document.readyState}}class Ke{started=!1;#f=null;constructor(e,t){this.delegate=e,this.eventTarget=t}start(){this.started||("loading"===this.eventTarget.readyState?this.eventTarget.addEventListener("DOMContentLoaded",this.#g,{once:!0}):this.#g())}stop(){this.started&&(this.eventTarget.removeEventListener("mouseenter",this.#b,{capture:!0,passive:!0}),this.eventTarget.removeEventListener("mouseleave",this.#v,{capture:!0,passive:!0}),this.eventTarget.removeEventListener("turbo:before-fetch-request",this.#S,!0),this.started=!1)}#g=()=>{this.eventTarget.addEventListener("mouseenter",this.#b,{capture:!0,passive:!0}),this.eventTarget.addEventListener("mouseleave",this.#v,{capture:!0,passive:!0}),this.eventTarget.addEventListener("turbo:before-fetch-request",this.#S,!0),this.started=!0};#b=e=>{if("false"===y("turbo-prefetch"))return;const t=e.target;if(t.matches&&t.matches("a[href]:not([target^=_]):not([download])")&&this.#w(t)){const e=t,s=P(e);if(this.delegate.canPrefetchRequestToLocation(e,s)){this.#f=e;const r=new X(this,z.get,s,new URLSearchParams,t);se.setLater(s.toString(),r,this.#y)}}};#v=e=>{e.target===this.#f&&this.#E()};#E=()=>{se.clear(),this.#f=null};#S=e=>{if("FORM"!==e.target.tagName&&"GET"===e.detail.fetchOptions.method){const t=se.get(e.detail.url.toString());t&&(e.detail.fetchRequest=t),se.clear()}};prepareRequest(e){const t=e.target;e.headers["X-Sec-Purpose"]="prefetch";const s=t.closest("turbo-frame"),r=t.getAttribute("data-turbo-frame")||s?.getAttribute("target")||s?.id;r&&"_top"!==r&&(e.headers["Turbo-Frame"]=r)}requestSucceededWithResponse(){}requestStarted(e){}requestErrored(e){}requestFinished(e){}requestPreventedHandlingResponse(e,t){}requestFailedWithResponse(e,t){}get#y(){return Number(y("turbo-prefetch-cache-time"))||1e4}#w(e){return!!e.getAttribute("href")&&(!Qe(e)&&(!Ye(e)&&(!Ze(e)&&(!et(e)&&!st(e)))))}}const Qe=e=>e.origin!==document.location.origin||!["http:","https:"].includes(e.protocol)||e.hasAttribute("target"),Ye=e=>e.pathname+e.search===document.location.pathname+document.location.search||e.href.startsWith("#"),Ze=e=>{if("false"===e.getAttribute("data-turbo-prefetch"))return!0;if("false"===e.getAttribute("data-turbo"))return!0;const t=A(e,"[data-turbo-prefetch]");return!(!t||"false"!==t.getAttribute("data-turbo-prefetch"))},et=e=>{const t=e.getAttribute("data-turbo-method");return!(!t||"get"===t.toLowerCase())||(!!tt(e)||(!!e.hasAttribute("data-turbo-confirm")||!!e.hasAttribute("data-turbo-stream")))},tt=e=>e.hasAttribute("data-remote")||e.hasAttribute("data-behavior")||e.hasAttribute("data-confirm")||e.hasAttribute("data-method"),st=e=>o("turbo:before-prefetch",{target:e,cancelable:!0}).defaultPrevented;class rt{constructor(e){this.delegate=e}proposeVisit(e,t={}){this.delegate.allowsVisitingLocationWithAction(e,t.action)&&this.delegate.visitProposedToLocation(e,t)}startVisit(e,t,s={}){this.stop(),this.currentVisit=new $e(this,I(e),t,{referrer:this.location,...s}),this.currentVisit.start()}submitForm(e,t){this.stop(),this.formSubmission=new ie(this,e,t,!0),this.formSubmission.start()}stop(){this.formSubmission&&(this.formSubmission.stop(),delete this.formSubmission),this.currentVisit&&(this.currentVisit.cancel(),delete this.currentVisit)}get adapter(){return this.delegate.adapter}get view(){return this.delegate.view}get rootLocation(){return this.view.snapshot.rootLocation}get history(){return this.delegate.history}formSubmissionStarted(e){"function"==typeof this.adapter.formSubmissionStarted&&this.adapter.formSubmissionStarted(e)}async formSubmissionSucceededWithResponse(e,t){if(e==this.formSubmission){const s=await t.responseHTML;if(s){const r=e.isSafe;r||this.view.clearSnapshotCache();const{statusCode:i,redirected:n}=t,o={action:this.#A(e,t),shouldCacheSnapshot:r,response:{statusCode:i,responseHTML:s,redirected:n}};this.proposeVisit(t.location,o)}}}async formSubmissionFailedWithResponse(e,t){const s=await t.responseHTML;if(s){const e=Pe.fromHTMLString(s);t.serverError?await this.view.renderError(e,this.currentVisit):await this.view.renderPage(e,!1,!0,this.currentVisit),e.shouldPreserveScrollPosition||this.view.scrollToTop(),this.view.clearSnapshotCache()}}formSubmissionErrored(e,t){console.error(t)}formSubmissionFinished(e){"function"==typeof this.adapter.formSubmissionFinished&&this.adapter.formSubmissionFinished(e)}linkPrefetchingIsEnabledForLocation(e){return"function"!=typeof this.adapter.linkPrefetchingIsEnabledForLocation||this.adapter.linkPrefetchingIsEnabledForLocation(e)}visitStarted(e){this.delegate.visitStarted(e)}visitCompleted(e){this.delegate.visitCompleted(e),delete this.currentVisit}locationWithActionIsSamePage(e,t){const s=q(e),r=q(this.view.lastRenderedLocation),i="restore"===t&&void 0===s;return"replace"!==t&&D(e)===D(this.view.lastRenderedLocation)&&(i||null!=s&&s!==r)}visitScrolledToSamePageLocation(e,t){this.delegate.visitScrolledToSamePageLocation(e,t)}get location(){return this.history.location}get restorationIdentifier(){return this.history.restorationIdentifier}#A(e,t){const{submitter:s,formElement:r}=e;return S(s,r)||this.#R(t)}#R(e){return e.redirected&&e.location.href===this.location?.href?"replace":"advance"}}const it=0,nt=1,ot=2,at=3;class ct{stage=it;started=!1;constructor(e){this.delegate=e}start(){this.started||(this.stage==it&&(this.stage=nt),document.addEventListener("readystatechange",this.interpretReadyState,!1),addEventListener("pagehide",this.pageWillUnload,!1),this.started=!0)}stop(){this.started&&(document.removeEventListener("readystatechange",this.interpretReadyState,!1),removeEventListener("pagehide",this.pageWillUnload,!1),this.started=!1)}interpretReadyState=()=>{const{readyState:e}=this;"interactive"==e?this.pageIsInteractive():"complete"==e&&this.pageIsComplete()};pageIsInteractive(){this.stage==nt&&(this.stage=ot,this.delegate.pageBecameInteractive())}pageIsComplete(){this.pageIsInteractive(),this.stage==ot&&(this.stage=at,this.delegate.pageLoaded())}pageWillUnload=()=>{this.delegate.pageWillUnload()};get readyState(){return document.readyState}}class lt{started=!1;constructor(e){this.delegate=e}start(){this.started||(addEventListener("scroll",this.onScroll,!1),this.onScroll(),this.started=!0)}stop(){this.started&&(removeEventListener("scroll",this.onScroll,!1),this.started=!1)}onScroll=()=>{this.updatePosition({x:window.pageXOffset,y:window.pageYOffset})};updatePosition(e){this.delegate.scrollPositionChanged(e)}}class ht{render({fragment:e}){pe.preservingPermanentElements(this,function(e){const t=ae(document.documentElement),s={};for(const r of t){const{id:t}=r;for(const i of e.querySelectorAll("turbo-stream")){const e=oe(i.templateElement.content,t);e&&(s[t]=[r,e])}}return s}(e),(()=>{!async function(e,t){const s=`turbo-stream-autofocus-${m()}`,r=e.querySelectorAll("turbo-stream"),i=function(e){for(const t of e){const e=L(t.templateElement.content);if(e)return e}return null}(r);let n=null;i&&(n=i.id?i.id:s,i.id=n);t(),await c();if((null==document.activeElement||document.activeElement==document.body)&&n){const e=document.getElementById(n);R(e)&&e.focus(),e&&e.id==s&&e.removeAttribute("id")}}(e,(()=>{!async function(e){const[t,s]=await async function(e,t){const s=t();return e(),await l(),[s,t()]}(e,(()=>document.activeElement)),r=t&&t.id;if(r){const e=document.getElementById(r);R(e)&&e!=s&&e.focus()}}((()=>{document.documentElement.appendChild(e)}))}))}))}enteringBardo(e,t){t.replaceWith(e.cloneNode(!0))}leavingBardo(){}}class dt{sources=new Set;#L=!1;constructor(e){this.delegate=e}start(){this.#L||(this.#L=!0,addEventListener("turbo:before-fetch-response",this.inspectFetchResponse,!1))}stop(){this.#L&&(this.#L=!1,removeEventListener("turbo:before-fetch-response",this.inspectFetchResponse,!1))}connectStreamSource(e){this.streamSourceIsConnected(e)||(this.sources.add(e),e.addEventListener("message",this.receiveMessageEvent,!1))}disconnectStreamSource(e){this.streamSourceIsConnected(e)&&(this.sources.delete(e),e.removeEventListener("message",this.receiveMessageEvent,!1))}streamSourceIsConnected(e){return this.sources.has(e)}inspectFetchResponse=e=>{const t=function(e){const t=e.detail?.fetchResponse;if(t instanceof W)return t}(e);t&&function(e){const t=e.contentType??"";return t.startsWith(te.contentType)}(t)&&(e.preventDefault(),this.receiveMessageResponse(t))};receiveMessageEvent=e=>{this.#L&&"string"==typeof e.data&&this.receiveMessageHTML(e.data)};async receiveMessageResponse(e){const t=await e.responseHTML;t&&this.receiveMessageHTML(t)}receiveMessageHTML(e){this.delegate.receivedMessageFromStream(te.wrap(e))}}class ut extends fe{static renderElement(e,t){const{documentElement:s,body:r}=document;s.replaceChild(t,r)}async render(){this.replaceHeadAndBody(),this.activateScriptElements()}replaceHeadAndBody(){const{documentElement:e,head:t}=document;e.replaceChild(this.newHead,t),this.renderElement(this.currentElement,this.newElement)}activateScriptElements(){for(const e of this.scriptElements){const t=e.parentNode;if(t){const s=n(e);t.replaceChild(s,e)}}}get newHead(){return this.newSnapshot.headSnapshot.element}get scriptElements(){return document.documentElement.querySelectorAll("script")}}class mt extends fe{static renderElement(e,t){document.body&&t instanceof HTMLBodyElement?document.body.replaceWith(t):document.documentElement.appendChild(t)}get shouldRender(){return this.newSnapshot.isVisitable&&this.trackedElementsAreIdentical}get reloadReason(){return this.newSnapshot.isVisitable?this.trackedElementsAreIdentical?void 0:{reason:"tracked_element_mismatch"}:{reason:"turbo_visit_control_is_reload"}}async prepareToRender(){this.#T(),await this.mergeHead()}async render(){this.willRender&&await this.replaceBody()}finishRendering(){super.finishRendering(),this.isPreview||this.focusFirstAutofocusableElement()}get currentHeadSnapshot(){return this.currentSnapshot.headSnapshot}get newHeadSnapshot(){return this.newSnapshot.headSnapshot}get newElement(){return this.newSnapshot.element}#T(){const{documentElement:e}=this.currentSnapshot,{lang:t}=this.newSnapshot;t?e.setAttribute("lang",t):e.removeAttribute("lang")}async mergeHead(){const e=this.mergeProvisionalElements(),t=this.copyNewHeadStylesheetElements();this.copyNewHeadScriptElements(),await e,await t,this.willRender&&this.removeUnusedDynamicStylesheetElements()}async replaceBody(){await this.preservingPermanentElements((async()=>{this.activateNewBody(),await this.assignNewBody()}))}get trackedElementsAreIdentical(){return this.currentHeadSnapshot.trackedElementSignature==this.newHeadSnapshot.trackedElementSignature}async copyNewHeadStylesheetElements(){const e=[];for(const t of this.newHeadStylesheetElements)e.push(b(t)),document.head.appendChild(t);await Promise.all(e)}copyNewHeadScriptElements(){for(const e of this.newHeadScriptElements)document.head.appendChild(n(e))}removeUnusedDynamicStylesheetElements(){for(const e of this.unusedDynamicStylesheetElements)document.head.removeChild(e)}async mergeProvisionalElements(){const e=[...this.newHeadProvisionalElements];for(const t of this.currentHeadProvisionalElements)this.isCurrentElementInElementList(t,e)||document.head.removeChild(t);for(const t of e)document.head.appendChild(t)}isCurrentElementInElementList(e,t){for(const[s,r]of t.entries()){if("TITLE"==e.tagName){if("TITLE"!=r.tagName)continue;if(e.innerHTML==r.innerHTML)return t.splice(s,1),!0}if(r.isEqualNode(e))return t.splice(s,1),!0}return!1}removeCurrentHeadProvisionalElements(){for(const e of this.currentHeadProvisionalElements)document.head.removeChild(e)}copyNewHeadProvisionalElements(){for(const e of this.newHeadProvisionalElements)document.head.appendChild(e)}activateNewBody(){document.adoptNode(this.newElement),this.activateNewBodyScriptElements()}activateNewBodyScriptElements(){for(const e of this.newBodyScriptElements){const t=n(e);e.replaceWith(t)}}async assignNewBody(){await this.renderElement(this.currentElement,this.newElement)}get unusedDynamicStylesheetElements(){return this.oldHeadStylesheetElements.filter((e=>"dynamic"===e.getAttribute("data-turbo-track")))}get oldHeadStylesheetElements(){return this.currentHeadSnapshot.getStylesheetElementsNotInSnapshot(this.newHeadSnapshot)}get newHeadStylesheetElements(){return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot)}get newHeadScriptElements(){return this.newHeadSnapshot.getScriptElementsNotInSnapshot(this.currentHeadSnapshot)}get currentHeadProvisionalElements(){return this.currentHeadSnapshot.provisionalElements}get newHeadProvisionalElements(){return this.newHeadSnapshot.provisionalElements}get newBodyScriptElements(){return this.newElement.querySelectorAll("script")}}class pt extends mt{static renderElement(e,t){ve(e,t,{callbacks:{beforeNodeMorphed:(e,t)=>!(we(e,t)&&!ye(e))||(e.reload(),!1)}}),o("turbo:morph",{detail:{currentElement:e,newElement:t}})}async preservingPermanentElements(e){return await e()}get renderMethod(){return"morph"}get shouldAutofocus(){return!1}}class ft{keys=[];snapshots={};constructor(e){this.size=e}has(e){return x(e)in this.snapshots}get(e){if(this.has(e)){const t=this.read(e);return this.touch(e),t}}put(e,t){return this.write(e,t),this.touch(e),t}clear(){this.snapshots={}}read(e){return this.snapshots[x(e)]}write(e,t){this.snapshots[x(e)]=t}touch(e){const t=x(e),s=this.keys.indexOf(t);s>-1&&this.keys.splice(s,1),this.keys.unshift(t),this.trim()}trim(){for(const e of this.keys.splice(this.size))delete this.snapshots[e]}}class gt extends le{snapshotCache=new ft(10);lastRenderedLocation=new URL(location.href);forceReloaded=!1;shouldTransitionTo(e){return this.snapshot.prefersViewTransitions&&e.prefersViewTransitions}renderPage(e,t=!1,s=!0,r){const i=new(this.isPageRefresh(r)&&this.snapshot.shouldMorphPage?pt:mt)(this.snapshot,e,t,s);return i.shouldRender?r?.changeHistory():this.forceReloaded=!0,this.render(i)}renderError(e,t){t?.changeHistory();const s=new ut(this.snapshot,e,!1);return this.render(s)}clearSnapshotCache(){this.snapshotCache.clear()}async cacheSnapshot(e=this.snapshot){if(e.isCacheable){this.delegate.viewWillCacheSnapshot();const{lastRenderedLocation:t}=this;await h();const s=e.clone();return this.snapshotCache.put(t,s),s}}getCachedSnapshotForLocation(e){return this.snapshotCache.get(e)}isPageRefresh(e){return!e||this.lastRenderedLocation.pathname===e.location.pathname&&"replace"===e.action}shouldPreserveScrollPosition(e){return this.isPageRefresh(e)&&this.snapshot.shouldPreserveScrollPosition}get snapshot(){return Pe.fromElement(this.element)}}class bt{selector="a[data-turbo-preload]";constructor(e,t){this.delegate=e,this.snapshotCache=t}start(){"loading"===document.readyState?document.addEventListener("DOMContentLoaded",this.#C):this.preloadOnLoadLinksForView(document.body)}stop(){document.removeEventListener("DOMContentLoaded",this.#C)}preloadOnLoadLinksForView(e){for(const t of e.querySelectorAll(this.selector))this.delegate.shouldPreloadLink(t)&&this.preloadURL(t)}async preloadURL(e){const t=new URL(e.href);if(this.snapshotCache.has(t))return;const s=new X(this,z.get,t,new URLSearchParams,e);await s.perform()}prepareRequest(e){e.headers["X-Sec-Purpose"]="prefetch"}async requestSucceededWithResponse(e,t){try{const s=await t.responseHTML,r=Pe.fromHTMLString(s);this.snapshotCache.put(e.url,r)}catch(e){}}requestStarted(e){}requestErrored(e){}requestFinished(e){}requestPreventedHandlingResponse(e,t){}requestFailedWithResponse(e,t){}#C=()=>{this.preloadOnLoadLinksForView(document.body)}}class vt{constructor(e){this.session=e}clear(){this.session.clearCache()}resetCacheControl(){this.#P("")}exemptPageFromCache(){this.#P("no-cache")}exemptPageFromPreview(){this.#P("no-preview")}#P(e){!function(e,t){let s=w(e);s||(s=document.createElement("meta"),s.setAttribute("name",e),document.head.appendChild(s)),s.setAttribute("content",t)}("turbo-cache-control",e)}}function St(e){Object.defineProperties(e,wt)}const wt={absoluteURL:{get(){return this.toString()}}};const yt=new class{serviceWorker;async start(e="/service-worker.js",{scope:t="/",type:s="module",native:r=!0}={}){if("serviceWorker"in navigator){r&&this.#k(),await this.#M(),this.#F(navigator.serviceWorker.controller,e);try{const r=await navigator.serviceWorker.register(e,{scope:t,type:s}),i=r.active||r.waiting||r.installing;return this.#F(i,e),this.serviceWorker=i,r}catch(e){console.error(e)}}else console.warn("Service Worker not available.")}#k(){!function(e,t,s){const r=`${[e,t].map(encodeURIComponent).join("=")}; path=/; expires=${new Date(Date.now()+s).toUTCString()}`;document.cookie=r}("x_user_agent",window.navigator.userAgent,31536e6)}#F(e,t){e&&!V(e.scriptURL,t)&&console.warn(`Expected service worker script ${t} but found ${e.scriptURL}. This may indicate multiple service workers or a cached version.`)}#M(){return new Promise((e=>{"complete"!==document.readyState?document.addEventListener("DOMContentLoaded",(()=>e())):e()}))}},Et=new class{navigator=new rt(this);history=new Xe(this);view=new gt(this,document.documentElement);adapter=new ze(this);pageObserver=new ct(this);cacheObserver=new Ge;linkPrefetchObserver=new Ke(this,document);linkClickObserver=new ue(this,window);formSubmitObserver=new ce(this,document);scrollObserver=new lt(this);streamObserver=new dt(this);formLinkClickObserver=new me(this,document.documentElement);frameRedirector=new Je(this,document.documentElement);streamMessageRenderer=new ht;cache=new vt(this);enabled=!0;started=!1;#I=150;constructor(e){this.recentRequests=e,this.preloader=new bt(this,this.view.snapshotCache),this.debouncedRefresh=this.refresh,this.pageRefreshDebouncePeriod=this.pageRefreshDebouncePeriod}start(){this.started||(this.pageObserver.start(),this.cacheObserver.start(),this.linkPrefetchObserver.start(),this.formLinkClickObserver.start(),this.linkClickObserver.start(),this.formSubmitObserver.start(),this.scrollObserver.start(),this.streamObserver.start(),this.frameRedirector.start(),this.history.start(),this.preloader.start(),this.started=!0,this.enabled=!0)}disable(){this.enabled=!1}stop(){this.started&&(this.pageObserver.stop(),this.cacheObserver.stop(),this.linkPrefetchObserver.stop(),this.formLinkClickObserver.stop(),this.linkClickObserver.stop(),this.formSubmitObserver.stop(),this.scrollObserver.stop(),this.streamObserver.stop(),this.frameRedirector.stop(),this.history.stop(),this.preloader.stop(),this.started=!1)}registerAdapter(e){this.adapter=e}visit(e,t={}){const s=t.frame?document.getElementById(t.frame):null;if(s instanceof r){const r=t.action||S(s);s.delegate.proposeVisitIfNavigatedWithAction(s,r),s.src=e.toString()}else this.navigator.proposeVisit(I(e),t)}refresh(e,t){const s=t&&this.recentRequests.has(t),r=e===document.baseURI;s||this.navigator.currentVisit||!r||this.visit(e,{action:"replace",shouldCacheSnapshot:!1})}connectStreamSource(e){this.streamObserver.connectStreamSource(e)}disconnectStreamSource(e){this.streamObserver.disconnectStreamSource(e)}renderStreamMessage(e){this.streamMessageRenderer.render(te.wrap(e))}clearCache(){this.view.clearSnapshotCache()}setProgressBarDelay(e){console.warn("Please replace `session.setProgressBarDelay(delay)` with `session.progressBarDelay = delay`. The function is deprecated and will be removed in a future version of Turbo.`"),this.progressBarDelay=e}set progressBarDelay(e){F.drive.progressBarDelay=e}get progressBarDelay(){return F.drive.progressBarDelay}set drive(e){F.drive.enabled=e}get drive(){return F.drive.enabled}set formMode(e){F.forms.mode=e}get formMode(){return F.forms.mode}get location(){return this.history.location}get restorationIdentifier(){return this.history.restorationIdentifier}get pageRefreshDebouncePeriod(){return this.#I}set pageRefreshDebouncePeriod(e){this.refresh=function(e,t){let s=null;return(...r)=>{clearTimeout(s),s=setTimeout((()=>e.apply(this,r)),t)}}(this.debouncedRefresh.bind(this),e),this.#I=e}shouldPreloadLink(e){const t=e.hasAttribute("data-turbo-method"),s=e.hasAttribute("data-turbo-stream"),i=e.getAttribute("data-turbo-frame"),n="_top"==i?null:document.getElementById(i)||A(e,"turbo-frame:not([disabled])");if(t||s||n instanceof r)return!1;{const t=new URL(e.href);return this.elementIsNavigatable(e)&&N(t,this.snapshot.rootLocation)}}historyPoppedToLocationWithRestorationIdentifierAndDirection(e,t,s){this.enabled?this.navigator.startVisit(e,t,{action:"restore",historyChanged:!0,direction:s}):this.adapter.pageInvalidated({reason:"turbo_disabled"})}scrollPositionChanged(e){this.history.updateRestorationData({scrollPosition:e})}willSubmitFormLinkToLocation(e,t){return this.elementIsNavigatable(e)&&N(t,this.snapshot.rootLocation)}submittedFormLinkToLocation(){}canPrefetchRequestToLocation(e,t){return this.elementIsNavigatable(e)&&N(t,this.snapshot.rootLocation)&&this.navigator.linkPrefetchingIsEnabledForLocation(t)}willFollowLinkToLocation(e,t,s){return this.elementIsNavigatable(e)&&N(t,this.snapshot.rootLocation)&&this.applicationAllowsFollowingLinkToLocation(e,t,s)}followedLinkToLocation(e,t){const s=this.getActionForLink(e),r=e.hasAttribute("data-turbo-stream");this.visit(t.href,{action:s,acceptsStreamResponse:r})}allowsVisitingLocationWithAction(e,t){return this.locationWithActionIsSamePage(e,t)||this.applicationAllowsVisitingLocation(e)}visitProposedToLocation(e,t){St(e),this.adapter.visitProposedToLocation(e,t)}visitStarted(e){e.acceptsStreamResponse||(f(document.documentElement),this.view.markVisitDirection(e.direction)),St(e.location),e.silent||this.notifyApplicationAfterVisitingLocation(e.location,e.action)}visitCompleted(e){this.view.unmarkVisitDirection(),g(document.documentElement),this.notifyApplicationAfterPageLoad(e.getTimingMetrics())}locationWithActionIsSamePage(e,t){return this.navigator.locationWithActionIsSamePage(e,t)}visitScrolledToSamePageLocation(e,t){this.notifyApplicationAfterVisitingSamePageLocation(e,t)}willSubmitForm(e,t){const s=H(e,t);return this.submissionIsNavigatable(e,t)&&N(I(s),this.snapshot.rootLocation)}formSubmitted(e,t){this.navigator.submitForm(e,t)}pageBecameInteractive(){this.view.lastRenderedLocation=this.location,this.notifyApplicationAfterPageLoad()}pageLoaded(){this.history.assumeControlOfScrollRestoration()}pageWillUnload(){this.history.relinquishControlOfScrollRestoration()}receivedMessageFromStream(e){this.renderStreamMessage(e)}viewWillCacheSnapshot(){this.navigator.currentVisit?.silent||this.notifyApplicationBeforeCachingSnapshot()}allowsImmediateRender({element:e},t){const s=this.notifyApplicationBeforeRender(e,t),{defaultPrevented:r,detail:{render:i}}=s;return this.view.renderer&&i&&(this.view.renderer.renderElement=i),!r}viewRenderedSnapshot(e,t,s){this.view.lastRenderedLocation=this.history.location,this.notifyApplicationAfterRender(s)}preloadOnLoadLinksForView(e){this.preloader.preloadOnLoadLinksForView(e)}viewInvalidated(e){this.adapter.pageInvalidated(e)}frameLoaded(e){this.notifyApplicationAfterFrameLoad(e)}frameRendered(e,t){this.notifyApplicationAfterFrameRender(e,t)}applicationAllowsFollowingLinkToLocation(e,t,s){return!this.notifyApplicationAfterClickingLinkToLocation(e,t,s).defaultPrevented}applicationAllowsVisitingLocation(e){return!this.notifyApplicationBeforeVisitingLocation(e).defaultPrevented}notifyApplicationAfterClickingLinkToLocation(e,t,s){return o("turbo:click",{target:e,detail:{url:t.href,originalEvent:s},cancelable:!0})}notifyApplicationBeforeVisitingLocation(e){return o("turbo:before-visit",{detail:{url:e.href},cancelable:!0})}notifyApplicationAfterVisitingLocation(e,t){return o("turbo:visit",{detail:{url:e.href,action:t}})}notifyApplicationBeforeCachingSnapshot(){return o("turbo:before-cache")}notifyApplicationBeforeRender(e,t){return o("turbo:before-render",{detail:{newBody:e,...t},cancelable:!0})}notifyApplicationAfterRender(e){return o("turbo:render",{detail:{renderMethod:e}})}notifyApplicationAfterPageLoad(e={}){return o("turbo:load",{detail:{url:this.location.href,timing:e}})}notifyApplicationAfterVisitingSamePageLocation(e,t){dispatchEvent(new HashChangeEvent("hashchange",{oldURL:e.toString(),newURL:t.toString()}))}notifyApplicationAfterFrameLoad(e){return o("turbo:frame-load",{target:e})}notifyApplicationAfterFrameRender(e,t){return o("turbo:frame-render",{detail:{fetchResponse:e},target:t,cancelable:!0})}submissionIsNavigatable(e,t){if("off"==F.forms.mode)return!1;{const s=!t||this.elementIsNavigatable(t);return"optin"==F.forms.mode?s&&null!=e.closest('[data-turbo="true"]'):s&&this.elementIsNavigatable(e)}}elementIsNavigatable(e){const t=A(e,"[data-turbo]"),s=A(e,"turbo-frame");return F.drive.enabled||s?!t||"false"!=t.getAttribute("data-turbo"):!!t&&"true"==t.getAttribute("data-turbo")}getActionForLink(e){return S(e)||"advance"}get snapshot(){return this.view.snapshot}}(_),{cache:At,navigator:Rt}=Et;function Lt(){Et.start()}function Tt(e){Et.registerAdapter(e)}function Ct(e,t){Et.visit(e,t)}function Pt(e){Et.connectStreamSource(e)}function kt(e){Et.disconnectStreamSource(e)}function Mt(e){Et.renderStreamMessage(e)}function Ft(){console.warn("Please replace `Turbo.clearCache()` with `Turbo.cache.clear()`. The top-level function is deprecated and will be removed in a future version of Turbo.`"),Et.clearCache()}function It(e){console.warn("Please replace `Turbo.setProgressBarDelay(delay)` with `Turbo.config.drive.progressBarDelay = delay`. The top-level function is deprecated and will be removed in a future version of Turbo.`"),F.drive.progressBarDelay=e}function qt(e){console.warn("Please replace `Turbo.setConfirmMethod(confirmMethod)` with `Turbo.config.forms.confirm = confirmMethod`. The top-level function is deprecated and will be removed in a future version of Turbo.`"),F.forms.confirm=e}function Ht(e){console.warn("Please replace `Turbo.setFormMode(mode)` with `Turbo.config.forms.mode = mode`. The top-level function is deprecated and will be removed in a future version of Turbo.`"),F.forms.mode=e}var Ot=Object.freeze({__proto__:null,navigator:Rt,session:Et,cache:At,PageRenderer:mt,PageSnapshot:Pe,FrameRenderer:ge,fetch:$,config:F,offline:yt,start:Lt,registerAdapter:Tt,visit:Ct,connectStreamSource:Pt,disconnectStreamSource:kt,renderStreamMessage:Mt,clearCache:Ft,setProgressBarDelay:It,setConfirmMethod:qt,setFormMode:Ht});class Bt extends Error{}function Nt(e){if(null!=e){const t=document.getElementById(e);if(t instanceof r)return t}}function Dt(e,t){if(e){const s=e.getAttribute("src");if(null!=s&&null!=t&&V(s,t))throw new Error(`Matching element has a source URL which references itself`);if(e.ownerDocument!==document&&(e=document.importNode(e,!0)),e instanceof r)return e.connectedCallback(),e.disconnectedCallback(),e}}const xt={after(){this.targetElements.forEach((e=>e.parentElement?.insertBefore(this.templateContent,e.nextSibling)))},append(){this.removeDuplicateTargetChildren(),this.targetElements.forEach((e=>e.append(this.templateContent)))},before(){this.targetElements.forEach((e=>e.parentElement?.insertBefore(this.templateContent,e)))},prepend(){this.removeDuplicateTargetChildren(),this.targetElements.forEach((e=>e.prepend(this.templateContent)))},remove(){this.targetElements.forEach((e=>e.remove()))},replace(){const e=this.getAttribute("method");this.targetElements.forEach((t=>{"morph"===e?ve(t,this.templateContent):t.replaceWith(this.templateContent)}))},update(){const e=this.getAttribute("method");this.targetElements.forEach((t=>{"morph"===e?Se(t,this.templateContent):(t.innerHTML="",t.append(this.templateContent))}))},refresh(){Et.refresh(this.baseURI,this.requestId)}};class Vt extends HTMLElement{static async renderElement(e){await e.performAction()}async connectedCallback(){try{await this.render()}catch(e){console.error(e)}finally{this.disconnect()}}async render(){return this.renderPromise??=(async()=>{const e=this.beforeRenderEvent;this.dispatchEvent(e)&&(await c(),await e.detail.render(this))})()}disconnect(){try{this.remove()}catch{}}removeDuplicateTargetChildren(){this.duplicateChildren.forEach((e=>e.remove()))}get duplicateChildren(){const e=this.targetElements.flatMap((e=>[...e.children])).filter((e=>!!e.getAttribute("id"))),t=[...this.templateContent?.children||[]].filter((e=>!!e.getAttribute("id"))).map((e=>e.getAttribute("id")));return e.filter((e=>t.includes(e.getAttribute("id"))))}get performAction(){if(this.action){const e=xt[this.action];if(e)return e;this.#q("unknown action")}this.#q("action attribute is missing")}get targetElements(){return this.target?this.targetElementsById:this.targets?this.targetElementsByQuery:void this.#q("target or targets attribute is missing")}get templateContent(){return this.templateElement.content.cloneNode(!0)}get templateElement(){if(null===this.firstElementChild){const e=this.ownerDocument.createElement("template");return this.appendChild(e),e}if(this.firstElementChild instanceof HTMLTemplateElement)return this.firstElementChild;this.#q("first child element must be a