From 6da1200b64111d7c2e21d526071f543826c28868 Mon Sep 17 00:00:00 2001 From: Ahmed Ejaz Date: Thu, 24 Jul 2025 02:30:37 +0500 Subject: [PATCH 01/76] Refactor product routes to remove feature toggle constraints and simplify access --- config/routes/admin.rb | 19 ++++++++----------- config/routes/spree.rb | 15 +-------------- 2 files changed, 9 insertions(+), 25 deletions(-) diff --git a/config/routes/admin.rb b/config/routes/admin.rb index 9c3eb66324..f4efd0a64e 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -73,17 +73,14 @@ Openfoodnetwork::Application.routes.draw do post :import, on: :collection end - constraints FeatureToggleConstraint.new(:admin_style_v3) do - # This might be easier to arrange once we rename the controller to plain old "products" - post '/products/bulk_update', to: 'products_v3#bulk_update' - get '/products', to: 'products_v3#index' - # we already have DELETE admin/products/:id here - delete 'products_v3/:id', to: 'products_v3#destroy', as: 'product_destroy' - delete 'products_v3/destroy_variant/:id', to: 'products_v3#destroy_variant', as: 'destroy_variant' - post 'clone/:id', to: 'products_v3#clone', as: 'clone_product' - - resources :product_preview, only: [:show] - end + # This might be easier to arrange once we rename the controller to plain old "products" + post '/products/bulk_update', to: 'products_v3#bulk_update' + get '/products', to: 'products_v3#index' + # we already have DELETE admin/products/:id here + delete 'products_v3/:id', to: 'products_v3#destroy', as: 'product_destroy' + delete 'products_v3/destroy_variant/:id', to: 'products_v3#destroy_variant', as: 'destroy_variant' + post 'clone/:id', to: 'products_v3#clone', as: 'clone_product' + resources :product_preview, only: [:show] resources :variant_overrides do post :bulk_update, on: :collection diff --git a/config/routes/spree.rb b/config/routes/spree.rb index c6c84fddbe..be474a7383 100644 --- a/config/routes/spree.rb +++ b/config/routes/spree.rb @@ -50,16 +50,8 @@ Spree::Core::Engine.routes.draw do resources :users - constraints FeatureToggleConstraint.new(:admin_style_v3, negate: true) do - # Show old bulk products screen - resources :products, :index do - post :bulk_update, :on => :collection, :as => :bulk_update - end - end - - resources :products, except: :index do + resources :products do member do - get :clone get :group_buy_options get :seo end @@ -83,11 +75,6 @@ Spree::Core::Engine.routes.draw do end end - if Rails.env.development? - # duplicate old path for reference when admin_style_v3 enabled - resources :products_old, to: 'products#index', only: :index - end - get '/variants/search', :to => "variants#search", :as => :search_variants resources :properties From 44cbe55c965829b5606adbed8d312a8786bf606a Mon Sep 17 00:00:00 2001 From: Ahmed Ejaz Date: Sun, 27 Jul 2025 05:25:50 +0500 Subject: [PATCH 02/76] Update product routes and views for consistency and clarity --- app/views/admin/products_v3/_table.html.haml | 2 +- app/views/spree/admin/shared/_product_sub_menu.html.haml | 2 +- config/routes/admin.rb | 5 ++--- config/routes/spree.rb | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/app/views/admin/products_v3/_table.html.haml b/app/views/admin/products_v3/_table.html.haml index 5226461c94..15d7646c6c 100644 --- a/app/views/admin/products_v3/_table.html.haml +++ b/app/views/admin/products_v3/_table.html.haml @@ -1,4 +1,4 @@ -= form_with url: bulk_update_admin_products_path, method: :post, id: "products-form", += form_with url: admin_products_bulk_update_path, method: :post, id: "products-form", builder: BulkFormBuilder, html: { data: { 'turbo-frame': "_self", controller: "bulk-form", diff --git a/app/views/spree/admin/shared/_product_sub_menu.html.haml b/app/views/spree/admin/shared/_product_sub_menu.html.haml index 91fc3e6bc5..7fe1ccc3ed 100644 --- a/app/views/spree/admin/shared/_product_sub_menu.html.haml +++ b/app/views/spree/admin/shared/_product_sub_menu.html.haml @@ -1,6 +1,6 @@ - content_for :sub_menu do %ul#sub_nav.inline-menu - = tab :products, :products_v3 + = tab :products, :products_v3, url: admin_products_path = tab :properties = tab :variant_overrides, url: main_app.admin_inventory_path, match_path: '/inventory' if feature?(:inventory, spree_current_user.enterprises) = tab :import, url: main_app.admin_product_import_path, match_path: '/product_import' diff --git a/config/routes/admin.rb b/config/routes/admin.rb index f4efd0a64e..9b844fcfd0 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -74,9 +74,8 @@ Openfoodnetwork::Application.routes.draw do end # This might be easier to arrange once we rename the controller to plain old "products" - post '/products/bulk_update', to: 'products_v3#bulk_update' - get '/products', to: 'products_v3#index' - # we already have DELETE admin/products/:id here + post '/products/bulk_update', to: 'products_v3#bulk_update', as: 'products_bulk_update' + get '/products', to: 'products_v3#index', as: 'products' delete 'products_v3/:id', to: 'products_v3#destroy', as: 'product_destroy' delete 'products_v3/destroy_variant/:id', to: 'products_v3#destroy_variant', as: 'destroy_variant' post 'clone/:id', to: 'products_v3#clone', as: 'clone_product' diff --git a/config/routes/spree.rb b/config/routes/spree.rb index be474a7383..512f708b14 100644 --- a/config/routes/spree.rb +++ b/config/routes/spree.rb @@ -50,7 +50,7 @@ Spree::Core::Engine.routes.draw do resources :users - resources :products do + resources :products, except: [:index, :destroy] do member do get :group_buy_options get :seo From 0fa67c69fd56735705598e56d1f5e1b2ef8e1646 Mon Sep 17 00:00:00 2001 From: Ahmed Ejaz Date: Sun, 27 Jul 2025 06:03:14 +0500 Subject: [PATCH 03/76] Remove bulk product update functionality Removes the bulk product update feature and its associated components: - Removes Angular-based bulk product editing controller and views - Deletes bulk product API endpoints and related controller actions - Removes product cloning and variant deletion functionality - Removes associated JavaScript tests and specs This appears to be part of a larger effort to modernize/simplify the product management interface, removing legacy Angular-based bulk editing in favor of a different approach. --- .../admin/bulk_product_update.js.coffee | 390 ------ .../resources/product_resource.js.coffee | 6 - .../admin/services/bulk_products.js.coffee | 76 -- app/controllers/api/v0/products_controller.rb | 27 - .../spree/admin/products_controller.rb | 27 - .../spree/admin/variants_controller.rb | 17 - .../spree/admin/products/index.html.haml | 14 - .../admin/products/index/_actions.html.haml | 11 - .../admin/products/index/_data.html.haml | 5 - .../admin/products/index/_filters.html.haml | 33 - .../admin/products/index/_header.html.haml | 10 - .../products/index/_indicators.html.haml | 14 - .../admin/products/index/_products.html.haml | 14 - .../products/index/_products_head.html.haml | 41 - .../index/_products_product.html.haml | 33 - .../index/_products_variant.html.haml | 39 - .../products/index/_save_button_row.html.haml | 3 - config/routes/api.rb | 1 - config/routes/spree.rb | 2 +- .../admin/bulk_product_update_spec.js.coffee | 1080 ----------------- .../services/bulk_products_spec.js.coffee | 188 --- 21 files changed, 1 insertion(+), 2030 deletions(-) delete mode 100644 app/assets/javascripts/admin/bulk_product_update.js.coffee delete mode 100644 app/assets/javascripts/admin/resources/resources/product_resource.js.coffee delete mode 100644 app/assets/javascripts/admin/services/bulk_products.js.coffee delete mode 100644 app/views/spree/admin/products/index.html.haml delete mode 100644 app/views/spree/admin/products/index/_actions.html.haml delete mode 100644 app/views/spree/admin/products/index/_data.html.haml delete mode 100644 app/views/spree/admin/products/index/_filters.html.haml delete mode 100644 app/views/spree/admin/products/index/_header.html.haml delete mode 100644 app/views/spree/admin/products/index/_indicators.html.haml delete mode 100644 app/views/spree/admin/products/index/_products.html.haml delete mode 100644 app/views/spree/admin/products/index/_products_head.html.haml delete mode 100644 app/views/spree/admin/products/index/_products_product.html.haml delete mode 100644 app/views/spree/admin/products/index/_products_variant.html.haml delete mode 100644 app/views/spree/admin/products/index/_save_button_row.html.haml delete mode 100644 spec/javascripts/unit/admin/bulk_product_update_spec.js.coffee delete mode 100644 spec/javascripts/unit/admin/services/bulk_products_spec.js.coffee diff --git a/app/assets/javascripts/admin/bulk_product_update.js.coffee b/app/assets/javascripts/admin/bulk_product_update.js.coffee deleted file mode 100644 index ab62e3f321..0000000000 --- a/app/assets/javascripts/admin/bulk_product_update.js.coffee +++ /dev/null @@ -1,390 +0,0 @@ -angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout, $filter, $http, $window, $location, BulkProducts, DisplayProperties, DirtyProducts, VariantUnitManager, StatusMessage, producers, Taxons, Columns, tax_categories, RequestMonitor, SortOptions, ErrorsParser, ProductFiltersUrl) -> - $scope.StatusMessage = StatusMessage - - $scope.columns = Columns.columns - - $scope.variant_unit_options = VariantUnitManager.variantUnitOptions() - - $scope.RequestMonitor = RequestMonitor - $scope.pagination = BulkProducts.pagination - $scope.per_page_options = [ - {id: 15, name: t('js.admin.orders.index.per_page', results: 15)}, - {id: 50, name: t('js.admin.orders.index.per_page', results: 50)}, - {id: 100, name: t('js.admin.orders.index.per_page', results: 100)} - ] - - $scope.q = { - producerFilter: "" - categoryFilter: "" - importDateFilter: "" - query: "" - sorting: "" - } - - $scope.sorting = "name asc" - $scope.producers = producers - $scope.taxons = Taxons.all - $scope.tax_categories = tax_categories - $scope.page = 1 - $scope.per_page = 15 - $scope.products = BulkProducts.products - $scope.DisplayProperties = DisplayProperties - - $scope.sortOptions = SortOptions - - $scope.initialise = -> - $scope.q = ProductFiltersUrl.loadFromUrl($location.search()) - $scope.fetchProducts() - - $scope.$watchCollection '[q.query, q.producerFilter, q.categoryFilter, q.importDateFilter, per_page]', -> - $scope.page = 1 # Reset page when changing filters for new search - - $scope.changePage = (newPage) -> - $scope.page = newPage - $scope.fetchProducts() - - $scope.fetchProducts = -> - removeClearedValues() - params = { - 'q[name_cont]': $scope.q.query, - 'q[variants_supplier_id_eq]': $scope.q.producerFilter, - 'q[variants_primary_taxon_id_eq]': $scope.q.categoryFilter, - 'q[s]': $scope.sorting, - import_date: $scope.q.importDateFilter, - page: $scope.page, - per_page: $scope.per_page - } - RequestMonitor.load(BulkProducts.fetch(params).$promise).then -> - # update url with the filters used - $location.search(ProductFiltersUrl.generate($scope.q)) - $scope.resetProducts() - - removeClearedValues = -> - delete $scope.q.producerFilter if $scope.q.producerFilter == "0" - delete $scope.q.categoryFilter if $scope.q.categoryFilter == "0" - delete $scope.q.importDateFilter if $scope.q.importDateFilter == "0" - - $timeout -> - if $scope.showLatestImport - $scope.q.importDateFilter = $scope.importDates[1].id - - $scope.resetProducts = -> - DirtyProducts.clear() - StatusMessage.clear() - - $scope.updateOnHand = (product) -> - on_demand_variants = [] - if product.variants - on_demand_variants = (variant for id, variant of product.variants when variant.on_demand) - - unless product.on_demand || on_demand_variants.length > 0 - product.on_hand = $scope.onHand(product) - - - $scope.onHand = (product) -> - onHand = 0 - if product.hasOwnProperty("variants") and product.variants instanceof Object - for id, variant of product.variants - onHand = onHand + parseInt(if variant.on_hand > 0 then variant.on_hand else 0) - else - onHand = "error" - onHand - - $scope.shiftTab = (tab) -> - $scope.visibleTab.visible = false unless $scope.visibleTab == tab || $scope.visibleTab == undefined - tab.visible = !tab.visible - $scope.visibleTab = tab - - $scope.resetSelectFilters = -> - $scope.q.query = "" - $scope.q.producerFilter = "0" - $scope.q.categoryFilter = "0" - $scope.q.importDateFilter = "0" - $scope.fetchProducts() - - $scope.$watch 'sortOptions', (sort) -> - return unless sort && sort.predicate != "" - - $scope.sorting = sort.getSortingExpr(defaultDirection: "asc") - $scope.fetchProducts() - , true - - confirm_unsaved_changes = () -> - (DirtyProducts.count() > 0 and confirm(t("unsaved_changes_confirmation"))) or (DirtyProducts.count() == 0) - - editProductUrl = (product, variant) -> - "/admin/products/" + product.id + ((if variant then "/variants/" + variant.id else "")) + "/edit" - - $scope.editWarn = (product, variant) -> - if confirm_unsaved_changes() - $window.location.href = ProductFiltersUrl.buildUrl(editProductUrl(product, variant), $scope.q) - - $scope.toggleShowAllVariants = -> - showVariants = !DisplayProperties.showVariants 0 - $scope.products.forEach (product) -> - DisplayProperties.setShowVariants product.id, showVariants - DisplayProperties.setShowVariants 0, showVariants - - $scope.addVariant = (product) -> - # Set new variant category to same as last product variant category to keep compactibility with deleted variant callback to set new variant category - newVariantId = $scope.nextVariantId(); - newVariantCategoryId = product.variants[product.variants.length - 1]?.category_id - product.variants.push - id: newVariantId - unit_value: null - unit_description: null - on_demand: false - display_as: null - display_name: null - on_hand: null - price: null - tax_category_id: null - category_id: newVariantCategoryId - DisplayProperties.setShowVariants product.id, true - DirtyProducts.addVariantProperty(product.id, newVariantId, 'category_id', newVariantCategoryId) - - - $scope.nextVariantId = -> - $scope.variantIdCounter = 0 unless $scope.variantIdCounter? - $scope.variantIdCounter -= 1 - $scope.variantIdCounter - - $scope.deleteProduct = (product) -> - if confirm(t('are_you_sure')) - $http( - method: "DELETE" - url: "/api/v0/products/" + product.id - ).then (response) -> - $scope.products.splice $scope.products.indexOf(product), 1 - DirtyProducts.deleteProduct product.id - $scope.displayDirtyProducts() - - - $scope.deleteVariant = (product, variant) -> - if product.variants.length > 1 - if !$scope.variantSaved(variant) - $scope.removeVariant(product, variant) - else - if confirm(t("are_you_sure")) - $http( - method: "DELETE" - url: "/api/v0/products/" + product.id + "/variants/" + variant.id - ).then (response) -> - $scope.removeVariant(product, variant) - else - alert(t("delete_product_variant")) - - $scope.removeVariant = (product, variant) -> - product.variants.splice product.variants.indexOf(variant), 1 - DirtyProducts.deleteVariant product.id, variant.id - $scope.displayDirtyProducts() - - - $scope.cloneProduct = (product) -> - BulkProducts.cloneProduct product - - $scope.hasVariants = (product) -> - product.variants.length > 0 - - - $scope.hasUnit = (variant) -> - variant.variant_unit_with_scale? - - $scope.variantSaved = (variant) -> - variant.hasOwnProperty('id') && variant.id > 0 - - - $scope.hasOnDemandVariants = (product) -> - (variant for id, variant of product.variants when variant.on_demand).length > 0 - - - $scope.submitProducts = -> - # Pack pack $scope.products, so they will match the list returned from the server, - # then pack $scope.dirtyProducts, ensuring that the correct product info is sent to the server. - $scope.packProduct product for id, product of $scope.products - $scope.packProduct product for id, product of DirtyProducts.all() - - productsToSubmit = filterSubmitProducts(DirtyProducts.all()) - if productsToSubmit.length > 0 - $scope.updateProducts productsToSubmit # Don't submit an empty list - else - StatusMessage.display 'alert', t("products_change") - - - $scope.updateProducts = (productsToSubmit) -> - $scope.displayUpdating() - $http( - method: "POST" - url: "/admin/products/bulk_update" - data: - products: productsToSubmit - filters: - 'q[name_cont]': $scope.q.query - 'q[variants_supplier_id_eq]': $scope.q.producerFilter - 'q[variants_primary_taxon_id_eq]': $scope.q.categoryFilter - 'q[s]': $scope.sorting - import_date: $scope.q.importDateFilter - page: $scope.page - per_page: $scope.per_page - ).then((response) -> - DirtyProducts.clear() - BulkProducts.updateVariantLists(response.data.products || []) - $timeout -> $scope.displaySuccess() - ).catch (response) -> - if response.status == 400 && response.data.errors? - errorsString = ErrorsParser.toString(response.data.errors, response.status) - $scope.displayFailure t("products_update_error") + "\n" + errorsString - else - $scope.displayFailure t("products_update_error_data") + response.status - - $scope.cancel = (destination) -> - $window.location = destination - - $scope.packProduct = (product) -> - if product.variants - for id, variant of product.variants - $scope.packVariant variant - - - $scope.packVariant = (variant) -> - if variant.variant_unit_with_scale - match = variant.variant_unit_with_scale.match(/^([^_]+)_([\d\.]+)$/) - if match - variant.variant_unit = match[1] - variant.variant_unit_scale = parseFloat(match[2]) - else - variant.variant_unit = variant.variant_unit_with_scale - variant.variant_unit_scale = null - - if variant.hasOwnProperty("unit_value_with_description") - match = variant.unit_value_with_description.match(/^([\d\.\,]+(?= |$)|)( |)(.*)$/) - if match - variant.unit_value = parseFloat(match[1].replace(",", ".")) - variant.unit_value = null if isNaN(variant.unit_value) - if variant.unit_value && variant.variant_unit_scale - variant.unit_value = parseFloat(window.bigDecimal.multiply(variant.unit_value, variant.variant_unit_scale, 2)) - variant.unit_description = match[3] - - $scope.incrementLimit = -> - if $scope.limit < $scope.products.length - $scope.limit = $scope.limit + 5 - - - $scope.displayUpdating = -> - StatusMessage.display 'progress', t("saving") - - - $scope.displaySuccess = -> - StatusMessage.display 'success',t("products_changes_saved") - $scope.bulk_product_form.$setPristine() - - - $scope.displayFailure = (failMessage) -> - StatusMessage.display 'failure', t("products_update_error_msg") + " #{failMessage}" - - - $scope.displayDirtyProducts = -> - count = DirtyProducts.count() - switch count - when 0 then StatusMessage.clear() - when 1 then StatusMessage.display 'notice', t("one_product_unsaved") - else StatusMessage.display 'notice', t("products_unsaved", n: count) - - -filterSubmitProducts = (productsToFilter) -> - filteredProducts = [] - if productsToFilter instanceof Object - angular.forEach productsToFilter, (product) -> - if product.hasOwnProperty("id") - filteredProduct = {id: product.id} - filteredVariants = [] - hasUpdatableProperty = false - - if product.hasOwnProperty("variants") - angular.forEach product.variants, (variant) -> - result = filterSubmitVariant variant - filteredVariant = result.filteredVariant - variantHasUpdatableProperty = result.hasUpdatableProperty - filteredVariants.push filteredVariant if variantHasUpdatableProperty - - if product.hasOwnProperty("name") - filteredProduct.name = product.name - hasUpdatableProperty = true - if product.hasOwnProperty("price") - filteredProduct.price = product.price - hasUpdatableProperty = true - if product.hasOwnProperty("on_hand") and filteredVariants.length == 0 #only update if no variants present - filteredProduct.on_hand = product.on_hand - hasUpdatableProperty = true - if product.hasOwnProperty("on_demand") and filteredVariants.length == 0 #only update if no variants present - filteredProduct.on_demand = product.on_demand - hasUpdatableProperty = true - if product.hasOwnProperty("inherits_properties") - filteredProduct.inherits_properties = product.inherits_properties - hasUpdatableProperty = true - if filteredVariants.length > 0 # Note that the name of the property changes to enable mass assignment of variants attributes with rails - filteredProduct.variants_attributes = filteredVariants - hasUpdatableProperty = true - filteredProducts.push filteredProduct if hasUpdatableProperty - - filteredProducts - - -filterSubmitVariant = (variant) -> - hasUpdatableProperty = false - filteredVariant = {} - if not variant.deleted_at? and variant.hasOwnProperty("id") - filteredVariant.id = variant.id unless variant.id <= 0 - if variant.hasOwnProperty("sku") - filteredVariant.sku = variant.sku - hasUpdatableProperty = true - if variant.hasOwnProperty("on_hand") - filteredVariant.on_hand = variant.on_hand - hasUpdatableProperty = true - if variant.hasOwnProperty("on_demand") - filteredVariant.on_demand = variant.on_demand - hasUpdatableProperty = true - if variant.hasOwnProperty("price") - filteredVariant.price = variant.price - hasUpdatableProperty = true - if variant.hasOwnProperty("unit_value") - filteredVariant.unit_value = variant.unit_value - hasUpdatableProperty = true - if variant.hasOwnProperty("unit_description") - filteredVariant.unit_description = variant.unit_description - hasUpdatableProperty = true - if variant.hasOwnProperty("display_name") - filteredVariant.display_name = variant.display_name - hasUpdatableProperty = true - if variant.hasOwnProperty("tax_category_id") - filteredVariant.tax_category_id = variant.tax_category_id - hasUpdatableProperty = true - if variant.hasOwnProperty("category_id") - filteredVariant.primary_taxon_id = variant.category_id - hasUpdatableProperty = true - if variant.hasOwnProperty("display_as") - filteredVariant.display_as = variant.display_as - hasUpdatableProperty = true - if variant.hasOwnProperty("producer_id") - filteredVariant.supplier_id = variant.producer_id - hasUpdatableProperty = true - if variant.hasOwnProperty("variant_unit_with_scale") - filteredVariant.variant_unit = variant.variant_unit - filteredVariant.variant_unit_scale = variant.variant_unit_scale - hasUpdatableProperty = true - if variant.hasOwnProperty("variant_unit_name") - filteredVariant.variant_unit_name = variant.variant_unit_name - hasUpdatableProperty = true - - {filteredVariant: filteredVariant, hasUpdatableProperty: hasUpdatableProperty} - - -toObjectWithIDKeys = (array) -> - object = {} - - for i of array - if array[i] instanceof Object and array[i].hasOwnProperty("id") - object[array[i].id] = angular.copy(array[i]) - object[array[i].id].variants = toObjectWithIDKeys(array[i].variants) if array[i].hasOwnProperty("variants") and array[i].variants instanceof Array - - object diff --git a/app/assets/javascripts/admin/resources/resources/product_resource.js.coffee b/app/assets/javascripts/admin/resources/resources/product_resource.js.coffee deleted file mode 100644 index 7d842d8eed..0000000000 --- a/app/assets/javascripts/admin/resources/resources/product_resource.js.coffee +++ /dev/null @@ -1,6 +0,0 @@ -angular.module("admin.resources").factory 'ProductResource', ($resource) -> - $resource('/admin/product/:id/:action.json', {}, { - 'index': - url: '/api/v0/products/bulk_products.json' - method: 'GET' - }) diff --git a/app/assets/javascripts/admin/services/bulk_products.js.coffee b/app/assets/javascripts/admin/services/bulk_products.js.coffee deleted file mode 100644 index a2823bf997..0000000000 --- a/app/assets/javascripts/admin/services/bulk_products.js.coffee +++ /dev/null @@ -1,76 +0,0 @@ -angular.module("ofn.admin").factory "BulkProducts", (ProductResource, dataFetcher, $http) -> - new class BulkProducts - products: [] - pagination: {} - - fetch: (params) -> - ProductResource.index params, (data) => - @products.length = 0 - @addProducts data.products - angular.extend(@pagination, data.pagination) - - cloneProduct: (product) -> - $http.post("/api/v0/products/" + product.id + "/clone").then (response) => - dataFetcher("/api/v0/products/" + response.data.id + "?template=bulk_show").then (newProduct) => - @unpackProduct newProduct - @insertProductAfter(product, newProduct) - - updateVariantLists: (serverProducts) -> - for server_product in serverProducts - product = @findProductInList(server_product.id, @products) - product.variants = server_product.variants - @loadVariantUnitValues product.variants - - find: (id) -> - @findProductInList id, @products - - findProductInList: (id, product_list) -> - products = (product for product in product_list when product.id == id) - if products.length == 0 then null else products[0] - - addProducts: (products) -> - for product in products - @unpackProduct product - @products.push product - - insertProductAfter: (product, newProduct) -> - index = @products.indexOf(product) - @products.splice(index + 1, 0, newProduct) - - unpackProduct: (product) -> - @loadVariantUnit product - - loadVariantUnit: (product) -> - @loadVariantUnitValues product.variants if product.variants - - loadVariantUnitValues: (variants) -> - for variant in variants - @loadVariantUnitValue variant - - loadVariantUnitValue: (variant) -> - variant.variant_unit_with_scale = - if variant.variant_unit && variant.variant_unit_scale && variant.variant_unit != 'items' - "#{variant.variant_unit}_#{variant.variant_unit_scale}" - else if variant.variant_unit - variant.variant_unit - else - null - - unit_value = @variantUnitValue variant - unit_value = if unit_value? then unit_value else '' - variant.unit_value_with_description = "#{unit_value} #{variant.unit_description || ''}".trim() - - variantUnitValue: (variant) -> - if variant.unit_value? - if variant.variant_unit_scale - variant_unit_value = @divideAsInteger variant.unit_value, variant.variant_unit_scale - parseFloat(window.bigDecimal.round(variant_unit_value, 2)) - else - variant.unit_value - else - null - - # forces integer division to avoid javascript floating point imprecision - # using one billion as the multiplier so that it works for numbers with up to 9 decimal places - divideAsInteger: (a, b) -> - (a * 1000000000) / (b * 1000000000) diff --git a/app/controllers/api/v0/products_controller.rb b/app/controllers/api/v0/products_controller.rb index 6f95e1e64e..1ba016cc5a 100644 --- a/app/controllers/api/v0/products_controller.rb +++ b/app/controllers/api/v0/products_controller.rb @@ -38,39 +38,12 @@ module Api end end - def destroy - authorize! :delete, Spree::Product - @product = product_finder.find_product - authorize! :delete, @product - @product.destroyed_by = current_api_user - @product.destroy - head :no_content - end - - def bulk_products - @products = product_finder.bulk_products - - render_paged_products @products - end - def overridable @products = product_finder.products_for_producers render_paged_products @products, ::Api::Admin::ProductSimpleSerializer end - # POST /api/products/:product_id/clone - # - def clone - authorize! :create, Spree::Product - original_product = product_finder.find_product_to_be_cloned - authorize! :update, original_product - - @product = original_product.duplicate - - render json: @product, serializer: Api::Admin::ProductSerializer, status: :created - end - private def product_finder diff --git a/app/controllers/spree/admin/products_controller.rb b/app/controllers/spree/admin/products_controller.rb index f51ba5503e..2e036ffdb6 100644 --- a/app/controllers/spree/admin/products_controller.rb +++ b/app/controllers/spree/admin/products_controller.rb @@ -19,11 +19,6 @@ module Spree before_action :load_spree_api_key, only: [:index, :variant_overrides] before_action :strip_new_properties, only: [:create, :update] - def index - @current_user = spree_current_user - @show_latest_import = params[:latest_import] || false - end - def show session[:return_to] ||= request.referer redirect_to( action: :edit ) @@ -65,28 +60,6 @@ module Spree end end - def bulk_update - product_set = product_set_from_params - - product_set.collection.each { |p| authorize! :update, p } - - if product_set.save - redirect_to main_app.bulk_products_api_v0_products_path(bulk_index_query) - elsif product_set.errors.present? - render json: { errors: product_set.errors }, status: :bad_request - else - render body: nil, status: :internal_server_error - end - end - - def clone - @new = @product.duplicate - raise "Clone failed" unless @new.save - - flash[:success] = t('.success') - redirect_to spree.admin_products_url - end - def group_buy_options @url_filters = ::ProductFilters.new.extract(request.query_parameters) end diff --git a/app/controllers/spree/admin/variants_controller.rb b/app/controllers/spree/admin/variants_controller.rb index 7ec309ea93..83fe131045 100644 --- a/app/controllers/spree/admin/variants_controller.rb +++ b/app/controllers/spree/admin/variants_controller.rb @@ -72,25 +72,8 @@ module Spree render json: @variants, each_serializer: ::Api::Admin::VariantSerializer end - def destroy - @url_filters = ::ProductFilters.new.extract(request.query_parameters) - - @variant = Spree::Variant.find(params[:id]) - flash[:success] = delete_variant - - redirect_to spree.admin_product_variants_url(params[:product_id], @url_filters) - end - protected - def delete_variant - if VariantDeleter.new.delete(@variant) - Spree.t('notice_messages.variant_deleted') - else - Spree.t('notice_messages.variant_not_deleted') - end - end - def create_before @object.save end diff --git a/app/views/spree/admin/products/index.html.haml b/app/views/spree/admin/products/index.html.haml deleted file mode 100644 index b78c73cb4f..0000000000 --- a/app/views/spree/admin/products/index.html.haml +++ /dev/null @@ -1,14 +0,0 @@ -= render 'spree/admin/products/index/header' -= render 'spree/admin/products/index/data' -= admin_inject_available_units - -%div{ "ng-app": 'ofn.admin', "ng-controller": 'AdminProductEditCtrl', "ng-init": 'initialise()' } - - = render 'spree/admin/products/index/filters' - %div{ 'ng-cloak' => true } - = render 'spree/admin/products/index/actions' - = render 'spree/admin/products/index/indicators' - = render 'spree/admin/products/index/products' - - %div{'ng-show' => "!RequestMonitor.loading && products.length > 0" } - = render partial: 'admin/shared/angular_pagination' diff --git a/app/views/spree/admin/products/index/_actions.html.haml b/app/views/spree/admin/products/index/_actions.html.haml deleted file mode 100644 index aea4ebeb61..0000000000 --- a/app/views/spree/admin/products/index/_actions.html.haml +++ /dev/null @@ -1,11 +0,0 @@ -.controls.sixteen.columns.alpha{ 'ng-hide' => 'RequestMonitor.loading || products.length == 0' } - .ten.columns.alpha.index-controls - .per-page{'ng-show' => '!RequestMonitor.loading && products.length > 0'} - %input.per-page-select.ofn-select2{type: 'number', data: 'per_page_options', 'min-search' => 999, 'ng-model' => 'per_page', 'ng-change' => 'fetchProducts()'} - - %span.per-page-feedback - {{ 'spree.admin.orders.index.results_found' | t:{number: pagination.results} }} - {{ 'spree.admin.orders.index.viewing' | t:{start: ((pagination.page -1) * pagination.per_page) +1, end: ((pagination.page -1) * pagination.per_page) + products.length} }} - - .six.columns.omega - %columns-dropdown{ action: "#{controller_name}_#{action_name}" } diff --git a/app/views/spree/admin/products/index/_data.html.haml b/app/views/spree/admin/products/index/_data.html.haml deleted file mode 100644 index 13a6dc780e..0000000000 --- a/app/views/spree/admin/products/index/_data.html.haml +++ /dev/null @@ -1,5 +0,0 @@ -= admin_inject_producers(@producers) -= admin_inject_taxons(@taxons) -= admin_inject_tax_categories(@tax_categories) -= admin_inject_spree_api_key(@spree_api_key) -= admin_inject_column_preferences diff --git a/app/views/spree/admin/products/index/_filters.html.haml b/app/views/spree/admin/products/index/_filters.html.haml deleted file mode 100644 index 57615eb09e..0000000000 --- a/app/views/spree/admin/products/index/_filters.html.haml +++ /dev/null @@ -1,33 +0,0 @@ -%fieldset - %legend{align: 'center'}= t(:search) - - .filters.sixteen.columns.alpha.omega - .quick_search.three.columns.alpha - %label{ for: 'quick_filter' } - %br - %input.quick-search.fullwidth{ name: "quick_filter", type: 'text', placeholder: t('admin.quick_search'), "ng-keypress": "$event.keyCode === 13 && fetchProducts()", "ng-model": 'q.query' } - .one.columns   - .filter_select.three.columns - %label{ for: 'producer_filter' }= t 'producer' - %br - %select.fullwidth{ id: 'producer_filter', "ofn-select2-min-search": 5, "ng-model": 'q.producerFilter', "ng-options": 'producer.id as producer.name for producer in producers' } - .filter_select.three.columns - %label{ for: 'category_filter' }= t 'category' - %br - %select.fullwidth{ id: 'category_filter', "ofn-select2-min-search": 5, "ng-model": 'q.categoryFilter', "ng-options": 'taxon.id as taxon.name for taxon in taxons' } - .filter_select.three.columns - %label{ for: 'import_filter' }= t 'import_date' - %br - %select.fullwidth{ id: 'import_date_filter', "ofn-select2-min-search": 5, "ng-model": 'q.importDateFilter', "ng-init": "importDates = #{@import_dates}; showLatestImport = #{@show_latest_import}", "ng-options": 'date.id as date.name for date in importDates' } - - .filter_clear.three.columns.omega - %label{ for: 'clear_all_filters' } - %br - %input.fullwidth.red{ :type => 'button', :id => 'clear_all_filters', :value => t('admin.clear_filters'), 'ng-click' => "resetSelectFilters()" } - - .clearfix - - .actions.filter-actions - %div - %a.button.icon-search{'ng-click' => 'fetchProducts()'} - = t(:filter_results) diff --git a/app/views/spree/admin/products/index/_header.html.haml b/app/views/spree/admin/products/index/_header.html.haml deleted file mode 100644 index f3314a1a39..0000000000 --- a/app/views/spree/admin/products/index/_header.html.haml +++ /dev/null @@ -1,10 +0,0 @@ -- content_for :page_title do - = t('.title') - -- content_for :page_actions do - %div{ :class => "toolbar" } - %ul{ :class => "actions header-action-links inline-menu" } - %li#new_product_link - = button_link_to t(:new_product), new_object_url, { :icon => 'icon-plus', :id => 'admin_new_product' } - -= render partial: 'spree/admin/shared/product_sub_menu' diff --git a/app/views/spree/admin/products/index/_indicators.html.haml b/app/views/spree/admin/products/index/_indicators.html.haml deleted file mode 100644 index 2907c04074..0000000000 --- a/app/views/spree/admin/products/index/_indicators.html.haml +++ /dev/null @@ -1,14 +0,0 @@ -.sixteen.columns.alpha#loading{ 'ng-if' => 'RequestMonitor.loading' } - = render partial: "components/admin_spinner" - %h1 - = t('.title') - -.sixteen.columns.alpha{ 'ng-show' => '!RequestMonitor.loading && products.length == 0 && q.query.length == 0' } - %h1#no_results= t('.no_products') - -.sixteen.columns.alpha{ 'ng-show' => '!RequestMonitor.loading && products.length == 0 && q.query.length != 0' } - %h1#no_results - = t('.no_results') - ' - {{q.query}} - ' diff --git a/app/views/spree/admin/products/index/_products.html.haml b/app/views/spree/admin/products/index/_products.html.haml deleted file mode 100644 index 032c783e7f..0000000000 --- a/app/views/spree/admin/products/index/_products.html.haml +++ /dev/null @@ -1,14 +0,0 @@ -.sixteen.columns.alpha{ 'ng-hide' => 'RequestMonitor.loading || products.length == 0' } - %form{ name: 'bulk_product_form', autocomplete: "off" } - %save-bar{ dirty: "bulk_product_form.$dirty", persist: "false" } - %input.red{ type: "button", value: t(:save_changes), "ng-click": "submitProducts()", "ng-disabled": "!bulk_product_form.$dirty" } - %input{ type: "button", value: t(:close), 'ng-click' => "cancel('#{admin_products_path}')" } - - %table.index#listing_products.bulk - - = render 'spree/admin/products/index/products_head' - - %tbody{ 'ng-repeat' => 'product in products', 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'" } - - = render 'spree/admin/products/index/products_product' - = render 'spree/admin/products/index/products_variant' diff --git a/app/views/spree/admin/products/index/_products_head.html.haml b/app/views/spree/admin/products/index/_products_head.html.haml deleted file mode 100644 index 9e6d7e7065..0000000000 --- a/app/views/spree/admin/products/index/_products_head.html.haml +++ /dev/null @@ -1,41 +0,0 @@ -%colgroup - %col.actions - %col.image{ "ng-show": 'columns.image.visible' } - %col.producer{ "ng-show": 'columns.producer.visible' } - %col.sku{ "ng-show": 'columns.sku.visible' } - %col.name{ "ng-show": 'columns.name.visible' } - %col.unit{ "ng-show": 'columns.unit.visible' } - %col.display_as{ "ng-show": 'columns.unit.visible' } - %col.price{ "ng-show": 'columns.price.visible' } - %col.on_hand{ "ng-show": 'columns.on_hand.visible' } - %col.on_demand{ "ng-show": 'columns.on_demand.visible' } - %col.category{ "ng-show": 'columns.category.visible' } - %col.tax_category{ "ng-show": 'columns.tax_category.visible' } - %col.inherits_properties{ "ng-show": 'columns.inherits_properties.visible' } - %col.import_date{ "ng-show": 'columns.import_date.visible' } - %col.actions - %col.actions - %col.actions - -%thead - %tr{ "ng-controller": "ColumnsCtrl" } - %th.left-actions - %a{ 'ng-click' => 'toggleShowAllVariants()', :style => 'color: red; cursor: pointer' } - = t(:expand_all) - %th.image{ 'ng-show' => 'columns.image.visible' } - %th.producer{ 'ng-show' => 'columns.producer.visible' }=t('admin.producer') - %th.sku{ 'ng-show' => 'columns.sku.visible' }=t('admin.sku') - %th.name{ 'ng-show' => 'columns.name.visible' } - = render partial: 'spree/admin/shared/sortable_header', locals: {column_name: 'name'} - %th.unit{ 'ng-show' => 'columns.unit.visible' }=t('.unit') - %th.display_as{ 'ng-show' => 'columns.unit.visible' }=t('.display_as') - %th.price{ 'ng-show' => 'columns.price.visible' }=t('admin.price') - %th.on_hand{ 'ng-show' => 'columns.on_hand.visible' }=t('admin.on_hand') - %th.on_demand{ 'ng-show' => 'columns.on_demand.visible' }=t('admin.on_demand?') - %th.category{ 'ng-show' => 'columns.category.visible' }=t('.category') - %th.tax_category{ 'ng-show' => 'columns.tax_category.visible' }=t('.tax_category') - %th.inherits_properties{ 'ng-show' => 'columns.inherits_properties.visible' }=t('.inherits_properties?') - %th.import_date{ 'ng-show' => 'columns.import_date.visible' }=t('.import_date') - %th.actions - %th.actions - %th.actions diff --git a/app/views/spree/admin/products/index/_products_product.html.haml b/app/views/spree/admin/products/index/_products_product.html.haml deleted file mode 100644 index aa10e5e29a..0000000000 --- a/app/views/spree/admin/products/index/_products_product.html.haml +++ /dev/null @@ -1,33 +0,0 @@ -%tr.product{ :id => "p_{{product.id}}" } - %td.left-actions - %a{ 'ofn-toggle-variants' => 'true', :class => "view-variants", 'ng-show' => 'hasVariants(product)' } - %a{ :class => "add-variant icon-plus-sign", 'ng-click' => "addVariant(product)", 'ng-show' => "!hasVariants(product) && hasUnit(product)" } - %td.image{ 'ng-show' => 'columns.image.visible' } - %a{class: 'image-modal'} - %img{'ng-src' => '{{ product.thumb_url }}'} - %td.producer{ 'ng-show' => 'columns.producer.visible' } - %td.sku{ 'ng-show' => 'columns.sku.visible' } - %input{ 'ng-model' => "product.sku", :name => 'product_sku', 'ofn-track-product' => 'sku', :type => 'text' } - %td.name{ 'ng-show' => 'columns.name.visible' } - %input{ 'ng-model' => "product.name", :name => 'product_name', 'ofn-track-product' => 'name', :type => 'text' } - %td.unit{ 'ng-show' => 'columns.unit.visible' } - %td.display_as{ 'ng-show' => 'columns.unit.visible' } - %td.price{ 'ng-show' => 'columns.price.visible' } - %input{ 'ng-model' => 'product.price', 'ofn-decimal' => :true, :name => 'price', 'ofn-track-product' => 'price', :type => 'text', 'ng-hide' => 'hasVariants(product)' } - %td.on_hand{ 'ng-show' => 'columns.on_hand.visible' } - %span{ 'ng-bind' => 'product.on_hand', :name => 'on_hand', 'ng-if' => '!hasOnDemandVariants(product) && (hasVariants(product) || product.on_demand)' } - %input.field{ 'ng-model' => 'product.on_hand', :name => 'on_hand', 'ofn-track-product' => 'on_hand', 'ng-if' => '!(hasVariants(product) || product.on_demand)', :type => 'number' } - %td.on_demand{ 'ng-show' => 'columns.on_demand.visible' } - %input.field{ 'ng-model' => 'product.on_demand', :name => 'on_demand', 'ofn-track-product' => 'on_demand', :type => 'checkbox', 'ng-hide' => 'hasVariants(product)' } - %td.category{ 'ng-if' => 'columns.category.visible' } - %td.tax_category{ 'ng-if' => 'columns.tax_category.visible' } - %td.inherits_properties{ 'ng-show' => 'columns.inherits_properties.visible' } - %input{ 'ng-model' => 'product.inherits_properties', :name => 'inherits_properties', 'ofn-track-product' => 'inherits_properties', type: "checkbox" } - %td.import_date{ 'ng-show' => 'columns.import_date.visible' } - %span {{(product.import_date | date:"MMMM dd, yyyy HH:mm") || ""}} - %td.actions - %a{ 'ng-click' => 'editWarn(product)', :class => "edit-product icon-edit no-text", 'ofn-with-tip' => t(:edit) } - %td.actions - %a{ 'ng-click' => 'cloneProduct(product)', :class => "clone-product icon-copy no-text", 'ofn-with-tip' => t(:clone) } - %td.actions - %a{ 'ng-click' => 'deleteProduct(product)', :class => "delete-product icon-trash no-text", 'ofn-with-tip' => t(:remove) } diff --git a/app/views/spree/admin/products/index/_products_variant.html.haml b/app/views/spree/admin/products/index/_products_variant.html.haml deleted file mode 100644 index 4aa5e13f78..0000000000 --- a/app/views/spree/admin/products/index/_products_variant.html.haml +++ /dev/null @@ -1,39 +0,0 @@ -%tr.variant{ :id => "v_{{variant.id}}", 'ng-repeat' => 'variant in product.variants', 'ng-show' => 'DisplayProperties.showVariants(product.id)', 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'" } - %td.left-actions - %a{ :class => "variant-item icon-caret-right", 'ng-hide' => "$last" } - %a{ :class => "add-variant icon-plus-sign", 'ng-click' => "addVariant(product)", 'ng-show' => "$last", 'ofn-with-tip' => t('.new_variant') } - %td{ 'ng-show' => 'columns.image.visible' } - %td{ 'ng-show' => 'columns.producer.visible' } - %select.fullwidth{ "data-controller": "tom-select", 'ng-model' => 'variant.producer_id', :name => 'producer_id', 'ofn-track-variant' => 'producer_id', 'ng-options' => 'producer.id as producer.name for producer in producers' } - %td{ 'ng-show' => 'columns.sku.visible' } - %input{ 'ng-model' => "variant.sku", :name => 'variant_sku', 'ofn-track-variant' => 'sku', :type => 'text' } - %td{ 'ng-show' => 'columns.name.visible' } - %input{ 'ng-model' => 'variant.display_name', :name => 'variant_display_name', 'ofn-track-variant' => 'display_name', :type => 'text', placeholder: "{{ product.name }}" } - %td.unit_value{ 'ng-show' => 'columns.unit.visible' } - %select.no-search{ "data-controller": "tom-select", 'ng-model' => 'variant.variant_unit_with_scale', :name => 'variant_unit_with_scale', 'ofn-track-variant' => 'variant_unit_with_scale', 'ng-options' => 'unit[1] as unit[0] for unit in variant_unit_options' } - %input{ 'ng-model' => 'variant.unit_value_with_description', :name => 'variant_unit_value_with_description', 'ofn-track-variant' => 'unit_value_with_description', :type => 'text', :placeholder => 'value', 'ng-show' => "hasUnit(variant)" } - %input{ 'ng-model' => 'variant.variant_unit_name', :name => 'variant_unit_name', 'ofn-track-variant' => 'variant_unit_name', :placeholder => 'unit', 'ng-show' => "variant.variant_unit_with_scale == 'items'", :type => 'text' } - %td.display_as{ 'ng-show' => 'columns.unit.visible' } - %input{ 'ofn-display-as' => 'variant', 'ng-model' => 'variant.display_as', name: 'variant_display_as', 'ofn-track-variant' => 'display_as', type: 'text', placeholder: '{{ placeholder_text }}' } - %td{ 'ng-show' => 'columns.price.visible' } - %input{ 'ng-model' => 'variant.price', 'ofn-decimal' => :true, :name => 'variant_price', 'ofn-track-variant' => 'price', :type => 'text' } - %td{ 'ng-show' => 'columns.on_hand.visible' } - %input.field{ 'ng-model' => 'variant.on_hand', 'ng-change' => 'updateOnHand(product)', :name => 'variant_on_hand', 'ng-if' => '!variant.on_demand', 'ofn-track-variant' => 'on_hand', :type => 'number' } - %span{ :name => 'variant_on_hand', 'ng-if' => 'variant.on_demand' } - = t(:on_demand) - %td{ 'ng-show' => 'columns.on_demand.visible' } - %input.field{ 'ng-model' => 'variant.on_demand', :name => 'variant_on_demand', 'ofn-track-variant' => 'on_demand', :type => 'checkbox' } - %td{ 'ng-show' => 'columns.category.visible' } - %input.fullwidth{ type: 'text', id: "p{{product.id}}_category_id", 'ng-model' => 'variant.category_id', 'ofn-taxon-autocomplete' => '', 'ofn-track-variant' => 'category_id', 'multiple-selection' => 'false', placeholder: 'Category' } - %td{ 'ng-show' => 'columns.tax_category.visible' } - %select.select2{ name: 'variant_tax_category_id', "ofn-track-variant": 'tax_category_id', "ng-model": 'variant.tax_category_id', "ng-options": 'tax_category.id as tax_category.name for tax_category in tax_categories' } - %option{ value: '' }= t(:none) - %td{ 'ng-show' => 'columns.inherits_properties.visible' } - %td{ 'ng-show' => 'columns.import_date.visible' } - %span {{variant.import_date | date:"MMMM dd, yyyy HH:mm"}} - %td.actions - %a{ 'ng-click' => 'editWarn(product,variant)', :class => "edit-variant icon-edit no-text", 'ng-show' => "variantSaved(variant)", 'ofn-with-tip' => t(:edit) } - %td.actions - %span.icon-warning-sign{ 'ng-if' => 'variant.variant_overrides_count > 0', 'ofn-with-tip' => "{{ 'spree.admin.products.index.products_variant.variant_has_n_overrides' | t:{n: variant.variant_overrides_count} }}" } - %td.actions - %a{ 'ng-click' => 'deleteVariant(product,variant)', "ng-class" => '{disabled: product.variants.length < 2}', :class => "delete-variant icon-trash no-text", 'ofn-with-tip' => t(:remove) } diff --git a/app/views/spree/admin/products/index/_save_button_row.html.haml b/app/views/spree/admin/products/index/_save_button_row.html.haml deleted file mode 100644 index 8818bf84b3..0000000000 --- a/app/views/spree/admin/products/index/_save_button_row.html.haml +++ /dev/null @@ -1,3 +0,0 @@ -.sixteen.columns.alpha{ 'ng-hide' => 'RequestMonitor.loading || products.length == 0', style: "margin-bottom: 10px" } - .four.columns.alpha - %input.four.columns.alpha{ :type => 'button', :value => t(:save_changes), 'ng-click' => 'submitProducts()'} diff --git a/config/routes/api.rb b/config/routes/api.rb index 30bd7cd1b1..2545554739 100644 --- a/config/routes/api.rb +++ b/config/routes/api.rb @@ -16,7 +16,6 @@ Openfoodnetwork::Application.routes.draw do namespace :v0 do resources :products do collection do - get :bulk_products get :overridable end post :clone diff --git a/config/routes/spree.rb b/config/routes/spree.rb index 512f708b14..a133ff7418 100644 --- a/config/routes/spree.rb +++ b/config/routes/spree.rb @@ -68,7 +68,7 @@ Spree::Core::Engine.routes.draw do end end - resources :variants do + resources :variants, except: :destroy do collection do post :update_positions end diff --git a/spec/javascripts/unit/admin/bulk_product_update_spec.js.coffee b/spec/javascripts/unit/admin/bulk_product_update_spec.js.coffee deleted file mode 100644 index 532f569d69..0000000000 --- a/spec/javascripts/unit/admin/bulk_product_update_spec.js.coffee +++ /dev/null @@ -1,1080 +0,0 @@ -describe "filtering products for submission to database", -> - it "accepts an object or an array and only returns an array", -> - expect(filterSubmitProducts([])).toEqual [] - expect(filterSubmitProducts({})).toEqual [] - expect(filterSubmitProducts(1: - id: 1 - name: "lala" - )).toEqual [ - id: 1 - name: "lala" - ] - expect(filterSubmitProducts([ - id: 1 - name: "lala" - ])).toEqual [ - id: 1 - name: "lala" - ] - expect(filterSubmitProducts(1)).toEqual [] - expect(filterSubmitProducts("2")).toEqual [] - expect(filterSubmitProducts(null)).toEqual [] - - it "only returns products which have an id property", -> - expect(filterSubmitProducts([ - { - id: 1 - name: "p1" - } - { - notanid: 2 - name: "p2" - } - ])).toEqual [ - id: 1 - name: "p1" - ] - - it "does not return a product object for products which have no propeties other than an id", -> - expect(filterSubmitProducts([ - { - id: 1 - someunwantedproperty: "something" - } - { - id: 2 - name: "p2" - } - ])).toEqual [ - id: 2 - name: "p2" - ] - - it "does not return an on_hand count when a product has variants", -> - testProduct = - id: 1 - on_hand: 5 - variants: [ - id: 1 - on_hand: 5 - price: 12.0 - ] - - expect(filterSubmitProducts([testProduct])).toEqual [ - id: 1 - variants_attributes: [ - id: 1 - on_hand: 5 - price: 12.0 - ] - ] - - it "returns variants as variants_attributes", -> - testProduct = - id: 1 - variants: [ - id: 1 - on_hand: 5 - price: 12.0 - unit_value: 250 - unit_description: "(bottle)" - ] - - expect(filterSubmitProducts([testProduct])).toEqual [ - id: 1 - variants_attributes: [ - id: 1 - on_hand: 5 - price: 12.0 - unit_value: 250 - unit_description: "(bottle)" - ] - ] - - it "ignores variants without an id, and those for which deleted_at is not null", -> - testProduct = - id: 1 - variants: [ - { - id: 1 - on_hand: 3 - price: 5.0 - } - { - on_hand: 1 - price: 15.0 - } - { - id: 2 - on_hand: 2 - deleted_at: new Date() - price: 20.0 - } - ] - - expect(filterSubmitProducts([testProduct])).toEqual [ - id: 1 - variants_attributes: [ - id: 1 - on_hand: 3 - price: 5.0 - ] - ] - - it "returns variants with a negative id without that id", -> - testProduct = - id: 1 - variants: [ - id: -1 - on_hand: 5 - price: 12.0 - unit_value: 250 - unit_description: "(bottle)" - ] - - expect(filterSubmitProducts([testProduct])).toEqual [ - id: 1 - variants_attributes: [ - on_hand: 5 - price: 12.0 - unit_value: 250 - unit_description: "(bottle)" - ] - ] - - it "does not return variants_attributes property if variants is an empty array", -> - testProduct = - id: 1 - price: 10 - variants: [] - - expect(filterSubmitProducts([testProduct])).toEqual [ - id: 1 - price: 10 - ] - - it "returns variant_unit_with_scale as variant_unit and variant_unit_scale", -> - testProduct = - id: 1 - variants: [ - id: 1 - variant_unit: 'weight' - variant_unit_scale: 1 - variant_unit_with_scale: 'weight_1' - ] - - expect(filterSubmitProducts([testProduct])).toEqual [ - id: 1 - variants_attributes: [ - id: 1 - variant_unit: 'weight' - variant_unit_scale: 1 - ] - ] - - it "returns stock properties of a product if no variant is provided", -> - testProduct = - id: 1 - name: "TestProduct" - on_hand: 0 - on_demand: false - - expect(filterSubmitProducts([testProduct])).toEqual [ - id: 1 - name: "TestProduct" - on_hand: 0 - on_demand: false - ] - - it "only returns the properties of products which ought to be updated", -> - testProduct = - id: 1 - name: "TestProduct" - description: "" - deleted_at: null - meta_keywords: null - shipping_category_id: null - created_at: null - updated_at: null - on_hand: 0 - on_demand: false - group_buy: null - group_buy_unit_size: null - variants: [ - id: 1 - on_hand: 2 - price: 10.0 - unit_value: 250 - tax_category_id: null - unit_description: "(bottle)" - display_as: "bottle" - display_name: "nothing" - producer_id: 5 - variant_unit: 'volume' - variant_unit_scale: 1 - variant_unit_name: 'loaf' - variant_unit_with_scale: 'volume_1' - ] - - expect(filterSubmitProducts([testProduct])).toEqual [ - id: 1 - name: "TestProduct" - variants_attributes: [ - id: 1 - on_hand: 2 - price: 10.0 - unit_value: 250 - tax_category_id: null - unit_description: "(bottle)" - display_as: "bottle" - display_name: "nothing" - supplier_id: 5 - variant_unit: 'volume' - variant_unit_scale: 1 - variant_unit_name: 'loaf' - ] - ] - -describe "AdminProductEditCtrl", -> - $ctrl = $scope = $timeout = $httpBackend = BulkProducts = DirtyProducts = DisplayProperties = ProductFiltersUrl = windowStub = null - - beforeEach -> - module "ofn.admin" - module ($provide)-> - $provide.value "producers", [] - $provide.value "taxons", [] - $provide.value "tax_categories", [] - $provide.value 'SpreeApiKey', 'API_KEY' - $provide.value 'columns', [] - null - module "admin.products" - module ($provide)-> - $provide.value "availableUnits", "g,kg,T,mL,L,kL" - null - - beforeEach inject((_$controller_, _$timeout_, $rootScope, _$httpBackend_, _BulkProducts_, _DirtyProducts_, _DisplayProperties_, _ProductFiltersUrl_) -> - $scope = $rootScope.$new() - $ctrl = _$controller_ - $timeout = _$timeout_ - $httpBackend = _$httpBackend_ - BulkProducts = _BulkProducts_ - DirtyProducts = _DirtyProducts_ - DisplayProperties = _DisplayProperties_ - ProductFiltersUrl = _ProductFiltersUrl_ - - # Stub the window object so we don't get redirected when href is updated - windowStub = {navigator: {userAgent: 'foo'}, location: {href: ''}} - - $ctrl "AdminProductEditCtrl", {$scope: $scope, $timeout: $timeout, $window: windowStub} - ) - - describe "loading data upon initialisation", -> - beforeEach -> - spyOn($scope, "fetchProducts").and.returnValue "nothing" - - it "gets a list of producers and then resets products with a list of data", -> - $scope.initialise() - expect($scope.fetchProducts.calls.count()).toBe 1 - - it "gets a list of products applying filters from the url", inject ($location) -> - query = 'lala' - producerFilter = 2 - categoryFilter = 5 - sorting = 'name desc' - importDateFilter = '2020-06-08' - $location.search({query: query, producerFilter: producerFilter, categoryFilter: categoryFilter, sorting: sorting, importDateFilter: importDateFilter}) - - $scope.initialise() - - expect($scope.q.query).toBe query - expect($scope.q.categoryFilter).toBe categoryFilter - expect($scope.q.sorting).toBe sorting - expect($scope.q.importDateFilter).toBe importDateFilter - - describe "fetching products", -> - $q = null - deferred = null - - beforeEach inject((_$q_) -> - $q = _$q_ - ) - - beforeEach -> - deferred = $q.defer() - deferred.resolve() - spyOn $scope, "resetProducts" - spyOn(BulkProducts, "fetch").and.returnValue deferred.promise - - it "calls resetProducts after data has been received", -> - $scope.fetchProducts() - $scope.$digest() - expect($scope.resetProducts).toHaveBeenCalled() - - it "updates url with filter after data has been received", inject ($location, $window) -> - query = 'lala' - producerFilter = 2 - categoryFilter = 5 - sorting = 'name desc' - importDateFilter = '2020-06-08' - - $scope.q.query = query - $scope.q.producerFilter = producerFilter - $scope.q.categoryFilter = categoryFilter - $scope.q.sorting = sorting - $scope.q.importDateFilter = importDateFilter - - $scope.fetchProducts() - $scope.$digest() - - encodedSorting = $window.encodeURIComponent(sorting) - encodedDate = $window.encodeURIComponent(importDateFilter) - expect($location.url()).toBe( - "?producerFilter=#{producerFilter}&categoryFilter=#{categoryFilter}&query=#{query}&sorting=#{encodedSorting}&importDateFilter=#{encodedDate}" - ) - - describe "resetting products", -> - beforeEach -> - spyOn DirtyProducts, "clear" - $scope.products = {} - $scope.resetProducts [ - { - id: 1 - name: "P1" - } - { - id: 3 - name: "P2" - } - ] - - it "resets dirtyProducts", -> - expect(DirtyProducts.clear).toHaveBeenCalled() - - describe "sorting products", -> - it "sorts products", -> - spyOn $scope, "fetchProducts" - - $scope.sortOptions.toggle('name') - $scope.$apply() - - expect($scope.sorting).toEqual 'name desc' - expect($scope.fetchProducts).toHaveBeenCalled() - - describe "updating the product on hand count", -> - it "updates when product is not available on demand", -> - spyOn($scope, "onHand").and.returnValue 123 - product = {on_demand: false} - $scope.updateOnHand(product) - expect(product.on_hand).toEqual 123 - - it "updates when product's variants are not available on demand", -> - spyOn($scope, "onHand").and.returnValue 123 - product = {on_demand: false, variants: [{on_demand: false}]} - $scope.updateOnHand(product) - expect(product.on_hand).toEqual 123 - - it "does nothing when the product is available on demand", -> - product = {on_demand: true} - $scope.updateOnHand(product) - expect(product.on_hand).toBeUndefined() - - it "does nothing when one of the variants is available on demand", -> - product = - on_demand: false - variants: [ - {on_demand: false, on_hand: 10} - {on_demand: true, on_hand: Infinity} - ] - $scope.updateOnHand(product) - expect(product.on_hand).toBeUndefined() - - - describe "getting on_hand counts when products have variants", -> - p1 = undefined - p2 = undefined - p3 = undefined - beforeEach -> - p1 = variants: - 1: - id: 1 - on_hand: 1 - - 2: - id: 2 - on_hand: 2 - - 3: - id: 3 - on_hand: 3 - - p2 = variants: - 4: - id: 4 - not_on_hand: 1 - - 5: - id: 5 - on_hand: 2 - - 6: - id: 6 - on_hand: 3 - - p3 = - not_variants: - 7: - id: 7 - on_hand: 1 - - 8: - id: 8 - on_hand: 2 - - variants: - 9: - id: 9 - on_hand: 3 - - it "sums variant on_hand properties", -> - expect($scope.onHand(p1)).toEqual 6 - - it "ignores items in variants without an on_hand property (adds 0)", -> - expect($scope.onHand(p2)).toEqual 5 - - it "ignores on_hand properties of objects in arrays which are not named 'variants' (adds 0)", -> - expect($scope.onHand(p3)).toEqual 3 - - it "returns 'error' if not given an object with a variants property that is an object", -> - expect($scope.onHand([])).toEqual "error" - expect($scope.onHand(not_variants: [])).toEqual "error" - - - describe "determining whether a product has variants that are available on demand", -> - it "returns true when at least one variant does", -> - product = - variants: [ - {on_demand: false} - {on_demand: true} - ] - expect($scope.hasOnDemandVariants(product)).toBe(true) - - it "returns false otherwise", -> - product = - variants: [ - {on_demand: false} - {on_demand: false} - ] - expect($scope.hasOnDemandVariants(product)).toBe(false) - - - describe "determining whether a product has variants", -> - it "returns true when it does", -> - product = - variants: [{id: 1}, {id: 2}] - expect($scope.hasVariants(product)).toBe(true) - - it "returns false when it does not", -> - product = - variants: [] - expect($scope.hasVariants(product)).toBe(false) - - - describe "determining whether a product has a unit", -> - it "returns true when it does", -> - variant ={variant_unit_with_scale: 'weight_1000'} - - expect($scope.hasUnit(variant)).toBe(true) - - it "returns false when its unit is undefined", -> - variant = {} - expect($scope.hasUnit(variant)).toBe(false) - - - describe "determining whether a variant has been saved", -> - it "returns true when it has a positive id", -> - variant = {id: 1} - expect($scope.variantSaved(variant)).toBe(true) - - it "returns false when it has no id", -> - variant = {} - expect($scope.variantSaved(variant)).toBe(false) - - it "returns false when it has a negative id", -> - variant = {id: -1} - expect($scope.variantSaved(variant)).toBe(false) - - - describe "submitting products to be updated", -> - describe "packing products", -> - beforeEach -> - window.bigDecimal = jasmine.createSpyObj "bigDecimal", ["multiply"] - window.bigDecimal.multiply.and.callFake (a, b, c) -> (a * b).toFixed(c) - - it "packs each variant", -> - spyOn $scope, "packVariant" - testVariant = {id: 1} - testProduct = - id: 1 - variants: {1: testVariant} - - $scope.packProduct(testProduct) - - expect($scope.packVariant).toHaveBeenCalledWith(testVariant) - - describe "packing variants", -> - beforeEach -> - window.bigDecimal = jasmine.createSpyObj "bigDecimal", ["multiply"] - window.bigDecimal.multiply.and.callFake (a, b, c) -> (a * b).toFixed(c) - - it "extracts variant_unit_with_scale into variant_unit and variant_unit_scale", -> - testVariant = - id: 1 - variant_unit: 'weight' - variant_unit_scale: 1 - variant_unit_with_scale: 'volume_1000' - - $scope.packVariant(testVariant) - - expect(testVariant).toEqual - id: 1 - variant_unit: 'volume' - variant_unit_scale: 1000 - variant_unit_with_scale: 'volume_1000' - - it "extracts when variant_unit_with_scale is 'items'", -> - testVariant = - id: 1 - variant_unit: 'weight' - variant_unit_scale: 1 - variant_unit_with_scale: 'items' - - $scope.packVariant(testVariant) - - expect(testVariant).toEqual - id: 1 - variant_unit: 'items' - variant_unit_scale: null - variant_unit_with_scale: 'items' - - it "extracts unit_value and unit_description from unit_value_with_description", -> - testVariant = {unit_value_with_description: "250.5 (bottle)"} - - $scope.packVariant(testVariant) - - expect(testVariant).toEqual - unit_value: 250.5 - unit_description: "(bottle)" - unit_value_with_description: "250.5 (bottle)" - - it "extracts into unit_value when only a number is provided", -> - testVariant = {variant_unit_scale: 1.0, unit_value_with_description: "250.5"} - - $scope.packVariant(testVariant) - - expect(testVariant).toEqual - unit_value: 250.5 - unit_description: '' - unit_value_with_description: "250.5" - variant_unit_scale: 1.0 - - it "extracts into unit_description when only a string is provided", -> - testVariant = {unit_value_with_description: "Medium"} - - $scope.packVariant(testVariant) - - expect(testVariant).toEqual - unit_value: null - unit_description: 'Medium' - unit_value_with_description: "Medium" - - it "extracts into unit_description when a string starting with a number is provided", -> - testVariant = {unit_value_with_description: "1kg"} - - $scope.packVariant(testVariant) - - expect(testVariant).toEqual - unit_value: null - unit_description: '1kg' - unit_value_with_description: "1kg" - - it "sets blank values when no value provided", -> - testVariant = {unit_value_with_description: ""} - - $scope.packVariant(testVariant) - - expect(testVariant).toEqual - unit_value: null - unit_description: '' - unit_value_with_description: "" - - it "sets nothing when the field is undefined", -> - testVariant = {} - - $scope.packVariant(testVariant) - - expect(testVariant).toEqual({}) - - it "sets zero when the field is zero", -> - testVariant = {variant_unit_scale: 1.0, unit_value_with_description: "0"} - - $scope.packVariant(testVariant) - - expect(testVariant).toEqual - unit_value: 0 - unit_description: '' - unit_value_with_description: "0" - variant_unit_scale: 1.0 - - it "converts value from chosen unit to base unit", -> - testVariant = {variant_unit_scale: 1000, unit_value_with_description: "250.5"} - - $scope.packVariant(testVariant) - - expect(testVariant).toEqual - unit_value: 250500 - unit_description: '' - unit_value_with_description: "250.5" - variant_unit_scale: 1000 - - it "does not convert value when using a non-scaled unit", -> - testVariant = {unit_value_with_description: "12"} - - $scope.packVariant(testVariant) - - expect(testVariant).toEqual - unit_value: 12 - unit_description: '' - unit_value_with_description: "12" - - it "converts unit_value into a float when a comma separated number is provided", -> - testVariant = {variant_unit_scale: 1.0, unit_value_with_description: "250,5"} - - $scope.packVariant(testVariant) - - expect(testVariant).toEqual - unit_value: 250.5 - unit_description: '' - unit_value_with_description: "250,5" - variant_unit_scale: 1.0 - - it "rounds off the unit_value upto 2 decimal places", -> - testVariant = {variant_unit_scale: 1.0, unit_value_with_description: "1234.567"} - - $scope.packVariant(testVariant) - - expect(testVariant).toEqual - unit_value: 1234.57 - unit_description: '' - unit_value_with_description: "1234.567" - variant_unit_scale: 1.0 - - - describe "filtering products", -> - beforeEach -> - spyOn $scope, "packProduct" - spyOn(window, "filterSubmitProducts").and.returnValue [ - { - id: 1 - value: 3 - } - { - id: 2 - value: 4 - } - ] - spyOn $scope, "updateProducts" - DirtyProducts.addProductProperty 1, "propName", "something" - DirtyProducts.addProductProperty 2, "propName", "something" - $scope.products = - 1: - id: 1 - 2: - id: 2 - - $scope.submitProducts() - - it "packs all products and all dirty products", -> - expect($scope.packProduct.calls.count()).toBe 4 - - it "filters returned dirty products", -> - expect(filterSubmitProducts).toHaveBeenCalledWith - 1: - id: 1 - propName: "something" - 2: - id: 2 - propName: "something" - - - it "sends dirty and filtered objects to submitProducts()", -> - expect($scope.updateProducts).toHaveBeenCalledWith [ - { - id: 1 - value: 3 - } - { - id: 2 - value: 4 - } - ] - - - describe "updating products", -> - it "submits products to be updated with a http post request to /admin/products/bulk_update", -> - $httpBackend.expectPOST("/admin/products/bulk_update").respond "list of products" - $scope.updateProducts "list of products" - $httpBackend.flush() - - it "runs displaySuccess() when post returns success", -> - spyOn $scope, "displaySuccess" - spyOn BulkProducts, "updateVariantLists" - spyOn DirtyProducts, "clear" - - $scope.bulk_product_form = jasmine.createSpyObj('bulk_product_form', ['$setPristine']) - - $scope.products = [ - { - id: 1 - name: "P1" - } - { - id: 2 - name: "P2" - } - ] - $httpBackend.expectPOST("/admin/products/bulk_update").respond 200, [ - { - id: 1 - name: "P1" - } - { - id: 2 - name: "P2" - } - ] - $scope.updateProducts "list of dirty products" - $httpBackend.flush() - $timeout.flush() - expect($scope.displaySuccess).toHaveBeenCalled() - expect($scope.bulk_product_form.$setPristine).toHaveBeenCalled - expect(DirtyProducts.clear).toHaveBeenCalled() - expect(BulkProducts.updateVariantLists).toHaveBeenCalled() - - it "runs displayFailure() when post returns an error", -> - spyOn $scope, "displayFailure" - $scope.products = "updated list of products" - $httpBackend.expectPOST("/admin/products/bulk_update").respond 500, "updated list of products" - $scope.updateProducts "updated list of products" - $httpBackend.flush() - expect($scope.displayFailure).toHaveBeenCalled() - - describe "displaying the error information when post returns 400", -> - beforeEach -> - spyOn $scope, "displayFailure" - $scope.products = "updated list of products" - - it "displays errors in an array", -> - $httpBackend.expectPOST("/admin/products/bulk_update").respond 400, { "errors": ["an error"] } - $scope.updateProducts "updated list of products" - $httpBackend.flush() - expect($scope.displayFailure).toHaveBeenCalledWith("Saving failed with the following error(s):\nan error\n") - - it "displays errors in a hash", -> - $httpBackend.expectPOST("/admin/products/bulk_update").respond 400, { "errors": { "base": ["a basic error"] } } - $scope.updateProducts "updated list of products" - $httpBackend.flush() - expect($scope.displayFailure).toHaveBeenCalledWith("Saving failed with the following error(s):\na basic error\n") - - - describe "adding variants", -> - beforeEach -> - spyOn DisplayProperties, 'setShowVariants' - - it "adds first and subsequent variants", -> - product = {id: 123, variants: [ - { - id: 1, price: 10.0, unit_value: 1, tax_category_id: null, unit_description: '1kg', - on_demand: false, on_hand: null, display_as: null, display_name: null, category_id: 2 - } - ]} - $scope.addVariant(product) - $scope.addVariant(product) - expect(product).toEqual - id: 123 - variants: [ - {id: 1, price: 10.0, unit_value: 1, tax_category_id: null, unit_description: '1kg', on_demand: false, on_hand: null, display_as: null, display_name: null, category_id: 2} - {id: -1, price: null, unit_value: null, tax_category_id: null, unit_description: null, on_demand: false, on_hand: null, display_as: null, display_name: null, category_id: 2} - {id: -2, price: null, unit_value: null, tax_category_id: null, unit_description: null, on_demand: false, on_hand: null, display_as: null, display_name: null, category_id: 2} - ] - - it "shows the variant(s)", -> - product = {id: 123, variants: []} - $scope.addVariant(product) - expect(DisplayProperties.setShowVariants).toHaveBeenCalledWith 123, true - - - describe "deleting products", -> - it "deletes products with a http delete request to /api/products/id", -> - spyOn(window, "confirm").and.returnValue true - $scope.products = [ - { - id: 9 - } - { - id: 13 - } - ] - $scope.dirtyProducts = {} - $httpBackend.expectDELETE("/api/v0/products/13").respond 200, "data" - $scope.deleteProduct $scope.products[1] - expect(window.confirm).toHaveBeenCalledWith "Are you sure?" - $httpBackend.flush() - - it "removes the specified product from both $scope.products and $scope.dirtyProducts (if it exists there)", -> - spyOn(window, "confirm").and.returnValue true - $scope.products = [ - { - id: 9 - } - { - id: 13 - } - ] - DirtyProducts.addProductProperty 9, "someProperty", "something" - DirtyProducts.addProductProperty 13, "name", "P1" - - $httpBackend.expectDELETE("/api/v0/products/13").respond 200, "data" - $scope.deleteProduct $scope.products[1] - $httpBackend.flush() - expect($scope.products).toEqual [ - id: 9 - ] - expect(DirtyProducts.all()).toEqual 9: - id: 9 - someProperty: "something" - - - - describe "deleting variants", -> - describe "when the variant is the only one left on the product", -> - it "alerts the user", -> - spyOn(window, "alert") - $scope.products = [ - {id: 1, variants: [{id: 1}]} - ] - $scope.deleteVariant $scope.products[0], $scope.products[0].variants[0] - expect(window.alert).toHaveBeenCalledWith "The last variant cannot be deleted!" - - describe "when the variant has not been saved", -> - it "removes the variant from products and dirtyProducts", -> - spyOn(window, "confirm").and.returnValue true - $scope.products = [ - {id: 1, variants: [{id: -1},{id: -2}]} - ] - DirtyProducts.addVariantProperty 1, -1, "something", "something" - DirtyProducts.addProductProperty 1, "something", "something" - $scope.deleteVariant $scope.products[0], $scope.products[0].variants[0] - expect($scope.products).toEqual([ - {id: 1, variants: [{id: -2}]} - ]) - expect(DirtyProducts.all()).toEqual - 1: { id: 1, something: 'something'} - - - describe "when the variant has been saved", -> - it "deletes variants with a http delete request to /api/products/(id)/variants/(variant_id)", -> - spyOn(window, "confirm").and.returnValue true - $scope.products = [ - { - id: 9 - variants: [{ - id: 3 - price: 12 - }, - { - id: 4 - price: 15 - } - ] - } - { - id: 13 - } - ] - $scope.dirtyProducts = {} - $httpBackend.expectDELETE("/api/v0/products/9/variants/3").respond 200, "data" - $scope.deleteVariant $scope.products[0], $scope.products[0].variants[0] - $httpBackend.flush() - - it "removes the specified variant from both the variants object and $scope.dirtyProducts (if it exists there)", -> - spyOn(window, "confirm").and.returnValue true - $scope.products = [ - { - id: 9 - variants: [ - { - id: 3 - price: 12.0 - } - { - id: 4 - price: 6.0 - } - ] - } - { - id: 13 - } - ] - DirtyProducts.addVariantProperty 9, 3, "price", 12.0 - DirtyProducts.addVariantProperty 9, 4, "price", 6.0 - DirtyProducts.addProductProperty 13, "name", "P1" - - $httpBackend.expectDELETE("/api/v0/products/9/variants/3").respond 200, "data" - $scope.deleteVariant $scope.products[0], $scope.products[0].variants[0] - $httpBackend.flush() - expect($scope.products[0].variants).toEqual [ - id: 4 - price: 6.0 - ] - expect(DirtyProducts.all()).toEqual - 9: - id: 9 - variants: - 4: - id: 4 - price: 6.0 - - 13: - id: 13 - name: "P1" - - describe "editWarn", -> - testProduct = testVariant = null - - beforeEach -> - testProduct = - id: 1 - name: "TestProduct" - description: "" - deleted_at: null - meta_keywords: null - shipping_category_id: null - created_at: null - updated_at: null - on_hand: 0 - on_demand: false - producer_id: 5 - group_buy: null - group_buy_unit_size: null - - describe 'product has variant', -> - it 'should load the edit product variant page', -> - testVariant = - id: 2 - name: "TestVariant" - - $scope.editWarn(testProduct, testVariant) - - expect(windowStub.location.href).toBe( - "/admin/products/#{testProduct.id}/variants/#{testVariant.id}/edit" - ) - - describe 'product has no variant', -> - it 'should display unsaved changes confirmation if there are any DirtyProduct', inject ($window, DirtyProducts) -> - spyOn($window, 'confirm') - spyOn(DirtyProducts, 'count').and.returnValue 2 - - $scope.editWarn(testProduct, null) - expect($window.confirm).toHaveBeenCalled() - - it 'should load the edit product page', inject -> - $scope.editWarn(testProduct, null) - - expect(windowStub.location.href).toBe( - "/admin/products/#{testProduct.id}/edit" - ) - - it 'should load edit product page including the selected filters', inject ($httpParamSerializer) -> - query = 'lala' - category = 3 - $scope.q.query = query - $scope.q.categoryFilter = category - - # use $httpParamSerializer as it will sort parameters alphabetically - expectedFilter = $httpParamSerializer({ query: query, categoryFilter: category }) - - $scope.editWarn(testProduct, null) - - expect(windowStub.location.href).toBe( - "/admin/products/#{testProduct.id}/edit?#{expectedFilter}" - ) - - describe "filtering products", -> - describe "clearing filters", -> - it "resets filter variables", -> - $scope.q.query = "lala" - $scope.q.producerFilter = "5" - $scope.q.categoryFilter = "6" - $scope.resetSelectFilters() - expect($scope.q.query).toBe "" - expect($scope.q.producerFilter).toBeUndefined - expect($scope.q.categoryFilter).toBeUndefined - - -describe "converting arrays of objects with ids to an object with ids as keys", -> - it "returns an object", -> - array = [] - expect(toObjectWithIDKeys(array)).toEqual {} - - it "adds each object in the array provided with an id to the returned object with the id as its key", -> - array = [ - { - id: 1 - } - { - id: 3 - } - ] - expect(toObjectWithIDKeys(array)).toEqual - 1: - id: 1 - - 3: - id: 3 - - - it "ignores items which are not objects and those which do not possess ids", -> - array = [ - { - id: 1 - } - "not an object" - { - notanid: 3 - } - ] - expect(toObjectWithIDKeys(array)).toEqual 1: - id: 1 - - - it "sends arrays with the key 'variants' to itself", -> - spyOn(window, "toObjectWithIDKeys").and.callThrough() - array = [ - { - id: 1 - variants: [id: 17] - } - { - id: 2 - variants: - 12: - id: 12 - } - ] - products = toObjectWithIDKeys(array) - expect(products["1"].variants).toEqual 17: - id: 17 - - expect(toObjectWithIDKeys).toHaveBeenCalledWith [id: 17] - expect(toObjectWithIDKeys).not.toHaveBeenCalledWith {12: {id: 12}} diff --git a/spec/javascripts/unit/admin/services/bulk_products_spec.js.coffee b/spec/javascripts/unit/admin/services/bulk_products_spec.js.coffee deleted file mode 100644 index ea741adc35..0000000000 --- a/spec/javascripts/unit/admin/services/bulk_products_spec.js.coffee +++ /dev/null @@ -1,188 +0,0 @@ -describe "BulkProducts service", -> - BulkProducts = $httpBackend = null - - beforeEach -> - module "ofn.admin" - window.bigDecimal = jasmine.createSpyObj "bigDecimal", ["round"] - window.bigDecimal.round.and.callFake (a, b) -> a.toFixed(b) - - beforeEach inject (_BulkProducts_, _$httpBackend_) -> - BulkProducts = _BulkProducts_ - $httpBackend = _$httpBackend_ - - describe "cloning products", -> - it "clones products using a http post request to /api/products/(id)/clone", -> - BulkProducts.products = [ - id: 13 - ] - $httpBackend.expectPOST("/api/v0/products/13/clone").respond 201, - id: 17 - $httpBackend.expectGET("/api/v0/products/17?template=bulk_show").respond 200, [ - id: 17 - ] - BulkProducts.cloneProduct BulkProducts.products[0] - $httpBackend.flush() - - it "adds the product", -> - originalProduct = - id: 16 - clonedProduct = - id: 17 - - spyOn(BulkProducts, "insertProductAfter") - spyOn(BulkProducts, "unpackProduct") - BulkProducts.products = [originalProduct] - $httpBackend.expectPOST("/api/v0/products/16/clone").respond 201, clonedProduct - $httpBackend.expectGET("/api/v0/products/17?template=bulk_show").respond 200, clonedProduct - BulkProducts.cloneProduct BulkProducts.products[0] - $httpBackend.flush() - expect(BulkProducts.unpackProduct).toHaveBeenCalledWith clonedProduct - BulkProducts.unpackProduct(clonedProduct) - expect(BulkProducts.insertProductAfter).toHaveBeenCalledWith originalProduct, clonedProduct - - - describe "preparing products", -> - beforeEach -> - spyOn BulkProducts, "loadVariantUnit" - - it "calls loadVariantUnit for the product", -> - product = {id: 123} - BulkProducts.unpackProduct product - expect(BulkProducts.loadVariantUnit).toHaveBeenCalled() - - - describe "loading variant unit", -> - describe "setting product variant_unit_with_scale field", -> - it "HERE 2 sets by combining variant_unit and variant_unit_scale", -> - product = - variants:[ - id: 10 - variant_unit: "volume" - variant_unit_scale: .001 - ] - BulkProducts.loadVariantUnit product - expect(product.variants[0].variant_unit_with_scale).toEqual "volume_0.001" - - it "sets to null when variant_unit is null", -> - product = - variants: [ - {variant_unit: null, variant_unit_scale: 1000} - ] - BulkProducts.loadVariantUnit product - - expect(product.variants[0].variant_unit_with_scale).toBeNull() - - it "sets to variant_unit when variant_unit_scale is null", -> - product = - variants: [ - {variant_unit: 'items', variant_unit_scale: null, variant_unit_name: 'foo'} - ] - BulkProducts.loadVariantUnit product - expect(product.variants[0].variant_unit_with_scale).toEqual "items" - - it "sets to variant_unit when variant_unit is 'items'", -> - product = - variants: [ - {variant_unit: 'items', variant_unit_scale: 1000, variant_unit_name: 'foo'} - ] - BulkProducts.loadVariantUnit product - expect(product.variants[0].variant_unit_with_scale).toEqual "items" - - it "loads data for variants (excl. master)", -> - spyOn BulkProducts, "loadVariantUnitValue" - - product = - variants: [ - {id: 2, variant_unit_scale: 1.0, unit_value: 2, unit_description: '(two)'} - ] - BulkProducts.loadVariantUnitValues product.variants - - expect(BulkProducts.loadVariantUnitValue).toHaveBeenCalledWith product.variants[0] - - describe "setting variant unit_value_with_description", -> - it "sets by combining unit_value and unit_description", -> - product = - variants: [ - {id: 1, variant_unit_scale: 1.0, unit_value: 1, unit_description: '(bottle)'} - ] - BulkProducts.loadVariantUnitValues product.variants - expect(product.variants[0]).toEqual - id: 1 - variant_unit_scale: 1.0, - variant_unit_with_scale: null, - unit_value: 1 - unit_description: '(bottle)' - unit_value_with_description: '1 (bottle)' - - it "uses unit_value when description is missing", -> - product = - variants: [ - {id: 1, variant_unit_scale: 1.0, unit_value: 1} - ] - BulkProducts.loadVariantUnitValues product.variants - expect(product.variants[0].unit_value_with_description).toEqual '1' - - it "uses unit_description when value is missing", -> - product = - variants: [ - {id: 1, variant_unit_scale: 1.0, unit_description: 'Small'} - ] - BulkProducts.loadVariantUnitValues product.variants - expect(product.variants[0].unit_value_with_description).toEqual 'Small' - - it "converts values from base value to chosen unit", -> - product = - variants: [ - id: 1, variant_unit_scale: 1000.0, unit_value: 2500 - ] - BulkProducts.loadVariantUnitValues product.variants - expect(product.variants[0].unit_value_with_description).toEqual '2.5' - - it "converts values from base value to chosen unit without breaking precision", -> - product = - variants: [ - {id: 1,variant_unit_scale: 0.001, unit_value: 0.35} - ] - BulkProducts.loadVariantUnitValues product.variants - expect(product.variants[0].unit_value_with_description).toEqual '350' - - it "displays a unit_value of zero", -> - product = - variants: [ - {id: 1, variant_unit_scale: 1.0, unit_value: 0} - ] - BulkProducts.loadVariantUnitValues product.variants - expect(product.variants[0].unit_value_with_description).toEqual '0' - - - describe "calculating the scaled unit value for a variant", -> - it "returns the scaled value when variant has a unit_value", -> - variant = {variant_unit_scale: 0.001, unit_value: 5} - expect(BulkProducts.variantUnitValue(variant)).toEqual 5000 - - it "returns the scaled value rounded off upto 2 decimal points", -> - variant = {variant_unit_scale: 28.35, unit_value: 1234.5} - expect(BulkProducts.variantUnitValue(variant)).toEqual 43.54 - - it "returns the unscaled value when the product has no scale", -> - variant = {unit_value: 5} - expect(BulkProducts.variantUnitValue(variant)).toEqual 5 - - it "returns zero when the value is zero", -> - variant = {unit_value: 0} - expect(BulkProducts.variantUnitValue(variant)).toEqual 0 - - it "returns null when the variant has no unit_value", -> - variant = {} - expect(BulkProducts.variantUnitValue(variant)).toEqual null - - - describe "fetching a product by id", -> - it "returns the product when it is present", -> - product = {id: 123} - BulkProducts.products = [product] - expect(BulkProducts.find(123)).toEqual product - - it "returns null when the product is not present", -> - BulkProducts.products = [] - expect(BulkProducts.find(123)).toBeNull() From fefd0239e6978fc77ceff3f2e68993e9c3a7b875 Mon Sep 17 00:00:00 2001 From: Ahmed Ejaz Date: Sun, 27 Jul 2025 06:15:14 +0500 Subject: [PATCH 04/76] Remove unused product image controller and directive; delete product image update route --- .../controllers/product_image_controller.js.coffee | 8 -------- .../admin/products/directives/image_modal.js.coffee | 6 ------ config/routes/api.rb | 2 -- 3 files changed, 16 deletions(-) delete mode 100644 app/assets/javascripts/admin/products/controllers/product_image_controller.js.coffee delete mode 100644 app/assets/javascripts/admin/products/directives/image_modal.js.coffee diff --git a/app/assets/javascripts/admin/products/controllers/product_image_controller.js.coffee b/app/assets/javascripts/admin/products/controllers/product_image_controller.js.coffee deleted file mode 100644 index fb447208e2..0000000000 --- a/app/assets/javascripts/admin/products/controllers/product_image_controller.js.coffee +++ /dev/null @@ -1,8 +0,0 @@ -angular.module("ofn.admin").controller "ProductImageCtrl", ($scope, ProductImageService) -> - $scope.imageUploader = ProductImageService.imageUploader - $scope.imagePreview = ProductImageService.imagePreview - - $scope.$watch 'product.image_url', (newValue, oldValue) -> - if newValue != oldValue - $scope.imagePreview = newValue - $scope.uploadModal.close() diff --git a/app/assets/javascripts/admin/products/directives/image_modal.js.coffee b/app/assets/javascripts/admin/products/directives/image_modal.js.coffee deleted file mode 100644 index 69ca1731fd..0000000000 --- a/app/assets/javascripts/admin/products/directives/image_modal.js.coffee +++ /dev/null @@ -1,6 +0,0 @@ -angular.module("ofn.admin").directive "imageModal", ($modal, ProductImageService) -> - restrict: 'C' - link: (scope, elem, attrs, ctrl) -> - elem.on "click", (ev) => - scope.uploadModal = $modal.open(templateUrl: 'admin/modals/image_upload.html', controller: ctrl, scope: scope, windowClass: 'simple-modal') - ProductImageService.configure(scope.product) diff --git a/config/routes/api.rb b/config/routes/api.rb index 2545554739..725d022b98 100644 --- a/config/routes/api.rb +++ b/config/routes/api.rb @@ -74,8 +74,6 @@ Openfoodnetwork::Application.routes.draw do resources :enterprise_fees, only: [:destroy] - post '/product_images/:product_id', to: 'product_images#update_product_image' - resources :states, :only => [:index, :show] resources :taxons, except: %i[show edit] From 025fc784a8bf79bf530a1dc2fd9d54a973f5988d Mon Sep 17 00:00:00 2001 From: Ahmed Ejaz Date: Sun, 27 Jul 2025 06:26:12 +0500 Subject: [PATCH 05/76] Refactor products_return_to_url method to remove url_filters parameter and simplify usage in views --- app/helpers/admin/products_helper.rb | 8 ++------ app/views/spree/admin/products/edit.html.haml | 4 ++-- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/app/helpers/admin/products_helper.rb b/app/helpers/admin/products_helper.rb index b2f7beada9..d7b8a2086f 100644 --- a/app/helpers/admin/products_helper.rb +++ b/app/helpers/admin/products_helper.rb @@ -32,12 +32,8 @@ module Admin [precised_unit_value, variant.unit_description].compact_blank.join(" ") end - def products_return_to_url(url_filters) - if feature?(:admin_style_v3, spree_current_user) - return session[:products_return_to_url] || admin_products_url - end - - "#{admin_products_path}#{url_filters.empty? ? '' : "#?#{url_filters.to_query}"}" + def products_return_to_url + session[:products_return_to_url] || admin_products_url end # if user hasn't saved any preferences on products page and there's only one producer; diff --git a/app/views/spree/admin/products/edit.html.haml b/app/views/spree/admin/products/edit.html.haml index 003a35eee6..7f6df174db 100644 --- a/app/views/spree/admin/products/edit.html.haml +++ b/app/views/spree/admin/products/edit.html.haml @@ -1,7 +1,7 @@ = admin_inject_available_units - content_for :page_actions do - %li= button_link_to t('admin.products.back_to_products_list'), products_return_to_url(@url_filters), :icon => 'icon-arrow-left' + %li= button_link_to t('admin.products.back_to_products_list'), products_return_to_url, :icon => 'icon-arrow-left' %li#new_product_link = button_link_to t(:new_product), new_object_url, { :icon => 'icon-plus', :id => 'admin_new_product' } @@ -18,6 +18,6 @@ = button t(:update), 'icon-refresh' - = button_link_to t(:cancel), products_return_to_url(@url_filters), icon: 'icon-remove' + = button_link_to t(:cancel), products_return_to_url, icon: 'icon-remove' #product-preview-modal-container From 6e055ddbdf6eb6bb107984b000bfcdbcfcb24e75 Mon Sep 17 00:00:00 2001 From: Ahmed Ejaz Date: Sun, 27 Jul 2025 06:32:44 +0500 Subject: [PATCH 06/76] Remove icon parameters from admin navigation tabs for simplification --- app/helpers/spree/admin/navigation_helper.rb | 8 +------- app/views/spree/admin/shared/_tabs.html.haml | 14 +++++++------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/app/helpers/spree/admin/navigation_helper.rb b/app/helpers/spree/admin/navigation_helper.rb index 7b408e5ef5..ccb141043f 100644 --- a/app/helpers/spree/admin/navigation_helper.rb +++ b/app/helpers/spree/admin/navigation_helper.rb @@ -29,13 +29,7 @@ module Spree scope: [:admin, :tab]).capitalize css_classes = [] - - if options[:icon] && !feature?(:admin_style_v3, spree_current_user) - link = link_to_with_icon(options[:icon], titleized_label, destination_url) - css_classes << 'tab-with-icon' - else - link = link_to(titleized_label, destination_url) - end + link = link_to(titleized_label, destination_url) selected = if options[:match_path] PathChecker diff --git a/app/views/spree/admin/shared/_tabs.html.haml b/app/views/spree/admin/shared/_tabs.html.haml index 3ad1a1d53a..ebe1c422ba 100644 --- a/app/views/spree/admin/shared/_tabs.html.haml +++ b/app/views/spree/admin/shared/_tabs.html.haml @@ -1,11 +1,11 @@ -= tab :overview, label: 'dashboard', url: spree.admin_dashboard_path, icon: 'icon-dashboard' -= tab :products, :properties, :inventory, :product_import, :images, :variants, :product_properties, :group_buy_options, :seo, :products_v3, :variant_overrides, url: admin_products_path, icon: 'icon-th-large' -= tab :order_cycles, url: main_app.admin_order_cycles_path, icon: 'icon-refresh' -= tab :orders, :subscriptions, :customer_details, :adjustments, :payments, :return_authorizations, url: admin_orders_path, icon: 'icon-shopping-cart' -= tab :reports, url: main_app.admin_reports_path, icon: 'icon-file' -= tab :general_settings, :terms_of_service_files, :mail_methods, :tax_categories, :tax_rates, :tax_settings, :zones, :countries, :states, :payment_methods, :taxons, :shipping_methods, :shipping_categories, :enterprise_fees, :contents, :invoice_settings, :matomo_settings, :stripe_connect_settings, :connected_app_settings, label: 'configuration', icon: 'icon-wrench', url: edit_admin_general_settings_path += tab :overview, label: 'dashboard', url: spree.admin_dashboard_path += tab :products, :properties, :inventory, :product_import, :images, :variants, :product_properties, :group_buy_options, :seo, :products_v3, :variant_overrides, url: admin_products_path += tab :order_cycles, url: main_app.admin_order_cycles_path += tab :orders, :subscriptions, :customer_details, :adjustments, :payments, :return_authorizations, url: admin_orders_path += tab :reports, url: main_app.admin_reports_path += tab :general_settings, :terms_of_service_files, :mail_methods, :tax_categories, :tax_rates, :tax_settings, :zones, :countries, :states, :payment_methods, :taxons, :shipping_methods, :shipping_categories, :enterprise_fees, :contents, :invoice_settings, :matomo_settings, :stripe_connect_settings, :connected_app_settings, label: 'configuration', url: edit_admin_general_settings_path = tab :enterprises, :enterprise_relationships, :vouchers, :oidc_settings, url: main_app.admin_enterprises_path = tab :customers, url: main_app.admin_customers_path = tab :enterprise_groups, url: main_app.admin_enterprise_groups_path, label: 'groups' - if can? :admin, Spree::User - = tab(:users, :enterprise_roles, url: spree.admin_users_path, icon: 'icon-user') + = tab(:users, :enterprise_roles, url: spree.admin_users_path) From 188b2eb7548810a75493d8c769a4253bdd015a40 Mon Sep 17 00:00:00 2001 From: Ahmed Ejaz Date: Sun, 27 Jul 2025 06:44:02 +0500 Subject: [PATCH 07/76] Simplify pagination next button by removing conditional icon rendering --- app/views/admin/shared/_stimulus_pagination.html.haml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/views/admin/shared/_stimulus_pagination.html.haml b/app/views/admin/shared/_stimulus_pagination.html.haml index ec73ecb92b..23860ead4e 100644 --- a/app/views/admin/shared/_stimulus_pagination.html.haml +++ b/app/views/admin/shared/_stimulus_pagination.html.haml @@ -22,9 +22,6 @@ - if pagy.next %button.page.next{ data: { action: 'click->search#changePage', page: pagy.next } } - - if feature?(:admin_style_v3, spree_current_user) - %i.icon-chevron-right{ data: { action: 'click->search#changePage', page: pagy.next } } - - else - != pagy_t('pagy.next') + %i.icon-chevron-right{ data: { action: 'click->search#changePage', page: pagy.next } } - else %button.page.disabled.pagination-next{disabled: "disabled"}!= pagy_t('pagy.next') From 1426b6eeb71398838272a5960cccd50783629d99 Mon Sep 17 00:00:00 2001 From: Ahmed Ejaz Date: Sun, 27 Jul 2025 07:02:53 +0500 Subject: [PATCH 08/76] Remove legacy admin styles in favor of v3 styling Completes migration to the new admin v3 styling system by: - Removing conditional stylesheet inclusion in admin head - Deleting all legacy admin style files and components - Making admin-style-v3 the default and only stylesheet This change reduces maintenance overhead and simplifies the admin styling codebase by removing the old styling system that was being conditionally loaded based on feature flags. --- app/views/spree/admin/shared/_head.html.haml | 7 +- app/webpacker/css/admin/all.scss | 125 --------- .../css/admin/components/actions.scss | 31 --- .../css/admin/components/buttons.scss | 93 ------- .../css/admin/components/date-picker.scss | 20 -- .../css/admin/components/messages.scss | 76 ----- .../css/admin/components/navigation.scss | 174 ------------ .../css/admin/components/pagination.scss | 32 --- .../css/admin/components/sidebar.scss | 26 -- .../css/admin/components/tom_select.scss | 141 ---------- .../css/admin/globals/variables.scss | 156 ----------- .../plugins/flatpickr-customization.scss | 68 ----- app/webpacker/css/admin/plugins/powertip.scss | 118 -------- app/webpacker/css/admin/sections/orders.scss | 65 ----- app/webpacker/css/admin/shared/forms.scss | 261 ------------------ app/webpacker/css/admin/shared/icons.scss | 25 -- app/webpacker/css/admin/shared/layout.scss | 134 --------- app/webpacker/css/admin/shared/tables.scss | 208 -------------- .../css/admin/shared/typography.scss | 158 ----------- .../css/admin/terms_of_service_banner.scss | 27 -- app/webpacker/packs/admin-styles.scss | 2 - 21 files changed, 1 insertion(+), 1946 deletions(-) delete mode 100644 app/webpacker/css/admin/all.scss delete mode 100644 app/webpacker/css/admin/components/actions.scss delete mode 100644 app/webpacker/css/admin/components/buttons.scss delete mode 100644 app/webpacker/css/admin/components/date-picker.scss delete mode 100644 app/webpacker/css/admin/components/messages.scss delete mode 100644 app/webpacker/css/admin/components/navigation.scss delete mode 100644 app/webpacker/css/admin/components/pagination.scss delete mode 100644 app/webpacker/css/admin/components/sidebar.scss delete mode 100644 app/webpacker/css/admin/components/tom_select.scss delete mode 100644 app/webpacker/css/admin/globals/variables.scss delete mode 100644 app/webpacker/css/admin/plugins/flatpickr-customization.scss delete mode 100644 app/webpacker/css/admin/plugins/powertip.scss delete mode 100644 app/webpacker/css/admin/sections/orders.scss delete mode 100644 app/webpacker/css/admin/shared/forms.scss delete mode 100644 app/webpacker/css/admin/shared/icons.scss delete mode 100644 app/webpacker/css/admin/shared/layout.scss delete mode 100644 app/webpacker/css/admin/shared/tables.scss delete mode 100644 app/webpacker/css/admin/shared/typography.scss delete mode 100644 app/webpacker/css/admin/terms_of_service_banner.scss delete mode 100644 app/webpacker/packs/admin-styles.scss diff --git a/app/views/spree/admin/shared/_head.html.haml b/app/views/spree/admin/shared/_head.html.haml index e1b987232c..5da4963871 100644 --- a/app/views/spree/admin/shared/_head.html.haml +++ b/app/views/spree/admin/shared/_head.html.haml @@ -14,12 +14,7 @@ = " - OFN #{t(:administration)}" %link{:href => "https://fonts.googleapis.com/css?family=Open+Sans:400italic,600italic,400,600&subset=latin,cyrillic,greek,vietnamese", :rel => "stylesheet", :type => "text/css"} - -- if feature?(:admin_style_v3, spree_current_user) - = stylesheet_pack_tag 'admin-style-v3', media: "screen, print" -- else - = stylesheet_pack_tag 'admin-styles', media: "screen, print" - += stylesheet_pack_tag 'admin-style-v3', media: "screen, print" = render "layouts/bugsnag_js" - if content_for? :minimal_js diff --git a/app/webpacker/css/admin/all.scss b/app/webpacker/css/admin/all.scss deleted file mode 100644 index a63e543f90..0000000000 --- a/app/webpacker/css/admin/all.scss +++ /dev/null @@ -1,125 +0,0 @@ -@import "vendor/assets/stylesheets/normalize"; -@import "vendor/assets/stylesheets/responsive-tables"; -@import "vendor/assets/stylesheets/jquery.powertip"; -@import "~jquery-ui/themes/base/core"; -@import "~jquery-ui/themes/base/button"; -@import "~jquery-ui/themes/base/resizable"; -@import "vendor/assets/stylesheets/jquery-ui-theme"; -@import "~jquery-ui/themes/base/dialog"; -@import "../shared/ng-tags-input.min"; -@import "vendor/assets/stylesheets/select2.css.scss"; -@import "~flatpickr/dist/flatpickr"; -@import "~flatpickr/dist/themes/material_blue"; -@import "~shortcut-buttons-flatpickr/dist/themes/light"; - -@import "globals/functions"; -@import "globals/palette"; -@import "globals/variables"; -@import "globals/mixins"; - -@import "plugins/font-awesome"; - -@import "../shared/variables/layout"; -@import "../shared/variables/variables"; -@import "../shared/utilities"; -@import "shared/typography"; -@import "shared/tables"; -@import "shared/icons"; -@import "shared/forms"; -@import "shared/layout"; -@import "shared/scroll_bar"; - -@import "../shared/trix"; - -@import "plugins/flatpickr-customization"; -@import "plugins/powertip"; -@import "plugins/select2"; - -@import "sections/orders"; -@import "sections/products"; - -@import "hacks/mozilla"; -@import "hacks/opera"; -@import "hacks/ie"; - -@import "components/actions"; -@import "components/alert-box"; -@import "components/alert_row"; -@import "components/buttons"; -@import "components/date-picker"; -@import "components/dialogs"; -@import "components/input"; -@import "components/jquery_dialog"; -@import "components/messages"; -@import "components/navigation"; -@import "components/ng-cloak"; -@import "components/page_actions"; -@import "components/pagination"; -@import "components/per_page_controls"; -@import "components/product_autocomplete"; -@import "components/progress"; -@import "components/save_bar"; -@import "components/sidebar"; -@import "components/simple_modal"; -@import "components/states"; -@import "components/stripe_connect_button"; -@import "components/subscriptions_states"; -@import "components/table-filter"; -@import "components/table_loading"; -@import "components/timepicker"; -@import "components/todo"; -@import "components/tooltip"; -@import "components/wizard_progress"; - -@import "pages/enterprise_form"; -@import "pages/subscription_form"; -@import "pages/subscription_line_items"; -@import "pages/subscription_review"; - -@import "advanced_settings"; -@import "alert"; -@import "animations"; -@import "change_type_form"; -@import "connected_apps"; -@import "customers"; -@import "dashboard_item"; -@import "dashboard-single-ent"; -@import "dialog"; -@import "disabled"; -@import "dropdown"; -@import "enterprise_index_panels"; -@import "enterprises"; -@import "filters_and_controls"; -@import "grid"; -@import "icons"; -@import "index_panel_buttons"; -@import "index_panels"; -@import "modals"; -@import "offsets"; -@import "openfoodnetwork"; -@import "order_cycles"; -@import "orders"; -@import "product_import"; -@import "products"; -@import "question-mark-tooltip"; -@import "relationships"; -@import "reports"; -@import "select2"; -@import "sidebar-item"; -@import "side_menu"; -@import "tables"; -@import "tag_rules"; -@import "terms_of_service_banner"; -@import "terms_of_service_files"; -@import "validation"; -@import "variant_overrides"; -@import "welcome"; - -@import "../shared/question-mark-icon"; -@import "question-mark-tooltip"; - -@import "~tom-select/src/scss/tom-select.default"; -@import "components/tom_select"; - -@import "app/components/modal_component/modal_component"; -@import "app/webpacker/css/admin/trix.scss"; diff --git a/app/webpacker/css/admin/components/actions.scss b/app/webpacker/css/admin/components/actions.scss deleted file mode 100644 index c766a284ac..0000000000 --- a/app/webpacker/css/admin/components/actions.scss +++ /dev/null @@ -1,31 +0,0 @@ -table tbody tr { - &.highlight { - @each $action in $actions { - &.action-#{$action} td { - background-color: get-value($actions, $actions-bg-colors, $action); - border-color: get-value($actions, $actions-brd-colors, $action); - } - } - - &.action-remove td, - &.action-void td { - text-decoration: line-through; - - &.actions { - text-decoration: none; - } - } - } - - &.before-highlight { - @each $action in $actions { - &.action-#{$action} td { - border-bottom-color: get-value($actions, $actions-brd-colors, $action); - } - } - } - - td.actions { - background-color: transparent !important; - } -} diff --git a/app/webpacker/css/admin/components/buttons.scss b/app/webpacker/css/admin/components/buttons.scss deleted file mode 100644 index 1291d04e15..0000000000 --- a/app/webpacker/css/admin/components/buttons.scss +++ /dev/null @@ -1,93 +0,0 @@ -input[type="submit"], -input[type="button"]:not(.trix-button), -button:not(.plain):not(.trix-button), -.button { - position: relative; - cursor: pointer; - font-size: 85%; - @include border-radius($border-radius); - display: inline-block; - padding: 8px 15px; - border: none; - background-color: $color-btn-bg; - color: $color-btn-text; - text-transform: uppercase; - font-weight: 600 !important; - - &:before { - font-weight: normal !important; - } - - &:visited, - &:active, - &:focus { - color: $color-btn-text; - } - - &:hover { - background-color: $color-btn-hover-bg; - color: $color-btn-hover-text; - } - - &:active:focus { - box-shadow: 0 0 8px 0 darken($color-btn-hover-bg, 5) inset; - } - - &.fullwidth { - width: 100%; - text-align: center; - } - - &.secondary { - background-color: transparent; - border: 1px solid $color-btn-bg; - color: $color-btn-bg; - - &:hover, - &:active, - &:focus { - background-color: #ebf3fb; - } - - &:active:focus { - box-shadow: none; - } - } - - .badge { - position: absolute; - top: 0; - right: 0; - transform: translateY(-50%); - font-size: 10px; - text-transform: capitalize; - padding: 0px 5px; - border-radius: 3px; - - &:before { - padding: 0; - } - - &.danger { - background-color: $color-warning; - } - &.success { - background-color: $spree-green; - } - } -} - -input.red, -a.button.red, -button.red { - background-color: $color-warning; - margin-right: 5px; - color: #ffffff; -} - -a.button.red { - &:not(:hover) { - color: #fff; - background-color: $color-warning; - } -} diff --git a/app/webpacker/css/admin/components/date-picker.scss b/app/webpacker/css/admin/components/date-picker.scss deleted file mode 100644 index a35b5a970e..0000000000 --- a/app/webpacker/css/admin/components/date-picker.scss +++ /dev/null @@ -1,20 +0,0 @@ -// scss-lint:disable QualifyingElement - -input.datetimepicker { - min-width: 12.9em; -} - -.container input[readonly].flatpickr-input, -.container input[readonly].datepicker, -.container input[readonly].datetimepicker { - background-color: $white; - cursor: pointer; -} - -img.ui-datepicker-trigger { - margin-left: -1.75em; - position: absolute; - margin-top: 0.5em; -} - -// scss-lint:enable QualifyingElement diff --git a/app/webpacker/css/admin/components/messages.scss b/app/webpacker/css/admin/components/messages.scss deleted file mode 100644 index f92498a599..0000000000 --- a/app/webpacker/css/admin/components/messages.scss +++ /dev/null @@ -1,76 +0,0 @@ -.errorExplanation { - padding: 10px; - border: 1px solid very-light($color-error, 12); - background-color: very-light($color-error, 6); - border-radius: 3px; - color: $color-error; - margin-bottom: 15px; - - h2 { - font-size: 140%; - color: $color-error; - margin-bottom: 5px; - } - - p { - padding: 10px 0; - } - - ul { - list-style-position: inside; - - li { - font-weight: $font-weight-bold; - } - } -} - -.flash-container { - position: fixed; - top: 0; - left: 0; - width: 100%; - z-index: 1000; - - .flash { - padding: 18px; - text-align: center; - font-size: 120%; - color: $color-1; - font-weight: 600; - margin-top: 0; - - &.notice { - background-color: rgba($color-notice, 0.8); - } - &.success { - background-color: rgba($color-success, 0.8); - } - &.error { - background-color: rgba($color-error, 0.8); - } - - // Adjust heights to fit main layout dimension (header, navbar...) - &:nth-child(2) { - padding: 24px; - } - &:nth-child(3) { - padding: 20px; - } - - .actions { - display: none; /* avoid adding new button on old design */ - } - } -} - -.notice:not(.flash) { - padding: 1rem; - margin-bottom: 1.5rem; - background-color: $spree-light-blue; - border-radius: $border-radius; - - a { - font-weight: bold; - } -} diff --git a/app/webpacker/css/admin/components/navigation.scss b/app/webpacker/css/admin/components/navigation.scss deleted file mode 100644 index 2aeb3a8a30..0000000000 --- a/app/webpacker/css/admin/components/navigation.scss +++ /dev/null @@ -1,174 +0,0 @@ -// Navigation -//--------------------------------------------------- -.inline-menu { - margin: 0; - -webkit-margin-before: 0; - -webkit-padding-start: 0; -} - -nav.menu { - ul { - list-style: none; - - li { - a { - padding: 10px 0; - display: block; - position: relative; - text-align: left; - border: 1px solid transparent; - text-transform: uppercase; - font-weight: 600; - font-size: 90%; - } - - &.active a { - color: $color-2; - border-left-width: 0; - border-bottom-color: $color-2; - } - - &:hover a { - color: $color-2; - } - } - } -} - -.admin-login-navigation-bar { - ul { - text-align: right; - - li { - padding: 5px 0 5px 10px; - text-align: right; - font-size: 90%; - color: $color-link; - margin-top: 8px; - - &.user-logged-in-as { - width: 50%; - color: $color-body-text; - } - - &:hover { - i { - color: $color-2; - } - } - } - } -} - -#admin-menu { - background-color: $color-3; - - ul { - display: flex; - } - - li { - min-width: 90px; - flex-grow: 1; - - a { - display: block; - padding: 25px 5px; - color: $color-1 !important; - text-transform: uppercase; - position: relative; - text-align: center; - font-weight: 600; - - i { - display: inline; - } - - &:hover { - background-color: $color-2; - - &:after { - content: ""; - position: absolute; - border-left: 10px solid transparent; - border-right: 10px solid transparent; - border-top: 5px solid $color-2; - bottom: 0px; - margin-bottom: -5px; - left: 50%; - margin-left: -10px; - z-index: 1; - } - } - - span.text { - font-weight: 600; - } - } - - a::before { - font-weight: normal; - padding-top: 0; - } - - .dropdown { - width: 300px; - background-color: $color-3; - width: 200px; - z-index: 100000; - - > li { - width: 200px !important; - - a:after { - display: none; - } - } - } - - &.selected a { - @extend a, :hover; - } - } -} - -#sub-menu { - background-color: $color-2; - padding-bottom: 0; - - li { - a { - display: block; - padding: 12px 20px; - color: $color-1; - text-align: center; - text-transform: uppercase; - position: relative; - font-size: 85%; - } - - &.selected a, - a:hover { - &:after { - content: ""; - position: absolute; - border-left: 10px solid transparent; - border-right: 10px solid transparent; - border-top: 5px solid $color-2; - bottom: 0px; - margin-bottom: -5px; - left: 50%; - margin-left: -10px; - z-index: 0; - } - } - } -} - -#header figure { - margin: 0.25em 0; -} - -#login-nav { - line-height: 1.75em; -} diff --git a/app/webpacker/css/admin/components/pagination.scss b/app/webpacker/css/admin/components/pagination.scss deleted file mode 100644 index 730abe8888..0000000000 --- a/app/webpacker/css/admin/components/pagination.scss +++ /dev/null @@ -1,32 +0,0 @@ -.pagination { - text-align: center; - margin: 2em 0 1em; - padding: 10px 0; - - .page { - padding: 5px 8px; - text-align: center; - display: inline-block; - text-align: center; - - &.current { - background-color: $color-2; - border-radius: 3px; - color: $color-1; - } - } - - button { - margin: 0 0.35em; - - &.active { - background-color: darken($spree-blue, 15%); - cursor: default; - } - - &.disabled { - background-color: $color-btn-disabled-bg; - cursor: default; - } - } -} diff --git a/app/webpacker/css/admin/components/sidebar.scss b/app/webpacker/css/admin/components/sidebar.scss deleted file mode 100644 index 960de96ab0..0000000000 --- a/app/webpacker/css/admin/components/sidebar.scss +++ /dev/null @@ -1,26 +0,0 @@ -// Sidebar -//--------------------------------------------------- -#sidebar { - overflow: visible; - border-top: 1px solid $color-border; - margin-top: 17px; - - .sidebar-title { - color: $color-2; - text-transform: uppercase; - text-align: center; - font-size: 14px; - font-weight: 600; - - > span { - display: inline; - background: #fff; - padding: 5px 10px; - position: relative; - top: -14px; - - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - } - } -} diff --git a/app/webpacker/css/admin/components/tom_select.scss b/app/webpacker/css/admin/components/tom_select.scss deleted file mode 100644 index 0783795e6e..0000000000 --- a/app/webpacker/css/admin/components/tom_select.scss +++ /dev/null @@ -1,141 +0,0 @@ -.ts-wrapper { - min-height: initial; -} - -.ts-wrapper.multi { - .ts-control { - box-shadow: none; - border-color: $pale-blue; - - &:focus { - border-color: $spree-green; - } - - [data-value] { - text-shadow: none; - background-image: none; - background-repeat: initial; - box-shadow: none; - background-color: $spree-blue; - } - } - - .ts-control > div { - border: none; - background-color: $spree-blue; - } -} - -.ts-wrapper.plugin-remove_button .item .remove { - font-weight: bold; -} - -.ts-dropdown { - margin-top: 0; - - .option { - min-height: 2.25em; - display: block; - } -} - -.ts-wrapper.single .ts-control, -.ts-dropdown.single { - border-color: $color-tbl-border; -} - -.ts-control, -.ts-wrapper.single.input-active .ts-control { - cursor: pointer; - padding: 6px 8px; - outline: 0 !important; - min-height: 2.5em; -} - -.ts-wrapper.single .ts-control { - padding-right: 2rem; -} - -.ts-wrapper.inline, -.ts-wrapper.inline.input-active { - width: fit-content; - - .ts-control { - padding-right: 2rem; - } -} - -.ts-wrapper.single .ts-control { - box-shadow: none; - background-image: none; - background-color: $white; -} - -.ts-wrapper.primary.focus .ts-control, -.ts-wrapper.primary .ts-control { - background-color: $spree-blue; - border-color: $spree-blue; - color: $white; - - &:after { - border-color: $white transparent transparent transparent; - } -} - -.ts-wrapper .select-multiple { - cursor: pointer; -} - -.ts-wrapper.dropdown-active.primary .ts-control { - background-color: $spree-green; - border-color: $spree-green; - color: $white; - - &:after { - border-color: transparent transparent $white transparent; - } -} - -.dropdown-input-wrap { - padding: 0.2em; - position: relative; - - &:before { - @extend [class^="icon-"]; - @extend .icon-search; - - position: absolute; - opacity: 0.4; - line-height: 2em; - left: 0.75em; - } - - .dropdown-input { - outline: 0; - padding: 4px 6px; - border: 1px solid $spree-green; - border-radius: 0.3em; - padding-left: 1.75em; - } -} - -.ts-wrapper.no-search { - .dropdown-input-wrap { - display: none; - } -} - -.ts-dropdown .create:hover, -.ts-dropdown #admin-menu li.selected a.create, -#admin-menu li.selected .ts-dropdown a.create, -.ts-dropdown .option:hover, -.ts-dropdown #admin-menu li.selected a.option, -#admin-menu li.selected .ts-dropdown a.option, -.ts-dropdown .active { - background-color: $spree-blue; - color: $white; -} - -.ts-dropdown [data-selectable] .highlight { - background: rgba(149, 180, 255, 0.26); -} diff --git a/app/webpacker/css/admin/globals/variables.scss b/app/webpacker/css/admin/globals/variables.scss deleted file mode 100644 index f35788dc5f..0000000000 --- a/app/webpacker/css/admin/globals/variables.scss +++ /dev/null @@ -1,156 +0,0 @@ -// ------------------------------------------------------------- -// Variables used in all other files -//-------------------------------------------------------------- - -// Fonts -//-------------------------------------------------------------- -$base-font-family: "Open Sans", "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif; - -// Colors -//-------------------------------------------------------------- - -// Body base colors -$color-body-bg: $color-1 !default; -$color-body-text: $color-4 !default; -$color-headers: $color-4 !default; -$color-link: $color-3 !default; -$color-link-hover: $color-2 !default; -$color-link-active: $color-2 !default; -$color-link-focus: $color-2 !default; -$color-link-visited: $color-3 !default; -$color-border: very-light($color-3, 12) !default; - -// Basic flash colors -$color-success: $color-2 !default; -$color-notice: $color-6 !default; -$color-warning: $color-5 !default; -$color-error: $color-5 !default; - -// Table colors -$color-tbl-odd: $color-1 !default; -$color-tbl-even: very-light($color-3, 4) !default; -$color-tbl-thead: very-light($color-3, 4) !default; -$color-tbl-border: $pale-blue !default; - -// Button colors -$color-btn-bg: $color-3 !default; -$color-btn-text: $color-1 !default; -$color-btn-hover-bg: $color-2 !default; -$color-btn-hover-text: $color-1 !default; -$color-btn-disabled-bg: $light-grey !default; - -// Actions colors -$color-action-edit-bg: very-light($color-success, 5 ) !default; -$color-action-edit-brd: very-light($color-success, 20 ) !default; -$color-action-clone-bg: very-light($color-notice, 5 ) !default; -$color-action-clone-brd: very-light($color-notice, 15 ) !default; -$color-action-remove-bg: very-light($color-error, 5 ) !default; -$color-action-remove-brd: very-light($color-error, 10 ) !default; -$color-action-void-bg: very-light($color-error, 10 ) !default; -$color-action-void-brd: very-light($color-error, 20 ) !default; -$color-action-cancel-bg: very-light($color-notice, 10 ) !default; -$color-action-cancel-brd: very-light($color-notice, 20 ) !default; -$color-action-capture-bg: very-light($color-success, 5 ) !default; -$color-action-capture-brd: very-light($color-success, 20 ) !default; -$color-action-save-bg: very-light($color-success, 5 ) !default; -$color-action-save-brd: very-light($color-success, 20 ) !default; -$color-action-mail-bg: very-light($color-success, 5 ) !default; -$color-action-mail-brd: very-light($color-success, 20 ) !default; - -// Select2 select field colors -$color-sel-bg: $color-3 !default; -$color-sel-text: $color-1 !default; -$color-sel-hover-bg: $color-2 !default; -$color-sel-hover-text: $color-1 !default; - -// Text inputs colors -$color-txt-brd: $color-border !default; -$color-txt-text: $color-3 !default; -$color-txt-hover-brd: $color-2 !default; -$vpadding-txt: 7px; -$hpadding-txt: 10px; - -// Modal colors -$color-modal-close-btn: $color-5 !default; -$color-modal-close-btn-hover: darken($color-5, 5%) !default; - -// States label colors -$color-ste-complete-bg: $color-success !default; -$color-ste-complete-text: $color-1 !default; -$color-ste-completed-bg: $color-success !default; -$color-ste-completed-text: $color-1 !default; -$color-ste-sold-bg: $color-success !default; -$color-ste-sold-text: $color-1 !default; -$color-ste-pending-bg: $color-notice !default; -$color-ste-pending-text: $color-1 !default; -$color-ste-requires_authorization-bg: $color-notice !default; -$color-ste-requires_authorization-text: $color-1 !default; -$color-ste-awaiting_return-bg: $color-notice !default; -$color-ste-awaiting_return-text: $color-1 !default; -$color-ste-returned-bg: $color-notice !default; -$color-ste-returned-text: $color-1 !default; -$color-ste-credit_owed-bg: $color-notice !default; -$color-ste-credit_owed-text: $color-1 !default; -$color-ste-paid-bg: $color-success !default; -$color-ste-paid-text: $color-1 !default; -$color-ste-shipped-bg: $color-success !default; -$color-ste-shipped-text: $color-1 !default; -$color-ste-balance_due-bg: $color-notice !default; -$color-ste-balance_due-text: $color-1 !default; -$color-ste-backorder-bg: $color-notice !default; -$color-ste-backorder-text: $color-1 !default; -$color-ste-none-bg: $color-error !default; -$color-ste-none-text: $color-1 !default; -$color-ste-ready-bg: $color-success !default; -$color-ste-ready-text: $color-1 !default; -$color-ste-void-bg: $color-error !default; -$color-ste-void-text: $color-1 !default; -$color-ste-canceled-bg: $color-error !default; -$color-ste-canceled-text: $color-1 !default; -$color-ste-address-bg: $color-error !default; -$color-ste-address-text: $color-1 !default; -$color-ste-checkout-bg: $color-notice !default; -$color-ste-checkout-text: $color-1 !default; -$color-ste-cart-bg: $color-notice !default; -$color-ste-cart-text: $color-1 !default; -$color-ste-payment-bg: $color-error !default; -$color-ste-payment-text: $color-1 !default; -$color-ste-delivery-bg: $color-success !default; -$color-ste-delivery-text: $color-1 !default; -$color-ste-confirmation-bg: $color-error !default; -$color-ste-confirmation-text: $color-1 !default; -$color-ste-active-bg: $color-success !default; -$color-ste-active-text: $color-1 !default; -$color-ste-inactive-bg: $color-notice !default; -$color-ste-inactive-text: $color-1 !default; - -// Available states -$states: completed, complete, sold, pending, awaiting_return, returned, credit_owed, paid, shipped, balance_due, backorder, checkout, cart, address, delivery, payment, confirmation, canceled, ready, void, requires_authorization, active, inactive !default; -$states-bg-colors: $color-ste-completed-bg, $color-ste-complete-bg, $color-ste-sold-bg, $color-ste-pending-bg, $color-ste-awaiting_return-bg, $color-ste-returned-bg, $color-ste-credit_owed-bg, $color-ste-paid-bg, $color-ste-shipped-bg, $color-ste-balance_due-bg, $color-ste-backorder-bg, $color-ste-checkout-bg, $color-ste-cart-bg, $color-ste-address-bg, $color-ste-delivery-bg, $color-ste-payment-bg, $color-ste-confirmation-bg, $color-ste-canceled-bg, $color-ste-ready-bg, $color-ste-void-bg, $color-ste-requires_authorization-bg, $color-ste-active-bg, $color-ste-inactive-bg !default; -$states-text-colors: $color-ste-completed-text, $color-ste-complete-text, $color-ste-sold-text, $color-ste-pending-text, $color-ste-awaiting_return-text, $color-ste-returned-text, $color-ste-credit_owed-text, $color-ste-paid-text, $color-ste-shipped-text, $color-ste-balance_due-text, $color-ste-backorder-text, $color-ste-checkout-text, $color-ste-cart-text, $color-ste-address-text, $color-ste-delivery-text, $color-ste-payment-text, $color-ste-confirmation-text, $color-ste-canceled-text, $color-ste-ready-text, $color-ste-void-text, $color-ste-requires_authorization-text, $color-ste-active-text, $color-ste-inactive-text !default; - -// Available actions -$actions: edit, clone, remove, void, capture, save, cancel, mail !default; -$actions-bg-colors: $color-action-edit-bg, $color-action-clone-bg, $color-action-remove-bg, $color-action-void-bg, $color-action-capture-bg, $color-action-save-bg, $color-action-cancel-bg, $color-action-mail-bg !default; -$actions-brd-colors: $color-action-edit-brd, $color-action-clone-brd, $color-action-remove-brd, $color-action-void-brd, $color-action-capture-brd, $color-action-save-brd, $color-action-cancel-brd, $color-action-mail-brd !default; - -// Sizes -//-------------------------------------------------------------- -$body-font-size: 13px !default; - -$h6-size: $body-font-size + 2 !default; -$h5-size: $h6-size + 2 !default; -$h4-size: $h5-size + 2 !default; -$h3-size: $h4-size + 2 !default; -$h2-size: $h3-size + 2 !default; -$h1-size: $h2-size + 2 !default; - -$border-radius: 3px !default; -$border-input: 1px solid #2e3132; // Copied over from admin_v3 variables as a temporary solution - -$font-weight-bold: 600 !default; -$font-weight-normal: 400 !default; - -// z-index -//-------------------------------------------------------------- -$tos-banner-z-index: 102; diff --git a/app/webpacker/css/admin/plugins/flatpickr-customization.scss b/app/webpacker/css/admin/plugins/flatpickr-customization.scss deleted file mode 100644 index 79bc735d4d..0000000000 --- a/app/webpacker/css/admin/plugins/flatpickr-customization.scss +++ /dev/null @@ -1,68 +0,0 @@ -$background-grey: #eceef1; -$background-blue: $color-3; - -// scss-lint:disable SelectorFormat - -.flatpickr-calendar { - border-radius: 0; - - // Disable animation - &.animate.open { - animation: none; - } - - &.arrowBottom::after { - border-top-color: $background-grey; - } - - &.arrowTop::after { - border-bottom-color: $background-blue; - } - - .flatpickr-months .flatpickr-month { - border-radius: 0; - } - - .flatpickr-months .flatpickr-month, - .flatpickr-current-month .flatpickr-monthDropdown-months { - background: $background-blue; - } - - .flatpickr-weekdays { - background: $background-blue; - - .flatpickr-weekday { - background: $background-blue; - } - } - - .flatpickr-day.selected, - .flatpickr-day.startRange, - .flatpickr-day.endRange, - .flatpickr-day.selected.inRange, - .flatpickr-day.startRange.inRange, - .flatpickr-day.endRange.inRange, - .flatpickr-day.selected:focus, - .flatpickr-day.startRange:focus, - .flatpickr-day.endRange:focus, - .flatpickr-day.selected:hover, - .flatpickr-day.startRange:hover, - .flatpickr-day.endRange:hover, - .flatpickr-day.selected.prevMonthDay, - .flatpickr-day.startRange.prevMonthDay, - .flatpickr-day.endRange.prevMonthDay, - .flatpickr-day.selected.nextMonthDay, - .flatpickr-day.startRange.nextMonthDay, - .flatpickr-day.endRange.nextMonthDay { - background: $background-blue; - border-color: $background-blue; - } -} - -// scss-lint:enable SelectorFormat - -// customization for shortcut-buttons -.shortcut-buttons-flatpickr-wrapper > .shortcut-buttons-flatpickr-buttons { - justify-content: space-between; - flex-grow: 1; -} diff --git a/app/webpacker/css/admin/plugins/powertip.scss b/app/webpacker/css/admin/plugins/powertip.scss deleted file mode 100644 index 4afc7dfddb..0000000000 --- a/app/webpacker/css/admin/plugins/powertip.scss +++ /dev/null @@ -1,118 +0,0 @@ -#powerTip { - background-color: $color-3; - padding: 5px 15px; - @include border-radius($border-radius); - - &.n:before, - &.ne:before, - &.nw:before { - border-top-width: 5px; - border-top-color: $color-3; - bottom: -5px; - } - - &.e:before { - border-right-width: 5px; - border-right-color: $color-3; - left: -5px; - } - &.s:before, - &.se:before, - &.sw:before { - border-bottom-width: 5px; - border-bottom-color: $color-3; - top: -5px; - } - &.w:before { - border-left-width: 5px; - border-left-color: $color-3; - right: -5px; - } - &.ne:before, - &.se:before { - border-right-width: 5px; - border-right-color: $color-3; - left: -5px; - } - &.nw:before, - &.sw:before { - border-left-width: 5px; - border-right-color: $color-3; - right: -5px; - } - - &.clone, - &.yellow, - &.cancel { - background-color: $color-notice; - - &.n:before, - &.ne:before, - &.nw:before { - border-top-color: $color-notice; - } - &.e:before, - &.nw:before, - &.sw:before { - border-right-color: $color-notice; - } - &.s:before, - &.se:before, - &.sw:before { - border-bottom-color: $color-notice; - } - &.w:before { - border-left-color: $color-notice; - } - } - &.edit, - &.green, - &.capture, - &.save, - &.add { - background-color: $color-success; - - &.n:before, - &.ne:before, - &.nw:before { - border-top-color: $color-success; - } - &.e:before, - &.nw:before, - &.sw:before { - border-right-color: $color-success; - } - &.s:before, - &.se:before, - &.sw:before { - border-bottom-color: $color-success; - } - &.w:before { - border-left-color: $color-success; - } - } - &.remove, - &.red, - &.void { - background-color: $color-error; - - &.n:before, - &.ne:before, - &.nw:before { - border-top-color: $color-error; - } - &.e:before, - &.nw:before, - &.sw:before { - border-right-color: $color-error; - } - &.s:before, - &.se:before, - &.sw:before { - border-bottom-color: $color-error; - } - &.w:before { - border-left-color: $color-error; - } - } -} diff --git a/app/webpacker/css/admin/sections/orders.scss b/app/webpacker/css/admin/sections/orders.scss deleted file mode 100644 index e6e45309d3..0000000000 --- a/app/webpacker/css/admin/sections/orders.scss +++ /dev/null @@ -1,65 +0,0 @@ -// Customize orders filter -.admin-orders-index-search { - select[data-placeholder="Status"] { - width: 100%; - } - - .select2-container { - width: 100% !important; - } -} - -// Order-total -.order-details-total { - text-align: center; - - .order-total { - font-size: 35px; - font-weight: 600; - color: $color-success; - } -} - -.admin-order-form-fields { - legend.stock-location { - color: $color-body-text; - - .shipment-number { - color: $color-success; - } - .stock-location-name { - color: $color-success; - } - } -} - -.insufficient-stock-items { - legend { - color: $color-error; - } - - table tr:last-child th { - border-bottom: 1px solid $color-tbl-border; - } -} - -// Customize orduct add fieldset -#add-line-item { - fieldset { - padding: 10px 0; - - .field { - margin-bottom: 0; - - input[type="text"], - input[type="number"] { - width: 100%; - } - } - .actions { - .button { - margin-top: 28px; - } - } - } -} diff --git a/app/webpacker/css/admin/shared/forms.scss b/app/webpacker/css/admin/shared/forms.scss deleted file mode 100644 index 03bee63048..0000000000 --- a/app/webpacker/css/admin/shared/forms.scss +++ /dev/null @@ -1,261 +0,0 @@ -$text-inputs: "input[type=text], input[type=password], input[type=email], input[type=url], input[type=tel]"; - -#{$text-inputs}, -input[type="date"], -input[type="datetime"], -input[type="time"], -input[type="number"], -textarea, -fieldset { - @include border-radius($border-radius); - padding: $vpadding-txt $hpadding-txt; - border: 1px solid $color-txt-brd; - color: $color-txt-text; - font-size: 90%; - - &:focus { - outline: none; - border-color: $color-txt-hover-brd; - } - - &[disabled] { - opacity: 0.7; - } -} - -textarea { - line-height: 19px; -} - -.fullwidth { - width: 100%; -} - -label { - font-weight: 600; - text-transform: uppercase; - font-size: 85%; - display: inline; - margin-bottom: 5px; - color: $color-4; - - &.inline { - display: inline-block !important; - } - - &.block { - display: block !important; - } -} - -.label-block label { - display: block; -} - -span.info { - font-style: italic; - font-size: 85%; - color: lighten($color-body-text, 15); - display: block; - line-height: 20px; - margin: 5px 0; -} - -.field { - padding: 10px 0; - - &.checkbox { - min-height: 70px; - - input[type="checkbox"] { - display: inline-block; - width: auto; - } - - label { - cursor: pointer; - display: block; - } - } - - ul { - border-top: 1px solid $color-border; - list-style: none; - padding-top: 5px; - - li { - display: inline-block; - padding-right: 10px; - - label { - font-weight: normal; - text-transform: none; - } - &.white-space-nowrap { - white-space: nowrap; - } - } - } - - // Errors described by default form builder - .field_with_errors { - label { - color: $color-error; - } - - input { - border-color: $color-error; - } - } - // Errors described by Spree::Admin::BaseHelper - .formError { - color: $color-error; - font-style: italic; - font-size: 85%; - } -} - -fieldset { - box-shadow: none; - box-sizing: border-box; - border-color: $color-border; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - margin-left: 0; - margin-right: 0; - position: relative; - margin-bottom: 35px; - padding: 10px 0 15px 0; - background-color: transparent; - border-left: none; - border-right: none; - border-radius: 0; - - &.no-border-bottom { - border-bottom: none; - margin-bottom: 0; - } - - &.no-border-top { - border-top: none; - padding-top: 0; - } - - legend { - background-color: $color-1; - color: $color-2; - font-size: 14px; - font-weight: 600; - text-transform: uppercase; - text-align: center; - padding: 8px 15px; - margin: 0 auto; - - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - - i { - color: $color-link; - } - } - - label { - color: lighten($color-body-text, 8); - } - - .filter-actions { - margin-bottom: -32px; - margin-top: 15px; - text-align: center; - - form { - display: inline-block; - } - - button, - .button, - input[type="submit"], - input[type="button"], - span.or { - @include border-radius($border-radius); - - -webkit-box-shadow: 0 0 0 15px $color-1; - -moz-box-shadow: 0 0 0 15px $color-1; - -ms-box-shadow: 0 0 0 15px $color-1; - -o-box-shadow: 0 0 0 15px $color-1; - box-shadow: 0 0 0 15px $color-1; - - &:hover { - border-color: $color-1; - } - - &:first-of-type { - margin-right: 1.25em; - } - } - - span.or { - background-color: $color-1; - border-width: 5px; - margin-left: 5px; - margin-right: 5px; - position: relative; - - -webkit-box-shadow: 0 0 0 5px $color-1; - -moz-box-shadow: 0 0 0 5px $color-1; - -ms-box-shadow: 0 0 0 5px $color-1; - -o-box-shadow: 0 0 0 5px $color-1; - box-shadow: 0 0 0 5px $color-1; - } - } - - &.labels-inline { - .field { - margin-bottom: 0; - display: table; - width: 100%; - - label, - input { - display: table-cell !important; - } - input { - width: 100%; - } - - &.checkbox { - input { - width: auto !important; - } - } - } - .actions { - padding: 0; - text-align: right; - } - } -} - -.form-buttons { - text-align: center; -} - -select { - @extend input, [type="text"]; - background-color: white; -} - -.inline-checkbox { - display: inline-flex; - align-items: center; - margin-top: 3px; - - input, - label { - cursor: pointer; - } - label { - margin: 0; - padding-left: 0.4rem; - } -} diff --git a/app/webpacker/css/admin/shared/icons.scss b/app/webpacker/css/admin/shared/icons.scss deleted file mode 100644 index 2e581b209b..0000000000 --- a/app/webpacker/css/admin/shared/icons.scss +++ /dev/null @@ -1,25 +0,0 @@ -// Some fixes for fontwesome stylesheets -[class^="icon-"], [class*=" icon-"] { - &:before { - padding-right: 5px; - } - - &.button, &.icon_link { - width: auto; - - &:before { - padding-top: 3px; - } - } -} - -.icon-email:before { @extend .icon-envelope, :before; } -.icon-resend_authorization_email:before { @extend .icon-envelope, :before; } -.icon-resume:before { @extend .icon-refresh, :before; } - -.icon-cancel:before, -.icon-void:before { @extend .icon-remove, :before; } - -.icon-capture, -.icon-capture_and_complete_order { @extend .icon-ok } -.icon-credit:before { @extend .icon-ok, :before ; } diff --git a/app/webpacker/css/admin/shared/layout.scss b/app/webpacker/css/admin/shared/layout.scss deleted file mode 100644 index 35d4c2613d..0000000000 --- a/app/webpacker/css/admin/shared/layout.scss +++ /dev/null @@ -1,134 +0,0 @@ -// Basics -//--------------------------------------------------- -* { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} - -.admin { - &__section-header { - padding: 15px 0; - background-color: very-light($color-3, 4); - border-bottom: 1px solid $color-border; - - .ofn-drop-down { - border: 0; - background-color: $spree-blue; - color: $color-1; - float: none; - margin-left: 3px; - &:hover, - &.expanded { - border: 0; - color: $color-1; - } - } - - &__content { - display: flex; - align-items: center; - justify-content: space-between; - flex-wrap: wrap; - @media all and (min-width: $tablet_breakpoint) { - flex-wrap: nowrap; - } - } - - &__title { - width: 100%; - margin-bottom: 10px; - @media all and (min-width: $tablet_breakpoint) { - margin-bottom: 0; - } - } - - &__actions { - display: flex; - flex: 1 0 auto; - align-items: center; - list-style: none; - @media all and (min-width: $tablet_breakpoint) { - justify-content: flex-end; - } - > li { - display: flex; - margin-right: 10px; - &:empty { - display: none; - } - &:last-child { - margin-right: 0; - } - } - } - } -} - -.hidden { - display: none; -} - -.float-right { - float: right; -} - -.float-left { - float: left; -} - -.mr-0 { - margin-right: 0 !important; -} - -.ml-0 { - margin-left: 0 !important; -} - -@media print { - .print-hidden { - display: none !important; - } -} - -// Header -//--------------------------------------------------- -#header { - background-color: $color-1; - padding: 5px 0; -} - -#logo { - height: 40px; -} - -.page-title { - i { - color: $color-2; - } -} - -// Content -//--------------------------------------------------- -#content { - background-color: $color-1; - position: relative; - z-index: 0; - padding: 0; - margin-top: 15px; -} - -// Footer -//--------------------------------------------------- -#footer { - margin-top: 15px; - border-top: 1px solid $color-border; - padding: 10px 0; -} - -@media print { - header, - nav { - display: none; - } -} diff --git a/app/webpacker/css/admin/shared/tables.scss b/app/webpacker/css/admin/shared/tables.scss deleted file mode 100644 index a518f223b1..0000000000 --- a/app/webpacker/css/admin/shared/tables.scss +++ /dev/null @@ -1,208 +0,0 @@ -table { - width: 100%; - margin-bottom: 15px; - border-collapse: separate; - - th, td { - padding: 7px 5px; - border-right: 1px solid $color-border; - border-bottom: 1px solid $color-border; - vertical-align: middle; - text-overflow: ellipsis; - - img { - border: 1px solid transparent; - } - - &:first-child { - border-left: 1px solid $color-border; - } - - a { - border-bottom: 1px dotted lighten($color-link, 10); - - &:hover { - border-color: lighten($color-link-hover, 10); - } - } - - .handle { - display: block !important; - text-align: center; - padding-right: 0; - } - - &.actions { - background-color: transparent; - border: none !important; - text-align: center; - - span.text { - font-size: $body-font-size; - } - - [class*='icon-'].no-text { - font-size: 120%; - background-color: very-light($color-3); - border: 1px solid $color-border; - border-radius: 15px; - width: 29px; - height: 29px; - display: inline-block; - padding-top: 2px; - - &:before { - text-align: center !important; - width: 27px; - display: inline-block; - } - - &:hover { - border-color: transparent; - } - } - - button[class*='icon-'] { - color: $color-link; - padding: 0 !important; - } - - .icon-envelope-alt, .icon-eye-open { - color: $color-link; - padding-left: 0px; - - &:hover { - background-color: $color-3; - color: $color-1; - } - } - .icon-trash:hover, .icon-void:hover { - background-color: $color-error; - color: $color-1; - } - .icon-cancel:hover { - background-color: $color-notice; - color: $color-1; - } - .icon-edit:hover, .icon-capture:hover, .icon-capture_and_complete_order:hover, .icon-ok:hover, .icon-plus:hover, .icon-road:hover { - background-color: $color-success; - color: $color-1; - } - .icon-copy:hover { - background-color: $color-notice; - color: $color-1; - } - } - - input[type="number"], - input[type="text"] { - width: 100%; - } - - &.no-border { - border-right: none; - } - - .handle { - @extend .icon-reorder; - font-family: FontAwesome; - text-decoration: inherit; - display: inline-block; - speak: none; - cursor: move; - } - - } - - &.no-borders { - td, th { - border: none !important; - } - - } - - thead { - th { - padding: 10px; - border-top: 1px solid $color-border; - border-bottom: none; - background-color: $color-tbl-thead; - text-transform: uppercase; - font-size: 85%; - font-weight: $font-weight-bold; - } - } - - tbody { - tr { - &:first-child th, - &:first-child td { - border-top: 1px solid $color-border; - } - &.even td { - background-color: $color-tbl-even; - - img { - border: 1px solid very-light($color-3, 6); - } - } - - &:hover td { - background-color: very-light($color-3, 5); - - img { - border: 1px solid $color-border; - } - } - - &.deleted td { - background-color: very-light($color-error, 6); - border-color: very-light($color-error, 15); - } - - &.ui-sortable-placeholder td { - border: 1px solid $color-2 !important; - visibility: visible !important; - - &.actions { - background-color: transparent; - border-right: none !important; - border-top: none !important; - border-bottom: none !important; - border-left: 1px solid $color-2 !important; - } - } - - &.ui-sortable-helper { - width: 100%; - - td { - background-color: lighten($color-3, 33); - border-bottom: 1px solid $color-border; - - &.actions { - display: none; - } - } - } - } - - &.no-border-top tr:first-child td { - border-top: none; - } - - &.grand-total { - td { - border-color: $color-2 !important; - text-transform: uppercase; - font-size: 110%; - font-weight: 600; - background-color: lighten($color-2, 50); - } - .total { - background-color: $color-2; - color: $color-1; - } - } - } -} diff --git a/app/webpacker/css/admin/shared/typography.scss b/app/webpacker/css/admin/shared/typography.scss deleted file mode 100644 index 4435933458..0000000000 --- a/app/webpacker/css/admin/shared/typography.scss +++ /dev/null @@ -1,158 +0,0 @@ -// Base -//-------------------------------------------------------------- -body, div, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, pre, form, p, blockquote, th, td { margin: 0; padding: 0; font-size: $body-font-size; } - -body { - font-family: $base-font-family; - font-size: $body-font-size; - font-weight: 400; - color: $color-body-text; - text-rendering: optimizeLegibility; -} - -hr { - border-top: 1px solid $color-border; - border-bottom: 1px solid white; - border-left: none; -} - -strong, b { - font-weight: 600; -} - -// links -//-------------------------------------------------------------- -a { - color: $color-link; - text-decoration: none; - line-height: inherit; - - &, &:hover, &:active, &:visited, &:focus { - outline: none; - } - - &:visited { - color: $color-link-visited; - } - &:focus { - color: $color-link-focus; - } - &:active { - color: $color-link-active; - } - &:hover { - color: $color-link-hover; - } -} - -// Headings -//-------------------------------------------------------------- - -h1,h2,h3,h4,h5,h6 { - font-weight: 600; - color: $color-headers; - line-height: 1.1; -} - -h1 { font-size: $h1-size; line-height: $h1-size + 6 } -h2 { font-size: $h2-size; line-height: $h1-size + 4 } -h3 { font-size: $h3-size; line-height: $h1-size + 2 } -h4 { font-size: $h4-size; line-height: $h1-size } -h5 { font-size: $h5-size; line-height: $h1-size } -h6 { font-size: $h6-size; line-height: $h1-size } - - -// Lists -//-------------------------------------------------------------- -ul { - &.inline-menu { - li { - display: inline-block; - } - } - &.fields { - list-style: none; - padding: 0; - margin: 0; - } -} - -dl { - width: 100%; - overflow: hidden; - margin: 5px 0; - color: lighten($color-body-text, 15); - - dt, dd { - float: left; - line-height: 16px; - padding: 5px; - text-align: justify; - } - - dt { - width: 40%; - font-weight: 600; - padding-left: 0; - text-transform: uppercase; - font-size: 85%; - } - - dd { - width: 60%; - padding-right: 0; - } - - dd:after { - content: ''; - clear: both; - } - -} - -// Helpers -.align-center { text-align: center } -.align-right { text-align: right } -.align-left { text-align: left } -.align-justify { text-align: justify } - -.uppercase { text-transform: uppercase } - -.green { color: $color-2 } -.blue { color: $color-3 } -.red { color: $color-5 } -.yellow { color: $color-6 } - -.no-objects-found { - text-align: center; - font-size: 120%; - text-transform: uppercase; - padding: 40px 0px; - color: lighten($color-body-text, 15); -} - -.text-normal { - font-size: 1rem; - font-weight: 300; -} - -.text-big { - font-size: 1.2rem; - font-weight: 300; -} - -.text-red { - color: $color-warning; -} - -input.text-big { - font-size: 1.1rem; -} - -.pad-top { - padding-top: 1em; -} - -.white-space-nowrap { - white-space: nowrap; -} diff --git a/app/webpacker/css/admin/terms_of_service_banner.scss b/app/webpacker/css/admin/terms_of_service_banner.scss deleted file mode 100644 index ca23f9b763..0000000000 --- a/app/webpacker/css/admin/terms_of_service_banner.scss +++ /dev/null @@ -1,27 +0,0 @@ -#banner-container { - position: fixed; - bottom: 0px; - left: 0; - width: 100%; - z-index: $tos-banner-z-index; - - .terms-of-service-banner { - padding: 18px; - text-align: center; - font-size: 120%; - color: white; - font-weight: 600; - margin-top: 0; - background-color: rgba($color-notice, 0.8); - display: flex; - - .column-left { - width: 70%; - } - - .column-right { - width: 30%; - text-align: center; - } - } -} diff --git a/app/webpacker/packs/admin-styles.scss b/app/webpacker/packs/admin-styles.scss deleted file mode 100644 index 32d33f37b6..0000000000 --- a/app/webpacker/packs/admin-styles.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import "../css/admin/all.scss"; -@import "../../../node_modules/trix/dist/trix.css"; From 1717c5376bcad4bde23e42d1d139326ce1418107 Mon Sep 17 00:00:00 2001 From: Ahmed Ejaz Date: Sun, 27 Jul 2025 07:11:07 +0500 Subject: [PATCH 09/76] Remove deprecated migrations for admin style v3 activation --- ...240625024328_activate_admin_style_v3_for_new_users.rb | 5 ----- ...710013128_enable_feature_admin_style_v3_for_admins.rb | 5 ----- .../20240715103415_enable_admin_style_v3_by_default.rb | 7 ------- ...150852_activate_admin_style_v3_for_25_p_cent_users.rb | 5 ----- ...40731065321_activate_admin_style_v3_for50_pc_users.rb | 5 ----- ...40810150912_activate_admin_style_v3_for75_pc_users.rb | 9 --------- .../20240828203544_fully_enable_admin_style_v3_flag.rb | 5 ----- 7 files changed, 41 deletions(-) delete mode 100644 db/migrate/20240625024328_activate_admin_style_v3_for_new_users.rb delete mode 100644 db/migrate/20240710013128_enable_feature_admin_style_v3_for_admins.rb delete mode 100644 db/migrate/20240715103415_enable_admin_style_v3_by_default.rb delete mode 100644 db/migrate/20240718150852_activate_admin_style_v3_for_25_p_cent_users.rb delete mode 100644 db/migrate/20240731065321_activate_admin_style_v3_for50_pc_users.rb delete mode 100644 db/migrate/20240810150912_activate_admin_style_v3_for75_pc_users.rb delete mode 100644 db/migrate/20240828203544_fully_enable_admin_style_v3_flag.rb diff --git a/db/migrate/20240625024328_activate_admin_style_v3_for_new_users.rb b/db/migrate/20240625024328_activate_admin_style_v3_for_new_users.rb deleted file mode 100644 index 55fb41febd..0000000000 --- a/db/migrate/20240625024328_activate_admin_style_v3_for_new_users.rb +++ /dev/null @@ -1,5 +0,0 @@ -class ActivateAdminStyleV3ForNewUsers < ActiveRecord::Migration[7.0] - def up - Flipper.enable_group(:admin_style_v3, :new_2024_07_03) - end -end diff --git a/db/migrate/20240710013128_enable_feature_admin_style_v3_for_admins.rb b/db/migrate/20240710013128_enable_feature_admin_style_v3_for_admins.rb deleted file mode 100644 index 9d04164719..0000000000 --- a/db/migrate/20240710013128_enable_feature_admin_style_v3_for_admins.rb +++ /dev/null @@ -1,5 +0,0 @@ -class EnableFeatureAdminStyleV3ForAdmins < ActiveRecord::Migration[7.0] - def up - Flipper.enable_group(:admin_style_v3, :admins) - end -end diff --git a/db/migrate/20240715103415_enable_admin_style_v3_by_default.rb b/db/migrate/20240715103415_enable_admin_style_v3_by_default.rb deleted file mode 100644 index 29099bcb0f..0000000000 --- a/db/migrate/20240715103415_enable_admin_style_v3_by_default.rb +++ /dev/null @@ -1,7 +0,0 @@ -class EnableAdminStyleV3ByDefault < ActiveRecord::Migration[7.0] - def up - if Rails.env.development? - Flipper.enable(:admin_style_v3) - end - end -end diff --git a/db/migrate/20240718150852_activate_admin_style_v3_for_25_p_cent_users.rb b/db/migrate/20240718150852_activate_admin_style_v3_for_25_p_cent_users.rb deleted file mode 100644 index 066a18745f..0000000000 --- a/db/migrate/20240718150852_activate_admin_style_v3_for_25_p_cent_users.rb +++ /dev/null @@ -1,5 +0,0 @@ -class ActivateAdminStyleV3For25PCentUsers < ActiveRecord::Migration[7.0] - def up - Flipper.enable_percentage_of_actors(:admin_style_v3, 25) - end -end diff --git a/db/migrate/20240731065321_activate_admin_style_v3_for50_pc_users.rb b/db/migrate/20240731065321_activate_admin_style_v3_for50_pc_users.rb deleted file mode 100644 index 0cde1950fb..0000000000 --- a/db/migrate/20240731065321_activate_admin_style_v3_for50_pc_users.rb +++ /dev/null @@ -1,5 +0,0 @@ -class ActivateAdminStyleV3For50PcUsers < ActiveRecord::Migration[7.0] - def up - Flipper.enable_percentage_of_actors(:admin_style_v3, 50) - end -end diff --git a/db/migrate/20240810150912_activate_admin_style_v3_for75_pc_users.rb b/db/migrate/20240810150912_activate_admin_style_v3_for75_pc_users.rb deleted file mode 100644 index cc412c0b58..0000000000 --- a/db/migrate/20240810150912_activate_admin_style_v3_for75_pc_users.rb +++ /dev/null @@ -1,9 +0,0 @@ -class ActivateAdminStyleV3For75PcUsers < ActiveRecord::Migration[7.0] - def up - Flipper.enable_percentage_of_actors(:admin_style_v3, 75) - end - - def down - Flipper.enable_percentage_of_actors(:admin_style_v3, 50) - end -end diff --git a/db/migrate/20240828203544_fully_enable_admin_style_v3_flag.rb b/db/migrate/20240828203544_fully_enable_admin_style_v3_flag.rb deleted file mode 100644 index db2fd6007f..0000000000 --- a/db/migrate/20240828203544_fully_enable_admin_style_v3_flag.rb +++ /dev/null @@ -1,5 +0,0 @@ -class FullyEnableAdminStyleV3Flag < ActiveRecord::Migration[7.0] - def up - Flipper.enable(:admin_style_v3) - end -end From acfe3f658986ba3df3b4b8a742ea2fa7357169a7 Mon Sep 17 00:00:00 2001 From: Ahmed Ejaz Date: Sun, 27 Jul 2025 07:20:16 +0500 Subject: [PATCH 10/76] Remove admin_style_v3 feature toggle and related conditional logic --- lib/open_food_network/feature_toggle.rb | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/lib/open_food_network/feature_toggle.rb b/lib/open_food_network/feature_toggle.rb index 604f949084..8e9932f169 100644 --- a/lib/open_food_network/feature_toggle.rb +++ b/lib/open_food_network/feature_toggle.rb @@ -8,16 +8,10 @@ module OpenFoodNetwork # module FeatureToggle def self.conditional_features - features = {} - if Rails.env.development? - features.merge!({ - "admin_style_v3" => <<~DESC, - Test the work-in-progress design updates. - DESC - }); - end + # Returns environment-specific features that are conditionally available + # Currently empty but can be used to add features based on environment - features + {} end # Please add your new feature here to appear in the Flipper UI. @@ -73,9 +67,6 @@ module OpenFoodNetwork ACTIVE_BY_DEFAULT = { # Copy features here that were activated in a migration so that new # instances, development and test environments have the feature active. - "admin_style_v3" => <<~DESC, - Test the work-in-progress design updates. - DESC }.freeze def self.setup! @@ -94,9 +85,6 @@ module OpenFoodNetwork # Checks weather a feature is enabled for any of the given actors. def self.enabled?(feature_name, *actors) - # TODO: Need to remove these checks when we fully remove the toggle from development as well - # need this check as Flipper won't recognize 'admin_style_v3' as it is removed for server envs - return true if !Rails.env.development? && feature_name == :admin_style_v3 return Flipper.enabled?(feature_name) if actors.empty? actors.any? do |actor| From 3f39d94bd30cf94d9942279fb520d77b67d49559 Mon Sep 17 00:00:00 2001 From: Ahmed Ejaz Date: Sun, 27 Jul 2025 07:21:10 +0500 Subject: [PATCH 11/76] Remove conditional rendering for previous page icon in pagination --- app/views/admin/shared/_stimulus_pagination.html.haml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/views/admin/shared/_stimulus_pagination.html.haml b/app/views/admin/shared/_stimulus_pagination.html.haml index 23860ead4e..632b766653 100644 --- a/app/views/admin/shared/_stimulus_pagination.html.haml +++ b/app/views/admin/shared/_stimulus_pagination.html.haml @@ -3,10 +3,7 @@ .pagination{ "data-controller": "search" } - if pagy.prev %button.page.prev{ data: { action: 'click->search#changePage', page: pagy.prev } } - - if feature?(:admin_style_v3, spree_current_user) - %i.icon-chevron-left{ data: { action: 'click->search#changePage', page: pagy.prev } } - - else - != pagy_t('pagy.prev') + %i.icon-chevron-left{ data: { action: 'click->search#changePage', page: pagy.prev } } - else %button.page.disabled{disabled: "disabled"}!= pagy_t('pagy.prev') From df4cf4b76809fad0a7aa1f463578b22b44e4158d Mon Sep 17 00:00:00 2001 From: Ahmed Ejaz Date: Sun, 27 Jul 2025 07:58:04 +0500 Subject: [PATCH 12/76] Fix specs --- .../spree/admin/variants_controller.rb | 17 ++ config/routes/spree.rb | 2 +- .../api/v0/products_controller_spec.rb | 176 ------------------ .../spree/admin/products_controller_spec.rb | 124 ------------ 4 files changed, 18 insertions(+), 301 deletions(-) diff --git a/app/controllers/spree/admin/variants_controller.rb b/app/controllers/spree/admin/variants_controller.rb index 83fe131045..7ec309ea93 100644 --- a/app/controllers/spree/admin/variants_controller.rb +++ b/app/controllers/spree/admin/variants_controller.rb @@ -72,8 +72,25 @@ module Spree render json: @variants, each_serializer: ::Api::Admin::VariantSerializer end + def destroy + @url_filters = ::ProductFilters.new.extract(request.query_parameters) + + @variant = Spree::Variant.find(params[:id]) + flash[:success] = delete_variant + + redirect_to spree.admin_product_variants_url(params[:product_id], @url_filters) + end + protected + def delete_variant + if VariantDeleter.new.delete(@variant) + Spree.t('notice_messages.variant_deleted') + else + Spree.t('notice_messages.variant_not_deleted') + end + end + def create_before @object.save end diff --git a/config/routes/spree.rb b/config/routes/spree.rb index a133ff7418..512f708b14 100644 --- a/config/routes/spree.rb +++ b/config/routes/spree.rb @@ -68,7 +68,7 @@ Spree::Core::Engine.routes.draw do end end - resources :variants, except: :destroy do + resources :variants do collection do post :update_positions end diff --git a/spec/controllers/api/v0/products_controller_spec.rb b/spec/controllers/api/v0/products_controller_spec.rb index 8150cf9332..09dab8cc70 100644 --- a/spec/controllers/api/v0/products_controller_spec.rb +++ b/spec/controllers/api/v0/products_controller_spec.rb @@ -62,33 +62,6 @@ RSpec.describe Api::V0::ProductsController do api_put :update, id: product.to_param, product: { name: "I hacked your store!" } assert_unauthorized! end - - it "cannot delete a product" do - api_delete :destroy, id: product.to_param - assert_unauthorized! - end - end - - context "as an enterprise user" do - let(:current_api_user) { supplier_enterprise_user(supplier) } - - it "can delete my product" do - expect(product.deleted_at).to be_nil - api_delete :destroy, id: product.to_param - - expect(response).to have_http_status(:no_content) - expect { product.reload }.not_to raise_error - expect(product.deleted_at).not_to be_nil - end - - it "is denied access to deleting another enterprises' product" do - expect(product_other_supplier.deleted_at).to be_nil - api_delete :destroy, id: product_other_supplier.to_param - - assert_unauthorized! - expect { product_other_supplier.reload }.not_to raise_error - expect(product_other_supplier.deleted_at).to be_nil - end end context "as an administrator" do @@ -137,155 +110,6 @@ RSpec.describe Api::V0::ProductsController do expect(json_response["error"]).to eq("Invalid resource. Please fix errors and try again.") expect(json_response["errors"]["name"]).to eq(["can't be blank"]) end - - it "can delete a product" do - expect(product.deleted_at).to be_nil - api_delete :destroy, id: product.to_param - - expect(response).to have_http_status(:no_content) - expect(product.reload.deleted_at).not_to be_nil - end - end - - describe '#clone' do - context 'as a normal user' do - before do - allow(current_api_user) - .to receive(:admin?).and_return(false) - end - - it 'denies access' do - spree_post :clone, product_id: product.id, format: :json - - assert_unauthorized! - end - end - - context 'as an enterprise user' do - let(:current_api_user) { supplier_enterprise_user(supplier) } - let!(:variant) { create(:variant, product_id: product.id) } - - it 'responds with a successful response' do - spree_post :clone, product_id: product.id, format: :json - - expect(response).to have_http_status(:created) - end - - it 'clones the product' do - spree_post :clone, product_id: product.id, format: :json - - expect(json_response['name']).to eq("COPY OF #{product.name}") - end - - it 'clones a product with image' do - spree_post :clone, product_id: product_with_image.id, format: :json - - expect(response).to have_http_status(:created) - expect(json_response['name']).to eq("COPY OF #{product_with_image.name}") - end - - # test cases related to bug #660: product duplication clones master variant - - # stock info - clone is set to zero - it '(does not) clone the stock info of the product' do - spree_post :clone, product_id: product.id, format: :json - expect(json_response.dig("variants", 0, "on_hand")).to eq(0) - end - - # variants: only the master variant of the product is cloned - it '(does not) clone variants from a product with several variants' do - spree_post :clone, product_id: product.id, format: :json - expect(Spree::Product.second.variants.count).not_to eq Spree::Product.first.variants.count - end - end - - context 'as an administrator' do - before do - allow(current_api_user) - .to receive(:admin?).and_return(true) - end - - it 'responds with a successful response' do - spree_post :clone, product_id: product.id, format: :json - - expect(response).to have_http_status(:created) - end - - it 'clones the product' do - spree_post :clone, product_id: product.id, format: :json - - expect(json_response['name']).to eq("COPY OF #{product.name}") - end - - it 'clones a product with image' do - spree_post :clone, product_id: product_with_image.id, format: :json - - expect(response).to have_http_status(:created) - expect(json_response['name']).to eq("COPY OF #{product_with_image.name}") - end - end - end - - describe '#bulk_products' do - context "as an enterprise user" do - let!(:taxon) { create(:taxon) } - let!(:product2) { create(:product, supplier_id: supplier.id, primary_taxon: taxon) } - let!(:product3) { create(:product, supplier_id: supplier2.id, primary_taxon: taxon) } - let!(:product4) { create(:product, supplier_id: supplier2.id) } - let(:current_api_user) { supplier_enterprise_user(supplier) } - - before { current_api_user.enterprise_roles.create(enterprise: supplier2) } - - it "returns a list of products" do - api_get :bulk_products, { page: 1, per_page: 15 }, format: :json - expect(returned_product_ids).to eq [product4.id, product3.id, product2.id, - other_product.id, product.id] - end - - it "returns pagination data" do - api_get :bulk_products, { page: 1, per_page: 15 }, format: :json - expect(json_response['pagination']).to eq "results" => 5, "pages" => 1, "page" => 1, - "per_page" => 15 - end - - it "uses defaults when page and per_page are not supplied" do - api_get :bulk_products, format: :json - expect(json_response['pagination']).to eq "results" => 5, "pages" => 1, "page" => 1, - "per_page" => 15 - end - - it "returns paginated products by page" do - api_get :bulk_products, { page: 1, per_page: 2 }, format: :json - expect(returned_product_ids).to eq [product4.id, product3.id] - - api_get :bulk_products, { page: 2, per_page: 2 }, format: :json - expect(returned_product_ids).to eq [product2.id, other_product.id] - end - - it "filters results by supplier" do - api_get :bulk_products, - { page: 1, per_page: 15, q: { variants_supplier_id_eq: supplier.id } }, - format: :json - expect(returned_product_ids).to eq [product2.id, other_product.id, product.id] - end - - it "filters results by product category" do - api_get :bulk_products, - { page: 1, per_page: 15, q: { variants_primary_taxon_id_eq: taxon.id } }, - format: :json - expect(returned_product_ids).to eq [product3.id, product2.id] - end - - it "filters results by import_date" do - product.variants.first.update_attribute :import_date, 1.day.ago - product2.variants.first.update_attribute :import_date, 2.days.ago - product3.variants.first.update_attribute :import_date, 1.day.ago - - api_get :bulk_products, { page: 1, per_page: 15, import_date: 1.day.ago.to_date.to_s }, - format: :json - expect(returned_product_ids).to eq [product3.id, product.id] - end - end end private diff --git a/spec/controllers/spree/admin/products_controller_spec.rb b/spec/controllers/spree/admin/products_controller_spec.rb index af89e197fc..f57392aa81 100644 --- a/spec/controllers/spree/admin/products_controller_spec.rb +++ b/spec/controllers/spree/admin/products_controller_spec.rb @@ -3,130 +3,6 @@ require 'spec_helper' RSpec.describe Spree::Admin::ProductsController do - describe 'bulk_update' do - context "updating a product we do not have access to" do - let(:s_managed) { create(:enterprise) } - let(:s_unmanaged) { create(:enterprise) } - let(:product) do - create(:simple_product, supplier_id: s_unmanaged.id, name: 'Peas') - end - - before do - controller_login_as_enterprise_user [s_managed] - spree_post :bulk_update, - "products" => [{ "id" => product.id, "name" => "Pine nuts" }] - end - - it "denies access" do - expect(response).to redirect_to unauthorized_path - end - - it "does not update any product" do - expect(product.reload.name).not_to eq("Pine nuts") - end - end - - context "when changing a product's variant_unit" do - let(:producer) { create(:enterprise) } - let!(:product) do - create( - :simple_product, - supplier_id: producer.id, - variant_unit: 'items', - variant_unit_scale: nil, - variant_unit_name: 'bunches', - unit_value: nil, - unit_description: 'some description' - ) - end - - before { controller_login_as_enterprise_user([producer]) } - - it 'succeeds' do - spree_post :bulk_update, - "products" => [ - { - "id" => product.id, - "variant_unit" => "weight", - "variant_unit_scale" => 1 - } - ] - - expect(response).to have_http_status(:found) - end - - it 'does not redirect to bulk_products' do - spree_post :bulk_update, - "products" => [ - { - "id" => product.id, - "variant_unit" => "weight", - "variant_unit_scale" => 1 - } - ] - - expect(response).to redirect_to( - '/api/v0/products/bulk_products' - ) - end - end - - context 'when passing empty variants_attributes' do - let(:producer) { create(:enterprise) } - let!(:product) do - create( - :simple_product, - supplier_id: producer.id, - variant_unit: 'items', - variant_unit_scale: nil, - variant_unit_name: 'bunches', - unit_value: nil, - unit_description: 'bunches' - ) - end - let!(:another_product) do - create( - :simple_product, - supplier_id: producer.id, - variant_unit: 'weight', - variant_unit_scale: 1000, - variant_unit_name: nil - ) - end - let!(:taxon) { create(:taxon) } - - before { controller_login_as_enterprise_user([producer]) } - - it 'does not fail' do - spree_post :bulk_update, - "products" => [ - { - "id" => another_product.id, - "variants_attributes" => [{}] - }, - { - "id" => product.id, - "variants_attributes" => [ - { - "on_hand" => 2, - "price" => "5.0", - "unit_value" => 4, - "variant_unit" => "weight", - "variant_unit_scale" => "1", - "unit_description" => "", - "display_name" => "name", - "primary_taxon_id" => taxon.id, - "supplier_id" => producer.id - } - ] - } - ] - - expect(response).to have_http_status(:found) - end - end - end - context "creating a new product" do let(:supplier) { create(:supplier_enterprise) } let(:taxon) { create(:taxon) } From f3f43225cb22fefcb80059af4987a9bb5c62a19d Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Tue, 29 Jul 2025 12:13:37 +1000 Subject: [PATCH 13/76] Remove unused test helper --- spec/base_spec_helper.rb | 1 - spec/support/request/distribution_helper.rb | 15 --------------- 2 files changed, 16 deletions(-) delete mode 100644 spec/support/request/distribution_helper.rb diff --git a/spec/base_spec_helper.rb b/spec/base_spec_helper.rb index 7f7ef01a12..adf32656f2 100644 --- a/spec/base_spec_helper.rb +++ b/spec/base_spec_helper.rb @@ -259,7 +259,6 @@ RSpec.configure do |config| config.include PreferencesHelper config.include OpenFoodNetwork::FiltersHelper config.include OpenFoodNetwork::EnterpriseGroupsHelper - config.include OpenFoodNetwork::DistributionHelper config.include OpenFoodNetwork::HtmlHelper config.include ActionView::Helpers::DateHelper config.include OpenFoodNetwork::PerformanceHelper diff --git a/spec/support/request/distribution_helper.rb b/spec/support/request/distribution_helper.rb deleted file mode 100644 index d0b00ec18b..0000000000 --- a/spec/support/request/distribution_helper.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -module OpenFoodNetwork - module DistributionHelper - def select_distribution(distributor, order_cycle = nil) - create_enterprise_group_for distributor - visit root_path - click_link distributor.name - - return unless order_cycle && page.has_select?('order_order_cycle_id') - - select_by_value order_cycle.id, from: 'order_order_cycle_id' - end - end -end From fe8b805e1f44fd8cf9d651cade4547c03a715070 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Fri, 4 Jul 2025 12:45:45 +1000 Subject: [PATCH 14/76] Add gem puffing-billy --- Gemfile | 1 + Gemfile.lock | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/Gemfile b/Gemfile index 342f6ec61e..a80dab5fe4 100644 --- a/Gemfile +++ b/Gemfile @@ -171,6 +171,7 @@ end group :test do gem 'pdf-reader' + gem 'puffing-billy' gem 'rails-controller-testing' gem 'simplecov', require: false gem 'simplecov-lcov', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 321fcbd450..b42743cb50 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -230,6 +230,7 @@ GEM ruby-rc4 (>= 0.1.5) concurrent-ruby (1.3.1) connection_pool (2.4.1) + cookiejar (0.3.4) crack (1.0.0) bigdecimal rexml @@ -269,11 +270,24 @@ GEM digest (3.1.1) docile (1.4.0) dotenv (3.1.2) + em-http-request (1.1.7) + addressable (>= 2.3.4) + cookiejar (!= 0.3.1) + em-socksify (>= 0.3) + eventmachine (>= 1.0.3) + http_parser.rb (>= 0.6.0) + em-socksify (0.3.3) + base64 + eventmachine (>= 1.0.0.beta.4) + em-synchrony (1.0.6) + eventmachine (>= 1.0.0.beta.1) email_validator (2.2.4) activemodel erubi (1.12.0) et-orbi (1.2.7) tzinfo + eventmachine (1.2.7) + eventmachine_httpserver (0.2.1) excon (0.81.0) execjs (2.7.0) factory_bot (6.2.0) @@ -347,6 +361,7 @@ GEM hashie (5.0.0) highline (2.0.3) htmlentities (4.3.4) + http_parser.rb (0.8.0) i18n (1.14.5) concurrent-ruby (~> 1.0) i18n-js (3.9.2) @@ -527,6 +542,14 @@ GEM psych (5.1.2) stringio public_suffix (5.0.5) + puffing-billy (4.0.2) + addressable (~> 2.5) + em-http-request (~> 1.1, >= 1.1.0) + em-synchrony + eventmachine (~> 1.2) + eventmachine_httpserver + http_parser.rb (~> 0.8.0) + multi_json puma (6.5.0) nio4r (~> 2.0) query_count (1.1.1) @@ -961,6 +984,7 @@ DEPENDENCIES pg (~> 1.2.3) private_address_check pry (~> 0.13.0) + puffing-billy puma query_count rack-mini-profiler (< 3.0.0) From a062a7b69712a8c28aceca4fc4b5cdd331ee2c89 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Fri, 4 Jul 2025 12:56:08 +1000 Subject: [PATCH 15/76] Add Billy proxy to Chrome in system specs And demonstrate the use of puffing-billy browser proxy. Billy can cache and record responses to browser requests. For that to work we need to allow network connections and disable VCR. But instead I found that the Billy proxy is just like any other Ruby backend code and its connections can be recorded with VCR instead. And instead of stubbing requests via Billy.proxy, we can use standard Webmock `stub_request`. Now we use puffing-billy just to relay browser requests via our Ruby app. --- .../handles_HTTPS.yml | 42 + .../loads_a_website.yml | 948 ++++++++++++++++++ spec/system/billy_spec.rb | 21 + spec/system/support/cuprite_setup.rb | 14 +- 4 files changed, 1022 insertions(+), 3 deletions(-) create mode 100644 spec/fixtures/vcr_cassettes/Testing_external_scripts_loaded_in_the_browser/handles_HTTPS.yml create mode 100644 spec/fixtures/vcr_cassettes/Testing_external_scripts_loaded_in_the_browser/loads_a_website.yml create mode 100644 spec/system/billy_spec.rb diff --git a/spec/fixtures/vcr_cassettes/Testing_external_scripts_loaded_in_the_browser/handles_HTTPS.yml b/spec/fixtures/vcr_cassettes/Testing_external_scripts_loaded_in_the_browser/handles_HTTPS.yml new file mode 100644 index 0000000000..2c1127632c --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Testing_external_scripts_loaded_in_the_browser/handles_HTTPS.yml @@ -0,0 +1,42 @@ +--- +http_interactions: +- request: + method: get + uri: http://www.gstatic.com/generate_204 + body: + encoding: UTF-8 + string: '' + headers: + Proxy-Connection: + - keep-alive + Pragma: + - no-cache + Cache-Control: + - no-cache + User-Agent: + - Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/138.0.0.0 + Safari/537.36 + Accept-Encoding: + - '' + Accept-Language: + - en-US,en;q=0.9 + Connection: + - close + response: + status: + code: 204 + message: No Content + headers: + Content-Length: + - '0' + Cross-Origin-Resource-Policy: + - cross-origin + Date: + - Fri, 11 Jul 2025 05:04:02 GMT + Connection: + - close + body: + encoding: UTF-8 + string: '' + recorded_at: Fri, 11 Jul 2025 05:04:02 GMT +recorded_with: VCR 6.2.0 diff --git a/spec/fixtures/vcr_cassettes/Testing_external_scripts_loaded_in_the_browser/loads_a_website.yml b/spec/fixtures/vcr_cassettes/Testing_external_scripts_loaded_in_the_browser/loads_a_website.yml new file mode 100644 index 0000000000..9299ce7d1a --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Testing_external_scripts_loaded_in_the_browser/loads_a_website.yml @@ -0,0 +1,948 @@ +--- +http_interactions: +- request: + method: get + uri: http://clients2.google.com/time/1/current?cup2hreq=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&cup2key=9:s5jDVMfcuUIhrqbHhePH_nBoAC1CjxTn99-PT031EBI + body: + encoding: UTF-8 + string: '' + headers: + Proxy-Connection: + - keep-alive + Pragma: + - no-cache + Cache-Control: + - no-cache + User-Agent: + - Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/138.0.0.0 + Safari/537.36 + Accept-Encoding: + - '' + Connection: + - close + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=utf-8 + X-Content-Type-Options: + - nosniff + X-Cup-Server-Proof: + - 304402203592554ffcc4bc9ea59f39b1ac3f09be988e1987742afef742d1b40a6fe1cc56022057ef8ace9edc80e72939c136e2b29069e75cc3886817ddbae3b7a9a1fb025c98:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 + Cache-Control: + - no-cache, no-store, max-age=0, must-revalidate + Pragma: + - no-cache + Expires: + - Mon, 01 Jan 1990 00:00:00 GMT + Date: + - Fri, 11 Jul 2025 04:56:31 GMT + Content-Disposition: + - attachment; filename="json.txt"; filename*=UTF-8''json.txt + Cross-Origin-Resource-Policy: + - same-site + Cross-Origin-Opener-Policy: + - same-origin + Server: + - ESF + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + Accept-Ranges: + - none + Vary: + - Sec-Fetch-Dest, Sec-Fetch-Mode, Sec-Fetch-Site,Accept-Encoding + Connection: + - close + Transfer-Encoding: + - chunked + body: + encoding: UTF-8 + string: |- + )]}' + {"current_time_millis":1752209791246,"server_nonce":3.589026436260661E-105} + recorded_at: Fri, 11 Jul 2025 04:56:31 GMT +- request: + method: post + uri: https://accounts.google.com/ListAccounts?gpsia=1&json=standard&source=ChromiumBrowser + body: + encoding: UTF-8 + string: " " + headers: + Connection: + - close + Content-Length: + - '1' + Origin: + - https://www.google.com + Content-Type: + - application/x-www-form-urlencoded + Sec-Fetch-Site: + - none + Sec-Fetch-Mode: + - no-cors + Sec-Fetch-Dest: + - empty + User-Agent: + - Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/138.0.0.0 + Safari/537.36 + Accept-Encoding: + - '' + Accept-Language: + - en-US,en;q=0.9 + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=utf-8 + Access-Control-Allow-Origin: + - https://www.google.com + Access-Control-Allow-Credentials: + - 'true' + X-Content-Type-Options: + - nosniff + Cache-Control: + - no-cache, no-store, max-age=0, must-revalidate + Pragma: + - no-cache + Expires: + - Mon, 01 Jan 1990 00:00:00 GMT + Date: + - Fri, 11 Jul 2025 04:56:32 GMT + Strict-Transport-Security: + - max-age=31536000; includeSubDomains + Cross-Origin-Opener-Policy: + - same-origin + Accept-Ch: + - Sec-CH-UA-Arch, Sec-CH-UA-Bitness, Sec-CH-UA-Full-Version, Sec-CH-UA-Full-Version-List, + Sec-CH-UA-Model, Sec-CH-UA-WoW64, Sec-CH-UA-Form-Factors, Sec-CH-UA-Platform, + Sec-CH-UA-Platform-Version + Content-Security-Policy: + - require-trusted-types-for 'script';report-uri /_/IdentityListAccountsHttp/cspreport + - script-src 'report-sample' 'nonce-DAr6J4mndfAlMG1hXXiENw' 'unsafe-inline';object-src + 'none';base-uri 'self';report-uri /_/IdentityListAccountsHttp/cspreport;worker-src + 'self' + - 'script-src ''unsafe-inline'' ''unsafe-eval'' blob: data: ''self'' https://apis.google.com + https://ssl.gstatic.com https://www.google.com https://www.googletagmanager.com + https://www.gstatic.com https://www.google-analytics.com;report-uri /_/IdentityListAccountsHttp/cspreport/allowlist' + - 'script-src ''unsafe-inline'' ''unsafe-eval'' blob: data:;report-uri /_/IdentityListAccountsHttp/cspreport/fine-allowlist' + Permissions-Policy: + - ch-ua-arch=*, ch-ua-bitness=*, ch-ua-full-version=*, ch-ua-full-version-list=*, + ch-ua-model=*, ch-ua-wow64=*, ch-ua-form-factors=*, ch-ua-platform=*, ch-ua-platform-version=* + Reporting-Endpoints: + - default="/_/IdentityListAccountsHttp/web-reports?context=eJzjEtHikmII0pBiOHxtB5Meyy0mIyAW4uFo2HzoMJvAjlX_JzMp6SblF8ZnpqTmlWSWVOZkFpckJifnl-aVFBenFpWlFsUbGRiZGpgbmOkZmMcXGAAAaV4b9A" + Server: + - ESF + X-Xss-Protection: + - '0' + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + Accept-Ranges: + - none + Vary: + - Origin,Accept-Encoding + Connection: + - close + Transfer-Encoding: + - chunked + body: + encoding: UTF-8 + string: '["gaia.l.a.r",[]]' + recorded_at: Fri, 11 Jul 2025 04:56:32 GMT +- request: + method: get + uri: https://deb.debian.org/debian/ + body: + encoding: UTF-8 + string: '' + headers: + Connection: + - close + Upgrade-Insecure-Requests: + - '1' + User-Agent: + - Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/138.0.0.0 + Safari/537.36 + Accept: + - text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 + Sec-Ch-Ua: + - '"Not)A;Brand";v="8", "Chromium";v="138"' + Sec-Ch-Ua-Mobile: + - "?0" + Sec-Ch-Ua-Platform: + - '"Linux"' + Sec-Fetch-Site: + - none + Sec-Fetch-Mode: + - navigate + Sec-Fetch-User: + - "?1" + Sec-Fetch-Dest: + - document + Accept-Encoding: + - '' + Accept-Language: + - en-US,en;q=0.9 + response: + status: + code: 200 + message: OK + headers: + Connection: + - close + Content-Length: + - '6121' + Server: + - Apache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - sameorigin + Referrer-Policy: + - no-referrer + X-Xss-Protection: + - '1' + Permissions-Policy: + - interest-cohort=() + X-Clacks-Overhead: + - GNU Terry Pratchett + Content-Type: + - text/html;charset=UTF-8 + Backend: + - 4qpvL1tJyeV1P6Tmf0Lj8g--F_skroutz_debian_backend_mirrors_debian_org + Via: + - 1.1 varnish, 1.1 varnish + Accept-Ranges: + - bytes + Age: + - '0' + Date: + - Fri, 11 Jul 2025 04:56:32 GMT + X-Served-By: + - cache-ams21082-AMS, cache-mel11231-MEL + X-Cache: + - HIT, MISS + X-Cache-Hits: + - 2, 0 + X-Timer: + - S1752209792.452248,VS0,VE236 + Vary: + - Accept-Encoding + X-Req-Url: + - recv="/debian/",deliver="/debian/" + body: + encoding: UTF-8 + string: | + + + + Index of /debian + + +

Index of /debian

+ + + + + + + + + + + + + + + + + + + +
[ICO]NameLast modifiedSize

[PARENTDIR]Parent Directory  -
[   ]README2025-05-17 08:29 1.2K
[   ]README.CD-manufacture2010-06-26 09:52 1.3K
[TXT]README.html2025-05-17 08:29 2.8K
[TXT]README.mirrors.html2017-03-04 20:08 291
[TXT]README.mirrors.txt2017-03-04 20:08 86
[DIR]dists/2025-05-17 08:29 -
[DIR]doc/2025-07-11 01:52 -
[   ]extrafiles2025-07-11 02:21 193K
[DIR]indices/2025-07-11 02:20 -
[   ]ls-lR.gz2025-07-11 02:14 15M
[DIR]pool/2022-10-05 17:09 -
[DIR]project/2008-11-17 23:05 -
[DIR]tools/2012-10-10 16:29 -
[DIR]zzz-dists/2023-10-07 11:07 -

+ + + + Debian Archive + + + + +

Debian Archive

+ +

See https://www.debian.org/ + for information about Debian GNU/Linux.

+ +

Current Releases

+ +

Four Debian releases are available on the main site:

+ +
+
+ +
Debian 10.13, or buster
+
Debian 10.13 was released Saturday, 10th September 2022. + Installation + and upgrading instructions, + More information +
+ +
Debian 11.11, or bullseye
+
Debian 11.11 was released Saturday, 31st August 2024. + Installation + and upgrading instructions, + More information +
+ +
Debian 12.11, or bookworm
+
Debian 12.11 was released Saturday, 17th May 2025. + Installation + and upgrading instructions, + More information +
+ +
Testing, or trixie
+
The current tested development snapshot is named trixie.
+ Packages which have been tested in unstable and passed automated + tests propagate to this release.
+ More information +
+ +
Unstable, or sid
+
The current development snapshot is named sid.
+ Untested candidate packages for future releases.
+ More information +
+
+
+ +

Old Releases

+ +

Older releases of Debian are at + http://archive.debian.org/debian-archive +
+ More information +

+ +

CDs

+ +

For more information about Debian CDs, please see + README.CD-manufacture. +
+ Further information +

+ +

Mirrors

+ +

For more information about Debian mirrors, please see + README.mirrors.html. +
+ Further information +

+ +

Other directories

+ + + + + +
doc Debian documentation.
indices Various indices of the site.
project Experimental packages and other miscellaneous files.
+ + + + + recorded_at: Fri, 11 Jul 2025 04:56:32 GMT +- request: + method: get + uri: https://deb.debian.org/icons/compressed.gif + body: + encoding: UTF-8 + string: '' + headers: + Connection: + - close + Sec-Ch-Ua-Platform: + - '"Linux"' + User-Agent: + - Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/138.0.0.0 + Safari/537.36 + Sec-Ch-Ua: + - '"Not)A;Brand";v="8", "Chromium";v="138"' + Sec-Ch-Ua-Mobile: + - "?0" + Accept: + - image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8 + Sec-Fetch-Site: + - same-origin + Sec-Fetch-Mode: + - no-cors + Sec-Fetch-Dest: + - image + Accept-Encoding: + - '' + Accept-Language: + - en-US,en;q=0.9 + response: + status: + code: 200 + message: OK + headers: + Connection: + - close + Content-Length: + - '1038' + Server: + - Apache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - sameorigin + Referrer-Policy: + - no-referrer + X-Xss-Protection: + - '1' + Permissions-Policy: + - interest-cohort=() + Last-Modified: + - Sat, 20 Nov 2004 20:16:24 GMT + Etag: + - '"40e-3e9564c23b600"' + X-Clacks-Overhead: + - GNU Terry Pratchett + Content-Type: + - image/gif + Backend: + - 4qpvL1tJyeV1P6Tmf0Lj8g--F_static_backend + Accept-Ranges: + - bytes + Age: + - '0' + Date: + - Fri, 11 Jul 2025 04:56:34 GMT + Via: + - 1.1 varnish + X-Served-By: + - cache-mel11275-MEL + X-Cache: + - HIT + X-Cache-Hits: + - '0' + X-Timer: + - S1752209794.092857,VS0,VE544 + X-Req-Url: + - recv="/icons/compressed.gif",deliver="/icons/compressed.gif" + body: + encoding: ASCII-8BIT + string: !binary |- + R0lGODlhFAAWAOcAAP//////zP//mf//Zv//M///AP/M///MzP/Mmf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9mZv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8AM/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zMAMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz/8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZmZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkzZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZAGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA/2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPMzDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNmmTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMAZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDMMwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBmAAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0AALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACIAAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAARAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3d1VVVURERCIiIhEREQAAACH+TlRoaXMgYXJ0IGlzIGluIHRoZSBwdWJsaWMgZG9tYWluLiBLZXZpbiBIdWdoZXMsIGtldmluaEBlaXQuY29tLCBTZXB0ZW1iZXIgMTk5NQAh+QQBAAAkACwAAAAAFAAWAAAImQBJCCTBqmDBgQgTDmQFAABDVgojEmzI0KHEhBUrWrwoMGNDihwnAvjHiqRJjhX/qVz5D+VHAFZiWmmZ8BGHji9hxqTJ4ZFAmzc1vpxJgkPPn0Y5CP04M6lPEkCN5mxoJelRqFY5TM36NGrPqV67Op0KM6rYnkup/gMq1mdamC1tdn36lijUpwjr0pSoFyUrmTJLhiTBkqXCgAA7 + recorded_at: Fri, 11 Jul 2025 04:56:34 GMT +- request: + method: get + uri: https://deb.debian.org/icons/blank.gif + body: + encoding: UTF-8 + string: '' + headers: + Connection: + - close + Sec-Ch-Ua-Platform: + - '"Linux"' + User-Agent: + - Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/138.0.0.0 + Safari/537.36 + Sec-Ch-Ua: + - '"Not)A;Brand";v="8", "Chromium";v="138"' + Sec-Ch-Ua-Mobile: + - "?0" + Accept: + - image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8 + Sec-Fetch-Site: + - same-origin + Sec-Fetch-Mode: + - no-cors + Sec-Fetch-Dest: + - image + Accept-Encoding: + - '' + Accept-Language: + - en-US,en;q=0.9 + response: + status: + code: 200 + message: OK + headers: + Connection: + - close + Content-Length: + - '148' + Server: + - Apache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - sameorigin + Referrer-Policy: + - no-referrer + X-Xss-Protection: + - '1' + Permissions-Policy: + - interest-cohort=() + Last-Modified: + - Sat, 20 Nov 2004 20:16:24 GMT + Etag: + - '"94-3e9564c23b600"' + X-Clacks-Overhead: + - GNU Terry Pratchett + Content-Type: + - image/gif + Backend: + - 4qpvL1tJyeV1P6Tmf0Lj8g--F_static_backend + Accept-Ranges: + - bytes + Age: + - '0' + Date: + - Fri, 11 Jul 2025 04:56:34 GMT + Via: + - 1.1 varnish + X-Served-By: + - cache-mel11256-MEL + X-Cache: + - HIT + X-Cache-Hits: + - '0' + X-Timer: + - S1752209794.092746,VS0,VE559 + X-Req-Url: + - recv="/icons/blank.gif",deliver="/icons/blank.gif" + body: + encoding: ASCII-8BIT + string: !binary |- + R0lGODlhFAAWAKEAAP///8z//wAAAAAAACH+TlRoaXMgYXJ0IGlzIGluIHRoZSBwdWJsaWMgZG9tYWluLiBLZXZpbiBIdWdoZXMsIGtldmluaEBlaXQuY29tLCBTZXB0ZW1iZXIgMTk5NQAh+QQBAAABACwAAAAAFAAWAAACE4yPqcvtD6OctNqLs968+w+GSQEAOw== + recorded_at: Fri, 11 Jul 2025 04:56:34 GMT +- request: + method: get + uri: https://deb.debian.org/icons/back.gif + body: + encoding: UTF-8 + string: '' + headers: + Connection: + - close + Sec-Ch-Ua-Platform: + - '"Linux"' + User-Agent: + - Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/138.0.0.0 + Safari/537.36 + Sec-Ch-Ua: + - '"Not)A;Brand";v="8", "Chromium";v="138"' + Sec-Ch-Ua-Mobile: + - "?0" + Accept: + - image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8 + Sec-Fetch-Site: + - same-origin + Sec-Fetch-Mode: + - no-cors + Sec-Fetch-Dest: + - image + Accept-Encoding: + - '' + Accept-Language: + - en-US,en;q=0.9 + response: + status: + code: 200 + message: OK + headers: + Connection: + - close + Content-Length: + - '216' + Server: + - Apache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - sameorigin + Referrer-Policy: + - no-referrer + X-Xss-Protection: + - '1' + Permissions-Policy: + - interest-cohort=() + Last-Modified: + - Sat, 20 Nov 2004 20:16:24 GMT + Etag: + - '"d8-3e9564c23b600"' + X-Clacks-Overhead: + - GNU Terry Pratchett + Content-Type: + - image/gif + Backend: + - 4qpvL1tJyeV1P6Tmf0Lj8g--F_static_backend + Accept-Ranges: + - bytes + Age: + - '0' + Date: + - Fri, 11 Jul 2025 04:56:34 GMT + Via: + - 1.1 varnish + X-Served-By: + - cache-mel11253-MEL + X-Cache: + - HIT + X-Cache-Hits: + - '0' + X-Timer: + - S1752209794.092777,VS0,VE544 + X-Req-Url: + - recv="/icons/back.gif",deliver="/icons/back.gif" + body: + encoding: ASCII-8BIT + string: !binary |- + R0lGODlhFAAWAMIAAP///8z//5mZmWZmZjMzMwAAAAAAAAAAACH+TlRoaXMgYXJ0IGlzIGluIHRoZSBwdWJsaWMgZG9tYWluLiBLZXZpbiBIdWdoZXMsIGtldmluaEBlaXQuY29tLCBTZXB0ZW1iZXIgMTk5NQAh+QQBAAABACwAAAAAFAAWAAADSxi63P4jEPJqEDNTu6LO3PVpnDdOFnaCkHQGBTcqRRxuWG0v+5LrNUZQ8QPqeMakkaZsFihOpyDajMCoOoJAGNVWkt7QVfzokc+LBAA7 + recorded_at: Fri, 11 Jul 2025 04:56:34 GMT +- request: + method: get + uri: https://deb.debian.org/icons/hand.right.gif + body: + encoding: UTF-8 + string: '' + headers: + Connection: + - close + Sec-Ch-Ua-Platform: + - '"Linux"' + User-Agent: + - Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/138.0.0.0 + Safari/537.36 + Sec-Ch-Ua: + - '"Not)A;Brand";v="8", "Chromium";v="138"' + Sec-Ch-Ua-Mobile: + - "?0" + Accept: + - image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8 + Sec-Fetch-Site: + - same-origin + Sec-Fetch-Mode: + - no-cors + Sec-Fetch-Dest: + - image + Accept-Encoding: + - '' + Accept-Language: + - en-US,en;q=0.9 + response: + status: + code: 200 + message: OK + headers: + Connection: + - close + Content-Length: + - '217' + Server: + - Apache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - sameorigin + Referrer-Policy: + - no-referrer + X-Xss-Protection: + - '1' + Permissions-Policy: + - interest-cohort=() + Last-Modified: + - Sat, 20 Nov 2004 20:16:24 GMT + Etag: + - '"d9-3e9564c23b600"' + X-Clacks-Overhead: + - GNU Terry Pratchett + Content-Type: + - image/gif + Backend: + - 4qpvL1tJyeV1P6Tmf0Lj8g--F_static_backend + Accept-Ranges: + - bytes + Age: + - '0' + Date: + - Fri, 11 Jul 2025 04:56:34 GMT + Via: + - 1.1 varnish + X-Served-By: + - cache-mel11263-MEL + X-Cache: + - HIT + X-Cache-Hits: + - '0' + X-Timer: + - S1752209794.137505,VS0,VE543 + X-Req-Url: + - recv="/icons/hand.right.gif",deliver="/icons/hand.right.gif" + body: + encoding: ASCII-8BIT + string: !binary |- + R0lGODlhFAAWAMIAAP/////Mmcz//5lmMwAAAAAAAAAAAAAAACH+TlRoaXMgYXJ0IGlzIGluIHRoZSBwdWJsaWMgZG9tYWluLiBLZXZpbiBIdWdoZXMsIGtldmluaEBlaXQuY29tLCBTZXB0ZW1iZXIgMTk5NQAh+QQBAAACACwAAAAAFAAWAAADTCi63P4wykkdubiSwDuRVydi5CWEYjBsKbe2rDjMdMwRw1iaaZx7jcDm8nOpVsFjsSh0CFuq46fxko0eKOtsiu0UuRHfVlOqmM9oSgIAOw== + recorded_at: Fri, 11 Jul 2025 04:56:34 GMT +- request: + method: get + uri: https://deb.debian.org/icons/text.gif + body: + encoding: UTF-8 + string: '' + headers: + Connection: + - close + Sec-Ch-Ua-Platform: + - '"Linux"' + User-Agent: + - Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/138.0.0.0 + Safari/537.36 + Sec-Ch-Ua: + - '"Not)A;Brand";v="8", "Chromium";v="138"' + Sec-Ch-Ua-Mobile: + - "?0" + Accept: + - image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8 + Sec-Fetch-Site: + - same-origin + Sec-Fetch-Mode: + - no-cors + Sec-Fetch-Dest: + - image + Accept-Encoding: + - '' + Accept-Language: + - en-US,en;q=0.9 + response: + status: + code: 200 + message: OK + headers: + Connection: + - close + Content-Length: + - '229' + Server: + - Apache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - sameorigin + Referrer-Policy: + - no-referrer + X-Xss-Protection: + - '1' + Permissions-Policy: + - interest-cohort=() + Last-Modified: + - Sat, 20 Nov 2004 20:16:24 GMT + Etag: + - '"e5-3e9564c23b600"' + X-Clacks-Overhead: + - GNU Terry Pratchett + Content-Type: + - image/gif + Backend: + - 4qpvL1tJyeV1P6Tmf0Lj8g--F_static_backend + Accept-Ranges: + - bytes + Age: + - '0' + Date: + - Fri, 11 Jul 2025 04:56:34 GMT + Via: + - 1.1 varnish + X-Served-By: + - cache-mel11266-MEL + X-Cache: + - HIT + X-Cache-Hits: + - '0' + X-Timer: + - S1752209794.137616,VS0,VE546 + X-Req-Url: + - recv="/icons/text.gif",deliver="/icons/text.gif" + body: + encoding: ASCII-8BIT + string: !binary |- + R0lGODlhFAAWAMIAAP///8z//5mZmTMzMwAAAAAAAAAAAAAAACH+TlRoaXMgYXJ0IGlzIGluIHRoZSBwdWJsaWMgZG9tYWluLiBLZXZpbiBIdWdoZXMsIGtldmluaEBlaXQuY29tLCBTZXB0ZW1iZXIgMTk5NQAh+QQBAAABACwAAAAAFAAWAAADWDi6vPEwDECrnSO+aTvPEddVIriN1wVxROtSxBDPJwq7bo23luALhJqt8gtKbrsXBSgcEo2spBLAPDp7UKT02bxWRdrp94rtbpdZMrrr/A5+8LhPFpHajQkAOw== + recorded_at: Fri, 11 Jul 2025 04:56:34 GMT +- request: + method: get + uri: https://deb.debian.org/icons/unknown.gif + body: + encoding: UTF-8 + string: '' + headers: + Connection: + - close + Sec-Ch-Ua-Platform: + - '"Linux"' + User-Agent: + - Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/138.0.0.0 + Safari/537.36 + Sec-Ch-Ua: + - '"Not)A;Brand";v="8", "Chromium";v="138"' + Sec-Ch-Ua-Mobile: + - "?0" + Accept: + - image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8 + Sec-Fetch-Site: + - same-origin + Sec-Fetch-Mode: + - no-cors + Sec-Fetch-Dest: + - image + Accept-Encoding: + - '' + Accept-Language: + - en-US,en;q=0.9 + response: + status: + code: 200 + message: OK + headers: + Connection: + - close + Content-Length: + - '245' + Server: + - Apache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - sameorigin + Referrer-Policy: + - no-referrer + X-Xss-Protection: + - '1' + Permissions-Policy: + - interest-cohort=() + Last-Modified: + - Sat, 20 Nov 2004 20:16:24 GMT + Etag: + - '"f5-3e9564c23b600"' + X-Clacks-Overhead: + - GNU Terry Pratchett + Content-Type: + - image/gif + Backend: + - 4qpvL1tJyeV1P6Tmf0Lj8g--F_static_backend + Accept-Ranges: + - bytes + Age: + - '0' + Date: + - Fri, 11 Jul 2025 04:56:34 GMT + Via: + - 1.1 varnish + X-Served-By: + - cache-mel11261-MEL + X-Cache: + - HIT + X-Cache-Hits: + - '0' + X-Timer: + - S1752209794.137743,VS0,VE554 + X-Req-Url: + - recv="/icons/unknown.gif",deliver="/icons/unknown.gif" + body: + encoding: ASCII-8BIT + string: !binary |- + R0lGODlhFAAWAMIAAP///8z//5mZmTMzMwAAAAAAAAAAAAAAACH+TlRoaXMgYXJ0IGlzIGluIHRoZSBwdWJsaWMgZG9tYWluLiBLZXZpbiBIdWdoZXMsIGtldmluaEBlaXQuY29tLCBTZXB0ZW1iZXIgMTk5NQAh+QQBAAABACwAAAAAFAAWAAADaDi6vPEwDECrnSO+aTvPEQcIAmGaIrhR5XmKgMq1LkoMN7ECrjDWp52r0iPpJJ0KjUAq7SxLE+sI+9V8vycFiM0iLb2O80s8JcfVJJTaGYrZYPNby5Ov6WolPD+XDJqAgSQ4EUCGQQEJADs= + recorded_at: Fri, 11 Jul 2025 04:56:34 GMT +- request: + method: get + uri: https://deb.debian.org/icons/folder.gif + body: + encoding: UTF-8 + string: '' + headers: + Connection: + - close + Sec-Ch-Ua-Platform: + - '"Linux"' + User-Agent: + - Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/138.0.0.0 + Safari/537.36 + Sec-Ch-Ua: + - '"Not)A;Brand";v="8", "Chromium";v="138"' + Sec-Ch-Ua-Mobile: + - "?0" + Accept: + - image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8 + Sec-Fetch-Site: + - same-origin + Sec-Fetch-Mode: + - no-cors + Sec-Fetch-Dest: + - image + Accept-Encoding: + - '' + Accept-Language: + - en-US,en;q=0.9 + response: + status: + code: 200 + message: OK + headers: + Connection: + - close + Content-Length: + - '225' + Server: + - Apache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - sameorigin + Referrer-Policy: + - no-referrer + X-Xss-Protection: + - '1' + Permissions-Policy: + - interest-cohort=() + Last-Modified: + - Sat, 20 Nov 2004 20:16:24 GMT + Etag: + - '"e1-3e9564c23b600"' + X-Clacks-Overhead: + - GNU Terry Pratchett + Content-Type: + - image/gif + Backend: + - 4qpvL1tJyeV1P6Tmf0Lj8g--F_static_backend + Accept-Ranges: + - bytes + Age: + - '0' + Date: + - Fri, 11 Jul 2025 04:56:35 GMT + Via: + - 1.1 varnish + X-Served-By: + - cache-mel11253-MEL + X-Cache: + - HIT + X-Cache-Hits: + - '0' + X-Timer: + - S1752209795.922267,VS0,VE545 + X-Req-Url: + - recv="/icons/folder.gif",deliver="/icons/folder.gif" + body: + encoding: ASCII-8BIT + string: !binary |- + R0lGODlhFAAWAMIAAP/////Mmcz//5lmMzMzMwAAAAAAAAAAACH+TlRoaXMgYXJ0IGlzIGluIHRoZSBwdWJsaWMgZG9tYWluLiBLZXZpbiBIdWdoZXMsIGtldmluaEBlaXQuY29tLCBTZXB0ZW1iZXIgMTk5NQAh+QQBAAACACwAAAAAFAAWAAADVCi63P4wyklZufjOErrvRcR9ZKYpxUB6aokGQyzHKxyO9RoTV54PPJyPBewNSUXhcWc8soJOIjTaSVJhVphWxd3CeILUbDwmgMPmtHrNIyxM8Iw7AQA7 + recorded_at: Fri, 11 Jul 2025 04:56:35 GMT +recorded_with: VCR 6.2.0 diff --git a/spec/system/billy_spec.rb b/spec/system/billy_spec.rb new file mode 100644 index 0000000000..251e827af8 --- /dev/null +++ b/spec/system/billy_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'system_helper' + +RSpec.describe "Testing external scripts loaded in the browser" do + it "loads a website", :vcr do + visit "http://deb.debian.org:80/debian/" + expect(page).to have_content "Debian Archive" + end + + it "handles HTTPS", :vcr do + visit "https://deb.debian.org:443/debian/" + expect(page).to have_content "Debian Archive" + end + + it "stubs content" do + stub_request(:get, "https://deb.debian.org:443").to_return(body: "stubbed") + visit "https://deb.debian.org:443" + expect(page).to have_content "stubbed" + end +end diff --git a/spec/system/support/cuprite_setup.rb b/spec/system/support/cuprite_setup.rb index 431d4d546d..6d44e72ba3 100644 --- a/spec/system/support/cuprite_setup.rb +++ b/spec/system/support/cuprite_setup.rb @@ -4,7 +4,9 @@ require "capybara/cuprite" headless = ActiveModel::Type::Boolean.new.cast(ENV.fetch("HEADLESS", true)) -browser_options = {} +browser_options = { + "ignore-certificate-errors" => nil, +} browser_options["no-sandbox"] = nil if ENV['CI'] || ENV['DOCKER'] Capybara.register_driver(:cuprite_ofn) do |app| @@ -15,10 +17,16 @@ Capybara.register_driver(:cuprite_ofn) do |app| process_timeout: 60, timeout: 60, # Don't load scripts from external sources, like google maps or stripe - url_whitelist: [%r{http://localhost}i, %r{http://0.0.0.0}i, %r{http://127.0.0.1}], + url_whitelist: [ + %r{^http://localhost}, %r{^http://0.0.0.0}, %r{http://127.0.0.1}, + + # Just for testing external connections: spec/system/billy_spec.rb + %r{^https?://deb.debian.org}, + ], inspector: true, headless:, - js_errors: true + js_errors: true, + proxy: { host: Billy.proxy.host, port: Billy.proxy.port }, ) end From d01474ebcd3997852246c790644d9aa8a4033985 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 30 Jul 2025 12:20:42 +1000 Subject: [PATCH 16/76] Ignore Chrome's automatic requests to Google services --- .../handles_HTTPS.yml | 653 ++++++++++++++- .../loads_a_website.yml | 743 ++++++++---------- spec/support/vcr_setup.rb | 12 +- 3 files changed, 997 insertions(+), 411 deletions(-) diff --git a/spec/fixtures/vcr_cassettes/Testing_external_scripts_loaded_in_the_browser/handles_HTTPS.yml b/spec/fixtures/vcr_cassettes/Testing_external_scripts_loaded_in_the_browser/handles_HTTPS.yml index 2c1127632c..7113b497f5 100644 --- a/spec/fixtures/vcr_cassettes/Testing_external_scripts_loaded_in_the_browser/handles_HTTPS.yml +++ b/spec/fixtures/vcr_cassettes/Testing_external_scripts_loaded_in_the_browser/handles_HTTPS.yml @@ -2,20 +2,18 @@ http_interactions: - request: method: get - uri: http://www.gstatic.com/generate_204 + uri: http://deb.debian.org/favicon.ico body: encoding: UTF-8 string: '' headers: Proxy-Connection: - keep-alive - Pragma: - - no-cache - Cache-Control: - - no-cache User-Agent: - Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/138.0.0.0 Safari/537.36 + Accept: + - image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8 Accept-Encoding: - '' Accept-Language: @@ -24,19 +22,646 @@ http_interactions: - close response: status: - code: 204 - message: No Content + code: 404 + message: Not Found headers: - Content-Length: - - '0' - Cross-Origin-Resource-Policy: - - cross-origin - Date: - - Fri, 11 Jul 2025 05:04:02 GMT Connection: - close + Content-Length: + - '260' + Server: + - Apache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - sameorigin + Referrer-Policy: + - no-referrer + X-Xss-Protection: + - '1' + Permissions-Policy: + - interest-cohort=() + Content-Type: + - text/html; charset=iso-8859-1 + Backend: + - 4qpvL1tJyeV1P6Tmf0Lj8g--F_static_backend + Accept-Ranges: + - bytes + Age: + - '0' + Date: + - Wed, 30 Jul 2025 02:22:26 GMT + Via: + - 1.1 varnish + X-Served-By: + - cache-syd10162-SYD + X-Cache: + - MISS + X-Cache-Hits: + - '0' + X-Timer: + - S1753842146.721877,VS0,VE615 + X-Req-Url: + - recv="/favicon.ico",deliver="/favicon.ico" + body: + encoding: UTF-8 + string: | + + + 404 Not Found + +

Not Found

+

The requested URL was not found on this server.

+
+
Apache Server at deb.debian.org Port 80
+ + recorded_at: Wed, 30 Jul 2025 02:22:27 GMT +- request: + method: get + uri: https://deb.debian.org/icons/compressed.gif body: encoding: UTF-8 string: '' - recorded_at: Fri, 11 Jul 2025 05:04:02 GMT + headers: + Connection: + - close + Sec-Ch-Ua-Platform: + - '"Linux"' + User-Agent: + - Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/138.0.0.0 + Safari/537.36 + Sec-Ch-Ua: + - '"Not)A;Brand";v="8", "Chromium";v="138"' + Sec-Ch-Ua-Mobile: + - "?0" + Accept: + - image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8 + Sec-Fetch-Site: + - same-origin + Sec-Fetch-Mode: + - no-cors + Sec-Fetch-Dest: + - image + Accept-Encoding: + - '' + Accept-Language: + - en-US,en;q=0.9 + response: + status: + code: 200 + message: OK + headers: + Connection: + - close + Content-Length: + - '1038' + Server: + - Apache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - sameorigin + Referrer-Policy: + - no-referrer + X-Xss-Protection: + - '1' + Permissions-Policy: + - interest-cohort=() + Last-Modified: + - Sat, 20 Nov 2004 20:16:24 GMT + Etag: + - '"40e-3e9564c23b600"' + X-Clacks-Overhead: + - GNU Terry Pratchett + Content-Type: + - image/gif + Backend: + - 4qpvL1tJyeV1P6Tmf0Lj8g--F_static_backend + Accept-Ranges: + - bytes + Date: + - Wed, 30 Jul 2025 02:22:27 GMT + Via: + - 1.1 varnish + Age: + - '2' + X-Served-By: + - cache-syd10139-SYD + X-Cache: + - HIT + X-Cache-Hits: + - '1' + X-Timer: + - S1753842148.531974,VS0,VE1 + X-Req-Url: + - recv="/icons/compressed.gif",deliver="/icons/compressed.gif" + body: + encoding: ASCII-8BIT + string: !binary |- + R0lGODlhFAAWAOcAAP//////zP//mf//Zv//M///AP/M///MzP/Mmf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9mZv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8AM/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zMAMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz/8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZmZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkzZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZAGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA/2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPMzDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNmmTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMAZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDMMwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBmAAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0AALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACIAAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAARAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3d1VVVURERCIiIhEREQAAACH+TlRoaXMgYXJ0IGlzIGluIHRoZSBwdWJsaWMgZG9tYWluLiBLZXZpbiBIdWdoZXMsIGtldmluaEBlaXQuY29tLCBTZXB0ZW1iZXIgMTk5NQAh+QQBAAAkACwAAAAAFAAWAAAImQBJCCTBqmDBgQgTDmQFAABDVgojEmzI0KHEhBUrWrwoMGNDihwnAvjHiqRJjhX/qVz5D+VHAFZiWmmZ8BGHji9hxqTJ4ZFAmzc1vpxJgkPPn0Y5CP04M6lPEkCN5mxoJelRqFY5TM36NGrPqV67Op0KM6rYnkup/gMq1mdamC1tdn36lijUpwjr0pSoFyUrmTJLhiTBkqXCgAA7 + recorded_at: Wed, 30 Jul 2025 02:22:27 GMT +- request: + method: get + uri: https://deb.debian.org/icons/blank.gif + body: + encoding: UTF-8 + string: '' + headers: + Connection: + - close + Sec-Ch-Ua-Platform: + - '"Linux"' + User-Agent: + - Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/138.0.0.0 + Safari/537.36 + Sec-Ch-Ua: + - '"Not)A;Brand";v="8", "Chromium";v="138"' + Sec-Ch-Ua-Mobile: + - "?0" + Accept: + - image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8 + Sec-Fetch-Site: + - same-origin + Sec-Fetch-Mode: + - no-cors + Sec-Fetch-Dest: + - image + Accept-Encoding: + - '' + Accept-Language: + - en-US,en;q=0.9 + response: + status: + code: 200 + message: OK + headers: + Connection: + - close + Content-Length: + - '148' + Server: + - Apache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - sameorigin + Referrer-Policy: + - no-referrer + X-Xss-Protection: + - '1' + Permissions-Policy: + - interest-cohort=() + Last-Modified: + - Sat, 20 Nov 2004 20:16:24 GMT + Etag: + - '"94-3e9564c23b600"' + X-Clacks-Overhead: + - GNU Terry Pratchett + Content-Type: + - image/gif + Backend: + - 4qpvL1tJyeV1P6Tmf0Lj8g--F_static_backend + Accept-Ranges: + - bytes + Date: + - Wed, 30 Jul 2025 02:22:27 GMT + Via: + - 1.1 varnish + Age: + - '3' + X-Served-By: + - cache-syd10176-SYD + X-Cache: + - HIT + X-Cache-Hits: + - '1' + X-Timer: + - S1753842148.541864,VS0,VE1 + X-Req-Url: + - recv="/icons/blank.gif",deliver="/icons/blank.gif" + body: + encoding: ASCII-8BIT + string: !binary |- + R0lGODlhFAAWAKEAAP///8z//wAAAAAAACH+TlRoaXMgYXJ0IGlzIGluIHRoZSBwdWJsaWMgZG9tYWluLiBLZXZpbiBIdWdoZXMsIGtldmluaEBlaXQuY29tLCBTZXB0ZW1iZXIgMTk5NQAh+QQBAAABACwAAAAAFAAWAAACE4yPqcvtD6OctNqLs968+w+GSQEAOw== + recorded_at: Wed, 30 Jul 2025 02:22:27 GMT +- request: + method: get + uri: https://deb.debian.org/icons/back.gif + body: + encoding: UTF-8 + string: '' + headers: + Connection: + - close + Sec-Ch-Ua-Platform: + - '"Linux"' + User-Agent: + - Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/138.0.0.0 + Safari/537.36 + Sec-Ch-Ua: + - '"Not)A;Brand";v="8", "Chromium";v="138"' + Sec-Ch-Ua-Mobile: + - "?0" + Accept: + - image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8 + Sec-Fetch-Site: + - same-origin + Sec-Fetch-Mode: + - no-cors + Sec-Fetch-Dest: + - image + Accept-Encoding: + - '' + Accept-Language: + - en-US,en;q=0.9 + response: + status: + code: 200 + message: OK + headers: + Connection: + - close + Content-Length: + - '216' + Server: + - Apache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - sameorigin + Referrer-Policy: + - no-referrer + X-Xss-Protection: + - '1' + Permissions-Policy: + - interest-cohort=() + Last-Modified: + - Sat, 20 Nov 2004 20:16:24 GMT + Etag: + - '"d8-3e9564c23b600"' + X-Clacks-Overhead: + - GNU Terry Pratchett + Content-Type: + - image/gif + Backend: + - 4qpvL1tJyeV1P6Tmf0Lj8g--F_static_backend + Accept-Ranges: + - bytes + Date: + - Wed, 30 Jul 2025 02:22:27 GMT + Via: + - 1.1 varnish + Age: + - '3' + X-Served-By: + - cache-syd10161-SYD + X-Cache: + - HIT + X-Cache-Hits: + - '1' + X-Timer: + - S1753842148.542278,VS0,VE1 + X-Req-Url: + - recv="/icons/back.gif",deliver="/icons/back.gif" + body: + encoding: ASCII-8BIT + string: !binary |- + R0lGODlhFAAWAMIAAP///8z//5mZmWZmZjMzMwAAAAAAAAAAACH+TlRoaXMgYXJ0IGlzIGluIHRoZSBwdWJsaWMgZG9tYWluLiBLZXZpbiBIdWdoZXMsIGtldmluaEBlaXQuY29tLCBTZXB0ZW1iZXIgMTk5NQAh+QQBAAABACwAAAAAFAAWAAADSxi63P4jEPJqEDNTu6LO3PVpnDdOFnaCkHQGBTcqRRxuWG0v+5LrNUZQ8QPqeMakkaZsFihOpyDajMCoOoJAGNVWkt7QVfzokc+LBAA7 + recorded_at: Wed, 30 Jul 2025 02:22:27 GMT +- request: + method: get + uri: https://deb.debian.org/icons/hand.right.gif + body: + encoding: UTF-8 + string: '' + headers: + Connection: + - close + Sec-Ch-Ua-Platform: + - '"Linux"' + User-Agent: + - Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/138.0.0.0 + Safari/537.36 + Sec-Ch-Ua: + - '"Not)A;Brand";v="8", "Chromium";v="138"' + Sec-Ch-Ua-Mobile: + - "?0" + Accept: + - image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8 + Sec-Fetch-Site: + - same-origin + Sec-Fetch-Mode: + - no-cors + Sec-Fetch-Dest: + - image + Accept-Encoding: + - '' + Accept-Language: + - en-US,en;q=0.9 + response: + status: + code: 200 + message: OK + headers: + Connection: + - close + Content-Length: + - '217' + Server: + - Apache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - sameorigin + Referrer-Policy: + - no-referrer + X-Xss-Protection: + - '1' + Permissions-Policy: + - interest-cohort=() + Last-Modified: + - Sat, 20 Nov 2004 20:16:24 GMT + Etag: + - '"d9-3e9564c23b600"' + X-Clacks-Overhead: + - GNU Terry Pratchett + Content-Type: + - image/gif + Backend: + - 4qpvL1tJyeV1P6Tmf0Lj8g--F_static_backend + Accept-Ranges: + - bytes + Date: + - Wed, 30 Jul 2025 02:22:27 GMT + Via: + - 1.1 varnish + Age: + - '3' + X-Served-By: + - cache-syd10121-SYD + X-Cache: + - HIT + X-Cache-Hits: + - '1' + X-Timer: + - S1753842148.542101,VS0,VE1 + X-Req-Url: + - recv="/icons/hand.right.gif",deliver="/icons/hand.right.gif" + body: + encoding: ASCII-8BIT + string: !binary |- + R0lGODlhFAAWAMIAAP/////Mmcz//5lmMwAAAAAAAAAAAAAAACH+TlRoaXMgYXJ0IGlzIGluIHRoZSBwdWJsaWMgZG9tYWluLiBLZXZpbiBIdWdoZXMsIGtldmluaEBlaXQuY29tLCBTZXB0ZW1iZXIgMTk5NQAh+QQBAAACACwAAAAAFAAWAAADTCi63P4wykkdubiSwDuRVydi5CWEYjBsKbe2rDjMdMwRw1iaaZx7jcDm8nOpVsFjsSh0CFuq46fxko0eKOtsiu0UuRHfVlOqmM9oSgIAOw== + recorded_at: Wed, 30 Jul 2025 02:22:27 GMT +- request: + method: get + uri: https://deb.debian.org/icons/unknown.gif + body: + encoding: UTF-8 + string: '' + headers: + Connection: + - close + Sec-Ch-Ua-Platform: + - '"Linux"' + User-Agent: + - Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/138.0.0.0 + Safari/537.36 + Sec-Ch-Ua: + - '"Not)A;Brand";v="8", "Chromium";v="138"' + Sec-Ch-Ua-Mobile: + - "?0" + Accept: + - image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8 + Sec-Fetch-Site: + - same-origin + Sec-Fetch-Mode: + - no-cors + Sec-Fetch-Dest: + - image + Accept-Encoding: + - '' + Accept-Language: + - en-US,en;q=0.9 + response: + status: + code: 200 + message: OK + headers: + Connection: + - close + Content-Length: + - '245' + Server: + - Apache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - sameorigin + Referrer-Policy: + - no-referrer + X-Xss-Protection: + - '1' + Permissions-Policy: + - interest-cohort=() + Last-Modified: + - Sat, 20 Nov 2004 20:16:24 GMT + Etag: + - '"f5-3e9564c23b600"' + X-Clacks-Overhead: + - GNU Terry Pratchett + Content-Type: + - image/gif + Backend: + - 4qpvL1tJyeV1P6Tmf0Lj8g--F_static_backend + Accept-Ranges: + - bytes + Date: + - Wed, 30 Jul 2025 02:22:27 GMT + Via: + - 1.1 varnish + Age: + - '3' + X-Served-By: + - cache-syd10172-SYD + X-Cache: + - HIT + X-Cache-Hits: + - '1' + X-Timer: + - S1753842148.542293,VS0,VE1 + X-Req-Url: + - recv="/icons/unknown.gif",deliver="/icons/unknown.gif" + body: + encoding: ASCII-8BIT + string: !binary |- + R0lGODlhFAAWAMIAAP///8z//5mZmTMzMwAAAAAAAAAAAAAAACH+TlRoaXMgYXJ0IGlzIGluIHRoZSBwdWJsaWMgZG9tYWluLiBLZXZpbiBIdWdoZXMsIGtldmluaEBlaXQuY29tLCBTZXB0ZW1iZXIgMTk5NQAh+QQBAAABACwAAAAAFAAWAAADaDi6vPEwDECrnSO+aTvPEQcIAmGaIrhR5XmKgMq1LkoMN7ECrjDWp52r0iPpJJ0KjUAq7SxLE+sI+9V8vycFiM0iLb2O80s8JcfVJJTaGYrZYPNby5Ov6WolPD+XDJqAgSQ4EUCGQQEJADs= + recorded_at: Wed, 30 Jul 2025 02:22:27 GMT +- request: + method: get + uri: https://deb.debian.org/icons/text.gif + body: + encoding: UTF-8 + string: '' + headers: + Connection: + - close + Sec-Ch-Ua-Platform: + - '"Linux"' + User-Agent: + - Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/138.0.0.0 + Safari/537.36 + Sec-Ch-Ua: + - '"Not)A;Brand";v="8", "Chromium";v="138"' + Sec-Ch-Ua-Mobile: + - "?0" + Accept: + - image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8 + Sec-Fetch-Site: + - same-origin + Sec-Fetch-Mode: + - no-cors + Sec-Fetch-Dest: + - image + Accept-Encoding: + - '' + Accept-Language: + - en-US,en;q=0.9 + response: + status: + code: 200 + message: OK + headers: + Connection: + - close + Content-Length: + - '229' + Server: + - Apache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - sameorigin + Referrer-Policy: + - no-referrer + X-Xss-Protection: + - '1' + Permissions-Policy: + - interest-cohort=() + Last-Modified: + - Sat, 20 Nov 2004 20:16:24 GMT + Etag: + - '"e5-3e9564c23b600"' + X-Clacks-Overhead: + - GNU Terry Pratchett + Content-Type: + - image/gif + Backend: + - 4qpvL1tJyeV1P6Tmf0Lj8g--F_static_backend + Accept-Ranges: + - bytes + Date: + - Wed, 30 Jul 2025 02:22:27 GMT + Via: + - 1.1 varnish + Age: + - '3' + X-Served-By: + - cache-syd10120-SYD + X-Cache: + - HIT + X-Cache-Hits: + - '1' + X-Timer: + - S1753842148.541994,VS0,VE1 + X-Req-Url: + - recv="/icons/text.gif",deliver="/icons/text.gif" + body: + encoding: ASCII-8BIT + string: !binary |- + R0lGODlhFAAWAMIAAP///8z//5mZmTMzMwAAAAAAAAAAAAAAACH+TlRoaXMgYXJ0IGlzIGluIHRoZSBwdWJsaWMgZG9tYWluLiBLZXZpbiBIdWdoZXMsIGtldmluaEBlaXQuY29tLCBTZXB0ZW1iZXIgMTk5NQAh+QQBAAABACwAAAAAFAAWAAADWDi6vPEwDECrnSO+aTvPEddVIriN1wVxROtSxBDPJwq7bo23luALhJqt8gtKbrsXBSgcEo2spBLAPDp7UKT02bxWRdrp94rtbpdZMrrr/A5+8LhPFpHajQkAOw== + recorded_at: Wed, 30 Jul 2025 02:22:27 GMT +- request: + method: get + uri: https://deb.debian.org/icons/folder.gif + body: + encoding: UTF-8 + string: '' + headers: + Connection: + - close + Sec-Ch-Ua-Platform: + - '"Linux"' + User-Agent: + - Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/138.0.0.0 + Safari/537.36 + Sec-Ch-Ua: + - '"Not)A;Brand";v="8", "Chromium";v="138"' + Sec-Ch-Ua-Mobile: + - "?0" + Accept: + - image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8 + Sec-Fetch-Site: + - same-origin + Sec-Fetch-Mode: + - no-cors + Sec-Fetch-Dest: + - image + Accept-Encoding: + - '' + Accept-Language: + - en-US,en;q=0.9 + response: + status: + code: 200 + message: OK + headers: + Connection: + - close + Content-Length: + - '225' + Server: + - Apache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - sameorigin + Referrer-Policy: + - no-referrer + X-Xss-Protection: + - '1' + Permissions-Policy: + - interest-cohort=() + Last-Modified: + - Sat, 20 Nov 2004 20:16:24 GMT + Etag: + - '"e1-3e9564c23b600"' + X-Clacks-Overhead: + - GNU Terry Pratchett + Content-Type: + - image/gif + Backend: + - 4qpvL1tJyeV1P6Tmf0Lj8g--F_static_backend + Accept-Ranges: + - bytes + Date: + - Wed, 30 Jul 2025 02:22:27 GMT + Via: + - 1.1 varnish + Age: + - '3' + X-Served-By: + - cache-syd10165-SYD + X-Cache: + - HIT + X-Cache-Hits: + - '1' + X-Timer: + - S1753842148.992546,VS0,VE1 + X-Req-Url: + - recv="/icons/folder.gif",deliver="/icons/folder.gif" + body: + encoding: ASCII-8BIT + string: !binary |- + R0lGODlhFAAWAMIAAP/////Mmcz//5lmMzMzMwAAAAAAAAAAACH+TlRoaXMgYXJ0IGlzIGluIHRoZSBwdWJsaWMgZG9tYWluLiBLZXZpbiBIdWdoZXMsIGtldmluaEBlaXQuY29tLCBTZXB0ZW1iZXIgMTk5NQAh+QQBAAACACwAAAAAFAAWAAADVCi63P4wyklZufjOErrvRcR9ZKYpxUB6aokGQyzHKxyO9RoTV54PPJyPBewNSUXhcWc8soJOIjTaSVJhVphWxd3CeILUbDwmgMPmtHrNIyxM8Iw7AQA7 + recorded_at: Wed, 30 Jul 2025 02:22:28 GMT recorded_with: VCR 6.2.0 diff --git a/spec/fixtures/vcr_cassettes/Testing_external_scripts_loaded_in_the_browser/loads_a_website.yml b/spec/fixtures/vcr_cassettes/Testing_external_scripts_loaded_in_the_browser/loads_a_website.yml index 9299ce7d1a..bc365ae21b 100644 --- a/spec/fixtures/vcr_cassettes/Testing_external_scripts_loaded_in_the_browser/loads_a_website.yml +++ b/spec/fixtures/vcr_cassettes/Testing_external_scripts_loaded_in_the_browser/loads_a_website.yml @@ -1,159 +1,5 @@ --- http_interactions: -- request: - method: get - uri: http://clients2.google.com/time/1/current?cup2hreq=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855&cup2key=9:s5jDVMfcuUIhrqbHhePH_nBoAC1CjxTn99-PT031EBI - body: - encoding: UTF-8 - string: '' - headers: - Proxy-Connection: - - keep-alive - Pragma: - - no-cache - Cache-Control: - - no-cache - User-Agent: - - Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/138.0.0.0 - Safari/537.36 - Accept-Encoding: - - '' - Connection: - - close - response: - status: - code: 200 - message: OK - headers: - Content-Type: - - application/json; charset=utf-8 - X-Content-Type-Options: - - nosniff - X-Cup-Server-Proof: - - 304402203592554ffcc4bc9ea59f39b1ac3f09be988e1987742afef742d1b40a6fe1cc56022057ef8ace9edc80e72939c136e2b29069e75cc3886817ddbae3b7a9a1fb025c98:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 - Cache-Control: - - no-cache, no-store, max-age=0, must-revalidate - Pragma: - - no-cache - Expires: - - Mon, 01 Jan 1990 00:00:00 GMT - Date: - - Fri, 11 Jul 2025 04:56:31 GMT - Content-Disposition: - - attachment; filename="json.txt"; filename*=UTF-8''json.txt - Cross-Origin-Resource-Policy: - - same-site - Cross-Origin-Opener-Policy: - - same-origin - Server: - - ESF - X-Xss-Protection: - - '0' - X-Frame-Options: - - SAMEORIGIN - Accept-Ranges: - - none - Vary: - - Sec-Fetch-Dest, Sec-Fetch-Mode, Sec-Fetch-Site,Accept-Encoding - Connection: - - close - Transfer-Encoding: - - chunked - body: - encoding: UTF-8 - string: |- - )]}' - {"current_time_millis":1752209791246,"server_nonce":3.589026436260661E-105} - recorded_at: Fri, 11 Jul 2025 04:56:31 GMT -- request: - method: post - uri: https://accounts.google.com/ListAccounts?gpsia=1&json=standard&source=ChromiumBrowser - body: - encoding: UTF-8 - string: " " - headers: - Connection: - - close - Content-Length: - - '1' - Origin: - - https://www.google.com - Content-Type: - - application/x-www-form-urlencoded - Sec-Fetch-Site: - - none - Sec-Fetch-Mode: - - no-cors - Sec-Fetch-Dest: - - empty - User-Agent: - - Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/138.0.0.0 - Safari/537.36 - Accept-Encoding: - - '' - Accept-Language: - - en-US,en;q=0.9 - response: - status: - code: 200 - message: OK - headers: - Content-Type: - - application/json; charset=utf-8 - Access-Control-Allow-Origin: - - https://www.google.com - Access-Control-Allow-Credentials: - - 'true' - X-Content-Type-Options: - - nosniff - Cache-Control: - - no-cache, no-store, max-age=0, must-revalidate - Pragma: - - no-cache - Expires: - - Mon, 01 Jan 1990 00:00:00 GMT - Date: - - Fri, 11 Jul 2025 04:56:32 GMT - Strict-Transport-Security: - - max-age=31536000; includeSubDomains - Cross-Origin-Opener-Policy: - - same-origin - Accept-Ch: - - Sec-CH-UA-Arch, Sec-CH-UA-Bitness, Sec-CH-UA-Full-Version, Sec-CH-UA-Full-Version-List, - Sec-CH-UA-Model, Sec-CH-UA-WoW64, Sec-CH-UA-Form-Factors, Sec-CH-UA-Platform, - Sec-CH-UA-Platform-Version - Content-Security-Policy: - - require-trusted-types-for 'script';report-uri /_/IdentityListAccountsHttp/cspreport - - script-src 'report-sample' 'nonce-DAr6J4mndfAlMG1hXXiENw' 'unsafe-inline';object-src - 'none';base-uri 'self';report-uri /_/IdentityListAccountsHttp/cspreport;worker-src - 'self' - - 'script-src ''unsafe-inline'' ''unsafe-eval'' blob: data: ''self'' https://apis.google.com - https://ssl.gstatic.com https://www.google.com https://www.googletagmanager.com - https://www.gstatic.com https://www.google-analytics.com;report-uri /_/IdentityListAccountsHttp/cspreport/allowlist' - - 'script-src ''unsafe-inline'' ''unsafe-eval'' blob: data:;report-uri /_/IdentityListAccountsHttp/cspreport/fine-allowlist' - Permissions-Policy: - - ch-ua-arch=*, ch-ua-bitness=*, ch-ua-full-version=*, ch-ua-full-version-list=*, - ch-ua-model=*, ch-ua-wow64=*, ch-ua-form-factors=*, ch-ua-platform=*, ch-ua-platform-version=* - Reporting-Endpoints: - - default="/_/IdentityListAccountsHttp/web-reports?context=eJzjEtHikmII0pBiOHxtB5Meyy0mIyAW4uFo2HzoMJvAjlX_JzMp6SblF8ZnpqTmlWSWVOZkFpckJifnl-aVFBenFpWlFsUbGRiZGpgbmOkZmMcXGAAAaV4b9A" - Server: - - ESF - X-Xss-Protection: - - '0' - Alt-Svc: - - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 - Accept-Ranges: - - none - Vary: - - Origin,Accept-Encoding - Connection: - - close - Transfer-Encoding: - - chunked - body: - encoding: UTF-8 - string: '["gaia.l.a.r",[]]' - recorded_at: Fri, 11 Jul 2025 04:56:32 GMT - request: method: get uri: https://deb.debian.org/debian/ @@ -196,7 +42,7 @@ http_interactions: Connection: - close Content-Length: - - '6121' + - '5820' Server: - Apache X-Content-Type-Options: @@ -214,23 +60,23 @@ http_interactions: Content-Type: - text/html;charset=UTF-8 Backend: - - 4qpvL1tJyeV1P6Tmf0Lj8g--F_skroutz_debian_backend_mirrors_debian_org + - 4qpvL1tJyeV1P6Tmf0Lj8g--F_accumu_debian_backend_mirrors_debian_org Via: - 1.1 varnish, 1.1 varnish Accept-Ranges: - bytes + Date: + - Wed, 30 Jul 2025 02:22:24 GMT Age: - '0' - Date: - - Fri, 11 Jul 2025 04:56:32 GMT X-Served-By: - - cache-ams21082-AMS, cache-mel11231-MEL + - cache-ams21082-AMS, cache-syd10146-SYD X-Cache: - - HIT, MISS + - HIT, HIT X-Cache-Hits: - - 2, 0 + - 4, 1 X-Timer: - - S1752209792.452248,VS0,VE236 + - S1753842144.042689,VS0,VE198 Vary: - Accept-Encoding X-Req-Url: @@ -251,25 +97,25 @@ http_interactions: [PARENTDIR]Parent Directory  - [   ]README2025-05-17 08:29 1.2K [   ]README.CD-manufacture2010-06-26 09:52 1.3K - [TXT]README.html2025-05-17 08:29 2.8K + [TXT]README.html2025-07-12 22:19 2.6K [TXT]README.mirrors.html2017-03-04 20:08 291 [TXT]README.mirrors.txt2017-03-04 20:08 86 - [DIR]dists/2025-05-17 08:29 - - [DIR]doc/2025-07-11 01:52 - - [   ]extrafiles2025-07-11 02:21 193K - [DIR]indices/2025-07-11 02:20 - - [   ]ls-lR.gz2025-07-11 02:14 15M + [DIR]dists/2025-07-22 17:15 - + [DIR]doc/2025-07-29 19:54 - + [   ]extrafiles2025-07-29 20:27 168K + [DIR]indices/2025-07-29 20:26 - + [   ]ls-lR.gz2025-07-29 20:23 12M [DIR]pool/2022-10-05 17:09 - [DIR]project/2008-11-17 23:05 - [DIR]tools/2012-10-10 16:29 - - [DIR]zzz-dists/2023-10-07 11:07 - + [DIR]zzz-dists/2025-07-12 22:20 -
Debian Archive - + @@ -285,13 +131,6 @@ http_interactions:
-
Debian 10.13, or buster
-
Debian 10.13 was released Saturday, 10th September 2022. - Installation - and upgrading instructions, - More information -
-
Debian 11.11, or bullseye
Debian 11.11 was released Saturday, 31st August 2024. Installation @@ -356,37 +195,29 @@ http_interactions: - recorded_at: Fri, 11 Jul 2025 04:56:32 GMT + recorded_at: Wed, 30 Jul 2025 02:22:24 GMT - request: method: get - uri: https://deb.debian.org/icons/compressed.gif + uri: http://deb.debian.org/debian/ body: encoding: UTF-8 string: '' headers: - Connection: - - close - Sec-Ch-Ua-Platform: - - '"Linux"' + Proxy-Connection: + - keep-alive + Upgrade-Insecure-Requests: + - '1' User-Agent: - Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/138.0.0.0 Safari/537.36 - Sec-Ch-Ua: - - '"Not)A;Brand";v="8", "Chromium";v="138"' - Sec-Ch-Ua-Mobile: - - "?0" Accept: - - image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8 - Sec-Fetch-Site: - - same-origin - Sec-Fetch-Mode: - - no-cors - Sec-Fetch-Dest: - - image + - text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Accept-Encoding: - '' Accept-Language: - en-US,en;q=0.9 + Connection: + - close response: status: code: 200 @@ -395,7 +226,7 @@ http_interactions: Connection: - close Content-Length: - - '1038' + - '5820' Server: - Apache X-Content-Type-Options: @@ -408,69 +239,167 @@ http_interactions: - '1' Permissions-Policy: - interest-cohort=() - Last-Modified: - - Sat, 20 Nov 2004 20:16:24 GMT - Etag: - - '"40e-3e9564c23b600"' X-Clacks-Overhead: - GNU Terry Pratchett Content-Type: - - image/gif + - text/html;charset=UTF-8 Backend: - - 4qpvL1tJyeV1P6Tmf0Lj8g--F_static_backend + - 4qpvL1tJyeV1P6Tmf0Lj8g--F_accumu_debian_backend_mirrors_debian_org + Via: + - 1.1 varnish, 1.1 varnish Accept-Ranges: - bytes Age: - '0' Date: - - Fri, 11 Jul 2025 04:56:34 GMT - Via: - - 1.1 varnish + - Wed, 30 Jul 2025 02:22:24 GMT X-Served-By: - - cache-mel11275-MEL + - cache-ams21082-AMS, cache-syd10179-SYD X-Cache: - - HIT + - HIT, MISS X-Cache-Hits: - - '0' + - 4, 0 X-Timer: - - S1752209794.092857,VS0,VE544 + - S1753842144.982706,VS0,VE258 + Vary: + - Accept-Encoding X-Req-Url: - - recv="/icons/compressed.gif",deliver="/icons/compressed.gif" + - recv="/debian/",deliver="/debian/" body: - encoding: ASCII-8BIT - string: !binary |- - R0lGODlhFAAWAOcAAP//////zP//mf//Zv//M///AP/M///MzP/Mmf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9mZv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8AM/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zMAMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz/8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZmZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkzZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZAGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA/2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPMzDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNmmTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMAZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDMMwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBmAAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0AALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACIAAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAARAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3d1VVVURERCIiIhEREQAAACH+TlRoaXMgYXJ0IGlzIGluIHRoZSBwdWJsaWMgZG9tYWluLiBLZXZpbiBIdWdoZXMsIGtldmluaEBlaXQuY29tLCBTZXB0ZW1iZXIgMTk5NQAh+QQBAAAkACwAAAAAFAAWAAAImQBJCCTBqmDBgQgTDmQFAABDVgojEmzI0KHEhBUrWrwoMGNDihwnAvjHiqRJjhX/qVz5D+VHAFZiWmmZ8BGHji9hxqTJ4ZFAmzc1vpxJgkPPn0Y5CP04M6lPEkCN5mxoJelRqFY5TM36NGrPqV67Op0KM6rYnkup/gMq1mdamC1tdn36lijUpwjr0pSoFyUrmTJLhiTBkqXCgAA7 - recorded_at: Fri, 11 Jul 2025 04:56:34 GMT + encoding: UTF-8 + string: | + + + + Index of /debian + + +

Index of /debian

+ + + + + + + + + + + + + + + + + + + +
[ICO]NameLast modifiedSize

[PARENTDIR]Parent Directory  -
[   ]README2025-05-17 08:29 1.2K
[   ]README.CD-manufacture2010-06-26 09:52 1.3K
[TXT]README.html2025-07-12 22:19 2.6K
[TXT]README.mirrors.html2017-03-04 20:08 291
[TXT]README.mirrors.txt2017-03-04 20:08 86
[DIR]dists/2025-07-22 17:15 -
[DIR]doc/2025-07-29 19:54 -
[   ]extrafiles2025-07-29 20:27 168K
[DIR]indices/2025-07-29 20:26 -
[   ]ls-lR.gz2025-07-29 20:23 12M
[DIR]pool/2022-10-05 17:09 -
[DIR]project/2008-11-17 23:05 -
[DIR]tools/2012-10-10 16:29 -
[DIR]zzz-dists/2025-07-12 22:20 -

+ + + + Debian Archive + + + + +

Debian Archive

+ +

See https://www.debian.org/ + for information about Debian GNU/Linux.

+ +

Current Releases

+ +

Four Debian releases are available on the main site:

+ +
+
+ +
Debian 11.11, or bullseye
+
Debian 11.11 was released Saturday, 31st August 2024. + Installation + and upgrading instructions, + More information +
+ +
Debian 12.11, or bookworm
+
Debian 12.11 was released Saturday, 17th May 2025. + Installation + and upgrading instructions, + More information +
+ +
Testing, or trixie
+
The current tested development snapshot is named trixie.
+ Packages which have been tested in unstable and passed automated + tests propagate to this release.
+ More information +
+ +
Unstable, or sid
+
The current development snapshot is named sid.
+ Untested candidate packages for future releases.
+ More information +
+
+
+ +

Old Releases

+ +

Older releases of Debian are at + http://archive.debian.org/debian-archive +
+ More information +

+ +

CDs

+ +

For more information about Debian CDs, please see + README.CD-manufacture. +
+ Further information +

+ +

Mirrors

+ +

For more information about Debian mirrors, please see + README.mirrors.html. +
+ Further information +

+ +

Other directories

+ + + + + +
doc Debian documentation.
indices Various indices of the site.
project Experimental packages and other miscellaneous files.
+ + + + + recorded_at: Wed, 30 Jul 2025 02:22:24 GMT - request: method: get - uri: https://deb.debian.org/icons/blank.gif + uri: http://deb.debian.org/icons/blank.gif body: encoding: UTF-8 string: '' headers: - Connection: - - close - Sec-Ch-Ua-Platform: - - '"Linux"' + Proxy-Connection: + - keep-alive User-Agent: - Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/138.0.0.0 Safari/537.36 - Sec-Ch-Ua: - - '"Not)A;Brand";v="8", "Chromium";v="138"' - Sec-Ch-Ua-Mobile: - - "?0" Accept: - image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8 - Sec-Fetch-Site: - - same-origin - Sec-Fetch-Mode: - - no-cors - Sec-Fetch-Dest: - - image Accept-Encoding: - '' Accept-Language: - en-US,en;q=0.9 + Connection: + - close response: status: code: 200 @@ -507,54 +436,44 @@ http_interactions: Age: - '0' Date: - - Fri, 11 Jul 2025 04:56:34 GMT + - Wed, 30 Jul 2025 02:22:24 GMT Via: - 1.1 varnish X-Served-By: - - cache-mel11256-MEL + - cache-syd10125-SYD X-Cache: - - HIT + - MISS X-Cache-Hits: - '0' X-Timer: - - S1752209794.092746,VS0,VE559 + - S1753842144.402396,VS0,VE414 X-Req-Url: - recv="/icons/blank.gif",deliver="/icons/blank.gif" body: encoding: ASCII-8BIT string: !binary |- R0lGODlhFAAWAKEAAP///8z//wAAAAAAACH+TlRoaXMgYXJ0IGlzIGluIHRoZSBwdWJsaWMgZG9tYWluLiBLZXZpbiBIdWdoZXMsIGtldmluaEBlaXQuY29tLCBTZXB0ZW1iZXIgMTk5NQAh+QQBAAABACwAAAAAFAAWAAACE4yPqcvtD6OctNqLs968+w+GSQEAOw== - recorded_at: Fri, 11 Jul 2025 04:56:34 GMT + recorded_at: Wed, 30 Jul 2025 02:22:24 GMT - request: method: get - uri: https://deb.debian.org/icons/back.gif + uri: http://deb.debian.org/icons/back.gif body: encoding: UTF-8 string: '' headers: - Connection: - - close - Sec-Ch-Ua-Platform: - - '"Linux"' + Proxy-Connection: + - keep-alive User-Agent: - Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/138.0.0.0 Safari/537.36 - Sec-Ch-Ua: - - '"Not)A;Brand";v="8", "Chromium";v="138"' - Sec-Ch-Ua-Mobile: - - "?0" Accept: - image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8 - Sec-Fetch-Site: - - same-origin - Sec-Fetch-Mode: - - no-cors - Sec-Fetch-Dest: - - image Accept-Encoding: - '' Accept-Language: - en-US,en;q=0.9 + Connection: + - close response: status: code: 200 @@ -591,54 +510,44 @@ http_interactions: Age: - '0' Date: - - Fri, 11 Jul 2025 04:56:34 GMT + - Wed, 30 Jul 2025 02:22:24 GMT Via: - 1.1 varnish X-Served-By: - - cache-mel11253-MEL + - cache-syd10140-SYD X-Cache: - - HIT + - MISS X-Cache-Hits: - '0' X-Timer: - - S1752209794.092777,VS0,VE544 + - S1753842144.402014,VS0,VE418 X-Req-Url: - recv="/icons/back.gif",deliver="/icons/back.gif" body: encoding: ASCII-8BIT string: !binary |- R0lGODlhFAAWAMIAAP///8z//5mZmWZmZjMzMwAAAAAAAAAAACH+TlRoaXMgYXJ0IGlzIGluIHRoZSBwdWJsaWMgZG9tYWluLiBLZXZpbiBIdWdoZXMsIGtldmluaEBlaXQuY29tLCBTZXB0ZW1iZXIgMTk5NQAh+QQBAAABACwAAAAAFAAWAAADSxi63P4jEPJqEDNTu6LO3PVpnDdOFnaCkHQGBTcqRRxuWG0v+5LrNUZQ8QPqeMakkaZsFihOpyDajMCoOoJAGNVWkt7QVfzokc+LBAA7 - recorded_at: Fri, 11 Jul 2025 04:56:34 GMT + recorded_at: Wed, 30 Jul 2025 02:22:24 GMT - request: method: get - uri: https://deb.debian.org/icons/hand.right.gif + uri: http://deb.debian.org/icons/hand.right.gif body: encoding: UTF-8 string: '' headers: - Connection: - - close - Sec-Ch-Ua-Platform: - - '"Linux"' + Proxy-Connection: + - keep-alive User-Agent: - Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/138.0.0.0 Safari/537.36 - Sec-Ch-Ua: - - '"Not)A;Brand";v="8", "Chromium";v="138"' - Sec-Ch-Ua-Mobile: - - "?0" Accept: - image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8 - Sec-Fetch-Site: - - same-origin - Sec-Fetch-Mode: - - no-cors - Sec-Fetch-Dest: - - image Accept-Encoding: - '' Accept-Language: - en-US,en;q=0.9 + Connection: + - close response: status: code: 200 @@ -675,54 +584,44 @@ http_interactions: Age: - '0' Date: - - Fri, 11 Jul 2025 04:56:34 GMT + - Wed, 30 Jul 2025 02:22:24 GMT Via: - 1.1 varnish X-Served-By: - - cache-mel11263-MEL + - cache-syd10160-SYD X-Cache: - - HIT + - MISS X-Cache-Hits: - '0' X-Timer: - - S1752209794.137505,VS0,VE543 + - S1753842144.402195,VS0,VE417 X-Req-Url: - recv="/icons/hand.right.gif",deliver="/icons/hand.right.gif" body: encoding: ASCII-8BIT string: !binary |- R0lGODlhFAAWAMIAAP/////Mmcz//5lmMwAAAAAAAAAAAAAAACH+TlRoaXMgYXJ0IGlzIGluIHRoZSBwdWJsaWMgZG9tYWluLiBLZXZpbiBIdWdoZXMsIGtldmluaEBlaXQuY29tLCBTZXB0ZW1iZXIgMTk5NQAh+QQBAAACACwAAAAAFAAWAAADTCi63P4wykkdubiSwDuRVydi5CWEYjBsKbe2rDjMdMwRw1iaaZx7jcDm8nOpVsFjsSh0CFuq46fxko0eKOtsiu0UuRHfVlOqmM9oSgIAOw== - recorded_at: Fri, 11 Jul 2025 04:56:34 GMT + recorded_at: Wed, 30 Jul 2025 02:22:24 GMT - request: method: get - uri: https://deb.debian.org/icons/text.gif + uri: http://deb.debian.org/icons/text.gif body: encoding: UTF-8 string: '' headers: - Connection: - - close - Sec-Ch-Ua-Platform: - - '"Linux"' + Proxy-Connection: + - keep-alive User-Agent: - Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/138.0.0.0 Safari/537.36 - Sec-Ch-Ua: - - '"Not)A;Brand";v="8", "Chromium";v="138"' - Sec-Ch-Ua-Mobile: - - "?0" Accept: - image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8 - Sec-Fetch-Site: - - same-origin - Sec-Fetch-Mode: - - no-cors - Sec-Fetch-Dest: - - image Accept-Encoding: - '' Accept-Language: - en-US,en;q=0.9 + Connection: + - close response: status: code: 200 @@ -759,138 +658,44 @@ http_interactions: Age: - '0' Date: - - Fri, 11 Jul 2025 04:56:34 GMT + - Wed, 30 Jul 2025 02:22:25 GMT Via: - 1.1 varnish X-Served-By: - - cache-mel11266-MEL + - cache-syd10179-SYD X-Cache: - - HIT + - MISS X-Cache-Hits: - '0' X-Timer: - - S1752209794.137616,VS0,VE546 + - S1753842144.431907,VS0,VE589 X-Req-Url: - recv="/icons/text.gif",deliver="/icons/text.gif" body: encoding: ASCII-8BIT string: !binary |- R0lGODlhFAAWAMIAAP///8z//5mZmTMzMwAAAAAAAAAAAAAAACH+TlRoaXMgYXJ0IGlzIGluIHRoZSBwdWJsaWMgZG9tYWluLiBLZXZpbiBIdWdoZXMsIGtldmluaEBlaXQuY29tLCBTZXB0ZW1iZXIgMTk5NQAh+QQBAAABACwAAAAAFAAWAAADWDi6vPEwDECrnSO+aTvPEddVIriN1wVxROtSxBDPJwq7bo23luALhJqt8gtKbrsXBSgcEo2spBLAPDp7UKT02bxWRdrp94rtbpdZMrrr/A5+8LhPFpHajQkAOw== - recorded_at: Fri, 11 Jul 2025 04:56:34 GMT + recorded_at: Wed, 30 Jul 2025 02:22:25 GMT - request: method: get - uri: https://deb.debian.org/icons/unknown.gif + uri: http://deb.debian.org/icons/folder.gif body: encoding: UTF-8 string: '' headers: - Connection: - - close - Sec-Ch-Ua-Platform: - - '"Linux"' + Proxy-Connection: + - keep-alive User-Agent: - Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/138.0.0.0 Safari/537.36 - Sec-Ch-Ua: - - '"Not)A;Brand";v="8", "Chromium";v="138"' - Sec-Ch-Ua-Mobile: - - "?0" Accept: - image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8 - Sec-Fetch-Site: - - same-origin - Sec-Fetch-Mode: - - no-cors - Sec-Fetch-Dest: - - image Accept-Encoding: - '' Accept-Language: - en-US,en;q=0.9 - response: - status: - code: 200 - message: OK - headers: Connection: - close - Content-Length: - - '245' - Server: - - Apache - X-Content-Type-Options: - - nosniff - X-Frame-Options: - - sameorigin - Referrer-Policy: - - no-referrer - X-Xss-Protection: - - '1' - Permissions-Policy: - - interest-cohort=() - Last-Modified: - - Sat, 20 Nov 2004 20:16:24 GMT - Etag: - - '"f5-3e9564c23b600"' - X-Clacks-Overhead: - - GNU Terry Pratchett - Content-Type: - - image/gif - Backend: - - 4qpvL1tJyeV1P6Tmf0Lj8g--F_static_backend - Accept-Ranges: - - bytes - Age: - - '0' - Date: - - Fri, 11 Jul 2025 04:56:34 GMT - Via: - - 1.1 varnish - X-Served-By: - - cache-mel11261-MEL - X-Cache: - - HIT - X-Cache-Hits: - - '0' - X-Timer: - - S1752209794.137743,VS0,VE554 - X-Req-Url: - - recv="/icons/unknown.gif",deliver="/icons/unknown.gif" - body: - encoding: ASCII-8BIT - string: !binary |- - R0lGODlhFAAWAMIAAP///8z//5mZmTMzMwAAAAAAAAAAAAAAACH+TlRoaXMgYXJ0IGlzIGluIHRoZSBwdWJsaWMgZG9tYWluLiBLZXZpbiBIdWdoZXMsIGtldmluaEBlaXQuY29tLCBTZXB0ZW1iZXIgMTk5NQAh+QQBAAABACwAAAAAFAAWAAADaDi6vPEwDECrnSO+aTvPEQcIAmGaIrhR5XmKgMq1LkoMN7ECrjDWp52r0iPpJJ0KjUAq7SxLE+sI+9V8vycFiM0iLb2O80s8JcfVJJTaGYrZYPNby5Ov6WolPD+XDJqAgSQ4EUCGQQEJADs= - recorded_at: Fri, 11 Jul 2025 04:56:34 GMT -- request: - method: get - uri: https://deb.debian.org/icons/folder.gif - body: - encoding: UTF-8 - string: '' - headers: - Connection: - - close - Sec-Ch-Ua-Platform: - - '"Linux"' - User-Agent: - - Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/138.0.0.0 - Safari/537.36 - Sec-Ch-Ua: - - '"Not)A;Brand";v="8", "Chromium";v="138"' - Sec-Ch-Ua-Mobile: - - "?0" - Accept: - - image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8 - Sec-Fetch-Site: - - same-origin - Sec-Fetch-Mode: - - no-cors - Sec-Fetch-Dest: - - image - Accept-Encoding: - - '' - Accept-Language: - - en-US,en;q=0.9 response: status: code: 200 @@ -927,22 +732,170 @@ http_interactions: Age: - '0' Date: - - Fri, 11 Jul 2025 04:56:35 GMT + - Wed, 30 Jul 2025 02:22:25 GMT Via: - 1.1 varnish X-Served-By: - - cache-mel11253-MEL + - cache-syd10182-SYD X-Cache: - - HIT + - MISS X-Cache-Hits: - '0' X-Timer: - - S1752209795.922267,VS0,VE545 + - S1753842144.432033,VS0,VE589 X-Req-Url: - recv="/icons/folder.gif",deliver="/icons/folder.gif" body: encoding: ASCII-8BIT string: !binary |- R0lGODlhFAAWAMIAAP/////Mmcz//5lmMzMzMwAAAAAAAAAAACH+TlRoaXMgYXJ0IGlzIGluIHRoZSBwdWJsaWMgZG9tYWluLiBLZXZpbiBIdWdoZXMsIGtldmluaEBlaXQuY29tLCBTZXB0ZW1iZXIgMTk5NQAh+QQBAAACACwAAAAAFAAWAAADVCi63P4wyklZufjOErrvRcR9ZKYpxUB6aokGQyzHKxyO9RoTV54PPJyPBewNSUXhcWc8soJOIjTaSVJhVphWxd3CeILUbDwmgMPmtHrNIyxM8Iw7AQA7 - recorded_at: Fri, 11 Jul 2025 04:56:35 GMT + recorded_at: Wed, 30 Jul 2025 02:22:25 GMT +- request: + method: get + uri: http://deb.debian.org/icons/unknown.gif + body: + encoding: UTF-8 + string: '' + headers: + Proxy-Connection: + - keep-alive + User-Agent: + - Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/138.0.0.0 + Safari/537.36 + Accept: + - image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8 + Accept-Encoding: + - '' + Accept-Language: + - en-US,en;q=0.9 + Connection: + - close + response: + status: + code: 200 + message: OK + headers: + Connection: + - close + Content-Length: + - '245' + Server: + - Apache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - sameorigin + Referrer-Policy: + - no-referrer + X-Xss-Protection: + - '1' + Permissions-Policy: + - interest-cohort=() + Last-Modified: + - Sat, 20 Nov 2004 20:16:24 GMT + Etag: + - '"f5-3e9564c23b600"' + X-Clacks-Overhead: + - GNU Terry Pratchett + Content-Type: + - image/gif + Backend: + - 4qpvL1tJyeV1P6Tmf0Lj8g--F_static_backend + Accept-Ranges: + - bytes + Age: + - '0' + Date: + - Wed, 30 Jul 2025 02:22:25 GMT + Via: + - 1.1 varnish + X-Served-By: + - cache-syd10136-SYD + X-Cache: + - MISS + X-Cache-Hits: + - '0' + X-Timer: + - S1753842144.402202,VS0,VE623 + X-Req-Url: + - recv="/icons/unknown.gif",deliver="/icons/unknown.gif" + body: + encoding: ASCII-8BIT + string: !binary |- + R0lGODlhFAAWAMIAAP///8z//5mZmTMzMwAAAAAAAAAAAAAAACH+TlRoaXMgYXJ0IGlzIGluIHRoZSBwdWJsaWMgZG9tYWluLiBLZXZpbiBIdWdoZXMsIGtldmluaEBlaXQuY29tLCBTZXB0ZW1iZXIgMTk5NQAh+QQBAAABACwAAAAAFAAWAAADaDi6vPEwDECrnSO+aTvPEQcIAmGaIrhR5XmKgMq1LkoMN7ECrjDWp52r0iPpJJ0KjUAq7SxLE+sI+9V8vycFiM0iLb2O80s8JcfVJJTaGYrZYPNby5Ov6WolPD+XDJqAgSQ4EUCGQQEJADs= + recorded_at: Wed, 30 Jul 2025 02:22:25 GMT +- request: + method: get + uri: http://deb.debian.org/icons/compressed.gif + body: + encoding: UTF-8 + string: '' + headers: + Proxy-Connection: + - keep-alive + User-Agent: + - Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/138.0.0.0 + Safari/537.36 + Accept: + - image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8 + Accept-Encoding: + - '' + Accept-Language: + - en-US,en;q=0.9 + Connection: + - close + response: + status: + code: 200 + message: OK + headers: + Connection: + - close + Content-Length: + - '1038' + Server: + - Apache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - sameorigin + Referrer-Policy: + - no-referrer + X-Xss-Protection: + - '1' + Permissions-Policy: + - interest-cohort=() + Last-Modified: + - Sat, 20 Nov 2004 20:16:24 GMT + Etag: + - '"40e-3e9564c23b600"' + X-Clacks-Overhead: + - GNU Terry Pratchett + Content-Type: + - image/gif + Backend: + - 4qpvL1tJyeV1P6Tmf0Lj8g--F_static_backend + Accept-Ranges: + - bytes + Age: + - '0' + Date: + - Wed, 30 Jul 2025 02:22:25 GMT + Via: + - 1.1 varnish + X-Served-By: + - cache-syd10169-SYD + X-Cache: + - MISS + X-Cache-Hits: + - '0' + X-Timer: + - S1753842145.932677,VS0,VE585 + X-Req-Url: + - recv="/icons/compressed.gif",deliver="/icons/compressed.gif" + body: + encoding: ASCII-8BIT + string: !binary |- + R0lGODlhFAAWAOcAAP//////zP//mf//Zv//M///AP/M///MzP/Mmf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9mZv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8AM/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zMAMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz/8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZmZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkzZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZAGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA/2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPMzDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNmmTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMAZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDMMwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBmAAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0AALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACIAAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAARAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3d1VVVURERCIiIhEREQAAACH+TlRoaXMgYXJ0IGlzIGluIHRoZSBwdWJsaWMgZG9tYWluLiBLZXZpbiBIdWdoZXMsIGtldmluaEBlaXQuY29tLCBTZXB0ZW1iZXIgMTk5NQAh+QQBAAAkACwAAAAAFAAWAAAImQBJCCTBqmDBgQgTDmQFAABDVgojEmzI0KHEhBUrWrwoMGNDihwnAvjHiqRJjhX/qVz5D+VHAFZiWmmZ8BGHji9hxqTJ4ZFAmzc1vpxJgkPPn0Y5CP04M6lPEkCN5mxoJelRqFY5TM36NGrPqV67Op0KM6rYnkup/gMq1mdamC1tdn36lijUpwjr0pSoFyUrmTJLhiTBkqXCgAA7 + recorded_at: Wed, 30 Jul 2025 02:22:25 GMT recorded_with: VCR 6.2.0 diff --git a/spec/support/vcr_setup.rb b/spec/support/vcr_setup.rb index ad44f367fa..df67e7d69d 100644 --- a/spec/support/vcr_setup.rb +++ b/spec/support/vcr_setup.rb @@ -5,9 +5,17 @@ require 'vcr' VCR.configure do |config| config.cassette_library_dir = "spec/fixtures/vcr_cassettes" config.hook_into :webmock - config.ignore_localhost = true config.configure_rspec_metadata! - config.ignore_localhost = true + + # Chrome calls a lot of services and they trip us up. + config.ignore_hosts( + "localhost", "127.0.0.1", "0.0.0.0", + "accounts.google.com", + "android.clients.google.com", + "clients2.google.com", + "content-autofill.googleapis.com", + "optimizationguide-pa.googleapis.com", + ) # Filter sensitive environment variables %w[ From f3dfbab109d54e545fba53fd71d09beae407594b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Jul 2025 10:00:39 +0000 Subject: [PATCH 17/76] Bump @floating-ui/dom from 1.7.2 to 1.7.3 Bumps [@floating-ui/dom](https://github.com/floating-ui/floating-ui/tree/HEAD/packages/dom) from 1.7.2 to 1.7.3. - [Release notes](https://github.com/floating-ui/floating-ui/releases) - [Changelog](https://github.com/floating-ui/floating-ui/blob/master/packages/dom/CHANGELOG.md) - [Commits](https://github.com/floating-ui/floating-ui/commits/@floating-ui/dom@1.7.3/packages/dom) --- updated-dependencies: - dependency-name: "@floating-ui/dom" dependency-version: 1.7.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 6510b2fae4..737c3aa200 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "pretty-quick": "pretty-quick" }, "dependencies": { - "@floating-ui/dom": "^1.7.2", + "@floating-ui/dom": "^1.7.3", "@hotwired/stimulus": "^3.2", "@hotwired/turbo": "^8.0.13", "@rails/webpacker": "5.4.4", diff --git a/yarn.lock b/yarn.lock index 2a69841d36..9b0dfe8478 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1127,19 +1127,19 @@ resolved "https://registry.yarnpkg.com/@csstools/convert-colors/-/convert-colors-1.4.0.tgz#ad495dc41b12e75d588c6db8b9834f08fa131eb7" integrity sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw== -"@floating-ui/core@^1.7.2": - version "1.7.2" - resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.7.2.tgz#3d1c35263950b314b6d5a72c8bfb9e3c1551aefd" - integrity sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw== +"@floating-ui/core@^1.7.3": + version "1.7.3" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.7.3.tgz#462d722f001e23e46d86fd2bd0d21b7693ccb8b7" + integrity sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w== dependencies: "@floating-ui/utils" "^0.2.10" -"@floating-ui/dom@^1.7.2": - version "1.7.2" - resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.7.2.tgz#3540b051cf5ce0d4f4db5fb2507a76e8ea5b4a45" - integrity sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA== +"@floating-ui/dom@^1.7.3": + version "1.7.3" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.7.3.tgz#6174ac3409e6a064bbdf1f4bb07188ee9461f8cf" + integrity sha512-uZA413QEpNuhtb3/iIKoYMSK07keHPYeXF02Zhd6e213j+d1NamLix/mCLxBUDW/Gx52sPH2m+chlUsyaBs/Ag== dependencies: - "@floating-ui/core" "^1.7.2" + "@floating-ui/core" "^1.7.3" "@floating-ui/utils" "^0.2.10" "@floating-ui/utils@^0.2.10": From 3363c523ea583601e22ecf079cf0f82112abcfbb Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Tue, 29 Jul 2025 11:52:10 +1000 Subject: [PATCH 18/76] Check more code for coverage * ApplicationJob should be covered by tests. * Spec should all be executed, except `xit` which should be avoided and can be flagged. --- .simplecov | 2 -- 1 file changed, 2 deletions(-) diff --git a/.simplecov b/.simplecov index 66573a094d..3298884377 100755 --- a/.simplecov +++ b/.simplecov @@ -4,10 +4,8 @@ SimpleCov.start 'rails' do add_filter '/bin/' add_filter '/config/' - add_filter '/jobs/application_job.rb' add_filter '/schemas/' add_filter '/lib/generators' - add_filter '/spec/' add_filter '/vendor/' add_filter '/public' add_filter '/swagger' From 76a1fe7767f9e256a938a6bfa1d49e53f8bea81d Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Thu, 31 Jul 2025 14:54:34 +1000 Subject: [PATCH 19/76] Ignore inaccurate coverage of rake tasks I tried several ways to get code coverage for rake tasks but I haven't succeeded yet. Somehow rake is confusing simplecov. --- .simplecov | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.simplecov b/.simplecov index 3298884377..dcfe2822f6 100755 --- a/.simplecov +++ b/.simplecov @@ -12,7 +12,9 @@ SimpleCov.start 'rails' do add_filter '/script' add_filter '/log' add_filter '/db' - add_filter '/lib/tasks/sample_data/' + + # We haven't managed to make simplecov recognise rake coverage accurately. + add_filter '/lib/tasks/' formatter SimpleCov::Formatter::SimpleFormatter end From 06867ff7eaffd4f01842deb791f1014de275cd38 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Tue, 29 Jul 2025 11:56:54 +1000 Subject: [PATCH 20/76] Remove unnecessary simplecov filters * /schemas doesn't exist. * /lib/generators doesn't exist. * /vendor doesn't contain rb files. * /public doesn't contain rb files. * /swagger doesn't contain rb files. * /log doens't contain rb files. --- .simplecov | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.simplecov b/.simplecov index dcfe2822f6..dd47f33e21 100755 --- a/.simplecov +++ b/.simplecov @@ -4,13 +4,7 @@ SimpleCov.start 'rails' do add_filter '/bin/' add_filter '/config/' - add_filter '/schemas/' - add_filter '/lib/generators' - add_filter '/vendor/' - add_filter '/public' - add_filter '/swagger' add_filter '/script' - add_filter '/log' add_filter '/db' # We haven't managed to make simplecov recognise rake coverage accurately. From d72bc494098a9f75f314e8a231958b7cfda4583e Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Tue, 29 Jul 2025 12:09:59 +1000 Subject: [PATCH 21/76] Compare coverage to upstream master when on fork --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f4a97638a9..c6711bb01a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -410,6 +410,6 @@ jobs: include-hidden-files: true - name: Compare SimpleCov results with Undercover run: | - git fetch --no-tags origin master:master + git fetch --no-tags origin ${{ github.event.pull_request.base.ref }}:master bundle exec undercover if: ${{ github.ref != 'refs/heads/master' }} # Does not run on master, as we can't fetch master in the master branch From c0924fbe5ee53d85ff66264674a12e8c71b53529 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 30 Jul 2025 12:49:27 +1000 Subject: [PATCH 22/76] Use new Undercover formatter for :nocov: support --- .rubocop_styleguide.yml | 1 + .undercover | 3 +-- Gemfile | 2 -- Gemfile.lock | 3 --- lib/tasks/simplecov.rake | 5 ++--- spec/lib/tasks/simplecov_spec.rb | 4 ++++ 6 files changed, 8 insertions(+), 10 deletions(-) diff --git a/.rubocop_styleguide.yml b/.rubocop_styleguide.yml index b82dbada1e..91c745cdd9 100644 --- a/.rubocop_styleguide.yml +++ b/.rubocop_styleguide.yml @@ -16,6 +16,7 @@ AllCops: - node_modules/**/* # Excluding: inadequate Naming/FileName rule rejects GemFile name with camelcase - engines/web/Gemfile + - .undercover Bundler/DuplicatedGem: Enabled: false diff --git a/.undercover b/.undercover index 95a9fe08c9..9b7d04fdb9 100644 --- a/.undercover +++ b/.undercover @@ -1,5 +1,4 @@ #!/bin/env ruby # frozen_string_literal: true --l coverage/lcov/openfoodnetwork.lcov --c master \ No newline at end of file +-c master diff --git a/Gemfile b/Gemfile index 342f6ec61e..a10912faba 100644 --- a/Gemfile +++ b/Gemfile @@ -172,8 +172,6 @@ end group :test do gem 'pdf-reader' gem 'rails-controller-testing' - gem 'simplecov', require: false - gem 'simplecov-lcov', require: false gem 'undercover', require: false gem 'vcr', require: false gem 'webmock', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 321fcbd450..60fc57e87f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -746,7 +746,6 @@ GEM simplecov-html (~> 0.11) simplecov_json_formatter (~> 0.1) simplecov-html (0.12.3) - simplecov-lcov (0.8.0) simplecov_json_formatter (0.1.4) spreadsheet_architect (5.0.0) caxlsx (>= 3.3.0, < 4) @@ -991,8 +990,6 @@ DEPENDENCIES shoulda-matchers sidekiq sidekiq-scheduler - simplecov - simplecov-lcov spreadsheet_architect spring spring-commands-rspec diff --git a/lib/tasks/simplecov.rake b/lib/tasks/simplecov.rake index d5bc344f06..cc7f5c95e6 100644 --- a/lib/tasks/simplecov.rake +++ b/lib/tasks/simplecov.rake @@ -5,7 +5,7 @@ namespace :simplecov do task :collate_results, # rubocop:disable Rails/RakeEnvironment doesn't need the full env [:path_to_results, :coverage_dir] do |_t, args| require "simplecov" - require "simplecov-lcov" + require "undercover/simplecov_formatter" path_to_results = args[:path_to_results].presence || "tmp/simplecov" output_path = args[:coverage_dir].presence || "coverage" @@ -15,9 +15,8 @@ namespace :simplecov do coverage_dir(output_path) end - SimpleCov::Formatter::LcovFormatter.config.report_with_single_file = true SimpleCov.collate Dir[File.join(path_to_results, "**", ".resultset.json")], "rails" do - formatter(SimpleCov::Formatter::LcovFormatter) + formatter(SimpleCov::Formatter::Undercover) coverage_dir(output_path) end end diff --git a/spec/lib/tasks/simplecov_spec.rb b/spec/lib/tasks/simplecov_spec.rb index 68e9d0bc5e..62204b12ef 100644 --- a/spec/lib/tasks/simplecov_spec.rb +++ b/spec/lib/tasks/simplecov_spec.rb @@ -26,6 +26,10 @@ RSpec.describe "simplecov.rake" do and change { File.exist?(File.join(output_dir, "index.html")) }. from(false). + to(true). + + and change { File.exist?(File.join(output_dir, "coverage.json")) }. + from(false). to(true) end end From 75b2fe1dd472655b9e79c50e1a79ed5fd305e11c Mon Sep 17 00:00:00 2001 From: Ahmed Ejaz Date: Fri, 1 Aug 2025 01:48:21 +0500 Subject: [PATCH 23/76] revert API removals --- app/controllers/api/v0/products_controller.rb | 27 +++ config/routes/api.rb | 3 + .../api/v0/products_controller_spec.rb | 176 ++++++++++++++++++ 3 files changed, 206 insertions(+) diff --git a/app/controllers/api/v0/products_controller.rb b/app/controllers/api/v0/products_controller.rb index 1ba016cc5a..6f95e1e64e 100644 --- a/app/controllers/api/v0/products_controller.rb +++ b/app/controllers/api/v0/products_controller.rb @@ -38,12 +38,39 @@ module Api end end + def destroy + authorize! :delete, Spree::Product + @product = product_finder.find_product + authorize! :delete, @product + @product.destroyed_by = current_api_user + @product.destroy + head :no_content + end + + def bulk_products + @products = product_finder.bulk_products + + render_paged_products @products + end + def overridable @products = product_finder.products_for_producers render_paged_products @products, ::Api::Admin::ProductSimpleSerializer end + # POST /api/products/:product_id/clone + # + def clone + authorize! :create, Spree::Product + original_product = product_finder.find_product_to_be_cloned + authorize! :update, original_product + + @product = original_product.duplicate + + render json: @product, serializer: Api::Admin::ProductSerializer, status: :created + end + private def product_finder diff --git a/config/routes/api.rb b/config/routes/api.rb index 725d022b98..30bd7cd1b1 100644 --- a/config/routes/api.rb +++ b/config/routes/api.rb @@ -16,6 +16,7 @@ Openfoodnetwork::Application.routes.draw do namespace :v0 do resources :products do collection do + get :bulk_products get :overridable end post :clone @@ -74,6 +75,8 @@ Openfoodnetwork::Application.routes.draw do resources :enterprise_fees, only: [:destroy] + post '/product_images/:product_id', to: 'product_images#update_product_image' + resources :states, :only => [:index, :show] resources :taxons, except: %i[show edit] diff --git a/spec/controllers/api/v0/products_controller_spec.rb b/spec/controllers/api/v0/products_controller_spec.rb index 09dab8cc70..8150cf9332 100644 --- a/spec/controllers/api/v0/products_controller_spec.rb +++ b/spec/controllers/api/v0/products_controller_spec.rb @@ -62,6 +62,33 @@ RSpec.describe Api::V0::ProductsController do api_put :update, id: product.to_param, product: { name: "I hacked your store!" } assert_unauthorized! end + + it "cannot delete a product" do + api_delete :destroy, id: product.to_param + assert_unauthorized! + end + end + + context "as an enterprise user" do + let(:current_api_user) { supplier_enterprise_user(supplier) } + + it "can delete my product" do + expect(product.deleted_at).to be_nil + api_delete :destroy, id: product.to_param + + expect(response).to have_http_status(:no_content) + expect { product.reload }.not_to raise_error + expect(product.deleted_at).not_to be_nil + end + + it "is denied access to deleting another enterprises' product" do + expect(product_other_supplier.deleted_at).to be_nil + api_delete :destroy, id: product_other_supplier.to_param + + assert_unauthorized! + expect { product_other_supplier.reload }.not_to raise_error + expect(product_other_supplier.deleted_at).to be_nil + end end context "as an administrator" do @@ -110,6 +137,155 @@ RSpec.describe Api::V0::ProductsController do expect(json_response["error"]).to eq("Invalid resource. Please fix errors and try again.") expect(json_response["errors"]["name"]).to eq(["can't be blank"]) end + + it "can delete a product" do + expect(product.deleted_at).to be_nil + api_delete :destroy, id: product.to_param + + expect(response).to have_http_status(:no_content) + expect(product.reload.deleted_at).not_to be_nil + end + end + + describe '#clone' do + context 'as a normal user' do + before do + allow(current_api_user) + .to receive(:admin?).and_return(false) + end + + it 'denies access' do + spree_post :clone, product_id: product.id, format: :json + + assert_unauthorized! + end + end + + context 'as an enterprise user' do + let(:current_api_user) { supplier_enterprise_user(supplier) } + let!(:variant) { create(:variant, product_id: product.id) } + + it 'responds with a successful response' do + spree_post :clone, product_id: product.id, format: :json + + expect(response).to have_http_status(:created) + end + + it 'clones the product' do + spree_post :clone, product_id: product.id, format: :json + + expect(json_response['name']).to eq("COPY OF #{product.name}") + end + + it 'clones a product with image' do + spree_post :clone, product_id: product_with_image.id, format: :json + + expect(response).to have_http_status(:created) + expect(json_response['name']).to eq("COPY OF #{product_with_image.name}") + end + + # test cases related to bug #660: product duplication clones master variant + + # stock info - clone is set to zero + it '(does not) clone the stock info of the product' do + spree_post :clone, product_id: product.id, format: :json + expect(json_response.dig("variants", 0, "on_hand")).to eq(0) + end + + # variants: only the master variant of the product is cloned + it '(does not) clone variants from a product with several variants' do + spree_post :clone, product_id: product.id, format: :json + expect(Spree::Product.second.variants.count).not_to eq Spree::Product.first.variants.count + end + end + + context 'as an administrator' do + before do + allow(current_api_user) + .to receive(:admin?).and_return(true) + end + + it 'responds with a successful response' do + spree_post :clone, product_id: product.id, format: :json + + expect(response).to have_http_status(:created) + end + + it 'clones the product' do + spree_post :clone, product_id: product.id, format: :json + + expect(json_response['name']).to eq("COPY OF #{product.name}") + end + + it 'clones a product with image' do + spree_post :clone, product_id: product_with_image.id, format: :json + + expect(response).to have_http_status(:created) + expect(json_response['name']).to eq("COPY OF #{product_with_image.name}") + end + end + end + + describe '#bulk_products' do + context "as an enterprise user" do + let!(:taxon) { create(:taxon) } + let!(:product2) { create(:product, supplier_id: supplier.id, primary_taxon: taxon) } + let!(:product3) { create(:product, supplier_id: supplier2.id, primary_taxon: taxon) } + let!(:product4) { create(:product, supplier_id: supplier2.id) } + let(:current_api_user) { supplier_enterprise_user(supplier) } + + before { current_api_user.enterprise_roles.create(enterprise: supplier2) } + + it "returns a list of products" do + api_get :bulk_products, { page: 1, per_page: 15 }, format: :json + expect(returned_product_ids).to eq [product4.id, product3.id, product2.id, + other_product.id, product.id] + end + + it "returns pagination data" do + api_get :bulk_products, { page: 1, per_page: 15 }, format: :json + expect(json_response['pagination']).to eq "results" => 5, "pages" => 1, "page" => 1, + "per_page" => 15 + end + + it "uses defaults when page and per_page are not supplied" do + api_get :bulk_products, format: :json + expect(json_response['pagination']).to eq "results" => 5, "pages" => 1, "page" => 1, + "per_page" => 15 + end + + it "returns paginated products by page" do + api_get :bulk_products, { page: 1, per_page: 2 }, format: :json + expect(returned_product_ids).to eq [product4.id, product3.id] + + api_get :bulk_products, { page: 2, per_page: 2 }, format: :json + expect(returned_product_ids).to eq [product2.id, other_product.id] + end + + it "filters results by supplier" do + api_get :bulk_products, + { page: 1, per_page: 15, q: { variants_supplier_id_eq: supplier.id } }, + format: :json + expect(returned_product_ids).to eq [product2.id, other_product.id, product.id] + end + + it "filters results by product category" do + api_get :bulk_products, + { page: 1, per_page: 15, q: { variants_primary_taxon_id_eq: taxon.id } }, + format: :json + expect(returned_product_ids).to eq [product3.id, product2.id] + end + + it "filters results by import_date" do + product.variants.first.update_attribute :import_date, 1.day.ago + product2.variants.first.update_attribute :import_date, 2.days.ago + product3.variants.first.update_attribute :import_date, 1.day.ago + + api_get :bulk_products, { page: 1, per_page: 15, import_date: 1.day.ago.to_date.to_s }, + format: :json + expect(returned_product_ids).to eq [product3.id, product.id] + end + end end private From bb7a31b286c82b49a558f9513df2593d1a087c6c Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Fri, 1 Aug 2025 12:37:40 +1000 Subject: [PATCH 24/76] Update all locales with the latest Transifex translations --- config/locales/hu.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/config/locales/hu.yml b/config/locales/hu.yml index cb72740645..d79fdae3b2 100644 --- a/config/locales/hu.yml +++ b/config/locales/hu.yml @@ -922,7 +922,7 @@ hu: not_updatable: nem frissíthető a meglévő termékeken termékimporttal values_must_be_same: azonos név alatt azonos terméknek kell szerepelnie blank: nem lehet üres - products_no_permission: nincs engedélye a vállalkozás termékeinek kezelésére + products_no_permission: nincs engedélyed a vállalkozás termékeinek kezelésére inventory_no_permission: nincs engedélye készlet létrehozására ehhez a termelőhöz none_saved: egyetlen terméket sem mentett sikeresen line_number: "%{number}. sor:" @@ -1011,7 +1011,7 @@ hu: producer: Termelő sku: SKU name: Név - display_name: Megjelenítendő név + display_name: Alternatív név category: Kategória description: Leírás units: Egységek @@ -2397,11 +2397,11 @@ hu: set_a_password: "Ezután a rendszer kéri, hogy állítson be egy jelszót, mielőtt felügyelheti a vállalkozást." mistakenly_sent: "Nem tudod, miért kaptad ezt az e-mailt? További információért fordulj %{owner_email}-hoz." producer_mail_greeting: "Kedves" - producer_mail_text_before: "Alább megtalálja a rendelési ciklusra vonatkozó frissítést:" - producer_mail_order_text: "Íme a termékei megrendelésének összefoglalása:" + producer_mail_text_before: "Küldjük az alábbi rendelési ciklusra vonatkozó összesítést:" + producer_mail_order_text: "Megrendelt termékek:" producer_mail_delivery_instructions: "Raktári átvételi/szállítási instrukciók:" - producer_mail_signoff: "Köszönet és jókívánságok" - producer_mail_order_customer_text: "Itt található a rendelések összegzése ügyfelek szerint csoportosítva" + producer_mail_signoff: "Üdvözlettel," + producer_mail_order_customer_text: "A rendelések vásárlók szerint csoportosítva:" shopping_oc_closed: A rendelések lezárva shopping_oc_closed_description: "Kérjük, várd meg, amíg a következő ciklus megnyílik." shopping_oc_last_closed: "Az utolsó ciklus ennyi ideje zárult: %{distance_of_time}" @@ -3472,7 +3472,7 @@ hu: compiling_invoices: "Vevői lapok összeállítása" bulk_invoice_created: "Vevői lapok létrehozva" bulk_invoice_failed: "Nem sikerült létrehozni a tömeges számlát" - please_wait: "A modul bezárása előtt várja meg, amíg a PDF elkészül." + please_wait: "A modul bezárása előtt várd meg, amíg a PDF elkészül." order_state: address: "cím" adjustments: "kiigazításokat" @@ -4445,7 +4445,7 @@ hu: sku: "SKU" unit_price: "Egységár" display_as: "Megjelenítés mint" - display_name: "Megjelenítendő név" + display_name: "Alternatív név" display_as_placeholder: 'például 2 kg' display_name_placeholder: 'például paradicsom' unit_scale: "Mértékegység skála" From bed33928e0e2b48f01b0f4d8925cda701a4ca7fc Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Mon, 4 Aug 2025 11:55:26 +1000 Subject: [PATCH 25/76] Declare simplecov as direct dependency The undercover docs recommended to remove it from the Gemfile but that's only valid if you use only undercover. We do rely directly on the simplecov gem to generate reports though. --- Gemfile | 1 + Gemfile.lock | 1 + 2 files changed, 2 insertions(+) diff --git a/Gemfile b/Gemfile index a10912faba..3a1e7cf4a0 100644 --- a/Gemfile +++ b/Gemfile @@ -172,6 +172,7 @@ end group :test do gem 'pdf-reader' gem 'rails-controller-testing' + gem 'simplecov', require: false gem 'undercover', require: false gem 'vcr', require: false gem 'webmock', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 60fc57e87f..c3881579b8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -990,6 +990,7 @@ DEPENDENCIES shoulda-matchers sidekiq sidekiq-scheduler + simplecov spreadsheet_architect spring spring-commands-rspec From f532c4712e0cde1c84b948df0ae2c05ee21ce7cc Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 30 Jul 2025 16:20:06 +1000 Subject: [PATCH 26/76] Load rake tasks only once for code coverage Apparently, Rake's way of reloading the task code confuses the code coverage report. Code tested by rake task specs was not recognised as covered even though it was. --- .../lib/tasks/data/truncate_data_rake_spec.rb | 8 ++--- spec/lib/tasks/enterprises_rake_spec.rb | 10 ++---- .../tasks/import_product_images_rake_spec.rb | 16 +++------ spec/lib/tasks/reset_spec.rb | 8 ++--- spec/lib/tasks/sample_data_rake_spec.rb | 8 ++--- spec/lib/tasks/simplecov_spec.rb | 7 ++-- spec/lib/tasks/users_rake_spec.rb | 10 ++---- spec/support/shared_contexts/rake.rb | 35 +++++++++++++++++++ 8 files changed, 54 insertions(+), 48 deletions(-) create mode 100644 spec/support/shared_contexts/rake.rb diff --git a/spec/lib/tasks/data/truncate_data_rake_spec.rb b/spec/lib/tasks/data/truncate_data_rake_spec.rb index a04221200a..87a13616e5 100644 --- a/spec/lib/tasks/data/truncate_data_rake_spec.rb +++ b/spec/lib/tasks/data/truncate_data_rake_spec.rb @@ -1,15 +1,13 @@ # frozen_string_literal: true require 'spec_helper' -require 'rake' RSpec.describe 'truncate_data.rake' do + include_context "rake" + describe ':truncate' do context 'when months_to_keep is specified' do it 'truncates order cycles closed earlier than months_to_keep months ago' do - Rake.application.rake_require 'tasks/data/truncate_data' - Rake::Task.define_task(:environment) - highline = instance_double(HighLine, agree: true) allow(HighLine).to receive(:new).and_return(highline) @@ -27,7 +25,7 @@ RSpec.describe 'truncate_data.rake' do create(:order, order_cycle: recent_order_cycle) months_to_keep = 6 - Rake.application.invoke_task "ofn:data:truncate[#{months_to_keep}]" + invoke_task "ofn:data:truncate[#{months_to_keep}]" expect(OrderCycle.all).to contain_exactly(recent_order_cycle) end diff --git a/spec/lib/tasks/enterprises_rake_spec.rb b/spec/lib/tasks/enterprises_rake_spec.rb index 2e98502f0e..89330dd47f 100644 --- a/spec/lib/tasks/enterprises_rake_spec.rb +++ b/spec/lib/tasks/enterprises_rake_spec.rb @@ -1,13 +1,9 @@ # frozen_string_literal: true require 'spec_helper' -require 'rake' RSpec.describe 'enterprises.rake' do - before(:all) do - Rake.application.rake_require("tasks/enterprises") - Rake::Task.define_task(:environment) - end + include_context "rake" describe ':remove_enterprise' do context 'when the enterprises exists' do @@ -15,7 +11,7 @@ RSpec.describe 'enterprises.rake' do enterprise = create(:enterprise) expect { - Rake.application.invoke_task "ofn:remove_enterprise[#{enterprise.id}]" + invoke_task "ofn:remove_enterprise[#{enterprise.id}]" }.to change { Enterprise.count }.by(-1) end end @@ -32,7 +28,7 @@ RSpec.describe 'enterprises.rake' do enterprise_diff.connected_apps.create expect { - Rake.application.invoke_task( + invoke_task( "ofn:enterprises:activate_connected_app_type[affiliate_sales_data]" ) }.to change { ConnectedApps::AffiliateSalesData.count }.by(1) diff --git a/spec/lib/tasks/import_product_images_rake_spec.rb b/spec/lib/tasks/import_product_images_rake_spec.rb index 7a6451b0f2..0e3ad6646f 100644 --- a/spec/lib/tasks/import_product_images_rake_spec.rb +++ b/spec/lib/tasks/import_product_images_rake_spec.rb @@ -1,23 +1,15 @@ # frozen_string_literal: true require 'spec_helper' -require 'rake' RSpec.describe 'ofn:import:product_images' do - before(:all) do - Rake.application.rake_require("tasks/import_product_images") - Rake::Task.define_task(:environment) - end - - before do - Rake::Task['ofn:import:product_images'].reenable - end + include_context "rake" describe 'task' do context "filename is blank" do it 'raises an error' do expect { - Rake.application.invoke_task('ofn:import:product_images') + invoke_task('ofn:import:product_images') }.to raise_error(RuntimeError, 'Filename required') end @@ -28,7 +20,7 @@ RSpec.describe 'ofn:import:product_images' do allow(CSV).to receive(:read).and_return(CSV::Table.new([])) expect { - Rake.application.invoke_task('ofn:import:product_images["path/to/csv/file.csv"]') + invoke_task('ofn:import:product_images["path/to/csv/file.csv"]') }.to raise_error(RuntimeError, 'CSV columns reqired: ["producer", "name", "image_url"]') end end @@ -74,7 +66,7 @@ RSpec.describe 'ofn:import:product_images' do OUTPUT expect { - Rake.application.invoke_task('ofn:import:product_images["path/to/csv/file.csv"]') + invoke_task('ofn:import:product_images["path/to/csv/file.csv"]') }.to output(expected_output).to_stdout end end diff --git a/spec/lib/tasks/reset_spec.rb b/spec/lib/tasks/reset_spec.rb index 99b0237bb5..43243e03ed 100644 --- a/spec/lib/tasks/reset_spec.rb +++ b/spec/lib/tasks/reset_spec.rb @@ -1,13 +1,9 @@ # frozen_string_literal: true require 'spec_helper' -require 'rake' RSpec.describe "reset.rake" do - before(:all) do - Rake.application.rake_require("tasks/reset") - Rake::Task.define_task(:environment) - end + include_context "rake" it "clears job queues" do job_class = Class.new do @@ -18,7 +14,7 @@ RSpec.describe "reset.rake" do queue = Sidekiq::Queue.all.first # rubocop:disable Rails/RedundantActiveRecordAllMethod expect { - Rake.application.invoke_task "ofn:reset_sidekiq" + invoke_task "ofn:reset_sidekiq" }.to change { queue.count }.to(0) diff --git a/spec/lib/tasks/sample_data_rake_spec.rb b/spec/lib/tasks/sample_data_rake_spec.rb index f16a1e3be0..6cb9d509f4 100644 --- a/spec/lib/tasks/sample_data_rake_spec.rb +++ b/spec/lib/tasks/sample_data_rake_spec.rb @@ -1,13 +1,9 @@ # frozen_string_literal: true require 'spec_helper' -require 'rake' RSpec.describe 'sample_data.rake' do - before(:all) do - Rake.application.rake_require 'tasks/sample_data' - Rake::Task.define_task(:environment) - end + include_context "rake" before do # Create seed data required by the sample data. @@ -16,7 +12,7 @@ RSpec.describe 'sample_data.rake' do end it "creates some sample data to play with" do - Rake.application.invoke_task "ofn:sample_data" + invoke_task "ofn:sample_data" expect(EnterpriseGroup.count).to eq 1 expect(Customer.count).to eq 2 diff --git a/spec/lib/tasks/simplecov_spec.rb b/spec/lib/tasks/simplecov_spec.rb index 62204b12ef..f752ea1e84 100644 --- a/spec/lib/tasks/simplecov_spec.rb +++ b/spec/lib/tasks/simplecov_spec.rb @@ -1,12 +1,9 @@ # frozen_string_literal: true require 'spec_helper' -require 'rake' RSpec.describe "simplecov.rake" do - before(:all) do - Rake.application.rake_require("tasks/simplecov") - end + include_context "rake" describe "simplecov:collate_results" do context "when there are reports to merge" do @@ -17,7 +14,7 @@ RSpec.describe "simplecov.rake" do output_dir = File.join(tmp_dir, "output") expect { - Rake.application.invoke_task( + invoke_task( "simplecov:collate_results[#{input_dir},#{output_dir}]" ) }.to change { Dir.exist?(output_dir) }. diff --git a/spec/lib/tasks/users_rake_spec.rb b/spec/lib/tasks/users_rake_spec.rb index ebf5e8a5a1..b5b2b623e5 100644 --- a/spec/lib/tasks/users_rake_spec.rb +++ b/spec/lib/tasks/users_rake_spec.rb @@ -4,18 +4,14 @@ require 'spec_helper' require 'rake' RSpec.describe 'users.rake' do - before do - Rake.application.rake_require 'tasks/users' - Rake::Task.define_task(:environment) - Rake::Task['ofn:remove_enterprise_limit'].reenable - end + include_context "rake" describe ':remove_enterprise_limit' do context 'when the user exists' do let(:user) { create(:user) } it 'sets the enterprise_limit to the maximum integer' do - Rake.application.invoke_task "ofn:remove_enterprise_limit[#{user.id}]" + invoke_task "ofn:remove_enterprise_limit[#{user.id}]" expect(user.reload.enterprise_limit).to eq(2_147_483_647) end @@ -24,7 +20,7 @@ RSpec.describe 'users.rake' do context 'when the user does not exist' do it 'raises' do expect { - Rake.application.invoke_task "ofn:remove_enterprise_limit[123]" + invoke_task "ofn:remove_enterprise_limit[123]" }.to raise_error(ActiveRecord::RecordNotFound) end end diff --git a/spec/support/shared_contexts/rake.rb b/spec/support/shared_contexts/rake.rb new file mode 100644 index 0000000000..3bb5891451 --- /dev/null +++ b/spec/support/shared_contexts/rake.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +# Let this context take of Rake testing gotchas. +# +# ```rb +# RSpec.describe "my_task.rake" do +# include_context "rake" +# # .. +# ``` +# +shared_context "rake" do + before(:all) do + # Make sure that Rake tasks are only loaded once. + # Otherwise we lose code coverage data. + if Rake::Task.tasks.empty? + Openfoodnetwork::Application.load_tasks + Rake::Task.define_task(:environment) + end + end + + # Use the same task string as you would on the command line. + # + # ```rb + # invoke_task "example:task[arg1,arg2]" + # ``` + # + # This helper makes sure that you can run a task multiple times, + # even within the same test example. + def invoke_task(task_string) + Rake.application.invoke_task(task_string) + ensure + name, _args = Rake.application.parse_task_string(task_string) + Rake::Task[name].reenable + end +end From 2555a9e710fb6a9e0c58ee06087c5a8c7794a7fb Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Tue, 5 Aug 2025 12:35:44 +1000 Subject: [PATCH 27/76] Ignore breaking code coverage for coverage spec When we test our code coverage compilation, it breaks the code coverage report for the current rspec process. By running that code separately, we gain a correct coverage report for the rest of the code again. So unfortunately, we can't report on the code coverage of this particular task and have to ignore it. But at least CI depends on the correct function of this task and would fail if it didn't work. --- .simplecov | 3 --- lib/tasks/simplecov.rake | 4 ++++ spec/lib/tasks/simplecov_spec.rb | 12 +++++++++--- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/.simplecov b/.simplecov index dd47f33e21..8f18990a6a 100755 --- a/.simplecov +++ b/.simplecov @@ -7,8 +7,5 @@ SimpleCov.start 'rails' do add_filter '/script' add_filter '/db' - # We haven't managed to make simplecov recognise rake coverage accurately. - add_filter '/lib/tasks/' - formatter SimpleCov::Formatter::SimpleFormatter end diff --git a/lib/tasks/simplecov.rake b/lib/tasks/simplecov.rake index cc7f5c95e6..369929c195 100644 --- a/lib/tasks/simplecov.rake +++ b/lib/tasks/simplecov.rake @@ -4,6 +4,9 @@ namespace :simplecov do desc "Collates all result sets produced during parallel test runs" task :collate_results, # rubocop:disable Rails/RakeEnvironment doesn't need the full env [:path_to_results, :coverage_dir] do |_t, args| + # This code is covered by a spec but trying to measure the code coverage of + # the spec breaks the coverage report. We need to ignore it to avoid warnings. + # :nocov: require "simplecov" require "undercover/simplecov_formatter" @@ -19,5 +22,6 @@ namespace :simplecov do formatter(SimpleCov::Formatter::Undercover) coverage_dir(output_path) end + # :nocov: end end diff --git a/spec/lib/tasks/simplecov_spec.rb b/spec/lib/tasks/simplecov_spec.rb index f752ea1e84..e93b6324b7 100644 --- a/spec/lib/tasks/simplecov_spec.rb +++ b/spec/lib/tasks/simplecov_spec.rb @@ -13,10 +13,16 @@ RSpec.describe "simplecov.rake" do Dir.mktmpdir do |tmp_dir| output_dir = File.join(tmp_dir, "output") + task_name = "simplecov:collate_results[#{input_dir},#{output_dir}]" + expect { - invoke_task( - "simplecov:collate_results[#{input_dir},#{output_dir}]" - ) + if ENV["COVERAGE"] + # Start task in a new process to not mess with our coverage report. + `bundle exec rake #{task_name}` + else + # Use the quick standard invocation in dev. + invoke_task(task_name) + end }.to change { Dir.exist?(output_dir) }. from(false). to(true). From 910ded1a8c66890f6246e78d2eaba3dda883f46f Mon Sep 17 00:00:00 2001 From: Maikel Date: Tue, 5 Aug 2025 13:49:44 +1000 Subject: [PATCH 28/76] Typo [skip ci] --- spec/support/shared_contexts/rake.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/support/shared_contexts/rake.rb b/spec/support/shared_contexts/rake.rb index 3bb5891451..4dcf5af07b 100644 --- a/spec/support/shared_contexts/rake.rb +++ b/spec/support/shared_contexts/rake.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -# Let this context take of Rake testing gotchas. +# Let this context take care of Rake testing gotchas. # # ```rb # RSpec.describe "my_task.rake" do From e6cffde8fb2b4c45fedf356bb2af9617c7b2364c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Aug 2025 18:31:11 +0000 Subject: [PATCH 29/76] Bump tmp from 0.2.1 to 0.2.4 Bumps [tmp](https://github.com/raszi/node-tmp) from 0.2.1 to 0.2.4. - [Changelog](https://github.com/raszi/node-tmp/blob/master/CHANGELOG.md) - [Commits](https://github.com/raszi/node-tmp/compare/v0.2.1...v0.2.4) --- updated-dependencies: - dependency-name: tmp dependency-version: 0.2.4 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/yarn.lock b/yarn.lock index 9b0dfe8478..5d61935266 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8999,11 +8999,9 @@ tinyexec@^0.3.2: integrity sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA== tmp@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" - integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== - dependencies: - rimraf "^3.0.0" + version "0.2.4" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.4.tgz#c6db987a2ccc97f812f17137b36af2b6521b0d13" + integrity sha512-UdiSoX6ypifLmrfQ/XfiawN6hkjSBpCjhKxxZcWlUUmoXLaCKQU0bx4HF/tdDK2uzRuchf1txGvrWBzYREssoQ== tmpl@1.0.x: version "1.0.5" From d469552afcd360481606d8412390b262aae75e91 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Thu, 7 Aug 2025 10:13:14 +1000 Subject: [PATCH 30/76] Fix schema version --- db/schema.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/schema.rb b/db/schema.rb index 641f3c4c61..16a093975c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2025_03_04_234657) do +ActiveRecord::Schema[7.0].define(version: 2025_07_09_012346) do # These are extensions that must be enabled in order to support this database enable_extension "pg_stat_statements" enable_extension "plpgsql" From 75c33b29d53160ac3b7e3ae79f0202e3eea51390 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Thu, 7 Aug 2025 10:41:13 +1000 Subject: [PATCH 31/76] Losen engine gemspec requirement for Dependabot Dependabot doesn't seem to be able to resolve the version correctly. We got this message: ``` Could not find compatible versions Because every version of web depends on Ruby = 0.0.1 and Gemfile depends on web >= 0, Ruby = 0.0.1 is required. So, because current Ruby version is = 3.1.4, version solving has failed. ``` --- engines/catalog/catalog.gemspec | 2 +- engines/dfc_provider/dfc_provider.gemspec | 2 +- engines/order_management/order_management.gemspec | 2 +- engines/web/web.gemspec | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/engines/catalog/catalog.gemspec b/engines/catalog/catalog.gemspec index 8256f05685..1db993f651 100644 --- a/engines/catalog/catalog.gemspec +++ b/engines/catalog/catalog.gemspec @@ -10,7 +10,7 @@ Gem::Specification.new do |s| s.authors = ["developers@ofn"] s.summary = "Catalog domain of the OFN solution." - s.required_ruby_version = File.read(File.expand_path("../../.ruby-version", __dir__)).chomp + s.required_ruby_version = ">= 1.0.0" # rubocop:disable Gemspec/RequiredRubyVersion s.files = Dir["{app,config,db,lib}/**/*"] + ["LICENSE.txt", "Rakefile", "README.rdoc"] s.metadata['rubygems_mfa_required'] = 'true' diff --git a/engines/dfc_provider/dfc_provider.gemspec b/engines/dfc_provider/dfc_provider.gemspec index 26be88b4f9..c3708ee4f7 100644 --- a/engines/dfc_provider/dfc_provider.gemspec +++ b/engines/dfc_provider/dfc_provider.gemspec @@ -13,7 +13,7 @@ Gem::Specification.new do |spec| spec.summary = 'Provides an API stack implementing DFC semantic ' \ 'specifications' - spec.required_ruby_version = File.read(File.expand_path("../../.ruby-version", __dir__)).chomp + spec.required_ruby_version = ">= 1.0.0" # rubocop:disable Gemspec/RequiredRubyVersion spec.files = Dir["{app,config,lib}/**/*"] + ['README.md'] diff --git a/engines/order_management/order_management.gemspec b/engines/order_management/order_management.gemspec index 6a978ecc35..78093bda50 100644 --- a/engines/order_management/order_management.gemspec +++ b/engines/order_management/order_management.gemspec @@ -10,7 +10,7 @@ Gem::Specification.new do |s| s.authors = ["developers@ofn"] s.summary = "Order Management domain of the OFN solution." - s.required_ruby_version = File.read(File.expand_path("../../.ruby-version", __dir__)).chomp + s.required_ruby_version = ">= 1.0.0" # rubocop:disable Gemspec/RequiredRubyVersion s.files = Dir["{app,config,db,lib}/**/*"] + ["LICENSE.txt", "Rakefile", "README.rdoc"] s.metadata['rubygems_mfa_required'] = 'true' diff --git a/engines/web/web.gemspec b/engines/web/web.gemspec index 086a224895..489eb61870 100644 --- a/engines/web/web.gemspec +++ b/engines/web/web.gemspec @@ -10,7 +10,7 @@ Gem::Specification.new do |s| s.authors = ["developers@ofn"] s.summary = "Web domain of the OFN solution." - s.required_ruby_version = File.read(File.expand_path("../../.ruby-version", __dir__)).chomp + s.required_ruby_version = ">= 1.0.0" # rubocop:disable Gemspec/RequiredRubyVersion s.files = Dir["{app,config,db,lib}/**/*"] + ["LICENSE.txt", "Rakefile", "README.rdoc"] s.metadata['rubygems_mfa_required'] = 'true' From 1ec570375f7bf5d235c4c7d354d919bde9eda196 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Thu, 7 Aug 2025 14:53:33 +1000 Subject: [PATCH 32/76] Remove Person from product catalog Early versions of the DFC standard demanded that all data is published in relationship to the authenticated user. But that is not necessary anymore and can add complications when a platform is authenticated as client user. --- .../app/controllers/dfc_provider/catalog_items_controller.rb | 4 ---- swagger/dfc.yaml | 3 --- 2 files changed, 7 deletions(-) diff --git a/engines/dfc_provider/app/controllers/dfc_provider/catalog_items_controller.rb b/engines/dfc_provider/app/controllers/dfc_provider/catalog_items_controller.rb index 00d0b63da7..1fe2a0bde2 100644 --- a/engines/dfc_provider/app/controllers/dfc_provider/catalog_items_controller.rb +++ b/engines/dfc_provider/app/controllers/dfc_provider/catalog_items_controller.rb @@ -7,16 +7,12 @@ module DfcProvider before_action :check_enterprise def index - person = PersonBuilder.person(current_user) - enterprises = current_user.enterprises.map do |enterprise| EnterpriseBuilder.enterprise(enterprise) end - person.affiliatedOrganizations = enterprises catalog_items = enterprises.flat_map(&:catalogItems) render json: DfcIo.export( - person, *enterprises, *catalog_items, *catalog_items.map(&:product), diff --git a/swagger/dfc.yaml b/swagger/dfc.yaml index 03022bcca1..b7e1acda5f 100644 --- a/swagger/dfc.yaml +++ b/swagger/dfc.yaml @@ -151,9 +151,6 @@ paths: value: "@context": https://www.datafoodconsortium.org "@graph": - - "@id": http://test.host/api/dfc/persons/12345 - "@type": dfc-b:Person - dfc-b:affiliates: http://test.host/api/dfc/enterprises/10000 - "@id": http://test.host/api/dfc/enterprises/10000 "@type": dfc-b:Enterprise dfc-b:hasAddress: http://test.host/api/dfc/addresses/40000 From 420deca43747625bc20db7da2a6dfbab4c93cf44 Mon Sep 17 00:00:00 2001 From: Carlos Chitty Date: Wed, 26 Mar 2025 12:33:05 -0400 Subject: [PATCH 33/76] Bump rails from 7.0.8 to 7.1.5.1 --- Gemfile.lock | 149 +++++++++++++++++++++++++++++---------------------- 1 file changed, 85 insertions(+), 64 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 6c1600ba7d..8877750db8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -49,53 +49,57 @@ GEM remote: https://rubygems.org/ specs: Ascii85 (1.1.0) - actioncable (7.0.8) - actionpack (= 7.0.8) - activesupport (= 7.0.8) + actioncable (7.1.5.1) + actionpack (= 7.1.5.1) + activesupport (= 7.1.5.1) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (7.0.8) - actionpack (= 7.0.8) - activejob (= 7.0.8) - activerecord (= 7.0.8) - activestorage (= 7.0.8) - activesupport (= 7.0.8) + zeitwerk (~> 2.6) + actionmailbox (7.1.5.1) + actionpack (= 7.1.5.1) + activejob (= 7.1.5.1) + activerecord (= 7.1.5.1) + activestorage (= 7.1.5.1) + activesupport (= 7.1.5.1) mail (>= 2.7.1) net-imap net-pop net-smtp - actionmailer (7.0.8) - actionpack (= 7.0.8) - actionview (= 7.0.8) - activejob (= 7.0.8) - activesupport (= 7.0.8) + actionmailer (7.1.5.1) + actionpack (= 7.1.5.1) + actionview (= 7.1.5.1) + activejob (= 7.1.5.1) + activesupport (= 7.1.5.1) mail (~> 2.5, >= 2.5.4) net-imap net-pop net-smtp - rails-dom-testing (~> 2.0) - actionpack (7.0.8) - actionview (= 7.0.8) - activesupport (= 7.0.8) - rack (~> 2.0, >= 2.2.4) + rails-dom-testing (~> 2.2) + actionpack (7.1.5.1) + actionview (= 7.1.5.1) + activesupport (= 7.1.5.1) + nokogiri (>= 1.8.5) + racc + rack (>= 2.2.4) + rack-session (>= 1.0.1) rack-test (>= 0.6.3) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.2.0) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) actionpack-action_caching (1.2.2) actionpack (>= 4.0.0) - actiontext (7.0.8) - actionpack (= 7.0.8) - activerecord (= 7.0.8) - activestorage (= 7.0.8) - activesupport (= 7.0.8) + actiontext (7.1.5.1) + actionpack (= 7.1.5.1) + activerecord (= 7.1.5.1) + activestorage (= 7.1.5.1) + activesupport (= 7.1.5.1) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.0.8) - activesupport (= 7.0.8) + actionview (7.1.5.1) + activesupport (= 7.1.5.1) builder (~> 3.1) - erubi (~> 1.4) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.1, >= 1.2.0) + erubi (~> 1.11) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) active_model_serializers (0.8.4) activemodel (>= 3.0) active_storage_validations (1.1.4) @@ -103,8 +107,8 @@ GEM activemodel (>= 5.2.0) activestorage (>= 5.2.0) activesupport (>= 5.2.0) - activejob (7.0.8) - activesupport (= 7.0.8) + activejob (7.1.5.1) + activesupport (= 7.1.5.1) globalid (>= 0.3.6) activemerchant (1.133.0) activesupport (>= 4.2) @@ -112,11 +116,12 @@ GEM i18n (>= 0.6.9) nokogiri (~> 1.4) rexml (~> 3.2.5) - activemodel (7.0.8) - activesupport (= 7.0.8) - activerecord (7.0.8) - activemodel (= 7.0.8) - activesupport (= 7.0.8) + activemodel (7.1.5.1) + activesupport (= 7.1.5.1) + activerecord (7.1.5.1) + activemodel (= 7.1.5.1) + activesupport (= 7.1.5.1) + timeout (>= 0.4.0) activerecord-import (1.6.0) activerecord (>= 4.2) activerecord-postgresql-adapter (0.0.1) @@ -128,17 +133,24 @@ GEM multi_json (~> 1.11, >= 1.11.2) rack (>= 2.0.8, < 4) railties (>= 6.1) - activestorage (7.0.8) - actionpack (= 7.0.8) - activejob (= 7.0.8) - activerecord (= 7.0.8) - activesupport (= 7.0.8) + activestorage (7.1.5.1) + actionpack (= 7.1.5.1) + activejob (= 7.1.5.1) + activerecord (= 7.1.5.1) + activesupport (= 7.1.5.1) marcel (~> 1.0) - mini_mime (>= 1.1.0) - activesupport (7.0.8) + activesupport (7.1.5.1) + base64 + benchmark (>= 0.3) + bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) + connection_pool (>= 2.2.5) + drb i18n (>= 1.6, < 2) + logger (>= 1.4.2) minitest (>= 5.1) + mutex_m + securerandom (>= 0.3) tzinfo (~> 2.0) acts-as-taggable-on (10.0.0) activerecord (>= 6.1, < 7.2) @@ -180,6 +192,7 @@ GEM base64 (0.2.0) bcp47_spec (0.2.1) bcrypt (3.1.20) + benchmark (0.4.1) bigdecimal (3.2.2) bindata (2.5.0) bindex (0.8.1) @@ -270,6 +283,7 @@ GEM digest (3.1.1) docile (1.4.0) dotenv (3.1.2) + drb (2.2.3) em-http-request (1.1.7) addressable (>= 2.3.4) cookiejar (!= 0.3.1) @@ -435,6 +449,7 @@ GEM listen (3.9.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) + logger (1.7.0) loofah (2.22.0) crass (~> 1.0.2) nokogiri (>= 1.12.0) @@ -463,6 +478,7 @@ GEM msgpack (1.7.2) multi_json (1.15.0) multi_xml (0.6.0) + mutex_m (0.3.0) net-http (0.4.1) uri net-imap (0.4.10) @@ -578,20 +594,23 @@ GEM rack-test (2.1.0) rack (>= 1.3) rack-timeout (0.7.0) - rails (7.0.8) - actioncable (= 7.0.8) - actionmailbox (= 7.0.8) - actionmailer (= 7.0.8) - actionpack (= 7.0.8) - actiontext (= 7.0.8) - actionview (= 7.0.8) - activejob (= 7.0.8) - activemodel (= 7.0.8) - activerecord (= 7.0.8) - activestorage (= 7.0.8) - activesupport (= 7.0.8) + rackup (1.0.1) + rack (< 3) + webrick + rails (7.1.5.1) + actioncable (= 7.1.5.1) + actionmailbox (= 7.1.5.1) + actionmailer (= 7.1.5.1) + actionpack (= 7.1.5.1) + actiontext (= 7.1.5.1) + actionview (= 7.1.5.1) + activejob (= 7.1.5.1) + activemodel (= 7.1.5.1) + activerecord (= 7.1.5.1) + activestorage (= 7.1.5.1) + activesupport (= 7.1.5.1) bundler (>= 1.15.0) - railties (= 7.0.8) + railties (= 7.1.5.1) rails-controller-testing (1.0.5) actionpack (>= 5.0.1.rc1) actionview (>= 5.0.1.rc1) @@ -612,13 +631,14 @@ GEM i18n (>= 0.7, < 2) railties (>= 6.0.0, < 8) rails_safe_tasks (1.0.0) - railties (7.0.8) - actionpack (= 7.0.8) - activesupport (= 7.0.8) - method_source + railties (7.1.5.1) + actionpack (= 7.1.5.1) + activesupport (= 7.1.5.1) + irb + rackup (>= 1.0.0) rake (>= 12.2) - thor (~> 1.0) - zeitwerk (~> 2.5) + thor (~> 1.0, >= 1.2.2) + zeitwerk (~> 2.6) rainbow (3.1.1) rake (13.2.1) ransack (4.1.1) @@ -752,6 +772,7 @@ GEM sprockets-rails (>= 2.0, < 4.0) tilt (>= 1.1, < 3) sd_notify (0.1.1) + securerandom (0.4.1) semantic_range (3.0.0) shoulda-matchers (6.2.0) activesupport (>= 5.2.0) From 3cb6a2617bc6a5e19ebe49d15d51482bb4e68e24 Mon Sep 17 00:00:00 2001 From: Carlos Chitty Date: Tue, 29 Apr 2025 15:19:40 -0400 Subject: [PATCH 34/76] Do not fail tests on deprecation warnings for the next rails version (7.2) --- config/environments/test.rb | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/config/environments/test.rb b/config/environments/test.rb index 2e14aea9c3..abda6964d9 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -54,15 +54,5 @@ Openfoodnetwork::Application.configure do # Print deprecation notices to the stderr # config.active_support.deprecation = :stderr - # Fail tests on deprecated code unless it's a known case to solve. - ActiveSupport::Deprecation.behavior = ->(message, callstack, deprecation_horizon, gem_name) do - allowed_warnings = [ - # List strings here to allow matching deprecations. - ] - unless allowed_warnings.any? { |pattern| message.match(pattern) } - ActiveSupport::Deprecation::DEFAULT_BEHAVIORS[:raise].call(message, callstack, deprecation_horizon, gem_name) - end - end - config.active_job.queue_adapter = :test end From a2f263e0811867e824819335e6caa09bec6526fb Mon Sep 17 00:00:00 2001 From: Carlos Chitty Date: Wed, 26 Mar 2025 12:50:43 -0400 Subject: [PATCH 35/76] User Rails.env.local? https://github.com/rails/rails/pull/46786 Solves rubocop failure in rails 7.1 bump branch https://github.com/openfoodfoundation/openfoodnetwork/actions/runs/14739687970/job/41374340281?pr=13232 --- app/controllers/api/v1/base_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/v1/base_controller.rb b/app/controllers/api/v1/base_controller.rb index f1e5e992fb..15a75cc0d1 100644 --- a/app/controllers/api/v1/base_controller.rb +++ b/app/controllers/api/v1/base_controller.rb @@ -54,7 +54,7 @@ module Api def error_during_processing(exception) Alert.raise(exception) - if Rails.env.development? || Rails.env.test? + if Rails.env.local? render status: :unprocessable_entity, json: json_api_error(exception.message, meta: exception.backtrace) else From 3153e99497327ef936fb8776c343c8bfec298362 Mon Sep 17 00:00:00 2001 From: Carlos Chitty Date: Wed, 30 Apr 2025 13:42:36 -0400 Subject: [PATCH 36/76] Update OpenOrderCycleJob test "syncing remote products" to expect 58 queries instead of 59 The main point of the test is to alert us if the query count increased (https://github.com/openfoodfoundation/openfoodnetwork/pull/13232#discussion_r2199896280). The missing query in rails 7.1: Spree::StockItem Load SELECT "spree_stock_items"."id", "spree_stock_items"."variant_id", "spree_stock_items"."count_on_hand", "spree_stock_items"."created_at", "spree_stock_items"."updated_at", "spree_stock_items"."backorderable", "spree_stock_items"."deleted_at", "spree_stock_items"."lock_version" FROM "spree_stock_items" WHERE "spree_stock_items"."id" = $1 LIMIT $2 FOR UPDATE --- spec/jobs/open_order_cycle_job_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/jobs/open_order_cycle_job_spec.rb b/spec/jobs/open_order_cycle_job_spec.rb index bf7c84d5ab..c6d1d1d27e 100644 --- a/spec/jobs/open_order_cycle_job_spec.rb +++ b/spec/jobs/open_order_cycle_job_spec.rb @@ -71,7 +71,7 @@ RSpec.describe OpenOrderCycleJob do .and change { variant.on_demand }.to(true) .and change { variant.on_hand }.by(0) .and change { variant_discontinued.on_hand }.to(0) - .and query_database 59 + .and query_database 58 end end From 45b712ddcdadba11cced15766aa90adcbe32177d Mon Sep 17 00:00:00 2001 From: Carlos Chitty Date: Wed, 30 Apr 2025 14:53:44 -0400 Subject: [PATCH 37/76] Set latest invoce date explicitly in Orders::GenerateInvoiceService test Solves CI failure: https://github.com/openfoodfoundation/openfoodnetwork/actions/runs/14760883756/job/41441014958?pr=13232 --- spec/services/orders/generate_invoice_service_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/services/orders/generate_invoice_service_spec.rb b/spec/services/orders/generate_invoice_service_spec.rb index a396a26897..e818eae266 100644 --- a/spec/services/orders/generate_invoice_service_spec.rb +++ b/spec/services/orders/generate_invoice_service_spec.rb @@ -8,6 +8,7 @@ RSpec.describe Orders::GenerateInvoiceService do let!(:latest_invoice){ create(:invoice, order:, + date: Time.zone.today - 2.days, data: invoice_data_generator.serialize_for_invoice) } From 32c96b72ad54d7680225af1a186824eceee9e4d6 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Fri, 8 Aug 2025 10:24:04 +1000 Subject: [PATCH 38/76] Update all locales with the latest Transifex translations --- config/locales/en_CA.yml | 1 + config/locales/en_FR.yml | 1 + config/locales/fr.yml | 1 + config/locales/fr_CA.yml | 1 + config/locales/hu.yml | 29 +++++++++++++++-------------- 5 files changed, 19 insertions(+), 14 deletions(-) diff --git a/config/locales/en_CA.yml b/config/locales/en_CA.yml index e438ac2afc..8198ac49a7 100644 --- a/config/locales/en_CA.yml +++ b/config/locales/en_CA.yml @@ -3315,6 +3315,7 @@ en_CA: product_importer_products_save_error: did not save any products successfully product_import_file_not_found_notice: 'File not found or could not be opened' product_import_no_data_in_spreadsheet_notice: 'No data found in spreadsheet' + product_import_inventory_disable: Importing into inventories is not available order_choosing_hub_notice: Your hub has been selected. order_cycle_selecting_notice: Your order cycle has been selected. adjustments_tax_rate_error: "^Please check that the tax rate for this adjustment is correct." diff --git a/config/locales/en_FR.yml b/config/locales/en_FR.yml index 306294cd3b..bf2d9a33c6 100644 --- a/config/locales/en_FR.yml +++ b/config/locales/en_FR.yml @@ -3315,6 +3315,7 @@ en_FR: product_importer_products_save_error: did not save any products successfully product_import_file_not_found_notice: 'File not found or could not be opened' product_import_no_data_in_spreadsheet_notice: 'No data found in spreadsheet' + product_import_inventory_disable: Importing into inventories is not available order_choosing_hub_notice: Your hub has been selected. order_cycle_selecting_notice: Your order cycle has been selected. adjustments_tax_rate_error: "^Please check that the tax rate for this adjustment is correct." diff --git a/config/locales/fr.yml b/config/locales/fr.yml index b925c8cad4..476ee1d29b 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -3321,6 +3321,7 @@ fr: product_importer_products_save_error: n'a pu sauvegarder aucun produit :-( product_import_file_not_found_notice: 'Fichier non trouvé ou impossible à ouvrir' product_import_no_data_in_spreadsheet_notice: 'Aucune donnée trouvée dans le tableau' + product_import_inventory_disable: L'importation vers les catalogues boutique n'est pas disponible order_choosing_hub_notice: Votre boutique a été sélectionnée. order_cycle_selecting_notice: Votre cycle de vente a été sélectionné. adjustments_tax_rate_error: "^Veuillez vérifier la TVA applicable pour cet ajustement." diff --git a/config/locales/fr_CA.yml b/config/locales/fr_CA.yml index ba7f389085..e936404006 100644 --- a/config/locales/fr_CA.yml +++ b/config/locales/fr_CA.yml @@ -3322,6 +3322,7 @@ fr_CA: product_importer_products_save_error: n'a pu sauvegarder aucun produit :-( product_import_file_not_found_notice: 'Fichier non trouvé ou impossible à ouvrir' product_import_no_data_in_spreadsheet_notice: 'Aucune donnée trouvée dans le tableau' + product_import_inventory_disable: L'importation vers les catalogues boutique n'est pas disponible order_choosing_hub_notice: Votre hub a été sélectionné. order_cycle_selecting_notice: Votre cycle de vente a été sélectionné. adjustments_tax_rate_error: "^Veuillez vérifier le type de taxe applicable pour cet ajustement." diff --git a/config/locales/hu.yml b/config/locales/hu.yml index d79fdae3b2..e39ea69362 100644 --- a/config/locales/hu.yml +++ b/config/locales/hu.yml @@ -530,7 +530,7 @@ hu: save: "Mentés" edit: "Szerkesztés" update: "Frissítés" - delete: "Töröl" + delete: "Törlés" add: "Hozzáadás" cut: "Kivágás" paste: "Beillesztés" @@ -580,7 +580,7 @@ hu: actions: edit: Szerkesztés clone: Klón - delete: Töröl + delete: Törlés remove: Eltávolítás preview: Előnézet image: @@ -907,7 +907,7 @@ hu: success: Sikeresen klónozta a terméket error: A terméket nem sikerült klónozni product_import: - title: Termék importálása + title: Termék importálás file_not_found: A fájl nem található, vagy nem nyitható meg no_data: Nem található adat a táblázatban confirm_reset: "Ezzel nullára állítod a készletszintet a vállalkozás összes olyan termékénél, \namely nem szerepel a feltöltött fájlban" @@ -970,6 +970,7 @@ hu: no_name: Névtelen blank_enterprise: egyes termékekhez nincs definiálva a vállalkozás reset_absent?: Hiányzó termékek visszaállítása + reset_absent_tip: Állítsa nullára a készletet az összes olyan terméknél, amely nem szerepel a fájlban overwrite_all: Az összes felülírása overwrite_empty: Ha üres, írja felül default_stock: Állítsd be a készletet @@ -977,15 +978,15 @@ hu: default_shipping_cat: Szállítási kategória beállítása default_available_date: Állítsd be az elérhetőségi dátumot validation_overview: Az importálás ellenőrzésének áttekintése - entries_found: Bejegyzések találhatók az importált fájlban + entries_found: bejegyzés található az importált fájlban entries_with_errors: Az elemek hibákat tartalmaznak, és nem importálhatók - products_to_create: Termékek jönnek létre + products_to_create: termék jön létre products_to_update: A termékek frissítésre kerülnek inventory_to_create: A készletelemek létrehozásra kerülnek inventory_to_update: A készletelemek frissülnek products_to_reset: A meglévő termékek készlete nullára áll vissza inventory_to_reset: A meglévő készletelemek készlete nullára áll vissza - line: Vonal + line: Sor item_line: Tételsor import_review: not_updatable_tip: "A következő mezők nem frissíthetők tömeges importálással meglévő termékek esetén:" @@ -993,14 +994,14 @@ hu: entries_table: not_updatable: Ez a mező nem frissíthető tömeges importálással meglévő termékeken save_results: - final_results: Végső eredmények importálása - products_created: Létrehozott termékek + final_results: Az importálás eredménye + products_created: létrehozott termék products_updated: Termékek frissítve inventory_created: Készletelemek létrehozva inventory_updated: A készletelemek frissítve - products_reset: A termékek készletszintjét nullára állítottuk vissza + products_reset: termék készletét nullára állítottuk inventory_reset: A készletelemek készletszintjét nullára állították vissza - all_saved: "Minden elem sikeresen mentve" + all_saved: "Minden termék mentése sikeres." some_saved: "elemek sikeresen mentve" save_errors: Mentse el a hibákat import_again: Másik fájl feltöltése @@ -1518,7 +1519,7 @@ hu: add_distributor: 'Elosztó hozzáadása' advanced_settings: automatic_notifications: Automatikus értesítések - automatic_notifications_tip: Automatikusan értesíti a termelőket rendeléseikről e-mailben, amikor a rendelési ciklusok lezárulnak + automatic_notifications_tip: Automatikusan értesíti a termelőket rendeléseikről e-mailben, amikor a rendelés ciklus lezárul title: További beállítások choose_product_tip: bejövő és kimenő termékeket csak %{inventory} készletére korlátozhatja. preferred_product_selection_from_coordinator_inventory_only_here: Csak a koordinátor leltár @@ -2865,7 +2866,7 @@ hu: admin_enterprise_groups_web_website_placeholder: "például www.truffles.com" admin_order_cycles: "Admin Rendelési Ciklusok" open: "Nyit" - close: "Zár" + close: "Bezárás" create: "Létrehozás" search: "Keresés" supplier: "Beszállító" @@ -2902,7 +2903,7 @@ hu: edit_order_cycle: "Rendelési Ciklus Szerkesztése" roles: "Szerepek" update: "Frissítés" - delete: Töröl + delete: Törlés add_producer_property: "Termelői tulajdonság hozzáadása" in_progress: "Folyamatban" started_at: "Kezdődött:" @@ -3888,7 +3889,7 @@ hu: total: "Összesen" edit: "Szerkesztés" split: "Hasított" - delete: "Töröl" + delete: "Törlés" cannot_set_shipping_method_without_address: "A áruátvételi módot nem lehet beállítani, amíg meg nem adják az ügyfél adatait." no_tracking_present: "Nincsenek megadva nyomkövetési adatok." tracking: "Követés" From 4cd0071dd41a4ed00186760f0ab3c10eeee73e27 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 25 Jun 2025 11:39:23 +1000 Subject: [PATCH 39/76] Allow only existing deprecations * Allow deprecated cache_format_version * Allow deprecated Rails.application.secrets * Allow deprecated Passing the class as positional argument * Allow deprecated alias_attribute with non-attribute targets * Allow deprecated model aliases * Allow deprecated action_dispatch.show_exceptions --- config/environments/test.rb | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/config/environments/test.rb b/config/environments/test.rb index abda6964d9..085bb94540 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -54,5 +54,33 @@ Openfoodnetwork::Application.configure do # Print deprecation notices to the stderr # config.active_support.deprecation = :stderr + # Fail tests on deprecated code unless it's a known case to solve. + Rails.application.deprecators.behavior = ->(message, callstack, deprecator) do + allowed_warnings = [ + # List strings here to allow matching deprecations. + # + # https://guides.rubyonrails.org/upgrading_ruby_on_rails.html#new-activesupport-cache-serialization-format + "config.active_support.cache_format_version", + + # `Rails.application.secrets` is deprecated in favor of `Rails.application.credentials` and will be removed in Rails 7.2 + "Rails.application.secrets", + + "Passing the class as positional argument", + + # Spree::Order model aliases `bill_address`, but `bill_address` is not an attribute. Starting in Rails 7.2, alias_attribute with non-attribute targets will raise. Use `alias_method :billing_address, :bill_address` or define the method manually. (called from initialize at app/models/spree/order.rb:188) + "alias_attribute with non-attribute targets will raise", + + # Spree::CreditCard model aliases `cc_type` and has a method called `cc_type=` defined. Starting in Rails 7.2 `brand=` will not be calling `cc_type=` anymore. You may want to additionally define `brand=` to preserve the current behavior. + "model aliases", + + # Setting action_dispatch.show_exceptions to true is deprecated. Set to :all instead. + # spec/requests/errors_spec.rb + "action_dispatch.show_exceptions", + ] + unless allowed_warnings.any? { |pattern| message.match(pattern) } + ActiveSupport::Deprecation::DEFAULT_BEHAVIORS[:raise].call(message, callstack, deprecator) + end + end + config.active_job.queue_adapter = :test end From 0166abcd2a878982195cee6436f4857c207a5dfe Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 25 Jun 2025 13:13:25 +1000 Subject: [PATCH 40/76] Remove deprecated and unnecessary config --- spec/base_spec_helper.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/spec/base_spec_helper.rb b/spec/base_spec_helper.rb index adf32656f2..29431ee0ee 100644 --- a/spec/base_spec_helper.rb +++ b/spec/base_spec_helper.rb @@ -68,9 +68,6 @@ InvisibleCaptcha.timestamp_enabled = false InvisibleCaptcha.spinner_enabled = false RSpec.configure do |config| - # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures - config.fixture_path = Rails.root.join('spec/fixtures').to_s - # If you're not using ActiveRecord, or you'd prefer not to run each of your # examples within a transaction, remove the following line or assign false # instead of true. From 25f396c126b53e6199aa977d26927d9ae0821a9f Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Tue, 27 May 2025 16:55:39 +1000 Subject: [PATCH 41/76] Add permission module with example data It's basically just copied from the Readme file still pointing to the development server and it's not interacting with OFN just yet. --- .../form/_connected_apps.html.haml | 2 ++ .../form/_dfc_permissions.html.haml | 27 +++++++++++++++++++ app/webpacker/css/admin/connected_apps.scss | 16 +++++++++++ 3 files changed, 45 insertions(+) create mode 100644 app/views/admin/enterprises/form/_dfc_permissions.html.haml diff --git a/app/views/admin/enterprises/form/_connected_apps.html.haml b/app/views/admin/enterprises/form/_connected_apps.html.haml index f24c2b34d9..0bb3ecf85d 100644 --- a/app/views/admin/enterprises/form/_connected_apps.html.haml +++ b/app/views/admin/enterprises/form/_connected_apps.html.haml @@ -1,3 +1,5 @@ - connected_apps_enabled.each do |type| = render partial: "/admin/enterprises/form/connected_apps/#{type}", locals: { enterprise:, connected_app: enterprise.connected_apps.public_send(type).first } + += render partial: "/admin/enterprises/form/dfc_permissions" diff --git a/app/views/admin/enterprises/form/_dfc_permissions.html.haml b/app/views/admin/enterprises/form/_dfc_permissions.html.haml new file mode 100644 index 0000000000..210cec5e3a --- /dev/null +++ b/app/views/admin/enterprises/form/_dfc_permissions.html.haml @@ -0,0 +1,27 @@ +%script{type: "module", src: "https://cdn.jsdelivr.net/npm/@startinblox/core@latest/dist/index.js"} +%script{type: "module"} + :plain + window.orbit = { + client: { + name: "Orbit", + logo: "https://cdn.startinblox.com/logos/ACME.svg", + }, + components: [], + getRoute: (route) => route, + getDefaultRoute: () => "", + getComponent: () => undefined, + getComponentFromRoute: () => undefined, + Swal: () => {}, + defaultRoute: "", + federations: {}, + componentSet: new Set(["routing", "menu", "menu-top", "autoLogin", "solid-permissioning"]), + }; + +:plain + + + +%script{type: "module", src: "https://cdn.jsdelivr.net/npm/@startinblox/solid-data-permissioning@latest/dist/index.js"} diff --git a/app/webpacker/css/admin/connected_apps.scss b/app/webpacker/css/admin/connected_apps.scss index f68d902ad0..975be5fb5f 100644 --- a/app/webpacker/css/admin/connected_apps.scss +++ b/app/webpacker/css/admin/connected_apps.scss @@ -68,3 +68,19 @@ } } } + +solid-permissioning { + @import url("https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=Montserrat:ital,wght@0,100..900;1,100..900&display=swap"); + + --font-family: "Inter", sans-serif; + --color-text: #181d27; + --color-primary: #0096AD; + --color-primary-dark: #007B8A; + --color-primary-light: #5498DA; + --color-secondary: #23A877; + --color-third: #F1F8FE; + --color-third-dark: #d8e6f5; + --color-heading: #0096AD; + --color-grey: #4d4d4d; + font-family: var(--font-family); +} From 60c8f4ee20b6069a510dab0b255f477829d2babe Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Thu, 15 May 2025 14:23:19 +1000 Subject: [PATCH 42/76] Add DFC API endpoint for listing platforms Only listing example JSON for now. This is not part of the official DFC API but it's a DFC-related API and therefore we put it in the same namespace. The DFC Permission Module will make authenticated requests to grant certain platforms certain permissions. --- .../form/_dfc_permissions.html.haml | 2 +- .../dfc_provider/platforms_controller.rb | 13 ++++ engines/dfc_provider/config/routes.rb | 1 + .../spec/requests/platforms_spec.rb | 31 ++++++++++ swagger/dfc.yaml | 62 +++++++++++++++++++ 5 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb create mode 100644 engines/dfc_provider/spec/requests/platforms_spec.rb diff --git a/app/views/admin/enterprises/form/_dfc_permissions.html.haml b/app/views/admin/enterprises/form/_dfc_permissions.html.haml index 210cec5e3a..dd12047bf6 100644 --- a/app/views/admin/enterprises/form/_dfc_permissions.html.haml +++ b/app/views/admin/enterprises/form/_dfc_permissions.html.haml @@ -19,7 +19,7 @@ :plain diff --git a/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb b/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb new file mode 100644 index 0000000000..e847dd49cb --- /dev/null +++ b/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module DfcProvider + class PlatformsController < DfcProvider::ApplicationController + before_action :check_enterprise + + def index + render json: <<~JSON + {"@context":"https://cdn.startinblox.com/owl/context-bis.jsonld","@id":"https://mydataserver.com/enterprises/1/platforms","dfc-t:platforms":{"@list":[{"@id":"https://waterlooregionfood.ca/portal/profile","@type":"dfc-t:Platform","_id":{"$oid":"682afcc4966dbb3aa7464d56"},"description":"A super duper portal for the waterloo region","dfc-t:hasAssignedScopes":{"@list":[{"@id":"https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/ReadEnterprise","@type":"dfc-t:Scope","dfc-t:scope":"ReadEnterprise"},{"@id":"https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/WriteEnterprise","@type":"dfc-t:Scope","dfc-t:scope":"WriteEnterprise"},{"@id":"https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/ReadProducts","@type":"dfc-t:Scope","dfc-t:scope":"ReadProducts"},{"@id":"https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/WriteProducts","@type":"dfc-t:Scope","dfc-t:scope":"WriteProducts"},{"@id":"https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/ReadOrders","@type":"dfc-t:Scope","dfc-t:scope":"ReadOrders"},{"@id":"https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/WriteOrders","@type":"dfc-t:Scope","dfc-t:scope":"WriteOrders"}],"@type":"rdf:List"},"termsandconditions":"https://waterlooregionfood.ca/terms-and-conditions","title":"Waterloo Region Food Portal"},{"@id":"https://anotherplatform.ca/portal/profile","@type":"dfc-t:Platform","_id":{"$oid":"682b2e2b031c28f69cda1645"},"description":"A super duper portal for the waterloo region","dfc-t:hasAssignedScopes":{"@list":[],"@type":"rdf:List"},"termsandconditions":"https://anotherplatform.ca/terms-and-conditions","title":"anotherplatform Portal"}],"@type":"rdf:List"}} + JSON + end + end +end diff --git a/engines/dfc_provider/config/routes.rb b/engines/dfc_provider/config/routes.rb index 647094b96b..690fab9965 100644 --- a/engines/dfc_provider/config/routes.rb +++ b/engines/dfc_provider/config/routes.rb @@ -5,6 +5,7 @@ DfcProvider::Engine.routes.draw do resources :enterprises, only: [:show] do resources :catalog_items, only: [:index, :show, :update] resources :offers, only: [:show, :update] + resources :platforms, only: [:index] resources :supplied_products, only: [:create, :show, :update] resources :social_medias, only: [:show] end diff --git a/engines/dfc_provider/spec/requests/platforms_spec.rb b/engines/dfc_provider/spec/requests/platforms_spec.rb new file mode 100644 index 0000000000..6a27c87bd8 --- /dev/null +++ b/engines/dfc_provider/spec/requests/platforms_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require_relative "../swagger_helper" + +RSpec.describe "Platforms", swagger_doc: "dfc.yaml" do + let!(:user) { create(:oidc_user) } + let!(:enterprise) do + create( + :distributor_enterprise, + id: 10_000, owner: user, name: "Fred's Farm", + ) + end + + before { login_as user } + + path "/api/dfc/enterprises/{enterprise_id}/platforms" do + parameter name: :enterprise_id, in: :path, type: :string + + get "List platforms with scopes" do + produces "application/json" + + response "200", "successful" do + let(:enterprise_id) { enterprise.id } + + run_test! do + expect(json_response["@id"]).to eq "https://mydataserver.com/enterprises/1/platforms" + end + end + end + end +end diff --git a/swagger/dfc.yaml b/swagger/dfc.yaml index 03022bcca1..542698c35e 100644 --- a/swagger/dfc.yaml +++ b/swagger/dfc.yaml @@ -558,6 +558,68 @@ paths: "@type": dfc-b:Person '404': description: not found + "/api/dfc/enterprises/{enterprise_id}/platforms": + parameters: + - name: enterprise_id + in: path + required: true + schema: + type: string + get: + summary: List platforms with scopes + tags: + - Platforms + responses: + '200': + description: successful + content: + application/json: + examples: + test_example: + value: + "@context": https://cdn.startinblox.com/owl/context-bis.jsonld + "@id": https://mydataserver.com/enterprises/1/platforms + dfc-t:platforms: + "@list": + - "@id": https://waterlooregionfood.ca/portal/profile + "@type": dfc-t:Platform + _id: + "$oid": 682afcc4966dbb3aa7464d56 + description: A super duper portal for the waterloo region + dfc-t:hasAssignedScopes: + "@list": + - "@id": https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/ReadEnterprise + "@type": dfc-t:Scope + dfc-t:scope: ReadEnterprise + - "@id": https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/WriteEnterprise + "@type": dfc-t:Scope + dfc-t:scope: WriteEnterprise + - "@id": https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/ReadProducts + "@type": dfc-t:Scope + dfc-t:scope: ReadProducts + - "@id": https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/WriteProducts + "@type": dfc-t:Scope + dfc-t:scope: WriteProducts + - "@id": https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/ReadOrders + "@type": dfc-t:Scope + dfc-t:scope: ReadOrders + - "@id": https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/WriteOrders + "@type": dfc-t:Scope + dfc-t:scope: WriteOrders + "@type": rdf:List + termsandconditions: https://waterlooregionfood.ca/terms-and-conditions + title: Waterloo Region Food Portal + - "@id": https://anotherplatform.ca/portal/profile + "@type": dfc-t:Platform + _id: + "$oid": 682b2e2b031c28f69cda1645 + description: A super duper portal for the waterloo region + dfc-t:hasAssignedScopes: + "@list": [] + "@type": rdf:List + termsandconditions: https://anotherplatform.ca/terms-and-conditions + title: anotherplatform Portal + "@type": rdf:List "/api/dfc/product_groups/{id}": parameters: - name: enterprise_id From c26686b430f53f90340bd818fc0c73e311988a01 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Thu, 15 May 2025 15:52:52 +1000 Subject: [PATCH 43/76] Add DfcPermission model to persist granted scopes --- app/models/dfc_permission.rb | 15 +++++++++++++++ .../20250515050349_create_dfc_permissions.rb | 16 ++++++++++++++++ db/schema.rb | 15 +++++++++++++++ spec/models/dfc_permission_spec.rb | 14 ++++++++++++++ 4 files changed, 60 insertions(+) create mode 100644 app/models/dfc_permission.rb create mode 100644 db/migrate/20250515050349_create_dfc_permissions.rb create mode 100644 spec/models/dfc_permission_spec.rb diff --git a/app/models/dfc_permission.rb b/app/models/dfc_permission.rb new file mode 100644 index 0000000000..6defe80559 --- /dev/null +++ b/app/models/dfc_permission.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +# Authorisations of a user allowing a platform to access to data. +class DfcPermission < ApplicationRecord + SCOPES = %w[ + ReadEnterprise ReadProducts ReadOrders + WriteEnterprise WriteProducts WriteOrders + ].freeze + + belongs_to :user, class_name: "Spree::User" + belongs_to :enterprise + + validates :grantee, presence: true + validates :scope, presence: true, inclusion: { in: SCOPES } +end diff --git a/db/migrate/20250515050349_create_dfc_permissions.rb b/db/migrate/20250515050349_create_dfc_permissions.rb new file mode 100644 index 0000000000..61c7b00ae8 --- /dev/null +++ b/db/migrate/20250515050349_create_dfc_permissions.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class CreateDfcPermissions < ActiveRecord::Migration[7.0] + def change + create_table :dfc_permissions do |t| + t.references :user, null: false, foreign_key: { to_table: :spree_users } + t.references :enterprise, null: false, foreign_key: true + t.string :grantee, null: false + t.string :scope, null: false + + t.timestamps + end + add_index :dfc_permissions, :grantee + add_index :dfc_permissions, :scope + end +end diff --git a/db/schema.rb b/db/schema.rb index 16a093975c..8b0718760e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -111,6 +111,19 @@ ActiveRecord::Schema[7.0].define(version: 2025_07_09_012346) do t.index ["user_id"], name: "index_customers_on_user_id" end + create_table "dfc_permissions", force: :cascade do |t| + t.bigint "user_id", null: false + t.bigint "enterprise_id", null: false + t.string "grantee", null: false + t.string "scope", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["enterprise_id"], name: "index_dfc_permissions_on_enterprise_id" + t.index ["grantee"], name: "index_dfc_permissions_on_grantee" + t.index ["scope"], name: "index_dfc_permissions_on_scope" + t.index ["user_id"], name: "index_dfc_permissions_on_user_id" + end + create_table "distributors_payment_methods", force: :cascade do |t| t.integer "distributor_id", null: false t.integer "payment_method_id", null: false @@ -1144,6 +1157,8 @@ ActiveRecord::Schema[7.0].define(version: 2025_07_09_012346) do add_foreign_key "customers", "spree_addresses", column: "bill_address_id", name: "customers_bill_address_id_fk" add_foreign_key "customers", "spree_addresses", column: "ship_address_id", name: "customers_ship_address_id_fk" add_foreign_key "customers", "spree_users", column: "user_id", name: "customers_user_id_fk" + add_foreign_key "dfc_permissions", "enterprises" + add_foreign_key "dfc_permissions", "spree_users", column: "user_id" add_foreign_key "distributors_payment_methods", "enterprises", column: "distributor_id", name: "distributors_payment_methods_distributor_id_fk" add_foreign_key "distributors_payment_methods", "spree_payment_methods", column: "payment_method_id", name: "distributors_payment_methods_payment_method_id_fk" add_foreign_key "distributors_shipping_methods", "enterprises", column: "distributor_id", name: "distributors_shipping_methods_distributor_id_fk" diff --git a/spec/models/dfc_permission_spec.rb b/spec/models/dfc_permission_spec.rb new file mode 100644 index 0000000000..22d93609ef --- /dev/null +++ b/spec/models/dfc_permission_spec.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe DfcPermission do + it { is_expected.to belong_to :user } + it { is_expected.to belong_to :enterprise } + it { is_expected.to validate_presence_of :grantee } + it { is_expected.to validate_presence_of :scope } + it { + is_expected.to validate_inclusion_of(:scope) + .in_array(%w[ReadEnterprise ReadProducts ReadOrders]) + } +end From 7032b3f463b4f3ca8106333ca516cb458b34dbac Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Thu, 12 Jun 2025 14:14:09 +1000 Subject: [PATCH 44/76] Add endpoint to update scopes of platform Dummy implementation only. --- .../dfc_provider/platforms_controller.rb | 12 +++++ engines/dfc_provider/config/routes.rb | 2 +- .../spec/requests/platforms_spec.rb | 18 +++++++ swagger/dfc.yaml | 50 +++++++++++++++++++ 4 files changed, 81 insertions(+), 1 deletion(-) diff --git a/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb b/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb index e847dd49cb..3b614d79a4 100644 --- a/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb +++ b/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb @@ -2,6 +2,12 @@ module DfcProvider class PlatformsController < DfcProvider::ApplicationController + # DANGER! + # This endpoint is open to CSRF attacks. + # This is a temporary measure until the DFC Permissions module accesses + # the API with a valid OIDC token to authenticate the user. + skip_before_action :verify_authenticity_token + before_action :check_enterprise def index @@ -9,5 +15,11 @@ module DfcProvider {"@context":"https://cdn.startinblox.com/owl/context-bis.jsonld","@id":"https://mydataserver.com/enterprises/1/platforms","dfc-t:platforms":{"@list":[{"@id":"https://waterlooregionfood.ca/portal/profile","@type":"dfc-t:Platform","_id":{"$oid":"682afcc4966dbb3aa7464d56"},"description":"A super duper portal for the waterloo region","dfc-t:hasAssignedScopes":{"@list":[{"@id":"https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/ReadEnterprise","@type":"dfc-t:Scope","dfc-t:scope":"ReadEnterprise"},{"@id":"https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/WriteEnterprise","@type":"dfc-t:Scope","dfc-t:scope":"WriteEnterprise"},{"@id":"https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/ReadProducts","@type":"dfc-t:Scope","dfc-t:scope":"ReadProducts"},{"@id":"https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/WriteProducts","@type":"dfc-t:Scope","dfc-t:scope":"WriteProducts"},{"@id":"https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/ReadOrders","@type":"dfc-t:Scope","dfc-t:scope":"ReadOrders"},{"@id":"https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/WriteOrders","@type":"dfc-t:Scope","dfc-t:scope":"WriteOrders"}],"@type":"rdf:List"},"termsandconditions":"https://waterlooregionfood.ca/terms-and-conditions","title":"Waterloo Region Food Portal"},{"@id":"https://anotherplatform.ca/portal/profile","@type":"dfc-t:Platform","_id":{"$oid":"682b2e2b031c28f69cda1645"},"description":"A super duper portal for the waterloo region","dfc-t:hasAssignedScopes":{"@list":[],"@type":"rdf:List"},"termsandconditions":"https://anotherplatform.ca/terms-and-conditions","title":"anotherplatform Portal"}],"@type":"rdf:List"}} JSON end + + def update + render json: <<~JSON + {"@id":"https://anotherplatform.ca/portal/profile","@type":"dfc-t:Platform","description":"A super duper portal for the waterloo region","dfc-t:hasAssignedScopes":{"@list":[{"@id":"https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/ReadEnterprise","@type":"dfc-t:Scope","dfc-t:scope":"ReadEnterprise"},{"@id":"https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/WriteEnterprise","@type":"dfc-t:Scope","dfc-t:scope":"WriteEnterprise"},{"@id":"https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/ReadProducts","@type":"dfc-t:Scope","dfc-t:scope":"ReadProducts"},{"@id":"https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/WriteProducts","@type":"dfc-t:Scope","dfc-t:scope":"WriteProducts"},{"@id":"https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/ReadOrders","@type":"dfc-t:Scope","dfc-t:scope":"ReadOrders"},{"@id":"https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/WriteOrders","@type":"dfc-t:Scope","dfc-t:scope":"WriteOrders"}],"@type":"rdf:List"},"termsandconditions":"https://anotherplatform.ca/terms-and-conditions","title":"anotherplatform Portal"} + JSON + end end end diff --git a/engines/dfc_provider/config/routes.rb b/engines/dfc_provider/config/routes.rb index 690fab9965..2de21e6406 100644 --- a/engines/dfc_provider/config/routes.rb +++ b/engines/dfc_provider/config/routes.rb @@ -5,7 +5,7 @@ DfcProvider::Engine.routes.draw do resources :enterprises, only: [:show] do resources :catalog_items, only: [:index, :show, :update] resources :offers, only: [:show, :update] - resources :platforms, only: [:index] + resources :platforms, only: [:index, :update] resources :supplied_products, only: [:create, :show, :update] resources :social_medias, only: [:show] end diff --git a/engines/dfc_provider/spec/requests/platforms_spec.rb b/engines/dfc_provider/spec/requests/platforms_spec.rb index 6a27c87bd8..1e493ae43e 100644 --- a/engines/dfc_provider/spec/requests/platforms_spec.rb +++ b/engines/dfc_provider/spec/requests/platforms_spec.rb @@ -28,4 +28,22 @@ RSpec.describe "Platforms", swagger_doc: "dfc.yaml" do end end end + + path "/api/dfc/enterprises/{enterprise_id}/platforms/{platform_id}" do + parameter name: :enterprise_id, in: :path, type: :string + parameter name: :platform_id, in: :path, type: :string + + put "Update authorized scopes of a platform" do + produces "application/json" + + response "200", "successful" do + let(:enterprise_id) { enterprise.id } + let(:platform_id) { "682b2e2b031c28f69cda1645" } + + run_test! do + expect(json_response["@id"]).to eq "https://anotherplatform.ca/portal/profile" + end + end + end + end end diff --git a/swagger/dfc.yaml b/swagger/dfc.yaml index 542698c35e..96ef5949af 100644 --- a/swagger/dfc.yaml +++ b/swagger/dfc.yaml @@ -620,6 +620,56 @@ paths: termsandconditions: https://anotherplatform.ca/terms-and-conditions title: anotherplatform Portal "@type": rdf:List + "/api/dfc/enterprises/{enterprise_id}/platforms/{platform_id}": + parameters: + - name: enterprise_id + in: path + required: true + schema: + type: string + - name: platform_id + in: path + required: true + schema: + type: string + put: + summary: Update authorized scopes of a platform + tags: + - Platforms + responses: + '200': + description: successful + content: + application/json: + examples: + test_example: + value: + "@id": https://anotherplatform.ca/portal/profile + "@type": dfc-t:Platform + description: A super duper portal for the waterloo region + dfc-t:hasAssignedScopes: + "@list": + - "@id": https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/ReadEnterprise + "@type": dfc-t:Scope + dfc-t:scope: ReadEnterprise + - "@id": https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/WriteEnterprise + "@type": dfc-t:Scope + dfc-t:scope: WriteEnterprise + - "@id": https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/ReadProducts + "@type": dfc-t:Scope + dfc-t:scope: ReadProducts + - "@id": https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/WriteProducts + "@type": dfc-t:Scope + dfc-t:scope: WriteProducts + - "@id": https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/ReadOrders + "@type": dfc-t:Scope + dfc-t:scope: ReadOrders + - "@id": https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/WriteOrders + "@type": dfc-t:Scope + dfc-t:scope: WriteOrders + "@type": rdf:List + termsandconditions: https://anotherplatform.ca/terms-and-conditions + title: anotherplatform Portal "/api/dfc/product_groups/{id}": parameters: - name: enterprise_id From 52aeec5ac4cf2f7628306443e81b45f073120814 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Thu, 12 Jun 2025 16:33:53 +1000 Subject: [PATCH 45/76] Update and list scopes for real --- .../form/_dfc_permissions.html.haml | 2 +- .../dfc_provider/platforms_controller.rb | 89 +++++++++++++- engines/dfc_provider/config/routes.rb | 2 +- .../spec/requests/platforms_spec.rb | 61 +++++++++- swagger/dfc.yaml | 113 ++++++++++-------- 5 files changed, 202 insertions(+), 65 deletions(-) diff --git a/app/views/admin/enterprises/form/_dfc_permissions.html.haml b/app/views/admin/enterprises/form/_dfc_permissions.html.haml index dd12047bf6..bb3f7ab6c3 100644 --- a/app/views/admin/enterprises/form/_dfc_permissions.html.haml +++ b/app/views/admin/enterprises/form/_dfc_permissions.html.haml @@ -19,7 +19,7 @@ :plain diff --git a/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb b/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb index 3b614d79a4..7da338949d 100644 --- a/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb +++ b/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb @@ -2,6 +2,12 @@ module DfcProvider class PlatformsController < DfcProvider::ApplicationController + # List of platform identifiers. + # local ID => semantic ID + PLATFORM_IDS = { + 'cqcm-dev' => "https://api.proxy-dev.cqcm.startinblox.com/profile", + }.freeze + # DANGER! # This endpoint is open to CSRF attacks. # This is a temporary measure until the DFC Permissions module accesses @@ -11,15 +17,86 @@ module DfcProvider before_action :check_enterprise def index - render json: <<~JSON - {"@context":"https://cdn.startinblox.com/owl/context-bis.jsonld","@id":"https://mydataserver.com/enterprises/1/platforms","dfc-t:platforms":{"@list":[{"@id":"https://waterlooregionfood.ca/portal/profile","@type":"dfc-t:Platform","_id":{"$oid":"682afcc4966dbb3aa7464d56"},"description":"A super duper portal for the waterloo region","dfc-t:hasAssignedScopes":{"@list":[{"@id":"https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/ReadEnterprise","@type":"dfc-t:Scope","dfc-t:scope":"ReadEnterprise"},{"@id":"https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/WriteEnterprise","@type":"dfc-t:Scope","dfc-t:scope":"WriteEnterprise"},{"@id":"https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/ReadProducts","@type":"dfc-t:Scope","dfc-t:scope":"ReadProducts"},{"@id":"https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/WriteProducts","@type":"dfc-t:Scope","dfc-t:scope":"WriteProducts"},{"@id":"https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/ReadOrders","@type":"dfc-t:Scope","dfc-t:scope":"ReadOrders"},{"@id":"https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/WriteOrders","@type":"dfc-t:Scope","dfc-t:scope":"WriteOrders"}],"@type":"rdf:List"},"termsandconditions":"https://waterlooregionfood.ca/terms-and-conditions","title":"Waterloo Region Food Portal"},{"@id":"https://anotherplatform.ca/portal/profile","@type":"dfc-t:Platform","_id":{"$oid":"682b2e2b031c28f69cda1645"},"description":"A super duper portal for the waterloo region","dfc-t:hasAssignedScopes":{"@list":[],"@type":"rdf:List"},"termsandconditions":"https://anotherplatform.ca/terms-and-conditions","title":"anotherplatform Portal"}],"@type":"rdf:List"}} - JSON + render json: platforms + end + + def show + render json: platform(params[:id]) end def update - render json: <<~JSON - {"@id":"https://anotherplatform.ca/portal/profile","@type":"dfc-t:Platform","description":"A super duper portal for the waterloo region","dfc-t:hasAssignedScopes":{"@list":[{"@id":"https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/ReadEnterprise","@type":"dfc-t:Scope","dfc-t:scope":"ReadEnterprise"},{"@id":"https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/WriteEnterprise","@type":"dfc-t:Scope","dfc-t:scope":"WriteEnterprise"},{"@id":"https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/ReadProducts","@type":"dfc-t:Scope","dfc-t:scope":"ReadProducts"},{"@id":"https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/WriteProducts","@type":"dfc-t:Scope","dfc-t:scope":"WriteProducts"},{"@id":"https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/ReadOrders","@type":"dfc-t:Scope","dfc-t:scope":"ReadOrders"},{"@id":"https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/WriteOrders","@type":"dfc-t:Scope","dfc-t:scope":"WriteOrders"}],"@type":"rdf:List"},"termsandconditions":"https://anotherplatform.ca/terms-and-conditions","title":"anotherplatform Portal"} - JSON + key = params[:id] + requested_platform = JSON.parse(request.body.read) + requested_scopes = requested_platform + .dig("dfc-t:hasAssignedScopes", "@list") + .pluck("@id") + .map { |uri| uri[/[a-zA-Z]+$/] } # return last part like ReadEnterprise + current_scopes = granted_scopes(key) + scopes_to_delete = current_scopes - requested_scopes + scopes_to_create = requested_scopes - current_scopes + + DfcPermission.where( + user: current_user, + enterprise: current_enterprise, + scope: scopes_to_delete, + grantee: key, + ).delete_all + + scopes_to_create.each do |scope| + DfcPermission.create!( + user: current_user, + enterprise: current_enterprise, + scope:, + grantee: key, + ) + end + render json: platform(key) + end + + private + + def platforms + id = DfcProvider::Engine.routes.url_helpers.enterprise_platforms_url(current_enterprise.id) + platforms = PLATFORM_IDS.keys.map(&method(:platform)) + + { + '@context': "https://cdn.startinblox.com/owl/context-bis.jsonld", + '@id': id, + 'dfc-t:platforms': { + '@type': "rdf:List", + '@list': platforms, + } + } + end + + def platform(key) + { + '@type': "dfc-t:Platform", + '@id': PLATFORM_IDS[key], + localId: key, + 'dfc-t:hasAssignedScopes': { + '@type': "rdf:List", + '@list': scopes(key), + } + } + end + + def scopes(platform_id) + granted_scopes(platform_id).map do |scope| + { + '@id': "https://example.com/scopes/#{scope}", + '@type': "dfc-t:Scope", + 'dfc-t:scope': scope, + } + end + end + + def granted_scopes(platform_id) + DfcPermission.where( + user: current_user, + enterprise: current_enterprise, + grantee: platform_id, + ).pluck(:scope) end end end diff --git a/engines/dfc_provider/config/routes.rb b/engines/dfc_provider/config/routes.rb index 2de21e6406..6c7c487040 100644 --- a/engines/dfc_provider/config/routes.rb +++ b/engines/dfc_provider/config/routes.rb @@ -5,7 +5,7 @@ DfcProvider::Engine.routes.draw do resources :enterprises, only: [:show] do resources :catalog_items, only: [:index, :show, :update] resources :offers, only: [:show, :update] - resources :platforms, only: [:index, :update] + resources :platforms, only: [:index, :show, :update] resources :supplied_products, only: [:create, :show, :update] resources :social_medias, only: [:show] end diff --git a/engines/dfc_provider/spec/requests/platforms_spec.rb b/engines/dfc_provider/spec/requests/platforms_spec.rb index 1e493ae43e..bc5ea5e372 100644 --- a/engines/dfc_provider/spec/requests/platforms_spec.rb +++ b/engines/dfc_provider/spec/requests/platforms_spec.rb @@ -23,7 +23,7 @@ RSpec.describe "Platforms", swagger_doc: "dfc.yaml" do let(:enterprise_id) { enterprise.id } run_test! do - expect(json_response["@id"]).to eq "https://mydataserver.com/enterprises/1/platforms" + expect(json_response["@id"]).to eq "http://test.host/api/dfc/enterprises/10000/platforms" end end end @@ -33,15 +33,68 @@ RSpec.describe "Platforms", swagger_doc: "dfc.yaml" do parameter name: :enterprise_id, in: :path, type: :string parameter name: :platform_id, in: :path, type: :string - put "Update authorized scopes of a platform" do + get "Show platform scopes" do produces "application/json" response "200", "successful" do let(:enterprise_id) { enterprise.id } - let(:platform_id) { "682b2e2b031c28f69cda1645" } + let(:platform_id) { "cqcm-dev" } run_test! do - expect(json_response["@id"]).to eq "https://anotherplatform.ca/portal/profile" + expect(json_response["@id"]).to eq "https://api.proxy-dev.cqcm.startinblox.com/profile" + end + end + end + + put "Update authorized scopes of a platform" do + consumes "application/json" + produces "application/json" + + parameter name: :platform, in: :body, schema: { + example: { + '@context': "https://cdn.startinblox.com/owl/context-bis.jsonld", + '@id': "http://localhost:3000/api/dfc/enterprises/3/platforms/cqcm-dev", + 'dfc-t:hasAssignedScopes': { + '@list': [ + { + '@id': "https://example.com/scopes/ReadEnterprise", + '@type': "dfc-t:Scope" + }, + { + '@id': "https://example.com/scopes/WriteEnterprise", + '@type': "dfc-t:Scope" + }, + { + '@id': "https://example.com/scopes/ReadProducts", + '@type': "dfc-t:Scope" + }, + { + '@id': "https://example.com/scopes/WriteProducts", + '@type': "dfc-t:Scope" + }, + { + '@id': "https://example.com/scopes/ReadOrders", + '@type': "dfc-t:Scope" + }, + { + '@id': "https://example.com/scopes/WriteOrders", + '@type': "dfc-t:Scope" + } + ], + '@type': "rdf:List" + } + } + } + + response "200", "successful" do + let(:enterprise_id) { enterprise.id } + let(:platform_id) { "cqcm-dev" } + let(:platform) do |example| + example.metadata[:operation][:parameters].first[:schema][:example] + end + + run_test! do + expect(json_response["@id"]).to eq "https://api.proxy-dev.cqcm.startinblox.com/profile" end end end diff --git a/swagger/dfc.yaml b/swagger/dfc.yaml index 96ef5949af..ecf27c436e 100644 --- a/swagger/dfc.yaml +++ b/swagger/dfc.yaml @@ -578,48 +578,16 @@ paths: test_example: value: "@context": https://cdn.startinblox.com/owl/context-bis.jsonld - "@id": https://mydataserver.com/enterprises/1/platforms + "@id": http://test.host/api/dfc/enterprises/10000/platforms dfc-t:platforms: - "@list": - - "@id": https://waterlooregionfood.ca/portal/profile - "@type": dfc-t:Platform - _id: - "$oid": 682afcc4966dbb3aa7464d56 - description: A super duper portal for the waterloo region - dfc-t:hasAssignedScopes: - "@list": - - "@id": https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/ReadEnterprise - "@type": dfc-t:Scope - dfc-t:scope: ReadEnterprise - - "@id": https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/WriteEnterprise - "@type": dfc-t:Scope - dfc-t:scope: WriteEnterprise - - "@id": https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/ReadProducts - "@type": dfc-t:Scope - dfc-t:scope: ReadProducts - - "@id": https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/WriteProducts - "@type": dfc-t:Scope - dfc-t:scope: WriteProducts - - "@id": https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/ReadOrders - "@type": dfc-t:Scope - dfc-t:scope: ReadOrders - - "@id": https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/WriteOrders - "@type": dfc-t:Scope - dfc-t:scope: WriteOrders - "@type": rdf:List - termsandconditions: https://waterlooregionfood.ca/terms-and-conditions - title: Waterloo Region Food Portal - - "@id": https://anotherplatform.ca/portal/profile - "@type": dfc-t:Platform - _id: - "$oid": 682b2e2b031c28f69cda1645 - description: A super duper portal for the waterloo region - dfc-t:hasAssignedScopes: - "@list": [] - "@type": rdf:List - termsandconditions: https://anotherplatform.ca/terms-and-conditions - title: anotherplatform Portal "@type": rdf:List + "@list": + - "@type": dfc-t:Platform + "@id": https://api.proxy-dev.cqcm.startinblox.com/profile + localId: cqcm-dev + dfc-t:hasAssignedScopes: + "@type": rdf:List + "@list": [] "/api/dfc/enterprises/{enterprise_id}/platforms/{platform_id}": parameters: - name: enterprise_id @@ -632,8 +600,8 @@ paths: required: true schema: type: string - put: - summary: Update authorized scopes of a platform + get: + summary: Show platform scopes tags: - Platforms responses: @@ -644,32 +612,71 @@ paths: examples: test_example: value: - "@id": https://anotherplatform.ca/portal/profile "@type": dfc-t:Platform - description: A super duper portal for the waterloo region + "@id": https://api.proxy-dev.cqcm.startinblox.com/profile + localId: cqcm-dev dfc-t:hasAssignedScopes: + "@type": rdf:List + "@list": [] + put: + summary: Update authorized scopes of a platform + parameters: [] + tags: + - Platforms + responses: + '200': + description: successful + content: + application/json: + examples: + test_example: + value: + "@type": dfc-t:Platform + "@id": https://api.proxy-dev.cqcm.startinblox.com/profile + localId: cqcm-dev + dfc-t:hasAssignedScopes: + "@type": rdf:List "@list": - - "@id": https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/ReadEnterprise + - "@id": https://example.com/scopes/ReadEnterprise "@type": dfc-t:Scope dfc-t:scope: ReadEnterprise - - "@id": https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/WriteEnterprise + - "@id": https://example.com/scopes/WriteEnterprise "@type": dfc-t:Scope dfc-t:scope: WriteEnterprise - - "@id": https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/ReadProducts + - "@id": https://example.com/scopes/ReadProducts "@type": dfc-t:Scope dfc-t:scope: ReadProducts - - "@id": https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/WriteProducts + - "@id": https://example.com/scopes/WriteProducts "@type": dfc-t:Scope dfc-t:scope: WriteProducts - - "@id": https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/ReadOrders + - "@id": https://example.com/scopes/ReadOrders "@type": dfc-t:Scope dfc-t:scope: ReadOrders - - "@id": https://data-server.cqcm.startinblox.com/enterprises/1/platforms/scopes/WriteOrders + - "@id": https://example.com/scopes/WriteOrders "@type": dfc-t:Scope dfc-t:scope: WriteOrders - "@type": rdf:List - termsandconditions: https://anotherplatform.ca/terms-and-conditions - title: anotherplatform Portal + requestBody: + content: + application/json: + schema: + example: + "@context": https://cdn.startinblox.com/owl/context-bis.jsonld + "@id": http://localhost:3000/api/dfc/enterprises/3/platforms/cqcm-dev + dfc-t:hasAssignedScopes: + "@list": + - "@id": https://example.com/scopes/ReadEnterprise + "@type": dfc-t:Scope + - "@id": https://example.com/scopes/WriteEnterprise + "@type": dfc-t:Scope + - "@id": https://example.com/scopes/ReadProducts + "@type": dfc-t:Scope + - "@id": https://example.com/scopes/WriteProducts + "@type": dfc-t:Scope + - "@id": https://example.com/scopes/ReadOrders + "@type": dfc-t:Scope + - "@id": https://example.com/scopes/WriteOrders + "@type": dfc-t:Scope + "@type": rdf:List "/api/dfc/product_groups/{id}": parameters: - name: enterprise_id From f65e4797cff01fd29ad89c9ba7284bbfbafe7ec2 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Thu, 3 Jul 2025 14:40:03 +1000 Subject: [PATCH 46/76] Add feature toggle for DFC dev platform --- app/helpers/admin/enterprises_helper.rb | 14 +++++++++++--- .../enterprises/form/_connected_apps.html.haml | 4 ++-- .../dfc_provider/application_controller.rb | 5 +++++ .../dfc_provider/platforms_controller.rb | 6 +++++- lib/open_food_network/feature_toggle.rb | 3 +++ spec/helpers/admin/enterprises_helper_spec.rb | 5 +++++ 6 files changed, 31 insertions(+), 6 deletions(-) diff --git a/app/helpers/admin/enterprises_helper.rb b/app/helpers/admin/enterprises_helper.rb index 83d1423fc5..82d3c01c99 100644 --- a/app/helpers/admin/enterprises_helper.rb +++ b/app/helpers/admin/enterprises_helper.rb @@ -26,8 +26,8 @@ module Admin show_enterprise_fees = can?(:manage_enterprise_fees, enterprise) && (is_shop || enterprise.is_primary_producer) show_connected_apps = can?(:manage_connected_apps, enterprise) && - feature?(:connected_apps, spree_current_user, enterprise) && - Spree::Config.connected_apps_enabled.present? + (connected_apps_enabled(enterprise).present? || + dfc_platforms_available?) show_inventory_settings = feature?(:inventory, spree_current_user.enterprises) && is_shop show_options = { @@ -42,11 +42,19 @@ module Admin build_enterprise_side_menu_items(is_shop:, show_options:) end - def connected_apps_enabled + def connected_apps_enabled(enterprise) + return [] unless feature?(:connected_apps, spree_current_user, enterprise) + connected_apps_enabled = Spree::Config.connected_apps_enabled&.split(',') || [] ConnectedApp::TYPES & connected_apps_enabled end + def dfc_platforms_available? + DfcProvider::PlatformsController::PLATFORM_IDS.keys.any? do |id| + feature?(id) + end + end + def enterprise_attachment_removal_modal_id attachment_removal_parameter # remove_logo|remove_promo_image|remove_white_label_logo end diff --git a/app/views/admin/enterprises/form/_connected_apps.html.haml b/app/views/admin/enterprises/form/_connected_apps.html.haml index 0bb3ecf85d..ac531eb315 100644 --- a/app/views/admin/enterprises/form/_connected_apps.html.haml +++ b/app/views/admin/enterprises/form/_connected_apps.html.haml @@ -1,5 +1,5 @@ -- connected_apps_enabled.each do |type| +- connected_apps_enabled(enterprise).each do |type| = render partial: "/admin/enterprises/form/connected_apps/#{type}", locals: { enterprise:, connected_app: enterprise.connected_apps.public_send(type).first } -= render partial: "/admin/enterprises/form/dfc_permissions" += render partial: "/admin/enterprises/form/dfc_permissions" if dfc_platforms_available? diff --git a/engines/dfc_provider/app/controllers/dfc_provider/application_controller.rb b/engines/dfc_provider/app/controllers/dfc_provider/application_controller.rb index 16ebe0411e..d403ba8958 100644 --- a/engines/dfc_provider/app/controllers/dfc_provider/application_controller.rb +++ b/engines/dfc_provider/app/controllers/dfc_provider/application_controller.rb @@ -63,5 +63,10 @@ module DfcProvider def import DfcIo.import(request.body) end + + # Checks weather a feature is enabled for any of the given actors. + def feature?(feature, *actors) + OpenFoodNetwork::FeatureToggle.enabled?(feature, *actors) + end end end diff --git a/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb b/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb index 7da338949d..374e82855f 100644 --- a/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb +++ b/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb @@ -57,7 +57,7 @@ module DfcProvider def platforms id = DfcProvider::Engine.routes.url_helpers.enterprise_platforms_url(current_enterprise.id) - platforms = PLATFORM_IDS.keys.map(&method(:platform)) + platforms = available_platforms.map(&method(:platform)) { '@context': "https://cdn.startinblox.com/owl/context-bis.jsonld", @@ -69,6 +69,10 @@ module DfcProvider } end + def available_platforms + PLATFORM_IDS.keys.select(&method(:feature?)) + end + def platform(key) { '@type': "dfc-t:Platform", diff --git a/lib/open_food_network/feature_toggle.rb b/lib/open_food_network/feature_toggle.rb index daef91de47..42a1cb6642 100644 --- a/lib/open_food_network/feature_toggle.rb +++ b/lib/open_food_network/feature_toggle.rb @@ -64,6 +64,9 @@ module OpenFoodNetwork "hub_address" => <<~DESC, Show the hub's address as shipping address on pickup orders. DESC + "cqcm-dev" => <<~DESC, + Show DFC Permissions interface to share data with CQCM dev platform. + DESC }.merge(conditional_features).freeze; # Features you would like to be enabled to start with. diff --git a/spec/helpers/admin/enterprises_helper_spec.rb b/spec/helpers/admin/enterprises_helper_spec.rb index a2b1e24255..84c9c5884d 100644 --- a/spec/helpers/admin/enterprises_helper_spec.rb +++ b/spec/helpers/admin/enterprises_helper_spec.rb @@ -38,5 +38,10 @@ RSpec.describe Admin::EnterprisesHelper do expect(visible_items.pluck(:name)).to include "inventory_settings" end end + + it "hides Connected Apps by default" do + user.enterprises << enterprise + expect(visible_items.pluck(:name)).not_to include "connected_apps" + end end end From 994f1ca6c6873bbf6d1f2cc098cf63afcec19295 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Tue, 29 Jul 2025 13:39:53 +1000 Subject: [PATCH 47/76] Update scope ids --- .../app/controllers/dfc_provider/platforms_controller.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb b/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb index 374e82855f..33bb0f3a56 100644 --- a/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb +++ b/engines/dfc_provider/app/controllers/dfc_provider/platforms_controller.rb @@ -88,9 +88,8 @@ module DfcProvider def scopes(platform_id) granted_scopes(platform_id).map do |scope| { - '@id': "https://example.com/scopes/#{scope}", + '@id': "https://github.com/datafoodconsortium/taxonomies/releases/latest/download/scopes.rdf##{scope}", '@type': "dfc-t:Scope", - 'dfc-t:scope': scope, } end end From 9d284b7110bc70a9c2ffc61bc38d42b815032c6e Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Tue, 29 Jul 2025 13:41:07 +1000 Subject: [PATCH 48/76] Set language to display scope labels --- app/views/admin/enterprises/form/_dfc_permissions.html.haml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/views/admin/enterprises/form/_dfc_permissions.html.haml b/app/views/admin/enterprises/form/_dfc_permissions.html.haml index bb3f7ab6c3..ffdb95fe46 100644 --- a/app/views/admin/enterprises/form/_dfc_permissions.html.haml +++ b/app/views/admin/enterprises/form/_dfc_permissions.html.haml @@ -21,7 +21,9 @@ + noRouter + auto-lang + lang="en"> %script{type: "module", src: "https://cdn.jsdelivr.net/npm/@startinblox/solid-data-permissioning@latest/dist/index.js"} From 2d3f18a71b6ed111fa33589fe83ff20a65386af4 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Thu, 3 Jul 2025 16:32:07 +1000 Subject: [PATCH 49/76] Load DFC Permissions module in system spec But we can't access the inside of the component yet. --- .../admin/enterprises/dfc_permissions_spec.rb | 32 +++++++++++++++++++ spec/system/support/cuprite_setup.rb | 7 ++++ 2 files changed, 39 insertions(+) create mode 100644 spec/system/admin/enterprises/dfc_permissions_spec.rb diff --git a/spec/system/admin/enterprises/dfc_permissions_spec.rb b/spec/system/admin/enterprises/dfc_permissions_spec.rb new file mode 100644 index 0000000000..8f84d072da --- /dev/null +++ b/spec/system/admin/enterprises/dfc_permissions_spec.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require "system_helper" + +RSpec.describe "DFC Permissions", feature: "cqcm-dev", vcr: true do + let(:enterprise) { create(:enterprise) } + + before do + login_as enterprise.owner + end + + it "is not visible when no platform is enabled" do + Flipper.disable("cqcm-dev") + visit edit_admin_enterprise_path(enterprise) + expect(page).not_to have_content "CONNECTED APPS" + end + + it "can share data with another platform" do + visit edit_admin_enterprise_path(enterprise) + + scroll_to :bottom + click_link "Connected apps" + + # TODO: interact with shadow root of web component + # + # expect(page).to have_content "Proxy Dev Portal" + # expect(page).to have_selector "svg.unchecked" # permission not granted + + # click_on "Agree and share" + # expect(page).to have_selector "svg.checked" # permission granted + end +end diff --git a/spec/system/support/cuprite_setup.rb b/spec/system/support/cuprite_setup.rb index 6d44e72ba3..1d5c195b0c 100644 --- a/spec/system/support/cuprite_setup.rb +++ b/spec/system/support/cuprite_setup.rb @@ -20,6 +20,12 @@ Capybara.register_driver(:cuprite_ofn) do |app| url_whitelist: [ %r{^http://localhost}, %r{^http://0.0.0.0}, %r{http://127.0.0.1}, + # Testing the DFC Permissions component by Startin'Blox: + %r{^https://cdn.jsdelivr.net/npm/@startinblox/}, + %r{^https://cdn.startinblox.com/}, + %r{^https://data-server.cqcm.startinblox.com/scopes$}, + %r{^https://api.proxy-dev.cqcm.startinblox.com/profile$}, + # Just for testing external connections: spec/system/billy_spec.rb %r{^https?://deb.debian.org}, ], @@ -44,6 +50,7 @@ RSpec.configure do |config| original_host = Rails.application.default_url_options[:host] Rails.application.default_url_options[:host] = "#{Capybara.current_session.server.host}:#{Capybara.current_session.server.port}" + DfcProvider::Engine.routes.default_url_options = Rails.application.default_url_options example.run Rails.application.default_url_options[:host] = original_host remove_downloaded_files From 210201514e8e2adea5a98d30f16f335ca8f96f41 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Tue, 29 Jul 2025 15:49:11 +1000 Subject: [PATCH 50/76] Add gem capybara-shadowdom to access web component --- Gemfile | 1 + Gemfile.lock | 3 + .../can_share_data_with_another_platform.yml | 54283 ++++++++++++++++ ...ot_visible_when_no_platform_is_enabled.yml | 157 + .../admin/enterprises/dfc_permissions_spec.rb | 6 +- 5 files changed, 54448 insertions(+), 2 deletions(-) create mode 100644 spec/fixtures/vcr_cassettes/DFC_Permissions/can_share_data_with_another_platform.yml create mode 100644 spec/fixtures/vcr_cassettes/DFC_Permissions/is_not_visible_when_no_platform_is_enabled.yml diff --git a/Gemfile b/Gemfile index fd77f79d72..bec0c273b9 100644 --- a/Gemfile +++ b/Gemfile @@ -152,6 +152,7 @@ end group :test, :development do gem 'bullet' gem 'capybara' + gem 'capybara-shadowdom' gem 'cuprite' gem 'database_cleaner', require: false gem 'debug', '>= 1.0.0' diff --git a/Gemfile.lock b/Gemfile.lock index 8877750db8..3ce3f396cb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -221,6 +221,8 @@ GEM rack-test (>= 0.6.3) regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) + capybara-shadowdom (0.3.0) + capybara caxlsx (3.3.0) htmlentities (~> 4.3, >= 4.3.4) marcel (~> 1.0) @@ -941,6 +943,7 @@ DEPENDENCIES cable_ready cancancan (~> 1.15.0) capybara + capybara-shadowdom catalog! coffee-rails (~> 5.0.0) combine_pdf diff --git a/spec/fixtures/vcr_cassettes/DFC_Permissions/can_share_data_with_another_platform.yml b/spec/fixtures/vcr_cassettes/DFC_Permissions/can_share_data_with_another_platform.yml new file mode 100644 index 0000000000..31eeb8ce0e --- /dev/null +++ b/spec/fixtures/vcr_cassettes/DFC_Permissions/can_share_data_with_another_platform.yml @@ -0,0 +1,54283 @@ +--- +http_interactions: +- request: + method: get + uri: https://cdn.jsdelivr.net/npm/@startinblox/solid-data-permissioning@latest/dist/index.js + body: + encoding: UTF-8 + string: '' + headers: + Connection: + - close + Origin: + - http://127.0.0.1:37737 + Sec-Ch-Ua-Platform: + - '"Linux"' + User-Agent: + - Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/138.0.0.0 + Safari/537.36 + Sec-Ch-Ua: + - '"Not)A;Brand";v="8", "Chromium";v="138"' + Sec-Ch-Ua-Mobile: + - "?0" + Accept: + - "*/*" + Sec-Fetch-Site: + - cross-site + Sec-Fetch-Mode: + - cors + Sec-Fetch-Dest: + - script + Referer: + - http://127.0.0.1:37737/ + Accept-Encoding: + - '' + Accept-Language: + - en-US,en;q=0.9 + response: + status: + code: 200 + message: OK + headers: + Date: + - Tue, 29 Jul 2025 04:39:23 GMT + Content-Type: + - application/javascript; charset=utf-8 + Transfer-Encoding: + - chunked + Connection: + - close + Cf-Ray: + - 9669eea4a8a1ca17-MEL + Access-Control-Allow-Origin: + - "*" + Access-Control-Expose-Headers: + - "*" + Timing-Allow-Origin: + - "*" + Cache-Control: + - public, max-age=604800, s-maxage=43200 + Cross-Origin-Resource-Policy: + - cross-origin + X-Content-Type-Options: + - nosniff + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + X-Jsd-Version: + - 3.2.2 + X-Jsd-Version-Type: + - version + Etag: + - W/"e66c-aEp9ZysNY7uIOkRFiyu/1NxBypM" + X-Served-By: + - cache-fra-eddf8230119-FRA, cache-lga21941-LGA + X-Cache: + - HIT, MISS + Vary: + - Accept-Encoding + Alt-Svc: + - h3=":443"; ma=86400 + Cf-Cache-Status: + - HIT + Age: + - '7599' + Report-To: + - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=0g0Pzpgi7hqlcZ%2B9bDw7Ogpr9Y2khGiZd6lI%2BlLaM4WsEKx04x7birCnmPguAKVLXrwe88bGKbNdXTUfPs7JwA1RYjGlyaoBgRrzc0GvdBv80tpyqnxyh5LGyXoqcQ0LJMo%3D"}],"group":"cf-nel","max_age":604800}' + Nel: + - '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}' + Server: + - cloudflare + body: + encoding: UTF-8 + string: "/**\n * @license\n * Copyright 2019 Google LLC\n * SPDX-License-Identifier: + BSD-3-Clause\n */\nconst W = globalThis, lt = W.ShadowRoot && (W.ShadyCSS + === void 0 || W.ShadyCSS.nativeShadow) && \"adoptedStyleSheets\" in Document.prototype + && \"replace\" in CSSStyleSheet.prototype, ct = Symbol(), gt = /* @__PURE__ + */ new WeakMap();\nlet Tt = class {\n constructor(t, s, i) {\n if (this._$cssResult$ + = !0, i !== ct) throw Error(\"CSSResult is not constructable. Use `unsafeCSS` + or `css` instead.\");\n this.cssText = t, this.t = s;\n }\n get styleSheet() + {\n let t = this.o;\n const s = this.t;\n if (lt && t === void 0) + {\n const i = s !== void 0 && s.length === 1;\n i && (t = gt.get(s)), + t === void 0 && ((this.o = t = new CSSStyleSheet()).replaceSync(this.cssText), + i && gt.set(s, t));\n }\n return t;\n }\n toString() {\n return + this.cssText;\n }\n};\nconst N = (e) => new Tt(typeof e == \"string\" ? e + : e + \"\", void 0, ct), Z = (e, ...t) => {\n const s = e.length === 1 ? + e[0] : t.reduce((i, o, r) => i + ((n) => {\n if (n._$cssResult$ === !0) + return n.cssText;\n if (typeof n == \"number\") return n;\n throw Error(\"Value + passed to 'css' function must be a 'css' function result: \" + n + \". Use + 'unsafeCSS' to pass non-literal values, but take care to ensure page security.\");\n + \ })(o) + e[r + 1], e[0]);\n return new Tt(s, e, ct);\n}, Jt = (e, t) => + {\n if (lt) e.adoptedStyleSheets = t.map((s) => s instanceof CSSStyleSheet + ? s : s.styleSheet);\n else for (const s of t) {\n const i = document.createElement(\"style\"), + o = W.litNonce;\n o !== void 0 && i.setAttribute(\"nonce\", o), i.textContent + = s.cssText, e.appendChild(i);\n }\n}, bt = lt ? (e) => e : (e) => e instanceof + CSSStyleSheet ? ((t) => {\n let s = \"\";\n for (const i of t.cssRules) + s += i.cssText;\n return N(s);\n})(e) : e;\n/**\n * @license\n * Copyright + 2017 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n */\nconst { is: + Kt, defineProperty: Yt, getOwnPropertyDescriptor: Zt, getOwnPropertyNames: + Gt, getOwnPropertySymbols: Qt, getPrototypeOf: te } = Object, G = globalThis, + mt = G.trustedTypes, ee = mt ? mt.emptyScript : \"\", se = G.reactiveElementPolyfillSupport, + L = (e, t) => e, B = { toAttribute(e, t) {\n switch (t) {\n case Boolean:\n + \ e = e ? ee : null;\n break;\n case Object:\n case Array:\n + \ e = e == null ? e : JSON.stringify(e);\n }\n return e;\n}, fromAttribute(e, + t) {\n let s = e;\n switch (t) {\n case Boolean:\n s = e !== null;\n + \ break;\n case Number:\n s = e === null ? null : Number(e);\n + \ break;\n case Object:\n case Array:\n try {\n s = + JSON.parse(e);\n } catch {\n s = null;\n }\n }\n return + s;\n} }, Q = (e, t) => !Kt(e, t), yt = { attribute: !0, type: String, converter: + B, reflect: !1, useDefault: !1, hasChanged: Q };\nSymbol.metadata ??= Symbol(\"metadata\"), + G.litPropertyMetadata ??= /* @__PURE__ */ new WeakMap();\nlet C = class extends + HTMLElement {\n static addInitializer(t) {\n this._$Ei(), (this.l ??= + []).push(t);\n }\n static get observedAttributes() {\n return this.finalize(), + this._$Eh && [...this._$Eh.keys()];\n }\n static createProperty(t, s = yt) + {\n if (s.state && (s.attribute = !1), this._$Ei(), this.prototype.hasOwnProperty(t) + && ((s = Object.create(s)).wrapped = !0), this.elementProperties.set(t, s), + !s.noAccessor) {\n const i = Symbol(), o = this.getPropertyDescriptor(t, + i, s);\n o !== void 0 && Yt(this.prototype, t, o);\n }\n }\n static + getPropertyDescriptor(t, s, i) {\n const { get: o, set: r } = Zt(this.prototype, + t) ?? { get() {\n return this[s];\n }, set(n) {\n this[s] = n;\n + \ } };\n return { get: o, set(n) {\n const l = o?.call(this);\n + \ r?.call(this, n), this.requestUpdate(t, l, i);\n }, configurable: + !0, enumerable: !0 };\n }\n static getPropertyOptions(t) {\n return this.elementProperties.get(t) + ?? yt;\n }\n static _$Ei() {\n if (this.hasOwnProperty(L(\"elementProperties\"))) + return;\n const t = te(this);\n t.finalize(), t.l !== void 0 && (this.l + = [...t.l]), this.elementProperties = new Map(t.elementProperties);\n }\n + \ static finalize() {\n if (this.hasOwnProperty(L(\"finalized\"))) return;\n + \ if (this.finalized = !0, this._$Ei(), this.hasOwnProperty(L(\"properties\"))) + {\n const s = this.properties, i = [...Gt(s), ...Qt(s)];\n for (const + o of i) this.createProperty(o, s[o]);\n }\n const t = this[Symbol.metadata];\n + \ if (t !== null) {\n const s = litPropertyMetadata.get(t);\n if + (s !== void 0) for (const [i, o] of s) this.elementProperties.set(i, o);\n + \ }\n this._$Eh = /* @__PURE__ */ new Map();\n for (const [s, i] of + this.elementProperties) {\n const o = this._$Eu(s, i);\n o !== void + 0 && this._$Eh.set(o, s);\n }\n this.elementStyles = this.finalizeStyles(this.styles);\n + \ }\n static finalizeStyles(t) {\n const s = [];\n if (Array.isArray(t)) + {\n const i = new Set(t.flat(1 / 0).reverse());\n for (const o of + i) s.unshift(bt(o));\n } else t !== void 0 && s.push(bt(t));\n return + s;\n }\n static _$Eu(t, s) {\n const i = s.attribute;\n return i === + !1 ? void 0 : typeof i == \"string\" ? i : typeof t == \"string\" ? t.toLowerCase() + : void 0;\n }\n constructor() {\n super(), this._$Ep = void 0, this.isUpdatePending + = !1, this.hasUpdated = !1, this._$Em = null, this._$Ev();\n }\n _$Ev() + {\n this._$ES = new Promise((t) => this.enableUpdating = t), this._$AL + = /* @__PURE__ */ new Map(), this._$E_(), this.requestUpdate(), this.constructor.l?.forEach((t) + => t(this));\n }\n addController(t) {\n (this._$EO ??= /* @__PURE__ */ + new Set()).add(t), this.renderRoot !== void 0 && this.isConnected && t.hostConnected?.();\n + \ }\n removeController(t) {\n this._$EO?.delete(t);\n }\n _$E_() {\n + \ const t = /* @__PURE__ */ new Map(), s = this.constructor.elementProperties;\n + \ for (const i of s.keys()) this.hasOwnProperty(i) && (t.set(i, this[i]), + delete this[i]);\n t.size > 0 && (this._$Ep = t);\n }\n createRenderRoot() + {\n const t = this.shadowRoot ?? this.attachShadow(this.constructor.shadowRootOptions);\n + \ return Jt(t, this.constructor.elementStyles), t;\n }\n connectedCallback() + {\n this.renderRoot ??= this.createRenderRoot(), this.enableUpdating(!0), + this._$EO?.forEach((t) => t.hostConnected?.());\n }\n enableUpdating(t) + {\n }\n disconnectedCallback() {\n this._$EO?.forEach((t) => t.hostDisconnected?.());\n + \ }\n attributeChangedCallback(t, s, i) {\n this._$AK(t, i);\n }\n _$ET(t, + s) {\n const i = this.constructor.elementProperties.get(t), o = this.constructor._$Eu(t, + i);\n if (o !== void 0 && i.reflect === !0) {\n const r = (i.converter?.toAttribute + !== void 0 ? i.converter : B).toAttribute(s, i.type);\n this._$Em = t, + r == null ? this.removeAttribute(o) : this.setAttribute(o, r), this._$Em = + null;\n }\n }\n _$AK(t, s) {\n const i = this.constructor, o = i._$Eh.get(t);\n + \ if (o !== void 0 && this._$Em !== o) {\n const r = i.getPropertyOptions(o), + n = typeof r.converter == \"function\" ? { fromAttribute: r.converter } : + r.converter?.fromAttribute !== void 0 ? r.converter : B;\n this._$Em + = o, this[o] = n.fromAttribute(s, r.type) ?? this._$Ej?.get(o) ?? null, this._$Em + = null;\n }\n }\n requestUpdate(t, s, i) {\n if (t !== void 0) {\n + \ const o = this.constructor, r = this[t];\n if (i ??= o.getPropertyOptions(t), + !((i.hasChanged ?? Q)(r, s) || i.useDefault && i.reflect && r === this._$Ej?.get(t) + && !this.hasAttribute(o._$Eu(t, i)))) return;\n this.C(t, s, i);\n }\n + \ this.isUpdatePending === !1 && (this._$ES = this._$EP());\n }\n C(t, + s, { useDefault: i, reflect: o, wrapped: r }, n) {\n i && !(this._$Ej ??= + /* @__PURE__ */ new Map()).has(t) && (this._$Ej.set(t, n ?? s ?? this[t]), + r !== !0 || n !== void 0) || (this._$AL.has(t) || (this.hasUpdated || i || + (s = void 0), this._$AL.set(t, s)), o === !0 && this._$Em !== t && (this._$Eq + ??= /* @__PURE__ */ new Set()).add(t));\n }\n async _$EP() {\n this.isUpdatePending + = !0;\n try {\n await this._$ES;\n } catch (s) {\n Promise.reject(s);\n + \ }\n const t = this.scheduleUpdate();\n return t != null && await + t, !this.isUpdatePending;\n }\n scheduleUpdate() {\n return this.performUpdate();\n + \ }\n performUpdate() {\n if (!this.isUpdatePending) return;\n if (!this.hasUpdated) + {\n if (this.renderRoot ??= this.createRenderRoot(), this._$Ep) {\n for + (const [o, r] of this._$Ep) this[o] = r;\n this._$Ep = void 0;\n }\n + \ const i = this.constructor.elementProperties;\n if (i.size > 0) + for (const [o, r] of i) {\n const { wrapped: n } = r, l = this[o];\n + \ n !== !0 || this._$AL.has(o) || l === void 0 || this.C(o, void 0, + r, l);\n }\n }\n let t = !1;\n const s = this._$AL;\n try + {\n t = this.shouldUpdate(s), t ? (this.willUpdate(s), this._$EO?.forEach((i) + => i.hostUpdate?.()), this.update(s)) : this._$EM();\n } catch (i) {\n + \ throw t = !1, this._$EM(), i;\n }\n t && this._$AE(s);\n }\n + \ willUpdate(t) {\n }\n _$AE(t) {\n this._$EO?.forEach((s) => s.hostUpdated?.()), + this.hasUpdated || (this.hasUpdated = !0, this.firstUpdated(t)), this.updated(t);\n + \ }\n _$EM() {\n this._$AL = /* @__PURE__ */ new Map(), this.isUpdatePending + = !1;\n }\n get updateComplete() {\n return this.getUpdateComplete();\n + \ }\n getUpdateComplete() {\n return this._$ES;\n }\n shouldUpdate(t) + {\n return !0;\n }\n update(t) {\n this._$Eq &&= this._$Eq.forEach((s) + => this._$ET(s, this[s])), this._$EM();\n }\n updated(t) {\n }\n firstUpdated(t) + {\n }\n};\nC.elementStyles = [], C.shadowRootOptions = { mode: \"open\" }, + C[L(\"elementProperties\")] = /* @__PURE__ */ new Map(), C[L(\"finalized\")] + = /* @__PURE__ */ new Map(), se?.({ ReactiveElement: C }), (G.reactiveElementVersions + ??= []).push(\"2.1.0\");\n/**\n * @license\n * Copyright 2017 Google LLC\n + * SPDX-License-Identifier: BSD-3-Clause\n */\nconst ht = globalThis, F = ht.trustedTypes, + vt = F ? F.createPolicy(\"lit-html\", { createHTML: (e) => e }) : void 0, + Rt = \"$lit$\", w = `lit$${Math.random().toFixed(9).slice(2)}$`, Lt = \"?\" + + w, ie = `<${Lt}>`, S = document, U = () => S.createComment(\"\"), D = (e) + => e === null || typeof e != \"object\" && typeof e != \"function\", pt = + Array.isArray, oe = (e) => pt(e) || typeof e?.[Symbol.iterator] == \"function\", + it = `[ \t\n\\f\\r]`, R = /<(?:(!--|\\/[^a-zA-Z])|(\\/?[a-zA-Z][^>\\s]*)|(\\/?$))/g, + $t = /-->/g, wt = />/g, x = RegExp(`>|${it}(?:([^\\\\s\"'>=/]+)(${it}*=${it}*(?:[^ + \t\n\\f\\r\"'\\`<>=]|(\"|')|))|$)`, \"g\"), _t = /'/g, xt = /\"/g, Ut = /^(?:script|style|textarea|title)$/i, + re = (e) => (t, ...s) => ({ _$litType$: e, strings: t, values: s }), f = re(1), + O = Symbol.for(\"lit-noChange\"), h = Symbol.for(\"lit-nothing\"), At = /* + @__PURE__ */ new WeakMap(), k = S.createTreeWalker(S, 129);\nfunction Dt(e, + t) {\n if (!pt(e) || !e.hasOwnProperty(\"raw\")) throw Error(\"invalid template + strings array\");\n return vt !== void 0 ? vt.createHTML(t) : t;\n}\nconst + ne = (e, t) => {\n const s = e.length - 1, i = [];\n let o, r = t === 2 + ? \"\" : t === 3 ? \"\" : \"\", n = R;\n for (let l = 0; l < s; + l++) {\n const a = e[l];\n let c, u, p = -1, b = 0;\n for (; b < + a.length && (n.lastIndex = b, u = n.exec(a), u !== null); ) b = n.lastIndex, + n === R ? u[1] === \"!--\" ? n = $t : u[1] !== void 0 ? n = wt : u[2] !== + void 0 ? (Ut.test(u[2]) && (o = RegExp(\"\" ? (n = o ?? R, p = -1) : + u[1] === void 0 ? p = -2 : (p = n.lastIndex - u[2].length, c = u[1], n = u[3] + === void 0 ? x : u[3] === '\"' ? xt : _t) : n === xt || n === _t ? n = x : + n === $t || n === wt ? n = R : (n = x, o = void 0);\n const $ = n === x + && e[l + 1].startsWith(\"/>\") ? \" \" : \"\";\n r += n === R ? a + ie + : p >= 0 ? (i.push(c), a.slice(0, p) + Rt + a.slice(p) + w + $) : a + w + + (p === -2 ? l : $);\n }\n return [Dt(e, r + (e[s] || \"\") + (t === 2 + ? \"\" : t === 3 ? \"\" : \"\")), i];\n};\nclass M {\n constructor({ + strings: t, _$litType$: s }, i) {\n let o;\n this.parts = [];\n let + r = 0, n = 0;\n const l = t.length - 1, a = this.parts, [c, u] = ne(t, + s);\n if (this.el = M.createElement(c, i), k.currentNode = this.el.content, + s === 2 || s === 3) {\n const p = this.el.content.firstChild;\n p.replaceWith(...p.childNodes);\n + \ }\n for (; (o = k.nextNode()) !== null && a.length < l; ) {\n if + (o.nodeType === 1) {\n if (o.hasAttributes()) for (const p of o.getAttributeNames()) + if (p.endsWith(Rt)) {\n const b = u[n++], $ = o.getAttribute(p).split(w), + z = /([.?@])?(.*)/.exec(b);\n a.push({ type: 1, index: r, name: z[2], + strings: $, ctor: z[1] === \".\" ? le : z[1] === \"?\" ? ce : z[1] === \"@\" + ? he : tt }), o.removeAttribute(p);\n } else p.startsWith(w) && (a.push({ + type: 6, index: r }), o.removeAttribute(p));\n if (Ut.test(o.tagName)) + {\n const p = o.textContent.split(w), b = p.length - 1;\n if + (b > 0) {\n o.textContent = F ? F.emptyScript : \"\";\n for + (let $ = 0; $ < b; $++) o.append(p[$], U()), k.nextNode(), a.push({ type: + 2, index: ++r });\n o.append(p[b], U());\n }\n }\n + \ } else if (o.nodeType === 8) if (o.data === Lt) a.push({ type: 2, index: + r });\n else {\n let p = -1;\n for (; (p = o.data.indexOf(w, + p + 1)) !== -1; ) a.push({ type: 7, index: r }), p += w.length - 1;\n }\n + \ r++;\n }\n }\n static createElement(t, s) {\n const i = S.createElement(\"template\");\n + \ return i.innerHTML = t, i;\n }\n}\nfunction T(e, t, s = e, i) {\n if + (t === O) return t;\n let o = i !== void 0 ? s._$Co?.[i] : s._$Cl;\n const + r = D(t) ? void 0 : t._$litDirective$;\n return o?.constructor !== r && (o?._$AO?.(!1), + r === void 0 ? o = void 0 : (o = new r(e), o._$AT(e, s, i)), i !== void 0 + ? (s._$Co ??= [])[i] = o : s._$Cl = o), o !== void 0 && (t = T(e, o._$AS(e, + t.values), o, i)), t;\n}\nclass ae {\n constructor(t, s) {\n this._$AV + = [], this._$AN = void 0, this._$AD = t, this._$AM = s;\n }\n get parentNode() + {\n return this._$AM.parentNode;\n }\n get _$AU() {\n return this._$AM._$AU;\n + \ }\n u(t) {\n const { el: { content: s }, parts: i } = this._$AD, o = + (t?.creationScope ?? S).importNode(s, !0);\n k.currentNode = o;\n let + r = k.nextNode(), n = 0, l = 0, a = i[0];\n for (; a !== void 0; ) {\n + \ if (n === a.index) {\n let c;\n a.type === 2 ? c = new + H(r, r.nextSibling, this, t) : a.type === 1 ? c = new a.ctor(r, a.name, a.strings, + this, t) : a.type === 6 && (c = new pe(r, this, t)), this._$AV.push(c), a + = i[++l];\n }\n n !== a?.index && (r = k.nextNode(), n++);\n }\n + \ return k.currentNode = S, o;\n }\n p(t) {\n let s = 0;\n for (const + i of this._$AV) i !== void 0 && (i.strings !== void 0 ? (i._$AI(t, i, s), + s += i.strings.length - 2) : i._$AI(t[s])), s++;\n }\n}\nclass H {\n get + _$AU() {\n return this._$AM?._$AU ?? this._$Cv;\n }\n constructor(t, + s, i, o) {\n this.type = 2, this._$AH = h, this._$AN = void 0, this._$AA + = t, this._$AB = s, this._$AM = i, this.options = o, this._$Cv = o?.isConnected + ?? !0;\n }\n get parentNode() {\n let t = this._$AA.parentNode;\n const + s = this._$AM;\n return s !== void 0 && t?.nodeType === 11 && (t = s.parentNode), + t;\n }\n get startNode() {\n return this._$AA;\n }\n get endNode() + {\n return this._$AB;\n }\n _$AI(t, s = this) {\n t = T(this, t, s), + D(t) ? t === h || t == null || t === \"\" ? (this._$AH !== h && this._$AR(), + this._$AH = h) : t !== this._$AH && t !== O && this._(t) : t._$litType$ !== + void 0 ? this.$(t) : t.nodeType !== void 0 ? this.T(t) : oe(t) ? this.k(t) + : this._(t);\n }\n O(t) {\n return this._$AA.parentNode.insertBefore(t, + this._$AB);\n }\n T(t) {\n this._$AH !== t && (this._$AR(), this._$AH + = this.O(t));\n }\n _(t) {\n this._$AH !== h && D(this._$AH) ? this._$AA.nextSibling.data + = t : this.T(S.createTextNode(t)), this._$AH = t;\n }\n $(t) {\n const + { values: s, _$litType$: i } = t, o = typeof i == \"number\" ? this._$AC(t) + : (i.el === void 0 && (i.el = M.createElement(Dt(i.h, i.h[0]), this.options)), + i);\n if (this._$AH?._$AD === o) this._$AH.p(s);\n else {\n const + r = new ae(o, this), n = r.u(this.options);\n r.p(s), this.T(n), this._$AH + = r;\n }\n }\n _$AC(t) {\n let s = At.get(t.strings);\n return + s === void 0 && At.set(t.strings, s = new M(t)), s;\n }\n k(t) {\n pt(this._$AH) + || (this._$AH = [], this._$AR());\n const s = this._$AH;\n let i, o + = 0;\n for (const r of t) o === s.length ? s.push(i = new H(this.O(U()), + this.O(U()), this, this.options)) : i = s[o], i._$AI(r), o++;\n o < s.length + && (this._$AR(i && i._$AB.nextSibling, o), s.length = o);\n }\n _$AR(t = + this._$AA.nextSibling, s) {\n for (this._$AP?.(!1, !0, s); t && t !== this._$AB; + ) {\n const i = t.nextSibling;\n t.remove(), t = i;\n }\n }\n + \ setConnected(t) {\n this._$AM === void 0 && (this._$Cv = t, this._$AP?.(t));\n + \ }\n}\nclass tt {\n get tagName() {\n return this.element.tagName;\n + \ }\n get _$AU() {\n return this._$AM._$AU;\n }\n constructor(t, s, + i, o, r) {\n this.type = 1, this._$AH = h, this._$AN = void 0, this.element + = t, this.name = s, this._$AM = o, this.options = r, i.length > 2 || i[0] + !== \"\" || i[1] !== \"\" ? (this._$AH = Array(i.length - 1).fill(new String()), + this.strings = i) : this._$AH = h;\n }\n _$AI(t, s = this, i, o) {\n const + r = this.strings;\n let n = !1;\n if (r === void 0) t = T(this, t, s, + 0), n = !D(t) || t !== this._$AH && t !== O, n && (this._$AH = t);\n else + {\n const l = t;\n let a, c;\n for (t = r[0], a = 0; a < r.length + - 1; a++) c = T(this, l[i + a], s, a), c === O && (c = this._$AH[a]), n ||= + !D(c) || c !== this._$AH[a], c === h ? t = h : t !== h && (t += (c ?? \"\") + + r[a + 1]), this._$AH[a] = c;\n }\n n && !o && this.j(t);\n }\n j(t) + {\n t === h ? this.element.removeAttribute(this.name) : this.element.setAttribute(this.name, + t ?? \"\");\n }\n}\nclass le extends tt {\n constructor() {\n super(...arguments), + this.type = 3;\n }\n j(t) {\n this.element[this.name] = t === h ? void + 0 : t;\n }\n}\nclass ce extends tt {\n constructor() {\n super(...arguments), + this.type = 4;\n }\n j(t) {\n this.element.toggleAttribute(this.name, + !!t && t !== h);\n }\n}\nclass he extends tt {\n constructor(t, s, i, o, + r) {\n super(t, s, i, o, r), this.type = 5;\n }\n _$AI(t, s = this) {\n + \ if ((t = T(this, t, s, 0) ?? h) === O) return;\n const i = this._$AH, + o = t === h && i !== h || t.capture !== i.capture || t.once !== i.once || + t.passive !== i.passive, r = t !== h && (i === h || o);\n o && this.element.removeEventListener(this.name, + this, i), r && this.element.addEventListener(this.name, this, t), this._$AH + = t;\n }\n handleEvent(t) {\n typeof this._$AH == \"function\" ? this._$AH.call(this.options?.host + ?? this.element, t) : this._$AH.handleEvent(t);\n }\n}\nclass pe {\n constructor(t, + s, i) {\n this.element = t, this.type = 6, this._$AN = void 0, this._$AM + = s, this.options = i;\n }\n get _$AU() {\n return this._$AM._$AU;\n + \ }\n _$AI(t) {\n T(this, t);\n }\n}\nconst de = ht.litHtmlPolyfillSupport;\nde?.(M, + H), (ht.litHtmlVersions ??= []).push(\"3.3.0\");\nconst ue = (e, t, s) => + {\n const i = s?.renderBefore ?? t;\n let o = i._$litPart$;\n if (o === + void 0) {\n const r = s?.renderBefore ?? null;\n i._$litPart$ = o = + new H(t.insertBefore(U(), r), r, void 0, s ?? {});\n }\n return o._$AI(e), + o;\n};\n/**\n * @license\n * Copyright 2017 Google LLC\n * SPDX-License-Identifier: + BSD-3-Clause\n */\nconst dt = globalThis;\nlet j = class extends C {\n constructor() + {\n super(...arguments), this.renderOptions = { host: this }, this._$Do + = void 0;\n }\n createRenderRoot() {\n const t = super.createRenderRoot();\n + \ return this.renderOptions.renderBefore ??= t.firstChild, t;\n }\n update(t) + {\n const s = this.render();\n this.hasUpdated || (this.renderOptions.isConnected + = this.isConnected), super.update(t), this._$Do = ue(s, this.renderRoot, this.renderOptions);\n + \ }\n connectedCallback() {\n super.connectedCallback(), this._$Do?.setConnected(!0);\n + \ }\n disconnectedCallback() {\n super.disconnectedCallback(), this._$Do?.setConnected(!1);\n + \ }\n render() {\n return O;\n }\n};\nj._$litElement$ = !0, j.finalized + = !0, dt.litElementHydrateSupport?.({ LitElement: j });\nconst fe = dt.litElementPolyfillSupport;\nfe?.({ + LitElement: j });\n(dt.litElementVersions ??= []).push(\"4.2.0\");\n/**\n + * @license\n * Copyright 2017 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n + */\nconst et = (e) => (t, s) => {\n s !== void 0 ? s.addInitializer(() => + {\n customElements.define(e, t);\n }) : customElements.define(e, t);\n};\n/**\n + * @license\n * Copyright 2017 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n + */\nconst ge = { attribute: !0, type: String, converter: B, reflect: !1, hasChanged: + Q }, be = (e = ge, t, s) => {\n const { kind: i, metadata: o } = s;\n let + r = globalThis.litPropertyMetadata.get(o);\n if (r === void 0 && globalThis.litPropertyMetadata.set(o, + r = /* @__PURE__ */ new Map()), i === \"setter\" && ((e = Object.create(e)).wrapped + = !0), r.set(s.name, e), i === \"accessor\") {\n const { name: n } = s;\n + \ return { set(l) {\n const a = t.get.call(this);\n t.set.call(this, + l), this.requestUpdate(n, a, e);\n }, init(l) {\n return l !== void + 0 && this.C(n, void 0, e, l), l;\n } };\n }\n if (i === \"setter\") {\n + \ const { name: n } = s;\n return function(l) {\n const a = this[n];\n + \ t.call(this, l), this.requestUpdate(n, a, e);\n };\n }\n throw + Error(\"Unsupported decorator location: \" + i);\n};\nfunction d(e) {\n return + (t, s) => typeof s == \"object\" ? be(e, t, s) : ((i, o, r) => {\n const + n = o.hasOwnProperty(r);\n return o.constructor.createProperty(r, i), n + ? Object.getOwnPropertyDescriptor(o, r) : void 0;\n })(e, t, s);\n}\n/**\n + * @license\n * Copyright 2017 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n + */\nfunction ut(e) {\n return d({ ...e, state: !0, attribute: !1 });\n}\n/**\n + * @license\n * Copyright 2021 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n + */\nconst nt = \"lit-localize-status\";\n/**\n * @license\n * Copyright 2021 + Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n */\nconst me = (e, + ...t) => ({\n strTag: !0,\n strings: e,\n values: t\n}), y = me, ye = (e) + => typeof e != \"string\" && \"strTag\" in e, Mt = (e, t, s) => {\n let i + = e[0];\n for (let o = 1; o < e.length; o++)\n i += t[s ? s[o - 1] : o + - 1], i += e[o];\n return i;\n};\n/**\n * @license\n * Copyright 2021 Google + LLC\n * SPDX-License-Identifier: BSD-3-Clause\n */\nconst Nt = (e) => ye(e) + ? Mt(e.strings, e.values) : e;\nlet g = Nt, kt = !1;\nfunction ve(e) {\n if + (kt)\n throw new Error(\"lit-localize can only be configured once\");\n + \ g = e, kt = !0;\n}\n/**\n * @license\n * Copyright 2021 Google LLC\n * SPDX-License-Identifier: + BSD-3-Clause\n */\nclass $e {\n constructor(t) {\n this.__litLocalizeEventHandler + = (s) => {\n s.detail.status === \"ready\" && this.host.requestUpdate();\n + \ }, this.host = t;\n }\n hostConnected() {\n window.addEventListener(nt, + this.__litLocalizeEventHandler);\n }\n hostDisconnected() {\n window.removeEventListener(nt, + this.__litLocalizeEventHandler);\n }\n}\nconst we = (e) => e.addController(new + $e(e)), _e = we;\n/**\n * @license\n * Copyright 2021 Google LLC\n * SPDX-License-Identifier: + BSD-3-Clause\n */\nconst st = () => (e, t) => (e.addInitializer(_e), e);\n/**\n + * @license\n * Copyright 2020 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n + */\nclass Ht {\n constructor() {\n this.settled = !1, this.promise = new + Promise((t, s) => {\n this._resolve = t, this._reject = s;\n });\n + \ }\n resolve(t) {\n this.settled = !0, this._resolve(t);\n }\n reject(t) + {\n this.settled = !0, this._reject(t);\n }\n}\n/**\n * @license\n * Copyright + 2014 Travis Webb\n * SPDX-License-Identifier: MIT\n */\nconst m = [];\nfor + (let e = 0; e < 256; e++)\n m[e] = (e >> 4 & 15).toString(16) + (e & 15).toString(16);\nfunction + xe(e) {\n let t = 0, s = 8997, i = 0, o = 33826, r = 0, n = 40164, l = 0, + a = 52210;\n for (let c = 0; c < e.length; c++)\n s ^= e.charCodeAt(c), + t = s * 435, i = o * 435, r = n * 435, l = a * 435, r += s << 8, l += o << + 8, i += t >>> 16, s = t & 65535, r += i >>> 16, o = i & 65535, a = l + (r + >>> 16) & 65535, n = r & 65535;\n return m[a >> 8] + m[a & 255] + m[n >> + 8] + m[n & 255] + m[o >> 8] + m[o & 255] + m[s >> 8] + m[s & 255];\n}\n/**\n + * @license\n * Copyright 2020 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n + */\nconst Ae = \"\x1E\", ke = \"h\", Se = \"s\";\nfunction Ee(e, t) {\n return + (t ? ke : Se) + xe(typeof e == \"string\" ? e : e.join(Ae));\n}\n/**\n * @license\n + * Copyright 2021 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n */\nconst + St = /* @__PURE__ */ new WeakMap(), Et = /* @__PURE__ */ new Map();\nfunction + Pe(e, t, s) {\n if (e) {\n const i = s?.id ?? Ce(t), o = e[i];\n if + (o) {\n if (typeof o == \"string\")\n return o;\n if (\"strTag\" + in o)\n return Mt(\n o.strings,\n // Cast `template` + because its type wasn't automatically narrowed (but\n // we know + it must be the same type as `localized`).\n t.values,\n o.values\n + \ );\n {\n let r = St.get(o);\n return r === void + 0 && (r = o.values, St.set(o, r)), {\n ...o,\n values: r.map((n) + => t.values[n])\n };\n }\n }\n }\n return Nt(t);\n}\nfunction + Ce(e) {\n const t = typeof e == \"string\" ? e : e.strings;\n let s = Et.get(t);\n + \ return s === void 0 && (s = Ee(t, typeof e != \"string\" && !(\"strTag\" + in e)), Et.set(t, s)), s;\n}\n/**\n * @license\n * Copyright 2021 Google LLC\n + * SPDX-License-Identifier: BSD-3-Clause\n */\nfunction ot(e) {\n window.dispatchEvent(new + CustomEvent(nt, { detail: e }));\n}\nlet X = \"\", rt, It, J, at, qt, A = + new Ht();\nA.resolve();\nlet V = 0;\nconst je = (e) => (ve((t, s) => Pe(qt, + t, s)), X = It = e.sourceLocale, J = new Set(e.targetLocales), J.add(e.sourceLocale), + at = e.loadLocale, { getLocale: Oe, setLocale: Te }), Oe = () => X, Te = (e) + => {\n if (e === (rt ?? X))\n return A.promise;\n if (!J || !at)\n throw + new Error(\"Internal error\");\n if (!J.has(e))\n throw new Error(\"Invalid + locale code\");\n V++;\n const t = V;\n return rt = e, A.settled && (A + = new Ht()), ot({ status: \"loading\", loadingLocale: e }), (e === It ? (\n + \ // We could switch to the source locale synchronously, but we prefer to\n + \ // queue it on a microtask so that switching locales is consistently\n + \ // asynchronous.\n Promise.resolve({ templates: void 0 })\n ) : at(e)).then((i) + => {\n V === t && (X = e, rt = void 0, qt = i.templates, ot({ status: \"ready\", + readyLocale: e }), A.resolve());\n }, (i) => {\n V === t && (ot({\n status: + \"error\",\n errorLocale: e,\n errorMessage: i.toString()\n }), + A.reject(i));\n }), A.promise;\n};\n/**\n * @license\n * Copyright 2017 Google + LLC\n * SPDX-License-Identifier: BSD-3-Clause\n */\nconst Re = Symbol();\nclass + Le {\n get taskComplete() {\n return this.t || (this.i === 1 ? this.t + = new Promise((t, s) => {\n this.o = t, this.h = s;\n }) : this.i + === 3 ? this.t = Promise.reject(this.l) : this.t = Promise.resolve(this.u)), + this.t;\n }\n constructor(t, s, i) {\n this.p = 0, this.i = 0, (this._ + = t).addController(this);\n const o = typeof s == \"object\" ? s : { task: + s, args: i };\n this.v = o.task, this.j = o.args, this.m = o.argsEqual + ?? Ue, this.k = o.onComplete, this.A = o.onError, this.autoRun = o.autoRun + ?? !0, \"initialValue\" in o && (this.u = o.initialValue, this.i = 2, this.O + = this.T?.());\n }\n hostUpdate() {\n this.autoRun === !0 && this.S();\n + \ }\n hostUpdated() {\n this.autoRun === \"afterUpdate\" && this.S();\n + \ }\n T() {\n if (this.j === void 0) return;\n const t = this.j();\n + \ if (!Array.isArray(t)) throw Error(\"The args function must return an + array\");\n return t;\n }\n async S() {\n const t = this.T(), s = + this.O;\n this.O = t, t === s || t === void 0 || s !== void 0 && this.m(s, + t) || await this.run(t);\n }\n async run(t) {\n let s, i;\n t ??= + this.T(), this.O = t, this.i === 1 ? this.q?.abort() : (this.t = void 0, this.o + = void 0, this.h = void 0), this.i = 1, this.autoRun === \"afterUpdate\" ? + queueMicrotask(() => this._.requestUpdate()) : this._.requestUpdate();\n const + o = ++this.p;\n this.q = new AbortController();\n let r = !1;\n try + {\n s = await this.v(t, { signal: this.q.signal });\n } catch (n) + {\n r = !0, i = n;\n }\n if (this.p === o) {\n if (s === Re) + this.i = 0;\n else {\n if (r === !1) {\n try {\n this.k?.(s);\n + \ } catch {\n }\n this.i = 2, this.o?.(s);\n } + else {\n try {\n this.A?.(i);\n } catch {\n }\n + \ this.i = 3, this.h?.(i);\n }\n this.u = s, this.l + = i;\n }\n this._.requestUpdate();\n }\n }\n abort(t) {\n this.i + === 1 && this.q?.abort(t);\n }\n get value() {\n return this.u;\n }\n + \ get error() {\n return this.l;\n }\n get status() {\n return this.i;\n + \ }\n render(t) {\n switch (this.i) {\n case 0:\n return t.initial?.();\n + \ case 1:\n return t.pending?.();\n case 2:\n return + t.complete?.(this.value);\n case 3:\n return t.error?.(this.error);\n + \ default:\n throw Error(\"Unexpected status: \" + this.i);\n }\n + \ }\n}\nconst Ue = (e, t) => e === t || e.length === t.length && e.every((s, + i) => !Q(s, t[i])), De = ':host{color:var(--color-text);display:block;font-family:Open + Sans,sans-serif;font-size:14px}.platforms-list{display:block;width:100%}.platforms-list + platform-block{display:block;margin-bottom:15px}.platforms-with-permissions + .block-title{position:relative;text-align:center;width:100%}.platforms-with-permissions + .block-title h2{background:#fff;color:var(--color-secondary);display:inline-block;padding:10px;position:relative;text-transform:uppercase;z-index:1}.platforms-with-permissions + .block-title:after{background:#cfe1f3;content:\"\";height:1px;left:0;position:absolute;right:0;top:50%;z-index:0}';\n!window.orbit + && !\"/\".includes(\"cypress\") && await new Promise((e) => {\n document.addEventListener(\"orbit-ready\", + () => {\n e(!0);\n });\n});\nconst zt = /* @__PURE__ */ Object.freeze(/* + @__PURE__ */ Object.defineProperty({\n __proto__: null\n}, Symbol.toStringTag, + { value: \"Module\" }));\nvar Me = Object.defineProperty, Ne = (e, t, s, i) + => {\n for (var o = void 0, r = e.length - 1, n; r >= 0; r--)\n (n = e[r]) + && (o = n(t, s, o) || o);\n return o && Me(t, s, o), o;\n};\nconst He = Object.values(zt);\nclass + ft extends j {\n constructor() {\n super(...arguments), this.objects = + [], this.hasType = (t, s = this.objects) => new Set(s.flatMap((o) => o[\"@type\"])).has(t);\n + \ }\n renderTemplateWhenWith(t, s, i = this.objects) {\n const o = (n, + l) => Array.isArray(n) ? n.some((a) => l(a)) : l(n);\n return t.filter(\n + \ (n) => o(n, (l) => He.includes(l))\n ).every(\n (n) => o(n, + (l) => this.hasType(l, i))\n ) ? s.call(this) : h;\n }\n}\nNe([\n d({ + attribute: !1 })\n], ft.prototype, \"objects\");\nvar Ie = Object.defineProperty, + _ = (e, t, s, i) => {\n for (var o = void 0, r = e.length - 1, n; r >= 0; + r--)\n (n = e[r]) && (o = n(t, s, o) || o);\n return o && Ie(t, s, o), + o;\n};\nclass v extends ft {\n constructor({\n defaultRoute: t = !1,\n + \ setupSubscriptions: s = !0,\n ignoreRouter: i = !1\n } = {}) {\n super(), + this.cherryPickedProperties = [], this.currentRoute = \"\";\n const o = + () => {\n this._attach(t, s, i).then(\n (r) => {\n r + && this.requestUpdate();\n }\n );\n };\n document.readyState + === \"loading\" ? document.addEventListener(\"DOMContentLoaded\", o) : o();\n + \ }\n async _attach(t, s, i) {\n return !this.orbit && window.orbit && + (this.orbit = window.orbit, s) ? (Ve({\n component: this,\n defaultRoute: + t,\n ignoreRouter: i\n }), this.route && (this.component = this.orbit.getComponentFromRoute(this.route), + this.component && this.orbit.components.map((o) => (o.uniq === this.component.uniq + && (o.instance = this), o))), await this._afterAttach(), Promise.resolve(!0)) + : Promise.resolve(!1);\n }\n async _afterAttach() {\n return Promise.resolve();\n + \ }\n _navigate(t) {\n window.sibRouter.previousRoute = this.currentRoute, + window.sibRouter.previousResource = window.sibRouter.currentResource;\n const + s = t.target?.closest(\"[navigation-target]\");\n let i = s.getAttribute(\"navigation-target\");\n + \ const o = s.getAttribute(\"navigation-subrouter\"), r = s.getAttribute(\"navigation-resource\"), + n = s.getAttribute(\"navigation-rdf-type\");\n if (n) {\n const l + = window.orbit?.components?.filter(\n (a) => a?.routeAttributes?.[\"rdf-type\"] + === n\n );\n l && (i = l[0]?.uniq);\n }\n i && We(\n (window.orbit + ? window.orbit.getRoute(i, !0) : i) + (o ? `-${o}` : \"\"),\n r\n ), + t.preventDefault();\n }\n _normalizeLdpContains(t) {\n return !Array.isArray(t) + && t !== null ? [t] : t;\n }\n async _expandContainer(t, s = !0) {\n const + i = [];\n for (const o of t) {\n const r = await this._getProxyValue(o, + s);\n r && i.push(r);\n }\n return i;\n }\n async _getProperties(t, + s = !0, i = this.cherryPickedProperties) {\n const o = await t.properties + || Object.keys(t), r = {\n \"@id\": t[\"@id\"],\n \"@type\": t[\"@type\"],\n + \ \"@context\": t.serverContext || t[\"@context\"],\n _originalResource: + t\n };\n for (const n of i)\n o?.includes(n.key) && (t[n.key].then + ? r[n.value] = await t.get(n.key) : r[n.value] = t[n.key], n.expand && (r[n.value] + = await this._getProxyValue(\n r[n.value],\n s,\n i\n + \ )), n.cast && (r[n.value] = n.cast(r[n.value])));\n return await + this._responseAdaptator(r);\n }\n async _getProxyValue(t, s = !0, i = this.cherryPickedProperties, + o = this.requestOptions) {\n try {\n if (t) {\n let r = t;\n + \ if (typeof t == \"string\" && (r = await window.sibStore.getData(\n + \ t,\n Ct,\n \"\",\n void 0,\n !0,\n + \ void 0,\n void 0,\n o?.headers\n )), typeof + t != \"string\" && t.isFullResource && (t.isFullResource?.() || (r = await + window.sibStore.getData(\n t[\"@id\"],\n Ct,\n \"\",\n + \ void 0,\n !0,\n void 0,\n void 0,\n o?.headers\n + \ ))), typeof t != \"string\" && !t.isFullResource && (r.properties + = Object.keys(r), r.get = (n) => r[n]), !r) return { _originalResource: r + };\n if (typeof r == \"object\" && r !== null) {\n if (r[\"ldp:contains\"]) + {\n const n = this._normalizeLdpContains(r[\"ldp:contains\"]);\n + \ return await this._expandContainer(n, s);\n }\n return + await this._getProperties(r, s, i);\n }\n }\n return;\n } + catch {\n }\n }\n async _responseAdaptator(t) {\n return Promise.resolve(t);\n + \ }\n gatekeeper() {\n if (!this.orbit || !this.noRouter && this.route + && this.currentRoute && !this.route.startsWith(this.currentRoute) || !this.dataSrc)\n + \ return h;\n }\n}\n_([\n d({ attribute: \"default-data-src\", reflect: + !0 })\n], v.prototype, \"defaultDataSrc\");\n_([\n d({ attribute: \"data-src\", + reflect: !0 })\n], v.prototype, \"dataSrc\");\n_([\n d({ attribute: \"nested-field\" + })\n], v.prototype, \"nestedField\");\n_([\n d({ attribute: \"uniq\" })\n], + v.prototype, \"uniq\");\n_([\n d({ attribute: \"route\" })\n], v.prototype, + \"route\");\n_([\n d({ attribute: !1 })\n], v.prototype, \"cherryPickedProperties\");\n_([\n + \ ut()\n], v.prototype, \"orbit\");\n_([\n ut()\n], v.prototype, \"currentRoute\");\nconst + qe = (e, { keywords: t = [], attributes: s = [\"dataSrc\"] } = {}) => {\n + \ t && s && (e.caching === void 0 && (e.caching = 0), e.hasCachedDatas === + void 0 && (e.hasCachedDatas = !1), e.cacheListener = (i) => {\n const o + = i.detail.id || i.detail.resource[\"@id\"];\n if (t.some((r) => o?.includes(r))) + {\n for (const r of s)\n e[r] && o !== e[r] && window.sibStore.clearCache(e[r]);\n + \ e.caching++, e.hasCachedDatas = !1, e.requestUpdate();\n }\n }, + e._subscriptions.add([\"save\", e.cacheListener]), e._subscribe());\n};\nfunction + ze() {\n return Math.random().toString(16).slice(2);\n}\nconst Ve = ({\n + \ component: e,\n defaultRoute: t = !1,\n ignoreRouter: s = !1\n}) => {\n + \ if (e.uniq || (e.uniq = ze(), t && !e.route && !s && (e.route = t)), e._subscriptions + = /* @__PURE__ */ new Set(), !s) {\n e.route || (e.route = e.uniq, window.orbit + && (e.route = window.orbit.getRoute(e.uniq))), e.noRouter = !0;\n let i + = document.querySelector(\"solid-router\");\n for (; i; )\n e.noRouter + = !1, e.currentRoute = i.currentRouteName, e.currentResource = window.sibRouter.currentResource, + i = document.querySelector(\n `[data-view=\"${i.currentRouteName}\"] + solid-router`\n );\n e.navigationListener = () => {\n let o = + document.querySelector(\"solid-router\");\n for (; o; )\n e.noRouter + = !1, e.currentRoute = o.currentRouteName, e.currentResource = window.sibRouter.currentResource, + o = document.querySelector(\n `[data-view=\"${o.currentRouteName}\"] + solid-router`\n );\n e.requestUpdate();\n }, e._subscriptions.add([\"navigate\", + e.navigationListener]);\n }\n e._subscribe = () => {\n e._unsubscribe();\n + \ for (const i of e._subscriptions)\n document.addEventListener(i[0], + i[1]);\n }, e._unsubscribe = () => {\n for (const i of e._subscriptions)\n + \ document.removeEventListener(i[0], i[1]);\n }, e._subscribe();\n}, + Pt = Object.values(zt);\nclass Vt extends j {\n constructor() {\n super(...arguments), + this.object = {\n \"@id\": \"\"\n }, this.isType = (t, s = this.object) + => {\n const i = s[\"@type\"];\n return Array.isArray(i) ? i.includes(t) + : typeof i == \"string\" ? i === t : !1;\n };\n }\n getNestedProperty(t, + s = this.object) {\n return t.split(\".\").reduce((i, o) => {\n if + (i === void 0)\n return;\n const r = o.match(/(.*)\\[(.*)\\]/);\n + \ if (r) {\n const n = r[1], l = r[2], a = i[n];\n if (!Array.isArray(a))\n + \ return;\n if (l === \"\")\n return a.length > 0 + ? a : void 0;\n const c = Number.parseInt(l, 10);\n return Number.isNaN(c) + || c < 0 || c >= a.length ? void 0 : i[n][c];\n }\n return i && + i[o] !== \"undefined\" ? i[o] : void 0;\n }, s);\n }\n renderTemplateWhenWith(t, + s, i = this.object) {\n const o = (l, a) => Array.isArray(l) ? l.some((c) + => a(c)) : a(l), r = t.filter(\n (l) => o(l, (a) => Pt.includes(a))\n + \ );\n return t.filter(\n (l) => o(l, (a) => !Pt.includes(a))\n + \ ).every(\n (l) => o(l, (a) => this.getNestedProperty(a, i))\n ) + && r.every(\n (l) => o(l, (a) => this.isType(a, i))\n ) ? s.call(this) + : h;\n }\n}\nconst We = (e, t = !1) => {\n window.dispatchEvent(\n new + CustomEvent(\"requestNavigation\", {\n detail: {\n route: e,\n + \ resource: typeof t == \"string\" ? { \"@id\": t } : t\n }\n })\n + \ );\n}, Ct = {\n \"@context\": { \"@vocab\": \"https://cdn.startinblox.com/owl#\", + foaf: \"http://xmlns.com/foaf/0.1/\", doap: \"http://usefulinc.com/ns/doap#\", + ldp: \"http://www.w3.org/ns/ldp#\", rdfs: \"http://www.w3.org/2000/01/rdf-schema#\", + rdf: \"http://www.w3.org/1999/02/22-rdf-syntax-ns#\", xsd: \"http://www.w3.org/2001/XMLSchema#\", + geo: \"http://www.w3.org/2003/01/geo/wgs84_pos#\", acl: \"http://www.w3.org/ns/auth/acl#\", + hd: \"http://cdn.startinblox.com/owl/ttl/vocab.ttl#\", sib: \"https://cdn.startinblox.com/owl#\", + tems: \"https://cdn.startinblox.com/owl/tems.jsonld#\", idx: \"https://ns.inria.fr/idx/terms#\", + solid: \"http://www.w3.org/ns/solid/terms#\", pim: \"http://www.w3.org/ns/pim/space#\", + stat: \"http://www.w3.org/ns/posix/stat#\", dcterms: \"http://purl.org/dc/terms/\", + dcat: \"https://www.w3.org/ns/dcat#\", sh: \"https://www.w3.org/ns/shacl#\", + \"dfc-t\": \"https://github.com/datafoodconsortium/ontology/blob/master/src/DFC_TechnicalOntology.owl#\", + \"foaf:primaryTopic\": { \"@type\": \"@id\" }, \"solid:publicTypeIndex\": + { \"@type\": \"@id\" }, \"solid:forClass\": { \"@type\": \"@id\" }, \"solid:instance\": + { \"@type\": \"@id\" }, \"solid:instanceContainer\": { \"@type\": \"@id\" + }, \"sh:path\": { \"@type\": \"@id\" }, \"sh:property\": { \"@type\": \"@id\" + }, \"idx:hasShape\": { \"@type\": \"@id\" }, \"idx:hasSubIndex\": { \"@type\": + \"@id\" }, \"idx:hasTarget\": { \"@type\": \"@id\" }, name: \"rdfs:label\", + deadline: \"xsd:dateTime\", lat: \"geo:lat\", lng: \"geo:long\", jabberID: + \"foaf:jabberID\", permissions: \"acl:accessControl\", mode: \"acl:mode\", + view: \"acl:Read\", change: \"acl:Write\", add: \"acl:Append\", delete: \"acl:Delete\", + control: \"acl:Control\" }\n};\nvar Be = Object.defineProperty, Fe = Object.getOwnPropertyDescriptor, + I = (e, t, s, i) => {\n for (var o = i > 1 ? void 0 : i ? Fe(t, s) : t, r + = e.length - 1, n; r >= 0; r--)\n (n = e[r]) && (o = (i ? n(t, s, o) : + n(o)) || o);\n return i && o && Be(t, s, o), o;\n};\nlet E = class extends + v {\n constructor() {\n super(...arguments), this.autoLang = !1, this.lang + = \"\", this.requestOptions = {\n headers: {}\n }, this.cherryPickedProperties + = [\n { key: \"name\", value: \"name\" },\n { key: \"description\", + value: \"description\" },\n { key: \"dfc-t:platforms\", value: \"platforms\" + },\n { key: \"dfc-t:scopes\", value: \"scopes\" },\n { key: \"dfc-t:hasAssignedScopes\", + value: \"dfc-t:hasAssignedScopes\" },\n { key: \"localId\", value: \"localId\" + }\n ], this._getResource = new Le(this, {\n task: async ([e, t]) => + {\n if (!(!e || !t || !this.noRouter && this.route && this.currentRoute + && !this.route.startsWith(this.currentRoute))) {\n if (!this.hasCachedDatas + || this.oldDataSrc !== e) {\n if (!e) return;\n this.datas + = await this._getProxyValue(e), this.scopes = await this._getScopes(t), this.hasCachedDatas + = !0, this.dataSrc = e, this.platformsObj = [], this.platforms = await this.datas.platforms;\n + \ for (const s of this.platforms[\"@list\"])\n this.platformsObj.push(await + this._getProxyValue(s[\"@id\"]));\n this.platforms = this.platformsObj, + this.listOfScopes = this.scopes.filter(\n (s) => s[\"@type\"]?.includes(\"http://www.w3.org/2004/02/skos/core#Concept\")\n + \ );\n }\n return this.oldDataSrc !== e && (this.oldDataSrc + = e), [this.platforms, this.listOfScopes, this.dataSrc];\n }\n },\n + \ args: () => [this.dataSrc, this.scopesUri, this.caching, this.currentRoute]\n + \ });\n }\n async _afterAttach() {\n return qe(this, {\n keywords: + [\"some-keyword\", \"for-invalidating\", \"cache\"]\n }), Promise.resolve();\n + \ }\n refreshAuthToken(e) {\n e.has(\"authToken\") && this.authToken && + (this.requestOptions = {\n headers: {\n Authorization: `Bearer + ${this.authToken}`\n }\n });\n }\n willUpdate(e) {\n this.refreshAuthToken(e);\n + \ }\n updated(e) {\n if (e.has(\"autoLang\") && this.autoLang && (this.lang + = window.navigator.language), e.has(\"lang\") && this.lang) {\n const + t = [\"en\", \"fr\"], s = this.lang.split(\"-\")[0];\n t.includes(s) + ? window.setLocale.map((i) => i(s)) : (console.warn(`Locale ${s} not supported, + defaulting to 'en'`), window.setLocale.map((i) => i(\"en\")));\n }\n this.refreshAuthToken(e);\n + \ }\n async _getScopes(e) {\n if (!e) return { scopes: { \"@list\": [] + } };\n try {\n const t = await fetch(e);\n if (!t.ok)\n throw + new Error(`HTTP error! status: ${t.status}`);\n return await t.json();\n + \ } catch (t) {\n return console.error(\"Error fetching scopes:\", + t), { scopes: { \"@list\": [] } };\n }\n }\n splitAndSortPlatforms(e) + {\n const t = e.filter(\n (i) => i[\"dfc-t:hasAssignedScopes\"][\"@list\"].length + === 0\n ), s = e.filter(\n (i) => i[\"dfc-t:hasAssignedScopes\"][\"@list\"].length + !== 0\n );\n return { platformsWithoutScopes: t, platformsWithScopes: + s };\n }\n render() {\n return this.gatekeeper() || this._getResource.render({\n + \ pending: () => f``,\n complete: (e) + => {\n if (!e) return h;\n const [t, s, i] = e, { platformsWithScopes: + o, platformsWithoutScopes: r } = this.splitAndSortPlatforms(t);\n return + f`
\n
\n + \ ${r.map(\n (n) => f`\n {\n this.hasCachedDatas + = !1, this._getResource.run();\n }}\n >\n + \ `\n )}\n
\n
\n

${g(\"Approved + platforms\")}

\n ${o.map(\n (n) => f`\n {\n this.hasCachedDatas + = !1, this._getResource.run();\n }}\n >\n + \ `\n )}\n
\n
`;\n }\n + \ });\n }\n};\nE.styles = Z`\n :host {\n --font-family: \"Inter\", + sans-serif;\n --color-text: #181d27;\n --color-primary: #0096AD;\n + \ --color-primary-dark: #007B8A;\n --color-primary-light: #5498DA;\n + \ --color-secondary: #23A877;\n --color-third: #F1F8FE;\n --color-third-dark: + #d8e6f5;\n --color-heading: #0096AD;\n --color-grey: #4d4d4d;\n + \ font-family: var(--font-family);\n }\n ${N(De)}\n `;\nI([\n + \ d({ attribute: \"scopes-uri\", reflect: !0 })\n], E.prototype, \"scopesUri\", + 2);\nI([\n d({ attribute: \"auto-lang\", type: Boolean })\n], E.prototype, + \"autoLang\", 2);\nI([\n d({ attribute: \"lang\" })\n], E.prototype, \"lang\", + 2);\nI([\n d({ attribute: \"auth-token\" })\n], E.prototype, \"authToken\", + 2);\nE = I([\n et(\"solid-permissioning\"),\n st()\n], E);\nconst Xe = ':host{user-select:none}:host([variant=without-permissions]) + .platform-name{background-color:var(--color-heading)}:host([variant=approved]) + .platform-name{background-color:var(--color-secondary)}.platform-name{border-radius:8px + 8px 0 0;color:#fff;display:block;font-weight:700;padding:16px;width:calc(100% + - 32px)}.block-flex{background-color:var(--color-third);border-radius:0 0 + 8px 8px;display:flex;justify-content:space-between}.block-flex .platform-description{display:block;padding:16px;width:40%}.block-flex + .platform-scopes{display:flex;flex-wrap:wrap;justify-content:space-between;padding:16px;width:60%}.block-flex + .platform-scopes .scopes-list{background-color:#fff;border-radius:8px;min-width:300px;padding:8px;width:350px}.block-flex + .platform-scopes .scopes-list p span{font-weight:600}.block-flex .platform-scopes + .scopes-list ul{list-style:none;margin:0;padding:0 8px}.block-flex .platform-scopes + .scopes-list ul li{display:flex}.block-flex .platform-scopes .scopes-list + ul li::marker{content:\"\"}.block-flex .platform-scopes .scopes-list ul li + label{display:flex}.block-flex .platform-scopes .scopes-list ul li label div{margin:auto}.block-flex + .platform-scopes .scopes-list ul li label .scope-label{width:calc(100% - 25px)}.block-flex + .platform-scopes .scopes-list ul li .checkbox-wrapper:hover .check{stroke-dashoffset:0}.block-flex + .platform-scopes .scopes-list ul li .checkbox-wrapper{display:inline-flex;height:40px;position:relative;width:25px}.block-flex + .platform-scopes .scopes-list ul li .checkbox-wrapper span{display:flex}.block-flex + .platform-scopes .scopes-list ul li .checkbox-wrapper svg{height:18px;margin:auto;width:18px}.block-flex + .platform-scopes .scopes-list ul li .checkbox-wrapper .background{transition:all + .6s ease;-webkit-transition:all .6s ease}.block-flex .platform-scopes .scopes-list + ul li .checkbox-wrapper .background.bckg-green{fill:var(--color-secondary)}.block-flex + .platform-scopes .scopes-list ul li .checkbox-wrapper .background.bckg-red{fill:var(--color-grey)}.block-flex + .platform-scopes .scopes-list ul li .checkbox-wrapper .stroke{fill:none;transition:all + .6s ease;-webkit-transition:all .6s ease}.block-flex .platform-scopes .scopes-list + ul li .checkbox-wrapper .check,.block-flex .platform-scopes .scopes-list ul + li .checkbox-wrapper .cross{fill:none;stroke:#fff;stroke-linecap:round;stroke-linejoin:round;stroke-width:2px;transition:all + .6s ease;-webkit-transition:all .6s ease}.block-flex .platform-scopes .scopes-list + ul li .checkbox-wrapper input[type=checkbox]{appearance:none;-webkit-appearance:none;height:100%;left:0;margin:0;opacity:0;position:absolute;top:0;width:100%}.block-flex + .platform-scopes .scopes-list ul li .checkbox-wrapper input[type=checkbox]:checked+span + svg .check,.block-flex .platform-scopes .scopes-list ul li .checkbox-wrapper + input[type=checkbox]:checked+span svg .stroke{stroke-dashoffset:0}.block-flex + .platform-scopes .terms-and-conditions{margin-top:10px}.block-flex .platform-scopes + .toggle-permissions{min-width:150px;padding:0 16px}.block-flex .platform-scopes + .toggle-permissions .button-light,.block-flex .platform-scopes .toggle-permissions + .button-permissions{border-radius:5px;box-shadow:0 2px 3px 1px #999;cursor:pointer;display:block;font-size:12px;font-weight:600;padding:8px + 10px;width:100%}.block-flex .platform-scopes .toggle-permissions .button-light:active,.block-flex + .platform-scopes .toggle-permissions .button-permissions:active{box-shadow:0 + 1px 3px 1px #666;transform:translateY(4px)}.block-flex .platform-scopes .toggle-permissions + .button-light.margin-bottom,.block-flex .platform-scopes .toggle-permissions + .button-permissions.margin-bottom{margin-bottom:10px}.block-flex .platform-scopes + .toggle-permissions .button-permissions{background-color:var(--color-primary);border:none;color:#fff}.block-flex + .platform-scopes .toggle-permissions .button-permissions:active{background-color:var(--color-primary-dark);box-shadow:0 + 1px 3px 1px #666;transform:translateY(4px)}.block-flex .platform-scopes .toggle-permissions + .button-light{background-color:initial;border:1px solid var(--color-primary-light);color:var(--color-primary-light);text-decoration:none}.block-flex + .platform-scopes .toggle-permissions .button-light:active{background-color:var(--color-third-dark)}@media + (max-width:800px){.block-flex .platform-scopes{display:block}.block-flex .platform-scopes + .toggle-permissions .button-light,.block-flex .platform-scopes .toggle-permissions + .button-permissions{margin-top:15px;width:unset}}@media (max-width:600px){.block-flex{display:block}}';\nvar + Je = Object.defineProperty, Ke = Object.getOwnPropertyDescriptor, q = (e, + t, s, i) => {\n for (var o = i > 1 ? void 0 : i ? Ke(t, s) : t, r = e.length + - 1, n; r >= 0; r--)\n (n = e[r]) && (o = (i ? n(t, s, o) : n(o)) || o);\n + \ return i && o && Je(t, s, o), o;\n};\nlet P = class extends Vt {\n constructor() + {\n super(...arguments), this.object = { \"@id\": \"\" }, this.listOfScopes + = [];\n }\n async firstUpdated() {\n try {\n this.platformDetails + = await this.fetchObjectDetails(this.object);\n } catch (e) {\n console.error(\"Erreur + lors du chargement de la plateforme :\", e);\n }\n }\n async fetchObjectDetails(e) + {\n const t = e[\"@id\"], i = await (await fetch(t)).json(), o = `${e[\"@id\"]}#me`, + r = i[\"@graph\"].find((n) => n[\"@id\"] === o);\n if (!r) {\n console.warn(`Aucun + noeud avec @id = ${o} trouvé`);\n return;\n }\n return r;\n }\n + \ getLabel(e, t = this.lang) {\n const s = e[\"http://www.w3.org/2004/02/skos/core#prefLabel\"]?.find((o) + => o[\"@language\"] === t);\n return s ? s[\"@value\"] : \"undefined\";\n + \ }\n async toggleScopeAssociation() {\n const e = [\n ...this.renderRoot.querySelectorAll(\n + \ '.checkbox-wrapper input[type=\"checkbox\"]'\n )\n ];\n for + (const n of e)\n n.checked = !n.checked;\n const t = e.filter((n) + => n.checked), s = this.object.localId, i = `${this.dataSrc}/${s}`, o = {\n + \ \"@context\": \"https://cdn.startinblox.com/owl/context-bis.jsonld\",\n + \ \"@id\": i,\n \"dfc-t:hasAssignedScopes\": {\n \"@list\": + t.map((n) => ({\n \"@id\": n.id,\n \"dfc-t:scope\": n.scope,\n + \ \"@type\": \"dfc-t:Scope\"\n })),\n \"@type\": \"rdf:List\"\n + \ }\n };\n let r = {\n headers: {\n Accept: \"application/ld+json\",\n + \ \"Cache-Control\": \"must-revalidate\",\n \"Content-Type\": + \"application/ld+json\"\n },\n method: \"PUT\",\n credentials: + \"include\",\n body: JSON.stringify(o)\n };\n this.authToken && + (r.headers.Authorization = `Bearer ${this.authToken}`), await window.sibStore.fetchAuthn(i, + r), this.dispatchEvent?.(\n new CustomEvent(\"put-success\", {\n bubbles: + !0,\n composed: !0\n })\n );\n }\n render() {\n if (this.isType(\"some:Type\"))\n + \ return h;\n const e = this.platformDetails || this.object, t = this.variant + === \"approved\" ? g(\"Stop sharing\") : g(\"Agree and share\"), s = this.variant + === \"approved\" ? f`${g(\"Currently sharing:\")}` : f`${e[\"dcterms:title\"]} + ${g(\"would like to:\")}`;\n return f`
\n
${e[\"dcterms:title\"]}
\n + \
\n
${e[\"dcterms:description\"]}
\n + \
\n
\n + \

${s}

\n
    \n ${this.listOfScopes.map((i) + => {\n const o = i[\"@id\"], r = this.getLabel(i), n = i[\"http://www.w3.org/2004/02/skos/core#notation\"][0][\"@value\"], + l = this.object[\"dfc-t:hasAssignedScopes\"][\"@list\"]?.some((a) => a[\"@id\"] + === o);\n return f`\n
  • \n \n
  • \n + \ `;\n })}\n
\n \n
\n
\n \n
\n
\n
\n
`;\n + \ }\n};\nP.styles = Z`\n ${N(Xe)}\n `;\nq([\n d({ attribute: !1, type: + Object })\n], P.prototype, \"object\", 2);\nq([\n d({ attribute: !1, type: + Array })\n], P.prototype, \"listOfScopes\", 2);\nq([\n d({ type: String })\n], + P.prototype, \"variant\", 2);\nq([\n ut()\n], P.prototype, \"platformDetails\", + 2);\nP = q([\n et(\"platform-block\"),\n st()\n], P);\nconst Wt = \":host{background-color:pink;user-select:none}\";\nvar + Ye = Object.defineProperty, Ze = Object.getOwnPropertyDescriptor, Bt = (e, + t, s, i) => {\n for (var o = i > 1 ? void 0 : i ? Ze(t, s) : t, r = e.length + - 1, n; r >= 0; r--)\n (n = e[r]) && (o = (i ? n(t, s, o) : n(o)) || o);\n + \ return i && o && Ye(t, s, o), o;\n};\nlet K = class extends Vt {\n constructor() + {\n super(...arguments), this.object = { \"@id\": \"\" };\n }\n render() + {\n if (this.isType(\"some:Type\"))\n return h;\n const e = this.object.platforms[\"@list\"];\n + \ return f`
\n ${g(y`Sample object description: ${this.object.description}`)}\n + \ ${g(y`Sample object named: ${this.object.name}`)}\n ${g(y`Sample + object platforms: ${this.object.platforms ? [\"@list\"] : []}`)}\n
    \n + \ ${e.map((t) => f`
  • ${t.description}
  • `)}\n
\n
`;\n + \ }\n};\nK.styles = Z`\n ${N(Wt)}\n `;\nBt([\n d({ attribute: !1, type: + Object })\n], K.prototype, \"object\", 2);\nK = Bt([\n et(\"sample-object\"),\n + \ st()\n], K);\nvar Ge = Object.defineProperty, Qe = Object.getOwnPropertyDescriptor, + Ft = (e, t, s, i) => {\n for (var o = i > 1 ? void 0 : i ? Qe(t, s) : t, + r = e.length - 1, n; r >= 0; r--)\n (n = e[r]) && (o = (i ? n(t, s, o) + : n(o)) || o);\n return i && o && Ge(t, s, o), o;\n};\nlet Y = class extends + ft {\n constructor() {\n super(...arguments), this.objects = [];\n }\n + \ render() {\n return this.hasType(\"some:Type\") ? h : f`${g(\"Here are + all of our objects:\")}
\n
    \n ${this.objects.map(\n (e) + => f``\n )}\n
`;\n + \ }\n};\nY.styles = Z`\n ${N(Wt)}\n `;\nFt([\n d({ attribute: !1, type: + Object })\n], Y.prototype, \"objects\", 2);\nY = Ft([\n et(\"sample-objects\"),\n + \ st()\n], Y);\nconst ts = {\n s35f31435ca3ad8ca: \"Approved platforms\",\n + \ sba31db18366c5881: \"Stop sharing\",\n s96317ca05a53c38d: \"Agree and share\",\n + \ s62dc562e39a40c55: \"Currently sharing:\",\n sfb78e342cc9c23a8: \"would + like to:\",\n s524953bb70eec8bb: \"Read our full terms here\",\n scda91a551506ae4c: + y`Sample object description: ${0}`,\n s9cb0b52fd65ea225: y`Sample object + named: ${0}`,\n s1dedefdb8808068a: y`Sample object platforms: ${0}`,\n se975788b1544119d: + \"Here are all of our objects:\"\n}, es = /* @__PURE__ */ Object.freeze(/* + @__PURE__ */ Object.defineProperty({\n __proto__: null,\n templates: ts\n}, + Symbol.toStringTag, { value: \"Module\" })), ss = {\n s35f31435ca3ad8ca: + \"Plate-formes approuvées\",\n s524953bb70eec8bb: \"Lire nos conditions générales + complètes ici\",\n s62dc562e39a40c55: \"Actuellement en partage :\",\n s96317ca05a53c38d: + \"Accepter et partager\",\n s9cb0b52fd65ea225: y`Objet d'exemple nommé : + ${0}`,\n sba31db18366c5881: \"Arrêter le partage\",\n scda91a551506ae4c: + y`Description de l'objet d'exemple : ${0}`,\n sfb78e342cc9c23a8: \"voudrait + :\",\n s1dedefdb8808068a: y`Sample object platforms: ${0}`,\n se975788b1544119d: + \"Here are all of our objects:\"\n}, is = /* @__PURE__ */ Object.freeze(/* + @__PURE__ */ Object.defineProperty({\n __proto__: null,\n templates: ss\n}, + Symbol.toStringTag, { value: \"Module\" })), os = \"en\", Xt = [\n \"en\",\n + \ \"fr\"\n], rs = /* @__PURE__ */ Object.assign({ \"../../generated/locales/en.ts\": + es, \"../../generated/locales/fr.ts\": is }), ns = new Map(\n Xt.map((e) + => [\n e,\n rs[`../../generated/locales/${e}.ts`]\n ])\n), { getLocale: + jt, setLocale: Ot } = je({\n sourceLocale: os,\n targetLocales: Xt,\n loadLocale: + async (e) => ns.get(e)\n});\nwindow.getLocale = window.getLocale ? window.getLocale.push(jt) + : [jt];\nwindow.setLocale = window.setLocale ? window.setLocale.push(Ot) : + [Ot];\n" + recorded_at: Tue, 29 Jul 2025 04:39:23 GMT +- request: + method: get + uri: https://cdn.jsdelivr.net/npm/@startinblox/core@latest/dist/index.js + body: + encoding: UTF-8 + string: '' + headers: + Connection: + - close + Origin: + - http://127.0.0.1:37737 + Sec-Ch-Ua-Platform: + - '"Linux"' + User-Agent: + - Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/138.0.0.0 + Safari/537.36 + Sec-Ch-Ua: + - '"Not)A;Brand";v="8", "Chromium";v="138"' + Sec-Ch-Ua-Mobile: + - "?0" + Accept: + - "*/*" + Sec-Fetch-Site: + - cross-site + Sec-Fetch-Mode: + - cors + Sec-Fetch-Dest: + - script + Referer: + - http://127.0.0.1:37737/ + Accept-Encoding: + - '' + Accept-Language: + - en-US,en;q=0.9 + response: + status: + code: 200 + message: OK + headers: + Date: + - Tue, 29 Jul 2025 04:39:23 GMT + Content-Type: + - application/javascript; charset=utf-8 + Transfer-Encoding: + - chunked + Connection: + - close + Access-Control-Allow-Origin: + - "*" + Access-Control-Expose-Headers: + - "*" + Timing-Allow-Origin: + - "*" + Cache-Control: + - public, max-age=604800, s-maxage=43200 + Cross-Origin-Resource-Policy: + - cross-origin + X-Content-Type-Options: + - nosniff + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + X-Jsd-Version: + - 2.0.1 + X-Jsd-Version-Type: + - version + Etag: + - W/"397890-uVG/t1TLgZ04SMdVcWXhG0nuuJQ" + X-Served-By: + - cache-fra-eddf8230128-FRA, cache-lga21956-LGA + X-Cache: + - MISS, MISS + Vary: + - Accept-Encoding + Alt-Svc: + - h3=":443"; ma=86400 + Cf-Cache-Status: + - HIT + Age: + - '7600' + Report-To: + - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=YFJpxqm%2Byc49OvcQ1aJBo5XLGg1sOHS6SjQDws3yU4LaNGdIOBaYXxyyL5m9CkclmwlFKZHJWxkb0rsOAtIf4V8%2FKMJWoAWWGXGfVd8Zuqn2hXrPLtVYLUfY3EQKAgcO2Zo%3D"}],"group":"cf-nel","max_age":604800}' + Nel: + - '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}' + Server: + - cloudflare + Cf-Ray: + - 9669eea4ad5be697-MEL + body: + encoding: UTF-8 + string: "var __defProp = Object.defineProperty;\nvar __defNormalProp = (obj, + key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: + true, writable: true, value }) : obj[key] = value;\nvar __publicField = (obj, + key, value) => __defNormalProp(obj, typeof key !== \"symbol\" ? key + \"\" + : key, value);\nvar _a2;\nimport { g as getDefaultExportFromCjs, s as store, + b as base_context, f as formatAttributesToServerPaginationOptions, m as mergeServerSearchOptions, + a as formatAttributesToServerSearchOptions, c as commonjsGlobal } from \"./store-1t_BHzwg.js\";\nimport + { d as defineComponent, n as normalizeContext, u as uniqID, e as evalTemplateString, + a as doesResourceContainList, c as compare, p as parseFieldsString, f as findClosingBracketMatchIndex, + g as generalComparator, b as fuzzyCompare, h as asyncQuerySelector, i as importInlineCSS, + j as importCSS, t as transformArrayToContainer, s as setDeepProperty } from + \"./helpers-4GFJ8HI8.js\";\nimport { k } from \"./helpers-4GFJ8HI8.js\";\nif + (!(\"flat\" in Array.prototype)) {\n Object.defineProperty(Array.prototype, + \"flat\", {\n configurable: true,\n value: function flat(depth = 1) + {\n depth = Number.isNaN(depth) ? 1 : Number(depth);\n if (depth + === 0) return Array.prototype.slice.call(this);\n return Array.prototype.reduce.call(\n + \ this,\n (acc, cur) => {\n if (Array.isArray(cur)) + {\n acc.push.apply(acc, flat.call(cur, depth - 1));\n } + else {\n acc.push(cur);\n }\n return acc;\n },\n + \ []\n );\n },\n writable: true\n });\n}\nif (!Element.prototype.toggleAttribute) + {\n Element.prototype.toggleAttribute = function(name, force = void 0) {\n + \ if (force !== void 0) force = !!force;\n if (this.hasAttribute(name)) + {\n if (force) return true;\n this.removeAttribute(name);\n return + false;\n }\n if (force === false) return false;\n this.setAttribute(name, + \"\");\n return true;\n };\n}\nvar loglevel$1 = { exports: {} };\nvar + loglevel = loglevel$1.exports;\nvar hasRequiredLoglevel;\nfunction requireLoglevel() + {\n if (hasRequiredLoglevel) return loglevel$1.exports;\n hasRequiredLoglevel + = 1;\n (function(module2) {\n (function(root2, definition) {\n if + (module2.exports) {\n module2.exports = definition();\n } else + {\n root2.log = definition();\n }\n })(loglevel, function() + {\n var noop = function() {\n };\n var undefinedType = \"undefined\";\n + \ var isIE = typeof window !== undefinedType && typeof window.navigator + !== undefinedType && /Trident\\/|MSIE /.test(window.navigator.userAgent);\n + \ var logMethods = [\n \"trace\",\n \"debug\",\n \"info\",\n + \ \"warn\",\n \"error\"\n ];\n var _loggersByName = + {};\n var defaultLogger = null;\n function bindMethod(obj, methodName) + {\n var method = obj[methodName];\n if (typeof method.bind === + \"function\") {\n return method.bind(obj);\n } else {\n try + {\n return Function.prototype.bind.call(method, obj);\n } + catch (e2) {\n return function() {\n return Function.prototype.apply.apply(method, + [obj, arguments]);\n };\n }\n }\n }\n function + traceForIE() {\n if (console.log) {\n if (console.log.apply) + {\n console.log.apply(console, arguments);\n } else {\n + \ Function.prototype.apply.apply(console.log, [console, arguments]);\n + \ }\n }\n if (console.trace) console.trace();\n }\n + \ function realMethod(methodName) {\n if (methodName === \"debug\") + {\n methodName = \"log\";\n }\n if (typeof console + === undefinedType) {\n return false;\n } else if (methodName + === \"trace\" && isIE) {\n return traceForIE;\n } else if + (console[methodName] !== void 0) {\n return bindMethod(console, methodName);\n + \ } else if (console.log !== void 0) {\n return bindMethod(console, + \"log\");\n } else {\n return noop;\n }\n }\n + \ function replaceLoggingMethods() {\n var level2 = this.getLevel();\n + \ for (var i3 = 0; i3 < logMethods.length; i3++) {\n var methodName + = logMethods[i3];\n this[methodName] = i3 < level2 ? noop : this.methodFactory(methodName, + level2, this.name);\n }\n this.log = this.debug;\n if + (typeof console === undefinedType && level2 < this.levels.SILENT) {\n return + \"No console available for logging\";\n }\n }\n function + enableLoggingWhenConsoleArrives(methodName) {\n return function() {\n + \ if (typeof console !== undefinedType) {\n replaceLoggingMethods.call(this);\n + \ this[methodName].apply(this, arguments);\n }\n };\n + \ }\n function defaultMethodFactory(methodName, _level, _loggerName) + {\n return realMethod(methodName) || enableLoggingWhenConsoleArrives.apply(this, + arguments);\n }\n function Logger(name, factory) {\n var + self2 = this;\n var inheritedLevel;\n var defaultLevel;\n var + userLevel;\n var storageKey = \"loglevel\";\n if (typeof name + === \"string\") {\n storageKey += \":\" + name;\n } else if + (typeof name === \"symbol\") {\n storageKey = void 0;\n }\n + \ function persistLevelIfPossible(levelNum) {\n var levelName + = (logMethods[levelNum] || \"silent\").toUpperCase();\n if (typeof + window === undefinedType || !storageKey) return;\n try {\n window.localStorage[storageKey] + = levelName;\n return;\n } catch (ignore) {\n }\n + \ try {\n window.document.cookie = encodeURIComponent(storageKey) + + \"=\" + levelName + \";\";\n } catch (ignore) {\n }\n + \ }\n function getPersistedLevel() {\n var storedLevel;\n + \ if (typeof window === undefinedType || !storageKey) return;\n try + {\n storedLevel = window.localStorage[storageKey];\n } + catch (ignore) {\n }\n if (typeof storedLevel === undefinedType) + {\n try {\n var cookie = window.document.cookie;\n + \ var cookieName = encodeURIComponent(storageKey);\n var + location2 = cookie.indexOf(cookieName + \"=\");\n if (location2 + !== -1) {\n storedLevel = /^([^;]+)/.exec(\n cookie.slice(location2 + + cookieName.length + 1)\n )[1];\n }\n } + catch (ignore) {\n }\n }\n if (self2.levels[storedLevel] + === void 0) {\n storedLevel = void 0;\n }\n return + storedLevel;\n }\n function clearPersistedLevel() {\n if + (typeof window === undefinedType || !storageKey) return;\n try {\n + \ window.localStorage.removeItem(storageKey);\n } catch + (ignore) {\n }\n try {\n window.document.cookie + = encodeURIComponent(storageKey) + \"=; expires=Thu, 01 Jan 1970 00:00:00 + UTC\";\n } catch (ignore) {\n }\n }\n function + normalizeLevel(input) {\n var level2 = input;\n if (typeof + level2 === \"string\" && self2.levels[level2.toUpperCase()] !== void 0) {\n + \ level2 = self2.levels[level2.toUpperCase()];\n }\n if + (typeof level2 === \"number\" && level2 >= 0 && level2 <= self2.levels.SILENT) + {\n return level2;\n } else {\n throw new TypeError(\"log.setLevel() + called with invalid level: \" + input);\n }\n }\n self2.name + = name;\n self2.levels = {\n \"TRACE\": 0,\n \"DEBUG\": + 1,\n \"INFO\": 2,\n \"WARN\": 3,\n \"ERROR\": 4,\n + \ \"SILENT\": 5\n };\n self2.methodFactory = factory + || defaultMethodFactory;\n self2.getLevel = function() {\n if + (userLevel != null) {\n return userLevel;\n } else if + (defaultLevel != null) {\n return defaultLevel;\n } else + {\n return inheritedLevel;\n }\n };\n self2.setLevel + = function(level2, persist) {\n userLevel = normalizeLevel(level2);\n + \ if (persist !== false) {\n persistLevelIfPossible(userLevel);\n + \ }\n return replaceLoggingMethods.call(self2);\n };\n + \ self2.setDefaultLevel = function(level2) {\n defaultLevel + = normalizeLevel(level2);\n if (!getPersistedLevel()) {\n self2.setLevel(level2, + false);\n }\n };\n self2.resetLevel = function() {\n + \ userLevel = null;\n clearPersistedLevel();\n replaceLoggingMethods.call(self2);\n + \ };\n self2.enableAll = function(persist) {\n self2.setLevel(self2.levels.TRACE, + persist);\n };\n self2.disableAll = function(persist) {\n self2.setLevel(self2.levels.SILENT, + persist);\n };\n self2.rebuild = function() {\n if + (defaultLogger !== self2) {\n inheritedLevel = normalizeLevel(defaultLogger.getLevel());\n + \ }\n replaceLoggingMethods.call(self2);\n if (defaultLogger + === self2) {\n for (var childName in _loggersByName) {\n _loggersByName[childName].rebuild();\n + \ }\n }\n };\n inheritedLevel = normalizeLevel(\n + \ defaultLogger ? defaultLogger.getLevel() : \"WARN\"\n );\n + \ var initialLevel = getPersistedLevel();\n if (initialLevel + != null) {\n userLevel = normalizeLevel(initialLevel);\n }\n + \ replaceLoggingMethods.call(self2);\n }\n defaultLogger = + new Logger();\n defaultLogger.getLogger = function getLogger(name) {\n + \ if (typeof name !== \"symbol\" && typeof name !== \"string\" || name + === \"\") {\n throw new TypeError(\"You must supply a name when creating + a logger.\");\n }\n var logger = _loggersByName[name];\n if + (!logger) {\n logger = _loggersByName[name] = new Logger(\n name,\n + \ defaultLogger.methodFactory\n );\n }\n return + logger;\n };\n var _log = typeof window !== undefinedType ? window.log + : void 0;\n defaultLogger.noConflict = function() {\n if (typeof + window !== undefinedType && window.log === defaultLogger) {\n window.log + = _log;\n }\n return defaultLogger;\n };\n defaultLogger.getLoggers + = function getLoggers() {\n return _loggersByName;\n };\n defaultLogger[\"default\"] + = defaultLogger;\n return defaultLogger;\n });\n })(loglevel$1);\n + \ return loglevel$1.exports;\n}\nvar loglevelExports = requireLoglevel();\nconst + log = /* @__PURE__ */ getDefaultExportFromCjs(loglevelExports);\nconst __vite_import_meta_env__ + = {};\nconst colors = {\n reset: \"\\x1B[0m\",\n trace: \"\\x1B[38;2;169;169;169m\",\n + \ // Gray\n debug: \"\\x1B[38;2;0;0;255m\",\n // Blue\n info: \"\\x1B[38;2;0;128;0m\",\n + \ // Green\n warn: \"\\x1B[38;2;218;165;32m\",\n // Yellow\n error: \"\\x1B[38;2;255;0;0m\"\n + \ // Red\n};\nfunction formatMessage(level2, messages) {\n const timestamp + = (/* @__PURE__ */ new Date()).toISOString();\n const color = colors[level2] + || colors.reset;\n const formattedMessages = messages.map((msg) => {\n if + (typeof msg === \"object\") {\n try {\n return JSON.stringify(msg, + null, 2);\n } catch {\n return String(msg);\n }\n }\n + \ return String(msg);\n }).join(\" \");\n return `${color}[${timestamp}] + ${formattedMessages}${colors.reset}`;\n}\nconst originalFactory = log.methodFactory;\nlog.methodFactory + = (methodName, logLevel, loggerName) => {\n const rawMethod = originalFactory(methodName, + logLevel, loggerName);\n return (...messages) => {\n rawMethod(formatMessage(methodName, + messages));\n };\n};\nif ((__vite_import_meta_env__ == null ? void 0 : __vite_import_meta_env__.VITE_DEBUG) + === \"True\") {\n log.setLevel(\"debug\");\n} else {\n log.setLevel(\"warn\");\n}\nfunction + trackRenderAsync(fn, context) {\n return async function(...args) {\n let + componentName = context ? context : this.name ? this.name : this.constructor.name;\n + \ if (this.element.id) {\n componentName += ` (id: ${this.element.id})`;\n + \ } else if (this.dataSrc) {\n componentName += ` (data-src: ${this.dataSrc})`;\n + \ } else if (this.resourceId) {\n componentName += ` (resourceId: ${this.resourceId})`;\n + \ }\n const startTime = performance.now();\n const result = await + fn.apply(this, args);\n const endTime = performance.now();\n const renderTime + = endTime - startTime;\n if (this.profiler) {\n this.profiler.updateStats(componentName, + renderTime);\n this.profiler.printStats();\n } else {\n log.debug(\n + \ `Component ${componentName} rendered in ${renderTime.toFixed(2)} ms`\n + \ );\n }\n return result;\n };\n}\nclass Profiler {\n constructor() + {\n __publicField(this, \"stats\", {});\n this.stats = {};\n }\n getOrCreateStats(componentName) + {\n if (!this.stats[componentName]) {\n this.stats[componentName] + = {\n renderCount: 0,\n totalExecutionTime: 0,\n lastExecutionTime: + 0,\n averageExecutionTime: 0,\n minExecutionTime: Number.POSITIVE_INFINITY,\n + \ maxExecutionTime: 0\n };\n }\n return this.stats[componentName];\n + \ }\n getStats(componentName) {\n return this.stats[componentName];\n + \ }\n updateStats(componentName, renderTime) {\n const stats = this.getOrCreateStats(componentName);\n + \ stats.renderCount++;\n stats.lastExecutionTime = renderTime;\n stats.totalExecutionTime + += renderTime;\n stats.averageExecutionTime = stats.totalExecutionTime + / stats.renderCount;\n stats.minExecutionTime = Math.min(stats.minExecutionTime, + renderTime);\n stats.maxExecutionTime = Math.max(stats.maxExecutionTime, + renderTime);\n }\n formatTime(time) {\n if (time >= 1e3) {\n return + `${(time / 1e3).toFixed(2)} seconds`;\n }\n return `${time.toFixed(2)} + ms`;\n }\n formatComponentStats(componentName, stats) {\n return `\nComponent: + ${componentName}\n Render Count: ${stats.renderCount}\n Total Execution + Time: ${this.formatTime(stats.totalExecutionTime)}\n Last Execution Time: + ${this.formatTime(stats.lastExecutionTime)}\n Average Execution Time: ${this.formatTime(stats.averageExecutionTime)}\n + \ Min Execution Time: ${this.formatTime(stats.minExecutionTime)}\n Max Execution + Time: ${this.formatTime(stats.maxExecutionTime)}\n`;\n }\n printStats(componentName) + {\n let output = \"Component Performance Stats:\\n\";\n if (componentName) + {\n const stats = this.stats[componentName];\n if (!stats) {\n output + += `Component ${componentName} not found.\n`;\n } else {\n output + += this.formatComponentStats(componentName, stats);\n }\n } else {\n + \ for (const [name, stats] of Object.entries(this.stats)) {\n output + += this.formatComponentStats(name, stats);\n }\n }\n log.debug(output);\n + \ }\n}\nclass Component {\n constructor(element) {\n __publicField(this, + \"element\");\n __publicField(this, \"profiler\");\n this.element = + element;\n this.profiler = new Profiler();\n }\n created() {\n }\n attached() + {\n }\n detached() {\n }\n attributesCallback(_key, _value, _oldValue) + {\n }\n}\nconst HOOKS = [\"created\", \"attached\", \"detached\"];\nconst + API = [\"name\", \"use\", \"attributes\", \"initialState\", ...HOOKS];\nconst + Compositor = {\n merge(component, mixins) {\n return {\n name: component.name,\n + \ attributes: Compositor.mergeAttributes([component, ...mixins]),\n initialState: + Compositor.mergeInitialState([component, ...mixins]),\n methods: Compositor.mergeMethods([component, + ...mixins]),\n accessors: Compositor.mergeAccessors([component, ...mixins]),\n + \ hooks: Compositor.mergeHooks([component, ...mixins])\n };\n },\n + \ mergeMixin(component) {\n function deepMergeMixin(mixinAccumulator, currentMixin) + {\n const { use: currentMixins } = currentMixin;\n if (currentMixins) + {\n for (const mix of currentMixins) {\n if (!mixinAccumulator.has(mix)) + {\n mixinAccumulator.set(mix, mix);\n deepMergeMixin(mixinAccumulator, + mix);\n } else {\n console.warn(`Duplicate mixin import + (${mix.name})`);\n }\n }\n }\n }\n const mixins + = /* @__PURE__ */ new Map();\n deepMergeMixin(mixins, component);\n return + Array.from(mixins.values());\n },\n mergeAttributes(mixins) {\n let attributes + = {};\n for (const mixin of mixins) {\n if (mixin.attributes) {\n + \ attributes = { ...mixin.attributes, ...attributes };\n }\n }\n + \ return attributes;\n },\n mergeInitialState(mixins) {\n let initialState + = {};\n for (const mixin of mixins) {\n if (!mixin.initialState) continue;\n + \ initialState = { ...mixin.initialState, ...initialState };\n }\n + \ return initialState;\n },\n mergeHooks(mixins) {\n const hooks = + {\n created: [],\n attached: [],\n detached: []\n };\n for + (const mixin of mixins.reverse()) {\n for (const hookName of HOOKS) {\n + \ if (!!mixin[hookName] && typeof mixin[hookName] === \"function\") + {\n hooks[hookName].push(mixin[hookName]);\n }\n }\n + \ }\n return hooks;\n },\n mergeMethods(mixins) {\n const methods + = /* @__PURE__ */ new Map();\n for (const mixin of mixins.reverse()) {\n + \ for (const key of Reflect.ownKeys(mixin)) {\n if (typeof key + !== \"string\") continue;\n if (API.includes(key)) continue;\n const + descriptor = Object.getOwnPropertyDescriptor(mixin, key);\n if ((descriptor + == null ? void 0 : descriptor.get) || (descriptor == null ? void 0 : descriptor.set)) + continue;\n if (typeof mixin[key] !== \"function\") continue;\n methods.set(key, + mixin[key]);\n }\n }\n return methods;\n },\n mergeAccessors(mixins) + {\n const accessors = {};\n for (const mixin of mixins.reverse()) {\n + \ for (const prop of Reflect.ownKeys(mixin)) {\n if (typeof prop + !== \"string\") continue;\n if (API.includes(prop)) continue;\n const + descriptor = Object.getOwnPropertyDescriptor(mixin, prop);\n if (!descriptor) + continue;\n if (!descriptor.get && !descriptor.set) continue;\n accessors[prop] + = { ...accessors[prop] };\n if (descriptor.get) accessors[prop].get + = descriptor.get;\n if (descriptor.set) accessors[prop].set = descriptor.set;\n + \ }\n }\n return accessors;\n }\n};\nconst ComponentFactory = {\n + \ build(component) {\n const { initialState, attributes, methods, hooks, + accessors, name } = Compositor.merge(component, Compositor.mergeMixin(component));\n + \ let componentConstructor = class extends Component {\n };\n componentConstructor + = ComponentFactory.bindInitialState(\n componentConstructor,\n initialState\n + \ );\n componentConstructor = ComponentFactory.bindAttributes(\n componentConstructor,\n + \ attributes\n );\n componentConstructor = ComponentFactory.bindMethods(\n + \ componentConstructor,\n methods\n );\n componentConstructor + = ComponentFactory.bindAccessors(\n componentConstructor,\n accessors\n + \ );\n componentConstructor = ComponentFactory.bindHooks(\n componentConstructor,\n + \ hooks\n );\n Reflect.defineProperty(componentConstructor, \"name\", + {\n value: name\n });\n return componentConstructor;\n },\n bindInitialState(componentConstructor, + initialState) {\n if (initialState) {\n for (const key of Reflect.ownKeys(initialState)) + {\n Reflect.defineProperty(componentConstructor.prototype, key, {\n + \ enumerable: true,\n writable: true,\n value: initialState[key]\n + \ });\n }\n }\n return componentConstructor;\n },\n bindAttributes(componentConstructor, + attributes) {\n if (attributes) {\n const attributesList = Reflect.ownKeys(attributes).map(\n + \ (key) => String(key)\n );\n const attributesCallback = {};\n + \ for (const key of attributesList) {\n const { default: def, type, + required, callback } = attributes[key];\n let fromType;\n let + toType;\n switch (type) {\n case String:\n fromType + = (value) => String(value);\n toType = (value) => value;\n break;\n + \ case Object:\n fromType = (value) => JSON.parse(value);\n + \ toType = (value) => JSON.stringify(value);\n break;\n + \ case Number:\n fromType = (value) => Number(value);\n + \ toType = (value) => Number(value).toString();\n break;\n + \ case Boolean:\n fromType = (value) => Boolean(value);\n + \ toType = (value) => value;\n break;\n default:\n + \ fromType = (value) => value;\n toType = (value) => + value;\n break;\n }\n const attribute2 = key.replace(/([a-z0-9])([A-Z0-9])/g, + \"$1-$2\").toLowerCase();\n Reflect.defineProperty(componentConstructor.prototype, + key, {\n enumerable: true,\n configurable: false,\n get: + function() {\n const element = this.element;\n if (!element.hasAttribute(attribute2)) + {\n if (required && type !== Boolean) {\n throw + new Error(`Attribute ${key} is required`);\n }\n return + def;\n }\n return fromType(element.getAttribute(attribute2));\n + \ },\n set: function(value) {\n const element + = this.element;\n if (type === Boolean) {\n if (!value) + {\n element.removeAttribute(attribute2);\n } else + {\n element.setAttribute(attribute2, \"\");\n }\n + \ } else {\n element.setAttribute(attribute2, toType(value));\n + \ }\n }\n });\n if (callback && typeof callback + === \"function\") {\n attributesCallback[key] = callback;\n }\n + \ }\n Reflect.defineProperty(componentConstructor, \"observedAttributes\", + {\n get: () => attributesList.map(\n (attr) => attr.replace(/([a-z0-9])([A-Z0-9])/g, + \"$1-$2\").toLowerCase()\n )\n });\n Reflect.defineProperty(\n + \ componentConstructor.prototype,\n \"attributesCallback\",\n + \ {\n value: function(key, newValue, oldValue) {\n if + (key in attributesCallback) {\n Reflect.apply(attributesCallback[key], + this, [\n newValue,\n oldValue\n ]);\n + \ }\n }\n }\n );\n Reflect.defineProperty(\n + \ componentConstructor.prototype,\n \"attributesCallback\",\n + \ attributesCallback\n );\n }\n return componentConstructor;\n + \ },\n bindAccessors(componentConstructor, accessors) {\n if (accessors) + {\n for (const property of Object.keys(accessors)) {\n Reflect.defineProperty(componentConstructor.prototype, + property, {\n get: function() {\n return Reflect.apply(accessors[property].get, + this, []);\n },\n set: function(value) {\n return + Reflect.apply(accessors[property].set, this, [value]);\n }\n });\n + \ }\n }\n return componentConstructor;\n },\n bindMethods(componentConstructor, + methods) {\n methods.forEach((method, methodName) => {\n Reflect.defineProperty(componentConstructor.prototype, + methodName, {\n value: function(...args) {\n return Reflect.apply(method, + this, args);\n }\n });\n });\n return componentConstructor;\n + \ },\n bindHooks(componentConstructor, hooks) {\n Reflect.defineProperty(componentConstructor.prototype, + \"created\", {\n value: function() {\n for (const hook of hooks.created) + {\n Reflect.apply(hook, this, []);\n }\n }\n });\n + \ Reflect.defineProperty(componentConstructor.prototype, \"attached\", {\n + \ value: function() {\n for (const hook of hooks.attached) {\n + \ Reflect.apply(hook, this, []);\n }\n }\n });\n Reflect.defineProperty(componentConstructor.prototype, + \"detached\", {\n value: function() {\n for (const hook of hooks.detached) + {\n Reflect.apply(hook, this, []);\n }\n }\n });\n + \ return componentConstructor;\n }\n};\nconst Sib = {\n register(componentDefinition) + {\n const component = ComponentFactory.build(componentDefinition);\n const + cls = Sib.toElement(component);\n defineComponent(component.name, cls);\n + \ },\n toElement(component) {\n return class extends HTMLElement {\n constructor() + {\n super();\n __publicField(this, \"component\");\n this.component + = new component(this);\n this.component.created();\n }\n /** + @deprecated use `component` instead */\n get _component() {\n return + this.component;\n }\n set _component(_component) {\n this.component + = _component;\n }\n static get observedAttributes() {\n return + component.observedAttributes;\n }\n attributeChangedCallback(name, + oldValue, newValue) {\n const attr = name.replace(\n /([a-z0-9])-([a-z0-9])/g,\n + \ (_c, p1, p2) => `${p1}${p2.toUpperCase()}`\n );\n this.component.attributesCallback(attr, + newValue, oldValue);\n }\n connectedCallback() {\n this.component.attached();\n + \ }\n disconnectedCallback() {\n this.component.detached();\n + \ }\n };\n }\n};\nconst AttributeBinderMixin = {\n name: \"attribute-binder-mixin\",\n + \ use: [],\n initialState: {\n bindedAttributes: null\n },\n created() + {\n this.bindedAttributes = {};\n },\n /**\n * Reset attributes values\n + \ */\n resetAttributesData() {\n for (const attr of Object.keys(this.bindedAttributes)) + {\n this.element.setAttribute(attr, this.bindedAttributes[attr]);\n }\n + \ },\n /**\n * Replace store://XXX attributes by corresponding data\n * + @param reset - set to false if no need to reset data\n */\n async replaceAttributesData(reset + = true) {\n if (reset) this.resetAttributesData();\n const oldAttributes + = {};\n for (const attr of this.element.attributes) {\n if (!attr.value.match(/^store:\\/\\/(resource|container|user)/)) + continue;\n if (!this.bindedAttributes[attr.name])\n this.bindedAttributes[attr.name] + = attr.value;\n oldAttributes[attr.name] = attr.value;\n }\n const + newAttributes = await this.transformAttributes(\n { ...oldAttributes + },\n this.resource\n );\n for (const attr of Object.keys(newAttributes)) + {\n if (oldAttributes[attr] === newAttributes[attr]) continue;\n this.element.setAttribute(attr, + newAttributes[attr]);\n }\n },\n /**\n * Transform attributes from + `store://...` to their actual value\n * @param attributes - object representing + attributes of an element\n * @param resource - resource to use to resolve + attributes\n * @returns - object representing attributes of an element with + resolved values\n */\n async transformAttributes(attributes, resource) + {\n var _a3;\n const isContainer = (_a3 = resource == null ? void 0 + : resource.isContainer) == null ? void 0 : _a3.call(resource);\n for (const + attr of Object.keys(attributes)) {\n const value = attributes[attr];\n + \ if (typeof value === \"string\") {\n if (!isContainer && resource + && value.startsWith(\"store://resource\")) {\n const path = value.replace(\"store://resource.\", + \"\");\n attributes[attr] = resource ? await resource[path] : \"\";\n + \ } else if (isContainer && resource && value.startsWith(\"store://container\")) + {\n const path = value.replace(\"store://container.\", \"\");\n attributes[attr] + = resource ? await resource[path] : \"\";\n } else if (value.startsWith(\"store://user\")) + {\n const userId = await this.retry(this.getUser.bind(this));\n const + user = (userId == null ? void 0 : userId[\"@id\"]) ? await store.getData(userId[\"@id\"], + this.context || base_context) : null;\n if (!user) {\n attributes[attr] + = \"\";\n continue;\n }\n const path = value.replace(\"store://user.\", + \"\");\n attributes[attr] = user ? await user[path] : \"\";\n }\n + \ }\n }\n return attributes;\n },\n /**\n * Returns logged in + user from sib-auth\n * @returns userId\n */\n async getUser() {\n const + sibAuth = document.querySelector(\"sib-auth\");\n return await sibAuth.getUser();\n + \ },\n /**\n * Retry [fn] for [maxRetries] times every [ms]\n * @param + fn\n * @param ms\n * @param maxRetries\n * @returns\n */\n async + retry(fn, ms = 200, maxRetries = 5) {\n return new Promise((resolve, reject) + => {\n let retries = 0;\n fn().then(resolve).catch(() => {\n setTimeout(() + => {\n ++retries;\n if (retries >= maxRetries) return reject();\n + \ this.retry(fn, ms).then(resolve);\n }, ms);\n });\n + \ });\n }\n};\nconst ContextMixin = {\n name: \"store-mixin\",\n use: + [],\n attributes: {\n extraContext: {\n type: String,\n default: + null\n }\n },\n get context() {\n return { ...base_context, ...this.extra_context + };\n },\n get extra_context() {\n const extraContextElement = this.extraContext + ? document.getElementById(this.extraContext) : (\n // take element extra + context first\n document.querySelector(\"[data-default-context]\")\n + \ );\n if (extraContextElement)\n return JSON.parse(extraContextElement.textContent + || \"{}\");\n return {};\n }\n};\n/**\n * @license\n * Copyright 2019 + Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n */\nconst t$2 = globalThis, + e$4 = t$2.ShadowRoot && (void 0 === t$2.ShadyCSS || t$2.ShadyCSS.nativeShadow) + && \"adoptedStyleSheets\" in Document.prototype && \"replace\" in CSSStyleSheet.prototype, + s$3 = Symbol(), o$5 = /* @__PURE__ */ new WeakMap();\nlet n$4 = class n {\n + \ constructor(t2, e2, o2) {\n if (this._$cssResult$ = true, o2 !== s$3) + throw Error(\"CSSResult is not constructable. Use `unsafeCSS` or `css` instead.\");\n + \ this.cssText = t2, this.t = e2;\n }\n get styleSheet() {\n let t2 + = this.o;\n const s2 = this.t;\n if (e$4 && void 0 === t2) {\n const + e2 = void 0 !== s2 && 1 === s2.length;\n e2 && (t2 = o$5.get(s2)), void + 0 === t2 && ((this.o = t2 = new CSSStyleSheet()).replaceSync(this.cssText), + e2 && o$5.set(s2, t2));\n }\n return t2;\n }\n toString() {\n return + this.cssText;\n }\n};\nconst r$4 = (t2) => new n$4(\"string\" == typeof t2 + ? t2 : t2 + \"\", void 0, s$3), S$1 = (s2, o2) => {\n if (e$4) s2.adoptedStyleSheets + = o2.map((t2) => t2 instanceof CSSStyleSheet ? t2 : t2.styleSheet);\n else + for (const e2 of o2) {\n const o3 = document.createElement(\"style\"), + n3 = t$2.litNonce;\n void 0 !== n3 && o3.setAttribute(\"nonce\", n3), o3.textContent + = e2.cssText, s2.appendChild(o3);\n }\n}, c$4 = e$4 ? (t2) => t2 : (t2) => + t2 instanceof CSSStyleSheet ? ((t3) => {\n let e2 = \"\";\n for (const s2 + of t3.cssRules) e2 += s2.cssText;\n return r$4(e2);\n})(t2) : t2;\n/**\n + * @license\n * Copyright 2017 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n + */\nconst { is: i$5, defineProperty: e$3, getOwnPropertyDescriptor: r$3, getOwnPropertyNames: + h$3, getOwnPropertySymbols: o$4, getPrototypeOf: n$3 } = Object, a$1 = globalThis, + c$3 = a$1.trustedTypes, l$1 = c$3 ? c$3.emptyScript : \"\", p$1 = a$1.reactiveElementPolyfillSupport, + d$1 = (t2, s2) => t2, u$1 = { toAttribute(t2, s2) {\n switch (s2) {\n case + Boolean:\n t2 = t2 ? l$1 : null;\n break;\n case Object:\n case + Array:\n t2 = null == t2 ? t2 : JSON.stringify(t2);\n }\n return t2;\n}, + fromAttribute(t2, s2) {\n let i3 = t2;\n switch (s2) {\n case Boolean:\n + \ i3 = null !== t2;\n break;\n case Number:\n i3 = null === + t2 ? null : Number(t2);\n break;\n case Object:\n case Array:\n + \ try {\n i3 = JSON.parse(t2);\n } catch (t3) {\n i3 + = null;\n }\n }\n return i3;\n} }, f$3 = (t2, s2) => !i$5(t2, s2), + y$1 = { attribute: true, type: String, converter: u$1, reflect: false, hasChanged: + f$3 };\nSymbol.metadata ?? (Symbol.metadata = Symbol(\"metadata\")), a$1.litPropertyMetadata + ?? (a$1.litPropertyMetadata = /* @__PURE__ */ new WeakMap());\nclass b extends + HTMLElement {\n static addInitializer(t2) {\n this._$Ei(), (this.l ?? + (this.l = [])).push(t2);\n }\n static get observedAttributes() {\n return + this.finalize(), this._$Eh && [...this._$Eh.keys()];\n }\n static createProperty(t2, + s2 = y$1) {\n if (s2.state && (s2.attribute = false), this._$Ei(), this.elementProperties.set(t2, + s2), !s2.noAccessor) {\n const i3 = Symbol(), r3 = this.getPropertyDescriptor(t2, + i3, s2);\n void 0 !== r3 && e$3(this.prototype, t2, r3);\n }\n }\n + \ static getPropertyDescriptor(t2, s2, i3) {\n const { get: e2, set: h2 + } = r$3(this.prototype, t2) ?? { get() {\n return this[s2];\n }, set(t3) + {\n this[s2] = t3;\n } };\n return { get() {\n return e2 == + null ? void 0 : e2.call(this);\n }, set(s3) {\n const r3 = e2 == null + ? void 0 : e2.call(this);\n h2.call(this, s3), this.requestUpdate(t2, + r3, i3);\n }, configurable: true, enumerable: true };\n }\n static getPropertyOptions(t2) + {\n return this.elementProperties.get(t2) ?? y$1;\n }\n static _$Ei() + {\n if (this.hasOwnProperty(d$1(\"elementProperties\"))) return;\n const + t2 = n$3(this);\n t2.finalize(), void 0 !== t2.l && (this.l = [...t2.l]), + this.elementProperties = new Map(t2.elementProperties);\n }\n static finalize() + {\n if (this.hasOwnProperty(d$1(\"finalized\"))) return;\n if (this.finalized + = true, this._$Ei(), this.hasOwnProperty(d$1(\"properties\"))) {\n const + t3 = this.properties, s2 = [...h$3(t3), ...o$4(t3)];\n for (const i3 + of s2) this.createProperty(i3, t3[i3]);\n }\n const t2 = this[Symbol.metadata];\n + \ if (null !== t2) {\n const s2 = litPropertyMetadata.get(t2);\n if + (void 0 !== s2) for (const [t3, i3] of s2) this.elementProperties.set(t3, + i3);\n }\n this._$Eh = /* @__PURE__ */ new Map();\n for (const [t3, + s2] of this.elementProperties) {\n const i3 = this._$Eu(t3, s2);\n void + 0 !== i3 && this._$Eh.set(i3, t3);\n }\n this.elementStyles = this.finalizeStyles(this.styles);\n + \ }\n static finalizeStyles(s2) {\n const i3 = [];\n if (Array.isArray(s2)) + {\n const e2 = new Set(s2.flat(1 / 0).reverse());\n for (const s3 + of e2) i3.unshift(c$4(s3));\n } else void 0 !== s2 && i3.push(c$4(s2));\n + \ return i3;\n }\n static _$Eu(t2, s2) {\n const i3 = s2.attribute;\n + \ return false === i3 ? void 0 : \"string\" == typeof i3 ? i3 : \"string\" + == typeof t2 ? t2.toLowerCase() : void 0;\n }\n constructor() {\n super(), + this._$Ep = void 0, this.isUpdatePending = false, this.hasUpdated = false, + this._$Em = null, this._$Ev();\n }\n _$Ev() {\n var _a3;\n this._$ES + = new Promise((t2) => this.enableUpdating = t2), this._$AL = /* @__PURE__ + */ new Map(), this._$E_(), this.requestUpdate(), (_a3 = this.constructor.l) + == null ? void 0 : _a3.forEach((t2) => t2(this));\n }\n addController(t2) + {\n var _a3;\n (this._$EO ?? (this._$EO = /* @__PURE__ */ new Set())).add(t2), + void 0 !== this.renderRoot && this.isConnected && ((_a3 = t2.hostConnected) + == null ? void 0 : _a3.call(t2));\n }\n removeController(t2) {\n var + _a3;\n (_a3 = this._$EO) == null ? void 0 : _a3.delete(t2);\n }\n _$E_() + {\n const t2 = /* @__PURE__ */ new Map(), s2 = this.constructor.elementProperties;\n + \ for (const i3 of s2.keys()) this.hasOwnProperty(i3) && (t2.set(i3, this[i3]), + delete this[i3]);\n t2.size > 0 && (this._$Ep = t2);\n }\n createRenderRoot() + {\n const t2 = this.shadowRoot ?? this.attachShadow(this.constructor.shadowRootOptions);\n + \ return S$1(t2, this.constructor.elementStyles), t2;\n }\n connectedCallback() + {\n var _a3;\n this.renderRoot ?? (this.renderRoot = this.createRenderRoot()), + this.enableUpdating(true), (_a3 = this._$EO) == null ? void 0 : _a3.forEach((t2) + => {\n var _a4;\n return (_a4 = t2.hostConnected) == null ? void + 0 : _a4.call(t2);\n });\n }\n enableUpdating(t2) {\n }\n disconnectedCallback() + {\n var _a3;\n (_a3 = this._$EO) == null ? void 0 : _a3.forEach((t2) + => {\n var _a4;\n return (_a4 = t2.hostDisconnected) == null ? void + 0 : _a4.call(t2);\n });\n }\n attributeChangedCallback(t2, s2, i3) {\n + \ this._$AK(t2, i3);\n }\n _$EC(t2, s2) {\n var _a3;\n const i3 + = this.constructor.elementProperties.get(t2), e2 = this.constructor._$Eu(t2, + i3);\n if (void 0 !== e2 && true === i3.reflect) {\n const r3 = (void + 0 !== ((_a3 = i3.converter) == null ? void 0 : _a3.toAttribute) ? i3.converter + : u$1).toAttribute(s2, i3.type);\n this._$Em = t2, null == r3 ? this.removeAttribute(e2) + : this.setAttribute(e2, r3), this._$Em = null;\n }\n }\n _$AK(t2, s2) + {\n var _a3;\n const i3 = this.constructor, e2 = i3._$Eh.get(t2);\n + \ if (void 0 !== e2 && this._$Em !== e2) {\n const t3 = i3.getPropertyOptions(e2), + r3 = \"function\" == typeof t3.converter ? { fromAttribute: t3.converter } + : void 0 !== ((_a3 = t3.converter) == null ? void 0 : _a3.fromAttribute) ? + t3.converter : u$1;\n this._$Em = e2, this[e2] = r3.fromAttribute(s2, + t3.type), this._$Em = null;\n }\n }\n requestUpdate(t2, s2, i3) {\n if + (void 0 !== t2) {\n if (i3 ?? (i3 = this.constructor.getPropertyOptions(t2)), + !(i3.hasChanged ?? f$3)(this[t2], s2)) return;\n this.P(t2, s2, i3);\n + \ }\n false === this.isUpdatePending && (this._$ES = this._$ET());\n + \ }\n P(t2, s2, i3) {\n this._$AL.has(t2) || this._$AL.set(t2, s2), true + === i3.reflect && this._$Em !== t2 && (this._$Ej ?? (this._$Ej = /* @__PURE__ + */ new Set())).add(t2);\n }\n async _$ET() {\n this.isUpdatePending = + true;\n try {\n await this._$ES;\n } catch (t3) {\n Promise.reject(t3);\n + \ }\n const t2 = this.scheduleUpdate();\n return null != t2 && await + t2, !this.isUpdatePending;\n }\n scheduleUpdate() {\n return this.performUpdate();\n + \ }\n performUpdate() {\n var _a3;\n if (!this.isUpdatePending) return;\n + \ if (!this.hasUpdated) {\n if (this.renderRoot ?? (this.renderRoot + = this.createRenderRoot()), this._$Ep) {\n for (const [t4, s3] of this._$Ep) + this[t4] = s3;\n this._$Ep = void 0;\n }\n const t3 = this.constructor.elementProperties;\n + \ if (t3.size > 0) for (const [s3, i3] of t3) true !== i3.wrapped || this._$AL.has(s3) + || void 0 === this[s3] || this.P(s3, this[s3], i3);\n }\n let t2 = false;\n + \ const s2 = this._$AL;\n try {\n t2 = this.shouldUpdate(s2), t2 + ? (this.willUpdate(s2), (_a3 = this._$EO) == null ? void 0 : _a3.forEach((t3) + => {\n var _a4;\n return (_a4 = t3.hostUpdate) == null ? void + 0 : _a4.call(t3);\n }), this.update(s2)) : this._$EU();\n } catch + (s3) {\n throw t2 = false, this._$EU(), s3;\n }\n t2 && this._$AE(s2);\n + \ }\n willUpdate(t2) {\n }\n _$AE(t2) {\n var _a3;\n (_a3 = this._$EO) + == null ? void 0 : _a3.forEach((t3) => {\n var _a4;\n return (_a4 + = t3.hostUpdated) == null ? void 0 : _a4.call(t3);\n }), this.hasUpdated + || (this.hasUpdated = true, this.firstUpdated(t2)), this.updated(t2);\n }\n + \ _$EU() {\n this._$AL = /* @__PURE__ */ new Map(), this.isUpdatePending + = false;\n }\n get updateComplete() {\n return this.getUpdateComplete();\n + \ }\n getUpdateComplete() {\n return this._$ES;\n }\n shouldUpdate(t2) + {\n return true;\n }\n update(t2) {\n this._$Ej && (this._$Ej = this._$Ej.forEach((t3) + => this._$EC(t3, this[t3]))), this._$EU();\n }\n updated(t2) {\n }\n firstUpdated(t2) + {\n }\n}\nb.elementStyles = [], b.shadowRootOptions = { mode: \"open\" }, + b[d$1(\"elementProperties\")] = /* @__PURE__ */ new Map(), b[d$1(\"finalized\")] + = /* @__PURE__ */ new Map(), p$1 == null ? void 0 : p$1({ ReactiveElement: + b }), (a$1.reactiveElementVersions ?? (a$1.reactiveElementVersions = [])).push(\"2.0.4\");\n/**\n + * @license\n * Copyright 2017 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n + */\nconst t$1 = globalThis, i$4 = t$1.trustedTypes, s$2 = i$4 ? i$4.createPolicy(\"lit-html\", + { createHTML: (t2) => t2 }) : void 0, e$2 = \"$lit$\", h$2 = `lit$${Math.random().toFixed(9).slice(2)}$`, + o$3 = \"?\" + h$2, n$2 = `<${o$3}>`, r$2 = document, l = () => r$2.createComment(\"\"), + c$2 = (t2) => null === t2 || \"object\" != typeof t2 && \"function\" != typeof + t2, a = Array.isArray, u = (t2) => a(t2) || \"function\" == typeof (t2 == + null ? void 0 : t2[Symbol.iterator]), d = \"[ \t\\n\\f\\r]\", f$2 = /<(?:(!--|\\/[^a-zA-Z])|(\\/?[a-zA-Z][^>\\s]*)|(\\/?$))/g, + v = /-->/g, _ = />/g, m$1 = RegExp(`>|${d}(?:([^\\\\s\"'>=/]+)(${d}*=${d}*(?:[^ + \t\n\\f\\r\"'\\`<>=]|(\"|')|))|$)`, \"g\"), p = /'/g, g = /\"/g, $ = /^(?:script|style|textarea|title)$/i, + y = (t2) => (i3, ...s2) => ({ _$litType$: t2, strings: i3, values: s2 }), + x = y(1), T = Symbol.for(\"lit-noChange\"), E = Symbol.for(\"lit-nothing\"), + A = /* @__PURE__ */ new WeakMap(), C = r$2.createTreeWalker(r$2, 129);\nfunction + P$1(t2, i3) {\n if (!a(t2) || !t2.hasOwnProperty(\"raw\")) throw Error(\"invalid + template strings array\");\n return void 0 !== s$2 ? s$2.createHTML(i3) : + i3;\n}\nconst V = (t2, i3) => {\n const s2 = t2.length - 1, o2 = [];\n let + r3, l2 = 2 === i3 ? \"\" : 3 === i3 ? \"\" : \"\", c2 = f$2;\n + \ for (let i4 = 0; i4 < s2; i4++) {\n const s3 = t2[i4];\n let a2, u2, + d2 = -1, y2 = 0;\n for (; y2 < s3.length && (c2.lastIndex = y2, u2 = c2.exec(s3), + null !== u2); ) y2 = c2.lastIndex, c2 === f$2 ? \"!--\" === u2[1] ? c2 = v + : void 0 !== u2[1] ? c2 = _ : void 0 !== u2[2] ? ($.test(u2[2]) && (r3 = RegExp(\"\" === u2[0] ? (c2 = r3 ?? f$2, d2 = -1) : void 0 === u2[1] ? d2 = -2 + : (d2 = c2.lastIndex - u2[2].length, a2 = u2[1], c2 = void 0 === u2[3] ? m$1 + : '\"' === u2[3] ? g : p) : c2 === g || c2 === p ? c2 = m$1 : c2 === v || + c2 === _ ? c2 = f$2 : (c2 = m$1, r3 = void 0);\n const x2 = c2 === m$1 + && t2[i4 + 1].startsWith(\"/>\") ? \" \" : \"\";\n l2 += c2 === f$2 ? s3 + + n$2 : d2 >= 0 ? (o2.push(a2), s3.slice(0, d2) + e$2 + s3.slice(d2) + h$2 + + x2) : s3 + h$2 + (-2 === d2 ? i4 : x2);\n }\n return [P$1(t2, l2 + (t2[s2] + || \"\") + (2 === i3 ? \"\" : 3 === i3 ? \"\" : \"\")), o2];\n};\nclass + N {\n constructor({ strings: t2, _$litType$: s2 }, n3) {\n let r3;\n this.parts + = [];\n let c2 = 0, a2 = 0;\n const u2 = t2.length - 1, d2 = this.parts, + [f2, v2] = V(t2, s2);\n if (this.el = N.createElement(f2, n3), C.currentNode + = this.el.content, 2 === s2 || 3 === s2) {\n const t3 = this.el.content.firstChild;\n + \ t3.replaceWith(...t3.childNodes);\n }\n for (; null !== (r3 = + C.nextNode()) && d2.length < u2; ) {\n if (1 === r3.nodeType) {\n if + (r3.hasAttributes()) for (const t3 of r3.getAttributeNames()) if (t3.endsWith(e$2)) + {\n const i3 = v2[a2++], s3 = r3.getAttribute(t3).split(h$2), e2 + = /([.?@])?(.*)/.exec(i3);\n d2.push({ type: 1, index: c2, name: + e2[2], strings: s3, ctor: \".\" === e2[1] ? H : \"?\" === e2[1] ? I : \"@\" + === e2[1] ? L : k2 }), r3.removeAttribute(t3);\n } else t3.startsWith(h$2) + && (d2.push({ type: 6, index: c2 }), r3.removeAttribute(t3));\n if + ($.test(r3.tagName)) {\n const t3 = r3.textContent.split(h$2), s3 + = t3.length - 1;\n if (s3 > 0) {\n r3.textContent = i$4 + ? i$4.emptyScript : \"\";\n for (let i3 = 0; i3 < s3; i3++) r3.append(t3[i3], + l()), C.nextNode(), d2.push({ type: 2, index: ++c2 });\n r3.append(t3[s3], + l());\n }\n }\n } else if (8 === r3.nodeType) if (r3.data + === o$3) d2.push({ type: 2, index: c2 });\n else {\n let t3 = + -1;\n for (; -1 !== (t3 = r3.data.indexOf(h$2, t3 + 1)); ) d2.push({ + type: 7, index: c2 }), t3 += h$2.length - 1;\n }\n c2++;\n }\n + \ }\n static createElement(t2, i3) {\n const s2 = r$2.createElement(\"template\");\n + \ return s2.innerHTML = t2, s2;\n }\n}\nfunction S(t2, i3, s2 = t2, e2) + {\n var _a3, _b;\n if (i3 === T) return i3;\n let h2 = void 0 !== e2 ? + (_a3 = s2._$Co) == null ? void 0 : _a3[e2] : s2._$Cl;\n const o2 = c$2(i3) + ? void 0 : i3._$litDirective$;\n return (h2 == null ? void 0 : h2.constructor) + !== o2 && ((_b = h2 == null ? void 0 : h2._$AO) == null ? void 0 : _b.call(h2, + false), void 0 === o2 ? h2 = void 0 : (h2 = new o2(t2), h2._$AT(t2, s2, e2)), + void 0 !== e2 ? (s2._$Co ?? (s2._$Co = []))[e2] = h2 : s2._$Cl = h2), void + 0 !== h2 && (i3 = S(t2, h2._$AS(t2, i3.values), h2, e2)), i3;\n}\nclass M + {\n constructor(t2, i3) {\n this._$AV = [], this._$AN = void 0, this._$AD + = t2, this._$AM = i3;\n }\n get parentNode() {\n return this._$AM.parentNode;\n + \ }\n get _$AU() {\n return this._$AM._$AU;\n }\n u(t2) {\n const + { el: { content: i3 }, parts: s2 } = this._$AD, e2 = ((t2 == null ? void 0 + : t2.creationScope) ?? r$2).importNode(i3, true);\n C.currentNode = e2;\n + \ let h2 = C.nextNode(), o2 = 0, n3 = 0, l2 = s2[0];\n for (; void 0 + !== l2; ) {\n if (o2 === l2.index) {\n let i4;\n 2 === + l2.type ? i4 = new R(h2, h2.nextSibling, this, t2) : 1 === l2.type ? i4 = + new l2.ctor(h2, l2.name, l2.strings, this, t2) : 6 === l2.type && (i4 = new + z(h2, this, t2)), this._$AV.push(i4), l2 = s2[++n3];\n }\n o2 !== + (l2 == null ? void 0 : l2.index) && (h2 = C.nextNode(), o2++);\n }\n return + C.currentNode = r$2, e2;\n }\n p(t2) {\n let i3 = 0;\n for (const + s2 of this._$AV) void 0 !== s2 && (void 0 !== s2.strings ? (s2._$AI(t2, s2, + i3), i3 += s2.strings.length - 2) : s2._$AI(t2[i3])), i3++;\n }\n}\nclass + R {\n get _$AU() {\n var _a3;\n return ((_a3 = this._$AM) == null ? + void 0 : _a3._$AU) ?? this._$Cv;\n }\n constructor(t2, i3, s2, e2) {\n this.type + = 2, this._$AH = E, this._$AN = void 0, this._$AA = t2, this._$AB = i3, this._$AM + = s2, this.options = e2, this._$Cv = (e2 == null ? void 0 : e2.isConnected) + ?? true;\n }\n get parentNode() {\n let t2 = this._$AA.parentNode;\n + \ const i3 = this._$AM;\n return void 0 !== i3 && 11 === (t2 == null + ? void 0 : t2.nodeType) && (t2 = i3.parentNode), t2;\n }\n get startNode() + {\n return this._$AA;\n }\n get endNode() {\n return this._$AB;\n + \ }\n _$AI(t2, i3 = this) {\n t2 = S(this, t2, i3), c$2(t2) ? t2 === E + || null == t2 || \"\" === t2 ? (this._$AH !== E && this._$AR(), this._$AH + = E) : t2 !== this._$AH && t2 !== T && this._(t2) : void 0 !== t2._$litType$ + ? this.$(t2) : void 0 !== t2.nodeType ? this.T(t2) : u(t2) ? this.k(t2) : + this._(t2);\n }\n O(t2) {\n return this._$AA.parentNode.insertBefore(t2, + this._$AB);\n }\n T(t2) {\n this._$AH !== t2 && (this._$AR(), this._$AH + = this.O(t2));\n }\n _(t2) {\n this._$AH !== E && c$2(this._$AH) ? this._$AA.nextSibling.data + = t2 : this.T(r$2.createTextNode(t2)), this._$AH = t2;\n }\n $(t2) {\n var + _a3;\n const { values: i3, _$litType$: s2 } = t2, e2 = \"number\" == typeof + s2 ? this._$AC(t2) : (void 0 === s2.el && (s2.el = N.createElement(P$1(s2.h, + s2.h[0]), this.options)), s2);\n if (((_a3 = this._$AH) == null ? void + 0 : _a3._$AD) === e2) this._$AH.p(i3);\n else {\n const t3 = new M(e2, + this), s3 = t3.u(this.options);\n t3.p(i3), this.T(s3), this._$AH = t3;\n + \ }\n }\n _$AC(t2) {\n let i3 = A.get(t2.strings);\n return void + 0 === i3 && A.set(t2.strings, i3 = new N(t2)), i3;\n }\n k(t2) {\n a(this._$AH) + || (this._$AH = [], this._$AR());\n const i3 = this._$AH;\n let s2, + e2 = 0;\n for (const h2 of t2) e2 === i3.length ? i3.push(s2 = new R(this.O(l()), + this.O(l()), this, this.options)) : s2 = i3[e2], s2._$AI(h2), e2++;\n e2 + < i3.length && (this._$AR(s2 && s2._$AB.nextSibling, e2), i3.length = e2);\n + \ }\n _$AR(t2 = this._$AA.nextSibling, i3) {\n var _a3;\n for ((_a3 + = this._$AP) == null ? void 0 : _a3.call(this, false, true, i3); t2 && t2 + !== this._$AB; ) {\n const i4 = t2.nextSibling;\n t2.remove(), t2 + = i4;\n }\n }\n setConnected(t2) {\n var _a3;\n void 0 === this._$AM + && (this._$Cv = t2, (_a3 = this._$AP) == null ? void 0 : _a3.call(this, t2));\n + \ }\n}\nclass k2 {\n get tagName() {\n return this.element.tagName;\n + \ }\n get _$AU() {\n return this._$AM._$AU;\n }\n constructor(t2, i3, + s2, e2, h2) {\n this.type = 1, this._$AH = E, this._$AN = void 0, this.element + = t2, this.name = i3, this._$AM = e2, this.options = h2, s2.length > 2 || + \"\" !== s2[0] || \"\" !== s2[1] ? (this._$AH = Array(s2.length - 1).fill(new + String()), this.strings = s2) : this._$AH = E;\n }\n _$AI(t2, i3 = this, + s2, e2) {\n const h2 = this.strings;\n let o2 = false;\n if (void + 0 === h2) t2 = S(this, t2, i3, 0), o2 = !c$2(t2) || t2 !== this._$AH && t2 + !== T, o2 && (this._$AH = t2);\n else {\n const e3 = t2;\n let + n3, r3;\n for (t2 = h2[0], n3 = 0; n3 < h2.length - 1; n3++) r3 = S(this, + e3[s2 + n3], i3, n3), r3 === T && (r3 = this._$AH[n3]), o2 || (o2 = !c$2(r3) + || r3 !== this._$AH[n3]), r3 === E ? t2 = E : t2 !== E && (t2 += (r3 ?? \"\") + + h2[n3 + 1]), this._$AH[n3] = r3;\n }\n o2 && !e2 && this.j(t2);\n + \ }\n j(t2) {\n t2 === E ? this.element.removeAttribute(this.name) : this.element.setAttribute(this.name, + t2 ?? \"\");\n }\n}\nclass H extends k2 {\n constructor() {\n super(...arguments), + this.type = 3;\n }\n j(t2) {\n this.element[this.name] = t2 === E ? void + 0 : t2;\n }\n}\nclass I extends k2 {\n constructor() {\n super(...arguments), + this.type = 4;\n }\n j(t2) {\n this.element.toggleAttribute(this.name, + !!t2 && t2 !== E);\n }\n}\nclass L extends k2 {\n constructor(t2, i3, s2, + e2, h2) {\n super(t2, i3, s2, e2, h2), this.type = 5;\n }\n _$AI(t2, + i3 = this) {\n if ((t2 = S(this, t2, i3, 0) ?? E) === T) return;\n const + s2 = this._$AH, e2 = t2 === E && s2 !== E || t2.capture !== s2.capture || + t2.once !== s2.once || t2.passive !== s2.passive, h2 = t2 !== E && (s2 === + E || e2);\n e2 && this.element.removeEventListener(this.name, this, s2), + h2 && this.element.addEventListener(this.name, this, t2), this._$AH = t2;\n + \ }\n handleEvent(t2) {\n var _a3;\n \"function\" == typeof this._$AH + ? this._$AH.call(((_a3 = this.options) == null ? void 0 : _a3.host) ?? this.element, + t2) : this._$AH.handleEvent(t2);\n }\n}\nclass z {\n constructor(t2, i3, + s2) {\n this.element = t2, this.type = 6, this._$AN = void 0, this._$AM + = i3, this.options = s2;\n }\n get _$AU() {\n return this._$AM._$AU;\n + \ }\n _$AI(t2) {\n S(this, t2);\n }\n}\nconst j = t$1.litHtmlPolyfillSupport;\nj + == null ? void 0 : j(N, R), (t$1.litHtmlVersions ?? (t$1.litHtmlVersions = + [])).push(\"3.2.1\");\nconst B = (t2, i3, s2) => {\n const e2 = (s2 == null + ? void 0 : s2.renderBefore) ?? i3;\n let h2 = e2._$litPart$;\n if (void + 0 === h2) {\n const t3 = (s2 == null ? void 0 : s2.renderBefore) ?? null;\n + \ e2._$litPart$ = h2 = new R(i3.insertBefore(l(), t3), t3, void 0, s2 ?? + {});\n }\n return h2._$AI(t2), h2;\n};\n/**\n * @license\n * Copyright 2017 + Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n */\nlet r$1 = class + r extends b {\n constructor() {\n super(...arguments), this.renderOptions + = { host: this }, this._$Do = void 0;\n }\n createRenderRoot() {\n var + _a3;\n const t2 = super.createRenderRoot();\n return (_a3 = this.renderOptions).renderBefore + ?? (_a3.renderBefore = t2.firstChild), t2;\n }\n update(t2) {\n const + s2 = this.render();\n this.hasUpdated || (this.renderOptions.isConnected + = this.isConnected), super.update(t2), this._$Do = B(s2, this.renderRoot, + this.renderOptions);\n }\n connectedCallback() {\n var _a3;\n super.connectedCallback(), + (_a3 = this._$Do) == null ? void 0 : _a3.setConnected(true);\n }\n disconnectedCallback() + {\n var _a3;\n super.disconnectedCallback(), (_a3 = this._$Do) == null + ? void 0 : _a3.setConnected(false);\n }\n render() {\n return T;\n }\n};\nr$1._$litElement$ + = true, r$1[\"finalized\"] = true, (_a2 = globalThis.litElementHydrateSupport) + == null ? void 0 : _a2.call(globalThis, { LitElement: r$1 });\nconst i$3 = + globalThis.litElementPolyfillSupport;\ni$3 == null ? void 0 : i$3({ LitElement: + r$1 });\n(globalThis.litElementVersions ?? (globalThis.litElementVersions + = [])).push(\"4.1.1\");\nconst ServerPaginationMixin = {\n name: \"server-pagination-mixin\",\n + \ use: [],\n attributes: {\n limit: {\n type: Number,\n default: + void 0\n },\n offset: {\n type: Number,\n default: void 0\n + \ },\n pageCount: {\n type: Number,\n default: 1e3\n },\n + \ pageNumber: {\n type: Number,\n default: 0\n }\n },\n initialState: + {\n currentOffset: []\n },\n attached() {\n if (this.limit) {\n this.setCurrentOffset(this.resourceId, + 0);\n const parentDiv = this.initServerPaginationDiv(this.div);\n this.renderCallbacks.push({\n + \ template: this.renderServerPaginationNav(this.resourceId, parentDiv),\n + \ parent: parentDiv\n });\n }\n },\n getCurrentOffset(resourceId, + limit) {\n return this.currentOffset[`${resourceId}#p${limit}`];\n },\n + \ async setCurrentOffset(resourceId, offset) {\n const index2 = `${resourceId}#p${this.limit}`;\n + \ this.currentOffset[index2] = this.offset = offset;\n this.pageNumber + = Number(this.offset / this.limit);\n this.currentPage[resourceId] = this.pageNumber;\n + \ await this.fetchData(this.dataSrc);\n },\n async decreaseCurrentOffset(resourceId) + {\n const index2 = `${resourceId}#p${this.limit}`;\n this.currentOffset[index2] + = this.offset = this.offset - this.limit;\n this.currentPage[index2] = + this.offset / this.limit;\n this.pageNumber = this.offset / this.limit;\n + \ this.updateNavButtons(resourceId, index2, -1);\n await this.fetchData(this.dataSrc);\n + \ },\n async increaseCurrentOffset(resourceId) {\n const index2 = `${resourceId}#p${this.limit}`;\n + \ this.currentOffset[index2] = this.offset = this.offset + this.limit;\n + \ this.currentPage[index2] = this.offset / this.limit;\n this.updateNavButtons(resourceId, + index2, 1);\n await this.fetchData(this.dataSrc);\n },\n updateNavButtons(resourceId, + index2, variance) {\n this.element.querySelector(\"[data-id='prev']\").disabled + = this.currentOffset[index2] <= 0;\n this.element.querySelector(\"[data-id='current']\").innerText + = this.getCurrentServedPage(resourceId, variance);\n },\n getServerNavElement(div) + {\n if (div) {\n const insertNode = div.parentNode || div;\n return + insertNode.querySelector(`nav[data-id=\"nav\"]`);\n }\n return null;\n + \ },\n getCurrentServedPage(context, variance) {\n this.currentPage[context] + = Number(this.currentPage[context]) + variance;\n this.pageNumber = this.currentPage[context];\n + \ return this.currentPage[context];\n },\n /**\n * Find nav element + or create it if not existing\n * @param div - insert nav next to this div\n + \ */\n initServerPaginationDiv(div) {\n let nav = this.getServerNavElement(div);\n + \ if (!nav) {\n nav = document.createElement(\"nav\");\n nav.setAttribute(\"data-id\", + \"nav\");\n const insertNode = div.parentNode || div;\n insertNode.appendChild(nav);\n + \ }\n return nav;\n },\n /**\n * Create pagination template\n */\n + \ renderServerPaginationNav(resourceId, div) {\n if (this.limit) {\n const + currentOffset = this.getCurrentOffset(resourceId, this.limit);\n const + currentPageNumber = this.getCurrentServedPage(resourceId, 1);\n const + pageCount = Math.ceil(this.pageCount / this.limit);\n B(\n x`\n + \ this.decreaseCurrentOffset(resourceId)}\n + \ >←\n \n ${currentPageNumber}\n + \ \n = (pageCount - 1) * this.limit}\n @click=${() => this.increaseCurrentOffset(resourceId)}\n + \ >→\n \n \n `,\n div\n + \ );\n }\n }\n};\nconst StoreMixin = {\n name: \"store-mixin\",\n + \ use: [AttributeBinderMixin, ContextMixin, ServerPaginationMixin],\n attributes: + {\n noRender: {\n type: String,\n default: null,\n callback: + function(value) {\n if (value === null) this.fetchData(this.dataSrc);\n + \ }\n },\n dataSrc: {\n type: String,\n default: null,\n + \ callback: async function(value) {\n var _a3, _b;\n const + filteredOnServer = ((_a3 = this.element.attributes[\"filtered-on\"]) == null + ? void 0 : _a3.value) === \"server\";\n const limited = ((_b = this.element.attributes.limit) + == null ? void 0 : _b.value) !== void 0;\n if (this.noRender === null + && !filteredOnServer && !limited) {\n await this.fetchData(value);\n + \ } else if (this.noRender === null && !filteredOnServer) {\n this.resourceId + = value;\n }\n }\n },\n loaderId: {\n type: String,\n + \ default: \"\"\n },\n nestedField: {\n type: String,\n default: + null\n },\n arrayField: {\n type: String,\n default: null,\n + \ callback: function(value) {\n if (value)\n this.predicateName + = store.getExpandedPredicate(\n this.arrayField,\n this.context\n + \ );\n }\n },\n predicateName: {\n type: String,\n + \ default: null\n }\n },\n initialState: {\n resources: [],\n + \ resourceId: null,\n subscription: null\n },\n created() {\n if + (this.element.closest(\"[no-render]\")) this.noRender = \"\";\n },\n detached() + {\n if (this.subscription) PubSub.unsubscribe(this.subscription);\n },\n + \ get resource() {\n var _a3;\n const id = this.resourceId;\n const + serverPagination = formatAttributesToServerPaginationOptions(\n this.element.attributes\n + \ );\n const serverSearch = mergeServerSearchOptions(\n formatAttributesToServerSearchOptions(this.element.attributes),\n + \ (_a3 = this.getDynamicServerSearch) == null ? void 0 : _a3.call(this)\n + \ // from `filterMixin`\n );\n return id ? store.get(id, serverPagination, + serverSearch) : null;\n },\n get loader() {\n return this.loaderId ? + document.getElementById(this.loaderId) : null;\n },\n async fetchData(value) + {\n var _a3;\n this.empty();\n if (this.subscription) PubSub.unsubscribe(this.subscription);\n + \ if (!value || value === \"undefined\") return;\n this.resourceId = + value;\n if (this.nestedField) {\n const resource = await store.getData(value, + this.context);\n const nestedResource = resource ? await resource[this.nestedField] + : null;\n this.resourceId = nestedResource ? await nestedResource[\"@id\"] + : null;\n if (resource && !this.resourceId && !nestedResource) {\n for + (const property in await resource) {\n console.log(`${property}: + ${await resource[property]}`);\n }\n throw `Error: the key \"${this.nestedField}\" + does not exist on the resource at id \"${await resource[\"@id\"]}\"`;\n }\n + \ }\n this.updateNavigateSubscription();\n this.subscription = PubSub.subscribe(\n + \ this.resourceId,\n this.updateDOM.bind(this)\n );\n const + serverPagination = formatAttributesToServerPaginationOptions(\n this.element.attributes\n + \ );\n const dynamicServerSearch = (_a3 = this.getDynamicServerSearch) + == null ? void 0 : _a3.call(this);\n const serverSearch = mergeServerSearchOptions(\n + \ formatAttributesToServerSearchOptions(this.element.attributes),\n dynamicServerSearch\n + \ );\n const forceRefetch = !!dynamicServerSearch;\n await store.getData(\n + \ this.resourceId,\n this.context,\n void 0,\n void 0,\n + \ forceRefetch,\n serverPagination,\n serverSearch\n );\n + \ this.updateDOM();\n },\n toggleLoaderHidden(toggle) {\n if (this.loader) + this.loader.toggleAttribute(\"hidden\", toggle);\n },\n updateNavigateSubscription() + {\n },\n async updateDOM() {\n this.toggleLoaderHidden(false);\n this.empty();\n + \ await this.replaceAttributesData();\n await this.populate();\n setTimeout(\n + \ () => (\n // Brings the dispatchEvent at the end of the queue\n + \ this.element.dispatchEvent(\n new CustomEvent(\"populate\", + {\n detail: { resource: { \"@id\": this.dataSrc } }\n })\n + \ )\n )\n );\n this.toggleLoaderHidden(true);\n },\n empty() + {\n },\n update() {\n if (this.noRender === null) this.updateDOM();\n + \ }\n};\nconst SolidAcChecker = {\n name: \"solid-ac-checker\",\n use: [StoreMixin],\n + \ attributes: {\n permission: {\n type: String,\n default: \"\"\n + \ },\n noPermission: {\n type: String,\n default: \"\"\n }\n + \ },\n populate: trackRenderAsync(async function() {\n if (!this.resource) + return;\n let displayElement;\n const permissions = await this.resource.permissions;\n + \ if (this.permission) {\n displayElement = permissions.some((p2) => + {\n const context = normalizeContext(this.context);\n return + context.expandTerm(p2, true) === this.permission;\n });\n } else if + (this.noPermission) {\n displayElement = permissions.every((p2) => {\n + \ const context = normalizeContext(this.context);\n return context.expandTerm(p2, + true) !== this.noPermission;\n });\n } else {\n console.warn(\n + \ 'solid-ac-checker: you should define at least one of \"permission\" + or \"no-permission\" attribute.'\n );\n return;\n }\n if (displayElement) + this.element.removeAttribute(\"hidden\");\n }, \"SolidAcChecker:populate\"),\n + \ empty() {\n this.element.setAttribute(\"hidden\", \"\");\n }\n};\nSib.register(SolidAcChecker);\nconst + NextMixin = {\n name: \"next-mixin\",\n use: [],\n attributes: {\n next: + {\n type: String,\n default: \"\"\n }\n },\n // Here \"even.target\" + points to the content of the widgets of the children of solid-display\n goToNext(resource) + {\n if (this.next) {\n this.element.dispatchEvent(\n new CustomEvent(\"requestNavigation\", + {\n bubbles: true,\n detail: { route: this.next, resource + }\n })\n );\n }\n }\n};\nvar supportCustomEvent = window.CustomEvent;\nif + (!supportCustomEvent || typeof supportCustomEvent === \"object\") {\n supportCustomEvent + = function CustomEvent2(event, x2) {\n x2 = x2 || {};\n var ev = document.createEvent(\"CustomEvent\");\n + \ ev.initCustomEvent(event, !!x2.bubbles, !!x2.cancelable, x2.detail || + null);\n return ev;\n };\n supportCustomEvent.prototype = window.Event.prototype;\n}\nfunction + safeDispatchEvent(target, event) {\n var check = \"on\" + event.type.toLowerCase();\n + \ if (typeof target[check] === \"function\") {\n target[check](event);\n + \ }\n return target.dispatchEvent(event);\n}\nfunction createsStackingContext(el) + {\n while (el && el !== document.body) {\n var s2 = window.getComputedStyle(el);\n + \ var invalid = function(k3, ok) {\n return !(s2[k3] === void 0 || + s2[k3] === ok);\n };\n if (s2.opacity < 1 || invalid(\"zIndex\", \"auto\") + || invalid(\"transform\", \"none\") || invalid(\"mixBlendMode\", \"normal\") + || invalid(\"filter\", \"none\") || invalid(\"perspective\", \"none\") || + s2[\"isolation\"] === \"isolate\" || s2.position === \"fixed\" || s2.webkitOverflowScrolling + === \"touch\") {\n return true;\n }\n el = el.parentElement;\n + \ }\n return false;\n}\nfunction findNearestDialog(el) {\n while (el) {\n + \ if (el.localName === \"dialog\") {\n return (\n /** @type + {HTMLDialogElement} */\n el\n );\n }\n if (el.parentElement) + {\n el = el.parentElement;\n } else if (el.parentNode) {\n el + = el.parentNode.host;\n } else {\n el = null;\n }\n }\n return + null;\n}\nfunction safeBlur(el) {\n while (el && el.shadowRoot && el.shadowRoot.activeElement) + {\n el = el.shadowRoot.activeElement;\n }\n if (el && el.blur && el !== + document.body) {\n el.blur();\n }\n}\nfunction inNodeList(nodeList, node) + {\n for (var i3 = 0; i3 < nodeList.length; ++i3) {\n if (nodeList[i3] + === node) {\n return true;\n }\n }\n return false;\n}\nfunction + isFormMethodDialog(el) {\n if (!el || !el.hasAttribute(\"method\")) {\n return + false;\n }\n return el.getAttribute(\"method\").toLowerCase() === \"dialog\";\n}\nfunction + findFocusableElementWithin(hostElement) {\n var opts = [\"button\", \"input\", + \"keygen\", \"select\", \"textarea\"];\n var query = opts.map(function(el) + {\n return el + \":not([disabled])\";\n });\n query.push('[tabindex]:not([disabled]):not([tabindex=\"\"])');\n + \ var target = hostElement.querySelector(query.join(\", \"));\n if (!target + && \"attachShadow\" in Element.prototype) {\n var elems = hostElement.querySelectorAll(\"*\");\n + \ for (var i3 = 0; i3 < elems.length; i3++) {\n if (elems[i3].tagName + && elems[i3].shadowRoot) {\n target = findFocusableElementWithin(elems[i3].shadowRoot);\n + \ if (target) {\n break;\n }\n }\n }\n }\n + \ return target;\n}\nfunction isConnected(element) {\n return element.isConnected + || document.body.contains(element);\n}\nfunction findFormSubmitter(event) + {\n if (event.submitter) {\n return event.submitter;\n }\n var form + = event.target;\n if (!(form instanceof HTMLFormElement)) {\n return null;\n + \ }\n var submitter = dialogPolyfill.formSubmitter;\n if (!submitter) {\n + \ var target = event.target;\n var root2 = \"getRootNode\" in target + && target.getRootNode() || document;\n submitter = root2.activeElement;\n + \ }\n if (!submitter || submitter.form !== form) {\n return null;\n }\n + \ return submitter;\n}\nfunction maybeHandleSubmit(event) {\n if (event.defaultPrevented) + {\n return;\n }\n var form = (\n /** @type {!HTMLFormElement} */\n + \ event.target\n );\n var value = dialogPolyfill.imagemapUseValue;\n var + submitter = findFormSubmitter(event);\n if (value === null && submitter) + {\n value = submitter.value;\n }\n var dialog = findNearestDialog(form);\n + \ if (!dialog) {\n return;\n }\n var formmethod = submitter && submitter.getAttribute(\"formmethod\") + || form.getAttribute(\"method\");\n if (formmethod !== \"dialog\") {\n return;\n + \ }\n event.preventDefault();\n if (value != null) {\n dialog.close(value);\n + \ } else {\n dialog.close();\n }\n}\nfunction dialogPolyfillInfo(dialog) + {\n this.dialog_ = dialog;\n this.replacedStyleTop_ = false;\n this.openAsModal_ + = false;\n if (!dialog.hasAttribute(\"role\")) {\n dialog.setAttribute(\"role\", + \"dialog\");\n }\n dialog.show = this.show.bind(this);\n dialog.showModal + = this.showModal.bind(this);\n dialog.close = this.close.bind(this);\n dialog.addEventListener(\"submit\", + maybeHandleSubmit, false);\n if (!(\"returnValue\" in dialog)) {\n dialog.returnValue + = \"\";\n }\n if (\"MutationObserver\" in window) {\n var mo = new MutationObserver(this.maybeHideModal.bind(this));\n + \ mo.observe(dialog, { attributes: true, attributeFilter: [\"open\"] });\n + \ } else {\n var removed = false;\n var cb = (function() {\n removed + ? this.downgradeModal() : this.maybeHideModal();\n removed = false;\n + \ }).bind(this);\n var timeout;\n var delayModel = function(ev) {\n + \ if (ev.target !== dialog) {\n return;\n }\n var cand + = \"DOMNodeRemoved\";\n removed |= ev.type.substr(0, cand.length) === + cand;\n window.clearTimeout(timeout);\n timeout = window.setTimeout(cb, + 0);\n };\n [\"DOMAttrModified\", \"DOMNodeRemoved\", \"DOMNodeRemovedFromDocument\"].forEach(function(name) + {\n dialog.addEventListener(name, delayModel);\n });\n }\n Object.defineProperty(dialog, + \"open\", {\n set: this.setOpen.bind(this),\n get: dialog.hasAttribute.bind(dialog, + \"open\")\n });\n this.backdrop_ = document.createElement(\"div\");\n this.backdrop_.className + = \"backdrop\";\n this.backdrop_.addEventListener(\"mouseup\", this.backdropMouseEvent_.bind(this));\n + \ this.backdrop_.addEventListener(\"mousedown\", this.backdropMouseEvent_.bind(this));\n + \ this.backdrop_.addEventListener(\"click\", this.backdropMouseEvent_.bind(this));\n}\ndialogPolyfillInfo.prototype + = /** @type {HTMLDialogElement.prototype} */\n{\n get dialog() {\n return + this.dialog_;\n },\n /**\n * Maybe remove this dialog from the modal top + layer. This is called when\n * a modal dialog may no longer be tenable, + e.g., when the dialog is no\n * longer open or is no longer part of the + DOM.\n */\n maybeHideModal: function() {\n if (this.dialog_.hasAttribute(\"open\") + && isConnected(this.dialog_)) {\n return;\n }\n this.downgradeModal();\n + \ },\n /**\n * Remove this dialog from the modal top layer, leaving it + as a non-modal.\n */\n downgradeModal: function() {\n if (!this.openAsModal_) + {\n return;\n }\n this.openAsModal_ = false;\n this.dialog_.style.zIndex + = \"\";\n if (this.replacedStyleTop_) {\n this.dialog_.style.top = + \"\";\n this.replacedStyleTop_ = false;\n }\n this.backdrop_.parentNode + && this.backdrop_.parentNode.removeChild(this.backdrop_);\n dialogPolyfill.dm.removeDialog(this);\n + \ },\n /**\n * @param {boolean} value whether to open or close this dialog\n + \ */\n setOpen: function(value) {\n if (value) {\n this.dialog_.hasAttribute(\"open\") + || this.dialog_.setAttribute(\"open\", \"\");\n } else {\n this.dialog_.removeAttribute(\"open\");\n + \ this.maybeHideModal();\n }\n },\n /**\n * Handles mouse events + ('mouseup', 'mousedown', 'click') on the fake .backdrop element, redirecting + them as if\n * they were on the dialog itself.\n *\n * @param {!Event} + e to redirect\n */\n backdropMouseEvent_: function(e2) {\n if (!this.dialog_.hasAttribute(\"tabindex\")) + {\n var fake = document.createElement(\"div\");\n this.dialog_.insertBefore(fake, + this.dialog_.firstChild);\n fake.tabIndex = -1;\n fake.focus();\n + \ this.dialog_.removeChild(fake);\n } else {\n this.dialog_.focus();\n + \ }\n var redirectedEvent = document.createEvent(\"MouseEvents\");\n + \ redirectedEvent.initMouseEvent(\n e2.type,\n e2.bubbles,\n e2.cancelable,\n + \ window,\n e2.detail,\n e2.screenX,\n e2.screenY,\n e2.clientX,\n + \ e2.clientY,\n e2.ctrlKey,\n e2.altKey,\n e2.shiftKey,\n + \ e2.metaKey,\n e2.button,\n e2.relatedTarget\n );\n this.dialog_.dispatchEvent(redirectedEvent);\n + \ e2.stopPropagation();\n },\n /**\n * Focuses on the first focusable + element within the dialog. This will always blur the current\n * focus, + even if nothing within the dialog is found.\n */\n focus_: function() {\n + \ var target = this.dialog_.querySelector(\"[autofocus]:not([disabled])\");\n + \ if (!target && this.dialog_.tabIndex >= 0) {\n target = this.dialog_;\n + \ }\n if (!target) {\n target = findFocusableElementWithin(this.dialog_);\n + \ }\n safeBlur(document.activeElement);\n target && target.focus();\n + \ },\n /**\n * Sets the zIndex for the backdrop and dialog.\n *\n * + @param {number} dialogZ\n * @param {number} backdropZ\n */\n updateZIndex: + function(dialogZ, backdropZ) {\n if (dialogZ < backdropZ) {\n throw + new Error(\"dialogZ should never be < backdropZ\");\n }\n this.dialog_.style.zIndex + = dialogZ;\n this.backdrop_.style.zIndex = backdropZ;\n },\n /**\n * + Shows the dialog. If the dialog is already open, this does nothing.\n */\n + \ show: function() {\n if (!this.dialog_.open) {\n this.setOpen(true);\n + \ this.focus_();\n }\n },\n /**\n * Show this dialog modally.\n + \ */\n showModal: function() {\n if (this.dialog_.hasAttribute(\"open\")) + {\n throw new Error(\"Failed to execute 'showModal' on dialog: The element + is already open, and therefore cannot be opened modally.\");\n }\n if + (!isConnected(this.dialog_)) {\n throw new Error(\"Failed to execute + 'showModal' on dialog: The element is not in a Document.\");\n }\n if + (!dialogPolyfill.dm.pushDialog(this)) {\n throw new Error(\"Failed to + execute 'showModal' on dialog: There are too many open modal dialogs.\");\n + \ }\n if (createsStackingContext(this.dialog_.parentElement)) {\n console.warn(\"A + dialog is being shown inside a stacking context. This may cause it to be unusable. + For more information, see this link: https://github.com/GoogleChrome/dialog-polyfill/#stacking-context\");\n + \ }\n this.setOpen(true);\n this.openAsModal_ = true;\n if (dialogPolyfill.needsCentering(this.dialog_)) + {\n dialogPolyfill.reposition(this.dialog_);\n this.replacedStyleTop_ + = true;\n } else {\n this.replacedStyleTop_ = false;\n }\n this.dialog_.parentNode.insertBefore(this.backdrop_, + this.dialog_.nextSibling);\n this.focus_();\n },\n /**\n * Closes this + HTMLDialogElement. This is optional vs clearing the open\n * attribute, + however this fires a 'close' event.\n *\n * @param {string=} opt_returnValue + to use as the returnValue\n */\n close: function(opt_returnValue) {\n if + (!this.dialog_.hasAttribute(\"open\")) {\n throw new Error(\"Failed to + execute 'close' on dialog: The element does not have an 'open' attribute, + and therefore cannot be closed.\");\n }\n this.setOpen(false);\n if + (opt_returnValue !== void 0) {\n this.dialog_.returnValue = opt_returnValue;\n + \ }\n var closeEvent = new supportCustomEvent(\"close\", {\n bubbles: + false,\n cancelable: false\n });\n safeDispatchEvent(this.dialog_, + closeEvent);\n }\n};\nvar dialogPolyfill = {};\ndialogPolyfill.reposition + = function(element) {\n var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;\n + \ var topValue = scrollTop + (window.innerHeight - element.offsetHeight) / + 2;\n element.style.top = Math.max(scrollTop, topValue) + \"px\";\n};\ndialogPolyfill.isInlinePositionSetByStylesheet + = function(element) {\n for (var i3 = 0; i3 < document.styleSheets.length; + ++i3) {\n var styleSheet = document.styleSheets[i3];\n var cssRules + = null;\n try {\n cssRules = styleSheet.cssRules;\n } catch (e2) + {\n }\n if (!cssRules) {\n continue;\n }\n for (var j2 = + 0; j2 < cssRules.length; ++j2) {\n var rule = cssRules[j2];\n var + selectedNodes = null;\n try {\n selectedNodes = document.querySelectorAll(rule.selectorText);\n + \ } catch (e2) {\n }\n if (!selectedNodes || !inNodeList(selectedNodes, + element)) {\n continue;\n }\n var cssTop = rule.style.getPropertyValue(\"top\");\n + \ var cssBottom = rule.style.getPropertyValue(\"bottom\");\n if (cssTop + && cssTop !== \"auto\" || cssBottom && cssBottom !== \"auto\") {\n return + true;\n }\n }\n }\n return false;\n};\ndialogPolyfill.needsCentering + = function(dialog) {\n var computedStyle = window.getComputedStyle(dialog);\n + \ if (computedStyle.position !== \"absolute\") {\n return false;\n }\n + \ if (dialog.style.top !== \"auto\" && dialog.style.top !== \"\" || dialog.style.bottom + !== \"auto\" && dialog.style.bottom !== \"\") {\n return false;\n }\n + \ return !dialogPolyfill.isInlinePositionSetByStylesheet(dialog);\n};\ndialogPolyfill.forceRegisterDialog + = function(element) {\n if (window.HTMLDialogElement || element.showModal) + {\n console.warn(\"This browser already supports , the polyfill + may not work correctly\", element);\n }\n if (element.localName !== \"dialog\") + {\n throw new Error(\"Failed to register dialog: The element is not a dialog.\");\n + \ }\n new dialogPolyfillInfo(\n /** @type {!HTMLDialogElement} */\n element\n + \ );\n};\ndialogPolyfill.registerDialog = function(element) {\n if (!element.showModal) + {\n dialogPolyfill.forceRegisterDialog(element);\n }\n};\ndialogPolyfill.DialogManager + = function() {\n this.pendingDialogStack = [];\n var checkDOM = this.checkDOM_.bind(this);\n + \ this.overlay = document.createElement(\"div\");\n this.overlay.className + = \"_dialog_overlay\";\n this.overlay.addEventListener(\"click\", (function(e2) + {\n this.forwardTab_ = void 0;\n e2.stopPropagation();\n checkDOM([]);\n + \ }).bind(this));\n this.handleKey_ = this.handleKey_.bind(this);\n this.handleFocus_ + = this.handleFocus_.bind(this);\n this.zIndexLow_ = 1e5;\n this.zIndexHigh_ + = 1e5 + 150;\n this.forwardTab_ = void 0;\n if (\"MutationObserver\" in + window) {\n this.mo_ = new MutationObserver(function(records) {\n var + removed = [];\n records.forEach(function(rec) {\n for (var i3 + = 0, c2; c2 = rec.removedNodes[i3]; ++i3) {\n if (!(c2 instanceof + Element)) {\n continue;\n } else if (c2.localName === + \"dialog\") {\n removed.push(c2);\n }\n removed + = removed.concat(c2.querySelectorAll(\"dialog\"));\n }\n });\n + \ removed.length && checkDOM(removed);\n });\n }\n};\ndialogPolyfill.DialogManager.prototype.blockDocument + = function() {\n document.documentElement.addEventListener(\"focus\", this.handleFocus_, + true);\n document.addEventListener(\"keydown\", this.handleKey_);\n this.mo_ + && this.mo_.observe(document, { childList: true, subtree: true });\n};\ndialogPolyfill.DialogManager.prototype.unblockDocument + = function() {\n document.documentElement.removeEventListener(\"focus\", + this.handleFocus_, true);\n document.removeEventListener(\"keydown\", this.handleKey_);\n + \ this.mo_ && this.mo_.disconnect();\n};\ndialogPolyfill.DialogManager.prototype.updateStacking + = function() {\n var zIndex = this.zIndexHigh_;\n for (var i3 = 0, dpi; + dpi = this.pendingDialogStack[i3]; ++i3) {\n dpi.updateZIndex(--zIndex, + --zIndex);\n if (i3 === 0) {\n this.overlay.style.zIndex = --zIndex;\n + \ }\n }\n var last = this.pendingDialogStack[0];\n if (last) {\n var + p2 = last.dialog.parentNode || document.body;\n p2.appendChild(this.overlay);\n + \ } else if (this.overlay.parentNode) {\n this.overlay.parentNode.removeChild(this.overlay);\n + \ }\n};\ndialogPolyfill.DialogManager.prototype.containedByTopDialog_ = function(candidate) + {\n while (candidate = findNearestDialog(candidate)) {\n for (var i3 = + 0, dpi; dpi = this.pendingDialogStack[i3]; ++i3) {\n if (dpi.dialog === + candidate) {\n return i3 === 0;\n }\n }\n candidate = candidate.parentElement;\n + \ }\n return false;\n};\ndialogPolyfill.DialogManager.prototype.handleFocus_ + = function(event) {\n var target = event.composedPath ? event.composedPath()[0] + : event.target;\n if (this.containedByTopDialog_(target)) {\n return;\n + \ }\n if (document.activeElement === document.documentElement) {\n return;\n + \ }\n event.preventDefault();\n event.stopPropagation();\n safeBlur(\n + \ /** @type {Element} */\n target\n );\n if (this.forwardTab_ === void + 0) {\n return;\n }\n var dpi = this.pendingDialogStack[0];\n var dialog + = dpi.dialog;\n var position = dialog.compareDocumentPosition(target);\n + \ if (position & Node.DOCUMENT_POSITION_PRECEDING) {\n if (this.forwardTab_) + {\n dpi.focus_();\n } else if (target !== document.documentElement) + {\n document.documentElement.focus();\n }\n }\n return false;\n};\ndialogPolyfill.DialogManager.prototype.handleKey_ + = function(event) {\n this.forwardTab_ = void 0;\n if (event.keyCode === + 27) {\n event.preventDefault();\n event.stopPropagation();\n var + cancelEvent = new supportCustomEvent(\"cancel\", {\n bubbles: false,\n + \ cancelable: true\n });\n var dpi = this.pendingDialogStack[0];\n + \ if (dpi && safeDispatchEvent(dpi.dialog, cancelEvent)) {\n dpi.dialog.close();\n + \ }\n } else if (event.keyCode === 9) {\n this.forwardTab_ = !event.shiftKey;\n + \ }\n};\ndialogPolyfill.DialogManager.prototype.checkDOM_ = function(removed) + {\n var clone = this.pendingDialogStack.slice();\n clone.forEach(function(dpi) + {\n if (removed.indexOf(dpi.dialog) !== -1) {\n dpi.downgradeModal();\n + \ } else {\n dpi.maybeHideModal();\n }\n });\n};\ndialogPolyfill.DialogManager.prototype.pushDialog + = function(dpi) {\n var allowed = (this.zIndexHigh_ - this.zIndexLow_) / + 2 - 1;\n if (this.pendingDialogStack.length >= allowed) {\n return false;\n + \ }\n if (this.pendingDialogStack.unshift(dpi) === 1) {\n this.blockDocument();\n + \ }\n this.updateStacking();\n return true;\n};\ndialogPolyfill.DialogManager.prototype.removeDialog + = function(dpi) {\n var index2 = this.pendingDialogStack.indexOf(dpi);\n + \ if (index2 === -1) {\n return;\n }\n this.pendingDialogStack.splice(index2, + 1);\n if (this.pendingDialogStack.length === 0) {\n this.unblockDocument();\n + \ }\n this.updateStacking();\n};\ndialogPolyfill.dm = new dialogPolyfill.DialogManager();\ndialogPolyfill.formSubmitter + = null;\ndialogPolyfill.imagemapUseValue = null;\nif (window.HTMLDialogElement + === void 0) {\n var testForm = document.createElement(\"form\");\n testForm.setAttribute(\"method\", + \"dialog\");\n if (testForm.method !== \"dialog\") {\n var methodDescriptor + = Object.getOwnPropertyDescriptor(HTMLFormElement.prototype, \"method\");\n + \ if (methodDescriptor) {\n var realGet = methodDescriptor.get;\n methodDescriptor.get + = function() {\n if (isFormMethodDialog(this)) {\n return + \"dialog\";\n }\n return realGet.call(this);\n };\n var + realSet = methodDescriptor.set;\n methodDescriptor.set = function(v2) + {\n if (typeof v2 === \"string\" && v2.toLowerCase() === \"dialog\") + {\n return this.setAttribute(\"method\", v2);\n }\n return + realSet.call(this, v2);\n };\n Object.defineProperty(HTMLFormElement.prototype, + \"method\", methodDescriptor);\n }\n }\n document.addEventListener(\"click\", + function(ev) {\n dialogPolyfill.formSubmitter = null;\n dialogPolyfill.imagemapUseValue + = null;\n if (ev.defaultPrevented) {\n return;\n }\n var target + = (\n /** @type {Element} */\n ev.target\n );\n if (\"composedPath\" + in ev) {\n var path = ev.composedPath();\n target = path.shift() + || target;\n }\n if (!target || !isFormMethodDialog(target.form)) {\n + \ return;\n }\n var valid = target.type === \"submit\" && [\"button\", + \"input\"].indexOf(target.localName) > -1;\n if (!valid) {\n if (!(target.localName + === \"input\" && target.type === \"image\")) {\n return;\n }\n + \ dialogPolyfill.imagemapUseValue = ev.offsetX + \",\" + ev.offsetY;\n + \ }\n var dialog = findNearestDialog(target);\n if (!dialog) {\n return;\n + \ }\n dialogPolyfill.formSubmitter = target;\n }, false);\n document.addEventListener(\"submit\", + function(ev) {\n var form = ev.target;\n var dialog = findNearestDialog(form);\n + \ if (dialog) {\n return;\n }\n var submitter = findFormSubmitter(ev);\n + \ var formmethod = submitter && submitter.getAttribute(\"formmethod\") || + form.getAttribute(\"method\");\n if (formmethod === \"dialog\") {\n ev.preventDefault();\n + \ }\n });\n var nativeFormSubmit = HTMLFormElement.prototype.submit;\n + \ var replacementFormSubmit = function() {\n if (!isFormMethodDialog(this)) + {\n return nativeFormSubmit.call(this);\n }\n var dialog = findNearestDialog(this);\n + \ dialog && dialog.close();\n };\n HTMLFormElement.prototype.submit = + replacementFormSubmit;\n}\n/**\n * @license\n * Copyright 2018 Google LLC\n + * SPDX-License-Identifier: BSD-3-Clause\n */\nconst o$2 = (o2) => o2 ?? E;\n/**\n + * @license\n * Copyright 2017 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n + */\nconst t = { ATTRIBUTE: 1, CHILD: 2, PROPERTY: 3 }, e$1 = (t2) => (...e2) + => ({ _$litDirective$: t2, values: e2 });\nlet i$2 = class i {\n constructor(t2) + {\n }\n get _$AU() {\n return this._$AM._$AU;\n }\n _$AT(t2, e2, i3) + {\n this._$Ct = t2, this._$AM = e2, this._$Ci = i3;\n }\n _$AS(t2, e2) + {\n return this.update(t2, e2);\n }\n update(t2, e2) {\n return this.render(...e2);\n + \ }\n};\nconst prevCache = /* @__PURE__ */ new WeakMap();\nclass SpreadDirective + extends i$2 {\n constructor(partInfo) {\n super(partInfo);\n __publicField(this, + \"spreadData\");\n this.spreadData = null;\n }\n render(spreadData) {\n + \ this.spreadData = spreadData;\n return T;\n }\n update(part, [spreadData]) + {\n const prevData = prevCache.get(part);\n if (prevData === spreadData) + {\n return T;\n }\n let element;\n prevCache.set(part, spreadData);\n + \ if (part.type === t.ATTRIBUTE || part.type === t.PROPERTY) {\n element + = part.element;\n } else {\n console.warn(\n \"Unsupported + part type or missing element, skipping update.\"\n );\n return T;\n + \ }\n if (spreadData) {\n for (const key in spreadData) {\n const + value = spreadData[key];\n if (value === T) continue;\n const + prefix = key[0];\n if (prefix === \"@\") {\n const prevHandler + = prevData == null ? void 0 : prevData[key];\n if (!prevHandler || + prevHandler !== value) {\n const name = key.slice(1);\n if + (prevHandler) element.removeEventListener(name, prevHandler);\n element.addEventListener(name, + value);\n }\n continue;\n }\n if (prefix === + \".\") {\n if (!prevData || prevData[key] !== value) {\n element[key.slice(1)] + = value;\n }\n continue;\n }\n if (prefix + === \"?\") {\n const attrName = key.slice(1);\n if (!prevData + || prevData[key] !== value) {\n if (value) {\n element.setAttribute(attrName, + \"\");\n } else {\n element.removeAttribute(attrName);\n + \ }\n }\n continue;\n }\n if (!prevData + || prevData[key] !== value) {\n if (value != null) {\n element.setAttribute(key, + String(value));\n } else {\n element.removeAttribute(key);\n + \ }\n }\n }\n }\n if (prevData) {\n for (const + key in prevData) {\n if (!spreadData || !(key in spreadData)) {\n const + prefix = key[0];\n if (prefix === \"@\") {\n element.removeEventListener(key.slice(1), + prevData[key]);\n continue;\n }\n if (prefix + === \".\") {\n element[key.slice(1)] = void 0;\n continue;\n + \ }\n if (prefix === \"?\") {\n element.removeAttribute(key.slice(1));\n + \ continue;\n }\n element.removeAttribute(key);\n + \ }\n }\n }\n return T;\n }\n}\nconst spread = e$1(SpreadDirective);\nconst + templateStringsCache = /* @__PURE__ */ new WeakMap();\nfunction filterOutNeedlessValues(arr, + needlessValues) {\n return arr.filter((_2, i3) => !needlessValues.some((nv) + => nv.index === i3));\n}\nfunction preHTML(strings, ...values) {\n let cachedStrings + = templateStringsCache.get(strings);\n if (cachedStrings) {\n for (const + cached of cachedStrings) {\n const { needlessValues: needlessValues2 + } = cached;\n const isSame = needlessValues2.every((nv) => values[nv.index] + === nv.value);\n if (isSame) {\n return x(\n cached.strings,\n + \ ...filterOutNeedlessValues(values, needlessValues2)\n );\n + \ }\n }\n }\n const needlessValues = [];\n const newStrings = [];\n + \ for (let i3 = 0; i3 < strings.length; i3++) {\n let str = strings[i3];\n + \ while (str.endsWith(\"<\") || str.length >= 2 && str.endsWith(\" import(\"./en-D7xQ8_VL.js\"),\n + \ fr: () => import(\"./fr-ClQZ5-J-.js\")\n };\n if (!translationsModules[langCode]) + {\n console.warn(\n `${langCode}.json translation file may not + exist, English is setted by default`\n );\n langCode = \"en\";\n + \ }\n const module2 = await translationsModules[langCode]();\n return + module2.default;\n },\n /**\n * Loads the right translation file and reload + the component\n */\n getLang() {\n const languageStorage = store._getLanguage();\n + \ if (languageStorage) {\n if (window.fetchTranslationPromise === void + 0) {\n window.fetchTranslationPromise = this.getTranslationModule(languageStorage);\n + \ }\n window.fetchTranslationPromise.then((res) => {\n if + (res) {\n this.translationData = res;\n this.update();\n + \ }\n });\n }\n },\n /**\n * Returns translation for a given + key\n * @param tradKey - string: key\n * @returns - string: translation\n + \ */\n t(tradKey) {\n return this.translationData[tradKey] || \"\";\n + \ }\n};\nconst ValidationMixin = {\n name: \"validation-mixin\",\n use: + [TranslationMixin],\n attributes: {\n confirmationMessage: {\n type: + String,\n default: null\n },\n confirmationType: {\n type: + String,\n default: null\n },\n confirmationSubmitText: {\n type: + String,\n default: null\n },\n confirmationCancelText: {\n type: + String,\n default: null\n },\n confirmationSubmitClass: {\n type: + String,\n default: void 0\n },\n confirmationCancelClass: {\n type: + String,\n default: void 0\n },\n confirmationWidget: {\n type: + String,\n default: void 0\n }\n },\n created() {\n this.dialogID + = uniqID();\n },\n showModal() {\n const dialog = document.getElementById(this.dialogID);\n + \ dialogPolyfill.registerDialog(dialog);\n return dialog.showModal();\n + \ },\n performAction() {\n if (this.element.hasAttribute(\"confirmation-message\") + && !this.confirmationType)\n console.warn(\"confirmation-type attribute + is missing.\");\n if (!this.confirmationType || this.confirmationType === + \"confirm\" && confirm(this.confirmationMessage || this.t(\"validation.message\")))\n + \ this.validateModal();\n if (this.confirmationType === \"dialog\") + {\n this.showModal();\n }\n },\n getModalDialog() {\n if (this.confirmationType + !== \"dialog\") return \"\";\n const quitDialog = () => {\n const + dialog = document.getElementById(this.dialogID);\n if (dialog == null) + return;\n dialog.close();\n };\n const confirmChoice = () => {\n + \ this.validateModal();\n quitDialog();\n };\n return x`\n + \ \n ${this.confirmationWidget ? + preHTML`<${this.confirmationWidget} value=${this.resourceId}>` + : x`

${this.confirmationMessage || this.t(\"validation.message\")}

`}\n + \
\n \n ${this.confirmationSubmitText || this.t(\"validation.submit-text\")}\n + \ \n \n ${this.confirmationCancelText + || this.t(\"validation.cancel-text\")}\n \n
\n + \
\n `;\n }\n};\nconst SolidDelete = {\n name: \"solid-delete\",\n + \ use: [NextMixin, ValidationMixin, AttributeBinderMixin, ContextMixin],\n + \ attributes: {\n dataSrc: {\n type: String,\n default: null,\n + \ callback: function() {\n this.resourceId = this.dataSrc;\n }\n + \ },\n dataLabel: {\n type: String,\n default: null,\n callback: + function(newValue, oldValue) {\n if (newValue !== oldValue) this.planRender();\n + \ }\n }\n },\n initialState: {\n renderPlanned: false\n },\n + \ created() {\n this.planRender();\n },\n planRender() {\n if (!this.renderPlanned) + {\n this.renderPlanned = true;\n setTimeout(() => {\n this.render();\n + \ this.renderPlanned = false;\n });\n }\n },\n delete(e2) + {\n e2.stopPropagation();\n if (!this.dataSrc) return;\n this.performAction();\n + \ },\n deletion() {\n return store.delete(this.dataSrc, this.context).then((response) + => {\n if (!response.ok) return;\n this.goToNext(null);\n const + eventData = {\n detail: { resource: { \"@id\": this.dataSrc } },\n + \ bubbles: true\n };\n this.element.dispatchEvent(new CustomEvent(\"save\", + eventData));\n this.element.dispatchEvent(new CustomEvent(\"resourceDeleted\", + eventData));\n });\n },\n validateModal() {\n return this.deletion();\n + \ },\n update() {\n this.render();\n },\n render: trackRenderAsync(async + function() {\n await this.replaceAttributesData(false);\n const button + = x`${this.getModalDialog()}`;\n + \ B(button, this.element);\n }, \"SolidDelete:render\")\n};\nSib.register(SolidDelete);\n/**\n + * @license\n * Copyright 2017 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n + */\nclass e extends i$2 {\n constructor(i3) {\n if (super(i3), this.it + = E, i3.type !== t.CHILD) throw Error(this.constructor.directiveName + \"() + can only be used in child bindings\");\n }\n render(r3) {\n if (r3 === + E || null == r3) return this._t = void 0, this.it = r3;\n if (r3 === T) + return r3;\n if (\"string\" != typeof r3) throw Error(this.constructor.directiveName + + \"() called with a non-string value\");\n if (r3 === this.it) return + this._t;\n this.it = r3;\n const s2 = [r3];\n return s2.raw = s2, + this._t = { _$litType$: this.constructor.resultType, strings: s2, values: + [] };\n }\n}\ne.directiveName = \"unsafeHTML\", e.resultType = 1;\nconst + o$1 = e$1(e);\nconst CounterMixin = {\n name: \"counter-mixin\",\n use: + [],\n attributes: {\n counterTemplate: {\n type: String,\n default: + null\n }\n },\n initialState: {\n counter: null,\n parentCounterDiv: + null\n },\n attached() {\n this.listPostProcessors.attach(\n this.countResources.bind(this),\n + \ \"CounterMixin:countResources\"\n );\n },\n async countResources(resources, + listPostProcessors, div, context) {\n if (this.counterTemplate) {\n this.initParentCounterDiv(div);\n + \ this.renderCallbacks.push({\n // add counter template to render + callback\n template: await this.renderCounter(resources.length),\n + \ parent: this.parentCounterDiv\n });\n }\n const nextProcessor + = listPostProcessors.shift();\n if (nextProcessor)\n await nextProcessor(resources, + listPostProcessors, div, context);\n },\n /**\n * Create the parent div + of the counter in the component.\n * @param div: parent div where to insert + the counter div\n */\n initParentCounterDiv(div) {\n if (this.parentCounterDiv) + return;\n this.parentCounterDiv = document.createElement(\"div\");\n this.element.insertBefore(this.parentCounterDiv, + div);\n },\n async renderCounter(resourceNumber) {\n let htmlCounter;\n + \ try {\n htmlCounter = await evalTemplateString(this.counterTemplate, + {\n counter: resourceNumber\n });\n } catch (e2) {\n console.error(new + Error(\"error in counter-template\"), e2);\n throw e2;\n }\n return + x`${o$1(htmlCounter)}`;\n }\n};\nconst FederationMixin = {\n name: \"federation-mixin\",\n + \ use: [],\n initialState: {\n containerFetched: null\n },\n attached() + {\n this.listPostProcessors.attach(\n this.fetchSources.bind(this),\n + \ \"FederationMixin:fetchSources\"\n );\n },\n async fetchSources(resources, + listPostProcessors, div, context) {\n this.containerFetched = [];\n let + newResources = await this.getResources(resources);\n newResources = [...new + Set(newResources)];\n this.resources = [...newResources];\n const nextProcessor + = listPostProcessors.shift();\n if (nextProcessor)\n await nextProcessor(newResources, + listPostProcessors, div, context);\n },\n async getResources(resources) + {\n if (!resources && this.resources) resources = this.resources;\n if + (!resources) return [];\n const newResources = [];\n const getChildResources + = async (res) => {\n var _a3;\n if (!res) return;\n if ((_a3 + = res.isContainer) == null ? void 0 : _a3.call(res)) {\n const containerId + = res[\"@id\"];\n if (!this.containerFetched.includes(containerId)) + {\n this.containerFetched.push(containerId);\n const resourcesFetched + = await this.fetchSource(containerId);\n if (resourcesFetched)\n + \ newResources.push(...await this.getResources(resourcesFetched));\n + \ }\n } else {\n newResources.push(res);\n }\n };\n + \ if (!Array.isArray(resources)) resources = [resources];\n await Promise.all(resources.map((res) + => getChildResources(res)));\n return newResources;\n },\n async fetchSource(containerId) + {\n const cachedContainer = store.get(containerId);\n if (!cachedContainer + || cachedContainer.getContainerList() === null) {\n store.clearCache(containerId);\n + \ }\n const container = await store.getData(containerId, this.context);\n + \ return container == null ? void 0 : container[\"listPredicate\"];\n }\n};\nconst + isSet$1 = (field, fields) => {\n if (!fields) return false;\n const foundSets + = fields.match(getSetRegexp(field));\n return foundSets ? foundSets.length + > 0 : false;\n};\nconst getSet = (field, fields) => {\n const setString = + fields.match(getSetRegexp(field));\n if (!setString) return [];\n const + firstSetBracket = fields.indexOf(setString[0]) + setString[0].length - 1;\n + \ const lastSetBracket = findClosingBracketMatchIndex(fields, firstSetBracket);\n + \ const set2 = fields.substring(firstSetBracket + 1, lastSetBracket);\n return + parseFieldsString(set2);\n};\nconst getSetRegexp = (field) => {\n return + new RegExp(`(^|\\\\,|\\\\(|\\\\s)\\\\s*${field}\\\\s*\\\\(`, \"g\");\n};\nconst + isSearchField = (field, searchForm) => {\n return searchForm.hasAttribute(`search-${field}`);\n};\nconst + getSearchField = (field, searchForm) => {\n return parseFieldsString(searchForm.getAttribute(`search-${field}`) + || \"\");\n};\nconst orThrow = (throwOn, ret) => {\n if (throwOn === true + && ret) throw true;\n if (throwOn === false && !ret) throw false;\n return + ret;\n};\nconst matchValue = async (val, query, throwOn) => {\n var _a3;\n + \ const subject = await val;\n if (subject == null && query.value === \"\") + return orThrow(throwOn, true);\n if (subject == null) return orThrow(throwOn, + false);\n if (query.list) {\n if (query.value.length === 0) return orThrow(throwOn, + true);\n for (const v2 of query.value) {\n if (await matchValue(subject, + { type: query.type, value: v2, list: false })) {\n return orThrow(throwOn, + true);\n }\n }\n return orThrow(throwOn, false);\n }\n if ((_a3 + = subject.isContainer) == null ? void 0 : _a3.call(subject)) {\n let ret + = Promise.resolve(query.value === \"\");\n for (const value of subject[\"listPredicate\"]) + {\n ret = await ret || await matchValue(value, query);\n if (ret) + return orThrow(throwOn, true);\n }\n return orThrow(throwOn, await ret);\n + \ }\n if (Array.isArray(subject)) {\n let ret = Promise.resolve(query.value + === \"\");\n for (const value of subject) {\n ret = await ret || await + matchValue(value, query);\n if (ret) {\n return true;\n }\n + \ }\n return orThrow(throwOn, await ret);\n }\n if (query.type === + \"string\" && typeof subject !== \"string\")\n return orThrow(throwOn, + compare.string(subject[\"@id\"], query.value));\n return orThrow(throwOn, + compare[query.type](subject, query.value));\n};\nconst cacheFieldsProps = + (cacheKey, filter, fields, searchForm) => {\n if (!window.cachePropsSearchFilter[cacheKey]) + {\n window.cachePropsSearchFilter[cacheKey] = {\n setFields: isSet$1(filter, + fields) ? getSet(filter, fields) : null,\n setSearchFields: isSearchField(filter, + searchForm) ? getSearchField(filter, searchForm) : null\n };\n }\n};\nconst + traversePath = async (resource, path, targetedType) => {\n var _a3;\n let + result = [];\n let currentRes;\n let remainingPath = path;\n if (path.length + === 0) return [];\n try {\n currentRes = await resource[path[0]];\n const + lastPath1El = path.shift();\n if (lastPath1El) remainingPath = path;\n + \ if (currentRes && remainingPath.length > 1) {\n result = await traversePath(currentRes, + remainingPath, targetedType);\n } else if (currentRes && Array.isArray(currentRes)) + {\n for (const res of currentRes) {\n if (remainingPath.length + > 1) {\n result = await traversePath(res, remainingPath, targetedType);\n + \ } else {\n let targetsRes = await res[remainingPath[0]];\n + \ if (!targetsRes) return [];\n if ((_a3 = targetsRes.isContainer) + == null ? void 0 : _a3.call(targetsRes)) {\n targetsRes = targetsRes[\"listPredicate\"];\n + \ }\n if (!Array.isArray(targetsRes)) targetsRes = [targetsRes];\n + \ for (const targetRes of targetsRes) {\n if (!result.some((item) + => item[\"@id\"] === targetRes[\"@id\"])) {\n result.push({ \"@id\": + targetRes[\"@id\"] });\n }\n }\n }\n }\n }\n + \ } catch (e2) {\n console.error(e2);\n return [];\n }\n return result;\n};\nconst + matchFilter = async (resource, filter, query, fieldsAttr, searchForm, filterId, + throwOn) => {\n let fields = null;\n const cacheKey = `${filter}_${filterId}`;\n + \ cacheFieldsProps(cacheKey, filter, fieldsAttr, searchForm);\n if (window.cachePropsSearchFilter[cacheKey].setFields + !== null) {\n fields = window.cachePropsSearchFilter[cacheKey].setFields;\n + \ } else if (window.cachePropsSearchFilter[cacheKey].setSearchFields !== null) + {\n fields = window.cachePropsSearchFilter[cacheKey].setSearchFields;\n + \ } else {\n if (!await resource[filter] && doesResourceContainList(filter)) + {\n const path1 = filter.split(\".\");\n const targetedType = path1[path1.length + - 1];\n let targetIds = [];\n targetIds = await traversePath(resource, + path1, targetedType);\n if (!Array.isArray(targetIds) || targetIds.length + === 0 && query.value !== \"\") {\n throw !throwOn;\n }\n return + await matchValue(targetIds, query, throwOn);\n }\n return matchValue(resource[filter], + query, throwOn);\n }\n try {\n await Promise.all(\n (fields || []).map(\n + \ (field) => matchFilter(\n resource,\n field,\n query,\n + \ fieldsAttr,\n searchForm,\n filterId,\n true\n + \ // stop searching when 1 filter is true (= OR)\n )\n )\n + \ );\n } catch {\n return true;\n }\n throw false;\n};\nconst matchFilters + = async (resource, filters, filterNames, fields, searchForm, filterId) => + {\n try {\n await Promise.all(\n filterNames.map(async (filter) => + {\n const match3 = await matchFilter(\n resource,\n filter,\n + \ filters[filter],\n fields,\n searchForm,\n filterId,\n + \ false\n // stop searching when 1 filter is false (= AND)\n + \ );\n return match3;\n })\n );\n } catch (_e) {\n return + false;\n }\n return true;\n};\nconst searchInResources = (resources, filters, + fields, searchForm) => {\n const filterNames = Object.keys(filters);\n const + filterId = uniqID();\n window.cachePropsSearchFilter = {};\n return Promise.all(\n + \ resources.map(async (resource) => {\n const match3 = await matchFilters(\n + \ resource,\n filters,\n filterNames,\n fields,\n + \ searchForm,\n filterId\n );\n return match3;\n })\n + \ );\n};\nconst FilterMixin = {\n name: \"filter-mixin\",\n use: [],\n initialState: + {\n searchCount: null\n },\n attributes: {\n searchFields: {\n type: + String,\n default: null\n },\n filteredBy: {\n type: String,\n + \ default: null,\n callback(newValue) {\n if (newValue && + this.searchForm && newValue !== this.searchForm.getAttribute(\"id\")) {\n + \ this.searchForm.component.detach(this);\n this.searchForm + = null;\n this.populate();\n }\n }\n },\n filteredOn: + {\n type: String,\n // 'server' | 'client'\n default: \"client\"\n + \ }\n },\n created() {\n this.searchCount = /* @__PURE__ */ new Map();\n + \ this.element.addEventListener(\"populate\", () => {\n var _a3;\n + \ if (!window.document.contains(this.element)) return;\n (_a3 = this.searchForm) + == null ? void 0 : _a3.component.updateAutoRanges();\n });\n },\n attached() + {\n const filteredBy = this.filteredBy;\n if (this.isFilteredOnServer() + && filteredBy) {\n this.searchForm = document.getElementById(filteredBy);\n + \ if (!this.searchForm) throw `#${filteredBy} is not in DOM`;\n this.searchForm.addEventListener(\n + \ \"formChange\",\n () => this.onServerSearchChange()\n );\n + \ } else {\n this.listPostProcessors.attach(\n this.filterCallback.bind(this),\n + \ \"FilterMixin:filterCallback\"\n );\n }\n },\n get filters() + {\n var _a3, _b;\n return ((_b = (_a3 = this.searchForm) == null ? void + 0 : _a3.component) == null ? void 0 : _b.value) ?? {};\n },\n set filters(filters) + {\n var _a3, _b;\n if ((_b = (_a3 = this.searchForm) == null ? void + 0 : _a3.component) == null ? void 0 : _b.value) {\n this.searchForm.component.value + = filters;\n this.filterList();\n }\n },\n isFilteredOnServer() + {\n return this.filteredOn === \"server\" && !!this.fetchData;\n },\n + \ async onServerSearchChange() {\n await this.fetchData(this.dataSrc);\n + \ this.empty();\n await this.populate();\n },\n getDynamicServerSearch() + {\n const filters = this.filters;\n if (this.isFilteredOnServer() && + filters) {\n const fields = Object.keys(filters);\n const value + = Object.values(filters).map(({ value: value2 }) => value2).filter((value2) + => !!value2).join(\" \").trim();\n if (fields.length > 0 && value) {\n + \ return { fields, value };\n }\n }\n return;\n },\n async + filterCallback(resources, listPostProcessors, div, context) {\n if (this.filteredBy + || this.searchFields) {\n if (!this.searchCount.has(context)) this.searchCount.set(context, + 1);\n if (!this.searchForm) await this.createFilter(context);\n const + filteredResources = await searchInResources(\n resources,\n this.filters,\n + \ this.fields,\n this.searchForm\n );\n resources = + resources.filter((_v, index2) => filteredResources[index2]);\n this.resources + = [...resources];\n }\n const nextProcessor = listPostProcessors.shift();\n + \ if (nextProcessor)\n await nextProcessor(\n resources,\n listPostProcessors,\n + \ div,\n context + (this.searchCount.get(context) || \"\")\n + \ );\n },\n async filterList(context) {\n this.searchCount.set(context, + this.searchCount.get(context) + 1);\n if (!this.resource) return;\n this.empty();\n + \ await this.populate();\n },\n async getValuesOfField(field) {\n const + arrayOfDataObjects = this.resource[\"listPredicate\"];\n const arrayOfDataIds + = [];\n for (const obj of arrayOfDataObjects) {\n const nextArrayOfObjects + = await obj[field];\n if (!nextArrayOfObjects) continue;\n if (typeof + nextArrayOfObjects !== \"object\") {\n console.warn(\n `The + format value of ${field} is not suitable with auto-range-[field] attribute`\n + \ );\n continue;\n }\n if (!nextArrayOfObjects.isContainer()) + {\n arrayOfDataIds.push(nextArrayOfObjects[\"@id\"]);\n continue;\n + \ }\n const children = nextArrayOfObjects[\"listPredicate\"];\n if + (!children) continue;\n arrayOfDataIds.push(...children.map((child) => + child[\"@id\"]));\n }\n return arrayOfDataIds;\n },\n async createFilter(context) + {\n const filteredBy = this.filteredBy;\n if (filteredBy != null) {\n + \ this.searchForm = document.getElementById(filteredBy);\n if (!this.searchForm) + throw `#${filteredBy} is not in DOM`;\n } else {\n this.searchForm + = document.createElement(\"solid-form-search\");\n }\n this.searchForm.component.attach(this);\n + \ this.searchForm.addEventListener(\"formChange\", () => {\n this.filterList(context);\n + \ });\n this.searchForm.toggleAttribute(\"naked\", true);\n if (filteredBy) + return;\n const searchAttributes = Array.from(this.element.attributes).filter((attr) + => attr.name.startsWith(\"search-\")).map((attr) => ({\n name: attr.name.replace(\"search-\", + \"\"),\n value: attr.value\n }));\n for (const { name, value } + of searchAttributes) {\n this.searchForm.setAttribute(name, value);\n + \ }\n this.element.insertBefore(this.searchForm, this.element.firstChild);\n + \ await this.searchForm.component.populate();\n }\n};\nconst GrouperMixin + = {\n name: \"grouper-mixin\",\n use: [],\n attributes: {\n groupBy: + {\n type: String,\n default: null\n },\n groupWidget: {\n + \ type: String,\n default: \"solid-group-default\"\n },\n groupClass: + {\n type: String,\n default: \"\"\n },\n orderGroupAsc: {\n + \ type: Boolean,\n default: null\n },\n orderGroupDesc: {\n + \ type: Boolean,\n default: null\n }\n },\n attached() {\n this.listPostProcessors.attach(\n + \ this.groupResources.bind(this),\n \"GrouperMixin:groupResources\"\n + \ );\n },\n async groupResources(resources, listPostProcessors, div, context) + {\n const nextProcessor = listPostProcessors.shift();\n if (this.groupBy) + {\n const groups = {};\n for (const resource of resources) {\n const + valueGroup = await resource[this.groupBy];\n if (valueGroup == null) + continue;\n if (!groups[valueGroup]) groups[valueGroup] = { resources: + [] };\n groups[valueGroup].resources.push(resource);\n }\n let + sortedKeys = Object.keys(groups);\n if (this.orderGroupAsc !== null || + this.orderGroupDesc !== null) {\n const order = this.orderGroupDesc + !== null ? \"desc\" : \"asc\";\n sortedKeys = Object.keys(groups).sort((a2, + b2) => {\n return generalComparator(a2, b2, order);\n });\n + \ }\n const parents = sortedKeys.map((g2) => ({\n group: g2,\n + \ parent: this.renderGroup(g2, div)\n }));\n for (const { + group, parent } of parents) {\n if (nextProcessor)\n await + nextProcessor(\n groups[group].resources,\n // give + only resources from group\n listPostProcessors.deepCopy(),\n // + copy post processors\n parent,\n // parent is group + widget\n `${context}_${group}`\n );\n }\n } else + {\n if (nextProcessor)\n await nextProcessor(resources, listPostProcessors, + div, context);\n }\n },\n /**\n * Create a group widget or find if + it already exists\n * @param groupName\n */\n renderGroup(groupName, + div) {\n let groupElt = this.element.querySelector(\n `${this.groupWidget}[value=\"${groupName}\"]`\n + \ );\n if (!groupElt) {\n groupElt = document.createElement(this.groupWidget);\n + \ groupElt.setAttribute(\"value\", groupName);\n if (this.groupClass) + groupElt.setAttribute(\"class\", this.groupClass);\n if (groupElt.component) + groupElt.component.render();\n div.appendChild(groupElt);\n }\n return + groupElt.querySelector(\"[data-content]\") || groupElt;\n }\n};\nconst HighlighterMixin + = {\n name: \"highlighter-mixin\",\n use: [],\n attached() {\n this.listPostProcessors.attach(\n + \ this.hightlightCallback.bind(this),\n \"HighlighterMixin:hightlightCallback\"\n + \ );\n },\n async hightlightCallback(resources, listPostProcessors, div, + context) {\n for (const attr of this.element.attributes) {\n if (attr.name.startsWith(\"highlight-\")) + {\n const field = attr.name.split(\"highlight-\")[1];\n resources + = await Promise.all(\n resources.map(async (resource) => ({\n sortingKey: + await resource[field],\n // fetch sorting value\n proxy: + resource\n // and keep proxy\n }))\n );\n resources + = this.sortHighlighted(resources, \"sortingKey\", attr.value);\n resources + = resources.map((resource) => resource.proxy);\n }\n }\n this.resources + = [...resources];\n const nextProcessor = listPostProcessors.shift();\n + \ if (nextProcessor)\n await nextProcessor(resources, listPostProcessors, + div, context);\n },\n sortHighlighted(resources, field, value) {\n for + (const [index2, res] of resources.entries()) {\n if (res[field] && res[field] + === value) {\n resources.splice(0, 0, resources.splice(index2, 1)[0]);\n + \ }\n }\n return resources;\n }\n};\nclass PostProcessorRegistry + {\n constructor(currentPostProcessors = []) {\n __publicField(this, \"currentPostProcessors\");\n + \ this.currentPostProcessors = [...currentPostProcessors];\n }\n attach(callback, + callbackName) {\n this.currentPostProcessors.push({ name: callbackName, + fn: callback });\n }\n getPostProcessors() {\n return this.currentPostProcessors;\n + \ }\n printCurrentCallbacks() {\n if (this.currentPostProcessors.length + === 0) {\n console.log(\"No post-processors registered.\");\n } else + {\n console.log(\"Registered post-processors:\");\n for (const postProcessor + of this.currentPostProcessors) {\n console.log(`- ${postProcessor.name}`);\n + \ }\n }\n }\n getFormattedCallbacks() {\n if (this.currentPostProcessors.length + === 0) {\n return \"No post-processors registered.\";\n }\n let + formattedText = \"Registered post-processors:\\n\";\n for (const postProcessor + of this.currentPostProcessors) {\n formattedText += `- ${postProcessor.name}\n`;\n + \ }\n return formattedText.trim();\n }\n static printFormattedCallbacks(currentPostProcessors) + {\n if (currentPostProcessors.length === 0) {\n return \"No post-processors + registered.\";\n }\n let formattedText = \"Registered post-processors:\\n\";\n + \ for (const postProcessor of currentPostProcessors) {\n formattedText + += `- ${postProcessor.name}\n`;\n }\n return formattedText.trim();\n + \ }\n whichCallbackExecutedNext() {\n if (this.currentPostProcessors.length + === 0) {\n return \"No post-processors registered.\";\n }\n return + `Next post-processor to be executed is: ${this.currentPostProcessors[0].name}`;\n + \ }\n deepCopy() {\n const copy = new PostProcessorRegistry(this.currentPostProcessors);\n + \ return copy;\n }\n shift() {\n const res = this.currentPostProcessors.shift();\n + \ if (res) {\n return res.fn;\n }\n return void 0;\n }\n}\nconst + ListMixin = {\n name: \"list-mixin\",\n use: [],\n attributes: {\n emptyWidget: + {\n type: String,\n default: null\n },\n emptyValue: {\n type: + String,\n default: \"\"\n }\n },\n initialState: {\n // Processors + functions to execute on the list before rendering\n listPostProcessors: + new PostProcessorRegistry(),\n // Rendering to execute after all the processors + have been executed\n renderCallbacks: []\n },\n created() {\n this.listPostProcessors + = new PostProcessorRegistry();\n this.renderCallbacks = [];\n },\n appendSingleElt(parent) + {\n this.appendChildElt(this.resource[\"@id\"], parent);\n },\n setElementAttribute(attr) + {\n const containerAttribute = \"solid-container\";\n const resourceAttribute + = \"solid-resource\";\n if (attr === \"resource\") {\n this.element.removeAttribute(containerAttribute);\n + \ this.element.setAttribute(resourceAttribute, \"\");\n } else {\n + \ this.element.removeAttribute(resourceAttribute);\n this.element.setAttribute(containerAttribute, + \"\");\n }\n },\n async populate() {\n var _a3, _b, _c, _d;\n const + listPostProcessorsCopy = this.listPostProcessors.deepCopy();\n const div + = this.div;\n if (!this.resource) return;\n if (!((_b = (_a3 = this.resource).isContainer) + == null ? void 0 : _b.call(_a3)) && !this.arrayField && !this.predicateName) + {\n this.setElementAttribute(\"resource\");\n this.appendSingleElt(div);\n + \ return;\n }\n if ((_d = (_c = this.resource).isContainer) == null + ? void 0 : _d.call(_c)) {\n this.setElementAttribute(\"container\");\n + \ this.renderCallbacks = [];\n listPostProcessorsCopy.attach(\n this.renderDOM.bind(this),\n + \ \"ListMixin:renderDOM\"\n );\n listPostProcessorsCopy.attach(\n + \ this.handleEmptyWidget.bind(this),\n \"ListMixin:handleEmptyWidget\"\n + \ );\n const nextProcessor = listPostProcessorsCopy.shift();\n await + nextProcessor(\n this.resource[\"listPredicate\"],\n listPostProcessorsCopy,\n + \ div,\n this.dataSrc\n );\n } else if (this.arrayField + && this.predicateName && this.resource[this.predicateName]) {\n this.setElementAttribute(\"container\");\n + \ this.renderCallbacks = [];\n listPostProcessorsCopy.attach(\n this.renderDOM.bind(this),\n + \ \"ListMixin:renderDOM\"\n );\n listPostProcessorsCopy.attach(\n + \ this.handleEmptyWidget.bind(this),\n \"ListMixin:handleEmptyWidget\"\n + \ );\n const nextProcessor = listPostProcessorsCopy.shift();\n await + nextProcessor(\n await this.resource[this.predicateName],\n listPostProcessorsCopy,\n + \ div,\n this.dataSrc\n );\n }\n for (const renderCallback + of this.renderCallbacks) {\n B(renderCallback.template, renderCallback.parent);\n + \ }\n },\n /**\n * Render resources in the DOM\n * @param resources\n + \ * @param listPostProcessors\n * @param div\n * @param context\n */\n + \ async renderDOM(resources, listPostProcessors, div, context) {\n for + (const resource of resources) {\n if (!resource) continue;\n this.appendChildElt(resource[\"@id\"], + div);\n }\n const nextProcessor = listPostProcessors.shift();\n if + (nextProcessor)\n await nextProcessor(resources, listPostProcessors, + div, context);\n },\n /**\n * Show empty widget if no resources in the + list\n * @param resources\n * @param listPostProcessors\n * @param div\n + \ * @param context\n */\n async handleEmptyWidget(resources, listPostProcessors, + div, context) {\n if (this.emptyWidget) {\n const emptyWidgetTemplate + = preHTML`\n <${this.emptyWidget}\n class=${this.emptyWidget}\n + \ value=${o$2(this.emptyValue)}\n >\n + \ `;\n if (!this.emptyWrapper) {\n this.emptyWrapper = document.createElement(\"span\");\n + \ this.element.appendChild(this.emptyWrapper);\n }\n B(\n + \ resources.length > 0 ? x`` : emptyWidgetTemplate,\n this.emptyWrapper\n + \ );\n }\n const nextProcessor = listPostProcessors.shift();\n if + (nextProcessor)\n await nextProcessor(resources, listPostProcessors, + div, context);\n }\n};\nconst PaginateMixin = {\n name: \"paginate-mixin\",\n + \ use: [],\n attributes: {\n paginateBy: {\n type: Number,\n default: + 0\n },\n paginateLoop: {\n type: String,\n default: null\n + \ }\n },\n initialState: {\n currentPage: []\n },\n created() {\n + \ this.currentPage = [];\n },\n attached() {\n this.listPostProcessors.attach(\n + \ this.paginateCallback.bind(this),\n \"PaginateMixin:paginateCallback\"\n + \ );\n },\n async paginateCallback(resources, listPostProcessors, div, + context) {\n if (this.paginateBy > 0) {\n if (!this.currentPage[context]) + this.currentPage[context] = 1;\n const parentDiv = this.initParentPaginationDiv(div, + context);\n this.renderCallbacks.push({\n template: this.renderPaginationNav(\n + \ this.getPageCount(resources.length),\n context,\n div\n + \ ),\n parent: parentDiv\n });\n const firstElementIndex + = (this.getCurrentPage(context) - 1) * this.paginateBy;\n resources = + resources.slice(\n firstElementIndex,\n firstElementIndex + + this.paginateBy\n );\n }\n const nextProcessor = listPostProcessors.shift();\n + \ if (nextProcessor)\n await nextProcessor(resources, listPostProcessors, + div, context);\n },\n getNavElement(div) {\n const insertNode = div.parentNode + || div;\n return insertNode.querySelector(`nav[data-id=\"nav\"]`);\n },\n + \ /**\n * Find nav element or create it if not existing\n * @param div + - insert nav next to this div\n */\n initParentPaginationDiv(div) {\n let + nav = this.getNavElement(div);\n if (!nav) {\n nav = document.createElement(\"nav\");\n + \ nav.setAttribute(\"data-id\", \"nav\");\n const insertNode = div.parentNode + || div;\n insertNode.appendChild(nav);\n }\n return nav;\n },\n + \ getCurrentPage(context) {\n return this.currentPage[context];\n },\n + \ setCurrentPage(page, context, pageCount) {\n if (page < 1) page = !this.shouldLoop() + ? 1 : pageCount;\n if (page > pageCount) page = !this.shouldLoop() ? pageCount + : 1;\n this.currentPage[context] = page;\n this.empty();\n this.populate();\n + \ },\n getPageCount(size) {\n return Math.max(1, Math.ceil(size / this.paginateBy));\n + \ },\n shouldLoop() {\n return this.paginateLoop !== null;\n },\n /**\n + \ * Create pagination template\n * @param pageCount\n * @param context\n + \ */\n renderPaginationNav(pageCount, context, div) {\n this.getNavElement(div).toggleAttribute(\"hidden\", + pageCount <= 1);\n const currentPage = this.getCurrentPage(context);\n + \ return x`\n this.setCurrentPage(currentPage + - 1, context, pageCount)}\n >←\n = pageCount}\n @click=${() + => this.setCurrentPage(currentPage + 1, context, pageCount)}\n >→\n + \ \n ${currentPage} / ${String(pageCount)}\n \n `;\n }\n};\nconst + RequiredMixin = {\n name: \"required-mixin\",\n use: [],\n attached() {\n + \ this.listPostProcessors.attach(\n this.requiredResources.bind(this),\n + \ \"RequiredMixin:requiredResources\"\n );\n },\n async requiredResources(resources, + listPostProcessors, div, context) {\n const displays = [];\n const requiredFields + = Array.from(this.element.attributes).filter((attr) => attr.name.startsWith(\"required-\")).map((attr) + => {\n return attr.value !== \"\" ? attr.value : attr.name.replace(\"required-\", + \"\");\n });\n if (requiredFields.length > 0) {\n for (const resource + of resources) {\n let hasProps = true;\n for (const field of + requiredFields) {\n const res = await resource[field];\n if + (!res || typeof res === \"object\" && \"@value\" in res && !res[\"@value\"]) + {\n hasProps = false;\n break;\n }\n }\n + \ if (hasProps) displays.push(resource);\n }\n }\n const + nextProcessor = listPostProcessors.shift();\n if (nextProcessor)\n await + nextProcessor(\n requiredFields.length > 0 ? displays : resources,\n + \ listPostProcessors,\n div,\n context\n );\n }\n};\nconst + SorterMixin = {\n name: \"sorter-mixin\",\n use: [],\n attributes: {\n + \ orderBy: {\n type: String,\n default: null\n },\n orderAsc: + {\n type: String,\n default: null\n },\n orderDesc: {\n type: + String,\n default: null\n },\n orderByRandom: {\n type: String,\n + \ default: null\n },\n sortedBy: {\n type: String,\n default: + null,\n callback(newValue) {\n if (newValue && this.sortForm && + newValue !== this.sortForm.getAttribute(\"id\")) {\n this.sortForm + = null;\n this.populate();\n }\n }\n }\n },\n initialState: + {\n randomOrder: null\n },\n attached() {\n this.listPostProcessors.attach(\n + \ this.orderCallback.bind(this),\n \"SorterMixin:orderCallback\"\n + \ );\n },\n created() {\n this.randomOrder = [];\n },\n async sorterList() + {\n if (!this.resource) return;\n this.empty();\n await this.populate();\n + \ },\n linkSorterForm() {\n this.sortForm.addEventListener(\"formChange\", + () => {\n this.sorterList();\n });\n },\n async orderCallback(resources, + listPostProcessors, div, context) {\n if (this.orderBy) this.orderAsc = + this.orderBy;\n let sortingKey = \"\";\n let orderValueToSort = \"\";\n + \ if (this.orderAsc || this.orderDesc) {\n sortingKey = this.orderAsc + || this.orderDesc;\n } else if (this.sortedBy) {\n const sortedBy + = this.sortedBy;\n if (sortedBy != null) {\n if (!this.sortForm) + {\n this.sortForm = document.getElementById(sortedBy);\n if + (!this.sortForm) throw `#${sortedBy} is not in DOM`;\n this.linkSorterForm();\n + \ }\n if (!this.sortForm.component.value.field) {\n console.warn(\"The + attribute field does not exist\");\n } else {\n sortingKey + = this.sortForm.component.value.field.value;\n }\n const orderField + = this.sortForm.component.value.order;\n orderValueToSort = (orderField + == null ? void 0 : orderField.value) ? orderField.value : \"asc\";\n }\n + \ }\n if (sortingKey) {\n let orderToSort = true;\n if (this.orderDesc + || orderValueToSort === \"desc\") orderToSort = false;\n resources = + (await Promise.all(\n resources.map(async (resource) => ({\n sortingKey: + await resource[sortingKey],\n // fetch sorting value\n proxy: + resource\n // and keep proxy\n }))\n )).sort(this.sortValuesByKey(\"sortingKey\", + orderToSort)).map((r3) => r3.proxy);\n } else if (this.isRandomSorted()) + {\n resources = this.shuffleResources(resources);\n }\n this.resources + = [...resources];\n const nextProcessor = listPostProcessors.shift();\n + \ if (nextProcessor)\n await nextProcessor(resources, listPostProcessors, + div, context);\n },\n isRandomSorted() {\n return this.orderByRandom + !== null;\n },\n sortValuesByKey(key, asc) {\n return (a2, b2) => {\n + \ if (!Object.hasOwn(a2, key)) return 1;\n if (!Object.hasOwn(b2, + key)) return -1;\n const varA = a2[key];\n const varB = b2[key];\n + \ let comparison = 0;\n if (typeof varA === \"string\" && typeof + varB === \"string\") {\n comparison = varA.localeCompare(varB, void + 0, {\n sensitivity: \"base\"\n });\n comparison = asc + ? comparison : -comparison;\n } else {\n if (varA > varB) comparison + = asc ? 1 : -1;\n else if (varA < varB) comparison = asc ? -1 : 1;\n + \ }\n return comparison;\n };\n },\n shuffleResources(array2) + {\n let currentIndex = array2.length;\n let temporaryValue;\n let + randomIndex;\n if (this.randomOrder.length !== array2.length) {\n this.randomOrder + = [...Array(array2.length).keys()];\n while (currentIndex !== 0) {\n + \ randomIndex = Math.floor(Math.random() * currentIndex);\n currentIndex + -= 1;\n temporaryValue = this.randomOrder[currentIndex];\n this.randomOrder[currentIndex] + = this.randomOrder[randomIndex];\n this.randomOrder[randomIndex] = + temporaryValue;\n }\n }\n return this.randomOrder.map((i3) => array2[i3]);\n + \ }\n};\nconst ActionMixin = {\n name: \"action-mixin\",\n attributes: {\n + \ src: {\n type: String,\n default: \"\",\n callback: function(newValue) + {\n this.addToAttributes(newValue, \"src\");\n }\n },\n targetSrc: + {\n type: String,\n default: \"\",\n callback: function(newValue) + {\n this.addToAttributes(newValue, \"target-src\");\n }\n }\n + \ }\n};\nconst BlankMixin = {\n name: \"blank-mixin\",\n created() {\n this.listAttributes.target + = \"_blank\";\n }\n};\nconst BooleanMixin = {\n name: \"boolean-mixin\",\n + \ get type() {\n return \"boolean\";\n }\n};\nconst MailtoMixin = {\n + \ name: \"mailto-mixin\",\n created() {\n this.listAttributes.mailto = + \"mailto:\";\n }\n};\nconst MultipleMixin = {\n name: \"multiple-mixin\",\n + \ attributes: {\n fields: {\n type: String,\n default: \"\",\n + \ callback: function(newValue) {\n this.addToAttributes(newValue, + \"fields\");\n }\n },\n next: {\n type: String,\n default: + \"\",\n callback: function(newValue) {\n this.addToAttributes(newValue, + \"next\");\n }\n },\n emptyWidget: {\n type: String,\n default: + \"\",\n callback: function(newValue) {\n this.addToAttributes(newValue, + \"emptyWidget\");\n }\n }\n }\n};\nconst NumberMixin = {\n name: + \"number-mixin\",\n get type() {\n return \"number\";\n }\n};\nconst + PlaceholderMixin = {\n name: \"placeholder-mixin\",\n attributes: {\n placeholder: + {\n type: String,\n default: \"\",\n callback: function(newValue) + {\n this.addToAttributes(newValue, \"placeholder\");\n }\n }\n + \ },\n attached() {\n this.listAttributes.placeholder = this.placeholder + || this.label || this.name || \"\";\n }\n};\nconst TelMixin = {\n name: + \"tel-mixin\",\n created() {\n this.listAttributes.tel = \"tel:\";\n }\n};\nconst + attributeDirectory = {\n multiple: MultipleMixin,\n action: ActionMixin,\n + \ blank: BlankMixin,\n mailto: MailtoMixin,\n tel: TelMixin,\n placeholder: + PlaceholderMixin,\n bool: BooleanMixin,\n num: NumberMixin\n};\nconst index$4 + = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({\n + \ __proto__: null,\n ActionMixin,\n BlankMixin,\n MailtoMixin,\n MultipleMixin,\n + \ PlaceholderMixin,\n TelMixin,\n attributeDirectory\n}, Symbol.toStringTag, + { value: \"Module\" }));\nconst BaseWidgetMixin = {\n name: \"widget-mixin\",\n + \ use: [],\n attributes: {\n value: {\n type: String,\n default: + \"\",\n callback: function() {\n this.planRender();\n }\n + \ },\n name: {\n type: String,\n default: \"\",\n callback: + function(newValue) {\n this.addToAttributes(newValue, \"name\");\n + \ }\n },\n label: {\n type: String,\n default: null,\n + \ callback: function(newValue) {\n this.addToAttributes(newValue, + \"label\");\n }\n },\n autoSubscribe: {\n type: String,\n + \ default: null,\n callback: function(newValue) {\n if (this.subscription) + PubSub.unsubscribe(this.subscription);\n this.subscribe(newValue);\n + \ }\n }\n },\n initialState: {\n listValueTransformations: new + PostProcessorRegistry(),\n listTemplateAdditions: new PostProcessorRegistry(),\n + \ listAttributes: {},\n listCallbacks: new PostProcessorRegistry(),\n + \ renderPlanned: false\n },\n get template() {\n return null;\n },\n + \ created() {\n this.listValueTransformations = new PostProcessorRegistry();\n + \ this.listAttributes = {};\n this.listTemplateAdditions = new PostProcessorRegistry();\n + \ this.listCallbacks = new PostProcessorRegistry();\n this.subscription + = null;\n },\n attached() {\n this.planRender();\n },\n detached() + {\n if (this.subscription) PubSub.unsubscribe(this.subscription);\n },\n + \ planRender() {\n if (!this.renderPlanned && document.body.contains(this.element)) + {\n this.renderPlanned = true;\n setTimeout(() => {\n this.render();\n + \ this.renderPlanned = false;\n });\n }\n },\n render() {\n + \ const listValueTransformationsCopy = this.listValueTransformations.deepCopy();\n + \ listValueTransformationsCopy.attach(\n this.renderTemplate.bind(this),\n + \ \"BaseWidgetMixin:renderTemplate\"\n );\n const nextProcessor + = listValueTransformationsCopy.shift();\n nextProcessor(this.value, listValueTransformationsCopy);\n + \ const listCallbacksCopy = this.listCallbacks.deepCopy();\n const nextCallback + = listCallbacksCopy.shift();\n if (nextCallback) {\n nextCallback(this.value, + listCallbacksCopy);\n }\n this.element.dispatchEvent(\n new CustomEvent(\"widgetRendered\", + { bubbles: true })\n );\n },\n renderTemplate(value) {\n const template + = this.template(value, { ...this.listAttributes });\n const listTemplateAdditionsCopy + = this.listTemplateAdditions.deepCopy();\n listTemplateAdditionsCopy.attach(\n + \ this.templateToDOM.bind(this),\n \"BaseWidgetMixin:templateToDOM\"\n + \ );\n const nextProcessor = listTemplateAdditionsCopy.shift();\n nextProcessor(template, + listTemplateAdditionsCopy);\n },\n templateToDOM(template) {\n B(template, + this.element);\n },\n addToAttributes(value, attrKey) {\n if (value !== + null && value !== this.listAttributes[attrKey]) {\n this.listAttributes[attrKey] + = value;\n this.planRender();\n }\n },\n subscribe(event) {\n this.subscription + = PubSub.subscribe(event, () => this.planRender());\n },\n update() {\n + \ this.planRender();\n }\n};\nvar slimselect$1 = { exports: {} };\nvar + slimselect = slimselect$1.exports;\nvar hasRequiredSlimselect;\nfunction requireSlimselect() + {\n if (hasRequiredSlimselect) return slimselect$1.exports;\n hasRequiredSlimselect + = 1;\n (function(module2, exports2) {\n (function(global2, factory) {\n + \ module2.exports = factory();\n })(slimselect, function() {\n class + CssClasses {\n constructor(classes) {\n if (!classes) {\n + \ classes = {};\n }\n this.main = classes.main + || \"ss-main\";\n this.placeholder = classes.placeholder || \"ss-placeholder\";\n + \ this.values = classes.values || \"ss-values\";\n this.single + = classes.single || \"ss-single\";\n this.max = classes.max || \"ss-max\";\n + \ this.value = classes.value || \"ss-value\";\n this.valueText + = classes.valueText || \"ss-value-text\";\n this.valueDelete = classes.valueDelete + || \"ss-value-delete\";\n this.valueOut = classes.valueOut || \"ss-value-out\";\n + \ this.deselect = classes.deselect || \"ss-deselect\";\n this.deselectPath + = classes.deselectPath || \"M10,10 L90,90 M10,90 L90,10\";\n this.arrow + = classes.arrow || \"ss-arrow\";\n this.arrowClose = classes.arrowClose + || \"M10,30 L50,70 L90,30\";\n this.arrowOpen = classes.arrowOpen + || \"M10,70 L50,30 L90,70\";\n this.content = classes.content || + \"ss-content\";\n this.openAbove = classes.openAbove || \"ss-open-above\";\n + \ this.openBelow = classes.openBelow || \"ss-open-below\";\n this.search + = classes.search || \"ss-search\";\n this.searchHighlighter = classes.searchHighlighter + || \"ss-search-highlight\";\n this.searching = classes.searching + || \"ss-searching\";\n this.addable = classes.addable || \"ss-addable\";\n + \ this.addablePath = classes.addablePath || \"M50,10 L50,90 M10,50 + L90,50\";\n this.list = classes.list || \"ss-list\";\n this.optgroup + = classes.optgroup || \"ss-optgroup\";\n this.optgroupLabel = classes.optgroupLabel + || \"ss-optgroup-label\";\n this.optgroupLabelText = classes.optgroupLabelText + || \"ss-optgroup-label-text\";\n this.optgroupActions = classes.optgroupActions + || \"ss-optgroup-actions\";\n this.optgroupSelectAll = classes.optgroupSelectAll + || \"ss-selectall\";\n this.optgroupSelectAllBox = classes.optgroupSelectAllBox + || \"M60,10 L10,10 L10,90 L90,90 L90,50\";\n this.optgroupSelectAllCheck + = classes.optgroupSelectAllCheck || \"M30,45 L50,70 L90,10\";\n this.optgroupClosable + = classes.optgroupClosable || \"ss-closable\";\n this.option = classes.option + || \"ss-option\";\n this.optionDelete = classes.optionDelete || \"M10,10 + L90,90 M10,90 L90,10\";\n this.highlighted = classes.highlighted + || \"ss-highlighted\";\n this.open = classes.open || \"ss-open\";\n + \ this.close = classes.close || \"ss-close\";\n this.selected + = classes.selected || \"ss-selected\";\n this.error = classes.error + || \"ss-error\";\n this.disabled = classes.disabled || \"ss-disabled\";\n + \ this.hide = classes.hide || \"ss-hide\";\n }\n }\n function + generateID() {\n return Math.random().toString(36).substring(2, 10);\n + \ }\n function hasClassInTree(element, className) {\n function + hasClass(e2, c2) {\n if (c2 && e2 && e2.classList && e2.classList.contains(c2)) + {\n return e2;\n }\n if (c2 && e2 && e2.dataset + && e2.dataset.id && e2.dataset.id === className) {\n return e2;\n + \ }\n return null;\n }\n function parentByClass(e2, + c2) {\n if (!e2 || e2 === document) {\n return null;\n + \ } else if (hasClass(e2, c2)) {\n return e2;\n } + else {\n return parentByClass(e2.parentNode, c2);\n }\n + \ }\n return hasClass(element, className) || parentByClass(element, + className);\n }\n function debounce(func, wait = 50, immediate = + false) {\n let timeout;\n return function(...args) {\n const + context = self;\n const later = () => {\n timeout = null;\n + \ if (!immediate) {\n func.apply(context, args);\n + \ }\n };\n const callNow = immediate && !timeout;\n + \ clearTimeout(timeout);\n timeout = setTimeout(later, wait);\n + \ if (callNow) {\n func.apply(context, args);\n }\n + \ };\n }\n function isEqual2(a2, b2) {\n return JSON.stringify(a2) + === JSON.stringify(b2);\n }\n function kebabCase(str) {\n const + result = str.replace(/[A-Z\\u00C0-\\u00D6\\u00D8-\\u00DE]/g, (match3) => \"-\" + + match3.toLowerCase());\n return str[0] === str[0].toUpperCase() ? + result.substring(1) : result;\n }\n class Optgroup {\n constructor(optgroup) + {\n this.id = !optgroup.id || optgroup.id === \"\" ? generateID() + : optgroup.id;\n this.label = optgroup.label || \"\";\n this.selectAll + = optgroup.selectAll === void 0 ? false : optgroup.selectAll;\n this.selectAllText + = optgroup.selectAllText || \"Select All\";\n this.closable = optgroup.closable + || \"off\";\n this.options = [];\n if (optgroup.options) + {\n for (const o2 of optgroup.options) {\n this.options.push(new + Option(o2));\n }\n }\n }\n }\n class + Option {\n constructor(option) {\n this.id = !option.id || + option.id === \"\" ? generateID() : option.id;\n this.value = option.value + === void 0 ? option.text : option.value;\n this.text = option.text + || \"\";\n this.html = option.html || \"\";\n this.selected + = option.selected !== void 0 ? option.selected : false;\n this.display + = option.display !== void 0 ? option.display : true;\n this.disabled + = option.disabled !== void 0 ? option.disabled : false;\n this.mandatory + = option.mandatory !== void 0 ? option.mandatory : false;\n this.placeholder + = option.placeholder !== void 0 ? option.placeholder : false;\n this.class + = option.class || \"\";\n this.style = option.style || \"\";\n this.data + = option.data || {};\n }\n }\n class Store {\n constructor(type, + data) {\n this.selectType = \"single\";\n this.data = [];\n + \ this.selectedOrder = [];\n this.selectType = type;\n this.setData(data);\n + \ }\n validateDataArray(data) {\n if (!Array.isArray(data)) + {\n return new Error(\"Data must be an array\");\n }\n + \ for (let dataObj of data) {\n if (dataObj instanceof + Optgroup || \"label\" in dataObj) {\n if (!(\"label\" in dataObj)) + {\n return new Error(\"Optgroup must have a label\");\n }\n + \ if (\"options\" in dataObj && dataObj.options) {\n for + (let option of dataObj.options) {\n const validationError + = this.validateOption(option);\n if (validationError) {\n + \ return validationError;\n }\n }\n + \ }\n } else if (dataObj instanceof Option || \"text\" + in dataObj) {\n const validationError = this.validateOption(dataObj);\n + \ if (validationError) {\n return validationError;\n + \ }\n } else {\n return new Error(\"Data + object must be a valid optgroup or option\");\n }\n }\n + \ return null;\n }\n validateOption(option) {\n if + (!(\"text\" in option)) {\n return new Error(\"Option must have + a text\");\n }\n return null;\n }\n partialToFullData(data) + {\n let dataFinal = [];\n data.forEach((dataObj) => {\n + \ if (dataObj instanceof Optgroup || \"label\" in dataObj) {\n let + optOptions = [];\n if (\"options\" in dataObj && dataObj.options) + {\n dataObj.options.forEach((option) => {\n optOptions.push(new + Option(option));\n });\n }\n if (optOptions.length + > 0) {\n dataFinal.push(new Optgroup(dataObj));\n }\n + \ }\n if (dataObj instanceof Option || \"text\" in dataObj) + {\n dataFinal.push(new Option(dataObj));\n }\n });\n + \ return dataFinal;\n }\n setData(data) {\n this.data + = this.partialToFullData(data);\n if (this.selectType === \"single\") + {\n this.setSelectedBy(\"id\", this.getSelected());\n }\n + \ }\n getData() {\n return this.filter(null, true);\n + \ }\n getDataOptions() {\n return this.filter(null, + false);\n }\n addOption(option, addToStart = false) {\n if + (addToStart) {\n let data = [new Option(option)];\n this.setData(data.concat(this.getData()));\n + \ } else {\n this.setData(this.getData().concat(new Option(option)));\n + \ }\n }\n setSelectedBy(selectedType, selectedValues) + {\n let firstOption = null;\n let hasSelected = false;\n + \ const selectedObjects = [];\n for (let dataObj of this.data) + {\n if (dataObj instanceof Optgroup) {\n for (let + option of dataObj.options) {\n if (!firstOption) {\n firstOption + = option;\n }\n option.selected = hasSelected + ? false : selectedValues.includes(option[selectedType]);\n if + (option.selected) {\n selectedObjects.push(option);\n if + (this.selectType === \"single\") {\n hasSelected = true;\n + \ }\n }\n }\n }\n if + (dataObj instanceof Option) {\n if (!firstOption) {\n firstOption + = dataObj;\n }\n dataObj.selected = hasSelected + ? false : selectedValues.includes(dataObj[selectedType]);\n if + (dataObj.selected) {\n selectedObjects.push(dataObj);\n if + (this.selectType === \"single\") {\n hasSelected = true;\n + \ }\n }\n }\n }\n if + (this.selectType === \"single\" && firstOption && !hasSelected) {\n firstOption.selected + = true;\n selectedObjects.push(firstOption);\n }\n const + selectedIds = selectedValues.map((value) => {\n var _a3;\n return + ((_a3 = selectedObjects.find((option) => option[selectedType] === value)) + === null || _a3 === void 0 ? void 0 : _a3.id) || \"\";\n });\n this.selectedOrder + = selectedIds;\n }\n getSelected() {\n return this.getSelectedOptions().map((option) + => option.id);\n }\n getSelectedValues() {\n return + this.getSelectedOptions().map((option) => option.value);\n }\n getSelectedOptions() + {\n return this.filter((opt) => {\n return opt.selected;\n + \ }, false);\n }\n getOptgroupByID(id) {\n for + (let dataObj of this.data) {\n if (dataObj instanceof Optgroup + && dataObj.id === id) {\n return dataObj;\n }\n }\n + \ return null;\n }\n getOptionByID(id) {\n let + options = this.filter((opt) => {\n return opt.id === id;\n }, + false);\n return options.length ? options[0] : null;\n }\n + \ getSelectType() {\n return this.selectType;\n }\n + \ getFirstOption() {\n let option = null;\n for (let + dataObj of this.data) {\n if (dataObj instanceof Optgroup) {\n + \ option = dataObj.options[0];\n } else if (dataObj + instanceof Option) {\n option = dataObj;\n }\n if + (option) {\n break;\n }\n }\n return + option;\n }\n search(search, searchFilter) {\n search + = search.trim();\n if (search === \"\") {\n return this.getData();\n + \ }\n return this.filter((opt) => {\n return searchFilter(opt, + search);\n }, true);\n }\n filter(filter, includeOptgroup) + {\n const dataSearch = [];\n this.data.forEach((dataObj) + => {\n if (dataObj instanceof Optgroup) {\n let optOptions + = [];\n dataObj.options.forEach((option) => {\n if + (!filter || filter(option)) {\n if (!includeOptgroup) {\n + \ dataSearch.push(new Option(option));\n } + else {\n optOptions.push(new Option(option));\n }\n + \ }\n });\n if (optOptions.length + > 0) {\n let optgroup = new Optgroup(dataObj);\n optgroup.options + = optOptions;\n dataSearch.push(optgroup);\n }\n + \ }\n if (dataObj instanceof Option) {\n if + (!filter || filter(dataObj)) {\n dataSearch.push(new Option(dataObj));\n + \ }\n }\n });\n return dataSearch;\n + \ }\n selectedOrderOptions(options) {\n const newOrder + = [];\n this.selectedOrder.forEach((id) => {\n const option + = options.find((opt) => opt.id === id);\n if (option) {\n newOrder.push(option);\n + \ }\n });\n options.forEach((option) => {\n let + isIn = false;\n newOrder.forEach((selectedOption) => {\n if + (option.id === selectedOption.id) {\n isIn = true;\n return;\n + \ }\n });\n if (!isIn) {\n newOrder.push(option);\n + \ }\n });\n return newOrder;\n }\n }\n + \ class Render {\n constructor(settings, classes, store2, callbacks) + {\n this.store = store2;\n this.settings = settings;\n this.classes + = classes;\n this.callbacks = callbacks;\n this.main = this.mainDiv();\n + \ this.content = this.contentDiv();\n this.updateClassStyles();\n + \ this.updateAriaAttributes();\n if (this.settings.contentLocation) + {\n this.settings.contentLocation.appendChild(this.content.main);\n + \ }\n }\n enable() {\n this.main.main.classList.remove(this.classes.disabled);\n + \ this.content.search.input.disabled = false;\n }\n disable() + {\n this.main.main.classList.add(this.classes.disabled);\n this.content.search.input.disabled + = true;\n }\n open() {\n this.main.arrow.path.setAttribute(\"d\", + this.classes.arrowOpen);\n this.main.main.classList.add(this.settings.openPosition + === \"up\" ? this.classes.openAbove : this.classes.openBelow);\n this.main.main.setAttribute(\"aria-expanded\", + \"true\");\n this.moveContent();\n const selectedOptions + = this.store.getSelectedOptions();\n if (selectedOptions.length) + {\n const selectedId = selectedOptions[selectedOptions.length - + 1].id;\n const selectedOption = this.content.list.querySelector('[data-id=\"' + + selectedId + '\"]');\n if (selectedOption) {\n this.ensureElementInView(this.content.list, + selectedOption);\n }\n }\n }\n close() {\n + \ this.main.main.classList.remove(this.classes.openAbove);\n this.main.main.classList.remove(this.classes.openBelow);\n + \ this.main.main.setAttribute(\"aria-expanded\", \"false\");\n this.content.main.classList.remove(this.classes.openAbove);\n + \ this.content.main.classList.remove(this.classes.openBelow);\n this.main.arrow.path.setAttribute(\"d\", + this.classes.arrowClose);\n }\n updateClassStyles() {\n this.main.main.className + = \"\";\n this.main.main.removeAttribute(\"style\");\n this.content.main.className + = \"\";\n this.content.main.removeAttribute(\"style\");\n this.main.main.classList.add(this.classes.main);\n + \ this.content.main.classList.add(this.classes.content);\n if + (this.settings.style !== \"\") {\n this.main.main.style.cssText + = this.settings.style;\n this.content.main.style.cssText = this.settings.style;\n + \ }\n if (this.settings.class.length) {\n for + (const c2 of this.settings.class) {\n if (c2.trim() !== \"\") + {\n this.main.main.classList.add(c2.trim());\n this.content.main.classList.add(c2.trim());\n + \ }\n }\n }\n if (this.settings.contentPosition + === \"relative\" || this.settings.contentPosition === \"fixed\") {\n this.content.main.classList.add(\"ss-\" + + this.settings.contentPosition);\n }\n }\n updateAriaAttributes() + {\n this.main.main.role = \"combobox\";\n this.main.main.setAttribute(\"aria-haspopup\", + \"listbox\");\n this.main.main.setAttribute(\"aria-controls\", this.content.main.id);\n + \ this.main.main.setAttribute(\"aria-expanded\", \"false\");\n this.content.main.setAttribute(\"role\", + \"listbox\");\n }\n mainDiv() {\n var _a3;\n const + main2 = document.createElement(\"div\");\n main2.dataset.id = this.settings.id;\n + \ main2.setAttribute(\"aria-label\", this.settings.ariaLabel);\n main2.tabIndex + = 0;\n main2.onkeydown = (e2) => {\n switch (e2.key) {\n + \ case \"ArrowUp\":\n case \"ArrowDown\":\n this.callbacks.open();\n + \ e2.key === \"ArrowDown\" ? this.highlight(\"down\") : this.highlight(\"up\");\n + \ return false;\n case \"Tab\":\n this.callbacks.close();\n + \ return true;\n case \"Enter\":\n case + \" \":\n this.callbacks.open();\n const highlighted + = this.content.list.querySelector(\".\" + this.classes.highlighted);\n if + (highlighted) {\n highlighted.click();\n }\n + \ return false;\n case \"Escape\":\n this.callbacks.close();\n + \ return false;\n }\n if (e2.key.length + === 1) {\n this.callbacks.open();\n }\n return + true;\n };\n main2.onclick = (e2) => {\n if (this.settings.disabled) + {\n return;\n }\n this.settings.isOpen + ? this.callbacks.close() : this.callbacks.open();\n };\n const + values = document.createElement(\"div\");\n values.classList.add(this.classes.values);\n + \ main2.appendChild(values);\n const deselect = document.createElement(\"div\");\n + \ deselect.classList.add(this.classes.deselect);\n const + selectedOptions = (_a3 = this.store) === null || _a3 === void 0 ? void 0 : + _a3.getSelectedOptions();\n if (!this.settings.allowDeselect || this.settings.isMultiple + && selectedOptions && selectedOptions.length <= 0) {\n deselect.classList.add(this.classes.hide);\n + \ } else {\n deselect.classList.remove(this.classes.hide);\n + \ }\n deselect.onclick = (e2) => {\n e2.stopPropagation();\n + \ if (this.settings.disabled) {\n return;\n }\n + \ let shouldDelete = true;\n const before = this.store.getSelectedOptions();\n + \ const after = [];\n if (this.callbacks.beforeChange) + {\n shouldDelete = this.callbacks.beforeChange(after, before) + === true;\n }\n if (shouldDelete) {\n if + (this.settings.isMultiple) {\n this.callbacks.setSelected([], + false);\n this.updateDeselectAll();\n } else {\n + \ const firstOption = this.store.getFirstOption();\n const + id = firstOption ? firstOption.id : \"\";\n this.callbacks.setSelected(id, + false);\n }\n if (this.settings.closeOnSelect) {\n + \ this.callbacks.close();\n }\n if + (this.callbacks.afterChange) {\n this.callbacks.afterChange(this.store.getSelectedOptions());\n + \ }\n }\n };\n const deselectSvg + = document.createElementNS(\"http://www.w3.org/2000/svg\", \"svg\");\n deselectSvg.setAttribute(\"viewBox\", + \"0 0 100 100\");\n const deselectPath = document.createElementNS(\"http://www.w3.org/2000/svg\", + \"path\");\n deselectPath.setAttribute(\"d\", this.classes.deselectPath);\n + \ deselectSvg.appendChild(deselectPath);\n deselect.appendChild(deselectSvg);\n + \ main2.appendChild(deselect);\n const arrow = document.createElementNS(\"http://www.w3.org/2000/svg\", + \"svg\");\n arrow.classList.add(this.classes.arrow);\n arrow.setAttribute(\"viewBox\", + \"0 0 100 100\");\n const arrowPath = document.createElementNS(\"http://www.w3.org/2000/svg\", + \"path\");\n arrowPath.setAttribute(\"d\", this.classes.arrowClose);\n + \ if (this.settings.alwaysOpen) {\n arrow.classList.add(this.classes.hide);\n + \ }\n arrow.appendChild(arrowPath);\n main2.appendChild(arrow);\n + \ return {\n main: main2,\n values,\n deselect: + {\n main: deselect,\n svg: deselectSvg,\n path: + deselectPath\n },\n arrow: {\n main: arrow,\n + \ path: arrowPath\n }\n };\n }\n mainFocus(eventType) + {\n if (eventType !== \"click\") {\n this.main.main.focus({ + preventScroll: true });\n }\n }\n placeholder() {\n + \ const placeholderOption = this.store.filter((o2) => o2.placeholder, + false);\n let placeholderText = this.settings.placeholderText;\n + \ if (placeholderOption.length) {\n if (placeholderOption[0].html + !== \"\") {\n placeholderText = placeholderOption[0].html;\n + \ } else if (placeholderOption[0].text !== \"\") {\n placeholderText + = placeholderOption[0].text;\n }\n }\n const + placeholder = document.createElement(\"div\");\n placeholder.classList.add(this.classes.placeholder);\n + \ placeholder.innerHTML = placeholderText;\n return placeholder;\n + \ }\n renderValues() {\n if (!this.settings.isMultiple) + {\n this.renderSingleValue();\n return;\n }\n + \ this.renderMultipleValues();\n this.updateDeselectAll();\n + \ }\n renderSingleValue() {\n const selected = this.store.filter((o2) + => {\n return o2.selected && !o2.placeholder;\n }, false);\n + \ const selectedSingle = selected.length > 0 ? selected[0] : null;\n + \ if (!selectedSingle) {\n this.main.values.innerHTML = + this.placeholder().outerHTML;\n } else {\n const singleValue + = document.createElement(\"div\");\n singleValue.classList.add(this.classes.single);\n + \ if (selectedSingle.html) {\n singleValue.innerHTML + = selectedSingle.html;\n } else {\n singleValue.innerText + = selectedSingle.text;\n }\n this.main.values.innerHTML + = singleValue.outerHTML;\n }\n if (!this.settings.allowDeselect + || !selected.length) {\n this.main.deselect.main.classList.add(this.classes.hide);\n + \ } else {\n this.main.deselect.main.classList.remove(this.classes.hide);\n + \ }\n }\n renderMultipleValues() {\n let currentNodes + = this.main.values.childNodes;\n let selectedOptions = this.store.filter((opt) + => {\n return opt.selected && opt.display;\n }, false);\n + \ if (selectedOptions.length === 0) {\n this.main.values.innerHTML + = this.placeholder().outerHTML;\n return;\n } else {\n + \ const placeholder = this.main.values.querySelector(\".\" + this.classes.placeholder);\n + \ if (placeholder) {\n placeholder.remove();\n }\n + \ }\n if (selectedOptions.length > this.settings.maxValuesShown) + {\n const singleValue = document.createElement(\"div\");\n singleValue.classList.add(this.classes.max);\n + \ singleValue.textContent = this.settings.maxValuesMessage.replace(\"{number}\", + selectedOptions.length.toString());\n this.main.values.innerHTML + = singleValue.outerHTML;\n return;\n } else {\n const + maxValuesMessage = this.main.values.querySelector(\".\" + this.classes.max);\n + \ if (maxValuesMessage) {\n maxValuesMessage.remove();\n + \ }\n }\n if (this.settings.keepOrder) {\n selectedOptions + = this.store.selectedOrderOptions(selectedOptions);\n }\n let + removeNodes = [];\n for (let i3 = 0; i3 < currentNodes.length; i3++) + {\n const node = currentNodes[i3];\n const id = node.getAttribute(\"data-id\");\n + \ if (id) {\n const found = selectedOptions.filter((opt) + => {\n return opt.id === id;\n }, false);\n if + (!found.length) {\n removeNodes.push(node);\n }\n + \ }\n }\n for (const n3 of removeNodes) {\n n3.classList.add(this.classes.valueOut);\n + \ setTimeout(() => {\n if (this.main.values.hasChildNodes() + && this.main.values.contains(n3)) {\n this.main.values.removeChild(n3);\n + \ }\n }, 100);\n }\n currentNodes + = this.main.values.childNodes;\n for (let d2 = 0; d2 < selectedOptions.length; + d2++) {\n let shouldAdd = true;\n for (let i3 = 0; i3 + < currentNodes.length; i3++) {\n if (selectedOptions[d2].id === + String(currentNodes[i3].dataset.id)) {\n shouldAdd = false;\n + \ }\n }\n if (shouldAdd) {\n if + (this.settings.keepOrder) {\n this.main.values.appendChild(this.multipleValue(selectedOptions[d2]));\n + \ } else {\n if (currentNodes.length === 0) {\n + \ this.main.values.appendChild(this.multipleValue(selectedOptions[d2]));\n + \ } else if (d2 === 0) {\n this.main.values.insertBefore(this.multipleValue(selectedOptions[d2]), + currentNodes[d2]);\n } else {\n currentNodes[d2 + - 1].insertAdjacentElement(\"afterend\", this.multipleValue(selectedOptions[d2]));\n + \ }\n }\n }\n }\n }\n + \ multipleValue(option) {\n const value = document.createElement(\"div\");\n + \ value.classList.add(this.classes.value);\n value.dataset.id + = option.id;\n const text2 = document.createElement(\"div\");\n text2.classList.add(this.classes.valueText);\n + \ text2.textContent = option.text;\n value.appendChild(text2);\n + \ if (!option.mandatory) {\n const deleteDiv = document.createElement(\"div\");\n + \ deleteDiv.classList.add(this.classes.valueDelete);\n deleteDiv.onclick + = (e2) => {\n e2.preventDefault();\n e2.stopPropagation();\n + \ if (this.settings.disabled) {\n return;\n }\n + \ let shouldDelete = true;\n const before = this.store.getSelectedOptions();\n + \ const after = before.filter((o2) => {\n return + o2.selected && o2.id !== option.id;\n }, true);\n if + (this.settings.minSelected && after.length < this.settings.minSelected) {\n + \ return;\n }\n if (this.callbacks.beforeChange) + {\n shouldDelete = this.callbacks.beforeChange(after, before) + === true;\n }\n if (shouldDelete) {\n let + selectedIds = [];\n for (const o2 of after) {\n if + (o2 instanceof Optgroup) {\n for (const c2 of o2.options) + {\n selectedIds.push(c2.id);\n }\n + \ }\n if (o2 instanceof Option) {\n selectedIds.push(o2.id);\n + \ }\n }\n this.callbacks.setSelected(selectedIds, + false);\n if (this.settings.closeOnSelect) {\n this.callbacks.close();\n + \ }\n if (this.callbacks.afterChange) {\n this.callbacks.afterChange(after);\n + \ }\n this.updateDeselectAll();\n }\n + \ };\n const deleteSvg = document.createElementNS(\"http://www.w3.org/2000/svg\", + \"svg\");\n deleteSvg.setAttribute(\"viewBox\", \"0 0 100 100\");\n + \ const deletePath = document.createElementNS(\"http://www.w3.org/2000/svg\", + \"path\");\n deletePath.setAttribute(\"d\", this.classes.optionDelete);\n + \ deleteSvg.appendChild(deletePath);\n deleteDiv.appendChild(deleteSvg);\n + \ value.appendChild(deleteDiv);\n }\n return value;\n + \ }\n contentDiv() {\n const main2 = document.createElement(\"div\");\n + \ main2.dataset.id = this.settings.id;\n const search = this.searchDiv();\n + \ main2.appendChild(search.main);\n const list2 = this.listDiv();\n + \ main2.appendChild(list2);\n return {\n main: + main2,\n search,\n list: list2\n };\n }\n + \ moveContent() {\n if (this.settings.contentPosition === \"relative\") + {\n this.moveContentBelow();\n return;\n }\n + \ if (this.settings.openPosition === \"down\") {\n this.moveContentBelow();\n + \ return;\n } else if (this.settings.openPosition === \"up\") + {\n this.moveContentAbove();\n return;\n }\n + \ if (this.putContent() === \"up\") {\n this.moveContentAbove();\n + \ } else {\n this.moveContentBelow();\n }\n }\n + \ searchDiv() {\n const main2 = document.createElement(\"div\");\n + \ const input = document.createElement(\"input\");\n const + addable = document.createElement(\"div\");\n main2.classList.add(this.classes.search);\n + \ const searchReturn = {\n main: main2,\n input\n + \ };\n if (!this.settings.showSearch) {\n main2.classList.add(this.classes.hide);\n + \ input.readOnly = true;\n }\n input.type = \"search\";\n + \ input.placeholder = this.settings.searchPlaceholder;\n input.tabIndex + = -1;\n input.setAttribute(\"aria-label\", this.settings.searchPlaceholder);\n + \ input.setAttribute(\"autocapitalize\", \"off\");\n input.setAttribute(\"autocomplete\", + \"off\");\n input.setAttribute(\"autocorrect\", \"off\");\n input.oninput + = debounce((e2) => {\n this.callbacks.search(e2.target.value);\n + \ }, 100);\n input.onkeydown = (e2) => {\n switch + (e2.key) {\n case \"ArrowUp\":\n case \"ArrowDown\":\n + \ e2.key === \"ArrowDown\" ? this.highlight(\"down\") : this.highlight(\"up\");\n + \ return false;\n case \"Tab\":\n this.callbacks.close();\n + \ return true;\n case \"Escape\":\n this.callbacks.close();\n + \ return false;\n case \" \":\n const + highlighted = this.content.list.querySelector(\".\" + this.classes.highlighted);\n + \ if (highlighted) {\n highlighted.click();\n + \ return false;\n }\n return + true;\n case \"Enter\":\n if (this.callbacks.addable) + {\n addable.click();\n return false;\n } + else {\n const highlighted2 = this.content.list.querySelector(\".\" + + this.classes.highlighted);\n if (highlighted2) {\n highlighted2.click();\n + \ return false;\n }\n }\n + \ return true;\n }\n return true;\n };\n + \ main2.appendChild(input);\n if (this.callbacks.addable) + {\n addable.classList.add(this.classes.addable);\n const + plus = document.createElementNS(\"http://www.w3.org/2000/svg\", \"svg\");\n + \ plus.setAttribute(\"viewBox\", \"0 0 100 100\");\n const + plusPath = document.createElementNS(\"http://www.w3.org/2000/svg\", \"path\");\n + \ plusPath.setAttribute(\"d\", this.classes.addablePath);\n plus.appendChild(plusPath);\n + \ addable.appendChild(plus);\n addable.onclick = (e2) + => {\n e2.preventDefault();\n e2.stopPropagation();\n + \ if (!this.callbacks.addable) {\n return;\n }\n + \ const inputValue = this.content.search.input.value.trim();\n + \ if (inputValue === \"\") {\n this.content.search.input.focus();\n + \ return;\n }\n const runFinish = + (oo) => {\n let newOption = new Option(oo);\n this.callbacks.addOption(newOption);\n + \ if (this.settings.isMultiple) {\n let ids + = this.store.getSelected();\n ids.push(newOption.id);\n this.callbacks.setSelected(ids, + true);\n } else {\n this.callbacks.setSelected([newOption.id], + true);\n }\n this.callbacks.search(\"\");\n + \ if (this.settings.closeOnSelect) {\n setTimeout(() + => {\n this.callbacks.close();\n }, 100);\n + \ }\n };\n const addableValue = this.callbacks.addable(inputValue);\n + \ if (addableValue === false || addableValue === void 0 || addableValue + === null) {\n return;\n }\n if (addableValue + instanceof Promise) {\n addableValue.then((value) => {\n if + (typeof value === \"string\") {\n runFinish({\n text: + value,\n value\n });\n } + else if (addableValue instanceof Error) {\n this.renderError(addableValue.message);\n + \ } else {\n runFinish(value);\n }\n + \ });\n } else if (typeof addableValue === \"string\") + {\n runFinish({\n text: addableValue,\n value: + addableValue\n });\n } else if (addableValue instanceof + Error) {\n this.renderError(addableValue.message);\n } + else {\n runFinish(addableValue);\n }\n return;\n + \ };\n main2.appendChild(addable);\n searchReturn.addable + = {\n main: addable,\n svg: plus,\n path: + plusPath\n };\n }\n return searchReturn;\n }\n + \ searchFocus() {\n this.content.search.input.focus();\n }\n + \ getOptions(notPlaceholder = false, notDisabled = false, notHidden + = false) {\n let query = \".\" + this.classes.option;\n if + (notPlaceholder) {\n query += \":not(.\" + this.classes.placeholder + + \")\";\n }\n if (notDisabled) {\n query += + \":not(.\" + this.classes.disabled + \")\";\n }\n if (notHidden) + {\n query += \":not(.\" + this.classes.hide + \")\";\n }\n + \ return Array.from(this.content.list.querySelectorAll(query));\n + \ }\n highlight(dir) {\n const options = this.getOptions(true, + true, true);\n if (options.length === 0) {\n return;\n + \ }\n if (options.length === 1) {\n if (!options[0].classList.contains(this.classes.highlighted)) + {\n options[0].classList.add(this.classes.highlighted);\n return;\n + \ }\n }\n let highlighted = false;\n for + (const o2 of options) {\n if (o2.classList.contains(this.classes.highlighted)) + {\n highlighted = true;\n }\n }\n if + (!highlighted) {\n for (const o2 of options) {\n if + (o2.classList.contains(this.classes.selected)) {\n o2.classList.add(this.classes.highlighted);\n + \ break;\n }\n }\n }\n for + (let i3 = 0; i3 < options.length; i3++) {\n if (options[i3].classList.contains(this.classes.highlighted)) + {\n const prevOption = options[i3];\n prevOption.classList.remove(this.classes.highlighted);\n + \ const prevParent = prevOption.parentElement;\n if + (prevParent && prevParent.classList.contains(this.classes.open)) {\n const + optgroupLabel = prevParent.querySelector(\".\" + this.classes.optgroupLabel);\n + \ if (optgroupLabel) {\n optgroupLabel.click();\n + \ }\n }\n let selectOption = options[dir + === \"down\" ? i3 + 1 < options.length ? i3 + 1 : 0 : i3 - 1 >= 0 ? i3 - 1 + : options.length - 1];\n selectOption.classList.add(this.classes.highlighted);\n + \ this.ensureElementInView(this.content.list, selectOption);\n + \ const selectParent = selectOption.parentElement;\n if + (selectParent && selectParent.classList.contains(this.classes.close)) {\n + \ const optgroupLabel = selectParent.querySelector(\".\" + this.classes.optgroupLabel);\n + \ if (optgroupLabel) {\n optgroupLabel.click();\n + \ }\n }\n return;\n }\n + \ }\n options[dir === \"down\" ? 0 : options.length - 1].classList.add(this.classes.highlighted);\n + \ this.ensureElementInView(this.content.list, options[dir === \"down\" + ? 0 : options.length - 1]);\n }\n listDiv() {\n const + options = document.createElement(\"div\");\n options.classList.add(this.classes.list);\n + \ return options;\n }\n renderError(error2) {\n this.content.list.innerHTML + = \"\";\n const errorDiv = document.createElement(\"div\");\n errorDiv.classList.add(this.classes.error);\n + \ errorDiv.textContent = error2;\n this.content.list.appendChild(errorDiv);\n + \ }\n renderSearching() {\n this.content.list.innerHTML + = \"\";\n const searchingDiv = document.createElement(\"div\");\n + \ searchingDiv.classList.add(this.classes.searching);\n searchingDiv.textContent + = this.settings.searchingText;\n this.content.list.appendChild(searchingDiv);\n + \ }\n renderOptions(data) {\n this.content.list.innerHTML + = \"\";\n if (data.length === 0) {\n const noResults = + document.createElement(\"div\");\n noResults.classList.add(this.classes.search);\n + \ if (this.callbacks.addable) {\n noResults.innerHTML + = this.settings.addableText.replace(\"{value}\", this.content.search.input.value);\n + \ } else {\n noResults.innerHTML = this.settings.searchText;\n + \ }\n this.content.list.appendChild(noResults);\n return;\n + \ }\n if (this.settings.allowDeselect && !this.settings.isMultiple) + {\n const placeholderOption = this.store.filter((o2) => o2.placeholder, + false);\n if (!placeholderOption.length) {\n this.store.addOption(new + Option({\n text: \"\",\n value: \"\",\n selected: + false,\n placeholder: true\n }), true);\n }\n + \ }\n for (const d2 of data) {\n if (d2 instanceof + Optgroup) {\n const optgroupEl = document.createElement(\"div\");\n + \ optgroupEl.classList.add(this.classes.optgroup);\n const + optgroupLabel = document.createElement(\"div\");\n optgroupLabel.classList.add(this.classes.optgroupLabel);\n + \ optgroupEl.appendChild(optgroupLabel);\n const + optgroupLabelText = document.createElement(\"div\");\n optgroupLabelText.classList.add(this.classes.optgroupLabelText);\n + \ optgroupLabelText.textContent = d2.label;\n optgroupLabel.appendChild(optgroupLabelText);\n + \ const optgroupActions = document.createElement(\"div\");\n optgroupActions.classList.add(this.classes.optgroupActions);\n + \ optgroupLabel.appendChild(optgroupActions);\n if + (this.settings.isMultiple && d2.selectAll) {\n const selectAll + = document.createElement(\"div\");\n selectAll.classList.add(this.classes.optgroupSelectAll);\n + \ let allSelected = true;\n for (const o2 of + d2.options) {\n if (!o2.selected) {\n allSelected + = false;\n break;\n }\n }\n + \ if (allSelected) {\n selectAll.classList.add(this.classes.selected);\n + \ }\n const selectAllText = document.createElement(\"span\");\n + \ selectAllText.textContent = d2.selectAllText;\n selectAll.appendChild(selectAllText);\n + \ const selectAllSvg = document.createElementNS(\"http://www.w3.org/2000/svg\", + \"svg\");\n selectAllSvg.setAttribute(\"viewBox\", \"0 0 100 + 100\");\n selectAll.appendChild(selectAllSvg);\n const + selectAllBox = document.createElementNS(\"http://www.w3.org/2000/svg\", \"path\");\n + \ selectAllBox.setAttribute(\"d\", this.classes.optgroupSelectAllBox);\n + \ selectAllSvg.appendChild(selectAllBox);\n const + selectAllCheck = document.createElementNS(\"http://www.w3.org/2000/svg\", + \"path\");\n selectAllCheck.setAttribute(\"d\", this.classes.optgroupSelectAllCheck);\n + \ selectAllSvg.appendChild(selectAllCheck);\n selectAll.addEventListener(\"click\", + (e2) => {\n e2.preventDefault();\n e2.stopPropagation();\n + \ const currentSelected = this.store.getSelected();\n if + (allSelected) {\n const newSelected = currentSelected.filter((s2) + => {\n for (const o2 of d2.options) {\n if + (s2 === o2.id) {\n return false;\n }\n + \ }\n return true;\n });\n + \ this.callbacks.setSelected(newSelected, true);\n return;\n + \ } else {\n const newSelected = currentSelected.concat(d2.options.map((o2) + => o2.id));\n for (const o2 of d2.options) {\n if + (!this.store.getOptionByID(o2.id)) {\n this.callbacks.addOption(o2);\n + \ }\n }\n this.callbacks.setSelected(newSelected, + true);\n return;\n }\n });\n + \ optgroupActions.appendChild(selectAll);\n }\n + \ if (d2.closable !== \"off\") {\n const optgroupClosable + = document.createElement(\"div\");\n optgroupClosable.classList.add(this.classes.optgroupClosable);\n + \ const optgroupClosableSvg = document.createElementNS(\"http://www.w3.org/2000/svg\", + \"svg\");\n optgroupClosableSvg.setAttribute(\"viewBox\", \"0 + 0 100 100\");\n optgroupClosableSvg.classList.add(this.classes.arrow);\n + \ optgroupClosable.appendChild(optgroupClosableSvg);\n const + optgroupClosableArrow = document.createElementNS(\"http://www.w3.org/2000/svg\", + \"path\");\n optgroupClosableSvg.appendChild(optgroupClosableArrow);\n + \ if (d2.options.some((o2) => o2.selected) || this.content.search.input.value.trim() + !== \"\") {\n optgroupClosable.classList.add(this.classes.open);\n + \ optgroupClosableArrow.setAttribute(\"d\", this.classes.arrowOpen);\n + \ } else if (d2.closable === \"open\") {\n optgroupEl.classList.add(this.classes.open);\n + \ optgroupClosableArrow.setAttribute(\"d\", this.classes.arrowOpen);\n + \ } else if (d2.closable === \"close\") {\n optgroupEl.classList.add(this.classes.close);\n + \ optgroupClosableArrow.setAttribute(\"d\", this.classes.arrowClose);\n + \ }\n optgroupLabel.addEventListener(\"click\", + (e2) => {\n e2.preventDefault();\n e2.stopPropagation();\n + \ if (optgroupEl.classList.contains(this.classes.close)) {\n + \ optgroupEl.classList.remove(this.classes.close);\n optgroupEl.classList.add(this.classes.open);\n + \ optgroupClosableArrow.setAttribute(\"d\", this.classes.arrowOpen);\n + \ } else {\n optgroupEl.classList.remove(this.classes.open);\n + \ optgroupEl.classList.add(this.classes.close);\n optgroupClosableArrow.setAttribute(\"d\", + this.classes.arrowClose);\n }\n });\n optgroupActions.appendChild(optgroupClosable);\n + \ }\n optgroupEl.appendChild(optgroupLabel);\n for + (const o2 of d2.options) {\n optgroupEl.appendChild(this.option(o2));\n + \ }\n this.content.list.appendChild(optgroupEl);\n + \ }\n if (d2 instanceof Option) {\n this.content.list.appendChild(this.option(d2));\n + \ }\n }\n }\n option(option) {\n if + (option.placeholder) {\n const placeholder = document.createElement(\"div\");\n + \ placeholder.classList.add(this.classes.option);\n placeholder.classList.add(this.classes.hide);\n + \ return placeholder;\n }\n const optionEl = document.createElement(\"div\");\n + \ optionEl.dataset.id = option.id;\n optionEl.classList.add(this.classes.option);\n + \ optionEl.setAttribute(\"role\", \"option\");\n if (option.class) + {\n option.class.split(\" \").forEach((dataClass) => {\n optionEl.classList.add(dataClass);\n + \ });\n }\n if (option.style) {\n optionEl.style.cssText + = option.style;\n }\n if (this.settings.searchHighlight + && this.content.search.input.value.trim() !== \"\") {\n optionEl.innerHTML + = this.highlightText(option.html !== \"\" ? option.html : option.text, this.content.search.input.value, + this.classes.searchHighlighter);\n } else if (option.html !== \"\") + {\n optionEl.innerHTML = option.html;\n } else {\n optionEl.textContent + = option.text;\n }\n if (this.settings.showOptionTooltips + && optionEl.textContent) {\n optionEl.setAttribute(\"title\", optionEl.textContent);\n + \ }\n if (!option.display) {\n optionEl.classList.add(this.classes.hide);\n + \ }\n if (option.disabled) {\n optionEl.classList.add(this.classes.disabled);\n + \ }\n if (option.selected && this.settings.hideSelected) + {\n optionEl.classList.add(this.classes.hide);\n }\n if + (option.selected) {\n optionEl.classList.add(this.classes.selected);\n + \ optionEl.setAttribute(\"aria-selected\", \"true\");\n this.main.main.setAttribute(\"aria-activedescendant\", + optionEl.id);\n } else {\n optionEl.classList.remove(this.classes.selected);\n + \ optionEl.setAttribute(\"aria-selected\", \"false\");\n }\n + \ optionEl.addEventListener(\"click\", (e2) => {\n e2.preventDefault();\n + \ e2.stopPropagation();\n const selectedOptions = this.store.getSelected();\n + \ const element = e2.currentTarget;\n const elementID + = String(element.dataset.id);\n if (option.disabled || option.selected + && !this.settings.allowDeselect) {\n return;\n }\n + \ if (this.settings.isMultiple && this.settings.maxSelected <= selectedOptions.length + && !option.selected || this.settings.isMultiple && this.settings.minSelected + >= selectedOptions.length && option.selected) {\n return;\n }\n + \ let shouldUpdate = false;\n const before = this.store.getSelectedOptions();\n + \ let after = [];\n if (this.settings.isMultiple) {\n + \ if (option.selected) {\n after = before.filter((o2) + => o2.id !== elementID);\n } else {\n after = + before.concat(option);\n }\n }\n if (!this.settings.isMultiple) + {\n if (option.selected) {\n after = [];\n } + else {\n after = [option];\n }\n }\n + \ if (!this.callbacks.beforeChange) {\n shouldUpdate + = true;\n }\n if (this.callbacks.beforeChange) {\n if + (this.callbacks.beforeChange(after, before) === false) {\n shouldUpdate + = false;\n } else {\n shouldUpdate = true;\n }\n + \ }\n if (shouldUpdate) {\n if (!this.store.getOptionByID(elementID)) + {\n this.callbacks.addOption(option);\n }\n this.callbacks.setSelected(after.map((o2) + => o2.id), false);\n if (this.settings.closeOnSelect) {\n this.callbacks.close();\n + \ }\n if (this.callbacks.afterChange) {\n this.callbacks.afterChange(after);\n + \ }\n }\n });\n return optionEl;\n + \ }\n destroy() {\n this.main.main.remove();\n this.content.main.remove();\n + \ }\n highlightText(str, search, className) {\n let + completedString = str;\n const regex2 = new RegExp(\"(?![^<]*>)(\" + + search.trim() + \")(?![^<]*>[^<>]*${originalTextFoundByRegex}`);\n + \ return completedString;\n }\n moveContentAbove() {\n + \ const mainHeight = this.main.main.offsetHeight;\n const + contentHeight = this.content.main.offsetHeight;\n this.main.main.classList.remove(this.classes.openBelow);\n + \ this.main.main.classList.add(this.classes.openAbove);\n this.content.main.classList.remove(this.classes.openBelow);\n + \ this.content.main.classList.add(this.classes.openAbove);\n const + containerRect = this.main.main.getBoundingClientRect();\n this.content.main.style.margin + = \"-\" + (mainHeight + contentHeight - 1) + \"px 0px 0px 0px\";\n this.content.main.style.top + = containerRect.top + containerRect.height + (this.settings.contentPosition + === \"fixed\" ? 0 : window.scrollY) + \"px\";\n this.content.main.style.left + = containerRect.left + (this.settings.contentPosition === \"fixed\" ? 0 : + window.scrollX) + \"px\";\n this.content.main.style.width = containerRect.width + + \"px\";\n }\n moveContentBelow() {\n this.main.main.classList.remove(this.classes.openAbove);\n + \ this.main.main.classList.add(this.classes.openBelow);\n this.content.main.classList.remove(this.classes.openAbove);\n + \ this.content.main.classList.add(this.classes.openBelow);\n const + containerRect = this.main.main.getBoundingClientRect();\n this.content.main.style.margin + = \"-1px 0px 0px 0px\";\n if (this.settings.contentPosition !== \"relative\") + {\n this.content.main.style.top = containerRect.top + containerRect.height + + (this.settings.contentPosition === \"fixed\" ? 0 : window.scrollY) + \"px\";\n + \ this.content.main.style.left = containerRect.left + (this.settings.contentPosition + === \"fixed\" ? 0 : window.scrollX) + \"px\";\n this.content.main.style.width + = containerRect.width + \"px\";\n }\n }\n ensureElementInView(container, + element) {\n const cTop = container.scrollTop + container.offsetTop;\n + \ const cBottom = cTop + container.clientHeight;\n const + eTop = element.offsetTop;\n const eBottom = eTop + element.clientHeight;\n + \ if (eTop < cTop) {\n container.scrollTop -= cTop - eTop;\n + \ } else if (eBottom > cBottom) {\n container.scrollTop + += eBottom - cBottom;\n }\n }\n putContent() {\n const + mainHeight = this.main.main.offsetHeight;\n const mainRect = this.main.main.getBoundingClientRect();\n + \ const contentHeight = this.content.main.offsetHeight;\n const + spaceBelow = window.innerHeight - (mainRect.top + mainHeight);\n if + (spaceBelow <= contentHeight) {\n if (mainRect.top > contentHeight) + {\n return \"up\";\n } else {\n return + \"down\";\n }\n }\n return \"down\";\n }\n + \ updateDeselectAll() {\n if (!this.store || !this.settings) + {\n return;\n }\n const selected = this.store.getSelectedOptions();\n + \ const hasSelectedItems = selected && selected.length > 0;\n const + isMultiple = this.settings.isMultiple;\n const allowDeselect = this.settings.allowDeselect;\n + \ const deselectButton = this.main.deselect.main;\n const + hideClass = this.classes.hide;\n if (allowDeselect && !(isMultiple + && !hasSelectedItems)) {\n deselectButton.classList.remove(hideClass);\n + \ } else {\n deselectButton.classList.add(hideClass);\n + \ }\n }\n }\n class Select {\n constructor(select) + {\n this.listen = false;\n this.observer = null;\n this.select + = select;\n this.valueChange = this.valueChange.bind(this);\n this.select.addEventListener(\"change\", + this.valueChange, {\n passive: true\n });\n this.observer + = new MutationObserver(this.observeCall.bind(this));\n this.changeListen(true);\n + \ }\n enable() {\n this.select.disabled = false;\n }\n + \ disable() {\n this.select.disabled = true;\n }\n hideUI() + {\n this.select.tabIndex = -1;\n this.select.style.display + = \"none\";\n this.select.setAttribute(\"aria-hidden\", \"true\");\n + \ }\n showUI() {\n this.select.removeAttribute(\"tabindex\");\n + \ this.select.style.display = \"\";\n this.select.removeAttribute(\"aria-hidden\");\n + \ }\n changeListen(listen) {\n this.listen = listen;\n + \ if (listen) {\n if (this.observer) {\n this.observer.observe(this.select, + {\n subtree: true,\n childList: true,\n attributes: + true\n });\n }\n }\n if (!listen) + {\n if (this.observer) {\n this.observer.disconnect();\n + \ }\n }\n }\n valueChange(ev) {\n if + (this.listen && this.onValueChange) {\n this.onValueChange(this.getSelectedOptions());\n + \ }\n return true;\n }\n observeCall(mutations) + {\n if (!this.listen) {\n return;\n }\n let + classChanged = false;\n let disabledChanged = false;\n let + optgroupOptionChanged = false;\n for (const m2 of mutations) {\n + \ if (m2.target === this.select) {\n if (m2.attributeName + === \"disabled\") {\n disabledChanged = true;\n }\n + \ if (m2.attributeName === \"class\") {\n classChanged + = true;\n }\n if (m2.type === \"childList\") {\n + \ for (const n3 of m2.addedNodes) {\n if (n3.nodeName + === \"OPTION\" && n3.value === this.select.value) {\n this.select.dispatchEvent(new + Event(\"change\"));\n break;\n }\n }\n + \ optgroupOptionChanged = true;\n }\n }\n + \ if (m2.target.nodeName === \"OPTGROUP\" || m2.target.nodeName + === \"OPTION\") {\n optgroupOptionChanged = true;\n }\n + \ }\n if (classChanged && this.onClassChange) {\n this.onClassChange(this.select.className.split(\" + \"));\n }\n if (disabledChanged && this.onDisabledChange) + {\n this.changeListen(false);\n this.onDisabledChange(this.select.disabled);\n + \ this.changeListen(true);\n }\n if (optgroupOptionChanged + && this.onOptionsChange) {\n this.changeListen(false);\n this.onOptionsChange(this.getData());\n + \ this.changeListen(true);\n }\n }\n getData() + {\n let data = [];\n const nodes = this.select.childNodes;\n + \ for (const n3 of nodes) {\n if (n3.nodeName === \"OPTGROUP\") + {\n data.push(this.getDataFromOptgroup(n3));\n }\n + \ if (n3.nodeName === \"OPTION\") {\n data.push(this.getDataFromOption(n3));\n + \ }\n }\n return data;\n }\n getDataFromOptgroup(optgroup) + {\n let data = {\n id: optgroup.id,\n label: + optgroup.label,\n selectAll: optgroup.dataset ? optgroup.dataset.selectall + === \"true\" : false,\n selectAllText: optgroup.dataset ? optgroup.dataset.selectalltext + : \"Select all\",\n closable: optgroup.dataset ? optgroup.dataset.closable + : \"off\",\n options: []\n };\n const options + = optgroup.childNodes;\n for (const o2 of options) {\n if + (o2.nodeName === \"OPTION\") {\n data.options.push(this.getDataFromOption(o2));\n + \ }\n }\n return data;\n }\n getDataFromOption(option) + {\n return {\n id: option.id,\n value: option.value,\n + \ text: option.text,\n html: option.dataset && option.dataset.html + ? option.dataset.html : \"\",\n selected: option.selected,\n display: + option.style.display !== \"none\",\n disabled: option.disabled,\n + \ mandatory: option.dataset ? option.dataset.mandatory === \"true\" + : false,\n placeholder: option.dataset.placeholder === \"true\",\n + \ class: option.className,\n style: option.style.cssText,\n + \ data: option.dataset\n };\n }\n getSelectedOptions() + {\n let options = [];\n const opts = this.select.childNodes;\n + \ for (const o2 of opts) {\n if (o2.nodeName === \"OPTGROUP\") + {\n const optgroupOptions = o2.childNodes;\n for + (const oo of optgroupOptions) {\n if (oo.nodeName === \"OPTION\") + {\n const option = oo;\n if (option.selected) + {\n options.push(this.getDataFromOption(option));\n }\n + \ }\n }\n }\n if (o2.nodeName + === \"OPTION\") {\n const option = o2;\n if (option.selected) + {\n options.push(this.getDataFromOption(option));\n }\n + \ }\n }\n return options;\n }\n getSelectedValues() + {\n return this.getSelectedOptions().map((option) => option.value);\n + \ }\n setSelected(ids) {\n this.changeListen(false);\n + \ const options = this.select.childNodes;\n for (const o2 + of options) {\n if (o2.nodeName === \"OPTGROUP\") {\n const + optgroup = o2;\n const optgroupOptions = optgroup.childNodes;\n + \ for (const oo of optgroupOptions) {\n if (oo.nodeName + === \"OPTION\") {\n const option = oo;\n option.selected + = ids.includes(option.id);\n }\n }\n }\n + \ if (o2.nodeName === \"OPTION\") {\n const option + = o2;\n option.selected = ids.includes(option.id);\n }\n + \ }\n this.changeListen(true);\n }\n setSelectedByValue(values) + {\n this.changeListen(false);\n const options = this.select.childNodes;\n + \ for (const o2 of options) {\n if (o2.nodeName === \"OPTGROUP\") + {\n const optgroup = o2;\n const optgroupOptions + = optgroup.childNodes;\n for (const oo of optgroupOptions) {\n + \ if (oo.nodeName === \"OPTION\") {\n const + option = oo;\n option.selected = values.includes(option.value);\n + \ }\n }\n }\n if (o2.nodeName + === \"OPTION\") {\n const option = o2;\n option.selected + = values.includes(option.value);\n }\n }\n this.changeListen(true);\n + \ }\n updateSelect(id, style, classes) {\n this.changeListen(false);\n + \ if (id) {\n this.select.dataset.id = id;\n }\n + \ if (style) {\n this.select.style.cssText = style;\n }\n + \ if (classes) {\n this.select.className = \"\";\n classes.forEach((c2) + => {\n if (c2.trim() !== \"\") {\n this.select.classList.add(c2.trim());\n + \ }\n });\n }\n this.changeListen(true);\n + \ }\n updateOptions(data) {\n this.changeListen(false);\n + \ this.select.innerHTML = \"\";\n for (const d2 of data) + {\n if (d2 instanceof Optgroup) {\n this.select.appendChild(this.createOptgroup(d2));\n + \ }\n if (d2 instanceof Option) {\n this.select.appendChild(this.createOption(d2));\n + \ }\n }\n this.select.dispatchEvent(new Event(\"change\", + { bubbles: true }));\n this.changeListen(true);\n }\n createOptgroup(optgroup) + {\n const optgroupEl = document.createElement(\"optgroup\");\n optgroupEl.id + = optgroup.id;\n optgroupEl.label = optgroup.label;\n if + (optgroup.selectAll) {\n optgroupEl.dataset.selectAll = \"true\";\n + \ }\n if (optgroup.closable !== \"off\") {\n optgroupEl.dataset.closable + = optgroup.closable;\n }\n if (optgroup.options) {\n for + (const o2 of optgroup.options) {\n optgroupEl.appendChild(this.createOption(o2));\n + \ }\n }\n return optgroupEl;\n }\n createOption(info) + {\n const optionEl = document.createElement(\"option\");\n optionEl.id + = info.id;\n optionEl.value = info.value;\n optionEl.textContent + = info.text;\n if (info.html !== \"\") {\n optionEl.setAttribute(\"data-html\", + info.html);\n }\n if (info.selected) {\n optionEl.selected + = info.selected;\n }\n if (info.disabled) {\n optionEl.disabled + = true;\n }\n if (!info.display) {\n optionEl.style.display + = \"none\";\n }\n if (info.placeholder) {\n optionEl.setAttribute(\"data-placeholder\", + \"true\");\n }\n if (info.mandatory) {\n optionEl.setAttribute(\"data-mandatory\", + \"true\");\n }\n if (info.class) {\n info.class.split(\" + \").forEach((optionClass) => {\n optionEl.classList.add(optionClass);\n + \ });\n }\n if (info.data && typeof info.data + === \"object\") {\n Object.keys(info.data).forEach((key) => {\n + \ optionEl.setAttribute(\"data-\" + kebabCase(key), info.data[key]);\n + \ });\n }\n return optionEl;\n }\n destroy() + {\n this.changeListen(false);\n this.select.removeEventListener(\"change\", + this.valueChange);\n if (this.observer) {\n this.observer.disconnect();\n + \ this.observer = null;\n }\n delete this.select.dataset.id;\n + \ this.showUI();\n }\n }\n class Settings {\n constructor(settings) + {\n this.id = \"\";\n this.style = \"\";\n this.class + = [];\n this.isMultiple = false;\n this.isOpen = false;\n + \ this.isFullOpen = false;\n this.intervalMove = null;\n + \ if (!settings) {\n settings = {};\n }\n this.id + = \"ss-\" + generateID();\n this.style = settings.style || \"\";\n + \ this.class = settings.class || [];\n this.disabled = settings.disabled + !== void 0 ? settings.disabled : false;\n this.alwaysOpen = settings.alwaysOpen + !== void 0 ? settings.alwaysOpen : false;\n this.showSearch = settings.showSearch + !== void 0 ? settings.showSearch : true;\n this.focusSearch = settings.focusSearch + !== void 0 ? settings.focusSearch : true;\n this.ariaLabel = settings.ariaLabel + || \"Combobox\";\n this.searchPlaceholder = settings.searchPlaceholder + || \"Search\";\n this.searchText = settings.searchText || \"No Results\";\n + \ this.searchingText = settings.searchingText || \"Searching...\";\n + \ this.searchHighlight = settings.searchHighlight !== void 0 ? settings.searchHighlight + : false;\n this.closeOnSelect = settings.closeOnSelect !== void 0 + ? settings.closeOnSelect : true;\n this.contentLocation = settings.contentLocation + || document.body;\n this.contentPosition = settings.contentPosition + || \"absolute\";\n this.openPosition = settings.openPosition || \"auto\";\n + \ this.placeholderText = settings.placeholderText !== void 0 ? settings.placeholderText + : \"Select Value\";\n this.allowDeselect = settings.allowDeselect + !== void 0 ? settings.allowDeselect : false;\n this.hideSelected + = settings.hideSelected !== void 0 ? settings.hideSelected : false;\n this.keepOrder + = settings.keepOrder !== void 0 ? settings.keepOrder : false;\n this.showOptionTooltips + = settings.showOptionTooltips !== void 0 ? settings.showOptionTooltips : false;\n + \ this.minSelected = settings.minSelected || 0;\n this.maxSelected + = settings.maxSelected || 1e3;\n this.timeoutDelay = settings.timeoutDelay + || 200;\n this.maxValuesShown = settings.maxValuesShown || 20;\n + \ this.maxValuesMessage = settings.maxValuesMessage || \"{number} + selected\";\n this.addableText = settings.addableText || 'Press \"Enter\" + to add {value}';\n }\n }\n class SlimSelect2 {\n constructor(config2) + {\n var _a3;\n this.events = {\n search: void + 0,\n searchFilter: (opt, search) => {\n return opt.text.toLowerCase().indexOf(search.toLowerCase()) + !== -1;\n },\n addable: void 0,\n beforeChange: + void 0,\n afterChange: void 0,\n beforeOpen: void 0,\n + \ afterOpen: void 0,\n beforeClose: void 0,\n afterClose: + void 0\n };\n this.windowResize = debounce(() => {\n if + (!this.settings.isOpen && !this.settings.isFullOpen) {\n return;\n + \ }\n this.render.moveContent();\n });\n this.windowScroll + = debounce(() => {\n if (!this.settings.isOpen && !this.settings.isFullOpen) + {\n return;\n }\n this.render.moveContent();\n + \ });\n this.documentClick = (e2) => {\n if (!this.settings.isOpen) + {\n return;\n }\n if (e2.target && !hasClassInTree(e2.target, + this.settings.id)) {\n this.close(e2.type);\n }\n + \ };\n this.windowVisibilityChange = () => {\n if + (document.hidden) {\n this.close();\n }\n };\n + \ this.selectEl = typeof config2.select === \"string\" ? document.querySelector(config2.select) + : config2.select;\n if (!this.selectEl) {\n if (config2.events + && config2.events.error) {\n config2.events.error(new Error(\"Could + not find select element\"));\n }\n return;\n }\n + \ if (this.selectEl.tagName !== \"SELECT\") {\n if (config2.events + && config2.events.error) {\n config2.events.error(new Error(\"Element + isnt of type select\"));\n }\n return;\n }\n + \ if (this.selectEl.dataset.ssid) {\n this.destroy();\n + \ }\n this.settings = new Settings(config2.settings);\n this.cssClasses + = new CssClasses(config2.cssClasses);\n const debounceEvents = [\"afterChange\", + \"beforeOpen\", \"afterOpen\", \"beforeClose\", \"afterClose\"];\n for + (const key in config2.events) {\n if (!config2.events.hasOwnProperty(key)) + {\n continue;\n }\n if (debounceEvents.indexOf(key) + !== -1) {\n this.events[key] = debounce(config2.events[key], + 100);\n } else {\n this.events[key] = config2.events[key];\n + \ }\n }\n this.settings.disabled = ((_a3 = config2.settings) + === null || _a3 === void 0 ? void 0 : _a3.disabled) ? config2.settings.disabled + : this.selectEl.disabled;\n this.settings.isMultiple = this.selectEl.multiple;\n + \ this.settings.style = this.selectEl.style.cssText;\n this.settings.class + = this.selectEl.className.split(\" \");\n this.select = new Select(this.selectEl);\n + \ this.select.updateSelect(this.settings.id, this.settings.style, + this.settings.class);\n this.select.hideUI();\n this.select.onValueChange + = (options) => {\n this.setSelected(options.map((option) => option.id));\n + \ };\n this.select.onClassChange = (classes) => {\n this.settings.class + = classes;\n this.render.updateClassStyles();\n };\n this.select.onDisabledChange + = (disabled) => {\n if (disabled) {\n this.disable();\n + \ } else {\n this.enable();\n }\n };\n + \ this.select.onOptionsChange = (data) => {\n this.setData(data);\n + \ };\n this.store = new Store(this.settings.isMultiple ? + \"multiple\" : \"single\", config2.data ? config2.data : this.select.getData());\n + \ if (config2.data) {\n this.select.updateOptions(this.store.getData());\n + \ }\n const renderCallbacks = {\n open: this.open.bind(this),\n + \ close: this.close.bind(this),\n addable: this.events.addable + ? this.events.addable : void 0,\n setSelected: this.setSelected.bind(this),\n + \ addOption: this.addOption.bind(this),\n search: this.search.bind(this),\n + \ beforeChange: this.events.beforeChange,\n afterChange: + this.events.afterChange\n };\n this.render = new Render(this.settings, + this.cssClasses, this.store, renderCallbacks);\n this.render.renderValues();\n + \ this.render.renderOptions(this.store.getData());\n const + selectAriaLabel = this.selectEl.getAttribute(\"aria-label\");\n const + selectAriaLabelledBy = this.selectEl.getAttribute(\"aria-labelledby\");\n + \ if (selectAriaLabel) {\n this.render.main.main.setAttribute(\"aria-label\", + selectAriaLabel);\n } else if (selectAriaLabelledBy) {\n this.render.main.main.setAttribute(\"aria-labelledby\", + selectAriaLabelledBy);\n }\n if (this.selectEl.parentNode) + {\n this.selectEl.parentNode.insertBefore(this.render.main.main, + this.selectEl.nextSibling);\n }\n window.addEventListener(\"resize\", + this.windowResize, false);\n if (this.settings.openPosition === \"auto\") + {\n window.addEventListener(\"scroll\", this.windowScroll, false);\n + \ }\n document.addEventListener(\"visibilitychange\", this.windowVisibilityChange);\n + \ if (this.settings.disabled) {\n this.disable();\n }\n + \ if (this.settings.alwaysOpen) {\n this.open();\n }\n + \ this.selectEl.slim = this;\n }\n enable() {\n this.settings.disabled + = false;\n this.select.enable();\n this.render.enable();\n + \ }\n disable() {\n this.settings.disabled = true;\n + \ this.select.disable();\n this.render.disable();\n }\n + \ getData() {\n return this.store.getData();\n }\n setData(data) + {\n const selected = this.store.getSelected();\n const err + = this.store.validateDataArray(data);\n if (err) {\n if + (this.events.error) {\n this.events.error(err);\n }\n + \ return;\n }\n this.store.setData(data);\n const + dataClean = this.store.getData();\n this.select.updateOptions(dataClean);\n + \ this.render.renderValues();\n this.render.renderOptions(dataClean);\n + \ if (this.events.afterChange && !isEqual2(selected, this.store.getSelected())) + {\n this.events.afterChange(this.store.getSelectedOptions());\n + \ }\n }\n getSelected() {\n let options = this.store.getSelectedOptions();\n + \ if (this.settings.keepOrder) {\n options = this.store.selectedOrderOptions(options);\n + \ }\n return options.map((option) => option.value);\n }\n + \ setSelected(values, runAfterChange = true) {\n const selected + = this.store.getSelected();\n const options = this.store.getDataOptions();\n + \ values = Array.isArray(values) ? values : [values];\n const + ids = [];\n for (const value of values) {\n if (options.find((option) + => option.id == value)) {\n ids.push(value);\n continue;\n + \ }\n for (const option of options.filter((option2) => + option2.value == value)) {\n ids.push(option.id);\n }\n + \ }\n this.store.setSelectedBy(\"id\", ids);\n const + data = this.store.getData();\n this.select.updateOptions(data);\n + \ this.render.renderValues();\n if (this.render.content.search.input.value + !== \"\") {\n this.search(this.render.content.search.input.value);\n + \ } else {\n this.render.renderOptions(data);\n }\n + \ if (runAfterChange && this.events.afterChange && !isEqual2(selected, + this.store.getSelected())) {\n this.events.afterChange(this.store.getSelectedOptions());\n + \ }\n }\n addOption(option) {\n const selected + = this.store.getSelected();\n if (!this.store.getDataOptions().some((o2) + => {\n var _a3;\n return o2.value === ((_a3 = option.value) + !== null && _a3 !== void 0 ? _a3 : option.text);\n })) {\n this.store.addOption(option);\n + \ }\n const data = this.store.getData();\n this.select.updateOptions(data);\n + \ this.render.renderValues();\n this.render.renderOptions(data);\n + \ if (this.events.afterChange && !isEqual2(selected, this.store.getSelected())) + {\n this.events.afterChange(this.store.getSelectedOptions());\n + \ }\n }\n open() {\n if (this.settings.disabled + || this.settings.isOpen) {\n return;\n }\n if + (this.events.beforeOpen) {\n this.events.beforeOpen();\n }\n + \ this.render.open();\n if (this.settings.showSearch && this.settings.focusSearch) + {\n this.render.searchFocus();\n }\n this.settings.isOpen + = true;\n setTimeout(() => {\n if (this.events.afterOpen) + {\n this.events.afterOpen();\n }\n if (this.settings.isOpen) + {\n this.settings.isFullOpen = true;\n }\n document.addEventListener(\"click\", + this.documentClick);\n }, this.settings.timeoutDelay);\n if + (this.settings.contentPosition === \"absolute\") {\n if (this.settings.intervalMove) + {\n clearInterval(this.settings.intervalMove);\n }\n + \ this.settings.intervalMove = setInterval(this.render.moveContent.bind(this.render), + 500);\n }\n }\n close(eventType = null) {\n if + (!this.settings.isOpen || this.settings.alwaysOpen) {\n return;\n + \ }\n if (this.events.beforeClose) {\n this.events.beforeClose();\n + \ }\n this.render.close();\n if (this.render.content.search.input.value + !== \"\") {\n this.search(\"\");\n }\n this.render.mainFocus(eventType);\n + \ this.settings.isOpen = false;\n this.settings.isFullOpen + = false;\n setTimeout(() => {\n if (this.events.afterClose) + {\n this.events.afterClose();\n }\n document.removeEventListener(\"click\", + this.documentClick);\n }, this.settings.timeoutDelay);\n if + (this.settings.intervalMove) {\n clearInterval(this.settings.intervalMove);\n + \ }\n }\n search(value) {\n if (this.render.content.search.input.value + !== value) {\n this.render.content.search.input.value = value;\n + \ }\n if (!this.events.search) {\n this.render.renderOptions(value + === \"\" ? this.store.getData() : this.store.search(value, this.events.searchFilter));\n + \ return;\n }\n this.render.renderSearching();\n + \ const searchResp = this.events.search(value, this.store.getSelectedOptions());\n + \ if (searchResp instanceof Promise) {\n searchResp.then((data) + => {\n this.render.renderOptions(this.store.partialToFullData(data));\n + \ }).catch((err) => {\n this.render.renderError(typeof + err === \"string\" ? err : err.message);\n });\n return;\n + \ } else if (Array.isArray(searchResp)) {\n this.render.renderOptions(this.store.partialToFullData(searchResp));\n + \ } else {\n this.render.renderError(\"Search event must + return a promise or an array of data\");\n }\n }\n destroy() + {\n document.removeEventListener(\"click\", this.documentClick);\n + \ window.removeEventListener(\"resize\", this.windowResize, false);\n + \ if (this.settings.openPosition === \"auto\") {\n window.removeEventListener(\"scroll\", + this.windowScroll, false);\n }\n document.removeEventListener(\"visibilitychange\", + this.windowVisibilityChange);\n this.store.setData([]);\n this.render.destroy();\n + \ this.select.destroy();\n }\n }\n return SlimSelect2;\n + \ });\n })(slimselect$1);\n return slimselect$1.exports;\n}\nvar slimselectExports + = requireSlimselect();\nconst SlimSelect = /* @__PURE__ */ getDefaultExportFromCjs(slimselectExports);\nconst + AutocompletionMixin = {\n name: \"autocompletion-mixin\",\n use: [TranslationMixin],\n + \ attributes: {\n searchText: {\n type: String,\n default: null,\n + \ callback: function(newValue) {\n this.addToAttributes(newValue, + \"searchText\");\n }\n },\n searchPlaceholder: {\n type: String,\n + \ default: null,\n callback: function(newValue) {\n this.addToAttributes(newValue, + \"searchPlaceholder\");\n }\n }\n },\n initialState: {\n slimSelect: + null\n },\n created() {\n importInlineCSS(\n \"slimselect-base\",\n + \ () => import(\"./slimselect-CT2Oyr_0.js\")\n );\n this.slimSelect + = null;\n this.addToAttributes(true, \"autocomplete\");\n this.listCallbacks.attach(\n + \ this.addCallback.bind(this),\n \"AutocompletionMixin:addCallback\"\n + \ );\n },\n detached() {\n var _a3;\n (_a3 = this.slimSelect) == + null ? void 0 : _a3.destroy();\n this.slimSelect = null;\n },\n addCallback(value, + listCallbacks) {\n asyncQuerySelector(\"select:has(option)\", this.element).then((select) + => {\n this.initSlimSelect(select);\n });\n const nextProcessor + = listCallbacks.shift();\n if (nextProcessor) nextProcessor(value, listCallbacks);\n + \ },\n async initSlimSelect(select) {\n var _a3;\n (_a3 = this.slimSelect) + == null ? void 0 : _a3.destroy();\n const slimSelect = new SlimSelect({\n + \ select,\n settings: {\n contentPosition: \"fixed\",\n placeholderText: + this.placeholder || this.t(\"autocompletion.placeholder\"),\n searchText: + this.searchText || this.t(\"autocompletion.searchText\"),\n searchPlaceholder: + this.searchPlaceholder || this.t(\"autocompletion.searchPlaceholder\"),\n + \ contentLocation: this.element\n },\n events: {\n afterChange: + () => {\n this.element.dispatchEvent(new Event(\"input\", { bubbles: + true }));\n },\n searchFilter: (option, filterValue) => fuzzyCompare(option.text, + filterValue)\n }\n });\n this.slimSelect = slimSelect;\n this.element.addEventListener(\"input\", + (e2) => {\n if (e2.target !== this.element) {\n e2.stopPropagation();\n + \ }\n });\n }\n};\nvar tinymce$2 = { exports: {} };\nvar hasRequiredTinymce;\nfunction + requireTinymce() {\n if (hasRequiredTinymce) return tinymce$2.exports;\n + \ hasRequiredTinymce = 1;\n (function(module2) {\n (function() {\n var + typeOf$1 = function(x2) {\n if (x2 === null) {\n return \"null\";\n + \ }\n if (x2 === void 0) {\n return \"undefined\";\n + \ }\n var t2 = typeof x2;\n if (t2 === \"object\" && (Array.prototype.isPrototypeOf(x2) + || x2.constructor && x2.constructor.name === \"Array\")) {\n return + \"array\";\n }\n if (t2 === \"object\" && (String.prototype.isPrototypeOf(x2) + || x2.constructor && x2.constructor.name === \"String\")) {\n return + \"string\";\n }\n return t2;\n };\n var isEquatableType + = function(x2) {\n return [\n \"undefined\",\n \"boolean\",\n + \ \"number\",\n \"string\",\n \"function\",\n \"xml\",\n + \ \"null\"\n ].indexOf(x2) !== -1;\n };\n var sort$1 + = function(xs, compareFn) {\n var clone2 = Array.prototype.slice.call(xs);\n + \ return clone2.sort(compareFn);\n };\n var contramap = function(eqa, + f2) {\n return eq$2(function(x2, y2) {\n return eqa.eq(f2(x2), + f2(y2));\n });\n };\n var eq$2 = function(f2) {\n return + { eq: f2 };\n };\n var tripleEq = eq$2(function(x2, y2) {\n return + x2 === y2;\n });\n var eqString = tripleEq;\n var eqArray = + function(eqa) {\n return eq$2(function(x2, y2) {\n if (x2.length + !== y2.length) {\n return false;\n }\n var len + = x2.length;\n for (var i3 = 0; i3 < len; i3++) {\n if + (!eqa.eq(x2[i3], y2[i3])) {\n return false;\n }\n + \ }\n return true;\n });\n };\n var eqSortedArray + = function(eqa, compareFn) {\n return contramap(eqArray(eqa), function(xs) + {\n return sort$1(xs, compareFn);\n });\n };\n var + eqRecord = function(eqa) {\n return eq$2(function(x2, y2) {\n var + kx = Object.keys(x2);\n var ky = Object.keys(y2);\n if (!eqSortedArray(eqString).eq(kx, + ky)) {\n return false;\n }\n var len = kx.length;\n + \ for (var i3 = 0; i3 < len; i3++) {\n var q = kx[i3];\n + \ if (!eqa.eq(x2[q], y2[q])) {\n return false;\n }\n + \ }\n return true;\n });\n };\n var eqAny + = eq$2(function(x2, y2) {\n if (x2 === y2) {\n return true;\n + \ }\n var tx = typeOf$1(x2);\n var ty = typeOf$1(y2);\n + \ if (tx !== ty) {\n return false;\n }\n if (isEquatableType(tx)) + {\n return x2 === y2;\n } else if (tx === \"array\") {\n return + eqArray(eqAny).eq(x2, y2);\n } else if (tx === \"object\") {\n return + eqRecord(eqAny).eq(x2, y2);\n }\n return false;\n });\n + \ const getPrototypeOf$1 = Object.getPrototypeOf;\n const hasProto + = (v2, constructor, predicate) => {\n var _a3;\n if (predicate(v2, + constructor.prototype)) {\n return true;\n } else {\n return + ((_a3 = v2.constructor) === null || _a3 === void 0 ? void 0 : _a3.name) === + constructor.name;\n }\n };\n const typeOf = (x2) => {\n const + t2 = typeof x2;\n if (x2 === null) {\n return \"null\";\n + \ } else if (t2 === \"object\" && Array.isArray(x2)) {\n return + \"array\";\n } else if (t2 === \"object\" && hasProto(x2, String, (o2, + proto) => proto.isPrototypeOf(o2))) {\n return \"string\";\n } + else {\n return t2;\n }\n };\n const isType$1 = + (type2) => (value2) => typeOf(value2) === type2;\n const isSimpleType + = (type2) => (value2) => typeof value2 === type2;\n const eq$1 = (t2) + => (a2) => t2 === a2;\n const is$4 = (value2, constructor) => isObject2(value2) + && hasProto(value2, constructor, (o2, proto) => getPrototypeOf$1(o2) === proto);\n + \ const isString2 = isType$1(\"string\");\n const isObject2 = isType$1(\"object\");\n + \ const isPlainObject2 = (value2) => is$4(value2, Object);\n const + isArray$1 = isType$1(\"array\");\n const isNull = eq$1(null);\n const + isBoolean2 = isSimpleType(\"boolean\");\n const isUndefined2 = eq$1(void + 0);\n const isNullable = (a2) => a2 === null || a2 === void 0;\n const + isNonNullable = (a2) => !isNullable(a2);\n const isFunction2 = isSimpleType(\"function\");\n + \ const isNumber2 = isSimpleType(\"number\");\n const isArrayOf = + (value2, pred) => {\n if (isArray$1(value2)) {\n for (let + i3 = 0, len = value2.length; i3 < len; ++i3) {\n if (!pred(value2[i3])) + {\n return false;\n }\n }\n return + true;\n }\n return false;\n };\n const noop = () => + {\n };\n const compose = (fa, fb) => {\n return (...args) + => {\n return fa(fb.apply(null, args));\n };\n };\n const + compose1 = (fbc, fab) => (a2) => fbc(fab(a2));\n const constant2 = (value2) + => {\n return () => {\n return value2;\n };\n };\n + \ const identity2 = (x2) => {\n return x2;\n };\n const + tripleEquals = (a2, b2) => {\n return a2 === b2;\n };\n function + curry(fn, ...initialArgs) {\n return (...restArgs) => {\n const + all2 = initialArgs.concat(restArgs);\n return fn.apply(null, all2);\n + \ };\n }\n const not = (f2) => (t2) => !f2(t2);\n const + die = (msg) => {\n return () => {\n throw new Error(msg);\n + \ };\n };\n const apply$1 = (f2) => {\n return f2();\n + \ };\n const call = (f2) => {\n f2();\n };\n const + never = constant2(false);\n const always = constant2(true);\n class + Optional {\n constructor(tag, value2) {\n this.tag = tag;\n + \ this.value = value2;\n }\n static some(value2) {\n + \ return new Optional(true, value2);\n }\n static none() + {\n return Optional.singletonNone;\n }\n fold(onNone, + onSome) {\n if (this.tag) {\n return onSome(this.value);\n + \ } else {\n return onNone();\n }\n }\n + \ isSome() {\n return this.tag;\n }\n isNone() + {\n return !this.tag;\n }\n map(mapper) {\n if + (this.tag) {\n return Optional.some(mapper(this.value));\n } + else {\n return Optional.none();\n }\n }\n bind(binder2) + {\n if (this.tag) {\n return binder2(this.value);\n } + else {\n return Optional.none();\n }\n }\n exists(predicate) + {\n return this.tag && predicate(this.value);\n }\n forall(predicate) + {\n return !this.tag || predicate(this.value);\n }\n filter(predicate) + {\n if (!this.tag || predicate(this.value)) {\n return + this;\n } else {\n return Optional.none();\n }\n + \ }\n getOr(replacement) {\n return this.tag ? this.value + : replacement;\n }\n or(replacement) {\n return this.tag + ? this : replacement;\n }\n getOrThunk(thunk) {\n return + this.tag ? this.value : thunk();\n }\n orThunk(thunk) {\n return + this.tag ? this : thunk();\n }\n getOrDie(message) {\n if + (!this.tag) {\n throw new Error(message !== null && message !== + void 0 ? message : \"Called getOrDie on None\");\n } else {\n return + this.value;\n }\n }\n static from(value2) {\n return + isNonNullable(value2) ? Optional.some(value2) : Optional.none();\n }\n + \ getOrNull() {\n return this.tag ? this.value : null;\n }\n + \ getOrUndefined() {\n return this.value;\n }\n each(worker) + {\n if (this.tag) {\n worker(this.value);\n }\n + \ }\n toArray() {\n return this.tag ? [this.value] : + [];\n }\n toString() {\n return this.tag ? `some(${this.value})` + : \"none()\";\n }\n }\n Optional.singletonNone = new Optional(false);\n + \ const nativeSlice = Array.prototype.slice;\n const nativeIndexOf + = Array.prototype.indexOf;\n const nativePush = Array.prototype.push;\n + \ const rawIndexOf = (ts, t2) => nativeIndexOf.call(ts, t2);\n const + indexOf$1 = (xs, x2) => {\n const r4 = rawIndexOf(xs, x2);\n return + r4 === -1 ? Optional.none() : Optional.some(r4);\n };\n const contains$2 + = (xs, x2) => rawIndexOf(xs, x2) > -1;\n const exists = (xs, pred) => + {\n for (let i3 = 0, len = xs.length; i3 < len; i3++) {\n const + x2 = xs[i3];\n if (pred(x2, i3)) {\n return true;\n }\n + \ }\n return false;\n };\n const map$3 = (xs, f2) => + {\n const len = xs.length;\n const r4 = new Array(len);\n for + (let i3 = 0; i3 < len; i3++) {\n const x2 = xs[i3];\n r4[i3] + = f2(x2, i3);\n }\n return r4;\n };\n const each$g + = (xs, f2) => {\n for (let i3 = 0, len = xs.length; i3 < len; i3++) + {\n const x2 = xs[i3];\n f2(x2, i3);\n }\n };\n + \ const eachr = (xs, f2) => {\n for (let i3 = xs.length - 1; i3 + >= 0; i3--) {\n const x2 = xs[i3];\n f2(x2, i3);\n }\n + \ };\n const partition$2 = (xs, pred) => {\n const pass = + [];\n const fail = [];\n for (let i3 = 0, len = xs.length; i3 + < len; i3++) {\n const x2 = xs[i3];\n const arr = pred(x2, + i3) ? pass : fail;\n arr.push(x2);\n }\n return {\n + \ pass,\n fail\n };\n };\n const filter$6 + = (xs, pred) => {\n const r4 = [];\n for (let i3 = 0, len = + xs.length; i3 < len; i3++) {\n const x2 = xs[i3];\n if (pred(x2, + i3)) {\n r4.push(x2);\n }\n }\n return r4;\n + \ };\n const foldr = (xs, f2, acc) => {\n eachr(xs, (x2, i3) + => {\n acc = f2(acc, x2, i3);\n });\n return acc;\n + \ };\n const foldl = (xs, f2, acc) => {\n each$g(xs, (x2, + i3) => {\n acc = f2(acc, x2, i3);\n });\n return acc;\n + \ };\n const findUntil$1 = (xs, pred, until) => {\n for (let + i3 = 0, len = xs.length; i3 < len; i3++) {\n const x2 = xs[i3];\n + \ if (pred(x2, i3)) {\n return Optional.some(x2);\n } + else if (until(x2, i3)) {\n break;\n }\n }\n return + Optional.none();\n };\n const find$2 = (xs, pred) => {\n return + findUntil$1(xs, pred, never);\n };\n const findIndex$2 = (xs, pred) + => {\n for (let i3 = 0, len = xs.length; i3 < len; i3++) {\n const + x2 = xs[i3];\n if (pred(x2, i3)) {\n return Optional.some(i3);\n + \ }\n }\n return Optional.none();\n };\n const + flatten = (xs) => {\n const r4 = [];\n for (let i3 = 0, len + = xs.length; i3 < len; ++i3) {\n if (!isArray$1(xs[i3])) {\n throw + new Error(\"Arr.flatten item \" + i3 + \" was not an array, input: \" + xs);\n + \ }\n nativePush.apply(r4, xs[i3]);\n }\n return + r4;\n };\n const bind$3 = (xs, f2) => flatten(map$3(xs, f2));\n + \ const forall = (xs, pred) => {\n for (let i3 = 0, len = xs.length; + i3 < len; ++i3) {\n const x2 = xs[i3];\n if (pred(x2, i3) + !== true) {\n return false;\n }\n }\n return + true;\n };\n const reverse = (xs) => {\n const r4 = nativeSlice.call(xs, + 0);\n r4.reverse();\n return r4;\n };\n const difference + = (a1, a2) => filter$6(a1, (x2) => !contains$2(a2, x2));\n const mapToObject + = (xs, f2) => {\n const r4 = {};\n for (let i3 = 0, len = xs.length; + i3 < len; i3++) {\n const x2 = xs[i3];\n r4[String(x2)] + = f2(x2, i3);\n }\n return r4;\n };\n const sort = + (xs, comparator) => {\n const copy2 = nativeSlice.call(xs, 0);\n copy2.sort(comparator);\n + \ return copy2;\n };\n const get$b = (xs, i3) => i3 >= 0 && + i3 < xs.length ? Optional.some(xs[i3]) : Optional.none();\n const head + = (xs) => get$b(xs, 0);\n const last$3 = (xs) => get$b(xs, xs.length + - 1);\n const from = isFunction2(Array.from) ? Array.from : (x2) => nativeSlice.call(x2);\n + \ const findMap = (arr, f2) => {\n for (let i3 = 0; i3 < arr.length; + i3++) {\n const r4 = f2(arr[i3], i3);\n if (r4.isSome()) + {\n return r4;\n }\n }\n return Optional.none();\n + \ };\n const unique$1 = (xs, comparator) => {\n const r4 = + [];\n const isDuplicated = isFunction2(comparator) ? (x2) => exists(r4, + (i3) => comparator(i3, x2)) : (x2) => contains$2(r4, x2);\n for (let + i3 = 0, len = xs.length; i3 < len; i3++) {\n const x2 = xs[i3];\n + \ if (!isDuplicated(x2)) {\n r4.push(x2);\n }\n + \ }\n return r4;\n };\n const keys2 = Object.keys;\n + \ const hasOwnProperty$22 = Object.hasOwnProperty;\n const each$f + = (obj, f2) => {\n const props = keys2(obj);\n for (let k3 = + 0, len = props.length; k3 < len; k3++) {\n const i3 = props[k3];\n + \ const x2 = obj[i3];\n f2(x2, i3);\n }\n };\n + \ const map$2 = (obj, f2) => {\n return tupleMap(obj, (x2, i3) + => ({\n k: i3,\n v: f2(x2, i3)\n }));\n };\n + \ const tupleMap = (obj, f2) => {\n const r4 = {};\n each$f(obj, + (x2, i3) => {\n const tuple = f2(x2, i3);\n r4[tuple.k] + = tuple.v;\n });\n return r4;\n };\n const objAcc + = (r4) => (x2, i3) => {\n r4[i3] = x2;\n };\n const internalFilter + = (obj, pred, onTrue, onFalse) => {\n const r4 = {};\n each$f(obj, + (x2, i3) => {\n (pred(x2, i3) ? onTrue : onFalse)(x2, i3);\n });\n + \ return r4;\n };\n const bifilter = (obj, pred) => {\n const + t2 = {};\n const f2 = {};\n internalFilter(obj, pred, objAcc(t2), + objAcc(f2));\n return {\n t: t2,\n f: f2\n };\n + \ };\n const filter$5 = (obj, pred) => {\n const t2 = {};\n + \ internalFilter(obj, pred, objAcc(t2), noop);\n return t2;\n + \ };\n const mapToArray2 = (obj, f2) => {\n const r4 = [];\n + \ each$f(obj, (value2, name2) => {\n r4.push(f2(value2, name2));\n + \ });\n return r4;\n };\n const values = (obj) => {\n + \ return mapToArray2(obj, identity2);\n };\n const get$a = + (obj, key) => {\n return has$2(obj, key) ? Optional.from(obj[key]) + : Optional.none();\n };\n const has$2 = (obj, key) => hasOwnProperty$22.call(obj, + key);\n const hasNonNullableKey = (obj, key) => has$2(obj, key) && obj[key] + !== void 0 && obj[key] !== null;\n const equal$1 = (a1, a2, eq3 = eqAny) + => eqRecord(eq3).eq(a1, a2);\n const stringArray = (a2) => {\n const + all2 = {};\n each$g(a2, (key) => {\n all2[key] = {};\n });\n + \ return keys2(all2);\n };\n const isArray2 = Array.isArray;\n + \ const toArray$1 = (obj) => {\n if (!isArray2(obj)) {\n const + array2 = [];\n for (let i3 = 0, l2 = obj.length; i3 < l2; i3++) {\n + \ array2[i3] = obj[i3];\n }\n return array2;\n + \ } else {\n return obj;\n }\n };\n const + each$e = (o2, cb, s2) => {\n let n3, l2;\n if (!o2) {\n return + false;\n }\n s2 = s2 || o2;\n if (o2.length !== void + 0) {\n for (n3 = 0, l2 = o2.length; n3 < l2; n3++) {\n if + (cb.call(s2, o2[n3], n3, o2) === false) {\n return false;\n }\n + \ }\n } else {\n for (n3 in o2) {\n if + (has$2(o2, n3)) {\n if (cb.call(s2, o2[n3], n3, o2) === false) + {\n return false;\n }\n }\n }\n + \ }\n return true;\n };\n const map$1 = (array2, callback) + => {\n const out = [];\n each$e(array2, (item, index2) => {\n + \ out.push(callback(item, index2, array2));\n });\n return + out;\n };\n const filter$4 = (a2, f2) => {\n const o2 = [];\n + \ each$e(a2, (v2, index2) => {\n if (!f2 || f2(v2, index2, + a2)) {\n o2.push(v2);\n }\n });\n return + o2;\n };\n const indexOf = (a2, v2) => {\n if (a2) {\n for + (let i3 = 0, l2 = a2.length; i3 < l2; i3++) {\n if (a2[i3] === + v2) {\n return i3;\n }\n }\n }\n return + -1;\n };\n const reduce = (collection, iteratee, accumulator, thisArg) + => {\n let acc = isUndefined2(accumulator) ? collection[0] : accumulator;\n + \ for (let i3 = 0; i3 < collection.length; i3++) {\n acc = + iteratee.call(thisArg, acc, collection[i3], i3);\n }\n return + acc;\n };\n const findIndex$1 = (array2, predicate, thisArg) => + {\n let i3, l2;\n for (i3 = 0, l2 = array2.length; i3 < l2; + i3++) {\n if (predicate.call(thisArg, array2[i3], i3, array2)) {\n + \ return i3;\n }\n }\n return -1;\n };\n + \ const last$2 = (collection) => collection[collection.length - 1];\n + \ const cached = (f2) => {\n let called = false;\n let r4;\n + \ return (...args) => {\n if (!called) {\n called + = true;\n r4 = f2.apply(null, args);\n }\n return + r4;\n };\n };\n const DeviceType = (os2, browser2, userAgent2, + mediaMatch2) => {\n const isiPad = os2.isiOS() && /ipad/i.test(userAgent2) + === true;\n const isiPhone = os2.isiOS() && !isiPad;\n const + isMobile = os2.isiOS() || os2.isAndroid();\n const isTouch2 = isMobile + || mediaMatch2(\"(pointer:coarse)\");\n const isTablet2 = isiPad || + !isiPhone && isMobile && mediaMatch2(\"(min-device-width:768px)\");\n const + isPhone2 = isiPhone || isMobile && !isTablet2;\n const iOSwebview = + browser2.isSafari() && os2.isiOS() && /safari/i.test(userAgent2) === false;\n + \ const isDesktop = !isPhone2 && !isTablet2 && !iOSwebview;\n return + {\n isiPad: constant2(isiPad),\n isiPhone: constant2(isiPhone),\n + \ isTablet: constant2(isTablet2),\n isPhone: constant2(isPhone2),\n + \ isTouch: constant2(isTouch2),\n isAndroid: os2.isAndroid,\n + \ isiOS: os2.isiOS,\n isWebView: constant2(iOSwebview),\n + \ isDesktop: constant2(isDesktop)\n };\n };\n const + firstMatch = (regexes, s2) => {\n for (let i3 = 0; i3 < regexes.length; + i3++) {\n const x2 = regexes[i3];\n if (x2.test(s2)) {\n + \ return x2;\n }\n }\n return void 0;\n };\n + \ const find$1 = (regexes, agent) => {\n const r4 = firstMatch(regexes, + agent);\n if (!r4) {\n return {\n major: 0,\n minor: + 0\n };\n }\n const group = (i3) => {\n return + Number(agent.replace(r4, \"$\" + i3));\n };\n return nu$3(group(1), + group(2));\n };\n const detect$5 = (versionRegexes, agent) => {\n + \ const cleanedAgent = String(agent).toLowerCase();\n if (versionRegexes.length + === 0) {\n return unknown$2();\n }\n return find$1(versionRegexes, + cleanedAgent);\n };\n const unknown$2 = () => {\n return + nu$3(0, 0);\n };\n const nu$3 = (major, minor) => {\n return + {\n major,\n minor\n };\n };\n const Version + = {\n nu: nu$3,\n detect: detect$5,\n unknown: unknown$2\n + \ };\n const detectBrowser$1 = (browsers2, userAgentData) => {\n + \ return findMap(userAgentData.brands, (uaBrand) => {\n const + lcBrand = uaBrand.brand.toLowerCase();\n return find$2(browsers2, + (browser2) => {\n var _a3;\n return lcBrand === ((_a3 + = browser2.brand) === null || _a3 === void 0 ? void 0 : _a3.toLowerCase());\n + \ }).map((info) => ({\n current: info.name,\n version: + Version.nu(parseInt(uaBrand.version, 10), 0)\n }));\n });\n + \ };\n const detect$4 = (candidates, userAgent2) => {\n const + agent = String(userAgent2).toLowerCase();\n return find$2(candidates, + (candidate) => {\n return candidate.search(agent);\n });\n + \ };\n const detectBrowser = (browsers2, userAgent2) => {\n return + detect$4(browsers2, userAgent2).map((browser2) => {\n const version2 + = Version.detect(browser2.versionRegexes, userAgent2);\n return {\n + \ current: browser2.name,\n version: version2\n };\n + \ });\n };\n const detectOs = (oses2, userAgent2) => {\n return + detect$4(oses2, userAgent2).map((os2) => {\n const version2 = Version.detect(os2.versionRegexes, + userAgent2);\n return {\n current: os2.name,\n version: + version2\n };\n });\n };\n const removeFromStart + = (str, numChars) => {\n return str.substring(numChars);\n };\n + \ const checkRange = (str, substr, start2) => substr === \"\" || str.length + >= substr.length && str.substr(start2, start2 + substr.length) === substr;\n + \ const removeLeading = (str, prefix) => {\n return startsWith(str, + prefix) ? removeFromStart(str, prefix.length) : str;\n };\n const + contains$1 = (str, substr) => {\n return str.indexOf(substr) !== -1;\n + \ };\n const startsWith = (str, prefix) => {\n return checkRange(str, + prefix, 0);\n };\n const endsWith = (str, suffix) => {\n return + checkRange(str, suffix, str.length - suffix.length);\n };\n const + blank = (r4) => (s2) => s2.replace(r4, \"\");\n const trim$3 = blank(/^\\s+|\\s+$/g);\n + \ const lTrim = blank(/^\\s+/g);\n const rTrim = blank(/\\s+$/g);\n + \ const isNotEmpty = (s2) => s2.length > 0;\n const isEmpty$3 = (s2) + => !isNotEmpty(s2);\n const repeat = (s2, count2) => count2 <= 0 ? \"\" + : new Array(count2 + 1).join(s2);\n const toInt = (value2, radix = 10) + => {\n const num = parseInt(value2, radix);\n return isNaN(num) + ? Optional.none() : Optional.some(num);\n };\n const normalVersionRegex + = /.*?version\\/\\ ?([0-9]+)\\.([0-9]+).*/;\n const checkContains = (target) + => {\n return (uastring) => {\n return contains$1(uastring, + target);\n };\n };\n const browsers = [\n {\n name: + \"Edge\",\n versionRegexes: [/.*?edge\\/ ?([0-9]+)\\.([0-9]+)$/],\n + \ search: (uastring) => {\n return contains$1(uastring, + \"edge/\") && contains$1(uastring, \"chrome\") && contains$1(uastring, \"safari\") + && contains$1(uastring, \"applewebkit\");\n }\n },\n {\n + \ name: \"Chromium\",\n brand: \"Chromium\",\n versionRegexes: + [\n /.*?chrome\\/([0-9]+)\\.([0-9]+).*/,\n normalVersionRegex\n + \ ],\n search: (uastring) => {\n return contains$1(uastring, + \"chrome\") && !contains$1(uastring, \"chromeframe\");\n }\n },\n + \ {\n name: \"IE\",\n versionRegexes: [\n /.*?msie\\ + ?([0-9]+)\\.([0-9]+).*/,\n /.*?rv:([0-9]+)\\.([0-9]+).*/\n ],\n + \ search: (uastring) => {\n return contains$1(uastring, + \"msie\") || contains$1(uastring, \"trident\");\n }\n },\n + \ {\n name: \"Opera\",\n versionRegexes: [\n normalVersionRegex,\n + \ /.*?opera\\/([0-9]+)\\.([0-9]+).*/\n ],\n search: + checkContains(\"opera\")\n },\n {\n name: \"Firefox\",\n + \ versionRegexes: [/.*?firefox\\/\\ ?([0-9]+)\\.([0-9]+).*/],\n search: + checkContains(\"firefox\")\n },\n {\n name: \"Safari\",\n + \ versionRegexes: [\n normalVersionRegex,\n /.*?cpu + os ([0-9]+)_([0-9]+).*/\n ],\n search: (uastring) => {\n + \ return (contains$1(uastring, \"safari\") || contains$1(uastring, + \"mobile/\")) && contains$1(uastring, \"applewebkit\");\n }\n }\n + \ ];\n const oses = [\n {\n name: \"Windows\",\n + \ search: checkContains(\"win\"),\n versionRegexes: [/.*?windows\\ + nt\\ ?([0-9]+)\\.([0-9]+).*/]\n },\n {\n name: \"iOS\",\n + \ search: (uastring) => {\n return contains$1(uastring, + \"iphone\") || contains$1(uastring, \"ipad\");\n },\n versionRegexes: + [\n /.*?version\\/\\ ?([0-9]+)\\.([0-9]+).*/,\n /.*cpu + os ([0-9]+)_([0-9]+).*/,\n /.*cpu iphone os ([0-9]+)_([0-9]+).*/\n + \ ]\n },\n {\n name: \"Android\",\n search: + checkContains(\"android\"),\n versionRegexes: [/.*?android\\ ?([0-9]+)\\.([0-9]+).*/]\n + \ },\n {\n name: \"macOS\",\n search: checkContains(\"mac + os x\"),\n versionRegexes: [/.*?mac\\ os\\ x\\ ?([0-9]+)_([0-9]+).*/]\n + \ },\n {\n name: \"Linux\",\n search: checkContains(\"linux\"),\n + \ versionRegexes: []\n },\n {\n name: \"Solaris\",\n + \ search: checkContains(\"sunos\"),\n versionRegexes: []\n + \ },\n {\n name: \"FreeBSD\",\n search: checkContains(\"freebsd\"),\n + \ versionRegexes: []\n },\n {\n name: \"ChromeOS\",\n + \ search: checkContains(\"cros\"),\n versionRegexes: [/.*?chrome\\/([0-9]+)\\.([0-9]+).*/]\n + \ }\n ];\n const PlatformInfo = {\n browsers: constant2(browsers),\n + \ oses: constant2(oses)\n };\n const edge = \"Edge\";\n const + chromium = \"Chromium\";\n const ie = \"IE\";\n const opera = \"Opera\";\n + \ const firefox = \"Firefox\";\n const safari = \"Safari\";\n const + unknown$1 = () => {\n return nu$2({\n current: void 0,\n version: + Version.unknown()\n });\n };\n const nu$2 = (info) => {\n + \ const current = info.current;\n const version2 = info.version;\n + \ const isBrowser = (name2) => () => current === name2;\n return + {\n current,\n version: version2,\n isEdge: isBrowser(edge),\n + \ isChromium: isBrowser(chromium),\n isIE: isBrowser(ie),\n + \ isOpera: isBrowser(opera),\n isFirefox: isBrowser(firefox),\n + \ isSafari: isBrowser(safari)\n };\n };\n const Browser + = {\n unknown: unknown$1,\n nu: nu$2\n };\n const + windows = \"Windows\";\n const ios = \"iOS\";\n const android = + \"Android\";\n const linux = \"Linux\";\n const macos = \"macOS\";\n + \ const solaris = \"Solaris\";\n const freebsd = \"FreeBSD\";\n const + chromeos = \"ChromeOS\";\n const unknown = () => {\n return nu$1({\n + \ current: void 0,\n version: Version.unknown()\n });\n + \ };\n const nu$1 = (info) => {\n const current = info.current;\n + \ const version2 = info.version;\n const isOS = (name2) => () + => current === name2;\n return {\n current,\n version: + version2,\n isWindows: isOS(windows),\n isiOS: isOS(ios),\n + \ isAndroid: isOS(android),\n isMacOS: isOS(macos),\n isLinux: + isOS(linux),\n isSolaris: isOS(solaris),\n isFreeBSD: isOS(freebsd),\n + \ isChromeOS: isOS(chromeos)\n };\n };\n const OperatingSystem + = {\n unknown,\n nu: nu$1\n };\n const detect$3 = + (userAgent2, userAgentDataOpt, mediaMatch2) => {\n const browsers2 + = PlatformInfo.browsers();\n const oses2 = PlatformInfo.oses();\n const + browser2 = userAgentDataOpt.bind((userAgentData) => detectBrowser$1(browsers2, + userAgentData)).orThunk(() => detectBrowser(browsers2, userAgent2)).fold(Browser.unknown, + Browser.nu);\n const os2 = detectOs(oses2, userAgent2).fold(OperatingSystem.unknown, + OperatingSystem.nu);\n const deviceType2 = DeviceType(os2, browser2, + userAgent2, mediaMatch2);\n return {\n browser: browser2,\n + \ os: os2,\n deviceType: deviceType2\n };\n };\n + \ const PlatformDetection = { detect: detect$3 };\n const mediaMatch + = (query) => window.matchMedia(query).matches;\n let platform$2 = cached(() + => PlatformDetection.detect(navigator.userAgent, Optional.from(navigator.userAgentData), + mediaMatch));\n const detect$2 = () => platform$2();\n const userAgent + = navigator.userAgent;\n const platform$1 = detect$2();\n const + browser$1 = platform$1.browser;\n const os = platform$1.os;\n const + deviceType = platform$1.deviceType;\n const windowsPhone = userAgent.indexOf(\"Windows + Phone\") !== -1;\n const Env = {\n transparentSrc: \"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7\",\n + \ documentMode: browser$1.isIE() ? document.documentMode || 7 : 10,\n + \ cacheSuffix: null,\n container: null,\n canHaveCSP: + !browser$1.isIE(),\n windowsPhone,\n browser: {\n current: + browser$1.current,\n version: browser$1.version,\n isChromium: + browser$1.isChromium,\n isEdge: browser$1.isEdge,\n isFirefox: + browser$1.isFirefox,\n isIE: browser$1.isIE,\n isOpera: + browser$1.isOpera,\n isSafari: browser$1.isSafari\n },\n os: + {\n current: os.current,\n version: os.version,\n isAndroid: + os.isAndroid,\n isChromeOS: os.isChromeOS,\n isFreeBSD: + os.isFreeBSD,\n isiOS: os.isiOS,\n isLinux: os.isLinux,\n + \ isMacOS: os.isMacOS,\n isSolaris: os.isSolaris,\n isWindows: + os.isWindows\n },\n deviceType: {\n isDesktop: deviceType.isDesktop,\n + \ isiPad: deviceType.isiPad,\n isiPhone: deviceType.isiPhone,\n + \ isPhone: deviceType.isPhone,\n isTablet: deviceType.isTablet,\n + \ isTouch: deviceType.isTouch,\n isWebView: deviceType.isWebView\n + \ }\n };\n const whiteSpaceRegExp$1 = /^\\s*|\\s*$/g;\n const + trim$2 = (str) => {\n return str === null || str === void 0 ? \"\" + : (\"\" + str).replace(whiteSpaceRegExp$1, \"\");\n };\n const is$3 + = (obj, type2) => {\n if (!type2) {\n return obj !== void + 0;\n }\n if (type2 === \"array\" && isArray2(obj)) {\n return + true;\n }\n return typeof obj === type2;\n };\n const + makeMap$4 = (items, delim, map3) => {\n let i3;\n items = items + || [];\n delim = delim || \",\";\n if (typeof items === \"string\") + {\n items = items.split(delim);\n }\n map3 = map3 || + {};\n i3 = items.length;\n while (i3--) {\n map3[items[i3]] + = {};\n }\n return map3;\n };\n const hasOwnProperty$12 + = has$2;\n const extend$3 = (obj, ...exts) => {\n for (let i3 + = 0; i3 < exts.length; i3++) {\n const ext = exts[i3];\n for + (const name2 in ext) {\n if (has$2(ext, name2)) {\n const + value2 = ext[name2];\n if (value2 !== void 0) {\n obj[name2] + = value2;\n }\n }\n }\n }\n return + obj;\n };\n const walk$4 = function(o2, f2, n3, s2) {\n s2 + = s2 || this;\n if (o2) {\n if (n3) {\n o2 = o2[n3];\n + \ }\n each$e(o2, (o3, i3) => {\n if (f2.call(s2, + o3, i3, n3) === false) {\n return false;\n }\n walk$4(o3, + f2, n3, s2);\n });\n }\n };\n const resolve$2 = + (n3, o2) => {\n let i3, l2;\n o2 = o2 || window;\n n3 + = n3.split(\".\");\n for (i3 = 0, l2 = n3.length; i3 < l2; i3++) {\n + \ o2 = o2[n3[i3]];\n if (!o2) {\n break;\n }\n + \ }\n return o2;\n };\n const explode$3 = (s2, d2) + => {\n if (!s2 || is$3(s2, \"array\")) {\n return s2;\n }\n + \ return map$1(s2.split(d2 || \",\"), trim$2);\n };\n const + _addCacheSuffix = (url2) => {\n const cacheSuffix = Env.cacheSuffix;\n + \ if (cacheSuffix) {\n url2 += (url2.indexOf(\"?\") === -1 + ? \"?\" : \"&\") + cacheSuffix;\n }\n return url2;\n };\n + \ const Tools = {\n trim: trim$2,\n isArray: isArray2,\n + \ is: is$3,\n toArray: toArray$1,\n makeMap: makeMap$4,\n + \ each: each$e,\n map: map$1,\n grep: filter$4,\n inArray: + indexOf,\n hasOwn: hasOwnProperty$12,\n extend: extend$3,\n + \ walk: walk$4,\n resolve: resolve$2,\n explode: explode$3,\n + \ _addCacheSuffix\n };\n const is$2 = (lhs, rhs, comparator + = tripleEquals) => lhs.exists((left) => comparator(left, rhs));\n const + cat = (arr) => {\n const r4 = [];\n const push = (x2) => {\n + \ r4.push(x2);\n };\n for (let i3 = 0; i3 < arr.length; + i3++) {\n arr[i3].each(push);\n }\n return r4;\n };\n + \ const lift2 = (oa, ob, f2) => oa.isSome() && ob.isSome() ? Optional.some(f2(oa.getOrDie(), + ob.getOrDie())) : Optional.none();\n const lift3 = (oa, ob, oc, f2) => + oa.isSome() && ob.isSome() && oc.isSome() ? Optional.some(f2(oa.getOrDie(), + ob.getOrDie(), oc.getOrDie())) : Optional.none();\n const someIf = (b2, + a2) => b2 ? Optional.some(a2) : Optional.none();\n typeof window !== + \"undefined\" ? window : Function(\"return this;\")();\n const COMMENT + = 8;\n const DOCUMENT = 9;\n const DOCUMENT_FRAGMENT = 11;\n const + ELEMENT = 1;\n const TEXT = 3;\n const name = (element) => {\n const + r4 = element.dom.nodeName;\n return r4.toLowerCase();\n };\n const + type$1 = (element) => element.dom.nodeType;\n const isType = (t2) => + (element) => type$1(element) === t2;\n const isComment$1 = (element) + => type$1(element) === COMMENT || name(element) === \"#comment\";\n const + isElement$7 = isType(ELEMENT);\n const isText$9 = isType(TEXT);\n const + isDocument$2 = isType(DOCUMENT);\n const isDocumentFragment$1 = isType(DOCUMENT_FRAGMENT);\n + \ const isTag = (tag) => (e2) => isElement$7(e2) && name(e2) === tag;\n + \ const rawSet = (dom3, key, value2) => {\n if (isString2(value2) + || isBoolean2(value2) || isNumber2(value2)) {\n dom3.setAttribute(key, + value2 + \"\");\n } else {\n console.error(\"Invalid call + to Attribute.set. Key \", key, \":: Value \", value2, \":: Element \", dom3);\n + \ throw new Error(\"Attribute value was not simple\");\n }\n + \ };\n const set$2 = (element, key, value2) => {\n rawSet(element.dom, + key, value2);\n };\n const setAll$1 = (element, attrs) => {\n const + dom3 = element.dom;\n each$f(attrs, (v2, k3) => {\n rawSet(dom3, + k3, v2);\n });\n };\n const get$9 = (element, key) => {\n + \ const v2 = element.dom.getAttribute(key);\n return v2 === null + ? void 0 : v2;\n };\n const getOpt = (element, key) => Optional.from(get$9(element, + key));\n const has$1 = (element, key) => {\n const dom3 = element.dom;\n + \ return dom3 && dom3.hasAttribute ? dom3.hasAttribute(key) : false;\n + \ };\n const remove$a = (element, key) => {\n element.dom.removeAttribute(key);\n + \ };\n const hasNone = (element) => {\n const attrs = element.dom.attributes;\n + \ return attrs === void 0 || attrs === null || attrs.length === 0;\n + \ };\n const clone$4 = (element) => foldl(element.dom.attributes, + (acc, attr) => {\n acc[attr.name] = attr.value;\n return acc;\n + \ }, {});\n const read$4 = (element, attr) => {\n const value2 + = get$9(element, attr);\n return value2 === void 0 || value2 === \"\" + ? [] : value2.split(\" \");\n };\n const add$4 = (element, attr, + id) => {\n const old = read$4(element, attr);\n const nu2 = + old.concat([id]);\n set$2(element, attr, nu2.join(\" \"));\n return + true;\n };\n const remove$9 = (element, attr, id) => {\n const + nu2 = filter$6(read$4(element, attr), (v2) => v2 !== id);\n if (nu2.length + > 0) {\n set$2(element, attr, nu2.join(\" \"));\n } else {\n + \ remove$a(element, attr);\n }\n return false;\n };\n + \ const supports = (element) => element.dom.classList !== void 0;\n const + get$8 = (element) => read$4(element, \"class\");\n const add$3 = (element, + clazz) => add$4(element, \"class\", clazz);\n const remove$8 = (element, + clazz) => remove$9(element, \"class\", clazz);\n const toggle$2 = (element, + clazz) => {\n if (contains$2(get$8(element), clazz)) {\n return + remove$8(element, clazz);\n } else {\n return add$3(element, + clazz);\n }\n };\n const add$2 = (element, clazz) => {\n + \ if (supports(element)) {\n element.dom.classList.add(clazz);\n + \ } else {\n add$3(element, clazz);\n }\n };\n + \ const cleanClass = (element) => {\n const classList = supports(element) + ? element.dom.classList : get$8(element);\n if (classList.length === + 0) {\n remove$a(element, \"class\");\n }\n };\n const + remove$7 = (element, clazz) => {\n if (supports(element)) {\n const + classList = element.dom.classList;\n classList.remove(clazz);\n } + else {\n remove$8(element, clazz);\n }\n cleanClass(element);\n + \ };\n const toggle$1 = (element, clazz) => {\n const result + = supports(element) ? element.dom.classList.toggle(clazz) : toggle$2(element, + clazz);\n cleanClass(element);\n return result;\n };\n + \ const has2 = (element, clazz) => supports(element) && element.dom.classList.contains(clazz);\n + \ const isSupported$1 = (dom3) => dom3.style !== void 0 && isFunction2(dom3.style.getPropertyValue);\n + \ const fromHtml$1 = (html2, scope) => {\n const doc = scope || + document;\n const div = doc.createElement(\"div\");\n div.innerHTML + = html2;\n if (!div.hasChildNodes() || div.childNodes.length > 1) {\n + \ const message = \"HTML does not have a single root node\";\n console.error(message, + html2);\n throw new Error(message);\n }\n return fromDom$2(div.childNodes[0]);\n + \ };\n const fromTag = (tag, scope) => {\n const doc = scope + || document;\n const node = doc.createElement(tag);\n return + fromDom$2(node);\n };\n const fromText = (text3, scope) => {\n const + doc = scope || document;\n const node = doc.createTextNode(text3);\n + \ return fromDom$2(node);\n };\n const fromDom$2 = (node) + => {\n if (node === null || node === void 0) {\n throw new + Error(\"Node cannot be null or undefined\");\n }\n return { + dom: node };\n };\n const fromPoint$2 = (docElm, x2, y2) => Optional.from(docElm.dom.elementFromPoint(x2, + y2)).map(fromDom$2);\n const SugarElement = {\n fromHtml: fromHtml$1,\n + \ fromTag,\n fromText,\n fromDom: fromDom$2,\n fromPoint: + fromPoint$2\n };\n const toArray = (target, f2) => {\n const + r4 = [];\n const recurse = (e2) => {\n r4.push(e2);\n return + f2(e2);\n };\n let cur = f2(target);\n do {\n cur + = cur.bind(recurse);\n } while (cur.isSome());\n return r4;\n + \ };\n const is$1 = (element, selector) => {\n const dom3 + = element.dom;\n if (dom3.nodeType !== ELEMENT) {\n return + false;\n } else {\n const elem = dom3;\n if (elem.matches + !== void 0) {\n return elem.matches(selector);\n } else + if (elem.msMatchesSelector !== void 0) {\n return elem.msMatchesSelector(selector);\n + \ } else if (elem.webkitMatchesSelector !== void 0) {\n return + elem.webkitMatchesSelector(selector);\n } else if (elem.mozMatchesSelector + !== void 0) {\n return elem.mozMatchesSelector(selector);\n } + else {\n throw new Error(\"Browser lacks native selectors\");\n + \ }\n }\n };\n const bypassSelector = (dom3) => dom3.nodeType + !== ELEMENT && dom3.nodeType !== DOCUMENT && dom3.nodeType !== DOCUMENT_FRAGMENT + || dom3.childElementCount === 0;\n const all = (selector, scope) => {\n + \ const base2 = scope === void 0 ? document : scope.dom;\n return + bypassSelector(base2) ? [] : map$3(base2.querySelectorAll(selector), SugarElement.fromDom);\n + \ };\n const one = (selector, scope) => {\n const base2 = + scope === void 0 ? document : scope.dom;\n return bypassSelector(base2) + ? Optional.none() : Optional.from(base2.querySelector(selector)).map(SugarElement.fromDom);\n + \ };\n const eq2 = (e1, e2) => e1.dom === e2.dom;\n const contains2 + = (e1, e2) => {\n const d1 = e1.dom;\n const d2 = e2.dom;\n + \ return d1 === d2 ? false : d1.contains(d2);\n };\n const + owner$1 = (element) => SugarElement.fromDom(element.dom.ownerDocument);\n + \ const documentOrOwner = (dos) => isDocument$2(dos) ? dos : owner$1(dos);\n + \ const documentElement = (element) => SugarElement.fromDom(documentOrOwner(element).dom.documentElement);\n + \ const defaultView = (element) => SugarElement.fromDom(documentOrOwner(element).dom.defaultView);\n + \ const parent = (element) => Optional.from(element.dom.parentNode).map(SugarElement.fromDom);\n + \ const parentElement = (element) => Optional.from(element.dom.parentElement).map(SugarElement.fromDom);\n + \ const parents$1 = (element, isRoot) => {\n const stop2 = isFunction2(isRoot) + ? isRoot : never;\n let dom3 = element.dom;\n const ret = [];\n + \ while (dom3.parentNode !== null && dom3.parentNode !== void 0) {\n + \ const rawParent = dom3.parentNode;\n const p2 = SugarElement.fromDom(rawParent);\n + \ ret.push(p2);\n if (stop2(p2) === true) {\n break;\n + \ } else {\n dom3 = rawParent;\n }\n }\n + \ return ret;\n };\n const siblings = (element) => {\n const + filterSelf = (elements) => filter$6(elements, (x2) => !eq2(element, x2));\n + \ return parent(element).map(children).map(filterSelf).getOr([]);\n + \ };\n const prevSibling = (element) => Optional.from(element.dom.previousSibling).map(SugarElement.fromDom);\n + \ const nextSibling = (element) => Optional.from(element.dom.nextSibling).map(SugarElement.fromDom);\n + \ const prevSiblings = (element) => reverse(toArray(element, prevSibling));\n + \ const nextSiblings = (element) => toArray(element, nextSibling);\n const + children = (element) => map$3(element.dom.childNodes, SugarElement.fromDom);\n + \ const child$1 = (element, index2) => {\n const cs = element.dom.childNodes;\n + \ return Optional.from(cs[index2]).map(SugarElement.fromDom);\n };\n + \ const firstChild = (element) => child$1(element, 0);\n const lastChild + = (element) => child$1(element, element.dom.childNodes.length - 1);\n const + childNodesCount = (element) => element.dom.childNodes.length;\n const + getHead = (doc) => {\n const b2 = doc.dom.head;\n if (b2 === + null || b2 === void 0) {\n throw new Error(\"Head is not available + yet\");\n }\n return SugarElement.fromDom(b2);\n };\n const + isShadowRoot = (dos) => isDocumentFragment$1(dos) && isNonNullable(dos.dom.host);\n + \ const supported = isFunction2(Element.prototype.attachShadow) && isFunction2(Node.prototype.getRootNode);\n + \ const isSupported = constant2(supported);\n const getRootNode = + supported ? (e2) => SugarElement.fromDom(e2.dom.getRootNode()) : documentOrOwner;\n + \ const getStyleContainer = (dos) => isShadowRoot(dos) ? dos : getHead(documentOrOwner(dos));\n + \ const getShadowRoot = (e2) => {\n const r4 = getRootNode(e2);\n + \ return isShadowRoot(r4) ? Optional.some(r4) : Optional.none();\n };\n + \ const getShadowHost = (e2) => SugarElement.fromDom(e2.dom.host);\n const + getOriginalEventTarget = (event) => {\n if (isSupported() && isNonNullable(event.target)) + {\n const el = SugarElement.fromDom(event.target);\n if + (isElement$7(el) && isOpenShadowHost(el)) {\n if (event.composed + && event.composedPath) {\n const composedPath = event.composedPath();\n + \ if (composedPath) {\n return head(composedPath);\n + \ }\n }\n }\n }\n return Optional.from(event.target);\n + \ };\n const isOpenShadowHost = (element) => isNonNullable(element.dom.shadowRoot);\n + \ const inBody = (element) => {\n const dom3 = isText$9(element) + ? element.dom.parentNode : element.dom;\n if (dom3 === void 0 || dom3 + === null || dom3.ownerDocument === null) {\n return false;\n }\n + \ const doc = dom3.ownerDocument;\n return getShadowRoot(SugarElement.fromDom(dom3)).fold(() + => doc.body.contains(dom3), compose1(inBody, getShadowHost));\n };\n + \ const internalSet = (dom3, property, value2) => {\n if (!isString2(value2)) + {\n console.error(\"Invalid call to CSS.set. Property \", property, + \":: Value \", value2, \":: Element \", dom3);\n throw new Error(\"CSS + value must be a string: \" + value2);\n }\n if (isSupported$1(dom3)) + {\n dom3.style.setProperty(property, value2);\n }\n };\n + \ const internalRemove = (dom3, property) => {\n if (isSupported$1(dom3)) + {\n dom3.style.removeProperty(property);\n }\n };\n const + set$1 = (element, property, value2) => {\n const dom3 = element.dom;\n + \ internalSet(dom3, property, value2);\n };\n const setAll + = (element, css) => {\n const dom3 = element.dom;\n each$f(css, + (v2, k3) => {\n internalSet(dom3, k3, v2);\n });\n };\n + \ const get$7 = (element, property) => {\n const dom3 = element.dom;\n + \ const styles = window.getComputedStyle(dom3);\n const r4 = + styles.getPropertyValue(property);\n return r4 === \"\" && !inBody(element) + ? getUnsafeProperty(dom3, property) : r4;\n };\n const getUnsafeProperty + = (dom3, property) => isSupported$1(dom3) ? dom3.style.getPropertyValue(property) + : \"\";\n const getRaw$1 = (element, property) => {\n const dom3 + = element.dom;\n const raw = getUnsafeProperty(dom3, property);\n return + Optional.from(raw).filter((r4) => r4.length > 0);\n };\n const getAllRaw + = (element) => {\n const css = {};\n const dom3 = element.dom;\n + \ if (isSupported$1(dom3)) {\n for (let i3 = 0; i3 < dom3.style.length; + i3++) {\n const ruleName = dom3.style.item(i3);\n css[ruleName] + = dom3.style[ruleName];\n }\n }\n return css;\n };\n + \ const remove$6 = (element, property) => {\n const dom3 = element.dom;\n + \ internalRemove(dom3, property);\n if (is$2(getOpt(element, + \"style\").map(trim$3), \"\")) {\n remove$a(element, \"style\");\n + \ }\n };\n const reflow = (e2) => e2.dom.offsetWidth;\n const + before$3 = (marker, element) => {\n const parent$1 = parent(marker);\n + \ parent$1.each((v2) => {\n v2.dom.insertBefore(element.dom, + marker.dom);\n });\n };\n const after$4 = (marker, element) + => {\n const sibling2 = nextSibling(marker);\n sibling2.fold(() + => {\n const parent$1 = parent(marker);\n parent$1.each((v2) + => {\n append$1(v2, element);\n });\n }, (v2) => + {\n before$3(v2, element);\n });\n };\n const prepend + = (parent2, element) => {\n const firstChild$1 = firstChild(parent2);\n + \ firstChild$1.fold(() => {\n append$1(parent2, element);\n + \ }, (v2) => {\n parent2.dom.insertBefore(element.dom, v2.dom);\n + \ });\n };\n const append$1 = (parent2, element) => {\n parent2.dom.appendChild(element.dom);\n + \ };\n const wrap$2 = (element, wrapper) => {\n before$3(element, + wrapper);\n append$1(wrapper, element);\n };\n const after$3 + = (marker, elements) => {\n each$g(elements, (x2, i3) => {\n const + e2 = i3 === 0 ? marker : elements[i3 - 1];\n after$4(e2, x2);\n });\n + \ };\n const append = (parent2, elements) => {\n each$g(elements, + (x2) => {\n append$1(parent2, x2);\n });\n };\n const + empty = (element) => {\n element.dom.textContent = \"\";\n each$g(children(element), + (rogue) => {\n remove$5(rogue);\n });\n };\n const + remove$5 = (element) => {\n const dom3 = element.dom;\n if (dom3.parentNode + !== null) {\n dom3.parentNode.removeChild(dom3);\n }\n };\n + \ const unwrap = (wrapper) => {\n const children$1 = children(wrapper);\n + \ if (children$1.length > 0) {\n after$3(wrapper, children$1);\n + \ }\n remove$5(wrapper);\n };\n const fromHtml = (html2, + scope) => {\n const doc = scope || document;\n const div = doc.createElement(\"div\");\n + \ div.innerHTML = html2;\n return children(SugarElement.fromDom(div));\n + \ };\n const fromDom$1 = (nodes) => map$3(nodes, SugarElement.fromDom);\n + \ const get$6 = (element) => element.dom.innerHTML;\n const set2 + = (element, content) => {\n const owner2 = owner$1(element);\n const + docDom = owner2.dom;\n const fragment = SugarElement.fromDom(docDom.createDocumentFragment());\n + \ const contentElements = fromHtml(content, docDom);\n append(fragment, + contentElements);\n empty(element);\n append$1(element, fragment);\n + \ };\n const getOuter = (element) => {\n const container = + SugarElement.fromTag(\"div\");\n const clone2 = SugarElement.fromDom(element.dom.cloneNode(true));\n + \ append$1(container, clone2);\n return get$6(container);\n };\n + \ const mkEvent = (target, x2, y2, stop2, prevent, kill, raw) => ({\n + \ target,\n x: x2,\n y: y2,\n stop: stop2,\n prevent,\n + \ kill,\n raw\n });\n const fromRawEvent = (rawEvent) + => {\n const target = SugarElement.fromDom(getOriginalEventTarget(rawEvent).getOr(rawEvent.target));\n + \ const stop2 = () => rawEvent.stopPropagation();\n const prevent + = () => rawEvent.preventDefault();\n const kill = compose(prevent, + stop2);\n return mkEvent(target, rawEvent.clientX, rawEvent.clientY, + stop2, prevent, kill, rawEvent);\n };\n const handle$1 = (filter2, + handler) => (rawEvent) => {\n if (filter2(rawEvent)) {\n handler(fromRawEvent(rawEvent));\n + \ }\n };\n const binder = (element, event, filter2, handler, + useCapture) => {\n const wrapped = handle$1(filter2, handler);\n element.dom.addEventListener(event, + wrapped, useCapture);\n return { unbind: curry(unbind, element, event, + wrapped, useCapture) };\n };\n const bind$2 = (element, event, filter2, + handler) => binder(element, event, filter2, handler, false);\n const + unbind = (element, event, handler, useCapture) => {\n element.dom.removeEventListener(event, + handler, useCapture);\n };\n const r3 = (left, top) => {\n const + translate2 = (x2, y2) => r3(left + x2, top + y2);\n return {\n left,\n + \ top,\n translate: translate2\n };\n };\n const + SugarPosition = r3;\n const boxPosition = (dom3) => {\n const + box = dom3.getBoundingClientRect();\n return SugarPosition(box.left, + box.top);\n };\n const firstDefinedOrZero = (a2, b2) => {\n if + (a2 !== void 0) {\n return a2;\n } else {\n return + b2 !== void 0 ? b2 : 0;\n }\n };\n const absolute = (element) + => {\n const doc = element.dom.ownerDocument;\n const body = + doc.body;\n const win = doc.defaultView;\n const html2 = doc.documentElement;\n + \ if (body === element.dom) {\n return SugarPosition(body.offsetLeft, + body.offsetTop);\n }\n const scrollTop = firstDefinedOrZero(win + === null || win === void 0 ? void 0 : win.pageYOffset, html2.scrollTop);\n + \ const scrollLeft = firstDefinedOrZero(win === null || win === void + 0 ? void 0 : win.pageXOffset, html2.scrollLeft);\n const clientTop + = firstDefinedOrZero(html2.clientTop, body.clientTop);\n const clientLeft + = firstDefinedOrZero(html2.clientLeft, body.clientLeft);\n return viewport(element).translate(scrollLeft + - clientLeft, scrollTop - clientTop);\n };\n const viewport = (element) + => {\n const dom3 = element.dom;\n const doc = dom3.ownerDocument;\n + \ const body = doc.body;\n if (body === dom3) {\n return + SugarPosition(body.offsetLeft, body.offsetTop);\n }\n if (!inBody(element)) + {\n return SugarPosition(0, 0);\n }\n return boxPosition(dom3);\n + \ };\n const get$5 = (_DOC) => {\n const doc = _DOC !== void + 0 ? _DOC.dom : document;\n const x2 = doc.body.scrollLeft || doc.documentElement.scrollLeft;\n + \ const y2 = doc.body.scrollTop || doc.documentElement.scrollTop;\n + \ return SugarPosition(x2, y2);\n };\n const to = (x2, y2, + _DOC) => {\n const doc = _DOC !== void 0 ? _DOC.dom : document;\n const + win = doc.defaultView;\n if (win) {\n win.scrollTo(x2, y2);\n + \ }\n };\n const intoView = (element, alignToTop) => {\n const + isSafari = detect$2().browser.isSafari();\n if (isSafari && isFunction2(element.dom.scrollIntoViewIfNeeded)) + {\n element.dom.scrollIntoViewIfNeeded(false);\n } else {\n + \ element.dom.scrollIntoView(alignToTop);\n }\n };\n const + get$4 = (_win) => {\n const win = _win === void 0 ? window : _win;\n + \ if (detect$2().browser.isFirefox()) {\n return Optional.none();\n + \ } else {\n return Optional.from(win.visualViewport);\n }\n + \ };\n const bounds = (x2, y2, width, height) => ({\n x: x2,\n + \ y: y2,\n width,\n height,\n right: x2 + width,\n + \ bottom: y2 + height\n });\n const getBounds = (_win) => + {\n const win = _win === void 0 ? window : _win;\n const doc + = win.document;\n const scroll = get$5(SugarElement.fromDom(doc));\n + \ return get$4(win).fold(() => {\n const html2 = win.document.documentElement;\n + \ const width = html2.clientWidth;\n const height = html2.clientHeight;\n + \ return bounds(scroll.left, scroll.top, width, height);\n }, + (visualViewport) => bounds(Math.max(visualViewport.pageLeft, scroll.left), + Math.max(visualViewport.pageTop, scroll.top), visualViewport.width, visualViewport.height));\n + \ };\n const isNodeType = (type2) => {\n return (node) => + {\n return !!node && node.nodeType === type2;\n };\n };\n + \ const isRestrictedNode = (node) => !!node && !Object.getPrototypeOf(node);\n + \ const isElement$6 = isNodeType(1);\n const matchNodeNames = (names) + => {\n const lowercasedNames = names.map((s2) => s2.toLowerCase());\n + \ return (node) => {\n if (node && node.nodeName) {\n const + nodeName = node.nodeName.toLowerCase();\n return contains$2(lowercasedNames, + nodeName);\n }\n return false;\n };\n };\n const + matchStyleValues = (name2, values2) => {\n const items = values2.toLowerCase().split(\" + \");\n return (node) => {\n if (isElement$6(node)) {\n for + (let i3 = 0; i3 < items.length; i3++) {\n const computed = node.ownerDocument.defaultView.getComputedStyle(node, + null);\n const cssValue = computed ? computed.getPropertyValue(name2) + : null;\n if (cssValue === items[i3]) {\n return + true;\n }\n }\n }\n return false;\n + \ };\n };\n const hasAttribute = (attrName) => {\n return + (node) => {\n return isElement$6(node) && node.hasAttribute(attrName);\n + \ };\n };\n const hasAttributeValue = (attrName, attrValue) + => {\n return (node) => {\n return isElement$6(node) && node.getAttribute(attrName) + === attrValue;\n };\n };\n const isBogus$2 = (node) => isElement$6(node) + && node.hasAttribute(\"data-mce-bogus\");\n const isBogusAll$1 = (node) + => isElement$6(node) && node.getAttribute(\"data-mce-bogus\") === \"all\";\n + \ const isTable$3 = (node) => isElement$6(node) && node.tagName === \"TABLE\";\n + \ const hasContentEditableState = (value2) => {\n return (node) + => {\n if (isElement$6(node)) {\n if (node.contentEditable + === value2) {\n return true;\n }\n if (node.getAttribute(\"data-mce-contenteditable\") + === value2) {\n return true;\n }\n }\n return + false;\n };\n };\n const isTextareaOrInput = matchNodeNames([\n + \ \"textarea\",\n \"input\"\n ]);\n const isText$8 + = isNodeType(3);\n const isCData = isNodeType(4);\n const isPi = + isNodeType(7);\n const isComment = isNodeType(8);\n const isDocument$1 + = isNodeType(9);\n const isDocumentFragment = isNodeType(11);\n const + isBr$5 = matchNodeNames([\"br\"]);\n const isImg = matchNodeNames([\"img\"]);\n + \ const isContentEditableTrue$4 = hasContentEditableState(\"true\");\n + \ const isContentEditableFalse$a = hasContentEditableState(\"false\");\n + \ const isTableCell$5 = matchNodeNames([\n \"td\",\n \"th\"\n + \ ]);\n const isMedia$2 = matchNodeNames([\n \"video\",\n + \ \"audio\",\n \"object\",\n \"embed\"\n ]);\n const + browser = detect$2().browser;\n const firstElement = (nodes) => find$2(nodes, + isElement$7);\n const getTableCaptionDeltaY = (elm) => {\n if + (browser.isFirefox() && name(elm) === \"table\") {\n return firstElement(children(elm)).filter((elm2) + => {\n return name(elm2) === \"caption\";\n }).bind((caption) + => {\n return firstElement(nextSiblings(caption)).map((body) => + {\n const bodyTop = body.dom.offsetTop;\n const + captionTop = caption.dom.offsetTop;\n const captionHeight = caption.dom.offsetHeight;\n + \ return bodyTop <= captionTop ? -captionHeight : 0;\n });\n + \ }).getOr(0);\n } else {\n return 0;\n }\n + \ };\n const hasChild = (elm, child2) => elm.children && contains$2(elm.children, + child2);\n const getPos = (body, elm, rootElm) => {\n let x2 = + 0, y2 = 0;\n const doc = body.ownerDocument;\n rootElm = rootElm + ? rootElm : body;\n if (elm) {\n if (rootElm === body && elm.getBoundingClientRect + && get$7(SugarElement.fromDom(body), \"position\") === \"static\") {\n const + pos = elm.getBoundingClientRect();\n x2 = pos.left + (doc.documentElement.scrollLeft + || body.scrollLeft) - doc.documentElement.clientLeft;\n y2 = pos.top + + (doc.documentElement.scrollTop || body.scrollTop) - doc.documentElement.clientTop;\n + \ return {\n x: x2,\n y: y2\n };\n + \ }\n let offsetParent = elm;\n while (offsetParent + && offsetParent !== rootElm && offsetParent.nodeType && !hasChild(offsetParent, + rootElm)) {\n const castOffsetParent = offsetParent;\n x2 + += castOffsetParent.offsetLeft || 0;\n y2 += castOffsetParent.offsetTop + || 0;\n offsetParent = castOffsetParent.offsetParent;\n }\n + \ offsetParent = elm.parentNode;\n while (offsetParent && + offsetParent !== rootElm && offsetParent.nodeType && !hasChild(offsetParent, + rootElm)) {\n x2 -= offsetParent.scrollLeft || 0;\n y2 + -= offsetParent.scrollTop || 0;\n offsetParent = offsetParent.parentNode;\n + \ }\n y2 += getTableCaptionDeltaY(SugarElement.fromDom(elm));\n + \ }\n return {\n x: x2,\n y: y2\n };\n + \ };\n var ClosestOrAncestor = (is2, ancestor2, scope, a2, isRoot) + => {\n if (is2(scope, a2)) {\n return Optional.some(scope);\n + \ } else if (isFunction2(isRoot) && isRoot(scope)) {\n return + Optional.none();\n } else {\n return ancestor2(scope, a2, + isRoot);\n }\n };\n const ancestor$3 = (scope, predicate, + isRoot) => {\n let element = scope.dom;\n const stop2 = isFunction2(isRoot) + ? isRoot : never;\n while (element.parentNode) {\n element + = element.parentNode;\n const el = SugarElement.fromDom(element);\n + \ if (predicate(el)) {\n return Optional.some(el);\n } + else if (stop2(el)) {\n break;\n }\n }\n return + Optional.none();\n };\n const closest$4 = (scope, predicate, isRoot) + => {\n const is2 = (s2, test2) => test2(s2);\n return ClosestOrAncestor(is2, + ancestor$3, scope, predicate, isRoot);\n };\n const sibling$1 = + (scope, predicate) => {\n const element = scope.dom;\n if (!element.parentNode) + {\n return Optional.none();\n }\n return child(SugarElement.fromDom(element.parentNode), + (x2) => !eq2(scope, x2) && predicate(x2));\n };\n const child = + (scope, predicate) => {\n const pred = (node) => predicate(SugarElement.fromDom(node));\n + \ const result = find$2(scope.dom.childNodes, pred);\n return + result.map(SugarElement.fromDom);\n };\n const descendant$1 = (scope, + predicate) => {\n const descend2 = (node) => {\n for (let + i3 = 0; i3 < node.childNodes.length; i3++) {\n const child2 = SugarElement.fromDom(node.childNodes[i3]);\n + \ if (predicate(child2)) {\n return Optional.some(child2);\n + \ }\n const res = descend2(node.childNodes[i3]);\n if + (res.isSome()) {\n return res;\n }\n }\n + \ return Optional.none();\n };\n return descend2(scope.dom);\n + \ };\n const ancestor$2 = (scope, selector, isRoot) => ancestor$3(scope, + (e2) => is$1(e2, selector), isRoot);\n const descendant = (scope, selector) + => one(selector, scope);\n const closest$3 = (scope, selector, isRoot) + => {\n const is2 = (element, selector2) => is$1(element, selector2);\n + \ return ClosestOrAncestor(is2, ancestor$2, scope, selector, isRoot);\n + \ };\n const StyleSheetLoader = (documentOrShadowRoot, settings = + {}) => {\n let idCount = 0;\n const loadedStates = {};\n const + edos = SugarElement.fromDom(documentOrShadowRoot);\n const doc = documentOrOwner(edos);\n + \ const maxLoadTime = settings.maxLoadTime || 5e3;\n const _setReferrerPolicy + = (referrerPolicy) => {\n settings.referrerPolicy = referrerPolicy;\n + \ };\n const addStyle = (element) => {\n append$1(getStyleContainer(edos), + element);\n };\n const removeStyle = (id) => {\n const + styleContainer = getStyleContainer(edos);\n descendant(styleContainer, + \"#\" + id).each(remove$5);\n };\n const getOrCreateState = + (url2) => get$a(loadedStates, url2).getOrThunk(() => ({\n id: \"mce-u\" + + idCount++,\n passed: [],\n failed: [],\n count: + 0\n }));\n const load = (url2) => new Promise((success, failure) + => {\n let link2;\n const urlWithSuffix = Tools._addCacheSuffix(url2);\n + \ const state = getOrCreateState(urlWithSuffix);\n loadedStates[urlWithSuffix] + = state;\n state.count++;\n const resolve2 = (callbacks, + status) => {\n each$g(callbacks, call);\n state.status + = status;\n state.passed = [];\n state.failed = [];\n + \ if (link2) {\n link2.onload = null;\n link2.onerror + = null;\n link2 = null;\n }\n };\n const + passed = () => resolve2(state.passed, 2);\n const failed = () => + resolve2(state.failed, 3);\n const wait = (testCallback, waitCallback) + => {\n if (!testCallback()) {\n if (Date.now() - startTime + < maxLoadTime) {\n setTimeout(waitCallback);\n } + else {\n failed();\n }\n }\n };\n + \ const waitForWebKitLinkLoaded = () => {\n wait(() => + {\n const styleSheets = documentOrShadowRoot.styleSheets;\n let + i3 = styleSheets.length;\n while (i3--) {\n const + styleSheet = styleSheets[i3];\n const owner2 = styleSheet.ownerNode;\n + \ if (owner2 && owner2.id === link2.id) {\n passed();\n + \ return true;\n }\n }\n return + false;\n }, waitForWebKitLinkLoaded);\n };\n if + (success) {\n state.passed.push(success);\n }\n if + (failure) {\n state.failed.push(failure);\n }\n if + (state.status === 1) {\n return;\n }\n if (state.status + === 2) {\n passed();\n return;\n }\n if + (state.status === 3) {\n failed();\n return;\n }\n + \ state.status = 1;\n const linkElem = SugarElement.fromTag(\"link\", + doc.dom);\n setAll$1(linkElem, {\n rel: \"stylesheet\",\n + \ type: \"text/css\",\n id: state.id\n });\n + \ const startTime = Date.now();\n if (settings.contentCssCors) + {\n set$2(linkElem, \"crossOrigin\", \"anonymous\");\n }\n + \ if (settings.referrerPolicy) {\n set$2(linkElem, \"referrerpolicy\", + settings.referrerPolicy);\n }\n link2 = linkElem.dom;\n + \ link2.onload = waitForWebKitLinkLoaded;\n link2.onerror + = failed;\n addStyle(linkElem);\n set$2(linkElem, \"href\", + urlWithSuffix);\n });\n const loadAll = (urls) => {\n const + loadedUrls = Promise.allSettled(map$3(urls, (url2) => load(url2).then(constant2(url2))));\n + \ return loadedUrls.then((results) => {\n const parts = + partition$2(results, (r4) => r4.status === \"fulfilled\");\n if + (parts.fail.length > 0) {\n return Promise.reject(map$3(parts.fail, + (result) => result.reason));\n } else {\n return map$3(parts.pass, + (result) => result.value);\n }\n });\n };\n const + unload = (url2) => {\n const urlWithSuffix = Tools._addCacheSuffix(url2);\n + \ get$a(loadedStates, urlWithSuffix).each((state) => {\n const + count2 = --state.count;\n if (count2 === 0) {\n delete + loadedStates[urlWithSuffix];\n removeStyle(state.id);\n }\n + \ });\n };\n const unloadAll = (urls) => {\n each$g(urls, + (url2) => {\n unload(url2);\n });\n };\n return + {\n load,\n loadAll,\n unload,\n unloadAll,\n + \ _setReferrerPolicy\n };\n };\n const create$c = + () => {\n const map3 = /* @__PURE__ */ new WeakMap();\n const + forElement = (referenceElement, settings) => {\n const root2 = getRootNode(referenceElement);\n + \ const rootDom = root2.dom;\n return Optional.from(map3.get(rootDom)).getOrThunk(() + => {\n const sl = StyleSheetLoader(rootDom, settings);\n map3.set(rootDom, + sl);\n return sl;\n });\n };\n return { + forElement };\n };\n const instance = create$c();\n class DomTreeWalker + {\n constructor(startNode, rootNode) {\n this.node = startNode;\n + \ this.rootNode = rootNode;\n this.current = this.current.bind(this);\n + \ this.next = this.next.bind(this);\n this.prev = this.prev.bind(this);\n + \ this.prev2 = this.prev2.bind(this);\n }\n current() + {\n return this.node;\n }\n next(shallow2) {\n this.node + = this.findSibling(this.node, \"firstChild\", \"nextSibling\", shallow2);\n + \ return this.node;\n }\n prev(shallow2) {\n this.node + = this.findSibling(this.node, \"lastChild\", \"previousSibling\", shallow2);\n + \ return this.node;\n }\n prev2(shallow2) {\n this.node + = this.findPreviousNode(this.node, \"lastChild\", \"previousSibling\", shallow2);\n + \ return this.node;\n }\n findSibling(node, startName, + siblingName, shallow2) {\n let sibling2, parent2;\n if (node) + {\n if (!shallow2 && node[startName]) {\n return node[startName];\n + \ }\n if (node !== this.rootNode) {\n sibling2 + = node[siblingName];\n if (sibling2) {\n return + sibling2;\n }\n for (parent2 = node.parentNode; + parent2 && parent2 !== this.rootNode; parent2 = parent2.parentNode) {\n sibling2 + = parent2[siblingName];\n if (sibling2) {\n return + sibling2;\n }\n }\n }\n }\n + \ }\n findPreviousNode(node, startName, siblingName, shallow2) + {\n let sibling2, parent2, child2;\n if (node) {\n sibling2 + = node[siblingName];\n if (this.rootNode && sibling2 === this.rootNode) + {\n return;\n }\n if (sibling2) {\n if + (!shallow2) {\n for (child2 = sibling2[startName]; child2; + child2 = child2[startName]) {\n if (!child2[startName]) {\n + \ return child2;\n }\n }\n + \ }\n return sibling2;\n }\n parent2 + = node.parentNode;\n if (parent2 && parent2 !== this.rootNode) + {\n return parent2;\n }\n }\n }\n + \ }\n const blocks = [\n \"article\",\n \"aside\",\n + \ \"details\",\n \"div\",\n \"dt\",\n \"figcaption\",\n + \ \"footer\",\n \"form\",\n \"fieldset\",\n \"header\",\n + \ \"hgroup\",\n \"html\",\n \"main\",\n \"nav\",\n + \ \"section\",\n \"summary\",\n \"body\",\n \"p\",\n + \ \"dl\",\n \"multicol\",\n \"dd\",\n \"figure\",\n + \ \"address\",\n \"center\",\n \"blockquote\",\n \"h1\",\n + \ \"h2\",\n \"h3\",\n \"h4\",\n \"h5\",\n \"h6\",\n + \ \"listing\",\n \"xmp\",\n \"pre\",\n \"plaintext\",\n + \ \"menu\",\n \"dir\",\n \"ul\",\n \"ol\",\n \"li\",\n + \ \"hr\",\n \"table\",\n \"tbody\",\n \"thead\",\n + \ \"tfoot\",\n \"th\",\n \"tr\",\n \"td\",\n \"caption\"\n + \ ];\n const tableCells = [\n \"td\",\n \"th\"\n ];\n + \ const tableSections = [\n \"thead\",\n \"tbody\",\n \"tfoot\"\n + \ ];\n const textBlocks = [\n \"h1\",\n \"h2\",\n \"h3\",\n + \ \"h4\",\n \"h5\",\n \"h6\",\n \"p\",\n \"div\",\n + \ \"address\",\n \"pre\",\n \"form\",\n \"blockquote\",\n + \ \"center\",\n \"dir\",\n \"fieldset\",\n \"header\",\n + \ \"footer\",\n \"article\",\n \"section\",\n \"hgroup\",\n + \ \"aside\",\n \"nav\",\n \"figure\"\n ];\n const + headings = [\n \"h1\",\n \"h2\",\n \"h3\",\n \"h4\",\n + \ \"h5\",\n \"h6\"\n ];\n const listItems$1 = [\n \"li\",\n + \ \"dd\",\n \"dt\"\n ];\n const lists = [\n \"ul\",\n + \ \"ol\",\n \"dl\"\n ];\n const wsElements = [\n \"pre\",\n + \ \"script\",\n \"textarea\",\n \"style\"\n ];\n + \ const lazyLookup = (items) => {\n let lookup2;\n return + (node) => {\n lookup2 = lookup2 ? lookup2 : mapToObject(items, always);\n + \ return has$2(lookup2, name(node));\n };\n };\n const + isHeading = lazyLookup(headings);\n const isBlock$2 = lazyLookup(blocks);\n + \ const isTable$2 = (node) => name(node) === \"table\";\n const isInline$1 + = (node) => isElement$7(node) && !isBlock$2(node);\n const isBr$4 = (node) + => isElement$7(node) && name(node) === \"br\";\n const isTextBlock$2 + = lazyLookup(textBlocks);\n const isList = lazyLookup(lists);\n const + isListItem = lazyLookup(listItems$1);\n const isTableSection = lazyLookup(tableSections);\n + \ const isTableCell$4 = lazyLookup(tableCells);\n const isWsPreserveElement + = lazyLookup(wsElements);\n const ancestor$1 = (scope, selector, isRoot) + => ancestor$2(scope, selector, isRoot).isSome();\n const zeroWidth = + \"\\uFEFF\";\n const nbsp = \" \";\n const isZwsp$1 = (char) => + char === zeroWidth;\n const removeZwsp = (s2) => s2.replace(/\\uFEFF/g, + \"\");\n const ZWSP$1 = zeroWidth;\n const isZwsp = isZwsp$1;\n + \ const trim$1 = removeZwsp;\n const isElement$5 = isElement$6;\n + \ const isText$7 = isText$8;\n const isCaretContainerBlock$1 = (node) + => {\n if (isText$7(node)) {\n node = node.parentNode;\n }\n + \ return isElement$5(node) && node.hasAttribute(\"data-mce-caret\");\n + \ };\n const isCaretContainerInline = (node) => isText$7(node) && + isZwsp(node.data);\n const isCaretContainer$2 = (node) => isCaretContainerBlock$1(node) + || isCaretContainerInline(node);\n const hasContent = (node) => node.firstChild + !== node.lastChild || !isBr$5(node.firstChild);\n const insertInline$1 + = (node, before2) => {\n const doc = node.ownerDocument;\n const + textNode = doc.createTextNode(ZWSP$1);\n const parentNode = node.parentNode;\n + \ if (!before2) {\n const sibling2 = node.nextSibling;\n if + (isText$7(sibling2)) {\n if (isCaretContainer$2(sibling2)) {\n + \ return sibling2;\n }\n if (startsWithCaretContainer$1(sibling2)) + {\n sibling2.splitText(1);\n return sibling2;\n + \ }\n }\n if (node.nextSibling) {\n parentNode.insertBefore(textNode, + node.nextSibling);\n } else {\n parentNode.appendChild(textNode);\n + \ }\n } else {\n const sibling2 = node.previousSibling;\n + \ if (isText$7(sibling2)) {\n if (isCaretContainer$2(sibling2)) + {\n return sibling2;\n }\n if (endsWithCaretContainer$1(sibling2)) + {\n return sibling2.splitText(sibling2.data.length - 1);\n }\n + \ }\n parentNode.insertBefore(textNode, node);\n }\n + \ return textNode;\n };\n const isBeforeInline = (pos) => + {\n const container = pos.container();\n if (!isText$8(container)) + {\n return false;\n }\n return container.data.charAt(pos.offset()) + === ZWSP$1 || pos.isAtStart() && isCaretContainerInline(container.previousSibling);\n + \ };\n const isAfterInline = (pos) => {\n const container + = pos.container();\n if (!isText$8(container)) {\n return + false;\n }\n return container.data.charAt(pos.offset() - 1) + === ZWSP$1 || pos.isAtEnd() && isCaretContainerInline(container.nextSibling);\n + \ };\n const createBogusBr = () => {\n const br = document.createElement(\"br\");\n + \ br.setAttribute(\"data-mce-bogus\", \"1\");\n return br;\n + \ };\n const insertBlock = (blockName, node, before2) => {\n const + doc = node.ownerDocument;\n const blockNode = doc.createElement(blockName);\n + \ blockNode.setAttribute(\"data-mce-caret\", before2 ? \"before\" : + \"after\");\n blockNode.setAttribute(\"data-mce-bogus\", \"all\");\n + \ blockNode.appendChild(createBogusBr());\n const parentNode + = node.parentNode;\n if (!before2) {\n if (node.nextSibling) + {\n parentNode.insertBefore(blockNode, node.nextSibling);\n } + else {\n parentNode.appendChild(blockNode);\n }\n } + else {\n parentNode.insertBefore(blockNode, node);\n }\n return + blockNode;\n };\n const startsWithCaretContainer$1 = (node) => isText$7(node) + && node.data[0] === ZWSP$1;\n const endsWithCaretContainer$1 = (node) + => isText$7(node) && node.data[node.data.length - 1] === ZWSP$1;\n const + trimBogusBr = (elm) => {\n const brs = elm.getElementsByTagName(\"br\");\n + \ const lastBr = brs[brs.length - 1];\n if (isBogus$2(lastBr)) + {\n lastBr.parentNode.removeChild(lastBr);\n }\n };\n + \ const showCaretContainerBlock = (caretContainer) => {\n if (caretContainer + && caretContainer.hasAttribute(\"data-mce-caret\")) {\n trimBogusBr(caretContainer);\n + \ caretContainer.removeAttribute(\"data-mce-caret\");\n caretContainer.removeAttribute(\"data-mce-bogus\");\n + \ caretContainer.removeAttribute(\"style\");\n caretContainer.removeAttribute(\"data-mce-style\");\n + \ caretContainer.removeAttribute(\"_moz_abspos\");\n return + caretContainer;\n }\n return null;\n };\n const isRangeInCaretContainerBlock + = (range2) => isCaretContainerBlock$1(range2.startContainer);\n const + isContentEditableTrue$3 = isContentEditableTrue$4;\n const isContentEditableFalse$9 + = isContentEditableFalse$a;\n const isBr$3 = isBr$5;\n const isText$6 + = isText$8;\n const isInvalidTextElement = matchNodeNames([\n \"script\",\n + \ \"style\",\n \"textarea\"\n ]);\n const isAtomicInline + = matchNodeNames([\n \"img\",\n \"input\",\n \"textarea\",\n + \ \"hr\",\n \"iframe\",\n \"video\",\n \"audio\",\n + \ \"object\",\n \"embed\"\n ]);\n const isTable$1 = + matchNodeNames([\"table\"]);\n const isCaretContainer$1 = isCaretContainer$2;\n + \ const isCaretCandidate$3 = (node) => {\n if (isCaretContainer$1(node)) + {\n return false;\n }\n if (isText$6(node)) {\n return + !isInvalidTextElement(node.parentNode);\n }\n return isAtomicInline(node) + || isBr$3(node) || isTable$1(node) || isNonUiContentEditableFalse(node);\n + \ };\n const isUnselectable = (node) => isElement$6(node) && node.getAttribute(\"unselectable\") + === \"true\";\n const isNonUiContentEditableFalse = (node) => isUnselectable(node) + === false && isContentEditableFalse$9(node);\n const isInEditable = (node, + root2) => {\n for (node = node.parentNode; node && node !== root2; + node = node.parentNode) {\n if (isNonUiContentEditableFalse(node)) + {\n return false;\n }\n if (isContentEditableTrue$3(node)) + {\n return true;\n }\n }\n return true;\n + \ };\n const isAtomicContentEditableFalse = (node) => {\n if + (!isNonUiContentEditableFalse(node)) {\n return false;\n }\n + \ return foldl(from(node.getElementsByTagName(\"*\")), (result, elm) + => {\n return result || isContentEditableTrue$3(elm);\n }, + false) !== true;\n };\n const isAtomic$1 = (node) => isAtomicInline(node) + || isAtomicContentEditableFalse(node);\n const isEditableCaretCandidate$1 + = (node, root2) => isCaretCandidate$3(node) && isInEditable(node, root2);\n + \ const whiteSpaceRegExp = /^[ \\t\\r\\n]*$/;\n const isWhitespaceText + = (text3) => whiteSpaceRegExp.test(text3);\n const isCollapsibleWhitespace$1 + = (c2) => \" \\f\t\\v\".indexOf(c2) !== -1;\n const isNewLineChar = (c2) + => c2 === \"\\n\" || c2 === \"\\r\";\n const isNewline = (text3, idx) + => idx < text3.length && idx >= 0 ? isNewLineChar(text3[idx]) : false;\n const + normalize$4 = (text3, tabSpaces = 4, isStartOfContent = true, isEndOfContent + = true) => {\n const tabSpace = repeat(\" \", tabSpaces);\n const + normalizedText = text3.replace(/\\t/g, tabSpace);\n const result = + foldl(normalizedText, (acc, c2) => {\n if (isCollapsibleWhitespace$1(c2) + || c2 === nbsp) {\n if (acc.pcIsSpace || acc.str === \"\" && isStartOfContent + || acc.str.length === normalizedText.length - 1 && isEndOfContent || isNewline(normalizedText, + acc.str.length + 1)) {\n return {\n pcIsSpace: + false,\n str: acc.str + nbsp\n };\n } + else {\n return {\n pcIsSpace: true,\n str: + acc.str + \" \"\n };\n }\n } else {\n return + {\n pcIsSpace: isNewLineChar(c2),\n str: acc.str + + c2\n };\n }\n }, {\n pcIsSpace: false,\n + \ str: \"\"\n });\n return result.str;\n };\n const + hasWhitespacePreserveParent = (node, rootNode) => {\n const rootElement + = SugarElement.fromDom(rootNode);\n const startNode = SugarElement.fromDom(node);\n + \ return ancestor$1(startNode, \"pre,code\", curry(eq2, rootElement));\n + \ };\n const isWhitespace$1 = (node, rootNode) => {\n return + isText$8(node) && isWhitespaceText(node.data) && hasWhitespacePreserveParent(node, + rootNode) === false;\n };\n const isNamedAnchor = (node) => {\n + \ return isElement$6(node) && node.nodeName === \"A\" && !node.hasAttribute(\"href\") + && (node.hasAttribute(\"name\") || node.hasAttribute(\"id\"));\n };\n + \ const isContent$1 = (node, rootNode) => {\n return isCaretCandidate$3(node) + && isWhitespace$1(node, rootNode) === false || isNamedAnchor(node) || isBookmark(node);\n + \ };\n const isBookmark = hasAttribute(\"data-mce-bookmark\");\n + \ const isBogus$1 = hasAttribute(\"data-mce-bogus\");\n const isBogusAll + = hasAttributeValue(\"data-mce-bogus\", \"all\");\n const isEmptyNode + = (targetNode, skipBogus) => {\n let brCount = 0;\n if (isContent$1(targetNode, + targetNode)) {\n return false;\n } else {\n let node + = targetNode.firstChild;\n if (!node) {\n return true;\n + \ }\n const walker = new DomTreeWalker(node, targetNode);\n + \ do {\n if (skipBogus) {\n if (isBogusAll(node)) + {\n node = walker.next(true);\n continue;\n + \ }\n if (isBogus$1(node)) {\n node + = walker.next();\n continue;\n }\n }\n + \ if (isBr$5(node)) {\n brCount++;\n node + = walker.next();\n continue;\n }\n if (isContent$1(node, + targetNode)) {\n return false;\n }\n node + = walker.next();\n } while (node);\n return brCount <= 1;\n + \ }\n };\n const isEmpty$2 = (elm, skipBogus = true) => isEmptyNode(elm.dom, + skipBogus);\n const isSpan = (node) => node.nodeName.toLowerCase() === + \"span\";\n const isInlineContent = (node, root2) => isNonNullable(node) + && (isContent$1(node, root2) || isInline$1(SugarElement.fromDom(node)));\n + \ const surroundedByInlineContent = (node, root2) => {\n const + prev2 = new DomTreeWalker(node, root2).prev(false);\n const next2 = + new DomTreeWalker(node, root2).next(false);\n const prevIsInline = + isUndefined2(prev2) || isInlineContent(prev2, root2);\n const nextIsInline + = isUndefined2(next2) || isInlineContent(next2, root2);\n return prevIsInline + && nextIsInline;\n };\n const isBookmarkNode$2 = (node) => isSpan(node) + && node.getAttribute(\"data-mce-type\") === \"bookmark\";\n const isKeepTextNode + = (node, root2) => isText$8(node) && node.data.length > 0 && surroundedByInlineContent(node, + root2);\n const isKeepElement = (node) => isElement$6(node) ? node.childNodes.length + > 0 : false;\n const isDocument = (node) => isDocumentFragment(node) + || isDocument$1(node);\n const trimNode = (dom3, node, root2) => {\n + \ const rootNode = root2 || node;\n if (isElement$6(node) && + isBookmarkNode$2(node)) {\n return node;\n }\n const + children2 = node.childNodes;\n for (let i3 = children2.length - 1; + i3 >= 0; i3--) {\n trimNode(dom3, children2[i3], rootNode);\n }\n + \ if (isElement$6(node)) {\n const currentChildren = node.childNodes;\n + \ if (currentChildren.length === 1 && isBookmarkNode$2(currentChildren[0])) + {\n node.parentNode.insertBefore(currentChildren[0], node);\n }\n + \ }\n if (!isDocument(node) && !isContent$1(node, rootNode) && + !isKeepElement(node) && !isKeepTextNode(node, rootNode)) {\n dom3.remove(node);\n + \ }\n return node;\n };\n const makeMap$3 = Tools.makeMap;\n + \ const attrsCharsRegExp = /[&<>\\\"\\u0060\\u007E-\\uD7FF\\uE000-\\uFFEF]|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]/g;\n + \ const textCharsRegExp = /[<>&\\u007E-\\uD7FF\\uE000-\\uFFEF]|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]/g;\n + \ const rawCharsRegExp = /[<>&\\\"\\']/g;\n const entityRegExp = + /&#([a-z0-9]+);?|&([a-z0-9]+);/gi;\n const asciiMap = {\n 128: + \"€\",\n 130: \"‚\",\n 131: \"ƒ\",\n 132: \"„\",\n 133: + \"…\",\n 134: \"†\",\n 135: \"‡\",\n 136: \"ˆ\",\n 137: + \"‰\",\n 138: \"Š\",\n 139: \"‹\",\n 140: \"Œ\",\n 142: + \"Ž\",\n 145: \"‘\",\n 146: \"’\",\n 147: \"“\",\n 148: + \"”\",\n 149: \"•\",\n 150: \"–\",\n 151: \"—\",\n 152: + \"˜\",\n 153: \"™\",\n 154: \"š\",\n 155: \"›\",\n 156: + \"œ\",\n 158: \"ž\",\n 159: \"Ÿ\"\n };\n const baseEntities + = {\n '\"': \""\",\n \"'\": \"'\",\n \"<\": + \"<\",\n \">\": \">\",\n \"&\": \"&\",\n \"`\": + \"`\"\n };\n const reverseEntities = {\n \"<\": \"<\",\n + \ \">\": \">\",\n \"&\": \"&\",\n \""\": '\"',\n + \ \"'\": `'`\n };\n const nativeDecode = (text3) => {\n + \ const elm = SugarElement.fromTag(\"div\").dom;\n elm.innerHTML + = text3;\n return elm.textContent || elm.innerText || text3;\n };\n + \ const buildEntitiesLookup = (items, radix) => {\n let i3, chr, + entity2;\n const lookup2 = {};\n if (items) {\n items + = items.split(\",\");\n radix = radix || 10;\n for (i3 = + 0; i3 < items.length; i3 += 2) {\n chr = String.fromCharCode(parseInt(items[i3], + radix));\n if (!baseEntities[chr]) {\n entity2 = \"&\" + + items[i3 + 1] + \";\";\n lookup2[chr] = entity2;\n lookup2[entity2] + = chr;\n }\n }\n return lookup2;\n }\n + \ };\n const namedEntities = buildEntitiesLookup(\"50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,t9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro\", + 32);\n const encodeRaw = (text3, attr) => text3.replace(attr ? attrsCharsRegExp + : textCharsRegExp, (chr) => {\n return baseEntities[chr] || chr;\n + \ });\n const encodeAllRaw = (text3) => (\"\" + text3).replace(rawCharsRegExp, + (chr) => {\n return baseEntities[chr] || chr;\n });\n const + encodeNumeric = (text3, attr) => text3.replace(attr ? attrsCharsRegExp : textCharsRegExp, + (chr) => {\n if (chr.length > 1) {\n return \"&#\" + ((chr.charCodeAt(0) + - 55296) * 1024 + (chr.charCodeAt(1) - 56320) + 65536) + \";\";\n }\n + \ return baseEntities[chr] || \"&#\" + chr.charCodeAt(0) + \";\";\n + \ });\n const encodeNamed = (text3, attr, entities) => {\n entities + = entities || namedEntities;\n return text3.replace(attr ? attrsCharsRegExp + : textCharsRegExp, (chr) => {\n return baseEntities[chr] || entities[chr] + || chr;\n });\n };\n const getEncodeFunc = (name2, entities) + => {\n const entitiesMap = buildEntitiesLookup(entities) || namedEntities;\n + \ const encodeNamedAndNumeric = (text3, attr) => text3.replace(attr + ? attrsCharsRegExp : textCharsRegExp, (chr) => {\n if (baseEntities[chr] + !== void 0) {\n return baseEntities[chr];\n }\n if + (entitiesMap[chr] !== void 0) {\n return entitiesMap[chr];\n }\n + \ if (chr.length > 1) {\n return \"&#\" + ((chr.charCodeAt(0) + - 55296) * 1024 + (chr.charCodeAt(1) - 56320) + 65536) + \";\";\n }\n + \ return \"&#\" + chr.charCodeAt(0) + \";\";\n });\n const + encodeCustomNamed = (text3, attr) => {\n return encodeNamed(text3, + attr, entitiesMap);\n };\n const nameMap = makeMap$3(name2.replace(/\\+/g, + \",\"));\n if (nameMap.named && nameMap.numeric) {\n return + encodeNamedAndNumeric;\n }\n if (nameMap.named) {\n if + (entities) {\n return encodeCustomNamed;\n }\n return + encodeNamed;\n }\n if (nameMap.numeric) {\n return + encodeNumeric;\n }\n return encodeRaw;\n };\n const + decode2 = (text3) => text3.replace(entityRegExp, (all2, numeric) => {\n if + (numeric) {\n if (numeric.charAt(0).toLowerCase() === \"x\") {\n + \ numeric = parseInt(numeric.substr(1), 16);\n } else {\n + \ numeric = parseInt(numeric, 10);\n }\n if (numeric + > 65535) {\n numeric -= 65536;\n return String.fromCharCode(55296 + + (numeric >> 10), 56320 + (numeric & 1023));\n }\n return + asciiMap[numeric] || String.fromCharCode(numeric);\n }\n return + reverseEntities[all2] || namedEntities[all2] || nativeDecode(all2);\n });\n + \ const Entities = {\n encodeRaw,\n encodeAllRaw,\n encodeNumeric,\n + \ encodeNamed,\n getEncodeFunc,\n decode: decode2\n };\n + \ const mapCache = {}, dummyObj = {};\n const makeMap$2 = Tools.makeMap, + each$d = Tools.each, extend$2 = Tools.extend, explode$2 = Tools.explode, inArray + = Tools.inArray;\n const split$1 = (items, delim) => {\n items + = Tools.trim(items);\n return items ? items.split(delim || \" \") : + [];\n };\n const compileSchema = (type2) => {\n const schema + = {};\n let globalAttributes, blockContent;\n let phrasingContent, + flowContent, html4BlockContent, html4PhrasingContent;\n const add3 + = (name2, attributes = \"\", children2 = \"\") => {\n const childNames + = split$1(children2);\n const names = split$1(name2);\n let + ni = names.length;\n while (ni--) {\n const attributesOrder + = split$1([\n globalAttributes,\n attributes\n ].join(\" + \"));\n schema[names[ni]] = {\n attributes: mapToObject(attributesOrder, + () => ({})),\n attributesOrder,\n children: mapToObject(childNames, + constant2(dummyObj))\n };\n }\n };\n const + addAttrs = (name2, attributes) => {\n const names = split$1(name2);\n + \ const attrs = split$1(attributes);\n let ni = names.length;\n + \ while (ni--) {\n const schemaItem = schema[names[ni]];\n + \ for (let i3 = 0, l2 = attrs.length; i3 < l2; i3++) {\n schemaItem.attributes[attrs[i3]] + = {};\n schemaItem.attributesOrder.push(attrs[i3]);\n }\n + \ }\n };\n if (mapCache[type2]) {\n return + mapCache[type2];\n }\n globalAttributes = \"id accesskey class + dir lang style tabindex title role\";\n blockContent = \"address blockquote + div dl fieldset form h1 h2 h3 h4 h5 h6 hr menu ol p pre table ul\";\n phrasingContent + = \"a abbr b bdo br button cite code del dfn em embed i iframe img input ins + kbd label map noscript object q s samp script select small span strong sub + sup textarea u var #text #comment\";\n if (type2 !== \"html4\") {\n + \ globalAttributes += \" contenteditable contextmenu draggable dropzone + hidden spellcheck translate\";\n blockContent += \" article aside + details dialog figure main header footer hgroup section nav\";\n phrasingContent + += \" audio canvas command datalist mark meter output picture progress time + wbr video ruby bdi keygen\";\n }\n if (type2 !== \"html5-strict\") + {\n globalAttributes += \" xml:lang\";\n html4PhrasingContent + = \"acronym applet basefont big font strike tt\";\n phrasingContent + = [\n phrasingContent,\n html4PhrasingContent\n ].join(\" + \");\n each$d(split$1(html4PhrasingContent), (name2) => {\n add3(name2, + \"\", phrasingContent);\n });\n html4BlockContent = \"center + dir isindex noframes\";\n blockContent = [\n blockContent,\n + \ html4BlockContent\n ].join(\" \");\n flowContent + = [\n blockContent,\n phrasingContent\n ].join(\" + \");\n each$d(split$1(html4BlockContent), (name2) => {\n add3(name2, + \"\", flowContent);\n });\n }\n flowContent = flowContent + || [\n blockContent,\n phrasingContent\n ].join(\" + \");\n add3(\"html\", \"manifest\", \"head body\");\n add3(\"head\", + \"\", \"base command link meta noscript script style title\");\n add3(\"title + hr noscript br\");\n add3(\"base\", \"href target\");\n add3(\"link\", + \"href rel media hreflang type sizes hreflang\");\n add3(\"meta\", + \"name http-equiv content charset\");\n add3(\"style\", \"media type + scoped\");\n add3(\"script\", \"src async defer type charset\");\n + \ add3(\"body\", \"onafterprint onbeforeprint onbeforeunload onblur + onerror onfocus onhashchange onload onmessage onoffline ononline onpagehide + onpageshow onpopstate onresize onscroll onstorage onunload\", flowContent);\n + \ add3(\"address dt dd div caption\", \"\", flowContent);\n add3(\"h1 + h2 h3 h4 h5 h6 pre p abbr code var samp kbd sub sup i b u bdo span legend + em strong small s cite dfn\", \"\", phrasingContent);\n add3(\"blockquote\", + \"cite\", flowContent);\n add3(\"ol\", \"reversed start type\", \"li\");\n + \ add3(\"ul\", \"\", \"li\");\n add3(\"li\", \"value\", flowContent);\n + \ add3(\"dl\", \"\", \"dt dd\");\n add3(\"a\", \"href target + rel media hreflang type\", phrasingContent);\n add3(\"q\", \"cite\", + phrasingContent);\n add3(\"ins del\", \"cite datetime\", flowContent);\n + \ add3(\"img\", \"src sizes srcset alt usemap ismap width height\");\n + \ add3(\"iframe\", \"src name width height\", flowContent);\n add3(\"embed\", + \"src type width height\");\n add3(\"object\", \"data type typemustmatch + name usemap form width height\", [\n flowContent,\n \"param\"\n + \ ].join(\" \"));\n add3(\"param\", \"name value\");\n add3(\"map\", + \"name\", [\n flowContent,\n \"area\"\n ].join(\" + \"));\n add3(\"area\", \"alt coords shape href target rel media hreflang + type\");\n add3(\"table\", \"border\", \"caption colgroup thead tfoot + tbody tr\" + (type2 === \"html4\" ? \" col\" : \"\"));\n add3(\"colgroup\", + \"span\", \"col\");\n add3(\"col\", \"span\");\n add3(\"tbody + thead tfoot\", \"\", \"tr\");\n add3(\"tr\", \"\", \"td th\");\n add3(\"td\", + \"colspan rowspan headers\", flowContent);\n add3(\"th\", \"colspan + rowspan headers scope abbr\", flowContent);\n add3(\"form\", \"accept-charset + action autocomplete enctype method name novalidate target\", flowContent);\n + \ add3(\"fieldset\", \"disabled form name\", [\n flowContent,\n + \ \"legend\"\n ].join(\" \"));\n add3(\"label\", \"form + for\", phrasingContent);\n add3(\"input\", \"accept alt autocomplete + checked dirname disabled form formaction formenctype formmethod formnovalidate + formtarget height list max maxlength min multiple name pattern readonly required + size src step type value width\");\n add3(\"button\", \"disabled form + formaction formenctype formmethod formnovalidate formtarget name type value\", + type2 === \"html4\" ? flowContent : phrasingContent);\n add3(\"select\", + \"disabled form multiple name required size\", \"option optgroup\");\n add3(\"optgroup\", + \"disabled label\", \"option\");\n add3(\"option\", \"disabled label + selected value\");\n add3(\"textarea\", \"cols dirname disabled form + maxlength name readonly required rows wrap\");\n add3(\"menu\", \"type + label\", [\n flowContent,\n \"li\"\n ].join(\" \"));\n + \ add3(\"noscript\", \"\", flowContent);\n if (type2 !== \"html4\") + {\n add3(\"wbr\");\n add3(\"ruby\", \"\", [\n phrasingContent,\n + \ \"rt rp\"\n ].join(\" \"));\n add3(\"figcaption\", + \"\", flowContent);\n add3(\"mark rt rp summary bdi\", \"\", phrasingContent);\n + \ add3(\"canvas\", \"width height\", flowContent);\n add3(\"video\", + \"src crossorigin poster preload autoplay mediagroup loop muted controls width + height buffered\", [\n flowContent,\n \"track source\"\n + \ ].join(\" \"));\n add3(\"audio\", \"src crossorigin preload + autoplay mediagroup loop muted controls buffered volume\", [\n flowContent,\n + \ \"track source\"\n ].join(\" \"));\n add3(\"picture\", + \"\", \"img source\");\n add3(\"source\", \"src srcset type media + sizes\");\n add3(\"track\", \"kind src srclang label default\");\n + \ add3(\"datalist\", \"\", [\n phrasingContent,\n \"option\"\n + \ ].join(\" \"));\n add3(\"article section nav aside main + header footer\", \"\", flowContent);\n add3(\"hgroup\", \"\", \"h1 + h2 h3 h4 h5 h6\");\n add3(\"figure\", \"\", [\n flowContent,\n + \ \"figcaption\"\n ].join(\" \"));\n add3(\"time\", + \"datetime\", phrasingContent);\n add3(\"dialog\", \"open\", flowContent);\n + \ add3(\"command\", \"type label icon disabled checked radiogroup + command\");\n add3(\"output\", \"for form name\", phrasingContent);\n + \ add3(\"progress\", \"value max\", phrasingContent);\n add3(\"meter\", + \"value min max low high optimum\", phrasingContent);\n add3(\"details\", + \"open\", [\n flowContent,\n \"summary\"\n ].join(\" + \"));\n add3(\"keygen\", \"autofocus challenge disabled form keytype + name\");\n }\n if (type2 !== \"html5-strict\") {\n addAttrs(\"script\", + \"language xml:space\");\n addAttrs(\"style\", \"xml:space\");\n + \ addAttrs(\"object\", \"declare classid code codebase codetype archive + standby align border hspace vspace\");\n addAttrs(\"embed\", \"align + name hspace vspace\");\n addAttrs(\"param\", \"valuetype type\");\n + \ addAttrs(\"a\", \"charset name rev shape coords\");\n addAttrs(\"br\", + \"clear\");\n addAttrs(\"applet\", \"codebase archive code object + alt name width height align hspace vspace\");\n addAttrs(\"img\", + \"name longdesc align border hspace vspace\");\n addAttrs(\"iframe\", + \"longdesc frameborder marginwidth marginheight scrolling align\");\n addAttrs(\"font + basefont\", \"size color face\");\n addAttrs(\"input\", \"usemap + align\");\n addAttrs(\"select\");\n addAttrs(\"textarea\");\n + \ addAttrs(\"h1 h2 h3 h4 h5 h6 div p legend caption\", \"align\");\n + \ addAttrs(\"ul\", \"type compact\");\n addAttrs(\"li\", + \"type\");\n addAttrs(\"ol dl menu dir\", \"compact\");\n addAttrs(\"pre\", + \"width xml:space\");\n addAttrs(\"hr\", \"align noshade size width\");\n + \ addAttrs(\"isindex\", \"prompt\");\n addAttrs(\"table\", + \"summary width frame rules cellspacing cellpadding align bgcolor\");\n addAttrs(\"col\", + \"width align char charoff valign\");\n addAttrs(\"colgroup\", \"width + align char charoff valign\");\n addAttrs(\"thead\", \"align char + charoff valign\");\n addAttrs(\"tr\", \"align char charoff valign + bgcolor\");\n addAttrs(\"th\", \"axis align char charoff valign nowrap + bgcolor width height\");\n addAttrs(\"form\", \"accept\");\n addAttrs(\"td\", + \"abbr axis scope align char charoff valign nowrap bgcolor width height\");\n + \ addAttrs(\"tfoot\", \"align char charoff valign\");\n addAttrs(\"tbody\", + \"align char charoff valign\");\n addAttrs(\"area\", \"nohref\");\n + \ addAttrs(\"body\", \"background bgcolor text link vlink alink\");\n + \ }\n if (type2 !== \"html4\") {\n addAttrs(\"input + button select textarea\", \"autofocus\");\n addAttrs(\"input textarea\", + \"placeholder\");\n addAttrs(\"a\", \"download\");\n addAttrs(\"link + script img\", \"crossorigin\");\n addAttrs(\"img\", \"loading\");\n + \ addAttrs(\"iframe\", \"sandbox seamless allowfullscreen loading\");\n + \ }\n if (type2 !== \"html4\") {\n each$g([\n schema.video,\n + \ schema.audio\n ], (item) => {\n delete item.children.audio;\n + \ delete item.children.video;\n });\n }\n each$d(split$1(\"a + form meter progress dfn\"), (name2) => {\n if (schema[name2]) {\n + \ delete schema[name2].children[name2];\n }\n });\n + \ delete schema.caption.children.table;\n delete schema.script;\n + \ mapCache[type2] = schema;\n return schema;\n };\n const + compileElementMap = (value2, mode) => {\n let styles;\n if (value2) + {\n styles = {};\n if (typeof value2 === \"string\") {\n + \ value2 = { \"*\": value2 };\n }\n each$d(value2, + (value3, key) => {\n styles[key] = styles[key.toUpperCase()] = + mode === \"map\" ? makeMap$2(value3, /[, ]/) : explode$2(value3, /[, ]/);\n + \ });\n }\n return styles;\n };\n const Schema + = (settings) => {\n var _a3;\n const elements = {};\n const + children2 = {};\n let patternElements = [];\n const customElementsMap + = {}, specialElements = {};\n const createLookupTable = (option2, defaultValue, + extendWith) => {\n let value2 = settings[option2];\n if + (!value2) {\n value2 = mapCache[option2];\n if (!value2) + {\n value2 = makeMap$2(defaultValue, \" \", makeMap$2(defaultValue.toUpperCase(), + \" \"));\n value2 = extend$2(value2, extendWith);\n mapCache[option2] + = value2;\n }\n } else {\n value2 = makeMap$2(value2, + /[, ]/, makeMap$2(value2.toUpperCase(), /[, ]/));\n }\n return + value2;\n };\n settings = settings || {};\n const schemaType + = (_a3 = settings.schema) !== null && _a3 !== void 0 ? _a3 : \"html5\";\n + \ const schemaItems = compileSchema(schemaType);\n if (settings.verify_html + === false) {\n settings.valid_elements = \"*[*]\";\n }\n const + validStyles = compileElementMap(settings.valid_styles);\n const invalidStyles + = compileElementMap(settings.invalid_styles, \"map\");\n const validClasses + = compileElementMap(settings.valid_classes, \"map\");\n const whitespaceElementsMap + = createLookupTable(\"whitespace_elements\", \"pre script noscript style textarea + video audio iframe object code\");\n const selfClosingElementsMap = + createLookupTable(\"self_closing_elements\", \"colgroup dd dt li option p + td tfoot th thead tr\");\n const voidElementsMap = createLookupTable(\"void_elements\", + \"area base basefont br col frame hr img input isindex link meta param embed + source wbr track\");\n const boolAttrMap = createLookupTable(\"boolean_attributes\", + \"checked compact declare defer disabled ismap multiple nohref noresize noshade + nowrap readonly selected autoplay loop controls allowfullscreen\");\n const + nonEmptyOrMoveCaretBeforeOnEnter = \"td th iframe video audio object script + code\";\n const nonEmptyElementsMap = createLookupTable(\"non_empty_elements\", + nonEmptyOrMoveCaretBeforeOnEnter + \" pre\", voidElementsMap);\n const + moveCaretBeforeOnEnterElementsMap = createLookupTable(\"move_caret_before_on_enter_elements\", + nonEmptyOrMoveCaretBeforeOnEnter + \" table\", voidElementsMap);\n const + textBlockElementsMap = createLookupTable(\"text_block_elements\", \"h1 h2 + h3 h4 h5 h6 p div address pre form blockquote center dir fieldset header footer + article section hgroup aside main nav figure\");\n const blockElementsMap + = createLookupTable(\"block_elements\", \"hr table tbody thead tfoot th tr + td li ol ul caption dl dt dd noscript menu isindex option datalist select + optgroup figcaption details summary\", textBlockElementsMap);\n const + textInlineElementsMap = createLookupTable(\"text_inline_elements\", \"span + strong b em i font strike u var cite dfn code mark q sup sub samp\");\n each$d(\"script + noscript iframe noframes noembed title style textarea xmp plaintext\".split(\" + \"), (name2) => {\n specialElements[name2] = new RegExp(\"]*>\", \"gi\");\n });\n const patternToRegExp = + (str) => new RegExp(\"^\" + str.replace(/([?+*])/g, \".$1\") + \"$\");\n const + addValidElements = (validElements) => {\n let ei, el, ai, al, matches, + element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder, + prefix, outputName, globalAttributes, globalAttributesOrder, value2;\n const + elementRuleRegExp = /^([#+\\-])?([^\\[!\\/]+)(?:\\/([^\\[!]+))?(?:(!?)\\[([^\\]]+)])?$/, + attrRuleRegExp = /^([!\\-])?(\\w+[\\\\:]:\\w+|[^=~<]+)?(?:([=~<])(.*))?$/, + hasPatternsRegExp = /[*?+]/;\n if (validElements) {\n const + validElementsArr = split$1(validElements, \",\");\n if (elements[\"@\"]) + {\n globalAttributes = elements[\"@\"].attributes;\n globalAttributesOrder + = elements[\"@\"].attributesOrder;\n }\n for (ei = 0, + el = validElementsArr.length; ei < el; ei++) {\n matches = elementRuleRegExp.exec(validElementsArr[ei]);\n + \ if (matches) {\n prefix = matches[1];\n elementName + = matches[2];\n outputName = matches[3];\n attrData + = matches[5];\n attributes = {};\n attributesOrder + = [];\n element = {\n attributes,\n attributesOrder\n + \ };\n if (prefix === \"#\") {\n element.paddEmpty + = true;\n }\n if (prefix === \"-\") {\n element.removeEmpty + = true;\n }\n if (matches[4] === \"!\") {\n + \ element.removeEmptyAttrs = true;\n }\n if + (globalAttributes) {\n each$f(globalAttributes, (value3, + key) => {\n attributes[key] = value3;\n });\n + \ attributesOrder.push.apply(attributesOrder, globalAttributesOrder);\n + \ }\n if (attrData) {\n attrData + = split$1(attrData, \"|\");\n for (ai = 0, al = attrData.length; + ai < al; ai++) {\n matches = attrRuleRegExp.exec(attrData[ai]);\n + \ if (matches) {\n attr = {};\n attrType + = matches[1];\n attrName = matches[2].replace(/[\\\\:]:/g, + \":\");\n prefix = matches[3];\n value2 + = matches[4];\n if (attrType === \"!\") {\n element.attributesRequired + = element.attributesRequired || [];\n element.attributesRequired.push(attrName);\n + \ attr.required = true;\n }\n if + (attrType === \"-\") {\n delete attributes[attrName];\n + \ attributesOrder.splice(inArray(attributesOrder, attrName), + 1);\n continue;\n }\n if + (prefix) {\n if (prefix === \"=\") {\n element.attributesDefault + = element.attributesDefault || [];\n element.attributesDefault.push({\n + \ name: attrName,\n value: + value2\n });\n attr.defaultValue + = value2;\n }\n if (prefix === + \"~\") {\n element.attributesForced = element.attributesForced + || [];\n element.attributesForced.push({\n name: + attrName,\n value: value2\n });\n + \ attr.forcedValue = value2;\n }\n + \ if (prefix === \"<\") {\n attr.validValues + = makeMap$2(value2, \"?\");\n }\n }\n + \ if (hasPatternsRegExp.test(attrName)) {\n element.attributePatterns + = element.attributePatterns || [];\n attr.pattern = + patternToRegExp(attrName);\n element.attributePatterns.push(attr);\n + \ } else {\n if (!attributes[attrName]) + {\n attributesOrder.push(attrName);\n }\n + \ attributes[attrName] = attr;\n }\n + \ }\n }\n }\n if + (!globalAttributes && elementName === \"@\") {\n globalAttributes + = attributes;\n globalAttributesOrder = attributesOrder;\n + \ }\n if (outputName) {\n element.outputName + = elementName;\n elements[outputName] = element;\n }\n + \ if (hasPatternsRegExp.test(elementName)) {\n element.pattern + = patternToRegExp(elementName);\n patternElements.push(element);\n + \ } else {\n elements[elementName] = element;\n + \ }\n }\n }\n }\n };\n + \ const setValidElements = (validElements) => {\n patternElements + = [];\n each$g(keys2(elements), (name2) => {\n delete + elements[name2];\n });\n addValidElements(validElements);\n + \ each$d(schemaItems, (element, name2) => {\n children2[name2] + = element.children;\n });\n };\n const addCustomElements + = (customElements2) => {\n const customElementRegExp = /^(~)?(.+)$/;\n + \ if (customElements2) {\n mapCache.text_block_elements + = mapCache.block_elements = null;\n each$d(split$1(customElements2, + \",\"), (rule) => {\n const matches = customElementRegExp.exec(rule), + inline2 = matches[1] === \"~\", cloneName = inline2 ? \"span\" : \"div\", + name2 = matches[2];\n children2[name2] = children2[cloneName];\n + \ customElementsMap[name2] = cloneName;\n if (!inline2) + {\n blockElementsMap[name2.toUpperCase()] = {};\n blockElementsMap[name2] + = {};\n }\n if (!elements[name2]) {\n let + customRule = elements[cloneName];\n customRule = extend$2({}, + customRule);\n delete customRule.removeEmptyAttrs;\n delete + customRule.removeEmpty;\n elements[name2] = customRule;\n }\n + \ each$d(children2, (element, elmName) => {\n if + (element[cloneName]) {\n children2[elmName] = element = extend$2({}, + children2[elmName]);\n element[name2] = element[cloneName];\n + \ }\n });\n });\n }\n };\n + \ const addValidChildren = (validChildren) => {\n const childRuleRegExp + = /^([+\\-]?)([A-Za-z0-9_\\-.\\u00b7\\u00c0-\\u00d6\\u00d8-\\u00f6\\u00f8-\\u037d\\u037f-\\u1fff\\u200c-\\u200d\\u203f-\\u2040\\u2070-\\u218f\\u2c00-\\u2fef\\u3001-\\ud7ff\\uf900-\\ufdcf\\ufdf0-\\ufffd]+)\\[([^\\]]+)]$/;\n + \ mapCache[schemaType] = null;\n if (validChildren) {\n each$d(split$1(validChildren, + \",\"), (rule) => {\n const matches = childRuleRegExp.exec(rule);\n + \ let parent2, prefix;\n if (matches) {\n prefix + = matches[1];\n if (prefix) {\n parent2 = + children2[matches[2]];\n } else {\n parent2 + = children2[matches[2]] = { \"#comment\": {} };\n }\n parent2 + = children2[matches[2]];\n each$d(split$1(matches[3], \"|\"), + (child2) => {\n if (prefix === \"-\") {\n delete + parent2[child2];\n } else {\n parent2[child2] + = {};\n }\n });\n }\n });\n + \ }\n };\n const getElementRule = (name2) => {\n let + element = elements[name2], i3;\n if (element) {\n return + element;\n }\n i3 = patternElements.length;\n while + (i3--) {\n element = patternElements[i3];\n if (element.pattern.test(name2)) + {\n return element;\n }\n }\n };\n + \ if (!settings.valid_elements) {\n each$d(schemaItems, (element, + name2) => {\n elements[name2] = {\n attributes: element.attributes,\n + \ attributesOrder: element.attributesOrder\n };\n children2[name2] + = element.children;\n });\n each$d(split$1(\"strong/b em/i\"), + (item) => {\n const items = split$1(item, \"/\");\n elements[items[1]].outputName + = items[0];\n });\n each$d(split$1(\"ol ul sub sup blockquote + span font a table tbody strong em b i\"), (name2) => {\n if (elements[name2]) + {\n elements[name2].removeEmpty = true;\n }\n });\n + \ each$d(split$1(\"p h1 h2 h3 h4 h5 h6 th td pre div address caption + li\"), (name2) => {\n elements[name2].paddEmpty = true;\n });\n + \ each$d(split$1(\"span\"), (name2) => {\n elements[name2].removeEmptyAttrs + = true;\n });\n } else {\n setValidElements(settings.valid_elements);\n + \ }\n addCustomElements(settings.custom_elements);\n addValidChildren(settings.valid_children);\n + \ addValidElements(settings.extended_valid_elements);\n addValidChildren(\"+ol[ul|ol],+ul[ul|ol]\");\n + \ each$d({\n dd: \"dl\",\n dt: \"dl\",\n li: + \"ul ol\",\n td: \"tr\",\n th: \"tr\",\n tr: \"tbody + thead tfoot\",\n tbody: \"table\",\n thead: \"table\",\n + \ tfoot: \"table\",\n legend: \"fieldset\",\n area: + \"map\",\n param: \"video audio object\"\n }, (parents2, item) + => {\n if (elements[item]) {\n elements[item].parentsRequired + = split$1(parents2);\n }\n });\n if (settings.invalid_elements) + {\n each$d(explode$2(settings.invalid_elements), (item) => {\n if + (elements[item]) {\n delete elements[item];\n }\n + \ });\n }\n if (!getElementRule(\"span\")) {\n addValidElements(\"span[!data-mce-type|*]\");\n + \ }\n const getValidStyles = constant2(validStyles);\n const + getInvalidStyles = constant2(invalidStyles);\n const getValidClasses + = constant2(validClasses);\n const getBoolAttrs = constant2(boolAttrMap);\n + \ const getBlockElements = constant2(blockElementsMap);\n const + getTextBlockElements = constant2(textBlockElementsMap);\n const getTextInlineElements + = constant2(textInlineElementsMap);\n const getVoidElements = constant2(Object.seal(voidElementsMap));\n + \ const getSelfClosingElements = constant2(selfClosingElementsMap);\n + \ const getNonEmptyElements = constant2(nonEmptyElementsMap);\n const + getMoveCaretBeforeOnEnterElements = constant2(moveCaretBeforeOnEnterElementsMap);\n + \ const getWhitespaceElements = constant2(whitespaceElementsMap);\n + \ const getSpecialElements = constant2(Object.seal(specialElements));\n + \ const isValidChild = (name2, child2) => {\n const parent2 + = children2[name2.toLowerCase()];\n return !!(parent2 && parent2[child2.toLowerCase()]);\n + \ };\n const isValid2 = (name2, attr) => {\n let attrPatterns, + i3;\n const rule = getElementRule(name2);\n if (rule) {\n + \ if (attr) {\n if (rule.attributes[attr]) {\n return + true;\n }\n attrPatterns = rule.attributePatterns;\n + \ if (attrPatterns) {\n i3 = attrPatterns.length;\n + \ while (i3--) {\n if (attrPatterns[i3].pattern.test(attr)) + {\n return true;\n }\n }\n + \ }\n } else {\n return true;\n }\n + \ }\n return false;\n };\n const getCustomElements + = constant2(customElementsMap);\n return {\n type: schemaType,\n + \ children: children2,\n elements,\n getValidStyles,\n + \ getValidClasses,\n getBlockElements,\n getInvalidStyles,\n + \ getVoidElements,\n getTextBlockElements,\n getTextInlineElements,\n + \ getBoolAttrs,\n getElementRule,\n getSelfClosingElements,\n + \ getNonEmptyElements,\n getMoveCaretBeforeOnEnterElements,\n + \ getWhitespaceElements,\n getSpecialElements,\n isValidChild,\n + \ isValid: isValid2,\n getCustomElements,\n addValidElements,\n + \ setValidElements,\n addCustomElements,\n addValidChildren\n + \ };\n };\n const Styles = (settings, schema) => {\n const + urlOrStrRegExp = /(?:url(?:(?:\\(\\s*\\\"([^\\\"]+)\\\"\\s*\\))|(?:\\(\\s*\\'([^\\']+)\\'\\s*\\))|(?:\\(\\s*([^)\\s]+)\\s*\\))))|(?:\\'([^\\']+)\\')|(?:\\\"([^\\\"]+)\\\")/gi;\n + \ const styleRegExp = /\\s*([^:]+):\\s*([^;]+);?/g;\n const trimRightRegExp + = /\\s+$/;\n let i3;\n const encodingLookup = {};\n let + validStyles;\n let invalidStyles;\n const invisibleChar = zeroWidth;\n + \ settings = settings || {};\n if (schema) {\n validStyles + = schema.getValidStyles();\n invalidStyles = schema.getInvalidStyles();\n + \ }\n const encodingItems = (`\\\\\" \\\\' \\\\; \\\\: ; : ` + + invisibleChar).split(\" \");\n for (i3 = 0; i3 < encodingItems.length; + i3++) {\n encodingLookup[encodingItems[i3]] = invisibleChar + i3;\n + \ encodingLookup[invisibleChar + i3] = encodingItems[i3];\n }\n + \ const self2 = {\n parse: (css) => {\n const styles + = {};\n let matches, name2, value2, isEncoded;\n const + urlConverter = settings.url_converter;\n const urlConverterScope + = settings.url_converter_scope || self2;\n const compress = (prefix, + suffix, noJoin) => {\n const top = styles[prefix + \"-top\" + + suffix];\n if (!top) {\n return;\n }\n + \ const right = styles[prefix + \"-right\" + suffix];\n if + (!right) {\n return;\n }\n const + bottom = styles[prefix + \"-bottom\" + suffix];\n if (!bottom) + {\n return;\n }\n const left = styles[prefix + + \"-left\" + suffix];\n if (!left) {\n return;\n + \ }\n const box = [\n top,\n right,\n + \ bottom,\n left\n ];\n i3 + = box.length - 1;\n while (i3--) {\n if (box[i3] + !== box[i3 + 1]) {\n break;\n }\n }\n + \ if (i3 > -1 && noJoin) {\n return;\n }\n + \ styles[prefix + suffix] = i3 === -1 ? box[0] : box.join(\" \");\n + \ delete styles[prefix + \"-top\" + suffix];\n delete + styles[prefix + \"-right\" + suffix];\n delete styles[prefix + + \"-bottom\" + suffix];\n delete styles[prefix + \"-left\" + + suffix];\n };\n const canCompress = (key) => {\n let + value3 = styles[key], i4;\n if (!value3) {\n return;\n + \ }\n value3 = value3.split(\" \");\n i4 + = value3.length;\n while (i4--) {\n if (value3[i4] + !== value3[0]) {\n return false;\n }\n }\n + \ styles[key] = value3[0];\n return true;\n };\n + \ const compress2 = (target, a2, b2, c2) => {\n if + (!canCompress(a2)) {\n return;\n }\n if + (!canCompress(b2)) {\n return;\n }\n if + (!canCompress(c2)) {\n return;\n }\n styles[target] + = styles[a2] + \" \" + styles[b2] + \" \" + styles[c2];\n delete + styles[a2];\n delete styles[b2];\n delete styles[c2];\n + \ };\n const encode2 = (str) => {\n isEncoded + = true;\n return encodingLookup[str];\n };\n const + decode3 = (str, keepSlashes) => {\n if (isEncoded) {\n str + = str.replace(/\\uFEFF[0-9]/g, (str2) => {\n return encodingLookup[str2];\n + \ });\n }\n if (!keepSlashes) {\n + \ str = str.replace(/\\\\([\\'\\\";:])/g, \"$1\");\n }\n + \ return str;\n };\n const decodeSingleHexSequence + = (escSeq) => {\n return String.fromCharCode(parseInt(escSeq.slice(1), + 16));\n };\n const decodeHexSequences = (value3) => + {\n return value3.replace(/\\\\[0-9a-f]+/gi, decodeSingleHexSequence);\n + \ };\n const processUrl = (match4, url2, url22, url3, + str, str2) => {\n str = str || str2;\n if (str) + {\n str = decode3(str);\n return `'` + str.replace(/\\'/g, + `\\\\'`) + `'`;\n }\n url2 = decode3(url2 || url22 + || url3);\n if (!settings.allow_script_urls) {\n const + scriptUrl = url2.replace(/[\\s\\r\\n]+/g, \"\");\n if (/(java|vb)script:/i.test(scriptUrl)) + {\n return \"\";\n }\n if (!settings.allow_svg_data_urls + && /^data:image\\/svg/i.test(scriptUrl)) {\n return \"\";\n + \ }\n }\n if (urlConverter) {\n url2 + = urlConverter.call(urlConverterScope, url2, \"style\");\n }\n + \ return `url('` + url2.replace(/\\'/g, `\\\\'`) + `')`;\n };\n + \ if (css) {\n css = css.replace(/[\\u0000-\\u001F]/g, + \"\");\n css = css.replace(/\\\\[\\\"\\';:\\uFEFF]/g, encode2).replace(/\\\"[^\\\"]+\\\"|\\'[^\\']+\\'/g, + (str) => {\n return str.replace(/[;:]/g, encode2);\n });\n + \ while (matches = styleRegExp.exec(css)) {\n styleRegExp.lastIndex + = matches.index + matches[0].length;\n name2 = matches[1].replace(trimRightRegExp, + \"\").toLowerCase();\n value2 = matches[2].replace(trimRightRegExp, + \"\");\n if (name2 && value2) {\n name2 = + decodeHexSequences(name2);\n value2 = decodeHexSequences(value2);\n + \ if (name2.indexOf(invisibleChar) !== -1 || name2.indexOf('\"') + !== -1) {\n continue;\n }\n if + (!settings.allow_script_urls && (name2 === \"behavior\" || /expression\\s*\\(|\\/\\*|\\*\\//.test(value2))) + {\n continue;\n }\n if + (name2 === \"font-weight\" && value2 === \"700\") {\n value2 + = \"bold\";\n } else if (name2 === \"color\" || name2 === + \"background-color\") {\n value2 = value2.toLowerCase();\n + \ }\n value2 = value2.replace(urlOrStrRegExp, + processUrl);\n styles[name2] = isEncoded ? decode3(value2, + true) : value2;\n }\n }\n compress(\"border\", + \"\", true);\n compress(\"border\", \"-width\");\n compress(\"border\", + \"-color\");\n compress(\"border\", \"-style\");\n compress(\"padding\", + \"\");\n compress(\"margin\", \"\");\n compress2(\"border\", + \"border-width\", \"border-style\", \"border-color\");\n if (styles.border + === \"medium none\") {\n delete styles.border;\n }\n + \ if (styles[\"border-image\"] === \"none\") {\n delete + styles[\"border-image\"];\n }\n }\n return + styles;\n },\n serialize: (styles, elementName) => {\n let + css = \"\";\n const serializeStyles = (name2) => {\n let + value2;\n const styleList = validStyles[name2];\n if + (styleList) {\n for (let i4 = 0, l2 = styleList.length; i4 + < l2; i4++) {\n name2 = styleList[i4];\n value2 + = styles[name2];\n if (value2) {\n css + += (css.length > 0 ? \" \" : \"\") + name2 + \": \" + value2 + \";\";\n }\n + \ }\n }\n };\n const isValid2 + = (name2, elementName2) => {\n let styleMap = invalidStyles[\"*\"];\n + \ if (styleMap && styleMap[name2]) {\n return false;\n + \ }\n styleMap = invalidStyles[elementName2];\n return + !(styleMap && styleMap[name2]);\n };\n if (elementName + && validStyles) {\n serializeStyles(\"*\");\n serializeStyles(elementName);\n + \ } else {\n each$f(styles, (value2, name2) => {\n + \ if (value2 && (!invalidStyles || isValid2(name2, elementName))) + {\n css += (css.length > 0 ? \" \" : \"\") + name2 + \": + \" + value2 + \";\";\n }\n });\n }\n + \ return css;\n }\n };\n return self2;\n + \ };\n const deprecated = {\n keyLocation: true,\n layerX: + true,\n layerY: true,\n returnValue: true,\n webkitMovementX: + true,\n webkitMovementY: true,\n keyIdentifier: true,\n mozPressure: + true\n };\n const isNativeEvent = (event) => event instanceof Event + || isFunction2(event.initEvent);\n const hasIsDefaultPrevented = (event) + => event.isDefaultPrevented === always || event.isDefaultPrevented === never;\n + \ const needsNormalizing = (event) => isNullable(event.preventDefault) + || isNativeEvent(event);\n const clone$3 = (originalEvent, data2) => + {\n const event = data2 !== null && data2 !== void 0 ? data2 : {};\n + \ for (const name2 in originalEvent) {\n if (!has$2(deprecated, + name2)) {\n event[name2] = originalEvent[name2];\n }\n + \ }\n if (isNonNullable(event.composedPath)) {\n event.composedPath + = () => originalEvent.composedPath();\n }\n return event;\n + \ };\n const normalize$32 = (type2, originalEvent, fallbackTarget, + data2) => {\n var _a3;\n const event = clone$3(originalEvent, + data2);\n event.type = type2;\n if (isNullable(event.target)) + {\n event.target = (_a3 = event.srcElement) !== null && _a3 !== void + 0 ? _a3 : fallbackTarget;\n }\n if (needsNormalizing(originalEvent)) + {\n event.preventDefault = () => {\n event.defaultPrevented + = true;\n event.isDefaultPrevented = always;\n if (isFunction2(originalEvent.preventDefault)) + {\n originalEvent.preventDefault();\n }\n };\n + \ event.stopPropagation = () => {\n event.cancelBubble + = true;\n event.isPropagationStopped = always;\n if + (isFunction2(originalEvent.stopPropagation)) {\n originalEvent.stopPropagation();\n + \ }\n };\n event.stopImmediatePropagation = () + => {\n event.isImmediatePropagationStopped = always;\n event.stopPropagation();\n + \ };\n if (!hasIsDefaultPrevented(event)) {\n event.isDefaultPrevented + = event.defaultPrevented === true ? always : never;\n event.isPropagationStopped + = event.cancelBubble === true ? always : never;\n event.isImmediatePropagationStopped + = never;\n }\n }\n return event;\n };\n const + eventExpandoPrefix = \"mce-data-\";\n const mouseEventRe = /^(?:mouse|contextmenu)|click/;\n + \ const addEvent = (target, name2, callback, capture) => {\n if + (target.addEventListener) {\n target.addEventListener(name2, callback, + capture || false);\n } else if (target.attachEvent) {\n target.attachEvent(\"on\" + + name2, callback);\n }\n };\n const removeEvent = (target, + name2, callback, capture) => {\n if (target.removeEventListener) {\n + \ target.removeEventListener(name2, callback, capture || false);\n + \ } else if (target.detachEvent) {\n target.detachEvent(\"on\" + + name2, callback);\n }\n };\n const isMouseEvent = (event) + => isNonNullable(event) && mouseEventRe.test(event.type);\n const fix + = (originalEvent, data2) => {\n const event = normalize$32(originalEvent.type, + originalEvent, document, data2);\n if (isMouseEvent(originalEvent) + && isUndefined2(originalEvent.pageX) && !isUndefined2(originalEvent.clientX)) + {\n const eventDoc = event.target.ownerDocument || document;\n const + doc = eventDoc.documentElement;\n const body = eventDoc.body;\n const + mouseEvent = event;\n mouseEvent.pageX = originalEvent.clientX + + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft + || body && body.clientLeft || 0);\n mouseEvent.pageY = originalEvent.clientY + + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop + || body && body.clientTop || 0);\n }\n return event;\n };\n + \ const bindOnReady = (win, callback, eventUtils) => {\n const + doc = win.document, event = { type: \"ready\" };\n if (eventUtils.domLoaded) + {\n callback(event);\n return;\n }\n const + isDocReady = () => {\n return doc.readyState === \"complete\" || + doc.readyState === \"interactive\" && doc.body;\n };\n const + readyHandler = () => {\n removeEvent(win, \"DOMContentLoaded\", readyHandler);\n + \ removeEvent(win, \"load\", readyHandler);\n if (!eventUtils.domLoaded) + {\n eventUtils.domLoaded = true;\n callback(event);\n + \ }\n win = null;\n };\n if (isDocReady()) + {\n readyHandler();\n } else {\n addEvent(win, \"DOMContentLoaded\", + readyHandler);\n }\n if (!eventUtils.domLoaded) {\n addEvent(win, + \"load\", readyHandler);\n }\n };\n class EventUtils {\n + \ constructor() {\n this.domLoaded = false;\n this.events + = {};\n this.count = 1;\n this.expando = eventExpandoPrefix + + (+/* @__PURE__ */ new Date()).toString(32);\n this.hasMouseEnterLeave + = \"onmouseenter\" in document.documentElement;\n this.hasFocusIn + = \"onfocusin\" in document.documentElement;\n this.count = 1;\n + \ }\n bind(target, names, callback, scope) {\n const + self2 = this;\n let id, callbackList, i3, name2, fakeName, nativeHandler, + capture;\n const win = window;\n const defaultNativeHandler + = (evt) => {\n self2.executeHandlers(fix(evt || win.event), id);\n + \ };\n if (!target || target.nodeType === 3 || target.nodeType + === 8) {\n return;\n }\n if (!target[self2.expando]) + {\n id = self2.count++;\n target[self2.expando] = id;\n + \ self2.events[id] = {};\n } else {\n id = target[self2.expando];\n + \ }\n scope = scope || target;\n const namesList + = names.split(\" \");\n i3 = namesList.length;\n while (i3--) + {\n name2 = namesList[i3];\n nativeHandler = defaultNativeHandler;\n + \ fakeName = capture = false;\n if (name2 === \"DOMContentLoaded\") + {\n name2 = \"ready\";\n }\n if (self2.domLoaded + && name2 === \"ready\" && target.readyState === \"complete\") {\n callback.call(scope, + fix({ type: name2 }));\n continue;\n }\n if + (!self2.hasMouseEnterLeave) {\n fakeName = self2.mouseEnterLeave[name2];\n + \ if (fakeName) {\n nativeHandler = (evt) => {\n + \ const current = evt.currentTarget;\n let + related = evt.relatedTarget;\n if (related && current.contains) + {\n related = current.contains(related);\n } + else {\n while (related && related !== current) {\n related + = related.parentNode;\n }\n }\n if + (!related) {\n evt = fix(evt || win.event);\n evt.type + = evt.type === \"mouseout\" ? \"mouseleave\" : \"mouseenter\";\n evt.target + = current;\n self2.executeHandlers(evt, id);\n }\n + \ };\n }\n }\n if (!self2.hasFocusIn + && (name2 === \"focusin\" || name2 === \"focusout\")) {\n capture + = true;\n fakeName = name2 === \"focusin\" ? \"focus\" : \"blur\";\n + \ nativeHandler = (evt) => {\n evt = fix(evt || + win.event);\n evt.type = evt.type === \"focus\" ? \"focusin\" + : \"focusout\";\n self2.executeHandlers(evt, id);\n };\n + \ }\n callbackList = self2.events[id][name2];\n if + (!callbackList) {\n self2.events[id][name2] = callbackList = + [{\n func: callback,\n scope\n }];\n + \ callbackList.fakeName = fakeName;\n callbackList.capture + = capture;\n callbackList.nativeHandler = nativeHandler;\n if + (name2 === \"ready\") {\n bindOnReady(target, nativeHandler, + self2);\n } else {\n addEvent(target, fakeName + || name2, nativeHandler, capture);\n }\n } else {\n + \ if (name2 === \"ready\" && self2.domLoaded) {\n callback(fix({ + type: name2 }));\n } else {\n callbackList.push({\n + \ func: callback,\n scope\n });\n + \ }\n }\n }\n target = callbackList + = null;\n return callback;\n }\n unbind(target, names, + callback) {\n let callbackList, i3, ci, name2, eventMap;\n if + (!target || target.nodeType === 3 || target.nodeType === 8) {\n return + this;\n }\n const id = target[this.expando];\n if + (id) {\n eventMap = this.events[id];\n if (names) {\n + \ const namesList = names.split(\" \");\n i3 = namesList.length;\n + \ while (i3--) {\n name2 = namesList[i3];\n callbackList + = eventMap[name2];\n if (callbackList) {\n if + (callback) {\n ci = callbackList.length;\n while + (ci--) {\n if (callbackList[ci].func === callback) {\n + \ const nativeHandler = callbackList.nativeHandler;\n + \ const fakeName = callbackList.fakeName, capture = + callbackList.capture;\n callbackList = callbackList.slice(0, + ci).concat(callbackList.slice(ci + 1));\n callbackList.nativeHandler + = nativeHandler;\n callbackList.fakeName = fakeName;\n + \ callbackList.capture = capture;\n eventMap[name2] + = callbackList;\n }\n }\n }\n + \ if (!callback || callbackList.length === 0) {\n delete + eventMap[name2];\n removeEvent(target, callbackList.fakeName + || name2, callbackList.nativeHandler, callbackList.capture);\n }\n + \ }\n }\n } else {\n each$f(eventMap, + (callbackList2, name3) => {\n removeEvent(target, callbackList2.fakeName + || name3, callbackList2.nativeHandler, callbackList2.capture);\n });\n + \ eventMap = {};\n }\n for (name2 in eventMap) + {\n if (has$2(eventMap, name2)) {\n return this;\n + \ }\n }\n delete this.events[id];\n try + {\n delete target[this.expando];\n } catch (ex) {\n + \ target[this.expando] = null;\n }\n }\n return + this;\n }\n fire(target, name2, args) {\n return this.dispatch(target, + name2, args);\n }\n dispatch(target, name2, args) {\n let + id;\n if (!target || target.nodeType === 3 || target.nodeType === + 8) {\n return this;\n }\n const event = fix({\n + \ type: name2,\n target\n }, args);\n do + {\n id = target[this.expando];\n if (id) {\n this.executeHandlers(event, + id);\n }\n target = target.parentNode || target.ownerDocument + || target.defaultView || target.parentWindow;\n } while (target && + !event.isPropagationStopped());\n return this;\n }\n clean(target) + {\n let i3, children2;\n if (!target || target.nodeType + === 3 || target.nodeType === 8) {\n return this;\n }\n + \ if (target[this.expando]) {\n this.unbind(target);\n + \ }\n if (!target.getElementsByTagName) {\n target + = target.document;\n }\n if (target && target.getElementsByTagName) + {\n this.unbind(target);\n children2 = target.getElementsByTagName(\"*\");\n + \ i3 = children2.length;\n while (i3--) {\n target + = children2[i3];\n if (target[this.expando]) {\n this.unbind(target);\n + \ }\n }\n }\n return this;\n }\n + \ destroy() {\n this.events = {};\n }\n cancel(e2) + {\n if (e2) {\n e2.preventDefault();\n e2.stopImmediatePropagation();\n + \ }\n return false;\n }\n executeHandlers(evt, + id) {\n const container = this.events[id];\n const callbackList + = container && container[evt.type];\n if (callbackList) {\n for + (let i3 = 0, l2 = callbackList.length; i3 < l2; i3++) {\n const + callback = callbackList[i3];\n if (callback && callback.func.call(callback.scope, + evt) === false) {\n evt.preventDefault();\n }\n + \ if (evt.isImmediatePropagationStopped()) {\n return;\n + \ }\n }\n }\n }\n }\n EventUtils.Event + = new EventUtils();\n const each$c = Tools.each;\n const grep = + Tools.grep;\n const internalStyleName = \"data-mce-style\";\n const + legacySetAttribute = (elm, name2, value2) => {\n if (isNullable(value2) + || value2 === \"\") {\n remove$a(elm, name2);\n } else {\n + \ set$2(elm, name2, value2);\n }\n };\n const setupAttrHooks + = (styles, settings, getContext2) => {\n const keepValues = settings.keep_values;\n + \ const keepUrlHook = {\n set: (elm, value2, name2) => {\n + \ const sugarElm = SugarElement.fromDom(elm);\n if (isFunction2(settings.url_converter) + && isNonNullable(value2)) {\n value2 = settings.url_converter.call(settings.url_converter_scope + || getContext2(), value2, name2, elm[0]);\n }\n const + internalName = \"data-mce-\" + name2;\n legacySetAttribute(sugarElm, + internalName, value2);\n legacySetAttribute(sugarElm, name2, value2);\n + \ },\n get: (elm, name2) => {\n const sugarElm + = SugarElement.fromDom(elm);\n return get$9(sugarElm, \"data-mce-\" + + name2) || get$9(sugarElm, name2);\n }\n };\n const + attrHooks = {\n style: {\n set: (elm, value2) => {\n const + sugarElm = SugarElement.fromDom(elm);\n if (isObject2(value2)) + {\n setAll(sugarElm, value2);\n return;\n }\n + \ if (keepValues) {\n legacySetAttribute(sugarElm, + internalStyleName, value2);\n }\n remove$a(sugarElm, + \"style\");\n if (isString2(value2)) {\n setAll(sugarElm, + styles.parse(value2));\n }\n },\n get: + (elm) => {\n const sugarElm = SugarElement.fromDom(elm);\n const + value2 = get$9(sugarElm, internalStyleName) || get$9(sugarElm, \"style\");\n + \ return styles.serialize(styles.parse(value2), name(sugarElm));\n + \ }\n }\n };\n if (keepValues) {\n attrHooks.href + = attrHooks.src = keepUrlHook;\n }\n return attrHooks;\n };\n + \ const updateInternalStyleAttr = (styles, elm) => {\n const rawValue + = get$9(elm, \"style\");\n const value2 = styles.serialize(styles.parse(rawValue), + name(elm));\n legacySetAttribute(elm, internalStyleName, value2);\n + \ };\n const findNodeIndex = (node, normalized) => {\n let + idx = 0, lastNodeType, nodeType;\n if (node) {\n for (lastNodeType + = node.nodeType, node = node.previousSibling; node; node = node.previousSibling) + {\n nodeType = node.nodeType;\n if (normalized && nodeType + === 3) {\n if (nodeType === lastNodeType || !node.nodeValue.length) + {\n continue;\n }\n }\n idx++;\n + \ lastNodeType = nodeType;\n }\n }\n return + idx;\n };\n const numericalCssMap = Tools.makeMap(\"fill-opacity + font-weight line-height opacity orphans widows z-index zoom\", \" \");\n const + camelCaseToHyphens = (name2) => name2.replace(/[A-Z]/g, (v2) => \"-\" + v2.toLowerCase());\n + \ const DOMUtils = (doc, settings = {}) => {\n const addedStyles + = {};\n const win = window;\n const files = {};\n let + counter = 0;\n const stdMode = true;\n const boxModel = true;\n + \ const styleSheetLoader = instance.forElement(SugarElement.fromDom(doc), + {\n contentCssCors: settings.contentCssCors,\n referrerPolicy: + settings.referrerPolicy\n });\n const boundEvents = [];\n const + schema = settings.schema ? settings.schema : Schema({});\n const styles + = Styles({\n url_converter: settings.url_converter,\n url_converter_scope: + settings.url_converter_scope\n }, settings.schema);\n const + events = settings.ownEvents ? new EventUtils() : EventUtils.Event;\n const + blockElementsMap = schema.getBlockElements();\n const isBlock2 = (node) + => {\n if (isString2(node)) {\n return has$2(blockElementsMap, + node);\n } else {\n return isElement$6(node) && has$2(blockElementsMap, + node.nodeName);\n }\n };\n const get2 = (elm) => elm + && doc && isString2(elm) ? doc.getElementById(elm) : elm;\n const _get + = (elm) => {\n const value2 = get2(elm);\n return isNonNullable(value2) + ? SugarElement.fromDom(value2) : null;\n };\n const getAttrib + = (elm, name2, defaultVal) => {\n let value2;\n const $elm + = _get(elm);\n if (isNonNullable($elm) && isElement$7($elm)) {\n + \ const hook = attrHooks[name2];\n if (hook && hook.get) + {\n value2 = hook.get($elm.dom, name2);\n } else {\n + \ value2 = get$9($elm, name2);\n }\n }\n return + isNonNullable(value2) ? value2 : defaultVal !== null && defaultVal !== void + 0 ? defaultVal : \"\";\n };\n const getAttribs = (elm) => {\n + \ const node = get2(elm);\n return isNullable(node) ? [] + : node.attributes;\n };\n const setAttrib = (elm, name2, value2) + => {\n run(elm, (e2) => {\n if (isElement$6(e2)) {\n const + $elm = SugarElement.fromDom(e2);\n if (value2 === \"\") {\n value2 + = null;\n }\n const originalValue = get$9($elm, + name2);\n const hook = attrHooks[name2];\n if (hook + && hook.set) {\n hook.set($elm.dom, value2, name2);\n } + else {\n legacySetAttribute($elm, name2, value2);\n }\n + \ if (originalValue !== value2 && settings.onSetAttrib) {\n settings.onSetAttrib({\n + \ attrElm: $elm,\n attrName: name2,\n attrValue: + value2\n });\n }\n }\n });\n + \ };\n const clone2 = (node, deep2) => {\n return node.cloneNode(deep2);\n + \ };\n const getRoot = () => settings.root_element || doc.body;\n + \ const getViewPort = (argWin) => {\n const vp = getBounds(argWin);\n + \ return {\n x: vp.x,\n y: vp.y,\n w: + vp.width,\n h: vp.height\n };\n };\n const + getPos$1 = (elm, rootElm) => getPos(doc.body, get2(elm), rootElm);\n const + setStyle = (elm, name2, value2) => {\n const convertStyleToString + = (cssValue, cssName) => {\n if (isString2(cssValue)) {\n return + cssValue;\n } else if (isNumber2(cssValue)) {\n return + has$2(numericalCssMap, cssName) ? cssValue + \"\" : cssValue + \"px\";\n } + else {\n return map$2(cssValue, convertStyleToString);\n }\n + \ };\n const applyStyle2 = ($elm, cssName, cssValue) => {\n + \ const normalizedName = camelCaseToHyphens(cssName);\n if + (isNullable(cssValue) || cssValue === \"\") {\n remove$6($elm, + normalizedName);\n } else {\n set$1($elm, normalizedName, + convertStyleToString(cssValue, normalizedName));\n }\n };\n + \ run(elm, (e2) => {\n const $elm = SugarElement.fromDom(e2);\n + \ if (isString2(name2)) {\n applyStyle2($elm, name2, + value2);\n } else {\n each$f(name2, (v2, n3) => {\n + \ applyStyle2($elm, n3, v2);\n });\n }\n + \ if (settings.update_styles) {\n updateInternalStyleAttr(styles, + $elm);\n }\n });\n };\n const setStyles + = (elm, stylesArg) => {\n setStyle(elm, stylesArg);\n };\n + \ const getStyle2 = (elm, name2, computed) => {\n const $elm + = get2(elm);\n if (isNullable($elm) || !isElement$6($elm)) {\n return + void 0;\n }\n if (computed) {\n return get$7(SugarElement.fromDom($elm), + camelCaseToHyphens(name2));\n } else {\n name2 = name2.replace(/-(\\D)/g, + (a2, b2) => b2.toUpperCase());\n if (name2 === \"float\") {\n name2 + = \"cssFloat\";\n }\n return $elm.style ? $elm.style[name2] + : void 0;\n }\n };\n const getSize = (elm) => {\n let + w, h2;\n const $elm = get2(elm);\n w = getStyle2($elm, \"width\");\n + \ h2 = getStyle2($elm, \"height\");\n if (w.indexOf(\"px\") + === -1) {\n w = 0;\n }\n if (h2.indexOf(\"px\") + === -1) {\n h2 = 0;\n }\n return {\n w: + parseInt(w, 10) || $elm.offsetWidth || $elm.clientWidth,\n h: parseInt(h2, + 10) || $elm.offsetHeight || $elm.clientHeight\n };\n };\n + \ const getRect = (elm) => {\n const $elm = get2(elm);\n const + pos = getPos$1($elm);\n const size = getSize($elm);\n return + {\n x: pos.x,\n y: pos.y,\n w: size.w,\n + \ h: size.h\n };\n };\n const is2 = (elm, + selector) => {\n if (!elm) {\n return false;\n }\n + \ const elms = isArray$1(elm) ? elm : [elm];\n return exists(elms, + (e2) => {\n return is$1(SugarElement.fromDom(e2), selector);\n + \ });\n };\n const getParents2 = (elm, selector, root2, + collect) => {\n const result = [];\n let selectorVal;\n + \ let node = get2(elm);\n collect = collect === void 0;\n + \ root2 = root2 || (getRoot().nodeName !== \"BODY\" ? getRoot().parentNode + : null);\n if (isString2(selector)) {\n selectorVal = + selector;\n if (selector === \"*\") {\n selector = + isElement$6;\n } else {\n selector = (node2) => is2(node2, + selectorVal);\n }\n }\n while (node) {\n if + (node === root2 || isNullable(node.nodeType) || isDocument$1(node) || isDocumentFragment(node)) + {\n break;\n }\n if (!selector || selector(node)) + {\n if (collect) {\n result.push(node);\n } + else {\n return [node];\n }\n }\n node + = node.parentNode;\n }\n return collect ? result : null;\n + \ };\n const getParent = (node, selector, root2) => {\n const + parents2 = getParents2(node, selector, root2, false);\n return parents2 + && parents2.length > 0 ? parents2[0] : null;\n };\n const _findSib + = (node, selector, name2) => {\n let func = selector;\n if + (node) {\n if (isString2(selector)) {\n func = (node2) + => {\n return is2(node2, selector);\n };\n }\n + \ for (node = node[name2]; node; node = node[name2]) {\n if + (isFunction2(func) && func(node)) {\n return node;\n }\n + \ }\n }\n return null;\n };\n const + getNext = (node, selector) => _findSib(node, selector, \"nextSibling\");\n + \ const getPrev = (node, selector) => _findSib(node, selector, \"previousSibling\");\n + \ const select2 = (selector, scope) => {\n var _a3, _b;\n const + elm = (_b = (_a3 = get2(scope)) !== null && _a3 !== void 0 ? _a3 : settings.root_element) + !== null && _b !== void 0 ? _b : doc;\n return from(elm.querySelectorAll(selector));\n + \ };\n const run = function(elm, func, scope) {\n const + context2 = scope !== null && scope !== void 0 ? scope : this;\n const + node = isString2(elm) ? get2(elm) : elm;\n if (!node) {\n return + false;\n }\n if (isArray$1(node) && (node.length || node.length + === 0)) {\n const result = [];\n each$c(node, (elm2, + i3) => {\n if (elm2) {\n result.push(func.call(context2, + isString2(elm2) ? get2(elm2) : elm2, i3));\n }\n });\n + \ return result;\n } else {\n return func.call(context2, + node);\n }\n };\n const setAttribs = (elm, attrs) => + {\n run(elm, ($elm) => {\n each$f(attrs, (value2, name2) + => {\n setAttrib($elm, name2, value2);\n });\n });\n + \ };\n const setHTML = (elm, html2) => {\n run(elm, + (e2) => {\n const $elm = SugarElement.fromDom(e2);\n set2($elm, + html2);\n });\n };\n const add3 = (parentElm, name2, + attrs, html2, create3) => run(parentElm, (parentElm2) => {\n const + newElm = isString2(name2) ? doc.createElement(name2) : name2;\n if + (isNonNullable(attrs)) {\n setAttribs(newElm, attrs);\n }\n + \ if (html2) {\n if (!isString2(html2) && html2.nodeType) + {\n newElm.appendChild(html2);\n } else if (isString2(html2)) + {\n setHTML(newElm, html2);\n }\n }\n return + !create3 ? parentElm2.appendChild(newElm) : newElm;\n });\n const + create2 = (name2, attrs, html2) => add3(doc.createElement(name2), name2, attrs, + html2, true);\n const decode3 = Entities.decode;\n const encode2 + = Entities.encodeAllRaw;\n const createHTML = (name2, attrs, html2 + = \"\") => {\n let outHtml = \"\", key;\n outHtml += \"<\" + + name2;\n for (key in attrs) {\n if (hasNonNullableKey(attrs, + key)) {\n outHtml += \" \" + key + '=\"' + encode2(attrs[key]) + + '\"';\n }\n }\n if (isEmpty$3(html2) && has$2(schema.getVoidElements(), + name2)) {\n return outHtml + \" />\";\n } else {\n return + outHtml + \">\" + html2 + \"\";\n }\n };\n + \ const createFragment2 = (html2) => {\n let node;\n const + container = doc.createElement(\"div\");\n const frag = doc.createDocumentFragment();\n + \ frag.appendChild(container);\n if (html2) {\n container.innerHTML + = html2;\n }\n while (node = container.firstChild) {\n frag.appendChild(node);\n + \ }\n frag.removeChild(container);\n return frag;\n + \ };\n const remove3 = (node, keepChildren) => {\n return + run(node, (n3) => {\n const $node = SugarElement.fromDom(n3);\n + \ if (keepChildren) {\n each$g(children($node), (child2) + => {\n if (isText$9(child2) && child2.dom.length === 0) {\n + \ remove$5(child2);\n } else {\n before$3($node, + child2);\n }\n });\n }\n remove$5($node);\n + \ return $node.dom;\n });\n };\n const removeAllAttribs + = (e2) => run(e2, (e3) => {\n const attrs = e3.attributes;\n for + (let i3 = attrs.length - 1; i3 >= 0; i3--) {\n e3.removeAttributeNode(attrs.item(i3));\n + \ }\n });\n const parseStyle = (cssText) => styles.parse(cssText);\n + \ const serializeStyle = (stylesArg, name2) => styles.serialize(stylesArg, + name2);\n const addStyle = (cssText) => {\n let head2, styleElm;\n + \ if (self2 !== DOMUtils.DOM && doc === document) {\n if + (addedStyles[cssText]) {\n return;\n }\n addedStyles[cssText] + = true;\n }\n styleElm = doc.getElementById(\"mceDefaultStyles\");\n + \ if (!styleElm) {\n styleElm = doc.createElement(\"style\");\n + \ styleElm.id = \"mceDefaultStyles\";\n styleElm.type + = \"text/css\";\n head2 = doc.getElementsByTagName(\"head\")[0];\n + \ if (head2.firstChild) {\n head2.insertBefore(styleElm, + head2.firstChild);\n } else {\n head2.appendChild(styleElm);\n + \ }\n }\n if (styleElm.styleSheet) {\n styleElm.styleSheet.cssText + += cssText;\n } else {\n styleElm.appendChild(doc.createTextNode(cssText));\n + \ }\n };\n const loadCSS = (urls) => {\n if + (!urls) {\n urls = \"\";\n }\n each$g(urls.split(\",\"), + (url2) => {\n files[url2] = true;\n styleSheetLoader.load(url2).catch(noop);\n + \ });\n };\n const toggleClass2 = (elm, cls, state) + => {\n run(elm, (e2) => {\n if (isElement$6(e2)) {\n const + $elm = SugarElement.fromDom(e2);\n const classes = cls.split(\" + \");\n each$g(classes, (c2) => {\n if (isNonNullable(state)) + {\n const fn = state ? add$2 : remove$7;\n fn($elm, + c2);\n } else {\n toggle$1($elm, c2);\n }\n + \ });\n }\n });\n };\n const + addClass = (elm, cls) => {\n toggleClass2(elm, cls, true);\n };\n + \ const removeClass = (elm, cls) => {\n toggleClass2(elm, cls, + false);\n };\n const hasClass2 = (elm, cls) => {\n const + $elm = _get(elm);\n const classes = cls.split(\" \");\n return + forall(classes, (c2) => has2($elm, c2));\n };\n const show = + (elm) => {\n run(elm, (e2) => remove$6(SugarElement.fromDom(e2), + \"display\"));\n };\n const hide = (elm) => {\n run(elm, + (e2) => set$1(SugarElement.fromDom(e2), \"display\", \"none\"));\n };\n + \ const isHidden = (elm) => {\n const $elm = _get(elm);\n return + is$2(getRaw$1($elm, \"display\"), \"none\");\n };\n const uniqueId2 + = (prefix) => (!prefix ? \"mce_\" : prefix) + counter++;\n const getOuterHTML + = (elm) => {\n const $elm = _get(elm);\n return isElement$6($elm.dom) + ? $elm.dom.outerHTML : getOuter($elm);\n };\n const setOuterHTML + = (elm, html2) => {\n run(elm, ($elm) => {\n if (isElement$6($elm)) + {\n $elm.outerHTML = html2;\n }\n });\n };\n + \ const insertAfter2 = (node, reference2) => {\n const referenceNode + = get2(reference2);\n return run(node, (node2) => {\n const + parent2 = referenceNode.parentNode;\n const nextSibling2 = referenceNode.nextSibling;\n + \ if (nextSibling2) {\n parent2.insertBefore(node2, + nextSibling2);\n } else {\n parent2.appendChild(node2);\n + \ }\n return node2;\n });\n };\n const + replace2 = (newElm, oldElm, keepChildren) => run(oldElm, (oldElm2) => {\n + \ if (isArray$1(oldElm2)) {\n newElm = newElm.cloneNode(true);\n + \ }\n if (keepChildren) {\n each$c(grep(oldElm2.childNodes), + (node) => {\n newElm.appendChild(node);\n });\n }\n + \ return oldElm2.parentNode.replaceChild(newElm, oldElm2);\n });\n + \ const rename = (elm, name2) => {\n let newElm;\n if + (elm.nodeName !== name2.toUpperCase()) {\n newElm = create2(name2);\n + \ each$c(getAttribs(elm), (attrNode) => {\n setAttrib(newElm, + attrNode.nodeName, getAttrib(elm, attrNode.nodeName));\n });\n + \ replace2(newElm, elm, true);\n }\n return newElm + || elm;\n };\n const findCommonAncestor = (a2, b2) => {\n let + ps = a2, pe;\n while (ps) {\n pe = b2;\n while + (pe && ps !== pe) {\n pe = pe.parentNode;\n }\n if + (ps === pe) {\n break;\n }\n ps = ps.parentNode;\n + \ }\n if (!ps && a2.ownerDocument) {\n return + a2.ownerDocument.documentElement;\n }\n return ps;\n };\n + \ const isNonEmptyElement2 = (node) => {\n if (isElement$6(node)) + {\n const isNamedAnchor2 = node.nodeName.toLowerCase() === \"a\" + && !getAttrib(node, \"href\") && getAttrib(node, \"id\");\n if + (getAttrib(node, \"name\") || getAttrib(node, \"data-mce-bookmark\") || isNamedAnchor2) + {\n return true;\n }\n }\n return + false;\n };\n const isEmpty3 = (node, elements) => {\n let + type2, name2, brCount = 0;\n if (isNonEmptyElement2(node)) {\n return + false;\n }\n node = node.firstChild;\n if (node) + {\n const walker = new DomTreeWalker(node, node.parentNode);\n + \ const whitespace = schema ? schema.getWhitespaceElements() : {};\n + \ elements = elements || (schema ? schema.getNonEmptyElements() + : null);\n do {\n type2 = node.nodeType;\n if + (isElement$6(node)) {\n const bogusVal = node.getAttribute(\"data-mce-bogus\");\n + \ if (bogusVal) {\n node = walker.next(bogusVal + === \"all\");\n continue;\n }\n name2 + = node.nodeName.toLowerCase();\n if (elements && elements[name2]) + {\n if (name2 === \"br\") {\n brCount++;\n + \ node = walker.next();\n continue;\n + \ }\n return false;\n }\n + \ if (isNonEmptyElement2(node)) {\n return + false;\n }\n }\n if (type2 === 8) + {\n return false;\n }\n if (type2 + === 3 && !isWhitespaceText(node.nodeValue)) {\n return false;\n + \ }\n if (type2 === 3 && node.parentNode && whitespace[node.parentNode.nodeName] + && isWhitespaceText(node.nodeValue)) {\n return false;\n }\n + \ node = walker.next();\n } while (node);\n }\n + \ return brCount <= 1;\n };\n const createRng = () => + doc.createRange();\n const split2 = (parentElm, splitElm, replacementElm) + => {\n let range2 = createRng();\n let beforeFragment;\n + \ let afterFragment;\n let parentNode;\n if (parentElm + && splitElm) {\n range2.setStart(parentElm.parentNode, findNodeIndex(parentElm));\n + \ range2.setEnd(splitElm.parentNode, findNodeIndex(splitElm));\n + \ beforeFragment = range2.extractContents();\n range2 + = createRng();\n range2.setStart(splitElm.parentNode, findNodeIndex(splitElm) + + 1);\n range2.setEnd(parentElm.parentNode, findNodeIndex(parentElm) + + 1);\n afterFragment = range2.extractContents();\n parentNode + = parentElm.parentNode;\n parentNode.insertBefore(trimNode(self2, + beforeFragment), parentElm);\n if (replacementElm) {\n parentNode.insertBefore(replacementElm, + parentElm);\n } else {\n parentNode.insertBefore(splitElm, + parentElm);\n }\n parentNode.insertBefore(trimNode(self2, + afterFragment), parentElm);\n remove3(parentElm);\n return + replacementElm || splitElm;\n }\n };\n const bind2 + = (target, name2, func, scope) => {\n if (isArray$1(target)) {\n + \ let i3 = target.length;\n const rv = [];\n while + (i3--) {\n rv[i3] = bind2(target[i3], name2, func, scope);\n + \ }\n return rv;\n } else {\n if + (settings.collect && (target === doc || target === win)) {\n boundEvents.push([\n + \ target,\n name2,\n func,\n scope\n + \ ]);\n }\n return events.bind(target, name2, + func, scope || self2);\n }\n };\n const unbind2 = (target, + name2, func) => {\n if (isArray$1(target)) {\n let i3 + = target.length;\n const rv = [];\n while (i3--) {\n + \ rv[i3] = unbind2(target[i3], name2, func);\n }\n + \ return rv;\n } else {\n if (boundEvents.length + > 0 && (target === doc || target === win)) {\n let i3 = boundEvents.length;\n + \ while (i3--) {\n const item = boundEvents[i3];\n + \ if (target === item[0] && (!name2 || name2 === item[1]) && + (!func || func === item[2])) {\n events.unbind(item[0], item[1], + item[2]);\n }\n }\n }\n return + events.unbind(target, name2, func);\n }\n };\n const + dispatch = (target, name2, evt) => events.dispatch(target, name2, evt);\n + \ const fire = (target, name2, evt) => events.dispatch(target, name2, + evt);\n const getContentEditable = (node) => {\n if (node + && isElement$6(node)) {\n const contentEditable = node.getAttribute(\"data-mce-contenteditable\");\n + \ if (contentEditable && contentEditable !== \"inherit\") {\n return + contentEditable;\n }\n return node.contentEditable !== + \"inherit\" ? node.contentEditable : null;\n } else {\n return + null;\n }\n };\n const getContentEditableParent = (node) + => {\n const root2 = getRoot();\n let state = null;\n for + (; node && node !== root2; node = node.parentNode) {\n state = + getContentEditable(node);\n if (state !== null) {\n break;\n + \ }\n }\n return state;\n };\n const + destroy2 = () => {\n if (boundEvents.length > 0) {\n let + i3 = boundEvents.length;\n while (i3--) {\n const + item = boundEvents[i3];\n events.unbind(item[0], item[1], item[2]);\n + \ }\n }\n each$f(files, (_2, url2) => {\n styleSheetLoader.unload(url2);\n + \ delete files[url2];\n });\n };\n const + isChildOf = (node, parent2) => {\n return node === parent2 || parent2.contains(node);\n + \ };\n const dumpRng = (r4) => \"startContainer: \" + r4.startContainer.nodeName + + \", startOffset: \" + r4.startOffset + \", endContainer: \" + r4.endContainer.nodeName + + \", endOffset: \" + r4.endOffset;\n const self2 = {\n doc,\n + \ settings,\n win,\n files,\n stdMode,\n + \ boxModel,\n styleSheetLoader,\n boundEvents,\n + \ styles,\n schema,\n events,\n isBlock: + isBlock2,\n root: null,\n clone: clone2,\n getRoot,\n + \ getViewPort,\n getRect,\n getSize,\n getParent,\n + \ getParents: getParents2,\n get: get2,\n getNext,\n + \ getPrev,\n select: select2,\n is: is2,\n add: + add3,\n create: create2,\n createHTML,\n createFragment: + createFragment2,\n remove: remove3,\n setStyle,\n getStyle: + getStyle2,\n setStyles,\n removeAllAttribs,\n setAttrib,\n + \ setAttribs,\n getAttrib,\n getPos: getPos$1,\n + \ parseStyle,\n serializeStyle,\n addStyle,\n loadCSS,\n + \ addClass,\n removeClass,\n hasClass: hasClass2,\n + \ toggleClass: toggleClass2,\n show,\n hide,\n isHidden,\n + \ uniqueId: uniqueId2,\n setHTML,\n getOuterHTML,\n + \ setOuterHTML,\n decode: decode3,\n encode: encode2,\n + \ insertAfter: insertAfter2,\n replace: replace2,\n rename,\n + \ findCommonAncestor,\n run,\n getAttribs,\n isEmpty: + isEmpty3,\n createRng,\n nodeIndex: findNodeIndex,\n split: + split2,\n bind: bind2,\n unbind: unbind2,\n fire,\n + \ dispatch,\n getContentEditable,\n getContentEditableParent,\n + \ destroy: destroy2,\n isChildOf,\n dumpRng\n };\n + \ const attrHooks = setupAttrHooks(styles, settings, constant2(self2));\n + \ return self2;\n };\n DOMUtils.DOM = DOMUtils(document);\n + \ DOMUtils.nodeIndex = findNodeIndex;\n const DOM$b = DOMUtils.DOM;\n + \ const QUEUED = 0;\n const LOADING = 1;\n const LOADED = 2;\n + \ const FAILED = 3;\n class ScriptLoader {\n constructor(settings + = {}) {\n this.states = {};\n this.queue = [];\n this.scriptLoadedCallbacks + = {};\n this.queueLoadedCallbacks = [];\n this.loading = + false;\n this.settings = settings;\n }\n _setReferrerPolicy(referrerPolicy) + {\n this.settings.referrerPolicy = referrerPolicy;\n }\n loadScript(url2) + {\n return new Promise((resolve2, reject) => {\n const + dom3 = DOM$b;\n let elm;\n const cleanup = () => {\n + \ dom3.remove(id);\n if (elm) {\n elm.onerror + = elm.onload = elm = null;\n }\n };\n const + done = () => {\n cleanup();\n resolve2();\n };\n + \ const error3 = () => {\n cleanup();\n reject(\"Failed + to load script: \" + url2);\n };\n const id = dom3.uniqueId();\n + \ elm = document.createElement(\"script\");\n elm.id + = id;\n elm.type = \"text/javascript\";\n elm.src = + Tools._addCacheSuffix(url2);\n if (this.settings.referrerPolicy) + {\n dom3.setAttrib(elm, \"referrerpolicy\", this.settings.referrerPolicy);\n + \ }\n elm.onload = done;\n elm.onerror = error3;\n + \ (document.getElementsByTagName(\"head\")[0] || document.body).appendChild(elm);\n + \ });\n }\n isDone(url2) {\n return this.states[url2] + === LOADED;\n }\n markDone(url2) {\n this.states[url2] + = LOADED;\n }\n add(url2) {\n const self2 = this;\n + \ self2.queue.push(url2);\n const state = self2.states[url2];\n + \ if (state === void 0) {\n self2.states[url2] = QUEUED;\n + \ }\n return new Promise((resolve2, reject) => {\n if + (!self2.scriptLoadedCallbacks[url2]) {\n self2.scriptLoadedCallbacks[url2] + = [];\n }\n self2.scriptLoadedCallbacks[url2].push({\n + \ resolve: resolve2,\n reject\n });\n + \ });\n }\n load(url2) {\n return this.add(url2);\n + \ }\n remove(url2) {\n delete this.states[url2];\n delete + this.scriptLoadedCallbacks[url2];\n }\n loadQueue() {\n const + queue = this.queue;\n this.queue = [];\n return this.loadScripts(queue);\n + \ }\n loadScripts(scripts) {\n const self2 = this;\n + \ const execCallbacks = (name2, url2) => {\n get$a(self2.scriptLoadedCallbacks, + url2).each((callbacks) => {\n each$g(callbacks, (callback) => + callback[name2](url2));\n });\n delete self2.scriptLoadedCallbacks[url2];\n + \ };\n const processResults = (results) => {\n const + failures = filter$6(results, (result) => result.status === \"rejected\");\n + \ if (failures.length > 0) {\n return Promise.reject(bind$3(failures, + ({ reason }) => isArray$1(reason) ? reason : [reason]));\n } else + {\n return Promise.resolve();\n }\n };\n + \ const load = (urls) => Promise.allSettled(map$3(urls, (url2) => + {\n if (self2.states[url2] === LOADED) {\n execCallbacks(\"resolve\", + url2);\n return Promise.resolve();\n } else if (self2.states[url2] + === FAILED) {\n execCallbacks(\"reject\", url2);\n return + Promise.reject(url2);\n } else {\n self2.states[url2] + = LOADING;\n return self2.loadScript(url2).then(() => {\n self2.states[url2] + = LOADED;\n execCallbacks(\"resolve\", url2);\n const + queue = self2.queue;\n if (queue.length > 0) {\n self2.queue + = [];\n return load(queue).then(processResults);\n }\n + \ }, () => {\n self2.states[url2] = FAILED;\n execCallbacks(\"reject\", + url2);\n return Promise.reject(url2);\n });\n + \ }\n }));\n const processQueue = (urls) => {\n + \ self2.loading = true;\n return load(urls).then((results) + => {\n self2.loading = false;\n const nextQueuedItem + = self2.queueLoadedCallbacks.shift();\n Optional.from(nextQueuedItem).each(call);\n + \ return processResults(results);\n });\n };\n + \ const uniqueScripts = stringArray(scripts);\n if (self2.loading) + {\n return new Promise((resolve2, reject) => {\n self2.queueLoadedCallbacks.push(() + => processQueue(uniqueScripts).then(resolve2, reject));\n });\n + \ } else {\n return processQueue(uniqueScripts);\n }\n + \ }\n }\n ScriptLoader.ScriptLoader = new ScriptLoader();\n + \ const Cell = (initial) => {\n let value2 = initial;\n const + get2 = () => {\n return value2;\n };\n const set3 = + (v2) => {\n value2 = v2;\n };\n return {\n get: + get2,\n set: set3\n };\n };\n const isRaw = (str) + => isObject2(str) && has$2(str, \"raw\");\n const isTokenised = (str) + => isArray$1(str) && str.length > 1;\n const data = {};\n const + currentCode = Cell(\"en\");\n const getLanguageData = () => get$a(data, + currentCode.get());\n const getData$1 = () => map$2(data, (value2) => + ({ ...value2 }));\n const setCode = (newCode) => {\n if (newCode) + {\n currentCode.set(newCode);\n }\n };\n const getCode + = () => currentCode.get();\n const add$1 = (code2, items) => {\n let + langData = data[code2];\n if (!langData) {\n data[code2] = + langData = {};\n }\n each$f(items, (translation, name2) => {\n + \ langData[name2.toLowerCase()] = translation;\n });\n };\n + \ const translate = (text3) => {\n const langData = getLanguageData().getOr({});\n + \ const toString = (obj) => {\n if (isFunction2(obj)) {\n return + Object.prototype.toString.call(obj);\n }\n return !isEmpty3(obj) + ? \"\" + obj : \"\";\n };\n const isEmpty3 = (text4) => text4 + === \"\" || text4 === null || text4 === void 0;\n const getLangData + = (text4) => {\n const textstr = toString(text4);\n return + get$a(langData, textstr.toLowerCase()).map(toString).getOr(textstr);\n };\n + \ const removeContext = (str) => str.replace(/{context:\\w+}$/, \"\");\n + \ if (isEmpty3(text3)) {\n return \"\";\n }\n if + (isRaw(text3)) {\n return toString(text3.raw);\n }\n if + (isTokenised(text3)) {\n const values2 = text3.slice(1);\n const + substitued = getLangData(text3[0]).replace(/\\{([0-9]+)\\}/g, ($1, $2) => + has$2(values2, $2) ? toString(values2[$2]) : $1);\n return removeContext(substitued);\n + \ }\n return removeContext(getLangData(text3));\n };\n const + isRtl$1 = () => getLanguageData().bind((items) => get$a(items, \"_dir\")).exists((dir) + => dir === \"rtl\");\n const hasCode = (code2) => has$2(data, code2);\n + \ const I18n = {\n getData: getData$1,\n setCode,\n getCode,\n + \ add: add$1,\n translate,\n isRtl: isRtl$1,\n hasCode\n + \ };\n const AddOnManager = () => {\n const items = [];\n + \ const urls = {};\n const lookup2 = {};\n const _listeners + = [];\n const runListeners = (name2, state) => {\n const matchedListeners + = filter$6(_listeners, (listener) => listener.name === name2 && listener.state + === state);\n each$g(matchedListeners, (listener) => listener.resolve());\n + \ };\n const isLoaded = (name2) => has$2(urls, name2);\n const + isAdded = (name2) => has$2(lookup2, name2);\n const get2 = (name2) + => {\n if (lookup2[name2]) {\n return lookup2[name2].instance;\n + \ }\n return void 0;\n };\n const loadLanguagePack + = (name2, languages) => {\n const language = I18n.getCode();\n const + wrappedLanguages = \",\" + (languages || \"\") + \",\";\n if (!language + || languages && wrappedLanguages.indexOf(\",\" + language + \",\") === -1) + {\n return;\n }\n ScriptLoader.ScriptLoader.add(urls[name2] + + \"/langs/\" + language + \".js\");\n };\n const requireLangPack + = (name2, languages) => {\n if (AddOnManager.languageLoad !== false) + {\n if (isLoaded(name2)) {\n loadLanguagePack(name2, + languages);\n } else {\n waitFor(name2, \"loaded\").then(() + => loadLanguagePack(name2, languages));\n }\n }\n };\n + \ const add3 = (id, addOn) => {\n items.push(addOn);\n lookup2[id] + = { instance: addOn };\n runListeners(id, \"added\");\n return + addOn;\n };\n const remove3 = (name2) => {\n delete + urls[name2];\n delete lookup2[name2];\n };\n const + createUrl = (baseUrl, dep) => {\n if (isString2(dep)) {\n return + isString2(baseUrl) ? {\n prefix: \"\",\n resource: + dep,\n suffix: \"\"\n } : {\n prefix: + baseUrl.prefix,\n resource: dep,\n suffix: baseUrl.suffix\n + \ };\n } else {\n return dep;\n }\n + \ };\n const load = (name2, addOnUrl) => {\n if (urls[name2]) + {\n return Promise.resolve();\n }\n let urlString + = isString2(addOnUrl) ? addOnUrl : addOnUrl.prefix + addOnUrl.resource + addOnUrl.suffix;\n + \ if (urlString.indexOf(\"/\") !== 0 && urlString.indexOf(\"://\") + === -1) {\n urlString = AddOnManager.baseURL + \"/\" + urlString;\n + \ }\n urls[name2] = urlString.substring(0, urlString.lastIndexOf(\"/\"));\n + \ const done = () => {\n runListeners(name2, \"loaded\");\n + \ return Promise.resolve();\n };\n if (lookup2[name2]) + {\n return done();\n } else {\n return ScriptLoader.ScriptLoader.add(urlString).then(done);\n + \ }\n };\n const waitFor = (name2, state = \"added\") + => {\n if (state === \"added\" && isAdded(name2)) {\n return + Promise.resolve();\n } else if (state === \"loaded\" && isLoaded(name2)) + {\n return Promise.resolve();\n } else {\n return + new Promise((resolve2) => {\n _listeners.push({\n name: + name2,\n state,\n resolve: resolve2\n });\n + \ });\n }\n };\n return {\n items,\n + \ urls,\n lookup: lookup2,\n get: get2,\n requireLangPack,\n + \ add: add3,\n remove: remove3,\n createUrl,\n load,\n + \ waitFor\n };\n };\n AddOnManager.languageLoad = + true;\n AddOnManager.baseURL = \"\";\n AddOnManager.PluginManager + = AddOnManager();\n AddOnManager.ThemeManager = AddOnManager();\n AddOnManager.ModelManager + = AddOnManager();\n const singleton = (doRevoke) => {\n const + subject = Cell(Optional.none());\n const revoke = () => subject.get().each(doRevoke);\n + \ const clear2 = () => {\n revoke();\n subject.set(Optional.none());\n + \ };\n const isSet2 = () => subject.get().isSome();\n const + get2 = () => subject.get();\n const set3 = (s2) => {\n revoke();\n + \ subject.set(Optional.some(s2));\n };\n return {\n + \ clear: clear2,\n isSet: isSet2,\n get: get2,\n + \ set: set3\n };\n };\n const value$2 = () => {\n + \ const subject = singleton(noop);\n const on2 = (f2) => subject.get().each(f2);\n + \ return {\n ...subject,\n on: on2\n };\n };\n + \ const first$1 = (fn, rate) => {\n let timer = null;\n const + cancel = () => {\n if (!isNull(timer)) {\n clearTimeout(timer);\n + \ timer = null;\n }\n };\n const throttle + = (...args) => {\n if (isNull(timer)) {\n timer = setTimeout(() + => {\n timer = null;\n fn.apply(null, args);\n }, + rate);\n }\n };\n return {\n cancel,\n throttle\n + \ };\n };\n const last$1 = (fn, rate) => {\n let timer + = null;\n const cancel = () => {\n if (!isNull(timer)) {\n + \ clearTimeout(timer);\n timer = null;\n }\n + \ };\n const throttle = (...args) => {\n cancel();\n + \ timer = setTimeout(() => {\n timer = null;\n fn.apply(null, + args);\n }, rate);\n };\n return {\n cancel,\n + \ throttle\n };\n };\n const descendants$1 = (scope, + predicate) => {\n let result = [];\n each$g(children(scope), + (x2) => {\n if (predicate(x2)) {\n result = result.concat([x2]);\n + \ }\n result = result.concat(descendants$1(x2, predicate));\n + \ });\n return result;\n };\n const descendants = (scope, + selector) => all(selector, scope);\n const annotation = constant2(\"mce-annotation\");\n + \ const dataAnnotation = constant2(\"data-mce-annotation\");\n const + dataAnnotationId = constant2(\"data-mce-annotation-uid\");\n const dataAnnotationActive + = constant2(\"data-mce-annotation-active\");\n const identify = (editor, + annotationName) => {\n const rng = editor.selection.getRng();\n const + start2 = SugarElement.fromDom(rng.startContainer);\n const root2 = + SugarElement.fromDom(editor.getBody());\n const selector = annotationName.fold(() + => \".\" + annotation(), (an) => `[${dataAnnotation()}=\"${an}\"]`);\n const + newStart = child$1(start2, rng.startOffset).getOr(start2);\n const + closest2 = closest$3(newStart, selector, (n3) => eq2(n3, root2));\n const + getAttr = (c2, property) => {\n if (has$1(c2, property)) {\n return + Optional.some(get$9(c2, property));\n } else {\n return + Optional.none();\n }\n };\n return closest2.bind((c2) + => getAttr(c2, `${dataAnnotationId()}`).bind((uid) => getAttr(c2, `${dataAnnotation()}`).map((name2) + => {\n const elements = findMarkers(editor, uid);\n return + {\n uid,\n name: name2,\n elements\n };\n + \ })));\n };\n const isAnnotation = (elem) => isElement$7(elem) + && has2(elem, annotation());\n const findMarkers = (editor, uid) => {\n + \ const body = SugarElement.fromDom(editor.getBody());\n return + descendants(body, `[${dataAnnotationId()}=\"${uid}\"]`);\n };\n const + findAll = (editor, name2) => {\n const body = SugarElement.fromDom(editor.getBody());\n + \ const markers = descendants(body, `[${dataAnnotation()}=\"${name2}\"]`);\n + \ const directory = {};\n each$g(markers, (m2) => {\n const + uid = get$9(m2, dataAnnotationId());\n const nodesAlready = get$a(directory, + uid).getOr([]);\n directory[uid] = nodesAlready.concat([m2]);\n });\n + \ return directory;\n };\n const setup$x = (editor, registry2) + => {\n const changeCallbacks = Cell({});\n const initData = + () => ({\n listeners: [],\n previous: value$2()\n });\n + \ const withCallbacks = (name2, f2) => {\n updateCallbacks(name2, + (data2) => {\n f2(data2);\n return data2;\n });\n + \ };\n const updateCallbacks = (name2, f2) => {\n const + callbackMap = changeCallbacks.get();\n const data2 = get$a(callbackMap, + name2).getOrThunk(initData);\n const outputData = f2(data2);\n callbackMap[name2] + = outputData;\n changeCallbacks.set(callbackMap);\n };\n const + fireCallbacks = (name2, uid, elements) => {\n withCallbacks(name2, + (data2) => {\n each$g(data2.listeners, (f2) => f2(true, name2, + {\n uid,\n nodes: map$3(elements, (elem) => elem.dom)\n + \ }));\n });\n };\n const fireNoAnnotation + = (name2) => {\n withCallbacks(name2, (data2) => {\n each$g(data2.listeners, + (f2) => f2(false, name2));\n });\n };\n const toggleActiveAttr + = (uid, state) => {\n each$g(findMarkers(editor, uid), (span) => + {\n if (state) {\n set$2(span, dataAnnotationActive(), + \"true\");\n } else {\n remove$a(span, dataAnnotationActive());\n + \ }\n });\n };\n const onNodeChange = last$1(() + => {\n const annotations = sort(registry2.getNames());\n each$g(annotations, + (name2) => {\n updateCallbacks(name2, (data2) => {\n const + prev2 = data2.previous.get();\n identify(editor, Optional.some(name2)).fold(() + => {\n prev2.each((uid) => {\n fireNoAnnotation(name2);\n + \ data2.previous.clear();\n toggleActiveAttr(uid, + false);\n });\n }, ({ uid, name: name3, elements + }) => {\n if (!is$2(prev2, uid)) {\n prev2.each((uid2) + => toggleActiveAttr(uid2, false));\n fireCallbacks(name3, + uid, elements);\n data2.previous.set(uid);\n toggleActiveAttr(uid, + true);\n }\n });\n return {\n previous: + data2.previous,\n listeners: data2.listeners\n };\n + \ });\n });\n }, 30);\n editor.on(\"remove\", + () => {\n onNodeChange.cancel();\n });\n editor.on(\"NodeChange\", + () => {\n onNodeChange.throttle();\n });\n const addListener + = (name2, f2) => {\n updateCallbacks(name2, (data2) => ({\n previous: + data2.previous,\n listeners: data2.listeners.concat([f2])\n }));\n + \ };\n return { addListener };\n };\n const setup$w + = (editor, registry2) => {\n const identifyParserNode = (span) => Optional.from(span.attr(dataAnnotation())).bind(registry2.lookup);\n + \ editor.serializer.addTempAttr(dataAnnotationActive());\n editor.serializer.addNodeFilter(\"span\", + (spans) => {\n each$g(spans, (span) => {\n identifyParserNode(span).each((settings) + => {\n if (settings.persistent === false) {\n span.unwrap();\n + \ }\n });\n });\n });\n };\n const + create$b = () => {\n const annotations = {};\n const register2 + = (name2, settings) => {\n annotations[name2] = {\n name: + name2,\n settings\n };\n };\n const lookup2 + = (name2) => get$a(annotations, name2).map((a2) => a2.settings);\n const + getNames = () => keys2(annotations);\n return {\n register: + register2,\n lookup: lookup2,\n getNames\n };\n };\n + \ let unique = 0;\n const generate$1 = (prefix) => {\n const + date = /* @__PURE__ */ new Date();\n const time = date.getTime();\n + \ const random = Math.floor(Math.random() * 1e9);\n unique++;\n + \ return prefix + \"_\" + random + unique + String(time);\n };\n + \ const add2 = (element, classes) => {\n each$g(classes, (x2) => + {\n add$2(element, x2);\n });\n };\n const clone$2 + = (original, isDeep) => SugarElement.fromDom(original.dom.cloneNode(isDeep));\n + \ const shallow$1 = (original) => clone$2(original, false);\n const + deep$1 = (original) => clone$2(original, true);\n const shallowAs = (original, + tag) => {\n const nu2 = SugarElement.fromTag(tag);\n const attributes + = clone$4(original);\n setAll$1(nu2, attributes);\n return nu2;\n + \ };\n const mutate = (original, tag) => {\n const nu2 = shallowAs(original, + tag);\n after$4(original, nu2);\n const children$1 = children(original);\n + \ append(nu2, children$1);\n remove$5(original);\n return + nu2;\n };\n const TextWalker = (startNode, rootNode, isBoundary2 + = never) => {\n const walker = new DomTreeWalker(startNode, rootNode);\n + \ const walk2 = (direction) => {\n let next2;\n do + {\n next2 = walker[direction]();\n } while (next2 && !isText$8(next2) + && !isBoundary2(next2));\n return Optional.from(next2).filter(isText$8);\n + \ };\n return {\n current: () => Optional.from(walker.current()).filter(isText$8),\n + \ next: () => walk2(\"next\"),\n prev: () => walk2(\"prev\"),\n + \ prev2: () => walk2(\"prev2\")\n };\n };\n const + TextSeeker = (dom3, isBoundary2) => {\n const isBlockBoundary = isBoundary2 + ? isBoundary2 : (node) => dom3.isBlock(node) || isBr$5(node) || isContentEditableFalse$a(node);\n + \ const walk2 = (node, offset, walker, process2) => {\n if + (isText$8(node)) {\n const newOffset = process2(node, offset, node.data);\n + \ if (newOffset !== -1) {\n return Optional.some({\n + \ container: node,\n offset: newOffset\n });\n + \ }\n }\n return walker().bind((next2) => walk2(next2.container, + next2.offset, walker, process2));\n };\n const backwards = (node, + offset, process2, root2) => {\n const walker = TextWalker(node, root2, + isBlockBoundary);\n return walk2(node, offset, () => walker.prev().map((prev2) + => ({\n container: prev2,\n offset: prev2.length\n })), + process2).getOrNull();\n };\n const forwards = (node, offset, + process2, root2) => {\n const walker = TextWalker(node, root2, isBlockBoundary);\n + \ return walk2(node, offset, () => walker.next().map((next2) => ({\n + \ container: next2,\n offset: 0\n })), process2).getOrNull();\n + \ };\n return {\n backwards,\n forwards\n };\n + \ };\n const round$1 = Math.round;\n const clone$1 = (rect) + => {\n if (!rect) {\n return {\n left: 0,\n top: + 0,\n bottom: 0,\n right: 0,\n width: 0,\n + \ height: 0\n };\n }\n return {\n left: + round$1(rect.left),\n top: round$1(rect.top),\n bottom: + round$1(rect.bottom),\n right: round$1(rect.right),\n width: + round$1(rect.width),\n height: round$1(rect.height)\n };\n + \ };\n const collapse = (rect, toStart) => {\n rect = clone$1(rect);\n + \ if (toStart) {\n rect.right = rect.left;\n } else + {\n rect.left = rect.left + rect.width;\n rect.right = rect.left;\n + \ }\n rect.width = 0;\n return rect;\n };\n const + isEqual2 = (rect1, rect2) => rect1.left === rect2.left && rect1.top === rect2.top + && rect1.bottom === rect2.bottom && rect1.right === rect2.right;\n const + isValidOverflow = (overflowY, rect1, rect2) => overflowY >= 0 && overflowY + <= Math.min(rect1.height, rect2.height) / 2;\n const isAbove$1 = (rect1, + rect2) => {\n const halfHeight = Math.min(rect2.height / 2, rect1.height + / 2);\n if (rect1.bottom - halfHeight < rect2.top) {\n return + true;\n }\n if (rect1.top > rect2.bottom) {\n return + false;\n }\n return isValidOverflow(rect2.top - rect1.bottom, + rect1, rect2);\n };\n const isBelow$1 = (rect1, rect2) => {\n if + (rect1.top > rect2.bottom) {\n return true;\n }\n if + (rect1.bottom < rect2.top) {\n return false;\n }\n return + isValidOverflow(rect2.bottom - rect1.top, rect1, rect2);\n };\n const + containsXY = (rect, clientX, clientY) => clientX >= rect.left && clientX <= + rect.right && clientY >= rect.top && clientY <= rect.bottom;\n const + boundingClientRectFromRects = (rects) => {\n return foldl(rects, (acc, + rect) => {\n return acc.fold(() => Optional.some(rect), (prevRect) + => {\n const left = Math.min(rect.left, prevRect.left);\n const + top = Math.min(rect.top, prevRect.top);\n const right = Math.max(rect.right, + prevRect.right);\n const bottom = Math.max(rect.bottom, prevRect.bottom);\n + \ return Optional.some({\n top,\n right,\n + \ bottom,\n left,\n width: right - left,\n + \ height: bottom - top\n });\n });\n }, + Optional.none());\n };\n const distanceToRectEdgeFromXY = (rect, + x2, y2) => {\n const cx = Math.max(Math.min(x2, rect.left + rect.width), + rect.left);\n const cy = Math.max(Math.min(y2, rect.top + rect.height), + rect.top);\n return Math.sqrt((x2 - cx) * (x2 - cx) + (y2 - cy) * (y2 + - cy));\n };\n const overlapY = (r1, r22) => Math.max(0, Math.min(r1.bottom, + r22.bottom) - Math.max(r1.top, r22.top));\n const clamp$2 = (value2, + min2, max2) => Math.min(Math.max(value2, min2), max2);\n const getSelectedNode + = (range2) => {\n const startContainer = range2.startContainer, startOffset + = range2.startOffset;\n if (startContainer.hasChildNodes() && range2.endOffset + === startOffset + 1) {\n return startContainer.childNodes[startOffset];\n + \ }\n return null;\n };\n const getNode$1 = (container, + offset) => {\n if (isElement$6(container) && container.hasChildNodes()) + {\n const childNodes = container.childNodes;\n const safeOffset + = clamp$2(offset, 0, childNodes.length - 1);\n return childNodes[safeOffset];\n + \ } else {\n return container;\n }\n };\n const + getNodeUnsafe = (container, offset) => {\n if (offset < 0 && isElement$6(container) + && container.hasChildNodes()) {\n return void 0;\n } else + {\n return getNode$1(container, offset);\n }\n };\n const + extendingChars = new RegExp(\"[̀-ͯ҃-҇҈-҉֑-ֽֿׁ-ׂׄ-ׇׅؐ-ًؚ-ٰٟۖ-ۜ۟-ۤۧ-۪ۨ-ܑۭܰ-݊ަ-ް߫-߳ࠖ-࠙ࠛ-ࠣࠥ-ࠧࠩ-࡙࠭-࡛ࣣ-ंऺ़ु-ै्॑-ॗॢ-ॣঁ়াু-ৄ্ৗৢ-ৣਁ-ਂ਼ੁ-ੂੇ-ੈੋ-੍ੑੰ-ੱੵઁ-ં઼ુ-ૅે-ૈ્ૢ-ૣଁ଼ାିୁ-ୄ୍ୖୗୢ-ୣஂாீ்ௗఀా-ీె-ైొ-్ౕ-ౖౢ-ౣಁ಼ಿೂೆೌ-್ೕ-ೖೢ-ೣഁാു-ൄ്ൗൢ-ൣ්ාි-ුූෟัิ-ฺ็-๎ັິ-ູົ-ຼ່-ໍ༘-ཱ༹༙༵༷-ཾྀ-྄྆-྇ྍ-ྗྙ-ྼ࿆ိ-ူဲ-့္-်ွ-ှၘ-ၙၞ-ၠၱ-ၴႂႅ-ႆႍႝ፝-፟ᜒ-᜔ᜲ-᜴ᝒ-ᝓᝲ-ᝳ឴-឵ិ-ួំ៉-៓៝᠋-᠍ᢩᤠ-ᤢᤧ-ᤨᤲ᤹-᤻ᨗ-ᨘᨛᩖᩘ-ᩞ᩠ᩢᩥ-ᩬᩳ-᩿᩼᪰-᪽᪾ᬀ-ᬃ᬴ᬶ-ᬺᬼᭂ᭫-᭳ᮀ-ᮁᮢ-ᮥᮨ-ᮩ᮫-ᮭ᯦ᯨ-ᯩᯭᯯ-ᯱᰬ-ᰳᰶ-᰷᳐-᳔᳒-᳢᳠-᳨᳭᳴᳸-᳹᷀-᷵᷼-᷿‌-‍⃐-⃜⃝-⃠⃡⃢-⃤⃥-⃰⳯-⵿⳱ⷠ-〪ⷿ-〭〮-゙〯-゚꙯꙰-꙲ꙴ-꙽ꚞ-ꚟ꛰-꛱ꠂ꠆ꠋꠥ-ꠦ꣄꣠-꣱ꤦ-꤭ꥇ-ꥑꦀ-ꦂ꦳ꦶ-ꦹꦼꧥꨩ-ꨮꨱ-ꨲꨵ-ꨶꩃꩌꩼꪰꪲ-ꪴꪷ-ꪸꪾ-꪿꫁ꫬ-ꫭ꫶ꯥꯨ꯭ﬞ︀-️︠-゙︯-゚]\");\n + \ const isExtendingChar = (ch) => typeof ch === \"string\" && ch.charCodeAt(0) + >= 768 && extendingChars.test(ch);\n const or = (...args) => {\n return + (x2) => {\n for (let i3 = 0; i3 < args.length; i3++) {\n if + (args[i3](x2)) {\n return true;\n }\n }\n + \ return false;\n };\n };\n const and = (...args) + => {\n return (x2) => {\n for (let i3 = 0; i3 < args.length; + i3++) {\n if (!args[i3](x2)) {\n return false;\n }\n + \ }\n return true;\n };\n };\n const isElement$4 + = isElement$6;\n const isCaretCandidate$2 = isCaretCandidate$3;\n const + isBlock$1 = matchStyleValues(\"display\", \"block table\");\n const isFloated + = matchStyleValues(\"float\", \"left right\");\n const isValidElementCaretCandidate + = and(isElement$4, isCaretCandidate$2, not(isFloated));\n const isNotPre + = not(matchStyleValues(\"white-space\", \"pre pre-line pre-wrap\"));\n const + isText$5 = isText$8;\n const isBr$2 = isBr$5;\n const nodeIndex$1 + = DOMUtils.nodeIndex;\n const resolveIndex$1 = getNodeUnsafe;\n const + createRange$1 = (doc) => \"createRange\" in doc ? doc.createRange() : DOMUtils.DOM.createRng();\n + \ const isWhiteSpace$1 = (chr) => chr && /[\\r\\n\\t ]/.test(chr);\n const + isRange = (rng) => !!rng.setStart && !!rng.setEnd;\n const isHiddenWhiteSpaceRange + = (range2) => {\n const container = range2.startContainer;\n const + offset = range2.startOffset;\n if (isWhiteSpace$1(range2.toString()) + && isNotPre(container.parentNode) && isText$8(container)) {\n const + text3 = container.data;\n if (isWhiteSpace$1(text3[offset - 1]) || + isWhiteSpace$1(text3[offset + 1])) {\n return true;\n }\n + \ }\n return false;\n };\n const getBrClientRect = + (brNode) => {\n const doc = brNode.ownerDocument;\n const rng + = createRange$1(doc);\n const nbsp$1 = doc.createTextNode(nbsp);\n + \ const parentNode = brNode.parentNode;\n parentNode.insertBefore(nbsp$1, + brNode);\n rng.setStart(nbsp$1, 0);\n rng.setEnd(nbsp$1, 1);\n + \ const clientRect = clone$1(rng.getBoundingClientRect());\n parentNode.removeChild(nbsp$1);\n + \ return clientRect;\n };\n const getBoundingClientRectWebKitText + = (rng) => {\n const sc = rng.startContainer;\n const ec = rng.endContainer;\n + \ const so = rng.startOffset;\n const eo = rng.endOffset;\n if + (sc === ec && isText$8(ec) && so === 0 && eo === 1) {\n const newRng + = rng.cloneRange();\n newRng.setEndAfter(ec);\n return getBoundingClientRect$1(newRng);\n + \ } else {\n return null;\n }\n };\n const + isZeroRect = (r4) => r4.left === 0 && r4.right === 0 && r4.top === 0 && r4.bottom + === 0;\n const getBoundingClientRect$1 = (item) => {\n let clientRect;\n + \ const clientRects = item.getClientRects();\n if (clientRects.length + > 0) {\n clientRect = clone$1(clientRects[0]);\n } else {\n + \ clientRect = clone$1(item.getBoundingClientRect());\n }\n + \ if (!isRange(item) && isBr$2(item) && isZeroRect(clientRect)) {\n + \ return getBrClientRect(item);\n }\n if (isZeroRect(clientRect) + && isRange(item)) {\n return getBoundingClientRectWebKitText(item);\n + \ }\n return clientRect;\n };\n const collapseAndInflateWidth + = (clientRect, toStart) => {\n const newClientRect = collapse(clientRect, + toStart);\n newClientRect.width = 1;\n newClientRect.right = + newClientRect.left + 1;\n return newClientRect;\n };\n const + getCaretPositionClientRects = (caretPosition) => {\n const clientRects + = [];\n const addUniqueAndValidRect = (clientRect) => {\n if + (clientRect.height === 0) {\n return;\n }\n if + (clientRects.length > 0) {\n if (isEqual2(clientRect, clientRects[clientRects.length + - 1])) {\n return;\n }\n }\n clientRects.push(clientRect);\n + \ };\n const addCharacterOffset = (container2, offset2) => {\n + \ const range2 = createRange$1(container2.ownerDocument);\n if + (offset2 < container2.data.length) {\n if (isExtendingChar(container2.data[offset2])) + {\n return clientRects;\n }\n if (isExtendingChar(container2.data[offset2 + - 1])) {\n range2.setStart(container2, offset2);\n range2.setEnd(container2, + offset2 + 1);\n if (!isHiddenWhiteSpaceRange(range2)) {\n addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect$1(range2), + false));\n return clientRects;\n }\n }\n + \ }\n if (offset2 > 0) {\n range2.setStart(container2, + offset2 - 1);\n range2.setEnd(container2, offset2);\n if + (!isHiddenWhiteSpaceRange(range2)) {\n addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect$1(range2), + false));\n }\n }\n if (offset2 < container2.data.length) + {\n range2.setStart(container2, offset2);\n range2.setEnd(container2, + offset2 + 1);\n if (!isHiddenWhiteSpaceRange(range2)) {\n addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect$1(range2), + true));\n }\n }\n };\n const container = + caretPosition.container();\n const offset = caretPosition.offset();\n + \ if (isText$5(container)) {\n addCharacterOffset(container, + offset);\n return clientRects;\n }\n if (isElement$4(container)) + {\n if (caretPosition.isAtEnd()) {\n const node = resolveIndex$1(container, + offset);\n if (isText$5(node)) {\n addCharacterOffset(node, + node.data.length);\n }\n if (isValidElementCaretCandidate(node) + && !isBr$2(node)) {\n addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect$1(node), + false));\n }\n } else {\n const node = resolveIndex$1(container, + offset);\n if (isText$5(node)) {\n addCharacterOffset(node, + 0);\n }\n if (isValidElementCaretCandidate(node) && + caretPosition.isAtEnd()) {\n addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect$1(node), + false));\n return clientRects;\n }\n const + beforeNode = resolveIndex$1(caretPosition.container(), caretPosition.offset() + - 1);\n if (isValidElementCaretCandidate(beforeNode) && !isBr$2(beforeNode)) + {\n if (isBlock$1(beforeNode) || isBlock$1(node) || !isValidElementCaretCandidate(node)) + {\n addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect$1(beforeNode), + false));\n }\n }\n if (isValidElementCaretCandidate(node)) + {\n addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect$1(node), + true));\n }\n }\n }\n return clientRects;\n + \ };\n const CaretPosition = (container, offset, clientRects) => + {\n const isAtStart = () => {\n if (isText$5(container)) {\n + \ return offset === 0;\n }\n return offset === + 0;\n };\n const isAtEnd = () => {\n if (isText$5(container)) + {\n return offset >= container.data.length;\n }\n return + offset >= container.childNodes.length;\n };\n const toRange + = () => {\n const range2 = createRange$1(container.ownerDocument);\n + \ range2.setStart(container, offset);\n range2.setEnd(container, + offset);\n return range2;\n };\n const getClientRects2 + = () => {\n if (!clientRects) {\n clientRects = getCaretPositionClientRects(CaretPosition(container, + offset));\n }\n return clientRects;\n };\n const + isVisible = () => getClientRects2().length > 0;\n const isEqual3 = + (caretPosition) => caretPosition && container === caretPosition.container() + && offset === caretPosition.offset();\n const getNode2 = (before2) + => resolveIndex$1(container, before2 ? offset - 1 : offset);\n return + {\n container: constant2(container),\n offset: constant2(offset),\n + \ toRange,\n getClientRects: getClientRects2,\n isVisible,\n + \ isAtStart,\n isAtEnd,\n isEqual: isEqual3,\n getNode: + getNode2\n };\n };\n CaretPosition.fromRangeStart = (range2) + => CaretPosition(range2.startContainer, range2.startOffset);\n CaretPosition.fromRangeEnd + = (range2) => CaretPosition(range2.endContainer, range2.endOffset);\n CaretPosition.after + = (node) => CaretPosition(node.parentNode, nodeIndex$1(node) + 1);\n CaretPosition.before + = (node) => CaretPosition(node.parentNode, nodeIndex$1(node));\n CaretPosition.isAbove + = (pos1, pos2) => lift2(head(pos2.getClientRects()), last$3(pos1.getClientRects()), + isAbove$1).getOr(false);\n CaretPosition.isBelow = (pos1, pos2) => lift2(last$3(pos2.getClientRects()), + head(pos1.getClientRects()), isBelow$1).getOr(false);\n CaretPosition.isAtStart + = (pos) => pos ? pos.isAtStart() : false;\n CaretPosition.isAtEnd = (pos) + => pos ? pos.isAtEnd() : false;\n CaretPosition.isTextPosition = (pos) + => pos ? isText$8(pos.container()) : false;\n CaretPosition.isElementPosition + = (pos) => CaretPosition.isTextPosition(pos) === false;\n const trimEmptyTextNode$1 + = (dom3, node) => {\n if (isText$8(node) && node.data.length === 0) + {\n dom3.remove(node);\n }\n };\n const insertNode + = (dom3, rng, node) => {\n rng.insertNode(node);\n trimEmptyTextNode$1(dom3, + node.previousSibling);\n trimEmptyTextNode$1(dom3, node.nextSibling);\n + \ };\n const insertFragment = (dom3, rng, frag) => {\n const + firstChild2 = Optional.from(frag.firstChild);\n const lastChild2 = + Optional.from(frag.lastChild);\n rng.insertNode(frag);\n firstChild2.each((child2) + => trimEmptyTextNode$1(dom3, child2.previousSibling));\n lastChild2.each((child2) + => trimEmptyTextNode$1(dom3, child2.nextSibling));\n };\n const + rangeInsertNode = (dom3, rng, node) => {\n if (isDocumentFragment(node)) + {\n insertFragment(dom3, rng, node);\n } else {\n insertNode(dom3, + rng, node);\n }\n };\n const isText$4 = isText$8;\n const + isBogus = isBogus$2;\n const nodeIndex = DOMUtils.nodeIndex;\n const + normalizedParent = (node) => {\n const parentNode = node.parentNode;\n + \ if (isBogus(parentNode)) {\n return normalizedParent(parentNode);\n + \ }\n return parentNode;\n };\n const getChildNodes + = (node) => {\n if (!node) {\n return [];\n }\n return + reduce(node.childNodes, (result, node2) => {\n if (isBogus(node2) + && node2.nodeName !== \"BR\") {\n result = result.concat(getChildNodes(node2));\n + \ } else {\n result.push(node2);\n }\n return + result;\n }, []);\n };\n const normalizedTextOffset = (node, + offset) => {\n while (node = node.previousSibling) {\n if + (!isText$4(node)) {\n break;\n }\n offset += + node.data.length;\n }\n return offset;\n };\n const + equal = (a2) => (b2) => a2 === b2;\n const normalizedNodeIndex = (node) + => {\n let nodes, index2;\n nodes = getChildNodes(normalizedParent(node));\n + \ index2 = findIndex$1(nodes, equal(node), node);\n nodes = nodes.slice(0, + index2 + 1);\n const numTextFragments = reduce(nodes, (result, node2, + i3) => {\n if (isText$4(node2) && isText$4(nodes[i3 - 1])) {\n result++;\n + \ }\n return result;\n }, 0);\n nodes = filter$4(nodes, + matchNodeNames([node.nodeName]));\n index2 = findIndex$1(nodes, equal(node), + node);\n return index2 - numTextFragments;\n };\n const createPathItem + = (node) => {\n let name2;\n if (isText$4(node)) {\n name2 + = \"text()\";\n } else {\n name2 = node.nodeName.toLowerCase();\n + \ }\n return name2 + \"[\" + normalizedNodeIndex(node) + \"]\";\n + \ };\n const parentsUntil$1 = (root2, node, predicate) => {\n const + parents2 = [];\n for (node = node.parentNode; node !== root2; node + = node.parentNode) {\n parents2.push(node);\n }\n return + parents2;\n };\n const create$a = (root2, caretPosition) => {\n + \ let container, offset, path = [], outputOffset, childNodes, parents2;\n + \ container = caretPosition.container();\n offset = caretPosition.offset();\n + \ if (isText$4(container)) {\n outputOffset = normalizedTextOffset(container, + offset);\n } else {\n childNodes = container.childNodes;\n + \ if (offset >= childNodes.length) {\n outputOffset = \"after\";\n + \ offset = childNodes.length - 1;\n } else {\n outputOffset + = \"before\";\n }\n container = childNodes[offset];\n }\n + \ path.push(createPathItem(container));\n parents2 = parentsUntil$1(root2, + container);\n parents2 = filter$4(parents2, not(isBogus$2));\n path + = path.concat(map$1(parents2, (node) => {\n return createPathItem(node);\n + \ }));\n return path.reverse().join(\"/\") + \",\" + outputOffset;\n + \ };\n const resolvePathItem = (node, name2, index2) => {\n let + nodes = getChildNodes(node);\n nodes = filter$4(nodes, (node2, index3) + => {\n return !isText$4(node2) || !isText$4(nodes[index3 - 1]);\n + \ });\n nodes = filter$4(nodes, matchNodeNames([name2]));\n return + nodes[index2];\n };\n const findTextPosition = (container, offset) + => {\n let node = container, targetOffset = 0, dataLen;\n while + (isText$4(node)) {\n dataLen = node.data.length;\n if (offset + >= targetOffset && offset <= targetOffset + dataLen) {\n container + = node;\n offset = offset - targetOffset;\n break;\n + \ }\n if (!isText$4(node.nextSibling)) {\n container + = node;\n offset = dataLen;\n break;\n }\n + \ targetOffset += dataLen;\n node = node.nextSibling;\n }\n + \ if (isText$4(container) && offset > container.data.length) {\n offset + = container.data.length;\n }\n return CaretPosition(container, + offset);\n };\n const resolve$1 = (root2, path) => {\n let + offset;\n if (!path) {\n return null;\n }\n const + parts = path.split(\",\");\n const paths = parts[0].split(\"/\");\n + \ offset = parts.length > 1 ? parts[1] : \"before\";\n const + container = reduce(paths, (result, value2) => {\n const match4 = + /([\\w\\-\\(\\)]+)\\[([0-9]+)\\]/.exec(value2);\n if (!match4) {\n + \ return null;\n }\n if (match4[1] === \"text()\") + {\n match4[1] = \"#text\";\n }\n return resolvePathItem(result, + match4[1], parseInt(match4[2], 10));\n }, root2);\n if (!container) + {\n return null;\n }\n if (!isText$4(container)) {\n + \ if (offset === \"after\") {\n offset = nodeIndex(container) + + 1;\n } else {\n offset = nodeIndex(container);\n }\n + \ return CaretPosition(container.parentNode, offset);\n }\n + \ return findTextPosition(container, parseInt(offset, 10));\n };\n + \ const isContentEditableFalse$8 = isContentEditableFalse$a;\n const + getNormalizedTextOffset = (trim2, container, offset) => {\n let node, + trimmedOffset;\n trimmedOffset = trim2(container.data.slice(0, offset)).length;\n + \ for (node = container.previousSibling; node && isText$8(node); node + = node.previousSibling) {\n trimmedOffset += trim2(node.data).length;\n + \ }\n return trimmedOffset;\n };\n const getPoint = + (dom3, trim2, normalized, rng, start2) => {\n let container = rng[start2 + ? \"startContainer\" : \"endContainer\"];\n let offset = rng[start2 + ? \"startOffset\" : \"endOffset\"];\n const point2 = [];\n let + childNodes, after2 = 0;\n const root2 = dom3.getRoot();\n if + (isText$8(container)) {\n point2.push(normalized ? getNormalizedTextOffset(trim2, + container, offset) : offset);\n } else {\n childNodes = container.childNodes;\n + \ if (offset >= childNodes.length && childNodes.length) {\n after2 + = 1;\n offset = Math.max(0, childNodes.length - 1);\n }\n + \ point2.push(dom3.nodeIndex(childNodes[offset], normalized) + after2);\n + \ }\n for (; container && container !== root2; container = container.parentNode) + {\n point2.push(dom3.nodeIndex(container, normalized));\n }\n + \ return point2;\n };\n const getLocation = (trim2, selection, + normalized, rng) => {\n const dom3 = selection.dom, bookmark = {};\n + \ bookmark.start = getPoint(dom3, trim2, normalized, rng, true);\n if + (!selection.isCollapsed()) {\n bookmark.end = getPoint(dom3, trim2, + normalized, rng, false);\n }\n if (isRangeInCaretContainerBlock(rng)) + {\n bookmark.isFakeCaret = true;\n }\n return bookmark;\n + \ };\n const findIndex = (dom3, name2, element) => {\n let + count2 = 0;\n Tools.each(dom3.select(name2), (node) => {\n if + (node.getAttribute(\"data-mce-bogus\") === \"all\") {\n return;\n + \ }\n if (node === element) {\n return false;\n + \ }\n count2++;\n });\n return count2;\n };\n + \ const moveEndPoint$1 = (rng, start2) => {\n let container, offset, + childNodes;\n const prefix = start2 ? \"start\" : \"end\";\n container + = rng[prefix + \"Container\"];\n offset = rng[prefix + \"Offset\"];\n + \ if (isElement$6(container) && container.nodeName === \"TR\") {\n childNodes + = container.childNodes;\n container = childNodes[Math.min(start2 + ? offset : offset - 1, childNodes.length - 1)];\n if (container) + {\n offset = start2 ? 0 : container.childNodes.length;\n rng[\"set\" + + (start2 ? \"Start\" : \"End\")](container, offset);\n }\n }\n + \ };\n const normalizeTableCellSelection = (rng) => {\n moveEndPoint$1(rng, + true);\n moveEndPoint$1(rng, false);\n return rng;\n };\n + \ const findSibling = (node, offset) => {\n let sibling2;\n if + (isElement$6(node)) {\n node = getNode$1(node, offset);\n if + (isContentEditableFalse$8(node)) {\n return node;\n }\n + \ }\n if (isCaretContainer$2(node)) {\n if (isText$8(node) + && isCaretContainerBlock$1(node)) {\n node = node.parentNode;\n + \ }\n sibling2 = node.previousSibling;\n if (isContentEditableFalse$8(sibling2)) + {\n return sibling2;\n }\n sibling2 = node.nextSibling;\n + \ if (isContentEditableFalse$8(sibling2)) {\n return sibling2;\n + \ }\n }\n };\n const findAdjacentContentEditableFalseElm + = (rng) => {\n return findSibling(rng.startContainer, rng.startOffset) + || findSibling(rng.endContainer, rng.endOffset);\n };\n const getOffsetBookmark + = (trim2, normalized, selection) => {\n const element = selection.getNode();\n + \ let name2 = element ? element.nodeName : null;\n const rng + = selection.getRng();\n if (isContentEditableFalse$8(element) || name2 + === \"IMG\") {\n return {\n name: name2,\n index: + findIndex(selection.dom, name2, element)\n };\n }\n const + sibling2 = findAdjacentContentEditableFalseElm(rng);\n if (sibling2) + {\n name2 = sibling2.tagName;\n return {\n name: + name2,\n index: findIndex(selection.dom, name2, sibling2)\n };\n + \ }\n return getLocation(trim2, selection, normalized, rng);\n + \ };\n const getCaretBookmark = (selection) => {\n const rng + = selection.getRng();\n return {\n start: create$a(selection.dom.getRoot(), + CaretPosition.fromRangeStart(rng)),\n end: create$a(selection.dom.getRoot(), + CaretPosition.fromRangeEnd(rng))\n };\n };\n const getRangeBookmark + = (selection) => {\n return { rng: selection.getRng() };\n };\n + \ const createBookmarkSpan = (dom3, id, filled) => {\n const args + = {\n \"data-mce-type\": \"bookmark\",\n id,\n \"style\": + \"overflow:hidden;line-height:0px\"\n };\n return filled ? dom3.create(\"span\", + args, \"\") : dom3.create(\"span\", args);\n };\n const + getPersistentBookmark = (selection, filled) => {\n const dom3 = selection.dom;\n + \ let rng = selection.getRng();\n const id = dom3.uniqueId();\n + \ const collapsed = selection.isCollapsed();\n const element + = selection.getNode();\n const name2 = element.nodeName;\n if + (name2 === \"IMG\") {\n return {\n name: name2,\n index: + findIndex(dom3, name2, element)\n };\n }\n const rng2 + = normalizeTableCellSelection(rng.cloneRange());\n if (!collapsed) + {\n rng2.collapse(false);\n const endBookmarkNode = createBookmarkSpan(dom3, + id + \"_end\", filled);\n rangeInsertNode(dom3, rng2, endBookmarkNode);\n + \ }\n rng = normalizeTableCellSelection(rng);\n rng.collapse(true);\n + \ const startBookmarkNode = createBookmarkSpan(dom3, id + \"_start\", + filled);\n rangeInsertNode(dom3, rng, startBookmarkNode);\n selection.moveToBookmark({\n + \ id,\n keep: true\n });\n return { id };\n + \ };\n const getBookmark$2 = (selection, type2, normalized) => {\n + \ if (type2 === 2) {\n return getOffsetBookmark(trim$1, normalized, + selection);\n } else if (type2 === 3) {\n return getCaretBookmark(selection);\n + \ } else if (type2) {\n return getRangeBookmark(selection);\n + \ } else {\n return getPersistentBookmark(selection, false);\n + \ }\n };\n const getUndoBookmark = curry(getOffsetBookmark, + identity2, true);\n const value$1 = (value2) => {\n const applyHelper + = (fn) => fn(value2);\n const constHelper = constant2(value2);\n const + outputHelper = () => output;\n const output = {\n tag: true,\n + \ inner: value2,\n fold: (_onError, onValue) => onValue(value2),\n + \ isValue: always,\n isError: never,\n map: (mapper) + => Result.value(mapper(value2)),\n mapError: outputHelper,\n bind: + applyHelper,\n exists: applyHelper,\n forall: applyHelper,\n + \ getOr: constHelper,\n or: outputHelper,\n getOrThunk: + constHelper,\n orThunk: outputHelper,\n getOrDie: constHelper,\n + \ each: (fn) => {\n fn(value2);\n },\n toOptional: + () => Optional.some(value2)\n };\n return output;\n };\n + \ const error2 = (error3) => {\n const outputHelper = () => output;\n + \ const output = {\n tag: false,\n inner: error3,\n + \ fold: (onError, _onValue) => onError(error3),\n isValue: + never,\n isError: always,\n map: outputHelper,\n mapError: + (mapper) => Result.error(mapper(error3)),\n bind: outputHelper,\n + \ exists: never,\n forall: always,\n getOr: identity2,\n + \ or: identity2,\n getOrThunk: apply$1,\n orThunk: + apply$1,\n getOrDie: die(String(error3)),\n each: noop,\n + \ toOptional: Optional.none\n };\n return output;\n + \ };\n const fromOption = (optional, err) => optional.fold(() => + error2(err), value$1);\n const Result = {\n value: value$1,\n + \ error: error2,\n fromOption\n };\n const generate + = (cases) => {\n if (!isArray$1(cases)) {\n throw new Error(\"cases + must be an array\");\n }\n if (cases.length === 0) {\n throw + new Error(\"there must be at least one case\");\n }\n const + constructors = [];\n const adt2 = {};\n each$g(cases, (acase, + count2) => {\n const keys$1 = keys2(acase);\n if (keys$1.length + !== 1) {\n throw new Error(\"one and only one name per case\");\n + \ }\n const key = keys$1[0];\n const value2 = acase[key];\n + \ if (adt2[key] !== void 0) {\n throw new Error(\"duplicate + key detected:\" + key);\n } else if (key === \"cata\") {\n throw + new Error(\"cannot have a case named cata (sorry)\");\n } else if + (!isArray$1(value2)) {\n throw new Error(\"case arguments must + be an array\");\n }\n constructors.push(key);\n adt2[key] + = (...args) => {\n const argLength = args.length;\n if + (argLength !== value2.length) {\n throw new Error(\"Wrong number + of arguments to case \" + key + \". Expected \" + value2.length + \" (\" + + value2 + \"), got \" + argLength);\n }\n const match4 + = (branches) => {\n const branchKeys = keys2(branches);\n if + (constructors.length !== branchKeys.length) {\n throw new Error(\"Wrong + number of arguments to match. Expected: \" + constructors.join(\",\") + \"\\nActual: + \" + branchKeys.join(\",\"));\n }\n const allReqd + = forall(constructors, (reqKey) => {\n return contains$2(branchKeys, + reqKey);\n });\n if (!allReqd) {\n throw + new Error(\"Not all branches were specified when using match. Specified: \" + + branchKeys.join(\", \") + \"\\nRequired: \" + constructors.join(\", \"));\n + \ }\n return branches[key].apply(null, args);\n };\n + \ return {\n fold: (...foldArgs) => {\n if + (foldArgs.length !== cases.length) {\n throw new Error(\"Wrong + number of arguments to fold. Expected \" + cases.length + \", got \" + foldArgs.length);\n + \ }\n const target = foldArgs[count2];\n return + target.apply(null, args);\n },\n match: match4,\n + \ log: (label) => {\n console.log(label, {\n constructors,\n + \ constructor: key,\n params: args\n });\n + \ }\n };\n };\n });\n return + adt2;\n };\n const Adt = { generate };\n Adt.generate([\n {\n + \ bothErrors: [\n \"error1\",\n \"error2\"\n + \ ]\n },\n {\n firstError: [\n \"error1\",\n + \ \"value2\"\n ]\n },\n {\n secondError: + [\n \"value1\",\n \"error2\"\n ]\n },\n + \ {\n bothValues: [\n \"value1\",\n \"value2\"\n + \ ]\n }\n ]);\n const partition$1 = (results) => + {\n const errors2 = [];\n const values2 = [];\n each$g(results, + (result) => {\n result.fold((err) => {\n errors2.push(err);\n + \ }, (value2) => {\n values2.push(value2);\n });\n + \ });\n return {\n errors: errors2,\n values: + values2\n };\n };\n const isInlinePattern = (pattern) => + pattern.type === \"inline-command\" || pattern.type === \"inline-format\";\n + \ const isBlockPattern = (pattern) => pattern.type === \"block-command\" + || pattern.type === \"block-format\";\n const sortPatterns = (patterns) + => sort(patterns, (a2, b2) => {\n if (a2.start.length === b2.start.length) + {\n return 0;\n }\n return a2.start.length > b2.start.length + ? -1 : 1;\n });\n const normalizePattern = (pattern) => {\n const + err = (message) => Result.error({\n message,\n pattern\n + \ });\n const formatOrCmd = (name2, onFormat, onCommand) => {\n + \ if (pattern.format !== void 0) {\n let formats;\n if + (isArray$1(pattern.format)) {\n if (!forall(pattern.format, isString2)) + {\n return err(name2 + \" pattern has non-string items in the + `format` array\");\n }\n formats = pattern.format;\n + \ } else if (isString2(pattern.format)) {\n formats + = [pattern.format];\n } else {\n return err(name2 + + \" pattern has non-string `format` parameter\");\n }\n return + Result.value(onFormat(formats));\n } else if (pattern.cmd !== void + 0) {\n if (!isString2(pattern.cmd)) {\n return err(name2 + + \" pattern has non-string `cmd` parameter\");\n }\n return + Result.value(onCommand(pattern.cmd, pattern.value));\n } else {\n + \ return err(name2 + \" pattern is missing both `format` and `cmd` + parameters\");\n }\n };\n if (!isObject2(pattern)) + {\n return err(\"Raw pattern is not an object\");\n }\n if + (!isString2(pattern.start)) {\n return err(\"Raw pattern is missing + `start` parameter\");\n }\n if (pattern.end !== void 0) {\n + \ if (!isString2(pattern.end)) {\n return err(\"Inline + pattern has non-string `end` parameter\");\n }\n if (pattern.start.length + === 0 && pattern.end.length === 0) {\n return err(\"Inline pattern + has empty `start` and `end` parameters\");\n }\n let start2 + = pattern.start;\n let end2 = pattern.end;\n if (end2.length + === 0) {\n end2 = start2;\n start2 = \"\";\n }\n + \ return formatOrCmd(\"Inline\", (format2) => ({\n type: + \"inline-format\",\n start: start2,\n end: end2,\n format: + format2\n }), (cmd, value2) => ({\n type: \"inline-command\",\n + \ start: start2,\n end: end2,\n cmd,\n value: + value2\n }));\n } else if (pattern.replacement !== void 0) + {\n if (!isString2(pattern.replacement)) {\n return err(\"Replacement + pattern has non-string `replacement` parameter\");\n }\n if + (pattern.start.length === 0) {\n return err(\"Replacement pattern + has empty `start` parameter\");\n }\n return Result.value({\n + \ type: \"inline-command\",\n start: \"\",\n end: + pattern.start,\n cmd: \"mceInsertContent\",\n value: + pattern.replacement\n });\n } else {\n if (pattern.start.length + === 0) {\n return err(\"Block pattern has empty `start` parameter\");\n + \ }\n return formatOrCmd(\"Block\", (formats) => ({\n type: + \"block-format\",\n start: pattern.start,\n format: + formats[0]\n }), (command, commandValue) => ({\n type: + \"block-command\",\n start: pattern.start,\n cmd: command,\n + \ value: commandValue\n }));\n }\n };\n const + getBlockPatterns = (patterns) => sortPatterns(filter$6(patterns, isBlockPattern));\n + \ const getInlinePatterns = (patterns) => filter$6(patterns, isInlinePattern);\n + \ const createPatternSet = (patterns) => ({\n inlinePatterns: getInlinePatterns(patterns),\n + \ blockPatterns: getBlockPatterns(patterns)\n });\n const + fromRawPatterns = (patterns) => {\n const normalized = partition$1(map$3(patterns, + normalizePattern));\n each$g(normalized.errors, (err) => console.error(err.message, + err.pattern));\n return normalized.values;\n };\n const deviceDetection$1 + = detect$2().deviceType;\n const isTouch = deviceDetection$1.isTouch();\n + \ const DOM$a = DOMUtils.DOM;\n const getHash = (value2) => {\n const + items = value2.indexOf(\"=\") > 0 ? value2.split(/[;,](?![^=;,]*(?:[;,]|$))/) + : value2.split(\",\");\n return foldl(items, (output, item) => {\n + \ const arr = item.split(\"=\");\n const key = arr[0];\n + \ const val = arr.length > 1 ? arr[1] : key;\n output[trim$3(key)] + = trim$3(val);\n return output;\n }, {});\n };\n const + isRegExp2 = (x2) => is$4(x2, RegExp);\n const option = (name2) => (editor) + => editor.options.get(name2);\n const stringOrObjectProcessor = (value2) + => isString2(value2) || isObject2(value2);\n const bodyOptionProcessor + = (editor, defaultValue = \"\") => (value2) => {\n const valid = isString2(value2);\n + \ if (valid) {\n if (value2.indexOf(\"=\") !== -1) {\n const + bodyObj = getHash(value2);\n return {\n value: get$a(bodyObj, + editor.id).getOr(defaultValue),\n valid\n };\n } + else {\n return {\n value: value2,\n valid\n + \ };\n }\n } else {\n return {\n valid: + false,\n message: \"Must be a string.\"\n };\n }\n + \ };\n const register$7 = (editor) => {\n const registerOption + = editor.options.register;\n registerOption(\"id\", {\n processor: + \"string\",\n default: editor.id\n });\n registerOption(\"selector\", + { processor: \"string\" });\n registerOption(\"target\", { processor: + \"object\" });\n registerOption(\"suffix\", { processor: \"string\" + });\n registerOption(\"cache_suffix\", { processor: \"string\" });\n + \ registerOption(\"base_url\", { processor: \"string\" });\n registerOption(\"referrer_policy\", + {\n processor: \"string\",\n default: \"\"\n });\n + \ registerOption(\"language_load\", { processor: \"boolean\" });\n registerOption(\"inline\", + {\n processor: \"boolean\",\n default: false\n });\n + \ registerOption(\"iframe_attrs\", {\n processor: \"object\",\n + \ default: {}\n });\n registerOption(\"doctype\", {\n + \ processor: \"string\",\n default: \"\"\n + \ });\n registerOption(\"document_base_url\", {\n processor: + \"string\",\n default: editor.documentBaseUrl\n });\n registerOption(\"body_id\", + {\n processor: bodyOptionProcessor(editor, \"tinymce\"),\n default: + \"tinymce\"\n });\n registerOption(\"body_class\", {\n processor: + bodyOptionProcessor(editor),\n default: \"\"\n });\n registerOption(\"content_security_policy\", + {\n processor: \"string\",\n default: \"\"\n });\n + \ registerOption(\"br_in_pre\", {\n processor: \"boolean\",\n + \ default: true\n });\n registerOption(\"forced_root_block\", + {\n processor: (value2) => {\n const valid = isString2(value2) + && isNotEmpty(value2);\n if (valid) {\n return {\n + \ value: value2,\n valid\n };\n + \ } else {\n return {\n valid: false,\n + \ message: \"Must be a non-empty string.\"\n };\n + \ }\n },\n default: \"p\"\n });\n registerOption(\"forced_root_block_attrs\", + {\n processor: \"object\",\n default: {}\n });\n + \ registerOption(\"br_newline_selector\", {\n processor: \"string\",\n + \ default: \".mce-toc h2,figcaption,caption\"\n });\n registerOption(\"no_newline_selector\", + {\n processor: \"string\",\n default: \"\"\n });\n + \ registerOption(\"keep_styles\", {\n processor: \"boolean\",\n + \ default: true\n });\n registerOption(\"end_container_on_empty_block\", + {\n processor: \"boolean\",\n default: false\n });\n + \ registerOption(\"font_size_style_values\", {\n processor: + \"string\",\n default: \"xx-small,x-small,small,medium,large,x-large,xx-large\"\n + \ });\n registerOption(\"font_size_legacy_values\", {\n processor: + \"string\",\n default: \"xx-small,small,medium,large,x-large,xx-large,300%\"\n + \ });\n registerOption(\"font_size_classes\", {\n processor: + \"string\",\n default: \"\"\n });\n registerOption(\"automatic_uploads\", + {\n processor: \"boolean\",\n default: true\n });\n + \ registerOption(\"images_reuse_filename\", {\n processor: + \"boolean\",\n default: false\n });\n registerOption(\"images_replace_blob_uris\", + {\n processor: \"boolean\",\n default: true\n });\n + \ registerOption(\"icons\", {\n processor: \"string\",\n default: + \"\"\n });\n registerOption(\"icons_url\", {\n processor: + \"string\",\n default: \"\"\n });\n registerOption(\"images_upload_url\", + {\n processor: \"string\",\n default: \"\"\n });\n + \ registerOption(\"images_upload_base_path\", {\n processor: + \"string\",\n default: \"\"\n });\n registerOption(\"images_upload_base_path\", + {\n processor: \"string\",\n default: \"\"\n });\n + \ registerOption(\"images_upload_credentials\", {\n processor: + \"boolean\",\n default: false\n });\n registerOption(\"images_upload_handler\", + { processor: \"function\" });\n registerOption(\"language\", {\n processor: + \"string\",\n default: \"en\"\n });\n registerOption(\"language_url\", + {\n processor: \"string\",\n default: \"\"\n });\n + \ registerOption(\"entity_encoding\", {\n processor: \"string\",\n + \ default: \"named\"\n });\n registerOption(\"indent\", + {\n processor: \"boolean\",\n default: true\n });\n + \ registerOption(\"indent_before\", {\n processor: \"string\",\n + \ default: \"p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,th,ul,ol,li,dl,dt,dd,area,table,thead,tfoot,tbody,tr,section,summary,article,hgroup,aside,figure,figcaption,option,optgroup,datalist\"\n + \ });\n registerOption(\"indent_after\", {\n processor: + \"string\",\n default: \"p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,th,ul,ol,li,dl,dt,dd,area,table,thead,tfoot,tbody,tr,section,summary,article,hgroup,aside,figure,figcaption,option,optgroup,datalist\"\n + \ });\n registerOption(\"indent_use_margin\", {\n processor: + \"boolean\",\n default: false\n });\n registerOption(\"indentation\", + {\n processor: \"string\",\n default: \"40px\"\n });\n + \ registerOption(\"content_css\", {\n processor: (value2) => + {\n const valid = value2 === false || isString2(value2) || isArrayOf(value2, + isString2);\n if (valid) {\n if (isString2(value2)) + {\n return {\n value: map$3(value2.split(\",\"), + trim$3),\n valid\n };\n } else + if (isArray$1(value2)) {\n return {\n value: + value2,\n valid\n };\n } else + if (value2 === false) {\n return {\n value: + [],\n valid\n };\n } else {\n + \ return {\n value: value2,\n valid\n + \ };\n }\n } else {\n return + {\n valid: false,\n message: \"Must be false, + a string or an array of strings.\"\n };\n }\n },\n + \ default: isInline(editor) ? [] : [\"default\"]\n });\n registerOption(\"content_style\", + { processor: \"string\" });\n registerOption(\"content_css_cors\", + {\n processor: \"boolean\",\n default: false\n });\n + \ registerOption(\"font_css\", {\n processor: (value2) => {\n + \ const valid = isString2(value2) || isArrayOf(value2, isString2);\n + \ if (valid) {\n const newValue = isArray$1(value2) + ? value2 : map$3(value2.split(\",\"), trim$3);\n return {\n value: + newValue,\n valid\n };\n } else {\n + \ return {\n valid: false,\n message: + \"Must be a string or an array of strings.\"\n };\n }\n + \ },\n default: []\n });\n registerOption(\"inline_boundaries\", + {\n processor: \"boolean\",\n default: true\n });\n + \ registerOption(\"inline_boundaries_selector\", {\n processor: + \"string\",\n default: \"a[href],code,.mce-annotation\"\n });\n + \ registerOption(\"object_resizing\", {\n processor: (value2) + => {\n const valid = isBoolean2(value2) || isString2(value2);\n + \ if (valid) {\n if (value2 === false || deviceDetection$1.isiPhone() + || deviceDetection$1.isiPad()) {\n return {\n value: + \"\",\n valid\n };\n } else {\n + \ return {\n value: value2 === true ? \"table,img,figure.image,div,video,iframe\" + : value2,\n valid\n };\n }\n + \ } else {\n return {\n valid: false,\n + \ message: \"Must be boolean or a string\"\n };\n + \ }\n },\n default: !isTouch\n });\n registerOption(\"resize_img_proportional\", + {\n processor: \"boolean\",\n default: true\n });\n + \ registerOption(\"event_root\", { processor: \"object\" });\n registerOption(\"service_message\", + { processor: \"string\" });\n registerOption(\"theme\", {\n processor: + (value2) => value2 === false || isString2(value2) || isFunction2(value2),\n + \ default: \"silver\"\n });\n registerOption(\"theme_url\", + { processor: \"string\" });\n registerOption(\"formats\", { processor: + \"object\" });\n registerOption(\"format_empty_lines\", {\n processor: + \"boolean\",\n default: false\n });\n registerOption(\"preview_styles\", + {\n processor: (value2) => {\n const valid = value2 === + false || isString2(value2);\n if (valid) {\n return + {\n value: value2 === false ? \"\" : value2,\n valid\n + \ };\n } else {\n return {\n valid: + false,\n message: \"Must be false or a string\"\n };\n + \ }\n },\n default: \"font-family font-size font-weight + font-style text-decoration text-transform color background-color border border-radius + outline text-shadow\"\n });\n registerOption(\"custom_ui_selector\", + {\n processor: \"string\",\n default: \"\"\n });\n + \ registerOption(\"hidden_input\", {\n processor: \"boolean\",\n + \ default: true\n });\n registerOption(\"submit_patch\", + {\n processor: \"boolean\",\n default: true\n });\n + \ registerOption(\"encoding\", { processor: \"string\" });\n registerOption(\"add_form_submit_trigger\", + {\n processor: \"boolean\",\n default: true\n });\n + \ registerOption(\"add_unload_trigger\", {\n processor: \"boolean\",\n + \ default: true\n });\n registerOption(\"custom_undo_redo_levels\", + {\n processor: \"number\",\n default: 0\n });\n registerOption(\"disable_nodechange\", + {\n processor: \"boolean\",\n default: false\n });\n + \ registerOption(\"readonly\", {\n processor: \"boolean\",\n + \ default: false\n });\n registerOption(\"plugins\", + {\n processor: \"string[]\",\n default: []\n });\n + \ registerOption(\"external_plugins\", { processor: \"object\" });\n + \ registerOption(\"forced_plugins\", { processor: \"string[]\" });\n + \ registerOption(\"model\", {\n processor: \"string\",\n default: + editor.hasPlugin(\"rtc\") ? \"plugin\" : \"dom\"\n });\n registerOption(\"model_url\", + { processor: \"string\" });\n registerOption(\"block_unsupported_drop\", + {\n processor: \"boolean\",\n default: true\n });\n + \ registerOption(\"visual\", {\n processor: \"boolean\",\n + \ default: true\n });\n registerOption(\"visual_table_class\", + {\n processor: \"string\",\n default: \"mce-item-table\"\n + \ });\n registerOption(\"visual_anchor_class\", {\n processor: + \"string\",\n default: \"mce-item-anchor\"\n });\n registerOption(\"iframe_aria_text\", + {\n processor: \"string\",\n default: \"Rich Text Area. + Press ALT-0 for help.\"\n });\n registerOption(\"setup\", { + processor: \"function\" });\n registerOption(\"init_instance_callback\", + { processor: \"function\" });\n registerOption(\"url_converter\", {\n + \ processor: \"function\",\n default: editor.convertURL\n + \ });\n registerOption(\"url_converter_scope\", {\n processor: + \"object\",\n default: editor\n });\n registerOption(\"urlconverter_callback\", + { processor: \"function\" });\n registerOption(\"allow_conditional_comments\", + {\n processor: \"boolean\",\n default: false\n });\n + \ registerOption(\"allow_html_data_urls\", {\n processor: \"boolean\",\n + \ default: false\n });\n registerOption(\"allow_svg_data_urls\", + { processor: \"boolean\" });\n registerOption(\"allow_html_in_named_anchor\", + {\n processor: \"boolean\",\n default: false\n });\n + \ registerOption(\"allow_script_urls\", {\n processor: \"boolean\",\n + \ default: false\n });\n registerOption(\"allow_unsafe_link_target\", + {\n processor: \"boolean\",\n default: false\n });\n + \ registerOption(\"convert_fonts_to_spans\", {\n processor: + \"boolean\",\n default: true,\n deprecated: true\n });\n + \ registerOption(\"fix_list_elements\", {\n processor: \"boolean\",\n + \ default: false\n });\n registerOption(\"preserve_cdata\", + {\n processor: \"boolean\",\n default: false\n });\n + \ registerOption(\"remove_trailing_brs\", { processor: \"boolean\" });\n + \ registerOption(\"inline_styles\", {\n processor: \"boolean\",\n + \ default: true,\n deprecated: true\n });\n registerOption(\"element_format\", + {\n processor: \"string\",\n default: \"html\"\n });\n + \ registerOption(\"entities\", { processor: \"string\" });\n registerOption(\"schema\", + {\n processor: \"string\",\n default: \"html5\"\n });\n + \ registerOption(\"convert_urls\", {\n processor: \"boolean\",\n + \ default: true\n });\n registerOption(\"relative_urls\", + {\n processor: \"boolean\",\n default: true\n });\n + \ registerOption(\"remove_script_host\", {\n processor: \"boolean\",\n + \ default: true\n });\n registerOption(\"custom_elements\", + { processor: \"string\" });\n registerOption(\"extended_valid_elements\", + { processor: \"string\" });\n registerOption(\"invalid_elements\", + { processor: \"string\" });\n registerOption(\"invalid_styles\", { + processor: stringOrObjectProcessor });\n registerOption(\"valid_children\", + { processor: \"string\" });\n registerOption(\"valid_classes\", { processor: + stringOrObjectProcessor });\n registerOption(\"valid_elements\", { + processor: \"string\" });\n registerOption(\"valid_styles\", { processor: + stringOrObjectProcessor });\n registerOption(\"verify_html\", {\n processor: + \"boolean\",\n default: true\n });\n registerOption(\"auto_focus\", + { processor: (value2) => isString2(value2) || value2 === true });\n registerOption(\"browser_spellcheck\", + {\n processor: \"boolean\",\n default: false\n });\n + \ registerOption(\"protect\", { processor: \"array\" });\n registerOption(\"images_file_types\", + {\n processor: \"string\",\n default: \"jpeg,jpg,jpe,jfi,jif,jfif,png,gif,bmp,webp\"\n + \ });\n registerOption(\"deprecation_warnings\", {\n processor: + \"boolean\",\n default: true\n });\n registerOption(\"a11y_advanced_options\", + {\n processor: \"boolean\",\n default: false\n });\n + \ registerOption(\"api_key\", { processor: \"string\" });\n registerOption(\"paste_block_drop\", + {\n processor: \"boolean\",\n default: false\n });\n + \ registerOption(\"paste_data_images\", {\n processor: \"boolean\",\n + \ default: true\n });\n registerOption(\"paste_preprocess\", + { processor: \"function\" });\n registerOption(\"paste_postprocess\", + { processor: \"function\" });\n registerOption(\"paste_webkit_styles\", + {\n processor: \"string\",\n default: \"none\"\n });\n + \ registerOption(\"paste_remove_styles_if_webkit\", {\n processor: + \"boolean\",\n default: true\n });\n registerOption(\"paste_merge_formats\", + {\n processor: \"boolean\",\n default: true\n });\n + \ registerOption(\"smart_paste\", {\n processor: \"boolean\",\n + \ default: true\n });\n registerOption(\"paste_as_text\", + {\n processor: \"boolean\",\n default: false\n });\n + \ registerOption(\"paste_tab_spaces\", {\n processor: \"number\",\n + \ default: 4\n });\n registerOption(\"text_patterns\", + {\n processor: (value2) => {\n if (isArrayOf(value2, isObject2) + || value2 === false) {\n const patterns = value2 === false ? + [] : value2;\n return {\n value: fromRawPatterns(patterns),\n + \ valid: true\n };\n } else {\n return + {\n valid: false,\n message: \"Must be an array + of objects or false.\"\n };\n }\n },\n default: + [\n {\n start: \"*\",\n end: \"*\",\n + \ format: \"italic\"\n },\n {\n start: + \"**\",\n end: \"**\",\n format: \"bold\"\n },\n + \ {\n start: \"#\",\n format: \"h1\"\n + \ },\n {\n start: \"##\",\n format: + \"h2\"\n },\n {\n start: \"###\",\n format: + \"h3\"\n },\n {\n start: \"####\",\n format: + \"h4\"\n },\n {\n start: \"#####\",\n format: + \"h5\"\n },\n {\n start: \"######\",\n + \ format: \"h6\"\n },\n {\n start: + \"1. \",\n cmd: \"InsertOrderedList\"\n },\n {\n + \ start: \"* \",\n cmd: \"InsertUnorderedList\"\n + \ },\n {\n start: \"- \",\n cmd: + \"InsertUnorderedList\"\n }\n ]\n });\n registerOption(\"noneditable_class\", + {\n processor: \"string\",\n default: \"mceNonEditable\"\n + \ });\n registerOption(\"editable_class\", {\n processor: + \"string\",\n default: \"mceEditable\"\n });\n registerOption(\"noneditable_regexp\", + {\n processor: (value2) => {\n if (isArrayOf(value2, isRegExp2)) + {\n return {\n value: value2,\n valid: + true\n };\n } else if (isRegExp2(value2)) {\n return + {\n value: [value2],\n valid: true\n };\n + \ } else {\n return {\n valid: false,\n + \ message: \"Must be a RegExp or an array of RegExp.\"\n };\n + \ }\n },\n default: []\n });\n registerOption(\"table_tab_navigation\", + {\n processor: \"boolean\",\n default: true\n });\n + \ editor.on(\"ScriptsLoaded\", () => {\n registerOption(\"directionality\", + {\n processor: \"string\",\n default: I18n.isRtl() ? + \"rtl\" : void 0\n });\n registerOption(\"placeholder\", + {\n processor: \"string\",\n default: DOM$a.getAttrib(editor.getElement(), + \"placeholder\")\n });\n });\n };\n const getIframeAttrs + = option(\"iframe_attrs\");\n const getDocType = option(\"doctype\");\n + \ const getDocumentBaseUrl = option(\"document_base_url\");\n const + getBodyId = option(\"body_id\");\n const getBodyClass = option(\"body_class\");\n + \ const getContentSecurityPolicy = option(\"content_security_policy\");\n + \ const shouldPutBrInPre$1 = option(\"br_in_pre\");\n const getForcedRootBlock + = option(\"forced_root_block\");\n const getForcedRootBlockAttrs = option(\"forced_root_block_attrs\");\n + \ const getBrNewLineSelector = option(\"br_newline_selector\");\n const + getNoNewLineSelector = option(\"no_newline_selector\");\n const shouldKeepStyles + = option(\"keep_styles\");\n const shouldEndContainerOnEmptyBlock = option(\"end_container_on_empty_block\");\n + \ const isAutomaticUploadsEnabled = option(\"automatic_uploads\");\n const + shouldReuseFileName = option(\"images_reuse_filename\");\n const shouldReplaceBlobUris + = option(\"images_replace_blob_uris\");\n const getIconPackName = option(\"icons\");\n + \ const getIconsUrl = option(\"icons_url\");\n const getImageUploadUrl + = option(\"images_upload_url\");\n const getImageUploadBasePath = option(\"images_upload_base_path\");\n + \ const getImagesUploadCredentials = option(\"images_upload_credentials\");\n + \ const getImagesUploadHandler = option(\"images_upload_handler\");\n + \ const shouldUseContentCssCors = option(\"content_css_cors\");\n const + getReferrerPolicy = option(\"referrer_policy\");\n const getLanguageCode + = option(\"language\");\n const getLanguageUrl = option(\"language_url\");\n + \ const shouldIndentUseMargin = option(\"indent_use_margin\");\n const + getIndentation = option(\"indentation\");\n const getContentCss = option(\"content_css\");\n + \ const getContentStyle = option(\"content_style\");\n const getFontCss + = option(\"font_css\");\n const getDirectionality = option(\"directionality\");\n + \ const getInlineBoundarySelector = option(\"inline_boundaries_selector\");\n + \ const getObjectResizing = option(\"object_resizing\");\n const + getResizeImgProportional = option(\"resize_img_proportional\");\n const + getPlaceholder = option(\"placeholder\");\n const getEventRoot = option(\"event_root\");\n + \ const getServiceMessage = option(\"service_message\");\n const + getTheme = option(\"theme\");\n const getThemeUrl = option(\"theme_url\");\n + \ const getModel = option(\"model\");\n const getModelUrl = option(\"model_url\");\n + \ const isInlineBoundariesEnabled = option(\"inline_boundaries\");\n const + getFormats = option(\"formats\");\n const getPreviewStyles = option(\"preview_styles\");\n + \ const canFormatEmptyLines = option(\"format_empty_lines\");\n const + getCustomUiSelector = option(\"custom_ui_selector\");\n const isInline + = option(\"inline\");\n const hasHiddenInput = option(\"hidden_input\");\n + \ const shouldPatchSubmit = option(\"submit_patch\");\n const shouldAddFormSubmitTrigger + = option(\"add_form_submit_trigger\");\n const shouldAddUnloadTrigger + = option(\"add_unload_trigger\");\n const getCustomUndoRedoLevels = option(\"custom_undo_redo_levels\");\n + \ const shouldDisableNodeChange = option(\"disable_nodechange\");\n const + isReadOnly$1 = option(\"readonly\");\n const hasContentCssCors = option(\"content_css_cors\");\n + \ const getPlugins = option(\"plugins\");\n const getExternalPlugins$1 + = option(\"external_plugins\");\n const shouldBlockUnsupportedDrop = + option(\"block_unsupported_drop\");\n const isVisualAidsEnabled = option(\"visual\");\n + \ const getVisualAidsTableClass = option(\"visual_table_class\");\n const + getVisualAidsAnchorClass = option(\"visual_anchor_class\");\n const getIframeAriaText + = option(\"iframe_aria_text\");\n const getSetupCallback = option(\"setup\");\n + \ const getInitInstanceCallback = option(\"init_instance_callback\");\n + \ const getUrlConverterCallback = option(\"urlconverter_callback\");\n + \ const getAutoFocus = option(\"auto_focus\");\n const shouldBrowserSpellcheck + = option(\"browser_spellcheck\");\n const getProtect = option(\"protect\");\n + \ const shouldPasteBlockDrop = option(\"paste_block_drop\");\n const + shouldPasteDataImages = option(\"paste_data_images\");\n const getPastePreProcess + = option(\"paste_preprocess\");\n const getPastePostProcess = option(\"paste_postprocess\");\n + \ const getPasteWebkitStyles = option(\"paste_webkit_styles\");\n const + shouldPasteRemoveWebKitStyles = option(\"paste_remove_styles_if_webkit\");\n + \ const shouldPasteMergeFormats = option(\"paste_merge_formats\");\n const + isSmartPasteEnabled = option(\"smart_paste\");\n const isPasteAsTextEnabled + = option(\"paste_as_text\");\n const getPasteTabSpaces = option(\"paste_tab_spaces\");\n + \ const shouldAllowHtmlDataUrls = option(\"allow_html_data_urls\");\n + \ const getTextPatterns = option(\"text_patterns\");\n const getNonEditableClass + = option(\"noneditable_class\");\n const getEditableClass = option(\"editable_class\");\n + \ const getNonEditableRegExps = option(\"noneditable_regexp\");\n const + getFontStyleValues = (editor) => Tools.explode(editor.options.get(\"font_size_style_values\"));\n + \ const getFontSizeClasses = (editor) => Tools.explode(editor.options.get(\"font_size_classes\"));\n + \ const isEncodingXml = (editor) => editor.options.get(\"encoding\") === + \"xml\";\n const getAllowedImageFileTypes = (editor) => Tools.explode(editor.options.get(\"images_file_types\"));\n + \ const hasTableTabNavigation = option(\"table_tab_navigation\");\n const + isElement$3 = isElement$6;\n const isText$3 = isText$8;\n const + removeNode$1 = (node) => {\n const parentNode = node.parentNode;\n + \ if (parentNode) {\n parentNode.removeChild(node);\n }\n + \ };\n const trimCount = (text3) => {\n const trimmedText + = trim$1(text3);\n return {\n count: text3.length - trimmedText.length,\n + \ text: trimmedText\n };\n };\n const deleteZwspChars + = (caretContainer) => {\n let idx;\n while ((idx = caretContainer.data.lastIndexOf(ZWSP$1)) + !== -1) {\n caretContainer.deleteData(idx, 1);\n }\n };\n + \ const removeUnchanged = (caretContainer, pos) => {\n remove$4(caretContainer);\n + \ return pos;\n };\n const removeTextAndReposition = (caretContainer, + pos) => {\n const before2 = trimCount(caretContainer.data.substr(0, + pos.offset()));\n const after2 = trimCount(caretContainer.data.substr(pos.offset()));\n + \ const text3 = before2.text + after2.text;\n if (text3.length + > 0) {\n deleteZwspChars(caretContainer);\n return CaretPosition(caretContainer, + pos.offset() - before2.count);\n } else {\n return pos;\n + \ }\n };\n const removeElementAndReposition = (caretContainer, + pos) => {\n const parentNode = pos.container();\n const newPosition + = indexOf$1(from(parentNode.childNodes), caretContainer).map((index2) => {\n + \ return index2 < pos.offset() ? CaretPosition(parentNode, pos.offset() + - 1) : pos;\n }).getOr(pos);\n remove$4(caretContainer);\n return + newPosition;\n };\n const removeTextCaretContainer = (caretContainer, + pos) => isText$3(caretContainer) && pos.container() === caretContainer ? removeTextAndReposition(caretContainer, + pos) : removeUnchanged(caretContainer, pos);\n const removeElementCaretContainer + = (caretContainer, pos) => pos.container() === caretContainer.parentNode ? + removeElementAndReposition(caretContainer, pos) : removeUnchanged(caretContainer, + pos);\n const removeAndReposition = (container, pos) => CaretPosition.isTextPosition(pos) + ? removeTextCaretContainer(container, pos) : removeElementCaretContainer(container, + pos);\n const remove$4 = (caretContainerNode) => {\n if (isElement$3(caretContainerNode) + && isCaretContainer$2(caretContainerNode)) {\n if (hasContent(caretContainerNode)) + {\n caretContainerNode.removeAttribute(\"data-mce-caret\");\n } + else {\n removeNode$1(caretContainerNode);\n }\n }\n + \ if (isText$3(caretContainerNode)) {\n deleteZwspChars(caretContainerNode);\n + \ if (caretContainerNode.data.length === 0) {\n removeNode$1(caretContainerNode);\n + \ }\n }\n };\n const isContentEditableFalse$7 = isContentEditableFalse$a;\n + \ const isMedia$1 = isMedia$2;\n const isTableCell$3 = isTableCell$5;\n + \ const inlineFakeCaretSelector = \"*[contentEditable=false],video,audio,embed,object\";\n + \ const getAbsoluteClientRect = (root2, element, before2) => {\n const + clientRect = collapse(element.getBoundingClientRect(), before2);\n let + scrollX;\n let scrollY;\n if (root2.tagName === \"BODY\") {\n + \ const docElm = root2.ownerDocument.documentElement;\n scrollX + = root2.scrollLeft || docElm.scrollLeft;\n scrollY = root2.scrollTop + || docElm.scrollTop;\n } else {\n const rootRect = root2.getBoundingClientRect();\n + \ scrollX = root2.scrollLeft - rootRect.left;\n scrollY = + root2.scrollTop - rootRect.top;\n }\n clientRect.left += scrollX;\n + \ clientRect.right += scrollX;\n clientRect.top += scrollY;\n + \ clientRect.bottom += scrollY;\n clientRect.width = 1;\n let + margin = element.offsetWidth - element.clientWidth;\n if (margin > + 0) {\n if (before2) {\n margin *= -1;\n }\n clientRect.left + += margin;\n clientRect.right += margin;\n }\n return + clientRect;\n };\n const trimInlineCaretContainers = (root2) => + {\n const fakeCaretTargetNodes = descendants(SugarElement.fromDom(root2), + inlineFakeCaretSelector);\n for (let i3 = 0; i3 < fakeCaretTargetNodes.length; + i3++) {\n const node = fakeCaretTargetNodes[i3].dom;\n let + sibling2 = node.previousSibling;\n if (endsWithCaretContainer$1(sibling2)) + {\n const data2 = sibling2.data;\n if (data2.length + === 1) {\n sibling2.parentNode.removeChild(sibling2);\n } + else {\n sibling2.deleteData(data2.length - 1, 1);\n }\n + \ }\n sibling2 = node.nextSibling;\n if (startsWithCaretContainer$1(sibling2)) + {\n const data2 = sibling2.data;\n if (data2.length + === 1) {\n sibling2.parentNode.removeChild(sibling2);\n } + else {\n sibling2.deleteData(0, 1);\n }\n }\n + \ }\n };\n const FakeCaret = (editor, root2, isBlock2, hasFocus2) + => {\n const lastVisualCaret = value$2();\n let cursorInterval;\n + \ let caretContainerNode;\n const caretBlock = getForcedRootBlock(editor);\n + \ const dom3 = editor.dom;\n const show = (before2, element) + => {\n let rng;\n hide();\n if (isTableCell$3(element)) + {\n return null;\n }\n if (isBlock2(element)) + {\n caretContainerNode = insertBlock(caretBlock, element, before2);\n + \ const clientRect = getAbsoluteClientRect(root2, element, before2);\n + \ dom3.setStyle(caretContainerNode, \"top\", clientRect.top);\n + \ const caret = dom3.create(\"div\", {\n \"class\": + \"mce-visual-caret\",\n \"data-mce-bogus\": \"all\"\n });\n + \ dom3.setStyles(caret, { ...clientRect });\n dom3.add(root2, + caret);\n lastVisualCaret.set({\n caret,\n element,\n + \ before: before2\n });\n if (before2) {\n + \ dom3.addClass(caret, \"mce-visual-caret-before\");\n }\n + \ startBlink();\n rng = element.ownerDocument.createRange();\n + \ rng.setStart(caretContainerNode, 0);\n rng.setEnd(caretContainerNode, + 0);\n } else {\n caretContainerNode = insertInline$1(element, + before2);\n rng = element.ownerDocument.createRange();\n if + (isInlineFakeCaretTarget(caretContainerNode.nextSibling)) {\n rng.setStart(caretContainerNode, + 0);\n rng.setEnd(caretContainerNode, 0);\n } else + {\n rng.setStart(caretContainerNode, 1);\n rng.setEnd(caretContainerNode, + 1);\n }\n return rng;\n }\n return + rng;\n };\n const hide = () => {\n trimInlineCaretContainers(root2);\n + \ if (caretContainerNode) {\n remove$4(caretContainerNode);\n + \ caretContainerNode = null;\n }\n lastVisualCaret.on((caretState) + => {\n dom3.remove(caretState.caret);\n lastVisualCaret.clear();\n + \ });\n if (cursorInterval) {\n clearInterval(cursorInterval);\n + \ cursorInterval = void 0;\n }\n };\n const + startBlink = () => {\n cursorInterval = setInterval(() => {\n lastVisualCaret.on((caretState) + => {\n if (hasFocus2()) {\n dom3.toggleClass(caretState.caret, + \"mce-visual-caret-hidden\");\n } else {\n dom3.addClass(caretState.caret, + \"mce-visual-caret-hidden\");\n }\n });\n }, + 500);\n };\n const reposition2 = () => {\n lastVisualCaret.on((caretState) + => {\n const clientRect = getAbsoluteClientRect(root2, caretState.element, + caretState.before);\n dom3.setStyles(caretState.caret, { ...clientRect + });\n });\n };\n const destroy2 = () => clearInterval(cursorInterval);\n + \ const getCss = () => \".mce-visual-caret {position: absolute;background-color: + black;background-color: currentcolor;}.mce-visual-caret-hidden {display: none;}*[data-mce-caret] + {position: absolute;left: -1000px;right: auto;top: 0;margin: 0;padding: 0;}\";\n + \ return {\n show,\n hide,\n getCss,\n reposition: + reposition2,\n destroy: destroy2\n };\n };\n const + isFakeCaretTableBrowser = () => Env.browser.isFirefox();\n const isInlineFakeCaretTarget + = (node) => isContentEditableFalse$7(node) || isMedia$1(node);\n const + isFakeCaretTarget = (node) => isInlineFakeCaretTarget(node) || isTable$3(node) + && isFakeCaretTableBrowser();\n const isContentEditableTrue$2 = isContentEditableTrue$4;\n + \ const isContentEditableFalse$6 = isContentEditableFalse$a;\n const + isMedia = isMedia$2;\n const isBlockLike = matchStyleValues(\"display\", + \"block table table-cell table-caption list-item\");\n const isCaretContainer + = isCaretContainer$2;\n const isCaretContainerBlock = isCaretContainerBlock$1;\n + \ const isElement$2 = isElement$6;\n const isCaretCandidate$1 = isCaretCandidate$3;\n + \ const isForwards = (direction) => direction > 0;\n const isBackwards + = (direction) => direction < 0;\n const skipCaretContainers = (walk2, + shallow2) => {\n let node;\n while (node = walk2(shallow2)) + {\n if (!isCaretContainerBlock(node)) {\n return node;\n + \ }\n }\n return null;\n };\n const findNode + = (node, direction, predicateFn, rootNode, shallow2) => {\n const walker + = new DomTreeWalker(node, rootNode);\n const isCefOrCaretContainer + = isContentEditableFalse$6(node) || isCaretContainerBlock(node);\n if + (isBackwards(direction)) {\n if (isCefOrCaretContainer) {\n node + = skipCaretContainers(walker.prev.bind(walker), true);\n if (predicateFn(node)) + {\n return node;\n }\n }\n while + (node = skipCaretContainers(walker.prev.bind(walker), shallow2)) {\n if + (predicateFn(node)) {\n return node;\n }\n }\n + \ }\n if (isForwards(direction)) {\n if (isCefOrCaretContainer) + {\n node = skipCaretContainers(walker.next.bind(walker), true);\n + \ if (predicateFn(node)) {\n return node;\n }\n + \ }\n while (node = skipCaretContainers(walker.next.bind(walker), + shallow2)) {\n if (predicateFn(node)) {\n return node;\n + \ }\n }\n }\n return null;\n };\n const + getEditingHost = (node, rootNode) => {\n const isCETrue = (node2) => + isContentEditableTrue$2(node2.dom);\n const isRoot = (node2) => node2.dom + === rootNode;\n return ancestor$3(SugarElement.fromDom(node), isCETrue, + isRoot).map((elm) => elm.dom).getOr(rootNode);\n };\n const getParentBlock$3 + = (node, rootNode) => {\n while (node && node !== rootNode) {\n if + (isBlockLike(node)) {\n return node;\n }\n node + = node.parentNode;\n }\n return null;\n };\n const + isInSameBlock = (caretPosition1, caretPosition2, rootNode) => getParentBlock$3(caretPosition1.container(), + rootNode) === getParentBlock$3(caretPosition2.container(), rootNode);\n const + getChildNodeAtRelativeOffset = (relativeOffset, caretPosition) => {\n if + (!caretPosition) {\n return null;\n }\n const container + = caretPosition.container();\n const offset = caretPosition.offset();\n + \ if (!isElement$2(container)) {\n return null;\n }\n + \ return container.childNodes[offset + relativeOffset];\n };\n + \ const beforeAfter = (before2, node) => {\n const range2 = node.ownerDocument.createRange();\n + \ if (before2) {\n range2.setStartBefore(node);\n range2.setEndBefore(node);\n + \ } else {\n range2.setStartAfter(node);\n range2.setEndAfter(node);\n + \ }\n return range2;\n };\n const isNodesInSameBlock + = (root2, node1, node2) => getParentBlock$3(node1, root2) === getParentBlock$3(node2, + root2);\n const lean = (left, root2, node) => {\n const siblingName + = left ? \"previousSibling\" : \"nextSibling\";\n while (node && node + !== root2) {\n let sibling2 = node[siblingName];\n if (isCaretContainer(sibling2)) + {\n sibling2 = sibling2[siblingName];\n }\n if + (isContentEditableFalse$6(sibling2) || isMedia(sibling2)) {\n if + (isNodesInSameBlock(root2, sibling2, node)) {\n return sibling2;\n + \ }\n break;\n }\n if (isCaretCandidate$1(sibling2)) + {\n break;\n }\n node = node.parentNode;\n }\n + \ return null;\n };\n const before$2 = curry(beforeAfter, + true);\n const after$2 = curry(beforeAfter, false);\n const normalizeRange + = (direction, root2, range2) => {\n let node;\n const leanLeft + = curry(lean, true, root2);\n const leanRight2 = curry(lean, false, + root2);\n let container = range2.startContainer;\n const offset + = range2.startOffset;\n if (isCaretContainerBlock$1(container)) {\n + \ if (!isElement$2(container)) {\n container = container.parentNode;\n + \ }\n const location2 = container.getAttribute(\"data-mce-caret\");\n + \ if (location2 === \"before\") {\n node = container.nextSibling;\n + \ if (isFakeCaretTarget(node)) {\n return before$2(node);\n + \ }\n }\n if (location2 === \"after\") {\n node + = container.previousSibling;\n if (isFakeCaretTarget(node)) {\n + \ return after$2(node);\n }\n }\n }\n + \ if (!range2.collapsed) {\n return range2;\n }\n if + (isText$8(container)) {\n if (isCaretContainer(container)) {\n if + (direction === 1) {\n node = leanRight2(container);\n if + (node) {\n return before$2(node);\n }\n node + = leanLeft(container);\n if (node) {\n return + after$2(node);\n }\n }\n if (direction + === -1) {\n node = leanLeft(container);\n if (node) + {\n return after$2(node);\n }\n node + = leanRight2(container);\n if (node) {\n return + before$2(node);\n }\n }\n return range2;\n + \ }\n if (endsWithCaretContainer$1(container) && offset >= + container.data.length - 1) {\n if (direction === 1) {\n node + = leanRight2(container);\n if (node) {\n return + before$2(node);\n }\n }\n return range2;\n + \ }\n if (startsWithCaretContainer$1(container) && offset + <= 1) {\n if (direction === -1) {\n node = leanLeft(container);\n + \ if (node) {\n return after$2(node);\n }\n + \ }\n return range2;\n }\n if (offset + === container.data.length) {\n node = leanRight2(container);\n + \ if (node) {\n return before$2(node);\n }\n + \ return range2;\n }\n if (offset === 0) {\n node + = leanLeft(container);\n if (node) {\n return after$2(node);\n + \ }\n return range2;\n }\n }\n return + range2;\n };\n const getRelativeCefElm = (forward, caretPosition) + => Optional.from(getChildNodeAtRelativeOffset(forward ? 0 : -1, caretPosition)).filter(isContentEditableFalse$6);\n + \ const getNormalizedRangeEndPoint = (direction, root2, range2) => {\n + \ const normalizedRange = normalizeRange(direction, root2, range2);\n + \ if (direction === -1) {\n return CaretPosition.fromRangeStart(normalizedRange);\n + \ }\n return CaretPosition.fromRangeEnd(normalizedRange);\n };\n + \ const getElementFromPosition = (pos) => Optional.from(pos.getNode()).map(SugarElement.fromDom);\n + \ const getElementFromPrevPosition = (pos) => Optional.from(pos.getNode(true)).map(SugarElement.fromDom);\n + \ const getVisualCaretPosition = (walkFn, caretPosition) => {\n while + (caretPosition = walkFn(caretPosition)) {\n if (caretPosition.isVisible()) + {\n return caretPosition;\n }\n }\n return + caretPosition;\n };\n const isMoveInsideSameBlock = (from2, to2) + => {\n const inSameBlock = isInSameBlock(from2, to2);\n if (!inSameBlock + && isBr$5(from2.getNode())) {\n return true;\n }\n return + inSameBlock;\n };\n var HDirection;\n (function(HDirection2) + {\n HDirection2[HDirection2[\"Backwards\"] = -1] = \"Backwards\";\n + \ HDirection2[HDirection2[\"Forwards\"] = 1] = \"Forwards\";\n })(HDirection + || (HDirection = {}));\n const isContentEditableFalse$5 = isContentEditableFalse$a;\n + \ const isText$2 = isText$8;\n const isElement$1 = isElement$6;\n + \ const isBr$1 = isBr$5;\n const isCaretCandidate = isCaretCandidate$3;\n + \ const isAtomic = isAtomic$1;\n const isEditableCaretCandidate = + isEditableCaretCandidate$1;\n const getParents$3 = (node, root2) => {\n + \ const parents2 = [];\n while (node && node !== root2) {\n parents2.push(node);\n + \ node = node.parentNode;\n }\n return parents2;\n };\n + \ const nodeAtIndex = (container, offset) => {\n if (container.hasChildNodes() + && offset < container.childNodes.length) {\n return container.childNodes[offset];\n + \ }\n return null;\n };\n const getCaretCandidatePosition + = (direction, node) => {\n if (isForwards(direction)) {\n if + (isCaretCandidate(node.previousSibling) && !isText$2(node.previousSibling)) + {\n return CaretPosition.before(node);\n }\n if + (isText$2(node)) {\n return CaretPosition(node, 0);\n }\n + \ }\n if (isBackwards(direction)) {\n if (isCaretCandidate(node.nextSibling) + && !isText$2(node.nextSibling)) {\n return CaretPosition.after(node);\n + \ }\n if (isText$2(node)) {\n return CaretPosition(node, + node.data.length);\n }\n }\n if (isBackwards(direction)) + {\n if (isBr$1(node)) {\n return CaretPosition.before(node);\n + \ }\n return CaretPosition.after(node);\n }\n return + CaretPosition.before(node);\n };\n const moveForwardFromBr = (root2, + nextNode) => {\n const nextSibling2 = nextNode.nextSibling;\n if + (nextSibling2 && isCaretCandidate(nextSibling2)) {\n if (isText$2(nextSibling2)) + {\n return CaretPosition(nextSibling2, 0);\n } else {\n + \ return CaretPosition.before(nextSibling2);\n }\n } + else {\n return findCaretPosition$1(HDirection.Forwards, CaretPosition.after(nextNode), + root2);\n }\n };\n const findCaretPosition$1 = (direction, + startPos, root2) => {\n let node;\n let nextNode;\n let + innerNode;\n let caretPosition;\n if (!isElement$1(root2) || + !startPos) {\n return null;\n }\n if (startPos.isEqual(CaretPosition.after(root2)) + && root2.lastChild) {\n caretPosition = CaretPosition.after(root2.lastChild);\n + \ if (isBackwards(direction) && isCaretCandidate(root2.lastChild) + && isElement$1(root2.lastChild)) {\n return isBr$1(root2.lastChild) + ? CaretPosition.before(root2.lastChild) : caretPosition;\n }\n } + else {\n caretPosition = startPos;\n }\n const container + = caretPosition.container();\n let offset = caretPosition.offset();\n + \ if (isText$2(container)) {\n if (isBackwards(direction) && + offset > 0) {\n return CaretPosition(container, --offset);\n }\n + \ if (isForwards(direction) && offset < container.length) {\n return + CaretPosition(container, ++offset);\n }\n node = container;\n + \ } else {\n if (isBackwards(direction) && offset > 0) {\n + \ nextNode = nodeAtIndex(container, offset - 1);\n if + (isCaretCandidate(nextNode)) {\n if (!isAtomic(nextNode)) {\n + \ innerNode = findNode(nextNode, direction, isEditableCaretCandidate, + nextNode);\n if (innerNode) {\n if (isText$2(innerNode)) + {\n return CaretPosition(innerNode, innerNode.data.length);\n + \ }\n return CaretPosition.after(innerNode);\n + \ }\n }\n if (isText$2(nextNode)) + {\n return CaretPosition(nextNode, nextNode.data.length);\n + \ }\n return CaretPosition.before(nextNode);\n }\n + \ }\n if (isForwards(direction) && offset < container.childNodes.length) + {\n nextNode = nodeAtIndex(container, offset);\n if + (isCaretCandidate(nextNode)) {\n if (isBr$1(nextNode)) {\n return + moveForwardFromBr(root2, nextNode);\n }\n if (!isAtomic(nextNode)) + {\n innerNode = findNode(nextNode, direction, isEditableCaretCandidate, + nextNode);\n if (innerNode) {\n if (isText$2(innerNode)) + {\n return CaretPosition(innerNode, 0);\n }\n + \ return CaretPosition.before(innerNode);\n }\n + \ }\n if (isText$2(nextNode)) {\n return + CaretPosition(nextNode, 0);\n }\n return CaretPosition.after(nextNode);\n + \ }\n }\n node = nextNode ? nextNode : caretPosition.getNode();\n + \ }\n if (isForwards(direction) && caretPosition.isAtEnd() || + isBackwards(direction) && caretPosition.isAtStart()) {\n node = findNode(node, + direction, always, root2, true);\n if (isEditableCaretCandidate(node, + root2)) {\n return getCaretCandidatePosition(direction, node);\n + \ }\n }\n nextNode = findNode(node, direction, isEditableCaretCandidate, + root2);\n const rootContentEditableFalseElm = last$2(filter$6(getParents$3(container, + root2), isContentEditableFalse$5));\n if (rootContentEditableFalseElm + && (!nextNode || !rootContentEditableFalseElm.contains(nextNode))) {\n if + (isForwards(direction)) {\n caretPosition = CaretPosition.after(rootContentEditableFalseElm);\n + \ } else {\n caretPosition = CaretPosition.before(rootContentEditableFalseElm);\n + \ }\n return caretPosition;\n }\n if (nextNode) + {\n return getCaretCandidatePosition(direction, nextNode);\n }\n + \ return null;\n };\n const CaretWalker = (root2) => ({\n + \ next: (caretPosition) => {\n return findCaretPosition$1(HDirection.Forwards, + caretPosition, root2);\n },\n prev: (caretPosition) => {\n return + findCaretPosition$1(HDirection.Backwards, caretPosition, root2);\n }\n + \ });\n const walkToPositionIn = (forward, root2, start2) => {\n + \ const position = forward ? CaretPosition.before(start2) : CaretPosition.after(start2);\n + \ return fromPosition(forward, root2, position);\n };\n const + afterElement = (node) => isBr$5(node) ? CaretPosition.before(node) : CaretPosition.after(node);\n + \ const isBeforeOrStart = (position) => {\n if (CaretPosition.isTextPosition(position)) + {\n return position.offset() === 0;\n } else {\n return + isCaretCandidate$3(position.getNode());\n }\n };\n const + isAfterOrEnd = (position) => {\n if (CaretPosition.isTextPosition(position)) + {\n const container = position.container();\n return position.offset() + === container.data.length;\n } else {\n return isCaretCandidate$3(position.getNode(true));\n + \ }\n };\n const isBeforeAfterSameElement = (from2, to2) => + !CaretPosition.isTextPosition(from2) && !CaretPosition.isTextPosition(to2) + && from2.getNode() === to2.getNode(true);\n const isAtBr = (position) + => !CaretPosition.isTextPosition(position) && isBr$5(position.getNode());\n + \ const shouldSkipPosition = (forward, from2, to2) => {\n if (forward) + {\n return !isBeforeAfterSameElement(from2, to2) && !isAtBr(from2) + && isAfterOrEnd(from2) && isBeforeOrStart(to2);\n } else {\n return + !isBeforeAfterSameElement(to2, from2) && isBeforeOrStart(from2) && isAfterOrEnd(to2);\n + \ }\n };\n const fromPosition = (forward, root2, pos) => {\n + \ const walker = CaretWalker(root2);\n return Optional.from(forward + ? walker.next(pos) : walker.prev(pos));\n };\n const navigate = + (forward, root2, from2) => fromPosition(forward, root2, from2).bind((to2) + => {\n if (isInSameBlock(from2, to2, root2) && shouldSkipPosition(forward, + from2, to2)) {\n return fromPosition(forward, root2, to2);\n } + else {\n return Optional.some(to2);\n }\n });\n const + navigateIgnore = (forward, root2, from2, ignoreFilter) => navigate(forward, + root2, from2).bind((pos) => ignoreFilter(pos) ? navigateIgnore(forward, root2, + pos, ignoreFilter) : Optional.some(pos));\n const positionIn = (forward, + element) => {\n const startNode = forward ? element.firstChild : element.lastChild;\n + \ if (isText$8(startNode)) {\n return Optional.some(CaretPosition(startNode, + forward ? 0 : startNode.data.length));\n } else if (startNode) {\n + \ if (isCaretCandidate$3(startNode)) {\n return Optional.some(forward + ? CaretPosition.before(startNode) : afterElement(startNode));\n } + else {\n return walkToPositionIn(forward, element, startNode);\n + \ }\n } else {\n return Optional.none();\n }\n + \ };\n const nextPosition = curry(fromPosition, true);\n const + prevPosition = curry(fromPosition, false);\n const firstPositionIn = + curry(positionIn, true);\n const lastPositionIn = curry(positionIn, false);\n + \ const CARET_ID$1 = \"_mce_caret\";\n const isCaretNode = (node) + => isElement$6(node) && node.id === CARET_ID$1;\n const getParentCaretContainer + = (body, node) => {\n while (node && node !== body) {\n if + (node.id === CARET_ID$1) {\n return node;\n }\n node + = node.parentNode;\n }\n return null;\n };\n const + isStringPathBookmark = (bookmark) => isString2(bookmark.start);\n const + isRangeBookmark = (bookmark) => has$2(bookmark, \"rng\");\n const isIdBookmark + = (bookmark) => has$2(bookmark, \"id\");\n const isIndexBookmark = (bookmark) + => has$2(bookmark, \"name\");\n const isPathBookmark = (bookmark) => + Tools.isArray(bookmark.start);\n const addBogus = (dom3, node) => {\n + \ if (isElement$6(node) && dom3.isBlock(node) && !node.innerHTML) {\n + \ node.innerHTML = '
';\n }\n return + node;\n };\n const resolveCaretPositionBookmark = (dom3, bookmark) + => {\n let pos;\n const rng = dom3.createRng();\n pos + = resolve$1(dom3.getRoot(), bookmark.start);\n rng.setStart(pos.container(), + pos.offset());\n pos = resolve$1(dom3.getRoot(), bookmark.end);\n rng.setEnd(pos.container(), + pos.offset());\n return rng;\n };\n const insertZwsp = (node, + rng) => {\n const textNode = node.ownerDocument.createTextNode(ZWSP$1);\n + \ node.appendChild(textNode);\n rng.setStart(textNode, 0);\n + \ rng.setEnd(textNode, 0);\n };\n const isEmpty$1 = (node) + => node.hasChildNodes() === false;\n const tryFindRangePosition = (node, + rng) => lastPositionIn(node).fold(never, (pos) => {\n rng.setStart(pos.container(), + pos.offset());\n rng.setEnd(pos.container(), pos.offset());\n return + true;\n });\n const padEmptyCaretContainer = (root2, node, rng) + => {\n if (isEmpty$1(node) && getParentCaretContainer(root2, node)) + {\n insertZwsp(node, rng);\n return true;\n } else + {\n return false;\n }\n };\n const setEndPoint = + (dom3, start2, bookmark, rng) => {\n const point2 = bookmark[start2 + ? \"start\" : \"end\"];\n let i3, node, offset, children2;\n const + root2 = dom3.getRoot();\n if (point2) {\n offset = point2[0];\n + \ for (node = root2, i3 = point2.length - 1; i3 >= 1; i3--) {\n children2 + = node.childNodes;\n if (padEmptyCaretContainer(root2, node, rng)) + {\n return true;\n }\n if (point2[i3] > + children2.length - 1) {\n if (padEmptyCaretContainer(root2, node, + rng)) {\n return true;\n }\n return + tryFindRangePosition(node, rng);\n }\n node = children2[point2[i3]];\n + \ }\n if (node.nodeType === 3) {\n offset = Math.min(point2[0], + node.nodeValue.length);\n }\n if (node.nodeType === 1) {\n + \ offset = Math.min(point2[0], node.childNodes.length);\n }\n + \ if (start2) {\n rng.setStart(node, offset);\n } + else {\n rng.setEnd(node, offset);\n }\n }\n return + true;\n };\n const isValidTextNode = (node) => isText$8(node) && + node.data.length > 0;\n const restoreEndPoint = (dom3, suffix, bookmark) + => {\n let marker = dom3.get(bookmark.id + \"_\" + suffix), node, idx, + next2, prev2;\n const keep = bookmark.keep;\n let container, + offset;\n if (marker) {\n node = marker.parentNode;\n if + (suffix === \"start\") {\n if (!keep) {\n idx = dom3.nodeIndex(marker);\n + \ } else {\n if (marker.hasChildNodes()) {\n node + = marker.firstChild;\n idx = 1;\n } else if (isValidTextNode(marker.nextSibling)) + {\n node = marker.nextSibling;\n idx = 0;\n + \ } else if (isValidTextNode(marker.previousSibling)) {\n node + = marker.previousSibling;\n idx = marker.previousSibling.data.length;\n + \ } else {\n node = marker.parentNode;\n idx + = dom3.nodeIndex(marker) + 1;\n }\n }\n container + = node;\n offset = idx;\n } else {\n if (!keep) + {\n idx = dom3.nodeIndex(marker);\n } else {\n if + (marker.hasChildNodes()) {\n node = marker.firstChild;\n idx + = 1;\n } else if (isValidTextNode(marker.previousSibling)) {\n + \ node = marker.previousSibling;\n idx = marker.previousSibling.data.length;\n + \ } else {\n node = marker.parentNode;\n idx + = dom3.nodeIndex(marker);\n }\n }\n container + = node;\n offset = idx;\n }\n if (!keep) {\n + \ prev2 = marker.previousSibling;\n next2 = marker.nextSibling;\n + \ Tools.each(Tools.grep(marker.childNodes), (node2) => {\n if + (isText$8(node2)) {\n node2.nodeValue = node2.nodeValue.replace(/\\uFEFF/g, + \"\");\n }\n });\n while (marker = dom3.get(bookmark.id + + \"_\" + suffix)) {\n dom3.remove(marker, true);\n }\n + \ if (prev2 && next2 && prev2.nodeType === next2.nodeType && isText$8(prev2) + && !Env.browser.isOpera()) {\n idx = prev2.nodeValue.length;\n + \ prev2.appendData(next2.nodeValue);\n dom3.remove(next2);\n + \ container = prev2;\n offset = idx;\n }\n + \ }\n return Optional.some(CaretPosition(container, offset));\n + \ } else {\n return Optional.none();\n }\n };\n + \ const resolvePaths = (dom3, bookmark) => {\n const rng = dom3.createRng();\n + \ if (setEndPoint(dom3, true, bookmark, rng) && setEndPoint(dom3, false, + bookmark, rng)) {\n return Optional.some(rng);\n } else {\n + \ return Optional.none();\n }\n };\n const resolveId + = (dom3, bookmark) => {\n const startPos = restoreEndPoint(dom3, \"start\", + bookmark);\n const endPos = restoreEndPoint(dom3, \"end\", bookmark);\n + \ return lift2(startPos, endPos.or(startPos), (spos, epos) => {\n const + rng = dom3.createRng();\n rng.setStart(addBogus(dom3, spos.container()), + spos.offset());\n rng.setEnd(addBogus(dom3, epos.container()), epos.offset());\n + \ return rng;\n });\n };\n const resolveIndex = (dom3, + bookmark) => Optional.from(dom3.select(bookmark.name)[bookmark.index]).map((elm) + => {\n const rng = dom3.createRng();\n rng.selectNode(elm);\n + \ return rng;\n });\n const resolve = (selection, bookmark) + => {\n const dom3 = selection.dom;\n if (bookmark) {\n if + (isPathBookmark(bookmark)) {\n return resolvePaths(dom3, bookmark);\n + \ } else if (isStringPathBookmark(bookmark)) {\n return + Optional.some(resolveCaretPositionBookmark(dom3, bookmark));\n } + else if (isIdBookmark(bookmark)) {\n return resolveId(dom3, bookmark);\n + \ } else if (isIndexBookmark(bookmark)) {\n return resolveIndex(dom3, + bookmark);\n } else if (isRangeBookmark(bookmark)) {\n return + Optional.some(bookmark.rng);\n }\n }\n return Optional.none();\n + \ };\n const getBookmark$1 = (selection, type2, normalized) => {\n + \ return getBookmark$2(selection, type2, normalized);\n };\n const + moveToBookmark = (selection, bookmark) => {\n resolve(selection, bookmark).each((rng) + => {\n selection.setRng(rng);\n });\n };\n const + isBookmarkNode$1 = (node) => {\n return isElement$6(node) && node.tagName + === \"SPAN\" && node.getAttribute(\"data-mce-type\") === \"bookmark\";\n };\n + \ const is = (expected) => (actual) => expected === actual;\n const + isNbsp = is(nbsp);\n const isWhiteSpace2 = (chr) => chr !== \"\" && \" + \\f\\n\\r\t\\v\".indexOf(chr) !== -1;\n const isContent = (chr) => !isWhiteSpace2(chr) + && !isNbsp(chr) && !isZwsp$1(chr);\n const hexColour = (value2) => ({ + value: value2 });\n const toHex = (component) => {\n const hex + = component.toString(16);\n return (hex.length === 1 ? \"0\" + hex + : hex).toUpperCase();\n };\n const fromRgba = (rgbaColour2) => {\n + \ const value2 = toHex(rgbaColour2.red) + toHex(rgbaColour2.green) + + toHex(rgbaColour2.blue);\n return hexColour(value2);\n };\n const + rgbRegex = /^\\s*rgb\\s*\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*\\)\\s*$/i;\n + \ const rgbaRegex = /^\\s*rgba\\s*\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d?(?:\\.\\d+)?)\\s*\\)\\s*$/i;\n + \ const rgbaColour = (red, green, blue, alpha) => ({\n red,\n green,\n + \ blue,\n alpha\n });\n const fromStringValues = (red, + green, blue, alpha) => {\n const r4 = parseInt(red, 10);\n const + g2 = parseInt(green, 10);\n const b2 = parseInt(blue, 10);\n const + a2 = parseFloat(alpha);\n return rgbaColour(r4, g2, b2, a2);\n };\n + \ const fromString = (rgbaString) => {\n if (rgbaString === \"transparent\") + {\n return Optional.some(rgbaColour(0, 0, 0, 0));\n }\n const + rgbMatch = rgbRegex.exec(rgbaString);\n if (rgbMatch !== null) {\n + \ return Optional.some(fromStringValues(rgbMatch[1], rgbMatch[2], + rgbMatch[3], \"1\"));\n }\n const rgbaMatch = rgbaRegex.exec(rgbaString);\n + \ if (rgbaMatch !== null) {\n return Optional.some(fromStringValues(rgbaMatch[1], + rgbaMatch[2], rgbaMatch[3], rgbaMatch[4]));\n }\n return Optional.none();\n + \ };\n const rgbaToHexString = (color) => fromString(color).map(fromRgba).map((h2) + => \"#\" + h2.value).getOr(color);\n const isNode = (node) => !!node.nodeType;\n + \ const isInlineBlock = (node) => {\n return node && /^(IMG)$/.test(node.nodeName);\n + \ };\n const moveStart = (dom3, selection, rng) => {\n const + offset = rng.startOffset;\n let container = rng.startContainer;\n if + (container === rng.endContainer) {\n if (isInlineBlock(container.childNodes[offset])) + {\n return;\n }\n }\n if (isElement$6(container)) + {\n const nodes = container.childNodes;\n let walker;\n + \ if (offset < nodes.length) {\n container = nodes[offset];\n + \ walker = new DomTreeWalker(container, dom3.getParent(container, + dom3.isBlock));\n } else {\n container = nodes[nodes.length + - 1];\n walker = new DomTreeWalker(container, dom3.getParent(container, + dom3.isBlock));\n walker.next(true);\n }\n for + (let node = walker.current(); node; node = walker.next()) {\n if + (isText$8(node) && !isWhiteSpaceNode$1(node)) {\n rng.setStart(node, + 0);\n selection.setRng(rng);\n return;\n }\n + \ }\n }\n };\n const getNonWhiteSpaceSibling = (node, + next2, inc) => {\n if (node) {\n const nextName = next2 ? + \"nextSibling\" : \"previousSibling\";\n for (node = node[nextName]; + node; node = node[nextName]) {\n if (isElement$6(node) || !isWhiteSpaceNode$1(node)) + {\n return node;\n }\n }\n }\n };\n + \ const isTextBlock$1 = (editor, name2) => {\n if (isNode(name2)) + {\n name2 = name2.nodeName;\n }\n return !!editor.schema.getTextBlockElements()[name2.toLowerCase()];\n + \ };\n const isValid = (ed, parent2, child2) => {\n return + ed.schema.isValidChild(parent2, child2);\n };\n const isWhiteSpaceNode$1 + = (node, allowSpaces = false) => {\n if (isNonNullable(node) && isText$8(node)) + {\n const data2 = allowSpaces ? node.data.replace(/ /g, \" \") : + node.data;\n return isWhitespaceText(data2);\n } else {\n + \ return false;\n }\n };\n const isEmptyTextNode$1 + = (node) => {\n return isNonNullable(node) && isText$8(node) && node.length + === 0;\n };\n const replaceVars = (value2, vars) => {\n if + (isFunction2(value2)) {\n value2 = value2(vars);\n } else + if (isNonNullable(vars)) {\n value2 = value2.replace(/%(\\w+)/g, + (str, name2) => {\n return vars[name2] || str;\n });\n + \ }\n return value2;\n };\n const isEq$5 = (str1, str2) + => {\n str1 = str1 || \"\";\n str2 = str2 || \"\";\n str1 + = \"\" + (str1.nodeName || str1);\n str2 = \"\" + (str2.nodeName || + str2);\n return str1.toLowerCase() === str2.toLowerCase();\n };\n + \ const normalizeStyleValue = (value2, name2) => {\n if (name2 + === \"color\" || name2 === \"backgroundColor\") {\n value2 = rgbaToHexString(value2);\n + \ }\n if (name2 === \"fontWeight\" && value2 === 700) {\n value2 + = \"bold\";\n }\n if (name2 === \"fontFamily\") {\n value2 + = value2.replace(/[\\'\\\"]/g, \"\").replace(/,\\s+/g, \",\");\n }\n + \ return \"\" + value2;\n };\n const getStyle = (dom3, node, + name2) => {\n return normalizeStyleValue(dom3.getStyle(node, name2), + name2);\n };\n const getTextDecoration = (dom3, node) => {\n let + decoration;\n dom3.getParent(node, (n3) => {\n decoration + = dom3.getStyle(n3, \"text-decoration\");\n return decoration && + decoration !== \"none\";\n });\n return decoration;\n };\n + \ const getParents$2 = (dom3, node, selector) => {\n return dom3.getParents(node, + selector, dom3.getRoot());\n };\n const isVariableFormatName = (editor, + formatName) => {\n const hasVariableValues = (format2) => {\n const + isVariableValue = (val) => val.length > 1 && val.charAt(0) === \"%\";\n return + exists([\n \"styles\",\n \"attributes\"\n ], + (key) => get$a(format2, key).exists((field2) => {\n const fieldValues + = isArray$1(field2) ? field2 : values(field2);\n return exists(fieldValues, + isVariableValue);\n }));\n };\n return exists(editor.formatter.get(formatName), + hasVariableValues);\n };\n const areSimilarFormats = (editor, formatName, + otherFormatName) => {\n const validKeys = [\n \"inline\",\n + \ \"block\",\n \"selector\",\n \"attributes\",\n + \ \"styles\",\n \"classes\"\n ];\n const filterObj + = (format2) => filter$5(format2, (_2, key) => exists(validKeys, (validKey) + => validKey === key));\n return exists(editor.formatter.get(formatName), + (fmt1) => {\n const filteredFmt1 = filterObj(fmt1);\n return + exists(editor.formatter.get(otherFormatName), (fmt2) => {\n const + filteredFmt2 = filterObj(fmt2);\n return equal$1(filteredFmt1, + filteredFmt2);\n });\n });\n };\n const isBlockFormat + = (format2) => hasNonNullableKey(format2, \"block\");\n const isSelectorFormat + = (format2) => hasNonNullableKey(format2, \"selector\");\n const isInlineFormat + = (format2) => hasNonNullableKey(format2, \"inline\");\n const isMixedFormat + = (format2) => isSelectorFormat(format2) && isInlineFormat(format2) && is$2(get$a(format2, + \"mixed\"), true);\n const shouldExpandToSelector = (format2) => isSelectorFormat(format2) + && format2.expand !== false && !isInlineFormat(format2);\n const isBookmarkNode + = isBookmarkNode$1;\n const getParents$1 = getParents$2;\n const + isWhiteSpaceNode = isWhiteSpaceNode$1;\n const isTextBlock = isTextBlock$1;\n + \ const isBogusBr = (node) => {\n return isBr$5(node) && node.getAttribute(\"data-mce-bogus\") + && !node.nextSibling;\n };\n const findParentContentEditable = (dom3, + node) => {\n let parent2 = node;\n while (parent2) {\n if + (isElement$6(parent2) && dom3.getContentEditable(parent2)) {\n return + dom3.getContentEditable(parent2) === \"false\" ? parent2 : node;\n }\n + \ parent2 = parent2.parentNode;\n }\n return node;\n + \ };\n const walkText = (start2, node, offset, predicate) => {\n + \ const str = node.data;\n for (let i3 = offset; start2 ? i3 + >= 0 : i3 < str.length; start2 ? i3-- : i3++) {\n if (predicate(str.charAt(i3))) + {\n return start2 ? i3 + 1 : i3;\n }\n }\n return + -1;\n };\n const findSpace = (start2, node, offset) => walkText(start2, + node, offset, (c2) => isNbsp(c2) || isWhiteSpace2(c2));\n const findContent + = (start2, node, offset) => walkText(start2, node, offset, isContent);\n const + findWordEndPoint = (dom3, body, container, offset, start2, includeTrailingSpaces) + => {\n let lastTextNode;\n const rootNode = dom3.getParent(container, + dom3.isBlock) || body;\n const walk2 = (container2, offset2, pred) + => {\n const textSeeker = TextSeeker(dom3);\n const walker + = start2 ? textSeeker.backwards : textSeeker.forwards;\n return Optional.from(walker(container2, + offset2, (text3, textOffset) => {\n if (isBookmarkNode(text3.parentNode)) + {\n return -1;\n } else {\n lastTextNode + = text3;\n return pred(start2, text3, textOffset);\n }\n + \ }, rootNode));\n };\n const spaceResult = walk2(container, + offset, findSpace);\n return spaceResult.bind((result) => includeTrailingSpaces + ? walk2(result.container, result.offset + (start2 ? -1 : 0), findContent) + : Optional.some(result)).orThunk(() => lastTextNode ? Optional.some({\n container: + lastTextNode,\n offset: start2 ? 0 : lastTextNode.length\n }) + : Optional.none());\n };\n const findSelectorEndPoint = (dom3, formatList, + rng, container, siblingName) => {\n if (isText$8(container) && isEmpty$3(container.data) + && container[siblingName]) {\n container = container[siblingName];\n + \ }\n const parents2 = getParents$1(dom3, container);\n for + (let i3 = 0; i3 < parents2.length; i3++) {\n for (let y2 = 0; y2 + < formatList.length; y2++) {\n const curFormat = formatList[y2];\n + \ if (isNonNullable(curFormat.collapsed) && curFormat.collapsed + !== rng.collapsed) {\n continue;\n }\n if + (isSelectorFormat(curFormat) && dom3.is(parents2[i3], curFormat.selector)) + {\n return parents2[i3];\n }\n }\n }\n + \ return container;\n };\n const findBlockEndPoint = (editor, + formatList, container, siblingName) => {\n let node = container;\n + \ const dom3 = editor.dom;\n const root2 = dom3.getRoot();\n + \ const format2 = formatList[0];\n if (isBlockFormat(format2)) + {\n node = format2.wrapper ? null : dom3.getParent(container, format2.block, + root2);\n }\n if (!node) {\n const scopeRoot = dom3.getParent(container, + \"LI,TD,TH\");\n node = dom3.getParent(isText$8(container) ? container.parentNode + : container, (node2) => node2 !== root2 && isTextBlock(editor, node2), scopeRoot);\n + \ }\n if (node && isBlockFormat(format2) && format2.wrapper) + {\n node = getParents$1(dom3, node, \"ul,ol\").reverse()[0] || node;\n + \ }\n if (!node) {\n node = container;\n while + (node[siblingName] && !dom3.isBlock(node[siblingName])) {\n node + = node[siblingName];\n if (isEq$5(node, \"br\")) {\n break;\n + \ }\n }\n }\n return node || container;\n + \ };\n const isAtBlockBoundary$1 = (dom3, root2, container, siblingName) + => {\n const parent2 = container.parentNode;\n if (isNonNullable(container[siblingName])) + {\n return false;\n } else if (parent2 === root2 || isNullable(parent2) + || dom3.isBlock(parent2)) {\n return true;\n } else {\n return + isAtBlockBoundary$1(dom3, root2, parent2, siblingName);\n }\n };\n + \ const findParentContainer = (dom3, formatList, container, offset, start2) + => {\n let parent2 = container;\n const siblingName = start2 + ? \"previousSibling\" : \"nextSibling\";\n const root2 = dom3.getRoot();\n + \ if (isText$8(container) && !isWhiteSpaceNode(container)) {\n if + (start2 ? offset > 0 : offset < container.data.length) {\n return + container;\n }\n }\n while (true) {\n if (!formatList[0].block_expand + && dom3.isBlock(parent2)) {\n return parent2;\n }\n for + (let sibling2 = parent2[siblingName]; sibling2; sibling2 = sibling2[siblingName]) + {\n const allowSpaces = isText$8(sibling2) && !isAtBlockBoundary$1(dom3, + root2, sibling2, siblingName);\n if (!isBookmarkNode(sibling2) + && !isBogusBr(sibling2) && !isWhiteSpaceNode(sibling2, allowSpaces)) {\n return + parent2;\n }\n }\n if (parent2 === root2 || parent2.parentNode + === root2) {\n container = parent2;\n break;\n }\n + \ parent2 = parent2.parentNode;\n }\n return container;\n + \ };\n const isSelfOrParentBookmark = (container) => isBookmarkNode(container.parentNode) + || isBookmarkNode(container);\n const expandRng = (editor, rng, formatList, + includeTrailingSpace = false) => {\n let { startContainer, startOffset, + endContainer, endOffset } = rng;\n const dom3 = editor.dom;\n const + format2 = formatList[0];\n if (isElement$6(startContainer) && startContainer.hasChildNodes()) + {\n startContainer = getNode$1(startContainer, startOffset);\n if + (isText$8(startContainer)) {\n startOffset = 0;\n }\n + \ }\n if (isElement$6(endContainer) && endContainer.hasChildNodes()) + {\n endContainer = getNode$1(endContainer, rng.collapsed ? endOffset + : endOffset - 1);\n if (isText$8(endContainer)) {\n endOffset + = endContainer.nodeValue.length;\n }\n }\n startContainer + = findParentContentEditable(dom3, startContainer);\n endContainer = + findParentContentEditable(dom3, endContainer);\n if (isSelfOrParentBookmark(startContainer)) + {\n startContainer = isBookmarkNode(startContainer) ? startContainer + : startContainer.parentNode;\n if (rng.collapsed) {\n startContainer + = startContainer.previousSibling || startContainer;\n } else {\n + \ startContainer = startContainer.nextSibling || startContainer;\n + \ }\n if (isText$8(startContainer)) {\n startOffset + = rng.collapsed ? startContainer.length : 0;\n }\n }\n if + (isSelfOrParentBookmark(endContainer)) {\n endContainer = isBookmarkNode(endContainer) + ? endContainer : endContainer.parentNode;\n if (rng.collapsed) {\n + \ endContainer = endContainer.nextSibling || endContainer;\n } + else {\n endContainer = endContainer.previousSibling || endContainer;\n + \ }\n if (isText$8(endContainer)) {\n endOffset + = rng.collapsed ? 0 : endContainer.length;\n }\n }\n if + (rng.collapsed) {\n const startPoint = findWordEndPoint(dom3, editor.getBody(), + startContainer, startOffset, true, includeTrailingSpace);\n startPoint.each(({ + container, offset }) => {\n startContainer = container;\n startOffset + = offset;\n });\n const endPoint = findWordEndPoint(dom3, + editor.getBody(), endContainer, endOffset, false, includeTrailingSpace);\n + \ endPoint.each(({ container, offset }) => {\n endContainer + = container;\n endOffset = offset;\n });\n }\n + \ if (isInlineFormat(format2) || format2.block_expand) {\n if + (!isInlineFormat(format2) || (!isText$8(startContainer) || startOffset === + 0)) {\n startContainer = findParentContainer(dom3, formatList, + startContainer, startOffset, true);\n }\n if (!isInlineFormat(format2) + || (!isText$8(endContainer) || endOffset === endContainer.nodeValue.length)) + {\n endContainer = findParentContainer(dom3, formatList, endContainer, + endOffset, false);\n }\n }\n if (shouldExpandToSelector(format2)) + {\n startContainer = findSelectorEndPoint(dom3, formatList, rng, + startContainer, \"previousSibling\");\n endContainer = findSelectorEndPoint(dom3, + formatList, rng, endContainer, \"nextSibling\");\n }\n if (isBlockFormat(format2) + || isSelectorFormat(format2)) {\n startContainer = findBlockEndPoint(editor, + formatList, startContainer, \"previousSibling\");\n endContainer + = findBlockEndPoint(editor, formatList, endContainer, \"nextSibling\");\n + \ if (isBlockFormat(format2)) {\n if (!dom3.isBlock(startContainer)) + {\n startContainer = findParentContainer(dom3, formatList, startContainer, + startOffset, true);\n }\n if (!dom3.isBlock(endContainer)) + {\n endContainer = findParentContainer(dom3, formatList, endContainer, + endOffset, false);\n }\n }\n }\n if (isElement$6(startContainer)) + {\n startOffset = dom3.nodeIndex(startContainer);\n startContainer + = startContainer.parentNode;\n }\n if (isElement$6(endContainer)) + {\n endOffset = dom3.nodeIndex(endContainer) + 1;\n endContainer + = endContainer.parentNode;\n }\n return {\n startContainer,\n + \ startOffset,\n endContainer,\n endOffset\n };\n + \ };\n const walk$3 = (dom3, rng, callback) => {\n const startOffset + = rng.startOffset;\n const startContainer = getNode$1(rng.startContainer, + startOffset);\n const endOffset = rng.endOffset;\n const endContainer + = getNode$1(rng.endContainer, endOffset - 1);\n const exclude = (nodes) + => {\n const firstNode = nodes[0];\n if (isText$8(firstNode) + && firstNode === startContainer && startOffset >= firstNode.data.length) {\n + \ nodes.splice(0, 1);\n }\n const lastNode = nodes[nodes.length + - 1];\n if (endOffset === 0 && nodes.length > 0 && lastNode === endContainer + && isText$8(lastNode)) {\n nodes.splice(nodes.length - 1, 1);\n + \ }\n return nodes;\n };\n const collectSiblings + = (node, name2, endNode) => {\n const siblings3 = [];\n for + (; node && node !== endNode; node = node[name2]) {\n siblings3.push(node);\n + \ }\n return siblings3;\n };\n const findEndPoint + = (node, root2) => dom3.getParent(node, (node2) => node2.parentNode === root2, + root2);\n const walkBoundary = (startNode, endNode, next2) => {\n const + siblingName = next2 ? \"nextSibling\" : \"previousSibling\";\n for + (let node = startNode, parent2 = node.parentNode; node && node !== endNode; + node = parent2) {\n parent2 = node.parentNode;\n const + siblings3 = collectSiblings(node === startNode ? node : node[siblingName], + siblingName);\n if (siblings3.length) {\n if (!next2) + {\n siblings3.reverse();\n }\n callback(exclude(siblings3));\n + \ }\n }\n };\n if (startContainer === endContainer) + {\n return callback(exclude([startContainer]));\n }\n const + ancestor2 = dom3.findCommonAncestor(startContainer, endContainer);\n if + (dom3.isChildOf(startContainer, endContainer)) {\n return walkBoundary(startContainer, + ancestor2, true);\n }\n if (dom3.isChildOf(endContainer, startContainer)) + {\n return walkBoundary(endContainer, ancestor2);\n }\n const + startPoint = findEndPoint(startContainer, ancestor2) || startContainer;\n + \ const endPoint = findEndPoint(endContainer, ancestor2) || endContainer;\n + \ walkBoundary(startContainer, startPoint, true);\n const siblings2 + = collectSiblings(startPoint === startContainer ? startPoint : startPoint.nextSibling, + \"nextSibling\", endPoint === endContainer ? endPoint.nextSibling : endPoint);\n + \ if (siblings2.length) {\n callback(exclude(siblings2));\n + \ }\n walkBoundary(endContainer, endPoint);\n };\n const + getRanges$1 = (selection) => {\n const ranges = [];\n if (selection) + {\n for (let i3 = 0; i3 < selection.rangeCount; i3++) {\n ranges.push(selection.getRangeAt(i3));\n + \ }\n }\n return ranges;\n };\n const getSelectedNodes + = (ranges) => {\n return bind$3(ranges, (range2) => {\n const + node = getSelectedNode(range2);\n return node ? [SugarElement.fromDom(node)] + : [];\n });\n };\n const hasMultipleRanges = (selection) + => {\n return getRanges$1(selection).length > 1;\n };\n const + getCellsFromRanges = (ranges) => filter$6(getSelectedNodes(ranges), isTableCell$4);\n + \ const getCellsFromElement = (elm) => descendants(elm, \"td[data-mce-selected],th[data-mce-selected]\");\n + \ const getCellsFromElementOrRanges = (ranges, element) => {\n const + selectedCells = getCellsFromElement(element);\n return selectedCells.length + > 0 ? selectedCells : getCellsFromRanges(ranges);\n };\n const getCellsFromEditor + = (editor) => getCellsFromElementOrRanges(getRanges$1(editor.selection.getSel()), + SugarElement.fromDom(editor.getBody()));\n const getClosestTable = (cell2, + isRoot) => ancestor$2(cell2, \"table\", isRoot);\n const getStartNode + = (rng) => {\n const sc = rng.startContainer, so = rng.startOffset;\n + \ if (isText$8(sc)) {\n return so === 0 ? Optional.some(SugarElement.fromDom(sc)) + : Optional.none();\n } else {\n return Optional.from(sc.childNodes[so]).map(SugarElement.fromDom);\n + \ }\n };\n const getEndNode = (rng) => {\n const ec + = rng.endContainer, eo = rng.endOffset;\n if (isText$8(ec)) {\n return + eo === ec.data.length ? Optional.some(SugarElement.fromDom(ec)) : Optional.none();\n + \ } else {\n return Optional.from(ec.childNodes[eo - 1]).map(SugarElement.fromDom);\n + \ }\n };\n const getFirstChildren = (node) => {\n return + firstChild(node).fold(constant2([node]), (child2) => {\n return [node].concat(getFirstChildren(child2));\n + \ });\n };\n const getLastChildren$1 = (node) => {\n return + lastChild(node).fold(constant2([node]), (child2) => {\n if (name(child2) + === \"br\") {\n return prevSibling(child2).map((sibling2) => {\n + \ return [node].concat(getLastChildren$1(sibling2));\n }).getOr([]);\n + \ } else {\n return [node].concat(getLastChildren$1(child2));\n + \ }\n });\n };\n const hasAllContentsSelected = (elm, + rng) => {\n return lift2(getStartNode(rng), getEndNode(rng), (startNode, + endNode) => {\n const start2 = find$2(getFirstChildren(elm), curry(eq2, + startNode));\n const end2 = find$2(getLastChildren$1(elm), curry(eq2, + endNode));\n return start2.isSome() && end2.isSome();\n }).getOr(false);\n + \ };\n const moveEndPoint = (dom3, rng, node, start2) => {\n const + root2 = node, walker = new DomTreeWalker(node, root2);\n const moveCaretBeforeOnEnterElementsMap + = filter$5(dom3.schema.getMoveCaretBeforeOnEnterElements(), (_2, name2) => + !contains$2([\n \"td\",\n \"th\",\n \"table\"\n + \ ], name2.toLowerCase()));\n do {\n if (isText$8(node) + && Tools.trim(node.nodeValue).length !== 0) {\n if (start2) {\n + \ rng.setStart(node, 0);\n } else {\n rng.setEnd(node, + node.nodeValue.length);\n }\n return;\n }\n + \ if (moveCaretBeforeOnEnterElementsMap[node.nodeName]) {\n if + (start2) {\n rng.setStartBefore(node);\n } else {\n + \ if (node.nodeName === \"BR\") {\n rng.setEndBefore(node);\n + \ } else {\n rng.setEndAfter(node);\n }\n + \ }\n return;\n }\n } while (node = start2 + ? walker.next() : walker.prev());\n if (root2.nodeName === \"BODY\") + {\n if (start2) {\n rng.setStart(root2, 0);\n } + else {\n rng.setEnd(root2, root2.childNodes.length);\n }\n + \ }\n };\n const hasAnyRanges = (editor) => {\n const + sel = editor.selection.getSel();\n return sel && sel.rangeCount > 0;\n + \ };\n const runOnRanges = (editor, executor) => {\n const + fakeSelectionNodes = getCellsFromEditor(editor);\n if (fakeSelectionNodes.length + > 0) {\n each$g(fakeSelectionNodes, (elem) => {\n const + node = elem.dom;\n const fakeNodeRng = editor.dom.createRng();\n + \ fakeNodeRng.setStartBefore(node);\n fakeNodeRng.setEndAfter(node);\n + \ executor(fakeNodeRng, true);\n });\n } else {\n + \ executor(editor.selection.getRng(), false);\n }\n };\n + \ const preserve = (selection, fillBookmark, executor) => {\n const + bookmark = getPersistentBookmark(selection, fillBookmark);\n executor(bookmark);\n + \ selection.moveToBookmark(bookmark);\n };\n const NodeValue + = (is2, name2) => {\n const get2 = (element) => {\n if (!is2(element)) + {\n throw new Error(\"Can only get \" + name2 + \" value of a \" + + name2 + \" node\");\n }\n return getOption2(element).getOr(\"\");\n + \ };\n const getOption2 = (element) => is2(element) ? Optional.from(element.dom.nodeValue) + : Optional.none();\n const set3 = (element, value2) => {\n if + (!is2(element)) {\n throw new Error(\"Can only set raw \" + name2 + + \" value of a \" + name2 + \" node\");\n }\n element.dom.nodeValue + = value2;\n };\n return {\n get: get2,\n getOption: + getOption2,\n set: set3\n };\n };\n const api$1 + = NodeValue(isText$9, \"text\");\n const get$3 = (element) => api$1.get(element);\n + \ const getOption = (element) => api$1.getOption(element);\n const + isZeroWidth = (elem) => isText$9(elem) && get$3(elem) === ZWSP$1;\n const + context = (editor, elem, wrapName, nodeName) => parent(elem).fold(() => \"skipping\", + (parent2) => {\n if (nodeName === \"br\" || isZeroWidth(elem)) {\n + \ return \"valid\";\n } else if (isAnnotation(elem)) {\n return + \"existing\";\n } else if (isCaretNode(elem.dom)) {\n return + \"caret\";\n } else if (!isValid(editor, wrapName, nodeName) || !isValid(editor, + name(parent2), wrapName)) {\n return \"invalid-child\";\n } + else {\n return \"valid\";\n }\n });\n const applyWordGrab + = (editor, rng) => {\n const r4 = expandRng(editor, rng, [{ inline: + \"span\" }]);\n rng.setStart(r4.startContainer, r4.startOffset);\n + \ rng.setEnd(r4.endContainer, r4.endOffset);\n editor.selection.setRng(rng);\n + \ };\n const makeAnnotation = (eDoc, { uid = generate$1(\"mce-annotation\"), + ...data2 }, annotationName, decorate) => {\n const master = SugarElement.fromTag(\"span\", + eDoc);\n add$2(master, annotation());\n set$2(master, `${dataAnnotationId()}`, + uid);\n set$2(master, `${dataAnnotation()}`, annotationName);\n const + { attributes = {}, classes = [] } = decorate(uid, data2);\n setAll$1(master, + attributes);\n add2(master, classes);\n return master;\n };\n + \ const annotate = (editor, rng, annotationName, decorate, data2) => {\n + \ const newWrappers = [];\n const master = makeAnnotation(editor.getDoc(), + data2, annotationName, decorate);\n const wrapper = value$2();\n const + finishWrapper = () => {\n wrapper.clear();\n };\n const + getOrOpenWrapper = () => wrapper.get().getOrThunk(() => {\n const + nu2 = shallow$1(master);\n newWrappers.push(nu2);\n wrapper.set(nu2);\n + \ return nu2;\n });\n const processElements = (elems) + => {\n each$g(elems, processElement);\n };\n const + processElement = (elem) => {\n const ctx = context(editor, elem, + \"span\", name(elem));\n switch (ctx) {\n case \"invalid-child\": + {\n finishWrapper();\n const children$1 = children(elem);\n + \ processElements(children$1);\n finishWrapper();\n + \ break;\n }\n case \"valid\": {\n const + w = getOrOpenWrapper();\n wrap$2(elem, w);\n break;\n + \ }\n }\n };\n const processNodes = (nodes) + => {\n const elems = map$3(nodes, SugarElement.fromDom);\n processElements(elems);\n + \ };\n walk$3(editor.dom, rng, (nodes) => {\n finishWrapper();\n + \ processNodes(nodes);\n });\n return newWrappers;\n + \ };\n const annotateWithBookmark = (editor, name2, settings, data2) + => {\n editor.undoManager.transact(() => {\n const selection + = editor.selection;\n const initialRng = selection.getRng();\n const + hasFakeSelection = getCellsFromEditor(editor).length > 0;\n if (initialRng.collapsed + && !hasFakeSelection) {\n applyWordGrab(editor, initialRng);\n + \ }\n if (selection.getRng().collapsed && !hasFakeSelection) + {\n const wrapper = makeAnnotation(editor.getDoc(), data2, name2, + settings.decorate);\n set2(wrapper, nbsp);\n selection.getRng().insertNode(wrapper.dom);\n + \ selection.select(wrapper.dom);\n } else {\n preserve(selection, + false, () => {\n runOnRanges(editor, (selectionRng) => {\n annotate(editor, + selectionRng, name2, settings.decorate, data2);\n });\n });\n + \ }\n });\n };\n const Annotator = (editor) => {\n + \ const registry2 = create$b();\n setup$w(editor, registry2);\n + \ const changes = setup$x(editor, registry2);\n return {\n register: + (name2, settings) => {\n registry2.register(name2, settings);\n + \ },\n annotate: (name2, data2) => {\n registry2.lookup(name2).each((settings) + => {\n annotateWithBookmark(editor, name2, settings, data2);\n + \ });\n },\n annotationChanged: (name2, callback) + => {\n changes.addListener(name2, callback);\n },\n remove: + (name2) => {\n const bookmark = editor.selection.getBookmark();\n + \ identify(editor, Optional.some(name2)).each(({ elements }) => + {\n each$g(elements, unwrap);\n });\n editor.selection.moveToBookmark(bookmark);\n + \ },\n removeAll: (name2) => {\n const bookmark + = editor.selection.getBookmark();\n each$f(findAll(editor, name2), + (spans, _2) => each$g(spans, unwrap));\n editor.selection.moveToBookmark(bookmark);\n + \ },\n getAll: (name2) => {\n const directory + = findAll(editor, name2);\n return map$2(directory, (elems) => + map$3(elems, (elem) => elem.dom));\n }\n };\n };\n const + BookmarkManager = (selection) => {\n return {\n getBookmark: + curry(getBookmark$1, selection),\n moveToBookmark: curry(moveToBookmark, + selection)\n };\n };\n BookmarkManager.isBookmarkNode = isBookmarkNode$1;\n + \ const isXYWithinRange = (clientX, clientY, range2) => {\n if + (range2.collapsed) {\n return false;\n } else {\n return + exists(range2.getClientRects(), (rect) => containsXY(rect, clientX, clientY));\n + \ }\n };\n const firePreProcess = (editor, args) => editor.dispatch(\"PreProcess\", + args);\n const firePostProcess = (editor, args) => editor.dispatch(\"PostProcess\", + args);\n const fireRemove = (editor) => editor.dispatch(\"remove\");\n + \ const fireDetach = (editor) => editor.dispatch(\"detach\");\n const + fireSwitchMode = (editor, mode) => editor.dispatch(\"SwitchMode\", { mode + });\n const fireObjectResizeStart = (editor, target, width, height, origin) + => {\n editor.dispatch(\"ObjectResizeStart\", {\n target,\n + \ width,\n height,\n origin\n });\n };\n + \ const fireObjectResized = (editor, target, width, height, origin) => + {\n editor.dispatch(\"ObjectResized\", {\n target,\n width,\n + \ height,\n origin\n });\n };\n const firePreInit + = (editor) => editor.dispatch(\"PreInit\");\n const firePostRender = + (editor) => editor.dispatch(\"PostRender\");\n const fireInit = (editor) + => editor.dispatch(\"Init\");\n const firePlaceholderToggle = (editor, + state) => editor.dispatch(\"PlaceholderToggle\", { state });\n const + fireError = (editor, errorType, error3) => editor.dispatch(errorType, error3);\n + \ const fireFormatApply = (editor, format2, node, vars) => editor.dispatch(\"FormatApply\", + {\n format: format2,\n node,\n vars\n });\n const + fireFormatRemove = (editor, format2, node, vars) => editor.dispatch(\"FormatRemove\", + {\n format: format2,\n node,\n vars\n });\n const + fireBeforeSetContent = (editor, args) => editor.dispatch(\"BeforeSetContent\", + args);\n const fireSetContent = (editor, args) => editor.dispatch(\"SetContent\", + args);\n const fireBeforeGetContent = (editor, args) => editor.dispatch(\"BeforeGetContent\", + args);\n const fireGetContent = (editor, args) => editor.dispatch(\"GetContent\", + args);\n const fireAutocompleterStart = (editor, args) => editor.dispatch(\"AutocompleterStart\", + args);\n const fireAutocompleterUpdate = (editor, args) => editor.dispatch(\"AutocompleterUpdate\", + args);\n const fireAutocompleterEnd = (editor) => editor.dispatch(\"AutocompleterEnd\");\n + \ const firePastePreProcess = (editor, html2, internal) => editor.dispatch(\"PastePreProcess\", + {\n content: html2,\n internal\n });\n const firePastePostProcess + = (editor, node, internal) => editor.dispatch(\"PastePostProcess\", {\n node,\n + \ internal\n });\n const firePastePlainTextToggle = (editor, + state) => editor.dispatch(\"PastePlainTextToggle\", { state });\n const + VK = {\n BACKSPACE: 8,\n DELETE: 46,\n DOWN: 40,\n ENTER: + 13,\n ESC: 27,\n LEFT: 37,\n RIGHT: 39,\n SPACEBAR: + 32,\n TAB: 9,\n UP: 38,\n PAGE_UP: 33,\n PAGE_DOWN: + 34,\n END: 35,\n HOME: 36,\n modifierPressed: (e2) => + {\n return e2.shiftKey || e2.ctrlKey || e2.altKey || VK.metaKeyPressed(e2);\n + \ },\n metaKeyPressed: (e2) => {\n return Env.os.isMacOS() + || Env.os.isiOS() ? e2.metaKey : e2.ctrlKey && !e2.altKey;\n }\n };\n + \ const ControlSelection = (selection, editor) => {\n const elementSelectionAttr + = \"data-mce-selected\";\n const dom3 = editor.dom, each2 = Tools.each;\n + \ let selectedElm, selectedElmGhost, resizeHelper, selectedHandle, resizeBackdrop;\n + \ let startX, startY, selectedElmX, selectedElmY, startW, startH, ratio, + resizeStarted;\n let width, height;\n const editableDoc = editor.getDoc(), + rootDocument = document;\n const abs = Math.abs, round2 = Math.round, + rootElement = editor.getBody();\n let startScrollWidth, startScrollHeight;\n + \ const resizeHandles = {\n nw: [\n 0,\n 0,\n + \ -1,\n -1\n ],\n ne: [\n 1,\n + \ 0,\n 1,\n -1\n ],\n se: + [\n 1,\n 1,\n 1,\n 1\n ],\n + \ sw: [\n 0,\n 1,\n -1,\n 1\n + \ ]\n };\n const isImage2 = (elm) => isNonNullable(elm) + && (isImg(elm) || editor.dom.is(elm, \"figure.image\"));\n const isMedia2 + = (elm) => isMedia$2(elm) || dom3.hasClass(elm, \"mce-preview-object\");\n + \ const isEventOnImageOutsideRange = (evt, range2) => {\n if + (evt.type === \"longpress\" || evt.type.indexOf(\"touch\") === 0) {\n const + touch = evt.touches[0];\n return isImage2(evt.target) && !isXYWithinRange(touch.clientX, + touch.clientY, range2);\n } else {\n return isImage2(evt.target) + && !isXYWithinRange(evt.clientX, evt.clientY, range2);\n }\n };\n + \ const contextMenuSelectImage = (evt) => {\n const target + = evt.target;\n if (isEventOnImageOutsideRange(evt, editor.selection.getRng()) + && !evt.isDefaultPrevented()) {\n editor.selection.select(target);\n + \ }\n };\n const getResizeTargets = (elm) => {\n if + (dom3.is(elm, \"figure.image\")) {\n return [elm.querySelector(\"img\")];\n + \ } else if (dom3.hasClass(elm, \"mce-preview-object\") && isNonNullable(elm.firstElementChild)) + {\n return [\n elm,\n elm.firstElementChild\n + \ ];\n } else {\n return [elm];\n }\n + \ };\n const isResizable = (elm) => {\n const selector + = getObjectResizing(editor);\n if (!selector) {\n return + false;\n }\n if (elm.getAttribute(\"data-mce-resize\") === + \"false\") {\n return false;\n }\n if (elm === + editor.getBody()) {\n return false;\n }\n if + (dom3.hasClass(elm, \"mce-preview-object\")) {\n return is$1(SugarElement.fromDom(elm.firstElementChild), + selector);\n } else {\n return is$1(SugarElement.fromDom(elm), + selector);\n }\n };\n const createGhostElement = (elm) + => {\n if (isMedia2(elm)) {\n return dom3.create(\"img\", + { src: Env.transparentSrc });\n } else {\n return elm.cloneNode(true);\n + \ }\n };\n const setSizeProp = (element, name2, value2) + => {\n if (isNonNullable(value2)) {\n const targets = + getResizeTargets(element);\n each$g(targets, (target) => {\n if + (target.style[name2] || !editor.schema.isValid(target.nodeName.toLowerCase(), + name2)) {\n dom3.setStyle(target, name2, value2);\n } + else {\n dom3.setAttrib(target, name2, \"\" + value2);\n }\n + \ });\n }\n };\n const setGhostElmSize = + (ghostElm, width2, height2) => {\n setSizeProp(ghostElm, \"width\", + width2);\n setSizeProp(ghostElm, \"height\", height2);\n };\n + \ const resizeGhostElement = (e2) => {\n let deltaX, deltaY, + proportional;\n let resizeHelperX, resizeHelperY;\n deltaX + = e2.screenX - startX;\n deltaY = e2.screenY - startY;\n width + = deltaX * selectedHandle[2] + startW;\n height = deltaY * selectedHandle[3] + + startH;\n width = width < 5 ? 5 : width;\n height = height + < 5 ? 5 : height;\n if ((isImage2(selectedElm) || isMedia2(selectedElm)) + && getResizeImgProportional(editor) !== false) {\n proportional + = !VK.modifierPressed(e2);\n } else {\n proportional = + VK.modifierPressed(e2);\n }\n if (proportional) {\n if + (abs(deltaX) > abs(deltaY)) {\n height = round2(width * ratio);\n + \ width = round2(height / ratio);\n } else {\n width + = round2(height / ratio);\n height = round2(width * ratio);\n + \ }\n }\n setGhostElmSize(selectedElmGhost, width, + height);\n resizeHelperX = selectedHandle.startPos.x + deltaX;\n + \ resizeHelperY = selectedHandle.startPos.y + deltaY;\n resizeHelperX + = resizeHelperX > 0 ? resizeHelperX : 0;\n resizeHelperY = resizeHelperY + > 0 ? resizeHelperY : 0;\n dom3.setStyles(resizeHelper, {\n left: + resizeHelperX,\n top: resizeHelperY,\n display: \"block\"\n + \ });\n resizeHelper.innerHTML = width + \" × \" + + height;\n if (selectedHandle[2] < 0 && selectedElmGhost.clientWidth + <= width) {\n dom3.setStyle(selectedElmGhost, \"left\", selectedElmX + + (startW - width));\n }\n if (selectedHandle[3] < 0 && + selectedElmGhost.clientHeight <= height) {\n dom3.setStyle(selectedElmGhost, + \"top\", selectedElmY + (startH - height));\n }\n deltaX + = rootElement.scrollWidth - startScrollWidth;\n deltaY = rootElement.scrollHeight + - startScrollHeight;\n if (deltaX + deltaY !== 0) {\n dom3.setStyles(resizeHelper, + {\n left: resizeHelperX - deltaX,\n top: resizeHelperY + - deltaY\n });\n }\n if (!resizeStarted) {\n + \ fireObjectResizeStart(editor, selectedElm, startW, startH, \"corner-\" + + selectedHandle.name);\n resizeStarted = true;\n }\n + \ };\n const endGhostResize = () => {\n const wasResizeStarted + = resizeStarted;\n resizeStarted = false;\n if (wasResizeStarted) + {\n setSizeProp(selectedElm, \"width\", width);\n setSizeProp(selectedElm, + \"height\", height);\n }\n dom3.unbind(editableDoc, \"mousemove\", + resizeGhostElement);\n dom3.unbind(editableDoc, \"mouseup\", endGhostResize);\n + \ if (rootDocument !== editableDoc) {\n dom3.unbind(rootDocument, + \"mousemove\", resizeGhostElement);\n dom3.unbind(rootDocument, + \"mouseup\", endGhostResize);\n }\n dom3.remove(selectedElmGhost);\n + \ dom3.remove(resizeHelper);\n dom3.remove(resizeBackdrop);\n + \ showResizeRect(selectedElm);\n if (wasResizeStarted) {\n + \ fireObjectResized(editor, selectedElm, width, height, \"corner-\" + + selectedHandle.name);\n dom3.setAttrib(selectedElm, \"style\", + dom3.getAttrib(selectedElm, \"style\"));\n }\n editor.nodeChanged();\n + \ };\n const showResizeRect = (targetElm) => {\n unbindResizeHandleEvents();\n + \ const position = dom3.getPos(targetElm, rootElement);\n const + selectedElmX2 = position.x;\n const selectedElmY2 = position.y;\n + \ const rect = targetElm.getBoundingClientRect();\n const + targetWidth = rect.width || rect.right - rect.left;\n const targetHeight + = rect.height || rect.bottom - rect.top;\n if (selectedElm !== targetElm) + {\n hideResizeRect();\n selectedElm = targetElm;\n width + = height = 0;\n }\n const e2 = editor.dispatch(\"ObjectSelected\", + { target: targetElm });\n const selectedValue = dom3.getAttrib(selectedElm, + elementSelectionAttr, \"1\");\n if (isResizable(targetElm) && !e2.isDefaultPrevented()) + {\n each2(resizeHandles, (handle2, name2) => {\n let + handleElm;\n const startDrag = (e3) => {\n const + target = getResizeTargets(selectedElm)[0];\n startX = e3.screenX;\n + \ startY = e3.screenY;\n startW = target.clientWidth;\n + \ startH = target.clientHeight;\n ratio = startH + / startW;\n selectedHandle = handle2;\n selectedHandle.name + = name2;\n selectedHandle.startPos = {\n x: + targetWidth * handle2[0] + selectedElmX2,\n y: targetHeight + * handle2[1] + selectedElmY2\n };\n startScrollWidth + = rootElement.scrollWidth;\n startScrollHeight = rootElement.scrollHeight;\n + \ resizeBackdrop = dom3.add(rootElement, \"div\", {\n \"class\": + \"mce-resize-backdrop\",\n \"data-mce-bogus\": \"all\"\n + \ });\n dom3.setStyles(resizeBackdrop, {\n position: + \"fixed\",\n left: \"0\",\n top: \"0\",\n + \ width: \"100%\",\n height: \"100%\"\n });\n + \ selectedElmGhost = createGhostElement(selectedElm);\n dom3.addClass(selectedElmGhost, + \"mce-clonedresizable\");\n dom3.setAttrib(selectedElmGhost, + \"data-mce-bogus\", \"all\");\n selectedElmGhost.contentEditable + = \"false\";\n dom3.setStyles(selectedElmGhost, {\n left: + selectedElmX2,\n top: selectedElmY2,\n margin: + 0\n });\n setGhostElmSize(selectedElmGhost, + targetWidth, targetHeight);\n selectedElmGhost.removeAttribute(elementSelectionAttr);\n + \ rootElement.appendChild(selectedElmGhost);\n dom3.bind(editableDoc, + \"mousemove\", resizeGhostElement);\n dom3.bind(editableDoc, + \"mouseup\", endGhostResize);\n if (rootDocument !== editableDoc) + {\n dom3.bind(rootDocument, \"mousemove\", resizeGhostElement);\n + \ dom3.bind(rootDocument, \"mouseup\", endGhostResize);\n + \ }\n resizeHelper = dom3.add(rootElement, \"div\", + {\n \"class\": \"mce-resize-helper\",\n \"data-mce-bogus\": + \"all\"\n }, startW + \" × \" + startH);\n };\n + \ handleElm = dom3.get(\"mceResizeHandle\" + name2);\n if + (handleElm) {\n dom3.remove(handleElm);\n }\n + \ handleElm = dom3.add(rootElement, \"div\", {\n \"id\": + \"mceResizeHandle\" + name2,\n \"data-mce-bogus\": \"all\",\n + \ \"class\": \"mce-resizehandle\",\n \"unselectable\": + true,\n \"style\": \"cursor:\" + name2 + \"-resize; margin:0; + padding:0\"\n });\n dom3.bind(handleElm, \"mousedown\", + (e3) => {\n e3.stopImmediatePropagation();\n e3.preventDefault();\n + \ startDrag(e3);\n });\n handle2.elm + = handleElm;\n dom3.setStyles(handleElm, {\n left: + targetWidth * handle2[0] + selectedElmX2 - handleElm.offsetWidth / 2,\n top: + targetHeight * handle2[1] + selectedElmY2 - handleElm.offsetHeight / 2\n });\n + \ });\n } else {\n hideResizeRect();\n }\n + \ if (!dom3.getAttrib(selectedElm, elementSelectionAttr)) {\n selectedElm.setAttribute(elementSelectionAttr, + selectedValue);\n }\n };\n const hideResizeRect = () + => {\n unbindResizeHandleEvents();\n if (selectedElm) {\n + \ selectedElm.removeAttribute(elementSelectionAttr);\n }\n + \ each$f(resizeHandles, (value2, name2) => {\n const handleElm + = dom3.get(\"mceResizeHandle\" + name2);\n if (handleElm) {\n dom3.unbind(handleElm);\n + \ dom3.remove(handleElm);\n }\n });\n };\n + \ const updateResizeRect = (e2) => {\n var _a3;\n let + startElm, controlElm;\n const isChildOrEqual = (node, parent2) => + {\n if (node) {\n do {\n if (node === + parent2) {\n return true;\n }\n } + while (node = node.parentNode);\n }\n };\n if + (resizeStarted || editor.removed) {\n return;\n }\n each2(dom3.select(\"img[data-mce-selected],hr[data-mce-selected]\"), + (img) => {\n img.removeAttribute(elementSelectionAttr);\n });\n + \ controlElm = e2.type === \"mousedown\" ? e2.target : selection.getNode();\n + \ controlElm = (_a3 = closest$3(SugarElement.fromDom(controlElm), + \"table,img,figure.image,hr,video,span.mce-preview-object\").getOrUndefined()) + === null || _a3 === void 0 ? void 0 : _a3.dom;\n if (isChildOrEqual(controlElm, + rootElement)) {\n disableGeckoResize();\n startElm = + selection.getStart(true);\n if (isChildOrEqual(startElm, controlElm) + && isChildOrEqual(selection.getEnd(true), controlElm)) {\n showResizeRect(controlElm);\n + \ return;\n }\n }\n hideResizeRect();\n + \ };\n const unbindResizeHandleEvents = () => {\n each$f(resizeHandles, + (handle2) => {\n if (handle2.elm) {\n dom3.unbind(handle2.elm);\n + \ delete handle2.elm;\n }\n });\n };\n + \ const disableGeckoResize = () => {\n try {\n editor.getDoc().execCommand(\"enableObjectResizing\", + false, \"false\");\n } catch (ex) {\n }\n };\n editor.on(\"init\", + () => {\n disableGeckoResize();\n const throttledUpdateResizeRect + = first$1((e2) => {\n if (!editor.composing) {\n updateResizeRect(e2);\n + \ }\n }, 0);\n editor.on(\"nodechange ResizeEditor + ResizeWindow ResizeContent drop FullscreenStateChanged\", throttledUpdateResizeRect.throttle);\n + \ editor.on(\"keyup compositionend\", (e2) => {\n if (selectedElm + && selectedElm.nodeName === \"TABLE\") {\n throttledUpdateResizeRect.throttle(e2);\n + \ }\n });\n editor.on(\"hide blur\", hideResizeRect);\n + \ editor.on(\"contextmenu longpress\", contextMenuSelectImage, true);\n + \ });\n editor.on(\"remove\", unbindResizeHandleEvents);\n const + destroy2 = () => {\n selectedElm = selectedElmGhost = resizeBackdrop + = null;\n };\n return {\n isResizable,\n showResizeRect,\n + \ hideResizeRect,\n updateResizeRect,\n destroy: + destroy2\n };\n };\n const setStart = (rng, situ) => {\n + \ situ.fold((e2) => {\n rng.setStartBefore(e2.dom);\n }, + (e2, o2) => {\n rng.setStart(e2.dom, o2);\n }, (e2) => {\n + \ rng.setStartAfter(e2.dom);\n });\n };\n const setFinish + = (rng, situ) => {\n situ.fold((e2) => {\n rng.setEndBefore(e2.dom);\n + \ }, (e2, o2) => {\n rng.setEnd(e2.dom, o2);\n }, (e2) + => {\n rng.setEndAfter(e2.dom);\n });\n };\n const + relativeToNative = (win, startSitu, finishSitu) => {\n const range2 + = win.document.createRange();\n setStart(range2, startSitu);\n setFinish(range2, + finishSitu);\n return range2;\n };\n const exactToNative + = (win, start2, soffset, finish, foffset) => {\n const rng = win.document.createRange();\n + \ rng.setStart(start2.dom, soffset);\n rng.setEnd(finish.dom, + foffset);\n return rng;\n };\n const adt$3 = Adt.generate([\n + \ {\n ltr: [\n \"start\",\n \"soffset\",\n + \ \"finish\",\n \"foffset\"\n ]\n },\n + \ {\n rtl: [\n \"start\",\n \"soffset\",\n + \ \"finish\",\n \"foffset\"\n ]\n }\n + \ ]);\n const fromRange = (win, type2, range2) => type2(SugarElement.fromDom(range2.startContainer), + range2.startOffset, SugarElement.fromDom(range2.endContainer), range2.endOffset);\n + \ const getRanges = (win, selection) => selection.match({\n domRange: + (rng) => {\n return {\n ltr: constant2(rng),\n rtl: + Optional.none\n };\n },\n relative: (startSitu, finishSitu) + => {\n return {\n ltr: cached(() => relativeToNative(win, + startSitu, finishSitu)),\n rtl: cached(() => Optional.some(relativeToNative(win, + finishSitu, startSitu)))\n };\n },\n exact: (start2, + soffset, finish, foffset) => {\n return {\n ltr: cached(() + => exactToNative(win, start2, soffset, finish, foffset)),\n rtl: + cached(() => Optional.some(exactToNative(win, finish, foffset, start2, soffset)))\n + \ };\n }\n });\n const doDiagnose = (win, ranges) + => {\n const rng = ranges.ltr();\n if (rng.collapsed) {\n const + reversed = ranges.rtl().filter((rev) => rev.collapsed === false);\n return + reversed.map((rev) => adt$3.rtl(SugarElement.fromDom(rev.endContainer), rev.endOffset, + SugarElement.fromDom(rev.startContainer), rev.startOffset)).getOrThunk(() + => fromRange(win, adt$3.ltr, rng));\n } else {\n return fromRange(win, + adt$3.ltr, rng);\n }\n };\n const diagnose = (win, selection) + => {\n const ranges = getRanges(win, selection);\n return doDiagnose(win, + ranges);\n };\n adt$3.ltr;\n adt$3.rtl;\n const create$9 + = (start2, soffset, finish, foffset) => ({\n start: start2,\n soffset,\n + \ finish,\n foffset\n });\n const SimRange = { create: + create$9 };\n const caretPositionFromPoint = (doc, x2, y2) => {\n var + _a3, _b;\n return Optional.from((_b = (_a3 = doc.dom).caretPositionFromPoint) + === null || _b === void 0 ? void 0 : _b.call(_a3, x2, y2)).bind((pos) => {\n + \ if (pos.offsetNode === null) {\n return Optional.none();\n + \ }\n const r4 = doc.dom.createRange();\n r4.setStart(pos.offsetNode, + pos.offset);\n r4.collapse();\n return Optional.some(r4);\n + \ });\n };\n const caretRangeFromPoint = (doc, x2, y2) => + {\n var _a3, _b;\n return Optional.from((_b = (_a3 = doc.dom).caretRangeFromPoint) + === null || _b === void 0 ? void 0 : _b.call(_a3, x2, y2));\n };\n const + availableSearch = (() => {\n if (document.caretPositionFromPoint) {\n + \ return caretPositionFromPoint;\n } else if (document.caretRangeFromPoint) + {\n return caretRangeFromPoint;\n } else {\n return + Optional.none;\n }\n })();\n const fromPoint$1 = (win, x2, + y2) => {\n const doc = SugarElement.fromDom(win.document);\n return + availableSearch(doc, x2, y2).map((rng) => SimRange.create(SugarElement.fromDom(rng.startContainer), + rng.startOffset, SugarElement.fromDom(rng.endContainer), rng.endOffset));\n + \ };\n const adt$2 = Adt.generate([\n { before: [\"element\"] + },\n {\n on: [\n \"element\",\n \"offset\"\n + \ ]\n },\n { after: [\"element\"] }\n ]);\n const + cata = (subject, onBefore, onOn, onAfter) => subject.fold(onBefore, onOn, + onAfter);\n const getStart$2 = (situ) => situ.fold(identity2, identity2, + identity2);\n const before$1 = adt$2.before;\n const on = adt$2.on;\n + \ const after$1 = adt$2.after;\n const Situ = {\n before: + before$1,\n on,\n after: after$1,\n cata,\n getStart: + getStart$2\n };\n const adt$1 = Adt.generate([\n { domRange: + [\"rng\"] },\n {\n relative: [\n \"startSitu\",\n + \ \"finishSitu\"\n ]\n },\n {\n exact: + [\n \"start\",\n \"soffset\",\n \"finish\",\n + \ \"foffset\"\n ]\n }\n ]);\n const exactFromRange + = (simRange) => adt$1.exact(simRange.start, simRange.soffset, simRange.finish, + simRange.foffset);\n const getStart$1 = (selection) => selection.match({\n + \ domRange: (rng) => SugarElement.fromDom(rng.startContainer),\n relative: + (startSitu, _finishSitu) => Situ.getStart(startSitu),\n exact: (start2, + _soffset, _finish, _foffset) => start2\n });\n const domRange = + adt$1.domRange;\n const relative = adt$1.relative;\n const exact + = adt$1.exact;\n const getWin = (selection) => {\n const start2 + = getStart$1(selection);\n return defaultView(start2);\n };\n + \ const range = SimRange.create;\n const SimSelection = {\n domRange,\n + \ relative,\n exact,\n exactFromRange,\n getWin,\n + \ range\n };\n const beforeSpecial = (element, offset) => + {\n const name$1 = name(element);\n if (\"input\" === name$1) + {\n return Situ.after(element);\n } else if (!contains$2([\n + \ \"br\",\n \"img\"\n ], name$1)) {\n return + Situ.on(element, offset);\n } else {\n return offset === 0 + ? Situ.before(element) : Situ.after(element);\n }\n };\n const + preprocessRelative = (startSitu, finishSitu) => {\n const start2 = + startSitu.fold(Situ.before, beforeSpecial, Situ.after);\n const finish + = finishSitu.fold(Situ.before, beforeSpecial, Situ.after);\n return + SimSelection.relative(start2, finish);\n };\n const preprocessExact + = (start2, soffset, finish, foffset) => {\n const startSitu = beforeSpecial(start2, + soffset);\n const finishSitu = beforeSpecial(finish, foffset);\n return + SimSelection.relative(startSitu, finishSitu);\n };\n const preprocess + = (selection) => selection.match({\n domRange: (rng) => {\n const + start2 = SugarElement.fromDom(rng.startContainer);\n const finish + = SugarElement.fromDom(rng.endContainer);\n return preprocessExact(start2, + rng.startOffset, finish, rng.endOffset);\n },\n relative: preprocessRelative,\n + \ exact: preprocessExact\n });\n const fromElements = (elements, + scope) => {\n const doc = document;\n const fragment = doc.createDocumentFragment();\n + \ each$g(elements, (element) => {\n fragment.appendChild(element.dom);\n + \ });\n return SugarElement.fromDom(fragment);\n };\n const + toNative = (selection) => {\n const win = SimSelection.getWin(selection).dom;\n + \ const getDomRange = (start2, soffset, finish, foffset) => exactToNative(win, + start2, soffset, finish, foffset);\n const filtered = preprocess(selection);\n + \ return diagnose(win, filtered).match({\n ltr: getDomRange,\n + \ rtl: getDomRange\n });\n };\n const getAtPoint + = (win, x2, y2) => fromPoint$1(win, x2, y2);\n const fromPoint = (clientX, + clientY, doc) => getAtPoint(doc.defaultView, clientX, clientY).map((simRange) + => {\n const rng = doc.createRange();\n rng.setStart(simRange.start.dom, + simRange.soffset);\n rng.setEnd(simRange.finish.dom, simRange.foffset);\n + \ return rng;\n }).getOrUndefined();\n const isEq$4 = (rng1, + rng2) => {\n return rng1 && rng2 && (rng1.startContainer === rng2.startContainer + && rng1.startOffset === rng2.startOffset) && (rng1.endContainer === rng2.endContainer + && rng1.endOffset === rng2.endOffset);\n };\n const findParent = + (node, rootNode, predicate) => {\n while (node && node !== rootNode) + {\n if (predicate(node)) {\n return node;\n }\n + \ node = node.parentNode;\n }\n return null;\n };\n + \ const hasParent$1 = (node, rootNode, predicate) => findParent(node, + rootNode, predicate) !== null;\n const hasParentWithName = (node, rootNode, + name2) => hasParent$1(node, rootNode, (node2) => {\n return node2.nodeName + === name2;\n });\n const isTable = (node) => node && node.nodeName + === \"TABLE\";\n const isTableCell$2 = (node) => node && /^(TD|TH|CAPTION)$/.test(node.nodeName);\n + \ const isCeFalseCaretContainer = (node, rootNode) => isCaretContainer$2(node) + && hasParent$1(node, rootNode, isCaretNode) === false;\n const hasBrBeforeAfter + = (dom3, node, left) => {\n const walker = new DomTreeWalker(node, + dom3.getParent(node.parentNode, dom3.isBlock) || dom3.getRoot());\n while + (node = walker[left ? \"prev\" : \"next\"]()) {\n if (isBr$5(node)) + {\n return true;\n }\n }\n };\n const + isPrevNode = (node, name2) => node.previousSibling && node.previousSibling.nodeName + === name2;\n const hasContentEditableFalseParent = (body, node) => {\n + \ while (node && node !== body) {\n if (isContentEditableFalse$a(node)) + {\n return true;\n }\n node = node.parentNode;\n + \ }\n return false;\n };\n const findTextNodeRelative + = (dom3, isAfterNode, collapsed, left, startNode) => {\n let lastInlineElement;\n + \ const body = dom3.getRoot();\n let node;\n const nonEmptyElementsMap + = dom3.schema.getNonEmptyElements();\n const parentBlockContainer = + dom3.getParent(startNode.parentNode, dom3.isBlock) || body;\n if (left + && isBr$5(startNode) && isAfterNode && dom3.isEmpty(parentBlockContainer)) + {\n return Optional.some(CaretPosition(startNode.parentNode, dom3.nodeIndex(startNode)));\n + \ }\n const walker = new DomTreeWalker(startNode, parentBlockContainer);\n + \ while (node = walker[left ? \"prev\" : \"next\"]()) {\n if + (dom3.getContentEditableParent(node) === \"false\" || isCeFalseCaretContainer(node, + body)) {\n return Optional.none();\n }\n if (isText$8(node) + && node.nodeValue.length > 0) {\n if (hasParentWithName(node, body, + \"A\") === false) {\n return Optional.some(CaretPosition(node, + left ? node.nodeValue.length : 0));\n }\n return Optional.none();\n + \ }\n if (dom3.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) + {\n return Optional.none();\n }\n lastInlineElement + = node;\n }\n if (collapsed && lastInlineElement) {\n return + Optional.some(CaretPosition(lastInlineElement, 0));\n }\n return + Optional.none();\n };\n const normalizeEndPoint = (dom3, collapsed, + start2, rng) => {\n let container, offset;\n const body = dom3.getRoot();\n + \ let node;\n let directionLeft, normalized = false;\n container + = rng[(start2 ? \"start\" : \"end\") + \"Container\"];\n offset = rng[(start2 + ? \"start\" : \"end\") + \"Offset\"];\n const isAfterNode = isElement$6(container) + && offset === container.childNodes.length;\n const nonEmptyElementsMap + = dom3.schema.getNonEmptyElements();\n directionLeft = start2;\n if + (isCaretContainer$2(container)) {\n return Optional.none();\n }\n + \ if (isElement$6(container) && offset > container.childNodes.length + - 1) {\n directionLeft = false;\n }\n if (isDocument$1(container)) + {\n container = body;\n offset = 0;\n }\n if + (container === body) {\n if (directionLeft) {\n node = + container.childNodes[offset > 0 ? offset - 1 : 0];\n if (node) + {\n if (isCaretContainer$2(node)) {\n return Optional.none();\n + \ }\n if (nonEmptyElementsMap[node.nodeName] || isTable(node)) + {\n return Optional.none();\n }\n }\n + \ }\n if (container.hasChildNodes()) {\n offset + = Math.min(!directionLeft && offset > 0 ? offset - 1 : offset, container.childNodes.length + - 1);\n container = container.childNodes[offset];\n offset + = isText$8(container) && isAfterNode ? container.data.length : 0;\n if + (!collapsed && container === body.lastChild && isTable(container)) {\n return + Optional.none();\n }\n if (hasContentEditableFalseParent(body, + container) || isCaretContainer$2(container)) {\n return Optional.none();\n + \ }\n if (container.hasChildNodes() && isTable(container) + === false) {\n node = container;\n const walker + = new DomTreeWalker(container, body);\n do {\n if + (isContentEditableFalse$a(node) || isCaretContainer$2(node)) {\n normalized + = false;\n break;\n }\n if + (isText$8(node) && node.nodeValue.length > 0) {\n offset + = directionLeft ? 0 : node.nodeValue.length;\n container + = node;\n normalized = true;\n break;\n + \ }\n if (nonEmptyElementsMap[node.nodeName.toLowerCase()] + && !isTableCell$2(node)) {\n offset = dom3.nodeIndex(node);\n + \ container = node.parentNode;\n if (!directionLeft) + {\n offset++;\n }\n normalized + = true;\n break;\n }\n } while + (node = directionLeft ? walker.next() : walker.prev());\n }\n }\n + \ }\n if (collapsed) {\n if (isText$8(container) && + offset === 0) {\n findTextNodeRelative(dom3, isAfterNode, collapsed, + true, container).each((pos) => {\n container = pos.container();\n + \ offset = pos.offset();\n normalized = true;\n });\n + \ }\n if (isElement$6(container)) {\n node = container.childNodes[offset];\n + \ if (!node) {\n node = container.childNodes[offset + - 1];\n }\n if (node && isBr$5(node) && !isPrevNode(node, + \"A\") && !hasBrBeforeAfter(dom3, node, false) && !hasBrBeforeAfter(dom3, + node, true)) {\n findTextNodeRelative(dom3, isAfterNode, collapsed, + true, node).each((pos) => {\n container = pos.container();\n + \ offset = pos.offset();\n normalized = true;\n + \ });\n }\n }\n }\n if (directionLeft + && !collapsed && isText$8(container) && offset === container.nodeValue.length) + {\n findTextNodeRelative(dom3, isAfterNode, collapsed, false, container).each((pos) + => {\n container = pos.container();\n offset = pos.offset();\n + \ normalized = true;\n });\n }\n return normalized + ? Optional.some(CaretPosition(container, offset)) : Optional.none();\n };\n + \ const normalize$22 = (dom3, rng) => {\n const collapsed = rng.collapsed, + normRng = rng.cloneRange();\n const startPos = CaretPosition.fromRangeStart(rng);\n + \ normalizeEndPoint(dom3, collapsed, true, normRng).each((pos) => {\n + \ if (!collapsed || !CaretPosition.isAbove(startPos, pos)) {\n normRng.setStart(pos.container(), + pos.offset());\n }\n });\n if (!collapsed) {\n normalizeEndPoint(dom3, + collapsed, false, normRng).each((pos) => {\n normRng.setEnd(pos.container(), + pos.offset());\n });\n }\n if (collapsed) {\n normRng.collapse(true);\n + \ }\n return isEq$4(rng, normRng) ? Optional.none() : Optional.some(normRng);\n + \ };\n const splitText = (node, offset) => {\n return node.splitText(offset);\n + \ };\n const split = (rng) => {\n let startContainer = rng.startContainer, + startOffset = rng.startOffset, endContainer = rng.endContainer, endOffset + = rng.endOffset;\n if (startContainer === endContainer && isText$8(startContainer)) + {\n if (startOffset > 0 && startOffset < startContainer.nodeValue.length) + {\n endContainer = splitText(startContainer, startOffset);\n startContainer + = endContainer.previousSibling;\n if (endOffset > startOffset) + {\n endOffset = endOffset - startOffset;\n startContainer + = endContainer = splitText(endContainer, endOffset).previousSibling;\n endOffset + = endContainer.nodeValue.length;\n startOffset = 0;\n } + else {\n endOffset = 0;\n }\n }\n } + else {\n if (isText$8(startContainer) && startOffset > 0 && startOffset + < startContainer.nodeValue.length) {\n startContainer = splitText(startContainer, + startOffset);\n startOffset = 0;\n }\n if (isText$8(endContainer) + && endOffset > 0 && endOffset < endContainer.nodeValue.length) {\n endContainer + = splitText(endContainer, endOffset).previousSibling;\n endOffset + = endContainer.nodeValue.length;\n }\n }\n return {\n + \ startContainer,\n startOffset,\n endContainer,\n + \ endOffset\n };\n };\n const RangeUtils = (dom3) + => {\n const walk2 = (rng, callback) => {\n return walk$3(dom3, + rng, callback);\n };\n const split$12 = split;\n const + normalize4 = (rng) => {\n return normalize$22(dom3, rng).fold(never, + (normalizedRng) => {\n rng.setStart(normalizedRng.startContainer, + normalizedRng.startOffset);\n rng.setEnd(normalizedRng.endContainer, + normalizedRng.endOffset);\n return true;\n });\n };\n + \ return {\n walk: walk2,\n split: split$12,\n normalize: + normalize4\n };\n };\n RangeUtils.compareRanges = isEq$4;\n + \ RangeUtils.getCaretRangeFromPoint = fromPoint;\n RangeUtils.getSelectedNode + = getSelectedNode;\n RangeUtils.getNode = getNode$1;\n const Dimension + = (name2, getOffset) => {\n const set3 = (element, h2) => {\n if + (!isNumber2(h2) && !h2.match(/^[0-9]+$/)) {\n throw new Error(name2 + + \".set accepts only positive integer values. Value was \" + h2);\n }\n + \ const dom3 = element.dom;\n if (isSupported$1(dom3)) {\n + \ dom3.style[name2] = h2 + \"px\";\n }\n };\n const + get2 = (element) => {\n const r4 = getOffset(element);\n if + (r4 <= 0 || r4 === null) {\n const css = get$7(element, name2);\n + \ return parseFloat(css) || 0;\n }\n return r4;\n + \ };\n const getOuter2 = get2;\n const aggregate = (element, + properties) => foldl(properties, (acc, property) => {\n const val + = get$7(element, property);\n const value2 = val === void 0 ? 0 : + parseInt(val, 10);\n return isNaN(value2) ? acc : acc + value2;\n + \ }, 0);\n const max2 = (element, value2, properties) => {\n + \ const cumulativeInclusions = aggregate(element, properties);\n const + absoluteMax = value2 > cumulativeInclusions ? value2 - cumulativeInclusions + : 0;\n return absoluteMax;\n };\n return {\n set: + set3,\n get: get2,\n getOuter: getOuter2,\n aggregate,\n + \ max: max2\n };\n };\n const api = Dimension(\"height\", + (element) => {\n const dom3 = element.dom;\n return inBody(element) + ? dom3.getBoundingClientRect().height : dom3.offsetHeight;\n });\n const + get$2 = (element) => api.get(element);\n const getDocument = () => SugarElement.fromDom(document);\n + \ const walkUp = (navigation, doc) => {\n const frame = navigation.view(doc);\n + \ return frame.fold(constant2([]), (f2) => {\n const parent2 + = navigation.owner(f2);\n const rest = walkUp(navigation, parent2);\n + \ return [f2].concat(rest);\n });\n };\n const pathTo + = (element, navigation) => {\n const d2 = navigation.owner(element);\n + \ return walkUp(navigation, d2);\n };\n const view = (doc) + => {\n var _a3;\n const element = doc.dom === document ? Optional.none() + : Optional.from((_a3 = doc.dom.defaultView) === null || _a3 === void 0 ? void + 0 : _a3.frameElement);\n return element.map(SugarElement.fromDom);\n + \ };\n const owner = (element) => documentOrOwner(element);\n var + Navigation = /* @__PURE__ */ Object.freeze({\n __proto__: null,\n view,\n + \ owner\n });\n const find = (element) => {\n const + doc = getDocument();\n const scroll = get$5(doc);\n const frames + = pathTo(element, Navigation);\n const offset = viewport(element);\n + \ const r4 = foldr(frames, (b2, a2) => {\n const loc = viewport(a2);\n + \ return {\n left: b2.left + loc.left,\n top: + b2.top + loc.top\n };\n }, {\n left: 0,\n top: + 0\n });\n return SugarPosition(r4.left + offset.left + scroll.left, + r4.top + offset.top + scroll.top);\n };\n const excludeFromDescend + = (element) => name(element) === \"textarea\";\n const fireScrollIntoViewEvent + = (editor, data2) => {\n const scrollEvent = editor.dispatch(\"ScrollIntoView\", + data2);\n return scrollEvent.isDefaultPrevented();\n };\n const + fireAfterScrollIntoViewEvent = (editor, data2) => {\n editor.dispatch(\"AfterScrollIntoView\", + data2);\n };\n const descend = (element, offset) => {\n const + children$1 = children(element);\n if (children$1.length === 0 || excludeFromDescend(element)) + {\n return {\n element,\n offset\n };\n + \ } else if (offset < children$1.length && !excludeFromDescend(children$1[offset])) + {\n return {\n element: children$1[offset],\n offset: + 0\n };\n } else {\n const last2 = children$1[children$1.length + - 1];\n if (excludeFromDescend(last2)) {\n return {\n + \ element,\n offset\n };\n } + else {\n if (name(last2) === \"img\") {\n return {\n + \ element: last2,\n offset: 1\n };\n + \ } else if (isText$9(last2)) {\n return {\n element: + last2,\n offset: get$3(last2).length\n };\n } + else {\n return {\n element: last2,\n offset: + children(last2).length\n };\n }\n }\n }\n + \ };\n const markerInfo = (element, cleanupFun) => {\n const + pos = absolute(element);\n const height = get$2(element);\n return + {\n element,\n bottom: pos.top + height,\n height,\n + \ pos,\n cleanup: cleanupFun\n };\n };\n const + createMarker$1 = (element, offset) => {\n const startPoint = descend(element, + offset);\n const span = SugarElement.fromHtml('' + ZWSP$1 + \"\");\n before$3(startPoint.element, + span);\n return markerInfo(span, () => remove$5(span));\n };\n + \ const elementMarker = (element) => markerInfo(SugarElement.fromDom(element), + noop);\n const withMarker = (editor, f2, rng, alignToTop) => {\n preserveWith(editor, + (_s, _e) => applyWithMarker(editor, f2, rng, alignToTop), rng);\n };\n + \ const withScrollEvents = (editor, doc, f2, marker, alignToTop) => {\n + \ const data2 = {\n elm: marker.element.dom,\n alignToTop\n + \ };\n if (fireScrollIntoViewEvent(editor, data2)) {\n return;\n + \ }\n const scrollTop = get$5(doc).top;\n f2(doc, scrollTop, + marker, alignToTop);\n fireAfterScrollIntoViewEvent(editor, data2);\n + \ };\n const applyWithMarker = (editor, f2, rng, alignToTop) => {\n + \ const body = SugarElement.fromDom(editor.getBody());\n const + doc = SugarElement.fromDom(editor.getDoc());\n reflow(body);\n const + marker = createMarker$1(SugarElement.fromDom(rng.startContainer), rng.startOffset);\n + \ withScrollEvents(editor, doc, f2, marker, alignToTop);\n marker.cleanup();\n + \ };\n const withElement = (editor, element, f2, alignToTop) => {\n + \ const doc = SugarElement.fromDom(editor.getDoc());\n withScrollEvents(editor, + doc, f2, elementMarker(element), alignToTop);\n };\n const preserveWith + = (editor, f2, rng) => {\n const startElement = rng.startContainer;\n + \ const startOffset = rng.startOffset;\n const endElement = rng.endContainer;\n + \ const endOffset = rng.endOffset;\n f2(SugarElement.fromDom(startElement), + SugarElement.fromDom(endElement));\n const newRng = editor.dom.createRng();\n + \ newRng.setStart(startElement, startOffset);\n newRng.setEnd(endElement, + endOffset);\n editor.selection.setRng(rng);\n };\n const + scrollToMarker = (marker, viewHeight, alignToTop, doc) => {\n const + pos = marker.pos;\n if (alignToTop) {\n to(pos.left, pos.top, + doc);\n } else {\n const y2 = pos.top - viewHeight + marker.height;\n + \ to(pos.left, y2, doc);\n }\n };\n const intoWindowIfNeeded + = (doc, scrollTop, viewHeight, marker, alignToTop) => {\n const viewportBottom + = viewHeight + scrollTop;\n const markerTop = marker.pos.top;\n const + markerBottom = marker.bottom;\n const largerThanViewport = markerBottom + - markerTop >= viewHeight;\n if (markerTop < scrollTop) {\n scrollToMarker(marker, + viewHeight, alignToTop !== false, doc);\n } else if (markerTop > viewportBottom) + {\n const align = largerThanViewport ? alignToTop !== false : alignToTop + === true;\n scrollToMarker(marker, viewHeight, align, doc);\n } + else if (markerBottom > viewportBottom && !largerThanViewport) {\n scrollToMarker(marker, + viewHeight, alignToTop === true, doc);\n }\n };\n const intoWindow + = (doc, scrollTop, marker, alignToTop) => {\n const viewHeight = doc.dom.defaultView.innerHeight;\n + \ intoWindowIfNeeded(doc, scrollTop, viewHeight, marker, alignToTop);\n + \ };\n const intoFrame = (doc, scrollTop, marker, alignToTop) => + {\n const frameViewHeight = doc.dom.defaultView.innerHeight;\n intoWindowIfNeeded(doc, + scrollTop, frameViewHeight, marker, alignToTop);\n const op = find(marker.element);\n + \ const viewportBounds = getBounds(window);\n if (op.top < viewportBounds.y) + {\n intoView(marker.element, alignToTop !== false);\n } else + if (op.top > viewportBounds.bottom) {\n intoView(marker.element, + alignToTop === true);\n }\n };\n const rangeIntoWindow = + (editor, rng, alignToTop) => withMarker(editor, intoWindow, rng, alignToTop);\n + \ const elementIntoWindow = (editor, element, alignToTop) => withElement(editor, + element, intoWindow, alignToTop);\n const rangeIntoFrame = (editor, rng, + alignToTop) => withMarker(editor, intoFrame, rng, alignToTop);\n const + elementIntoFrame = (editor, element, alignToTop) => withElement(editor, element, + intoFrame, alignToTop);\n const scrollElementIntoView = (editor, element, + alignToTop) => {\n const scroller = editor.inline ? elementIntoWindow + : elementIntoFrame;\n scroller(editor, element, alignToTop);\n };\n + \ const scrollRangeIntoView = (editor, rng, alignToTop) => {\n const + scroller = editor.inline ? rangeIntoWindow : rangeIntoFrame;\n scroller(editor, + rng, alignToTop);\n };\n const focus$1 = (element) => element.dom.focus();\n + \ const hasFocus$1 = (element) => {\n const root2 = getRootNode(element).dom;\n + \ return element.dom === root2.activeElement;\n };\n const + active$1 = (root2 = getDocument()) => Optional.from(root2.dom.activeElement).map(SugarElement.fromDom);\n + \ const search = (element) => active$1(getRootNode(element)).filter((e2) + => element.dom.contains(e2.dom));\n const clamp$1 = (offset, element) + => {\n const max2 = isText$9(element) ? get$3(element).length : children(element).length + + 1;\n if (offset > max2) {\n return max2;\n } else + if (offset < 0) {\n return 0;\n }\n return offset;\n + \ };\n const normalizeRng = (rng) => SimSelection.range(rng.start, + clamp$1(rng.soffset, rng.start), rng.finish, clamp$1(rng.foffset, rng.finish));\n + \ const isOrContains = (root2, elm) => !isRestrictedNode(elm.dom) && (contains2(root2, + elm) || eq2(root2, elm));\n const isRngInRoot = (root2) => (rng) => isOrContains(root2, + rng.start) && isOrContains(root2, rng.finish);\n const shouldStore = + (editor) => editor.inline;\n const nativeRangeToSelectionRange = (r4) + => SimSelection.range(SugarElement.fromDom(r4.startContainer), r4.startOffset, + SugarElement.fromDom(r4.endContainer), r4.endOffset);\n const readRange + = (win) => {\n const selection = win.getSelection();\n const + rng = !selection || selection.rangeCount === 0 ? Optional.none() : Optional.from(selection.getRangeAt(0));\n + \ return rng.map(nativeRangeToSelectionRange);\n };\n const + getBookmark = (root2) => {\n const win = defaultView(root2);\n return + readRange(win.dom).filter(isRngInRoot(root2));\n };\n const validate + = (root2, bookmark) => Optional.from(bookmark).filter(isRngInRoot(root2)).map(normalizeRng);\n + \ const bookmarkToNativeRng = (bookmark) => {\n const rng = document.createRange();\n + \ try {\n rng.setStart(bookmark.start.dom, bookmark.soffset);\n + \ rng.setEnd(bookmark.finish.dom, bookmark.foffset);\n return + Optional.some(rng);\n } catch (_2) {\n return Optional.none();\n + \ }\n };\n const store2 = (editor) => {\n const newBookmark + = shouldStore(editor) ? getBookmark(SugarElement.fromDom(editor.getBody())) + : Optional.none();\n editor.bookmark = newBookmark.isSome() ? newBookmark + : editor.bookmark;\n };\n const getRng = (editor) => {\n const + bookmark = editor.bookmark ? editor.bookmark : Optional.none();\n return + bookmark.bind((x2) => validate(SugarElement.fromDom(editor.getBody()), x2)).bind(bookmarkToNativeRng);\n + \ };\n const restore = (editor) => {\n getRng(editor).each((rng) + => editor.selection.setRng(rng));\n };\n const isEditorUIElement$1 + = (elm) => {\n const className = elm.className.toString();\n return + className.indexOf(\"tox-\") !== -1 || className.indexOf(\"mce-\") !== -1;\n + \ };\n const FocusManager = { isEditorUIElement: isEditorUIElement$1 + };\n const wrappedSetTimeout = (callback, time) => {\n if (!isNumber2(time)) + {\n time = 0;\n }\n return setTimeout(callback, time);\n + \ };\n const wrappedSetInterval = (callback, time) => {\n if + (!isNumber2(time)) {\n time = 0;\n }\n return setInterval(callback, + time);\n };\n const Delay = {\n setEditorTimeout: (editor, + callback, time) => {\n return wrappedSetTimeout(() => {\n if + (!editor.removed) {\n callback();\n }\n }, + time);\n },\n setEditorInterval: (editor, callback, time) => + {\n const timer = wrappedSetInterval(() => {\n if (!editor.removed) + {\n callback();\n } else {\n clearInterval(timer);\n + \ }\n }, time);\n return timer;\n }\n };\n + \ const isManualNodeChange = (e2) => {\n return e2.type === \"nodechange\" + && e2.selectionChange;\n };\n const registerPageMouseUp = (editor, + throttledStore) => {\n const mouseUpPage = () => {\n throttledStore.throttle();\n + \ };\n DOMUtils.DOM.bind(document, \"mouseup\", mouseUpPage);\n + \ editor.on(\"remove\", () => {\n DOMUtils.DOM.unbind(document, + \"mouseup\", mouseUpPage);\n });\n };\n const registerMouseUp + = (editor, throttledStore) => {\n editor.on(\"mouseup touchend\", (_e) + => {\n throttledStore.throttle();\n });\n };\n const + registerEditorEvents = (editor, throttledStore) => {\n registerMouseUp(editor, + throttledStore);\n editor.on(\"keyup NodeChange AfterSetSelectionRange\", + (e2) => {\n if (!isManualNodeChange(e2)) {\n store2(editor);\n + \ }\n });\n };\n const register$6 = (editor) => {\n + \ const throttledStore = first$1(() => {\n store2(editor);\n + \ }, 0);\n editor.on(\"init\", () => {\n if (editor.inline) + {\n registerPageMouseUp(editor, throttledStore);\n }\n + \ registerEditorEvents(editor, throttledStore);\n });\n editor.on(\"remove\", + () => {\n throttledStore.cancel();\n });\n };\n let + documentFocusInHandler;\n const DOM$9 = DOMUtils.DOM;\n const isEditorUIElement + = (elm) => {\n return FocusManager.isEditorUIElement(elm);\n };\n + \ const isEditorContentAreaElement = (elm) => {\n const classList + = elm.classList;\n if (classList !== void 0) {\n return classList.contains(\"tox-edit-area\") + || classList.contains(\"tox-edit-area__iframe\") || classList.contains(\"mce-content-body\");\n + \ } else {\n return false;\n }\n };\n const + isUIElement = (editor, elm) => {\n const customSelector = getCustomUiSelector(editor);\n + \ const parent2 = DOM$9.getParent(elm, (elm2) => {\n return + isEditorUIElement(elm2) || (customSelector ? editor.dom.is(elm2, customSelector) + : false);\n });\n return parent2 !== null;\n };\n const + getActiveElement = (editor) => {\n try {\n const root2 = getRootNode(SugarElement.fromDom(editor.getElement()));\n + \ return active$1(root2).fold(() => document.body, (x2) => x2.dom);\n + \ } catch (ex) {\n return document.body;\n }\n };\n + \ const registerEvents$1 = (editorManager, e2) => {\n const editor + = e2.editor;\n register$6(editor);\n editor.on(\"focusin\", + () => {\n const focusedEditor = editorManager.focusedEditor;\n if + (focusedEditor !== editor) {\n if (focusedEditor) {\n focusedEditor.dispatch(\"blur\", + { focusedEditor: editor });\n }\n editorManager.setActive(editor);\n + \ editorManager.focusedEditor = editor;\n editor.dispatch(\"focus\", + { blurredEditor: focusedEditor });\n editor.focus(true);\n }\n + \ });\n editor.on(\"focusout\", () => {\n Delay.setEditorTimeout(editor, + () => {\n const focusedEditor = editorManager.focusedEditor;\n + \ if (!isUIElement(editor, getActiveElement(editor)) && focusedEditor + === editor) {\n editor.dispatch(\"blur\", { focusedEditor: null + });\n editorManager.focusedEditor = null;\n }\n });\n + \ });\n if (!documentFocusInHandler) {\n documentFocusInHandler + = (e3) => {\n const activeEditor = editorManager.activeEditor;\n + \ if (activeEditor) {\n getOriginalEventTarget(e3).each((target) + => {\n if (target.ownerDocument === document) {\n if + (target !== document.body && !isUIElement(activeEditor, target) && editorManager.focusedEditor + === activeEditor) {\n activeEditor.dispatch(\"blur\", { + focusedEditor: null });\n editorManager.focusedEditor = + null;\n }\n }\n });\n }\n + \ };\n DOM$9.bind(document, \"focusin\", documentFocusInHandler);\n + \ }\n };\n const unregisterDocumentEvents = (editorManager, + e2) => {\n if (editorManager.focusedEditor === e2.editor) {\n editorManager.focusedEditor + = null;\n }\n if (!editorManager.activeEditor) {\n DOM$9.unbind(document, + \"focusin\", documentFocusInHandler);\n documentFocusInHandler = + null;\n }\n };\n const setup$v = (editorManager) => {\n editorManager.on(\"AddEditor\", + curry(registerEvents$1, editorManager));\n editorManager.on(\"RemoveEditor\", + curry(unregisterDocumentEvents, editorManager));\n };\n const getContentEditableHost + = (editor, node) => editor.dom.getParent(node, (node2) => editor.dom.getContentEditable(node2) + === \"true\");\n const getCollapsedNode = (rng) => rng.collapsed ? Optional.from(getNode$1(rng.startContainer, + rng.startOffset)).map(SugarElement.fromDom) : Optional.none();\n const + getFocusInElement = (root2, rng) => getCollapsedNode(rng).bind((node) => {\n + \ if (isTableSection(node)) {\n return Optional.some(node);\n + \ } else if (contains2(root2, node) === false) {\n return Optional.some(root2);\n + \ } else {\n return Optional.none();\n }\n });\n + \ const normalizeSelection$1 = (editor, rng) => {\n getFocusInElement(SugarElement.fromDom(editor.getBody()), + rng).bind((elm) => {\n return firstPositionIn(elm.dom);\n }).fold(() + => {\n editor.selection.normalize();\n return;\n }, + (caretPos) => editor.selection.setRng(caretPos.toRange()));\n };\n const + focusBody = (body) => {\n if (body.setActive) {\n try {\n + \ body.setActive();\n } catch (ex) {\n body.focus();\n + \ }\n } else {\n body.focus();\n }\n };\n + \ const hasElementFocus = (elm) => hasFocus$1(elm) || search(elm).isSome();\n + \ const hasIframeFocus = (editor) => editor.iframeElement && hasFocus$1(SugarElement.fromDom(editor.iframeElement));\n + \ const hasInlineFocus = (editor) => {\n const rawBody = editor.getBody();\n + \ return rawBody && hasElementFocus(SugarElement.fromDom(rawBody));\n + \ };\n const hasUiFocus = (editor) => {\n const dos = getRootNode(SugarElement.fromDom(editor.getElement()));\n + \ return active$1(dos).filter((elem) => !isEditorContentAreaElement(elem.dom) + && isUIElement(editor, elem.dom)).isSome();\n };\n const hasFocus + = (editor) => editor.inline ? hasInlineFocus(editor) : hasIframeFocus(editor);\n + \ const hasEditorOrUiFocus = (editor) => hasFocus(editor) || hasUiFocus(editor);\n + \ const focusEditor = (editor) => {\n const selection = editor.selection;\n + \ const body = editor.getBody();\n let rng = selection.getRng();\n + \ editor.quirks.refreshContentEditable();\n if (editor.bookmark + !== void 0 && hasFocus(editor) === false) {\n getRng(editor).each((bookmarkRng) + => {\n editor.selection.setRng(bookmarkRng);\n rng = + bookmarkRng;\n });\n }\n const contentEditableHost + = getContentEditableHost(editor, selection.getNode());\n if (editor.dom.isChildOf(contentEditableHost, + body)) {\n focusBody(contentEditableHost);\n normalizeSelection$1(editor, + rng);\n activateEditor(editor);\n return;\n }\n if + (!editor.inline) {\n if (!Env.browser.isOpera()) {\n focusBody(body);\n + \ }\n editor.getWin().focus();\n }\n if (Env.browser.isFirefox() + || editor.inline) {\n focusBody(body);\n normalizeSelection$1(editor, + rng);\n }\n activateEditor(editor);\n };\n const activateEditor + = (editor) => editor.editorManager.setActive(editor);\n const focus = + (editor, skipFocus) => {\n if (editor.removed) {\n return;\n + \ }\n if (skipFocus) {\n activateEditor(editor);\n } + else {\n focusEditor(editor);\n }\n };\n const getEndpointElement + = (root2, rng, start2, real, resolve2) => {\n const container = start2 + ? rng.startContainer : rng.endContainer;\n const offset = start2 ? + rng.startOffset : rng.endOffset;\n return Optional.from(container).map(SugarElement.fromDom).map((elm) + => !real || !rng.collapsed ? child$1(elm, resolve2(elm, offset)).getOr(elm) + : elm).bind((elm) => isElement$7(elm) ? Optional.some(elm) : parent(elm).filter(isElement$7)).map((elm) + => elm.dom).getOr(root2);\n };\n const getStart = (root2, rng, real) + => getEndpointElement(root2, rng, true, real, (elm, offset) => Math.min(childNodesCount(elm), + offset));\n const getEnd$1 = (root2, rng, real) => getEndpointElement(root2, + rng, false, real, (elm, offset) => offset > 0 ? offset - 1 : offset);\n const + skipEmptyTextNodes = (node, forwards) => {\n const orig = node;\n while + (node && isText$8(node) && node.length === 0) {\n node = forwards + ? node.nextSibling : node.previousSibling;\n }\n return node + || orig;\n };\n const getNode = (root2, rng) => {\n let elm, + startContainer, endContainer;\n if (!rng) {\n return root2;\n + \ }\n startContainer = rng.startContainer;\n endContainer + = rng.endContainer;\n const startOffset = rng.startOffset;\n const + endOffset = rng.endOffset;\n elm = rng.commonAncestorContainer;\n if + (!rng.collapsed) {\n if (startContainer === endContainer) {\n if + (endOffset - startOffset < 2) {\n if (startContainer.hasChildNodes()) + {\n elm = startContainer.childNodes[startOffset];\n }\n + \ }\n }\n if (startContainer.nodeType === 3 && + endContainer.nodeType === 3) {\n if (startContainer.length === + startOffset) {\n startContainer = skipEmptyTextNodes(startContainer.nextSibling, + true);\n } else {\n startContainer = startContainer.parentNode;\n + \ }\n if (endOffset === 0) {\n endContainer + = skipEmptyTextNodes(endContainer.previousSibling, false);\n } + else {\n endContainer = endContainer.parentNode;\n }\n + \ if (startContainer && startContainer === endContainer) {\n return + startContainer;\n }\n }\n }\n if (elm && + elm.nodeType === 3) {\n return elm.parentNode;\n }\n return + elm;\n };\n const getSelectedBlocks = (dom3, rng, startElm, endElm) + => {\n let node;\n const selectedBlocks = [];\n const + root2 = dom3.getRoot();\n startElm = dom3.getParent(startElm || getStart(root2, + rng, rng.collapsed), dom3.isBlock);\n endElm = dom3.getParent(endElm + || getEnd$1(root2, rng, rng.collapsed), dom3.isBlock);\n if (startElm + && startElm !== root2) {\n selectedBlocks.push(startElm);\n }\n + \ if (startElm && endElm && startElm !== endElm) {\n node = + startElm;\n const walker = new DomTreeWalker(startElm, root2);\n + \ while ((node = walker.next()) && node !== endElm) {\n if + (dom3.isBlock(node)) {\n selectedBlocks.push(node);\n }\n + \ }\n }\n if (endElm && startElm !== endElm && endElm + !== root2) {\n selectedBlocks.push(endElm);\n }\n return + selectedBlocks;\n };\n const select = (dom3, node, content) => Optional.from(node).map((node2) + => {\n const idx = dom3.nodeIndex(node2);\n const rng = dom3.createRng();\n + \ rng.setStart(node2.parentNode, idx);\n rng.setEnd(node2.parentNode, + idx + 1);\n if (content) {\n moveEndPoint(dom3, rng, node2, + true);\n moveEndPoint(dom3, rng, node2, false);\n }\n return + rng;\n });\n const processRanges = (editor, ranges) => map$3(ranges, + (range2) => {\n const evt = editor.dispatch(\"GetSelectionRange\", + { range: range2 });\n return evt.range !== range2 ? evt.range : range2;\n + \ });\n const getEnd = (element) => name(element) === \"img\" ? 1 + : getOption(element).fold(() => children(element).length, (v2) => v2.length);\n + \ const isTextNodeWithCursorPosition = (el) => getOption(el).filter((text3) + => text3.trim().length !== 0 || text3.indexOf(nbsp) > -1).isSome();\n const + elementsWithCursorPosition = [\n \"img\",\n \"br\"\n ];\n + \ const isCursorPosition = (elem) => {\n const hasCursorPosition + = isTextNodeWithCursorPosition(elem);\n return hasCursorPosition || + contains$2(elementsWithCursorPosition, name(elem));\n };\n const + first = (element) => descendant$1(element, isCursorPosition);\n const + last = (element) => descendantRtl(element, isCursorPosition);\n const + descendantRtl = (scope, predicate) => {\n const descend2 = (element) + => {\n const children$1 = children(element);\n for (let + i3 = children$1.length - 1; i3 >= 0; i3--) {\n const child2 = children$1[i3];\n + \ if (predicate(child2)) {\n return Optional.some(child2);\n + \ }\n const res = descend2(child2);\n if (res.isSome()) + {\n return res;\n }\n }\n return + Optional.none();\n };\n return descend2(scope);\n };\n + \ const autocompleteSelector = \"[data-mce-autocompleter]\";\n const + create$8 = (editor, range2) => {\n if (findIn(SugarElement.fromDom(editor.getBody())).isNone()) + {\n const wrapper = SugarElement.fromHtml('', editor.getDoc());\n append$1(wrapper, + SugarElement.fromDom(range2.extractContents()));\n range2.insertNode(wrapper.dom);\n + \ parent(wrapper).each((elm) => elm.dom.normalize());\n last(wrapper).map((last2) + => {\n editor.selection.setCursorLocation(last2.dom, getEnd(last2));\n + \ });\n }\n };\n const detect$1 = (elm) => closest$3(elm, + autocompleteSelector);\n const findIn = (elm) => descendant(elm, autocompleteSelector);\n + \ const remove$3 = (editor, elm) => findIn(elm).each((wrapper) => {\n + \ const bookmark = editor.selection.getBookmark();\n unwrap(wrapper);\n + \ editor.selection.moveToBookmark(bookmark);\n });\n const + typeLookup = {\n \"#text\": 3,\n \"#comment\": 8,\n \"#cdata\": + 4,\n \"#pi\": 7,\n \"#doctype\": 10,\n \"#document-fragment\": + 11\n };\n const walk$2 = (node, root2, prev2) => {\n const + startName = prev2 ? \"lastChild\" : \"firstChild\";\n const siblingName + = prev2 ? \"prev\" : \"next\";\n if (node[startName]) {\n return + node[startName];\n }\n if (node !== root2) {\n let + sibling2 = node[siblingName];\n if (sibling2) {\n return + sibling2;\n }\n for (let parent2 = node.parent; parent2 + && parent2 !== root2; parent2 = parent2.parent) {\n sibling2 = + parent2[siblingName];\n if (sibling2) {\n return sibling2;\n + \ }\n }\n }\n };\n const isEmptyTextNode + = (node) => {\n if (!isWhitespaceText(node.value)) {\n return + false;\n }\n const parentNode = node.parent;\n if (parentNode + && (parentNode.name !== \"span\" || parentNode.attr(\"style\")) && /^[ ]+$/.test(node.value)) + {\n return false;\n }\n return true;\n };\n const + isNonEmptyElement = (node) => {\n const isNamedAnchor2 = node.name + === \"a\" && !node.attr(\"href\") && node.attr(\"id\");\n return node.attr(\"name\") + || node.attr(\"id\") && !node.firstChild || node.attr(\"data-mce-bookmark\") + || isNamedAnchor2;\n };\n class AstNode {\n constructor(name2, + type2) {\n this.name = name2;\n this.type = type2;\n if + (type2 === 1) {\n this.attributes = [];\n this.attributes.map + = {};\n }\n }\n static create(name2, attrs) {\n const + node = new AstNode(name2, typeLookup[name2] || 1);\n if (attrs) {\n + \ each$f(attrs, (value2, attrName) => {\n node.attr(attrName, + value2);\n });\n }\n return node;\n }\n + \ replace(node) {\n const self2 = this;\n if (node.parent) + {\n node.remove();\n }\n self2.insert(node, self2);\n + \ self2.remove();\n return self2;\n }\n attr(name2, + value2) {\n const self2 = this;\n let attrs;\n if + (typeof name2 !== \"string\") {\n if (name2 !== void 0 && name2 + !== null) {\n each$f(name2, (value3, key) => {\n self2.attr(key, + value3);\n });\n }\n return self2;\n }\n + \ if (attrs = self2.attributes) {\n if (value2 !== void + 0) {\n if (value2 === null) {\n if (name2 in attrs.map) + {\n delete attrs.map[name2];\n let i3 = + attrs.length;\n while (i3--) {\n if (attrs[i3].name + === name2) {\n attrs.splice(i3, 1);\n return + self2;\n }\n }\n }\n return + self2;\n }\n if (name2 in attrs.map) {\n let + i3 = attrs.length;\n while (i3--) {\n if (attrs[i3].name + === name2) {\n attrs[i3].value = value2;\n break;\n + \ }\n }\n } else {\n attrs.push({\n + \ name: name2,\n value: value2\n });\n + \ }\n attrs.map[name2] = value2;\n return + self2;\n }\n return attrs.map[name2];\n }\n + \ }\n clone() {\n const self2 = this;\n const + clone2 = new AstNode(self2.name, self2.type);\n let selfAttrs;\n + \ if (selfAttrs = self2.attributes) {\n const cloneAttrs + = [];\n cloneAttrs.map = {};\n for (let i3 = 0, l2 = + selfAttrs.length; i3 < l2; i3++) {\n const selfAttr = selfAttrs[i3];\n + \ if (selfAttr.name !== \"id\") {\n cloneAttrs[cloneAttrs.length] + = {\n name: selfAttr.name,\n value: selfAttr.value\n + \ };\n cloneAttrs.map[selfAttr.name] = selfAttr.value;\n + \ }\n }\n clone2.attributes = cloneAttrs;\n + \ }\n clone2.value = self2.value;\n return clone2;\n + \ }\n wrap(wrapper) {\n const self2 = this;\n self2.parent.insert(wrapper, + self2);\n wrapper.append(self2);\n return self2;\n }\n + \ unwrap() {\n const self2 = this;\n for (let node + = self2.firstChild; node; ) {\n const next2 = node.next;\n self2.insert(node, + self2, true);\n node = next2;\n }\n self2.remove();\n + \ }\n remove() {\n const self2 = this, parent2 = self2.parent, + next2 = self2.next, prev2 = self2.prev;\n if (parent2) {\n if + (parent2.firstChild === self2) {\n parent2.firstChild = next2;\n + \ if (next2) {\n next2.prev = null;\n }\n + \ } else {\n prev2.next = next2;\n }\n if + (parent2.lastChild === self2) {\n parent2.lastChild = prev2;\n + \ if (prev2) {\n prev2.next = null;\n }\n + \ } else {\n next2.prev = prev2;\n }\n self2.parent + = self2.next = self2.prev = null;\n }\n return self2;\n + \ }\n append(node) {\n const self2 = this;\n if + (node.parent) {\n node.remove();\n }\n const + last2 = self2.lastChild;\n if (last2) {\n last2.next = + node;\n node.prev = last2;\n self2.lastChild = node;\n + \ } else {\n self2.lastChild = self2.firstChild = node;\n + \ }\n node.parent = self2;\n return node;\n }\n + \ insert(node, refNode, before2) {\n if (node.parent) {\n node.remove();\n + \ }\n const parent2 = refNode.parent || this;\n if + (before2) {\n if (refNode === parent2.firstChild) {\n parent2.firstChild + = node;\n } else {\n refNode.prev.next = node;\n }\n + \ node.prev = refNode.prev;\n node.next = refNode;\n + \ refNode.prev = node;\n } else {\n if (refNode + === parent2.lastChild) {\n parent2.lastChild = node;\n } + else {\n refNode.next.prev = node;\n }\n node.next + = refNode.next;\n node.prev = refNode;\n refNode.next + = node;\n }\n node.parent = parent2;\n return node;\n + \ }\n getAll(name2) {\n const self2 = this;\n const + collection = [];\n for (let node = self2.firstChild; node; node = + walk$2(node, self2)) {\n if (node.name === name2) {\n collection.push(node);\n + \ }\n }\n return collection;\n }\n children() + {\n const self2 = this;\n const collection = [];\n for + (let node = self2.firstChild; node; node = node.next) {\n collection.push(node);\n + \ }\n return collection;\n }\n empty() {\n + \ const self2 = this;\n if (self2.firstChild) {\n const + nodes = [];\n for (let node = self2.firstChild; node; node = walk$2(node, + self2)) {\n nodes.push(node);\n }\n let + i3 = nodes.length;\n while (i3--) {\n const node = + nodes[i3];\n node.parent = node.firstChild = node.lastChild = + node.next = node.prev = null;\n }\n }\n self2.firstChild + = self2.lastChild = null;\n return self2;\n }\n isEmpty(elements, + whitespace = {}, predicate) {\n const self2 = this;\n let + node = self2.firstChild;\n if (isNonEmptyElement(self2)) {\n return + false;\n }\n if (node) {\n do {\n if + (node.type === 1) {\n if (node.attr(\"data-mce-bogus\")) {\n + \ continue;\n }\n if (elements[node.name]) + {\n return false;\n }\n if + (isNonEmptyElement(node)) {\n return false;\n }\n + \ }\n if (node.type === 8) {\n return + false;\n }\n if (node.type === 3 && !isEmptyTextNode(node)) + {\n return false;\n }\n if (node.type + === 3 && node.parent && whitespace[node.parent.name] && isWhitespaceText(node.value)) + {\n return false;\n }\n if (predicate + && predicate(node)) {\n return false;\n }\n } + while (node = walk$2(node, self2));\n }\n return true;\n + \ }\n walk(prev2) {\n return walk$2(this, null, prev2);\n + \ }\n }\n const isConditionalComment = (html2, startIndex) + => /^\\s*\\[if [\\w\\W]+\\]>.*/.test(html2.substr(startIndex));\n + \ const findCommentEndIndex = (html2, isBogus2, startIndex = 0) => {\n + \ const lcHtml = html2.toLowerCase();\n if (lcHtml.indexOf(\"[if + \", startIndex) !== -1 && isConditionalComment(lcHtml, startIndex)) {\n const + endIfIndex = lcHtml.indexOf(\"[endif]\", startIndex);\n return lcHtml.indexOf(\">\", + endIfIndex);\n } else {\n if (isBogus2) {\n const + endIndex = lcHtml.indexOf(\">\", startIndex);\n return endIndex + !== -1 ? endIndex : lcHtml.length;\n } else {\n const + endCommentRegexp = /--!?>/g;\n endCommentRegexp.lastIndex = startIndex;\n + \ const match4 = endCommentRegexp.exec(html2);\n return + match4 ? match4.index + match4[0].length : lcHtml.length;\n }\n }\n + \ };\n const findMatchingEndTagIndex = (schema, html2, startIndex) + => {\n const startTagRegExp = /<([!?\\/])?([A-Za-z0-9\\-_:.]+)/g;\n + \ const endTagRegExp = /(?:\\s(?:[^'\">]+(?:\"[^\"]*\"|'[^']*'))*[^\"'>]*(?:\"[^\">]*|'[^'>]*)?|\\s*|\\/)>/g;\n + \ const voidElements = schema.getVoidElements();\n let count2 + = 1, index2 = startIndex;\n while (count2 !== 0) {\n startTagRegExp.lastIndex + = index2;\n while (true) {\n const startMatch = startTagRegExp.exec(html2);\n + \ if (startMatch === null) {\n return index2;\n } + else if (startMatch[1] === \"!\") {\n if (startsWith(startMatch[2], + \"--\")) {\n index2 = findCommentEndIndex(html2, false, startMatch.index + + \"!--\".length);\n } else {\n index2 = findCommentEndIndex(html2, + true, startMatch.index + 1);\n }\n break;\n } + else {\n endTagRegExp.lastIndex = startTagRegExp.lastIndex;\n + \ const endMatch = endTagRegExp.exec(html2);\n if + (isNull(endMatch) || endMatch.index !== startTagRegExp.lastIndex) {\n continue;\n + \ }\n if (startMatch[1] === \"/\") {\n count2 + -= 1;\n } else if (!has$2(voidElements, startMatch[2])) {\n count2 + += 1;\n }\n index2 = startTagRegExp.lastIndex + + endMatch[0].length;\n break;\n }\n }\n }\n + \ return index2;\n };\n const trimHtml$1 = (tempAttrs, html2) + => {\n const trimContentRegExp = new RegExp([\"\\\\s?(\" + tempAttrs.join(\"|\") + + ')=\"[^\"]+\"'].join(\"|\"), \"gi\");\n return html2.replace(trimContentRegExp, + \"\");\n };\n const trimInternal = (serializer, html2) => {\n const + bogusAllRegExp = /<(\\w+) [^>]*data-mce-bogus=\"all\"[^>]*>/g;\n const + schema = serializer.schema;\n let content = trimHtml$1(serializer.getTempAttrs(), + html2);\n const voidElements = schema.getVoidElements();\n let + matches;\n while (matches = bogusAllRegExp.exec(content)) {\n const + index2 = bogusAllRegExp.lastIndex;\n const matchLength = matches[0].length;\n + \ let endTagIndex;\n if (voidElements[matches[1]]) {\n endTagIndex + = index2;\n } else {\n endTagIndex = findMatchingEndTagIndex(schema, + content, index2);\n }\n content = content.substring(0, index2 + - matchLength) + content.substring(endTagIndex);\n bogusAllRegExp.lastIndex + = index2 - matchLength;\n }\n return trim$1(content);\n };\n + \ const trimExternal = trimInternal;\n const trimEmptyContents = + (editor, html2) => {\n const blockName = getForcedRootBlock(editor);\n + \ const emptyRegExp = new RegExp(`^(<${blockName}[^>]*>( | |\\\\s| |
|)<\\\\/${blockName}>[\\r\n]*|
[\\r\n]*)$`);\n return + html2.replace(emptyRegExp, \"\");\n };\n const getContentFromBody + = (editor, args, body) => {\n let content;\n if (args.format + === \"raw\") {\n content = Tools.trim(trimExternal(editor.serializer, + body.innerHTML));\n } else if (args.format === \"text\") {\n content + = editor.dom.isEmpty(body) ? \"\" : trim$1(body.innerText || body.textContent);\n + \ } else if (args.format === \"tree\") {\n content = editor.serializer.serialize(body, + args);\n } else {\n content = trimEmptyContents(editor, editor.serializer.serialize(body, + args));\n }\n const shouldTrim = args.format !== \"text\" && + !isWsPreserveElement(SugarElement.fromDom(body));\n return shouldTrim + && isString2(content) ? Tools.trim(content) : content;\n };\n const + getContentInternal = (editor, args) => Optional.from(editor.getBody()).fold(constant2(args.format + === \"tree\" ? new AstNode(\"body\", 11) : \"\"), (body) => getContentFromBody(editor, + args, body));\n const each$b = Tools.each;\n const ElementUtils + = (dom3) => {\n const compare2 = (node1, node2) => {\n if + (node1.nodeName !== node2.nodeName) {\n return false;\n }\n + \ const getAttribs = (node) => {\n const attribs = {};\n + \ each$b(dom3.getAttribs(node), (attr) => {\n const + name2 = attr.nodeName.toLowerCase();\n if (name2.indexOf(\"_\") + !== 0 && name2 !== \"style\" && name2.indexOf(\"data-\") !== 0) {\n attribs[name2] + = dom3.getAttrib(node, name2);\n }\n });\n return + attribs;\n };\n const compareObjects = (obj1, obj2) => {\n + \ let value2, name2;\n for (name2 in obj1) {\n if + (has$2(obj1, name2)) {\n value2 = obj2[name2];\n if + (typeof value2 === \"undefined\") {\n return false;\n }\n + \ if (obj1[name2] !== value2) {\n return false;\n + \ }\n delete obj2[name2];\n }\n + \ }\n for (name2 in obj2) {\n if (has$2(obj2, + name2)) {\n return false;\n }\n }\n + \ return true;\n };\n if (!compareObjects(getAttribs(node1), + getAttribs(node2))) {\n return false;\n }\n if + (!compareObjects(dom3.parseStyle(dom3.getAttrib(node1, \"style\")), dom3.parseStyle(dom3.getAttrib(node2, + \"style\")))) {\n return false;\n }\n return + !isBookmarkNode$1(node1) && !isBookmarkNode$1(node2);\n };\n return + { compare: compare2 };\n };\n const makeMap$1 = Tools.makeMap;\n + \ const Writer = (settings) => {\n const html2 = [];\n settings + = settings || {};\n const indent2 = settings.indent;\n const + indentBefore = makeMap$1(settings.indent_before || \"\");\n const indentAfter + = makeMap$1(settings.indent_after || \"\");\n const encode2 = Entities.getEncodeFunc(settings.entity_encoding + || \"raw\", settings.entities);\n const htmlOutput = settings.element_format + !== \"xhtml\";\n return {\n start: (name2, attrs, empty2) + => {\n let i3, l2, attr, value2;\n if (indent2 && indentBefore[name2] + && html2.length > 0) {\n value2 = html2[html2.length - 1];\n + \ if (value2.length > 0 && value2 !== \"\\n\") {\n html2.push(\"\\n\");\n + \ }\n }\n html2.push(\"<\", name2);\n if + (attrs) {\n for (i3 = 0, l2 = attrs.length; i3 < l2; i3++) {\n + \ attr = attrs[i3];\n html2.push(\" \", attr.name, + '=\"', encode2(attr.value, true), '\"');\n }\n }\n + \ if (!empty2 || htmlOutput) {\n html2[html2.length] + = \">\";\n } else {\n html2[html2.length] = \" />\";\n + \ }\n if (empty2 && indent2 && indentAfter[name2] && + html2.length > 0) {\n value2 = html2[html2.length - 1];\n if + (value2.length > 0 && value2 !== \"\\n\") {\n html2.push(\"\\n\");\n + \ }\n }\n },\n end: (name2) => {\n + \ let value2;\n html2.push(\"\");\n if + (indent2 && indentAfter[name2] && html2.length > 0) {\n value2 + = html2[html2.length - 1];\n if (value2.length > 0 && value2 + !== \"\\n\") {\n html2.push(\"\\n\");\n }\n }\n + \ },\n text: (text3, raw) => {\n if (text3.length + > 0) {\n html2[html2.length] = raw ? text3 : encode2(text3);\n + \ }\n },\n cdata: (text3) => {\n html2.push(\"\");\n },\n comment: (text3) => {\n html2.push(\"\");\n },\n pi: (name2, text3) => {\n if + (text3) {\n html2.push(\"\");\n } else {\n html2.push(\"\");\n + \ }\n if (indent2) {\n html2.push(\"\\n\");\n + \ }\n },\n doctype: (text3) => {\n html2.push(\"\", indent2 ? \"\\n\" : \"\");\n },\n reset: () + => {\n html2.length = 0;\n },\n getContent: () + => {\n return html2.join(\"\").replace(/\\n$/, \"\");\n }\n + \ };\n };\n const HtmlSerializer = (settings, schema = Schema()) + => {\n const writer = Writer(settings);\n settings = settings + || {};\n settings.validate = \"validate\" in settings ? settings.validate + : true;\n const serialize = (node) => {\n const validate2 + = settings.validate;\n const handlers = {\n 3: (node2) + => {\n writer.text(node2.value, node2.raw);\n },\n + \ 8: (node2) => {\n writer.comment(node2.value);\n + \ },\n 7: (node2) => {\n writer.pi(node2.name, + node2.value);\n },\n 10: (node2) => {\n writer.doctype(node2.value);\n + \ },\n 4: (node2) => {\n writer.cdata(node2.value);\n + \ },\n 11: (node2) => {\n if (node2 = node2.firstChild) + {\n do {\n walk2(node2);\n } + while (node2 = node2.next);\n }\n }\n };\n + \ writer.reset();\n const walk2 = (node2) => {\n const + handler = handlers[node2.type];\n if (!handler) {\n const + name2 = node2.name;\n const isEmpty3 = name2 in schema.getVoidElements();\n + \ let attrs = node2.attributes;\n if (validate2 && + attrs && attrs.length > 1) {\n const sortedAttrs = [];\n sortedAttrs.map + = {};\n const elementRule = schema.getElementRule(node2.name);\n + \ if (elementRule) {\n for (let i3 = 0, l2 + = elementRule.attributesOrder.length; i3 < l2; i3++) {\n const + attrName = elementRule.attributesOrder[i3];\n if (attrName + in attrs.map) {\n const attrValue = attrs.map[attrName];\n + \ sortedAttrs.map[attrName] = attrValue;\n sortedAttrs.push({\n + \ name: attrName,\n value: attrValue\n + \ });\n }\n }\n for + (let i3 = 0, l2 = attrs.length; i3 < l2; i3++) {\n const + attrName = attrs[i3].name;\n if (!(attrName in sortedAttrs.map)) + {\n const attrValue = attrs.map[attrName];\n sortedAttrs.map[attrName] + = attrValue;\n sortedAttrs.push({\n name: + attrName,\n value: attrValue\n });\n + \ }\n }\n attrs = sortedAttrs;\n + \ }\n }\n writer.start(name2, attrs, + isEmpty3);\n if (!isEmpty3) {\n let child2 = node2.firstChild;\n + \ if (child2) {\n if ((name2 === \"pre\" || + name2 === \"textarea\") && child2.type === 3 && child2.value[0] === \"\\n\") + {\n writer.text(\"\\n\", true);\n }\n + \ do {\n walk2(child2);\n } + while (child2 = child2.next);\n }\n writer.end(name2);\n + \ }\n } else {\n handler(node2);\n }\n + \ };\n if (node.type === 1 && !settings.inner) {\n walk2(node);\n + \ } else if (node.type === 3) {\n handlers[3](node);\n + \ } else {\n handlers[11](node);\n }\n return + writer.getContent();\n };\n return { serialize };\n };\n + \ const nonInheritableStyles = /* @__PURE__ */ new Set();\n (() => + {\n const nonInheritableStylesArr = [\n \"margin\",\n \"margin-left\",\n + \ \"margin-right\",\n \"margin-top\",\n \"margin-bottom\",\n + \ \"padding\",\n \"padding-left\",\n \"padding-right\",\n + \ \"padding-top\",\n \"padding-bottom\",\n \"border\",\n + \ \"border-width\",\n \"border-style\",\n \"border-color\",\n + \ \"background\",\n \"background-attachment\",\n \"background-clip\",\n + \ \"background-color\",\n \"background-image\",\n \"background-origin\",\n + \ \"background-position\",\n \"background-repeat\",\n \"background-size\",\n + \ \"float\",\n \"position\",\n \"left\",\n \"right\",\n + \ \"top\",\n \"bottom\",\n \"z-index\",\n \"display\",\n + \ \"transform\",\n \"width\",\n \"max-width\",\n + \ \"min-width\",\n \"height\",\n \"max-height\",\n + \ \"min-height\",\n \"overflow\",\n \"overflow-x\",\n + \ \"overflow-y\",\n \"text-overflow\",\n \"vertical-align\",\n + \ \"transition\",\n \"transition-delay\",\n \"transition-duration\",\n + \ \"transition-property\",\n \"transition-timing-function\"\n + \ ];\n each$g(nonInheritableStylesArr, (style) => {\n nonInheritableStyles.add(style);\n + \ });\n })();\n const shorthandStyleProps = [\n \"font\",\n + \ \"text-decoration\",\n \"text-emphasis\"\n ];\n const + getStyleProps = (dom3, node) => keys2(dom3.parseStyle(dom3.getAttrib(node, + \"style\")));\n const isNonInheritableStyle = (style) => nonInheritableStyles.has(style);\n + \ const hasInheritableStyles = (dom3, node) => forall(getStyleProps(dom3, + node), (style) => !isNonInheritableStyle(style));\n const getLonghandStyleProps + = (styles) => filter$6(styles, (style) => exists(shorthandStyleProps, (prop) + => startsWith(style, prop)));\n const hasStyleConflict = (dom3, node, + parentNode) => {\n const nodeStyleProps = getStyleProps(dom3, node);\n + \ const parentNodeStyleProps = getStyleProps(dom3, parentNode);\n const + valueMismatch = (prop) => {\n var _a3, _b;\n const nodeValue + = (_a3 = dom3.getStyle(node, prop)) !== null && _a3 !== void 0 ? _a3 : \"\";\n + \ const parentValue = (_b = dom3.getStyle(parentNode, prop)) !== null + && _b !== void 0 ? _b : \"\";\n return isNotEmpty(nodeValue) && isNotEmpty(parentValue) + && nodeValue !== parentValue;\n };\n return exists(nodeStyleProps, + (nodeStyleProp) => {\n const propExists = (props) => exists(props, + (prop) => prop === nodeStyleProp);\n if (!propExists(parentNodeStyleProps) + && propExists(shorthandStyleProps)) {\n const longhandProps = getLonghandStyleProps(parentNodeStyleProps);\n + \ return exists(longhandProps, valueMismatch);\n } else + {\n return valueMismatch(nodeStyleProp);\n }\n });\n + \ };\n const isChar = (forward, predicate, pos) => Optional.from(pos.container()).filter(isText$8).exists((text3) + => {\n const delta = forward ? 0 : -1;\n return predicate(text3.data.charAt(pos.offset() + + delta));\n });\n const isBeforeSpace = curry(isChar, true, isWhiteSpace2);\n + \ const isAfterSpace = curry(isChar, false, isWhiteSpace2);\n const + isEmptyText = (pos) => {\n const container = pos.container();\n return + isText$8(container) && (container.data.length === 0 || isZwsp(container.data) + && BookmarkManager.isBookmarkNode(container.parentNode));\n };\n const + matchesElementPosition = (before2, predicate) => (pos) => Optional.from(getChildNodeAtRelativeOffset(before2 + ? 0 : -1, pos)).filter(predicate).isSome();\n const isImageBlock = (node) + => isImg(node) && get$7(SugarElement.fromDom(node), \"display\") === \"block\";\n + \ const isCefNode = (node) => isContentEditableFalse$a(node) && !isBogusAll$1(node);\n + \ const isBeforeImageBlock = matchesElementPosition(true, isImageBlock);\n + \ const isAfterImageBlock = matchesElementPosition(false, isImageBlock);\n + \ const isBeforeMedia = matchesElementPosition(true, isMedia$2);\n const + isAfterMedia = matchesElementPosition(false, isMedia$2);\n const isBeforeTable + = matchesElementPosition(true, isTable$3);\n const isAfterTable = matchesElementPosition(false, + isTable$3);\n const isBeforeContentEditableFalse = matchesElementPosition(true, + isCefNode);\n const isAfterContentEditableFalse = matchesElementPosition(false, + isCefNode);\n const getLastChildren = (elm) => {\n const children2 + = [];\n let rawNode = elm.dom;\n while (rawNode) {\n children2.push(SugarElement.fromDom(rawNode));\n + \ rawNode = rawNode.lastChild;\n }\n return children2;\n + \ };\n const removeTrailingBr = (elm) => {\n const allBrs + = descendants(elm, \"br\");\n const brs = filter$6(getLastChildren(elm).slice(-1), + isBr$4);\n if (allBrs.length === brs.length) {\n each$g(brs, + remove$5);\n }\n };\n const fillWithPaddingBr = (elm) => + {\n empty(elm);\n append$1(elm, SugarElement.fromHtml('
'));\n + \ };\n const trimBlockTrailingBr = (elm) => {\n lastChild(elm).each((lastChild2) + => {\n prevSibling(lastChild2).each((lastChildPrevSibling) => {\n + \ if (isBlock$2(elm) && isBr$4(lastChild2) && isBlock$2(lastChildPrevSibling)) + {\n remove$5(lastChild2);\n }\n });\n });\n + \ };\n const dropLast = (xs) => xs.slice(0, -1);\n const parentsUntil + = (start2, root2, predicate) => {\n if (contains2(root2, start2)) {\n + \ return dropLast(parents$1(start2, (elm) => {\n return + predicate(elm) || eq2(elm, root2);\n }));\n } else {\n return + [];\n }\n };\n const parents = (start2, root2) => parentsUntil(start2, + root2, never);\n const parentsAndSelf = (start2, root2) => [start2].concat(parents(start2, + root2));\n const navigateIgnoreEmptyTextNodes = (forward, root2, from2) + => navigateIgnore(forward, root2, from2, isEmptyText);\n const getClosestBlock$1 + = (root2, pos) => find$2(parentsAndSelf(SugarElement.fromDom(pos.container()), + root2), isBlock$2);\n const isAtBeforeAfterBlockBoundary = (forward, + root2, pos) => navigateIgnoreEmptyTextNodes(forward, root2.dom, pos).forall((newPos) + => getClosestBlock$1(root2, pos).fold(() => isInSameBlock(newPos, pos, root2.dom) + === false, (fromBlock) => isInSameBlock(newPos, pos, root2.dom) === false + && contains2(fromBlock, SugarElement.fromDom(newPos.container()))));\n const + isAtBlockBoundary = (forward, root2, pos) => getClosestBlock$1(root2, pos).fold(() + => navigateIgnoreEmptyTextNodes(forward, root2.dom, pos).forall((newPos) => + isInSameBlock(newPos, pos, root2.dom) === false), (parent2) => navigateIgnoreEmptyTextNodes(forward, + parent2.dom, pos).isNone());\n const isAtStartOfBlock = curry(isAtBlockBoundary, + false);\n const isAtEndOfBlock = curry(isAtBlockBoundary, true);\n const + isBeforeBlock = curry(isAtBeforeAfterBlockBoundary, false);\n const isAfterBlock + = curry(isAtBeforeAfterBlockBoundary, true);\n const isBr = (pos) => + getElementFromPosition(pos).exists(isBr$4);\n const findBr = (forward, + root2, pos) => {\n const parentBlocks = filter$6(parentsAndSelf(SugarElement.fromDom(pos.container()), + root2), isBlock$2);\n const scope = head(parentBlocks).getOr(root2);\n + \ return fromPosition(forward, scope.dom, pos).filter(isBr);\n };\n + \ const isBeforeBr$1 = (root2, pos) => getElementFromPosition(pos).exists(isBr$4) + || findBr(true, root2, pos).isSome();\n const isAfterBr = (root2, pos) + => getElementFromPrevPosition(pos).exists(isBr$4) || findBr(false, root2, + pos).isSome();\n const findPreviousBr = curry(findBr, false);\n const + findNextBr = curry(findBr, true);\n const isInMiddleOfText = (pos) => + CaretPosition.isTextPosition(pos) && !pos.isAtStart() && !pos.isAtEnd();\n + \ const getClosestBlock = (root2, pos) => {\n const parentBlocks + = filter$6(parentsAndSelf(SugarElement.fromDom(pos.container()), root2), isBlock$2);\n + \ return head(parentBlocks).getOr(root2);\n };\n const hasSpaceBefore + = (root2, pos) => {\n if (isInMiddleOfText(pos)) {\n return + isAfterSpace(pos);\n } else {\n return isAfterSpace(pos) || + prevPosition(getClosestBlock(root2, pos).dom, pos).exists(isAfterSpace);\n + \ }\n };\n const hasSpaceAfter = (root2, pos) => {\n if + (isInMiddleOfText(pos)) {\n return isBeforeSpace(pos);\n } + else {\n return isBeforeSpace(pos) || nextPosition(getClosestBlock(root2, + pos).dom, pos).exists(isBeforeSpace);\n }\n };\n const isPreValue + = (value2) => contains$2([\n \"pre\",\n \"pre-wrap\"\n ], + value2);\n const isInPre = (pos) => getElementFromPosition(pos).bind((elm) + => closest$4(elm, isElement$7)).exists((elm) => isPreValue(get$7(elm, \"white-space\")));\n + \ const isAtBeginningOfBody = (root2, pos) => prevPosition(root2.dom, + pos).isNone();\n const isAtEndOfBody = (root2, pos) => nextPosition(root2.dom, + pos).isNone();\n const isAtLineBoundary = (root2, pos) => isAtBeginningOfBody(root2, + pos) || isAtEndOfBody(root2, pos) || isAtStartOfBlock(root2, pos) || isAtEndOfBlock(root2, + pos) || isAfterBr(root2, pos) || isBeforeBr$1(root2, pos);\n const needsToHaveNbsp + = (root2, pos) => {\n if (isInPre(pos)) {\n return false;\n + \ } else {\n return isAtLineBoundary(root2, pos) || hasSpaceBefore(root2, + pos) || hasSpaceAfter(root2, pos);\n }\n };\n const needsToBeNbspLeft + = (root2, pos) => {\n if (isInPre(pos)) {\n return false;\n + \ } else {\n return isAtStartOfBlock(root2, pos) || isBeforeBlock(root2, + pos) || isAfterBr(root2, pos) || hasSpaceBefore(root2, pos);\n }\n + \ };\n const leanRight = (pos) => {\n const container = pos.container();\n + \ const offset = pos.offset();\n if (isText$8(container) && offset + < container.data.length) {\n return CaretPosition(container, offset + + 1);\n } else {\n return pos;\n }\n };\n const + needsToBeNbspRight = (root2, pos) => {\n if (isInPre(pos)) {\n return + false;\n } else {\n return isAtEndOfBlock(root2, pos) || isAfterBlock(root2, + pos) || isBeforeBr$1(root2, pos) || hasSpaceAfter(root2, pos);\n }\n + \ };\n const needsToBeNbsp = (root2, pos) => needsToBeNbspLeft(root2, + pos) || needsToBeNbspRight(root2, leanRight(pos));\n const isNbspAt = + (text3, offset) => isNbsp(text3.charAt(offset));\n const hasNbsp = (pos) + => {\n const container = pos.container();\n return isText$8(container) + && contains$1(container.data, nbsp);\n };\n const normalizeNbspMiddle + = (text3) => {\n const chars = text3.split(\"\");\n return map$3(chars, + (chr, i3) => {\n if (isNbsp(chr) && i3 > 0 && i3 < chars.length - + 1 && isContent(chars[i3 - 1]) && isContent(chars[i3 + 1])) {\n return + \" \";\n } else {\n return chr;\n }\n }).join(\"\");\n + \ };\n const normalizeNbspAtStart = (root2, node) => {\n const + text3 = node.data;\n const firstPos = CaretPosition(node, 0);\n if + (isNbspAt(text3, 0) && !needsToBeNbsp(root2, firstPos)) {\n node.data + = \" \" + text3.slice(1);\n return true;\n } else {\n return + false;\n }\n };\n const normalizeNbspInMiddleOfTextNode = + (node) => {\n const text3 = node.data;\n const newText = normalizeNbspMiddle(text3);\n + \ if (newText !== text3) {\n node.data = newText;\n return + true;\n } else {\n return false;\n }\n };\n const + normalizeNbspAtEnd = (root2, node) => {\n const text3 = node.data;\n + \ const lastPos = CaretPosition(node, text3.length - 1);\n if + (isNbspAt(text3, text3.length - 1) && !needsToBeNbsp(root2, lastPos)) {\n + \ node.data = text3.slice(0, -1) + \" \";\n return true;\n + \ } else {\n return false;\n }\n };\n const + normalizeNbsps = (root2, pos) => Optional.some(pos).filter(hasNbsp).bind((pos2) + => {\n const container = pos2.container();\n const normalized + = normalizeNbspAtStart(root2, container) || normalizeNbspInMiddleOfTextNode(container) + || normalizeNbspAtEnd(root2, container);\n return normalized ? Optional.some(pos2) + : Optional.none();\n });\n const normalizeNbspsInEditor = (editor) + => {\n const root2 = SugarElement.fromDom(editor.getBody());\n if + (editor.selection.isCollapsed()) {\n normalizeNbsps(root2, CaretPosition.fromRangeStart(editor.selection.getRng())).each((pos) + => {\n editor.selection.setRng(pos.toRange());\n });\n + \ }\n };\n const normalize$12 = (node, offset, count2) => + {\n if (count2 === 0) {\n return;\n }\n const + elm = SugarElement.fromDom(node);\n const root2 = ancestor$3(elm, isBlock$2).getOr(elm);\n + \ const whitespace = node.data.slice(offset, offset + count2);\n const + isEndOfContent = offset + count2 >= node.data.length && needsToBeNbspRight(root2, + CaretPosition(node, node.data.length));\n const isStartOfContent = + offset === 0 && needsToBeNbspLeft(root2, CaretPosition(node, 0));\n node.replaceData(offset, + count2, normalize$4(whitespace, 4, isStartOfContent, isEndOfContent));\n };\n + \ const normalizeWhitespaceAfter = (node, offset) => {\n const + content = node.data.slice(offset);\n const whitespaceCount = content.length + - lTrim(content).length;\n normalize$12(node, offset, whitespaceCount);\n + \ };\n const normalizeWhitespaceBefore = (node, offset) => {\n const + content = node.data.slice(0, offset);\n const whitespaceCount = content.length + - rTrim(content).length;\n normalize$12(node, offset - whitespaceCount, + whitespaceCount);\n };\n const mergeTextNodes = (prevNode, nextNode, + normalizeWhitespace, mergeToPrev = true) => {\n const whitespaceOffset + = rTrim(prevNode.data).length;\n const newNode = mergeToPrev ? prevNode + : nextNode;\n const removeNode2 = mergeToPrev ? nextNode : prevNode;\n + \ if (mergeToPrev) {\n newNode.appendData(removeNode2.data);\n + \ } else {\n newNode.insertData(0, removeNode2.data);\n }\n + \ remove$5(SugarElement.fromDom(removeNode2));\n if (normalizeWhitespace) + {\n normalizeWhitespaceAfter(newNode, whitespaceOffset);\n }\n + \ return newNode;\n };\n const needsReposition = (pos, elm) + => {\n const container = pos.container();\n const offset = pos.offset();\n + \ return CaretPosition.isTextPosition(pos) === false && container === + elm.parentNode && offset > CaretPosition.before(elm).offset();\n };\n + \ const reposition = (elm, pos) => needsReposition(pos, elm) ? CaretPosition(pos.container(), + pos.offset() - 1) : pos;\n const beforeOrStartOf = (node) => isText$8(node) + ? CaretPosition(node, 0) : CaretPosition.before(node);\n const afterOrEndOf + = (node) => isText$8(node) ? CaretPosition(node, node.data.length) : CaretPosition.after(node);\n + \ const getPreviousSiblingCaretPosition = (elm) => {\n if (isCaretCandidate$3(elm.previousSibling)) + {\n return Optional.some(afterOrEndOf(elm.previousSibling));\n } + else {\n return elm.previousSibling ? lastPositionIn(elm.previousSibling) + : Optional.none();\n }\n };\n const getNextSiblingCaretPosition + = (elm) => {\n if (isCaretCandidate$3(elm.nextSibling)) {\n return + Optional.some(beforeOrStartOf(elm.nextSibling));\n } else {\n return + elm.nextSibling ? firstPositionIn(elm.nextSibling) : Optional.none();\n }\n + \ };\n const findCaretPositionBackwardsFromElm = (rootElement, elm) + => {\n const startPosition = CaretPosition.before(elm.previousSibling + ? elm.previousSibling : elm.parentNode);\n return prevPosition(rootElement, + startPosition).fold(() => nextPosition(rootElement, CaretPosition.after(elm)), + Optional.some);\n };\n const findCaretPositionForwardsFromElm = + (rootElement, elm) => nextPosition(rootElement, CaretPosition.after(elm)).fold(() + => prevPosition(rootElement, CaretPosition.before(elm)), Optional.some);\n + \ const findCaretPositionBackwards = (rootElement, elm) => getPreviousSiblingCaretPosition(elm).orThunk(() + => getNextSiblingCaretPosition(elm)).orThunk(() => findCaretPositionBackwardsFromElm(rootElement, + elm));\n const findCaretPositionForward = (rootElement, elm) => getNextSiblingCaretPosition(elm).orThunk(() + => getPreviousSiblingCaretPosition(elm)).orThunk(() => findCaretPositionForwardsFromElm(rootElement, + elm));\n const findCaretPosition = (forward, rootElement, elm) => forward + ? findCaretPositionForward(rootElement, elm) : findCaretPositionBackwards(rootElement, + elm);\n const findCaretPosOutsideElmAfterDelete = (forward, rootElement, + elm) => findCaretPosition(forward, rootElement, elm).map(curry(reposition, + elm));\n const setSelection$1 = (editor, forward, pos) => {\n pos.fold(() + => {\n editor.focus();\n }, (pos2) => {\n editor.selection.setRng(pos2.toRange(), + forward);\n });\n };\n const eqRawNode = (rawNode) => (elm) + => elm.dom === rawNode;\n const isBlock = (editor, elm) => elm && has$2(editor.schema.getBlockElements(), + name(elm));\n const paddEmptyBlock = (elm) => {\n if (isEmpty$2(elm)) + {\n const br = SugarElement.fromHtml('
');\n + \ empty(elm);\n append$1(elm, br);\n return Optional.some(CaretPosition.before(br.dom));\n + \ } else {\n return Optional.none();\n }\n };\n + \ const deleteNormalized = (elm, afterDeletePosOpt, normalizeWhitespace) + => {\n const prevTextOpt = prevSibling(elm).filter(isText$9);\n const + nextTextOpt = nextSibling(elm).filter(isText$9);\n remove$5(elm);\n + \ return lift3(prevTextOpt, nextTextOpt, afterDeletePosOpt, (prev2, + next2, pos) => {\n const prevNode = prev2.dom, nextNode = next2.dom;\n + \ const offset = prevNode.data.length;\n mergeTextNodes(prevNode, + nextNode, normalizeWhitespace);\n return pos.container() === nextNode + ? CaretPosition(prevNode, offset) : pos;\n }).orThunk(() => {\n if + (normalizeWhitespace) {\n prevTextOpt.each((elm2) => normalizeWhitespaceBefore(elm2.dom, + elm2.dom.length));\n nextTextOpt.each((elm2) => normalizeWhitespaceAfter(elm2.dom, + 0));\n }\n return afterDeletePosOpt;\n });\n };\n + \ const isInlineElement = (editor, element) => has$2(editor.schema.getTextInlineElements(), + name(element));\n const deleteElement$2 = (editor, forward, elm, moveCaret2 + = true) => {\n const afterDeletePos = findCaretPosOutsideElmAfterDelete(forward, + editor.getBody(), elm.dom);\n const parentBlock = ancestor$3(elm, curry(isBlock, + editor), eqRawNode(editor.getBody()));\n const normalizedAfterDeletePos + = deleteNormalized(elm, afterDeletePos, isInlineElement(editor, elm));\n if + (editor.dom.isEmpty(editor.getBody())) {\n editor.setContent(\"\");\n + \ editor.selection.setCursorLocation();\n } else {\n parentBlock.bind(paddEmptyBlock).fold(() + => {\n if (moveCaret2) {\n setSelection$1(editor, + forward, normalizedAfterDeletePos);\n }\n }, (paddPos) + => {\n if (moveCaret2) {\n setSelection$1(editor, + forward, Optional.some(paddPos));\n }\n });\n }\n + \ };\n const isRootFromElement = (root2) => (cur) => eq2(root2, cur);\n + \ const getTableCells = (table3) => descendants(table3, \"td,th\");\n + \ const getTableDetailsFromRange = (rng, isRoot) => {\n const getTable2 + = (node) => getClosestTable(SugarElement.fromDom(node), isRoot);\n const + startTable = getTable2(rng.startContainer);\n const endTable = getTable2(rng.endContainer);\n + \ const isStartInTable = startTable.isSome();\n const isEndInTable + = endTable.isSome();\n const isSameTable = lift2(startTable, endTable, + eq2).getOr(false);\n const isMultiTable = !isSameTable && isStartInTable + && isEndInTable;\n return {\n startTable,\n endTable,\n + \ isStartInTable,\n isEndInTable,\n isSameTable,\n + \ isMultiTable\n };\n };\n const tableCellRng = (start2, + end2) => ({\n start: start2,\n end: end2\n });\n const + tableSelection = (rng, table3, cells2) => ({\n rng,\n table: + table3,\n cells: cells2\n });\n const deleteAction = Adt.generate([\n + \ {\n singleCellTable: [\n \"rng\",\n \"cell\"\n + \ ]\n },\n { fullTable: [\"table\"] },\n {\n + \ partialTable: [\n \"cells\",\n \"outsideDetails\"\n + \ ]\n },\n {\n multiTable: [\n \"startTableCells\",\n + \ \"endTableCells\",\n \"betweenRng\"\n ]\n + \ }\n ]);\n const getClosestCell$1 = (container, isRoot) => + closest$3(SugarElement.fromDom(container), \"td,th\", isRoot);\n const + isExpandedCellRng = (cellRng) => !eq2(cellRng.start, cellRng.end);\n const + getTableFromCellRng = (cellRng, isRoot) => getClosestTable(cellRng.start, + isRoot).bind((startParentTable) => getClosestTable(cellRng.end, isRoot).bind((endParentTable) + => someIf(eq2(startParentTable, endParentTable), startParentTable)));\n const + isSingleCellTable = (cellRng, isRoot) => !isExpandedCellRng(cellRng) && getTableFromCellRng(cellRng, + isRoot).exists((table3) => {\n const rows = table3.dom.rows;\n return + rows.length === 1 && rows[0].cells.length === 1;\n });\n const getCellRng + = (rng, isRoot) => {\n const startCell = getClosestCell$1(rng.startContainer, + isRoot);\n const endCell = getClosestCell$1(rng.endContainer, isRoot);\n + \ return lift2(startCell, endCell, tableCellRng);\n };\n const + getCellRangeFromStartTable = (isRoot) => (startCell) => getClosestTable(startCell, + isRoot).bind((table3) => last$3(getTableCells(table3)).map((endCell) => tableCellRng(startCell, + endCell)));\n const getCellRangeFromEndTable = (isRoot) => (endCell) + => getClosestTable(endCell, isRoot).bind((table3) => head(getTableCells(table3)).map((startCell) + => tableCellRng(startCell, endCell)));\n const getTableSelectionFromCellRng + = (isRoot) => (cellRng) => getTableFromCellRng(cellRng, isRoot).map((table3) + => tableSelection(cellRng, table3, getTableCells(table3)));\n const getTableSelections + = (cellRng, selectionDetails, rng, isRoot) => {\n if (rng.collapsed + || !cellRng.forall(isExpandedCellRng)) {\n return Optional.none();\n + \ } else if (selectionDetails.isSameTable) {\n const sameTableSelection + = cellRng.bind(getTableSelectionFromCellRng(isRoot));\n return Optional.some({\n + \ start: sameTableSelection,\n end: sameTableSelection\n + \ });\n } else {\n const startCell = getClosestCell$1(rng.startContainer, + isRoot);\n const endCell = getClosestCell$1(rng.endContainer, isRoot);\n + \ const startTableSelection = startCell.bind(getCellRangeFromStartTable(isRoot)).bind(getTableSelectionFromCellRng(isRoot));\n + \ const endTableSelection = endCell.bind(getCellRangeFromEndTable(isRoot)).bind(getTableSelectionFromCellRng(isRoot));\n + \ return Optional.some({\n start: startTableSelection,\n + \ end: endTableSelection\n });\n }\n };\n const + getCellIndex = (cells2, cell2) => findIndex$2(cells2, (x2) => eq2(x2, cell2));\n + \ const getSelectedCells = (tableSelection2) => lift2(getCellIndex(tableSelection2.cells, + tableSelection2.rng.start), getCellIndex(tableSelection2.cells, tableSelection2.rng.end), + (startIndex, endIndex) => tableSelection2.cells.slice(startIndex, endIndex + + 1));\n const isSingleCellTableContentSelected = (optCellRng, rng, isRoot) + => optCellRng.exists((cellRng) => isSingleCellTable(cellRng, isRoot) && hasAllContentsSelected(cellRng.start, + rng));\n const unselectCells = (rng, selectionDetails) => {\n const + { startTable, endTable } = selectionDetails;\n const otherContentRng + = rng.cloneRange();\n startTable.each((table3) => otherContentRng.setStartAfter(table3.dom));\n + \ endTable.each((table3) => otherContentRng.setEndBefore(table3.dom));\n + \ return otherContentRng;\n };\n const handleSingleTable = + (cellRng, selectionDetails, rng, isRoot) => getTableSelections(cellRng, selectionDetails, + rng, isRoot).bind(({ start: start2, end: end2 }) => start2.or(end2)).bind((tableSelection2) + => {\n const { isSameTable } = selectionDetails;\n const selectedCells + = getSelectedCells(tableSelection2).getOr([]);\n if (isSameTable && + tableSelection2.cells.length === selectedCells.length) {\n return + Optional.some(deleteAction.fullTable(tableSelection2.table));\n } else + if (selectedCells.length > 0) {\n if (isSameTable) {\n return + Optional.some(deleteAction.partialTable(selectedCells, Optional.none()));\n + \ } else {\n const otherContentRng = unselectCells(rng, + selectionDetails);\n return Optional.some(deleteAction.partialTable(selectedCells, + Optional.some({\n ...selectionDetails,\n rng: otherContentRng\n + \ })));\n }\n } else {\n return Optional.none();\n + \ }\n });\n const handleMultiTable = (cellRng, selectionDetails, + rng, isRoot) => getTableSelections(cellRng, selectionDetails, rng, isRoot).bind(({ + start: start2, end: end2 }) => {\n const startTableSelectedCells = + start2.bind(getSelectedCells).getOr([]);\n const endTableSelectedCells + = end2.bind(getSelectedCells).getOr([]);\n if (startTableSelectedCells.length + > 0 && endTableSelectedCells.length > 0) {\n const otherContentRng + = unselectCells(rng, selectionDetails);\n return Optional.some(deleteAction.multiTable(startTableSelectedCells, + endTableSelectedCells, otherContentRng));\n } else {\n return + Optional.none();\n }\n });\n const getActionFromRange = (root2, + rng) => {\n const isRoot = isRootFromElement(root2);\n const + optCellRng = getCellRng(rng, isRoot);\n const selectionDetails = getTableDetailsFromRange(rng, + isRoot);\n if (isSingleCellTableContentSelected(optCellRng, rng, isRoot)) + {\n return optCellRng.map((cellRng) => deleteAction.singleCellTable(rng, + cellRng.start));\n } else if (selectionDetails.isMultiTable) {\n return + handleMultiTable(optCellRng, selectionDetails, rng, isRoot);\n } else + {\n return handleSingleTable(optCellRng, selectionDetails, rng, isRoot);\n + \ }\n };\n const freefallRtl = (root2) => {\n const + child2 = isComment$1(root2) ? prevSibling(root2) : lastChild(root2);\n return + child2.bind(freefallRtl).orThunk(() => Optional.some(root2));\n };\n + \ const cleanCells = (cells2) => each$g(cells2, (cell2) => {\n remove$a(cell2, + \"contenteditable\");\n fillWithPaddingBr(cell2);\n });\n const + getOutsideBlock = (editor, container) => Optional.from(editor.dom.getParent(container, + editor.dom.isBlock)).map(SugarElement.fromDom);\n const handleEmptyBlock + = (editor, startInTable, emptyBlock2) => {\n emptyBlock2.each((block2) + => {\n if (startInTable) {\n remove$5(block2);\n } + else {\n fillWithPaddingBr(block2);\n editor.selection.setCursorLocation(block2.dom, + 0);\n }\n });\n };\n const deleteContentInsideCell + = (editor, cell2, rng, isFirstCellInSelection) => {\n const insideTableRng + = rng.cloneRange();\n if (isFirstCellInSelection) {\n insideTableRng.setStart(rng.startContainer, + rng.startOffset);\n insideTableRng.setEndAfter(cell2.dom.lastChild);\n + \ } else {\n insideTableRng.setStartBefore(cell2.dom.firstChild);\n + \ insideTableRng.setEnd(rng.endContainer, rng.endOffset);\n }\n + \ deleteCellContents(editor, insideTableRng, cell2, false).each((action2) + => action2());\n };\n const collapseAndRestoreCellSelection = (editor) + => {\n const selectedCells = getCellsFromEditor(editor);\n const + selectedNode = SugarElement.fromDom(editor.selection.getNode());\n if + (isTableCell$5(selectedNode.dom) && isEmpty$2(selectedNode)) {\n editor.selection.setCursorLocation(selectedNode.dom, + 0);\n } else {\n editor.selection.collapse(true);\n }\n + \ if (selectedCells.length > 1 && exists(selectedCells, (cell2) => eq2(cell2, + selectedNode))) {\n set$2(selectedNode, \"data-mce-selected\", \"1\");\n + \ }\n };\n const emptySingleTableCells = (editor, cells2, + outsideDetails) => Optional.some(() => {\n const editorRng = editor.selection.getRng();\n + \ const cellsToClean = outsideDetails.bind(({ rng, isStartInTable }) + => {\n const outsideBlock = getOutsideBlock(editor, isStartInTable + ? rng.endContainer : rng.startContainer);\n rng.deleteContents();\n + \ handleEmptyBlock(editor, isStartInTable, outsideBlock.filter(isEmpty$2));\n + \ const endPointCell = isStartInTable ? cells2[0] : cells2[cells2.length + - 1];\n deleteContentInsideCell(editor, endPointCell, editorRng, + isStartInTable);\n if (!isEmpty$2(endPointCell)) {\n return + Optional.some(isStartInTable ? cells2.slice(1) : cells2.slice(0, -1));\n } + else {\n return Optional.none();\n }\n }).getOr(cells2);\n + \ cleanCells(cellsToClean);\n collapseAndRestoreCellSelection(editor);\n + \ });\n const emptyMultiTableCells = (editor, startTableCells, endTableCells, + betweenRng) => Optional.some(() => {\n const rng = editor.selection.getRng();\n + \ const startCell = startTableCells[0];\n const endCell = endTableCells[endTableCells.length + - 1];\n deleteContentInsideCell(editor, startCell, rng, true);\n deleteContentInsideCell(editor, + endCell, rng, false);\n const startTableCellsToClean = isEmpty$2(startCell) + ? startTableCells : startTableCells.slice(1);\n const endTableCellsToClean + = isEmpty$2(endCell) ? endTableCells : endTableCells.slice(0, -1);\n cleanCells(startTableCellsToClean.concat(endTableCellsToClean));\n + \ betweenRng.deleteContents();\n collapseAndRestoreCellSelection(editor);\n + \ });\n const deleteCellContents = (editor, rng, cell2, moveSelection2 + = true) => Optional.some(() => {\n rng.deleteContents();\n const + lastNode = freefallRtl(cell2).getOr(cell2);\n const lastBlock = SugarElement.fromDom(editor.dom.getParent(lastNode.dom, + editor.dom.isBlock));\n if (isEmpty$2(lastBlock)) {\n fillWithPaddingBr(lastBlock);\n + \ if (moveSelection2) {\n editor.selection.setCursorLocation(lastBlock.dom, + 0);\n }\n }\n if (!eq2(cell2, lastBlock)) {\n const + additionalCleanupNodes = is$2(parent(lastBlock), cell2) ? [] : siblings(lastBlock);\n + \ each$g(additionalCleanupNodes.concat(children(cell2)), (node) => + {\n if (!eq2(node, lastBlock) && !contains2(node, lastBlock) && + isEmpty$2(node)) {\n remove$5(node);\n }\n });\n + \ }\n });\n const deleteTableElement = (editor, table3) => + Optional.some(() => deleteElement$2(editor, false, table3));\n const + deleteCellRange = (editor, rootElm, rng) => getActionFromRange(rootElm, rng).bind((action2) + => action2.fold(curry(deleteCellContents, editor), curry(deleteTableElement, + editor), curry(emptySingleTableCells, editor), curry(emptyMultiTableCells, + editor)));\n const deleteCaptionRange = (editor, caption) => emptyElement(editor, + caption);\n const deleteTableRange = (editor, rootElm, rng, startElm) + => getParentCaption(rootElm, startElm).fold(() => deleteCellRange(editor, + rootElm, rng), (caption) => deleteCaptionRange(editor, caption));\n const + deleteRange$2 = (editor, startElm, selectedCells) => {\n const rootNode + = SugarElement.fromDom(editor.getBody());\n const rng = editor.selection.getRng();\n + \ return selectedCells.length !== 0 ? emptySingleTableCells(editor, + selectedCells, Optional.none()) : deleteTableRange(editor, rootNode, rng, + startElm);\n };\n const getParentCell = (rootElm, elm) => find$2(parentsAndSelf(elm, + rootElm), isTableCell$4);\n const getParentCaption = (rootElm, elm) => + find$2(parentsAndSelf(elm, rootElm), isTag(\"caption\"));\n const deleteBetweenCells + = (editor, rootElm, forward, fromCell, from2) => navigate(forward, editor.getBody(), + from2).bind((to2) => getParentCell(rootElm, SugarElement.fromDom(to2.getNode())).bind((toCell) + => eq2(toCell, fromCell) ? Optional.none() : Optional.some(noop)));\n const + emptyElement = (editor, elm) => Optional.some(() => {\n fillWithPaddingBr(elm);\n + \ editor.selection.setCursorLocation(elm.dom, 0);\n });\n const + isDeleteOfLastCharPos = (fromCaption, forward, from2, to2) => firstPositionIn(fromCaption.dom).bind((first2) + => lastPositionIn(fromCaption.dom).map((last2) => forward ? from2.isEqual(first2) + && to2.isEqual(last2) : from2.isEqual(last2) && to2.isEqual(first2))).getOr(true);\n + \ const emptyCaretCaption = (editor, elm) => emptyElement(editor, elm);\n + \ const validateCaretCaption = (rootElm, fromCaption, to2) => getParentCaption(rootElm, + SugarElement.fromDom(to2.getNode())).fold(() => Optional.some(noop), (toCaption) + => someIf(!eq2(toCaption, fromCaption), noop));\n const deleteCaretInsideCaption + = (editor, rootElm, forward, fromCaption, from2) => navigate(forward, editor.getBody(), + from2).fold(() => Optional.some(noop), (to2) => isDeleteOfLastCharPos(fromCaption, + forward, from2, to2) ? emptyCaretCaption(editor, fromCaption) : validateCaretCaption(rootElm, + fromCaption, to2));\n const deleteCaretCells = (editor, forward, rootElm, + startElm) => {\n const from2 = CaretPosition.fromRangeStart(editor.selection.getRng());\n + \ return getParentCell(rootElm, startElm).bind((fromCell) => isEmpty$2(fromCell) + ? emptyElement(editor, fromCell) : deleteBetweenCells(editor, rootElm, forward, + fromCell, from2));\n };\n const deleteCaretCaption = (editor, forward, + rootElm, fromCaption) => {\n const from2 = CaretPosition.fromRangeStart(editor.selection.getRng());\n + \ return isEmpty$2(fromCaption) ? emptyElement(editor, fromCaption) + : deleteCaretInsideCaption(editor, rootElm, forward, fromCaption, from2);\n + \ };\n const isNearTable = (forward, pos) => forward ? isBeforeTable(pos) + : isAfterTable(pos);\n const isBeforeOrAfterTable = (editor, forward) + => {\n const fromPos = CaretPosition.fromRangeStart(editor.selection.getRng());\n + \ return isNearTable(forward, fromPos) || fromPosition(forward, editor.getBody(), + fromPos).exists((pos) => isNearTable(forward, pos));\n };\n const + deleteCaret$3 = (editor, forward, startElm) => {\n const rootElm = + SugarElement.fromDom(editor.getBody());\n return getParentCaption(rootElm, + startElm).fold(() => deleteCaretCells(editor, forward, rootElm, startElm).orThunk(() + => someIf(isBeforeOrAfterTable(editor, forward), noop)), (fromCaption) => + deleteCaretCaption(editor, forward, rootElm, fromCaption));\n };\n const + backspaceDelete$9 = (editor, forward) => {\n const startElm = SugarElement.fromDom(editor.selection.getStart(true));\n + \ const cells2 = getCellsFromEditor(editor);\n return editor.selection.isCollapsed() + && cells2.length === 0 ? deleteCaret$3(editor, forward, startElm) : deleteRange$2(editor, + startElm, cells2);\n };\n const getContentEditableRoot$1 = (root2, + node) => {\n while (node && node !== root2) {\n if (isContentEditableTrue$4(node) + || isContentEditableFalse$a(node)) {\n return node;\n }\n + \ node = node.parentNode;\n }\n return null;\n };\n + \ const traverse2 = (node, fn) => {\n fn(node);\n if (node.firstChild) + {\n traverse2(node.firstChild, fn);\n }\n if (node.next) + {\n traverse2(node.next, fn);\n }\n };\n const matchNode$1 + = (nodeFilters, attributeFilters, node, matches) => {\n const name2 + = node.name;\n for (let ni = 0, nl = nodeFilters.length; ni < nl; ni++) + {\n const filter2 = nodeFilters[ni];\n if (filter2.name + === name2) {\n const match4 = matches.nodes[name2];\n if + (match4) {\n match4.nodes.push(node);\n } else {\n + \ matches.nodes[name2] = {\n filter: filter2,\n + \ nodes: [node]\n };\n }\n }\n + \ }\n if (node.attributes) {\n for (let ai = 0, al = + attributeFilters.length; ai < al; ai++) {\n const filter2 = attributeFilters[ai];\n + \ const attrName = filter2.name;\n if (attrName in node.attributes.map) + {\n const match4 = matches.attributes[attrName];\n if + (match4) {\n match4.nodes.push(node);\n } else + {\n matches.attributes[attrName] = {\n filter: + filter2,\n nodes: [node]\n };\n }\n + \ }\n }\n }\n };\n const findMatchingNodes + = (nodeFilters, attributeFilters, node) => {\n const matches = {\n + \ nodes: {},\n attributes: {}\n };\n if (node.firstChild) + {\n traverse2(node.firstChild, (node2) => {\n matchNode$1(nodeFilters, + attributeFilters, node2, matches);\n });\n }\n return + matches;\n };\n const runFilters = (matches, args) => {\n const + run = (matchRecord) => {\n each$f(matchRecord, (match4) => {\n const + nodes = filter$6(match4.nodes, (node) => isNonNullable(node.parent));\n each$g(match4.filter.callbacks, + (callback) => {\n callback(nodes, match4.filter.name, args);\n + \ });\n });\n };\n run(matches.nodes);\n + \ run(matches.attributes);\n };\n const filter$3 = (nodeFilters, + attributeFilters, node, args = {}) => {\n const matches = findMatchingNodes(nodeFilters, + attributeFilters, node);\n runFilters(matches, args);\n };\n const + paddEmptyNode = (settings, args, blockElements, node) => {\n if (args.insert + && blockElements[node.name]) {\n node.empty().append(new AstNode(\"br\", + 1));\n } else {\n node.empty().append(new AstNode(\"#text\", + 3)).value = nbsp;\n }\n };\n const isPaddedWithNbsp = (node) + => hasOnlyChild(node, \"#text\") && node.firstChild.value === nbsp;\n const + hasOnlyChild = (node, name2) => node && node.firstChild && node.firstChild + === node.lastChild && node.firstChild.name === name2;\n const isPadded + = (schema, node) => {\n const rule = schema.getElementRule(node.name);\n + \ return rule && rule.paddEmpty;\n };\n const isEmpty2 = (schema, + nonEmptyElements, whitespaceElements, node) => node.isEmpty(nonEmptyElements, + whitespaceElements, (node2) => isPadded(schema, node2));\n const isLineBreakNode + = (node, blockElements) => node && (node.name in blockElements || node.name + === \"br\");\n const removeOrUnwrapInvalidNode = (node, schema, originalNodeParent + = node.parent) => {\n if (schema.getSpecialElements()[node.name]) {\n + \ node.empty().remove();\n } else {\n const children2 + = node.children();\n for (const childNode of children2) {\n if + (!schema.isValidChild(originalNodeParent.name, childNode.name)) {\n removeOrUnwrapInvalidNode(childNode, + schema, originalNodeParent);\n }\n }\n node.unwrap();\n + \ }\n };\n const cleanInvalidNodes = (nodes, schema, onCreate + = noop) => {\n const textBlockElements = schema.getTextBlockElements();\n + \ const nonEmptyElements = schema.getNonEmptyElements();\n const + whitespaceElements = schema.getWhitespaceElements();\n const nonSplittableElements + = Tools.makeMap(\"tr,td,th,tbody,thead,tfoot,table\");\n const fixed + = /* @__PURE__ */ new Set();\n for (let ni = 0; ni < nodes.length; + ni++) {\n const node = nodes[ni];\n let parent2;\n let + newParent;\n let tempNode;\n if (!node.parent || fixed.has(node)) + {\n continue;\n }\n if (textBlockElements[node.name] + && node.parent.name === \"li\") {\n let sibling2 = node.next;\n + \ while (sibling2) {\n if (textBlockElements[sibling2.name]) + {\n sibling2.name = \"li\";\n fixed.add(sibling2);\n + \ node.parent.insert(sibling2, node.parent);\n } + else {\n break;\n }\n sibling2 = + sibling2.next;\n }\n node.unwrap();\n continue;\n + \ }\n const parents2 = [node];\n for (parent2 = + node.parent; parent2 && !schema.isValidChild(parent2.name, node.name) && !nonSplittableElements[parent2.name]; + parent2 = parent2.parent) {\n parents2.push(parent2);\n }\n + \ if (parent2 && parents2.length > 1) {\n if (schema.isValidChild(parent2.name, + node.name)) {\n parents2.reverse();\n newParent + = parents2[0].clone();\n onCreate(newParent);\n let + currentNode = newParent;\n for (let i3 = 0; i3 < parents2.length + - 1; i3++) {\n if (schema.isValidChild(currentNode.name, parents2[i3].name)) + {\n tempNode = parents2[i3].clone();\n onCreate(tempNode);\n + \ currentNode.append(tempNode);\n } else {\n + \ tempNode = currentNode;\n }\n for + (let childNode = parents2[i3].firstChild; childNode && childNode !== parents2[i3 + + 1]; ) {\n const nextNode = childNode.next;\n tempNode.append(childNode);\n + \ childNode = nextNode;\n }\n currentNode + = tempNode;\n }\n if (!isEmpty2(schema, nonEmptyElements, + whitespaceElements, newParent)) {\n parent2.insert(newParent, + parents2[0], true);\n parent2.insert(node, newParent);\n } + else {\n parent2.insert(node, parents2[0], true);\n }\n + \ parent2 = parents2[0];\n if (isEmpty2(schema, nonEmptyElements, + whitespaceElements, parent2) || hasOnlyChild(parent2, \"br\")) {\n parent2.empty().remove();\n + \ }\n } else {\n removeOrUnwrapInvalidNode(node, + schema);\n }\n } else if (node.parent) {\n if + (node.name === \"li\") {\n let sibling2 = node.prev;\n if + (sibling2 && (sibling2.name === \"ul\" || sibling2.name === \"ol\")) {\n sibling2.append(node);\n + \ continue;\n }\n sibling2 = node.next;\n + \ if (sibling2 && (sibling2.name === \"ul\" || sibling2.name === + \"ol\")) {\n sibling2.insert(node, sibling2.firstChild, true);\n + \ continue;\n }\n const wrapper = + new AstNode(\"ul\", 1);\n onCreate(wrapper);\n node.wrap(wrapper);\n + \ continue;\n }\n if (schema.isValidChild(node.parent.name, + \"div\") && schema.isValidChild(\"div\", node.name)) {\n const + wrapper = new AstNode(\"div\", 1);\n onCreate(wrapper);\n node.wrap(wrapper);\n + \ } else {\n removeOrUnwrapInvalidNode(node, schema);\n + \ }\n }\n }\n };\n const createRange = + (sc, so, ec, eo) => {\n const rng = document.createRange();\n rng.setStart(sc, + so);\n rng.setEnd(ec, eo);\n return rng;\n };\n const + normalizeBlockSelectionRange = (rng) => {\n const startPos = CaretPosition.fromRangeStart(rng);\n + \ const endPos = CaretPosition.fromRangeEnd(rng);\n const rootNode + = rng.commonAncestorContainer;\n return fromPosition(false, rootNode, + endPos).map((newEndPos) => {\n if (!isInSameBlock(startPos, endPos, + rootNode) && isInSameBlock(startPos, newEndPos, rootNode)) {\n return + createRange(startPos.container(), startPos.offset(), newEndPos.container(), + newEndPos.offset());\n } else {\n return rng;\n }\n + \ }).getOr(rng);\n };\n const normalize3 = (rng) => rng.collapsed + ? rng : normalizeBlockSelectionRange(rng);\n const hasOnlyOneChild$1 + = (node) => {\n return node.firstChild && node.firstChild === node.lastChild;\n + \ };\n const isPaddingNode = (node) => {\n return node.name + === \"br\" || node.value === nbsp;\n };\n const isPaddedEmptyBlock + = (schema, node) => {\n const blockElements = schema.getBlockElements();\n + \ return blockElements[node.name] && hasOnlyOneChild$1(node) && isPaddingNode(node.firstChild);\n + \ };\n const isEmptyFragmentElement = (schema, node) => {\n const + nonEmptyElements = schema.getNonEmptyElements();\n return node && (node.isEmpty(nonEmptyElements) + || isPaddedEmptyBlock(schema, node));\n };\n const isListFragment + = (schema, fragment) => {\n let firstChild2 = fragment.firstChild;\n + \ let lastChild2 = fragment.lastChild;\n if (firstChild2 && firstChild2.name + === \"meta\") {\n firstChild2 = firstChild2.next;\n }\n if + (lastChild2 && lastChild2.attr(\"id\") === \"mce_marker\") {\n lastChild2 + = lastChild2.prev;\n }\n if (isEmptyFragmentElement(schema, + lastChild2)) {\n lastChild2 = lastChild2.prev;\n }\n if + (!firstChild2 || firstChild2 !== lastChild2) {\n return false;\n + \ }\n return firstChild2.name === \"ul\" || firstChild2.name + === \"ol\";\n };\n const cleanupDomFragment = (domFragment) => {\n + \ const firstChild2 = domFragment.firstChild;\n const lastChild2 + = domFragment.lastChild;\n if (firstChild2 && firstChild2.nodeName + === \"META\") {\n firstChild2.parentNode.removeChild(firstChild2);\n + \ }\n if (lastChild2 && lastChild2.id === \"mce_marker\") {\n + \ lastChild2.parentNode.removeChild(lastChild2);\n }\n return + domFragment;\n };\n const toDomFragment = (dom3, serializer, fragment) + => {\n const html2 = serializer.serialize(fragment);\n const + domFragment = dom3.createFragment(html2);\n return cleanupDomFragment(domFragment);\n + \ };\n const listItems = (elm) => {\n return filter$6(elm.childNodes, + (child2) => {\n return child2.nodeName === \"LI\";\n });\n + \ };\n const isPadding = (node) => {\n return node.data === + nbsp || isBr$5(node);\n };\n const isListItemPadded = (node) => + {\n return node && node.firstChild && node.firstChild === node.lastChild + && isPadding(node.firstChild);\n };\n const isEmptyOrPadded = (elm) + => {\n return !elm.firstChild || isListItemPadded(elm);\n };\n + \ const trimListItems = (elms) => {\n return elms.length > 0 && + isEmptyOrPadded(elms[elms.length - 1]) ? elms.slice(0, -1) : elms;\n };\n + \ const getParentLi = (dom3, node) => {\n const parentBlock = dom3.getParent(node, + dom3.isBlock);\n return parentBlock && parentBlock.nodeName === \"LI\" + ? parentBlock : null;\n };\n const isParentBlockLi = (dom3, node) + => {\n return !!getParentLi(dom3, node);\n };\n const getSplit + = (parentNode, rng) => {\n const beforeRng = rng.cloneRange();\n const + afterRng = rng.cloneRange();\n beforeRng.setStartBefore(parentNode);\n + \ afterRng.setEndAfter(parentNode);\n return [\n beforeRng.cloneContents(),\n + \ afterRng.cloneContents()\n ];\n };\n const findFirstIn + = (node, rootNode) => {\n const caretPos = CaretPosition.before(node);\n + \ const caretWalker = CaretWalker(rootNode);\n const newCaretPos + = caretWalker.next(caretPos);\n return newCaretPos ? newCaretPos.toRange() + : null;\n };\n const findLastOf = (node, rootNode) => {\n const + caretPos = CaretPosition.after(node);\n const caretWalker = CaretWalker(rootNode);\n + \ const newCaretPos = caretWalker.prev(caretPos);\n return newCaretPos + ? newCaretPos.toRange() : null;\n };\n const insertMiddle = (target, + elms, rootNode, rng) => {\n const parts = getSplit(target, rng);\n + \ const parentElm = target.parentNode;\n parentElm.insertBefore(parts[0], + target);\n Tools.each(elms, (li) => {\n parentElm.insertBefore(li, + target);\n });\n parentElm.insertBefore(parts[1], target);\n + \ parentElm.removeChild(target);\n return findLastOf(elms[elms.length + - 1], rootNode);\n };\n const insertBefore$1 = (target, elms, rootNode) + => {\n const parentElm = target.parentNode;\n Tools.each(elms, + (elm) => {\n parentElm.insertBefore(elm, target);\n });\n + \ return findFirstIn(target, rootNode);\n };\n const insertAfter$1 + = (target, elms, rootNode, dom3) => {\n dom3.insertAfter(elms.reverse(), + target);\n return findLastOf(elms[0], rootNode);\n };\n const + insertAtCaret$1 = (serializer, dom3, rng, fragment) => {\n const domFragment + = toDomFragment(dom3, serializer, fragment);\n const liTarget = getParentLi(dom3, + rng.startContainer);\n const liElms = trimListItems(listItems(domFragment.firstChild));\n + \ const BEGINNING = 1, END = 2;\n const rootNode = dom3.getRoot();\n + \ const isAt = (location2) => {\n const caretPos = CaretPosition.fromRangeStart(rng);\n + \ const caretWalker = CaretWalker(dom3.getRoot());\n const + newPos = location2 === BEGINNING ? caretWalker.prev(caretPos) : caretWalker.next(caretPos);\n + \ return newPos ? getParentLi(dom3, newPos.getNode()) !== liTarget + : true;\n };\n if (isAt(BEGINNING)) {\n return insertBefore$1(liTarget, + liElms, rootNode);\n } else if (isAt(END)) {\n return insertAfter$1(liTarget, + liElms, rootNode, dom3);\n }\n return insertMiddle(liTarget, + liElms, rootNode, rng);\n };\n const isTableCell$1 = isTableCell$5;\n + \ const isTableCellContentSelected = (dom3, rng, cell2) => {\n if + (cell2 !== null) {\n const endCell = dom3.getParent(rng.endContainer, + isTableCell$1);\n return cell2 === endCell && hasAllContentsSelected(SugarElement.fromDom(cell2), + rng);\n } else {\n return false;\n }\n };\n const + validInsertion = (editor, value2, parentNode) => {\n if (parentNode.getAttribute(\"data-mce-bogus\") + === \"all\") {\n parentNode.parentNode.insertBefore(editor.dom.createFragment(value2), + parentNode);\n } else {\n const node = parentNode.firstChild;\n + \ const node2 = parentNode.lastChild;\n if (!node || node + === node2 && node.nodeName === \"BR\") {\n editor.dom.setHTML(parentNode, + value2);\n } else {\n editor.selection.setContent(value2, + { no_events: true });\n }\n }\n };\n const trimBrsFromTableCell + = (dom3, elm) => {\n Optional.from(dom3.getParent(elm, \"td,th\")).map(SugarElement.fromDom).each(trimBlockTrailingBr);\n + \ };\n const reduceInlineTextElements = (editor, merge3) => {\n const + textInlineElements = editor.schema.getTextInlineElements();\n const + dom3 = editor.dom;\n if (merge3) {\n const root2 = editor.getBody();\n + \ const elementUtils = ElementUtils(dom3);\n Tools.each(dom3.select(\"*[data-mce-fragment]\"), + (node) => {\n const isInline2 = isNonNullable(textInlineElements[node.nodeName.toLowerCase()]);\n + \ if (isInline2 && hasInheritableStyles(dom3, node)) {\n for + (let parentNode = node.parentNode; isNonNullable(parentNode) && parentNode + !== root2; parentNode = parentNode.parentNode) {\n const styleConflict + = hasStyleConflict(dom3, node, parentNode);\n if (styleConflict) + {\n break;\n }\n if (elementUtils.compare(parentNode, + node)) {\n dom3.remove(node, true);\n break;\n + \ }\n }\n }\n });\n }\n + \ };\n const markFragmentElements = (fragment) => {\n let + node = fragment;\n while (node = node.walk()) {\n if (node.type + === 1) {\n node.attr(\"data-mce-fragment\", \"1\");\n }\n + \ }\n };\n const unmarkFragmentElements = (elm) => {\n Tools.each(elm.getElementsByTagName(\"*\"), + (elm2) => {\n elm2.removeAttribute(\"data-mce-fragment\");\n });\n + \ };\n const isPartOfFragment = (node) => {\n return !!node.getAttribute(\"data-mce-fragment\");\n + \ };\n const canHaveChildren = (editor, node) => {\n return + node && !editor.schema.getVoidElements()[node.nodeName];\n };\n const + moveSelectionToMarker = (editor, marker) => {\n let nextRng;\n const + dom3 = editor.dom;\n const selection = editor.selection;\n if + (!marker) {\n return;\n }\n selection.scrollIntoView(marker);\n + \ const parentEditableElm = getContentEditableRoot$1(editor.getBody(), + marker);\n if (dom3.getContentEditable(parentEditableElm) === \"false\") + {\n dom3.remove(marker);\n selection.select(parentEditableElm);\n + \ return;\n }\n let rng = dom3.createRng();\n const + node = marker.previousSibling;\n if (isText$8(node)) {\n rng.setStart(node, + node.nodeValue.length);\n const node2 = marker.nextSibling;\n if + (isText$8(node2)) {\n node.appendData(node2.data);\n node2.parentNode.removeChild(node2);\n + \ }\n } else {\n rng.setStartBefore(marker);\n rng.setEndBefore(marker);\n + \ }\n const findNextCaretRng = (rng2) => {\n let caretPos + = CaretPosition.fromRangeStart(rng2);\n const caretWalker = CaretWalker(editor.getBody());\n + \ caretPos = caretWalker.next(caretPos);\n if (caretPos) + {\n return caretPos.toRange();\n }\n };\n const + parentBlock = dom3.getParent(marker, dom3.isBlock);\n dom3.remove(marker);\n + \ if (parentBlock && dom3.isEmpty(parentBlock)) {\n empty(SugarElement.fromDom(parentBlock));\n + \ rng.setStart(parentBlock, 0);\n rng.setEnd(parentBlock, + 0);\n if (!isTableCell$1(parentBlock) && !isPartOfFragment(parentBlock) + && (nextRng = findNextCaretRng(rng))) {\n rng = nextRng;\n dom3.remove(parentBlock);\n + \ } else {\n dom3.add(parentBlock, dom3.create(\"br\", + { \"data-mce-bogus\": \"1\" }));\n }\n }\n selection.setRng(rng);\n + \ };\n const deleteSelectedContent = (editor) => {\n const + dom3 = editor.dom;\n const rng = normalize3(editor.selection.getRng());\n + \ editor.selection.setRng(rng);\n const startCell = dom3.getParent(rng.startContainer, + isTableCell$1);\n if (isTableCellContentSelected(dom3, rng, startCell)) + {\n deleteCellContents(editor, rng, SugarElement.fromDom(startCell));\n + \ } else {\n editor.getDoc().execCommand(\"Delete\", false, + null);\n }\n };\n const insertHtmlAtCaret = (editor, value2, + details) => {\n let parentNode;\n let rng, node;\n const + selection = editor.selection;\n const dom3 = editor.dom;\n const + parser = editor.parser;\n const merge3 = details.merge;\n const + serializer = HtmlSerializer({ validate: true }, editor.schema);\n const + bookmarkHtml = '';\n + \ if (value2.indexOf(\"{$caret}\") === -1) {\n value2 += \"{$caret}\";\n + \ }\n value2 = value2.replace(/\\{\\$caret\\}/, bookmarkHtml);\n + \ rng = selection.getRng();\n const caretElement = rng.startContainer + || (rng.parentElement ? rng.parentElement() : null);\n const body = + editor.getBody();\n if (caretElement === body && selection.isCollapsed()) + {\n if (dom3.isBlock(body.firstChild) && canHaveChildren(editor, + body.firstChild) && dom3.isEmpty(body.firstChild)) {\n rng = dom3.createRng();\n + \ rng.setStart(body.firstChild, 0);\n rng.setEnd(body.firstChild, + 0);\n selection.setRng(rng);\n }\n }\n if + (!selection.isCollapsed()) {\n deleteSelectedContent(editor);\n }\n + \ parentNode = selection.getNode();\n const parserArgs = {\n + \ context: parentNode.nodeName.toLowerCase(),\n data: details.data,\n + \ insert: true\n };\n const fragment = parser.parse(value2, + parserArgs);\n if (details.paste === true && isListFragment(editor.schema, + fragment) && isParentBlockLi(dom3, parentNode)) {\n rng = insertAtCaret$1(serializer, + dom3, selection.getRng(), fragment);\n selection.setRng(rng);\n return + value2;\n }\n markFragmentElements(fragment);\n node + = fragment.lastChild;\n if (node.attr(\"id\") === \"mce_marker\") {\n + \ const marker = node;\n for (node = node.prev; node; node + = node.walk(true)) {\n if (node.type === 3 || !dom3.isBlock(node.name)) + {\n if (editor.schema.isValidChild(node.parent.name, \"span\")) + {\n node.parent.insert(marker, node, node.name === \"br\");\n + \ }\n break;\n }\n }\n }\n + \ editor._selectionOverrides.showBlockCaretContainer(parentNode);\n + \ if (!parserArgs.invalid) {\n value2 = serializer.serialize(fragment);\n + \ validInsertion(editor, value2, parentNode);\n } else {\n + \ editor.selection.setContent(bookmarkHtml);\n parentNode + = selection.getNode();\n const rootNode = editor.getBody();\n if + (parentNode.nodeType === 9) {\n parentNode = node = rootNode;\n + \ } else {\n node = parentNode;\n }\n while + (node !== rootNode) {\n parentNode = node;\n node = + node.parentNode;\n }\n value2 = parentNode === rootNode + ? rootNode.innerHTML : dom3.getOuterHTML(parentNode);\n const root2 + = parser.parse(value2);\n for (let markerNode = root2; markerNode; + markerNode = markerNode.walk()) {\n if (markerNode.attr(\"id\") + === \"mce_marker\") {\n markerNode.replace(fragment);\n break;\n + \ }\n }\n const toExtract = fragment.children();\n + \ const parent2 = fragment.parent.name;\n fragment.unwrap();\n + \ const invalidChildren = filter$6(toExtract, (node2) => !editor.schema.isValidChild(parent2, + node2.name));\n cleanInvalidNodes(invalidChildren, editor.schema);\n + \ filter$3(parser.getNodeFilters(), parser.getAttributeFilters(), + root2);\n value2 = serializer.serialize(root2);\n if (parentNode + === rootNode) {\n dom3.setHTML(rootNode, value2);\n } + else {\n dom3.setOuterHTML(parentNode, value2);\n }\n + \ }\n reduceInlineTextElements(editor, merge3);\n moveSelectionToMarker(editor, + dom3.get(\"mce_marker\"));\n unmarkFragmentElements(editor.getBody());\n + \ trimBrsFromTableCell(dom3, selection.getStart());\n return + value2;\n };\n const isTreeNode = (content) => content instanceof + AstNode;\n const moveSelection = (editor) => {\n if (hasFocus(editor)) + {\n firstPositionIn(editor.getBody()).each((pos) => {\n const + node = pos.getNode();\n const caretPos = isTable$3(node) ? firstPositionIn(node).getOr(pos) + : pos;\n editor.selection.setRng(caretPos.toRange());\n });\n + \ }\n };\n const setEditorHtml = (editor, html2, noSelection) + => {\n editor.dom.setHTML(editor.getBody(), html2);\n if (noSelection + !== true) {\n moveSelection(editor);\n }\n };\n const + setContentString = (editor, body, content, args) => {\n if (content.length + === 0 || /^\\s+$/.test(content)) {\n const padd = '
';\n + \ if (body.nodeName === \"TABLE\") {\n content = \"\" + + padd + \"\";\n } else if (/^(UL|OL)$/.test(body.nodeName)) + {\n content = \"
  • \" + padd + \"
  • \";\n }\n const + forcedRootBlockName = getForcedRootBlock(editor);\n if (editor.schema.isValidChild(body.nodeName.toLowerCase(), + forcedRootBlockName.toLowerCase())) {\n content = padd;\n content + = editor.dom.createHTML(forcedRootBlockName, getForcedRootBlockAttrs(editor), + content);\n } else if (!content) {\n content = padd;\n + \ }\n setEditorHtml(editor, content, args.no_selection);\n + \ return {\n content,\n html: content\n };\n + \ } else {\n if (args.format !== \"raw\") {\n content + = HtmlSerializer({ validate: false }, editor.schema).serialize(editor.parser.parse(content, + {\n isRootContent: true,\n insert: true\n }));\n + \ }\n const trimmedHtml = isWsPreserveElement(SugarElement.fromDom(body)) + ? content : Tools.trim(content);\n setEditorHtml(editor, trimmedHtml, + args.no_selection);\n return {\n content: trimmedHtml,\n + \ html: trimmedHtml\n };\n }\n };\n const + setContentTree = (editor, body, content, args) => {\n filter$3(editor.parser.getNodeFilters(), + editor.parser.getAttributeFilters(), content);\n const html2 = HtmlSerializer({ + validate: false }, editor.schema).serialize(content);\n const trimmedHtml + = isWsPreserveElement(SugarElement.fromDom(body)) ? html2 : Tools.trim(html2);\n + \ setEditorHtml(editor, trimmedHtml, args.no_selection);\n return + {\n content,\n html: trimmedHtml\n };\n };\n + \ const setContentInternal = (editor, content, args) => {\n return + Optional.from(editor.getBody()).map((body) => {\n if (isTreeNode(content)) + {\n return setContentTree(editor, body, content, args);\n } + else {\n return setContentString(editor, body, content, args);\n + \ }\n }).getOr({\n content,\n html: isTreeNode(args.content) + ? \"\" : args.content\n });\n };\n const sibling = (scope, + predicate) => sibling$1(scope, predicate).isSome();\n const ensureIsRoot + = (isRoot) => isFunction2(isRoot) ? isRoot : never;\n const ancestor + = (scope, transform, isRoot) => {\n let element = scope.dom;\n const + stop2 = ensureIsRoot(isRoot);\n while (element.parentNode) {\n element + = element.parentNode;\n const el = SugarElement.fromDom(element);\n + \ const transformed = transform(el);\n if (transformed.isSome()) + {\n return transformed;\n } else if (stop2(el)) {\n break;\n + \ }\n }\n return Optional.none();\n };\n const + closest$2 = (scope, transform, isRoot) => {\n const current = transform(scope);\n + \ const stop2 = ensureIsRoot(isRoot);\n return current.orThunk(() + => stop2(scope) ? Optional.none() : ancestor(scope, transform, stop2));\n + \ };\n const isEq$3 = isEq$5;\n const matchesUnInheritedFormatSelector + = (ed, node, name2) => {\n const formatList = ed.formatter.get(name2);\n + \ if (formatList) {\n for (let i3 = 0; i3 < formatList.length; + i3++) {\n const format2 = formatList[i3];\n if (isSelectorFormat(format2) + && format2.inherit === false && ed.dom.is(node, format2.selector)) {\n return + true;\n }\n }\n }\n return false;\n };\n + \ const matchParents = (editor, node, name2, vars, similar) => {\n const + root2 = editor.dom.getRoot();\n if (node === root2) {\n return + false;\n }\n node = editor.dom.getParent(node, (node2) => {\n + \ if (matchesUnInheritedFormatSelector(editor, node2, name2)) {\n + \ return true;\n }\n return node2.parentNode === + root2 || !!matchNode(editor, node2, name2, vars, true);\n });\n return + !!matchNode(editor, node, name2, vars, similar);\n };\n const matchName + = (dom3, node, format2) => {\n if (isInlineFormat(format2) && isEq$3(node, + format2.inline)) {\n return true;\n }\n if (isBlockFormat(format2) + && isEq$3(node, format2.block)) {\n return true;\n }\n if + (isSelectorFormat(format2)) {\n return isElement$6(node) && dom3.is(node, + format2.selector);\n }\n return false;\n };\n const + matchItems = (dom3, node, format2, itemName, similar, vars) => {\n const + items = format2[itemName];\n if (isFunction2(format2.onmatch)) {\n + \ return format2.onmatch(node, format2, itemName);\n }\n if + (items) {\n if (isUndefined2(items.length)) {\n for (const + key in items) {\n if (has$2(items, key)) {\n const + value2 = itemName === \"attributes\" ? dom3.getAttrib(node, key) : getStyle(dom3, + node, key);\n const expectedValue = replaceVars(items[key], + vars);\n const isEmptyValue = isNullable(value2) || isEmpty$3(value2);\n + \ if (isEmptyValue && isNullable(expectedValue)) {\n continue;\n + \ }\n if (similar && isEmptyValue && !format2.exact) + {\n return false;\n }\n if + ((!similar || format2.exact) && !isEq$3(value2, normalizeStyleValue(expectedValue, + key))) {\n return false;\n }\n }\n + \ }\n } else {\n for (let i3 = 0; i3 < items.length; + i3++) {\n if (itemName === \"attributes\" ? dom3.getAttrib(node, + items[i3]) : getStyle(dom3, node, items[i3])) {\n return true;\n + \ }\n }\n }\n }\n return true;\n + \ };\n const matchNode = (ed, node, name2, vars, similar) => {\n + \ const formatList = ed.formatter.get(name2);\n const dom3 = + ed.dom;\n if (formatList && node) {\n for (let i3 = 0; i3 + < formatList.length; i3++) {\n const format2 = formatList[i3];\n + \ if (matchName(ed.dom, node, format2) && matchItems(dom3, node, + format2, \"attributes\", similar, vars) && matchItems(dom3, node, format2, + \"styles\", similar, vars)) {\n const classes = format2.classes;\n + \ if (classes) {\n for (let x2 = 0; x2 < classes.length; + x2++) {\n if (!ed.dom.hasClass(node, replaceVars(classes[x2], + vars))) {\n return;\n }\n }\n + \ }\n return format2;\n }\n }\n + \ }\n };\n const match$2 = (editor, name2, vars, node, similar) + => {\n if (node) {\n return matchParents(editor, node, name2, + vars, similar);\n }\n node = editor.selection.getNode();\n if + (matchParents(editor, node, name2, vars, similar)) {\n return true;\n + \ }\n const startNode = editor.selection.getStart();\n if + (startNode !== node) {\n if (matchParents(editor, startNode, name2, + vars, similar)) {\n return true;\n }\n }\n return + false;\n };\n const matchAll = (editor, names, vars) => {\n const + matchedFormatNames = [];\n const checkedMap = {};\n const startElement + = editor.selection.getStart();\n editor.dom.getParent(startElement, + (node) => {\n for (let i3 = 0; i3 < names.length; i3++) {\n const + name2 = names[i3];\n if (!checkedMap[name2] && matchNode(editor, + node, name2, vars)) {\n checkedMap[name2] = true;\n matchedFormatNames.push(name2);\n + \ }\n }\n }, editor.dom.getRoot());\n return + matchedFormatNames;\n };\n const closest$1 = (editor, names) => + {\n const isRoot = (elm) => eq2(elm, SugarElement.fromDom(editor.getBody()));\n + \ const match4 = (elm, name2) => matchNode(editor, elm.dom, name2) ? + Optional.some(name2) : Optional.none();\n return Optional.from(editor.selection.getStart(true)).bind((rawElm) + => closest$2(SugarElement.fromDom(rawElm), (elm) => findMap(names, (name2) + => match4(elm, name2)), isRoot)).getOrNull();\n };\n const canApply + = (editor, name2) => {\n const formatList = editor.formatter.get(name2);\n + \ const dom3 = editor.dom;\n if (formatList) {\n const + startNode = editor.selection.getStart();\n const parents2 = getParents$2(dom3, + startNode);\n for (let x2 = formatList.length - 1; x2 >= 0; x2--) + {\n const format2 = formatList[x2];\n if (!isSelectorFormat(format2)) + {\n return true;\n }\n for (let i3 = parents2.length + - 1; i3 >= 0; i3--) {\n if (dom3.is(parents2[i3], format2.selector)) + {\n return true;\n }\n }\n }\n + \ }\n return false;\n };\n const matchAllOnNode = (editor, + node, formatNames) => foldl(formatNames, (acc, name2) => {\n const + matchSimilar = isVariableFormatName(editor, name2);\n if (editor.formatter.matchNode(node, + name2, {}, matchSimilar)) {\n return acc.concat([name2]);\n } + else {\n return acc;\n }\n }, []);\n const ZWSP + = ZWSP$1, CARET_ID = \"_mce_caret\";\n const importNode = (ownerDocument, + node) => {\n return ownerDocument.importNode(node, true);\n };\n + \ const getEmptyCaretContainers = (node) => {\n const nodes = [];\n + \ while (node) {\n if (node.nodeType === 3 && node.nodeValue + !== ZWSP || node.childNodes.length > 1) {\n return [];\n }\n + \ if (node.nodeType === 1) {\n nodes.push(node);\n }\n + \ node = node.firstChild;\n }\n return nodes;\n };\n + \ const isCaretContainerEmpty = (node) => {\n return getEmptyCaretContainers(node).length + > 0;\n };\n const findFirstTextNode = (node) => {\n if (node) + {\n const walker = new DomTreeWalker(node, node);\n for + (node = walker.current(); node; node = walker.next()) {\n if (isText$8(node)) + {\n return node;\n }\n }\n }\n return + null;\n };\n const createCaretContainer = (fill) => {\n const + caretContainer = SugarElement.fromTag(\"span\");\n setAll$1(caretContainer, + {\n \"id\": CARET_ID,\n \"data-mce-bogus\": \"1\",\n \"data-mce-type\": + \"format-caret\"\n });\n if (fill) {\n append$1(caretContainer, + SugarElement.fromText(ZWSP));\n }\n return caretContainer;\n + \ };\n const trimZwspFromCaretContainer = (caretContainerNode) => + {\n const textNode = findFirstTextNode(caretContainerNode);\n if + (textNode && textNode.nodeValue.charAt(0) === ZWSP) {\n textNode.deleteData(0, + 1);\n }\n return textNode;\n };\n const removeCaretContainerNode + = (editor, node, moveCaret2 = true) => {\n const dom3 = editor.dom, + selection = editor.selection;\n if (isCaretContainerEmpty(node)) {\n + \ deleteElement$2(editor, false, SugarElement.fromDom(node), moveCaret2);\n + \ } else {\n const rng = selection.getRng();\n const + block2 = dom3.getParent(node, dom3.isBlock);\n const startContainer + = rng.startContainer;\n const startOffset = rng.startOffset;\n const + endContainer = rng.endContainer;\n const endOffset = rng.endOffset;\n + \ const textNode = trimZwspFromCaretContainer(node);\n dom3.remove(node, + true);\n if (startContainer === textNode && startOffset > 0) {\n + \ rng.setStart(textNode, startOffset - 1);\n }\n if + (endContainer === textNode && endOffset > 0) {\n rng.setEnd(textNode, + endOffset - 1);\n }\n if (block2 && dom3.isEmpty(block2)) + {\n fillWithPaddingBr(SugarElement.fromDom(block2));\n }\n + \ selection.setRng(rng);\n }\n };\n const removeCaretContainer + = (editor, node, moveCaret2 = true) => {\n const dom3 = editor.dom, + selection = editor.selection;\n if (!node) {\n node = getParentCaretContainer(editor.getBody(), + selection.getStart());\n if (!node) {\n while (node = + dom3.get(CARET_ID)) {\n removeCaretContainerNode(editor, node, + false);\n }\n }\n } else {\n removeCaretContainerNode(editor, + node, moveCaret2);\n }\n };\n const insertCaretContainerNode + = (editor, caretContainer, formatNode) => {\n const dom3 = editor.dom, + block2 = dom3.getParent(formatNode, curry(isTextBlock$1, editor));\n if + (block2 && dom3.isEmpty(block2)) {\n formatNode.parentNode.replaceChild(caretContainer, + formatNode);\n } else {\n removeTrailingBr(SugarElement.fromDom(formatNode));\n + \ if (dom3.isEmpty(formatNode)) {\n formatNode.parentNode.replaceChild(caretContainer, + formatNode);\n } else {\n dom3.insertAfter(caretContainer, + formatNode);\n }\n }\n };\n const appendNode = (parentNode, + node) => {\n parentNode.appendChild(node);\n return node;\n + \ };\n const insertFormatNodesIntoCaretContainer = (formatNodes, + caretContainer) => {\n const innerMostFormatNode = foldr(formatNodes, + (parentNode, formatNode) => {\n return appendNode(parentNode, formatNode.cloneNode(false));\n + \ }, caretContainer);\n return appendNode(innerMostFormatNode, + innerMostFormatNode.ownerDocument.createTextNode(ZWSP));\n };\n const + cleanFormatNode = (editor, caretContainer, formatNode, name2, vars, similar) + => {\n const formatter = editor.formatter;\n const dom3 = editor.dom;\n + \ const validFormats = filter$6(keys2(formatter.get()), (formatName) + => formatName !== name2 && !contains$1(formatName, \"removeformat\"));\n const + matchedFormats = matchAllOnNode(editor, formatNode, validFormats);\n const + uniqueFormats = filter$6(matchedFormats, (fmtName) => !areSimilarFormats(editor, + fmtName, name2));\n if (uniqueFormats.length > 0) {\n const + clonedFormatNode = formatNode.cloneNode(false);\n dom3.add(caretContainer, + clonedFormatNode);\n formatter.remove(name2, vars, clonedFormatNode, + similar);\n dom3.remove(clonedFormatNode);\n return Optional.some(clonedFormatNode);\n + \ } else {\n return Optional.none();\n }\n };\n + \ const applyCaretFormat = (editor, name2, vars) => {\n let caretContainer, + textNode;\n const selection = editor.selection;\n const selectionRng + = selection.getRng();\n let offset = selectionRng.startOffset;\n const + container = selectionRng.startContainer;\n const text3 = container.nodeValue;\n + \ caretContainer = getParentCaretContainer(editor.getBody(), selection.getStart());\n + \ if (caretContainer) {\n textNode = findFirstTextNode(caretContainer);\n + \ }\n const wordcharRegex = /[^\\s\\u00a0\\u00ad\\u200b\\ufeff]/;\n + \ if (text3 && offset > 0 && offset < text3.length && wordcharRegex.test(text3.charAt(offset)) + && wordcharRegex.test(text3.charAt(offset - 1))) {\n const bookmark + = selection.getBookmark();\n selectionRng.collapse(true);\n let + rng = expandRng(editor, selectionRng, editor.formatter.get(name2));\n rng + = split(rng);\n editor.formatter.apply(name2, vars, rng);\n selection.moveToBookmark(bookmark);\n + \ } else {\n if (!caretContainer || textNode.nodeValue !== + ZWSP) {\n caretContainer = importNode(editor.getDoc(), createCaretContainer(true).dom);\n + \ textNode = caretContainer.firstChild;\n selectionRng.insertNode(caretContainer);\n + \ offset = 1;\n editor.formatter.apply(name2, vars, caretContainer);\n + \ } else {\n editor.formatter.apply(name2, vars, caretContainer);\n + \ }\n selection.setCursorLocation(textNode, offset);\n }\n + \ };\n const removeCaretFormat = (editor, name2, vars, similar) => + {\n const dom3 = editor.dom;\n const selection = editor.selection;\n + \ let hasContentAfter, node, formatNode;\n const parents2 = [];\n + \ const rng = selection.getRng();\n const container = rng.startContainer;\n + \ const offset = rng.startOffset;\n node = container;\n if + (container.nodeType === 3) {\n if (offset !== container.nodeValue.length) + {\n hasContentAfter = true;\n }\n node = node.parentNode;\n + \ }\n while (node) {\n if (matchNode(editor, node, name2, + vars, similar)) {\n formatNode = node;\n break;\n }\n + \ if (node.nextSibling) {\n hasContentAfter = true;\n }\n + \ parents2.push(node);\n node = node.parentNode;\n }\n + \ if (!formatNode) {\n return;\n }\n if (hasContentAfter) + {\n const bookmark = selection.getBookmark();\n rng.collapse(true);\n + \ let expandedRng = expandRng(editor, rng, editor.formatter.get(name2), + true);\n expandedRng = split(expandedRng);\n editor.formatter.remove(name2, + vars, expandedRng, similar);\n selection.moveToBookmark(bookmark);\n + \ } else {\n const caretContainer = getParentCaretContainer(editor.getBody(), + formatNode);\n const newCaretContainer = createCaretContainer(false).dom;\n + \ insertCaretContainerNode(editor, newCaretContainer, caretContainer + !== null ? caretContainer : formatNode);\n const cleanedFormatNode + = cleanFormatNode(editor, newCaretContainer, formatNode, name2, vars, similar);\n + \ const caretTextNode = insertFormatNodesIntoCaretContainer(parents2.concat(cleanedFormatNode.toArray()), + newCaretContainer);\n removeCaretContainerNode(editor, caretContainer, + false);\n selection.setCursorLocation(caretTextNode, 1);\n if + (dom3.isEmpty(formatNode)) {\n dom3.remove(formatNode);\n }\n + \ }\n };\n const disableCaretContainer = (editor, keyCode) + => {\n const selection = editor.selection, body = editor.getBody();\n + \ removeCaretContainer(editor, null, false);\n if ((keyCode === + 8 || keyCode === 46) && selection.isCollapsed() && selection.getStart().innerHTML + === ZWSP) {\n removeCaretContainer(editor, getParentCaretContainer(body, + selection.getStart()));\n }\n if (keyCode === 37 || keyCode + === 39) {\n removeCaretContainer(editor, getParentCaretContainer(body, + selection.getStart()));\n }\n };\n const setup$u = (editor) + => {\n editor.on(\"mouseup keydown\", (e2) => {\n disableCaretContainer(editor, + e2.keyCode);\n });\n };\n const replaceWithCaretFormat = + (targetNode, formatNodes) => {\n const caretContainer = createCaretContainer(false);\n + \ const innerMost = insertFormatNodesIntoCaretContainer(formatNodes, + caretContainer.dom);\n before$3(SugarElement.fromDom(targetNode), caretContainer);\n + \ remove$5(SugarElement.fromDom(targetNode));\n return CaretPosition(innerMost, + 0);\n };\n const isFormatElement = (editor, element) => {\n const + inlineElements = editor.schema.getTextInlineElements();\n return has$2(inlineElements, + name(element)) && !isCaretNode(element.dom) && !isBogus$2(element.dom);\n + \ };\n const isEmptyCaretFormatElement = (element) => {\n return + isCaretNode(element.dom) && isCaretContainerEmpty(element.dom);\n };\n + \ const postProcessHooks = {};\n const filter$2 = filter$4;\n const + each$a = each$e;\n const addPostProcessHook = (name2, hook) => {\n const + hooks = postProcessHooks[name2];\n if (!hooks) {\n postProcessHooks[name2] + = [];\n }\n postProcessHooks[name2].push(hook);\n };\n + \ const postProcess$12 = (name2, editor) => {\n each$a(postProcessHooks[name2], + (hook) => {\n hook(editor);\n });\n };\n addPostProcessHook(\"pre\", + (editor) => {\n const rng = editor.selection.getRng();\n let + blocks2;\n const hasPreSibling = (pre) => {\n return isPre2(pre.previousSibling) + && indexOf(blocks2, pre.previousSibling) !== -1;\n };\n const + joinPre = (pre1, pre2) => {\n const sPre2 = SugarElement.fromDom(pre2);\n + \ const doc = documentOrOwner(sPre2).dom;\n remove$5(sPre2);\n + \ append(SugarElement.fromDom(pre1), [\n SugarElement.fromTag(\"br\", + doc),\n SugarElement.fromTag(\"br\", doc),\n ...children(sPre2)\n + \ ]);\n };\n const isPre2 = matchNodeNames([\"pre\"]);\n + \ if (!rng.collapsed) {\n blocks2 = editor.selection.getSelectedBlocks();\n + \ each$a(filter$2(filter$2(blocks2, isPre2), hasPreSibling), (pre) + => {\n joinPre(pre.previousSibling, pre);\n });\n }\n + \ });\n const each$9 = Tools.each;\n const isElementNode$1 = + (node) => isElement$6(node) && !isBookmarkNode$1(node) && !isCaretNode(node) + && !isBogus$2(node);\n const findElementSibling = (node, siblingName) + => {\n for (let sibling2 = node; sibling2; sibling2 = sibling2[siblingName]) + {\n if (isText$8(sibling2) && isNotEmpty(sibling2.data)) {\n return + node;\n }\n if (isElement$6(sibling2) && !isBookmarkNode$1(sibling2)) + {\n return sibling2;\n }\n }\n return node;\n + \ };\n const mergeSiblingsNodes = (dom3, prev2, next2) => {\n const + elementUtils = ElementUtils(dom3);\n if (prev2 && next2) {\n prev2 + = findElementSibling(prev2, \"previousSibling\");\n next2 = findElementSibling(next2, + \"nextSibling\");\n if (elementUtils.compare(prev2, next2)) {\n for + (let sibling2 = prev2.nextSibling; sibling2 && sibling2 !== next2; ) {\n const + tmpSibling = sibling2;\n sibling2 = sibling2.nextSibling;\n prev2.appendChild(tmpSibling);\n + \ }\n dom3.remove(next2);\n Tools.each(Tools.grep(next2.childNodes), + (node) => {\n prev2.appendChild(node);\n });\n return + prev2;\n }\n }\n return next2;\n };\n const + mergeSiblings = (dom3, format2, vars, node) => {\n if (node && format2.merge_siblings + !== false) {\n const newNode = mergeSiblingsNodes(dom3, getNonWhiteSpaceSibling(node), + node);\n mergeSiblingsNodes(dom3, newNode, getNonWhiteSpaceSibling(newNode, + true));\n }\n };\n const clearChildStyles = (dom3, format2, + node) => {\n if (format2.clear_child_styles) {\n const selector + = format2.links ? \"*:not(a)\" : \"*\";\n each$9(dom3.select(selector, + node), (node2) => {\n if (isElementNode$1(node2)) {\n each$9(format2.styles, + (value2, name2) => {\n dom3.setStyle(node2, name2, \"\");\n + \ });\n }\n });\n }\n };\n const + processChildElements = (node, filter2, process2) => {\n each$9(node.childNodes, + (node2) => {\n if (isElementNode$1(node2)) {\n if (filter2(node2)) + {\n process2(node2);\n }\n if (node2.hasChildNodes()) + {\n processChildElements(node2, filter2, process2);\n }\n + \ }\n });\n };\n const unwrapEmptySpan = (dom3, node) + => {\n if (node.nodeName === \"SPAN\" && dom3.getAttribs(node).length + === 0) {\n dom3.remove(node, true);\n }\n };\n const + hasStyle = (dom3, name2) => (node) => !!(node && getStyle(dom3, node, name2));\n + \ const applyStyle = (dom3, name2, value2) => (node) => {\n dom3.setStyle(node, + name2, value2);\n if (node.getAttribute(\"style\") === \"\") {\n node.removeAttribute(\"style\");\n + \ }\n unwrapEmptySpan(dom3, node);\n };\n const removeResult + = Adt.generate([\n { keep: [] },\n { rename: [\"name\"] },\n + \ { removed: [] }\n ]);\n const MCE_ATTR_RE = /^(src|href|style)$/;\n + \ const each$8 = Tools.each;\n const isEq$2 = isEq$5;\n const + isTableCellOrRow = (node) => /^(TR|TH|TD)$/.test(node.nodeName);\n const + isChildOfInlineParent = (dom3, node, parent2) => dom3.isChildOf(node, parent2) + && node !== parent2 && !dom3.isBlock(parent2);\n const getContainer = + (ed, rng, start2) => {\n let container = rng[start2 ? \"startContainer\" + : \"endContainer\"];\n let offset = rng[start2 ? \"startOffset\" : + \"endOffset\"];\n if (isElement$6(container)) {\n const lastIdx + = container.childNodes.length - 1;\n if (!start2 && offset) {\n offset--;\n + \ }\n container = container.childNodes[offset > lastIdx ? + lastIdx : offset];\n }\n if (isText$8(container) && start2 && + offset >= container.nodeValue.length) {\n container = new DomTreeWalker(container, + ed.getBody()).next() || container;\n }\n if (isText$8(container) + && !start2 && offset === 0) {\n container = new DomTreeWalker(container, + ed.getBody()).prev() || container;\n }\n return container;\n + \ };\n const normalizeTableSelection = (node, start2) => {\n const + prop = start2 ? \"firstChild\" : \"lastChild\";\n if (isTableCellOrRow(node) + && node[prop]) {\n const childNode = node[prop];\n if (node.nodeName + === \"TR\") {\n return childNode[prop] || childNode;\n } + else {\n return childNode;\n }\n }\n return + node;\n };\n const wrap$1 = (dom3, node, name2, attrs) => {\n const + wrapper = dom3.create(name2, attrs);\n node.parentNode.insertBefore(wrapper, + node);\n wrapper.appendChild(node);\n return wrapper;\n };\n + \ const wrapWithSiblings = (dom3, node, next2, name2, attrs) => {\n const + start2 = SugarElement.fromDom(node);\n const wrapper = SugarElement.fromDom(dom3.create(name2, + attrs));\n const siblings2 = next2 ? nextSiblings(start2) : prevSiblings(start2);\n + \ append(wrapper, siblings2);\n if (next2) {\n before$3(start2, + wrapper);\n prepend(wrapper, start2);\n } else {\n after$4(start2, + wrapper);\n append$1(wrapper, start2);\n }\n return + wrapper.dom;\n };\n const isColorFormatAndAnchor = (node, format2) + => format2.links && node.nodeName === \"A\";\n const removeNode = (ed, + node, format2) => {\n const parentNode = node.parentNode;\n let + rootBlockElm;\n const dom3 = ed.dom;\n const forcedRootBlock + = getForcedRootBlock(ed);\n if (isBlockFormat(format2)) {\n if + (parentNode === dom3.getRoot()) {\n if (!format2.list_block || + !isEq$2(node, format2.list_block)) {\n each$g(from(node.childNodes), + (node2) => {\n if (isValid(ed, forcedRootBlock, node2.nodeName.toLowerCase())) + {\n if (!rootBlockElm) {\n rootBlockElm + = wrap$1(dom3, node2, forcedRootBlock);\n dom3.setAttribs(rootBlockElm, + getForcedRootBlockAttrs(ed));\n } else {\n rootBlockElm.appendChild(node2);\n + \ }\n } else {\n rootBlockElm + = null;\n }\n });\n }\n }\n + \ }\n if (isMixedFormat(format2) && !isEq$2(format2.inline, node)) + {\n return;\n }\n dom3.remove(node, true);\n };\n + \ const removeFormatInternal = (ed, format2, vars, node, compareNode) + => {\n let stylesModified;\n const dom3 = ed.dom;\n if + (!matchName(dom3, node, format2) && !isColorFormatAndAnchor(node, format2)) + {\n return removeResult.keep();\n }\n const elm = node;\n + \ if (isInlineFormat(format2) && format2.remove === \"all\" && isArray$1(format2.preserve_attributes)) + {\n const attrsToPreserve = filter$6(dom3.getAttribs(elm), (attr) + => contains$2(format2.preserve_attributes, attr.name.toLowerCase()));\n dom3.removeAllAttribs(elm);\n + \ each$g(attrsToPreserve, (attr) => dom3.setAttrib(elm, attr.name, + attr.value));\n if (attrsToPreserve.length > 0) {\n return + removeResult.rename(\"span\");\n }\n }\n if (format2.remove + !== \"all\") {\n each$8(format2.styles, (value2, name2) => {\n value2 + = normalizeStyleValue(replaceVars(value2, vars), name2 + \"\");\n if + (isNumber2(name2)) {\n name2 = value2;\n compareNode + = null;\n }\n if (format2.remove_similar || (!compareNode + || isEq$2(getStyle(dom3, compareNode, name2), value2))) {\n dom3.setStyle(elm, + name2, \"\");\n }\n stylesModified = true;\n });\n + \ if (stylesModified && dom3.getAttrib(elm, \"style\") === \"\") {\n + \ elm.removeAttribute(\"style\");\n elm.removeAttribute(\"data-mce-style\");\n + \ }\n each$8(format2.attributes, (value2, name2) => {\n let + valueOut;\n value2 = replaceVars(value2, vars);\n if + (isNumber2(name2)) {\n name2 = value2;\n compareNode + = null;\n }\n if (format2.remove_similar || (!compareNode + || isEq$2(dom3.getAttrib(compareNode, name2), value2))) {\n if + (name2 === \"class\") {\n value2 = dom3.getAttrib(elm, name2);\n + \ if (value2) {\n valueOut = \"\";\n each$g(value2.split(/\\s+/), + (cls) => {\n if (/mce\\-\\w+/.test(cls)) {\n valueOut + += (valueOut ? \" \" : \"\") + cls;\n }\n });\n + \ if (valueOut) {\n dom3.setAttrib(elm, + name2, valueOut);\n return;\n }\n }\n + \ }\n if (MCE_ATTR_RE.test(name2)) {\n elm.removeAttribute(\"data-mce-\" + + name2);\n }\n if (name2 === \"style\" && matchNodeNames([\"li\"])(elm) + && dom3.getStyle(elm, \"list-style-type\") === \"none\") {\n elm.removeAttribute(name2);\n + \ dom3.setStyle(elm, \"list-style-type\", \"none\");\n return;\n + \ }\n if (name2 === \"class\") {\n elm.removeAttribute(\"className\");\n + \ }\n elm.removeAttribute(name2);\n }\n + \ });\n each$8(format2.classes, (value2) => {\n value2 + = replaceVars(value2, vars);\n if (!compareNode || dom3.hasClass(compareNode, + value2)) {\n dom3.removeClass(elm, value2);\n }\n + \ });\n const attrs = dom3.getAttribs(elm);\n for + (let i3 = 0; i3 < attrs.length; i3++) {\n const attrName = attrs[i3].nodeName;\n + \ if (attrName.indexOf(\"_\") !== 0 && attrName.indexOf(\"data-\") + !== 0) {\n return removeResult.keep();\n }\n }\n + \ }\n if (format2.remove !== \"none\") {\n removeNode(ed, + elm, format2);\n return removeResult.removed();\n }\n return + removeResult.keep();\n };\n const removeFormat$1 = (ed, format2, + vars, node, compareNode) => removeFormatInternal(ed, format2, vars, node, + compareNode).fold(never, (newName) => {\n ed.dom.rename(node, newName);\n + \ return true;\n }, always);\n const findFormatRoot = (editor, + container, name2, vars, similar) => {\n let formatRoot;\n each$g(getParents$2(editor.dom, + container.parentNode).reverse(), (parent2) => {\n if (!formatRoot + && parent2.id !== \"_start\" && parent2.id !== \"_end\") {\n const + format2 = matchNode(editor, parent2, name2, vars, similar);\n if + (format2 && format2.split !== false) {\n formatRoot = parent2;\n + \ }\n }\n });\n return formatRoot;\n };\n + \ const removeFormatFromClone = (editor, format2, vars, clone2) => removeFormatInternal(editor, + format2, vars, clone2, clone2).fold(constant2(clone2), (newName) => {\n const + fragment = editor.dom.createFragment();\n fragment.appendChild(clone2);\n + \ return editor.dom.rename(clone2, newName);\n }, constant2(null));\n + \ const wrapAndSplit = (editor, formatList, formatRoot, container, target, + split2, format2, vars) => {\n let clone2, lastClone, firstClone;\n + \ const dom3 = editor.dom;\n if (formatRoot) {\n const + formatRootParent = formatRoot.parentNode;\n for (let parent2 = container.parentNode; + parent2 && parent2 !== formatRootParent; parent2 = parent2.parentNode) {\n + \ clone2 = dom3.clone(parent2, false);\n for (let i3 + = 0; i3 < formatList.length; i3++) {\n clone2 = removeFormatFromClone(editor, + formatList[i3], vars, clone2);\n if (clone2 === null) {\n break;\n + \ }\n }\n if (clone2) {\n if + (lastClone) {\n clone2.appendChild(lastClone);\n }\n + \ if (!firstClone) {\n firstClone = clone2;\n }\n + \ lastClone = clone2;\n }\n }\n if + (!format2.mixed || !dom3.isBlock(formatRoot)) {\n container = dom3.split(formatRoot, + container);\n }\n if (lastClone) {\n target.parentNode.insertBefore(lastClone, + target);\n firstClone.appendChild(target);\n if (isInlineFormat(format2)) + {\n mergeSiblings(dom3, format2, vars, lastClone);\n }\n + \ }\n }\n return container;\n };\n const remove$2 + = (ed, name2, vars, node, similar) => {\n const formatList = ed.formatter.get(name2);\n + \ const format2 = formatList[0];\n let contentEditable = true;\n + \ const dom3 = ed.dom;\n const selection = ed.selection;\n const + splitToFormatRoot = (container) => {\n const formatRoot = findFormatRoot(ed, + container, name2, vars, similar);\n return wrapAndSplit(ed, formatList, + formatRoot, container, container, true, format2, vars);\n };\n const + isRemoveBookmarkNode = (node2) => isBookmarkNode$1(node2) && isElement$6(node2) + && (node2.id === \"_start\" || node2.id === \"_end\");\n const removeNodeFormat + = (node2) => exists(formatList, (fmt) => removeFormat$1(ed, fmt, vars, node2, + node2));\n const process2 = (node2) => {\n let lastContentEditable + = true;\n let hasContentEditableState2 = false;\n if (isElement$6(node2) + && dom3.getContentEditable(node2)) {\n lastContentEditable = contentEditable;\n + \ contentEditable = dom3.getContentEditable(node2) === \"true\";\n + \ hasContentEditableState2 = true;\n }\n const + children2 = from(node2.childNodes);\n if (contentEditable && !hasContentEditableState2) + {\n const removed = removeNodeFormat(node2);\n const + currentNodeMatches = removed || exists(formatList, (f2) => matchName(dom3, + node2, f2));\n const parentNode = node2.parentNode;\n if + (!currentNodeMatches && isNonNullable(parentNode) && shouldExpandToSelector(format2)) + {\n removeNodeFormat(parentNode);\n }\n }\n + \ if (format2.deep) {\n if (children2.length) {\n for + (let i3 = 0; i3 < children2.length; i3++) {\n process2(children2[i3]);\n + \ }\n if (hasContentEditableState2) {\n contentEditable + = lastContentEditable;\n }\n }\n }\n const + textDecorations = [\n \"underline\",\n \"line-through\",\n + \ \"overline\"\n ];\n each$g(textDecorations, + (decoration) => {\n if (isElement$6(node2) && ed.dom.getStyle(node2, + \"text-decoration\") === decoration && node2.parentNode && getTextDecoration(dom3, + node2.parentNode) === decoration) {\n removeFormat$1(ed, {\n + \ deep: false,\n exact: true,\n inline: + \"span\",\n styles: { textDecoration: decoration }\n }, + null, node2);\n }\n });\n };\n const unwrap2 + = (start2) => {\n const node2 = dom3.get(start2 ? \"_start\" : \"_end\");\n + \ let out = node2[start2 ? \"firstChild\" : \"lastChild\"];\n if + (isRemoveBookmarkNode(out)) {\n out = out[start2 ? \"firstChild\" + : \"lastChild\"];\n }\n if (isText$8(out) && out.data.length + === 0) {\n out = start2 ? node2.previousSibling || node2.nextSibling + : node2.nextSibling || node2.previousSibling;\n }\n dom3.remove(node2, + true);\n return out;\n };\n const removeRngStyle = + (rng) => {\n let startContainer, endContainer;\n let expandedRng + = expandRng(ed, rng, formatList, rng.collapsed);\n if (format2.split) + {\n expandedRng = split(expandedRng);\n startContainer + = getContainer(ed, expandedRng, true);\n endContainer = getContainer(ed, + expandedRng);\n if (startContainer !== endContainer) {\n startContainer + = normalizeTableSelection(startContainer, true);\n endContainer + = normalizeTableSelection(endContainer, false);\n if (isChildOfInlineParent(dom3, + startContainer, endContainer)) {\n const marker = Optional.from(startContainer.firstChild).getOr(startContainer);\n + \ splitToFormatRoot(wrapWithSiblings(dom3, marker, true, \"span\", + {\n \"id\": \"_start\",\n \"data-mce-type\": + \"bookmark\"\n }));\n unwrap2(true);\n return;\n + \ }\n if (isChildOfInlineParent(dom3, endContainer, + startContainer)) {\n const marker = Optional.from(endContainer.lastChild).getOr(endContainer);\n + \ splitToFormatRoot(wrapWithSiblings(dom3, marker, false, \"span\", + {\n \"id\": \"_end\",\n \"data-mce-type\": + \"bookmark\"\n }));\n unwrap2(false);\n return;\n + \ }\n startContainer = wrap$1(dom3, startContainer, + \"span\", {\n \"id\": \"_start\",\n \"data-mce-type\": + \"bookmark\"\n });\n endContainer = wrap$1(dom3, + endContainer, \"span\", {\n \"id\": \"_end\",\n \"data-mce-type\": + \"bookmark\"\n });\n const newRng = dom3.createRng();\n + \ newRng.setStartAfter(startContainer);\n newRng.setEndBefore(endContainer);\n + \ walk$3(dom3, newRng, (nodes) => {\n each$g(nodes, + (n3) => {\n if (!isBookmarkNode$1(n3) && !isBookmarkNode$1(n3.parentNode)) + {\n splitToFormatRoot(n3);\n }\n });\n + \ });\n splitToFormatRoot(startContainer);\n splitToFormatRoot(endContainer);\n + \ startContainer = unwrap2(true);\n endContainer + = unwrap2();\n } else {\n startContainer = endContainer + = splitToFormatRoot(startContainer);\n }\n expandedRng.startContainer + = startContainer.parentNode ? startContainer.parentNode : startContainer;\n + \ expandedRng.startOffset = dom3.nodeIndex(startContainer);\n expandedRng.endContainer + = endContainer.parentNode ? endContainer.parentNode : endContainer;\n expandedRng.endOffset + = dom3.nodeIndex(endContainer) + 1;\n }\n walk$3(dom3, expandedRng, + (nodes) => {\n each$g(nodes, process2);\n });\n };\n + \ if (node) {\n if (isNode(node)) {\n const rng + = dom3.createRng();\n rng.setStartBefore(node);\n rng.setEndAfter(node);\n + \ removeRngStyle(rng);\n } else {\n removeRngStyle(node);\n + \ }\n fireFormatRemove(ed, name2, node, vars);\n return;\n + \ }\n if (dom3.getContentEditable(selection.getNode()) === \"false\") + {\n node = selection.getNode();\n for (let i3 = 0; i3 < + formatList.length; i3++) {\n if (formatList[i3].ceFalseOverride) + {\n if (removeFormat$1(ed, formatList[i3], vars, node, node)) + {\n break;\n }\n }\n }\n fireFormatRemove(ed, + name2, node, vars);\n return;\n }\n if (!selection.isCollapsed() + || !isInlineFormat(format2) || getCellsFromEditor(ed).length) {\n preserve(selection, + true, () => {\n runOnRanges(ed, removeRngStyle);\n });\n + \ if (isInlineFormat(format2) && match$2(ed, name2, vars, selection.getStart())) + {\n moveStart(dom3, selection, selection.getRng());\n }\n + \ ed.nodeChanged();\n } else {\n removeCaretFormat(ed, + name2, vars, similar);\n }\n fireFormatRemove(ed, name2, node, + vars);\n };\n const each$7 = Tools.each;\n const mergeTextDecorationsAndColor + = (dom3, format2, vars, node) => {\n const processTextDecorationsAndColor + = (n3) => {\n if (n3.nodeType === 1 && n3.parentNode && n3.parentNode.nodeType + === 1) {\n const textDecoration = getTextDecoration(dom3, n3.parentNode);\n + \ if (dom3.getStyle(n3, \"color\") && textDecoration) {\n dom3.setStyle(n3, + \"text-decoration\", textDecoration);\n } else if (dom3.getStyle(n3, + \"text-decoration\") === textDecoration) {\n dom3.setStyle(n3, + \"text-decoration\", null);\n }\n }\n };\n if + (format2.styles && (format2.styles.color || format2.styles.textDecoration)) + {\n Tools.walk(node, processTextDecorationsAndColor, \"childNodes\");\n + \ processTextDecorationsAndColor(node);\n }\n };\n const + mergeBackgroundColorAndFontSize = (dom3, format2, vars, node) => {\n if + (format2.styles && format2.styles.backgroundColor) {\n processChildElements(node, + hasStyle(dom3, \"fontSize\"), applyStyle(dom3, \"backgroundColor\", replaceVars(format2.styles.backgroundColor, + vars)));\n }\n };\n const mergeSubSup = (dom3, format2, vars, + node) => {\n if (isInlineFormat(format2) && (format2.inline === \"sub\" + || format2.inline === \"sup\")) {\n processChildElements(node, hasStyle(dom3, + \"fontSize\"), applyStyle(dom3, \"fontSize\", \"\"));\n dom3.remove(dom3.select(format2.inline + === \"sup\" ? \"sub\" : \"sup\", node), true);\n }\n };\n const + mergeWithChildren = (editor, formatList, vars, node) => {\n each$7(formatList, + (format2) => {\n if (isInlineFormat(format2)) {\n each$7(editor.dom.select(format2.inline, + node), (child2) => {\n if (!isElementNode$1(child2)) {\n return;\n + \ }\n removeFormat$1(editor, format2, vars, child2, + format2.exact ? child2 : null);\n });\n }\n clearChildStyles(editor.dom, + format2, node);\n });\n };\n const mergeWithParents = (editor, + format2, name2, vars, node) => {\n if (matchNode(editor, node.parentNode, + name2, vars)) {\n if (removeFormat$1(editor, format2, vars, node)) + {\n return;\n }\n }\n if (format2.merge_with_parents) + {\n editor.dom.getParent(node.parentNode, (parent2) => {\n if + (matchNode(editor, parent2, name2, vars)) {\n removeFormat$1(editor, + format2, vars, node);\n return true;\n }\n });\n + \ }\n };\n const each$6 = Tools.each;\n const isElementNode + = (node) => {\n return isElement$6(node) && !isBookmarkNode$1(node) + && !isCaretNode(node) && !isBogus$2(node);\n };\n const canFormatBR + = (editor, format2, node, parentName) => {\n if (canFormatEmptyLines(editor) + && isInlineFormat(format2)) {\n const validBRParentElements = {\n + \ ...editor.schema.getTextBlockElements(),\n td: {},\n + \ th: {},\n li: {},\n dt: {},\n dd: + {},\n figcaption: {},\n caption: {},\n details: + {},\n summary: {}\n };\n const hasCaretNodeSibling + = sibling(SugarElement.fromDom(node), (sibling2) => isCaretNode(sibling2.dom));\n + \ return hasNonNullableKey(validBRParentElements, parentName) && isEmpty$2(SugarElement.fromDom(node.parentNode), + false) && !hasCaretNodeSibling;\n } else {\n return false;\n + \ }\n };\n const applyFormat$1 = (ed, name2, vars, node) => + {\n const formatList = ed.formatter.get(name2);\n const format2 + = formatList[0];\n const isCollapsed = !node && ed.selection.isCollapsed();\n + \ const dom3 = ed.dom;\n const selection = ed.selection;\n const + setElementFormat = (elm, fmt = format2) => {\n if (isFunction2(fmt.onformat)) + {\n fmt.onformat(elm, fmt, vars, node);\n }\n each$6(fmt.styles, + (value2, name3) => {\n dom3.setStyle(elm, name3, replaceVars(value2, + vars));\n });\n if (fmt.styles) {\n const styleVal + = dom3.getAttrib(elm, \"style\");\n if (styleVal) {\n dom3.setAttrib(elm, + \"data-mce-style\", styleVal);\n }\n }\n each$6(fmt.attributes, + (value2, name3) => {\n dom3.setAttrib(elm, name3, replaceVars(value2, + vars));\n });\n each$6(fmt.classes, (value2) => {\n value2 + = replaceVars(value2, vars);\n if (!dom3.hasClass(elm, value2)) + {\n dom3.addClass(elm, value2);\n }\n });\n + \ };\n const applyNodeStyle = (formatList2, node2) => {\n let + found = false;\n each$6(formatList2, (format3) => {\n if + (!isSelectorFormat(format3)) {\n return false;\n }\n + \ if (isNonNullable(format3.collapsed) && format3.collapsed !== + isCollapsed) {\n return;\n }\n if (dom3.is(node2, + format3.selector) && !isCaretNode(node2)) {\n setElementFormat(node2, + format3);\n found = true;\n return false;\n }\n + \ });\n return found;\n };\n const createWrapElement + = (wrapName) => {\n if (isString2(wrapName)) {\n const + wrapElm = dom3.create(wrapName);\n setElementFormat(wrapElm);\n + \ return wrapElm;\n } else {\n return null;\n + \ }\n };\n const applyRngStyle = (dom4, rng, nodeSpecific) + => {\n const newWrappers = [];\n let contentEditable = true;\n + \ const wrapName = format2.inline || format2.block;\n const + wrapElm = createWrapElement(wrapName);\n walk$3(dom4, rng, (nodes) + => {\n let currentWrapElm;\n const process2 = (node2) + => {\n let hasContentEditableState2 = false;\n let + lastContentEditable = contentEditable;\n const nodeName = node2.nodeName.toLowerCase();\n + \ const parentNode = node2.parentNode;\n const parentName + = parentNode.nodeName.toLowerCase();\n if (isElement$6(node2) + && dom4.getContentEditable(node2)) {\n lastContentEditable + = contentEditable;\n contentEditable = dom4.getContentEditable(node2) + === \"true\";\n hasContentEditableState2 = true;\n }\n + \ if (isBr$5(node2) && !canFormatBR(ed, format2, node2, parentName)) + {\n currentWrapElm = null;\n if (isBlockFormat(format2)) + {\n dom4.remove(node2);\n }\n return;\n + \ }\n if (isBlockFormat(format2) && format2.wrapper + && matchNode(ed, node2, name2, vars)) {\n currentWrapElm = + null;\n return;\n }\n if (contentEditable + && !hasContentEditableState2 && isBlockFormat(format2) && !format2.wrapper + && isTextBlock$1(ed, nodeName) && isValid(ed, parentName, wrapName)) {\n const + elm = dom4.rename(node2, wrapName);\n setElementFormat(elm);\n + \ newWrappers.push(elm);\n currentWrapElm = null;\n + \ return;\n }\n if (isSelectorFormat(format2)) + {\n let found = applyNodeStyle(formatList, node2);\n if + (!found && isNonNullable(parentNode) && shouldExpandToSelector(format2)) {\n + \ found = applyNodeStyle(formatList, parentNode);\n }\n + \ if (!isInlineFormat(format2) || found) {\n currentWrapElm + = null;\n return;\n }\n }\n if + (contentEditable && !hasContentEditableState2 && isValid(ed, wrapName, nodeName) + && isValid(ed, parentName, wrapName) && !(!nodeSpecific && isText$8(node2) + && isZwsp(node2.data)) && !isCaretNode(node2) && (!isInlineFormat(format2) + || !dom4.isBlock(node2))) {\n if (!currentWrapElm) {\n currentWrapElm + = dom4.clone(wrapElm, false);\n node2.parentNode.insertBefore(currentWrapElm, + node2);\n newWrappers.push(currentWrapElm);\n }\n + \ currentWrapElm.appendChild(node2);\n } else {\n + \ currentWrapElm = null;\n each$g(from(node2.childNodes), + process2);\n if (hasContentEditableState2) {\n contentEditable + = lastContentEditable;\n }\n currentWrapElm + = null;\n }\n };\n each$g(nodes, process2);\n + \ });\n if (format2.links === true) {\n each$g(newWrappers, + (node2) => {\n const process2 = (node3) => {\n if + (node3.nodeName === \"A\") {\n setElementFormat(node3, format2);\n + \ }\n each$g(from(node3.childNodes), process2);\n + \ };\n process2(node2);\n });\n }\n + \ each$g(newWrappers, (node2) => {\n const getChildCount + = (node3) => {\n let count2 = 0;\n each$g(node3.childNodes, + (node4) => {\n if (!isEmptyTextNode$1(node4) && !isBookmarkNode$1(node4)) + {\n count2++;\n }\n });\n return + count2;\n };\n const mergeStyles = (node3) => {\n const + childElement = find$2(node3.childNodes, isElementNode).filter((child2) => + matchName(dom4, child2, format2));\n return childElement.map((child2) + => {\n const clone2 = dom4.clone(child2, false);\n setElementFormat(clone2);\n + \ dom4.replace(clone2, node3, true);\n dom4.remove(child2, + true);\n return clone2;\n }).getOr(node3);\n };\n + \ const childCount = getChildCount(node2);\n if ((newWrappers.length + > 1 || !dom4.isBlock(node2)) && childCount === 0) {\n dom4.remove(node2, + true);\n return;\n }\n if (isInlineFormat(format2) + || isBlockFormat(format2) && format2.wrapper) {\n if (!format2.exact + && childCount === 1) {\n node2 = mergeStyles(node2);\n }\n + \ mergeWithChildren(ed, formatList, vars, node2);\n mergeWithParents(ed, + format2, name2, vars, node2);\n mergeBackgroundColorAndFontSize(dom4, + format2, vars, node2);\n mergeTextDecorationsAndColor(dom4, format2, + vars, node2);\n mergeSubSup(dom4, format2, vars, node2);\n mergeSiblings(dom4, + format2, vars, node2);\n }\n });\n };\n if + (dom3.getContentEditable(selection.getNode()) === \"false\") {\n node + = selection.getNode();\n for (let i3 = 0, l2 = formatList.length; + i3 < l2; i3++) {\n const formatItem = formatList[i3];\n if + (formatItem.ceFalseOverride && isSelectorFormat(formatItem) && dom3.is(node, + formatItem.selector)) {\n setElementFormat(node, formatItem);\n + \ break;\n }\n }\n fireFormatApply(ed, + name2, node, vars);\n return;\n }\n if (format2) {\n + \ if (node) {\n if (isNode(node)) {\n if (!applyNodeStyle(formatList, + node)) {\n const rng = dom3.createRng();\n rng.setStartBefore(node);\n + \ rng.setEndAfter(node);\n applyRngStyle(dom3, + expandRng(ed, rng, formatList), true);\n }\n } else + {\n applyRngStyle(dom3, node, true);\n }\n } + else {\n if (!isCollapsed || !isInlineFormat(format2) || getCellsFromEditor(ed).length) + {\n selection.setRng(normalize3(selection.getRng()));\n preserve(selection, + true, () => {\n runOnRanges(ed, (selectionRng, fake) => {\n + \ const expandedRng = fake ? selectionRng : expandRng(ed, + selectionRng, formatList);\n applyRngStyle(dom3, expandedRng, + false);\n });\n });\n moveStart(dom3, + selection, selection.getRng());\n ed.nodeChanged();\n } + else {\n applyCaretFormat(ed, name2, vars);\n }\n + \ }\n postProcess$12(name2, ed);\n }\n fireFormatApply(ed, + name2, node, vars);\n };\n const hasVars = (value2) => has$2(value2, + \"vars\");\n const setup$t = (registeredFormatListeners, editor) => {\n + \ registeredFormatListeners.set({});\n editor.on(\"NodeChange\", + (e2) => {\n updateAndFireChangeCallbacks(editor, e2.element, registeredFormatListeners.get());\n + \ });\n editor.on(\"FormatApply FormatRemove\", (e2) => {\n const + element = Optional.from(e2.node).map((nodeOrRange) => isNode(nodeOrRange) + ? nodeOrRange : nodeOrRange.startContainer).bind((node) => isElement$6(node) + ? Optional.some(node) : Optional.from(node.parentElement)).getOrThunk(() => + fallbackElement(editor));\n updateAndFireChangeCallbacks(editor, + element, registeredFormatListeners.get());\n });\n };\n const + fallbackElement = (editor) => editor.selection.getStart();\n const matchingNode + = (editor, parents2, format2, similar, vars) => {\n const isMatchingNode + = (node) => {\n const matchingFormat = editor.formatter.matchNode(node, + format2, vars !== null && vars !== void 0 ? vars : {}, similar);\n return + !isUndefined2(matchingFormat);\n };\n const isUnableToMatch + = (node) => {\n if (matchesUnInheritedFormatSelector(editor, node, + format2)) {\n return true;\n } else {\n if + (!similar) {\n return isNonNullable(editor.formatter.matchNode(node, + format2, vars, true));\n } else {\n return false;\n + \ }\n }\n };\n return findUntil$1(parents2, + isMatchingNode, isUnableToMatch);\n };\n const getParents = (editor, + elm) => {\n const element = elm !== null && elm !== void 0 ? elm : + fallbackElement(editor);\n return filter$6(getParents$2(editor.dom, + element), (node) => isElement$6(node) && !isBogus$2(node));\n };\n const + updateAndFireChangeCallbacks = (editor, elm, registeredCallbacks) => {\n const + parents2 = getParents(editor, elm);\n each$f(registeredCallbacks, (data2, + format2) => {\n const runIfChanged = (spec) => {\n const + match4 = matchingNode(editor, parents2, format2, spec.similar, hasVars(spec) + ? spec.vars : void 0);\n const isSet2 = match4.isSome();\n if + (spec.state.get() !== isSet2) {\n spec.state.set(isSet2);\n const + node = match4.getOr(elm);\n if (hasVars(spec)) {\n spec.callback(isSet2, + {\n node,\n format: format2,\n parents: + parents2\n });\n } else {\n each$g(spec.callbacks, + (callback) => callback(isSet2, {\n node,\n format: + format2,\n parents: parents2\n }));\n }\n + \ }\n };\n each$g([\n data2.withSimilar,\n + \ data2.withoutSimilar\n ], runIfChanged);\n each$g(data2.withVars, + runIfChanged);\n });\n };\n const addListeners = (editor, + registeredFormatListeners, formats, callback, similar, vars) => {\n const + formatChangeItems = registeredFormatListeners.get();\n each$g(formats.split(\",\"), + (format2) => {\n const group = get$a(formatChangeItems, format2).getOrThunk(() + => {\n const base2 = {\n withSimilar: {\n state: + Cell(false),\n similar: true,\n callbacks: []\n + \ },\n withoutSimilar: {\n state: + Cell(false),\n similar: false,\n callbacks: + []\n },\n withVars: []\n };\n formatChangeItems[format2] + = base2;\n return base2;\n });\n const getCurrent + = () => {\n const parents2 = getParents(editor);\n return + matchingNode(editor, parents2, format2, similar, vars).isSome();\n };\n + \ if (isUndefined2(vars)) {\n const toAppendTo = similar + ? group.withSimilar : group.withoutSimilar;\n toAppendTo.callbacks.push(callback);\n + \ if (toAppendTo.callbacks.length === 1) {\n toAppendTo.state.set(getCurrent());\n + \ }\n } else {\n group.withVars.push({\n state: + Cell(getCurrent()),\n similar,\n vars,\n callback\n + \ });\n }\n });\n registeredFormatListeners.set(formatChangeItems);\n + \ };\n const removeListeners = (registeredFormatListeners, formats, + callback) => {\n const formatChangeItems = registeredFormatListeners.get();\n + \ each$g(formats.split(\",\"), (format2) => get$a(formatChangeItems, + format2).each((group) => {\n formatChangeItems[format2] = {\n withSimilar: + {\n ...group.withSimilar,\n callbacks: filter$6(group.withSimilar.callbacks, + (cb) => cb !== callback)\n },\n withoutSimilar: {\n + \ ...group.withoutSimilar,\n callbacks: filter$6(group.withoutSimilar.callbacks, + (cb) => cb !== callback)\n },\n withVars: filter$6(group.withVars, + (item) => item.callback !== callback)\n };\n }));\n registeredFormatListeners.set(formatChangeItems);\n + \ };\n const formatChangedInternal = (editor, registeredFormatListeners, + formats, callback, similar, vars) => {\n if (registeredFormatListeners.get() + === null) {\n setup$t(registeredFormatListeners, editor);\n }\n + \ addListeners(editor, registeredFormatListeners, formats, callback, + similar, vars);\n return { unbind: () => removeListeners(registeredFormatListeners, + formats, callback) };\n };\n const toggle = (editor, name2, vars, + node) => {\n const fmt = editor.formatter.get(name2);\n if (match$2(editor, + name2, vars, node) && (!(\"toggle\" in fmt[0]) || fmt[0].toggle)) {\n remove$2(editor, + name2, vars, node);\n } else {\n applyFormat$1(editor, name2, + vars, node);\n }\n };\n function _toConsumableArray(arr) + {\n if (Array.isArray(arr)) {\n for (var i3 = 0, arr2 = Array(arr.length); + i3 < arr.length; i3++) {\n arr2[i3] = arr[i3];\n }\n return + arr2;\n } else {\n return Array.from(arr);\n }\n }\n + \ var hasOwnProperty2 = Object.hasOwnProperty, setPrototypeOf = Object.setPrototypeOf, + isFrozen = Object.isFrozen, getPrototypeOf = Object.getPrototypeOf, getOwnPropertyDescriptor + = Object.getOwnPropertyDescriptor;\n var freeze = Object.freeze, seal + = Object.seal, create$7 = Object.create;\n var _ref = typeof Reflect + !== \"undefined\" && Reflect, apply2 = _ref.apply, construct = _ref.construct;\n + \ if (!apply2) {\n apply2 = function apply3(fun, thisValue, args) + {\n return fun.apply(thisValue, args);\n };\n }\n if + (!freeze) {\n freeze = function freeze2(x2) {\n return x2;\n + \ };\n }\n if (!seal) {\n seal = function seal2(x2) + {\n return x2;\n };\n }\n if (!construct) {\n construct + = function construct2(Func, args) {\n return new (Function.prototype.bind.apply(Func, + [null].concat(_toConsumableArray(args))))();\n };\n }\n var + arrayForEach = unapply(Array.prototype.forEach);\n var arrayPop = unapply(Array.prototype.pop);\n + \ var arrayPush2 = unapply(Array.prototype.push);\n var stringToLowerCase + = unapply(String.prototype.toLowerCase);\n var stringMatch = unapply(String.prototype.match);\n + \ var stringReplace = unapply(String.prototype.replace);\n var stringIndexOf + = unapply(String.prototype.indexOf);\n var stringTrim = unapply(String.prototype.trim);\n + \ var regExpTest = unapply(RegExp.prototype.test);\n var typeErrorCreate + = unconstruct(TypeError);\n function unapply(func) {\n return + function(thisArg) {\n for (var _len = arguments.length, args = Array(_len + > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {\n args[_key + - 1] = arguments[_key];\n }\n return apply2(func, thisArg, + args);\n };\n }\n function unconstruct(func) {\n return + function() {\n for (var _len2 = arguments.length, args = Array(_len2), + _key2 = 0; _key2 < _len2; _key2++) {\n args[_key2] = arguments[_key2];\n + \ }\n return construct(func, args);\n };\n }\n + \ function addToSet(set3, array2) {\n if (setPrototypeOf) {\n setPrototypeOf(set3, + null);\n }\n var l2 = array2.length;\n while (l2--) {\n + \ var element = array2[l2];\n if (typeof element === \"string\") + {\n var lcElement = stringToLowerCase(element);\n if + (lcElement !== element) {\n if (!isFrozen(array2)) {\n array2[l2] + = lcElement;\n }\n element = lcElement;\n }\n + \ }\n set3[element] = true;\n }\n return set3;\n + \ }\n function clone(object2) {\n var newObject = create$7(null);\n + \ var property = void 0;\n for (property in object2) {\n if + (apply2(hasOwnProperty2, object2, [property])) {\n newObject[property] + = object2[property];\n }\n }\n return newObject;\n + \ }\n function lookupGetter(object2, prop) {\n while (object2 + !== null) {\n var desc = getOwnPropertyDescriptor(object2, prop);\n + \ if (desc) {\n if (desc.get) {\n return unapply(desc.get);\n + \ }\n if (typeof desc.value === \"function\") {\n return + unapply(desc.value);\n }\n }\n object2 = getPrototypeOf(object2);\n + \ }\n function fallbackValue(element) {\n console.warn(\"fallback + value for\", element);\n return null;\n }\n return + fallbackValue;\n }\n var html = freeze([\n \"a\",\n \"abbr\",\n + \ \"acronym\",\n \"address\",\n \"area\",\n \"article\",\n + \ \"aside\",\n \"audio\",\n \"b\",\n \"bdi\",\n + \ \"bdo\",\n \"big\",\n \"blink\",\n \"blockquote\",\n + \ \"body\",\n \"br\",\n \"button\",\n \"canvas\",\n + \ \"caption\",\n \"center\",\n \"cite\",\n \"code\",\n + \ \"col\",\n \"colgroup\",\n \"content\",\n \"data\",\n + \ \"datalist\",\n \"dd\",\n \"decorator\",\n \"del\",\n + \ \"details\",\n \"dfn\",\n \"dialog\",\n \"dir\",\n + \ \"div\",\n \"dl\",\n \"dt\",\n \"element\",\n + \ \"em\",\n \"fieldset\",\n \"figcaption\",\n \"figure\",\n + \ \"font\",\n \"footer\",\n \"form\",\n \"h1\",\n + \ \"h2\",\n \"h3\",\n \"h4\",\n \"h5\",\n \"h6\",\n + \ \"head\",\n \"header\",\n \"hgroup\",\n \"hr\",\n + \ \"html\",\n \"i\",\n \"img\",\n \"input\",\n + \ \"ins\",\n \"kbd\",\n \"label\",\n \"legend\",\n + \ \"li\",\n \"main\",\n \"map\",\n \"mark\",\n + \ \"marquee\",\n \"menu\",\n \"menuitem\",\n \"meter\",\n + \ \"nav\",\n \"nobr\",\n \"ol\",\n \"optgroup\",\n + \ \"option\",\n \"output\",\n \"p\",\n \"picture\",\n + \ \"pre\",\n \"progress\",\n \"q\",\n \"rp\",\n + \ \"rt\",\n \"ruby\",\n \"s\",\n \"samp\",\n \"section\",\n + \ \"select\",\n \"shadow\",\n \"small\",\n \"source\",\n + \ \"spacer\",\n \"span\",\n \"strike\",\n \"strong\",\n + \ \"style\",\n \"sub\",\n \"summary\",\n \"sup\",\n + \ \"table\",\n \"tbody\",\n \"td\",\n \"template\",\n + \ \"textarea\",\n \"tfoot\",\n \"th\",\n \"thead\",\n + \ \"time\",\n \"tr\",\n \"track\",\n \"tt\",\n + \ \"u\",\n \"ul\",\n \"var\",\n \"video\",\n \"wbr\"\n + \ ]);\n var svg = freeze([\n \"svg\",\n \"a\",\n \"altglyph\",\n + \ \"altglyphdef\",\n \"altglyphitem\",\n \"animatecolor\",\n + \ \"animatemotion\",\n \"animatetransform\",\n \"circle\",\n + \ \"clippath\",\n \"defs\",\n \"desc\",\n \"ellipse\",\n + \ \"filter\",\n \"font\",\n \"g\",\n \"glyph\",\n + \ \"glyphref\",\n \"hkern\",\n \"image\",\n \"line\",\n + \ \"lineargradient\",\n \"marker\",\n \"mask\",\n \"metadata\",\n + \ \"mpath\",\n \"path\",\n \"pattern\",\n \"polygon\",\n + \ \"polyline\",\n \"radialgradient\",\n \"rect\",\n \"stop\",\n + \ \"style\",\n \"switch\",\n \"symbol\",\n \"text\",\n + \ \"textpath\",\n \"title\",\n \"tref\",\n \"tspan\",\n + \ \"view\",\n \"vkern\"\n ]);\n var svgFilters = freeze([\n + \ \"feBlend\",\n \"feColorMatrix\",\n \"feComponentTransfer\",\n + \ \"feComposite\",\n \"feConvolveMatrix\",\n \"feDiffuseLighting\",\n + \ \"feDisplacementMap\",\n \"feDistantLight\",\n \"feFlood\",\n + \ \"feFuncA\",\n \"feFuncB\",\n \"feFuncG\",\n \"feFuncR\",\n + \ \"feGaussianBlur\",\n \"feImage\",\n \"feMerge\",\n + \ \"feMergeNode\",\n \"feMorphology\",\n \"feOffset\",\n + \ \"fePointLight\",\n \"feSpecularLighting\",\n \"feSpotLight\",\n + \ \"feTile\",\n \"feTurbulence\"\n ]);\n var svgDisallowed + = freeze([\n \"animate\",\n \"color-profile\",\n \"cursor\",\n + \ \"discard\",\n \"fedropshadow\",\n \"font-face\",\n + \ \"font-face-format\",\n \"font-face-name\",\n \"font-face-src\",\n + \ \"font-face-uri\",\n \"foreignobject\",\n \"hatch\",\n + \ \"hatchpath\",\n \"mesh\",\n \"meshgradient\",\n \"meshpatch\",\n + \ \"meshrow\",\n \"missing-glyph\",\n \"script\",\n \"set\",\n + \ \"solidcolor\",\n \"unknown\",\n \"use\"\n ]);\n + \ var mathMl = freeze([\n \"math\",\n \"menclose\",\n \"merror\",\n + \ \"mfenced\",\n \"mfrac\",\n \"mglyph\",\n \"mi\",\n + \ \"mlabeledtr\",\n \"mmultiscripts\",\n \"mn\",\n \"mo\",\n + \ \"mover\",\n \"mpadded\",\n \"mphantom\",\n \"mroot\",\n + \ \"mrow\",\n \"ms\",\n \"mspace\",\n \"msqrt\",\n + \ \"mstyle\",\n \"msub\",\n \"msup\",\n \"msubsup\",\n + \ \"mtable\",\n \"mtd\",\n \"mtext\",\n \"mtr\",\n + \ \"munder\",\n \"munderover\"\n ]);\n var mathMlDisallowed + = freeze([\n \"maction\",\n \"maligngroup\",\n \"malignmark\",\n + \ \"mlongdiv\",\n \"mscarries\",\n \"mscarry\",\n \"msgroup\",\n + \ \"mstack\",\n \"msline\",\n \"msrow\",\n \"semantics\",\n + \ \"annotation\",\n \"annotation-xml\",\n \"mprescripts\",\n + \ \"none\"\n ]);\n var text2 = freeze([\"#text\"]);\n var + html$1 = freeze([\n \"accept\",\n \"action\",\n \"align\",\n + \ \"alt\",\n \"autocapitalize\",\n \"autocomplete\",\n + \ \"autopictureinpicture\",\n \"autoplay\",\n \"background\",\n + \ \"bgcolor\",\n \"border\",\n \"capture\",\n \"cellpadding\",\n + \ \"cellspacing\",\n \"checked\",\n \"cite\",\n \"class\",\n + \ \"clear\",\n \"color\",\n \"cols\",\n \"colspan\",\n + \ \"controls\",\n \"controlslist\",\n \"coords\",\n \"crossorigin\",\n + \ \"datetime\",\n \"decoding\",\n \"default\",\n \"dir\",\n + \ \"disabled\",\n \"disablepictureinpicture\",\n \"disableremoteplayback\",\n + \ \"download\",\n \"draggable\",\n \"enctype\",\n \"enterkeyhint\",\n + \ \"face\",\n \"for\",\n \"headers\",\n \"height\",\n + \ \"hidden\",\n \"high\",\n \"href\",\n \"hreflang\",\n + \ \"id\",\n \"inputmode\",\n \"integrity\",\n \"ismap\",\n + \ \"kind\",\n \"label\",\n \"lang\",\n \"list\",\n + \ \"loading\",\n \"loop\",\n \"low\",\n \"max\",\n + \ \"maxlength\",\n \"media\",\n \"method\",\n \"min\",\n + \ \"minlength\",\n \"multiple\",\n \"muted\",\n \"name\",\n + \ \"nonce\",\n \"noshade\",\n \"novalidate\",\n \"nowrap\",\n + \ \"open\",\n \"optimum\",\n \"pattern\",\n \"placeholder\",\n + \ \"playsinline\",\n \"poster\",\n \"preload\",\n \"pubdate\",\n + \ \"radiogroup\",\n \"readonly\",\n \"rel\",\n \"required\",\n + \ \"rev\",\n \"reversed\",\n \"role\",\n \"rows\",\n + \ \"rowspan\",\n \"spellcheck\",\n \"scope\",\n \"selected\",\n + \ \"shape\",\n \"size\",\n \"sizes\",\n \"span\",\n + \ \"srclang\",\n \"start\",\n \"src\",\n \"srcset\",\n + \ \"step\",\n \"style\",\n \"summary\",\n \"tabindex\",\n + \ \"title\",\n \"translate\",\n \"type\",\n \"usemap\",\n + \ \"valign\",\n \"value\",\n \"width\",\n \"xmlns\",\n + \ \"slot\"\n ]);\n var svg$1 = freeze([\n \"accent-height\",\n + \ \"accumulate\",\n \"additive\",\n \"alignment-baseline\",\n + \ \"ascent\",\n \"attributename\",\n \"attributetype\",\n + \ \"azimuth\",\n \"basefrequency\",\n \"baseline-shift\",\n + \ \"begin\",\n \"bias\",\n \"by\",\n \"class\",\n + \ \"clip\",\n \"clippathunits\",\n \"clip-path\",\n \"clip-rule\",\n + \ \"color\",\n \"color-interpolation\",\n \"color-interpolation-filters\",\n + \ \"color-profile\",\n \"color-rendering\",\n \"cx\",\n + \ \"cy\",\n \"d\",\n \"dx\",\n \"dy\",\n \"diffuseconstant\",\n + \ \"direction\",\n \"display\",\n \"divisor\",\n \"dur\",\n + \ \"edgemode\",\n \"elevation\",\n \"end\",\n \"fill\",\n + \ \"fill-opacity\",\n \"fill-rule\",\n \"filter\",\n \"filterunits\",\n + \ \"flood-color\",\n \"flood-opacity\",\n \"font-family\",\n + \ \"font-size\",\n \"font-size-adjust\",\n \"font-stretch\",\n + \ \"font-style\",\n \"font-variant\",\n \"font-weight\",\n + \ \"fx\",\n \"fy\",\n \"g1\",\n \"g2\",\n \"glyph-name\",\n + \ \"glyphref\",\n \"gradientunits\",\n \"gradienttransform\",\n + \ \"height\",\n \"href\",\n \"id\",\n \"image-rendering\",\n + \ \"in\",\n \"in2\",\n \"k\",\n \"k1\",\n \"k2\",\n + \ \"k3\",\n \"k4\",\n \"kerning\",\n \"keypoints\",\n + \ \"keysplines\",\n \"keytimes\",\n \"lang\",\n \"lengthadjust\",\n + \ \"letter-spacing\",\n \"kernelmatrix\",\n \"kernelunitlength\",\n + \ \"lighting-color\",\n \"local\",\n \"marker-end\",\n + \ \"marker-mid\",\n \"marker-start\",\n \"markerheight\",\n + \ \"markerunits\",\n \"markerwidth\",\n \"maskcontentunits\",\n + \ \"maskunits\",\n \"max\",\n \"mask\",\n \"media\",\n + \ \"method\",\n \"mode\",\n \"min\",\n \"name\",\n + \ \"numoctaves\",\n \"offset\",\n \"operator\",\n \"opacity\",\n + \ \"order\",\n \"orient\",\n \"orientation\",\n \"origin\",\n + \ \"overflow\",\n \"paint-order\",\n \"path\",\n \"pathlength\",\n + \ \"patterncontentunits\",\n \"patterntransform\",\n \"patternunits\",\n + \ \"points\",\n \"preservealpha\",\n \"preserveaspectratio\",\n + \ \"primitiveunits\",\n \"r\",\n \"rx\",\n \"ry\",\n + \ \"radius\",\n \"refx\",\n \"refy\",\n \"repeatcount\",\n + \ \"repeatdur\",\n \"restart\",\n \"result\",\n \"rotate\",\n + \ \"scale\",\n \"seed\",\n \"shape-rendering\",\n \"specularconstant\",\n + \ \"specularexponent\",\n \"spreadmethod\",\n \"startoffset\",\n + \ \"stddeviation\",\n \"stitchtiles\",\n \"stop-color\",\n + \ \"stop-opacity\",\n \"stroke-dasharray\",\n \"stroke-dashoffset\",\n + \ \"stroke-linecap\",\n \"stroke-linejoin\",\n \"stroke-miterlimit\",\n + \ \"stroke-opacity\",\n \"stroke\",\n \"stroke-width\",\n + \ \"style\",\n \"surfacescale\",\n \"systemlanguage\",\n + \ \"tabindex\",\n \"targetx\",\n \"targety\",\n \"transform\",\n + \ \"transform-origin\",\n \"text-anchor\",\n \"text-decoration\",\n + \ \"text-rendering\",\n \"textlength\",\n \"type\",\n + \ \"u1\",\n \"u2\",\n \"unicode\",\n \"values\",\n + \ \"viewbox\",\n \"visibility\",\n \"version\",\n \"vert-adv-y\",\n + \ \"vert-origin-x\",\n \"vert-origin-y\",\n \"width\",\n + \ \"word-spacing\",\n \"wrap\",\n \"writing-mode\",\n + \ \"xchannelselector\",\n \"ychannelselector\",\n \"x\",\n + \ \"x1\",\n \"x2\",\n \"xmlns\",\n \"y\",\n \"y1\",\n + \ \"y2\",\n \"z\",\n \"zoomandpan\"\n ]);\n var + mathMl$1 = freeze([\n \"accent\",\n \"accentunder\",\n \"align\",\n + \ \"bevelled\",\n \"close\",\n \"columnsalign\",\n \"columnlines\",\n + \ \"columnspan\",\n \"denomalign\",\n \"depth\",\n \"dir\",\n + \ \"display\",\n \"displaystyle\",\n \"encoding\",\n \"fence\",\n + \ \"frame\",\n \"height\",\n \"href\",\n \"id\",\n + \ \"largeop\",\n \"length\",\n \"linethickness\",\n \"lspace\",\n + \ \"lquote\",\n \"mathbackground\",\n \"mathcolor\",\n + \ \"mathsize\",\n \"mathvariant\",\n \"maxsize\",\n \"minsize\",\n + \ \"movablelimits\",\n \"notation\",\n \"numalign\",\n + \ \"open\",\n \"rowalign\",\n \"rowlines\",\n \"rowspacing\",\n + \ \"rowspan\",\n \"rspace\",\n \"rquote\",\n \"scriptlevel\",\n + \ \"scriptminsize\",\n \"scriptsizemultiplier\",\n \"selection\",\n + \ \"separator\",\n \"separators\",\n \"stretchy\",\n \"subscriptshift\",\n + \ \"supscriptshift\",\n \"symmetric\",\n \"voffset\",\n + \ \"width\",\n \"xmlns\"\n ]);\n var xml = freeze([\n + \ \"xlink:href\",\n \"xml:id\",\n \"xlink:title\",\n \"xml:space\",\n + \ \"xmlns:xlink\"\n ]);\n var MUSTACHE_EXPR = seal(/\\{\\{[\\s\\S]*|[\\s\\S]*\\}\\}/gm);\n + \ var ERB_EXPR = seal(/<%[\\s\\S]*|[\\s\\S]*%>/gm);\n var DATA_ATTR + = seal(/^data-[\\-\\w.\\u00B7-\\uFFFF]/);\n var ARIA_ATTR = seal(/^aria-[\\-\\w]+$/);\n + \ var IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\\-]+(?:[^a-z+.\\-:]|$))/i);\n + \ var IS_SCRIPT_OR_DATA = seal(/^(?:\\w+script|data):/i);\n var ATTR_WHITESPACE + = seal(/[\\u0000-\\u0020\\u00A0\\u1680\\u180E\\u2000-\\u2029\\u205F\\u3000]/g);\n + \ var DOCTYPE_NAME = seal(/^html$/i);\n var _typeof = typeof Symbol + === \"function\" && typeof Symbol.iterator === \"symbol\" ? function(obj) + {\n return typeof obj;\n } : function(obj) {\n return obj + && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== + Symbol.prototype ? \"symbol\" : typeof obj;\n };\n function _toConsumableArray$1(arr) + {\n if (Array.isArray(arr)) {\n for (var i3 = 0, arr2 = Array(arr.length); + i3 < arr.length; i3++) {\n arr2[i3] = arr[i3];\n }\n return + arr2;\n } else {\n return Array.from(arr);\n }\n }\n + \ var getGlobal = function getGlobal2() {\n return typeof window + === \"undefined\" ? null : window;\n };\n var _createTrustedTypesPolicy + = function _createTrustedTypesPolicy2(trustedTypes, document2) {\n if + ((typeof trustedTypes === \"undefined\" ? \"undefined\" : _typeof(trustedTypes)) + !== \"object\" || typeof trustedTypes.createPolicy !== \"function\") {\n return + null;\n }\n var suffix = null;\n var ATTR_NAME = \"data-tt-policy-suffix\";\n + \ if (document2.currentScript && document2.currentScript.hasAttribute(ATTR_NAME)) + {\n suffix = document2.currentScript.getAttribute(ATTR_NAME);\n }\n + \ var policyName = \"dompurify\" + (suffix ? \"#\" + suffix : \"\");\n + \ try {\n return trustedTypes.createPolicy(policyName, {\n + \ createHTML: function createHTML(html$$1) {\n return + html$$1;\n }\n });\n } catch (_2) {\n console.warn(\"TrustedTypes + policy \" + policyName + \" could not be created.\");\n return null;\n + \ }\n };\n function createDOMPurify() {\n var window2 + = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : getGlobal();\n + \ var DOMPurify = function DOMPurify2(root2) {\n return createDOMPurify(root2);\n + \ };\n DOMPurify.version = \"2.3.6\";\n DOMPurify.removed + = [];\n if (!window2 || !window2.document || window2.document.nodeType + !== 9) {\n DOMPurify.isSupported = false;\n return DOMPurify;\n + \ }\n var originalDocument = window2.document;\n var document2 + = window2.document;\n var DocumentFragment = window2.DocumentFragment, + HTMLTemplateElement = window2.HTMLTemplateElement, Node2 = window2.Node, Element2 + = window2.Element, NodeFilter = window2.NodeFilter, _window$NamedNodeMap = + window2.NamedNodeMap, NamedNodeMap = _window$NamedNodeMap === void 0 ? window2.NamedNodeMap + || window2.MozNamedAttrMap : _window$NamedNodeMap, HTMLFormElement2 = window2.HTMLFormElement, + DOMParser2 = window2.DOMParser, trustedTypes = window2.trustedTypes;\n var + ElementPrototype = Element2.prototype;\n var cloneNode = lookupGetter(ElementPrototype, + \"cloneNode\");\n var getNextSibling = lookupGetter(ElementPrototype, + \"nextSibling\");\n var getChildNodes2 = lookupGetter(ElementPrototype, + \"childNodes\");\n var getParentNode = lookupGetter(ElementPrototype, + \"parentNode\");\n if (typeof HTMLTemplateElement === \"function\") + {\n var template = document2.createElement(\"template\");\n if + (template.content && template.content.ownerDocument) {\n document2 + = template.content.ownerDocument;\n }\n }\n var trustedTypesPolicy + = _createTrustedTypesPolicy(trustedTypes, originalDocument);\n var + emptyHTML = trustedTypesPolicy ? trustedTypesPolicy.createHTML(\"\") : \"\";\n + \ var _document = document2, implementation = _document.implementation, + createNodeIterator = _document.createNodeIterator, createDocumentFragment + = _document.createDocumentFragment, getElementsByTagName = _document.getElementsByTagName;\n + \ var importNode2 = originalDocument.importNode;\n var documentMode + = {};\n try {\n documentMode = clone(document2).documentMode + ? document2.documentMode : {};\n } catch (_2) {\n }\n var + hooks = {};\n DOMPurify.isSupported = typeof getParentNode === \"function\" + && implementation && typeof implementation.createHTMLDocument !== \"undefined\" + && documentMode !== 9;\n var MUSTACHE_EXPR$$1 = MUSTACHE_EXPR, ERB_EXPR$$1 + = ERB_EXPR, DATA_ATTR$$1 = DATA_ATTR, ARIA_ATTR$$1 = ARIA_ATTR, IS_SCRIPT_OR_DATA$$1 + = IS_SCRIPT_OR_DATA, ATTR_WHITESPACE$$1 = ATTR_WHITESPACE;\n var IS_ALLOWED_URI$$1 + = IS_ALLOWED_URI;\n var ALLOWED_TAGS = null;\n var DEFAULT_ALLOWED_TAGS + = addToSet({}, [].concat(_toConsumableArray$1(html), _toConsumableArray$1(svg), + _toConsumableArray$1(svgFilters), _toConsumableArray$1(mathMl), _toConsumableArray$1(text2)));\n + \ var ALLOWED_ATTR = null;\n var DEFAULT_ALLOWED_ATTR = addToSet({}, + [].concat(_toConsumableArray$1(html$1), _toConsumableArray$1(svg$1), _toConsumableArray$1(mathMl$1), + _toConsumableArray$1(xml)));\n var CUSTOM_ELEMENT_HANDLING = Object.seal(Object.create(null, + {\n tagNameCheck: {\n writable: true,\n configurable: + false,\n enumerable: true,\n value: null\n },\n + \ attributeNameCheck: {\n writable: true,\n configurable: + false,\n enumerable: true,\n value: null\n },\n + \ allowCustomizedBuiltInElements: {\n writable: true,\n + \ configurable: false,\n enumerable: true,\n value: + false\n }\n }));\n var FORBID_TAGS = null;\n var + FORBID_ATTR = null;\n var ALLOW_ARIA_ATTR = true;\n var ALLOW_DATA_ATTR + = true;\n var ALLOW_UNKNOWN_PROTOCOLS = false;\n var SAFE_FOR_TEMPLATES + = false;\n var WHOLE_DOCUMENT = false;\n var SET_CONFIG = false;\n + \ var FORCE_BODY = false;\n var RETURN_DOM = false;\n var + RETURN_DOM_FRAGMENT = false;\n var RETURN_TRUSTED_TYPE = false;\n var + SANITIZE_DOM = true;\n var KEEP_CONTENT = true;\n var IN_PLACE + = false;\n var USE_PROFILES = {};\n var FORBID_CONTENTS = null;\n + \ var DEFAULT_FORBID_CONTENTS = addToSet({}, [\n \"annotation-xml\",\n + \ \"audio\",\n \"colgroup\",\n \"desc\",\n \"foreignobject\",\n + \ \"head\",\n \"iframe\",\n \"math\",\n \"mi\",\n + \ \"mn\",\n \"mo\",\n \"ms\",\n \"mtext\",\n + \ \"noembed\",\n \"noframes\",\n \"noscript\",\n + \ \"plaintext\",\n \"script\",\n \"style\",\n \"svg\",\n + \ \"template\",\n \"thead\",\n \"title\",\n \"video\",\n + \ \"xmp\"\n ]);\n var DATA_URI_TAGS = null;\n var + DEFAULT_DATA_URI_TAGS = addToSet({}, [\n \"audio\",\n \"video\",\n + \ \"img\",\n \"source\",\n \"image\",\n \"track\"\n + \ ]);\n var URI_SAFE_ATTRIBUTES = null;\n var DEFAULT_URI_SAFE_ATTRIBUTES + = addToSet({}, [\n \"alt\",\n \"class\",\n \"for\",\n + \ \"id\",\n \"label\",\n \"name\",\n \"pattern\",\n + \ \"placeholder\",\n \"role\",\n \"summary\",\n + \ \"title\",\n \"value\",\n \"style\",\n \"xmlns\"\n + \ ]);\n var MATHML_NAMESPACE = \"http://www.w3.org/1998/Math/MathML\";\n + \ var SVG_NAMESPACE = \"http://www.w3.org/2000/svg\";\n var HTML_NAMESPACE + = \"http://www.w3.org/1999/xhtml\";\n var NAMESPACE = HTML_NAMESPACE;\n + \ var IS_EMPTY_INPUT = false;\n var PARSER_MEDIA_TYPE = void + 0;\n var SUPPORTED_PARSER_MEDIA_TYPES = [\n \"application/xhtml+xml\",\n + \ \"text/html\"\n ];\n var DEFAULT_PARSER_MEDIA_TYPE + = \"text/html\";\n var transformCaseFunc = void 0;\n var CONFIG + = null;\n var formElement = document2.createElement(\"form\");\n var + isRegexOrFunction = function isRegexOrFunction2(testValue) {\n return + testValue instanceof RegExp || testValue instanceof Function;\n };\n + \ var _parseConfig = function _parseConfig2(cfg) {\n if (CONFIG + && CONFIG === cfg) {\n return;\n }\n if (!cfg + || (typeof cfg === \"undefined\" ? \"undefined\" : _typeof(cfg)) !== \"object\") + {\n cfg = {};\n }\n cfg = clone(cfg);\n ALLOWED_TAGS + = \"ALLOWED_TAGS\" in cfg ? addToSet({}, cfg.ALLOWED_TAGS) : DEFAULT_ALLOWED_TAGS;\n + \ ALLOWED_ATTR = \"ALLOWED_ATTR\" in cfg ? addToSet({}, cfg.ALLOWED_ATTR) + : DEFAULT_ALLOWED_ATTR;\n URI_SAFE_ATTRIBUTES = \"ADD_URI_SAFE_ATTR\" + in cfg ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), cfg.ADD_URI_SAFE_ATTR) + : DEFAULT_URI_SAFE_ATTRIBUTES;\n DATA_URI_TAGS = \"ADD_DATA_URI_TAGS\" + in cfg ? addToSet(clone(DEFAULT_DATA_URI_TAGS), cfg.ADD_DATA_URI_TAGS) : DEFAULT_DATA_URI_TAGS;\n + \ FORBID_CONTENTS = \"FORBID_CONTENTS\" in cfg ? addToSet({}, cfg.FORBID_CONTENTS) + : DEFAULT_FORBID_CONTENTS;\n FORBID_TAGS = \"FORBID_TAGS\" in cfg + ? addToSet({}, cfg.FORBID_TAGS) : {};\n FORBID_ATTR = \"FORBID_ATTR\" + in cfg ? addToSet({}, cfg.FORBID_ATTR) : {};\n USE_PROFILES = \"USE_PROFILES\" + in cfg ? cfg.USE_PROFILES : false;\n ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR + !== false;\n ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false;\n ALLOW_UNKNOWN_PROTOCOLS + = cfg.ALLOW_UNKNOWN_PROTOCOLS || false;\n SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES + || false;\n WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false;\n RETURN_DOM + = cfg.RETURN_DOM || false;\n RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT + || false;\n RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false;\n + \ FORCE_BODY = cfg.FORCE_BODY || false;\n SANITIZE_DOM = + cfg.SANITIZE_DOM !== false;\n KEEP_CONTENT = cfg.KEEP_CONTENT !== + false;\n IN_PLACE = cfg.IN_PLACE || false;\n IS_ALLOWED_URI$$1 + = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI$$1;\n NAMESPACE = cfg.NAMESPACE + || HTML_NAMESPACE;\n if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck)) + {\n CUSTOM_ELEMENT_HANDLING.tagNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck;\n + \ }\n if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)) + {\n CUSTOM_ELEMENT_HANDLING.attributeNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck;\n + \ }\n if (cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements + === \"boolean\") {\n CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements + = cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements;\n }\n + \ PARSER_MEDIA_TYPE = SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) + === -1 ? PARSER_MEDIA_TYPE = DEFAULT_PARSER_MEDIA_TYPE : PARSER_MEDIA_TYPE + = cfg.PARSER_MEDIA_TYPE;\n transformCaseFunc = PARSER_MEDIA_TYPE + === \"application/xhtml+xml\" ? function(x2) {\n return x2;\n } + : stringToLowerCase;\n if (SAFE_FOR_TEMPLATES) {\n ALLOW_DATA_ATTR + = false;\n }\n if (RETURN_DOM_FRAGMENT) {\n RETURN_DOM + = true;\n }\n if (USE_PROFILES) {\n ALLOWED_TAGS + = addToSet({}, [].concat(_toConsumableArray$1(text2)));\n ALLOWED_ATTR + = [];\n if (USE_PROFILES.html === true) {\n addToSet(ALLOWED_TAGS, + html);\n addToSet(ALLOWED_ATTR, html$1);\n }\n if + (USE_PROFILES.svg === true) {\n addToSet(ALLOWED_TAGS, svg);\n + \ addToSet(ALLOWED_ATTR, svg$1);\n addToSet(ALLOWED_ATTR, + xml);\n }\n if (USE_PROFILES.svgFilters === true) {\n + \ addToSet(ALLOWED_TAGS, svgFilters);\n addToSet(ALLOWED_ATTR, + svg$1);\n addToSet(ALLOWED_ATTR, xml);\n }\n if + (USE_PROFILES.mathMl === true) {\n addToSet(ALLOWED_TAGS, mathMl);\n + \ addToSet(ALLOWED_ATTR, mathMl$1);\n addToSet(ALLOWED_ATTR, + xml);\n }\n }\n if (cfg.ADD_TAGS) {\n if + (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {\n ALLOWED_TAGS = clone(ALLOWED_TAGS);\n + \ }\n addToSet(ALLOWED_TAGS, cfg.ADD_TAGS);\n }\n + \ if (cfg.ADD_ATTR) {\n if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) + {\n ALLOWED_ATTR = clone(ALLOWED_ATTR);\n }\n addToSet(ALLOWED_ATTR, + cfg.ADD_ATTR);\n }\n if (cfg.ADD_URI_SAFE_ATTR) {\n addToSet(URI_SAFE_ATTRIBUTES, + cfg.ADD_URI_SAFE_ATTR);\n }\n if (cfg.FORBID_CONTENTS) {\n + \ if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {\n FORBID_CONTENTS + = clone(FORBID_CONTENTS);\n }\n addToSet(FORBID_CONTENTS, + cfg.FORBID_CONTENTS);\n }\n if (KEEP_CONTENT) {\n ALLOWED_TAGS[\"#text\"] + = true;\n }\n if (WHOLE_DOCUMENT) {\n addToSet(ALLOWED_TAGS, + [\n \"html\",\n \"head\",\n \"body\"\n + \ ]);\n }\n if (ALLOWED_TAGS.table) {\n addToSet(ALLOWED_TAGS, + [\"tbody\"]);\n delete FORBID_TAGS.tbody;\n }\n if + (freeze) {\n freeze(cfg);\n }\n CONFIG = cfg;\n + \ };\n var MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, [\n + \ \"mi\",\n \"mo\",\n \"mn\",\n \"ms\",\n + \ \"mtext\"\n ]);\n var HTML_INTEGRATION_POINTS = addToSet({}, + [\n \"foreignobject\",\n \"desc\",\n \"title\",\n + \ \"annotation-xml\"\n ]);\n var COMMON_SVG_AND_HTML_ELEMENTS + = addToSet({}, [\n \"title\",\n \"style\",\n \"font\",\n + \ \"a\",\n \"script\"\n ]);\n var ALL_SVG_TAGS + = addToSet({}, svg);\n addToSet(ALL_SVG_TAGS, svgFilters);\n addToSet(ALL_SVG_TAGS, + svgDisallowed);\n var ALL_MATHML_TAGS = addToSet({}, mathMl);\n addToSet(ALL_MATHML_TAGS, + mathMlDisallowed);\n var _checkValidNamespace = function _checkValidNamespace2(element) + {\n var parent2 = getParentNode(element);\n if (!parent2 + || !parent2.tagName) {\n parent2 = {\n namespaceURI: + HTML_NAMESPACE,\n tagName: \"template\"\n };\n }\n + \ var tagName = stringToLowerCase(element.tagName);\n var + parentTagName = stringToLowerCase(parent2.tagName);\n if (element.namespaceURI + === SVG_NAMESPACE) {\n if (parent2.namespaceURI === HTML_NAMESPACE) + {\n return tagName === \"svg\";\n }\n if + (parent2.namespaceURI === MATHML_NAMESPACE) {\n return tagName + === \"svg\" && (parentTagName === \"annotation-xml\" || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);\n + \ }\n return Boolean(ALL_SVG_TAGS[tagName]);\n }\n + \ if (element.namespaceURI === MATHML_NAMESPACE) {\n if + (parent2.namespaceURI === HTML_NAMESPACE) {\n return tagName + === \"math\";\n }\n if (parent2.namespaceURI === SVG_NAMESPACE) + {\n return tagName === \"math\" && HTML_INTEGRATION_POINTS[parentTagName];\n + \ }\n return Boolean(ALL_MATHML_TAGS[tagName]);\n }\n + \ if (element.namespaceURI === HTML_NAMESPACE) {\n if (parent2.namespaceURI + === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {\n return + false;\n }\n if (parent2.namespaceURI === MATHML_NAMESPACE + && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {\n return + false;\n }\n return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] + || !ALL_SVG_TAGS[tagName]);\n }\n return false;\n };\n + \ var _forceRemove = function _forceRemove2(node) {\n arrayPush2(DOMPurify.removed, + { element: node });\n try {\n node.parentNode.removeChild(node);\n + \ } catch (_2) {\n try {\n node.outerHTML + = emptyHTML;\n } catch (_3) {\n node.remove();\n }\n + \ }\n };\n var _removeAttribute = function _removeAttribute2(name2, + node) {\n try {\n arrayPush2(DOMPurify.removed, {\n attribute: + node.getAttributeNode(name2),\n from: node\n });\n + \ } catch (_2) {\n arrayPush2(DOMPurify.removed, {\n attribute: + null,\n from: node\n });\n }\n node.removeAttribute(name2);\n + \ if (name2 === \"is\" && !ALLOWED_ATTR[name2]) {\n if + (RETURN_DOM || RETURN_DOM_FRAGMENT) {\n try {\n _forceRemove(node);\n + \ } catch (_2) {\n }\n } else {\n try + {\n node.setAttribute(name2, \"\");\n } catch + (_2) {\n }\n }\n }\n };\n var + _initDocument = function _initDocument2(dirty) {\n var doc = void + 0;\n var leadingWhitespace = void 0;\n if (FORCE_BODY) {\n + \ dirty = \"\" + dirty;\n } else {\n var + matches = stringMatch(dirty, /^[\\r\\n\\t ]+/);\n leadingWhitespace + = matches && matches[0];\n }\n if (PARSER_MEDIA_TYPE === + \"application/xhtml+xml\") {\n dirty = '' + + dirty + \"\";\n }\n var dirtyPayload = trustedTypesPolicy + ? trustedTypesPolicy.createHTML(dirty) : dirty;\n if (NAMESPACE === + HTML_NAMESPACE) {\n try {\n doc = new DOMParser2().parseFromString(dirtyPayload, + PARSER_MEDIA_TYPE);\n } catch (_2) {\n }\n }\n + \ if (!doc || !doc.documentElement) {\n doc = implementation.createDocument(NAMESPACE, + \"template\", null);\n try {\n doc.documentElement.innerHTML + = IS_EMPTY_INPUT ? \"\" : dirtyPayload;\n } catch (_2) {\n }\n + \ }\n var body = doc.body || doc.documentElement;\n if + (dirty && leadingWhitespace) {\n body.insertBefore(document2.createTextNode(leadingWhitespace), + body.childNodes[0] || null);\n }\n if (NAMESPACE === HTML_NAMESPACE) + {\n return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? \"html\" + : \"body\")[0];\n }\n return WHOLE_DOCUMENT ? doc.documentElement + : body;\n };\n var _createIterator = function _createIterator2(root2) + {\n return createNodeIterator.call(root2.ownerDocument || root2, + root2, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT, + null, false);\n };\n var _isClobbered = function _isClobbered2(elm) + {\n return elm instanceof HTMLFormElement2 && (typeof elm.nodeName + !== \"string\" || typeof elm.textContent !== \"string\" || typeof elm.removeChild + !== \"function\" || !(elm.attributes instanceof NamedNodeMap) || typeof elm.removeAttribute + !== \"function\" || typeof elm.setAttribute !== \"function\" || typeof elm.namespaceURI + !== \"string\" || typeof elm.insertBefore !== \"function\");\n };\n + \ var _isNode = function _isNode2(object2) {\n return (typeof + Node2 === \"undefined\" ? \"undefined\" : _typeof(Node2)) === \"object\" ? + object2 instanceof Node2 : object2 && (typeof object2 === \"undefined\" ? + \"undefined\" : _typeof(object2)) === \"object\" && typeof object2.nodeType + === \"number\" && typeof object2.nodeName === \"string\";\n };\n var + _executeHook = function _executeHook2(entryPoint, currentNode, data2) {\n + \ if (!hooks[entryPoint]) {\n return;\n }\n arrayForEach(hooks[entryPoint], + function(hook) {\n hook.call(DOMPurify, currentNode, data2, CONFIG);\n + \ });\n };\n var _sanitizeElements = function _sanitizeElements2(currentNode) + {\n var content = void 0;\n _executeHook(\"beforeSanitizeElements\", + currentNode, null);\n if (_isClobbered(currentNode)) {\n _forceRemove(currentNode);\n + \ return true;\n }\n if (regExpTest(/[\\u0080-\\uFFFF]/, + currentNode.nodeName)) {\n _forceRemove(currentNode);\n return + true;\n }\n var tagName = transformCaseFunc(currentNode.nodeName);\n + \ _executeHook(\"uponSanitizeElement\", currentNode, {\n tagName,\n + \ allowedTags: ALLOWED_TAGS\n });\n if (currentNode.hasChildNodes() + && !_isNode(currentNode.firstElementChild) && (!_isNode(currentNode.content) + || !_isNode(currentNode.content.firstElementChild)) && regExpTest(/<[/\\w]/g, + currentNode.innerHTML) && regExpTest(/<[/\\w]/g, currentNode.textContent)) + {\n _forceRemove(currentNode);\n return true;\n }\n + \ if (tagName === \"select\" && regExpTest(/