From 57927feed5b15b37adac82145a4ad0f55b664c19 Mon Sep 17 00:00:00 2001 From: Raphael Perrin <123095702+raphaelperrin-swisspost@users.noreply.github.com> Date: Fri, 14 Jul 2023 07:16:52 +0200 Subject: [PATCH 01/18] Fix typo in description --- .github/workflows/main-01-pkgdown.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main-01-pkgdown.yml b/.github/workflows/main-01-pkgdown.yml index 094f1938..95c564de 100644 --- a/.github/workflows/main-01-pkgdown.yml +++ b/.github/workflows/main-01-pkgdown.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: inputs: trigger_next: - description: 'Whether to run the subsequent workflows after triggering this one manully.' + description: 'Whether to run the subsequent workflows after triggering this one manually.' required: false default: false push: From 51b303035129b56318b7ec6db0cfb9eda7a6fc79 Mon Sep 17 00:00:00 2001 From: Raphael Perrin <123095702+raphaelperrin-swisspost@users.noreply.github.com> Date: Fri, 14 Jul 2023 07:18:22 +0200 Subject: [PATCH 02/18] Update main-02-test-coverage.yml Fix typo in description --- .github/workflows/main-02-test-coverage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main-02-test-coverage.yml b/.github/workflows/main-02-test-coverage.yml index 1436755f..dd543816 100644 --- a/.github/workflows/main-02-test-coverage.yml +++ b/.github/workflows/main-02-test-coverage.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: inputs: trigger_next: - description: 'Whether to run the subsequent workflows after triggering this one manully.' + description: 'Whether to run the subsequent workflows after triggering this one manually.' required: false default: false repository_dispatch: From bb7e65663b223723a332cf40f43b4f387a20feba Mon Sep 17 00:00:00 2001 From: Raphael Perrin <123095702+raphaelperrin-swisspost@users.noreply.github.com> Date: Fri, 14 Jul 2023 07:18:34 +0200 Subject: [PATCH 03/18] Update main-03-R-CMD-check-mac.yml Fix typo in description --- .github/workflows/main-03-R-CMD-check-mac.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main-03-R-CMD-check-mac.yml b/.github/workflows/main-03-R-CMD-check-mac.yml index a47110ab..e06cace1 100644 --- a/.github/workflows/main-03-R-CMD-check-mac.yml +++ b/.github/workflows/main-03-R-CMD-check-mac.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: inputs: trigger_next: - description: 'Whether to run the subsequent workflows after triggering this one manully.' + description: 'Whether to run the subsequent workflows after triggering this one manually.' required: false default: false repository_dispatch: From 40ea6637eec29b25f28183c9b6a169777aa78091 Mon Sep 17 00:00:00 2001 From: Raphael Perrin <123095702+raphaelperrin-swisspost@users.noreply.github.com> Date: Fri, 14 Jul 2023 07:18:48 +0200 Subject: [PATCH 04/18] Update main-04-R-CMD-check-windows.yml Fix typo in description --- .github/workflows/main-04-R-CMD-check-windows.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main-04-R-CMD-check-windows.yml b/.github/workflows/main-04-R-CMD-check-windows.yml index 8e0637db..142be019 100644 --- a/.github/workflows/main-04-R-CMD-check-windows.yml +++ b/.github/workflows/main-04-R-CMD-check-windows.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: inputs: trigger_next: - description: 'Whether to run the subsequent workflows after triggering this one manully.' + description: 'Whether to run the subsequent workflows after triggering this one manually.' required: false default: false repository_dispatch: From ea94b614ae8e47720b33d58dafdd4e4c1e51f2fa Mon Sep 17 00:00:00 2001 From: Raphael Perrin <123095702+raphaelperrin-swisspost@users.noreply.github.com> Date: Fri, 14 Jul 2023 07:18:58 +0200 Subject: [PATCH 05/18] Update main-05-R-CMD-check-linux.yml --- .github/workflows/main-05-R-CMD-check-linux.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main-05-R-CMD-check-linux.yml b/.github/workflows/main-05-R-CMD-check-linux.yml index 7f5c4394..c33cb314 100644 --- a/.github/workflows/main-05-R-CMD-check-linux.yml +++ b/.github/workflows/main-05-R-CMD-check-linux.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: inputs: trigger_next: - description: 'Whether to run the subsequent workflows after triggering this one manully.' + description: 'Whether to run the subsequent workflows after triggering this one manually.' required: false default: false repository_dispatch: From 7b12ab10785cecb71bdb8eee63f29549120d69d2 Mon Sep 17 00:00:00 2001 From: "Steven M. Mortimer" Date: Mon, 4 Nov 2024 14:33:05 -0600 Subject: [PATCH 06/18] Update Github Action --- .github/workflows/main-01-pkgdown.yml | 4 ++-- .github/workflows/main-02-test-coverage.yml | 4 ++-- .github/workflows/main-03-R-CMD-check-mac.yml | 4 ++-- .github/workflows/main-04-R-CMD-check-windows.yml | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/main-01-pkgdown.yml b/.github/workflows/main-01-pkgdown.yml index 61452342..90d52287 100644 --- a/.github/workflows/main-01-pkgdown.yml +++ b/.github/workflows/main-01-pkgdown.yml @@ -90,7 +90,7 @@ jobs: - name: Trigger next workflow if: ${{ (github.event_name != 'workflow_dispatch' && success()) || (github.event_name == 'workflow_dispatch' && github.event.inputs.trigger_next && success()) }} - uses: peter-evans/repository-dispatch@v1 + uses: peter-evans/repository-dispatch@v3 with: token: ${{ secrets.REPO_GHA_PAT }} repository: StevenMMortimer/salesforcer @@ -99,7 +99,7 @@ jobs: - name: Set final R-CMD-check status if: ${{ (github.event_name != 'workflow_dispatch' && failure()) }} - uses: peter-evans/repository-dispatch@v1 + uses: peter-evans/repository-dispatch@v3 with: token: ${{ secrets.REPO_GHA_PAT }} repository: StevenMMortimer/salesforcer diff --git a/.github/workflows/main-02-test-coverage.yml b/.github/workflows/main-02-test-coverage.yml index 4d3c654a..86bef053 100644 --- a/.github/workflows/main-02-test-coverage.yml +++ b/.github/workflows/main-02-test-coverage.yml @@ -86,7 +86,7 @@ jobs: - name: Trigger next workflow if: ${{ (github.event_name != 'workflow_dispatch' && success()) || (github.event_name == 'workflow_dispatch' && github.event.inputs.trigger_next && success()) }} - uses: peter-evans/repository-dispatch@v1 + uses: peter-evans/repository-dispatch@v3 with: token: ${{ secrets.REPO_GHA_PAT }} repository: StevenMMortimer/salesforcer @@ -95,7 +95,7 @@ jobs: - name: Set final R-CMD-check status if: ${{ (github.event_name != 'workflow_dispatch' && failure()) }} - uses: peter-evans/repository-dispatch@v1 + uses: peter-evans/repository-dispatch@v3 with: token: ${{ secrets.REPO_GHA_PAT }} repository: StevenMMortimer/salesforcer diff --git a/.github/workflows/main-03-R-CMD-check-mac.yml b/.github/workflows/main-03-R-CMD-check-mac.yml index 407a74cc..01d53812 100644 --- a/.github/workflows/main-03-R-CMD-check-mac.yml +++ b/.github/workflows/main-03-R-CMD-check-mac.yml @@ -104,7 +104,7 @@ jobs: - name: Trigger next workflow if: ${{ (github.event_name != 'workflow_dispatch' && success()) || (github.event_name == 'workflow_dispatch' && github.event.inputs.trigger_next && success()) }} - uses: peter-evans/repository-dispatch@v1 + uses: peter-evans/repository-dispatch@v3 with: token: ${{ secrets.REPO_GHA_PAT }} repository: StevenMMortimer/salesforcer @@ -113,7 +113,7 @@ jobs: - name: Set final R-CMD-check status if: ${{ (github.event_name != 'workflow_dispatch' && failure()) }} - uses: peter-evans/repository-dispatch@v1 + uses: peter-evans/repository-dispatch@v3 with: token: ${{ secrets.REPO_GHA_PAT }} repository: StevenMMortimer/salesforcer diff --git a/.github/workflows/main-04-R-CMD-check-windows.yml b/.github/workflows/main-04-R-CMD-check-windows.yml index 92f86130..c97d97c8 100644 --- a/.github/workflows/main-04-R-CMD-check-windows.yml +++ b/.github/workflows/main-04-R-CMD-check-windows.yml @@ -104,7 +104,7 @@ jobs: - name: Trigger next workflow if: ${{ (github.event_name != 'workflow_dispatch' && success()) || (github.event_name == 'workflow_dispatch' && github.event.inputs.trigger_next && success()) }} - uses: peter-evans/repository-dispatch@v1 + uses: peter-evans/repository-dispatch@v3 with: token: ${{ secrets.REPO_GHA_PAT }} repository: StevenMMortimer/salesforcer @@ -113,7 +113,7 @@ jobs: - name: Set final R-CMD-check status if: ${{ (github.event_name != 'workflow_dispatch' && failure()) }} - uses: peter-evans/repository-dispatch@v1 + uses: peter-evans/repository-dispatch@v3 with: token: ${{ secrets.REPO_GHA_PAT }} repository: StevenMMortimer/salesforcer From 4b8580916bf21af85e8a71f5a9cc51b528e05bb3 Mon Sep 17 00:00:00 2001 From: "Steven M. Mortimer" Date: Mon, 4 Nov 2024 15:31:48 -0600 Subject: [PATCH 07/18] Fix broken and moved URLs --- DESCRIPTION | 2 +- R/attachments.R | 362 ++++++++--------- R/utils-control.R | 364 +++++++++--------- README.Rmd | 4 +- README.md | 6 +- man/sf_control.Rd | 12 +- man/sf_create_attachment.Rd | 16 +- man/sf_download_attachment.Rd | 6 +- man/sf_update_attachment.Rd | 4 +- .../doc/working-with-attachments.Rmd | 4 +- vignettes/getting-started.Rmd | 2 +- 11 files changed, 391 insertions(+), 391 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 3e00a40e..f5348d91 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -4,7 +4,7 @@ Version: 1.0.2 Date: 2024-11-04 Description: Functions connecting to the 'Salesforce' Platform APIs (REST, SOAP, Bulk 1.0, Bulk 2.0, Metadata, Reports and Dashboards) - . + . "API" is an acronym for "application programming interface". Most all calls from these APIs are supported as they use CSV, XML or JSON data that can be parsed into R data structures. For more details please see the 'Salesforce' diff --git a/R/attachments.R b/R/attachments.R index e5431655..ba97eaf7 100644 --- a/R/attachments.R +++ b/R/attachments.R @@ -1,15 +1,15 @@ #' Download an Attachment -#' +#' #' @description #' `r lifecycle::badge("stable")` -#' -#' This function will allow you to download an attachment to disk based on the +#' +#' This function will allow you to download an attachment to disk based on the #' attachment body, file name, and path. -#' +#' #' @importFrom tools file_ext #' @importFrom httr content -#' @param body \code{character}; a URL path to the body of the attachment in -#' Salesforce, typically retrieved by \code{\link{sf_query}} on the Attachment +#' @param body \code{character}; a URL path to the body of the attachment in +#' Salesforce, typically retrieved by \code{\link{sf_query}} on the Attachment #' object. Alternatively, you can specify the Salesforce Id of the Attachment. #' @param name \code{character}; the name of the file you would like to save the #' content to. Note that you should include the file extension in this name @@ -19,56 +19,56 @@ #' whenever possible for the best performance. #' @template sf_id #' @template object_name -#' @param path \code{character}; a directory path where to create file, defaults +#' @param path \code{character}; a directory path where to create file, defaults #' to the current directory. -#' @return \code{character}; invisibly return the file path of the downloaded +#' @return \code{character}; invisibly return the file path of the downloaded #' content #' @family Attachment functions #' @section Salesforce Documentation: #' \itemize{ #' \item \href{https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_sobject_blob_retrieve.htm}{Get Attachment Content from a Record} #' } -#' @examples +#' @examples #' \dontrun{ #' # downloading all attachments for a Parent record -#' # if your attachment name doesn't include the extension, then you can use the +#' # if your attachment name doesn't include the extension, then you can use the #' # ContentType column to append it to the Name, if needed -#' queried_attachments <- sf_query("SELECT Id, Body, Name, ContentType -#' FROM Attachment +#' queried_attachments <- sf_query("SELECT Id, Body, Name, ContentType +#' FROM Attachment #' WHERE ParentId = '0016A0000035mJ5'") #' mapply(sf_download_attachment, queried_attachments$Body, queried_attachments$Name) -#' +#' #' # downloading an attachment by its Id #' # (the file name will be the same as it exists in Salesforce) #' sf_download_attachment(sf_id = queried_attachments$Id[1]) #' } -#' @export -sf_download_attachment <- function(body, - name = NULL, - sf_id = NULL, +#' @export +sf_download_attachment <- function(body, + name = NULL, + sf_id = NULL, object_name = c("Attachment", "Document"), path = "."){ - + object_name <- match.arg(object_name) - + if(!is.null(sf_id)){ - this_url <- sprintf("%s/services/data/v48.0/sobjects/%s/%s/Body", + this_url <- sprintf("%s/services/data/v48.0/sobjects/%s/%s/Body", salesforcer_state()$instance_url, object_name, sf_id) } else { this_url <- sprintf("%s%s", salesforcer_state()$instance_url, body) } resp <- rGET(this_url) - + if(is.null(name)){ # need to derive the id and then pull if(is.null(sf_id)){ object_name <- gsub("sobjects/(.*)/(.*)/Body", "\\1", this_url) sf_id <- gsub("sobjects/(.*)/(.*)/Body", "\\2", this_url) } - details <- sf_query(sprintf("SELECT Name, ContentType + details <- sf_query(sprintf("SELECT Name, ContentType FROM %s - WHERE Id = '%s'", - object_name, + WHERE Id = '%s'", + object_name, sf_id) ) content_ext <- file_ext(details$Name[1]) @@ -78,103 +78,103 @@ sf_download_attachment <- function(body, name <- details$Name[1] } } - + f <- file.path(path, name) writeBin(content(resp, "raw"), f) return(invisible(f)) } #' Create Attachments -#' +#' #' @description #' `r lifecycle::badge("experimental")` -#' -#' This function will allow you to create attachments (and other blob data, such as -#' Documents) by supplying file paths (absolute or relative) to media that you -#' would like to upload to Salesforce along with accompanying metadata, such as +#' +#' This function will allow you to create attachments (and other blob data, such as +#' Documents) by supplying file paths (absolute or relative) to media that you +#' would like to upload to Salesforce along with accompanying metadata, such as #' a Description, Keywords, ParentId, FolderId, etc. -#' +#' #' @template attachment_input_data #' @template object_name #' @template api_type #' @template control -#' @param ... arguments passed to \code{\link{sf_control}} or further downstream +#' @param ... arguments passed to \code{\link{sf_control}} or further downstream #' to \code{\link{sf_bulk_operation}} #' @template verbose #' @return \code{tbl_df} with details of the created records -#' @note The length of any file name can’t exceed 512 bytes (per Bulk 1.0 API). -#' The SOAP API create call restricts these files to a maximum size of 25 MB. For a file -#' attached to a Solution, the limit is 1.5 MB. The maximum email attachment size is 3 MB. -#' You can only create or update documents to a maximum size of 5 MB. The REST API -#' allows you to insert or update blob data limited to 50 MB of text data or 37.5 MB +#' @note The length of any file name can’t exceed 512 bytes (per Bulk 1.0 API). +#' The SOAP API create call restricts these files to a maximum size of 25 MB. For a file +#' attached to a Solution, the limit is 1.5 MB. The maximum email attachment size is 3 MB. +#' You can only create or update documents to a maximum size of 5 MB. The REST API +#' allows you to insert or update blob data limited to 50 MB of text data or 37.5 MB #' of base64–encoded data. #' @family Attachment functions #' @section Salesforce Documentation: #' \itemize{ -#' \item \href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_objects_attachment.htm}{Attachment Object (SOAP)} -#' \item \href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_objects_document.htm}{Document Object (SOAP)} +#' \item \href{https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_attachment.htm}{Attachment Object (SOAP)} +#' \item \href{https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_document.htm}{Document Object (SOAP)} #' \item \href{https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_sobject_insert_update_blob.htm}{Insert or Update Blob Data} #' } -#' @examples +#' @examples #' \dontrun{ #' # upload two PDFs from working directory to a particular record as Attachments #' file_path1 <- here::here("doc1.pdf") #' file_path2 <- here::here("doc2.pdf") #' parent_record_id <- "0036A000002C6MmQAK" -#' attachment_details <- tibble(Body = c(file_path1, file_path2), +#' attachment_details <- tibble(Body = c(file_path1, file_path2), #' ParentId = rep(parent_record_id, 2)) #' result <- sf_create_attachment(attachment_details) -#' -#' # the function supports inserting all blob content, just update the +#' +#' # the function supports inserting all blob content, just update the #' # object_name argument to add the PDF as a Document instead of an Attachment -#' document_details <- tibble(Name = "doc1.pdf", -#' Description = "Test Document 1", +#' document_details <- tibble(Name = "doc1.pdf", +#' Description = "Test Document 1", #' Body = file_path1, #' FolderId = "00l6A000001EgIwQAK", # replace with your FolderId! #' Keywords = "example,test,document") #' result <- sf_create_attachment(document_details, object_name = "Document") -#' -#' # the Bulk API can be invoked using api_type="Bulk 1.0" which will automatically -#' # take a data.frame of Attachment info and create a ZIP file with CSV manifest +#' +#' # the Bulk API can be invoked using api_type="Bulk 1.0" which will automatically +#' # take a data.frame of Attachment info and create a ZIP file with CSV manifest #' # that is required for that API #' result <- sf_create_attachment(attachment_details, api_type="Bulk 1.0") #' } #' @export -sf_create_attachment <- function(attachment_input_data, +sf_create_attachment <- function(attachment_input_data, object_name = c("Attachment", "Document"), api_type = c("SOAP", "REST", "Bulk 1.0", "Bulk 2.0"), control = list(...), ..., verbose = FALSE){ - + api_type <- match.arg(api_type) object_name <- match.arg(object_name) - - # determine how to pass along the control args + + # determine how to pass along the control args all_args <- list(...) control_args <- return_matching_controls(control) control_args$api_type <- api_type control_args$operation <- "insert" if("AssignmentRuleHeader" %in% names(control_args)){ if(!object_name %in% c("Account", "Case", "Lead")){ - stop(paste0("The AssignmentRuleHeader can only be used when creating, ", + stop(paste0("The AssignmentRuleHeader can only be used when creating, ", "updating, or upserting an Account, Case, or Lead."), .call=FALSE) } } - + if(api_type == "SOAP"){ resultset <- sf_create_attachment_soap(attachment_input_data = attachment_input_data, object_name = object_name, - control = control_args, + control = control_args, verbose = verbose) } else if(api_type == "REST"){ resultset <- sf_create_attachment_rest(attachment_input_data = attachment_input_data, object_name = object_name, - control = control_args, + control = control_args, verbose = verbose) } else if(api_type == "Bulk 1.0"){ - resultset <- sf_create_attachment_bulk_v1(attachment_input_data = attachment_input_data, + resultset <- sf_create_attachment_bulk_v1(attachment_input_data = attachment_input_data, object_name = object_name, - control = control_args, + control = control_args, verbose = verbose, ...) } else { catch_unknown_api(api_type, c("SOAP", "REST", "Bulk 1.0")) @@ -183,7 +183,7 @@ sf_create_attachment <- function(attachment_input_data, } #' Create Attachment using SOAP API -#' +#' #' @importFrom readr cols type_convert #' @importFrom httr content #' @importFrom xml2 xml_ns_strip xml_find_all @@ -191,21 +191,21 @@ sf_create_attachment <- function(attachment_input_data, #' @importFrom dplyr bind_rows #' @note This function is meant to be used internally. Only use when debugging. #' @keywords internal -sf_create_attachment_soap <- function(attachment_input_data, +sf_create_attachment_soap <- function(attachment_input_data, object_name = c("Attachment", "Document"), control, ..., verbose = FALSE){ - + object_name <- match.arg(object_name) - input_data <- sf_input_data_validation(operation = sprintf("create_%s", - tolower(object_name)), + input_data <- sf_input_data_validation(operation = sprintf("create_%s", + tolower(object_name)), attachment_input_data) # check if files exist at paths specified and encode which is required for SOAP input_data <- check_and_encode_files(input_data, encode=TRUE) - + control <- do.call("sf_control", control) if("AllOrNoneHeader" %in% names(control)){ - message(paste0("The `AllOrNoneHeader` is ignored when creating attachments ", + message(paste0("The `AllOrNoneHeader` is ignored when creating attachments ", "since the procedure iterates one at a time.")) } base_soap_url <- make_base_soap_url() @@ -217,32 +217,32 @@ sf_create_attachment_soap <- function(attachment_input_data, object_name = object_name, root = r) request_body <- as(xml_dat, "character") - httr_response <- rPOST(url = base_soap_url, - headers = c("SOAPAction"="create", - "Content-Type"="text/xml"), + httr_response <- rPOST(url = base_soap_url, + headers = c("SOAPAction"="create", + "Content-Type"="text/xml"), body = request_body) if(verbose){ make_verbose_httr_message(httr_response$request$method, - httr_response$request$url, - httr_response$request$headers, + httr_response$request$url, + httr_response$request$headers, request_body) } catch_errors(httr_response) response_parsed <- content(httr_response, encoding="UTF-8") this_set <- response_parsed %>% xml_ns_strip() %>% - xml_find_all('.//result') %>% + xml_find_all('.//result') %>% map_df(xml_nodeset_to_df) resultset <- safe_bind_rows(list(resultset, this_set)) } resultset <- resultset %>% - sf_reorder_cols() %>% + sf_reorder_cols() %>% sf_guess_cols() return(resultset) } #' Create Attachment using REST API -#' +#' #' @importFrom readr cols type_convert #' @importFrom dplyr as_tibble bind_rows #' @importFrom jsonlite toJSON fromJSON prettify @@ -250,26 +250,26 @@ sf_create_attachment_soap <- function(attachment_input_data, #' @importFrom curl form_data form_file #' @note This function is meant to be used internally. Only use when debugging. #' @keywords internal -sf_create_attachment_rest <- function(attachment_input_data, +sf_create_attachment_rest <- function(attachment_input_data, object_name = c("Attachment", "Document"), - control, ..., + control, ..., verbose = FALSE){ - + object_name <- match.arg(object_name) - input_data <- sf_input_data_validation(operation = sprintf("create_%s", - tolower(object_name)), + input_data <- sf_input_data_validation(operation = sprintf("create_%s", + tolower(object_name)), attachment_input_data) # check if files exist at paths specified since REST doesn't need them encoded input_data <- check_and_encode_files(input_data, encode=FALSE) - + control <- do.call("sf_control", control) if("AllOrNoneHeader" %in% names(control)){ - message(paste0("The `AllOrNoneHeader` is ignored when creating attachments ", + message(paste0("The `AllOrNoneHeader` is ignored when creating attachments ", "since the procedure iterates one at a time.")) } - request_headers <- c("Accept" = "application/json", + request_headers <- c("Accept" = "application/json", "Content-Type" = "application/json") - target_url <- make_rest_objects_url(object_name) + target_url <- make_rest_objects_url(object_name) resultset <- NULL for(i in 1:nrow(input_data)){ this_data <- as.list(input_data[i, , drop=TRUE]) @@ -284,10 +284,10 @@ sf_create_attachment_rest <- function(attachment_input_data, httr_response <- rPOST(url = target_url, body = request_body, encode = "multipart") if(verbose){ make_verbose_httr_message(httr_response$request$method, - httr_response$request$url, - httr_response$request$headers, + httr_response$request$url, + httr_response$request$headers, paste("--boundary_string ", - prettify(json_metadata), + prettify(json_metadata), "--boundary_string ", sprintf("Binary data from file: %s", this_file_path), "--boundary_string-- ", @@ -295,34 +295,34 @@ sf_create_attachment_rest <- function(attachment_input_data, } catch_errors(httr_response) response_parsed <- content(httr_response, as = "text", encoding = "UTF-8") - # NOTE: Wrap response with "[]" to make the JSON an array of results, otherwise + # NOTE: Wrap response with "[]" to make the JSON an array of results, otherwise # converting the fromJSON response into a dataframe will choke on the empty list - # I have no idea why fromJSON would parse things differently. - # This doesn't occur with the calls to the composite REST API like with create - # and update because those inherently passed as JSON arrays + # I have no idea why fromJSON would parse things differently. + # This doesn't occur with the calls to the composite REST API like with create + # and update because those inherently passed as JSON arrays resultset <- safe_bind_rows(list(resultset, fromJSON(sprintf("[%s]", response_parsed)))) } resultset <- resultset %>% as_tibble(.name_repair = "unique") %>% - sf_reorder_cols() %>% + sf_reorder_cols() %>% sf_guess_cols() return(resultset) } #' Create Attachments using Bulk 1.0 API -#' +#' #' @note This function is meant to be used internally. Only use when debugging. #' @keywords internal -sf_create_attachment_bulk_v1 <- function(attachment_input_data, +sf_create_attachment_bulk_v1 <- function(attachment_input_data, object_name = c("Attachment", "Document"), content_type = "ZIP_CSV", control, ..., verbose = FALSE){ object_name <- match.arg(object_name) control <- do.call("sf_control", control) - resultset <- sf_bulk_operation(input_data = attachment_input_data, - object_name = object_name, - operation = "insert", + resultset <- sf_bulk_operation(input_data = attachment_input_data, + object_name = object_name, + operation = "insert", api_type = "Bulk 1.0", content_type = content_type, control = control, ..., @@ -331,38 +331,38 @@ sf_create_attachment_bulk_v1 <- function(attachment_input_data, } #' Update Attachments -#' +#' #' @description #' `r lifecycle::badge("experimental")` -#' -#' This function will allow you to update attachments (and other blob data, such as -#' Documents) by providing the Id of the attachment record and the file paths -#' (absolute or relative) to media that you would like to upload to Salesforce -#' along with other supported metadata for this operation (\code{Name}, +#' +#' This function will allow you to update attachments (and other blob data, such as +#' Documents) by providing the Id of the attachment record and the file paths +#' (absolute or relative) to media that you would like to upload to Salesforce +#' along with other supported metadata for this operation (\code{Name}, #' \code{Body}, \code{IsPrivate}, and \code{OwnerId}). -#' +#' #' @template attachment_input_data #' @template object_name #' @template api_type #' @template control -#' @param ... arguments passed to \code{\link{sf_control}} or further downstream +#' @param ... arguments passed to \code{\link{sf_control}} or further downstream #' to \code{\link{sf_bulk_operation}} #' @template verbose #' @return \code{tbl_df} with details of the created records -#' @note The length of any file name can’t exceed 512 bytes (per Bulk 1.0 API). -#' The SOAP API create call restricts these files to a maximum size of 25 MB. For a file -#' attached to a Solution, the limit is 1.5 MB. The maximum email attachment size is 3 MB. -#' You can only create or update documents to a maximum size of 5 MB. The REST API -#' allows you to insert or update blob data limited to 50 MB of text data or 37.5 MB +#' @note The length of any file name can’t exceed 512 bytes (per Bulk 1.0 API). +#' The SOAP API create call restricts these files to a maximum size of 25 MB. For a file +#' attached to a Solution, the limit is 1.5 MB. The maximum email attachment size is 3 MB. +#' You can only create or update documents to a maximum size of 5 MB. The REST API +#' allows you to insert or update blob data limited to 50 MB of text data or 37.5 MB #' of base64–encoded data. #' @family Attachment functions #' @section Salesforce Documentation: #' \itemize{ -#' \item \href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_objects_attachment.htm}{Attachment Object (SOAP)} -#' \item \href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_objects_document.htm}{Document Object (SOAP)} +#' \item \href{https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_attachment.htm}{Attachment Object (SOAP)} +#' \item \href{https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_document.htm}{Document Object (SOAP)} #' \item \href{https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_sobject_insert_update_blob.htm}{Insert or Update Blob Data} #' } -#' @examples +#' @examples #' \dontrun{ #' # upload a PDF to a particular record as an Attachment #' file_path <- system.file("extdata", @@ -371,7 +371,7 @@ sf_create_attachment_bulk_v1 <- function(attachment_input_data, #' parent_record_id <- "0036A000002C6MmQAK" # replace with your own ParentId! #' attachment_details <- tibble(Body = file_path, ParentId = parent_record_id) #' create_result <- sf_create_attachment(attachment_details) -#' +#' #' # download, zip, and re-upload the PDF #' pdf_path <- sf_download_attachment(sf_id = create_result$id[1]) #' zipped_path <- paste0(pdf_path, ".zip") @@ -387,34 +387,34 @@ sf_update_attachment <- function(attachment_input_data, verbose = FALSE){ object_name <- match.arg(object_name) api_type <- match.arg(api_type) - - # determine how to pass along the control args + + # determine how to pass along the control args all_args <- list(...) control_args <- return_matching_controls(control) control_args$api_type <- api_type control_args$operation <- "update" if("AssignmentRuleHeader" %in% names(control_args)){ if(!object_name %in% c("Account", "Case", "Lead")){ - stop(paste0("The AssignmentRuleHeader can only be used when creating, ", + stop(paste0("The AssignmentRuleHeader can only be used when creating, ", "updating, or upserting an Account, Case, or Lead."), .call=FALSE) } } - + if(api_type == "SOAP"){ resultset <- sf_update_attachment_soap(attachment_input_data = attachment_input_data, object_name = object_name, - control = control_args, + control = control_args, verbose = verbose) } else if(api_type == "REST"){ resultset <- sf_update_attachment_rest(attachment_input_data = attachment_input_data, object_name = object_name, - control = control_args, + control = control_args, verbose = verbose) - + } else if(api_type == "Bulk 1.0"){ - resultset <- sf_update_attachment_bulk_v1(attachment_input_data = attachment_input_data, + resultset <- sf_update_attachment_bulk_v1(attachment_input_data = attachment_input_data, object_name = object_name, - control = control_args, + control = control_args, verbose = verbose, ...) } else { catch_unknown_api(api_type, c("SOAP", "REST")) @@ -423,7 +423,7 @@ sf_update_attachment <- function(attachment_input_data, } #' Update Attachment using SOAP API -#' +#' #' @importFrom readr cols type_convert #' @importFrom httr content #' @importFrom xml2 xml_ns_strip xml_find_all @@ -431,20 +431,20 @@ sf_update_attachment <- function(attachment_input_data, #' @importFrom dplyr bind_rows #' @note This function is meant to be used internally. Only use when debugging. #' @keywords internal -sf_update_attachment_soap <- function(attachment_input_data, +sf_update_attachment_soap <- function(attachment_input_data, object_name = c("Attachment"), control, ..., verbose = FALSE){ object_name <- match.arg(object_name) - input_data <- sf_input_data_validation(operation = sprintf("update_%s", - tolower(object_name)), + input_data <- sf_input_data_validation(operation = sprintf("update_%s", + tolower(object_name)), attachment_input_data) # check if files exist at paths specified and encode which is required for SOAP input_data <- check_and_encode_files(input_data, encode=TRUE) - + control <- do.call("sf_control", control) if("AllOrNoneHeader" %in% names(control)){ - message(paste0("The `AllOrNoneHeader` is ignored when updating attachments ", + message(paste0("The `AllOrNoneHeader` is ignored when updating attachments ", "since the procedure iterates one at a time.")) } base_soap_url <- make_base_soap_url() @@ -456,32 +456,32 @@ sf_update_attachment_soap <- function(attachment_input_data, object_name = object_name, root = r) request_body <- as(xml_dat, "character") - httr_response <- rPOST(url = base_soap_url, - headers = c("SOAPAction" = "update", - "Content-Type" = "text/xml"), + httr_response <- rPOST(url = base_soap_url, + headers = c("SOAPAction" = "update", + "Content-Type" = "text/xml"), body = request_body) if(verbose){ make_verbose_httr_message(httr_response$request$method, - httr_response$request$url, - httr_response$request$headers, + httr_response$request$url, + httr_response$request$headers, request_body) } catch_errors(httr_response) response_parsed <- content(httr_response, as="parsed", encoding="UTF-8") this_set <- response_parsed %>% xml_ns_strip() %>% - xml_find_all('.//result') %>% + xml_find_all('.//result') %>% map_df(xml_nodeset_to_df) resultset <- safe_bind_rows(list(resultset, this_set)) } resultset <- resultset %>% - sf_reorder_cols() %>% + sf_reorder_cols() %>% sf_guess_cols() return(resultset) } #' Update Attachment using REST API -#' +#' #' @importFrom readr cols type_convert #' @importFrom dplyr as_tibble bind_rows #' @importFrom jsonlite toJSON fromJSON prettify @@ -490,23 +490,23 @@ sf_update_attachment_soap <- function(attachment_input_data, #' @importFrom httr status_code http_error content #' @note This function is meant to be used internally. Only use when debugging. #' @keywords internal -sf_update_attachment_rest <- function(attachment_input_data, +sf_update_attachment_rest <- function(attachment_input_data, object_name = c("Attachment"), - control, ..., + control, ..., verbose = FALSE){ object_name <- match.arg(object_name) - input_data <- sf_input_data_validation(operation = sprintf("update_%s", - tolower(object_name)), + input_data <- sf_input_data_validation(operation = sprintf("update_%s", + tolower(object_name)), attachment_input_data) # check if files exist at paths specified since REST doesn't need them encoded input_data <- check_and_encode_files(input_data, encode=FALSE) - + control <- do.call("sf_control", control) if("AllOrNoneHeader" %in% names(control)){ - message(paste0("The `AllOrNoneHeader` is ignored when updating attachments ", + message(paste0("The `AllOrNoneHeader` is ignored when updating attachments ", "since the procedure iterates one at a time.")) } - request_headers <- c("Accept" = "application/json", + request_headers <- c("Accept" = "application/json", "Content-Type" = "application/json") resultset <- NULL for(i in 1:nrow(input_data)){ @@ -522,21 +522,21 @@ sf_update_attachment_rest <- function(attachment_input_data, Body = form_file(this_file_path, type = guess_type(this_file_path)) ) # post the multipart body - httr_response <- rPATCH(url = target_url, - body = request_body, + httr_response <- rPATCH(url = target_url, + body = request_body, encode = "multipart") if(verbose){ make_verbose_httr_message(httr_response$request$method, - httr_response$request$url, - httr_response$request$headers, + httr_response$request$url, + httr_response$request$headers, paste("--boundary_string ", - prettify(json_metadata), + prettify(json_metadata), "--boundary_string ", sprintf("Binary data from file: %s", this_file_path), "--boundary_string-- ", sep = "\n")) } - + # format the result because if successful, it will be blank! if(status_code(httr_response) == 204){ this_resultset <- tibble(id = this_id, success = TRUE, errors = list(list())) @@ -545,25 +545,25 @@ sf_update_attachment_rest <- function(attachment_input_data, parsed_error <- parse_error_code_and_message(response_parsed) this_resultset <- tibble(id = this_id, success = FALSE, errors = list(parsed_error)) } else { - # Assuming we need to wrap response with "[]" to make the JSON an array of + # Assuming we need to wrap response with "[]" to make the JSON an array of # like we do with `sf_create_attachment_rest()` response_parsed <- content(httr_response, as = "text", encoding = "UTF-8") - this_resultset <- fromJSON(sprintf("[%s]", response_parsed)) + this_resultset <- fromJSON(sprintf("[%s]", response_parsed)) } resultset <- safe_bind_rows(list(resultset, this_resultset)) } resultset <- resultset %>% as_tibble(.name_repair = "unique") %>% - sf_reorder_cols() %>% + sf_reorder_cols() %>% sf_guess_cols() return(resultset) } #' Update Attachments using Bulk 1.0 API -#' +#' #' @note This function is meant to be used internally. Only use when debugging. #' @keywords internal -sf_update_attachment_bulk_v1 <- function(attachment_input_data, +sf_update_attachment_bulk_v1 <- function(attachment_input_data, object_name = c("Attachment"), content_type = "ZIP_CSV", control, ..., @@ -581,27 +581,27 @@ sf_update_attachment_bulk_v1 <- function(attachment_input_data, } #' Delete Attachments -#' +#' #' @description #' `r lifecycle::badge("experimental")` -#' -#' This function is a wrapper around \code{\link{sf_delete}} that accepts a list -#' of Ids and assumes that they are in the Attachment object and should be deleted. -#' This function is solely provided as a convenience and to provide the last +#' +#' This function is a wrapper around \code{\link{sf_delete}} that accepts a list +#' of Ids and assumes that they are in the Attachment object and should be deleted. +#' This function is solely provided as a convenience and to provide the last #' attachment function to parallel the CRUD functionality for all other records. -#' +#' #' @template ids #' @template object_name #' @template api_type -#' @param ... arguments passed to \code{\link{sf_control}} or further downstream +#' @param ... arguments passed to \code{\link{sf_control}} or further downstream #' to \code{\link{sf_bulk_operation}} #' @template verbose #' @return \code{tbl_df} with details of the deleted records -#' @note Because the SOAP and REST calls chunk data into batches of 200 records -#' the AllOrNoneHeader will only apply to the success or failure of every batch +#' @note Because the SOAP and REST calls chunk data into batches of 200 records +#' the AllOrNoneHeader will only apply to the success or failure of every batch #' of records and not all records submitted to the function. #' @family Attachment functions -#' @examples +#' @examples #' \dontrun{ #' # upload a PDF to a particular record as an Attachment #' file_path <- system.file("extdata", @@ -610,7 +610,7 @@ sf_update_attachment_bulk_v1 <- function(attachment_input_data, #' parent_record_id <- "0036A000002C6MmQAK" # replace with your own ParentId! #' attachment_details <- tibble(Body = file_path, ParentId = parent_record_id) #' create_result <- sf_create_attachment(attachment_details) -#' +#' #' # now delete the attachment #' # note that the function below is just running the following! #' # sf_delete(ids = create_result$id) @@ -624,27 +624,27 @@ sf_delete_attachment <- function(ids, verbose = FALSE){ object_name <- match.arg(object_name) api_type <- match.arg(api_type) - sf_delete(ids = ids, - object_name = object_name, - api_type = api_type, - ..., + sf_delete(ids = ids, + object_name = object_name, + api_type = api_type, + ..., verbose = verbose) } #' Check that file paths exist and data is encoded if specified -#' +#' #' @importFrom base64enc base64encode #' @family Attachment functions -#' @param dat \code{tbl_df} or \code{list} of information regarding attachments +#' @param dat \code{tbl_df} or \code{list} of information regarding attachments #' stored locally that will be encoded for use in the APIs. -#' @param column \code{character}; a string that indicates which column in the +#' @param column \code{character}; a string that indicates which column in the #' \code{dat} argument is storing the body of information that needs to be encoded. -#' @param encode \code{logical}; a indicator of whether the body column should -#' be encoded in this step, which allows us to utilize this function for checking +#' @param encode \code{logical}; a indicator of whether the body column should +#' be encoded in this step, which allows us to utilize this function for checking #' or checking and encoding. -#' @param n_check \code{integer}; an integer specifying how many elements in the -#' \code{dat} argument that should be checked to see if the referenced file path -#' exists locally. This fails the function early if users accidentally specify +#' @param n_check \code{integer}; an integer specifying how many elements in the +#' \code{dat} argument that should be checked to see if the referenced file path +#' exists locally. This fails the function early if users accidentally specify #' the wrong path. #' @note This function is meant to be used internally. Only use when debugging. #' @keywords internal @@ -652,7 +652,7 @@ sf_delete_attachment <- function(ids, check_and_encode_files <- function(dat, column = "Body", encode = TRUE, n_check = 100){ # Documents can be created from Urls so ignore encoding if there is no "Body" column if(column == "Body" & !("Body" %in% names(dat))){ - # assume that the column was not specified and left as "Body" default but the + # assume that the column was not specified and left as "Body" default but the # data itself contains another column holding the body data, typically a Url } else { # stop if the body is a factor @@ -667,23 +667,23 @@ check_and_encode_files <- function(dat, column = "Body", encode = TRUE, n_check message(sprintf("Row %s, File Not Found: %s", i, dat[[i, column]])) } if(sum(!files_exist) > 5){ - message(sprintf("There were %s files not found. Run `sapply(dat[,'%s'], file.exists)` to see them all)", + message(sprintf("There were %s files not found. Run `sapply(dat[,'%s'], file.exists)` to see them all)", sum(!files_exist), column)) } if(nrow(dat) > n_check){ - message(sprintf("Only checked the first %s rows of the '%s' column are valid file paths.", + message(sprintf("Only checked the first %s rows of the '%s' column are valid file paths.", n_check, column)) } stop(sprintf("Cannot process until all values in '%s' column are valid file paths.", column)) } - # in the event that the Name field is missing then, create it using the file's + # in the event that the Name field is missing then, create it using the file's # basename (includes the extension; for example, "doc1.pdf") if(column == "Body" & ("Body" %in% names(dat)) & !("Name" %in% names(dat))){ dat[,"Name"] <- basename(dat[["Body"]]) } if(encode){ # base64 encode the values in the target column - dat[,column] <- sapply(dat[[column]], base64encode) + dat[,column] <- sapply(dat[[column]], base64encode) } } return(dat) diff --git a/R/utils-control.R b/R/utils-control.R index 6dd7db29..92cbfd6f 100644 --- a/R/utils-control.R +++ b/R/utils-control.R @@ -1,178 +1,178 @@ #' Auxiliary for Controlling Calls to Salesforce APIs -#' -#' Typically only used internally by functions when control parameters are passed -#' through via dots (...), but it can be called directly to control the behavior -#' of API calls. This function behaves exactly like \code{\link[stats]{glm.control}} +#' +#' Typically only used internally by functions when control parameters are passed +#' through via dots (...), but it can be called directly to control the behavior +#' of API calls. This function behaves exactly like \code{\link[stats]{glm.control}} #' for the \code{\link[stats]{glm}} function. -#' +#' #' @importFrom purrr modify modify_if map -#' @param AllOrNoneHeader \code{list}; containing the \code{allOrNone} element with -#' a value of \code{TRUE} or \code{FALSE}. This control specifies whether a call rolls back all changes -#' unless all records are processed successfully. This control is available in -#' SOAP, REST, and Metadata APIs for the following functions: \code{\link{sf_create}}, -#' \code{\link{sf_delete}}, \code{\link{sf_update}}, \code{\link{sf_upsert}}, \code{\link{sf_create_metadata}}, -#' \code{\link{sf_delete_metadata}}, \code{\link{sf_update_metadata}}, \code{\link{sf_upsert_metadata}}. -#' For more information, read the Salesforce documentation +#' @param AllOrNoneHeader \code{list}; containing the \code{allOrNone} element with +#' a value of \code{TRUE} or \code{FALSE}. This control specifies whether a call rolls back all changes +#' unless all records are processed successfully. This control is available in +#' SOAP, REST, and Metadata APIs for the following functions: \code{\link{sf_create}}, +#' \code{\link{sf_delete}}, \code{\link{sf_update}}, \code{\link{sf_upsert}}, \code{\link{sf_create_metadata}}, +#' \code{\link{sf_delete_metadata}}, \code{\link{sf_update_metadata}}, \code{\link{sf_upsert_metadata}}. +#' For more information, read the Salesforce documentation #' \href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_header_allornoneheader.htm}{here}. -#' @param AllowFieldTruncationHeader \code{list}; containing the \code{allowFieldTruncation} -#' element with a value of \code{TRUE} or \code{FALSE}. This control specifies the truncation behavior -#' for some field types in SOAP API version 15.0 and later for the following functions: -#' \code{\link{sf_create}}, \code{\link{sf_update}}, \code{\link{sf_upsert}}. For -#' more information, read the Salesforce documentation +#' @param AllowFieldTruncationHeader \code{list}; containing the \code{allowFieldTruncation} +#' element with a value of \code{TRUE} or \code{FALSE}. This control specifies the truncation behavior +#' for some field types in SOAP API version 15.0 and later for the following functions: +#' \code{\link{sf_create}}, \code{\link{sf_update}}, \code{\link{sf_upsert}}. For +#' more information, read the Salesforce documentation #' \href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_header_allowfieldtruncation.htm}{here}. -#' @param AssignmentRuleHeader \code{list}; containing the \code{useDefaultRule} -#' element with a value of \code{TRUE} or \code{FALSE} or the \code{assignmentRuleId} element. -#' This control specifies the assignment rule to use when creating or updating an -#' Account, Case, or Lead for the following functions: \code{\link{sf_create}}, -#' \code{\link{sf_update}}, \code{\link{sf_upsert}}. For more information, read the Salesforce documentation +#' @param AssignmentRuleHeader \code{list}; containing the \code{useDefaultRule} +#' element with a value of \code{TRUE} or \code{FALSE} or the \code{assignmentRuleId} element. +#' This control specifies the assignment rule to use when creating or updating an +#' Account, Case, or Lead for the following functions: \code{\link{sf_create}}, +#' \code{\link{sf_update}}, \code{\link{sf_upsert}}. For more information, read the Salesforce documentation #' \href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_header_assignmentruleheader.htm}{here}. -#' @param DisableFeedTrackingHeader \code{list}; containing the \code{disableFeedTracking} -#' element with a value of \code{TRUE} or \code{FALSE}. This control specifies whether -#' the changes made in the current call are tracked in feeds for SOAP API calls made -#' with the following functions: \code{\link{sf_create}}, \code{\link{sf_delete}}, -#' \code{\link{sf_update}}, \code{\link{sf_upsert}}. For more information, read the Salesforce documentation +#' @param DisableFeedTrackingHeader \code{list}; containing the \code{disableFeedTracking} +#' element with a value of \code{TRUE} or \code{FALSE}. This control specifies whether +#' the changes made in the current call are tracked in feeds for SOAP API calls made +#' with the following functions: \code{\link{sf_create}}, \code{\link{sf_delete}}, +#' \code{\link{sf_update}}, \code{\link{sf_upsert}}. For more information, read the Salesforce documentation #' \href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_header_disablefeedtracking.htm}{here}. -#' @param DuplicateRuleHeader \code{list}; containing the \code{allowSave}, -#' \code{includeRecordDetails}, and \code{runAsCurrentUser} elements each with a -#' value of \code{TRUE} or \code{FALSE}. This control specifies how duplicate rules should be applied -#' when using the following functions: \code{\link{sf_create}}, \code{\link{sf_update}}, -#' \code{\link{sf_upsert}}. For more information, read the Salesforce documentation +#' @param DuplicateRuleHeader \code{list}; containing the \code{allowSave}, +#' \code{includeRecordDetails}, and \code{runAsCurrentUser} elements each with a +#' value of \code{TRUE} or \code{FALSE}. This control specifies how duplicate rules should be applied +#' when using the following functions: \code{\link{sf_create}}, \code{\link{sf_update}}, +#' \code{\link{sf_upsert}}. For more information, read the Salesforce documentation #' \href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_header_duplicateruleheader.htm}{here}. -#' @param EmailHeader \code{list}; containing the \code{triggerAutoResponseEmail}, -#' \code{triggerOtherEmail}, and \code{triggerUserEmail} elements each with a -#' value of \code{TRUE} or \code{FALSE}. This control determines if an email notification should be sent -#' when a request is processed by SOAP API calls made with the following functions: -#' \code{\link{sf_create}}, \code{\link{sf_delete}}, \code{\link{sf_update}}, \code{\link{sf_upsert}}, -#' \code{\link{sf_reset_password}}. For more information, read the Salesforce documentation +#' @param EmailHeader \code{list}; containing the \code{triggerAutoResponseEmail}, +#' \code{triggerOtherEmail}, and \code{triggerUserEmail} elements each with a +#' value of \code{TRUE} or \code{FALSE}. This control determines if an email notification should be sent +#' when a request is processed by SOAP API calls made with the following functions: +#' \code{\link{sf_create}}, \code{\link{sf_delete}}, \code{\link{sf_update}}, \code{\link{sf_upsert}}, +#' \code{\link{sf_reset_password}}. For more information, read the Salesforce documentation #' \href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_header_emailheader.htm}{here}. -#' @param LocaleOptions \code{list}; containing the \code{language} element. This control -#' specifies the language of the labels returned by the \code{\link{sf_describe_objects}} -#' function using the SOAP API. The value must be a valid user locale (language and country), such as -#' \code{de_DE} or \code{en_GB}. For more information, read the Salesforce documentation -#' \href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_header_localeheader.htm}{here}. -#' The list of valid user locales is available -#' \href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_objects_categorynodelocalization.htm#languagelocalekey_desc}{here}. -#' @param MruHeader \code{list}; containing the \code{updateMru} element with a value -#' of \code{TRUE} or \code{FALSE}. This control indicates whether to update the list -#' of most recently used items (\code{TRUE}) or not (\code{FALSE}) in the Recent Items -#' section of the sidebar in the Salesforce user interface. This works for SOAP API calls -#' made with the following functions: \code{\link{sf_create}}, \code{\link{sf_update}}, -#' \code{\link{sf_upsert}}, \code{\link{sf_retrieve}}, \code{\link{sf_query}}. For more -#' information, read the Salesforce documentation +#' @param LocaleOptions \code{list}; containing the \code{language} element. This control +#' specifies the language of the labels returned by the \code{\link{sf_describe_objects}} +#' function using the SOAP API. The value must be a valid user locale (language and country), such as +#' \code{de_DE} or \code{en_GB}. For more information, read the Salesforce documentation +#' \href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_header_localeheader.htm}{here}. +#' The list of valid user locales is available +#' \href{https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/api/sforce_api_objects_categorynodelocalization.htm#languagelocalekey_desc}{here}. +#' @param MruHeader \code{list}; containing the \code{updateMru} element with a value +#' of \code{TRUE} or \code{FALSE}. This control indicates whether to update the list +#' of most recently used items (\code{TRUE}) or not (\code{FALSE}) in the Recent Items +#' section of the sidebar in the Salesforce user interface. This works for SOAP API calls +#' made with the following functions: \code{\link{sf_create}}, \code{\link{sf_update}}, +#' \code{\link{sf_upsert}}, \code{\link{sf_retrieve}}, \code{\link{sf_query}}. For more +#' information, read the Salesforce documentation #' \href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_header_mruheader.htm}{here}. -#' @param OwnerChangeOptions \code{list}; containing the \code{options} element. -#' This control specifies the details of ownership of attachments and notes when a -#' record’s owner is changed. This works for SOAP API calls made with the following functions: -#' \code{\link{sf_update}}, \code{\link{sf_upsert}}. For more information, read the Salesforce documentation +#' @param OwnerChangeOptions \code{list}; containing the \code{options} element. +#' This control specifies the details of ownership of attachments and notes when a +#' record’s owner is changed. This works for SOAP API calls made with the following functions: +#' \code{\link{sf_update}}, \code{\link{sf_upsert}}. For more information, read the Salesforce documentation #' \href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_header_ownerchangeoptions.htm}{here}. -#' @param QueryOptions \code{list}; containing the \code{batchSize} element. -#' This control specifies the batch size for query results . This works for SOAP or -#' REST API calls made with the following functions: \code{\link{sf_query}}, -#' \code{\link{sf_retrieve}}. For more information, read the Salesforce documentation +#' @param QueryOptions \code{list}; containing the \code{batchSize} element. +#' This control specifies the batch size for query results . This works for SOAP or +#' REST API calls made with the following functions: \code{\link{sf_query}}, +#' \code{\link{sf_retrieve}}. For more information, read the Salesforce documentation #' \href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_header_queryoptions.htm}{here}. -#' @param UserTerritoryDeleteHeader \code{list}; containing the \code{transferToUserId} element. -#' This control specifies a user to whom open opportunities are assigned when the current -#' owner is removed from a territory. This works for the \code{\link{sf_delete}} function -#' using the SOAP API. For more information, read the Salesforce documentation +#' @param UserTerritoryDeleteHeader \code{list}; containing the \code{transferToUserId} element. +#' This control specifies a user to whom open opportunities are assigned when the current +#' owner is removed from a territory. This works for the \code{\link{sf_delete}} function +#' using the SOAP API. For more information, read the Salesforce documentation #' \href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_header_userterritorydeleteheader.htm}{here}. -#' @param BatchRetryHeader \code{list}; containing the \code{Sforce-Disable-Batch-Retry} element. -#' When you create a bulk job, the Batch Retry control lets you disable retries -#' for unfinished batches included in the job. This works for most operations run through -#' the Bulk 1.0 API (e.g. \code{sf_create(., api_type = "Bulk 1.0")}) or creating -#' a Bulk 1.0 job with \code{\link{sf_create_job_bulk}}. For more information, read the Salesforce documentation +#' @param BatchRetryHeader \code{list}; containing the \code{Sforce-Disable-Batch-Retry} element. +#' When you create a bulk job, the Batch Retry control lets you disable retries +#' for unfinished batches included in the job. This works for most operations run through +#' the Bulk 1.0 API (e.g. \code{sf_create(., api_type = "Bulk 1.0")}) or creating +#' a Bulk 1.0 job with \code{\link{sf_create_job_bulk}}. For more information, read the Salesforce documentation #' \href{https://developer.salesforce.com/docs/atlas.en-us.api_asynch.meta/api_asynch/async_api_headers_disable_batch_retry.htm}{here}. -#' @param LineEndingHeader \code{list}; containing the \code{Sforce-Line-Ending} element. -#' When you’re creating a bulk upload job, the Line Ending control lets you -#' specify whether line endings are read as line feeds (LFs) or as carriage returns -#' and line feeds (CRLFs) for fields of type Text Area and Text Area (Long). This +#' @param LineEndingHeader \code{list}; containing the \code{Sforce-Line-Ending} element. +#' When you’re creating a bulk upload job, the Line Ending control lets you +#' specify whether line endings are read as line feeds (LFs) or as carriage returns +#' and line feeds (CRLFs) for fields of type Text Area and Text Area (Long). This #' works for most operations run through the Bulk APIs and/or creating a Bulk -#' job from scratch with \code{\link{sf_create_job_bulk}}. However, note that -#' as of \code{readr v1.3.1} all CSV files end with the line feed character -#' ("\\n") regardless of the operating system. So it is usually best to not specify -#' this argument. For more information, read the Salesforce documentation +#' job from scratch with \code{\link{sf_create_job_bulk}}. However, note that +#' as of \code{readr v1.3.1} all CSV files end with the line feed character +#' ("\\n") regardless of the operating system. So it is usually best to not specify +#' this argument. For more information, read the Salesforce documentation #' \href{https://developer.salesforce.com/docs/atlas.en-us.api_asynch.meta/api_asynch/async_api_headers_line_ending.htm}{here}. -#' @param PKChunkingHeader \code{list}; containing the \code{Sforce-Enable-PKChunking} element. -#' Use the PK Chunking control to enable automatic primary key (PK) chunking -#' for a bulk query job. This works for queries run through the Bulk 1.0 API either via -#' \code{sf_query(., api_type = "Bulk 1.0")}) or \code{\link{sf_query_bulk}}. For -#' more information, read the Salesforce documentation +#' @param PKChunkingHeader \code{list}; containing the \code{Sforce-Enable-PKChunking} element. +#' Use the PK Chunking control to enable automatic primary key (PK) chunking +#' for a bulk query job. This works for queries run through the Bulk 1.0 API either via +#' \code{sf_query(., api_type = "Bulk 1.0")}) or \code{\link{sf_query_bulk}}. For +#' more information, read the Salesforce documentation #' \href{https://developer.salesforce.com/docs/atlas.en-us.api_asynch.meta/api_asynch/async_api_headers_enable_pk_chunking.htm}{here}. #' @template api_type #' @template operation -#' @examples +#' @examples #' \dontrun{ -#' this_control <- sf_control(DuplicateRuleHeader=list(allowSave=TRUE, -#' includeRecordDetails=FALSE, +#' this_control <- sf_control(DuplicateRuleHeader=list(allowSave=TRUE, +#' includeRecordDetails=FALSE, #' runAsCurrentUser=TRUE)) #' new_contact <- c(FirstName = "Test", LastName = "Contact-Create") #' new_record <- sf_create(new_contact, "Contact", control = this_control) -#' +#' #' # specifying the controls directly and are picked up by dots -#' new_record <- sf_create(new_contact, "Contact", -#' DuplicateRuleHeader = list(allowSave=TRUE, -#' includeRecordDetails=FALSE, +#' new_record <- sf_create(new_contact, "Contact", +#' DuplicateRuleHeader = list(allowSave=TRUE, +#' includeRecordDetails=FALSE, #' runAsCurrentUser=TRUE)) #' } #' @export -sf_control <- function(AllOrNoneHeader=list(allOrNone=FALSE), - AllowFieldTruncationHeader=list(allowFieldTruncation=FALSE), +sf_control <- function(AllOrNoneHeader=list(allOrNone=FALSE), + AllowFieldTruncationHeader=list(allowFieldTruncation=FALSE), AssignmentRuleHeader=list(useDefaultRule=TRUE), - DisableFeedTrackingHeader=list(disableFeedTracking=FALSE), - DuplicateRuleHeader=list(allowSave=FALSE, - includeRecordDetails=FALSE, - runAsCurrentUser=TRUE), - EmailHeader=list(triggerAutoResponseEmail=FALSE, - triggerOtherEmail=FALSE, + DisableFeedTrackingHeader=list(disableFeedTracking=FALSE), + DuplicateRuleHeader=list(allowSave=FALSE, + includeRecordDetails=FALSE, + runAsCurrentUser=TRUE), + EmailHeader=list(triggerAutoResponseEmail=FALSE, + triggerOtherEmail=FALSE, triggerUserEmail=TRUE), LocaleOptions=list(language="en_US"), - MruHeader=list(updateMru=FALSE), - OwnerChangeOptions=list(options=list(list(execute=TRUE, + MruHeader=list(updateMru=FALSE), + OwnerChangeOptions=list(options=list(list(execute=TRUE, type="EnforceNewOwnerHasReadAccess"), - list(execute=FALSE, + list(execute=FALSE, type="KeepAccountTeam"), - list(execute=FALSE, + list(execute=FALSE, type="KeepSalesTeam"), - list(execute=FALSE, + list(execute=FALSE, type="KeepSalesTeamGrantCurrentOwnerReadWriteAccess"), - list(execute=FALSE, + list(execute=FALSE, type="SendEmail"), - list(execute=FALSE, + list(execute=FALSE, type="TransferAllOwnedCases"), - list(execute=TRUE, + list(execute=TRUE, type="TransferContacts"), - list(execute=TRUE, + list(execute=TRUE, type="TransferContracts"), - list(execute=FALSE, + list(execute=FALSE, type="TransferNotesAndAttachments"), - list(execute=TRUE, + list(execute=TRUE, type="TransferOpenActivities"), - list(execute=TRUE, + list(execute=TRUE, type="TransferOrders"), - list(execute=FALSE, + list(execute=FALSE, type="TransferOtherOpenOpportunities"), - list(execute=FALSE, + list(execute=FALSE, type="TransferOwnedClosedOpportunities"), - list(execute=FALSE, - type="TransferOwnedOpenCases"), - list(execute=FALSE, + list(execute=FALSE, + type="TransferOwnedOpenCases"), + list(execute=FALSE, type="TransferOwnedOpenOpportunities"))), QueryOptions=list(batchSize=500), - UserTerritoryDeleteHeader=list(transferToUserId=NA), - BatchRetryHeader=list(`Sforce-Disable-Batch-Retry`=FALSE), - LineEndingHeader=list(`Sforce-Line-Ending`=NA), - PKChunkingHeader=list(`Sforce-Enable-PKChunking`=FALSE), + UserTerritoryDeleteHeader=list(transferToUserId=NA), + BatchRetryHeader=list(`Sforce-Disable-Batch-Retry`=FALSE), + LineEndingHeader=list(`Sforce-Line-Ending`=NA), + PKChunkingHeader=list(`Sforce-Enable-PKChunking`=FALSE), api_type = NULL, operation = NULL){ - + # determine which elements were supplied, dropping the function call itself supplied_arguments <- as.list(match.call()[-1]) - + # now eval them to no longer be objects of class "call" - supplied_arguments <- supplied_arguments %>% - map(eval) %>% + supplied_arguments <- supplied_arguments %>% + map(eval) %>% # convert boolean args to lowercase modify(~modify_if(., is.logical, tolower)) - + # check that they are all lists list_argument <- sapply(supplied_arguments, is.list) if(!all(list_argument)){ @@ -183,51 +183,51 @@ sf_control <- function(AllOrNoneHeader=list(allOrNone=FALSE), mismatched_warn_str <- c(mismatched_warn_str, n) } } - mismatched_warn_str <- paste0(mismatched_warn_str, collapse=", ") + mismatched_warn_str <- paste0(mismatched_warn_str, collapse=", ") stop( sprintf(paste0("The following control arguments were not provided as lists: \n%s\n\n", - "Review the argument defaults in 'sf_control()' for help formatting."), + "Review the argument defaults in 'sf_control()' for help formatting."), mismatched_warn_str) , call. = FALSE ) } } - + # check that the controls valid for the API and operation - supplied_arguments <- filter_valid_controls(supplied_arguments, - api_type = api_type, + supplied_arguments <- filter_valid_controls(supplied_arguments, + api_type = api_type, operation = operation) - + return(supplied_arguments) } #' Return the Accepted Control Arguments by API Type -#' +#' #' @template api_type -#' @return \code{character}; a vector of strings indicating which control arguments +#' @return \code{character}; a vector of strings indicating which control arguments #' are accepted by the specified API. #' @note This function is meant to be used internally. Only use when debugging. #' @keywords internal #' @export accepted_controls_by_api <- function(api_type = c("SOAP", "REST", "Bulk 1.0", "Bulk 2.0", "Metadata")){ this_api_type <- match.arg(api_type) - switch(this_api_type, - "SOAP" = c("AllOrNoneHeader", "AllowFieldTruncationHeader", "AssignmentRuleHeader", + switch(this_api_type, + "SOAP" = c("AllOrNoneHeader", "AllowFieldTruncationHeader", "AssignmentRuleHeader", "DisableFeedTrackingHeader", "DuplicateRuleHeader", - "EmailHeader", "LocaleOptions", - "MruHeader", "OwnerChangeOptions", + "EmailHeader", "LocaleOptions", + "MruHeader", "OwnerChangeOptions", "QueryOptions", "UserTerritoryDeleteHeader"), "REST" = c("AllOrNoneHeader", "AssignmentRuleHeader", "QueryOptions"), "Bulk 1.0" = c("LineEndingHeader", "BatchRetryHeader", "PKChunkingHeader"), - "Bulk 2.0" = c("LineEndingHeader"), + "Bulk 2.0" = c("LineEndingHeader"), "Metadata" = c("AllOrNoneHeader"), character(0)) } #' Return the Accepted Control Arguments by Operation -#' +#' #' @template operation -#' @return \code{character}; a vector of strings indicating which control arguments +#' @return \code{character}; a vector of strings indicating which control arguments #' are accepted by the specified operation. #' @note This function is meant to be used internally. Only use when debugging. #' @keywords internal @@ -239,126 +239,126 @@ accepted_controls_by_operation <- function(operation = c("create" , "insert", "convertLead", "merge", "describeSObjects", "resetPassword")){ - record_creation_controls <- c("AllOrNoneHeader", "AllowFieldTruncationHeader", - "AssignmentRuleHeader", "DisableFeedTrackingHeader", + record_creation_controls <- c("AllOrNoneHeader", "AllowFieldTruncationHeader", + "AssignmentRuleHeader", "DisableFeedTrackingHeader", "DuplicateRuleHeader", "EmailHeader", "MruHeader") query_controls <- c("MruHeader", "QueryOptions", "PKChunkingHeader") bulk_controls <- c("BatchRetryHeader", "LineEndingHeader") this_operation <- match.arg(operation) - switch(this_operation, + switch(this_operation, "create" = c(record_creation_controls, bulk_controls), "insert" = c(record_creation_controls, bulk_controls), "update" = c(record_creation_controls, bulk_controls, "OwnerChangeOptions"), "upsert" = c(record_creation_controls, bulk_controls, "OwnerChangeOptions"), - "delete" = c("AllOrNoneHeader", "DisableFeedTrackingHeader", "EmailHeader", + "delete" = c("AllOrNoneHeader", "DisableFeedTrackingHeader", "EmailHeader", "UserTerritoryDeleteHeader", bulk_controls), - "undelete" = c("AllOrNoneHeader", "AllowFieldTruncationHeader"), - "hardDelete" = bulk_controls, + "undelete" = c("AllOrNoneHeader", "AllowFieldTruncationHeader"), + "hardDelete" = bulk_controls, "query" = query_controls, - "queryall" = query_controls, + "queryall" = query_controls, "retrieve" = c("MruHeader"), "convertLead" = c("AllowFieldTruncationHeader", "DisableFeedTrackingHeader"), - "merge" = c("AllowFieldTruncationHeader", "DisableFeedTrackingHeader"), + "merge" = c("AllowFieldTruncationHeader", "DisableFeedTrackingHeader"), "describeSObjects" = c("LocaleOptions"), "resetPassword" = c("EmailHeader"), character(0)) } #' Filter Out Control Arguments by API or Operation -#' -#' @param supplied \code{list}; a list of input data regarding the control arguments -#' along with the with API and operation information to make a complete assessment +#' +#' @param supplied \code{list}; a list of input data regarding the control arguments +#' along with the with API and operation information to make a complete assessment #' of which control arguments are applicable. #' @template api_type #' @template operation -#' @return \code{character}; a vector of strings returning only the control arguments +#' @return \code{character}; a vector of strings returning only the control arguments #' that are accepted by the specified API and operation. #' @note This function is meant to be used internally. Only use when debugging. #' @keywords internal #' @export filter_valid_controls <- function(supplied, api_type = NULL, operation = NULL){ - + if(is.null(api_type) & !is.null(supplied$api_type)){ api_type <- supplied$api_type } # remove the api_type from the supplied args, if it exists supplied$api_type <- NULL - + if(is.null(operation) & !is.null(supplied$operation)){ operation <- supplied$operation } # remove the api_type from the supplied args, if it exists - supplied$operation <- NULL - + supplied$operation <- NULL + if(!is.null(api_type) ){ valid <- accepted_controls_by_api(api_type) - # provide a warning before dropping + # provide a warning before dropping if(length(setdiff(names(supplied), valid)) > 0){ - warn_w_errors_listed(sprintf(paste0("Ignoring the following controls which ", + warn_w_errors_listed(sprintf(paste0("Ignoring the following controls which ", "are not used in the %s API: %s"), - api_type, + api_type, paste0(setdiff(names(supplied), valid), collapse=", "))) } supplied <- supplied[intersect(names(supplied), valid)] } - + if(!is.null(operation)){ valid <- accepted_controls_by_operation(operation) - # provide a warning before dropping + # provide a warning before dropping if(length(setdiff(names(supplied), valid)) > 0){ - warn_w_errors_listed(sprintf(paste0("Ignoring the following controls which ", + warn_w_errors_listed(sprintf(paste0("Ignoring the following controls which ", "are not used in the %s operation: %s"), - operation, + operation, paste0(setdiff(names(supplied), valid), collapse=", "))) } supplied <- supplied[intersect(names(supplied), valid)] } - + return(supplied) } #' Of All Args Return Ones Matching Control Arguments -#' -#' @param args \code{character}; a vector of strings that represent the function +#' +#' @param args \code{character}; a vector of strings that represent the function #' arguments. -#' @return \code{character}; a vector of strings returning only the function arguments -#' that match control arguments so that users can specify them directly in each -#' function and not have to construct a control object every time in order to +#' @return \code{character}; a vector of strings returning only the function arguments +#' that match control arguments so that users can specify them directly in each +#' function and not have to construct a control object every time in order to #' pass only one or two control arguments. #' @note This function is meant to be used internally. Only use when debugging. #' @keywords internal #' @export return_matching_controls <- function(args){ - possible_controls <- formals(sf_control) + possible_controls <- formals(sf_control) idx <- names(args) %in% names(possible_controls) return(args[idx]) } -# HELD BACK CONTROL OPTIONS BECAUSE THEY MAY BE CONFUSING OR NOT FEASIBLE GIVEN +# HELD BACK CONTROL OPTIONS BECAUSE THEY MAY BE CONFUSING OR NOT FEASIBLE GIVEN # THE WAY THE PACKAGE IS CURRENTLY WRITTEN: #CallOptions=list(client=NA, defaultNamespace=NA) #' CallOptions \code{list}; containing the \code{client} and \code{defaultNamespace} -#' elements. This control specifies the call options needed to work with a specific client. -#' This control is only available for use with the Partner WSDL and works for most all functions -#' in this package. For more information, read the Salesforce documentation +#' elements. This control specifies the call options needed to work with a specific client. +#' This control is only available for use with the Partner WSDL and works for most all functions +#' in this package. For more information, read the Salesforce documentation #' \href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_header_calloptions.htm}{here}. -#LimitInfoHeader=list(current="20", -# limit="250", +#LimitInfoHeader=list(current="20", +# limit="250", # type="API REQUESTS") -#' LimitInfoHeader \code{list}; containing the \code{current}, \code{limit}, -#' and \code{type} elements. This control determines if limit information for the organization should -#' be returned in the header each request response. This works for most all functions, -#' except \code{\link{sf_auth()}}. For more information, read the Salesforce documentation +#' LimitInfoHeader \code{list}; containing the \code{current}, \code{limit}, +#' and \code{type} elements. This control determines if limit information for the organization should +#' be returned in the header each request response. This works for most all functions, +#' except \code{\link{sf_auth()}}. For more information, read the Salesforce documentation #' \href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_header_limitinfo.htm}{here}. #PackageVersionHeader=list(packageVersions=NA) -#' PackageVersionHeader \code{list}; containing the \code{packageVersions} element. -#' This header specifies the package version for each installed managed package in -#' API version 16.0 and later. This works for most all functions in this package. -#' For more information, read the Salesforce documentation +#' PackageVersionHeader \code{list}; containing the \code{packageVersions} element. +#' This header specifies the package version for each installed managed package in +#' API version 16.0 and later. This works for most all functions in this package. +#' For more information, read the Salesforce documentation #' \href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_header_packageversionheader.htm}{here}. #ContentTypeHeader=list(`Content-Type`="application/xml") diff --git a/README.Rmd b/README.Rmd index f56bfd19..0bb8cb5e 100644 --- a/README.Rmd +++ b/README.Rmd @@ -327,10 +327,10 @@ most all operations supported by the Salesforce APIs are available via this pack This package makes requests best formatted to match what the APIs require as input. This articulation is not perfect and continued progress will be made to add and improve functionality. For details on formatting, attributes, and methods please refer to -[Salesforce's documentation](https://trailhead.salesforce.com/en/content/learn/modules/api_basics/api_basics_overview) as they are explained better there. More information +[Salesforce's documentation](https://trailhead.salesforce.com/content/learn/modules/api_basics/api_basics_overview) as they are explained better there. More information is also available on the {salesforcer} pkgdown website at https://stevenmmortimer.github.io/salesforcer/. -[Get supported salesforcer with the Tidelift Subscription](https://tidelift.com/subscription/pkg/cran-salesforcer?utm_source=cran-salesforcer&utm_medium=referral&utm_campaign=readme) +[Get supported salesforcer with the Tidelift Subscription](https://tidelift.com?utm_source=cran-salesforcer&utm_medium=referral&utm_campaign=readme) --- Please note that this project is released with a [Contributor Code of Conduct](https://github.com/stevenmmortimer/salesforcer/blob/main/.github/CODE_OF_CONDUCT.md). diff --git a/README.md b/README.md index 805975dd..24b5ac07 100644 --- a/README.md +++ b/README.md @@ -410,7 +410,7 @@ Future APIs to support (roughly in priority order): - [Industries API](https://developer.salesforce.com/docs/atlas.en-us.api_rest_industries.meta/api_rest_industries/intro.htm) - [Data.com - API](https://developer.salesforce.com/docs/atlas.en-us.datadotcom_api_dev_guide.meta/datadotcom_api_dev_guide/datadotcom_api_dev_guide_intro.htm) + API](https://developer.salesforce.com/docs/sales/datadotcom-api/guide/datadotcom_api_dev_guide_intro.html) ## Credits @@ -432,13 +432,13 @@ requests best formatted to match what the APIs require as input. This articulation is not perfect and continued progress will be made to add and improve functionality. For details on formatting, attributes, and methods please refer to [Salesforce’s -documentation](https://trailhead.salesforce.com/en/content/learn/modules/api_basics/api_basics_overview) +documentation](https://trailhead.salesforce.com/content/learn/modules/api_basics/api_basics_overview) as they are explained better there. More information is also available on the {salesforcer} pkgdown website at . [Get supported salesforcer with the Tidelift -Subscription](https://tidelift.com/subscription/pkg/cran-salesforcer?utm_source=cran-salesforcer&utm_medium=referral&utm_campaign=readme) +Subscription](https://tidelift.com?utm_source=cran-salesforcer&utm_medium=referral&utm_campaign=readme) ------------------------------------------------------------------------ diff --git a/man/sf_control.Rd b/man/sf_control.Rd index 54fc4867..ab724d8e 100644 --- a/man/sf_control.Rd +++ b/man/sf_control.Rd @@ -89,7 +89,7 @@ function using the SOAP API. The value must be a valid user locale (language and \code{de_DE} or \code{en_GB}. For more information, read the Salesforce documentation \href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_header_localeheader.htm}{here}. The list of valid user locales is available -\href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_objects_categorynodelocalization.htm#languagelocalekey_desc}{here}.} +\href{https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/api/sforce_api_objects_categorynodelocalization.htm#languagelocalekey_desc}{here}.} \item{MruHeader}{\code{list}; containing the \code{updateMru} element with a value of \code{TRUE} or \code{FALSE}. This control indicates whether to update the list @@ -159,16 +159,16 @@ for the \code{\link[stats]{glm}} function. } \examples{ \dontrun{ -this_control <- sf_control(DuplicateRuleHeader=list(allowSave=TRUE, - includeRecordDetails=FALSE, +this_control <- sf_control(DuplicateRuleHeader=list(allowSave=TRUE, + includeRecordDetails=FALSE, runAsCurrentUser=TRUE)) new_contact <- c(FirstName = "Test", LastName = "Contact-Create") new_record <- sf_create(new_contact, "Contact", control = this_control) # specifying the controls directly and are picked up by dots -new_record <- sf_create(new_contact, "Contact", - DuplicateRuleHeader = list(allowSave=TRUE, - includeRecordDetails=FALSE, +new_record <- sf_create(new_contact, "Contact", + DuplicateRuleHeader = list(allowSave=TRUE, + includeRecordDetails=FALSE, runAsCurrentUser=TRUE)) } } diff --git a/man/sf_create_attachment.Rd b/man/sf_create_attachment.Rd index a223bfb8..45f46949 100644 --- a/man/sf_create_attachment.Rd +++ b/man/sf_create_attachment.Rd @@ -63,8 +63,8 @@ of base64–encoded data. \section{Salesforce Documentation}{ \itemize{ -\item \href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_objects_attachment.htm}{Attachment Object (SOAP)} -\item \href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_objects_document.htm}{Document Object (SOAP)} +\item \href{https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_attachment.htm}{Attachment Object (SOAP)} +\item \href{https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_document.htm}{Document Object (SOAP)} \item \href{https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_sobject_insert_update_blob.htm}{Insert or Update Blob Data} } } @@ -75,21 +75,21 @@ of base64–encoded data. file_path1 <- here::here("doc1.pdf") file_path2 <- here::here("doc2.pdf") parent_record_id <- "0036A000002C6MmQAK" -attachment_details <- tibble(Body = c(file_path1, file_path2), +attachment_details <- tibble(Body = c(file_path1, file_path2), ParentId = rep(parent_record_id, 2)) result <- sf_create_attachment(attachment_details) -# the function supports inserting all blob content, just update the +# the function supports inserting all blob content, just update the # object_name argument to add the PDF as a Document instead of an Attachment -document_details <- tibble(Name = "doc1.pdf", - Description = "Test Document 1", +document_details <- tibble(Name = "doc1.pdf", + Description = "Test Document 1", Body = file_path1, FolderId = "00l6A000001EgIwQAK", # replace with your FolderId! Keywords = "example,test,document") result <- sf_create_attachment(document_details, object_name = "Document") -# the Bulk API can be invoked using api_type="Bulk 1.0" which will automatically -# take a data.frame of Attachment info and create a ZIP file with CSV manifest +# the Bulk API can be invoked using api_type="Bulk 1.0" which will automatically +# take a data.frame of Attachment info and create a ZIP file with CSV manifest # that is required for that API result <- sf_create_attachment(attachment_details, api_type="Bulk 1.0") } diff --git a/man/sf_download_attachment.Rd b/man/sf_download_attachment.Rd index abac9e58..252d971c 100644 --- a/man/sf_download_attachment.Rd +++ b/man/sf_download_attachment.Rd @@ -53,10 +53,10 @@ attachment body, file name, and path. \examples{ \dontrun{ # downloading all attachments for a Parent record -# if your attachment name doesn't include the extension, then you can use the +# if your attachment name doesn't include the extension, then you can use the # ContentType column to append it to the Name, if needed -queried_attachments <- sf_query("SELECT Id, Body, Name, ContentType - FROM Attachment +queried_attachments <- sf_query("SELECT Id, Body, Name, ContentType + FROM Attachment WHERE ParentId = '0016A0000035mJ5'") mapply(sf_download_attachment, queried_attachments$Body, queried_attachments$Name) diff --git a/man/sf_update_attachment.Rd b/man/sf_update_attachment.Rd index b65cdafc..a6b76b9d 100644 --- a/man/sf_update_attachment.Rd +++ b/man/sf_update_attachment.Rd @@ -64,8 +64,8 @@ of base64–encoded data. \section{Salesforce Documentation}{ \itemize{ -\item \href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_objects_attachment.htm}{Attachment Object (SOAP)} -\item \href{https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_objects_document.htm}{Document Object (SOAP)} +\item \href{https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_attachment.htm}{Attachment Object (SOAP)} +\item \href{https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_document.htm}{Document Object (SOAP)} \item \href{https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_sobject_insert_update_blob.htm}{Insert or Update Blob Data} } } diff --git a/revdep/library.noindex/salesforcer/old/salesforcer/doc/working-with-attachments.Rmd b/revdep/library.noindex/salesforcer/old/salesforcer/doc/working-with-attachments.Rmd index e44bc328..bab41f1c 100644 --- a/revdep/library.noindex/salesforcer/old/salesforcer/doc/working-with-attachments.Rmd +++ b/revdep/library.noindex/salesforcer/old/salesforcer/doc/working-with-attachments.Rmd @@ -277,9 +277,9 @@ functions exactly as they are described in the Salesforce documentation so that they are flexible enough to handle most all cases that the APIs were intended to support. - * **Attachment Object**: https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_objects_attachment.htm + * **Attachment Object**: https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_attachment.htm - * **Document Object**: https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_objects_document.htm + * **Document Object**: https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_document.htm * **REST API Upload Attachment**: https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_sobject_insert_update_blob.htm diff --git a/vignettes/getting-started.Rmd b/vignettes/getting-started.Rmd index 4023c213..440e5d12 100644 --- a/vignettes/getting-started.Rmd +++ b/vignettes/getting-started.Rmd @@ -203,7 +203,7 @@ is not found based an an "External Id" field, then Salesforce will create the record instead of updating one. Below is an example where we create 2 records, then upsert 3, where 2 are matched and updated and one is created. **NOTE**: You will need to create a custom field on the target object and ensure it is labeled as -an "External Id" field. Read more at: https://blog.jeffdouglas.com/2010/05/07/using-exernal-id-fields-in-salesforce/. +an "External Id" field. ```{r} n <- 2 From 0a3a1e2ac857e27fd8bcf8264977fc253f9b806e Mon Sep 17 00:00:00 2001 From: "Steven M. Mortimer" Date: Mon, 4 Nov 2024 15:35:39 -0600 Subject: [PATCH 08/18] Remove place where we always neede to manually update the version number --- README.Rmd | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.Rmd b/README.Rmd index 0bb8cb5e..ef19124f 100644 --- a/README.Rmd +++ b/README.Rmd @@ -69,7 +69,7 @@ Package features include: ## Installation ```{r, eval = FALSE} -# install the current CRAN version (1.0.2) +# install the current CRAN version install.packages("salesforcer") # or get the development version on GitHub diff --git a/README.md b/README.md index 24b5ac07..9de51435 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ Package features include: ## Installation ``` r -# install the current CRAN version (1.0.2) +# install the current CRAN version install.packages("salesforcer") # or get the development version on GitHub From 68c898a3c0a28a5507d7efde3887ef8ca2197530 Mon Sep 17 00:00:00 2001 From: "Steven M. Mortimer" Date: Mon, 4 Nov 2024 16:08:26 -0600 Subject: [PATCH 09/18] Fixing pkgdown grumblings --- _pkgdown.yml | 21 ++++++++++++++------- index.Rmd | 2 +- index.md | 2 +- vignettes/supported-queries.Rmd | 2 ++ vignettes/working-with-bulk-apis.Rmd | 2 ++ vignettes/working-with-reports.Rmd | 2 +- 6 files changed, 21 insertions(+), 10 deletions(-) diff --git a/_pkgdown.yml b/_pkgdown.yml index 68a7c20c..ef4cd48d 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -1,6 +1,7 @@ title: salesforcer -url: https://stevenmmortimer.github.io/salesforcer +url: https://stevenmmortimer.github.io/salesforcer/ template: + bootstrap: 5 params: bootswatch: spacelab ganalytics: UA-98603021-2 @@ -11,13 +12,19 @@ template: home: strip_header: true - + +template: + opengraph: + image: + src: man/figures/salesforcer.png + alt: "salesforcer package logo" + navbar: type: default left: - text: "Articles" icon: fas fa-book - menu: + menu: - text: Getting Started href: articles/getting-started.html - text: Supported Queries @@ -44,7 +51,7 @@ navbar: - text: GitHub icon: fa-github fa-lg href: https://github.com/StevenMMortimer/salesforcer - + reference: - title: Authentication desc: Function to authenticate to your Salesforce Org. @@ -72,12 +79,12 @@ reference: - '`sf_create_attachment`' - '`sf_download_attachment`' - '`sf_update_attachment`' - - '`sf_delete_attachment`' + - '`sf_delete_attachment`' - title: Bulk API Functions desc: Convenience functions to perform async CRUD and query operations. contents: - '`sf_run_bulk_query`' - - '`sf_run_bulk_operation`' + - '`sf_run_bulk_operation`' - title: Report Functions desc: Functions to create, retrieve, update, delete and query reports and their data. contents: @@ -139,7 +146,7 @@ reference: - '`sf_list_rest_api_versions`' - '`sf_list_resources`' - '`sf_list_api_limits`' - - '`sf_list_objects`' + - '`sf_list_objects`' - '`sf_user_info`' - '`sf_server_timestamp`' - '`sf_set_password`' diff --git a/index.Rmd b/index.Rmd index be089a9d..dad50965 100644 --- a/index.Rmd +++ b/index.Rmd @@ -22,7 +22,7 @@ options(tibble.print_min = 5L, tibble.print_max = 5L)
- +salesforcer package logo {salesforcer} is an R package that connects to Salesforce Platform APIs using tidy principles. The package implements actions from the REST, SOAP, Bulk 1.0, diff --git a/index.md b/index.md index da65c7fd..9c7483d9 100644 --- a/index.md +++ b/index.md @@ -14,7 +14,7 @@ Status](https://codecov.io/gh/stevenmmortimer/salesforcer/branch/main/graph/badg
- +salesforcer package logo {salesforcer} is an R package that connects to Salesforce Platform APIs using tidy principles. The package implements actions from the REST, diff --git a/vignettes/supported-queries.Rmd b/vignettes/supported-queries.Rmd index f21331db..23b54208 100644 --- a/vignettes/supported-queries.Rmd +++ b/vignettes/supported-queries.Rmd @@ -137,6 +137,8 @@ new_contacts_res <- sf_create(new_contacts, "Contact", api_type = "Bulk 2.0") **Performance test** ```{r run-performance-test, message=FALSE} +#| fig.alt: > +#| A violin plot showing the distribution of latency times for REST API and SOAP API qry <- function(api_type){ sf_query( sprintf("SELECT Id, Name, Owner.Id, diff --git a/vignettes/working-with-bulk-apis.Rmd b/vignettes/working-with-bulk-apis.Rmd index ba7d2104..a82b909d 100644 --- a/vignettes/working-with-bulk-apis.Rmd +++ b/vignettes/working-with-bulk-apis.Rmd @@ -170,6 +170,8 @@ at a time). I would encourage users to experiment to see what works best in thei Salesforce Org. ```{r, warning=FALSE, message=FALSE} +#| fig.alt: > +#| A violin plot showing the distribution of latency times for Bulk API and Bulk 2.0 API soql <- "SELECT Id, Name FROM Contact" bulk1_query <- function(){sf_query(soql, "Contact", api_type="Bulk 1.0")} bulk2_query <- function(){sf_query(soql, api_type="Bulk 2.0")} # Bulk 2.0 doesn't need object name diff --git a/vignettes/working-with-reports.Rmd b/vignettes/working-with-reports.Rmd index 855b990e..e94dca5a 100644 --- a/vignettes/working-with-reports.Rmd +++ b/vignettes/working-with-reports.Rmd @@ -70,7 +70,7 @@ Org. It typically follows the pattern: `https://na1.salesforce.com/00O/o` you should see the results. Below is a screenshot of how a report may look in your Org. Note the report Id in the URL bar. -![](./working-with-reports_files/report-screenshot.png) +![Alt](./working-with-reports_files/report-screenshot.png "A screenshot of the report id where it's embedded in the URL when viewing in a browser") The report Id above (`"00O3s000006tE7zEAE"`) is the only information needed to pull those same results from an R session, like so: From c815386f2b344b736d9e7ddbe2895ff617964da0 Mon Sep 17 00:00:00 2001 From: "Steven M. Mortimer" Date: Mon, 4 Nov 2024 16:10:38 -0600 Subject: [PATCH 10/18] Test linux using latest version of ubuntu --- .github/workflows/main-05-R-CMD-check-linux.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main-05-R-CMD-check-linux.yml b/.github/workflows/main-05-R-CMD-check-linux.yml index 64982c59..44790b7c 100644 --- a/.github/workflows/main-05-R-CMD-check-linux.yml +++ b/.github/workflows/main-05-R-CMD-check-linux.yml @@ -22,7 +22,7 @@ jobs: max-parallel: 1 matrix: config: - - {os: ubuntu-18.04, r: 'release'} + - {os: ubuntu-latest, r: 'release'} env: R_REMOTES_NO_ERRORS_FROM_WARNINGS: true From aec0e7cf88a7cd29ce287e52b322a3fad4955665 Mon Sep 17 00:00:00 2001 From: "Steven M. Mortimer" Date: Mon, 4 Nov 2024 16:11:58 -0600 Subject: [PATCH 11/18] Try new pkgdown yaml --- _pkgdown.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/_pkgdown.yml b/_pkgdown.yml index ef4cd48d..75943428 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -2,6 +2,10 @@ title: salesforcer url: https://stevenmmortimer.github.io/salesforcer/ template: bootstrap: 5 + opengraph: + image: + src: man/figures/salesforcer.png + alt: "salesforcer package logo" params: bootswatch: spacelab ganalytics: UA-98603021-2 @@ -13,12 +17,6 @@ template: home: strip_header: true -template: - opengraph: - image: - src: man/figures/salesforcer.png - alt: "salesforcer package logo" - navbar: type: default left: From 860fbe5ab69690ad32aaf4bfd2a4a4ce818b2de5 Mon Sep 17 00:00:00 2001 From: "Steven M. Mortimer" Date: Mon, 4 Nov 2024 16:23:01 -0600 Subject: [PATCH 12/18] Fix pkgdown grumblings --- DESCRIPTION | 2 +- cran-comments.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index f5348d91..4b39bfbc 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -29,7 +29,7 @@ Authors@R: role = c("ctb", "cph"), email = "joanna.zhao@alumni.ubc.ca")) License: MIT + file LICENSE -URL: https://github.com/StevenMMortimer/salesforcer +URL: https://github.com/StevenMMortimer/salesforcer, https://stevenmmortimer.github.io/salesforcer/ BugReports: https://github.com/StevenMMortimer/salesforcer/issues Depends: R (>= 3.6.0) diff --git a/cran-comments.md b/cran-comments.md index 4dc2a94a..4493683e 100644 --- a/cran-comments.md +++ b/cran-comments.md @@ -3,9 +3,9 @@ ## Test environments * Local Mac OS install, R-release 4.4.1 -* Ubuntu 18.04 (on GitHub Actions), R-release, R 4.4.2 -* Mac OS 10.15.5 (on GitHub Actions) R-release, R 4.4.2 -* Microsoft Windows Server 2019 10.0.17763 (on GitHub Actions) R-release, R 4.4.2 +* Ubuntu 22.04 (ubuntu-latest on GitHub Actions), R-release 4.4.2 +* macOS 14 Arm64 (macos-latest on GitHub Actions) R-release 4.4.2 +* Windows Server 2022 (windows-latest on GitHub Actions) R-release 4.4.2 * win-builder (R-release 4.4.2) ## R CMD check results From b46b05708352e4ec43694ea2bd1ffcffa288376b Mon Sep 17 00:00:00 2001 From: "Steven M. Mortimer" Date: Mon, 4 Nov 2024 16:25:14 -0600 Subject: [PATCH 13/18] Remove unnecessary version reference --- index.Rmd | 2 +- index.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/index.Rmd b/index.Rmd index dad50965..0d8280bc 100644 --- a/index.Rmd +++ b/index.Rmd @@ -70,7 +70,7 @@ Package features include: ## Installation ```{r, eval = FALSE} -# install the current CRAN version (1.0.1) +# install the current CRAN version install.packages("salesforcer") # or get the development version on GitHub diff --git a/index.md b/index.md index 9c7483d9..b0b68898 100644 --- a/index.md +++ b/index.md @@ -70,7 +70,7 @@ Package features include: ## Installation ``` r -# install the current CRAN version (1.0.1) +# install the current CRAN version install.packages("salesforcer") # or get the development version on GitHub From 943b29285dfa095316bfc463601700b2a6be477a Mon Sep 17 00:00:00 2001 From: "Steven M. Mortimer" Date: Mon, 4 Nov 2024 18:21:13 -0600 Subject: [PATCH 14/18] Set v2 for setup-r action --- .github/workflows/main-04-R-CMD-check-windows.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main-04-R-CMD-check-windows.yml b/.github/workflows/main-04-R-CMD-check-windows.yml index c97d97c8..d9d6fbd4 100644 --- a/.github/workflows/main-04-R-CMD-check-windows.yml +++ b/.github/workflows/main-04-R-CMD-check-windows.yml @@ -42,7 +42,7 @@ jobs: SALESFORCER_TOKEN_PASSPHRASE: ${{ secrets.SALESFORCER_TOKEN_PASSPHRASE }} shell: bash - - uses: r-lib/actions/setup-r@master + - uses: r-lib/actions/setup-r@v2 with: r-version: ${{ matrix.config.r }} From a28a4bb71351403e324e63958f14bd7aef67eb9e Mon Sep 17 00:00:00 2001 From: "Steven M. Mortimer" Date: Mon, 4 Nov 2024 20:59:12 -0600 Subject: [PATCH 15/18] Update GHA workflows to use new r-lib actions --- .github/workflows/main-03-R-CMD-check-mac.yml | 60 ++------------- .../workflows/main-04-R-CMD-check-windows.yml | 73 ++++--------------- .../workflows/main-05-R-CMD-check-linux.yml | 59 ++------------- 3 files changed, 28 insertions(+), 164 deletions(-) diff --git a/.github/workflows/main-03-R-CMD-check-mac.yml b/.github/workflows/main-03-R-CMD-check-mac.yml index 01d53812..0c05082b 100644 --- a/.github/workflows/main-03-R-CMD-check-mac.yml +++ b/.github/workflows/main-03-R-CMD-check-mac.yml @@ -22,7 +22,7 @@ jobs: max-parallel: 1 matrix: config: - - {os: macOS-latest, r: 'release'} + - {os: macOS-latest, r: 'release'} env: R_REMOTES_NO_ERRORS_FROM_WARNINGS: true @@ -46,61 +46,15 @@ jobs: use-public-rspm: true r-version: ${{ matrix.config.r }} - - uses: r-lib/actions/setup-pandoc@v2 - - - name: Query dependencies - run: | - install.packages("remotes") - saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2) - shell: Rscript {0} - - - name: Cache R packages - if: runner.os != 'Windows' - uses: actions/cache@v4 + - uses: r-lib/actions/setup-r-dependencies@v2 with: - path: ${{ env.R_LIBS_USER }} - key: ${{ runner.os }}-r-${{ matrix.config.r }}-3-${{ hashFiles('.github/depends.Rds') }} - restore-keys: ${{ runner.os }}-r-${{ matrix.config.r }}-3- - - - name: Install system dependencies - if: runner.os == 'Linux' - env: - RHUB_PLATFORM: linux-x86_64-ubuntu-gcc - run: | - Rscript -e "remotes::install_github('r-hub/sysreqs')" - sysreqs=$(Rscript -e "cat(sysreqs::sysreq_commands('DESCRIPTION'))") - sudo -s eval "$sysreqs" - - - name: Install dependencies - run: | - remotes::install_deps(dependencies = TRUE) - remotes::install_cran("rcmdcheck") - shell: Rscript {0} - - - name: Session info - run: | - options(width = 100) - pkgs <- installed.packages()[, "Package"] - sessioninfo::session_info(pkgs, include_base = TRUE) - shell: Rscript {0} - - - name: Check - env: - _R_CHECK_CRAN_INCOMING_REMOTE_: false - run: rcmdcheck::rcmdcheck(args = c("--no-manual", "--as-cran"), error_on = "warning", check_dir = "check") - shell: Rscript {0} - - - name: Show testthat output - if: always() - run: find check -name 'testthat.Rout*' -exec cat '{}' \; || true - shell: bash + extra-packages: any::rcmdcheck + needs: check - - name: Upload check results - if: failure() - uses: actions/upload-artifact@v4 + - uses: r-lib/actions/check-r-package@v2 with: - name: ${{ runner.os }}-r-${{ matrix.config.r }}-results - path: check + upload-snapshots: true + build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")' - name: Trigger next workflow if: ${{ (github.event_name != 'workflow_dispatch' && success()) || (github.event_name == 'workflow_dispatch' && github.event.inputs.trigger_next && success()) }} diff --git a/.github/workflows/main-04-R-CMD-check-windows.yml b/.github/workflows/main-04-R-CMD-check-windows.yml index d9d6fbd4..087fc9bd 100644 --- a/.github/workflows/main-04-R-CMD-check-windows.yml +++ b/.github/workflows/main-04-R-CMD-check-windows.yml @@ -9,12 +9,12 @@ on: default: false repository_dispatch: types: [main-04-R-CMD-check-windows] - + jobs: R-CMD-check-windows: runs-on: ${{ matrix.config.os }} - + name: ${{ matrix.config.os }} (${{ matrix.config.r }}) strategy: @@ -23,7 +23,7 @@ jobs: matrix: config: - {os: windows-latest, r: 'release'} - + env: R_REMOTES_NO_ERRORS_FROM_WARNINGS: true RSPM: ${{ matrix.config.rspm }} @@ -41,67 +41,22 @@ jobs: env: SALESFORCER_TOKEN_PASSPHRASE: ${{ secrets.SALESFORCER_TOKEN_PASSPHRASE }} shell: bash - + - uses: r-lib/actions/setup-r@v2 with: + use-public-rspm: true r-version: ${{ matrix.config.r }} - - - uses: r-lib/actions/setup-pandoc@v2 - - - name: Query dependencies - run: | - install.packages("remotes") - saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2) - shell: Rscript {0} - - - name: Cache R packages - if: runner.os != 'Windows' - uses: actions/cache@v4 - with: - path: ${{ env.R_LIBS_USER }} - key: ${{ runner.os }}-r-${{ matrix.config.r }}-3-${{ hashFiles('.github/depends.Rds') }} - restore-keys: ${{ runner.os }}-r-${{ matrix.config.r }}-3- - - - name: Install system dependencies - if: runner.os == 'Linux' - env: - RHUB_PLATFORM: linux-x86_64-ubuntu-gcc - run: | - Rscript -e "remotes::install_github('r-hub/sysreqs')" - sysreqs=$(Rscript -e "cat(sysreqs::sysreq_commands('DESCRIPTION'))") - sudo -s eval "$sysreqs" - - - name: Install dependencies - run: | - remotes::install_deps(dependencies = TRUE) - remotes::install_cran("rcmdcheck") - shell: Rscript {0} - - name: Session info - run: | - options(width = 100) - pkgs <- installed.packages()[, "Package"] - sessioninfo::session_info(pkgs, include_base = TRUE) - shell: Rscript {0} - - - name: Check - env: - _R_CHECK_CRAN_INCOMING_REMOTE_: false - run: rcmdcheck::rcmdcheck(args = c("--no-manual", "--as-cran"), error_on = "warning", check_dir = "check") - shell: Rscript {0} - - - name: Show testthat output - if: always() - run: find check -name 'testthat.Rout*' -exec cat '{}' \; || true - shell: bash + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::rcmdcheck + needs: check - - name: Upload check results - if: failure() - uses: actions/upload-artifact@v4 + - uses: r-lib/actions/check-r-package@v2 with: - name: ${{ runner.os }}-r-${{ matrix.config.r }}-results - path: check - + upload-snapshots: true + build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")' + - name: Trigger next workflow if: ${{ (github.event_name != 'workflow_dispatch' && success()) || (github.event_name == 'workflow_dispatch' && github.event.inputs.trigger_next && success()) }} uses: peter-evans/repository-dispatch@v3 @@ -110,7 +65,7 @@ jobs: repository: StevenMMortimer/salesforcer event-type: main-05-R-CMD-check-linux client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}"}' - + - name: Set final R-CMD-check status if: ${{ (github.event_name != 'workflow_dispatch' && failure()) }} uses: peter-evans/repository-dispatch@v3 diff --git a/.github/workflows/main-05-R-CMD-check-linux.yml b/.github/workflows/main-05-R-CMD-check-linux.yml index 44790b7c..dd121c09 100644 --- a/.github/workflows/main-05-R-CMD-check-linux.yml +++ b/.github/workflows/main-05-R-CMD-check-linux.yml @@ -43,63 +43,18 @@ jobs: - uses: r-lib/actions/setup-r@v2 with: + use-public-rspm: true r-version: ${{ matrix.config.r }} - - uses: r-lib/actions/setup-pandoc@v2 - - - name: Query dependencies - run: | - install.packages("remotes") - saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2) - shell: Rscript {0} - - - name: Cache R packages - if: runner.os != 'Windows' - uses: actions/cache@v4 + - uses: r-lib/actions/setup-r-dependencies@v2 with: - path: ${{ env.R_LIBS_USER }} - key: ${{ runner.os }}-r-${{ matrix.config.r }}-3-${{ hashFiles('.github/depends.Rds') }} - restore-keys: ${{ runner.os }}-r-${{ matrix.config.r }}-3- - - - name: Install system dependencies - if: runner.os == 'Linux' - env: - RHUB_PLATFORM: linux-x86_64-ubuntu-gcc - run: | - Rscript -e "remotes::install_github('r-hub/sysreqs')" - sysreqs=$(Rscript -e "cat(sysreqs::sysreq_commands('DESCRIPTION'))") - sudo -s eval "$sysreqs" - - - name: Install dependencies - run: | - remotes::install_deps(dependencies = TRUE) - remotes::install_cran("rcmdcheck") - shell: Rscript {0} - - - name: Session info - run: | - options(width = 100) - pkgs <- installed.packages()[, "Package"] - sessioninfo::session_info(pkgs, include_base = TRUE) - shell: Rscript {0} - - - name: Check - env: - _R_CHECK_CRAN_INCOMING_REMOTE_: false - run: rcmdcheck::rcmdcheck(args = c("--no-manual", "--as-cran"), error_on = "warning", check_dir = "check") - shell: Rscript {0} - - - name: Show testthat output - if: always() - run: find check -name 'testthat.Rout*' -exec cat '{}' \; || true - shell: bash + extra-packages: any::rcmdcheck + needs: check - - name: Upload check results - if: failure() - uses: actions/upload-artifact@v4 + - uses: r-lib/actions/check-r-package@v2 with: - name: ${{ runner.os }}-r-${{ matrix.config.r }}-results - path: check + upload-snapshots: true + build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")' - name: Set final R-CMD-check status if: ${{ (github.event_name != 'workflow_dispatch') || (github.event_name == 'workflow_dispatch' && github.event.inputs.trigger_next) }} From c7867b6acac76549f91dd0c34fa780e04ee6fdc6 Mon Sep 17 00:00:00 2001 From: "Steven M. Mortimer" Date: Mon, 4 Nov 2024 20:59:25 -0600 Subject: [PATCH 16/18] Update CRAN Comments --- cran-comments.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cran-comments.md b/cran-comments.md index 4493683e..0a33b2a2 100644 --- a/cran-comments.md +++ b/cran-comments.md @@ -6,11 +6,11 @@ * Ubuntu 22.04 (ubuntu-latest on GitHub Actions), R-release 4.4.2 * macOS 14 Arm64 (macos-latest on GitHub Actions) R-release 4.4.2 * Windows Server 2022 (windows-latest on GitHub Actions) R-release 4.4.2 -* win-builder (R-release 4.4.2) +* win-builder (Windows Server 2022 x64 (build 20348)), R-devel (2024-11-03 r87286 ucrt) ## R CMD check results -* checking CRAN incoming feasibility ... NOTE +* checking CRAN incoming feasibility ... [17s] NOTE Maintainer: 'Steven M. Mortimer ' 0 errors ✓ | 0 warnings ✓ | 1 notes ✖ From 4b6388f6d6d54a82f1c99961a0f43f02818c8e49 Mon Sep 17 00:00:00 2001 From: "Steven M. Mortimer" Date: Mon, 4 Nov 2024 21:12:57 -0600 Subject: [PATCH 17/18] Update GHA workflows to match Hadley's recent changes --- .github/workflows/main-01-pkgdown.yml | 46 ++++++------------ .github/workflows/main-02-test-coverage.yml | 53 +++++++++++---------- 2 files changed, 43 insertions(+), 56 deletions(-) diff --git a/.github/workflows/main-01-pkgdown.yml b/.github/workflows/main-01-pkgdown.yml index 90d52287..353abe62 100644 --- a/.github/workflows/main-01-pkgdown.yml +++ b/.github/workflows/main-01-pkgdown.yml @@ -46,47 +46,29 @@ jobs: echo GITHUB_SHA = $GITHUB_SHA echo GITHUB_REF = $GITHUB_REF + - uses: r-lib/actions/setup-pandoc@v2 + - uses: r-lib/actions/setup-r@v2 with: r-version: 'release' use-public-rspm: true - - uses: r-lib/actions/setup-pandoc@v2 - - - name: Query dependencies - run: | - install.packages("remotes") - saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2) - shell: Rscript {0} - - - name: Cache R packages - uses: actions/cache@v4 + - uses: r-lib/actions/setup-r-dependencies@v2 with: - path: ${{ env.R_LIBS_USER }} - key: macOS-r-4.4-1-${{ hashFiles('.github/depends.Rds') }} - restore-keys: macOS-r-4.4-1- + extra-packages: any::pkgdown, local::. + needs: website - - name: Install dependencies - run: | - remotes::install_deps(dependencies = TRUE) - remotes::install_dev("pkgdown") - shell: Rscript {0} - - - name: Session info - run: | - options(width = 100) - pkgs <- installed.packages()[, "Package"] - sessioninfo::session_info(pkgs, include_base = TRUE) + - name: Build site + run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) shell: Rscript {0} - - name: Install package - run: R CMD INSTALL . - - - name: Deploy package - run: | - git config --local user.email "actions@github.com" - git config --local user.name "GitHub Actions" - Rscript -e 'pkgdown::deploy_to_branch(new_process = FALSE)' + - name: Deploy to GitHub pages + if: github.event_name != 'pull_request' + uses: JamesIves/github-pages-deploy-action@v4.5.0 + with: + clean: false + branch: gh-pages + folder: docs - name: Trigger next workflow if: ${{ (github.event_name != 'workflow_dispatch' && success()) || (github.event_name == 'workflow_dispatch' && github.event.inputs.trigger_next && success()) }} diff --git a/.github/workflows/main-02-test-coverage.yml b/.github/workflows/main-02-test-coverage.yml index 86bef053..84100c54 100644 --- a/.github/workflows/main-02-test-coverage.yml +++ b/.github/workflows/main-02-test-coverage.yml @@ -16,7 +16,7 @@ on: jobs: test-coverage: - runs-on: macOS-latest + runs-on: ubuntu-latest env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} @@ -52,37 +52,42 @@ jobs: r-version: 'release' use-public-rspm: true - - uses: r-lib/actions/setup-pandoc@v2 + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::covr, any::xml2 + needs: coverage - - name: Query dependencies + - name: Test coverage run: | - install.packages("remotes") - saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2) + cov <- covr::package_coverage( + quiet = FALSE, + clean = FALSE, + install_path = file.path(normalizePath(Sys.getenv("RUNNER_TEMP"), winslash = "/"), "package") + ) + covr::to_cobertura(cov) shell: Rscript {0} - - name: Cache R packages - uses: actions/cache@v4 + - uses: codecov/codecov-action@v4 with: - path: ${{ env.R_LIBS_USER }} - key: macOS-r-4.4-1-${{ hashFiles('.github/depends.Rds') }} - restore-keys: macOS-r-4.4-1- + fail_ci_if_error: ${{ github.event_name != 'pull_request' && true || false }} + file: ./cobertura.xml + plugin: noop + disable_search: true + token: ${{ secrets.CODECOV_TOKEN }} - - name: Install dependencies + - name: Show testthat output + if: always() run: | - remotes::install_deps(dependencies = TRUE) - remotes::install_cran("covr") - shell: Rscript {0} + ## -------------------------------------------------------------------- + find '${{ runner.temp }}/package' -name 'testthat.Rout*' -exec cat '{}' \; || true + shell: bash - - name: Session info - run: | - options(width = 100) - pkgs <- installed.packages()[, "Package"] - sessioninfo::session_info(pkgs, include_base = TRUE) - shell: Rscript {0} - - - name: Test coverage - run: covr::codecov(quiet=FALSE) - shell: Rscript {0} + - name: Upload test results + if: failure() + uses: actions/upload-artifact@v4 + with: + name: coverage-test-failures + path: ${{ runner.temp }}/package - name: Trigger next workflow if: ${{ (github.event_name != 'workflow_dispatch' && success()) || (github.event_name == 'workflow_dispatch' && github.event.inputs.trigger_next && success()) }} From ed94e644a8056f0d7c098df16784c855127bf31f Mon Sep 17 00:00:00 2001 From: "Steven M. Mortimer" Date: Mon, 4 Nov 2024 21:13:19 -0600 Subject: [PATCH 18/18] Update local build of pkgdown site --- docs/404.html | 192 +- docs/CODE_OF_CONDUCT.html | 167 +- docs/CONTRIBUTING.html | 170 +- docs/LICENSE-text.html | 167 +- docs/LICENSE.html | 167 +- docs/SECURITY.html | 167 +- docs/SUPPORT.html | 167 +- docs/articles/getting-started.html | 487 +- docs/articles/index.html | 166 +- docs/articles/passing-control-args.html | 463 +- docs/articles/supported-queries.html | 935 +- .../transitioning-from-RForcecom.html | 407 +- docs/articles/working-with-attachments.html | 623 +- docs/articles/working-with-bulk-apis.html | 479 +- .../figure-html/unnamed-chunk-7-1.png | Bin 41338 -> 40104 bytes docs/articles/working-with-metadata.html | 526 +- docs/articles/working-with-reports.html | 712 +- docs/authors.html | 202 +- .../bootstrap-5.3.1/bootstrap.bundle.min.js | 7 + .../bootstrap.bundle.min.js.map | 1 + docs/deps/bootstrap-5.3.1/bootstrap.min.css | 5 + docs/deps/bootstrap-5.3.1/font.css | 400 + .../07d40e985ad7c747025dabb9f22142c4.woff2 | Bin 0 -> 16456 bytes .../fonts/1Ptug8zYS_SKggPNyC0ITw.woff2 | Bin 0 -> 48336 bytes .../fonts/1Ptug8zYS_SKggPNyCAIT5lu.woff2 | Bin 0 -> 26988 bytes .../fonts/1Ptug8zYS_SKggPNyCIIT5lu.woff2 | Bin 0 -> 11384 bytes .../fonts/1Ptug8zYS_SKggPNyCMIT5lu.woff2 | Bin 0 -> 30860 bytes .../fonts/1Ptug8zYS_SKggPNyCkIT5lu.woff2 | Bin 0 -> 25796 bytes .../1f5e011d6aae0d98fc0518e1a303e99a.woff2 | Bin 0 -> 10332 bytes .../fonts/4iCs6KVjbNBYlgoKcQ72j00.woff2 | Bin 0 -> 46796 bytes .../fonts/4iCs6KVjbNBYlgoKcg72j00.woff2 | Bin 0 -> 24448 bytes .../fonts/4iCs6KVjbNBYlgoKcw72j00.woff2 | Bin 0 -> 14588 bytes .../fonts/4iCs6KVjbNBYlgoKew72j00.woff2 | Bin 0 -> 20860 bytes .../fonts/4iCs6KVjbNBYlgoKfA72j00.woff2 | Bin 0 -> 15116 bytes .../fonts/4iCs6KVjbNBYlgoKfw72.woff2 | Bin 0 -> 34852 bytes .../fonts/4iCv6KVjbNBYlgoCxCvjs2yNL4U.woff2 | Bin 0 -> 12936 bytes .../fonts/4iCv6KVjbNBYlgoCxCvjsGyN.woff2 | Bin 0 -> 29752 bytes .../fonts/4iCv6KVjbNBYlgoCxCvjtGyNL4U.woff2 | Bin 0 -> 18200 bytes .../fonts/4iCv6KVjbNBYlgoCxCvjvGyNL4U.woff2 | Bin 0 -> 13284 bytes .../fonts/4iCv6KVjbNBYlgoCxCvjvWyNL4U.woff2 | Bin 0 -> 20876 bytes .../fonts/4iCv6KVjbNBYlgoCxCvjvmyNL4U.woff2 | Bin 0 -> 37840 bytes .../626330658504e338ee86aec8e957426b.woff2 | Bin 0 -> 21616 bytes ...K1dSBYKcSV-LCoeQqfX1RYOo3qPZ7jsDJT9g.woff2 | Bin 0 -> 1036 bytes ...K1dSBYKcSV-LCoeQqfX1RYOo3qPZ7ksDJT9g.woff2 | Bin 0 -> 1212 bytes .../6xK1dSBYKcSV-LCoeQqfX1RYOo3qPZ7nsDI.woff2 | Bin 0 -> 14160 bytes ...K1dSBYKcSV-LCoeQqfX1RYOo3qPZ7osDJT9g.woff2 | Bin 0 -> 5736 bytes ...K1dSBYKcSV-LCoeQqfX1RYOo3qPZ7psDJT9g.woff2 | Bin 0 -> 19612 bytes ...K1dSBYKcSV-LCoeQqfX1RYOo3qPZ7qsDJT9g.woff2 | Bin 0 -> 1028 bytes ...K1dSBYKcSV-LCoeQqfX1RYOo3qPZ7rsDJT9g.woff2 | Bin 0 -> 908 bytes .../6xK3dSBYKcSV-LCoeQqfX1RYOo3qN67lqDY.woff2 | Bin 0 -> 5836 bytes .../6xK3dSBYKcSV-LCoeQqfX1RYOo3qNK7lqDY.woff2 | Bin 0 -> 6004 bytes .../6xK3dSBYKcSV-LCoeQqfX1RYOo3qNa7lqDY.woff2 | Bin 0 -> 5024 bytes .../6xK3dSBYKcSV-LCoeQqfX1RYOo3qNq7lqDY.woff2 | Bin 0 -> 20616 bytes .../6xK3dSBYKcSV-LCoeQqfX1RYOo3qO67lqDY.woff2 | Bin 0 -> 7036 bytes .../6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2 | Bin 0 -> 14892 bytes .../6xK3dSBYKcSV-LCoeQqfX1RYOo3qPK7lqDY.woff2 | Bin 0 -> 7972 bytes ...ydSBYKcSV-LCoeQqfX1RYOo3i54rwkxduz8A.woff2 | Bin 0 -> 7968 bytes ...ydSBYKcSV-LCoeQqfX1RYOo3i54rwlBduz8A.woff2 | Bin 0 -> 6912 bytes ...6xKydSBYKcSV-LCoeQqfX1RYOo3i54rwlxdu.woff2 | Bin 0 -> 14824 bytes ...ydSBYKcSV-LCoeQqfX1RYOo3i54rwmBduz8A.woff2 | Bin 0 -> 5828 bytes ...ydSBYKcSV-LCoeQqfX1RYOo3i54rwmRduz8A.woff2 | Bin 0 -> 20428 bytes ...ydSBYKcSV-LCoeQqfX1RYOo3i54rwmhduz8A.woff2 | Bin 0 -> 5016 bytes ...ydSBYKcSV-LCoeQqfX1RYOo3i54rwmxduz8A.woff2 | Bin 0 -> 5944 bytes ...ydSBYKcSV-LCoeQqfX1RYOo3ig4vwkxduz8A.woff2 | Bin 0 -> 7860 bytes ...ydSBYKcSV-LCoeQqfX1RYOo3ig4vwlBduz8A.woff2 | Bin 0 -> 6904 bytes ...6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2 | Bin 0 -> 14712 bytes ...ydSBYKcSV-LCoeQqfX1RYOo3ig4vwmBduz8A.woff2 | Bin 0 -> 5728 bytes ...ydSBYKcSV-LCoeQqfX1RYOo3ig4vwmRduz8A.woff2 | Bin 0 -> 20392 bytes ...ydSBYKcSV-LCoeQqfX1RYOo3ig4vwmhduz8A.woff2 | Bin 0 -> 4972 bytes ...ydSBYKcSV-LCoeQqfX1RYOo3ig4vwmxduz8A.woff2 | Bin 0 -> 5948 bytes ...ydSBYKcSV-LCoeQqfX1RYOo3ik4zwkxduz8A.woff2 | Bin 0 -> 7912 bytes ...ydSBYKcSV-LCoeQqfX1RYOo3ik4zwlBduz8A.woff2 | Bin 0 -> 6968 bytes ...6xKydSBYKcSV-LCoeQqfX1RYOo3ik4zwlxdu.woff2 | Bin 0 -> 14780 bytes ...ydSBYKcSV-LCoeQqfX1RYOo3ik4zwmBduz8A.woff2 | Bin 0 -> 5816 bytes ...ydSBYKcSV-LCoeQqfX1RYOo3ik4zwmRduz8A.woff2 | Bin 0 -> 20388 bytes ...ydSBYKcSV-LCoeQqfX1RYOo3ik4zwmhduz8A.woff2 | Bin 0 -> 4928 bytes ...ydSBYKcSV-LCoeQqfX1RYOo3ik4zwmxduz8A.woff2 | Bin 0 -> 5996 bytes .../CSR54z1Qlv-GDxkbKVQ_dFsvWNReuQ.woff2 | Bin 0 -> 13436 bytes .../CSR54z1Qlv-GDxkbKVQ_dFsvWNpeudwk.woff2 | Bin 0 -> 12228 bytes .../fonts/CSR64z1Qlv-GDxkbKVQ_fO4KTet_.woff2 | Bin 0 -> 19980 bytes .../fonts/CSR64z1Qlv-GDxkbKVQ_fOAKTQ.woff2 | Bin 0 -> 13360 bytes ..._QiYsKILxRpg3hIP6sJ7fM7PqlONvQlMIXxw.woff2 | Bin 0 -> 2312 bytes .../HI_QiYsKILxRpg3hIP6sJ7fM7PqlONvUlMI.woff2 | Bin 0 -> 21792 bytes ..._QiYsKILxRpg3hIP6sJ7fM7PqlONvXlMIXxw.woff2 | Bin 0 -> 1832 bytes ..._QiYsKILxRpg3hIP6sJ7fM7PqlONvYlMIXxw.woff2 | Bin 0 -> 1636 bytes ..._QiYsKILxRpg3hIP6sJ7fM7PqlONvZlMIXxw.woff2 | Bin 0 -> 1864 bytes ..._QiYsKILxRpg3hIP6sJ7fM7PqlONvalMIXxw.woff2 | Bin 0 -> 29280 bytes ..._QiYsKILxRpg3hIP6sJ7fM7PqlONvblMIXxw.woff2 | Bin 0 -> 7700 bytes .../HI_SiYsKILxRpg3hIP6sJ7fM7PqlM-vWjMY.woff2 | Bin 0 -> 28908 bytes .../HI_SiYsKILxRpg3hIP6sJ7fM7PqlMOvWjMY.woff2 | Bin 0 -> 8488 bytes .../HI_SiYsKILxRpg3hIP6sJ7fM7PqlMevWjMY.woff2 | Bin 0 -> 2932 bytes .../HI_SiYsKILxRpg3hIP6sJ7fM7PqlMuvWjMY.woff2 | Bin 0 -> 7692 bytes .../HI_SiYsKILxRpg3hIP6sJ7fM7PqlOevWjMY.woff2 | Bin 0 -> 13872 bytes .../HI_SiYsKILxRpg3hIP6sJ7fM7PqlPevW.woff2 | Bin 0 -> 21528 bytes .../HI_SiYsKILxRpg3hIP6sJ7fM7PqlPuvWjMY.woff2 | Bin 0 -> 10312 bytes .../fonts/JTUSjIg1_i6t8kCHKm459W1hyzbi.woff2 | Bin 0 -> 21288 bytes .../fonts/JTUSjIg1_i6t8kCHKm459WRhyzbi.woff2 | Bin 0 -> 23516 bytes .../fonts/JTUSjIg1_i6t8kCHKm459WZhyzbi.woff2 | Bin 0 -> 9512 bytes .../fonts/JTUSjIg1_i6t8kCHKm459Wdhyzbi.woff2 | Bin 0 -> 27812 bytes .../fonts/JTUSjIg1_i6t8kCHKm459Wlhyw.woff2 | Bin 0 -> 33092 bytes .../fonts/KFOlCnqEu92Fr1MmEU9fABc4EsA.woff2 | Bin 0 -> 9840 bytes .../fonts/KFOlCnqEu92Fr1MmEU9fBBc4.woff2 | Bin 0 -> 15920 bytes .../fonts/KFOlCnqEu92Fr1MmEU9fBxc4EsA.woff2 | Bin 0 -> 7016 bytes .../fonts/KFOlCnqEu92Fr1MmEU9fCBc4EsA.woff2 | Bin 0 -> 1500 bytes .../fonts/KFOlCnqEu92Fr1MmEU9fCRc4EsA.woff2 | Bin 0 -> 14968 bytes .../fonts/KFOlCnqEu92Fr1MmEU9fChc4EsA.woff2 | Bin 0 -> 11800 bytes .../fonts/KFOlCnqEu92Fr1MmEU9fCxc4EsA.woff2 | Bin 0 -> 5604 bytes .../fonts/KFOlCnqEu92Fr1MmSU5fABc4EsA.woff2 | Bin 0 -> 9576 bytes .../fonts/KFOlCnqEu92Fr1MmSU5fBBc4.woff2 | Bin 0 -> 15740 bytes .../fonts/KFOlCnqEu92Fr1MmSU5fBxc4EsA.woff2 | Bin 0 -> 7120 bytes .../fonts/KFOlCnqEu92Fr1MmSU5fCBc4EsA.woff2 | Bin 0 -> 1480 bytes .../fonts/KFOlCnqEu92Fr1MmSU5fCRc4EsA.woff2 | Bin 0 -> 15000 bytes .../fonts/KFOlCnqEu92Fr1MmSU5fChc4EsA.woff2 | Bin 0 -> 11796 bytes .../fonts/KFOlCnqEu92Fr1MmSU5fCxc4EsA.woff2 | Bin 0 -> 5468 bytes .../fonts/KFOlCnqEu92Fr1MmWUlfABc4EsA.woff2 | Bin 0 -> 9644 bytes .../fonts/KFOlCnqEu92Fr1MmWUlfBBc4.woff2 | Bin 0 -> 15860 bytes .../fonts/KFOlCnqEu92Fr1MmWUlfBxc4EsA.woff2 | Bin 0 -> 6936 bytes .../fonts/KFOlCnqEu92Fr1MmWUlfCBc4EsA.woff2 | Bin 0 -> 1432 bytes .../fonts/KFOlCnqEu92Fr1MmWUlfCRc4EsA.woff2 | Bin 0 -> 14684 bytes .../fonts/KFOlCnqEu92Fr1MmWUlfChc4EsA.woff2 | Bin 0 -> 11824 bytes .../fonts/KFOlCnqEu92Fr1MmWUlfCxc4EsA.woff2 | Bin 0 -> 5548 bytes .../fonts/KFOmCnqEu92Fr1Mu4WxKOzY.woff2 | Bin 0 -> 7112 bytes .../fonts/KFOmCnqEu92Fr1Mu4mxK.woff2 | Bin 0 -> 15744 bytes .../fonts/KFOmCnqEu92Fr1Mu5mxKOzY.woff2 | Bin 0 -> 9628 bytes .../fonts/KFOmCnqEu92Fr1Mu72xKOzY.woff2 | Bin 0 -> 15344 bytes .../fonts/KFOmCnqEu92Fr1Mu7GxKOzY.woff2 | Bin 0 -> 11872 bytes .../fonts/KFOmCnqEu92Fr1Mu7WxKOzY.woff2 | Bin 0 -> 5560 bytes .../fonts/KFOmCnqEu92Fr1Mu7mxKOzY.woff2 | Bin 0 -> 1484 bytes .../fonts/QGYpz_kZZAGCONcK2A4bGOj8mNhN.woff2 | Bin 0 -> 78908 bytes .../fonts/S6u8w4BMUTPHjxsAUi-qJCY.woff2 | Bin 0 -> 5600 bytes .../fonts/S6u8w4BMUTPHjxsAXC-q.woff2 | Bin 0 -> 24408 bytes .../fonts/S6u9w4BMUTPHh6UVSwaPGR_p.woff2 | Bin 0 -> 5368 bytes .../fonts/S6u9w4BMUTPHh6UVSwiPGQ.woff2 | Bin 0 -> 23040 bytes .../fonts/S6u9w4BMUTPHh7USSwaPGR_p.woff2 | Bin 0 -> 5624 bytes .../fonts/S6u9w4BMUTPHh7USSwiPGQ.woff2 | Bin 0 -> 23236 bytes .../fonts/S6uyw4BMUTPHjx4wXg.woff2 | Bin 0 -> 23580 bytes .../fonts/S6uyw4BMUTPHjxAwXjeu.woff2 | Bin 0 -> 5472 bytes ...73FwrK3iLTeHuS_fvQtMwCp50KnMa0ZL7SUc.woff2 | Bin 0 -> 17600 bytes ...UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1ZL7.woff2 | Bin 0 -> 46704 bytes ...73FwrK3iLTeHuS_fvQtMwCp50KnMa1pL7SUc.woff2 | Bin 0 -> 22480 bytes ...73FwrK3iLTeHuS_fvQtMwCp50KnMa25L7SUc.woff2 | Bin 0 -> 79940 bytes ...73FwrK3iLTeHuS_fvQtMwCp50KnMa2JL7SUc.woff2 | Bin 0 -> 27284 bytes ...73FwrK3iLTeHuS_fvQtMwCp50KnMa2ZL7SUc.woff2 | Bin 0 -> 12732 bytes ...73FwrK3iLTeHuS_fvQtMwCp50KnMa2pL7SUc.woff2 | Bin 0 -> 10540 bytes .../fonts/XRXV3I6Li01BKofIMeaBXso.woff2 | Bin 0 -> 20708 bytes .../fonts/XRXV3I6Li01BKofINeaB.woff2 | Bin 0 -> 39124 bytes .../fonts/XRXV3I6Li01BKofIO-aBXso.woff2 | Bin 0 -> 34608 bytes .../fonts/XRXV3I6Li01BKofIOOaBXso.woff2 | Bin 0 -> 28868 bytes .../fonts/XRXV3I6Li01BKofIOuaBXso.woff2 | Bin 0 -> 12960 bytes .../c2f002b3a87d3f9bfeebb23d32cfd9f8.woff2 | Bin 0 -> 27216 bytes .../ee91700cdbf7ce16c054c2bb8946c736.woff2 | Bin 0 -> 31052 bytes ...MiZpBA-UFUIcVXSCEkx2cmqvXlWqW106F15M.woff2 | Bin 0 -> 25968 bytes ...MiZpBA-UFUIcVXSCEkx2cmqvXlWqWt06F15M.woff2 | Bin 0 -> 37696 bytes ...MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtE6F15M.woff2 | Bin 0 -> 54888 bytes ...MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtU6F15M.woff2 | Bin 0 -> 4880 bytes ...MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtk6F15M.woff2 | Bin 0 -> 17136 bytes ...MiZpBA-UFUIcVXSCEkx2cmqvXlWqWu06F15M.woff2 | Bin 0 -> 17064 bytes ...126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuU6F.woff2 | Bin 0 -> 50296 bytes ...MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuk6F15M.woff2 | Bin 0 -> 22780 bytes ...MiZpBA-UFUIcVXSCEkx2cmqvXlWqWvU6F15M.woff2 | Bin 0 -> 32204 bytes ...MiZpBA-UFUIcVXSCEkx2cmqvXlWqWxU6F15M.woff2 | Bin 0 -> 50484 bytes ...Gs126MiZpBA-UvWbX2vVnXBbObj2OVTS-muw.woff2 | Bin 0 -> 48236 bytes ...126MiZpBA-UvWbX2vVnXBbObj2OVTS2mu1aB.woff2 | Bin 0 -> 16516 bytes ...126MiZpBA-UvWbX2vVnXBbObj2OVTSCmu1aB.woff2 | Bin 0 -> 16552 bytes ...126MiZpBA-UvWbX2vVnXBbObj2OVTSGmu1aB.woff2 | Bin 0 -> 35328 bytes ...126MiZpBA-UvWbX2vVnXBbObj2OVTSKmu1aB.woff2 | Bin 0 -> 49436 bytes ...126MiZpBA-UvWbX2vVnXBbObj2OVTSOmu1aB.woff2 | Bin 0 -> 4524 bytes ...126MiZpBA-UvWbX2vVnXBbObj2OVTSumu1aB.woff2 | Bin 0 -> 26736 bytes ...126MiZpBA-UvWbX2vVnXBbObj2OVTSymu1aB.woff2 | Bin 0 -> 21272 bytes ...126MiZpBA-UvWbX2vVnXBbObj2OVTUGmu1aB.woff2 | Bin 0 -> 24984 bytes ...126MiZpBA-UvWbX2vVnXBbObj2OVTVOmu1aB.woff2 | Bin 0 -> 47136 bytes .../fonts/q5uGsou0JOdh94bfuQltOxU.woff2 | Bin 0 -> 19248 bytes .../fonts/q5uGsou0JOdh94bfvQlt.woff2 | Bin 0 -> 25376 bytes .../bootstrap-toc-1.0.1/bootstrap-toc.min.js | 5 + .../deps/clipboard.js-2.0.11/clipboard.min.js | 7 + docs/deps/data-deps.txt | 13 + docs/deps/font-awesome-6.4.2/css/all.css | 7968 +++++++++++ docs/deps/font-awesome-6.4.2/css/all.min.css | 9 + docs/deps/font-awesome-6.4.2/css/v4-shims.css | 2194 ++++ .../font-awesome-6.4.2/css/v4-shims.min.css | 6 + .../webfonts/fa-brands-400.ttf | Bin 0 -> 189684 bytes .../webfonts/fa-brands-400.woff2 | Bin 0 -> 109808 bytes .../webfonts/fa-regular-400.ttf | Bin 0 -> 63348 bytes .../webfonts/fa-regular-400.woff2 | Bin 0 -> 24488 bytes .../webfonts/fa-solid-900.ttf | Bin 0 -> 394668 bytes .../webfonts/fa-solid-900.woff2 | Bin 0 -> 150020 bytes .../webfonts/fa-v4compatibility.ttf | Bin 0 -> 10172 bytes .../webfonts/fa-v4compatibility.woff2 | Bin 0 -> 4568 bytes docs/deps/headroom-0.11.0/headroom.min.js | 7 + .../headroom-0.11.0/jQuery.headroom.min.js | 7 + docs/deps/jquery-3.6.0/jquery-3.6.0.js | 10881 ++++++++++++++++ docs/deps/jquery-3.6.0/jquery-3.6.0.min.js | 2 + docs/deps/jquery-3.6.0/jquery-3.6.0.min.map | 1 + .../search-1.0.0/autocomplete.jquery.min.js | 7 + docs/deps/search-1.0.0/fuse.min.js | 9 + docs/deps/search-1.0.0/mark.min.js | 7 + docs/index.html | 514 +- docs/katex-auto.js | 14 + docs/lightswitch.js | 85 + docs/news/index.html | 219 +- docs/pkgdown.js | 184 +- docs/pkgdown.yml | 7 +- docs/reference/VERB_n.html | 196 +- docs/reference/accepted_controls_by_api.html | 200 +- .../accepted_controls_by_operation.html | 204 +- docs/reference/bind_query_resultsets.html | 206 +- .../build_manifest_xml_from_list.html | 204 +- .../build_metadata_xml_from_list.html | 224 +- docs/reference/build_proxy.html | 187 +- docs/reference/build_soap_xml_from_list.html | 248 +- docs/reference/catch_errors.html | 196 +- docs/reference/catch_unknown_api.html | 199 +- docs/reference/check_and_encode_files.html | 210 +- .../collapse_list_with_dupe_names.html | 214 +- .../combine_parent_and_child_resultsets.html | 204 +- docs/reference/compact2.html | 204 +- docs/reference/drop_attributes.html | 208 +- .../drop_attributes_recursively.html | 217 +- docs/reference/drop_empty_recursively.html | 199 +- docs/reference/drop_nested_child_records.html | 199 +- .../extract_nested_child_records.html | 199 +- .../extract_records_from_xml_node.html | 215 +- .../extract_records_from_xml_nodeset.html | 215 +- ...t_records_from_xml_nodeset_of_records.html | 221 +- docs/reference/filter_valid_controls.html | 204 +- docs/reference/flatten_tbl_df.html | 200 +- .../reference/format_headers_for_verbose.html | 196 +- docs/reference/format_report_row.html | 233 +- docs/reference/get_os.html | 204 +- .../guess_object_name_from_soql.html | 196 +- docs/reference/index.html | 1161 +- docs/reference/is_legit_token.html | 196 +- .../list_extract_parent_and_child_result.html | 202 +- ...analytics_folder_child_operations_url.html | 196 +- ...make_analytics_folder_collections_url.html | 187 +- .../make_analytics_folder_operations_url.html | 196 +- ...make_analytics_folder_share_by_id_url.html | 200 +- ...analytics_folder_share_recipients_url.html | 206 +- .../make_analytics_folder_shares_url.html | 196 +- ...analytics_notification_operations_url.html | 187 +- ...ke_analytics_notifications_limits_url.html | 187 +- ...make_analytics_notifications_list_url.html | 187 +- docs/reference/make_base_metadata_url.html | 187 +- docs/reference/make_base_rest_url.html | 187 +- docs/reference/make_base_soap_url.html | 187 +- .../make_bulk_batch_details_url.html | 204 +- .../reference/make_bulk_batch_status_url.html | 204 +- docs/reference/make_bulk_batches_url.html | 200 +- docs/reference/make_bulk_create_job_url.html | 206 +- docs/reference/make_bulk_delete_job_url.html | 200 +- .../make_bulk_end_job_generic_url.html | 200 +- .../reference/make_bulk_get_all_jobs_url.html | 212 +- .../make_bulk_get_all_query_jobs_url.html | 214 +- docs/reference/make_bulk_get_job_url.html | 212 +- docs/reference/make_bulk_job_records_url.html | 212 +- .../reference/make_bulk_query_result_url.html | 218 +- docs/reference/make_bulk_query_url.html | 200 +- docs/reference/make_chatter_users_url.html | 187 +- docs/reference/make_composite_batch_url.html | 187 +- docs/reference/make_composite_url.html | 187 +- docs/reference/make_dashboard_copy_url.html | 196 +- .../make_dashboard_describe_url.html | 196 +- ...e_dashboard_filter_operators_list_url.html | 196 +- ...dashboard_filter_options_analysis_url.html | 196 +- docs/reference/make_dashboard_status_url.html | 196 +- docs/reference/make_dashboard_url.html | 196 +- docs/reference/make_dashboards_list_url.html | 187 +- docs/reference/make_login_url.html | 196 +- .../make_parameterized_search_url.html | 204 +- docs/reference/make_query_url.html | 204 +- docs/reference/make_report_copy_url.html | 196 +- docs/reference/make_report_create_url.html | 187 +- docs/reference/make_report_describe_url.html | 196 +- docs/reference/make_report_execute_url.html | 204 +- docs/reference/make_report_fields_url.html | 196 +- ...make_report_filter_operators_list_url.html | 196 +- docs/reference/make_report_instance_url.html | 200 +- .../make_report_instances_list_url.html | 196 +- docs/reference/make_report_query_url.html | 187 +- .../make_report_type_describe_url.html | 196 +- .../reference/make_report_types_list_url.html | 187 +- docs/reference/make_report_url.html | 196 +- docs/reference/make_reports_list_url.html | 187 +- docs/reference/make_rest_describe_url.html | 196 +- docs/reference/make_rest_objects_url.html | 196 +- docs/reference/make_rest_record_url.html | 200 +- docs/reference/make_search_url.html | 196 +- docs/reference/make_soap_xml_skeleton.html | 206 +- docs/reference/make_verbose_httr_message.html | 230 +- docs/reference/map_sf_type_to_r_type.html | 203 +- docs/reference/merge_null_to_na.html | 200 +- docs/reference/message_w_errors_listed.html | 202 +- docs/reference/metadata_type_validator.html | 203 +- .../parameterized_search_control.html | 255 +- .../parse_error_code_and_message.html | 196 +- docs/reference/parse_report_detail_rows.html | 243 +- docs/reference/patched_tempdir.html | 205 +- docs/reference/rDELETE.html | 187 +- docs/reference/rGET.html | 187 +- docs/reference/rPATCH.html | 187 +- docs/reference/rPOST.html | 187 +- docs/reference/rPUT.html | 187 +- docs/reference/records_list_to_tbl.html | 209 +- .../remove_empty_linked_object_cols.html | 200 +- docs/reference/return_matching_controls.html | 196 +- docs/reference/rforcecom.bulkAction.html | 282 +- docs/reference/rforcecom.bulkQuery.html | 231 +- docs/reference/rforcecom.create.html | 205 +- docs/reference/rforcecom.delete.html | 205 +- .../rforcecom.getObjectDescription.html | 205 +- .../rforcecom.getServerTimestamp.html | 193 +- docs/reference/rforcecom.login.html | 224 +- docs/reference/rforcecom.query.html | 205 +- docs/reference/rforcecom.retrieve.html | 245 +- docs/reference/rforcecom.search.html | 197 +- docs/reference/rforcecom.update.html | 209 +- docs/reference/rforcecom.upsert.html | 213 +- docs/reference/safe_bind_rows.html | 214 +- docs/reference/salesforcer-package.html | 186 +- docs/reference/salesforcer.html | 8 + docs/reference/salesforcer_state.html | 187 +- docs/reference/session_id_available.html | 190 +- docs/reference/set_null_elements_to_na.html | 200 +- .../set_null_elements_to_na_recursively.html | 199 +- docs/reference/sf_abort_job_bulk.html | 226 +- docs/reference/sf_access_token.html | 196 +- .../sf_analytics_notification_create.html | 192 +- .../sf_analytics_notification_delete.html | 192 +- .../sf_analytics_notification_describe.html | 192 +- .../sf_analytics_notification_update.html | 196 +- .../sf_analytics_notifications_limits.html | 204 +- .../sf_analytics_notifications_list.html | 210 +- docs/reference/sf_auth.html | 294 +- docs/reference/sf_auth_check.html | 203 +- docs/reference/sf_auth_refresh.html | 200 +- docs/reference/sf_batch_details_bulk.html | 247 +- docs/reference/sf_batch_status_bulk.html | 249 +- docs/reference/sf_build_cols_spec.html | 199 +- docs/reference/sf_bulk_operation.html | 8 + docs/reference/sf_close_job_bulk.html | 240 +- docs/reference/sf_control.html | 346 +- docs/reference/sf_convert_lead.html | 276 +- docs/reference/sf_copy_dashboard.html | 196 +- docs/reference/sf_copy_report.html | 254 +- docs/reference/sf_create.html | 290 +- docs/reference/sf_create_attachment.html | 302 +- .../sf_create_attachment_bulk_v1.html | 197 +- docs/reference/sf_create_attachment_rest.html | 195 +- docs/reference/sf_create_attachment_soap.html | 195 +- docs/reference/sf_create_batches_bulk.html | 273 +- docs/reference/sf_create_bulk_v1.html | 197 +- docs/reference/sf_create_bulk_v2.html | 197 +- docs/reference/sf_create_job_bulk.html | 314 +- docs/reference/sf_create_job_bulk_v1.html | 244 +- docs/reference/sf_create_job_bulk_v2.html | 248 +- docs/reference/sf_create_metadata.html | 344 +- docs/reference/sf_create_report.html | 279 +- docs/reference/sf_create_rest.html | 197 +- docs/reference/sf_create_soap.html | 197 +- docs/reference/sf_delete.html | 280 +- docs/reference/sf_delete_attachment.html | 268 +- docs/reference/sf_delete_bulk_v1.html | 197 +- docs/reference/sf_delete_bulk_v2.html | 197 +- docs/reference/sf_delete_dashboard.html | 192 +- docs/reference/sf_delete_job_bulk.html | 210 +- docs/reference/sf_delete_metadata.html | 247 +- docs/reference/sf_delete_report.html | 238 +- docs/reference/sf_delete_report_instance.html | 244 +- docs/reference/sf_delete_rest.html | 197 +- docs/reference/sf_delete_soap.html | 197 +- docs/reference/sf_describe_dashboard.html | 208 +- .../sf_describe_dashboard_components.html | 196 +- docs/reference/sf_describe_metadata.html | 213 +- docs/reference/sf_describe_object_fields.html | 210 +- docs/reference/sf_describe_objects.html | 245 +- docs/reference/sf_describe_report.html | 244 +- docs/reference/sf_describe_report_type.html | 235 +- docs/reference/sf_download_attachment.html | 266 +- docs/reference/sf_empty_recycle_bin.html | 259 +- docs/reference/sf_end_job_bulk.html | 218 +- docs/reference/sf_execute_report.html | 352 +- .../sf_filter_dashboard_operators_list.html | 183 +- .../sf_filter_dashboard_options_analysis.html | 208 +- docs/reference/sf_find_duplicates.html | 264 +- docs/reference/sf_find_duplicates_by_id.html | 244 +- docs/reference/sf_format_date.html | 200 +- docs/reference/sf_format_datetime.html | 200 +- docs/reference/sf_format_time.AsIs.html | 8 + docs/reference/sf_format_time.Date.html | 8 + docs/reference/sf_format_time.NULL.html | 8 + docs/reference/sf_format_time.POSIXct.html | 8 + docs/reference/sf_format_time.POSIXlt.html | 8 + docs/reference/sf_format_time.POSIXt.html | 8 + docs/reference/sf_format_time.character.html | 8 + docs/reference/sf_format_time.data.frame.html | 246 +- docs/reference/sf_format_time.html | 256 +- docs/reference/sf_format_time.list.html | 246 +- docs/reference/sf_format_time.logical.html | 8 + docs/reference/sf_format_time.numeric.html | 8 + docs/reference/sf_get_all_jobs_bulk.html | 253 +- .../reference/sf_get_all_query_jobs_bulk.html | 265 +- docs/reference/sf_get_dashboard_data.html | 208 +- docs/reference/sf_get_dashboard_results.html | 213 +- docs/reference/sf_get_dashboard_status.html | 208 +- docs/reference/sf_get_deleted.html | 220 +- docs/reference/sf_get_job_bulk.html | 234 +- docs/reference/sf_get_job_records_bulk.html | 255 +- .../sf_get_report_instance_results.html | 304 +- docs/reference/sf_get_updated.html | 220 +- docs/reference/sf_guess_cols.html | 196 +- docs/reference/sf_input_data_validation.html | 200 +- docs/reference/sf_job_batches_bulk.html | 233 +- docs/reference/sf_list_api_limits.html | 200 +- docs/reference/sf_list_dashboards.html | 203 +- docs/reference/sf_list_metadata.html | 228 +- docs/reference/sf_list_objects.html | 196 +- docs/reference/sf_list_report_fields.html | 252 +- .../sf_list_report_filter_operators.html | 233 +- docs/reference/sf_list_report_instances.html | 247 +- docs/reference/sf_list_report_types.html | 231 +- docs/reference/sf_list_reports.html | 248 +- docs/reference/sf_list_resources.html | 197 +- docs/reference/sf_list_rest_api_versions.html | 197 +- docs/reference/sf_merge.html | 288 +- docs/reference/sf_query.html | 303 +- docs/reference/sf_query_bulk.html | 8 + docs/reference/sf_query_bulk_v1.html | 296 +- docs/reference/sf_query_bulk_v2.html | 296 +- docs/reference/sf_query_report.html | 223 +- docs/reference/sf_query_result_bulk.html | 282 +- docs/reference/sf_query_result_bulk_v1.html | 276 +- docs/reference/sf_query_result_bulk_v2.html | 277 +- docs/reference/sf_read_metadata.html | 222 +- docs/reference/sf_refresh_dashboard.html | 196 +- docs/reference/sf_rename_metadata.html | 228 +- docs/reference/sf_reorder_cols.html | 196 +- docs/reference/sf_report_folder_children.html | 196 +- docs/reference/sf_report_folder_create.html | 192 +- docs/reference/sf_report_folder_delete.html | 192 +- docs/reference/sf_report_folder_describe.html | 192 +- .../sf_report_folder_share_delete.html | 196 +- .../sf_report_folder_share_describe.html | 196 +- .../sf_report_folder_share_recipients.html | 214 +- .../sf_report_folder_share_update.html | 200 +- .../sf_report_folder_shares_add.html | 199 +- .../sf_report_folder_shares_list.html | 192 +- .../sf_report_folder_shares_update.html | 199 +- docs/reference/sf_report_folder_update.html | 196 +- docs/reference/sf_report_folders_list.html | 183 +- docs/reference/sf_reset_password.html | 225 +- docs/reference/sf_rest_list.html | 208 +- docs/reference/sf_retrieve.html | 272 +- docs/reference/sf_retrieve_metadata.html | 247 +- .../sf_retrieve_metadata_check_status.html | 244 +- docs/reference/sf_retrieve_rest.html | 199 +- docs/reference/sf_retrieve_soap.html | 199 +- docs/reference/sf_run_bulk_operation.html | 360 +- docs/reference/sf_run_bulk_query.html | 339 +- docs/reference/sf_run_report.html | 385 +- docs/reference/sf_search.html | 290 +- docs/reference/sf_server_timestamp.html | 196 +- docs/reference/sf_session_id.html | 196 +- .../sf_set_dashboard_sticky_filter.html | 213 +- docs/reference/sf_set_password.html | 213 +- docs/reference/sf_submit_query_bulk.html | 229 +- docs/reference/sf_undelete.html | 259 +- docs/reference/sf_update.html | 282 +- docs/reference/sf_update_attachment.html | 289 +- .../sf_update_attachment_bulk_v1.html | 197 +- docs/reference/sf_update_attachment_rest.html | 195 +- docs/reference/sf_update_attachment_soap.html | 195 +- docs/reference/sf_update_bulk_v1.html | 197 +- docs/reference/sf_update_bulk_v2.html | 197 +- docs/reference/sf_update_dashboard.html | 196 +- docs/reference/sf_update_metadata.html | 292 +- docs/reference/sf_update_report.html | 265 +- docs/reference/sf_update_rest.html | 197 +- docs/reference/sf_update_soap.html | 197 +- docs/reference/sf_upload_complete_bulk.html | 220 +- docs/reference/sf_upsert.html | 298 +- docs/reference/sf_upsert_bulk_v1.html | 199 +- docs/reference/sf_upsert_bulk_v2.html | 199 +- docs/reference/sf_upsert_metadata.html | 298 +- docs/reference/sf_upsert_rest.html | 199 +- docs/reference/sf_upsert_soap.html | 199 +- docs/reference/sf_user_info.html | 209 +- docs/reference/sf_write_csv.html | 204 +- docs/reference/simplify_report_metadata.html | 206 +- docs/reference/stop_w_errors_listed.html | 202 +- docs/reference/token_available.html | 190 +- docs/reference/unbox_list_elements.html | 204 +- .../unbox_list_elements_recursively.html | 204 +- docs/reference/unnest_col.html | 203 +- docs/reference/valid_metadata_list.html | 185 +- .../validate_get_all_jobs_params.html | 204 +- docs/reference/warn_w_errors_listed.html | 202 +- docs/reference/xmlToList2.html | 200 +- docs/reference/xml_drop_and_unlist.html | 202 +- .../xml_drop_and_unlist_recursively.html | 200 +- .../xml_extract_parent_and_child_result.html | 202 +- docs/reference/xml_nodeset_to_df.html | 196 +- docs/search.json | 1 + docs/sitemap.xml | 1274 +- vignettes/working-with-attachments.Rmd | 4 +- 504 files changed, 48258 insertions(+), 45342 deletions(-) create mode 100644 docs/deps/bootstrap-5.3.1/bootstrap.bundle.min.js create mode 100644 docs/deps/bootstrap-5.3.1/bootstrap.bundle.min.js.map create mode 100644 docs/deps/bootstrap-5.3.1/bootstrap.min.css create mode 100644 docs/deps/bootstrap-5.3.1/font.css create mode 100644 docs/deps/bootstrap-5.3.1/fonts/07d40e985ad7c747025dabb9f22142c4.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/1Ptug8zYS_SKggPNyC0ITw.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/1Ptug8zYS_SKggPNyCAIT5lu.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/1Ptug8zYS_SKggPNyCIIT5lu.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/1Ptug8zYS_SKggPNyCMIT5lu.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/1Ptug8zYS_SKggPNyCkIT5lu.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/1f5e011d6aae0d98fc0518e1a303e99a.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/4iCs6KVjbNBYlgoKcQ72j00.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/4iCs6KVjbNBYlgoKcg72j00.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/4iCs6KVjbNBYlgoKcw72j00.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/4iCs6KVjbNBYlgoKew72j00.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/4iCs6KVjbNBYlgoKfA72j00.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/4iCs6KVjbNBYlgoKfw72.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/4iCv6KVjbNBYlgoCxCvjs2yNL4U.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/4iCv6KVjbNBYlgoCxCvjsGyN.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/4iCv6KVjbNBYlgoCxCvjtGyNL4U.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/4iCv6KVjbNBYlgoCxCvjvGyNL4U.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/4iCv6KVjbNBYlgoCxCvjvWyNL4U.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/4iCv6KVjbNBYlgoCxCvjvmyNL4U.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/626330658504e338ee86aec8e957426b.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/6xK1dSBYKcSV-LCoeQqfX1RYOo3qPZ7jsDJT9g.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/6xK1dSBYKcSV-LCoeQqfX1RYOo3qPZ7ksDJT9g.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/6xK1dSBYKcSV-LCoeQqfX1RYOo3qPZ7nsDI.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/6xK1dSBYKcSV-LCoeQqfX1RYOo3qPZ7osDJT9g.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/6xK1dSBYKcSV-LCoeQqfX1RYOo3qPZ7psDJT9g.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/6xK1dSBYKcSV-LCoeQqfX1RYOo3qPZ7qsDJT9g.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/6xK1dSBYKcSV-LCoeQqfX1RYOo3qPZ7rsDJT9g.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qN67lqDY.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qNK7lqDY.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qNa7lqDY.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qNq7lqDY.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qO67lqDY.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qPK7lqDY.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3i54rwkxduz8A.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3i54rwlBduz8A.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3i54rwlxdu.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3i54rwmBduz8A.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3i54rwmRduz8A.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3i54rwmhduz8A.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3i54rwmxduz8A.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwkxduz8A.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlBduz8A.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwmBduz8A.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwmRduz8A.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwmhduz8A.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwmxduz8A.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ik4zwkxduz8A.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ik4zwlBduz8A.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ik4zwlxdu.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ik4zwmBduz8A.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ik4zwmRduz8A.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ik4zwmhduz8A.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ik4zwmxduz8A.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/CSR54z1Qlv-GDxkbKVQ_dFsvWNReuQ.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/CSR54z1Qlv-GDxkbKVQ_dFsvWNpeudwk.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/CSR64z1Qlv-GDxkbKVQ_fO4KTet_.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/CSR64z1Qlv-GDxkbKVQ_fOAKTQ.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/HI_QiYsKILxRpg3hIP6sJ7fM7PqlONvQlMIXxw.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/HI_QiYsKILxRpg3hIP6sJ7fM7PqlONvUlMI.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/HI_QiYsKILxRpg3hIP6sJ7fM7PqlONvXlMIXxw.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/HI_QiYsKILxRpg3hIP6sJ7fM7PqlONvYlMIXxw.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/HI_QiYsKILxRpg3hIP6sJ7fM7PqlONvZlMIXxw.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/HI_QiYsKILxRpg3hIP6sJ7fM7PqlONvalMIXxw.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/HI_QiYsKILxRpg3hIP6sJ7fM7PqlONvblMIXxw.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/HI_SiYsKILxRpg3hIP6sJ7fM7PqlM-vWjMY.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/HI_SiYsKILxRpg3hIP6sJ7fM7PqlMOvWjMY.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/HI_SiYsKILxRpg3hIP6sJ7fM7PqlMevWjMY.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/HI_SiYsKILxRpg3hIP6sJ7fM7PqlMuvWjMY.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/HI_SiYsKILxRpg3hIP6sJ7fM7PqlOevWjMY.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/HI_SiYsKILxRpg3hIP6sJ7fM7PqlPevW.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/HI_SiYsKILxRpg3hIP6sJ7fM7PqlPuvWjMY.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/JTUSjIg1_i6t8kCHKm459W1hyzbi.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/JTUSjIg1_i6t8kCHKm459WRhyzbi.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/JTUSjIg1_i6t8kCHKm459WZhyzbi.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/JTUSjIg1_i6t8kCHKm459Wdhyzbi.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/JTUSjIg1_i6t8kCHKm459Wlhyw.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/KFOlCnqEu92Fr1MmEU9fABc4EsA.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/KFOlCnqEu92Fr1MmEU9fBBc4.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/KFOlCnqEu92Fr1MmEU9fBxc4EsA.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/KFOlCnqEu92Fr1MmEU9fCBc4EsA.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/KFOlCnqEu92Fr1MmEU9fCRc4EsA.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/KFOlCnqEu92Fr1MmEU9fChc4EsA.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/KFOlCnqEu92Fr1MmEU9fCxc4EsA.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/KFOlCnqEu92Fr1MmSU5fABc4EsA.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/KFOlCnqEu92Fr1MmSU5fBBc4.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/KFOlCnqEu92Fr1MmSU5fBxc4EsA.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/KFOlCnqEu92Fr1MmSU5fCBc4EsA.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/KFOlCnqEu92Fr1MmSU5fCRc4EsA.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/KFOlCnqEu92Fr1MmSU5fChc4EsA.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/KFOlCnqEu92Fr1MmSU5fCxc4EsA.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/KFOlCnqEu92Fr1MmWUlfABc4EsA.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/KFOlCnqEu92Fr1MmWUlfBBc4.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/KFOlCnqEu92Fr1MmWUlfBxc4EsA.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/KFOlCnqEu92Fr1MmWUlfCBc4EsA.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/KFOlCnqEu92Fr1MmWUlfCRc4EsA.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/KFOlCnqEu92Fr1MmWUlfChc4EsA.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/KFOlCnqEu92Fr1MmWUlfCxc4EsA.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/KFOmCnqEu92Fr1Mu4WxKOzY.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/KFOmCnqEu92Fr1Mu4mxK.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/KFOmCnqEu92Fr1Mu5mxKOzY.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/KFOmCnqEu92Fr1Mu72xKOzY.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/KFOmCnqEu92Fr1Mu7GxKOzY.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/KFOmCnqEu92Fr1Mu7WxKOzY.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/KFOmCnqEu92Fr1Mu7mxKOzY.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/QGYpz_kZZAGCONcK2A4bGOj8mNhN.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/S6u8w4BMUTPHjxsAUi-qJCY.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/S6u8w4BMUTPHjxsAXC-q.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/S6u9w4BMUTPHh6UVSwaPGR_p.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/S6u9w4BMUTPHh6UVSwiPGQ.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/S6u9w4BMUTPHh7USSwaPGR_p.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/S6u9w4BMUTPHh7USSwiPGQ.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/S6uyw4BMUTPHjx4wXg.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/S6uyw4BMUTPHjxAwXjeu.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa0ZL7SUc.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1ZL7.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1pL7SUc.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa25L7SUc.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2JL7SUc.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2ZL7SUc.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2pL7SUc.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/XRXV3I6Li01BKofIMeaBXso.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/XRXV3I6Li01BKofINeaB.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/XRXV3I6Li01BKofIO-aBXso.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/XRXV3I6Li01BKofIOOaBXso.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/XRXV3I6Li01BKofIOuaBXso.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/c2f002b3a87d3f9bfeebb23d32cfd9f8.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/ee91700cdbf7ce16c054c2bb8946c736.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqW106F15M.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWt06F15M.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtE6F15M.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtU6F15M.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtk6F15M.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWu06F15M.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuU6F.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuk6F15M.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWvU6F15M.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWxU6F15M.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS-muw.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS2mu1aB.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSCmu1aB.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSGmu1aB.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSKmu1aB.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSOmu1aB.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSumu1aB.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSymu1aB.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTUGmu1aB.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTVOmu1aB.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/q5uGsou0JOdh94bfuQltOxU.woff2 create mode 100644 docs/deps/bootstrap-5.3.1/fonts/q5uGsou0JOdh94bfvQlt.woff2 create mode 100644 docs/deps/bootstrap-toc-1.0.1/bootstrap-toc.min.js create mode 100644 docs/deps/clipboard.js-2.0.11/clipboard.min.js create mode 100644 docs/deps/data-deps.txt create mode 100644 docs/deps/font-awesome-6.4.2/css/all.css create mode 100644 docs/deps/font-awesome-6.4.2/css/all.min.css create mode 100644 docs/deps/font-awesome-6.4.2/css/v4-shims.css create mode 100644 docs/deps/font-awesome-6.4.2/css/v4-shims.min.css create mode 100644 docs/deps/font-awesome-6.4.2/webfonts/fa-brands-400.ttf create mode 100644 docs/deps/font-awesome-6.4.2/webfonts/fa-brands-400.woff2 create mode 100644 docs/deps/font-awesome-6.4.2/webfonts/fa-regular-400.ttf create mode 100644 docs/deps/font-awesome-6.4.2/webfonts/fa-regular-400.woff2 create mode 100644 docs/deps/font-awesome-6.4.2/webfonts/fa-solid-900.ttf create mode 100644 docs/deps/font-awesome-6.4.2/webfonts/fa-solid-900.woff2 create mode 100644 docs/deps/font-awesome-6.4.2/webfonts/fa-v4compatibility.ttf create mode 100644 docs/deps/font-awesome-6.4.2/webfonts/fa-v4compatibility.woff2 create mode 100644 docs/deps/headroom-0.11.0/headroom.min.js create mode 100644 docs/deps/headroom-0.11.0/jQuery.headroom.min.js create mode 100644 docs/deps/jquery-3.6.0/jquery-3.6.0.js create mode 100644 docs/deps/jquery-3.6.0/jquery-3.6.0.min.js create mode 100644 docs/deps/jquery-3.6.0/jquery-3.6.0.min.map create mode 100644 docs/deps/search-1.0.0/autocomplete.jquery.min.js create mode 100644 docs/deps/search-1.0.0/fuse.min.js create mode 100644 docs/deps/search-1.0.0/mark.min.js create mode 100644 docs/katex-auto.js create mode 100644 docs/lightswitch.js create mode 100644 docs/reference/salesforcer.html create mode 100644 docs/reference/sf_bulk_operation.html create mode 100644 docs/reference/sf_format_time.AsIs.html create mode 100644 docs/reference/sf_format_time.Date.html create mode 100644 docs/reference/sf_format_time.NULL.html create mode 100644 docs/reference/sf_format_time.POSIXct.html create mode 100644 docs/reference/sf_format_time.POSIXlt.html create mode 100644 docs/reference/sf_format_time.POSIXt.html create mode 100644 docs/reference/sf_format_time.character.html create mode 100644 docs/reference/sf_format_time.logical.html create mode 100644 docs/reference/sf_format_time.numeric.html create mode 100644 docs/reference/sf_query_bulk.html create mode 100644 docs/search.json diff --git a/docs/404.html b/docs/404.html index 2e77dbaf..e142e5ee 100644 --- a/docs/404.html +++ b/docs/404.html @@ -4,7 +4,7 @@ - + Page not found (404) • salesforcer @@ -12,19 +12,14 @@ - - - - - - - - - - + + + + + + + - - - -
-
-
- +
+
+
Content not found. Please use links in the navbar. -
+ +
+ - -
-
-

-

Site built with pkgdown 2.0.2.

-
-
-
- - - + diff --git a/docs/CODE_OF_CONDUCT.html b/docs/CODE_OF_CONDUCT.html index c98f91e1..caaf9e40 100644 --- a/docs/CODE_OF_CONDUCT.html +++ b/docs/CODE_OF_CONDUCT.html @@ -1,102 +1,49 @@ -Contributor Code of Conduct • salesforcer - - -
-
-
- + + +
+
+
+
@@ -109,40 +56,22 @@

Contributor Code of Conduct

This Code of Conduct is adapted from the Contributor Covenant (http://contributor-covenant.org), version 1.0.0, available at http://contributor-covenant.org/version/1/0/0/

-
+
+ - +
+ +
+ -
-
-

Site built with pkgdown 2.0.2.

-
-
- - - + diff --git a/docs/CONTRIBUTING.html b/docs/CONTRIBUTING.html index 7bd77e25..59fdb3f0 100644 --- a/docs/CONTRIBUTING.html +++ b/docs/CONTRIBUTING.html @@ -1,102 +1,49 @@ -Contributing to {salesforcer} • salesforcer - - -
-
-
- + + +
+
+
+
@@ -112,7 +59,7 @@

Implementing a feature or bugfix

Pull request process

  • Fork the package and clone onto your computer. If you haven’t done this before, we recommend using:
  • -
usethis::create_from_github("StevenMMortimer/salesforcer", fork = TRUE)
+
usethis::create_from_github("StevenMMortimer/salesforcer", fork = TRUE)
  • Install all development dependences with devtools::install_dev_deps(), and then make sure the package passes R CMD check by running devtools::check(). If R CMD check doesn’t pass cleanly, it’s a good idea to ask for help before continuing.

  • Create a Git branch for your pull request (PR). We recommend using usethis::pr_init("brief-description-of-change").

  • Make your changes, commit to git, and then create a PR by running usethis::pr_push(), and following the prompts in your browser. The title of your PR should briefly describe the change. The body of your PR should contain Fixes #issue-number.

  • @@ -131,40 +78,23 @@

    Code of Conduct

-
+
+ - +
+ -
-
-

Site built with pkgdown 2.0.2.

-
-
- - - + diff --git a/docs/LICENSE-text.html b/docs/LICENSE-text.html index 18ba3939..a1ba18be 100644 --- a/docs/LICENSE-text.html +++ b/docs/LICENSE-text.html @@ -1,142 +1,71 @@ -License • salesforcer - - -
-
-
- + + +
+
+
+
YEAR: 2018-2020
 COPYRIGHT HOLDER: Steven M. Mortimer
 
-
+
+ - +
+ +
+ -
-
-

Site built with pkgdown 2.0.2.

-
-
- - - + diff --git a/docs/LICENSE.html b/docs/LICENSE.html index a75e5c85..38ed5974 100644 --- a/docs/LICENSE.html +++ b/docs/LICENSE.html @@ -1,102 +1,49 @@ -NA • salesforcer - - -
-
-
- + + +
+
+
+
@@ -107,40 +54,22 @@

NA

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

-
+
+ - +
+ +
+ -
-
-

Site built with pkgdown 2.0.2.

-
-
- - - + diff --git a/docs/SECURITY.html b/docs/SECURITY.html index 6f276660..c9a4fd3d 100644 --- a/docs/SECURITY.html +++ b/docs/SECURITY.html @@ -1,102 +1,49 @@ -NA • salesforcer - - -
-
-
- + + +
+
+
+
@@ -104,40 +51,22 @@

Security contact informationTidelift security contact. Tidelift will coordinate the fix and disclosure.

-
+
+ - +
+ +
+ -
-
-

Site built with pkgdown 2.0.2.

-
-
- - - + diff --git a/docs/SUPPORT.html b/docs/SUPPORT.html index 9c651adc..5c54e84b 100644 --- a/docs/SUPPORT.html +++ b/docs/SUPPORT.html @@ -1,102 +1,49 @@ -Getting help with {salesforcer} • salesforcer - - -
-
-
- + + +
+
+
+
@@ -108,40 +55,22 @@

Getting help with {salesforcer}

Again, thank you for taking the time to contribute back to {salesforcer}. Your efforts will undoubtedly help other users as well!

-
+
+ - +
+ +
+ -
-
-

Site built with pkgdown 2.0.2.

-
-
- - - + diff --git a/docs/articles/getting-started.html b/docs/articles/getting-started.html index 73e34c62..c08500a5 100644 --- a/docs/articles/getting-started.html +++ b/docs/articles/getting-started.html @@ -4,7 +4,7 @@ - + Getting Started • salesforcer @@ -12,20 +12,12 @@ - - - - - - - - - - - + + + + + - - + + Skip to contents -
-
-
- +

After logging in with sf_auth(), you can check your +connectivity by looking at the information returned about the current +user. It should be information about you!

-# pull down information of person logged in
-# it's a simple easy call to get started 
-# and confirm a connection to the APIs
-user_info <- sf_user_info()
-sprintf("Organization Id: %s", user_info$organizationId)
-#> [1] "Organization Id: 00D6A0000003dN3UAI"
-sprintf("User Id: %s", user_info$userId)
-#> [1] "User Id: 0056A000000MPRjQAO"
+# pull down information of person logged in +# it's a simple easy call to get started +# and confirm a connection to the APIs +user_info <- sf_user_info() +sprintf("Organization Id: %s", user_info$organizationId) +#> [1] "Organization Id: 00D6A0000003dN3UAI" +sprintf("User Id: %s", user_info$userId) +#> [1] "User Id: 0056A000000MPRjQAO"

Creating records

-

Salesforce has objects and those objects contain records. One default object is the “Contact” object. This example shows how to create two records in the Contact object.

+

Salesforce has objects and those objects contain records. One default +object is the “Contact” object. This example shows how to create two +records in the Contact object.

-n <- 2
-new_contacts <- tibble(FirstName = rep("Test", n),
-                       LastName = paste0("Contact-Create-", 1:n))
-created_records <- sf_create(new_contacts, "Contact")
-created_records
-#> # A tibble: 2 × 2
-#>   id                 success
-#>   <chr>              <lgl>  
-#> 1 0033s00001BXHrOAAX TRUE   
-#> 2 0033s00001BXHrPAAX TRUE
+n <- 2 +new_contacts <- tibble(FirstName = rep("Test", n), + LastName = paste0("Contact-Create-", 1:n)) +created_records <- sf_create(new_contacts, "Contact") +created_records +#> # A tibble: 2 × 2 +#> id success +#> <chr> <lgl> +#> 1 003Kg000002AaC4IAK TRUE +#> 2 003Kg000002AaC5IAK TRUE

Retrieving records

-

Retrieve pulls down a specific set of records and fields. It’s very similar to running a query, but doesn’t use SOQL. Here is an example where we retrieve the data we just created.

+

Retrieve pulls down a specific set of records and fields. It’s very +similar to running a query, but doesn’t use SOQL. Here is an example +where we retrieve the data we just created.

-retrieved_records <- sf_retrieve(ids=created_records$id, 
-                                 fields=c("FirstName", "LastName"), 
-                                 object_name="Contact")
-retrieved_records
-#> # A tibble: 2 × 4
-#>   sObject Id                 FirstName LastName        
-#>   <chr>   <chr>              <chr>     <chr>           
-#> 1 Contact 0033s00001BXHrOAAX Test      Contact-Create-1
-#> 2 Contact 0033s00001BXHrPAAX Test      Contact-Create-2
+retrieved_records <- sf_retrieve(ids=created_records$id, + fields=c("FirstName", "LastName"), + object_name="Contact") +retrieved_records +#> # A tibble: 2 × 4 +#> sObject Id FirstName LastName +#> <chr> <chr> <chr> <chr> +#> 1 Contact 003Kg000002AaC4IAK Test Contact-Create-1 +#> 2 Contact 003Kg000002AaC5IAK Test Contact-Create-2

Querying records

-

Salesforce has proprietary form of SQL called SOQL (Salesforce Object Query Language). SOQL is a powerful tool that allows you to return the fields of records in almost any object in Salesforce including Accounts, Contacts, Tasks, Opportunities, even Attachments! Below is an example where we grab the data we just created including Account object information for which the Contact record is associated with.

+

Salesforce has proprietary form of SQL called SOQL (Salesforce Object +Query Language). SOQL is a powerful tool that allows you to return the +fields of records in almost any object in Salesforce including Accounts, +Contacts, Tasks, Opportunities, even Attachments! Below is an example +where we grab the data we just created including Account object +information for which the Contact record is associated with.

-my_soql <- sprintf("SELECT Id, 
-                           Account.Name, 
-                           FirstName, 
-                           LastName 
-                    FROM Contact 
-                    WHERE Id in ('%s')", 
-                   paste0(created_records$id , collapse="','"))
-
-queried_records <- sf_query(my_soql)
-queried_records
-#> # A tibble: 2 × 3
-#>   Id                 FirstName LastName        
-#>   <chr>              <chr>     <chr>           
-#> 1 0033s00001BXHrOAAX Test      Contact-Create-1
-#> 2 0033s00001BXHrPAAX Test      Contact-Create-2
-

NOTE: In the example above, you’ll notice that the "Account.Name" column does not appear in the results. This is because the SOAP and REST APIs only return an empty Account object for the record if there is no relationship to an account ( see #78). There is no reliable way to extract and rebuild the empty columns based on the query string. If there were Account information, an additional column titled "Account.Name" would appear in the results. Note, that the Bulk 1.0 and Bulk 2.0 APIs will return "Account.Name" as a column of all NA values for this query because they return results differently.

+my_soql <- sprintf("SELECT Id, + Account.Name, + FirstName, + LastName + FROM Contact + WHERE Id in ('%s')", + paste0(created_records$id , collapse="','")) + +queried_records <- sf_query(my_soql) +queried_records +#> # A tibble: 2 × 3 +#> Id FirstName LastName +#> <chr> <chr> <chr> +#> 1 003Kg000002AaC4IAK Test Contact-Create-1 +#> 2 003Kg000002AaC5IAK Test Contact-Create-2
+

NOTE: In the example above, you’ll notice that the +"Account.Name" column does not appear in the results. This +is because the SOAP and REST APIs only return an empty Account object +for the record if there is no relationship to an account ( see +#78). +There is no reliable way to extract and rebuild the empty columns based +on the query string. If there were Account information, an additional +column titled "Account.Name" would appear in the results. +Note, that the Bulk 1.0 and Bulk 2.0 APIs will return +"Account.Name" as a column of all NA values +for this query because they return results differently.

Updating records

-

After creating records you can update them using sf_update(). Updating a record requires you to pass the Salesforce Id of the record. Salesforce creates a unique 18-character identifier on each record and uses that to know which record to attach the update information you provide. Simply include a field or column in your update dataset called “Id” and the information will be matched. Here is an example where we update each of the records we created earlier with a new first name called “TestTest”.

+

After creating records you can update them using +sf_update(). Updating a record requires you to pass the +Salesforce Id of the record. Salesforce creates a unique +18-character identifier on each record and uses that to know which +record to attach the update information you provide. Simply include a +field or column in your update dataset called “Id” and the information +will be matched. Here is an example where we update each of the records +we created earlier with a new first name called “TestTest”.

-# Update some of those records
-queried_records <- queried_records %>%
-  mutate(FirstName = "TestTest")
-
-updated_records <- sf_update(queried_records, object_name="Contact")
-updated_records
-#> # A tibble: 2 × 2
-#>   id                 success
-#>   <chr>              <lgl>  
-#> 1 0033s00001BXHrOAAX TRUE   
-#> 2 0033s00001BXHrPAAX TRUE
+# Update some of those records +queried_records <- queried_records %>% + mutate(FirstName = "TestTest") + +updated_records <- sf_update(queried_records, object_name="Contact") +updated_records +#> # A tibble: 2 × 2 +#> id success +#> <chr> <lgl> +#> 1 003Kg000002AaC4IAK TRUE +#> 2 003Kg000002AaC5IAK TRUE

Deleting records

-

You can also delete records in Salesforce. The method implements a “soft” delete meaning that the deleted records go to the Recycle Bin which can be emptied or queried against later in the event that the record needed.

+

You can also delete records in Salesforce. The method implements a +“soft” delete meaning that the deleted records go to the Recycle Bin +which can be emptied or queried against later in the event that the +record needed.

-deleted_records <- sf_delete(updated_records$id)
-deleted_records
-#> # A tibble: 2 × 2
-#>   id                 success
-#>   <chr>              <lgl>  
-#> 1 0033s00001BXHrOAAX TRUE   
-#> 2 0033s00001BXHrPAAX TRUE
+deleted_records <- sf_delete(updated_records$id) +deleted_records +#> # A tibble: 2 × 3 +#> id success errors +#> <chr> <lgl> <list> +#> 1 003Kg000002AaC4IAK TRUE <list [0]> +#> 2 003Kg000002AaC5IAK TRUE <list [0]>

Upserting records

-

Finally, Salesforce has a unique method called “upsert” that allows you to create and/or update records at the same time. More specifically, if the record is not found based an an “External Id” field, then Salesforce will create the record instead of updating one. Below is an example where we create 2 records, then upsert 3, where 2 are matched and updated and one is created. NOTE: You will need to create a custom field on the target object and ensure it is labeled as an “External Id” field. Read more at: https://blog.jeffdouglas.com/2010/05/07/using-exernal-id-fields-in-salesforce/.

+

Finally, Salesforce has a unique method called “upsert” that allows +you to create and/or update records at the same time. More specifically, +if the record is not found based an an “External Id” field, then +Salesforce will create the record instead of updating one. Below is an +example where we create 2 records, then upsert 3, where 2 are matched +and updated and one is created. NOTE: You will need to +create a custom field on the target object and ensure it is labeled as +an “External Id” field.

-n <- 2
-new_contacts <- tibble(FirstName = rep("Test", n),
-                       LastName = paste0("Contact-Create-", 1:n), 
-                       My_External_Id__c=letters[1:n])
-created_records <- sf_create(new_contacts, "Contact")
-
-upserted_contacts <- tibble(FirstName = rep("Test", n),
-                            LastName = paste0("Contact-Upsert-", 1:n), 
-                            My_External_Id__c=letters[1:n])
-new_record <- tibble(FirstName = "Test",
-                     LastName = paste0("Contact-Upsert-", n+1), 
-                     My_External_Id__c=letters[n+1])
-upserted_contacts <- bind_rows(upserted_contacts, new_record)
-
-upserted_records <- sf_upsert(input_data=upserted_contacts, 
-                              object_name="Contact", 
-                              external_id_fieldname="My_External_Id__c")
-upserted_records
-#> # A tibble: 3 × 3
-#>   id                 success created
-#>   <chr>              <lgl>   <lgl>  
-#> 1 0033s00001BXHrTAAX TRUE    FALSE  
-#> 2 0033s00001BXHrUAAX TRUE    FALSE  
-#> 3 0033s00001BXHrYAAX TRUE    TRUE
+n <- 2 +new_contacts <- tibble(FirstName = rep("Test", n), + LastName = paste0("Contact-Create-", 1:n), + My_External_Id__c=letters[1:n]) +created_records <- sf_create(new_contacts, "Contact") + +upserted_contacts <- tibble(FirstName = rep("Test", n), + LastName = paste0("Contact-Upsert-", 1:n), + My_External_Id__c=letters[1:n]) +new_record <- tibble(FirstName = "Test", + LastName = paste0("Contact-Upsert-", n+1), + My_External_Id__c=letters[n+1]) +upserted_contacts <- bind_rows(upserted_contacts, new_record) + +upserted_records <- sf_upsert(input_data=upserted_contacts, + object_name="Contact", + external_id_fieldname="My_External_Id__c") +upserted_records +#> # A tibble: 3 × 3 +#> id success created +#> <chr> <lgl> <lgl> +#> 1 003Kg000002AaC9IAK TRUE FALSE +#> 2 003Kg000002AaCAIA0 TRUE FALSE +#> 3 003Kg000002AaCEIA0 TRUE TRUE

Check out the Tests

-

The {salesforcer} package has quite a bit of unit test coverage to track any changes made between newly released versions of the Salesforce API (typically 4 each year). These tests are an excellent source of examples because they cover most all cases of utilizing the package functions. You can access them here: https://github.com/StevenMMortimer/salesforcer/tree/main/tests/testthat/

+

The {salesforcer} package has quite a bit of unit test coverage to +track any changes made between newly released versions of the Salesforce +API (typically 4 each year). These tests are an excellent source of +examples because they cover most all cases of utilizing the package +functions. You can access them here: +https://github.com/StevenMMortimer/salesforcer/tree/main/tests/testthat/

- + + + - -
-
-

-

Site built with pkgdown 2.0.2.

-
-
- - - - + diff --git a/docs/articles/index.html b/docs/articles/index.html index 7047f8cc..350e98d9 100644 --- a/docs/articles/index.html +++ b/docs/articles/index.html @@ -1,107 +1,53 @@ -Articles • salesforcer - - -
-
-
- + + +
+
+
+

All vignettes

-

+
Getting Started
@@ -120,34 +66,22 @@

All vignettes

Working with Reports
-
-
+
-
- - - +
+ + + + + + diff --git a/docs/articles/passing-control-args.html b/docs/articles/passing-control-args.html index 5efaf91a..9883977a 100644 --- a/docs/articles/passing-control-args.html +++ b/docs/articles/passing-control-args.html @@ -4,7 +4,7 @@ - + Passing Control Args • salesforcer @@ -12,20 +12,12 @@ - - - - - - - - - - - + + + + + - - + + Skip to contents -
-
-
-

An example using the DuplicateRuleHeader

-

The DuplicateRuleHeader that controls whether the duplicate rules are followed when inserting records from the API, has three fields:

+

The DuplicateRuleHeader that controls whether the +duplicate rules are followed when inserting records from the API, has +three fields:

    -
  1. allowSave - For a duplicate rule, when the Alert option is enabled, bypass alerts and save duplicate records by setting this property to true. Prevent duplicate records from being saved by setting this property to false.

  2. -
  3. includeRecordDetails - Get fields and values for records detected as duplicates by setting this property to true. Get only record IDs for records detected as duplicates by setting this property to false.

  4. -
  5. runAsCurrentUser - Make sure that sharing rules for the current user are enforced when duplicate rules run by setting this property to true. Use the sharing rules specified in the class for the request by setting this property to false. If no sharing rules are specified, Apex code runs in system context and sharing rules for the current user are not enforced.

  6. +
  7. allowSave - For a duplicate rule, when the Alert +option is enabled, bypass alerts and save duplicate records by setting +this property to true. Prevent duplicate records from being saved by +setting this property to false.

  8. +
  9. includeRecordDetails - Get fields and values for +records detected as duplicates by setting this property to true. Get +only record IDs for records detected as duplicates by setting this +property to false.

  10. +
  11. runAsCurrentUser - Make sure that sharing rules for +the current user are enforced when duplicate rules run by setting this +property to true. Use the sharing rules specified in the class for the +request by setting this property to false. If no sharing rules are +specified, Apex code runs in system context and sharing rules for the +current user are not enforced.

-

Specifying these arguments requires a list structure in R, which may seem redundant in some cases, but is necessary to follow in order to build the API request correctly.

+

Specifying these arguments requires a list structure in +R, which may seem redundant in some cases, but is necessary to follow in +order to build the API request correctly.

-# override the duplicate rules ...
-record2 <- sf_create(new_contact,
-                     object_name = "Contact",
-                     DuplicateRuleHeader = list(allowSave = TRUE, 
-                                                includeRecordDetails = FALSE, 
-                                                runAsCurrentUser = TRUE))
-record2
-#> # A tibble: 1 × 2
-#>   id                 success
-#>   <chr>              <lgl>  
-#> 1 0033s00001BXHrdAAH TRUE
-
-# ... or succumb to the duplicate rules
-record3 <- sf_create(new_contact,
-                     object_name = "Contact",
-                     DuplicateRuleHeader = list(allowSave = FALSE, 
-                                                includeRecordDetails = FALSE, 
-                                                runAsCurrentUser = TRUE))
-record3
-#> # A tibble: 1 × 2
-#>   success errors    
-#>   <lgl>   <list>    
-#> 1 FALSE   <list [1]>
-

Per the description above, note that setting allowSave=TRUE will not override rules where the “Action on Create” for a rule is set to “Block”. If the duplicate rule’s action is “Allow” with an alert, then setting allowSave=TRUE means the record will be created with no warning message. If allowSave=FALSE, then the record will be prevented from being created. For additional information on the DuplicateRuleHeader, please see the Salesforce documentation at: https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_header_duplicateruleheader.htm

-

Finally, you may notice during your use that only certain control arguments are permitted based on the API. For example, the DuplicateRuleHeader is not implemented in the REST API like it is in the SOAP API. In the example below you should take note of two things:

+# override the duplicate rules ... +record2 <- sf_create(new_contact, + object_name = "Contact", + DuplicateRuleHeader = list(allowSave = TRUE, + includeRecordDetails = FALSE, + runAsCurrentUser = TRUE)) +record2 +#> # A tibble: 1 × 2 +#> id success +#> <chr> <lgl> +#> 1 003Kg000002AaCJIA0 TRUE + +# ... or succumb to the duplicate rules +record3 <- sf_create(new_contact, + object_name = "Contact", + DuplicateRuleHeader = list(allowSave = FALSE, + includeRecordDetails = FALSE, + runAsCurrentUser = TRUE)) +record3 +#> # A tibble: 1 × 2 +#> success errors +#> <lgl> <list> +#> 1 FALSE <list [1]>
+

Per the description above, note that setting +allowSave=TRUE will not override rules where the “Action on +Create” for a rule is set to “Block”. If the duplicate rule’s action is +“Allow” with an alert, then setting allowSave=TRUE means +the record will be created with no warning message. If +allowSave=FALSE, then the record will be prevented from +being created. For additional information on the +DuplicateRuleHeader, please see the Salesforce +documentation at: +https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_header_duplicateruleheader.htm

+

Finally, you may notice during your use that only certain control +arguments are permitted based on the API. For example, the +DuplicateRuleHeader is not implemented in the REST API like +it is in the SOAP API. In the example below you should take note of two +things:

    -
  1. When using the REST API and setting the DuplicateRuleHeader, then you will notice a warning that the header was ignored. You will receive warnings when trying to set any control parameters for an API or operation that does not recognize that particular control.

  2. -
  3. In this example, you cannot bypass the duplicate rule alert to create the record if using the REST API like you can with the SOAP API.

  4. +
  5. When using the REST API and setting the +DuplicateRuleHeader, then you will notice a warning that +the header was ignored. You will receive warnings when trying to set any +control parameters for an API or operation that does not recognize that +particular control.

  6. +
  7. In this example, you cannot bypass the duplicate rule alert to +create the record if using the REST API like you can with the SOAP +API.

-record4 <- sf_create(new_contact,
-                     object_name = "Contact",
-                     DuplicateRuleHeader = list(allowSave = FALSE, 
-                                                includeRecordDetails = FALSE, 
-                                                runAsCurrentUser = TRUE),
-                     api_type = "REST")
-#> Warning: Ignoring the following controls which are not used in the REST API:
-#> DuplicateRuleHeader
-record4
-#> # A tibble: 1 × 2
-#>   success errors    
-#>   <lgl>   <list>    
-#> 1 FALSE   <list [1]>
+record4 <- sf_create(new_contact, + object_name = "Contact", + DuplicateRuleHeader = list(allowSave = FALSE, + includeRecordDetails = FALSE, + runAsCurrentUser = TRUE), + api_type = "REST") +#> Warning: Ignoring the following controls which are not used in the REST API: +#> DuplicateRuleHeader +record4 +#> # A tibble: 1 × 2 +#> success errors +#> <lgl> <list> +#> 1 FALSE <list [1]>

Creating the control argument with sf_control

-

If this type of control structure is new to you, take a look at the documentation for the glm and glm.control functions. The way these two functions behave is exactly how functions like sf_create and sf_control work with each other. As demonstrated above you can pass any number of arbitrary controls into the function and they are all gathered up into the control by control = list(...). However, you can specify the control directly like this:

+

If this type of control structure is new to you, take a look at the +documentation for the glm and glm.control +functions. The way these two functions behave is exactly how functions +like sf_create and sf_control work with each +other. As demonstrated above you can pass any number of arbitrary +controls into the function and they are all gathered up into the control +by control = list(...). However, you can specify the +control directly like this:

-sf_query("SELECT Id, Name FROM Account LIMIT 1000",
-         object_name = "Account",
-         control = sf_control(QueryOptions = list(batchSize = 200)))
-#> # A tibble: 15 × 2
-#>   Id                 Name                                
-#>   <chr>              <chr>                               
-#> 1 0013s00000zFgA6AAK KEEP Test Account With Child Records
-#> 2 0013s00000zFdugAAC KEEP Test Account With Child Records
-#> 3 0013s000014jF2vAAE Test Account For Performance Test   
-#> 4 0016A0000035mJEQAY GenePoint                           
-#> 5 0016A0000035mJCQAY United Oil & Gas, UK                
-#> # … with 10 more rows
+sf_query("SELECT Id, Name FROM Account LIMIT 1000", + object_name = "Account", + control = sf_control(QueryOptions = list(batchSize = 200))) +#> # A tibble: 16 × 2 +#> Id Name +#> <chr> <chr> +#> 1 0013s00000zFgA6AAK KEEP Test Account With Child Records +#> 2 0013s00000zFdugAAC KEEP Test Account With Child Records +#> 3 0013s000014jF2HAAU Test Account For Performance Test +#> 4 0016A0000035mJEQAY GenePoint +#> 5 0016A0000035mJCQAY United Oil & Gas, UK +#> # ℹ 11 more rows
-

Backwards compatibility for all_or_none and other named arguments +

Backwards compatibility for all_or_none and other named +arguments

-

You may already be taking advantage of the all_or_none or line_ending arguments which are control arguments that were explicity included in functions. These argument essentially hard coded values to pass the AllOrNoneHeader and LineEndingHeader control parameters. Starting with the 0.1.3 release it is no longer necessary and preferable not to have an argument like all_or_none listed explicity as an argument since it can be provided in the control argument. Note: the all_or_none argument and other explicit control arguments will still be available in {salesforcer} 0.1.3 but will provide a deprecated warning. They will be removed in the next CRAN release of the package so it will be important to update your code now if you are explicitly passing these arguments and see a deprecation warning.

+

You may already be taking advantage of the all_or_none +or line_ending arguments which are control arguments that +were explicity included in functions. These argument essentially hard +coded values to pass the AllOrNoneHeader and +LineEndingHeader control parameters. Starting with the +0.1.3 release it is no longer necessary and preferable not to have an +argument like all_or_none listed explicity as an argument +since it can be provided in the control argument. Note: the +all_or_none argument and other explicit control arguments +will still be available in {salesforcer} 0.1.3 but will provide a +deprecated warning. They will be removed in the next CRAN release of the +package so it will be important to update your code now if you are +explicitly passing these arguments and see a deprecation warning.

-

Below is a list of links that go directly to the control arguments (a.k.a headers) for the different APIs. I highly recommend reading this documentation before setting a control parameter in R so you know exactly what the behavior will be and how to specify it in R. You may notice that some controls are not included in the R package. Some may be added in the future if requested and some will not be added given the scope of the package. One final note is that some arguments in the REST API, like the “All or None” behavior is not a header, but a parameter in the API call. For this reason you will not see it listed in the REST API Headers section, but it is set in this R package using the AllOrNoneHeader argument in sf_control just to provide consistency between the SOAP and REST APIs. It would be confusing to have two arguments named differently, one for each API, but to do the exact same thing from R. For this reason, many of the control arguments match exactly as they are listed in the SOAP API, but can be used across other APIs even if not exactly written that way in the Salesforce documentation referenced below.

+

Below is a list of links that go directly to the control arguments +(a.k.a headers) for the different APIs. I highly recommend reading this +documentation before setting a control parameter in R so you know +exactly what the behavior will be and how to specify it in R. You may +notice that some controls are not included in the R package. Some may be +added in the future if requested and some will not be added given the +scope of the package. One final note is that some arguments in the REST +API, like the “All or None” behavior is not a header, but a parameter in +the API call. For this reason you will not see it listed in the REST API +Headers section, but it is set in this R package using the +AllOrNoneHeader argument in sf_control just to +provide consistency between the SOAP and REST APIs. It would be +confusing to have two arguments named differently, one for each API, but +to do the exact same thing from R. For this reason, many of the control +arguments match exactly as they are listed in the SOAP API, but can be +used across other APIs even if not exactly written that way in the +Salesforce documentation referenced below.

- + + + - -
-
-

-

Site built with pkgdown 2.0.2.

-
-
- - - - + diff --git a/docs/articles/supported-queries.html b/docs/articles/supported-queries.html index fe0e4adc..76fad47c 100644 --- a/docs/articles/supported-queries.html +++ b/docs/articles/supported-queries.html @@ -4,7 +4,7 @@ - + Supported Queries • salesforcer @@ -12,20 +12,12 @@ - - - - - - - - - - - + + + + + - - + + Skip to contents -
-
-
-

REST vs. SOAP API query performance test

-

Below is a small example to roughly demonstrate the magnitude of the performance difference between the REST and SOAP APIs when querying 1,000 records.

+

Below is a small example to roughly demonstrate the magnitude of the +performance difference between the REST and SOAP APIs when querying +1,000 records.

Setup performance test

-# create a new account 
-# (if replicating, you may or may not have an external id field in your Org)
-prefix <- paste0("APerfTest-", as.integer(runif(1,1,99999)))
-new_account <- sf_create(
-  tibble(
-    Name = "Test Account For Performance Test", 
-    My_External_Id__c = prefix,
-    Description = paste0("This is a test account with 1,000 records for ", 
-                         "testing the performance differences between the ", 
-                         "SOAP and REST APIs.")
-  ), 
-  object_name = "Account"
-)
-
-# create and associate a thousand new contacts with that account
-# (again, you may or may not have an external id field in your Org)
-n <- 1000
-prefix <- paste0("CPerfTest-", as.integer(runif(1,1,99999)), "-")
-new_contacts <- tibble(FirstName = rep("Test", n),
-                       LastName = paste0("Query-Vignette", 1:n), 
-                       test_number__c = 999.9,
-                       AccountId = rep(new_account$id, n),
-                       My_External_Id__c=paste0(prefix, 1:n))
-new_contacts_res <- sf_create(new_contacts, "Contact", api_type = "Bulk 2.0")
+# create a new account +# (if replicating, you may or may not have an external id field in your Org) +prefix <- paste0("APerfTest-", as.integer(runif(1,1,99999))) +new_account <- sf_create( + tibble( + Name = "Test Account For Performance Test", + My_External_Id__c = prefix, + Description = paste0("This is a test account with 1,000 records for ", + "testing the performance differences between the ", + "SOAP and REST APIs.") + ), + object_name = "Account" +) + +# create and associate a thousand new contacts with that account +# (again, you may or may not have an external id field in your Org) +n <- 1000 +prefix <- paste0("CPerfTest-", as.integer(runif(1,1,99999)), "-") +new_contacts <- tibble(FirstName = rep("Test", n), + LastName = paste0("Query-Vignette", 1:n), + test_number__c = 999.9, + AccountId = rep(new_account$id, n), + My_External_Id__c=paste0(prefix, 1:n)) +new_contacts_res <- sf_create(new_contacts, "Contact", api_type = "Bulk 2.0")

Performance test

-qry <- function(api_type){
-  sf_query(
-    sprintf("SELECT Id, Name, Owner.Id, 
-               (SELECT Id, LastName, Owner.Id FROM Contacts) 
-            FROM Account
-            WHERE Id = '%s'", 
-            new_account$id), 
-    api_type = api_type
-  )
-}
-res <- microbenchmark::microbenchmark(
-  qry("REST"),
-  qry("SOAP"),
-  times = 5, 
-  unit = "s"
-)
-res
-#> Unit: seconds
-#>         expr       min        lq      mean    median        uq      max neval
-#>  qry("REST") 0.4983136 0.5125766 0.6485466 0.5268722 0.6138641 1.091107     5
-#>  qry("SOAP") 2.0455009 2.1894474 2.1916884 2.2099502 2.2191440 2.294400     5
-
-suppressWarnings(suppressMessages(
-  ggplot2::autoplot(res) + 
-    ggplot2::scale_y_continuous(name="Time [seconds]", n.breaks=6)
-))
-

-

As seen in the limited test above, the REST API can be anywhere from 4-6x faster than the SOAP API for a query on 1,000 contact records associated with a single Account. Breaking up the number of records returned into smaller batches by setting QueryOptions = list(batchSize = 200) typically does not affect this result very much but it also depends on the number of fields in the query. For the REST API the default is 2,000 records per batch with a minimum of 200 and maximum of 2,000. For the SOAP API the default is 500 records per batch. For both APIs it is important to note that there is no guarantee that the requested batch size is the actual batch size. Changes are made as necessary to maximize performance. For example, the SOAP API states “batch size will be no more than 200 if the SOQL statement selects two or more custom fields of type long text”. The REST API mentions that the limit imposed by Salesforce’s app servers is around 20,000 characters which can cause batches to be smaller. In short, it’s generally okay to use the default batch sizes since Salesforce may optimize over your specified batch size anyways.

+qry <- function(api_type){ + sf_query( + sprintf("SELECT Id, Name, Owner.Id, + (SELECT Id, LastName, Owner.Id FROM Contacts) + FROM Account + WHERE Id = '%s'", + new_account$id), + api_type = api_type + ) +} +res <- microbenchmark::microbenchmark( + qry("REST"), + qry("SOAP"), + times = 5, + unit = "s" +) +res +#> Unit: seconds +#> expr min lq mean median uq max neval +#> qry("REST") 0.5055699 0.5247751 0.5825909 0.5510435 0.6099239 0.7216419 5 +#> qry("SOAP") 2.1696590 2.1971458 2.3724357 2.3276357 2.3316420 2.8360959 5 + +suppressWarnings(suppressMessages( + ggplot2::autoplot(res) + + ggplot2::scale_y_continuous(name="Time [seconds]", n.breaks=6) +))
+

A violin plot showing the distribution of latency times for REST API and SOAP API

+

As seen in the limited test above, the REST API can be anywhere from +4-6x faster than the SOAP API for a query on 1,000 +contact records associated with a single Account. Breaking up the number +of records returned into smaller batches by setting +QueryOptions = list(batchSize = 200) typically does not +affect this result very much but it also depends on the number of fields +in the query. For the REST API the default is 2,000 records per batch +with a minimum of 200 and maximum of 2,000. For the SOAP API the default +is 500 records per batch. For both APIs it is important to note that +there is no guarantee that the requested batch size is the actual batch +size. Changes are made as necessary to maximize performance. For +example, the SOAP API states “batch size will be no more than 200 if the +SOQL statement selects two or more custom fields of type long text”. The +REST API mentions that the limit imposed by Salesforce’s app servers is +around 20,000 characters which can cause batches to be smaller. In +short, it’s generally okay to use the default batch sizes since +Salesforce may optimize over your specified batch size anyways.

When to use the Bulk APIs for queries

-

A general rule of thumb for using the Bulk APIs (Bulk 1.0 and Bulk 2.0) for queries is anytime you need to retrieve more than 10,000 records. The main reasons to not use the Bulk APIs are twofold. First, they do not support complex relationship queries or aggregate queries. If you need to write a nested relationship or aggregate query involving a large number of records you may be tempted to use the REST API. However, it is recommended to perform two or more separate bulk queries that retrieve the records you need and then join or aggregate the results in R.

+

A general rule of thumb for using the Bulk APIs (Bulk 1.0 and Bulk +2.0) for queries is anytime you need to retrieve more than 10,000 +records. The main reasons to not use the Bulk APIs are twofold. First, +they do not support complex relationship queries or aggregate queries. +If you need to write a nested relationship or aggregate query involving +a large number of records you may be tempted to use the REST API. +However, it is recommended to perform two or more separate bulk queries +that retrieve the records you need and then join or aggregate the +results in R.

-# nested relationship query 
-# (supposed to return the id and first name of all contacts on each account)
-try(
-  sf_query(
-    "SELECT Id, Name, 
-      (SELECT Id, FirstName FROM Contacts)
-    FROM Account",
-    api_type = "Bulk 2.0"
-  )
-)
-#> Request failed [400]. Retrying in 1.9 seconds...
-#> Request failed [400]. Retrying in 3.8 seconds...
-#> Error : API_ERROR: Aggregate Relationships not supported in Bulk V2 Query with CSV content type
-
-# aggregate query
-# (supposed to return the count of contacts per account)
-try(
-  sf_query(
-    "SELECT Account.Id, Count(Name) contacts_n
-    FROM Contact
-    GROUP BY Account.Id",  
-    api_type = "Bulk 2.0"
-  )
-)
-#> Request failed [400]. Retrying in 1.6 seconds...
-#> Request failed [400]. Retrying in 1.4 seconds...
-#> Error : API_ERROR: Aggregate Relationships not supported in Bulk Query
-

The two queries above were trying to pull all the contacts for each account and then get a count of how many contacts there are per account. If you have a lot of records, using the REST API to return these results may not be feasible. Even though the Bulk APIs cannot handle the same query, they can pull down massive amounts of data quickly. In this case you can pull down all of the Contact records and all of the Account records and then perform the calculation using dplyr, like so:

+# nested relationship query +# (supposed to return the id and first name of all contacts on each account) +try( + sf_query( + "SELECT Id, Name, + (SELECT Id, FirstName FROM Contacts) + FROM Account", + api_type = "Bulk 2.0" + ) +) +#> Request failed [400]. Retrying in 1 seconds... +#> Request failed [400]. Retrying in 1 seconds... +#> Error : API_ERROR: Aggregate Relationships not supported in Bulk V2 Query with CSV content type + +# aggregate query +# (supposed to return the count of contacts per account) +try( + sf_query( + "SELECT Account.Id, Count(Name) contacts_n + FROM Contact + GROUP BY Account.Id", + api_type = "Bulk 2.0" + ) +) +#> Request failed [400]. Retrying in 1 seconds... +#> Request failed [400]. Retrying in 3.9 seconds... +#> Error : API_ERROR: Aggregate Relationships not supported in Bulk Query
+

The two queries above were trying to pull all the contacts for each +account and then get a count of how many contacts there are per account. +If you have a lot of records, using the REST API to return these results +may not be feasible. Even though the Bulk APIs cannot handle the same +query, they can pull down massive amounts of data quickly. In this case +you can pull down all of the Contact records and all of the Account +records and then perform the calculation using dplyr, +like so:

-contacts <- sf_query("SELECT Id, FirstName, Account.Id
-                     FROM Contact", 
-                     api_type = "Bulk 2.0")
-
-accounts <- sf_query("SELECT Id, Name 
-                     FROM Account", 
-                     api_type = "Bulk 2.0")
-
-nested_query_recs <- accounts %>% 
-  left_join(contacts %>% 
-              rename(`Contact.Id` = Id, 
-                     `Contact.FirstName` = FirstName), 
-            by = c("Id" = "Account.Id"))
-nested_query_recs
-#> # A tibble: 419 × 4
-#>   Id                 Name                            Contact.Id Contact.FirstNa…
-#>   <chr>              <chr>                           <chr>      <chr>           
-#> 1 0013s00000zFdugAAC KEEP Test Account With Child R… 0033s0000… KEEP            
-#> 2 0013s00000zFdugAAC KEEP Test Account With Child R… 0033s0000… KEEP            
-#> 3 0013s00000zFdugAAC KEEP Test Account With Child R… 0033s0000… KEEP            
-#> 4 0013s00000zFdugAAC KEEP Test Account With Child R… 0033s0000… KEEP            
-#> 5 0013s00000zFdugAAC KEEP Test Account With Child R… 0033s0000… KEEP            
-#> # … with 414 more rows
-
-aggregate_query_recs <- nested_query_recs %>% 
-  group_by(Id) %>%
-  summarize(.groups = 'drop', 
-            contacts_n = sum(!is.na(Contact.Id)))
-aggregate_query_recs
-#> # A tibble: 16 × 2
-#>   Id                 contacts_n
-#>   <chr>                   <int>
-#> 1 0013s00000zFdugAAC        300
-#> 2 0013s00000zFgA6AAK          0
-#> 3 0013s000014jF2HAAU          0
-#> 4 0013s000014jF2vAAE          0
-#> 5 0013s000014jFj6AAE          0
-#> # … with 11 more rows
-

The second reason to not use the Bulk APIs is that there is a performance overhead associated with every bulk (asynchronous) job that involves checking the status of the job until it succeeds or fails before retrieving the results.

-

The example below is provided so that you can take this code as an example to run your own performance test of queries that return 10K, 100K, 1M+ records to see where the Bulk APIs outperform the REST API.

+contacts <- sf_query("SELECT Id, FirstName, Account.Id + FROM Contact", + api_type = "Bulk 2.0") + +accounts <- sf_query("SELECT Id, Name + FROM Account", + api_type = "Bulk 2.0") + +nested_query_recs <- accounts %>% + left_join(contacts %>% + rename(`Contact.Id` = Id, + `Contact.FirstName` = FirstName), + by = c("Id" = "Account.Id")) +nested_query_recs +#> # A tibble: 518 × 4 +#> Id Name Contact.Id Contact.FirstName +#> <chr> <chr> <chr> <chr> +#> 1 0013s00000zFdugAAC KEEP Test Account With Child … 0033s0000… KEEP +#> 2 0013s00000zFdugAAC KEEP Test Account With Child … 0033s0000… KEEP +#> 3 0013s00000zFdugAAC KEEP Test Account With Child … 0033s0000… KEEP +#> 4 0013s00000zFdugAAC KEEP Test Account With Child … 0033s0000… KEEP +#> 5 0013s00000zFdugAAC KEEP Test Account With Child … 0033s0000… KEEP +#> # ℹ 513 more rows + +aggregate_query_recs <- nested_query_recs %>% + group_by(Id) %>% + summarize(.groups = 'drop', + contacts_n = sum(!is.na(Contact.Id))) +aggregate_query_recs +#> # A tibble: 17 × 2 +#> Id contacts_n +#> <chr> <int> +#> 1 0013s00000zFdugAAC 300 +#> 2 0013s00000zFgA6AAK 0 +#> 3 0013s000014jF2HAAU 0 +#> 4 0013s000014jF2vAAE 0 +#> 5 0013s000014jFj6AAE 0 +#> # ℹ 12 more rows
+

The second reason to not use the Bulk APIs is that there is a +performance overhead associated with every bulk (asynchronous) job that +involves checking the status of the job until it succeeds or fails +before retrieving the results.

+

The example below is provided so that you can take this code as an +example to run your own performance test of queries that return 10K, +100K, 1M+ records to see where the Bulk APIs outperform the REST +API.

-qry_compare <- function(api_type){
-  soql <- sprintf("SELECT Id, LastName, Account.Id, Account.Name, Owner.Id
-                   FROM Contact
-                   WHERE Account.Id = '%s'", 
-                   new_account$id)
-  sf_query(soql, api_type = api_type)
-}
-
-res <- microbenchmark::microbenchmark(
-  qry_compare("REST"),
-  qry_compare("Bulk 1.0"),
-  qry_compare("Bulk 2.0"),
-  times = 5, 
-  unit = "s"
-)
-

Note that the Bulk 1.0 API requires users to specify the target object along with their submitted SOQL. This is because it is needed when creating the bulk job that will manage and execute the query.

+qry_compare <- function(api_type){ + soql <- sprintf("SELECT Id, LastName, Account.Id, Account.Name, Owner.Id + FROM Contact + WHERE Account.Id = '%s'", + new_account$id) + sf_query(soql, api_type = api_type) +} + +res <- microbenchmark::microbenchmark( + qry_compare("REST"), + qry_compare("Bulk 1.0"), + qry_compare("Bulk 2.0"), + times = 5, + unit = "s" +) +

Note that the Bulk 1.0 API requires users to specify the target +object along with their submitted SOQL. This is because it is needed +when creating the bulk job that will manage and execute the query.

-queried_records <- sf_query(soql, api_type = "Bulk 1.0")
-#> Guessed 'Contact' as the object_name from supplied SOQL.
-#> Please set `object_name` explicitly if this is incorrect because it is required by the Bulk APIs.
-

As you can see above the {salesforcer} package will try to infer the object in the query if not explicitly provided. If it does not guess correctly, then please specify.

+queried_records <- sf_query(soql, api_type = "Bulk 1.0") +#> Guessed 'Contact' as the object_name from supplied SOQL. +#> Please set `object_name` explicitly if this is incorrect because it is required by the Bulk APIs. +

As you can see above the {salesforcer} package will try to infer the +object in the query if not explicitly provided. If it does not guess +correctly, then please specify.

Cleanup after performance tests

-

By keeping track of the account ids used in our tests, it is fairly easy to find and delete these test records from our Org to save space.

+

By keeping track of the account ids used in our tests, it is fairly +easy to find and delete these test records from our Org to save +space.

-# cleanup performance test Contact records ...
-contacts_to_delete <- sf_query(
-  sprintf("SELECT Id 
-          FROM Contact 
-          WHERE Account.Id = '%s'",
-          new_account$id)
-)
-sf_delete(contacts_to_delete$Id, "Contact", api_type="Bulk 2.0")
-#> # A tibble: 99 × 4
-#>   Id                 sf__Id             sf__Created sf__Error
-#>   <chr>              <chr>              <lgl>       <lgl>    
-#> 1 0033s00001BXHrnAAH 0033s00001BXHrnAAH FALSE       NA       
-#> 2 0033s00001BXHroAAH 0033s00001BXHroAAH FALSE       NA       
-#> 3 0033s00001BXHrpAAH 0033s00001BXHrpAAH FALSE       NA       
-#> 4 0033s00001BXHrqAAH 0033s00001BXHrqAAH FALSE       NA       
-#> 5 0033s00001BXHrrAAH 0033s00001BXHrrAAH FALSE       NA       
-#> # … with 94 more rows
-
-# ... and finally delete the account
-sf_delete(new_account$id)
-#> # A tibble: 1 × 2
-#>   id                 success
-#>   <chr>              <lgl>  
-#> 1 0013s000019J2otAAC TRUE
+# cleanup performance test Contact records ... +contacts_to_delete <- sf_query( + sprintf("SELECT Id + FROM Contact + WHERE Account.Id = '%s'", + new_account$id) +) +sf_delete(contacts_to_delete$Id, "Contact", api_type="Bulk 2.0") +#> # A tibble: 99 × 4 +#> Id sf__Id sf__Created sf__Error +#> <chr> <chr> <lgl> <lgl> +#> 1 003Kg000002AaCOIA0 003Kg000002AaCOIA0 FALSE NA +#> 2 003Kg000002AaCPIA0 003Kg000002AaCPIA0 FALSE NA +#> 3 003Kg000002AaCQIA0 003Kg000002AaCQIA0 FALSE NA +#> 4 003Kg000002AaCRIA0 003Kg000002AaCRIA0 FALSE NA +#> 5 003Kg000002AaCSIA0 003Kg000002AaCSIA0 FALSE NA +#> # ℹ 94 more rows + +# ... and finally delete the account +sf_delete(new_account$id) +#> # A tibble: 1 × 3 +#> id success errors +#> <chr> <lgl> <list> +#> 1 001Kg0000034pEVIAY TRUE <list [0]>

Relationship queries

-

Salesforce supports retrieving fields from related objects when querying another object. This is similar to performing a JOIN in SQL, but without having to specify the join keys because Salesforce already knows the relationship between the two objects. There are two types of relationship queries (1. child-to-parent lookups and 2. parent-to-child nested queries) detailed in the sections below.

+

Salesforce supports retrieving fields from related objects when +querying another object. This is similar to performing a JOIN in SQL, +but without having to specify the join keys because Salesforce already +knows the relationship between the two objects. There are two types of +relationship queries (1. child-to-parent lookups and 2. parent-to-child +nested queries) detailed in the sections below.

child-to-parent “lookup” queries

-

The first type of relationship query and the most common is child to parent. For example, the Contact object (child) to their parent, the Account object. In order to pull down parent object fields with your child record query, you just need to prefix any fields from the related object by concatenating the name of the object with the field name separated by a period. In the example below we are retrieving all Contact object records that have a relationship to an Account.

+

The first type of relationship query and the most common is child to +parent. For example, the Contact object (child) to their parent, the +Account object. In order to pull down parent object fields with your +child record query, you just need to prefix any fields from the related +object by concatenating the name of the object with the field name +separated by a period. In the example below we are retrieving all +Contact object records that have a relationship to an Account.

-# child-to-parent relationship (e.g. Account.Name from Contact record)
-sf_query(
-  "SELECT Id, FirstName, Account.Name
-  FROM Contact
-  WHERE Account.Id != null"
-)
-#> # A tibble: 315 × 3
-#>   Id                 FirstName Account.Name                        
-#>   <chr>              <chr>     <chr>                               
-#> 1 0033s000012NkzwAAC KEEP      KEEP Test Account With Child Records
-#> 2 0033s000012NkzxAAC KEEP      KEEP Test Account With Child Records
-#> 3 0033s000012NkzyAAC KEEP      KEEP Test Account With Child Records
-#> 4 0033s000012NkzzAAC KEEP      KEEP Test Account With Child Records
-#> 5 0033s000012Nl00AAC KEEP      KEEP Test Account With Child Records
-#> # … with 310 more rows
-

Sometimes you may notice that the requested relationship fields do not appear in the query results. This is because the SOAP and REST APIs do not return any related object information if it does not exist on the record and there is no reliable way to extract and rebuild the empty columns based on the query string. In the example below, if there were Account information an additional column titled "Account.Name" would appear in the results.

+# child-to-parent relationship (e.g. Account.Name from Contact record) +sf_query( + "SELECT Id, FirstName, Account.Name + FROM Contact + WHERE Account.Id != null" +) +#> # A tibble: 414 × 3 +#> Id FirstName Account.Name +#> <chr> <chr> <chr> +#> 1 0033s000012NkzwAAC KEEP KEEP Test Account With Child Records +#> 2 0033s000012NkzxAAC KEEP KEEP Test Account With Child Records +#> 3 0033s000012NkzyAAC KEEP KEEP Test Account With Child Records +#> 4 0033s000012NkzzAAC KEEP KEEP Test Account With Child Records +#> 5 0033s000012Nl00AAC KEEP KEEP Test Account With Child Records +#> # ℹ 409 more rows
+

Sometimes you may notice that the requested relationship fields do +not appear in the query results. This is because the SOAP and REST APIs +do not return any related object information if it does not exist on the +record and there is no reliable way to extract and rebuild the empty +columns based on the query string. In the example below, if there were +Account information an additional column titled +"Account.Name" would appear in the results.

-# child-to-parent relationship (e.g. Account.Name from Contact record)
-sf_query(
-  "SELECT Id, FirstName, Account.Name
-  FROM Contact
-  WHERE Account.Id = null"
-)
-#> # A tibble: 78 × 2
-#>   Id                 FirstName
-#>   <chr>              <chr>    
-#> 1 0033s000014B3IZAA0 Rick     
-#> 2 0033s000014B3IaAAK Rick     
-#> 3 0033s000014B3IbAAK Rick     
-#> 4 0033s000012Nd6FAAS Jenny    
-#> 5 0033s000012NdARAA0 Jenny    
-#> # … with 73 more rows
-

Note, that the Bulk 1.0 and Bulk 2.0 APIs will return "Account.Name" as a column of all NA values for this query because they return results differently.

-

Finally, one aspect to note is that the Bulk 2.0 API does not support child-to-parent-grandparent relationships as seen in the example below:

+# child-to-parent relationship (e.g. Account.Name from Contact record) +sf_query( + "SELECT Id, FirstName, Account.Name + FROM Contact + WHERE Account.Id = null" +) +#> # A tibble: 418 × 2 +#> Id FirstName +#> <chr> <chr> +#> 1 003Kg000002AYKTIA4 Test +#> 2 003Kg000002AYKSIA4 Test +#> 3 003Kg000002AYHUIA4 Test +#> 4 003Kg000002AYHVIA4 Test +#> 5 003Kg000002AYHeIAO Test +#> # ℹ 413 more rows
+

Note, that the Bulk 1.0 and Bulk 2.0 APIs will return +"Account.Name" as a column of all NA values +for this query because they return results differently.

+

Finally, one aspect to note is that the Bulk 2.0 API does not support +child-to-parent-grandparent relationships as seen in the example +below:

-try(
-  sf_query("SELECT Id, FirstName, Account.Owner.Id
-            FROM Contact", 
-           api_type = "Bulk 2.0")
-)
-#> # A tibble: 393 × 3
-#>   Id                 FirstName Account.Owner.Id
-#>   <chr>              <chr>     <chr>           
-#> 1 0033s000012Nd60AAC Jenny     NA              
-#> 2 0033s000012Nd65AAC Jenny     NA              
-#> 3 0033s000012Nd6FAAS Jenny     NA              
-#> 4 0033s000012Nd6UAAS Jenny     NA              
-#> 5 0033s000012NdARAA0 Jenny     NA              
-#> # … with 388 more rows
+try( + sf_query("SELECT Id, FirstName, Account.Owner.Id + FROM Contact", + api_type = "Bulk 2.0") +) +#> # A tibble: 832 × 3 +#> Id FirstName Account.Owner.Id +#> <chr> <chr> <chr> +#> 1 0033s000012Nd60AAC Jenny NA +#> 2 0033s000012Nd65AAC Jenny NA +#> 3 0033s000012Nd6FAAS Jenny NA +#> 4 0033s000012Nd6UAAS Jenny NA +#> 5 0033s000012NdARAA0 Jenny NA +#> # ℹ 827 more rows

parent-to-child “nested” queries

-

Instead of “looking up” a related field, users can write queries that retrieve the individual records related to a parent. For example, if you would like all of the Accounts and their Contacts you can write the query like so:

+

Instead of “looking up” a related field, users can write queries that +retrieve the individual records related to a parent. For example, if you +would like all of the Accounts and their Contacts you can write the +query like so:

-sf_query(
-  "SELECT Id, Name, 
-    (SELECT Id, FirstName FROM Contacts)
-  FROM Account"
-)
-#> # A tibble: 320 × 4
-#>   Id                 Name                            Contact.FirstNa… Contact.Id
-#>   <chr>              <chr>                           <chr>            <chr>     
-#> 1 0013s00000zFgA6AAK KEEP Test Account With Child R… NA               NA        
-#> 2 0013s00000zFdugAAC KEEP Test Account With Child R… KEEP             0033s0000…
-#> 3 0013s00000zFdugAAC KEEP Test Account With Child R… KEEP             0033s0000…
-#> 4 0013s00000zFdugAAC KEEP Test Account With Child R… KEEP             0033s0000…
-#> 5 0013s00000zFdugAAC KEEP Test Account With Child R… KEEP             0033s0000…
-#> # … with 315 more rows
-

At first glance this query may appear the same as a lookup query on the Contact object that includes the account id and name. However, the small difference is that every Account is included, regardless of whether or not they have a Contact. This can be helpful when you want to ensure a query contains all of the parent records and their child records, if they exist. Also, note that the plural object name is used inside the nested query (“Contacts” instead of “Contact”).

-

Finally, a parent-to-child nested query can also contain a child-to-parent lookup relationship within it. Below is an example where the Owner Id on the Contact is included so you can know who is responsible for the Contacts under each Account.

+sf_query( + "SELECT Id, Name, + (SELECT Id, FirstName FROM Contacts) + FROM Account" +) +#> # A tibble: 419 × 4 +#> Id Name Contact.FirstName Contact.Id +#> <chr> <chr> <chr> <chr> +#> 1 0013s00000zFgA6AAK KEEP Test Account With Child … NA NA +#> 2 0013s00000zFdugAAC KEEP Test Account With Child … KEEP 0033s0000… +#> 3 0013s00000zFdugAAC KEEP Test Account With Child … KEEP 0033s0000… +#> 4 0013s00000zFdugAAC KEEP Test Account With Child … KEEP 0033s0000… +#> 5 0013s00000zFdugAAC KEEP Test Account With Child … KEEP 0033s0000… +#> # ℹ 414 more rows
+

At first glance this query may appear the same as a lookup query on +the Contact object that includes the account id and name. However, the +small difference is that every Account is included, regardless of +whether or not they have a Contact. This can be helpful when you want to +ensure a query contains all of the parent records and their child +records, if they exist. Also, note that the plural object name is used +inside the nested query (“Contacts” instead of “Contact”).

+

Finally, a parent-to-child nested query can also contain a +child-to-parent lookup relationship within it. Below is an example where +the Owner Id on the Contact is included so you can know who is +responsible for the Contacts under each Account.

-sf_query(
-  "SELECT Name, Owner.Id, 
-    (SELECT Id, FirstName, Owner.Id FROM Contacts)
-   FROM Account"
-)
-#> # A tibble: 320 × 5
-#>   Name                     Contact.FirstNa… Contact.Id Contact.Owner.Id Owner.Id
-#>   <chr>                    <chr>            <chr>      <chr>            <chr>   
-#> 1 KEEP Test Account With … NA               NA         NA               0056A00…
-#> 2 KEEP Test Account With … KEEP             0033s0000… 0056A000000MPRj… 0056A00…
-#> 3 KEEP Test Account With … KEEP             0033s0000… 0056A000000MPRj… 0056A00…
-#> 4 KEEP Test Account With … KEEP             0033s0000… 0056A000000MPRj… 0056A00…
-#> 5 KEEP Test Account With … KEEP             0033s0000… 0056A000000MPRj… 0056A00…
-#> # … with 315 more rows
+sf_query( + "SELECT Name, Owner.Id, + (SELECT Id, FirstName, Owner.Id FROM Contacts) + FROM Account" +) +#> # A tibble: 419 × 5 +#> Name Contact.FirstName Contact.Id Contact.Owner.Id Owner.Id +#> <chr> <chr> <chr> <chr> <chr> +#> 1 KEEP Test Account With… NA NA NA 0056A00… +#> 2 KEEP Test Account With… KEEP 0033s0000… 0056A000000MPRj… 0056A00… +#> 3 KEEP Test Account With… KEEP 0033s0000… 0056A000000MPRj… 0056A00… +#> 4 KEEP Test Account With… KEEP 0033s0000… 0056A000000MPRj… 0056A00… +#> 5 KEEP Test Account With… KEEP 0033s0000… 0056A000000MPRj… 0056A00… +#> # ℹ 414 more rows

Troubleshooting

-

If you are having an issue with a query please submit in the {salesforcer} GitHub repository at: https://github.com/StevenMMortimer/salesforcer/issues. As a maintainer, queries are tough to debug because every Salesforce Org is unique. Custom objects or relationships created in your Salesforce Org may be different or even impossible to test in another Org. When filing your issue please make an attempt to understand the query and debug a little bit on your own. Here are a few suggestions:

+

If you are having an issue with a query please submit in the +{salesforcer} GitHub repository at: +https://github.com/StevenMMortimer/salesforcer/issues. +As a maintainer, queries are tough to debug because every Salesforce Org +is unique. Custom objects or relationships created in your Salesforce +Org may be different or even impossible to test in another Org. When +filing your issue please make an attempt to understand the query and +debug a little bit on your own. Here are a few suggestions:

  1. -

    Slightly modify your function call to sf_query() to observe the results. Here are a few prompting questions that may assist you:

    +

    Slightly modify your function call to sf_query() to +observe the results. Here are a few prompting questions that may assist +you:

      -
    • What do you see when you set verbose=TRUE argument?

    • -
    • What happens if you change the control argument, specifically the batch size?

    • -
    • What happens if you try using a different API (e.g. “SOAP” vs “REST” or “Bulk 1.0” vs “Bulk 2.0”)?

    • +
    • What do you see when you set verbose=TRUE +argument?

    • +
    • What happens if you change the control argument, +specifically the batch size?

    • +
    • What happens if you try using a different API (e.g. “SOAP” vs +“REST” or “Bulk 1.0” vs “Bulk 2.0”)?

    • What happens if you change your query slightly?

    • -
    • Do you need a parent-to-child nested relationship query or will a child-to-parent lookup suffice?

    • +
    • Do you need a parent-to-child nested relationship query or will a +child-to-parent lookup suffice?

  2. -
  3. Check out Salesforce’s Workbench tool to see how it constructs specific queries that you are debugging. The tool is available at https://workbench.developerforce.com and requires a Salesforce login (the same credentials as you normally would use).

  4. -
  5. Double check Salesforce’s SOQL reference guide to see whether your query is supported or limited in some way.

  6. -
  7. Review query unit tests at: https://github.com/StevenMMortimer/salesforcer/blob/main/tests/testthat/test-query.R. These unit tests were written to cover a variety of use cases and to track any changes made between newly released versions of the Salesforce API (typically 4 each year). These tests are an excellent source of examples that may be helpful in troubleshooting your own query.

  8. +
  9. Check out Salesforce’s Workbench tool to see how it constructs +specific queries that you are debugging. The tool is available at +https://workbench.developerforce.com and requires a +Salesforce login (the same credentials as you normally would +use).

  10. +
  11. Double check Salesforce’s +SOQL +reference guide to see whether your query is supported or limited in +some way.

  12. +
  13. Review query unit tests at: +https://github.com/StevenMMortimer/salesforcer/blob/main/tests/testthat/test-query.R. +These unit tests were written to cover a variety of use cases and to +track any changes made between newly released versions of the Salesforce +API (typically 4 each year). These tests are an excellent source of +examples that may be helpful in troubleshooting your own query.

  14. -

    Roll up your sleeves and dive into the source code for the {salesforcer} package. The main scripts to review are:

    +

    Roll up your sleeves and dive into the source code for the +{salesforcer} package. The main scripts to review are:

- + + - -
-
-

-

Site built with pkgdown 2.0.2.

-
-
- - - - + diff --git a/docs/articles/transitioning-from-RForcecom.html b/docs/articles/transitioning-from-RForcecom.html index 3e8d7dc9..7a00a217 100644 --- a/docs/articles/transitioning-from-RForcecom.html +++ b/docs/articles/transitioning-from-RForcecom.html @@ -4,7 +4,7 @@ - + Transitioning from RForcecom • salesforcer @@ -12,20 +12,12 @@ - - - - - - - - - - - + + + + + - - + + Skip to contents -
-
-
-

CRUD Operations

-

“CRUD” operations (Create, Retrieve, Update, Delete) in the {RForcecom} package only operate on one record at a time. One benefit to using the {salesforcer} package is that these operations will accept a named vector (one record) or an entire data.frame or tbl_df of records to churn through. However, rest assured that the replicated functions behave exactly the same way if you are hesitant to making the switch.

-

Here is an example showing the reduction in code of using {salesforcer} if you would like to create multiple records.

+

“CRUD” operations (Create, Retrieve, Update, Delete) in the +{RForcecom} package only operate on one record at a time. One benefit to +using the {salesforcer} package is that these operations will accept a +named vector (one record) or an entire data.frame or +tbl_df of records to churn through. However, rest assured +that the replicated functions behave exactly the same way if you are +hesitant to making the switch.

+

Here is an example showing the reduction in code of using +{salesforcer} if you would like to create multiple records.

-n <- 2
-new_contacts <- tibble(FirstName = rep("Test", n),
-                       LastName = paste0("Contact-Create-", 1:n))
-
-# the RForcecom way (requires a loop)
-# rforcecom_results <- NULL
-# for(i in 1:nrow(new_contacts)){
-#   temp <- RForcecom::rforcecom.create(session, 
-#                                       objectName = "Contact", 
-#                                       fields = unlist(slice(new_contacts,i)))
-#   rforcecom_results <- bind_rows(rforcecom_results, temp)
-# }
-
-# the better way in salesforcer to do multiple records
-salesforcer_results <- sf_create(new_contacts, object_name="Contact")
-salesforcer_results
-#> # A tibble: 2 × 2
-#>   id                 success
-#>   <chr>              <lgl>  
-#> 1 0033s00001BXHtnAAH TRUE   
-#> 2 0033s00001BXHtoAAH TRUE
+n <- 2 +new_contacts <- tibble(FirstName = rep("Test", n), + LastName = paste0("Contact-Create-", 1:n)) + +# the RForcecom way (requires a loop) +# rforcecom_results <- NULL +# for(i in 1:nrow(new_contacts)){ +# temp <- RForcecom::rforcecom.create(session, +# objectName = "Contact", +# fields = unlist(slice(new_contacts,i))) +# rforcecom_results <- bind_rows(rforcecom_results, temp) +# } + +# the better way in salesforcer to do multiple records +salesforcer_results <- sf_create(new_contacts, object_name="Contact") +salesforcer_results +#> # A tibble: 2 × 2 +#> id success +#> <chr> <lgl> +#> 1 003Kg000002AaDzIAK TRUE +#> 2 003Kg000002AaE0IAK TRUE

Query

-

{salesforcer} also has better printing and type-casting when returning query result thanks to features of the {readr} package.

+

{salesforcer} also has better printing and type-casting when +returning query result thanks to features of the {readr} package.

-this_soql <- "SELECT Id, Email FROM Contact LIMIT 5"
-
-# the RForcecom way
-# RForcecom::rforcecom.query(session, soqlQuery = this_soql)
-
-# the better way in salesforcer to query
-salesforcer_results <- sf_query(this_soql)
-salesforcer_results
-#> # A tibble: 5 × 2
-#>   Id                 Email               
-#>   <chr>              <chr>               
-#> 1 0033s000014B3IZAA0 rick.james@gmail.com
-#> 2 0033s000014B3IaAAK rick.james@gmail.com
-#> 3 0033s000014B3IbAAK rick.james@gmail.com
-#> 4 0033s000012Nd6FAAS jennyw@gmail.com    
-#> 5 0033s000012NdARAA0 jennyw@gmail.com
+this_soql <- "SELECT Id, Email FROM Contact LIMIT 5" + +# the RForcecom way +# RForcecom::rforcecom.query(session, soqlQuery = this_soql) + +# the better way in salesforcer to query +salesforcer_results <- sf_query(this_soql) +salesforcer_results +#> # A tibble: 5 × 1 +#> Id +#> <chr> +#> 1 003Kg000002AYKTIA4 +#> 2 003Kg000002AYKSIA4 +#> 3 003Kg000002AYHUIA4 +#> 4 003Kg000002AYHVIA4 +#> 5 003Kg000002AYHeIAO

Describe

-

The {RForcecom} package has the function rforcecom.getObjectDescription() which returns a data.frame with one row per field on an object. The same function in {salesforcer} is named sf_describe_object_fields() and also has better printing and datatype casting by using tibbles.

+

The {RForcecom} package has the function +rforcecom.getObjectDescription() which returns a +data.frame with one row per field on an object. The same +function in {salesforcer} is named +sf_describe_object_fields() and also has better printing +and datatype casting by using tibbles.

-# the RForcecom way
-# RForcecom::rforcecom.getObjectDescription(session, objectName='Account')
-
-# the better way in salesforcer to get object fields
-result2 <- salesforcer::sf_describe_object_fields('Account')
-result2
-#> # A tibble: 68 × 39
-#>   aggregatable aiPredictionField autoNumber byteLength calculated caseSensitive
-#>   <chr>        <chr>             <chr>      <chr>      <chr>      <chr>        
-#> 1 true         false             false      18         false      false        
-#> 2 false        false             false      0          false      false        
-#> 3 true         false             false      18         false      false        
-#> 4 true         false             false      765        false      false        
-#> 5 true         false             false      765        false      false        
-#> # … with 63 more rows, and 33 more variables: compoundFieldName <chr>,
-#> #   createable <chr>, custom <chr>, defaultedOnCreate <chr>,
-#> #   defaultValue <list>, deprecatedAndHidden <chr>, digits <chr>,
-#> #   externalId <chr>, extraTypeInfo <chr>, filterable <chr>, groupable <chr>,
-#> #   idLookup <chr>, label <chr>, length <chr>, name <chr>, nameField <chr>,
-#> #   namePointing <chr>, nillable <chr>, permissionable <chr>,
-#> #   picklistValues <list>, polymorphicForeignKey <chr>, precision <chr>, …
+# the RForcecom way +# RForcecom::rforcecom.getObjectDescription(session, objectName='Account') + +# the better way in salesforcer to get object fields +result2 <- salesforcer::sf_describe_object_fields('Account') +result2 +#> # A tibble: 68 × 39 +#> aggregatable aiPredictionField autoNumber byteLength calculated caseSensitive +#> <chr> <chr> <chr> <chr> <chr> <chr> +#> 1 true false false 18 false false +#> 2 false false false 0 false false +#> 3 true false false 18 false false +#> 4 true false false 765 false false +#> 5 true false false 765 false false +#> # ℹ 63 more rows +#> # ℹ 33 more variables: compoundFieldName <chr>, createable <chr>, custom <chr>, +#> # defaultedOnCreate <chr>, defaultValue <list>, deprecatedAndHidden <chr>, +#> # digits <chr>, externalId <chr>, extraTypeInfo <chr>, filterable <chr>, +#> # groupable <chr>, idLookup <chr>, label <chr>, length <chr>, name <chr>, +#> # nameField <chr>, namePointing <chr>, nillable <chr>, permissionable <chr>, +#> # picklistValues <list>, polymorphicForeignKey <chr>, precision <chr>, …
- + + + - -
-
-

-

Site built with pkgdown 2.0.2.

-
-
- - - - + diff --git a/docs/articles/working-with-attachments.html b/docs/articles/working-with-attachments.html index aef81322..bf3075a5 100644 --- a/docs/articles/working-with-attachments.html +++ b/docs/articles/working-with-attachments.html @@ -4,7 +4,7 @@ - + Working with Attachments • salesforcer @@ -12,20 +12,12 @@ - - - - - - - - - - - + + + + + - - + + Skip to contents -
-
-
-

Downloading Attachments

-

After uploading attachments to Salesforce you can download them by first querying the metadata in the Attachment object. This metadata provides the Id for the blob data attachment for download. A convenience function, sf_download_attachment(), has been created to download attachments quickly. The example below shows how to query the metadata of attachments belonging to particular ParentId.

+

After uploading attachments to Salesforce you can download them by +first querying the metadata in the Attachment object. This metadata +provides the Id for the blob data attachment for download. A convenience +function, sf_download_attachment(), has been created to +download attachments quickly. The example below shows how to query the +metadata of attachments belonging to particular ParentId.

-# pull down all attachments associated with a particular record
-queried_attachments <- sf_query(sprintf("SELECT Id, Body, Name, ParentId
-                                         FROM Attachment
-                                         WHERE ParentId IN ('%s', '%s')", 
-                                         parent_record_id1, parent_record_id2))
-queried_attachments
-#> # A tibble: 6 × 4
-#>   Id                 Body                                         Name  ParentId
-#>   <chr>              <chr>                                        <chr> <chr>   
-#> 1 00P3s00000gWnCtEAK /services/data/v54.0/sobjects/Attachment/00… old-… 0016A00…
-#> 2 00P3s00000gWnCjEAK /services/data/v54.0/sobjects/Attachment/00… clou… 0016A00…
-#> 3 00P3s00000gWnCoEAK /services/data/v54.0/sobjects/Attachment/00… logo… 0016A00…
-#> 4 00P3s00000gWnD8EAK /services/data/v54.0/sobjects/Attachment/00… old-… 0016A00…
-#> 5 00P3s00000gWnD3EAK /services/data/v54.0/sobjects/Attachment/00… logo… 0016A00…
-#> # … with 1 more row
-

Before downloading the attachments using the Body it is important to consider whether the attachment names are repeated or duplicates. If so, then the attachments with the same exact name will be overwritten on the local filesystem as they are downloaded. To avoid this problem there are two common strategies:

+# pull down all attachments associated with a particular record +queried_attachments <- sf_query(sprintf("SELECT Id, Body, Name, ParentId + FROM Attachment + WHERE ParentId IN ('%s', '%s')", + parent_record_id1, parent_record_id2)) +queried_attachments +#> # A tibble: 6 × 4 +#> Id Body Name ParentId +#> <chr> <chr> <chr> <chr> +#> 1 00PKg000001Egl0MAC /services/data/v61.0/sobjects/Attachment/00… logo… 0016A00… +#> 2 00PKg000001EgkvMAC /services/data/v61.0/sobjects/Attachment/00… clou… 0016A00… +#> 3 00PKg000001Egl5MAC /services/data/v61.0/sobjects/Attachment/00… old-… 0016A00… +#> 4 00PKg000001EglKMAS /services/data/v61.0/sobjects/Attachment/00… old-… 0016A00… +#> 5 00PKg000001EglAMAS /services/data/v61.0/sobjects/Attachment/00… clou… 0016A00… +#> # ℹ 1 more row
+

Before downloading the attachments using the Body it is important to +consider whether the attachment names are repeated or duplicates. If so, +then the attachments with the same exact name will be overwritten on the +local filesystem as they are downloaded. To avoid this problem there are +two common strategies:

    -
  1. Create a new column (e.g. unique_name) that is the concatenation of the Attachment Id and the Attachment’s name which is guaranteed to be unique.
  2. -
  3. Save the attachments in separate folders for each ParentId record.
  4. +
  5. Create a new column (e.g. unique_name) that is the +concatenation of the Attachment Id and the Attachment’s name which is +guaranteed to be unique.
  6. +
  7. Save the attachments in separate folders for each ParentId +record.
-

As long as the same ParentId record doesn’t name attachments with the same name, then Strategy #2 above will work. In addition, it may help better organize the documents if you are planning to download many and then upload again to Salesforce using the Bulk API as demonstrated later in this script.

+

As long as the same ParentId record doesn’t name attachments with the +same name, then Strategy #2 above will work. In addition, it may help +better organize the documents if you are planning to download many and +then upload again to Salesforce using the Bulk API as demonstrated later +in this script.

-# create a new folder for each ParentId in the dataset
-temp_dir <- tempdir()
-for (pid in unique(queried_attachments$ParentId)){
-  dir.create(file.path(temp_dir, pid), showWarnings = FALSE) # ignore if already exists
-}
-
-# create a new columns in the queried data so that we can pass this information 
-# on to the function `sf_download_attachment()` that will actually perform the download
-queried_attachments <- queried_attachments %>% 
-  # Strategy 1: Unique file names (ununsed here, but shown as an example)
-  mutate(unique_name = paste(Id, Name, sep='___')) %>% 
-  # Strategy 2: Separate folders per parent
-  mutate(Path = file.path(temp_dir, ParentId))
-
-# download all of the attachments for a single ParentId record to their own folder
-download_result <- mapply(sf_download_attachment, 
-                          body = queried_attachments$Body, 
-                          name = queried_attachments$Name, 
-                          path = queried_attachments$Path)
-download_result
-#>                               /services/data/v54.0/sobjects/Attachment/00P3s00000gWnCtEAK/Body 
-#> "/var/folders/s2/1mxmzrg52tx7l0pg8lq1320w0000gn/T//Rtmp6PB2EF/0016A0000035mJ4QAI/old-logo.png" 
-#>                               /services/data/v54.0/sobjects/Attachment/00P3s00000gWnCjEAK/Body 
-#>    "/var/folders/s2/1mxmzrg52tx7l0pg8lq1320w0000gn/T//Rtmp6PB2EF/0016A0000035mJ4QAI/cloud.png" 
-#>                               /services/data/v54.0/sobjects/Attachment/00P3s00000gWnCoEAK/Body 
-#>     "/var/folders/s2/1mxmzrg52tx7l0pg8lq1320w0000gn/T//Rtmp6PB2EF/0016A0000035mJ4QAI/logo.png" 
-#>                               /services/data/v54.0/sobjects/Attachment/00P3s00000gWnD8EAK/Body 
-#> "/var/folders/s2/1mxmzrg52tx7l0pg8lq1320w0000gn/T//Rtmp6PB2EF/0016A0000035mJ5QAI/old-logo.png" 
-#>                               /services/data/v54.0/sobjects/Attachment/00P3s00000gWnD3EAK/Body 
-#>     "/var/folders/s2/1mxmzrg52tx7l0pg8lq1320w0000gn/T//Rtmp6PB2EF/0016A0000035mJ5QAI/logo.png" 
-#>                               /services/data/v54.0/sobjects/Attachment/00P3s00000gWnCyEAK/Body 
-#>    "/var/folders/s2/1mxmzrg52tx7l0pg8lq1320w0000gn/T//Rtmp6PB2EF/0016A0000035mJ5QAI/cloud.png"
+# create a new folder for each ParentId in the dataset +temp_dir <- tempdir() +for (pid in unique(queried_attachments$ParentId)){ + dir.create(file.path(temp_dir, pid), showWarnings = FALSE) # ignore if already exists +} + +# create a new columns in the queried data so that we can pass this information +# on to the function `sf_download_attachment()` that will actually perform the download +queried_attachments <- queried_attachments %>% + # Strategy 1: Unique file names (ununsed here, but shown as an example) + mutate(unique_name = paste(Id, Name, sep='___')) %>% + # Strategy 2: Separate folders per parent + mutate(Path = file.path(temp_dir, ParentId)) + +# download all of the attachments for a single ParentId record to their own folder +download_result <- mapply(sf_download_attachment, + body = queried_attachments$Body, + name = queried_attachments$Name, + path = queried_attachments$Path) +download_result +#> /services/data/v61.0/sobjects/Attachment/00PKg000001Egl0MAC/Body +#> "/var/folders/s2/1mxmzrg52tx7l0pg8lq1320w0000gn/T//RtmpNdZR06/0016A0000035mJ4QAI/logo.png" +#> /services/data/v61.0/sobjects/Attachment/00PKg000001EgkvMAC/Body +#> "/var/folders/s2/1mxmzrg52tx7l0pg8lq1320w0000gn/T//RtmpNdZR06/0016A0000035mJ4QAI/cloud.png" +#> /services/data/v61.0/sobjects/Attachment/00PKg000001Egl5MAC/Body +#> "/var/folders/s2/1mxmzrg52tx7l0pg8lq1320w0000gn/T//RtmpNdZR06/0016A0000035mJ4QAI/old-logo.png" +#> /services/data/v61.0/sobjects/Attachment/00PKg000001EglKMAS/Body +#> "/var/folders/s2/1mxmzrg52tx7l0pg8lq1320w0000gn/T//RtmpNdZR06/0016A0000035mJ5QAI/old-logo.png" +#> /services/data/v61.0/sobjects/Attachment/00PKg000001EglAMAS/Body +#> "/var/folders/s2/1mxmzrg52tx7l0pg8lq1320w0000gn/T//RtmpNdZR06/0016A0000035mJ5QAI/cloud.png" +#> /services/data/v61.0/sobjects/Attachment/00PKg000001EglFMAS/Body +#> "/var/folders/s2/1mxmzrg52tx7l0pg8lq1320w0000gn/T//RtmpNdZR06/0016A0000035mJ5QAI/logo.png"

Updating Attachments

-

Once an Attachment record is created, you are still able to update the content and metadata (Name, IsPrivate, and OwnerId) for the record. The sf_update_attachment() function works just like sf_update() except that you will need to include the Body column where the content of the attachment is help. NOTE: If you just want to update the metadata and not the Attachment content itself, you can simply use sf_update() to modify fields other than the Body field on the record.

-

The example below demonstrates how to add an attachment to a record, then download it, zip it, re-upload to the record in order to save disk space in your Org.

+

Once an Attachment record is created, you are still able to update +the content and metadata (Name, IsPrivate, and +OwnerId) for the record. The +sf_update_attachment() function works just like +sf_update() except that you will need to include the +Body column where the content of the attachment is help. +NOTE: If you just want to update the metadata and not +the Attachment content itself, you can simply use +sf_update() to modify fields other than the +Body field on the record.

+

The example below demonstrates how to add an attachment to a record, +then download it, zip it, re-upload to the record in order to save disk +space in your Org.

-# upload a PDF to a particular record as an Attachment
-file_path <- system.file("extdata",
-                         "data-wrangling-cheatsheet.pdf",
-                         package = "salesforcer")
-parent_record_id <- "0036A000002C6MmQAK" # replace with your own ParentId!
-attachment_details <- tibble(Body = file_path, ParentId = parent_record_id)
-create_result <- sf_create_attachment(attachment_details)
-
-# download, zip, and re-upload the PDF
-pdf_path <- sf_download_attachment(sf_id = create_result$id[1])
-zipped_path <- paste0(pdf_path, ".zip")
-zip(zipped_path, pdf_path, flags = "-qq") # quiet zipping messages
-attachment_details <- tibble(Id = create_result$id, Body = zipped_path)
-sf_update_attachment(attachment_details)
-#> # A tibble: 1 × 2
-#>   id                 success
-#>   <chr>              <lgl>  
-#> 1 00P3s00000gWnDDEA0 TRUE
-

There is a function sf_delete_attachment(), which simply wraps sf_delete() so feel free to use for clarity in your code that you’re working with attachments use sf_delete().

+# upload a PDF to a particular record as an Attachment +file_path <- system.file("extdata", + "data-wrangling-cheatsheet.pdf", + package = "salesforcer") +parent_record_id <- "0036A000002C6MmQAK" # replace with your own ParentId! +attachment_details <- tibble(Body = file_path, ParentId = parent_record_id) +create_result <- sf_create_attachment(attachment_details) + +# download, zip, and re-upload the PDF +pdf_path <- sf_download_attachment(sf_id = create_result$id[1]) +zipped_path <- paste0(pdf_path, ".zip") +zip(zipped_path, pdf_path, flags = "-qq") # quiet zipping messages +attachment_details <- tibble(Id = create_result$id, Body = zipped_path) +sf_update_attachment(attachment_details) +#> # A tibble: 1 × 2 +#> id success +#> <chr> <lgl> +#> 1 00PKg000001EglPMAS TRUE
+

There is a function sf_delete_attachment(), which simply +wraps sf_delete() so feel free to use for clarity in your +code that you’re working with attachments use +sf_delete().

-sf_delete_attachment(ids = create_result$id)
-#> # A tibble: 1 × 2
-#>   id                 success
-#>   <chr>              <lgl>  
-#> 1 00P3s00000gWnDDEA0 TRUE
-# sf_delete(ids = create_result$id) # would also work
-

Uploading large batches of attachments using the Bulk API

-

The SOAP and REST APIs are good for working with a few attachments at a time. However, the Bulk API can be invoked using api_type=“Bulk 1.0” to automatically take a data.frame or tbl_df of Attachment field data and create a ZIP file with CSV manifest that is required by that API to upload. In the example above, we downloaded the three attachments each belonging to two different parent records. Assuming that I have run that code and the files are in that my computer I can demonstrate that all of them can be uploaded at once using the Bulk 1.0 API.

+sf_delete_attachment(ids = create_result$id) +#> # A tibble: 1 × 2 +#> id success +#> <chr> <lgl> +#> 1 00PKg000001EglPMAS TRUE +# sf_delete(ids = create_result$id) # would also work
+

Uploading large batches of attachments using the Bulk +API

+

The SOAP and REST APIs are good for working with a few attachments at +a time. However, the Bulk API can be invoked using api_type=“Bulk 1.0” +to automatically take a data.frame or tbl_df +of Attachment field data and create a ZIP file with CSV manifest that is +required by that API to upload. In the example above, we downloaded the +three attachments each belonging to two different parent records. +Assuming that I have run that code and the files are in that my computer +I can demonstrate that all of them can be uploaded at once using the +Bulk 1.0 API.

-# create the attachment metadata required (Name, Body, ParentId)
-attachment_details <- queried_attachments %>% 
-  mutate(Body = file.path(Path, Name)) %>% 
-  select(Name, Body, ParentId) 
+# create the attachment metadata required (Name, Body, ParentId) +attachment_details <- queried_attachments %>% + mutate(Body = file.path(Path, Name)) %>% + select(Name, Body, ParentId)
-result <- sf_create_attachment(attachment_details, api_type="Bulk 1.0")
-result
-#> # A tibble: 6 × 4
-#>   Id                 Success Created Error
-#>   <chr>              <lgl>   <lgl>   <lgl>
-#> 1 00P3s00000gWnDIEA0 TRUE    TRUE    NA   
-#> 2 00P3s00000gWnDJEA0 TRUE    TRUE    NA   
-#> 3 00P3s00000gWnDKEA0 TRUE    TRUE    NA   
-#> 4 00P3s00000gWnDLEA0 TRUE    TRUE    NA   
-#> 5 00P3s00000gWnDMEA0 TRUE    TRUE    NA   
-#> # … with 1 more row
-

NOTE: As of v48.0 (Spring ’20), it does not appear that the Bulk 2.0 API supports working with Attachments, so the Bulk 1.0 API must be used for bulk functionality.

-

Finally, you are able to update Attachments with the Bulk API just as shown above in the REST/SOAP API examples.

+result <- sf_create_attachment(attachment_details, api_type="Bulk 1.0") +result +#> # A tibble: 6 × 4 +#> Id Success Created Error +#> <chr> <lgl> <lgl> <lgl> +#> 1 00PKg000001EglUMAS TRUE TRUE NA +#> 2 00PKg000001EglVMAS TRUE TRUE NA +#> 3 00PKg000001EglWMAS TRUE TRUE NA +#> 4 00PKg000001EglXMAS TRUE TRUE NA +#> 5 00PKg000001EglYMAS TRUE TRUE NA +#> # ℹ 1 more row +

NOTE: As of v48.0 (Spring ’20), it does not appear +that the Bulk 2.0 API supports working with Attachments, so the Bulk 1.0 +API must be used for bulk functionality.

+

Finally, you are able to update Attachments with the Bulk API just as +shown above in the REST/SOAP API examples.

Extending to Documents and Other Blob Data

-

The commands for working with Attachments also work for uploading documents and other blob data as well. Documents are just like Attachments in Salesforce except instead of having an associated ParentId they have an associated FolderId where the blob will be associated with upon creation. Here is a brief example of uploading a PDF (“Document”) to a Folder

+

The commands for working with Attachments also work for uploading +documents and other blob data as well. Documents are just like +Attachments in Salesforce except instead of having an associated +ParentId they have an associated FolderId where the blob will be +associated with upon creation. Here is a brief example of uploading a +PDF (“Document”) to a Folder

-# the function supports inserting all types of blob content, just update the 
-# object_name argument to add the PDF as a Document instead of an Attachment
-document_details <- tibble(Name = "Data Wrangling Cheatsheet - Test 1",
-                           Description = "RStudio cheatsheet covering dplyr and tidyr.",
-                           Body = system.file("extdata", 
-                                              "data-wrangling-cheatsheet.pdf",
-                                              package="salesforcer"),
-                           FolderId = "00l6A000001EgIwQAK",
-                           Keywords = "test,cheatsheet,document")
-result <- sf_create_attachment(document_details, object_name = "Document")
-result
-#> # A tibble: 1 × 2
-#>   id                 success
-#>   <chr>              <lgl>  
-#> 1 0153s000002bthmAAA TRUE
-

With Documents, users are also able to save storage by specifying a Url instead of a a file path where the Body content is stored locally. Specifying the Url field will reference the URL instead of uploading into the Salesforce org, thereby saving space if limited in your organization.

+# the function supports inserting all types of blob content, just update the +# object_name argument to add the PDF as a Document instead of an Attachment +document_details <- tibble(Name = "Data Wrangling Cheatsheet - Test 1", + Description = "RStudio cheatsheet covering dplyr and tidyr.", + Body = system.file("extdata", + "data-wrangling-cheatsheet.pdf", + package="salesforcer"), + FolderId = "00l6A000001EgIwQAK", + Keywords = "test,cheatsheet,document") +result <- sf_create_attachment(document_details, object_name = "Document") +result +#> # A tibble: 1 × 2 +#> id success +#> <chr> <lgl> +#> 1 015Kg000000x3ZmIAI TRUE
+

With Documents, users are also able to save storage by specifying a +Url instead of a a file path where the Body +content is stored locally. Specifying the Url field will +reference the URL instead of uploading into the Salesforce org, thereby +saving space if limited in your organization.

-cheatsheet_url <- "https://rstudio.com/wp-content/uploads/2015/02/data-wrangling-cheatsheet.pdf"
-document_details <- tibble(Name = "Data Wrangling Cheatsheet - Test 2",
-                           Description = "RStudio cheatsheet covering dplyr and tidyr.",
-                           Url = cheatsheet_url,  
-                           FolderId = "00l6A000001EgIwQAK",
-                           Keywords = "test,cheatsheet,document")
-result <- sf_create_attachment(document_details, object_name = "Document")
-result
-#> # A tibble: 1 × 2
-#>   id                 success
-#>   <chr>              <lgl>  
-#> 1 0153s000002bthrAAA TRUE
+cheatsheet_url <- "https://rstudio.com/wp-content/uploads/2015/02/data-wrangling-cheatsheet.pdf" +document_details <- tibble(Name = "Data Wrangling Cheatsheet - Test 2", + Description = "RStudio cheatsheet covering dplyr and tidyr.", + Url = cheatsheet_url, + FolderId = "00l6A000001EgIwQAK", + Keywords = "test,cheatsheet,document") +result <- sf_create_attachment(document_details, object_name = "Document") +result +#> # A tibble: 1 × 2 +#> id success +#> <chr> <lgl> +#> 1 015Kg000000x3ZrIAI TRUE
-

Below is a list of links to existing Salesforce documentation that provide more detail into how Attachments, Documents, and other blob data are handled via their APIs. As with many functions in {salesforcer}, we have tried to translate these functions exactly as they are described in the Salesforce documentation so that they are flexible enough to handle most all cases that the APIs were intended to support.

+

Below is a list of links to existing Salesforce documentation that +provide more detail into how Attachments, Documents, and other blob data +are handled via their APIs. As with many functions in {salesforcer}, we +have tried to translate these functions exactly as they are described in +the Salesforce documentation so that they are flexible enough to handle +most all cases that the APIs were intended to support.

- + + + - -
-
-

-

Site built with pkgdown 2.0.2.

-
-
- - - - + diff --git a/docs/articles/working-with-bulk-apis.html b/docs/articles/working-with-bulk-apis.html index a4d4267f..3ea6a3fb 100644 --- a/docs/articles/working-with-bulk-apis.html +++ b/docs/articles/working-with-bulk-apis.html @@ -4,7 +4,7 @@ - + Working with Bulk APIs • salesforcer @@ -12,20 +12,12 @@ - - - - - - - - - - - + + + + + - - + + Skip to contents -
-
-
-

A Complete Bulk API Workflow

-

To show a more lengthy example of using the Bulk 1.0 API, below is a workflow of that creates 2 records, queries them, and deletes them. This is just an example. Typically, you’d want to use the Bulk APIs over the REST or SOAP APIs when dealing with over 10,000 records.

+

To show a more lengthy example of using the Bulk 1.0 API, below is a +workflow of that creates 2 records, queries them, and deletes them. This +is just an example. Typically, you’d want to use the Bulk APIs over the +REST or SOAP APIs when dealing with over 10,000 records.

-object <- "Contact"
-created_records <- sf_create(new_contacts, object_name=object, api_type="Bulk 1.0")
-created_records
-#> # A tibble: 2 × 4
-#>   Id                 Success Created Error
-#>   <chr>              <lgl>   <lgl>   <lgl>
-#> 1 0033s00001BXHu7AAH TRUE    TRUE    NA   
-#> 2 0033s00001BXHu8AAH TRUE    TRUE    NA
-
-# query bulk
-my_soql <- sprintf("SELECT Id,
-                           FirstName, 
-                           LastName
-                    FROM Contact 
-                    WHERE Id in ('%s')", 
-                   paste0(created_records$Id , collapse="','"))
-
-queried_records <- sf_query(my_soql, object_name=object, api_type="Bulk 1.0")
-queried_records
-#> # A tibble: 2 × 3
-#>   Id                 FirstName LastName        
-#>   <chr>              <chr>     <chr>           
-#> 1 0033s00001BXHu7AAH Test      Contact-Create-1
-#> 2 0033s00001BXHu8AAH Test      Contact-Create-2
-
-# delete bulk
-deleted_records <- sf_delete(queried_records$Id, object_name=object, api_type="Bulk 1.0")
-deleted_records
-#> # A tibble: 2 × 4
-#>   Id                 Success Created Error
-#>   <chr>              <lgl>   <lgl>   <lgl>
-#> 1 0033s00001BXHu7AAH TRUE    FALSE   NA   
-#> 2 0033s00001BXHu8AAH TRUE    FALSE   NA
+object <- "Contact" +created_records <- sf_create(new_contacts, object_name=object, api_type="Bulk 1.0") +created_records +#> # A tibble: 2 × 4 +#> Id Success Created Error +#> <chr> <lgl> <lgl> <lgl> +#> 1 003Kg000002AaEEIA0 TRUE TRUE NA +#> 2 003Kg000002AaEFIA0 TRUE TRUE NA + +# query bulk +my_soql <- sprintf("SELECT Id, + FirstName, + LastName + FROM Contact + WHERE Id in ('%s')", + paste0(created_records$Id , collapse="','")) + +queried_records <- sf_query(my_soql, object_name=object, api_type="Bulk 1.0") +queried_records +#> # A tibble: 2 × 3 +#> Id FirstName LastName +#> <chr> <chr> <chr> +#> 1 003Kg000002AaEEIA0 Test Contact-Create-1 +#> 2 003Kg000002AaEFIA0 Test Contact-Create-2 + +# delete bulk +deleted_records <- sf_delete(queried_records$Id, object_name=object, api_type="Bulk 1.0") +deleted_records +#> # A tibble: 2 × 4 +#> Id Success Created Error +#> <chr> <lgl> <lgl> <lgl> +#> 1 003Kg000002AaEEIA0 TRUE FALSE NA +#> 2 003Kg000002AaEFIA0 TRUE FALSE NA

Query Limitations

-

There is one limitation to Bulk queries is that it does not support the following operations or structures of SOQL:

+

There is one limitation to Bulk queries is that it does not support +the following operations or structures of SOQL:

  • COUNT
  • ROLLUP
  • @@ -240,110 +194,111 @@

    Query Limitations

    Using the Bulk 2.0 API

    -

    Salesforce has more recently introduced the Bulk 2.0 API which is supposed to be faster and have a more consistent JSON/REST based API than the Bulk 1.0 API. In some cases I have noticed that the ordering of the result records will differ from the order of the input data because the data is batched and processed asynchronously. by Salesforce instead of R. However, The Bulk 2.0 API returns every single field that was included in the call so if you have an identifying key your dataset, then it should not be a problem to join on that key with your original data to bring in the newly assigned Salesforce Id that is generated when the record was created in Salesforce. However, I have find it just seems wasteful to transfer all of the field information back after the query and have not found a significant performance improvement between the Bulk 1.0 and Bulk 2.0. Finally, note that the status field names (“Success”, “Created”, “Error”) are different from the Bulk 2.0 API.

    +

    Salesforce has more recently introduced the Bulk 2.0 API which is +supposed to be faster and have a more consistent JSON/REST based API +than the Bulk 1.0 API. In some cases I have noticed that the ordering of +the result records will differ from the order of the input data because +the data is batched and processed asynchronously. by Salesforce instead +of R. However, The Bulk 2.0 API returns every single field that was +included in the call so if you have an identifying key your dataset, +then it should not be a problem to join on that key with your original +data to bring in the newly assigned Salesforce Id that is generated when +the record was created in Salesforce. However, I have find it just seems +wasteful to transfer all of the field information back after the query +and have not found a significant performance improvement between the +Bulk 1.0 and Bulk 2.0. Finally, note that the status field names +(“Success”, “Created”, “Error”) are different from the Bulk 2.0 API.

    -n <- 20
    -prefix <- paste0("Bulk-", as.integer(runif(1,1,100000)), "-")
    -new_contacts <- tibble(FirstName = rep("Test", n),
    -                       LastName = paste0("Contact-Create-", 1:n),
    -                       test_number__c = 1:n,
    -                       My_External_Id__c=paste0(prefix, letters[1:n]))
    -
    -created_records_v1 <- sf_create(new_contacts[1:10,], 
    -                                object_name = "Contact", 
    -                                api_type = "Bulk 1.0")
    -created_records_v1
    -#> # A tibble: 10 × 4
    -#>   Id                 Success Created Error
    -#>   <chr>              <lgl>   <lgl>   <lgl>
    -#> 1 0033s00001BXHuCAAX TRUE    TRUE    NA   
    -#> 2 0033s00001BXHuDAAX TRUE    TRUE    NA   
    -#> 3 0033s00001BXHuEAAX TRUE    TRUE    NA   
    -#> 4 0033s00001BXHuFAAX TRUE    TRUE    NA   
    -#> 5 0033s00001BXHuGAAX TRUE    TRUE    NA   
    -#> # … with 5 more rows
    -
    -created_records_v2 <- sf_create(new_contacts[11:20,],
    -                                object_name = "Contact", 
    -                                api_type = "Bulk 2.0")
    -created_records_v2
    -#> # A tibble: 10 × 7
    -#>   sf__Id             sf__Created sf__Error FirstName LastName   My_External_Id_…
    -#>   <chr>              <lgl>       <lgl>     <chr>     <chr>      <chr>           
    -#> 1 0033s00001BXHuMAAX TRUE        NA        Test      Contact-C… Bulk-94075-k    
    -#> 2 0033s00001BXHuNAAX TRUE        NA        Test      Contact-C… Bulk-94075-l    
    -#> 3 0033s00001BXHuOAAX TRUE        NA        Test      Contact-C… Bulk-94075-m    
    -#> 4 0033s00001BXHuPAAX TRUE        NA        Test      Contact-C… Bulk-94075-n    
    -#> 5 0033s00001BXHuQAAX TRUE        NA        Test      Contact-C… Bulk-94075-o    
    -#> # … with 5 more rows, and 1 more variable: test_number__c <dbl>
    +n <- 20 +prefix <- paste0("Bulk-", as.integer(runif(1,1,100000)), "-") +new_contacts <- tibble(FirstName = rep("Test", n), + LastName = paste0("Contact-Create-", 1:n), + test_number__c = 1:n, + My_External_Id__c=paste0(prefix, letters[1:n])) + +created_records_v1 <- sf_create(new_contacts[1:10,], + object_name = "Contact", + api_type = "Bulk 1.0") +created_records_v1 +#> # A tibble: 10 × 4 +#> Id Success Created Error +#> <chr> <lgl> <lgl> <lgl> +#> 1 003Kg000002AaCKIA0 TRUE TRUE NA +#> 2 003Kg000002AaCLIA0 TRUE TRUE NA +#> 3 003Kg000002AaCMIA0 TRUE TRUE NA +#> 4 003Kg000002AaCNIA0 TRUE TRUE NA +#> 5 003Kg000002AaEJIA0 TRUE TRUE NA +#> # ℹ 5 more rows + +created_records_v2 <- sf_create(new_contacts[11:20,], + object_name = "Contact", + api_type = "Bulk 2.0") +created_records_v2 +#> # A tibble: 10 × 7 +#> sf__Id sf__Created sf__Error FirstName LastName My_External_Id__c +#> <chr> <lgl> <lgl> <chr> <chr> <chr> +#> 1 003Kg000002AaETIA0 TRUE NA Test Contact-… Bulk-60076-k +#> 2 003Kg000002AaEUIA0 TRUE NA Test Contact-… Bulk-60076-l +#> 3 003Kg000002AaEVIA0 TRUE NA Test Contact-… Bulk-60076-m +#> 4 003Kg000002AaEWIA0 TRUE NA Test Contact-… Bulk-60076-n +#> 5 003Kg000002AaEXIA0 TRUE NA Test Contact-… Bulk-60076-o +#> # ℹ 5 more rows +#> # ℹ 1 more variable: test_number__c <dbl>

Performance Benchmarks for Bulk Queries

-

Below is a simple performance benchmark between the Bulk 1.0 and Bulk 2.0 APIs for a small query. In general, the Bulk 2.0 should be faster. One potential reason for the implementation in R to be faster is that the entire recordset is parsed at once from a downloaded CSV of the results when using the Bulk 1.0 API. The Bulk 2.0 retrieves the same data in large batches (typically 50,000 records at a time). I would encourage users to experiment to see what works best in their Salesforce Org.

+

Below is a simple performance benchmark between the Bulk 1.0 and Bulk +2.0 APIs for a small query. In general, the Bulk 2.0 should be faster. +One potential reason for the implementation in R to be faster is that +the entire recordset is parsed at once from a downloaded CSV of the +results when using the Bulk 1.0 API. The Bulk 2.0 retrieves the same +data in large batches (typically 50,000 records at a time). I would +encourage users to experiment to see what works best in their Salesforce +Org.

-soql <- "SELECT Id, Name FROM Contact"
-bulk1_query <- function(){sf_query(soql, "Contact", api_type="Bulk 1.0")}
-bulk2_query <- function(){sf_query(soql, api_type="Bulk 2.0")} # Bulk 2.0 doesn't need object name
-
-res <- microbenchmark::microbenchmark(
-  bulk1_query(),
-  bulk2_query(), 
-  times=8, 
-  unit = "s"
-)
-res
-#> Unit: seconds
-#>           expr      min       lq     mean    median        uq      max neval
-#>  bulk1_query() 8.189368 8.670841 10.05203  8.987773  9.573661 17.76234     8
-#>  bulk2_query() 7.033586 8.555408 11.25908 12.549040 13.558243 13.71369     8
-
-suppressWarnings(suppressMessages(
-  ggplot2::autoplot(res) + 
-    ggplot2::scale_y_continuous(name="Time [seconds]", n.breaks=6)
-))
-

+soql <- "SELECT Id, Name FROM Contact" +bulk1_query <- function(){sf_query(soql, "Contact", api_type="Bulk 1.0")} +bulk2_query <- function(){sf_query(soql, api_type="Bulk 2.0")} # Bulk 2.0 doesn't need object name + +res <- microbenchmark::microbenchmark( + bulk1_query(), + bulk2_query(), + times=8, + unit = "s" +) +res +#> Unit: seconds +#> expr min lq mean median uq max neval +#> bulk1_query() 7.772903 7.967777 8.162064 8.044740 8.399207 8.700162 8 +#> bulk2_query() 6.647128 6.668776 6.722084 6.692282 6.737156 6.933114 8 + +suppressWarnings(suppressMessages( + ggplot2::autoplot(res) + + ggplot2::scale_y_continuous(name="Time [seconds]", n.breaks=6) +))
+

A violin plot showing the distribution of latency times for Bulk API and Bulk 2.0 API

- + + + - -
-
-

-

Site built with pkgdown 2.0.2.

-
-
- - - - + diff --git a/docs/articles/working-with-bulk-apis_files/figure-html/unnamed-chunk-7-1.png b/docs/articles/working-with-bulk-apis_files/figure-html/unnamed-chunk-7-1.png index f015a1f0af41862eb214648fcca9d74c449c6a26..5ee1c9dd0631e7319afec8ff0e59e27346389ff1 100644 GIT binary patch literal 40104 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 literal 41338 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 A1^@s6 diff --git a/docs/articles/working-with-metadata.html b/docs/articles/working-with-metadata.html index 1fa06890..7dedcd1a 100644 --- a/docs/articles/working-with-metadata.html +++ b/docs/articles/working-with-metadata.html @@ -4,7 +4,7 @@ - + Working with Metadata • salesforcer @@ -12,20 +12,12 @@ - - - - - - - - - - - + + + + + - - + + Skip to contents -
-
-
- +

If you prefer to be more precise about collecting and formatting the +field data you can work directly with the nested lists that the APIs +return. In this example we look at the picklist values of fields on the +Account object.

-describe_obj_result <- sf_describe_objects(object_names=c('Account', 'Contact'))
-
-# confirm that the Account object is queryable
-describe_obj_result[[1]][c('label', 'queryable')]
-#> $label
-#> [1] "Account"
-#> 
-#> $queryable
-#> [1] "true"
-
-# now show the different picklist values for the Account Type field
-all_fields <- describe_obj_result[[1]][names(describe_obj_result[[1]]) == "fields"]
-
-acct_type_field_idx <- which(sapply(all_fields, 
-                                    FUN=function(x){x$label}) == "Account Type")
-acct_type_field <- all_fields[[acct_type_field_idx]]
-acct_type_field[which(names(acct_type_field) == "picklistValues")] %>% 
-  map_df(as_tibble)
-#> # A tibble: 7 × 4
-#>   active defaultValue label                      value                     
-#>   <chr>  <chr>        <chr>                      <chr>                     
-#> 1 true   false        Prospect                   Prospect                  
-#> 2 true   false        Customer - Direct          Customer - Direct         
-#> 3 true   false        Customer - Channel         Customer - Channel        
-#> 4 true   false        Channel Partner / Reseller Channel Partner / Reseller
-#> 5 true   false        Installation Partner       Installation Partner      
-#> # … with 2 more rows
-

It is recommended that you try out the various metadata functions sf_read_metadata(), sf_list_metadata(), sf_describe_metadata(), and sf_describe_objects() in order to see which information best suits your use case.

+describe_obj_result <- sf_describe_objects(object_names=c('Account', 'Contact')) + +# confirm that the Account object is queryable +describe_obj_result[[1]][c('label', 'queryable')] +#> $label +#> [1] "Account" +#> +#> $queryable +#> [1] "true" + +# now show the different picklist values for the Account Type field +all_fields <- describe_obj_result[[1]][names(describe_obj_result[[1]]) == "fields"] + +acct_type_field_idx <- which(sapply(all_fields, + FUN=function(x){x$label}) == "Account Type") +acct_type_field <- all_fields[[acct_type_field_idx]] +acct_type_field[which(names(acct_type_field) == "picklistValues")] %>% + map_df(as_tibble) +#> # A tibble: 7 × 4 +#> active defaultValue label value +#> <chr> <chr> <chr> <chr> +#> 1 true false Prospect Prospect +#> 2 true false Customer - Direct Customer - Direct +#> 3 true false Customer - Channel Customer - Channel +#> 4 true false Channel Partner / Reseller Channel Partner / Reseller +#> 5 true false Installation Partner Installation Partner +#> # ℹ 2 more rows
+

It is recommended that you try out the various metadata functions +sf_read_metadata(), sf_list_metadata(), +sf_describe_metadata(), and +sf_describe_objects() in order to see which information +best suits your use case.

Modifying Metadata

-

Where the Metadata API really shines is when it comes to CRUD operations on metadata. In this example we will create an object, add fields to it, then delete that object.

+

Where the Metadata API really shines is when it comes to CRUD +operations on metadata. In this example we will create an object, add +fields to it, then delete that object.

-# create an object
-base_obj_name <- "Custom_Account1"
-custom_object <- list()
-custom_object$fullName <- paste0(base_obj_name, "__c")
-custom_object$label <- paste0(gsub("_", " ", base_obj_name))
-custom_object$pluralLabel <- paste0(base_obj_name, "s")
-custom_object$nameField <- list(displayFormat = 'AN-{0000}', 
-                                label = paste0(base_obj_name, ' Number'), 
-                                type = 'AutoNumber')
-custom_object$deploymentStatus <- 'Deployed'
-custom_object$sharingModel <- 'ReadWrite'
-custom_object$enableActivities <- 'true'
-custom_object$description <- paste0(base_obj_name, " created by the Metadata API")
-custom_object_result <- sf_create_metadata(metadata_type = 'CustomObject',
-                                           metadata = custom_object)
-
-# add fields to the object
-custom_fields <- tibble(fullName=c(paste0(base_obj_name, '__c.CustomField3__c'), 
-                                   paste0(base_obj_name, '__c.CustomField4__c')), 
-                        label=c('Test Field3', 'Test Field4'), 
-                        length=c(100, 100), 
-                        type=c('Text', 'Text'))
-create_fields_result <- sf_create_metadata(metadata_type = 'CustomField', 
-                                           metadata = custom_fields)
-
-# delete the object
-delete_obj_result <- sf_delete_metadata(metadata_type = 'CustomObject', 
-                                        object_names = c('Custom_Account1__c'))
-

Note that newly created custom fields are not editable by default, meaning that you will not be able to insert records into them until updating the field level security of your user profile. Run the following code to determine the user profiles in your org and updating the field permissions on an object that you may have created with the example code above.

+# create an object +base_obj_name <- "Custom_Account1" +custom_object <- list() +custom_object$fullName <- paste0(base_obj_name, "__c") +custom_object$label <- paste0(gsub("_", " ", base_obj_name)) +custom_object$pluralLabel <- paste0(base_obj_name, "s") +custom_object$nameField <- list(displayFormat = 'AN-{0000}', + label = paste0(base_obj_name, ' Number'), + type = 'AutoNumber') +custom_object$deploymentStatus <- 'Deployed' +custom_object$sharingModel <- 'ReadWrite' +custom_object$enableActivities <- 'true' +custom_object$description <- paste0(base_obj_name, " created by the Metadata API") +custom_object_result <- sf_create_metadata(metadata_type = 'CustomObject', + metadata = custom_object) + +# add fields to the object +custom_fields <- tibble(fullName=c(paste0(base_obj_name, '__c.CustomField3__c'), + paste0(base_obj_name, '__c.CustomField4__c')), + label=c('Test Field3', 'Test Field4'), + length=c(100, 100), + type=c('Text', 'Text')) +create_fields_result <- sf_create_metadata(metadata_type = 'CustomField', + metadata = custom_fields) + +# delete the object +delete_obj_result <- sf_delete_metadata(metadata_type = 'CustomObject', + object_names = c('Custom_Account1__c'))
+

Note that newly created custom fields are not editable by default, +meaning that you will not be able to insert records into them until +updating the field level security of your user profile. Run the +following code to determine the user profiles in your org and updating +the field permissions on an object that you may have created with the +example code above.

-# get list of user proviles in order to get the "fullName" 
-# parameter correct in the next call
-my_queries <- list(list(type = 'Profile'))
-profiles_list <- sf_list_metadata(queries = my_queries)
+# get list of user proviles in order to get the "fullName" 
+# parameter correct in the next call
+my_queries <- list(list(type = 'Profile'))
+profiles_list <- sf_list_metadata(queries = my_queries)
+
+admin_editable <- list(fullName = 'Admin', 
+                       fieldPermissions=list(field=paste0(custom_object$fullName, 
+                                                          '.CustomField3__c'),
+                                             editable='true'), 
+                       fieldPermissions=list(field=paste0(custom_object$fullName, 
+                                                          '.CustomField4__c'),
+                                             editable='true'))
+
+# update the field level security to "editable" for your fields
+prof_update <- sf_update_metadata(metadata_type = 'Profile', 
+                                  metadata = admin_editable)
+
+# now try inserting values into that custom object's fields
+my_new_data = tibble(CustomField3__c = "Hello World", CustomField4__c = "Hello World")
+added_record <- sf_create(my_new_data, object_name = custom_object$fullName)
+
+ + -admin_editable <- list(fullName = 'Admin', - fieldPermissions=list(field=paste0(custom_object$fullName, - '.CustomField3__c'), - editable='true'), - fieldPermissions=list(field=paste0(custom_object$fullName, - '.CustomField4__c'), - editable='true')) -# update the field level security to "editable" for your fields -prof_update <- sf_update_metadata(metadata_type = 'Profile', - metadata = admin_editable) -# now try inserting values into that custom object's fields -my_new_data = tibble(CustomField3__c = "Hello World", CustomField4__c = "Hello World") -added_record <- sf_create(my_new_data, object_name = custom_object$fullName) +
- - -
-
-
-

-

Site built with pkgdown 2.0.2.

-
-
- - - - - + diff --git a/docs/articles/working-with-reports.html b/docs/articles/working-with-reports.html index e9054056..35fd1eab 100644 --- a/docs/articles/working-with-reports.html +++ b/docs/articles/working-with-reports.html @@ -4,7 +4,7 @@ - + Working with Reports • salesforcer @@ -12,20 +12,12 @@ - - - - - - - - - - - + + + + + - - + + Skip to contents -
-
-
-

Filtering a Report on the Fly

-

The neat thing about using the API is that you can retrieve the results of a report with different filters applied. This allows you to obtain exactly the results needed without having to create separate copies of the same report. It takes some basic understanding of how report operators need to be supplied to the API, but it is not too difficult. Below is an example that only includes contact records created prior to this month and belong to an Account with a non-NULL Billing City.

+

The neat thing about using the API is that you can retrieve the +results of a report with different filters applied. This allows you to +obtain exactly the results needed without having to create separate +copies of the same report. It takes some basic understanding of how +report operators need to be supplied to the API, but it is not too +difficult. Below is an example that only includes contact records +created prior to this month and belong to an Account with a non-NULL +Billing City.

-# filter records that was created before this month
-filter1 <- list(column = "CREATED_DATE",
-                        operator = "lessThan", 
-                        value = "THIS_MONTH")
-
-# filter records where the account billing address city is not empty
-filter2 <-  list(column = "ACCOUNT.ADDRESS1_CITY",
-                        operator = "notEqual", 
-                        value = "")
-
-# combine filter1 and filter2 using 'AND' which means that records must meet both filters
-results_using_AND <- sf_run_report(my_report_id, 
-                                   report_boolean_logic = "1 AND 2",
-                                   report_filters = list(filter1, filter2))
-results_using_AND
-#> # A tibble: 14 × 8
-#>   `Contact ID`    `First Name` `test number` `Contact Owner` `Account ID`   
-#>   <chr>           <chr>                <dbl> <chr>           <chr>          
-#> 1 0036A000002C6Mm Edna                    NA Steven Mortimer 0016A0000035mJE
-#> 2 0036A000002C6Mk Tom                     NA Steven Mortimer 0016A0000035mJD
-#> 3 0036A000002C6Ml Liz                     NA Steven Mortimer 0016A0000035mJD
-#> 4 0036A000002C6MW Rose                    NA Steven Mortimer 0016A0000035mJ4
-#> 5 0036A000002C6MX Sean                    NA Steven Mortimer 0016A0000035mJ4
-#> # … with 9 more rows, and 3 more variables: `Account Name` <chr>,
-#> #   `Billing City` <chr>, `Account Owner` <chr>
-

This second example shows how to return only the Top N number of records and combine the filter using the logical “OR” instead of “AND”.

+# filter records that was created before this month +filter1 <- list(column = "CREATED_DATE", + operator = "lessThan", + value = "THIS_MONTH") + +# filter records where the account billing address city is not empty +filter2 <- list(column = "ACCOUNT.ADDRESS1_CITY", + operator = "notEqual", + value = "") + +# combine filter1 and filter2 using 'AND' which means that records must meet both filters +results_using_AND <- sf_run_report(my_report_id, + report_boolean_logic = "1 AND 2", + report_filters = list(filter1, filter2)) +results_using_AND +#> # A tibble: 14 × 8 +#> `Contact ID` `First Name` `test number` `Contact Owner` `Account ID` +#> <chr> <chr> <dbl> <chr> <chr> +#> 1 0036A000002C6MW Rose NA Steven Mortimer 0016A0000035mJ4 +#> 2 0036A000002C6MX Sean NA Steven Mortimer 0016A0000035mJ4 +#> 3 0036A000002C6MY Jack 99 Steven Mortimer 0016A0000035mJ5 +#> 4 0036A000002C6Mb Tim NA Steven Mortimer 0016A0000035mJ8 +#> 5 0036A000002C6Mc John 23 Steven Mortimer 0016A0000035mJ8 +#> # ℹ 9 more rows +#> # ℹ 3 more variables: `Account Name` <chr>, `Billing City` <chr>, +#> # `Account Owner` <chr>
+

This second example shows how to return only the Top N number of +records and combine the filter using the logical “OR” instead of +“AND”.

-# combine filter1 and filter2 using 'OR' which means that records must meet one 
-# of the filters but also throw in a row limit based on a specific sort order
-results_using_OR <- sf_run_report(my_report_id, 
-                                  report_boolean_logic = "1 OR 2",
-                                  report_filters = list(filter1, filter2), 
-                                  sort_by = "Contact.test_number__c", 
-                                  decreasing = TRUE, 
-                                  top_n = 5)
-results_using_OR
-#> # A tibble: 5 × 8
-#>   `Contact ID`    `First Name` `test number` `Contact Owner` `Account ID`   
-#>   <chr>           <chr>                <dbl> <chr>           <chr>          
-#> 1 0033s000012Nl0r KEEP                  1000 Steven Mortimer 0013s00000zFdug
-#> 2 0033s000012Nl0s KEEP                  1000 Steven Mortimer 0013s00000zFdug
-#> 3 0033s000012Nl0v KEEP                  1000 Steven Mortimer 0013s00000zFdug
-#> 4 0033s000012Nl0u KEEP                  1000 Steven Mortimer 0013s00000zFdug
-#> 5 0033s000012Nl0t KEEP                  1000 Steven Mortimer 0013s00000zFdug
-#> # … with 3 more variables: `Account Name` <chr>, `Billing City` <chr>,
-#> #   `Account Owner` <chr>
-

I was able to determine some of the potential ways to filter by first reviewing the reportFilters element in the existing report metadata and also reviewing the list of report filter operators.

-

First, you can always take the report filter specification from the report metadata and tailor it to your needs. Below is an example showing how to get that metadata for our report. You can select specific elements to better understand the structure of the report.

+# combine filter1 and filter2 using 'OR' which means that records must meet one +# of the filters but also throw in a row limit based on a specific sort order +results_using_OR <- sf_run_report(my_report_id, + report_boolean_logic = "1 OR 2", + report_filters = list(filter1, filter2), + sort_by = "Contact.test_number__c", + decreasing = TRUE, + top_n = 5) +results_using_OR +#> # A tibble: 5 × 8 +#> `Contact ID` `First Name` `test number` `Contact Owner` `Account ID` +#> <chr> <chr> <dbl> <chr> <chr> +#> 1 0033s00001CWqsU Test 1000 Steven Mortimer 0013s00001Aizxg +#> 2 0033s00001CWqsV Test 1000 Steven Mortimer 0013s00001Aizxg +#> 3 0033s00001CWqt7 Test 1000 Steven Mortimer 0013s00001Aizxg +#> 4 0033s00001CWqt6 Test 1000 Steven Mortimer 0013s00001Aizxg +#> 5 0033s00001CWqsW Test 1000 Steven Mortimer 0013s00001Aizxg +#> # ℹ 3 more variables: `Account Name` <chr>, `Billing City` <chr>, +#> # `Account Owner` <chr>
+

I was able to determine some of the potential ways to filter by first +reviewing the reportFilters element in the existing report +metadata and also reviewing the list of report filter operators.

+

First, you can always take the report filter specification from the +report metadata and tailor it to your needs. Below is an example showing +how to get that metadata for our report. You can select specific +elements to better understand the structure of the report.

-report_details <- sf_describe_report(my_report_id)
-report_details$reportMetadata$reportType$type
-#> [1] "ContactList"
-report_details$reportMetadata$reportFilters
-#> [[1]]
-#> [[1]]$column
-#> [1] "CREATED_DATE"
-#> 
-#> [[1]]$filterType
-#> [1] "fieldValue"
-#> 
-#> [[1]]$isRunPageEditable
-#> [1] TRUE
-#> 
-#> [[1]]$operator
-#> [1] "lessThan"
-#> 
-#> [[1]]$value
-#> [1] "2019-07-19T04:00:00Z"
-#> 
-#> 
-#> [[2]]
-#> [[2]]$column
-#> [1] "ACCOUNT.ADDRESS1_CITY"
-#> 
-#> [[2]]$filterType
-#> [1] "fieldValue"
-#> 
-#> [[2]]$isRunPageEditable
-#> [1] TRUE
-#> 
-#> [[2]]$operator
-#> [1] "notEqual"
-#> 
-#> [[2]]$value
-#> [1] ""
-

Second, Salesforce has a few API endpoints that tell you the fields on the report or the report type, more generally, and all the ways you can declare a filter on a particular field type and. The reportTypeMetadata element returned on the report description also has detailed information on how to filter the report. For example, it already contains the start and end dates that would be applied when using the “LAST_MONTH” filter value on a date field.

+report_details <- sf_describe_report(my_report_id) +report_details$reportMetadata$reportType$type +#> [1] "ContactList" +report_details$reportMetadata$reportFilters +#> [[1]] +#> [[1]]$column +#> [1] "CREATED_DATE" +#> +#> [[1]]$filterType +#> [1] "fieldValue" +#> +#> [[1]]$isRunPageEditable +#> [1] TRUE +#> +#> [[1]]$operator +#> [1] "lessThan" +#> +#> [[1]]$value +#> [1] "2019-07-19T04:00:00Z" +#> +#> +#> [[2]] +#> [[2]]$column +#> [1] "ACCOUNT.ADDRESS1_CITY" +#> +#> [[2]]$filterType +#> [1] "fieldValue" +#> +#> [[2]]$isRunPageEditable +#> [1] TRUE +#> +#> [[2]]$operator +#> [1] "notEqual" +#> +#> [[2]]$value +#> [1] ""
+

Second, Salesforce has a few API endpoints that tell you the fields +on the report or the report type, more generally, and all the ways you +can declare a filter on a particular field type and. The +reportTypeMetadata element returned on the report +description also has detailed information on how to filter the report. +For example, it already contains the start and end dates that would be +applied when using the “LAST_MONTH” filter value on a date field.

-report_details$reportTypeMetadata$standardDateFilterDurationGroups[[6]]$standardDateFilterDurations[[1]]
-#> $endDate
-#> [1] "2022-01-31"
-#> 
-#> $label
-#> [1] "Last Month"
-#> 
-#> $startDate
-#> [1] "2022-01-01"
-#> 
-#> $value
-#> [1] "LAST_MONTH"
-

Digging into the metadata of the report will allow you to better understand what filters you can set when filtering on the fly. In the example below you should notice that the field names on the report do not match the names of the typical API field names for the object, so please review carefully the fields on the report. For example, the CREATED_DATE report field is based on the CreatedDate object field.

+report_details$reportTypeMetadata$standardDateFilterDurationGroups[[6]]$standardDateFilterDurations[[1]] +#> $endDate +#> [1] "2024-10-31" +#> +#> $label +#> [1] "Last Month" +#> +#> $startDate +#> [1] "2024-10-01" +#> +#> $value +#> [1] "LAST_MONTH"
+

Digging into the metadata of the report will allow you to better +understand what filters you can set when filtering on the fly. In the +example below you should notice that the field names on the report do +not match the names of the typical API field names for the object, so +please review carefully the fields on the report. For example, the +CREATED_DATE report field is based on the +CreatedDate object field.

-# report fields
-report_fields <- sf_list_report_fields(my_report_id)
-head(names(report_fields$equivalentFieldIndices))
-#> [1] "CONTACT_CREATED_ALIAS"     "Contact.My_External_Id__c"
-#> [3] "IS_EMAIL_BOUNCED"          "REPORTS_TO"               
-#> [5] "ADDRESS2_ZIP"              "LAST_ACTIVITY"
-
-report_filters <- sf_list_report_filter_operators()
-unique_supported_fields <- report_filters %>% 
-  distinct(supported_field_type) %>% 
-  unlist()
-unique_supported_fields
-#>        supported_field_type1        supported_field_type2 
-#>                       "date"                    "address" 
-#>        supported_field_type3        supported_field_type4 
-#>                     "string"                     "double" 
-#>        supported_field_type5        supported_field_type6 
-#>                   "picklist"                   "textarea" 
-#>        supported_field_type7        supported_field_type8 
-#>            "encryptedstring"                    "percent" 
-#>        supported_field_type9       supported_field_type10 
-#>                        "int"                        "url" 
-#>       supported_field_type11       supported_field_type12 
-#>                  "reference"                   "datetime" 
-#>       supported_field_type13       supported_field_type14 
-#>                    "boolean"                      "phone" 
-#>       supported_field_type15       supported_field_type16 
-#> "datacategorygroupreference"                   "currency" 
-#>       supported_field_type17       supported_field_type18 
-#>                   "location"                       "html" 
-#>       supported_field_type19       supported_field_type20 
-#>                         "id"                       "time" 
-#>       supported_field_type21       supported_field_type22 
-#>                      "email"              "multipicklist"
-
-# operators to filter a picklist field
-picklist_field_operators <- report_filters %>% 
-  filter(supported_field_type == "picklist")
-picklist_field_operators
-#> # A tibble: 9 × 3
-#>   supported_field_type label         name       
-#>   <chr>                <chr>         <chr>      
-#> 1 picklist             equals        equals     
-#> 2 picklist             not equal to  notEqual   
-#> 3 picklist             less than     lessThan   
-#> 4 picklist             greater than  greaterThan
-#> 5 picklist             less or equal lessOrEqual
-#> # … with 4 more rows
+# report fields +report_fields <- sf_list_report_fields(my_report_id) +head(names(report_fields$equivalentFieldIndices)) +#> [1] "CONTACT_CREATED_ALIAS" "Contact.My_External_Id__c" +#> [3] "IS_EMAIL_BOUNCED" "REPORTS_TO" +#> [5] "ADDRESS2_ZIP" "LAST_ACTIVITY" + +report_filters <- sf_list_report_filter_operators() +unique_supported_fields <- report_filters %>% + distinct(supported_field_type) %>% + unlist() +unique_supported_fields +#> supported_field_type1 supported_field_type2 +#> "date" "address" +#> supported_field_type3 supported_field_type4 +#> "string" "double" +#> supported_field_type5 supported_field_type6 +#> "picklist" "textarea" +#> supported_field_type7 supported_field_type8 +#> "encryptedstring" "percent" +#> supported_field_type9 supported_field_type10 +#> "int" "url" +#> supported_field_type11 supported_field_type12 +#> "reference" "datetime" +#> supported_field_type13 supported_field_type14 +#> "boolean" "phone" +#> supported_field_type15 supported_field_type16 +#> "datacategorygroupreference" "currency" +#> supported_field_type17 supported_field_type18 +#> "location" "html" +#> supported_field_type19 supported_field_type20 +#> "id" "time" +#> supported_field_type21 supported_field_type22 +#> "email" "multipicklist" + +# operators to filter a picklist field +picklist_field_operators <- report_filters %>% + filter(supported_field_type == "picklist") +picklist_field_operators +#> # A tibble: 9 × 3 +#> supported_field_type label name +#> <chr> <chr> <chr> +#> 1 picklist equals equals +#> 2 picklist not equal to notEqual +#> 3 picklist less than lessThan +#> 4 picklist greater than greaterThan +#> 5 picklist less or equal lessOrEqual +#> # ℹ 4 more rows

Managing your Reports

-

The API also allows you to perform many admin functions like creating, copying, updating, or deleting reports and report instances. Take advantage of these functions as needed to keep your Org’s report list well-maintained. Below is a simple flow of creating, updating, and deleting a single report, but the amount you’re able to customize is completely up to you. The {salesforcer} package should support any operation that the Reports and Dashboards REST API supports.

+

The API also allows you to perform many admin functions like +creating, copying, updating, or deleting reports and report instances. +Take advantage of these functions as needed to keep your Org’s report +list well-maintained. Below is a simple flow of creating, updating, and +deleting a single report, but the amount you’re able to customize is +completely up to you. The {salesforcer} package should support any +operation that the Reports and Dashboards REST API supports.

-# first, grab all possible reports in your Org
-all_reports <- sf_query("SELECT Id, Name FROM Report")
-
-# second, get the id of the report to update
-this_report_id <- all_reports$Id[1]
-
-new_report <- sf_copy_report(this_report_id)
-#> Naming the new report: 'ReportName - Copy'
-
-# third, update the report (2 ways shown)
-my_updated_report <- sf_update_report(new_report$reportMetadata$id,
-                                      report_metadata =
-                                        list(reportMetadata =
-                                          list(name = "Updated Name!")))
-my_updated_report$reportMetadata$name
-#> [1] "Updated Name!"
-
-# alternatively, pull down its metadata and update the name
-report_details <- sf_describe_report(new_report$reportMetadata$id)
-report_details$reportMetadata$name <- paste0(report_details$reportMetadata$name,
-                                             " - UPDATED AGAIN!")
-
-# update the report by passing the metadata
-my_updated_report <- sf_update_report(new_report$reportMetadata$id,
-                                      report_metadata = report_details)
-my_updated_report$reportMetadata$name
-#> [1] "Updated Name! - UPDATED AGAIN!"
-
-# fourth, delete that report using its Id
-success <- sf_delete_report(new_report$reportMetadata$id)
-success
-#> [1] TRUE
+# first, grab all possible reports in your Org +all_reports <- sf_query("SELECT Id, Name FROM Report") + +# second, get the id of the report to update +this_report_id <- all_reports$Id[1] + +new_report <- sf_copy_report(this_report_id) +#> Naming the new report: 'ReportName - Copy' + +# third, update the report (2 ways shown) +my_updated_report <- sf_update_report(new_report$reportMetadata$id, + report_metadata = + list(reportMetadata = + list(name = "Updated Name!"))) +my_updated_report$reportMetadata$name +#> [1] "Updated Name!" + +# alternatively, pull down its metadata and update the name +report_details <- sf_describe_report(new_report$reportMetadata$id) +report_details$reportMetadata$name <- paste0(report_details$reportMetadata$name, + " - UPDATED AGAIN!") + +# update the report by passing the metadata +my_updated_report <- sf_update_report(new_report$reportMetadata$id, + report_metadata = report_details) +my_updated_report$reportMetadata$name +#> [1] "Updated Name! - UPDATED AGAIN!" + +# fourth, delete that report using its Id +success <- sf_delete_report(new_report$reportMetadata$id) +success +#> [1] TRUE

Troubleshooting

-

If you are having an issue with a report please submit in the {salesforcer} GitHub repository at: https://github.com/StevenMMortimer/salesforcer/issues. As a maintainer, reports can be are tough to debug because every Salesforce Org is unique. When filing your issue please make an attempt to understand the query and debug a little bit on your own. Here are a few suggestions:

+

If you are having an issue with a report please submit in the +{salesforcer} GitHub repository at: +https://github.com/StevenMMortimer/salesforcer/issues. +As a maintainer, reports can be are tough to debug because every +Salesforce Org is unique. When filing your issue please make an attempt +to understand the query and debug a little bit on your own. Here are a +few suggestions:

  1. -

    Slightly modify your function call to sf_run_report() to observe the results. Here are a few prompting questions that may assist you:

    +

    Slightly modify your function call to +sf_run_report() to observe the results. Here are a few +prompting questions that may assist you:

      -
    • What do you see when you set verbose=TRUE argument?

    • -
    • What happens if you run sync. vs. async. (e.g. async=TRUE vs. FALSE)?

    • -
    • What happens if you try running a different type of report?

    • +
    • What do you see when you set verbose=TRUE +argument?

    • +
    • What happens if you run sync. vs. async. (e.g. async=TRUE +vs. FALSE)?

    • +
    • What happens if you try running a different type of +report?

  2. -
  3. Double check Salesforce’s Reports and Dashboards REST API Developer Guide to see whether if your report type would be supported or limited in some way.

  4. -
  5. Review report unit tests at: https://github.com/StevenMMortimer/salesforcer/blob/main/tests/testthat/test-report.R. These unit tests were written to cover a variety of use cases and to track any changes made between newly released versions of the Salesforce API (typically 4 each year). These tests are an excellent source of examples that may be helpful in troubleshooting your own case.

  6. +
  7. Double check Salesforce’s +Reports +and Dashboards REST API Developer Guide to see whether if your +report type would be supported or limited in some way.

  8. +
  9. Review report unit tests at: +https://github.com/StevenMMortimer/salesforcer/blob/main/tests/testthat/test-report.R. +These unit tests were written to cover a variety of use cases and to +track any changes made between newly released versions of the Salesforce +API (typically 4 each year). These tests are an excellent source of +examples that may be helpful in troubleshooting your own case.

  10. -

    Roll up your sleeves and dive into the source code for the {salesforcer} package. The main scripts to review are:

    +

    Roll up your sleeves and dive into the source code for the +{salesforcer} package. The main scripts to review are:

- + + - -
-
-

-

Site built with pkgdown 2.0.2.

-
-
- - - - + diff --git a/docs/authors.html b/docs/authors.html index d1cf2a70..2b5006bc 100644 --- a/docs/authors.html +++ b/docs/authors.html @@ -1,173 +1,105 @@ -Authors and Citation • salesforcer - - -
-
-
-
- - - + + +
+ + +
+
+
+
+ +
+

Authors

+
  • -

    Steven M. Mortimer. Author, maintainer. +

    Steven M. Mortimer. Author, maintainer.

  • -

    Takekatsu Hiramura. Contributor. +

    Takekatsu Hiramura. Contributor.

  • -

    Jennifer Bryan. Contributor, copyright holder. +

    Jennifer Bryan. Contributor, copyright holder.

  • -

    Joanna Zhao. Contributor, copyright holder. +

    Joanna Zhao. Contributor, copyright holder.

-
-
-

Citation

- Source: DESCRIPTION -
-
+
+

Citation

+

Source: DESCRIPTION

-

Mortimer SM (2022). +

Mortimer SM (2024). salesforcer: An Implementation of 'Salesforce' APIs Using Tidy Principles. -R package version 1.0.1, https://github.com/StevenMMortimer/salesforcer. +R package version 1.0.2, https://stevenmmortimer.github.io/salesforcer/, https://github.com/StevenMMortimer/salesforcer.

-
@Manual{,
+      
@Manual{,
   title = {salesforcer: An Implementation of 'Salesforce' APIs Using Tidy Principles},
   author = {Steven M. Mortimer},
-  year = {2022},
-  note = {R package version 1.0.1},
+  year = {2024},
+  note = {R package version 1.0.2, https://stevenmmortimer.github.io/salesforcer/},
   url = {https://github.com/StevenMMortimer/salesforcer},
 }
+
-
+
+ +
+ + +
+ -
-
-

Site built with pkgdown 2.0.2.

-
-
- - - + diff --git a/docs/deps/bootstrap-5.3.1/bootstrap.bundle.min.js b/docs/deps/bootstrap-5.3.1/bootstrap.bundle.min.js new file mode 100644 index 00000000..e8f21f70 --- /dev/null +++ b/docs/deps/bootstrap-5.3.1/bootstrap.bundle.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v5.3.1 (https://getbootstrap.com/) + * Copyright 2011-2023 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e()}(this,(function(){"use strict";const t=new Map,e={set(e,i,n){t.has(e)||t.set(e,new Map);const s=t.get(e);s.has(i)||0===s.size?s.set(i,n):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(s.keys())[0]}.`)},get:(e,i)=>t.has(e)&&t.get(e).get(i)||null,remove(e,i){if(!t.has(e))return;const n=t.get(e);n.delete(i),0===n.size&&t.delete(e)}},i="transitionend",n=t=>(t&&window.CSS&&window.CSS.escape&&(t=t.replace(/#([^\s"#']+)/g,((t,e)=>`#${CSS.escape(e)}`))),t),s=t=>{t.dispatchEvent(new Event(i))},o=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),r=t=>o(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(n(t)):null,a=t=>{if(!o(t)||0===t.getClientRects().length)return!1;const e="visible"===getComputedStyle(t).getPropertyValue("visibility"),i=t.closest("details:not([open])");if(!i)return e;if(i!==t){const e=t.closest("summary");if(e&&e.parentNode!==i)return!1;if(null===e)return!1}return e},l=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),c=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?c(t.parentNode):null},h=()=>{},d=t=>{t.offsetHeight},u=()=>window.jQuery&&!document.body.hasAttribute("data-bs-no-jquery")?window.jQuery:null,f=[],p=()=>"rtl"===document.documentElement.dir,m=t=>{var e;e=()=>{const e=u();if(e){const i=t.NAME,n=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=n,t.jQueryInterface)}},"loading"===document.readyState?(f.length||document.addEventListener("DOMContentLoaded",(()=>{for(const t of f)t()})),f.push(e)):e()},g=(t,e=[],i=t)=>"function"==typeof t?t(...e):i,_=(t,e,n=!0)=>{if(!n)return void g(t);const o=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(e)+5;let r=!1;const a=({target:n})=>{n===e&&(r=!0,e.removeEventListener(i,a),g(t))};e.addEventListener(i,a),setTimeout((()=>{r||s(e)}),o)},b=(t,e,i,n)=>{const s=t.length;let o=t.indexOf(e);return-1===o?!i&&n?t[s-1]:t[0]:(o+=i?1:-1,n&&(o=(o+s)%s),t[Math.max(0,Math.min(o,s-1))])},v=/[^.]*(?=\..*)\.|.*/,y=/\..*/,w=/::\d+$/,A={};let E=1;const T={mouseenter:"mouseover",mouseleave:"mouseout"},C=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function O(t,e){return e&&`${e}::${E++}`||t.uidEvent||E++}function x(t){const e=O(t);return t.uidEvent=e,A[e]=A[e]||{},A[e]}function k(t,e,i=null){return Object.values(t).find((t=>t.callable===e&&t.delegationSelector===i))}function L(t,e,i){const n="string"==typeof e,s=n?i:e||i;let o=I(t);return C.has(o)||(o=t),[n,s,o]}function S(t,e,i,n,s){if("string"!=typeof e||!t)return;let[o,r,a]=L(e,i,n);if(e in T){const t=t=>function(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};r=t(r)}const l=x(t),c=l[a]||(l[a]={}),h=k(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const d=O(r,e.replace(v,"")),u=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parentNode)for(const a of o)if(a===r)return P(s,{delegateTarget:r}),n.oneOff&&N.off(t,s.type,e,i),i.apply(r,[s])}}(t,i,r):function(t,e){return function i(n){return P(n,{delegateTarget:t}),i.oneOff&&N.off(t,n.type,e),e.apply(t,[n])}}(t,r);u.delegationSelector=o?i:null,u.callable=r,u.oneOff=s,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function D(t,e,i,n,s){const o=k(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function $(t,e,i,n){const s=e[i]||{};for(const[o,r]of Object.entries(s))o.includes(n)&&D(t,e,i,r.callable,r.delegationSelector)}function I(t){return t=t.replace(y,""),T[t]||t}const N={on(t,e,i,n){S(t,e,i,n,!1)},one(t,e,i,n){S(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=L(e,i,n),a=r!==e,l=x(t),c=l[r]||{},h=e.startsWith(".");if(void 0===o){if(h)for(const i of Object.keys(l))$(t,l,i,e.slice(1));for(const[i,n]of Object.entries(c)){const s=i.replace(w,"");a&&!e.includes(s)||D(t,l,r,n.callable,n.delegationSelector)}}else{if(!Object.keys(c).length)return;D(t,l,r,o,s?i:null)}},trigger(t,e,i){if("string"!=typeof e||!t)return null;const n=u();let s=null,o=!0,r=!0,a=!1;e!==I(e)&&n&&(s=n.Event(e,i),n(t).trigger(s),o=!s.isPropagationStopped(),r=!s.isImmediatePropagationStopped(),a=s.isDefaultPrevented());const l=P(new Event(e,{bubbles:o,cancelable:!0}),i);return a&&l.preventDefault(),r&&t.dispatchEvent(l),l.defaultPrevented&&s&&s.preventDefault(),l}};function P(t,e={}){for(const[i,n]of Object.entries(e))try{t[i]=n}catch(e){Object.defineProperty(t,i,{configurable:!0,get:()=>n})}return t}function M(t){if("true"===t)return!0;if("false"===t)return!1;if(t===Number(t).toString())return Number(t);if(""===t||"null"===t)return null;if("string"!=typeof t)return t;try{return JSON.parse(decodeURIComponent(t))}catch(e){return t}}function j(t){return t.replace(/[A-Z]/g,(t=>`-${t.toLowerCase()}`))}const F={setDataAttribute(t,e,i){t.setAttribute(`data-bs-${j(e)}`,i)},removeDataAttribute(t,e){t.removeAttribute(`data-bs-${j(e)}`)},getDataAttributes(t){if(!t)return{};const e={},i=Object.keys(t.dataset).filter((t=>t.startsWith("bs")&&!t.startsWith("bsConfig")));for(const n of i){let i=n.replace(/^bs/,"");i=i.charAt(0).toLowerCase()+i.slice(1,i.length),e[i]=M(t.dataset[n])}return e},getDataAttribute:(t,e)=>M(t.getAttribute(`data-bs-${j(e)}`))};class H{static get Default(){return{}}static get DefaultType(){return{}}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}_getConfig(t){return t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t}_mergeConfigObj(t,e){const i=o(e)?F.getDataAttribute(e,"config"):{};return{...this.constructor.Default,..."object"==typeof i?i:{},...o(e)?F.getDataAttributes(e):{},..."object"==typeof t?t:{}}}_typeCheckConfig(t,e=this.constructor.DefaultType){for(const[n,s]of Object.entries(e)){const e=t[n],r=o(e)?"element":null==(i=e)?`${i}`:Object.prototype.toString.call(i).match(/\s([a-z]+)/i)[1].toLowerCase();if(!new RegExp(s).test(r))throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option "${n}" provided type "${r}" but expected type "${s}".`)}var i}}class W extends H{constructor(t,i){super(),(t=r(t))&&(this._element=t,this._config=this._getConfig(i),e.set(this._element,this.constructor.DATA_KEY,this))}dispose(){e.remove(this._element,this.constructor.DATA_KEY),N.off(this._element,this.constructor.EVENT_KEY);for(const t of Object.getOwnPropertyNames(this))this[t]=null}_queueCallback(t,e,i=!0){_(t,e,i)}_getConfig(t){return t=this._mergeConfigObj(t,this._element),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}static getInstance(t){return e.get(r(t),this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.3.1"}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}static eventName(t){return`${t}${this.EVENT_KEY}`}}const B=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i=`#${i.split("#")[1]}`),e=i&&"#"!==i?i.trim():null}return n(e)},z={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter((t=>t.matches(e))),parents(t,e){const i=[];let n=t.parentNode.closest(e);for(;n;)i.push(n),n=n.parentNode.closest(e);return i},prev(t,e){let i=t.previousElementSibling;for(;i;){if(i.matches(e))return[i];i=i.previousElementSibling}return[]},next(t,e){let i=t.nextElementSibling;for(;i;){if(i.matches(e))return[i];i=i.nextElementSibling}return[]},focusableChildren(t){const e=["a","button","input","textarea","select","details","[tabindex]",'[contenteditable="true"]'].map((t=>`${t}:not([tabindex^="-"])`)).join(",");return this.find(e,t).filter((t=>!l(t)&&a(t)))},getSelectorFromElement(t){const e=B(t);return e&&z.findOne(e)?e:null},getElementFromSelector(t){const e=B(t);return e?z.findOne(e):null},getMultipleElementsFromSelector(t){const e=B(t);return e?z.find(e):[]}},R=(t,e="hide")=>{const i=`click.dismiss${t.EVENT_KEY}`,n=t.NAME;N.on(document,i,`[data-bs-dismiss="${n}"]`,(function(i){if(["A","AREA"].includes(this.tagName)&&i.preventDefault(),l(this))return;const s=z.getElementFromSelector(this)||this.closest(`.${n}`);t.getOrCreateInstance(s)[e]()}))},q=".bs.alert",V=`close${q}`,K=`closed${q}`;class Q extends W{static get NAME(){return"alert"}close(){if(N.trigger(this._element,V).defaultPrevented)return;this._element.classList.remove("show");const t=this._element.classList.contains("fade");this._queueCallback((()=>this._destroyElement()),this._element,t)}_destroyElement(){this._element.remove(),N.trigger(this._element,K),this.dispose()}static jQueryInterface(t){return this.each((function(){const e=Q.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}R(Q,"close"),m(Q);const X='[data-bs-toggle="button"]';class Y extends W{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each((function(){const e=Y.getOrCreateInstance(this);"toggle"===t&&e[t]()}))}}N.on(document,"click.bs.button.data-api",X,(t=>{t.preventDefault();const e=t.target.closest(X);Y.getOrCreateInstance(e).toggle()})),m(Y);const U=".bs.swipe",G=`touchstart${U}`,J=`touchmove${U}`,Z=`touchend${U}`,tt=`pointerdown${U}`,et=`pointerup${U}`,it={endCallback:null,leftCallback:null,rightCallback:null},nt={endCallback:"(function|null)",leftCallback:"(function|null)",rightCallback:"(function|null)"};class st extends H{constructor(t,e){super(),this._element=t,t&&st.isSupported()&&(this._config=this._getConfig(e),this._deltaX=0,this._supportPointerEvents=Boolean(window.PointerEvent),this._initEvents())}static get Default(){return it}static get DefaultType(){return nt}static get NAME(){return"swipe"}dispose(){N.off(this._element,U)}_start(t){this._supportPointerEvents?this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX):this._deltaX=t.touches[0].clientX}_end(t){this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX-this._deltaX),this._handleSwipe(),g(this._config.endCallback)}_move(t){this._deltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this._deltaX}_handleSwipe(){const t=Math.abs(this._deltaX);if(t<=40)return;const e=t/this._deltaX;this._deltaX=0,e&&g(e>0?this._config.rightCallback:this._config.leftCallback)}_initEvents(){this._supportPointerEvents?(N.on(this._element,tt,(t=>this._start(t))),N.on(this._element,et,(t=>this._end(t))),this._element.classList.add("pointer-event")):(N.on(this._element,G,(t=>this._start(t))),N.on(this._element,J,(t=>this._move(t))),N.on(this._element,Z,(t=>this._end(t))))}_eventIsPointerPenTouch(t){return this._supportPointerEvents&&("pen"===t.pointerType||"touch"===t.pointerType)}static isSupported(){return"ontouchstart"in document.documentElement||navigator.maxTouchPoints>0}}const ot=".bs.carousel",rt=".data-api",at="next",lt="prev",ct="left",ht="right",dt=`slide${ot}`,ut=`slid${ot}`,ft=`keydown${ot}`,pt=`mouseenter${ot}`,mt=`mouseleave${ot}`,gt=`dragstart${ot}`,_t=`load${ot}${rt}`,bt=`click${ot}${rt}`,vt="carousel",yt="active",wt=".active",At=".carousel-item",Et=wt+At,Tt={ArrowLeft:ht,ArrowRight:ct},Ct={interval:5e3,keyboard:!0,pause:"hover",ride:!1,touch:!0,wrap:!0},Ot={interval:"(number|boolean)",keyboard:"boolean",pause:"(string|boolean)",ride:"(boolean|string)",touch:"boolean",wrap:"boolean"};class xt extends W{constructor(t,e){super(t,e),this._interval=null,this._activeElement=null,this._isSliding=!1,this.touchTimeout=null,this._swipeHelper=null,this._indicatorsElement=z.findOne(".carousel-indicators",this._element),this._addEventListeners(),this._config.ride===vt&&this.cycle()}static get Default(){return Ct}static get DefaultType(){return Ot}static get NAME(){return"carousel"}next(){this._slide(at)}nextWhenVisible(){!document.hidden&&a(this._element)&&this.next()}prev(){this._slide(lt)}pause(){this._isSliding&&s(this._element),this._clearInterval()}cycle(){this._clearInterval(),this._updateInterval(),this._interval=setInterval((()=>this.nextWhenVisible()),this._config.interval)}_maybeEnableCycle(){this._config.ride&&(this._isSliding?N.one(this._element,ut,(()=>this.cycle())):this.cycle())}to(t){const e=this._getItems();if(t>e.length-1||t<0)return;if(this._isSliding)return void N.one(this._element,ut,(()=>this.to(t)));const i=this._getItemIndex(this._getActive());if(i===t)return;const n=t>i?at:lt;this._slide(n,e[t])}dispose(){this._swipeHelper&&this._swipeHelper.dispose(),super.dispose()}_configAfterMerge(t){return t.defaultInterval=t.interval,t}_addEventListeners(){this._config.keyboard&&N.on(this._element,ft,(t=>this._keydown(t))),"hover"===this._config.pause&&(N.on(this._element,pt,(()=>this.pause())),N.on(this._element,mt,(()=>this._maybeEnableCycle()))),this._config.touch&&st.isSupported()&&this._addTouchEventListeners()}_addTouchEventListeners(){for(const t of z.find(".carousel-item img",this._element))N.on(t,gt,(t=>t.preventDefault()));const t={leftCallback:()=>this._slide(this._directionToOrder(ct)),rightCallback:()=>this._slide(this._directionToOrder(ht)),endCallback:()=>{"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout((()=>this._maybeEnableCycle()),500+this._config.interval))}};this._swipeHelper=new st(this._element,t)}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=Tt[t.key];e&&(t.preventDefault(),this._slide(this._directionToOrder(e)))}_getItemIndex(t){return this._getItems().indexOf(t)}_setActiveIndicatorElement(t){if(!this._indicatorsElement)return;const e=z.findOne(wt,this._indicatorsElement);e.classList.remove(yt),e.removeAttribute("aria-current");const i=z.findOne(`[data-bs-slide-to="${t}"]`,this._indicatorsElement);i&&(i.classList.add(yt),i.setAttribute("aria-current","true"))}_updateInterval(){const t=this._activeElement||this._getActive();if(!t)return;const e=Number.parseInt(t.getAttribute("data-bs-interval"),10);this._config.interval=e||this._config.defaultInterval}_slide(t,e=null){if(this._isSliding)return;const i=this._getActive(),n=t===at,s=e||b(this._getItems(),i,n,this._config.wrap);if(s===i)return;const o=this._getItemIndex(s),r=e=>N.trigger(this._element,e,{relatedTarget:s,direction:this._orderToDirection(t),from:this._getItemIndex(i),to:o});if(r(dt).defaultPrevented)return;if(!i||!s)return;const a=Boolean(this._interval);this.pause(),this._isSliding=!0,this._setActiveIndicatorElement(o),this._activeElement=s;const l=n?"carousel-item-start":"carousel-item-end",c=n?"carousel-item-next":"carousel-item-prev";s.classList.add(c),d(s),i.classList.add(l),s.classList.add(l),this._queueCallback((()=>{s.classList.remove(l,c),s.classList.add(yt),i.classList.remove(yt,c,l),this._isSliding=!1,r(ut)}),i,this._isAnimated()),a&&this.cycle()}_isAnimated(){return this._element.classList.contains("slide")}_getActive(){return z.findOne(Et,this._element)}_getItems(){return z.find(At,this._element)}_clearInterval(){this._interval&&(clearInterval(this._interval),this._interval=null)}_directionToOrder(t){return p()?t===ct?lt:at:t===ct?at:lt}_orderToDirection(t){return p()?t===lt?ct:ht:t===lt?ht:ct}static jQueryInterface(t){return this.each((function(){const e=xt.getOrCreateInstance(this,t);if("number"!=typeof t){if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}else e.to(t)}))}}N.on(document,bt,"[data-bs-slide], [data-bs-slide-to]",(function(t){const e=z.getElementFromSelector(this);if(!e||!e.classList.contains(vt))return;t.preventDefault();const i=xt.getOrCreateInstance(e),n=this.getAttribute("data-bs-slide-to");return n?(i.to(n),void i._maybeEnableCycle()):"next"===F.getDataAttribute(this,"slide")?(i.next(),void i._maybeEnableCycle()):(i.prev(),void i._maybeEnableCycle())})),N.on(window,_t,(()=>{const t=z.find('[data-bs-ride="carousel"]');for(const e of t)xt.getOrCreateInstance(e)})),m(xt);const kt=".bs.collapse",Lt=`show${kt}`,St=`shown${kt}`,Dt=`hide${kt}`,$t=`hidden${kt}`,It=`click${kt}.data-api`,Nt="show",Pt="collapse",Mt="collapsing",jt=`:scope .${Pt} .${Pt}`,Ft='[data-bs-toggle="collapse"]',Ht={parent:null,toggle:!0},Wt={parent:"(null|element)",toggle:"boolean"};class Bt extends W{constructor(t,e){super(t,e),this._isTransitioning=!1,this._triggerArray=[];const i=z.find(Ft);for(const t of i){const e=z.getSelectorFromElement(t),i=z.find(e).filter((t=>t===this._element));null!==e&&i.length&&this._triggerArray.push(t)}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return Ht}static get DefaultType(){return Wt}static get NAME(){return"collapse"}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let t=[];if(this._config.parent&&(t=this._getFirstLevelChildren(".collapse.show, .collapse.collapsing").filter((t=>t!==this._element)).map((t=>Bt.getOrCreateInstance(t,{toggle:!1})))),t.length&&t[0]._isTransitioning)return;if(N.trigger(this._element,Lt).defaultPrevented)return;for(const e of t)e.hide();const e=this._getDimension();this._element.classList.remove(Pt),this._element.classList.add(Mt),this._element.style[e]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;const i=`scroll${e[0].toUpperCase()+e.slice(1)}`;this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(Mt),this._element.classList.add(Pt,Nt),this._element.style[e]="",N.trigger(this._element,St)}),this._element,!0),this._element.style[e]=`${this._element[i]}px`}hide(){if(this._isTransitioning||!this._isShown())return;if(N.trigger(this._element,Dt).defaultPrevented)return;const t=this._getDimension();this._element.style[t]=`${this._element.getBoundingClientRect()[t]}px`,d(this._element),this._element.classList.add(Mt),this._element.classList.remove(Pt,Nt);for(const t of this._triggerArray){const e=z.getElementFromSelector(t);e&&!this._isShown(e)&&this._addAriaAndCollapsedClass([t],!1)}this._isTransitioning=!0,this._element.style[t]="",this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(Mt),this._element.classList.add(Pt),N.trigger(this._element,$t)}),this._element,!0)}_isShown(t=this._element){return t.classList.contains(Nt)}_configAfterMerge(t){return t.toggle=Boolean(t.toggle),t.parent=r(t.parent),t}_getDimension(){return this._element.classList.contains("collapse-horizontal")?"width":"height"}_initializeChildren(){if(!this._config.parent)return;const t=this._getFirstLevelChildren(Ft);for(const e of t){const t=z.getElementFromSelector(e);t&&this._addAriaAndCollapsedClass([e],this._isShown(t))}}_getFirstLevelChildren(t){const e=z.find(jt,this._config.parent);return z.find(t,this._config.parent).filter((t=>!e.includes(t)))}_addAriaAndCollapsedClass(t,e){if(t.length)for(const i of t)i.classList.toggle("collapsed",!e),i.setAttribute("aria-expanded",e)}static jQueryInterface(t){const e={};return"string"==typeof t&&/show|hide/.test(t)&&(e.toggle=!1),this.each((function(){const i=Bt.getOrCreateInstance(this,e);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t]()}}))}}N.on(document,It,Ft,(function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();for(const t of z.getMultipleElementsFromSelector(this))Bt.getOrCreateInstance(t,{toggle:!1}).toggle()})),m(Bt);var zt="top",Rt="bottom",qt="right",Vt="left",Kt="auto",Qt=[zt,Rt,qt,Vt],Xt="start",Yt="end",Ut="clippingParents",Gt="viewport",Jt="popper",Zt="reference",te=Qt.reduce((function(t,e){return t.concat([e+"-"+Xt,e+"-"+Yt])}),[]),ee=[].concat(Qt,[Kt]).reduce((function(t,e){return t.concat([e,e+"-"+Xt,e+"-"+Yt])}),[]),ie="beforeRead",ne="read",se="afterRead",oe="beforeMain",re="main",ae="afterMain",le="beforeWrite",ce="write",he="afterWrite",de=[ie,ne,se,oe,re,ae,le,ce,he];function ue(t){return t?(t.nodeName||"").toLowerCase():null}function fe(t){if(null==t)return window;if("[object Window]"!==t.toString()){var e=t.ownerDocument;return e&&e.defaultView||window}return t}function pe(t){return t instanceof fe(t).Element||t instanceof Element}function me(t){return t instanceof fe(t).HTMLElement||t instanceof HTMLElement}function ge(t){return"undefined"!=typeof ShadowRoot&&(t instanceof fe(t).ShadowRoot||t instanceof ShadowRoot)}const _e={name:"applyStyles",enabled:!0,phase:"write",fn:function(t){var e=t.state;Object.keys(e.elements).forEach((function(t){var i=e.styles[t]||{},n=e.attributes[t]||{},s=e.elements[t];me(s)&&ue(s)&&(Object.assign(s.style,i),Object.keys(n).forEach((function(t){var e=n[t];!1===e?s.removeAttribute(t):s.setAttribute(t,!0===e?"":e)})))}))},effect:function(t){var e=t.state,i={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,i.popper),e.styles=i,e.elements.arrow&&Object.assign(e.elements.arrow.style,i.arrow),function(){Object.keys(e.elements).forEach((function(t){var n=e.elements[t],s=e.attributes[t]||{},o=Object.keys(e.styles.hasOwnProperty(t)?e.styles[t]:i[t]).reduce((function(t,e){return t[e]="",t}),{});me(n)&&ue(n)&&(Object.assign(n.style,o),Object.keys(s).forEach((function(t){n.removeAttribute(t)})))}))}},requires:["computeStyles"]};function be(t){return t.split("-")[0]}var ve=Math.max,ye=Math.min,we=Math.round;function Ae(){var t=navigator.userAgentData;return null!=t&&t.brands&&Array.isArray(t.brands)?t.brands.map((function(t){return t.brand+"/"+t.version})).join(" "):navigator.userAgent}function Ee(){return!/^((?!chrome|android).)*safari/i.test(Ae())}function Te(t,e,i){void 0===e&&(e=!1),void 0===i&&(i=!1);var n=t.getBoundingClientRect(),s=1,o=1;e&&me(t)&&(s=t.offsetWidth>0&&we(n.width)/t.offsetWidth||1,o=t.offsetHeight>0&&we(n.height)/t.offsetHeight||1);var r=(pe(t)?fe(t):window).visualViewport,a=!Ee()&&i,l=(n.left+(a&&r?r.offsetLeft:0))/s,c=(n.top+(a&&r?r.offsetTop:0))/o,h=n.width/s,d=n.height/o;return{width:h,height:d,top:c,right:l+h,bottom:c+d,left:l,x:l,y:c}}function Ce(t){var e=Te(t),i=t.offsetWidth,n=t.offsetHeight;return Math.abs(e.width-i)<=1&&(i=e.width),Math.abs(e.height-n)<=1&&(n=e.height),{x:t.offsetLeft,y:t.offsetTop,width:i,height:n}}function Oe(t,e){var i=e.getRootNode&&e.getRootNode();if(t.contains(e))return!0;if(i&&ge(i)){var n=e;do{if(n&&t.isSameNode(n))return!0;n=n.parentNode||n.host}while(n)}return!1}function xe(t){return fe(t).getComputedStyle(t)}function ke(t){return["table","td","th"].indexOf(ue(t))>=0}function Le(t){return((pe(t)?t.ownerDocument:t.document)||window.document).documentElement}function Se(t){return"html"===ue(t)?t:t.assignedSlot||t.parentNode||(ge(t)?t.host:null)||Le(t)}function De(t){return me(t)&&"fixed"!==xe(t).position?t.offsetParent:null}function $e(t){for(var e=fe(t),i=De(t);i&&ke(i)&&"static"===xe(i).position;)i=De(i);return i&&("html"===ue(i)||"body"===ue(i)&&"static"===xe(i).position)?e:i||function(t){var e=/firefox/i.test(Ae());if(/Trident/i.test(Ae())&&me(t)&&"fixed"===xe(t).position)return null;var i=Se(t);for(ge(i)&&(i=i.host);me(i)&&["html","body"].indexOf(ue(i))<0;){var n=xe(i);if("none"!==n.transform||"none"!==n.perspective||"paint"===n.contain||-1!==["transform","perspective"].indexOf(n.willChange)||e&&"filter"===n.willChange||e&&n.filter&&"none"!==n.filter)return i;i=i.parentNode}return null}(t)||e}function Ie(t){return["top","bottom"].indexOf(t)>=0?"x":"y"}function Ne(t,e,i){return ve(t,ye(e,i))}function Pe(t){return Object.assign({},{top:0,right:0,bottom:0,left:0},t)}function Me(t,e){return e.reduce((function(e,i){return e[i]=t,e}),{})}const je={name:"arrow",enabled:!0,phase:"main",fn:function(t){var e,i=t.state,n=t.name,s=t.options,o=i.elements.arrow,r=i.modifiersData.popperOffsets,a=be(i.placement),l=Ie(a),c=[Vt,qt].indexOf(a)>=0?"height":"width";if(o&&r){var h=function(t,e){return Pe("number"!=typeof(t="function"==typeof t?t(Object.assign({},e.rects,{placement:e.placement})):t)?t:Me(t,Qt))}(s.padding,i),d=Ce(o),u="y"===l?zt:Vt,f="y"===l?Rt:qt,p=i.rects.reference[c]+i.rects.reference[l]-r[l]-i.rects.popper[c],m=r[l]-i.rects.reference[l],g=$e(o),_=g?"y"===l?g.clientHeight||0:g.clientWidth||0:0,b=p/2-m/2,v=h[u],y=_-d[c]-h[f],w=_/2-d[c]/2+b,A=Ne(v,w,y),E=l;i.modifiersData[n]=((e={})[E]=A,e.centerOffset=A-w,e)}},effect:function(t){var e=t.state,i=t.options.element,n=void 0===i?"[data-popper-arrow]":i;null!=n&&("string"!=typeof n||(n=e.elements.popper.querySelector(n)))&&Oe(e.elements.popper,n)&&(e.elements.arrow=n)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function Fe(t){return t.split("-")[1]}var He={top:"auto",right:"auto",bottom:"auto",left:"auto"};function We(t){var e,i=t.popper,n=t.popperRect,s=t.placement,o=t.variation,r=t.offsets,a=t.position,l=t.gpuAcceleration,c=t.adaptive,h=t.roundOffsets,d=t.isFixed,u=r.x,f=void 0===u?0:u,p=r.y,m=void 0===p?0:p,g="function"==typeof h?h({x:f,y:m}):{x:f,y:m};f=g.x,m=g.y;var _=r.hasOwnProperty("x"),b=r.hasOwnProperty("y"),v=Vt,y=zt,w=window;if(c){var A=$e(i),E="clientHeight",T="clientWidth";A===fe(i)&&"static"!==xe(A=Le(i)).position&&"absolute"===a&&(E="scrollHeight",T="scrollWidth"),(s===zt||(s===Vt||s===qt)&&o===Yt)&&(y=Rt,m-=(d&&A===w&&w.visualViewport?w.visualViewport.height:A[E])-n.height,m*=l?1:-1),s!==Vt&&(s!==zt&&s!==Rt||o!==Yt)||(v=qt,f-=(d&&A===w&&w.visualViewport?w.visualViewport.width:A[T])-n.width,f*=l?1:-1)}var C,O=Object.assign({position:a},c&&He),x=!0===h?function(t,e){var i=t.x,n=t.y,s=e.devicePixelRatio||1;return{x:we(i*s)/s||0,y:we(n*s)/s||0}}({x:f,y:m},fe(i)):{x:f,y:m};return f=x.x,m=x.y,l?Object.assign({},O,((C={})[y]=b?"0":"",C[v]=_?"0":"",C.transform=(w.devicePixelRatio||1)<=1?"translate("+f+"px, "+m+"px)":"translate3d("+f+"px, "+m+"px, 0)",C)):Object.assign({},O,((e={})[y]=b?m+"px":"",e[v]=_?f+"px":"",e.transform="",e))}const Be={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(t){var e=t.state,i=t.options,n=i.gpuAcceleration,s=void 0===n||n,o=i.adaptive,r=void 0===o||o,a=i.roundOffsets,l=void 0===a||a,c={placement:be(e.placement),variation:Fe(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:s,isFixed:"fixed"===e.options.strategy};null!=e.modifiersData.popperOffsets&&(e.styles.popper=Object.assign({},e.styles.popper,We(Object.assign({},c,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:r,roundOffsets:l})))),null!=e.modifiersData.arrow&&(e.styles.arrow=Object.assign({},e.styles.arrow,We(Object.assign({},c,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})},data:{}};var ze={passive:!0};const Re={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(t){var e=t.state,i=t.instance,n=t.options,s=n.scroll,o=void 0===s||s,r=n.resize,a=void 0===r||r,l=fe(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach((function(t){t.addEventListener("scroll",i.update,ze)})),a&&l.addEventListener("resize",i.update,ze),function(){o&&c.forEach((function(t){t.removeEventListener("scroll",i.update,ze)})),a&&l.removeEventListener("resize",i.update,ze)}},data:{}};var qe={left:"right",right:"left",bottom:"top",top:"bottom"};function Ve(t){return t.replace(/left|right|bottom|top/g,(function(t){return qe[t]}))}var Ke={start:"end",end:"start"};function Qe(t){return t.replace(/start|end/g,(function(t){return Ke[t]}))}function Xe(t){var e=fe(t);return{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function Ye(t){return Te(Le(t)).left+Xe(t).scrollLeft}function Ue(t){var e=xe(t),i=e.overflow,n=e.overflowX,s=e.overflowY;return/auto|scroll|overlay|hidden/.test(i+s+n)}function Ge(t){return["html","body","#document"].indexOf(ue(t))>=0?t.ownerDocument.body:me(t)&&Ue(t)?t:Ge(Se(t))}function Je(t,e){var i;void 0===e&&(e=[]);var n=Ge(t),s=n===(null==(i=t.ownerDocument)?void 0:i.body),o=fe(n),r=s?[o].concat(o.visualViewport||[],Ue(n)?n:[]):n,a=e.concat(r);return s?a:a.concat(Je(Se(r)))}function Ze(t){return Object.assign({},t,{left:t.x,top:t.y,right:t.x+t.width,bottom:t.y+t.height})}function ti(t,e,i){return e===Gt?Ze(function(t,e){var i=fe(t),n=Le(t),s=i.visualViewport,o=n.clientWidth,r=n.clientHeight,a=0,l=0;if(s){o=s.width,r=s.height;var c=Ee();(c||!c&&"fixed"===e)&&(a=s.offsetLeft,l=s.offsetTop)}return{width:o,height:r,x:a+Ye(t),y:l}}(t,i)):pe(e)?function(t,e){var i=Te(t,!1,"fixed"===e);return i.top=i.top+t.clientTop,i.left=i.left+t.clientLeft,i.bottom=i.top+t.clientHeight,i.right=i.left+t.clientWidth,i.width=t.clientWidth,i.height=t.clientHeight,i.x=i.left,i.y=i.top,i}(e,i):Ze(function(t){var e,i=Le(t),n=Xe(t),s=null==(e=t.ownerDocument)?void 0:e.body,o=ve(i.scrollWidth,i.clientWidth,s?s.scrollWidth:0,s?s.clientWidth:0),r=ve(i.scrollHeight,i.clientHeight,s?s.scrollHeight:0,s?s.clientHeight:0),a=-n.scrollLeft+Ye(t),l=-n.scrollTop;return"rtl"===xe(s||i).direction&&(a+=ve(i.clientWidth,s?s.clientWidth:0)-o),{width:o,height:r,x:a,y:l}}(Le(t)))}function ei(t){var e,i=t.reference,n=t.element,s=t.placement,o=s?be(s):null,r=s?Fe(s):null,a=i.x+i.width/2-n.width/2,l=i.y+i.height/2-n.height/2;switch(o){case zt:e={x:a,y:i.y-n.height};break;case Rt:e={x:a,y:i.y+i.height};break;case qt:e={x:i.x+i.width,y:l};break;case Vt:e={x:i.x-n.width,y:l};break;default:e={x:i.x,y:i.y}}var c=o?Ie(o):null;if(null!=c){var h="y"===c?"height":"width";switch(r){case Xt:e[c]=e[c]-(i[h]/2-n[h]/2);break;case Yt:e[c]=e[c]+(i[h]/2-n[h]/2)}}return e}function ii(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=void 0===n?t.placement:n,o=i.strategy,r=void 0===o?t.strategy:o,a=i.boundary,l=void 0===a?Ut:a,c=i.rootBoundary,h=void 0===c?Gt:c,d=i.elementContext,u=void 0===d?Jt:d,f=i.altBoundary,p=void 0!==f&&f,m=i.padding,g=void 0===m?0:m,_=Pe("number"!=typeof g?g:Me(g,Qt)),b=u===Jt?Zt:Jt,v=t.rects.popper,y=t.elements[p?b:u],w=function(t,e,i,n){var s="clippingParents"===e?function(t){var e=Je(Se(t)),i=["absolute","fixed"].indexOf(xe(t).position)>=0&&me(t)?$e(t):t;return pe(i)?e.filter((function(t){return pe(t)&&Oe(t,i)&&"body"!==ue(t)})):[]}(t):[].concat(e),o=[].concat(s,[i]),r=o[0],a=o.reduce((function(e,i){var s=ti(t,i,n);return e.top=ve(s.top,e.top),e.right=ye(s.right,e.right),e.bottom=ye(s.bottom,e.bottom),e.left=ve(s.left,e.left),e}),ti(t,r,n));return a.width=a.right-a.left,a.height=a.bottom-a.top,a.x=a.left,a.y=a.top,a}(pe(y)?y:y.contextElement||Le(t.elements.popper),l,h,r),A=Te(t.elements.reference),E=ei({reference:A,element:v,strategy:"absolute",placement:s}),T=Ze(Object.assign({},v,E)),C=u===Jt?T:A,O={top:w.top-C.top+_.top,bottom:C.bottom-w.bottom+_.bottom,left:w.left-C.left+_.left,right:C.right-w.right+_.right},x=t.modifiersData.offset;if(u===Jt&&x){var k=x[s];Object.keys(O).forEach((function(t){var e=[qt,Rt].indexOf(t)>=0?1:-1,i=[zt,Rt].indexOf(t)>=0?"y":"x";O[t]+=k[i]*e}))}return O}function ni(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=i.boundary,o=i.rootBoundary,r=i.padding,a=i.flipVariations,l=i.allowedAutoPlacements,c=void 0===l?ee:l,h=Fe(n),d=h?a?te:te.filter((function(t){return Fe(t)===h})):Qt,u=d.filter((function(t){return c.indexOf(t)>=0}));0===u.length&&(u=d);var f=u.reduce((function(e,i){return e[i]=ii(t,{placement:i,boundary:s,rootBoundary:o,padding:r})[be(i)],e}),{});return Object.keys(f).sort((function(t,e){return f[t]-f[e]}))}const si={name:"flip",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name;if(!e.modifiersData[n]._skip){for(var s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0===r||r,l=i.fallbackPlacements,c=i.padding,h=i.boundary,d=i.rootBoundary,u=i.altBoundary,f=i.flipVariations,p=void 0===f||f,m=i.allowedAutoPlacements,g=e.options.placement,_=be(g),b=l||(_!==g&&p?function(t){if(be(t)===Kt)return[];var e=Ve(t);return[Qe(t),e,Qe(e)]}(g):[Ve(g)]),v=[g].concat(b).reduce((function(t,i){return t.concat(be(i)===Kt?ni(e,{placement:i,boundary:h,rootBoundary:d,padding:c,flipVariations:p,allowedAutoPlacements:m}):i)}),[]),y=e.rects.reference,w=e.rects.popper,A=new Map,E=!0,T=v[0],C=0;C=0,S=L?"width":"height",D=ii(e,{placement:O,boundary:h,rootBoundary:d,altBoundary:u,padding:c}),$=L?k?qt:Vt:k?Rt:zt;y[S]>w[S]&&($=Ve($));var I=Ve($),N=[];if(o&&N.push(D[x]<=0),a&&N.push(D[$]<=0,D[I]<=0),N.every((function(t){return t}))){T=O,E=!1;break}A.set(O,N)}if(E)for(var P=function(t){var e=v.find((function(e){var i=A.get(e);if(i)return i.slice(0,t).every((function(t){return t}))}));if(e)return T=e,"break"},M=p?3:1;M>0&&"break"!==P(M);M--);e.placement!==T&&(e.modifiersData[n]._skip=!0,e.placement=T,e.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function oi(t,e,i){return void 0===i&&(i={x:0,y:0}),{top:t.top-e.height-i.y,right:t.right-e.width+i.x,bottom:t.bottom-e.height+i.y,left:t.left-e.width-i.x}}function ri(t){return[zt,qt,Rt,Vt].some((function(e){return t[e]>=0}))}const ai={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(t){var e=t.state,i=t.name,n=e.rects.reference,s=e.rects.popper,o=e.modifiersData.preventOverflow,r=ii(e,{elementContext:"reference"}),a=ii(e,{altBoundary:!0}),l=oi(r,n),c=oi(a,s,o),h=ri(l),d=ri(c);e.modifiersData[i]={referenceClippingOffsets:l,popperEscapeOffsets:c,isReferenceHidden:h,hasPopperEscaped:d},e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-reference-hidden":h,"data-popper-escaped":d})}},li={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.offset,o=void 0===s?[0,0]:s,r=ee.reduce((function(t,i){return t[i]=function(t,e,i){var n=be(t),s=[Vt,zt].indexOf(n)>=0?-1:1,o="function"==typeof i?i(Object.assign({},e,{placement:t})):i,r=o[0],a=o[1];return r=r||0,a=(a||0)*s,[Vt,qt].indexOf(n)>=0?{x:a,y:r}:{x:r,y:a}}(i,e.rects,o),t}),{}),a=r[e.placement],l=a.x,c=a.y;null!=e.modifiersData.popperOffsets&&(e.modifiersData.popperOffsets.x+=l,e.modifiersData.popperOffsets.y+=c),e.modifiersData[n]=r}},ci={name:"popperOffsets",enabled:!0,phase:"read",fn:function(t){var e=t.state,i=t.name;e.modifiersData[i]=ei({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})},data:{}},hi={name:"preventOverflow",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0!==r&&r,l=i.boundary,c=i.rootBoundary,h=i.altBoundary,d=i.padding,u=i.tether,f=void 0===u||u,p=i.tetherOffset,m=void 0===p?0:p,g=ii(e,{boundary:l,rootBoundary:c,padding:d,altBoundary:h}),_=be(e.placement),b=Fe(e.placement),v=!b,y=Ie(_),w="x"===y?"y":"x",A=e.modifiersData.popperOffsets,E=e.rects.reference,T=e.rects.popper,C="function"==typeof m?m(Object.assign({},e.rects,{placement:e.placement})):m,O="number"==typeof C?{mainAxis:C,altAxis:C}:Object.assign({mainAxis:0,altAxis:0},C),x=e.modifiersData.offset?e.modifiersData.offset[e.placement]:null,k={x:0,y:0};if(A){if(o){var L,S="y"===y?zt:Vt,D="y"===y?Rt:qt,$="y"===y?"height":"width",I=A[y],N=I+g[S],P=I-g[D],M=f?-T[$]/2:0,j=b===Xt?E[$]:T[$],F=b===Xt?-T[$]:-E[$],H=e.elements.arrow,W=f&&H?Ce(H):{width:0,height:0},B=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},z=B[S],R=B[D],q=Ne(0,E[$],W[$]),V=v?E[$]/2-M-q-z-O.mainAxis:j-q-z-O.mainAxis,K=v?-E[$]/2+M+q+R+O.mainAxis:F+q+R+O.mainAxis,Q=e.elements.arrow&&$e(e.elements.arrow),X=Q?"y"===y?Q.clientTop||0:Q.clientLeft||0:0,Y=null!=(L=null==x?void 0:x[y])?L:0,U=I+K-Y,G=Ne(f?ye(N,I+V-Y-X):N,I,f?ve(P,U):P);A[y]=G,k[y]=G-I}if(a){var J,Z="x"===y?zt:Vt,tt="x"===y?Rt:qt,et=A[w],it="y"===w?"height":"width",nt=et+g[Z],st=et-g[tt],ot=-1!==[zt,Vt].indexOf(_),rt=null!=(J=null==x?void 0:x[w])?J:0,at=ot?nt:et-E[it]-T[it]-rt+O.altAxis,lt=ot?et+E[it]+T[it]-rt-O.altAxis:st,ct=f&&ot?function(t,e,i){var n=Ne(t,e,i);return n>i?i:n}(at,et,lt):Ne(f?at:nt,et,f?lt:st);A[w]=ct,k[w]=ct-et}e.modifiersData[n]=k}},requiresIfExists:["offset"]};function di(t,e,i){void 0===i&&(i=!1);var n,s,o=me(e),r=me(e)&&function(t){var e=t.getBoundingClientRect(),i=we(e.width)/t.offsetWidth||1,n=we(e.height)/t.offsetHeight||1;return 1!==i||1!==n}(e),a=Le(e),l=Te(t,r,i),c={scrollLeft:0,scrollTop:0},h={x:0,y:0};return(o||!o&&!i)&&(("body"!==ue(e)||Ue(a))&&(c=(n=e)!==fe(n)&&me(n)?{scrollLeft:(s=n).scrollLeft,scrollTop:s.scrollTop}:Xe(n)),me(e)?((h=Te(e,!0)).x+=e.clientLeft,h.y+=e.clientTop):a&&(h.x=Ye(a))),{x:l.left+c.scrollLeft-h.x,y:l.top+c.scrollTop-h.y,width:l.width,height:l.height}}function ui(t){var e=new Map,i=new Set,n=[];function s(t){i.add(t.name),[].concat(t.requires||[],t.requiresIfExists||[]).forEach((function(t){if(!i.has(t)){var n=e.get(t);n&&s(n)}})),n.push(t)}return t.forEach((function(t){e.set(t.name,t)})),t.forEach((function(t){i.has(t.name)||s(t)})),n}var fi={placement:"bottom",modifiers:[],strategy:"absolute"};function pi(){for(var t=arguments.length,e=new Array(t),i=0;iNumber.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return(this._inNavbar||"static"===this._config.display)&&(F.setDataAttribute(this._menu,"popper","static"),t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,...g(this._config.popperConfig,[t])}}_selectMenuItem({key:t,target:e}){const i=z.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter((t=>a(t)));i.length&&b(i,e,t===Ti,!i.includes(e)).focus()}static jQueryInterface(t){return this.each((function(){const e=qi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}static clearMenus(t){if(2===t.button||"keyup"===t.type&&"Tab"!==t.key)return;const e=z.find(Ni);for(const i of e){const e=qi.getInstance(i);if(!e||!1===e._config.autoClose)continue;const n=t.composedPath(),s=n.includes(e._menu);if(n.includes(e._element)||"inside"===e._config.autoClose&&!s||"outside"===e._config.autoClose&&s)continue;if(e._menu.contains(t.target)&&("keyup"===t.type&&"Tab"===t.key||/input|select|option|textarea|form/i.test(t.target.tagName)))continue;const o={relatedTarget:e._element};"click"===t.type&&(o.clickEvent=t),e._completeHide(o)}}static dataApiKeydownHandler(t){const e=/input|textarea/i.test(t.target.tagName),i="Escape"===t.key,n=[Ei,Ti].includes(t.key);if(!n&&!i)return;if(e&&!i)return;t.preventDefault();const s=this.matches(Ii)?this:z.prev(this,Ii)[0]||z.next(this,Ii)[0]||z.findOne(Ii,t.delegateTarget.parentNode),o=qi.getOrCreateInstance(s);if(n)return t.stopPropagation(),o.show(),void o._selectMenuItem(t);o._isShown()&&(t.stopPropagation(),o.hide(),s.focus())}}N.on(document,Si,Ii,qi.dataApiKeydownHandler),N.on(document,Si,Pi,qi.dataApiKeydownHandler),N.on(document,Li,qi.clearMenus),N.on(document,Di,qi.clearMenus),N.on(document,Li,Ii,(function(t){t.preventDefault(),qi.getOrCreateInstance(this).toggle()})),m(qi);const Vi="backdrop",Ki="show",Qi=`mousedown.bs.${Vi}`,Xi={className:"modal-backdrop",clickCallback:null,isAnimated:!1,isVisible:!0,rootElement:"body"},Yi={className:"string",clickCallback:"(function|null)",isAnimated:"boolean",isVisible:"boolean",rootElement:"(element|string)"};class Ui extends H{constructor(t){super(),this._config=this._getConfig(t),this._isAppended=!1,this._element=null}static get Default(){return Xi}static get DefaultType(){return Yi}static get NAME(){return Vi}show(t){if(!this._config.isVisible)return void g(t);this._append();const e=this._getElement();this._config.isAnimated&&d(e),e.classList.add(Ki),this._emulateAnimation((()=>{g(t)}))}hide(t){this._config.isVisible?(this._getElement().classList.remove(Ki),this._emulateAnimation((()=>{this.dispose(),g(t)}))):g(t)}dispose(){this._isAppended&&(N.off(this._element,Qi),this._element.remove(),this._isAppended=!1)}_getElement(){if(!this._element){const t=document.createElement("div");t.className=this._config.className,this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_configAfterMerge(t){return t.rootElement=r(t.rootElement),t}_append(){if(this._isAppended)return;const t=this._getElement();this._config.rootElement.append(t),N.on(t,Qi,(()=>{g(this._config.clickCallback)})),this._isAppended=!0}_emulateAnimation(t){_(t,this._getElement(),this._config.isAnimated)}}const Gi=".bs.focustrap",Ji=`focusin${Gi}`,Zi=`keydown.tab${Gi}`,tn="backward",en={autofocus:!0,trapElement:null},nn={autofocus:"boolean",trapElement:"element"};class sn extends H{constructor(t){super(),this._config=this._getConfig(t),this._isActive=!1,this._lastTabNavDirection=null}static get Default(){return en}static get DefaultType(){return nn}static get NAME(){return"focustrap"}activate(){this._isActive||(this._config.autofocus&&this._config.trapElement.focus(),N.off(document,Gi),N.on(document,Ji,(t=>this._handleFocusin(t))),N.on(document,Zi,(t=>this._handleKeydown(t))),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,N.off(document,Gi))}_handleFocusin(t){const{trapElement:e}=this._config;if(t.target===document||t.target===e||e.contains(t.target))return;const i=z.focusableChildren(e);0===i.length?e.focus():this._lastTabNavDirection===tn?i[i.length-1].focus():i[0].focus()}_handleKeydown(t){"Tab"===t.key&&(this._lastTabNavDirection=t.shiftKey?tn:"forward")}}const on=".fixed-top, .fixed-bottom, .is-fixed, .sticky-top",rn=".sticky-top",an="padding-right",ln="margin-right";class cn{constructor(){this._element=document.body}getWidth(){const t=document.documentElement.clientWidth;return Math.abs(window.innerWidth-t)}hide(){const t=this.getWidth();this._disableOverFlow(),this._setElementAttributes(this._element,an,(e=>e+t)),this._setElementAttributes(on,an,(e=>e+t)),this._setElementAttributes(rn,ln,(e=>e-t))}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,an),this._resetElementAttributes(on,an),this._resetElementAttributes(rn,ln)}isOverflowing(){return this.getWidth()>0}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(t,e,i){const n=this.getWidth();this._applyManipulationCallback(t,(t=>{if(t!==this._element&&window.innerWidth>t.clientWidth+n)return;this._saveInitialAttribute(t,e);const s=window.getComputedStyle(t).getPropertyValue(e);t.style.setProperty(e,`${i(Number.parseFloat(s))}px`)}))}_saveInitialAttribute(t,e){const i=t.style.getPropertyValue(e);i&&F.setDataAttribute(t,e,i)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,(t=>{const i=F.getDataAttribute(t,e);null!==i?(F.removeDataAttribute(t,e),t.style.setProperty(e,i)):t.style.removeProperty(e)}))}_applyManipulationCallback(t,e){if(o(t))e(t);else for(const i of z.find(t,this._element))e(i)}}const hn=".bs.modal",dn=`hide${hn}`,un=`hidePrevented${hn}`,fn=`hidden${hn}`,pn=`show${hn}`,mn=`shown${hn}`,gn=`resize${hn}`,_n=`click.dismiss${hn}`,bn=`mousedown.dismiss${hn}`,vn=`keydown.dismiss${hn}`,yn=`click${hn}.data-api`,wn="modal-open",An="show",En="modal-static",Tn={backdrop:!0,focus:!0,keyboard:!0},Cn={backdrop:"(boolean|string)",focus:"boolean",keyboard:"boolean"};class On extends W{constructor(t,e){super(t,e),this._dialog=z.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._isTransitioning=!1,this._scrollBar=new cn,this._addEventListeners()}static get Default(){return Tn}static get DefaultType(){return Cn}static get NAME(){return"modal"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||this._isTransitioning||N.trigger(this._element,pn,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isTransitioning=!0,this._scrollBar.hide(),document.body.classList.add(wn),this._adjustDialog(),this._backdrop.show((()=>this._showElement(t))))}hide(){this._isShown&&!this._isTransitioning&&(N.trigger(this._element,dn).defaultPrevented||(this._isShown=!1,this._isTransitioning=!0,this._focustrap.deactivate(),this._element.classList.remove(An),this._queueCallback((()=>this._hideModal()),this._element,this._isAnimated())))}dispose(){N.off(window,hn),N.off(this._dialog,hn),this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new Ui({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new sn({trapElement:this._element})}_showElement(t){document.body.contains(this._element)||document.body.append(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0;const e=z.findOne(".modal-body",this._dialog);e&&(e.scrollTop=0),d(this._element),this._element.classList.add(An),this._queueCallback((()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,N.trigger(this._element,mn,{relatedTarget:t})}),this._dialog,this._isAnimated())}_addEventListeners(){N.on(this._element,vn,(t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():this._triggerBackdropTransition())})),N.on(window,gn,(()=>{this._isShown&&!this._isTransitioning&&this._adjustDialog()})),N.on(this._element,bn,(t=>{N.one(this._element,_n,(e=>{this._element===t.target&&this._element===e.target&&("static"!==this._config.backdrop?this._config.backdrop&&this.hide():this._triggerBackdropTransition())}))}))}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide((()=>{document.body.classList.remove(wn),this._resetAdjustments(),this._scrollBar.reset(),N.trigger(this._element,fn)}))}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(N.trigger(this._element,un).defaultPrevented)return;const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._element.style.overflowY;"hidden"===e||this._element.classList.contains(En)||(t||(this._element.style.overflowY="hidden"),this._element.classList.add(En),this._queueCallback((()=>{this._element.classList.remove(En),this._queueCallback((()=>{this._element.style.overflowY=e}),this._dialog)}),this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),i=e>0;if(i&&!t){const t=p()?"paddingLeft":"paddingRight";this._element.style[t]=`${e}px`}if(!i&&t){const t=p()?"paddingRight":"paddingLeft";this._element.style[t]=`${e}px`}}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each((function(){const i=On.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t](e)}}))}}N.on(document,yn,'[data-bs-toggle="modal"]',(function(t){const e=z.getElementFromSelector(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),N.one(e,pn,(t=>{t.defaultPrevented||N.one(e,fn,(()=>{a(this)&&this.focus()}))}));const i=z.findOne(".modal.show");i&&On.getInstance(i).hide(),On.getOrCreateInstance(e).toggle(this)})),R(On),m(On);const xn=".bs.offcanvas",kn=".data-api",Ln=`load${xn}${kn}`,Sn="show",Dn="showing",$n="hiding",In=".offcanvas.show",Nn=`show${xn}`,Pn=`shown${xn}`,Mn=`hide${xn}`,jn=`hidePrevented${xn}`,Fn=`hidden${xn}`,Hn=`resize${xn}`,Wn=`click${xn}${kn}`,Bn=`keydown.dismiss${xn}`,zn={backdrop:!0,keyboard:!0,scroll:!1},Rn={backdrop:"(boolean|string)",keyboard:"boolean",scroll:"boolean"};class qn extends W{constructor(t,e){super(t,e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get Default(){return zn}static get DefaultType(){return Rn}static get NAME(){return"offcanvas"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||N.trigger(this._element,Nn,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._backdrop.show(),this._config.scroll||(new cn).hide(),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add(Dn),this._queueCallback((()=>{this._config.scroll&&!this._config.backdrop||this._focustrap.activate(),this._element.classList.add(Sn),this._element.classList.remove(Dn),N.trigger(this._element,Pn,{relatedTarget:t})}),this._element,!0))}hide(){this._isShown&&(N.trigger(this._element,Mn).defaultPrevented||(this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.add($n),this._backdrop.hide(),this._queueCallback((()=>{this._element.classList.remove(Sn,$n),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._config.scroll||(new cn).reset(),N.trigger(this._element,Fn)}),this._element,!0)))}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_initializeBackDrop(){const t=Boolean(this._config.backdrop);return new Ui({className:"offcanvas-backdrop",isVisible:t,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:t?()=>{"static"!==this._config.backdrop?this.hide():N.trigger(this._element,jn)}:null})}_initializeFocusTrap(){return new sn({trapElement:this._element})}_addEventListeners(){N.on(this._element,Bn,(t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():N.trigger(this._element,jn))}))}static jQueryInterface(t){return this.each((function(){const e=qn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}N.on(document,Wn,'[data-bs-toggle="offcanvas"]',(function(t){const e=z.getElementFromSelector(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),l(this))return;N.one(e,Fn,(()=>{a(this)&&this.focus()}));const i=z.findOne(In);i&&i!==e&&qn.getInstance(i).hide(),qn.getOrCreateInstance(e).toggle(this)})),N.on(window,Ln,(()=>{for(const t of z.find(In))qn.getOrCreateInstance(t).show()})),N.on(window,Hn,(()=>{for(const t of z.find("[aria-modal][class*=show][class*=offcanvas-]"))"fixed"!==getComputedStyle(t).position&&qn.getOrCreateInstance(t).hide()})),R(qn),m(qn);const Vn={"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},Kn=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),Qn=/^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i,Xn=(t,e)=>{const i=t.nodeName.toLowerCase();return e.includes(i)?!Kn.has(i)||Boolean(Qn.test(t.nodeValue)):e.filter((t=>t instanceof RegExp)).some((t=>t.test(i)))},Yn={allowList:Vn,content:{},extraClass:"",html:!1,sanitize:!0,sanitizeFn:null,template:"
"},Un={allowList:"object",content:"object",extraClass:"(string|function)",html:"boolean",sanitize:"boolean",sanitizeFn:"(null|function)",template:"string"},Gn={entry:"(string|element|function|null)",selector:"(string|element)"};class Jn extends H{constructor(t){super(),this._config=this._getConfig(t)}static get Default(){return Yn}static get DefaultType(){return Un}static get NAME(){return"TemplateFactory"}getContent(){return Object.values(this._config.content).map((t=>this._resolvePossibleFunction(t))).filter(Boolean)}hasContent(){return this.getContent().length>0}changeContent(t){return this._checkContent(t),this._config.content={...this._config.content,...t},this}toHtml(){const t=document.createElement("div");t.innerHTML=this._maybeSanitize(this._config.template);for(const[e,i]of Object.entries(this._config.content))this._setContent(t,i,e);const e=t.children[0],i=this._resolvePossibleFunction(this._config.extraClass);return i&&e.classList.add(...i.split(" ")),e}_typeCheckConfig(t){super._typeCheckConfig(t),this._checkContent(t.content)}_checkContent(t){for(const[e,i]of Object.entries(t))super._typeCheckConfig({selector:e,entry:i},Gn)}_setContent(t,e,i){const n=z.findOne(i,t);n&&((e=this._resolvePossibleFunction(e))?o(e)?this._putElementInTemplate(r(e),n):this._config.html?n.innerHTML=this._maybeSanitize(e):n.textContent=e:n.remove())}_maybeSanitize(t){return this._config.sanitize?function(t,e,i){if(!t.length)return t;if(i&&"function"==typeof i)return i(t);const n=(new window.DOMParser).parseFromString(t,"text/html"),s=[].concat(...n.body.querySelectorAll("*"));for(const t of s){const i=t.nodeName.toLowerCase();if(!Object.keys(e).includes(i)){t.remove();continue}const n=[].concat(...t.attributes),s=[].concat(e["*"]||[],e[i]||[]);for(const e of n)Xn(e,s)||t.removeAttribute(e.nodeName)}return n.body.innerHTML}(t,this._config.allowList,this._config.sanitizeFn):t}_resolvePossibleFunction(t){return g(t,[this])}_putElementInTemplate(t,e){if(this._config.html)return e.innerHTML="",void e.append(t);e.textContent=t.textContent}}const Zn=new Set(["sanitize","allowList","sanitizeFn"]),ts="fade",es="show",is=".modal",ns="hide.bs.modal",ss="hover",os="focus",rs={AUTO:"auto",TOP:"top",RIGHT:p()?"left":"right",BOTTOM:"bottom",LEFT:p()?"right":"left"},as={allowList:Vn,animation:!0,boundary:"clippingParents",container:!1,customClass:"",delay:0,fallbackPlacements:["top","right","bottom","left"],html:!1,offset:[0,6],placement:"top",popperConfig:null,sanitize:!0,sanitizeFn:null,selector:!1,template:'',title:"",trigger:"hover focus"},ls={allowList:"object",animation:"boolean",boundary:"(string|element)",container:"(string|element|boolean)",customClass:"(string|function)",delay:"(number|object)",fallbackPlacements:"array",html:"boolean",offset:"(array|string|function)",placement:"(string|function)",popperConfig:"(null|object|function)",sanitize:"boolean",sanitizeFn:"(null|function)",selector:"(string|boolean)",template:"string",title:"(string|element|function)",trigger:"string"};class cs extends W{constructor(t,e){if(void 0===vi)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(t,e),this._isEnabled=!0,this._timeout=0,this._isHovered=null,this._activeTrigger={},this._popper=null,this._templateFactory=null,this._newContent=null,this.tip=null,this._setListeners(),this._config.selector||this._fixTitle()}static get Default(){return as}static get DefaultType(){return ls}static get NAME(){return"tooltip"}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(){this._isEnabled&&(this._activeTrigger.click=!this._activeTrigger.click,this._isShown()?this._leave():this._enter())}dispose(){clearTimeout(this._timeout),N.off(this._element.closest(is),ns,this._hideModalHandler),this._element.getAttribute("data-bs-original-title")&&this._element.setAttribute("title",this._element.getAttribute("data-bs-original-title")),this._disposePopper(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this._isWithContent()||!this._isEnabled)return;const t=N.trigger(this._element,this.constructor.eventName("show")),e=(c(this._element)||this._element.ownerDocument.documentElement).contains(this._element);if(t.defaultPrevented||!e)return;this._disposePopper();const i=this._getTipElement();this._element.setAttribute("aria-describedby",i.getAttribute("id"));const{container:n}=this._config;if(this._element.ownerDocument.documentElement.contains(this.tip)||(n.append(i),N.trigger(this._element,this.constructor.eventName("inserted"))),this._popper=this._createPopper(i),i.classList.add(es),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))N.on(t,"mouseover",h);this._queueCallback((()=>{N.trigger(this._element,this.constructor.eventName("shown")),!1===this._isHovered&&this._leave(),this._isHovered=!1}),this.tip,this._isAnimated())}hide(){if(this._isShown()&&!N.trigger(this._element,this.constructor.eventName("hide")).defaultPrevented){if(this._getTipElement().classList.remove(es),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))N.off(t,"mouseover",h);this._activeTrigger.click=!1,this._activeTrigger[os]=!1,this._activeTrigger[ss]=!1,this._isHovered=null,this._queueCallback((()=>{this._isWithActiveTrigger()||(this._isHovered||this._disposePopper(),this._element.removeAttribute("aria-describedby"),N.trigger(this._element,this.constructor.eventName("hidden")))}),this.tip,this._isAnimated())}}update(){this._popper&&this._popper.update()}_isWithContent(){return Boolean(this._getTitle())}_getTipElement(){return this.tip||(this.tip=this._createTipElement(this._newContent||this._getContentForTemplate())),this.tip}_createTipElement(t){const e=this._getTemplateFactory(t).toHtml();if(!e)return null;e.classList.remove(ts,es),e.classList.add(`bs-${this.constructor.NAME}-auto`);const i=(t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t})(this.constructor.NAME).toString();return e.setAttribute("id",i),this._isAnimated()&&e.classList.add(ts),e}setContent(t){this._newContent=t,this._isShown()&&(this._disposePopper(),this.show())}_getTemplateFactory(t){return this._templateFactory?this._templateFactory.changeContent(t):this._templateFactory=new Jn({...this._config,content:t,extraClass:this._resolvePossibleFunction(this._config.customClass)}),this._templateFactory}_getContentForTemplate(){return{".tooltip-inner":this._getTitle()}}_getTitle(){return this._resolvePossibleFunction(this._config.title)||this._element.getAttribute("data-bs-original-title")}_initializeOnDelegatedTarget(t){return this.constructor.getOrCreateInstance(t.delegateTarget,this._getDelegateConfig())}_isAnimated(){return this._config.animation||this.tip&&this.tip.classList.contains(ts)}_isShown(){return this.tip&&this.tip.classList.contains(es)}_createPopper(t){const e=g(this._config.placement,[this,t,this._element]),i=rs[e.toUpperCase()];return bi(this._element,t,this._getPopperConfig(i))}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_resolvePossibleFunction(t){return g(t,[this._element])}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"preSetPlacement",enabled:!0,phase:"beforeMain",fn:t=>{this._getTipElement().setAttribute("data-popper-placement",t.state.placement)}}]};return{...e,...g(this._config.popperConfig,[e])}}_setListeners(){const t=this._config.trigger.split(" ");for(const e of t)if("click"===e)N.on(this._element,this.constructor.eventName("click"),this._config.selector,(t=>{this._initializeOnDelegatedTarget(t).toggle()}));else if("manual"!==e){const t=e===ss?this.constructor.eventName("mouseenter"):this.constructor.eventName("focusin"),i=e===ss?this.constructor.eventName("mouseleave"):this.constructor.eventName("focusout");N.on(this._element,t,this._config.selector,(t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusin"===t.type?os:ss]=!0,e._enter()})),N.on(this._element,i,this._config.selector,(t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusout"===t.type?os:ss]=e._element.contains(t.relatedTarget),e._leave()}))}this._hideModalHandler=()=>{this._element&&this.hide()},N.on(this._element.closest(is),ns,this._hideModalHandler)}_fixTitle(){const t=this._element.getAttribute("title");t&&(this._element.getAttribute("aria-label")||this._element.textContent.trim()||this._element.setAttribute("aria-label",t),this._element.setAttribute("data-bs-original-title",t),this._element.removeAttribute("title"))}_enter(){this._isShown()||this._isHovered?this._isHovered=!0:(this._isHovered=!0,this._setTimeout((()=>{this._isHovered&&this.show()}),this._config.delay.show))}_leave(){this._isWithActiveTrigger()||(this._isHovered=!1,this._setTimeout((()=>{this._isHovered||this.hide()}),this._config.delay.hide))}_setTimeout(t,e){clearTimeout(this._timeout),this._timeout=setTimeout(t,e)}_isWithActiveTrigger(){return Object.values(this._activeTrigger).includes(!0)}_getConfig(t){const e=F.getDataAttributes(this._element);for(const t of Object.keys(e))Zn.has(t)&&delete e[t];return t={...e,..."object"==typeof t&&t?t:{}},t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t.container=!1===t.container?document.body:r(t.container),"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),t}_getDelegateConfig(){const t={};for(const[e,i]of Object.entries(this._config))this.constructor.Default[e]!==i&&(t[e]=i);return t.selector=!1,t.trigger="manual",t}_disposePopper(){this._popper&&(this._popper.destroy(),this._popper=null),this.tip&&(this.tip.remove(),this.tip=null)}static jQueryInterface(t){return this.each((function(){const e=cs.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}m(cs);const hs={...cs.Default,content:"",offset:[0,8],placement:"right",template:'',trigger:"click"},ds={...cs.DefaultType,content:"(null|string|element|function)"};class us extends cs{static get Default(){return hs}static get DefaultType(){return ds}static get NAME(){return"popover"}_isWithContent(){return this._getTitle()||this._getContent()}_getContentForTemplate(){return{".popover-header":this._getTitle(),".popover-body":this._getContent()}}_getContent(){return this._resolvePossibleFunction(this._config.content)}static jQueryInterface(t){return this.each((function(){const e=us.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}m(us);const fs=".bs.scrollspy",ps=`activate${fs}`,ms=`click${fs}`,gs=`load${fs}.data-api`,_s="active",bs="[href]",vs=".nav-link",ys=`${vs}, .nav-item > ${vs}, .list-group-item`,ws={offset:null,rootMargin:"0px 0px -25%",smoothScroll:!1,target:null,threshold:[.1,.5,1]},As={offset:"(number|null)",rootMargin:"string",smoothScroll:"boolean",target:"element",threshold:"array"};class Es extends W{constructor(t,e){super(t,e),this._targetLinks=new Map,this._observableSections=new Map,this._rootElement="visible"===getComputedStyle(this._element).overflowY?null:this._element,this._activeTarget=null,this._observer=null,this._previousScrollData={visibleEntryTop:0,parentScrollTop:0},this.refresh()}static get Default(){return ws}static get DefaultType(){return As}static get NAME(){return"scrollspy"}refresh(){this._initializeTargetsAndObservables(),this._maybeEnableSmoothScroll(),this._observer?this._observer.disconnect():this._observer=this._getNewObserver();for(const t of this._observableSections.values())this._observer.observe(t)}dispose(){this._observer.disconnect(),super.dispose()}_configAfterMerge(t){return t.target=r(t.target)||document.body,t.rootMargin=t.offset?`${t.offset}px 0px -30%`:t.rootMargin,"string"==typeof t.threshold&&(t.threshold=t.threshold.split(",").map((t=>Number.parseFloat(t)))),t}_maybeEnableSmoothScroll(){this._config.smoothScroll&&(N.off(this._config.target,ms),N.on(this._config.target,ms,bs,(t=>{const e=this._observableSections.get(t.target.hash);if(e){t.preventDefault();const i=this._rootElement||window,n=e.offsetTop-this._element.offsetTop;if(i.scrollTo)return void i.scrollTo({top:n,behavior:"smooth"});i.scrollTop=n}})))}_getNewObserver(){const t={root:this._rootElement,threshold:this._config.threshold,rootMargin:this._config.rootMargin};return new IntersectionObserver((t=>this._observerCallback(t)),t)}_observerCallback(t){const e=t=>this._targetLinks.get(`#${t.target.id}`),i=t=>{this._previousScrollData.visibleEntryTop=t.target.offsetTop,this._process(e(t))},n=(this._rootElement||document.documentElement).scrollTop,s=n>=this._previousScrollData.parentScrollTop;this._previousScrollData.parentScrollTop=n;for(const o of t){if(!o.isIntersecting){this._activeTarget=null,this._clearActiveClass(e(o));continue}const t=o.target.offsetTop>=this._previousScrollData.visibleEntryTop;if(s&&t){if(i(o),!n)return}else s||t||i(o)}}_initializeTargetsAndObservables(){this._targetLinks=new Map,this._observableSections=new Map;const t=z.find(bs,this._config.target);for(const e of t){if(!e.hash||l(e))continue;const t=z.findOne(decodeURI(e.hash),this._element);a(t)&&(this._targetLinks.set(decodeURI(e.hash),e),this._observableSections.set(e.hash,t))}}_process(t){this._activeTarget!==t&&(this._clearActiveClass(this._config.target),this._activeTarget=t,t.classList.add(_s),this._activateParents(t),N.trigger(this._element,ps,{relatedTarget:t}))}_activateParents(t){if(t.classList.contains("dropdown-item"))z.findOne(".dropdown-toggle",t.closest(".dropdown")).classList.add(_s);else for(const e of z.parents(t,".nav, .list-group"))for(const t of z.prev(e,ys))t.classList.add(_s)}_clearActiveClass(t){t.classList.remove(_s);const e=z.find(`${bs}.${_s}`,t);for(const t of e)t.classList.remove(_s)}static jQueryInterface(t){return this.each((function(){const e=Es.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}))}}N.on(window,gs,(()=>{for(const t of z.find('[data-bs-spy="scroll"]'))Es.getOrCreateInstance(t)})),m(Es);const Ts=".bs.tab",Cs=`hide${Ts}`,Os=`hidden${Ts}`,xs=`show${Ts}`,ks=`shown${Ts}`,Ls=`click${Ts}`,Ss=`keydown${Ts}`,Ds=`load${Ts}`,$s="ArrowLeft",Is="ArrowRight",Ns="ArrowUp",Ps="ArrowDown",Ms="Home",js="End",Fs="active",Hs="fade",Ws="show",Bs=":not(.dropdown-toggle)",zs='[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',Rs=`.nav-link${Bs}, .list-group-item${Bs}, [role="tab"]${Bs}, ${zs}`,qs=`.${Fs}[data-bs-toggle="tab"], .${Fs}[data-bs-toggle="pill"], .${Fs}[data-bs-toggle="list"]`;class Vs extends W{constructor(t){super(t),this._parent=this._element.closest('.list-group, .nav, [role="tablist"]'),this._parent&&(this._setInitialAttributes(this._parent,this._getChildren()),N.on(this._element,Ss,(t=>this._keydown(t))))}static get NAME(){return"tab"}show(){const t=this._element;if(this._elemIsActive(t))return;const e=this._getActiveElem(),i=e?N.trigger(e,Cs,{relatedTarget:t}):null;N.trigger(t,xs,{relatedTarget:e}).defaultPrevented||i&&i.defaultPrevented||(this._deactivate(e,t),this._activate(t,e))}_activate(t,e){t&&(t.classList.add(Fs),this._activate(z.getElementFromSelector(t)),this._queueCallback((()=>{"tab"===t.getAttribute("role")?(t.removeAttribute("tabindex"),t.setAttribute("aria-selected",!0),this._toggleDropDown(t,!0),N.trigger(t,ks,{relatedTarget:e})):t.classList.add(Ws)}),t,t.classList.contains(Hs)))}_deactivate(t,e){t&&(t.classList.remove(Fs),t.blur(),this._deactivate(z.getElementFromSelector(t)),this._queueCallback((()=>{"tab"===t.getAttribute("role")?(t.setAttribute("aria-selected",!1),t.setAttribute("tabindex","-1"),this._toggleDropDown(t,!1),N.trigger(t,Os,{relatedTarget:e})):t.classList.remove(Ws)}),t,t.classList.contains(Hs)))}_keydown(t){if(![$s,Is,Ns,Ps,Ms,js].includes(t.key))return;t.stopPropagation(),t.preventDefault();const e=this._getChildren().filter((t=>!l(t)));let i;if([Ms,js].includes(t.key))i=e[t.key===Ms?0:e.length-1];else{const n=[Is,Ps].includes(t.key);i=b(e,t.target,n,!0)}i&&(i.focus({preventScroll:!0}),Vs.getOrCreateInstance(i).show())}_getChildren(){return z.find(Rs,this._parent)}_getActiveElem(){return this._getChildren().find((t=>this._elemIsActive(t)))||null}_setInitialAttributes(t,e){this._setAttributeIfNotExists(t,"role","tablist");for(const t of e)this._setInitialAttributesOnChild(t)}_setInitialAttributesOnChild(t){t=this._getInnerElement(t);const e=this._elemIsActive(t),i=this._getOuterElement(t);t.setAttribute("aria-selected",e),i!==t&&this._setAttributeIfNotExists(i,"role","presentation"),e||t.setAttribute("tabindex","-1"),this._setAttributeIfNotExists(t,"role","tab"),this._setInitialAttributesOnTargetPanel(t)}_setInitialAttributesOnTargetPanel(t){const e=z.getElementFromSelector(t);e&&(this._setAttributeIfNotExists(e,"role","tabpanel"),t.id&&this._setAttributeIfNotExists(e,"aria-labelledby",`${t.id}`))}_toggleDropDown(t,e){const i=this._getOuterElement(t);if(!i.classList.contains("dropdown"))return;const n=(t,n)=>{const s=z.findOne(t,i);s&&s.classList.toggle(n,e)};n(".dropdown-toggle",Fs),n(".dropdown-menu",Ws),i.setAttribute("aria-expanded",e)}_setAttributeIfNotExists(t,e,i){t.hasAttribute(e)||t.setAttribute(e,i)}_elemIsActive(t){return t.classList.contains(Fs)}_getInnerElement(t){return t.matches(Rs)?t:z.findOne(Rs,t)}_getOuterElement(t){return t.closest(".nav-item, .list-group-item")||t}static jQueryInterface(t){return this.each((function(){const e=Vs.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}))}}N.on(document,Ls,zs,(function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),l(this)||Vs.getOrCreateInstance(this).show()})),N.on(window,Ds,(()=>{for(const t of z.find(qs))Vs.getOrCreateInstance(t)})),m(Vs);const Ks=".bs.toast",Qs=`mouseover${Ks}`,Xs=`mouseout${Ks}`,Ys=`focusin${Ks}`,Us=`focusout${Ks}`,Gs=`hide${Ks}`,Js=`hidden${Ks}`,Zs=`show${Ks}`,to=`shown${Ks}`,eo="hide",io="show",no="showing",so={animation:"boolean",autohide:"boolean",delay:"number"},oo={animation:!0,autohide:!0,delay:5e3};class ro extends W{constructor(t,e){super(t,e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get Default(){return oo}static get DefaultType(){return so}static get NAME(){return"toast"}show(){N.trigger(this._element,Zs).defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add("fade"),this._element.classList.remove(eo),d(this._element),this._element.classList.add(io,no),this._queueCallback((()=>{this._element.classList.remove(no),N.trigger(this._element,to),this._maybeScheduleHide()}),this._element,this._config.animation))}hide(){this.isShown()&&(N.trigger(this._element,Gs).defaultPrevented||(this._element.classList.add(no),this._queueCallback((()=>{this._element.classList.add(eo),this._element.classList.remove(no,io),N.trigger(this._element,Js)}),this._element,this._config.animation)))}dispose(){this._clearTimeout(),this.isShown()&&this._element.classList.remove(io),super.dispose()}isShown(){return this._element.classList.contains(io)}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout((()=>{this.hide()}),this._config.delay)))}_onInteraction(t,e){switch(t.type){case"mouseover":case"mouseout":this._hasMouseInteraction=e;break;case"focusin":case"focusout":this._hasKeyboardInteraction=e}if(e)return void this._clearTimeout();const i=t.relatedTarget;this._element===i||this._element.contains(i)||this._maybeScheduleHide()}_setListeners(){N.on(this._element,Qs,(t=>this._onInteraction(t,!0))),N.on(this._element,Xs,(t=>this._onInteraction(t,!1))),N.on(this._element,Ys,(t=>this._onInteraction(t,!0))),N.on(this._element,Us,(t=>this._onInteraction(t,!1)))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each((function(){const e=ro.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}return R(ro),m(ro),{Alert:Q,Button:Y,Carousel:xt,Collapse:Bt,Dropdown:qi,Modal:On,Offcanvas:qn,Popover:us,ScrollSpy:Es,Tab:Vs,Toast:ro,Tooltip:cs}})); +//# sourceMappingURL=bootstrap.bundle.min.js.map \ No newline at end of file diff --git a/docs/deps/bootstrap-5.3.1/bootstrap.bundle.min.js.map b/docs/deps/bootstrap-5.3.1/bootstrap.bundle.min.js.map new file mode 100644 index 00000000..3863da8b --- /dev/null +++ b/docs/deps/bootstrap-5.3.1/bootstrap.bundle.min.js.map @@ -0,0 +1 @@ +{"version":3,"names":["elementMap","Map","Data","set","element","key","instance","has","instanceMap","get","size","console","error","Array","from","keys","remove","delete","TRANSITION_END","parseSelector","selector","window","CSS","escape","replace","match","id","triggerTransitionEnd","dispatchEvent","Event","isElement","object","jquery","nodeType","getElement","length","document","querySelector","isVisible","getClientRects","elementIsVisible","getComputedStyle","getPropertyValue","closedDetails","closest","summary","parentNode","isDisabled","Node","ELEMENT_NODE","classList","contains","disabled","hasAttribute","getAttribute","findShadowRoot","documentElement","attachShadow","getRootNode","root","ShadowRoot","noop","reflow","offsetHeight","getjQuery","jQuery","body","DOMContentLoadedCallbacks","isRTL","dir","defineJQueryPlugin","plugin","callback","$","name","NAME","JQUERY_NO_CONFLICT","fn","jQueryInterface","Constructor","noConflict","readyState","addEventListener","push","execute","possibleCallback","args","defaultValue","executeAfterTransition","transitionElement","waitForTransition","emulatedDuration","transitionDuration","transitionDelay","floatTransitionDuration","Number","parseFloat","floatTransitionDelay","split","getTransitionDurationFromElement","called","handler","target","removeEventListener","setTimeout","getNextActiveElement","list","activeElement","shouldGetNext","isCycleAllowed","listLength","index","indexOf","Math","max","min","namespaceRegex","stripNameRegex","stripUidRegex","eventRegistry","uidEvent","customEvents","mouseenter","mouseleave","nativeEvents","Set","makeEventUid","uid","getElementEvents","findHandler","events","callable","delegationSelector","Object","values","find","event","normalizeParameters","originalTypeEvent","delegationFunction","isDelegated","typeEvent","getTypeEvent","addHandler","oneOff","wrapFunction","relatedTarget","delegateTarget","call","this","handlers","previousFunction","domElements","querySelectorAll","domElement","hydrateObj","EventHandler","off","type","apply","bootstrapDelegationHandler","bootstrapHandler","removeHandler","Boolean","removeNamespacedHandlers","namespace","storeElementEvent","handlerKey","entries","includes","on","one","inNamespace","isNamespace","startsWith","elementEvent","slice","keyHandlers","trigger","jQueryEvent","bubbles","nativeDispatch","defaultPrevented","isPropagationStopped","isImmediatePropagationStopped","isDefaultPrevented","evt","cancelable","preventDefault","obj","meta","value","_unused","defineProperty","configurable","normalizeData","toString","JSON","parse","decodeURIComponent","normalizeDataKey","chr","toLowerCase","Manipulator","setDataAttribute","setAttribute","removeDataAttribute","removeAttribute","getDataAttributes","attributes","bsKeys","dataset","filter","pureKey","charAt","getDataAttribute","Config","Default","DefaultType","Error","_getConfig","config","_mergeConfigObj","_configAfterMerge","_typeCheckConfig","jsonConfig","constructor","configTypes","property","expectedTypes","valueType","prototype","RegExp","test","TypeError","toUpperCase","BaseComponent","super","_element","_config","DATA_KEY","dispose","EVENT_KEY","propertyName","getOwnPropertyNames","_queueCallback","isAnimated","getInstance","getOrCreateInstance","VERSION","eventName","getSelector","hrefAttribute","trim","SelectorEngine","concat","Element","findOne","children","child","matches","parents","ancestor","prev","previous","previousElementSibling","next","nextElementSibling","focusableChildren","focusables","map","join","el","getSelectorFromElement","getElementFromSelector","getMultipleElementsFromSelector","enableDismissTrigger","component","method","clickEvent","tagName","EVENT_CLOSE","EVENT_CLOSED","Alert","close","_destroyElement","each","data","undefined","SELECTOR_DATA_TOGGLE","Button","toggle","button","EVENT_TOUCHSTART","EVENT_TOUCHMOVE","EVENT_TOUCHEND","EVENT_POINTERDOWN","EVENT_POINTERUP","endCallback","leftCallback","rightCallback","Swipe","isSupported","_deltaX","_supportPointerEvents","PointerEvent","_initEvents","_start","_eventIsPointerPenTouch","clientX","touches","_end","_handleSwipe","_move","absDeltaX","abs","direction","add","pointerType","navigator","maxTouchPoints","DATA_API_KEY","ORDER_NEXT","ORDER_PREV","DIRECTION_LEFT","DIRECTION_RIGHT","EVENT_SLIDE","EVENT_SLID","EVENT_KEYDOWN","EVENT_MOUSEENTER","EVENT_MOUSELEAVE","EVENT_DRAG_START","EVENT_LOAD_DATA_API","EVENT_CLICK_DATA_API","CLASS_NAME_CAROUSEL","CLASS_NAME_ACTIVE","SELECTOR_ACTIVE","SELECTOR_ITEM","SELECTOR_ACTIVE_ITEM","KEY_TO_DIRECTION","ArrowLeft","ArrowRight","interval","keyboard","pause","ride","touch","wrap","Carousel","_interval","_activeElement","_isSliding","touchTimeout","_swipeHelper","_indicatorsElement","_addEventListeners","cycle","_slide","nextWhenVisible","hidden","_clearInterval","_updateInterval","setInterval","_maybeEnableCycle","to","items","_getItems","activeIndex","_getItemIndex","_getActive","order","defaultInterval","_keydown","_addTouchEventListeners","img","swipeConfig","_directionToOrder","endCallBack","clearTimeout","_setActiveIndicatorElement","activeIndicator","newActiveIndicator","elementInterval","parseInt","isNext","nextElement","nextElementIndex","triggerEvent","_orderToDirection","isCycling","directionalClassName","orderClassName","completeCallBack","_isAnimated","clearInterval","carousel","slideIndex","carousels","EVENT_SHOW","EVENT_SHOWN","EVENT_HIDE","EVENT_HIDDEN","CLASS_NAME_SHOW","CLASS_NAME_COLLAPSE","CLASS_NAME_COLLAPSING","CLASS_NAME_DEEPER_CHILDREN","parent","Collapse","_isTransitioning","_triggerArray","toggleList","elem","filterElement","foundElement","_initializeChildren","_addAriaAndCollapsedClass","_isShown","hide","show","activeChildren","_getFirstLevelChildren","activeInstance","dimension","_getDimension","style","scrollSize","complete","getBoundingClientRect","selected","triggerArray","isOpen","top","bottom","right","left","auto","basePlacements","start","end","clippingParents","viewport","popper","reference","variationPlacements","reduce","acc","placement","placements","beforeRead","read","afterRead","beforeMain","main","afterMain","beforeWrite","write","afterWrite","modifierPhases","getNodeName","nodeName","getWindow","node","ownerDocument","defaultView","isHTMLElement","HTMLElement","isShadowRoot","applyStyles$1","enabled","phase","_ref","state","elements","forEach","styles","assign","effect","_ref2","initialStyles","position","options","strategy","margin","arrow","hasOwnProperty","attribute","requires","getBasePlacement","round","getUAString","uaData","userAgentData","brands","isArray","item","brand","version","userAgent","isLayoutViewport","includeScale","isFixedStrategy","clientRect","scaleX","scaleY","offsetWidth","width","height","visualViewport","addVisualOffsets","x","offsetLeft","y","offsetTop","getLayoutRect","rootNode","isSameNode","host","isTableElement","getDocumentElement","getParentNode","assignedSlot","getTrueOffsetParent","offsetParent","getOffsetParent","isFirefox","currentNode","css","transform","perspective","contain","willChange","getContainingBlock","getMainAxisFromPlacement","within","mathMax","mathMin","mergePaddingObject","paddingObject","expandToHashMap","hashMap","arrow$1","_state$modifiersData$","arrowElement","popperOffsets","modifiersData","basePlacement","axis","len","padding","rects","toPaddingObject","arrowRect","minProp","maxProp","endDiff","startDiff","arrowOffsetParent","clientSize","clientHeight","clientWidth","centerToReference","center","offset","axisProp","centerOffset","_options$element","requiresIfExists","getVariation","unsetSides","mapToStyles","_Object$assign2","popperRect","variation","offsets","gpuAcceleration","adaptive","roundOffsets","isFixed","_offsets$x","_offsets$y","_ref3","hasX","hasY","sideX","sideY","win","heightProp","widthProp","_Object$assign","commonStyles","_ref4","dpr","devicePixelRatio","roundOffsetsByDPR","computeStyles$1","_ref5","_options$gpuAccelerat","_options$adaptive","_options$roundOffsets","passive","eventListeners","_options$scroll","scroll","_options$resize","resize","scrollParents","scrollParent","update","hash","getOppositePlacement","matched","getOppositeVariationPlacement","getWindowScroll","scrollLeft","pageXOffset","scrollTop","pageYOffset","getWindowScrollBarX","isScrollParent","_getComputedStyle","overflow","overflowX","overflowY","getScrollParent","listScrollParents","_element$ownerDocumen","isBody","updatedList","rectToClientRect","rect","getClientRectFromMixedType","clippingParent","html","layoutViewport","getViewportRect","clientTop","clientLeft","getInnerBoundingClientRect","winScroll","scrollWidth","scrollHeight","getDocumentRect","computeOffsets","commonX","commonY","mainAxis","detectOverflow","_options","_options$placement","_options$strategy","_options$boundary","boundary","_options$rootBoundary","rootBoundary","_options$elementConte","elementContext","_options$altBoundary","altBoundary","_options$padding","altContext","clippingClientRect","mainClippingParents","clipperElement","getClippingParents","firstClippingParent","clippingRect","accRect","getClippingRect","contextElement","referenceClientRect","popperClientRect","elementClientRect","overflowOffsets","offsetData","multiply","computeAutoPlacement","flipVariations","_options$allowedAutoP","allowedAutoPlacements","allPlacements","allowedPlacements","overflows","sort","a","b","flip$1","_skip","_options$mainAxis","checkMainAxis","_options$altAxis","altAxis","checkAltAxis","specifiedFallbackPlacements","fallbackPlacements","_options$flipVariatio","preferredPlacement","oppositePlacement","getExpandedFallbackPlacements","referenceRect","checksMap","makeFallbackChecks","firstFittingPlacement","i","_basePlacement","isStartVariation","isVertical","mainVariationSide","altVariationSide","checks","every","check","_loop","_i","fittingPlacement","reset","getSideOffsets","preventedOffsets","isAnySideFullyClipped","some","side","hide$1","preventOverflow","referenceOverflow","popperAltOverflow","referenceClippingOffsets","popperEscapeOffsets","isReferenceHidden","hasPopperEscaped","offset$1","_options$offset","invertDistance","skidding","distance","distanceAndSkiddingToXY","_data$state$placement","popperOffsets$1","preventOverflow$1","_options$tether","tether","_options$tetherOffset","tetherOffset","isBasePlacement","tetherOffsetValue","normalizedTetherOffsetValue","offsetModifierState","_offsetModifierState$","mainSide","altSide","additive","minLen","maxLen","arrowPaddingObject","arrowPaddingMin","arrowPaddingMax","arrowLen","minOffset","maxOffset","clientOffset","offsetModifierValue","tetherMax","preventedOffset","_offsetModifierState$2","_mainSide","_altSide","_offset","_len","_min","_max","isOriginSide","_offsetModifierValue","_tetherMin","_tetherMax","_preventedOffset","v","withinMaxClamp","getCompositeRect","elementOrVirtualElement","isOffsetParentAnElement","offsetParentIsScaled","isElementScaled","modifiers","visited","result","modifier","dep","depModifier","DEFAULT_OPTIONS","areValidElements","arguments","_key","popperGenerator","generatorOptions","_generatorOptions","_generatorOptions$def","defaultModifiers","_generatorOptions$def2","defaultOptions","pending","orderedModifiers","effectCleanupFns","isDestroyed","setOptions","setOptionsAction","cleanupModifierEffects","merged","orderModifiers","current","existing","m","_ref$options","cleanupFn","forceUpdate","_state$elements","_state$orderedModifie","_state$orderedModifie2","Promise","resolve","then","destroy","onFirstUpdate","createPopper","computeStyles","applyStyles","flip","ARROW_UP_KEY","ARROW_DOWN_KEY","EVENT_KEYDOWN_DATA_API","EVENT_KEYUP_DATA_API","SELECTOR_DATA_TOGGLE_SHOWN","SELECTOR_MENU","PLACEMENT_TOP","PLACEMENT_TOPEND","PLACEMENT_BOTTOM","PLACEMENT_BOTTOMEND","PLACEMENT_RIGHT","PLACEMENT_LEFT","autoClose","display","popperConfig","Dropdown","_popper","_parent","_menu","_inNavbar","_detectNavbar","_createPopper","focus","_completeHide","Popper","referenceElement","_getPopperConfig","_getPlacement","parentDropdown","isEnd","_getOffset","popperData","defaultBsPopperConfig","_selectMenuItem","clearMenus","openToggles","context","composedPath","isMenuTarget","dataApiKeydownHandler","isInput","isEscapeEvent","isUpOrDownEvent","getToggleButton","stopPropagation","EVENT_MOUSEDOWN","className","clickCallback","rootElement","Backdrop","_isAppended","_append","_getElement","_emulateAnimation","backdrop","createElement","append","EVENT_FOCUSIN","EVENT_KEYDOWN_TAB","TAB_NAV_BACKWARD","autofocus","trapElement","FocusTrap","_isActive","_lastTabNavDirection","activate","_handleFocusin","_handleKeydown","deactivate","shiftKey","SELECTOR_FIXED_CONTENT","SELECTOR_STICKY_CONTENT","PROPERTY_PADDING","PROPERTY_MARGIN","ScrollBarHelper","getWidth","documentWidth","innerWidth","_disableOverFlow","_setElementAttributes","calculatedValue","_resetElementAttributes","isOverflowing","_saveInitialAttribute","styleProperty","scrollbarWidth","_applyManipulationCallback","setProperty","actualValue","removeProperty","callBack","sel","EVENT_HIDE_PREVENTED","EVENT_RESIZE","EVENT_CLICK_DISMISS","EVENT_MOUSEDOWN_DISMISS","EVENT_KEYDOWN_DISMISS","CLASS_NAME_OPEN","CLASS_NAME_STATIC","Modal","_dialog","_backdrop","_initializeBackDrop","_focustrap","_initializeFocusTrap","_scrollBar","_adjustDialog","_showElement","_hideModal","handleUpdate","modalBody","transitionComplete","_triggerBackdropTransition","event2","_resetAdjustments","isModalOverflowing","initialOverflowY","isBodyOverflowing","paddingLeft","paddingRight","showEvent","alreadyOpen","CLASS_NAME_SHOWING","CLASS_NAME_HIDING","OPEN_SELECTOR","Offcanvas","blur","completeCallback","DefaultAllowlist","area","br","col","code","div","em","hr","h1","h2","h3","h4","h5","h6","li","ol","p","pre","s","small","span","sub","sup","strong","u","ul","uriAttributes","SAFE_URL_PATTERN","allowedAttribute","allowedAttributeList","attributeName","nodeValue","attributeRegex","regex","allowList","content","extraClass","sanitize","sanitizeFn","template","DefaultContentType","entry","TemplateFactory","getContent","_resolvePossibleFunction","hasContent","changeContent","_checkContent","toHtml","templateWrapper","innerHTML","_maybeSanitize","text","_setContent","arg","templateElement","_putElementInTemplate","textContent","unsafeHtml","sanitizeFunction","createdDocument","DOMParser","parseFromString","elementName","attributeList","allowedAttributes","sanitizeHtml","DISALLOWED_ATTRIBUTES","CLASS_NAME_FADE","SELECTOR_MODAL","EVENT_MODAL_HIDE","TRIGGER_HOVER","TRIGGER_FOCUS","AttachmentMap","AUTO","TOP","RIGHT","BOTTOM","LEFT","animation","container","customClass","delay","title","Tooltip","_isEnabled","_timeout","_isHovered","_activeTrigger","_templateFactory","_newContent","tip","_setListeners","_fixTitle","enable","disable","toggleEnabled","click","_leave","_enter","_hideModalHandler","_disposePopper","_isWithContent","isInTheDom","_getTipElement","_isWithActiveTrigger","_getTitle","_createTipElement","_getContentForTemplate","_getTemplateFactory","tipId","prefix","floor","random","getElementById","getUID","setContent","_initializeOnDelegatedTarget","_getDelegateConfig","attachment","triggers","eventIn","eventOut","_setTimeout","timeout","dataAttributes","dataAttribute","Popover","_getContent","EVENT_ACTIVATE","EVENT_CLICK","SELECTOR_TARGET_LINKS","SELECTOR_NAV_LINKS","SELECTOR_LINK_ITEMS","rootMargin","smoothScroll","threshold","ScrollSpy","_targetLinks","_observableSections","_rootElement","_activeTarget","_observer","_previousScrollData","visibleEntryTop","parentScrollTop","refresh","_initializeTargetsAndObservables","_maybeEnableSmoothScroll","disconnect","_getNewObserver","section","observe","observableSection","scrollTo","behavior","IntersectionObserver","_observerCallback","targetElement","_process","userScrollsDown","isIntersecting","_clearActiveClass","entryIsLowerThanPrevious","targetLinks","anchor","decodeURI","_activateParents","listGroup","activeNodes","spy","ARROW_LEFT_KEY","ARROW_RIGHT_KEY","HOME_KEY","END_KEY","NOT_SELECTOR_DROPDOWN_TOGGLE","SELECTOR_INNER_ELEM","SELECTOR_DATA_TOGGLE_ACTIVE","Tab","_setInitialAttributes","_getChildren","innerElem","_elemIsActive","active","_getActiveElem","hideEvent","_deactivate","_activate","relatedElem","_toggleDropDown","nextActiveElement","preventScroll","_setAttributeIfNotExists","_setInitialAttributesOnChild","_getInnerElement","isActive","outerElem","_getOuterElement","_setInitialAttributesOnTargetPanel","open","EVENT_MOUSEOVER","EVENT_MOUSEOUT","EVENT_FOCUSOUT","CLASS_NAME_HIDE","autohide","Toast","_hasMouseInteraction","_hasKeyboardInteraction","_clearTimeout","_maybeScheduleHide","isShown","_onInteraction","isInteracting"],"sources":["../../js/src/dom/data.js","../../js/src/util/index.js","../../js/src/dom/event-handler.js","../../js/src/dom/manipulator.js","../../js/src/util/config.js","../../js/src/base-component.js","../../js/src/dom/selector-engine.js","../../js/src/util/component-functions.js","../../js/src/alert.js","../../js/src/button.js","../../js/src/util/swipe.js","../../js/src/carousel.js","../../js/src/collapse.js","../../node_modules/@popperjs/core/lib/enums.js","../../node_modules/@popperjs/core/lib/dom-utils/getNodeName.js","../../node_modules/@popperjs/core/lib/dom-utils/getWindow.js","../../node_modules/@popperjs/core/lib/dom-utils/instanceOf.js","../../node_modules/@popperjs/core/lib/modifiers/applyStyles.js","../../node_modules/@popperjs/core/lib/utils/getBasePlacement.js","../../node_modules/@popperjs/core/lib/utils/math.js","../../node_modules/@popperjs/core/lib/utils/userAgent.js","../../node_modules/@popperjs/core/lib/dom-utils/isLayoutViewport.js","../../node_modules/@popperjs/core/lib/dom-utils/getBoundingClientRect.js","../../node_modules/@popperjs/core/lib/dom-utils/getLayoutRect.js","../../node_modules/@popperjs/core/lib/dom-utils/contains.js","../../node_modules/@popperjs/core/lib/dom-utils/getComputedStyle.js","../../node_modules/@popperjs/core/lib/dom-utils/isTableElement.js","../../node_modules/@popperjs/core/lib/dom-utils/getDocumentElement.js","../../node_modules/@popperjs/core/lib/dom-utils/getParentNode.js","../../node_modules/@popperjs/core/lib/dom-utils/getOffsetParent.js","../../node_modules/@popperjs/core/lib/utils/getMainAxisFromPlacement.js","../../node_modules/@popperjs/core/lib/utils/within.js","../../node_modules/@popperjs/core/lib/utils/mergePaddingObject.js","../../node_modules/@popperjs/core/lib/utils/getFreshSideObject.js","../../node_modules/@popperjs/core/lib/utils/expandToHashMap.js","../../node_modules/@popperjs/core/lib/modifiers/arrow.js","../../node_modules/@popperjs/core/lib/utils/getVariation.js","../../node_modules/@popperjs/core/lib/modifiers/computeStyles.js","../../node_modules/@popperjs/core/lib/modifiers/eventListeners.js","../../node_modules/@popperjs/core/lib/utils/getOppositePlacement.js","../../node_modules/@popperjs/core/lib/utils/getOppositeVariationPlacement.js","../../node_modules/@popperjs/core/lib/dom-utils/getWindowScroll.js","../../node_modules/@popperjs/core/lib/dom-utils/getWindowScrollBarX.js","../../node_modules/@popperjs/core/lib/dom-utils/isScrollParent.js","../../node_modules/@popperjs/core/lib/dom-utils/getScrollParent.js","../../node_modules/@popperjs/core/lib/dom-utils/listScrollParents.js","../../node_modules/@popperjs/core/lib/utils/rectToClientRect.js","../../node_modules/@popperjs/core/lib/dom-utils/getClippingRect.js","../../node_modules/@popperjs/core/lib/dom-utils/getViewportRect.js","../../node_modules/@popperjs/core/lib/dom-utils/getDocumentRect.js","../../node_modules/@popperjs/core/lib/utils/computeOffsets.js","../../node_modules/@popperjs/core/lib/utils/detectOverflow.js","../../node_modules/@popperjs/core/lib/utils/computeAutoPlacement.js","../../node_modules/@popperjs/core/lib/modifiers/flip.js","../../node_modules/@popperjs/core/lib/modifiers/hide.js","../../node_modules/@popperjs/core/lib/modifiers/offset.js","../../node_modules/@popperjs/core/lib/modifiers/popperOffsets.js","../../node_modules/@popperjs/core/lib/modifiers/preventOverflow.js","../../node_modules/@popperjs/core/lib/utils/getAltAxis.js","../../node_modules/@popperjs/core/lib/dom-utils/getCompositeRect.js","../../node_modules/@popperjs/core/lib/dom-utils/getNodeScroll.js","../../node_modules/@popperjs/core/lib/dom-utils/getHTMLElementScroll.js","../../node_modules/@popperjs/core/lib/utils/orderModifiers.js","../../node_modules/@popperjs/core/lib/createPopper.js","../../node_modules/@popperjs/core/lib/utils/debounce.js","../../node_modules/@popperjs/core/lib/utils/mergeByName.js","../../node_modules/@popperjs/core/lib/popper-lite.js","../../node_modules/@popperjs/core/lib/popper.js","../../js/src/dropdown.js","../../js/src/util/backdrop.js","../../js/src/util/focustrap.js","../../js/src/util/scrollbar.js","../../js/src/modal.js","../../js/src/offcanvas.js","../../js/src/util/sanitizer.js","../../js/src/util/template-factory.js","../../js/src/tooltip.js","../../js/src/popover.js","../../js/src/scrollspy.js","../../js/src/tab.js","../../js/src/toast.js","../../js/index.umd.js"],"sourcesContent":["/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/data.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n/**\n * Constants\n */\n\nconst elementMap = new Map()\n\nexport default {\n set(element, key, instance) {\n if (!elementMap.has(element)) {\n elementMap.set(element, new Map())\n }\n\n const instanceMap = elementMap.get(element)\n\n // make it clear we only want one instance per element\n // can be removed later when multiple key/instances are fine to be used\n if (!instanceMap.has(key) && instanceMap.size !== 0) {\n // eslint-disable-next-line no-console\n console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(instanceMap.keys())[0]}.`)\n return\n }\n\n instanceMap.set(key, instance)\n },\n\n get(element, key) {\n if (elementMap.has(element)) {\n return elementMap.get(element).get(key) || null\n }\n\n return null\n },\n\n remove(element, key) {\n if (!elementMap.has(element)) {\n return\n }\n\n const instanceMap = elementMap.get(element)\n\n instanceMap.delete(key)\n\n // free up element references if there are no instances left for an element\n if (instanceMap.size === 0) {\n elementMap.delete(element)\n }\n }\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/index.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nconst MAX_UID = 1_000_000\nconst MILLISECONDS_MULTIPLIER = 1000\nconst TRANSITION_END = 'transitionend'\n\n/**\n * Properly escape IDs selectors to handle weird IDs\n * @param {string} selector\n * @returns {string}\n */\nconst parseSelector = selector => {\n if (selector && window.CSS && window.CSS.escape) {\n // document.querySelector needs escaping to handle IDs (html5+) containing for instance /\n selector = selector.replace(/#([^\\s\"#']+)/g, (match, id) => `#${CSS.escape(id)}`)\n }\n\n return selector\n}\n\n// Shout-out Angus Croll (https://goo.gl/pxwQGp)\nconst toType = object => {\n if (object === null || object === undefined) {\n return `${object}`\n }\n\n return Object.prototype.toString.call(object).match(/\\s([a-z]+)/i)[1].toLowerCase()\n}\n\n/**\n * Public Util API\n */\n\nconst getUID = prefix => {\n do {\n prefix += Math.floor(Math.random() * MAX_UID)\n } while (document.getElementById(prefix))\n\n return prefix\n}\n\nconst getTransitionDurationFromElement = element => {\n if (!element) {\n return 0\n }\n\n // Get transition-duration of the element\n let { transitionDuration, transitionDelay } = window.getComputedStyle(element)\n\n const floatTransitionDuration = Number.parseFloat(transitionDuration)\n const floatTransitionDelay = Number.parseFloat(transitionDelay)\n\n // Return 0 if element or transition duration is not found\n if (!floatTransitionDuration && !floatTransitionDelay) {\n return 0\n }\n\n // If multiple durations are defined, take the first\n transitionDuration = transitionDuration.split(',')[0]\n transitionDelay = transitionDelay.split(',')[0]\n\n return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER\n}\n\nconst triggerTransitionEnd = element => {\n element.dispatchEvent(new Event(TRANSITION_END))\n}\n\nconst isElement = object => {\n if (!object || typeof object !== 'object') {\n return false\n }\n\n if (typeof object.jquery !== 'undefined') {\n object = object[0]\n }\n\n return typeof object.nodeType !== 'undefined'\n}\n\nconst getElement = object => {\n // it's a jQuery object or a node element\n if (isElement(object)) {\n return object.jquery ? object[0] : object\n }\n\n if (typeof object === 'string' && object.length > 0) {\n return document.querySelector(parseSelector(object))\n }\n\n return null\n}\n\nconst isVisible = element => {\n if (!isElement(element) || element.getClientRects().length === 0) {\n return false\n }\n\n const elementIsVisible = getComputedStyle(element).getPropertyValue('visibility') === 'visible'\n // Handle `details` element as its content may falsie appear visible when it is closed\n const closedDetails = element.closest('details:not([open])')\n\n if (!closedDetails) {\n return elementIsVisible\n }\n\n if (closedDetails !== element) {\n const summary = element.closest('summary')\n if (summary && summary.parentNode !== closedDetails) {\n return false\n }\n\n if (summary === null) {\n return false\n }\n }\n\n return elementIsVisible\n}\n\nconst isDisabled = element => {\n if (!element || element.nodeType !== Node.ELEMENT_NODE) {\n return true\n }\n\n if (element.classList.contains('disabled')) {\n return true\n }\n\n if (typeof element.disabled !== 'undefined') {\n return element.disabled\n }\n\n return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false'\n}\n\nconst findShadowRoot = element => {\n if (!document.documentElement.attachShadow) {\n return null\n }\n\n // Can find the shadow root otherwise it'll return the document\n if (typeof element.getRootNode === 'function') {\n const root = element.getRootNode()\n return root instanceof ShadowRoot ? root : null\n }\n\n if (element instanceof ShadowRoot) {\n return element\n }\n\n // when we don't find a shadow root\n if (!element.parentNode) {\n return null\n }\n\n return findShadowRoot(element.parentNode)\n}\n\nconst noop = () => {}\n\n/**\n * Trick to restart an element's animation\n *\n * @param {HTMLElement} element\n * @return void\n *\n * @see https://www.charistheo.io/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation\n */\nconst reflow = element => {\n element.offsetHeight // eslint-disable-line no-unused-expressions\n}\n\nconst getjQuery = () => {\n if (window.jQuery && !document.body.hasAttribute('data-bs-no-jquery')) {\n return window.jQuery\n }\n\n return null\n}\n\nconst DOMContentLoadedCallbacks = []\n\nconst onDOMContentLoaded = callback => {\n if (document.readyState === 'loading') {\n // add listener on the first call when the document is in loading state\n if (!DOMContentLoadedCallbacks.length) {\n document.addEventListener('DOMContentLoaded', () => {\n for (const callback of DOMContentLoadedCallbacks) {\n callback()\n }\n })\n }\n\n DOMContentLoadedCallbacks.push(callback)\n } else {\n callback()\n }\n}\n\nconst isRTL = () => document.documentElement.dir === 'rtl'\n\nconst defineJQueryPlugin = plugin => {\n onDOMContentLoaded(() => {\n const $ = getjQuery()\n /* istanbul ignore if */\n if ($) {\n const name = plugin.NAME\n const JQUERY_NO_CONFLICT = $.fn[name]\n $.fn[name] = plugin.jQueryInterface\n $.fn[name].Constructor = plugin\n $.fn[name].noConflict = () => {\n $.fn[name] = JQUERY_NO_CONFLICT\n return plugin.jQueryInterface\n }\n }\n })\n}\n\nconst execute = (possibleCallback, args = [], defaultValue = possibleCallback) => {\n return typeof possibleCallback === 'function' ? possibleCallback(...args) : defaultValue\n}\n\nconst executeAfterTransition = (callback, transitionElement, waitForTransition = true) => {\n if (!waitForTransition) {\n execute(callback)\n return\n }\n\n const durationPadding = 5\n const emulatedDuration = getTransitionDurationFromElement(transitionElement) + durationPadding\n\n let called = false\n\n const handler = ({ target }) => {\n if (target !== transitionElement) {\n return\n }\n\n called = true\n transitionElement.removeEventListener(TRANSITION_END, handler)\n execute(callback)\n }\n\n transitionElement.addEventListener(TRANSITION_END, handler)\n setTimeout(() => {\n if (!called) {\n triggerTransitionEnd(transitionElement)\n }\n }, emulatedDuration)\n}\n\n/**\n * Return the previous/next element of a list.\n *\n * @param {array} list The list of elements\n * @param activeElement The active element\n * @param shouldGetNext Choose to get next or previous element\n * @param isCycleAllowed\n * @return {Element|elem} The proper element\n */\nconst getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => {\n const listLength = list.length\n let index = list.indexOf(activeElement)\n\n // if the element does not exist in the list return an element\n // depending on the direction and if cycle is allowed\n if (index === -1) {\n return !shouldGetNext && isCycleAllowed ? list[listLength - 1] : list[0]\n }\n\n index += shouldGetNext ? 1 : -1\n\n if (isCycleAllowed) {\n index = (index + listLength) % listLength\n }\n\n return list[Math.max(0, Math.min(index, listLength - 1))]\n}\n\nexport {\n defineJQueryPlugin,\n execute,\n executeAfterTransition,\n findShadowRoot,\n getElement,\n getjQuery,\n getNextActiveElement,\n getTransitionDurationFromElement,\n getUID,\n isDisabled,\n isElement,\n isRTL,\n isVisible,\n noop,\n onDOMContentLoaded,\n parseSelector,\n reflow,\n triggerTransitionEnd,\n toType\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/event-handler.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { getjQuery } from '../util/index.js'\n\n/**\n * Constants\n */\n\nconst namespaceRegex = /[^.]*(?=\\..*)\\.|.*/\nconst stripNameRegex = /\\..*/\nconst stripUidRegex = /::\\d+$/\nconst eventRegistry = {} // Events storage\nlet uidEvent = 1\nconst customEvents = {\n mouseenter: 'mouseover',\n mouseleave: 'mouseout'\n}\n\nconst nativeEvents = new Set([\n 'click',\n 'dblclick',\n 'mouseup',\n 'mousedown',\n 'contextmenu',\n 'mousewheel',\n 'DOMMouseScroll',\n 'mouseover',\n 'mouseout',\n 'mousemove',\n 'selectstart',\n 'selectend',\n 'keydown',\n 'keypress',\n 'keyup',\n 'orientationchange',\n 'touchstart',\n 'touchmove',\n 'touchend',\n 'touchcancel',\n 'pointerdown',\n 'pointermove',\n 'pointerup',\n 'pointerleave',\n 'pointercancel',\n 'gesturestart',\n 'gesturechange',\n 'gestureend',\n 'focus',\n 'blur',\n 'change',\n 'reset',\n 'select',\n 'submit',\n 'focusin',\n 'focusout',\n 'load',\n 'unload',\n 'beforeunload',\n 'resize',\n 'move',\n 'DOMContentLoaded',\n 'readystatechange',\n 'error',\n 'abort',\n 'scroll'\n])\n\n/**\n * Private methods\n */\n\nfunction makeEventUid(element, uid) {\n return (uid && `${uid}::${uidEvent++}`) || element.uidEvent || uidEvent++\n}\n\nfunction getElementEvents(element) {\n const uid = makeEventUid(element)\n\n element.uidEvent = uid\n eventRegistry[uid] = eventRegistry[uid] || {}\n\n return eventRegistry[uid]\n}\n\nfunction bootstrapHandler(element, fn) {\n return function handler(event) {\n hydrateObj(event, { delegateTarget: element })\n\n if (handler.oneOff) {\n EventHandler.off(element, event.type, fn)\n }\n\n return fn.apply(element, [event])\n }\n}\n\nfunction bootstrapDelegationHandler(element, selector, fn) {\n return function handler(event) {\n const domElements = element.querySelectorAll(selector)\n\n for (let { target } = event; target && target !== this; target = target.parentNode) {\n for (const domElement of domElements) {\n if (domElement !== target) {\n continue\n }\n\n hydrateObj(event, { delegateTarget: target })\n\n if (handler.oneOff) {\n EventHandler.off(element, event.type, selector, fn)\n }\n\n return fn.apply(target, [event])\n }\n }\n }\n}\n\nfunction findHandler(events, callable, delegationSelector = null) {\n return Object.values(events)\n .find(event => event.callable === callable && event.delegationSelector === delegationSelector)\n}\n\nfunction normalizeParameters(originalTypeEvent, handler, delegationFunction) {\n const isDelegated = typeof handler === 'string'\n // TODO: tooltip passes `false` instead of selector, so we need to check\n const callable = isDelegated ? delegationFunction : (handler || delegationFunction)\n let typeEvent = getTypeEvent(originalTypeEvent)\n\n if (!nativeEvents.has(typeEvent)) {\n typeEvent = originalTypeEvent\n }\n\n return [isDelegated, callable, typeEvent]\n}\n\nfunction addHandler(element, originalTypeEvent, handler, delegationFunction, oneOff) {\n if (typeof originalTypeEvent !== 'string' || !element) {\n return\n }\n\n let [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction)\n\n // in case of mouseenter or mouseleave wrap the handler within a function that checks for its DOM position\n // this prevents the handler from being dispatched the same way as mouseover or mouseout does\n if (originalTypeEvent in customEvents) {\n const wrapFunction = fn => {\n return function (event) {\n if (!event.relatedTarget || (event.relatedTarget !== event.delegateTarget && !event.delegateTarget.contains(event.relatedTarget))) {\n return fn.call(this, event)\n }\n }\n }\n\n callable = wrapFunction(callable)\n }\n\n const events = getElementEvents(element)\n const handlers = events[typeEvent] || (events[typeEvent] = {})\n const previousFunction = findHandler(handlers, callable, isDelegated ? handler : null)\n\n if (previousFunction) {\n previousFunction.oneOff = previousFunction.oneOff && oneOff\n\n return\n }\n\n const uid = makeEventUid(callable, originalTypeEvent.replace(namespaceRegex, ''))\n const fn = isDelegated ?\n bootstrapDelegationHandler(element, handler, callable) :\n bootstrapHandler(element, callable)\n\n fn.delegationSelector = isDelegated ? handler : null\n fn.callable = callable\n fn.oneOff = oneOff\n fn.uidEvent = uid\n handlers[uid] = fn\n\n element.addEventListener(typeEvent, fn, isDelegated)\n}\n\nfunction removeHandler(element, events, typeEvent, handler, delegationSelector) {\n const fn = findHandler(events[typeEvent], handler, delegationSelector)\n\n if (!fn) {\n return\n }\n\n element.removeEventListener(typeEvent, fn, Boolean(delegationSelector))\n delete events[typeEvent][fn.uidEvent]\n}\n\nfunction removeNamespacedHandlers(element, events, typeEvent, namespace) {\n const storeElementEvent = events[typeEvent] || {}\n\n for (const [handlerKey, event] of Object.entries(storeElementEvent)) {\n if (handlerKey.includes(namespace)) {\n removeHandler(element, events, typeEvent, event.callable, event.delegationSelector)\n }\n }\n}\n\nfunction getTypeEvent(event) {\n // allow to get the native events from namespaced events ('click.bs.button' --> 'click')\n event = event.replace(stripNameRegex, '')\n return customEvents[event] || event\n}\n\nconst EventHandler = {\n on(element, event, handler, delegationFunction) {\n addHandler(element, event, handler, delegationFunction, false)\n },\n\n one(element, event, handler, delegationFunction) {\n addHandler(element, event, handler, delegationFunction, true)\n },\n\n off(element, originalTypeEvent, handler, delegationFunction) {\n if (typeof originalTypeEvent !== 'string' || !element) {\n return\n }\n\n const [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction)\n const inNamespace = typeEvent !== originalTypeEvent\n const events = getElementEvents(element)\n const storeElementEvent = events[typeEvent] || {}\n const isNamespace = originalTypeEvent.startsWith('.')\n\n if (typeof callable !== 'undefined') {\n // Simplest case: handler is passed, remove that listener ONLY.\n if (!Object.keys(storeElementEvent).length) {\n return\n }\n\n removeHandler(element, events, typeEvent, callable, isDelegated ? handler : null)\n return\n }\n\n if (isNamespace) {\n for (const elementEvent of Object.keys(events)) {\n removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.slice(1))\n }\n }\n\n for (const [keyHandlers, event] of Object.entries(storeElementEvent)) {\n const handlerKey = keyHandlers.replace(stripUidRegex, '')\n\n if (!inNamespace || originalTypeEvent.includes(handlerKey)) {\n removeHandler(element, events, typeEvent, event.callable, event.delegationSelector)\n }\n }\n },\n\n trigger(element, event, args) {\n if (typeof event !== 'string' || !element) {\n return null\n }\n\n const $ = getjQuery()\n const typeEvent = getTypeEvent(event)\n const inNamespace = event !== typeEvent\n\n let jQueryEvent = null\n let bubbles = true\n let nativeDispatch = true\n let defaultPrevented = false\n\n if (inNamespace && $) {\n jQueryEvent = $.Event(event, args)\n\n $(element).trigger(jQueryEvent)\n bubbles = !jQueryEvent.isPropagationStopped()\n nativeDispatch = !jQueryEvent.isImmediatePropagationStopped()\n defaultPrevented = jQueryEvent.isDefaultPrevented()\n }\n\n const evt = hydrateObj(new Event(event, { bubbles, cancelable: true }), args)\n\n if (defaultPrevented) {\n evt.preventDefault()\n }\n\n if (nativeDispatch) {\n element.dispatchEvent(evt)\n }\n\n if (evt.defaultPrevented && jQueryEvent) {\n jQueryEvent.preventDefault()\n }\n\n return evt\n }\n}\n\nfunction hydrateObj(obj, meta = {}) {\n for (const [key, value] of Object.entries(meta)) {\n try {\n obj[key] = value\n } catch {\n Object.defineProperty(obj, key, {\n configurable: true,\n get() {\n return value\n }\n })\n }\n }\n\n return obj\n}\n\nexport default EventHandler\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/manipulator.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nfunction normalizeData(value) {\n if (value === 'true') {\n return true\n }\n\n if (value === 'false') {\n return false\n }\n\n if (value === Number(value).toString()) {\n return Number(value)\n }\n\n if (value === '' || value === 'null') {\n return null\n }\n\n if (typeof value !== 'string') {\n return value\n }\n\n try {\n return JSON.parse(decodeURIComponent(value))\n } catch {\n return value\n }\n}\n\nfunction normalizeDataKey(key) {\n return key.replace(/[A-Z]/g, chr => `-${chr.toLowerCase()}`)\n}\n\nconst Manipulator = {\n setDataAttribute(element, key, value) {\n element.setAttribute(`data-bs-${normalizeDataKey(key)}`, value)\n },\n\n removeDataAttribute(element, key) {\n element.removeAttribute(`data-bs-${normalizeDataKey(key)}`)\n },\n\n getDataAttributes(element) {\n if (!element) {\n return {}\n }\n\n const attributes = {}\n const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs') && !key.startsWith('bsConfig'))\n\n for (const key of bsKeys) {\n let pureKey = key.replace(/^bs/, '')\n pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1, pureKey.length)\n attributes[pureKey] = normalizeData(element.dataset[key])\n }\n\n return attributes\n },\n\n getDataAttribute(element, key) {\n return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`))\n }\n}\n\nexport default Manipulator\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/config.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Manipulator from '../dom/manipulator.js'\nimport { isElement, toType } from './index.js'\n\n/**\n * Class definition\n */\n\nclass Config {\n // Getters\n static get Default() {\n return {}\n }\n\n static get DefaultType() {\n return {}\n }\n\n static get NAME() {\n throw new Error('You have to implement the static method \"NAME\", for each component!')\n }\n\n _getConfig(config) {\n config = this._mergeConfigObj(config)\n config = this._configAfterMerge(config)\n this._typeCheckConfig(config)\n return config\n }\n\n _configAfterMerge(config) {\n return config\n }\n\n _mergeConfigObj(config, element) {\n const jsonConfig = isElement(element) ? Manipulator.getDataAttribute(element, 'config') : {} // try to parse\n\n return {\n ...this.constructor.Default,\n ...(typeof jsonConfig === 'object' ? jsonConfig : {}),\n ...(isElement(element) ? Manipulator.getDataAttributes(element) : {}),\n ...(typeof config === 'object' ? config : {})\n }\n }\n\n _typeCheckConfig(config, configTypes = this.constructor.DefaultType) {\n for (const [property, expectedTypes] of Object.entries(configTypes)) {\n const value = config[property]\n const valueType = isElement(value) ? 'element' : toType(value)\n\n if (!new RegExp(expectedTypes).test(valueType)) {\n throw new TypeError(\n `${this.constructor.NAME.toUpperCase()}: Option \"${property}\" provided type \"${valueType}\" but expected type \"${expectedTypes}\".`\n )\n }\n }\n }\n}\n\nexport default Config\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap base-component.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Data from './dom/data.js'\nimport EventHandler from './dom/event-handler.js'\nimport Config from './util/config.js'\nimport { executeAfterTransition, getElement } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst VERSION = '5.3.1'\n\n/**\n * Class definition\n */\n\nclass BaseComponent extends Config {\n constructor(element, config) {\n super()\n\n element = getElement(element)\n if (!element) {\n return\n }\n\n this._element = element\n this._config = this._getConfig(config)\n\n Data.set(this._element, this.constructor.DATA_KEY, this)\n }\n\n // Public\n dispose() {\n Data.remove(this._element, this.constructor.DATA_KEY)\n EventHandler.off(this._element, this.constructor.EVENT_KEY)\n\n for (const propertyName of Object.getOwnPropertyNames(this)) {\n this[propertyName] = null\n }\n }\n\n _queueCallback(callback, element, isAnimated = true) {\n executeAfterTransition(callback, element, isAnimated)\n }\n\n _getConfig(config) {\n config = this._mergeConfigObj(config, this._element)\n config = this._configAfterMerge(config)\n this._typeCheckConfig(config)\n return config\n }\n\n // Static\n static getInstance(element) {\n return Data.get(getElement(element), this.DATA_KEY)\n }\n\n static getOrCreateInstance(element, config = {}) {\n return this.getInstance(element) || new this(element, typeof config === 'object' ? config : null)\n }\n\n static get VERSION() {\n return VERSION\n }\n\n static get DATA_KEY() {\n return `bs.${this.NAME}`\n }\n\n static get EVENT_KEY() {\n return `.${this.DATA_KEY}`\n }\n\n static eventName(name) {\n return `${name}${this.EVENT_KEY}`\n }\n}\n\nexport default BaseComponent\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/selector-engine.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { isDisabled, isVisible, parseSelector } from '../util/index.js'\n\nconst getSelector = element => {\n let selector = element.getAttribute('data-bs-target')\n\n if (!selector || selector === '#') {\n let hrefAttribute = element.getAttribute('href')\n\n // The only valid content that could double as a selector are IDs or classes,\n // so everything starting with `#` or `.`. If a \"real\" URL is used as the selector,\n // `document.querySelector` will rightfully complain it is invalid.\n // See https://github.com/twbs/bootstrap/issues/32273\n if (!hrefAttribute || (!hrefAttribute.includes('#') && !hrefAttribute.startsWith('.'))) {\n return null\n }\n\n // Just in case some CMS puts out a full URL with the anchor appended\n if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) {\n hrefAttribute = `#${hrefAttribute.split('#')[1]}`\n }\n\n selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null\n }\n\n return parseSelector(selector)\n}\n\nconst SelectorEngine = {\n find(selector, element = document.documentElement) {\n return [].concat(...Element.prototype.querySelectorAll.call(element, selector))\n },\n\n findOne(selector, element = document.documentElement) {\n return Element.prototype.querySelector.call(element, selector)\n },\n\n children(element, selector) {\n return [].concat(...element.children).filter(child => child.matches(selector))\n },\n\n parents(element, selector) {\n const parents = []\n let ancestor = element.parentNode.closest(selector)\n\n while (ancestor) {\n parents.push(ancestor)\n ancestor = ancestor.parentNode.closest(selector)\n }\n\n return parents\n },\n\n prev(element, selector) {\n let previous = element.previousElementSibling\n\n while (previous) {\n if (previous.matches(selector)) {\n return [previous]\n }\n\n previous = previous.previousElementSibling\n }\n\n return []\n },\n // TODO: this is now unused; remove later along with prev()\n next(element, selector) {\n let next = element.nextElementSibling\n\n while (next) {\n if (next.matches(selector)) {\n return [next]\n }\n\n next = next.nextElementSibling\n }\n\n return []\n },\n\n focusableChildren(element) {\n const focusables = [\n 'a',\n 'button',\n 'input',\n 'textarea',\n 'select',\n 'details',\n '[tabindex]',\n '[contenteditable=\"true\"]'\n ].map(selector => `${selector}:not([tabindex^=\"-\"])`).join(',')\n\n return this.find(focusables, element).filter(el => !isDisabled(el) && isVisible(el))\n },\n\n getSelectorFromElement(element) {\n const selector = getSelector(element)\n\n if (selector) {\n return SelectorEngine.findOne(selector) ? selector : null\n }\n\n return null\n },\n\n getElementFromSelector(element) {\n const selector = getSelector(element)\n\n return selector ? SelectorEngine.findOne(selector) : null\n },\n\n getMultipleElementsFromSelector(element) {\n const selector = getSelector(element)\n\n return selector ? SelectorEngine.find(selector) : []\n }\n}\n\nexport default SelectorEngine\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/component-functions.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport SelectorEngine from '../dom/selector-engine.js'\nimport { isDisabled } from './index.js'\n\nconst enableDismissTrigger = (component, method = 'hide') => {\n const clickEvent = `click.dismiss${component.EVENT_KEY}`\n const name = component.NAME\n\n EventHandler.on(document, clickEvent, `[data-bs-dismiss=\"${name}\"]`, function (event) {\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n if (isDisabled(this)) {\n return\n }\n\n const target = SelectorEngine.getElementFromSelector(this) || this.closest(`.${name}`)\n const instance = component.getOrCreateInstance(target)\n\n // Method argument is left, for Alert and only, as it doesn't implement the 'hide' method\n instance[method]()\n })\n}\n\nexport {\n enableDismissTrigger\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap alert.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport { enableDismissTrigger } from './util/component-functions.js'\nimport { defineJQueryPlugin } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'alert'\nconst DATA_KEY = 'bs.alert'\nconst EVENT_KEY = `.${DATA_KEY}`\n\nconst EVENT_CLOSE = `close${EVENT_KEY}`\nconst EVENT_CLOSED = `closed${EVENT_KEY}`\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\n\n/**\n * Class definition\n */\n\nclass Alert extends BaseComponent {\n // Getters\n static get NAME() {\n return NAME\n }\n\n // Public\n close() {\n const closeEvent = EventHandler.trigger(this._element, EVENT_CLOSE)\n\n if (closeEvent.defaultPrevented) {\n return\n }\n\n this._element.classList.remove(CLASS_NAME_SHOW)\n\n const isAnimated = this._element.classList.contains(CLASS_NAME_FADE)\n this._queueCallback(() => this._destroyElement(), this._element, isAnimated)\n }\n\n // Private\n _destroyElement() {\n this._element.remove()\n EventHandler.trigger(this._element, EVENT_CLOSED)\n this.dispose()\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Alert.getOrCreateInstance(this)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](this)\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nenableDismissTrigger(Alert, 'close')\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Alert)\n\nexport default Alert\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap button.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport { defineJQueryPlugin } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'button'\nconst DATA_KEY = 'bs.button'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst CLASS_NAME_ACTIVE = 'active'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"button\"]'\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\n/**\n * Class definition\n */\n\nclass Button extends BaseComponent {\n // Getters\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle() {\n // Toggle class and sync the `aria-pressed` attribute with the return value of the `.toggle()` method\n this._element.setAttribute('aria-pressed', this._element.classList.toggle(CLASS_NAME_ACTIVE))\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Button.getOrCreateInstance(this)\n\n if (config === 'toggle') {\n data[config]()\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, event => {\n event.preventDefault()\n\n const button = event.target.closest(SELECTOR_DATA_TOGGLE)\n const data = Button.getOrCreateInstance(button)\n\n data.toggle()\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Button)\n\nexport default Button\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/swipe.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport Config from './config.js'\nimport { execute } from './index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'swipe'\nconst EVENT_KEY = '.bs.swipe'\nconst EVENT_TOUCHSTART = `touchstart${EVENT_KEY}`\nconst EVENT_TOUCHMOVE = `touchmove${EVENT_KEY}`\nconst EVENT_TOUCHEND = `touchend${EVENT_KEY}`\nconst EVENT_POINTERDOWN = `pointerdown${EVENT_KEY}`\nconst EVENT_POINTERUP = `pointerup${EVENT_KEY}`\nconst POINTER_TYPE_TOUCH = 'touch'\nconst POINTER_TYPE_PEN = 'pen'\nconst CLASS_NAME_POINTER_EVENT = 'pointer-event'\nconst SWIPE_THRESHOLD = 40\n\nconst Default = {\n endCallback: null,\n leftCallback: null,\n rightCallback: null\n}\n\nconst DefaultType = {\n endCallback: '(function|null)',\n leftCallback: '(function|null)',\n rightCallback: '(function|null)'\n}\n\n/**\n * Class definition\n */\n\nclass Swipe extends Config {\n constructor(element, config) {\n super()\n this._element = element\n\n if (!element || !Swipe.isSupported()) {\n return\n }\n\n this._config = this._getConfig(config)\n this._deltaX = 0\n this._supportPointerEvents = Boolean(window.PointerEvent)\n this._initEvents()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n dispose() {\n EventHandler.off(this._element, EVENT_KEY)\n }\n\n // Private\n _start(event) {\n if (!this._supportPointerEvents) {\n this._deltaX = event.touches[0].clientX\n\n return\n }\n\n if (this._eventIsPointerPenTouch(event)) {\n this._deltaX = event.clientX\n }\n }\n\n _end(event) {\n if (this._eventIsPointerPenTouch(event)) {\n this._deltaX = event.clientX - this._deltaX\n }\n\n this._handleSwipe()\n execute(this._config.endCallback)\n }\n\n _move(event) {\n this._deltaX = event.touches && event.touches.length > 1 ?\n 0 :\n event.touches[0].clientX - this._deltaX\n }\n\n _handleSwipe() {\n const absDeltaX = Math.abs(this._deltaX)\n\n if (absDeltaX <= SWIPE_THRESHOLD) {\n return\n }\n\n const direction = absDeltaX / this._deltaX\n\n this._deltaX = 0\n\n if (!direction) {\n return\n }\n\n execute(direction > 0 ? this._config.rightCallback : this._config.leftCallback)\n }\n\n _initEvents() {\n if (this._supportPointerEvents) {\n EventHandler.on(this._element, EVENT_POINTERDOWN, event => this._start(event))\n EventHandler.on(this._element, EVENT_POINTERUP, event => this._end(event))\n\n this._element.classList.add(CLASS_NAME_POINTER_EVENT)\n } else {\n EventHandler.on(this._element, EVENT_TOUCHSTART, event => this._start(event))\n EventHandler.on(this._element, EVENT_TOUCHMOVE, event => this._move(event))\n EventHandler.on(this._element, EVENT_TOUCHEND, event => this._end(event))\n }\n }\n\n _eventIsPointerPenTouch(event) {\n return this._supportPointerEvents && (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH)\n }\n\n // Static\n static isSupported() {\n return 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0\n }\n}\n\nexport default Swipe\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap carousel.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport Manipulator from './dom/manipulator.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport {\n defineJQueryPlugin,\n getNextActiveElement,\n isRTL,\n isVisible,\n reflow,\n triggerTransitionEnd\n} from './util/index.js'\nimport Swipe from './util/swipe.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'carousel'\nconst DATA_KEY = 'bs.carousel'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst ARROW_LEFT_KEY = 'ArrowLeft'\nconst ARROW_RIGHT_KEY = 'ArrowRight'\nconst TOUCHEVENT_COMPAT_WAIT = 500 // Time for mouse compat events to fire after touch\n\nconst ORDER_NEXT = 'next'\nconst ORDER_PREV = 'prev'\nconst DIRECTION_LEFT = 'left'\nconst DIRECTION_RIGHT = 'right'\n\nconst EVENT_SLIDE = `slide${EVENT_KEY}`\nconst EVENT_SLID = `slid${EVENT_KEY}`\nconst EVENT_KEYDOWN = `keydown${EVENT_KEY}`\nconst EVENT_MOUSEENTER = `mouseenter${EVENT_KEY}`\nconst EVENT_MOUSELEAVE = `mouseleave${EVENT_KEY}`\nconst EVENT_DRAG_START = `dragstart${EVENT_KEY}`\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_CAROUSEL = 'carousel'\nconst CLASS_NAME_ACTIVE = 'active'\nconst CLASS_NAME_SLIDE = 'slide'\nconst CLASS_NAME_END = 'carousel-item-end'\nconst CLASS_NAME_START = 'carousel-item-start'\nconst CLASS_NAME_NEXT = 'carousel-item-next'\nconst CLASS_NAME_PREV = 'carousel-item-prev'\n\nconst SELECTOR_ACTIVE = '.active'\nconst SELECTOR_ITEM = '.carousel-item'\nconst SELECTOR_ACTIVE_ITEM = SELECTOR_ACTIVE + SELECTOR_ITEM\nconst SELECTOR_ITEM_IMG = '.carousel-item img'\nconst SELECTOR_INDICATORS = '.carousel-indicators'\nconst SELECTOR_DATA_SLIDE = '[data-bs-slide], [data-bs-slide-to]'\nconst SELECTOR_DATA_RIDE = '[data-bs-ride=\"carousel\"]'\n\nconst KEY_TO_DIRECTION = {\n [ARROW_LEFT_KEY]: DIRECTION_RIGHT,\n [ARROW_RIGHT_KEY]: DIRECTION_LEFT\n}\n\nconst Default = {\n interval: 5000,\n keyboard: true,\n pause: 'hover',\n ride: false,\n touch: true,\n wrap: true\n}\n\nconst DefaultType = {\n interval: '(number|boolean)', // TODO:v6 remove boolean support\n keyboard: 'boolean',\n pause: '(string|boolean)',\n ride: '(boolean|string)',\n touch: 'boolean',\n wrap: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Carousel extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._interval = null\n this._activeElement = null\n this._isSliding = false\n this.touchTimeout = null\n this._swipeHelper = null\n\n this._indicatorsElement = SelectorEngine.findOne(SELECTOR_INDICATORS, this._element)\n this._addEventListeners()\n\n if (this._config.ride === CLASS_NAME_CAROUSEL) {\n this.cycle()\n }\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n next() {\n this._slide(ORDER_NEXT)\n }\n\n nextWhenVisible() {\n // FIXME TODO use `document.visibilityState`\n // Don't call next when the page isn't visible\n // or the carousel or its parent isn't visible\n if (!document.hidden && isVisible(this._element)) {\n this.next()\n }\n }\n\n prev() {\n this._slide(ORDER_PREV)\n }\n\n pause() {\n if (this._isSliding) {\n triggerTransitionEnd(this._element)\n }\n\n this._clearInterval()\n }\n\n cycle() {\n this._clearInterval()\n this._updateInterval()\n\n this._interval = setInterval(() => this.nextWhenVisible(), this._config.interval)\n }\n\n _maybeEnableCycle() {\n if (!this._config.ride) {\n return\n }\n\n if (this._isSliding) {\n EventHandler.one(this._element, EVENT_SLID, () => this.cycle())\n return\n }\n\n this.cycle()\n }\n\n to(index) {\n const items = this._getItems()\n if (index > items.length - 1 || index < 0) {\n return\n }\n\n if (this._isSliding) {\n EventHandler.one(this._element, EVENT_SLID, () => this.to(index))\n return\n }\n\n const activeIndex = this._getItemIndex(this._getActive())\n if (activeIndex === index) {\n return\n }\n\n const order = index > activeIndex ? ORDER_NEXT : ORDER_PREV\n\n this._slide(order, items[index])\n }\n\n dispose() {\n if (this._swipeHelper) {\n this._swipeHelper.dispose()\n }\n\n super.dispose()\n }\n\n // Private\n _configAfterMerge(config) {\n config.defaultInterval = config.interval\n return config\n }\n\n _addEventListeners() {\n if (this._config.keyboard) {\n EventHandler.on(this._element, EVENT_KEYDOWN, event => this._keydown(event))\n }\n\n if (this._config.pause === 'hover') {\n EventHandler.on(this._element, EVENT_MOUSEENTER, () => this.pause())\n EventHandler.on(this._element, EVENT_MOUSELEAVE, () => this._maybeEnableCycle())\n }\n\n if (this._config.touch && Swipe.isSupported()) {\n this._addTouchEventListeners()\n }\n }\n\n _addTouchEventListeners() {\n for (const img of SelectorEngine.find(SELECTOR_ITEM_IMG, this._element)) {\n EventHandler.on(img, EVENT_DRAG_START, event => event.preventDefault())\n }\n\n const endCallBack = () => {\n if (this._config.pause !== 'hover') {\n return\n }\n\n // If it's a touch-enabled device, mouseenter/leave are fired as\n // part of the mouse compatibility events on first tap - the carousel\n // would stop cycling until user tapped out of it;\n // here, we listen for touchend, explicitly pause the carousel\n // (as if it's the second time we tap on it, mouseenter compat event\n // is NOT fired) and after a timeout (to allow for mouse compatibility\n // events to fire) we explicitly restart cycling\n\n this.pause()\n if (this.touchTimeout) {\n clearTimeout(this.touchTimeout)\n }\n\n this.touchTimeout = setTimeout(() => this._maybeEnableCycle(), TOUCHEVENT_COMPAT_WAIT + this._config.interval)\n }\n\n const swipeConfig = {\n leftCallback: () => this._slide(this._directionToOrder(DIRECTION_LEFT)),\n rightCallback: () => this._slide(this._directionToOrder(DIRECTION_RIGHT)),\n endCallback: endCallBack\n }\n\n this._swipeHelper = new Swipe(this._element, swipeConfig)\n }\n\n _keydown(event) {\n if (/input|textarea/i.test(event.target.tagName)) {\n return\n }\n\n const direction = KEY_TO_DIRECTION[event.key]\n if (direction) {\n event.preventDefault()\n this._slide(this._directionToOrder(direction))\n }\n }\n\n _getItemIndex(element) {\n return this._getItems().indexOf(element)\n }\n\n _setActiveIndicatorElement(index) {\n if (!this._indicatorsElement) {\n return\n }\n\n const activeIndicator = SelectorEngine.findOne(SELECTOR_ACTIVE, this._indicatorsElement)\n\n activeIndicator.classList.remove(CLASS_NAME_ACTIVE)\n activeIndicator.removeAttribute('aria-current')\n\n const newActiveIndicator = SelectorEngine.findOne(`[data-bs-slide-to=\"${index}\"]`, this._indicatorsElement)\n\n if (newActiveIndicator) {\n newActiveIndicator.classList.add(CLASS_NAME_ACTIVE)\n newActiveIndicator.setAttribute('aria-current', 'true')\n }\n }\n\n _updateInterval() {\n const element = this._activeElement || this._getActive()\n\n if (!element) {\n return\n }\n\n const elementInterval = Number.parseInt(element.getAttribute('data-bs-interval'), 10)\n\n this._config.interval = elementInterval || this._config.defaultInterval\n }\n\n _slide(order, element = null) {\n if (this._isSliding) {\n return\n }\n\n const activeElement = this._getActive()\n const isNext = order === ORDER_NEXT\n const nextElement = element || getNextActiveElement(this._getItems(), activeElement, isNext, this._config.wrap)\n\n if (nextElement === activeElement) {\n return\n }\n\n const nextElementIndex = this._getItemIndex(nextElement)\n\n const triggerEvent = eventName => {\n return EventHandler.trigger(this._element, eventName, {\n relatedTarget: nextElement,\n direction: this._orderToDirection(order),\n from: this._getItemIndex(activeElement),\n to: nextElementIndex\n })\n }\n\n const slideEvent = triggerEvent(EVENT_SLIDE)\n\n if (slideEvent.defaultPrevented) {\n return\n }\n\n if (!activeElement || !nextElement) {\n // Some weirdness is happening, so we bail\n // TODO: change tests that use empty divs to avoid this check\n return\n }\n\n const isCycling = Boolean(this._interval)\n this.pause()\n\n this._isSliding = true\n\n this._setActiveIndicatorElement(nextElementIndex)\n this._activeElement = nextElement\n\n const directionalClassName = isNext ? CLASS_NAME_START : CLASS_NAME_END\n const orderClassName = isNext ? CLASS_NAME_NEXT : CLASS_NAME_PREV\n\n nextElement.classList.add(orderClassName)\n\n reflow(nextElement)\n\n activeElement.classList.add(directionalClassName)\n nextElement.classList.add(directionalClassName)\n\n const completeCallBack = () => {\n nextElement.classList.remove(directionalClassName, orderClassName)\n nextElement.classList.add(CLASS_NAME_ACTIVE)\n\n activeElement.classList.remove(CLASS_NAME_ACTIVE, orderClassName, directionalClassName)\n\n this._isSliding = false\n\n triggerEvent(EVENT_SLID)\n }\n\n this._queueCallback(completeCallBack, activeElement, this._isAnimated())\n\n if (isCycling) {\n this.cycle()\n }\n }\n\n _isAnimated() {\n return this._element.classList.contains(CLASS_NAME_SLIDE)\n }\n\n _getActive() {\n return SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element)\n }\n\n _getItems() {\n return SelectorEngine.find(SELECTOR_ITEM, this._element)\n }\n\n _clearInterval() {\n if (this._interval) {\n clearInterval(this._interval)\n this._interval = null\n }\n }\n\n _directionToOrder(direction) {\n if (isRTL()) {\n return direction === DIRECTION_LEFT ? ORDER_PREV : ORDER_NEXT\n }\n\n return direction === DIRECTION_LEFT ? ORDER_NEXT : ORDER_PREV\n }\n\n _orderToDirection(order) {\n if (isRTL()) {\n return order === ORDER_PREV ? DIRECTION_LEFT : DIRECTION_RIGHT\n }\n\n return order === ORDER_PREV ? DIRECTION_RIGHT : DIRECTION_LEFT\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Carousel.getOrCreateInstance(this, config)\n\n if (typeof config === 'number') {\n data.to(config)\n return\n }\n\n if (typeof config === 'string') {\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_SLIDE, function (event) {\n const target = SelectorEngine.getElementFromSelector(this)\n\n if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) {\n return\n }\n\n event.preventDefault()\n\n const carousel = Carousel.getOrCreateInstance(target)\n const slideIndex = this.getAttribute('data-bs-slide-to')\n\n if (slideIndex) {\n carousel.to(slideIndex)\n carousel._maybeEnableCycle()\n return\n }\n\n if (Manipulator.getDataAttribute(this, 'slide') === 'next') {\n carousel.next()\n carousel._maybeEnableCycle()\n return\n }\n\n carousel.prev()\n carousel._maybeEnableCycle()\n})\n\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n const carousels = SelectorEngine.find(SELECTOR_DATA_RIDE)\n\n for (const carousel of carousels) {\n Carousel.getOrCreateInstance(carousel)\n }\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Carousel)\n\nexport default Carousel\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap collapse.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport {\n defineJQueryPlugin,\n getElement,\n reflow\n} from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'collapse'\nconst DATA_KEY = 'bs.collapse'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_COLLAPSE = 'collapse'\nconst CLASS_NAME_COLLAPSING = 'collapsing'\nconst CLASS_NAME_COLLAPSED = 'collapsed'\nconst CLASS_NAME_DEEPER_CHILDREN = `:scope .${CLASS_NAME_COLLAPSE} .${CLASS_NAME_COLLAPSE}`\nconst CLASS_NAME_HORIZONTAL = 'collapse-horizontal'\n\nconst WIDTH = 'width'\nconst HEIGHT = 'height'\n\nconst SELECTOR_ACTIVES = '.collapse.show, .collapse.collapsing'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"collapse\"]'\n\nconst Default = {\n parent: null,\n toggle: true\n}\n\nconst DefaultType = {\n parent: '(null|element)',\n toggle: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Collapse extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._isTransitioning = false\n this._triggerArray = []\n\n const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE)\n\n for (const elem of toggleList) {\n const selector = SelectorEngine.getSelectorFromElement(elem)\n const filterElement = SelectorEngine.find(selector)\n .filter(foundElement => foundElement === this._element)\n\n if (selector !== null && filterElement.length) {\n this._triggerArray.push(elem)\n }\n }\n\n this._initializeChildren()\n\n if (!this._config.parent) {\n this._addAriaAndCollapsedClass(this._triggerArray, this._isShown())\n }\n\n if (this._config.toggle) {\n this.toggle()\n }\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle() {\n if (this._isShown()) {\n this.hide()\n } else {\n this.show()\n }\n }\n\n show() {\n if (this._isTransitioning || this._isShown()) {\n return\n }\n\n let activeChildren = []\n\n // find active children\n if (this._config.parent) {\n activeChildren = this._getFirstLevelChildren(SELECTOR_ACTIVES)\n .filter(element => element !== this._element)\n .map(element => Collapse.getOrCreateInstance(element, { toggle: false }))\n }\n\n if (activeChildren.length && activeChildren[0]._isTransitioning) {\n return\n }\n\n const startEvent = EventHandler.trigger(this._element, EVENT_SHOW)\n if (startEvent.defaultPrevented) {\n return\n }\n\n for (const activeInstance of activeChildren) {\n activeInstance.hide()\n }\n\n const dimension = this._getDimension()\n\n this._element.classList.remove(CLASS_NAME_COLLAPSE)\n this._element.classList.add(CLASS_NAME_COLLAPSING)\n\n this._element.style[dimension] = 0\n\n this._addAriaAndCollapsedClass(this._triggerArray, true)\n this._isTransitioning = true\n\n const complete = () => {\n this._isTransitioning = false\n\n this._element.classList.remove(CLASS_NAME_COLLAPSING)\n this._element.classList.add(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW)\n\n this._element.style[dimension] = ''\n\n EventHandler.trigger(this._element, EVENT_SHOWN)\n }\n\n const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1)\n const scrollSize = `scroll${capitalizedDimension}`\n\n this._queueCallback(complete, this._element, true)\n this._element.style[dimension] = `${this._element[scrollSize]}px`\n }\n\n hide() {\n if (this._isTransitioning || !this._isShown()) {\n return\n }\n\n const startEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n if (startEvent.defaultPrevented) {\n return\n }\n\n const dimension = this._getDimension()\n\n this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px`\n\n reflow(this._element)\n\n this._element.classList.add(CLASS_NAME_COLLAPSING)\n this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW)\n\n for (const trigger of this._triggerArray) {\n const element = SelectorEngine.getElementFromSelector(trigger)\n\n if (element && !this._isShown(element)) {\n this._addAriaAndCollapsedClass([trigger], false)\n }\n }\n\n this._isTransitioning = true\n\n const complete = () => {\n this._isTransitioning = false\n this._element.classList.remove(CLASS_NAME_COLLAPSING)\n this._element.classList.add(CLASS_NAME_COLLAPSE)\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n }\n\n this._element.style[dimension] = ''\n\n this._queueCallback(complete, this._element, true)\n }\n\n _isShown(element = this._element) {\n return element.classList.contains(CLASS_NAME_SHOW)\n }\n\n // Private\n _configAfterMerge(config) {\n config.toggle = Boolean(config.toggle) // Coerce string values\n config.parent = getElement(config.parent)\n return config\n }\n\n _getDimension() {\n return this._element.classList.contains(CLASS_NAME_HORIZONTAL) ? WIDTH : HEIGHT\n }\n\n _initializeChildren() {\n if (!this._config.parent) {\n return\n }\n\n const children = this._getFirstLevelChildren(SELECTOR_DATA_TOGGLE)\n\n for (const element of children) {\n const selected = SelectorEngine.getElementFromSelector(element)\n\n if (selected) {\n this._addAriaAndCollapsedClass([element], this._isShown(selected))\n }\n }\n }\n\n _getFirstLevelChildren(selector) {\n const children = SelectorEngine.find(CLASS_NAME_DEEPER_CHILDREN, this._config.parent)\n // remove children if greater depth\n return SelectorEngine.find(selector, this._config.parent).filter(element => !children.includes(element))\n }\n\n _addAriaAndCollapsedClass(triggerArray, isOpen) {\n if (!triggerArray.length) {\n return\n }\n\n for (const element of triggerArray) {\n element.classList.toggle(CLASS_NAME_COLLAPSED, !isOpen)\n element.setAttribute('aria-expanded', isOpen)\n }\n }\n\n // Static\n static jQueryInterface(config) {\n const _config = {}\n if (typeof config === 'string' && /show|hide/.test(config)) {\n _config.toggle = false\n }\n\n return this.each(function () {\n const data = Collapse.getOrCreateInstance(this, _config)\n\n if (typeof config === 'string') {\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n // preventDefault only for elements (which change the URL) not inside the collapsible element\n if (event.target.tagName === 'A' || (event.delegateTarget && event.delegateTarget.tagName === 'A')) {\n event.preventDefault()\n }\n\n for (const element of SelectorEngine.getMultipleElementsFromSelector(this)) {\n Collapse.getOrCreateInstance(element, { toggle: false }).toggle()\n }\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Collapse)\n\nexport default Collapse\n","export var top = 'top';\nexport var bottom = 'bottom';\nexport var right = 'right';\nexport var left = 'left';\nexport var auto = 'auto';\nexport var basePlacements = [top, bottom, right, left];\nexport var start = 'start';\nexport var end = 'end';\nexport var clippingParents = 'clippingParents';\nexport var viewport = 'viewport';\nexport var popper = 'popper';\nexport var reference = 'reference';\nexport var variationPlacements = /*#__PURE__*/basePlacements.reduce(function (acc, placement) {\n return acc.concat([placement + \"-\" + start, placement + \"-\" + end]);\n}, []);\nexport var placements = /*#__PURE__*/[].concat(basePlacements, [auto]).reduce(function (acc, placement) {\n return acc.concat([placement, placement + \"-\" + start, placement + \"-\" + end]);\n}, []); // modifiers that need to read the DOM\n\nexport var beforeRead = 'beforeRead';\nexport var read = 'read';\nexport var afterRead = 'afterRead'; // pure-logic modifiers\n\nexport var beforeMain = 'beforeMain';\nexport var main = 'main';\nexport var afterMain = 'afterMain'; // modifier with the purpose to write to the DOM (or write into a framework state)\n\nexport var beforeWrite = 'beforeWrite';\nexport var write = 'write';\nexport var afterWrite = 'afterWrite';\nexport var modifierPhases = [beforeRead, read, afterRead, beforeMain, main, afterMain, beforeWrite, write, afterWrite];","export default function getNodeName(element) {\n return element ? (element.nodeName || '').toLowerCase() : null;\n}","export default function getWindow(node) {\n if (node == null) {\n return window;\n }\n\n if (node.toString() !== '[object Window]') {\n var ownerDocument = node.ownerDocument;\n return ownerDocument ? ownerDocument.defaultView || window : window;\n }\n\n return node;\n}","import getWindow from \"./getWindow.js\";\n\nfunction isElement(node) {\n var OwnElement = getWindow(node).Element;\n return node instanceof OwnElement || node instanceof Element;\n}\n\nfunction isHTMLElement(node) {\n var OwnElement = getWindow(node).HTMLElement;\n return node instanceof OwnElement || node instanceof HTMLElement;\n}\n\nfunction isShadowRoot(node) {\n // IE 11 has no ShadowRoot\n if (typeof ShadowRoot === 'undefined') {\n return false;\n }\n\n var OwnElement = getWindow(node).ShadowRoot;\n return node instanceof OwnElement || node instanceof ShadowRoot;\n}\n\nexport { isElement, isHTMLElement, isShadowRoot };","import getNodeName from \"../dom-utils/getNodeName.js\";\nimport { isHTMLElement } from \"../dom-utils/instanceOf.js\"; // This modifier takes the styles prepared by the `computeStyles` modifier\n// and applies them to the HTMLElements such as popper and arrow\n\nfunction applyStyles(_ref) {\n var state = _ref.state;\n Object.keys(state.elements).forEach(function (name) {\n var style = state.styles[name] || {};\n var attributes = state.attributes[name] || {};\n var element = state.elements[name]; // arrow is optional + virtual elements\n\n if (!isHTMLElement(element) || !getNodeName(element)) {\n return;\n } // Flow doesn't support to extend this property, but it's the most\n // effective way to apply styles to an HTMLElement\n // $FlowFixMe[cannot-write]\n\n\n Object.assign(element.style, style);\n Object.keys(attributes).forEach(function (name) {\n var value = attributes[name];\n\n if (value === false) {\n element.removeAttribute(name);\n } else {\n element.setAttribute(name, value === true ? '' : value);\n }\n });\n });\n}\n\nfunction effect(_ref2) {\n var state = _ref2.state;\n var initialStyles = {\n popper: {\n position: state.options.strategy,\n left: '0',\n top: '0',\n margin: '0'\n },\n arrow: {\n position: 'absolute'\n },\n reference: {}\n };\n Object.assign(state.elements.popper.style, initialStyles.popper);\n state.styles = initialStyles;\n\n if (state.elements.arrow) {\n Object.assign(state.elements.arrow.style, initialStyles.arrow);\n }\n\n return function () {\n Object.keys(state.elements).forEach(function (name) {\n var element = state.elements[name];\n var attributes = state.attributes[name] || {};\n var styleProperties = Object.keys(state.styles.hasOwnProperty(name) ? state.styles[name] : initialStyles[name]); // Set all values to an empty string to unset them\n\n var style = styleProperties.reduce(function (style, property) {\n style[property] = '';\n return style;\n }, {}); // arrow is optional + virtual elements\n\n if (!isHTMLElement(element) || !getNodeName(element)) {\n return;\n }\n\n Object.assign(element.style, style);\n Object.keys(attributes).forEach(function (attribute) {\n element.removeAttribute(attribute);\n });\n });\n };\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'applyStyles',\n enabled: true,\n phase: 'write',\n fn: applyStyles,\n effect: effect,\n requires: ['computeStyles']\n};","import { auto } from \"../enums.js\";\nexport default function getBasePlacement(placement) {\n return placement.split('-')[0];\n}","export var max = Math.max;\nexport var min = Math.min;\nexport var round = Math.round;","export default function getUAString() {\n var uaData = navigator.userAgentData;\n\n if (uaData != null && uaData.brands && Array.isArray(uaData.brands)) {\n return uaData.brands.map(function (item) {\n return item.brand + \"/\" + item.version;\n }).join(' ');\n }\n\n return navigator.userAgent;\n}","import getUAString from \"../utils/userAgent.js\";\nexport default function isLayoutViewport() {\n return !/^((?!chrome|android).)*safari/i.test(getUAString());\n}","import { isElement, isHTMLElement } from \"./instanceOf.js\";\nimport { round } from \"../utils/math.js\";\nimport getWindow from \"./getWindow.js\";\nimport isLayoutViewport from \"./isLayoutViewport.js\";\nexport default function getBoundingClientRect(element, includeScale, isFixedStrategy) {\n if (includeScale === void 0) {\n includeScale = false;\n }\n\n if (isFixedStrategy === void 0) {\n isFixedStrategy = false;\n }\n\n var clientRect = element.getBoundingClientRect();\n var scaleX = 1;\n var scaleY = 1;\n\n if (includeScale && isHTMLElement(element)) {\n scaleX = element.offsetWidth > 0 ? round(clientRect.width) / element.offsetWidth || 1 : 1;\n scaleY = element.offsetHeight > 0 ? round(clientRect.height) / element.offsetHeight || 1 : 1;\n }\n\n var _ref = isElement(element) ? getWindow(element) : window,\n visualViewport = _ref.visualViewport;\n\n var addVisualOffsets = !isLayoutViewport() && isFixedStrategy;\n var x = (clientRect.left + (addVisualOffsets && visualViewport ? visualViewport.offsetLeft : 0)) / scaleX;\n var y = (clientRect.top + (addVisualOffsets && visualViewport ? visualViewport.offsetTop : 0)) / scaleY;\n var width = clientRect.width / scaleX;\n var height = clientRect.height / scaleY;\n return {\n width: width,\n height: height,\n top: y,\n right: x + width,\n bottom: y + height,\n left: x,\n x: x,\n y: y\n };\n}","import getBoundingClientRect from \"./getBoundingClientRect.js\"; // Returns the layout rect of an element relative to its offsetParent. Layout\n// means it doesn't take into account transforms.\n\nexport default function getLayoutRect(element) {\n var clientRect = getBoundingClientRect(element); // Use the clientRect sizes if it's not been transformed.\n // Fixes https://github.com/popperjs/popper-core/issues/1223\n\n var width = element.offsetWidth;\n var height = element.offsetHeight;\n\n if (Math.abs(clientRect.width - width) <= 1) {\n width = clientRect.width;\n }\n\n if (Math.abs(clientRect.height - height) <= 1) {\n height = clientRect.height;\n }\n\n return {\n x: element.offsetLeft,\n y: element.offsetTop,\n width: width,\n height: height\n };\n}","import { isShadowRoot } from \"./instanceOf.js\";\nexport default function contains(parent, child) {\n var rootNode = child.getRootNode && child.getRootNode(); // First, attempt with faster native method\n\n if (parent.contains(child)) {\n return true;\n } // then fallback to custom implementation with Shadow DOM support\n else if (rootNode && isShadowRoot(rootNode)) {\n var next = child;\n\n do {\n if (next && parent.isSameNode(next)) {\n return true;\n } // $FlowFixMe[prop-missing]: need a better way to handle this...\n\n\n next = next.parentNode || next.host;\n } while (next);\n } // Give up, the result is false\n\n\n return false;\n}","import getWindow from \"./getWindow.js\";\nexport default function getComputedStyle(element) {\n return getWindow(element).getComputedStyle(element);\n}","import getNodeName from \"./getNodeName.js\";\nexport default function isTableElement(element) {\n return ['table', 'td', 'th'].indexOf(getNodeName(element)) >= 0;\n}","import { isElement } from \"./instanceOf.js\";\nexport default function getDocumentElement(element) {\n // $FlowFixMe[incompatible-return]: assume body is always available\n return ((isElement(element) ? element.ownerDocument : // $FlowFixMe[prop-missing]\n element.document) || window.document).documentElement;\n}","import getNodeName from \"./getNodeName.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport { isShadowRoot } from \"./instanceOf.js\";\nexport default function getParentNode(element) {\n if (getNodeName(element) === 'html') {\n return element;\n }\n\n return (// this is a quicker (but less type safe) way to save quite some bytes from the bundle\n // $FlowFixMe[incompatible-return]\n // $FlowFixMe[prop-missing]\n element.assignedSlot || // step into the shadow DOM of the parent of a slotted node\n element.parentNode || ( // DOM Element detected\n isShadowRoot(element) ? element.host : null) || // ShadowRoot detected\n // $FlowFixMe[incompatible-call]: HTMLElement is a Node\n getDocumentElement(element) // fallback\n\n );\n}","import getWindow from \"./getWindow.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport getComputedStyle from \"./getComputedStyle.js\";\nimport { isHTMLElement, isShadowRoot } from \"./instanceOf.js\";\nimport isTableElement from \"./isTableElement.js\";\nimport getParentNode from \"./getParentNode.js\";\nimport getUAString from \"../utils/userAgent.js\";\n\nfunction getTrueOffsetParent(element) {\n if (!isHTMLElement(element) || // https://github.com/popperjs/popper-core/issues/837\n getComputedStyle(element).position === 'fixed') {\n return null;\n }\n\n return element.offsetParent;\n} // `.offsetParent` reports `null` for fixed elements, while absolute elements\n// return the containing block\n\n\nfunction getContainingBlock(element) {\n var isFirefox = /firefox/i.test(getUAString());\n var isIE = /Trident/i.test(getUAString());\n\n if (isIE && isHTMLElement(element)) {\n // In IE 9, 10 and 11 fixed elements containing block is always established by the viewport\n var elementCss = getComputedStyle(element);\n\n if (elementCss.position === 'fixed') {\n return null;\n }\n }\n\n var currentNode = getParentNode(element);\n\n if (isShadowRoot(currentNode)) {\n currentNode = currentNode.host;\n }\n\n while (isHTMLElement(currentNode) && ['html', 'body'].indexOf(getNodeName(currentNode)) < 0) {\n var css = getComputedStyle(currentNode); // This is non-exhaustive but covers the most common CSS properties that\n // create a containing block.\n // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block\n\n if (css.transform !== 'none' || css.perspective !== 'none' || css.contain === 'paint' || ['transform', 'perspective'].indexOf(css.willChange) !== -1 || isFirefox && css.willChange === 'filter' || isFirefox && css.filter && css.filter !== 'none') {\n return currentNode;\n } else {\n currentNode = currentNode.parentNode;\n }\n }\n\n return null;\n} // Gets the closest ancestor positioned element. Handles some edge cases,\n// such as table ancestors and cross browser bugs.\n\n\nexport default function getOffsetParent(element) {\n var window = getWindow(element);\n var offsetParent = getTrueOffsetParent(element);\n\n while (offsetParent && isTableElement(offsetParent) && getComputedStyle(offsetParent).position === 'static') {\n offsetParent = getTrueOffsetParent(offsetParent);\n }\n\n if (offsetParent && (getNodeName(offsetParent) === 'html' || getNodeName(offsetParent) === 'body' && getComputedStyle(offsetParent).position === 'static')) {\n return window;\n }\n\n return offsetParent || getContainingBlock(element) || window;\n}","export default function getMainAxisFromPlacement(placement) {\n return ['top', 'bottom'].indexOf(placement) >= 0 ? 'x' : 'y';\n}","import { max as mathMax, min as mathMin } from \"./math.js\";\nexport function within(min, value, max) {\n return mathMax(min, mathMin(value, max));\n}\nexport function withinMaxClamp(min, value, max) {\n var v = within(min, value, max);\n return v > max ? max : v;\n}","import getFreshSideObject from \"./getFreshSideObject.js\";\nexport default function mergePaddingObject(paddingObject) {\n return Object.assign({}, getFreshSideObject(), paddingObject);\n}","export default function getFreshSideObject() {\n return {\n top: 0,\n right: 0,\n bottom: 0,\n left: 0\n };\n}","export default function expandToHashMap(value, keys) {\n return keys.reduce(function (hashMap, key) {\n hashMap[key] = value;\n return hashMap;\n }, {});\n}","import getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getLayoutRect from \"../dom-utils/getLayoutRect.js\";\nimport contains from \"../dom-utils/contains.js\";\nimport getOffsetParent from \"../dom-utils/getOffsetParent.js\";\nimport getMainAxisFromPlacement from \"../utils/getMainAxisFromPlacement.js\";\nimport { within } from \"../utils/within.js\";\nimport mergePaddingObject from \"../utils/mergePaddingObject.js\";\nimport expandToHashMap from \"../utils/expandToHashMap.js\";\nimport { left, right, basePlacements, top, bottom } from \"../enums.js\"; // eslint-disable-next-line import/no-unused-modules\n\nvar toPaddingObject = function toPaddingObject(padding, state) {\n padding = typeof padding === 'function' ? padding(Object.assign({}, state.rects, {\n placement: state.placement\n })) : padding;\n return mergePaddingObject(typeof padding !== 'number' ? padding : expandToHashMap(padding, basePlacements));\n};\n\nfunction arrow(_ref) {\n var _state$modifiersData$;\n\n var state = _ref.state,\n name = _ref.name,\n options = _ref.options;\n var arrowElement = state.elements.arrow;\n var popperOffsets = state.modifiersData.popperOffsets;\n var basePlacement = getBasePlacement(state.placement);\n var axis = getMainAxisFromPlacement(basePlacement);\n var isVertical = [left, right].indexOf(basePlacement) >= 0;\n var len = isVertical ? 'height' : 'width';\n\n if (!arrowElement || !popperOffsets) {\n return;\n }\n\n var paddingObject = toPaddingObject(options.padding, state);\n var arrowRect = getLayoutRect(arrowElement);\n var minProp = axis === 'y' ? top : left;\n var maxProp = axis === 'y' ? bottom : right;\n var endDiff = state.rects.reference[len] + state.rects.reference[axis] - popperOffsets[axis] - state.rects.popper[len];\n var startDiff = popperOffsets[axis] - state.rects.reference[axis];\n var arrowOffsetParent = getOffsetParent(arrowElement);\n var clientSize = arrowOffsetParent ? axis === 'y' ? arrowOffsetParent.clientHeight || 0 : arrowOffsetParent.clientWidth || 0 : 0;\n var centerToReference = endDiff / 2 - startDiff / 2; // Make sure the arrow doesn't overflow the popper if the center point is\n // outside of the popper bounds\n\n var min = paddingObject[minProp];\n var max = clientSize - arrowRect[len] - paddingObject[maxProp];\n var center = clientSize / 2 - arrowRect[len] / 2 + centerToReference;\n var offset = within(min, center, max); // Prevents breaking syntax highlighting...\n\n var axisProp = axis;\n state.modifiersData[name] = (_state$modifiersData$ = {}, _state$modifiersData$[axisProp] = offset, _state$modifiersData$.centerOffset = offset - center, _state$modifiersData$);\n}\n\nfunction effect(_ref2) {\n var state = _ref2.state,\n options = _ref2.options;\n var _options$element = options.element,\n arrowElement = _options$element === void 0 ? '[data-popper-arrow]' : _options$element;\n\n if (arrowElement == null) {\n return;\n } // CSS selector\n\n\n if (typeof arrowElement === 'string') {\n arrowElement = state.elements.popper.querySelector(arrowElement);\n\n if (!arrowElement) {\n return;\n }\n }\n\n if (!contains(state.elements.popper, arrowElement)) {\n return;\n }\n\n state.elements.arrow = arrowElement;\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'arrow',\n enabled: true,\n phase: 'main',\n fn: arrow,\n effect: effect,\n requires: ['popperOffsets'],\n requiresIfExists: ['preventOverflow']\n};","export default function getVariation(placement) {\n return placement.split('-')[1];\n}","import { top, left, right, bottom, end } from \"../enums.js\";\nimport getOffsetParent from \"../dom-utils/getOffsetParent.js\";\nimport getWindow from \"../dom-utils/getWindow.js\";\nimport getDocumentElement from \"../dom-utils/getDocumentElement.js\";\nimport getComputedStyle from \"../dom-utils/getComputedStyle.js\";\nimport getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getVariation from \"../utils/getVariation.js\";\nimport { round } from \"../utils/math.js\"; // eslint-disable-next-line import/no-unused-modules\n\nvar unsetSides = {\n top: 'auto',\n right: 'auto',\n bottom: 'auto',\n left: 'auto'\n}; // Round the offsets to the nearest suitable subpixel based on the DPR.\n// Zooming can change the DPR, but it seems to report a value that will\n// cleanly divide the values into the appropriate subpixels.\n\nfunction roundOffsetsByDPR(_ref, win) {\n var x = _ref.x,\n y = _ref.y;\n var dpr = win.devicePixelRatio || 1;\n return {\n x: round(x * dpr) / dpr || 0,\n y: round(y * dpr) / dpr || 0\n };\n}\n\nexport function mapToStyles(_ref2) {\n var _Object$assign2;\n\n var popper = _ref2.popper,\n popperRect = _ref2.popperRect,\n placement = _ref2.placement,\n variation = _ref2.variation,\n offsets = _ref2.offsets,\n position = _ref2.position,\n gpuAcceleration = _ref2.gpuAcceleration,\n adaptive = _ref2.adaptive,\n roundOffsets = _ref2.roundOffsets,\n isFixed = _ref2.isFixed;\n var _offsets$x = offsets.x,\n x = _offsets$x === void 0 ? 0 : _offsets$x,\n _offsets$y = offsets.y,\n y = _offsets$y === void 0 ? 0 : _offsets$y;\n\n var _ref3 = typeof roundOffsets === 'function' ? roundOffsets({\n x: x,\n y: y\n }) : {\n x: x,\n y: y\n };\n\n x = _ref3.x;\n y = _ref3.y;\n var hasX = offsets.hasOwnProperty('x');\n var hasY = offsets.hasOwnProperty('y');\n var sideX = left;\n var sideY = top;\n var win = window;\n\n if (adaptive) {\n var offsetParent = getOffsetParent(popper);\n var heightProp = 'clientHeight';\n var widthProp = 'clientWidth';\n\n if (offsetParent === getWindow(popper)) {\n offsetParent = getDocumentElement(popper);\n\n if (getComputedStyle(offsetParent).position !== 'static' && position === 'absolute') {\n heightProp = 'scrollHeight';\n widthProp = 'scrollWidth';\n }\n } // $FlowFixMe[incompatible-cast]: force type refinement, we compare offsetParent with window above, but Flow doesn't detect it\n\n\n offsetParent = offsetParent;\n\n if (placement === top || (placement === left || placement === right) && variation === end) {\n sideY = bottom;\n var offsetY = isFixed && offsetParent === win && win.visualViewport ? win.visualViewport.height : // $FlowFixMe[prop-missing]\n offsetParent[heightProp];\n y -= offsetY - popperRect.height;\n y *= gpuAcceleration ? 1 : -1;\n }\n\n if (placement === left || (placement === top || placement === bottom) && variation === end) {\n sideX = right;\n var offsetX = isFixed && offsetParent === win && win.visualViewport ? win.visualViewport.width : // $FlowFixMe[prop-missing]\n offsetParent[widthProp];\n x -= offsetX - popperRect.width;\n x *= gpuAcceleration ? 1 : -1;\n }\n }\n\n var commonStyles = Object.assign({\n position: position\n }, adaptive && unsetSides);\n\n var _ref4 = roundOffsets === true ? roundOffsetsByDPR({\n x: x,\n y: y\n }, getWindow(popper)) : {\n x: x,\n y: y\n };\n\n x = _ref4.x;\n y = _ref4.y;\n\n if (gpuAcceleration) {\n var _Object$assign;\n\n return Object.assign({}, commonStyles, (_Object$assign = {}, _Object$assign[sideY] = hasY ? '0' : '', _Object$assign[sideX] = hasX ? '0' : '', _Object$assign.transform = (win.devicePixelRatio || 1) <= 1 ? \"translate(\" + x + \"px, \" + y + \"px)\" : \"translate3d(\" + x + \"px, \" + y + \"px, 0)\", _Object$assign));\n }\n\n return Object.assign({}, commonStyles, (_Object$assign2 = {}, _Object$assign2[sideY] = hasY ? y + \"px\" : '', _Object$assign2[sideX] = hasX ? x + \"px\" : '', _Object$assign2.transform = '', _Object$assign2));\n}\n\nfunction computeStyles(_ref5) {\n var state = _ref5.state,\n options = _ref5.options;\n var _options$gpuAccelerat = options.gpuAcceleration,\n gpuAcceleration = _options$gpuAccelerat === void 0 ? true : _options$gpuAccelerat,\n _options$adaptive = options.adaptive,\n adaptive = _options$adaptive === void 0 ? true : _options$adaptive,\n _options$roundOffsets = options.roundOffsets,\n roundOffsets = _options$roundOffsets === void 0 ? true : _options$roundOffsets;\n var commonStyles = {\n placement: getBasePlacement(state.placement),\n variation: getVariation(state.placement),\n popper: state.elements.popper,\n popperRect: state.rects.popper,\n gpuAcceleration: gpuAcceleration,\n isFixed: state.options.strategy === 'fixed'\n };\n\n if (state.modifiersData.popperOffsets != null) {\n state.styles.popper = Object.assign({}, state.styles.popper, mapToStyles(Object.assign({}, commonStyles, {\n offsets: state.modifiersData.popperOffsets,\n position: state.options.strategy,\n adaptive: adaptive,\n roundOffsets: roundOffsets\n })));\n }\n\n if (state.modifiersData.arrow != null) {\n state.styles.arrow = Object.assign({}, state.styles.arrow, mapToStyles(Object.assign({}, commonStyles, {\n offsets: state.modifiersData.arrow,\n position: 'absolute',\n adaptive: false,\n roundOffsets: roundOffsets\n })));\n }\n\n state.attributes.popper = Object.assign({}, state.attributes.popper, {\n 'data-popper-placement': state.placement\n });\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'computeStyles',\n enabled: true,\n phase: 'beforeWrite',\n fn: computeStyles,\n data: {}\n};","import getWindow from \"../dom-utils/getWindow.js\"; // eslint-disable-next-line import/no-unused-modules\n\nvar passive = {\n passive: true\n};\n\nfunction effect(_ref) {\n var state = _ref.state,\n instance = _ref.instance,\n options = _ref.options;\n var _options$scroll = options.scroll,\n scroll = _options$scroll === void 0 ? true : _options$scroll,\n _options$resize = options.resize,\n resize = _options$resize === void 0 ? true : _options$resize;\n var window = getWindow(state.elements.popper);\n var scrollParents = [].concat(state.scrollParents.reference, state.scrollParents.popper);\n\n if (scroll) {\n scrollParents.forEach(function (scrollParent) {\n scrollParent.addEventListener('scroll', instance.update, passive);\n });\n }\n\n if (resize) {\n window.addEventListener('resize', instance.update, passive);\n }\n\n return function () {\n if (scroll) {\n scrollParents.forEach(function (scrollParent) {\n scrollParent.removeEventListener('scroll', instance.update, passive);\n });\n }\n\n if (resize) {\n window.removeEventListener('resize', instance.update, passive);\n }\n };\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'eventListeners',\n enabled: true,\n phase: 'write',\n fn: function fn() {},\n effect: effect,\n data: {}\n};","var hash = {\n left: 'right',\n right: 'left',\n bottom: 'top',\n top: 'bottom'\n};\nexport default function getOppositePlacement(placement) {\n return placement.replace(/left|right|bottom|top/g, function (matched) {\n return hash[matched];\n });\n}","var hash = {\n start: 'end',\n end: 'start'\n};\nexport default function getOppositeVariationPlacement(placement) {\n return placement.replace(/start|end/g, function (matched) {\n return hash[matched];\n });\n}","import getWindow from \"./getWindow.js\";\nexport default function getWindowScroll(node) {\n var win = getWindow(node);\n var scrollLeft = win.pageXOffset;\n var scrollTop = win.pageYOffset;\n return {\n scrollLeft: scrollLeft,\n scrollTop: scrollTop\n };\n}","import getBoundingClientRect from \"./getBoundingClientRect.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport getWindowScroll from \"./getWindowScroll.js\";\nexport default function getWindowScrollBarX(element) {\n // If has a CSS width greater than the viewport, then this will be\n // incorrect for RTL.\n // Popper 1 is broken in this case and never had a bug report so let's assume\n // it's not an issue. I don't think anyone ever specifies width on \n // anyway.\n // Browsers where the left scrollbar doesn't cause an issue report `0` for\n // this (e.g. Edge 2019, IE11, Safari)\n return getBoundingClientRect(getDocumentElement(element)).left + getWindowScroll(element).scrollLeft;\n}","import getComputedStyle from \"./getComputedStyle.js\";\nexport default function isScrollParent(element) {\n // Firefox wants us to check `-x` and `-y` variations as well\n var _getComputedStyle = getComputedStyle(element),\n overflow = _getComputedStyle.overflow,\n overflowX = _getComputedStyle.overflowX,\n overflowY = _getComputedStyle.overflowY;\n\n return /auto|scroll|overlay|hidden/.test(overflow + overflowY + overflowX);\n}","import getParentNode from \"./getParentNode.js\";\nimport isScrollParent from \"./isScrollParent.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport { isHTMLElement } from \"./instanceOf.js\";\nexport default function getScrollParent(node) {\n if (['html', 'body', '#document'].indexOf(getNodeName(node)) >= 0) {\n // $FlowFixMe[incompatible-return]: assume body is always available\n return node.ownerDocument.body;\n }\n\n if (isHTMLElement(node) && isScrollParent(node)) {\n return node;\n }\n\n return getScrollParent(getParentNode(node));\n}","import getScrollParent from \"./getScrollParent.js\";\nimport getParentNode from \"./getParentNode.js\";\nimport getWindow from \"./getWindow.js\";\nimport isScrollParent from \"./isScrollParent.js\";\n/*\ngiven a DOM element, return the list of all scroll parents, up the list of ancesors\nuntil we get to the top window object. This list is what we attach scroll listeners\nto, because if any of these parent elements scroll, we'll need to re-calculate the\nreference element's position.\n*/\n\nexport default function listScrollParents(element, list) {\n var _element$ownerDocumen;\n\n if (list === void 0) {\n list = [];\n }\n\n var scrollParent = getScrollParent(element);\n var isBody = scrollParent === ((_element$ownerDocumen = element.ownerDocument) == null ? void 0 : _element$ownerDocumen.body);\n var win = getWindow(scrollParent);\n var target = isBody ? [win].concat(win.visualViewport || [], isScrollParent(scrollParent) ? scrollParent : []) : scrollParent;\n var updatedList = list.concat(target);\n return isBody ? updatedList : // $FlowFixMe[incompatible-call]: isBody tells us target will be an HTMLElement here\n updatedList.concat(listScrollParents(getParentNode(target)));\n}","export default function rectToClientRect(rect) {\n return Object.assign({}, rect, {\n left: rect.x,\n top: rect.y,\n right: rect.x + rect.width,\n bottom: rect.y + rect.height\n });\n}","import { viewport } from \"../enums.js\";\nimport getViewportRect from \"./getViewportRect.js\";\nimport getDocumentRect from \"./getDocumentRect.js\";\nimport listScrollParents from \"./listScrollParents.js\";\nimport getOffsetParent from \"./getOffsetParent.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport getComputedStyle from \"./getComputedStyle.js\";\nimport { isElement, isHTMLElement } from \"./instanceOf.js\";\nimport getBoundingClientRect from \"./getBoundingClientRect.js\";\nimport getParentNode from \"./getParentNode.js\";\nimport contains from \"./contains.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport rectToClientRect from \"../utils/rectToClientRect.js\";\nimport { max, min } from \"../utils/math.js\";\n\nfunction getInnerBoundingClientRect(element, strategy) {\n var rect = getBoundingClientRect(element, false, strategy === 'fixed');\n rect.top = rect.top + element.clientTop;\n rect.left = rect.left + element.clientLeft;\n rect.bottom = rect.top + element.clientHeight;\n rect.right = rect.left + element.clientWidth;\n rect.width = element.clientWidth;\n rect.height = element.clientHeight;\n rect.x = rect.left;\n rect.y = rect.top;\n return rect;\n}\n\nfunction getClientRectFromMixedType(element, clippingParent, strategy) {\n return clippingParent === viewport ? rectToClientRect(getViewportRect(element, strategy)) : isElement(clippingParent) ? getInnerBoundingClientRect(clippingParent, strategy) : rectToClientRect(getDocumentRect(getDocumentElement(element)));\n} // A \"clipping parent\" is an overflowable container with the characteristic of\n// clipping (or hiding) overflowing elements with a position different from\n// `initial`\n\n\nfunction getClippingParents(element) {\n var clippingParents = listScrollParents(getParentNode(element));\n var canEscapeClipping = ['absolute', 'fixed'].indexOf(getComputedStyle(element).position) >= 0;\n var clipperElement = canEscapeClipping && isHTMLElement(element) ? getOffsetParent(element) : element;\n\n if (!isElement(clipperElement)) {\n return [];\n } // $FlowFixMe[incompatible-return]: https://github.com/facebook/flow/issues/1414\n\n\n return clippingParents.filter(function (clippingParent) {\n return isElement(clippingParent) && contains(clippingParent, clipperElement) && getNodeName(clippingParent) !== 'body';\n });\n} // Gets the maximum area that the element is visible in due to any number of\n// clipping parents\n\n\nexport default function getClippingRect(element, boundary, rootBoundary, strategy) {\n var mainClippingParents = boundary === 'clippingParents' ? getClippingParents(element) : [].concat(boundary);\n var clippingParents = [].concat(mainClippingParents, [rootBoundary]);\n var firstClippingParent = clippingParents[0];\n var clippingRect = clippingParents.reduce(function (accRect, clippingParent) {\n var rect = getClientRectFromMixedType(element, clippingParent, strategy);\n accRect.top = max(rect.top, accRect.top);\n accRect.right = min(rect.right, accRect.right);\n accRect.bottom = min(rect.bottom, accRect.bottom);\n accRect.left = max(rect.left, accRect.left);\n return accRect;\n }, getClientRectFromMixedType(element, firstClippingParent, strategy));\n clippingRect.width = clippingRect.right - clippingRect.left;\n clippingRect.height = clippingRect.bottom - clippingRect.top;\n clippingRect.x = clippingRect.left;\n clippingRect.y = clippingRect.top;\n return clippingRect;\n}","import getWindow from \"./getWindow.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport getWindowScrollBarX from \"./getWindowScrollBarX.js\";\nimport isLayoutViewport from \"./isLayoutViewport.js\";\nexport default function getViewportRect(element, strategy) {\n var win = getWindow(element);\n var html = getDocumentElement(element);\n var visualViewport = win.visualViewport;\n var width = html.clientWidth;\n var height = html.clientHeight;\n var x = 0;\n var y = 0;\n\n if (visualViewport) {\n width = visualViewport.width;\n height = visualViewport.height;\n var layoutViewport = isLayoutViewport();\n\n if (layoutViewport || !layoutViewport && strategy === 'fixed') {\n x = visualViewport.offsetLeft;\n y = visualViewport.offsetTop;\n }\n }\n\n return {\n width: width,\n height: height,\n x: x + getWindowScrollBarX(element),\n y: y\n };\n}","import getDocumentElement from \"./getDocumentElement.js\";\nimport getComputedStyle from \"./getComputedStyle.js\";\nimport getWindowScrollBarX from \"./getWindowScrollBarX.js\";\nimport getWindowScroll from \"./getWindowScroll.js\";\nimport { max } from \"../utils/math.js\"; // Gets the entire size of the scrollable document area, even extending outside\n// of the `` and `` rect bounds if horizontally scrollable\n\nexport default function getDocumentRect(element) {\n var _element$ownerDocumen;\n\n var html = getDocumentElement(element);\n var winScroll = getWindowScroll(element);\n var body = (_element$ownerDocumen = element.ownerDocument) == null ? void 0 : _element$ownerDocumen.body;\n var width = max(html.scrollWidth, html.clientWidth, body ? body.scrollWidth : 0, body ? body.clientWidth : 0);\n var height = max(html.scrollHeight, html.clientHeight, body ? body.scrollHeight : 0, body ? body.clientHeight : 0);\n var x = -winScroll.scrollLeft + getWindowScrollBarX(element);\n var y = -winScroll.scrollTop;\n\n if (getComputedStyle(body || html).direction === 'rtl') {\n x += max(html.clientWidth, body ? body.clientWidth : 0) - width;\n }\n\n return {\n width: width,\n height: height,\n x: x,\n y: y\n };\n}","import getBasePlacement from \"./getBasePlacement.js\";\nimport getVariation from \"./getVariation.js\";\nimport getMainAxisFromPlacement from \"./getMainAxisFromPlacement.js\";\nimport { top, right, bottom, left, start, end } from \"../enums.js\";\nexport default function computeOffsets(_ref) {\n var reference = _ref.reference,\n element = _ref.element,\n placement = _ref.placement;\n var basePlacement = placement ? getBasePlacement(placement) : null;\n var variation = placement ? getVariation(placement) : null;\n var commonX = reference.x + reference.width / 2 - element.width / 2;\n var commonY = reference.y + reference.height / 2 - element.height / 2;\n var offsets;\n\n switch (basePlacement) {\n case top:\n offsets = {\n x: commonX,\n y: reference.y - element.height\n };\n break;\n\n case bottom:\n offsets = {\n x: commonX,\n y: reference.y + reference.height\n };\n break;\n\n case right:\n offsets = {\n x: reference.x + reference.width,\n y: commonY\n };\n break;\n\n case left:\n offsets = {\n x: reference.x - element.width,\n y: commonY\n };\n break;\n\n default:\n offsets = {\n x: reference.x,\n y: reference.y\n };\n }\n\n var mainAxis = basePlacement ? getMainAxisFromPlacement(basePlacement) : null;\n\n if (mainAxis != null) {\n var len = mainAxis === 'y' ? 'height' : 'width';\n\n switch (variation) {\n case start:\n offsets[mainAxis] = offsets[mainAxis] - (reference[len] / 2 - element[len] / 2);\n break;\n\n case end:\n offsets[mainAxis] = offsets[mainAxis] + (reference[len] / 2 - element[len] / 2);\n break;\n\n default:\n }\n }\n\n return offsets;\n}","import getClippingRect from \"../dom-utils/getClippingRect.js\";\nimport getDocumentElement from \"../dom-utils/getDocumentElement.js\";\nimport getBoundingClientRect from \"../dom-utils/getBoundingClientRect.js\";\nimport computeOffsets from \"./computeOffsets.js\";\nimport rectToClientRect from \"./rectToClientRect.js\";\nimport { clippingParents, reference, popper, bottom, top, right, basePlacements, viewport } from \"../enums.js\";\nimport { isElement } from \"../dom-utils/instanceOf.js\";\nimport mergePaddingObject from \"./mergePaddingObject.js\";\nimport expandToHashMap from \"./expandToHashMap.js\"; // eslint-disable-next-line import/no-unused-modules\n\nexport default function detectOverflow(state, options) {\n if (options === void 0) {\n options = {};\n }\n\n var _options = options,\n _options$placement = _options.placement,\n placement = _options$placement === void 0 ? state.placement : _options$placement,\n _options$strategy = _options.strategy,\n strategy = _options$strategy === void 0 ? state.strategy : _options$strategy,\n _options$boundary = _options.boundary,\n boundary = _options$boundary === void 0 ? clippingParents : _options$boundary,\n _options$rootBoundary = _options.rootBoundary,\n rootBoundary = _options$rootBoundary === void 0 ? viewport : _options$rootBoundary,\n _options$elementConte = _options.elementContext,\n elementContext = _options$elementConte === void 0 ? popper : _options$elementConte,\n _options$altBoundary = _options.altBoundary,\n altBoundary = _options$altBoundary === void 0 ? false : _options$altBoundary,\n _options$padding = _options.padding,\n padding = _options$padding === void 0 ? 0 : _options$padding;\n var paddingObject = mergePaddingObject(typeof padding !== 'number' ? padding : expandToHashMap(padding, basePlacements));\n var altContext = elementContext === popper ? reference : popper;\n var popperRect = state.rects.popper;\n var element = state.elements[altBoundary ? altContext : elementContext];\n var clippingClientRect = getClippingRect(isElement(element) ? element : element.contextElement || getDocumentElement(state.elements.popper), boundary, rootBoundary, strategy);\n var referenceClientRect = getBoundingClientRect(state.elements.reference);\n var popperOffsets = computeOffsets({\n reference: referenceClientRect,\n element: popperRect,\n strategy: 'absolute',\n placement: placement\n });\n var popperClientRect = rectToClientRect(Object.assign({}, popperRect, popperOffsets));\n var elementClientRect = elementContext === popper ? popperClientRect : referenceClientRect; // positive = overflowing the clipping rect\n // 0 or negative = within the clipping rect\n\n var overflowOffsets = {\n top: clippingClientRect.top - elementClientRect.top + paddingObject.top,\n bottom: elementClientRect.bottom - clippingClientRect.bottom + paddingObject.bottom,\n left: clippingClientRect.left - elementClientRect.left + paddingObject.left,\n right: elementClientRect.right - clippingClientRect.right + paddingObject.right\n };\n var offsetData = state.modifiersData.offset; // Offsets can be applied only to the popper element\n\n if (elementContext === popper && offsetData) {\n var offset = offsetData[placement];\n Object.keys(overflowOffsets).forEach(function (key) {\n var multiply = [right, bottom].indexOf(key) >= 0 ? 1 : -1;\n var axis = [top, bottom].indexOf(key) >= 0 ? 'y' : 'x';\n overflowOffsets[key] += offset[axis] * multiply;\n });\n }\n\n return overflowOffsets;\n}","import getVariation from \"./getVariation.js\";\nimport { variationPlacements, basePlacements, placements as allPlacements } from \"../enums.js\";\nimport detectOverflow from \"./detectOverflow.js\";\nimport getBasePlacement from \"./getBasePlacement.js\";\nexport default function computeAutoPlacement(state, options) {\n if (options === void 0) {\n options = {};\n }\n\n var _options = options,\n placement = _options.placement,\n boundary = _options.boundary,\n rootBoundary = _options.rootBoundary,\n padding = _options.padding,\n flipVariations = _options.flipVariations,\n _options$allowedAutoP = _options.allowedAutoPlacements,\n allowedAutoPlacements = _options$allowedAutoP === void 0 ? allPlacements : _options$allowedAutoP;\n var variation = getVariation(placement);\n var placements = variation ? flipVariations ? variationPlacements : variationPlacements.filter(function (placement) {\n return getVariation(placement) === variation;\n }) : basePlacements;\n var allowedPlacements = placements.filter(function (placement) {\n return allowedAutoPlacements.indexOf(placement) >= 0;\n });\n\n if (allowedPlacements.length === 0) {\n allowedPlacements = placements;\n } // $FlowFixMe[incompatible-type]: Flow seems to have problems with two array unions...\n\n\n var overflows = allowedPlacements.reduce(function (acc, placement) {\n acc[placement] = detectOverflow(state, {\n placement: placement,\n boundary: boundary,\n rootBoundary: rootBoundary,\n padding: padding\n })[getBasePlacement(placement)];\n return acc;\n }, {});\n return Object.keys(overflows).sort(function (a, b) {\n return overflows[a] - overflows[b];\n });\n}","import getOppositePlacement from \"../utils/getOppositePlacement.js\";\nimport getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getOppositeVariationPlacement from \"../utils/getOppositeVariationPlacement.js\";\nimport detectOverflow from \"../utils/detectOverflow.js\";\nimport computeAutoPlacement from \"../utils/computeAutoPlacement.js\";\nimport { bottom, top, start, right, left, auto } from \"../enums.js\";\nimport getVariation from \"../utils/getVariation.js\"; // eslint-disable-next-line import/no-unused-modules\n\nfunction getExpandedFallbackPlacements(placement) {\n if (getBasePlacement(placement) === auto) {\n return [];\n }\n\n var oppositePlacement = getOppositePlacement(placement);\n return [getOppositeVariationPlacement(placement), oppositePlacement, getOppositeVariationPlacement(oppositePlacement)];\n}\n\nfunction flip(_ref) {\n var state = _ref.state,\n options = _ref.options,\n name = _ref.name;\n\n if (state.modifiersData[name]._skip) {\n return;\n }\n\n var _options$mainAxis = options.mainAxis,\n checkMainAxis = _options$mainAxis === void 0 ? true : _options$mainAxis,\n _options$altAxis = options.altAxis,\n checkAltAxis = _options$altAxis === void 0 ? true : _options$altAxis,\n specifiedFallbackPlacements = options.fallbackPlacements,\n padding = options.padding,\n boundary = options.boundary,\n rootBoundary = options.rootBoundary,\n altBoundary = options.altBoundary,\n _options$flipVariatio = options.flipVariations,\n flipVariations = _options$flipVariatio === void 0 ? true : _options$flipVariatio,\n allowedAutoPlacements = options.allowedAutoPlacements;\n var preferredPlacement = state.options.placement;\n var basePlacement = getBasePlacement(preferredPlacement);\n var isBasePlacement = basePlacement === preferredPlacement;\n var fallbackPlacements = specifiedFallbackPlacements || (isBasePlacement || !flipVariations ? [getOppositePlacement(preferredPlacement)] : getExpandedFallbackPlacements(preferredPlacement));\n var placements = [preferredPlacement].concat(fallbackPlacements).reduce(function (acc, placement) {\n return acc.concat(getBasePlacement(placement) === auto ? computeAutoPlacement(state, {\n placement: placement,\n boundary: boundary,\n rootBoundary: rootBoundary,\n padding: padding,\n flipVariations: flipVariations,\n allowedAutoPlacements: allowedAutoPlacements\n }) : placement);\n }, []);\n var referenceRect = state.rects.reference;\n var popperRect = state.rects.popper;\n var checksMap = new Map();\n var makeFallbackChecks = true;\n var firstFittingPlacement = placements[0];\n\n for (var i = 0; i < placements.length; i++) {\n var placement = placements[i];\n\n var _basePlacement = getBasePlacement(placement);\n\n var isStartVariation = getVariation(placement) === start;\n var isVertical = [top, bottom].indexOf(_basePlacement) >= 0;\n var len = isVertical ? 'width' : 'height';\n var overflow = detectOverflow(state, {\n placement: placement,\n boundary: boundary,\n rootBoundary: rootBoundary,\n altBoundary: altBoundary,\n padding: padding\n });\n var mainVariationSide = isVertical ? isStartVariation ? right : left : isStartVariation ? bottom : top;\n\n if (referenceRect[len] > popperRect[len]) {\n mainVariationSide = getOppositePlacement(mainVariationSide);\n }\n\n var altVariationSide = getOppositePlacement(mainVariationSide);\n var checks = [];\n\n if (checkMainAxis) {\n checks.push(overflow[_basePlacement] <= 0);\n }\n\n if (checkAltAxis) {\n checks.push(overflow[mainVariationSide] <= 0, overflow[altVariationSide] <= 0);\n }\n\n if (checks.every(function (check) {\n return check;\n })) {\n firstFittingPlacement = placement;\n makeFallbackChecks = false;\n break;\n }\n\n checksMap.set(placement, checks);\n }\n\n if (makeFallbackChecks) {\n // `2` may be desired in some cases – research later\n var numberOfChecks = flipVariations ? 3 : 1;\n\n var _loop = function _loop(_i) {\n var fittingPlacement = placements.find(function (placement) {\n var checks = checksMap.get(placement);\n\n if (checks) {\n return checks.slice(0, _i).every(function (check) {\n return check;\n });\n }\n });\n\n if (fittingPlacement) {\n firstFittingPlacement = fittingPlacement;\n return \"break\";\n }\n };\n\n for (var _i = numberOfChecks; _i > 0; _i--) {\n var _ret = _loop(_i);\n\n if (_ret === \"break\") break;\n }\n }\n\n if (state.placement !== firstFittingPlacement) {\n state.modifiersData[name]._skip = true;\n state.placement = firstFittingPlacement;\n state.reset = true;\n }\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'flip',\n enabled: true,\n phase: 'main',\n fn: flip,\n requiresIfExists: ['offset'],\n data: {\n _skip: false\n }\n};","import { top, bottom, left, right } from \"../enums.js\";\nimport detectOverflow from \"../utils/detectOverflow.js\";\n\nfunction getSideOffsets(overflow, rect, preventedOffsets) {\n if (preventedOffsets === void 0) {\n preventedOffsets = {\n x: 0,\n y: 0\n };\n }\n\n return {\n top: overflow.top - rect.height - preventedOffsets.y,\n right: overflow.right - rect.width + preventedOffsets.x,\n bottom: overflow.bottom - rect.height + preventedOffsets.y,\n left: overflow.left - rect.width - preventedOffsets.x\n };\n}\n\nfunction isAnySideFullyClipped(overflow) {\n return [top, right, bottom, left].some(function (side) {\n return overflow[side] >= 0;\n });\n}\n\nfunction hide(_ref) {\n var state = _ref.state,\n name = _ref.name;\n var referenceRect = state.rects.reference;\n var popperRect = state.rects.popper;\n var preventedOffsets = state.modifiersData.preventOverflow;\n var referenceOverflow = detectOverflow(state, {\n elementContext: 'reference'\n });\n var popperAltOverflow = detectOverflow(state, {\n altBoundary: true\n });\n var referenceClippingOffsets = getSideOffsets(referenceOverflow, referenceRect);\n var popperEscapeOffsets = getSideOffsets(popperAltOverflow, popperRect, preventedOffsets);\n var isReferenceHidden = isAnySideFullyClipped(referenceClippingOffsets);\n var hasPopperEscaped = isAnySideFullyClipped(popperEscapeOffsets);\n state.modifiersData[name] = {\n referenceClippingOffsets: referenceClippingOffsets,\n popperEscapeOffsets: popperEscapeOffsets,\n isReferenceHidden: isReferenceHidden,\n hasPopperEscaped: hasPopperEscaped\n };\n state.attributes.popper = Object.assign({}, state.attributes.popper, {\n 'data-popper-reference-hidden': isReferenceHidden,\n 'data-popper-escaped': hasPopperEscaped\n });\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'hide',\n enabled: true,\n phase: 'main',\n requiresIfExists: ['preventOverflow'],\n fn: hide\n};","import getBasePlacement from \"../utils/getBasePlacement.js\";\nimport { top, left, right, placements } from \"../enums.js\"; // eslint-disable-next-line import/no-unused-modules\n\nexport function distanceAndSkiddingToXY(placement, rects, offset) {\n var basePlacement = getBasePlacement(placement);\n var invertDistance = [left, top].indexOf(basePlacement) >= 0 ? -1 : 1;\n\n var _ref = typeof offset === 'function' ? offset(Object.assign({}, rects, {\n placement: placement\n })) : offset,\n skidding = _ref[0],\n distance = _ref[1];\n\n skidding = skidding || 0;\n distance = (distance || 0) * invertDistance;\n return [left, right].indexOf(basePlacement) >= 0 ? {\n x: distance,\n y: skidding\n } : {\n x: skidding,\n y: distance\n };\n}\n\nfunction offset(_ref2) {\n var state = _ref2.state,\n options = _ref2.options,\n name = _ref2.name;\n var _options$offset = options.offset,\n offset = _options$offset === void 0 ? [0, 0] : _options$offset;\n var data = placements.reduce(function (acc, placement) {\n acc[placement] = distanceAndSkiddingToXY(placement, state.rects, offset);\n return acc;\n }, {});\n var _data$state$placement = data[state.placement],\n x = _data$state$placement.x,\n y = _data$state$placement.y;\n\n if (state.modifiersData.popperOffsets != null) {\n state.modifiersData.popperOffsets.x += x;\n state.modifiersData.popperOffsets.y += y;\n }\n\n state.modifiersData[name] = data;\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'offset',\n enabled: true,\n phase: 'main',\n requires: ['popperOffsets'],\n fn: offset\n};","import computeOffsets from \"../utils/computeOffsets.js\";\n\nfunction popperOffsets(_ref) {\n var state = _ref.state,\n name = _ref.name;\n // Offsets are the actual position the popper needs to have to be\n // properly positioned near its reference element\n // This is the most basic placement, and will be adjusted by\n // the modifiers in the next step\n state.modifiersData[name] = computeOffsets({\n reference: state.rects.reference,\n element: state.rects.popper,\n strategy: 'absolute',\n placement: state.placement\n });\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'popperOffsets',\n enabled: true,\n phase: 'read',\n fn: popperOffsets,\n data: {}\n};","import { top, left, right, bottom, start } from \"../enums.js\";\nimport getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getMainAxisFromPlacement from \"../utils/getMainAxisFromPlacement.js\";\nimport getAltAxis from \"../utils/getAltAxis.js\";\nimport { within, withinMaxClamp } from \"../utils/within.js\";\nimport getLayoutRect from \"../dom-utils/getLayoutRect.js\";\nimport getOffsetParent from \"../dom-utils/getOffsetParent.js\";\nimport detectOverflow from \"../utils/detectOverflow.js\";\nimport getVariation from \"../utils/getVariation.js\";\nimport getFreshSideObject from \"../utils/getFreshSideObject.js\";\nimport { min as mathMin, max as mathMax } from \"../utils/math.js\";\n\nfunction preventOverflow(_ref) {\n var state = _ref.state,\n options = _ref.options,\n name = _ref.name;\n var _options$mainAxis = options.mainAxis,\n checkMainAxis = _options$mainAxis === void 0 ? true : _options$mainAxis,\n _options$altAxis = options.altAxis,\n checkAltAxis = _options$altAxis === void 0 ? false : _options$altAxis,\n boundary = options.boundary,\n rootBoundary = options.rootBoundary,\n altBoundary = options.altBoundary,\n padding = options.padding,\n _options$tether = options.tether,\n tether = _options$tether === void 0 ? true : _options$tether,\n _options$tetherOffset = options.tetherOffset,\n tetherOffset = _options$tetherOffset === void 0 ? 0 : _options$tetherOffset;\n var overflow = detectOverflow(state, {\n boundary: boundary,\n rootBoundary: rootBoundary,\n padding: padding,\n altBoundary: altBoundary\n });\n var basePlacement = getBasePlacement(state.placement);\n var variation = getVariation(state.placement);\n var isBasePlacement = !variation;\n var mainAxis = getMainAxisFromPlacement(basePlacement);\n var altAxis = getAltAxis(mainAxis);\n var popperOffsets = state.modifiersData.popperOffsets;\n var referenceRect = state.rects.reference;\n var popperRect = state.rects.popper;\n var tetherOffsetValue = typeof tetherOffset === 'function' ? tetherOffset(Object.assign({}, state.rects, {\n placement: state.placement\n })) : tetherOffset;\n var normalizedTetherOffsetValue = typeof tetherOffsetValue === 'number' ? {\n mainAxis: tetherOffsetValue,\n altAxis: tetherOffsetValue\n } : Object.assign({\n mainAxis: 0,\n altAxis: 0\n }, tetherOffsetValue);\n var offsetModifierState = state.modifiersData.offset ? state.modifiersData.offset[state.placement] : null;\n var data = {\n x: 0,\n y: 0\n };\n\n if (!popperOffsets) {\n return;\n }\n\n if (checkMainAxis) {\n var _offsetModifierState$;\n\n var mainSide = mainAxis === 'y' ? top : left;\n var altSide = mainAxis === 'y' ? bottom : right;\n var len = mainAxis === 'y' ? 'height' : 'width';\n var offset = popperOffsets[mainAxis];\n var min = offset + overflow[mainSide];\n var max = offset - overflow[altSide];\n var additive = tether ? -popperRect[len] / 2 : 0;\n var minLen = variation === start ? referenceRect[len] : popperRect[len];\n var maxLen = variation === start ? -popperRect[len] : -referenceRect[len]; // We need to include the arrow in the calculation so the arrow doesn't go\n // outside the reference bounds\n\n var arrowElement = state.elements.arrow;\n var arrowRect = tether && arrowElement ? getLayoutRect(arrowElement) : {\n width: 0,\n height: 0\n };\n var arrowPaddingObject = state.modifiersData['arrow#persistent'] ? state.modifiersData['arrow#persistent'].padding : getFreshSideObject();\n var arrowPaddingMin = arrowPaddingObject[mainSide];\n var arrowPaddingMax = arrowPaddingObject[altSide]; // If the reference length is smaller than the arrow length, we don't want\n // to include its full size in the calculation. If the reference is small\n // and near the edge of a boundary, the popper can overflow even if the\n // reference is not overflowing as well (e.g. virtual elements with no\n // width or height)\n\n var arrowLen = within(0, referenceRect[len], arrowRect[len]);\n var minOffset = isBasePlacement ? referenceRect[len] / 2 - additive - arrowLen - arrowPaddingMin - normalizedTetherOffsetValue.mainAxis : minLen - arrowLen - arrowPaddingMin - normalizedTetherOffsetValue.mainAxis;\n var maxOffset = isBasePlacement ? -referenceRect[len] / 2 + additive + arrowLen + arrowPaddingMax + normalizedTetherOffsetValue.mainAxis : maxLen + arrowLen + arrowPaddingMax + normalizedTetherOffsetValue.mainAxis;\n var arrowOffsetParent = state.elements.arrow && getOffsetParent(state.elements.arrow);\n var clientOffset = arrowOffsetParent ? mainAxis === 'y' ? arrowOffsetParent.clientTop || 0 : arrowOffsetParent.clientLeft || 0 : 0;\n var offsetModifierValue = (_offsetModifierState$ = offsetModifierState == null ? void 0 : offsetModifierState[mainAxis]) != null ? _offsetModifierState$ : 0;\n var tetherMin = offset + minOffset - offsetModifierValue - clientOffset;\n var tetherMax = offset + maxOffset - offsetModifierValue;\n var preventedOffset = within(tether ? mathMin(min, tetherMin) : min, offset, tether ? mathMax(max, tetherMax) : max);\n popperOffsets[mainAxis] = preventedOffset;\n data[mainAxis] = preventedOffset - offset;\n }\n\n if (checkAltAxis) {\n var _offsetModifierState$2;\n\n var _mainSide = mainAxis === 'x' ? top : left;\n\n var _altSide = mainAxis === 'x' ? bottom : right;\n\n var _offset = popperOffsets[altAxis];\n\n var _len = altAxis === 'y' ? 'height' : 'width';\n\n var _min = _offset + overflow[_mainSide];\n\n var _max = _offset - overflow[_altSide];\n\n var isOriginSide = [top, left].indexOf(basePlacement) !== -1;\n\n var _offsetModifierValue = (_offsetModifierState$2 = offsetModifierState == null ? void 0 : offsetModifierState[altAxis]) != null ? _offsetModifierState$2 : 0;\n\n var _tetherMin = isOriginSide ? _min : _offset - referenceRect[_len] - popperRect[_len] - _offsetModifierValue + normalizedTetherOffsetValue.altAxis;\n\n var _tetherMax = isOriginSide ? _offset + referenceRect[_len] + popperRect[_len] - _offsetModifierValue - normalizedTetherOffsetValue.altAxis : _max;\n\n var _preventedOffset = tether && isOriginSide ? withinMaxClamp(_tetherMin, _offset, _tetherMax) : within(tether ? _tetherMin : _min, _offset, tether ? _tetherMax : _max);\n\n popperOffsets[altAxis] = _preventedOffset;\n data[altAxis] = _preventedOffset - _offset;\n }\n\n state.modifiersData[name] = data;\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'preventOverflow',\n enabled: true,\n phase: 'main',\n fn: preventOverflow,\n requiresIfExists: ['offset']\n};","export default function getAltAxis(axis) {\n return axis === 'x' ? 'y' : 'x';\n}","import getBoundingClientRect from \"./getBoundingClientRect.js\";\nimport getNodeScroll from \"./getNodeScroll.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport { isHTMLElement } from \"./instanceOf.js\";\nimport getWindowScrollBarX from \"./getWindowScrollBarX.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport isScrollParent from \"./isScrollParent.js\";\nimport { round } from \"../utils/math.js\";\n\nfunction isElementScaled(element) {\n var rect = element.getBoundingClientRect();\n var scaleX = round(rect.width) / element.offsetWidth || 1;\n var scaleY = round(rect.height) / element.offsetHeight || 1;\n return scaleX !== 1 || scaleY !== 1;\n} // Returns the composite rect of an element relative to its offsetParent.\n// Composite means it takes into account transforms as well as layout.\n\n\nexport default function getCompositeRect(elementOrVirtualElement, offsetParent, isFixed) {\n if (isFixed === void 0) {\n isFixed = false;\n }\n\n var isOffsetParentAnElement = isHTMLElement(offsetParent);\n var offsetParentIsScaled = isHTMLElement(offsetParent) && isElementScaled(offsetParent);\n var documentElement = getDocumentElement(offsetParent);\n var rect = getBoundingClientRect(elementOrVirtualElement, offsetParentIsScaled, isFixed);\n var scroll = {\n scrollLeft: 0,\n scrollTop: 0\n };\n var offsets = {\n x: 0,\n y: 0\n };\n\n if (isOffsetParentAnElement || !isOffsetParentAnElement && !isFixed) {\n if (getNodeName(offsetParent) !== 'body' || // https://github.com/popperjs/popper-core/issues/1078\n isScrollParent(documentElement)) {\n scroll = getNodeScroll(offsetParent);\n }\n\n if (isHTMLElement(offsetParent)) {\n offsets = getBoundingClientRect(offsetParent, true);\n offsets.x += offsetParent.clientLeft;\n offsets.y += offsetParent.clientTop;\n } else if (documentElement) {\n offsets.x = getWindowScrollBarX(documentElement);\n }\n }\n\n return {\n x: rect.left + scroll.scrollLeft - offsets.x,\n y: rect.top + scroll.scrollTop - offsets.y,\n width: rect.width,\n height: rect.height\n };\n}","import getWindowScroll from \"./getWindowScroll.js\";\nimport getWindow from \"./getWindow.js\";\nimport { isHTMLElement } from \"./instanceOf.js\";\nimport getHTMLElementScroll from \"./getHTMLElementScroll.js\";\nexport default function getNodeScroll(node) {\n if (node === getWindow(node) || !isHTMLElement(node)) {\n return getWindowScroll(node);\n } else {\n return getHTMLElementScroll(node);\n }\n}","export default function getHTMLElementScroll(element) {\n return {\n scrollLeft: element.scrollLeft,\n scrollTop: element.scrollTop\n };\n}","import { modifierPhases } from \"../enums.js\"; // source: https://stackoverflow.com/questions/49875255\n\nfunction order(modifiers) {\n var map = new Map();\n var visited = new Set();\n var result = [];\n modifiers.forEach(function (modifier) {\n map.set(modifier.name, modifier);\n }); // On visiting object, check for its dependencies and visit them recursively\n\n function sort(modifier) {\n visited.add(modifier.name);\n var requires = [].concat(modifier.requires || [], modifier.requiresIfExists || []);\n requires.forEach(function (dep) {\n if (!visited.has(dep)) {\n var depModifier = map.get(dep);\n\n if (depModifier) {\n sort(depModifier);\n }\n }\n });\n result.push(modifier);\n }\n\n modifiers.forEach(function (modifier) {\n if (!visited.has(modifier.name)) {\n // check for visited object\n sort(modifier);\n }\n });\n return result;\n}\n\nexport default function orderModifiers(modifiers) {\n // order based on dependencies\n var orderedModifiers = order(modifiers); // order based on phase\n\n return modifierPhases.reduce(function (acc, phase) {\n return acc.concat(orderedModifiers.filter(function (modifier) {\n return modifier.phase === phase;\n }));\n }, []);\n}","import getCompositeRect from \"./dom-utils/getCompositeRect.js\";\nimport getLayoutRect from \"./dom-utils/getLayoutRect.js\";\nimport listScrollParents from \"./dom-utils/listScrollParents.js\";\nimport getOffsetParent from \"./dom-utils/getOffsetParent.js\";\nimport orderModifiers from \"./utils/orderModifiers.js\";\nimport debounce from \"./utils/debounce.js\";\nimport mergeByName from \"./utils/mergeByName.js\";\nimport detectOverflow from \"./utils/detectOverflow.js\";\nimport { isElement } from \"./dom-utils/instanceOf.js\";\nvar DEFAULT_OPTIONS = {\n placement: 'bottom',\n modifiers: [],\n strategy: 'absolute'\n};\n\nfunction areValidElements() {\n for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {\n args[_key] = arguments[_key];\n }\n\n return !args.some(function (element) {\n return !(element && typeof element.getBoundingClientRect === 'function');\n });\n}\n\nexport function popperGenerator(generatorOptions) {\n if (generatorOptions === void 0) {\n generatorOptions = {};\n }\n\n var _generatorOptions = generatorOptions,\n _generatorOptions$def = _generatorOptions.defaultModifiers,\n defaultModifiers = _generatorOptions$def === void 0 ? [] : _generatorOptions$def,\n _generatorOptions$def2 = _generatorOptions.defaultOptions,\n defaultOptions = _generatorOptions$def2 === void 0 ? DEFAULT_OPTIONS : _generatorOptions$def2;\n return function createPopper(reference, popper, options) {\n if (options === void 0) {\n options = defaultOptions;\n }\n\n var state = {\n placement: 'bottom',\n orderedModifiers: [],\n options: Object.assign({}, DEFAULT_OPTIONS, defaultOptions),\n modifiersData: {},\n elements: {\n reference: reference,\n popper: popper\n },\n attributes: {},\n styles: {}\n };\n var effectCleanupFns = [];\n var isDestroyed = false;\n var instance = {\n state: state,\n setOptions: function setOptions(setOptionsAction) {\n var options = typeof setOptionsAction === 'function' ? setOptionsAction(state.options) : setOptionsAction;\n cleanupModifierEffects();\n state.options = Object.assign({}, defaultOptions, state.options, options);\n state.scrollParents = {\n reference: isElement(reference) ? listScrollParents(reference) : reference.contextElement ? listScrollParents(reference.contextElement) : [],\n popper: listScrollParents(popper)\n }; // Orders the modifiers based on their dependencies and `phase`\n // properties\n\n var orderedModifiers = orderModifiers(mergeByName([].concat(defaultModifiers, state.options.modifiers))); // Strip out disabled modifiers\n\n state.orderedModifiers = orderedModifiers.filter(function (m) {\n return m.enabled;\n });\n runModifierEffects();\n return instance.update();\n },\n // Sync update – it will always be executed, even if not necessary. This\n // is useful for low frequency updates where sync behavior simplifies the\n // logic.\n // For high frequency updates (e.g. `resize` and `scroll` events), always\n // prefer the async Popper#update method\n forceUpdate: function forceUpdate() {\n if (isDestroyed) {\n return;\n }\n\n var _state$elements = state.elements,\n reference = _state$elements.reference,\n popper = _state$elements.popper; // Don't proceed if `reference` or `popper` are not valid elements\n // anymore\n\n if (!areValidElements(reference, popper)) {\n return;\n } // Store the reference and popper rects to be read by modifiers\n\n\n state.rects = {\n reference: getCompositeRect(reference, getOffsetParent(popper), state.options.strategy === 'fixed'),\n popper: getLayoutRect(popper)\n }; // Modifiers have the ability to reset the current update cycle. The\n // most common use case for this is the `flip` modifier changing the\n // placement, which then needs to re-run all the modifiers, because the\n // logic was previously ran for the previous placement and is therefore\n // stale/incorrect\n\n state.reset = false;\n state.placement = state.options.placement; // On each update cycle, the `modifiersData` property for each modifier\n // is filled with the initial data specified by the modifier. This means\n // it doesn't persist and is fresh on each update.\n // To ensure persistent data, use `${name}#persistent`\n\n state.orderedModifiers.forEach(function (modifier) {\n return state.modifiersData[modifier.name] = Object.assign({}, modifier.data);\n });\n\n for (var index = 0; index < state.orderedModifiers.length; index++) {\n if (state.reset === true) {\n state.reset = false;\n index = -1;\n continue;\n }\n\n var _state$orderedModifie = state.orderedModifiers[index],\n fn = _state$orderedModifie.fn,\n _state$orderedModifie2 = _state$orderedModifie.options,\n _options = _state$orderedModifie2 === void 0 ? {} : _state$orderedModifie2,\n name = _state$orderedModifie.name;\n\n if (typeof fn === 'function') {\n state = fn({\n state: state,\n options: _options,\n name: name,\n instance: instance\n }) || state;\n }\n }\n },\n // Async and optimistically optimized update – it will not be executed if\n // not necessary (debounced to run at most once-per-tick)\n update: debounce(function () {\n return new Promise(function (resolve) {\n instance.forceUpdate();\n resolve(state);\n });\n }),\n destroy: function destroy() {\n cleanupModifierEffects();\n isDestroyed = true;\n }\n };\n\n if (!areValidElements(reference, popper)) {\n return instance;\n }\n\n instance.setOptions(options).then(function (state) {\n if (!isDestroyed && options.onFirstUpdate) {\n options.onFirstUpdate(state);\n }\n }); // Modifiers have the ability to execute arbitrary code before the first\n // update cycle runs. They will be executed in the same order as the update\n // cycle. This is useful when a modifier adds some persistent data that\n // other modifiers need to use, but the modifier is run after the dependent\n // one.\n\n function runModifierEffects() {\n state.orderedModifiers.forEach(function (_ref) {\n var name = _ref.name,\n _ref$options = _ref.options,\n options = _ref$options === void 0 ? {} : _ref$options,\n effect = _ref.effect;\n\n if (typeof effect === 'function') {\n var cleanupFn = effect({\n state: state,\n name: name,\n instance: instance,\n options: options\n });\n\n var noopFn = function noopFn() {};\n\n effectCleanupFns.push(cleanupFn || noopFn);\n }\n });\n }\n\n function cleanupModifierEffects() {\n effectCleanupFns.forEach(function (fn) {\n return fn();\n });\n effectCleanupFns = [];\n }\n\n return instance;\n };\n}\nexport var createPopper = /*#__PURE__*/popperGenerator(); // eslint-disable-next-line import/no-unused-modules\n\nexport { detectOverflow };","export default function debounce(fn) {\n var pending;\n return function () {\n if (!pending) {\n pending = new Promise(function (resolve) {\n Promise.resolve().then(function () {\n pending = undefined;\n resolve(fn());\n });\n });\n }\n\n return pending;\n };\n}","export default function mergeByName(modifiers) {\n var merged = modifiers.reduce(function (merged, current) {\n var existing = merged[current.name];\n merged[current.name] = existing ? Object.assign({}, existing, current, {\n options: Object.assign({}, existing.options, current.options),\n data: Object.assign({}, existing.data, current.data)\n }) : current;\n return merged;\n }, {}); // IE11 does not support Object.values\n\n return Object.keys(merged).map(function (key) {\n return merged[key];\n });\n}","import { popperGenerator, detectOverflow } from \"./createPopper.js\";\nimport eventListeners from \"./modifiers/eventListeners.js\";\nimport popperOffsets from \"./modifiers/popperOffsets.js\";\nimport computeStyles from \"./modifiers/computeStyles.js\";\nimport applyStyles from \"./modifiers/applyStyles.js\";\nvar defaultModifiers = [eventListeners, popperOffsets, computeStyles, applyStyles];\nvar createPopper = /*#__PURE__*/popperGenerator({\n defaultModifiers: defaultModifiers\n}); // eslint-disable-next-line import/no-unused-modules\n\nexport { createPopper, popperGenerator, defaultModifiers, detectOverflow };","import { popperGenerator, detectOverflow } from \"./createPopper.js\";\nimport eventListeners from \"./modifiers/eventListeners.js\";\nimport popperOffsets from \"./modifiers/popperOffsets.js\";\nimport computeStyles from \"./modifiers/computeStyles.js\";\nimport applyStyles from \"./modifiers/applyStyles.js\";\nimport offset from \"./modifiers/offset.js\";\nimport flip from \"./modifiers/flip.js\";\nimport preventOverflow from \"./modifiers/preventOverflow.js\";\nimport arrow from \"./modifiers/arrow.js\";\nimport hide from \"./modifiers/hide.js\";\nvar defaultModifiers = [eventListeners, popperOffsets, computeStyles, applyStyles, offset, flip, preventOverflow, arrow, hide];\nvar createPopper = /*#__PURE__*/popperGenerator({\n defaultModifiers: defaultModifiers\n}); // eslint-disable-next-line import/no-unused-modules\n\nexport { createPopper, popperGenerator, defaultModifiers, detectOverflow }; // eslint-disable-next-line import/no-unused-modules\n\nexport { createPopper as createPopperLite } from \"./popper-lite.js\"; // eslint-disable-next-line import/no-unused-modules\n\nexport * from \"./modifiers/index.js\";","/**\n * --------------------------------------------------------------------------\n * Bootstrap dropdown.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport * as Popper from '@popperjs/core'\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport Manipulator from './dom/manipulator.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport {\n defineJQueryPlugin,\n execute,\n getElement,\n getNextActiveElement,\n isDisabled,\n isElement,\n isRTL,\n isVisible,\n noop\n} from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'dropdown'\nconst DATA_KEY = 'bs.dropdown'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst ESCAPE_KEY = 'Escape'\nconst TAB_KEY = 'Tab'\nconst ARROW_UP_KEY = 'ArrowUp'\nconst ARROW_DOWN_KEY = 'ArrowDown'\nconst RIGHT_MOUSE_BUTTON = 2 // MouseEvent.button value for the secondary button, usually the right button\n\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_DROPUP = 'dropup'\nconst CLASS_NAME_DROPEND = 'dropend'\nconst CLASS_NAME_DROPSTART = 'dropstart'\nconst CLASS_NAME_DROPUP_CENTER = 'dropup-center'\nconst CLASS_NAME_DROPDOWN_CENTER = 'dropdown-center'\n\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"dropdown\"]:not(.disabled):not(:disabled)'\nconst SELECTOR_DATA_TOGGLE_SHOWN = `${SELECTOR_DATA_TOGGLE}.${CLASS_NAME_SHOW}`\nconst SELECTOR_MENU = '.dropdown-menu'\nconst SELECTOR_NAVBAR = '.navbar'\nconst SELECTOR_NAVBAR_NAV = '.navbar-nav'\nconst SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)'\n\nconst PLACEMENT_TOP = isRTL() ? 'top-end' : 'top-start'\nconst PLACEMENT_TOPEND = isRTL() ? 'top-start' : 'top-end'\nconst PLACEMENT_BOTTOM = isRTL() ? 'bottom-end' : 'bottom-start'\nconst PLACEMENT_BOTTOMEND = isRTL() ? 'bottom-start' : 'bottom-end'\nconst PLACEMENT_RIGHT = isRTL() ? 'left-start' : 'right-start'\nconst PLACEMENT_LEFT = isRTL() ? 'right-start' : 'left-start'\nconst PLACEMENT_TOPCENTER = 'top'\nconst PLACEMENT_BOTTOMCENTER = 'bottom'\n\nconst Default = {\n autoClose: true,\n boundary: 'clippingParents',\n display: 'dynamic',\n offset: [0, 2],\n popperConfig: null,\n reference: 'toggle'\n}\n\nconst DefaultType = {\n autoClose: '(boolean|string)',\n boundary: '(string|element)',\n display: 'string',\n offset: '(array|string|function)',\n popperConfig: '(null|object|function)',\n reference: '(string|element|object)'\n}\n\n/**\n * Class definition\n */\n\nclass Dropdown extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._popper = null\n this._parent = this._element.parentNode // dropdown wrapper\n // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/\n this._menu = SelectorEngine.next(this._element, SELECTOR_MENU)[0] ||\n SelectorEngine.prev(this._element, SELECTOR_MENU)[0] ||\n SelectorEngine.findOne(SELECTOR_MENU, this._parent)\n this._inNavbar = this._detectNavbar()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle() {\n return this._isShown() ? this.hide() : this.show()\n }\n\n show() {\n if (isDisabled(this._element) || this._isShown()) {\n return\n }\n\n const relatedTarget = {\n relatedTarget: this._element\n }\n\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, relatedTarget)\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._createPopper()\n\n // If this is a touch-enabled device we add extra\n // empty mouseover listeners to the body's immediate children;\n // only needed because of broken event delegation on iOS\n // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n if ('ontouchstart' in document.documentElement && !this._parent.closest(SELECTOR_NAVBAR_NAV)) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.on(element, 'mouseover', noop)\n }\n }\n\n this._element.focus()\n this._element.setAttribute('aria-expanded', true)\n\n this._menu.classList.add(CLASS_NAME_SHOW)\n this._element.classList.add(CLASS_NAME_SHOW)\n EventHandler.trigger(this._element, EVENT_SHOWN, relatedTarget)\n }\n\n hide() {\n if (isDisabled(this._element) || !this._isShown()) {\n return\n }\n\n const relatedTarget = {\n relatedTarget: this._element\n }\n\n this._completeHide(relatedTarget)\n }\n\n dispose() {\n if (this._popper) {\n this._popper.destroy()\n }\n\n super.dispose()\n }\n\n update() {\n this._inNavbar = this._detectNavbar()\n if (this._popper) {\n this._popper.update()\n }\n }\n\n // Private\n _completeHide(relatedTarget) {\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE, relatedTarget)\n if (hideEvent.defaultPrevented) {\n return\n }\n\n // If this is a touch-enabled device we remove the extra\n // empty mouseover listeners we added for iOS support\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.off(element, 'mouseover', noop)\n }\n }\n\n if (this._popper) {\n this._popper.destroy()\n }\n\n this._menu.classList.remove(CLASS_NAME_SHOW)\n this._element.classList.remove(CLASS_NAME_SHOW)\n this._element.setAttribute('aria-expanded', 'false')\n Manipulator.removeDataAttribute(this._menu, 'popper')\n EventHandler.trigger(this._element, EVENT_HIDDEN, relatedTarget)\n }\n\n _getConfig(config) {\n config = super._getConfig(config)\n\n if (typeof config.reference === 'object' && !isElement(config.reference) &&\n typeof config.reference.getBoundingClientRect !== 'function'\n ) {\n // Popper virtual elements require a getBoundingClientRect method\n throw new TypeError(`${NAME.toUpperCase()}: Option \"reference\" provided type \"object\" without a required \"getBoundingClientRect\" method.`)\n }\n\n return config\n }\n\n _createPopper() {\n if (typeof Popper === 'undefined') {\n throw new TypeError('Bootstrap\\'s dropdowns require Popper (https://popper.js.org)')\n }\n\n let referenceElement = this._element\n\n if (this._config.reference === 'parent') {\n referenceElement = this._parent\n } else if (isElement(this._config.reference)) {\n referenceElement = getElement(this._config.reference)\n } else if (typeof this._config.reference === 'object') {\n referenceElement = this._config.reference\n }\n\n const popperConfig = this._getPopperConfig()\n this._popper = Popper.createPopper(referenceElement, this._menu, popperConfig)\n }\n\n _isShown() {\n return this._menu.classList.contains(CLASS_NAME_SHOW)\n }\n\n _getPlacement() {\n const parentDropdown = this._parent\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) {\n return PLACEMENT_RIGHT\n }\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) {\n return PLACEMENT_LEFT\n }\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPUP_CENTER)) {\n return PLACEMENT_TOPCENTER\n }\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPDOWN_CENTER)) {\n return PLACEMENT_BOTTOMCENTER\n }\n\n // We need to trim the value because custom properties can also include spaces\n const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end'\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPUP)) {\n return isEnd ? PLACEMENT_TOPEND : PLACEMENT_TOP\n }\n\n return isEnd ? PLACEMENT_BOTTOMEND : PLACEMENT_BOTTOM\n }\n\n _detectNavbar() {\n return this._element.closest(SELECTOR_NAVBAR) !== null\n }\n\n _getOffset() {\n const { offset } = this._config\n\n if (typeof offset === 'string') {\n return offset.split(',').map(value => Number.parseInt(value, 10))\n }\n\n if (typeof offset === 'function') {\n return popperData => offset(popperData, this._element)\n }\n\n return offset\n }\n\n _getPopperConfig() {\n const defaultBsPopperConfig = {\n placement: this._getPlacement(),\n modifiers: [{\n name: 'preventOverflow',\n options: {\n boundary: this._config.boundary\n }\n },\n {\n name: 'offset',\n options: {\n offset: this._getOffset()\n }\n }]\n }\n\n // Disable Popper if we have a static display or Dropdown is in Navbar\n if (this._inNavbar || this._config.display === 'static') {\n Manipulator.setDataAttribute(this._menu, 'popper', 'static') // TODO: v6 remove\n defaultBsPopperConfig.modifiers = [{\n name: 'applyStyles',\n enabled: false\n }]\n }\n\n return {\n ...defaultBsPopperConfig,\n ...execute(this._config.popperConfig, [defaultBsPopperConfig])\n }\n }\n\n _selectMenuItem({ key, target }) {\n const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(element => isVisible(element))\n\n if (!items.length) {\n return\n }\n\n // if target isn't included in items (e.g. when expanding the dropdown)\n // allow cycling to get the last item in case key equals ARROW_UP_KEY\n getNextActiveElement(items, target, key === ARROW_DOWN_KEY, !items.includes(target)).focus()\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Dropdown.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n\n static clearMenus(event) {\n if (event.button === RIGHT_MOUSE_BUTTON || (event.type === 'keyup' && event.key !== TAB_KEY)) {\n return\n }\n\n const openToggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE_SHOWN)\n\n for (const toggle of openToggles) {\n const context = Dropdown.getInstance(toggle)\n if (!context || context._config.autoClose === false) {\n continue\n }\n\n const composedPath = event.composedPath()\n const isMenuTarget = composedPath.includes(context._menu)\n if (\n composedPath.includes(context._element) ||\n (context._config.autoClose === 'inside' && !isMenuTarget) ||\n (context._config.autoClose === 'outside' && isMenuTarget)\n ) {\n continue\n }\n\n // Tab navigation through the dropdown menu or events from contained inputs shouldn't close the menu\n if (context._menu.contains(event.target) && ((event.type === 'keyup' && event.key === TAB_KEY) || /input|select|option|textarea|form/i.test(event.target.tagName))) {\n continue\n }\n\n const relatedTarget = { relatedTarget: context._element }\n\n if (event.type === 'click') {\n relatedTarget.clickEvent = event\n }\n\n context._completeHide(relatedTarget)\n }\n }\n\n static dataApiKeydownHandler(event) {\n // If not an UP | DOWN | ESCAPE key => not a dropdown command\n // If input/textarea && if key is other than ESCAPE => not a dropdown command\n\n const isInput = /input|textarea/i.test(event.target.tagName)\n const isEscapeEvent = event.key === ESCAPE_KEY\n const isUpOrDownEvent = [ARROW_UP_KEY, ARROW_DOWN_KEY].includes(event.key)\n\n if (!isUpOrDownEvent && !isEscapeEvent) {\n return\n }\n\n if (isInput && !isEscapeEvent) {\n return\n }\n\n event.preventDefault()\n\n // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/\n const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE) ?\n this :\n (SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0] ||\n SelectorEngine.next(this, SELECTOR_DATA_TOGGLE)[0] ||\n SelectorEngine.findOne(SELECTOR_DATA_TOGGLE, event.delegateTarget.parentNode))\n\n const instance = Dropdown.getOrCreateInstance(getToggleButton)\n\n if (isUpOrDownEvent) {\n event.stopPropagation()\n instance.show()\n instance._selectMenuItem(event)\n return\n }\n\n if (instance._isShown()) { // else is escape and we check if it is shown\n event.stopPropagation()\n instance.hide()\n getToggleButton.focus()\n }\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_DATA_TOGGLE, Dropdown.dataApiKeydownHandler)\nEventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_MENU, Dropdown.dataApiKeydownHandler)\nEventHandler.on(document, EVENT_CLICK_DATA_API, Dropdown.clearMenus)\nEventHandler.on(document, EVENT_KEYUP_DATA_API, Dropdown.clearMenus)\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n event.preventDefault()\n Dropdown.getOrCreateInstance(this).toggle()\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Dropdown)\n\nexport default Dropdown\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/backdrop.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport Config from './config.js'\nimport { execute, executeAfterTransition, getElement, reflow } from './index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'backdrop'\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\nconst EVENT_MOUSEDOWN = `mousedown.bs.${NAME}`\n\nconst Default = {\n className: 'modal-backdrop',\n clickCallback: null,\n isAnimated: false,\n isVisible: true, // if false, we use the backdrop helper without adding any element to the dom\n rootElement: 'body' // give the choice to place backdrop under different elements\n}\n\nconst DefaultType = {\n className: 'string',\n clickCallback: '(function|null)',\n isAnimated: 'boolean',\n isVisible: 'boolean',\n rootElement: '(element|string)'\n}\n\n/**\n * Class definition\n */\n\nclass Backdrop extends Config {\n constructor(config) {\n super()\n this._config = this._getConfig(config)\n this._isAppended = false\n this._element = null\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n show(callback) {\n if (!this._config.isVisible) {\n execute(callback)\n return\n }\n\n this._append()\n\n const element = this._getElement()\n if (this._config.isAnimated) {\n reflow(element)\n }\n\n element.classList.add(CLASS_NAME_SHOW)\n\n this._emulateAnimation(() => {\n execute(callback)\n })\n }\n\n hide(callback) {\n if (!this._config.isVisible) {\n execute(callback)\n return\n }\n\n this._getElement().classList.remove(CLASS_NAME_SHOW)\n\n this._emulateAnimation(() => {\n this.dispose()\n execute(callback)\n })\n }\n\n dispose() {\n if (!this._isAppended) {\n return\n }\n\n EventHandler.off(this._element, EVENT_MOUSEDOWN)\n\n this._element.remove()\n this._isAppended = false\n }\n\n // Private\n _getElement() {\n if (!this._element) {\n const backdrop = document.createElement('div')\n backdrop.className = this._config.className\n if (this._config.isAnimated) {\n backdrop.classList.add(CLASS_NAME_FADE)\n }\n\n this._element = backdrop\n }\n\n return this._element\n }\n\n _configAfterMerge(config) {\n // use getElement() with the default \"body\" to get a fresh Element on each instantiation\n config.rootElement = getElement(config.rootElement)\n return config\n }\n\n _append() {\n if (this._isAppended) {\n return\n }\n\n const element = this._getElement()\n this._config.rootElement.append(element)\n\n EventHandler.on(element, EVENT_MOUSEDOWN, () => {\n execute(this._config.clickCallback)\n })\n\n this._isAppended = true\n }\n\n _emulateAnimation(callback) {\n executeAfterTransition(callback, this._getElement(), this._config.isAnimated)\n }\n}\n\nexport default Backdrop\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/focustrap.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport SelectorEngine from '../dom/selector-engine.js'\nimport Config from './config.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'focustrap'\nconst DATA_KEY = 'bs.focustrap'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst EVENT_FOCUSIN = `focusin${EVENT_KEY}`\nconst EVENT_KEYDOWN_TAB = `keydown.tab${EVENT_KEY}`\n\nconst TAB_KEY = 'Tab'\nconst TAB_NAV_FORWARD = 'forward'\nconst TAB_NAV_BACKWARD = 'backward'\n\nconst Default = {\n autofocus: true,\n trapElement: null // The element to trap focus inside of\n}\n\nconst DefaultType = {\n autofocus: 'boolean',\n trapElement: 'element'\n}\n\n/**\n * Class definition\n */\n\nclass FocusTrap extends Config {\n constructor(config) {\n super()\n this._config = this._getConfig(config)\n this._isActive = false\n this._lastTabNavDirection = null\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n activate() {\n if (this._isActive) {\n return\n }\n\n if (this._config.autofocus) {\n this._config.trapElement.focus()\n }\n\n EventHandler.off(document, EVENT_KEY) // guard against infinite focus loop\n EventHandler.on(document, EVENT_FOCUSIN, event => this._handleFocusin(event))\n EventHandler.on(document, EVENT_KEYDOWN_TAB, event => this._handleKeydown(event))\n\n this._isActive = true\n }\n\n deactivate() {\n if (!this._isActive) {\n return\n }\n\n this._isActive = false\n EventHandler.off(document, EVENT_KEY)\n }\n\n // Private\n _handleFocusin(event) {\n const { trapElement } = this._config\n\n if (event.target === document || event.target === trapElement || trapElement.contains(event.target)) {\n return\n }\n\n const elements = SelectorEngine.focusableChildren(trapElement)\n\n if (elements.length === 0) {\n trapElement.focus()\n } else if (this._lastTabNavDirection === TAB_NAV_BACKWARD) {\n elements[elements.length - 1].focus()\n } else {\n elements[0].focus()\n }\n }\n\n _handleKeydown(event) {\n if (event.key !== TAB_KEY) {\n return\n }\n\n this._lastTabNavDirection = event.shiftKey ? TAB_NAV_BACKWARD : TAB_NAV_FORWARD\n }\n}\n\nexport default FocusTrap\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/scrollBar.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Manipulator from '../dom/manipulator.js'\nimport SelectorEngine from '../dom/selector-engine.js'\nimport { isElement } from './index.js'\n\n/**\n * Constants\n */\n\nconst SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top'\nconst SELECTOR_STICKY_CONTENT = '.sticky-top'\nconst PROPERTY_PADDING = 'padding-right'\nconst PROPERTY_MARGIN = 'margin-right'\n\n/**\n * Class definition\n */\n\nclass ScrollBarHelper {\n constructor() {\n this._element = document.body\n }\n\n // Public\n getWidth() {\n // https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes\n const documentWidth = document.documentElement.clientWidth\n return Math.abs(window.innerWidth - documentWidth)\n }\n\n hide() {\n const width = this.getWidth()\n this._disableOverFlow()\n // give padding to element to balance the hidden scrollbar width\n this._setElementAttributes(this._element, PROPERTY_PADDING, calculatedValue => calculatedValue + width)\n // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements to keep showing fullwidth\n this._setElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING, calculatedValue => calculatedValue + width)\n this._setElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN, calculatedValue => calculatedValue - width)\n }\n\n reset() {\n this._resetElementAttributes(this._element, 'overflow')\n this._resetElementAttributes(this._element, PROPERTY_PADDING)\n this._resetElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING)\n this._resetElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN)\n }\n\n isOverflowing() {\n return this.getWidth() > 0\n }\n\n // Private\n _disableOverFlow() {\n this._saveInitialAttribute(this._element, 'overflow')\n this._element.style.overflow = 'hidden'\n }\n\n _setElementAttributes(selector, styleProperty, callback) {\n const scrollbarWidth = this.getWidth()\n const manipulationCallBack = element => {\n if (element !== this._element && window.innerWidth > element.clientWidth + scrollbarWidth) {\n return\n }\n\n this._saveInitialAttribute(element, styleProperty)\n const calculatedValue = window.getComputedStyle(element).getPropertyValue(styleProperty)\n element.style.setProperty(styleProperty, `${callback(Number.parseFloat(calculatedValue))}px`)\n }\n\n this._applyManipulationCallback(selector, manipulationCallBack)\n }\n\n _saveInitialAttribute(element, styleProperty) {\n const actualValue = element.style.getPropertyValue(styleProperty)\n if (actualValue) {\n Manipulator.setDataAttribute(element, styleProperty, actualValue)\n }\n }\n\n _resetElementAttributes(selector, styleProperty) {\n const manipulationCallBack = element => {\n const value = Manipulator.getDataAttribute(element, styleProperty)\n // We only want to remove the property if the value is `null`; the value can also be zero\n if (value === null) {\n element.style.removeProperty(styleProperty)\n return\n }\n\n Manipulator.removeDataAttribute(element, styleProperty)\n element.style.setProperty(styleProperty, value)\n }\n\n this._applyManipulationCallback(selector, manipulationCallBack)\n }\n\n _applyManipulationCallback(selector, callBack) {\n if (isElement(selector)) {\n callBack(selector)\n return\n }\n\n for (const sel of SelectorEngine.find(selector, this._element)) {\n callBack(sel)\n }\n }\n}\n\nexport default ScrollBarHelper\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap modal.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport Backdrop from './util/backdrop.js'\nimport { enableDismissTrigger } from './util/component-functions.js'\nimport FocusTrap from './util/focustrap.js'\nimport { defineJQueryPlugin, isRTL, isVisible, reflow } from './util/index.js'\nimport ScrollBarHelper from './util/scrollbar.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'modal'\nconst DATA_KEY = 'bs.modal'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst ESCAPE_KEY = 'Escape'\n\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_RESIZE = `resize${EVENT_KEY}`\nconst EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}`\nconst EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY}`\nconst EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_OPEN = 'modal-open'\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_STATIC = 'modal-static'\n\nconst OPEN_SELECTOR = '.modal.show'\nconst SELECTOR_DIALOG = '.modal-dialog'\nconst SELECTOR_MODAL_BODY = '.modal-body'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"modal\"]'\n\nconst Default = {\n backdrop: true,\n focus: true,\n keyboard: true\n}\n\nconst DefaultType = {\n backdrop: '(boolean|string)',\n focus: 'boolean',\n keyboard: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Modal extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element)\n this._backdrop = this._initializeBackDrop()\n this._focustrap = this._initializeFocusTrap()\n this._isShown = false\n this._isTransitioning = false\n this._scrollBar = new ScrollBarHelper()\n\n this._addEventListeners()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle(relatedTarget) {\n return this._isShown ? this.hide() : this.show(relatedTarget)\n }\n\n show(relatedTarget) {\n if (this._isShown || this._isTransitioning) {\n return\n }\n\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, {\n relatedTarget\n })\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._isShown = true\n this._isTransitioning = true\n\n this._scrollBar.hide()\n\n document.body.classList.add(CLASS_NAME_OPEN)\n\n this._adjustDialog()\n\n this._backdrop.show(() => this._showElement(relatedTarget))\n }\n\n hide() {\n if (!this._isShown || this._isTransitioning) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n\n if (hideEvent.defaultPrevented) {\n return\n }\n\n this._isShown = false\n this._isTransitioning = true\n this._focustrap.deactivate()\n\n this._element.classList.remove(CLASS_NAME_SHOW)\n\n this._queueCallback(() => this._hideModal(), this._element, this._isAnimated())\n }\n\n dispose() {\n EventHandler.off(window, EVENT_KEY)\n EventHandler.off(this._dialog, EVENT_KEY)\n\n this._backdrop.dispose()\n this._focustrap.deactivate()\n\n super.dispose()\n }\n\n handleUpdate() {\n this._adjustDialog()\n }\n\n // Private\n _initializeBackDrop() {\n return new Backdrop({\n isVisible: Boolean(this._config.backdrop), // 'static' option will be translated to true, and booleans will keep their value,\n isAnimated: this._isAnimated()\n })\n }\n\n _initializeFocusTrap() {\n return new FocusTrap({\n trapElement: this._element\n })\n }\n\n _showElement(relatedTarget) {\n // try to append dynamic modal\n if (!document.body.contains(this._element)) {\n document.body.append(this._element)\n }\n\n this._element.style.display = 'block'\n this._element.removeAttribute('aria-hidden')\n this._element.setAttribute('aria-modal', true)\n this._element.setAttribute('role', 'dialog')\n this._element.scrollTop = 0\n\n const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog)\n if (modalBody) {\n modalBody.scrollTop = 0\n }\n\n reflow(this._element)\n\n this._element.classList.add(CLASS_NAME_SHOW)\n\n const transitionComplete = () => {\n if (this._config.focus) {\n this._focustrap.activate()\n }\n\n this._isTransitioning = false\n EventHandler.trigger(this._element, EVENT_SHOWN, {\n relatedTarget\n })\n }\n\n this._queueCallback(transitionComplete, this._dialog, this._isAnimated())\n }\n\n _addEventListeners() {\n EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {\n if (event.key !== ESCAPE_KEY) {\n return\n }\n\n if (this._config.keyboard) {\n this.hide()\n return\n }\n\n this._triggerBackdropTransition()\n })\n\n EventHandler.on(window, EVENT_RESIZE, () => {\n if (this._isShown && !this._isTransitioning) {\n this._adjustDialog()\n }\n })\n\n EventHandler.on(this._element, EVENT_MOUSEDOWN_DISMISS, event => {\n // a bad trick to segregate clicks that may start inside dialog but end outside, and avoid listen to scrollbar clicks\n EventHandler.one(this._element, EVENT_CLICK_DISMISS, event2 => {\n if (this._element !== event.target || this._element !== event2.target) {\n return\n }\n\n if (this._config.backdrop === 'static') {\n this._triggerBackdropTransition()\n return\n }\n\n if (this._config.backdrop) {\n this.hide()\n }\n })\n })\n }\n\n _hideModal() {\n this._element.style.display = 'none'\n this._element.setAttribute('aria-hidden', true)\n this._element.removeAttribute('aria-modal')\n this._element.removeAttribute('role')\n this._isTransitioning = false\n\n this._backdrop.hide(() => {\n document.body.classList.remove(CLASS_NAME_OPEN)\n this._resetAdjustments()\n this._scrollBar.reset()\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n })\n }\n\n _isAnimated() {\n return this._element.classList.contains(CLASS_NAME_FADE)\n }\n\n _triggerBackdropTransition() {\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)\n if (hideEvent.defaultPrevented) {\n return\n }\n\n const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight\n const initialOverflowY = this._element.style.overflowY\n // return if the following background transition hasn't yet completed\n if (initialOverflowY === 'hidden' || this._element.classList.contains(CLASS_NAME_STATIC)) {\n return\n }\n\n if (!isModalOverflowing) {\n this._element.style.overflowY = 'hidden'\n }\n\n this._element.classList.add(CLASS_NAME_STATIC)\n this._queueCallback(() => {\n this._element.classList.remove(CLASS_NAME_STATIC)\n this._queueCallback(() => {\n this._element.style.overflowY = initialOverflowY\n }, this._dialog)\n }, this._dialog)\n\n this._element.focus()\n }\n\n /**\n * The following methods are used to handle overflowing modals\n */\n\n _adjustDialog() {\n const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight\n const scrollbarWidth = this._scrollBar.getWidth()\n const isBodyOverflowing = scrollbarWidth > 0\n\n if (isBodyOverflowing && !isModalOverflowing) {\n const property = isRTL() ? 'paddingLeft' : 'paddingRight'\n this._element.style[property] = `${scrollbarWidth}px`\n }\n\n if (!isBodyOverflowing && isModalOverflowing) {\n const property = isRTL() ? 'paddingRight' : 'paddingLeft'\n this._element.style[property] = `${scrollbarWidth}px`\n }\n }\n\n _resetAdjustments() {\n this._element.style.paddingLeft = ''\n this._element.style.paddingRight = ''\n }\n\n // Static\n static jQueryInterface(config, relatedTarget) {\n return this.each(function () {\n const data = Modal.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](relatedTarget)\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n const target = SelectorEngine.getElementFromSelector(this)\n\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n EventHandler.one(target, EVENT_SHOW, showEvent => {\n if (showEvent.defaultPrevented) {\n // only register focus restorer if modal will actually get shown\n return\n }\n\n EventHandler.one(target, EVENT_HIDDEN, () => {\n if (isVisible(this)) {\n this.focus()\n }\n })\n })\n\n // avoid conflict when clicking modal toggler while another one is open\n const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR)\n if (alreadyOpen) {\n Modal.getInstance(alreadyOpen).hide()\n }\n\n const data = Modal.getOrCreateInstance(target)\n\n data.toggle(this)\n})\n\nenableDismissTrigger(Modal)\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Modal)\n\nexport default Modal\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap offcanvas.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport Backdrop from './util/backdrop.js'\nimport { enableDismissTrigger } from './util/component-functions.js'\nimport FocusTrap from './util/focustrap.js'\nimport {\n defineJQueryPlugin,\n isDisabled,\n isVisible\n} from './util/index.js'\nimport ScrollBarHelper from './util/scrollbar.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'offcanvas'\nconst DATA_KEY = 'bs.offcanvas'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\nconst ESCAPE_KEY = 'Escape'\n\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_SHOWING = 'showing'\nconst CLASS_NAME_HIDING = 'hiding'\nconst CLASS_NAME_BACKDROP = 'offcanvas-backdrop'\nconst OPEN_SELECTOR = '.offcanvas.show'\n\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_RESIZE = `resize${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`\n\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"offcanvas\"]'\n\nconst Default = {\n backdrop: true,\n keyboard: true,\n scroll: false\n}\n\nconst DefaultType = {\n backdrop: '(boolean|string)',\n keyboard: 'boolean',\n scroll: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Offcanvas extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._isShown = false\n this._backdrop = this._initializeBackDrop()\n this._focustrap = this._initializeFocusTrap()\n this._addEventListeners()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle(relatedTarget) {\n return this._isShown ? this.hide() : this.show(relatedTarget)\n }\n\n show(relatedTarget) {\n if (this._isShown) {\n return\n }\n\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, { relatedTarget })\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._isShown = true\n this._backdrop.show()\n\n if (!this._config.scroll) {\n new ScrollBarHelper().hide()\n }\n\n this._element.setAttribute('aria-modal', true)\n this._element.setAttribute('role', 'dialog')\n this._element.classList.add(CLASS_NAME_SHOWING)\n\n const completeCallBack = () => {\n if (!this._config.scroll || this._config.backdrop) {\n this._focustrap.activate()\n }\n\n this._element.classList.add(CLASS_NAME_SHOW)\n this._element.classList.remove(CLASS_NAME_SHOWING)\n EventHandler.trigger(this._element, EVENT_SHOWN, { relatedTarget })\n }\n\n this._queueCallback(completeCallBack, this._element, true)\n }\n\n hide() {\n if (!this._isShown) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n\n if (hideEvent.defaultPrevented) {\n return\n }\n\n this._focustrap.deactivate()\n this._element.blur()\n this._isShown = false\n this._element.classList.add(CLASS_NAME_HIDING)\n this._backdrop.hide()\n\n const completeCallback = () => {\n this._element.classList.remove(CLASS_NAME_SHOW, CLASS_NAME_HIDING)\n this._element.removeAttribute('aria-modal')\n this._element.removeAttribute('role')\n\n if (!this._config.scroll) {\n new ScrollBarHelper().reset()\n }\n\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n }\n\n this._queueCallback(completeCallback, this._element, true)\n }\n\n dispose() {\n this._backdrop.dispose()\n this._focustrap.deactivate()\n super.dispose()\n }\n\n // Private\n _initializeBackDrop() {\n const clickCallback = () => {\n if (this._config.backdrop === 'static') {\n EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)\n return\n }\n\n this.hide()\n }\n\n // 'static' option will be translated to true, and booleans will keep their value\n const isVisible = Boolean(this._config.backdrop)\n\n return new Backdrop({\n className: CLASS_NAME_BACKDROP,\n isVisible,\n isAnimated: true,\n rootElement: this._element.parentNode,\n clickCallback: isVisible ? clickCallback : null\n })\n }\n\n _initializeFocusTrap() {\n return new FocusTrap({\n trapElement: this._element\n })\n }\n\n _addEventListeners() {\n EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {\n if (event.key !== ESCAPE_KEY) {\n return\n }\n\n if (this._config.keyboard) {\n this.hide()\n return\n }\n\n EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)\n })\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Offcanvas.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](this)\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n const target = SelectorEngine.getElementFromSelector(this)\n\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n if (isDisabled(this)) {\n return\n }\n\n EventHandler.one(target, EVENT_HIDDEN, () => {\n // focus on trigger when it is closed\n if (isVisible(this)) {\n this.focus()\n }\n })\n\n // avoid conflict when clicking a toggler of an offcanvas, while another is open\n const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR)\n if (alreadyOpen && alreadyOpen !== target) {\n Offcanvas.getInstance(alreadyOpen).hide()\n }\n\n const data = Offcanvas.getOrCreateInstance(target)\n data.toggle(this)\n})\n\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n for (const selector of SelectorEngine.find(OPEN_SELECTOR)) {\n Offcanvas.getOrCreateInstance(selector).show()\n }\n})\n\nEventHandler.on(window, EVENT_RESIZE, () => {\n for (const element of SelectorEngine.find('[aria-modal][class*=show][class*=offcanvas-]')) {\n if (getComputedStyle(element).position !== 'fixed') {\n Offcanvas.getOrCreateInstance(element).hide()\n }\n }\n})\n\nenableDismissTrigger(Offcanvas)\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Offcanvas)\n\nexport default Offcanvas\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/sanitizer.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n// js-docs-start allow-list\nconst ARIA_ATTRIBUTE_PATTERN = /^aria-[\\w-]*$/i\n\nexport const DefaultAllowlist = {\n // Global attributes allowed on any supplied element below.\n '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],\n a: ['target', 'href', 'title', 'rel'],\n area: [],\n b: [],\n br: [],\n col: [],\n code: [],\n div: [],\n em: [],\n hr: [],\n h1: [],\n h2: [],\n h3: [],\n h4: [],\n h5: [],\n h6: [],\n i: [],\n img: ['src', 'srcset', 'alt', 'title', 'width', 'height'],\n li: [],\n ol: [],\n p: [],\n pre: [],\n s: [],\n small: [],\n span: [],\n sub: [],\n sup: [],\n strong: [],\n u: [],\n ul: []\n}\n// js-docs-end allow-list\n\nconst uriAttributes = new Set([\n 'background',\n 'cite',\n 'href',\n 'itemtype',\n 'longdesc',\n 'poster',\n 'src',\n 'xlink:href'\n])\n\n/**\n * A pattern that recognizes URLs that are safe wrt. XSS in URL navigation\n * contexts.\n *\n * Shout-out to Angular https://github.com/angular/angular/blob/15.2.8/packages/core/src/sanitization/url_sanitizer.ts#L38\n */\n// eslint-disable-next-line unicorn/better-regex\nconst SAFE_URL_PATTERN = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i\n\nconst allowedAttribute = (attribute, allowedAttributeList) => {\n const attributeName = attribute.nodeName.toLowerCase()\n\n if (allowedAttributeList.includes(attributeName)) {\n if (uriAttributes.has(attributeName)) {\n return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue))\n }\n\n return true\n }\n\n // Check if a regular expression validates the attribute.\n return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp)\n .some(regex => regex.test(attributeName))\n}\n\nexport function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) {\n if (!unsafeHtml.length) {\n return unsafeHtml\n }\n\n if (sanitizeFunction && typeof sanitizeFunction === 'function') {\n return sanitizeFunction(unsafeHtml)\n }\n\n const domParser = new window.DOMParser()\n const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html')\n const elements = [].concat(...createdDocument.body.querySelectorAll('*'))\n\n for (const element of elements) {\n const elementName = element.nodeName.toLowerCase()\n\n if (!Object.keys(allowList).includes(elementName)) {\n element.remove()\n continue\n }\n\n const attributeList = [].concat(...element.attributes)\n const allowedAttributes = [].concat(allowList['*'] || [], allowList[elementName] || [])\n\n for (const attribute of attributeList) {\n if (!allowedAttribute(attribute, allowedAttributes)) {\n element.removeAttribute(attribute.nodeName)\n }\n }\n }\n\n return createdDocument.body.innerHTML\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/template-factory.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport SelectorEngine from '../dom/selector-engine.js'\nimport Config from './config.js'\nimport { DefaultAllowlist, sanitizeHtml } from './sanitizer.js'\nimport { execute, getElement, isElement } from './index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'TemplateFactory'\n\nconst Default = {\n allowList: DefaultAllowlist,\n content: {}, // { selector : text , selector2 : text2 , }\n extraClass: '',\n html: false,\n sanitize: true,\n sanitizeFn: null,\n template: '
'\n}\n\nconst DefaultType = {\n allowList: 'object',\n content: 'object',\n extraClass: '(string|function)',\n html: 'boolean',\n sanitize: 'boolean',\n sanitizeFn: '(null|function)',\n template: 'string'\n}\n\nconst DefaultContentType = {\n entry: '(string|element|function|null)',\n selector: '(string|element)'\n}\n\n/**\n * Class definition\n */\n\nclass TemplateFactory extends Config {\n constructor(config) {\n super()\n this._config = this._getConfig(config)\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n getContent() {\n return Object.values(this._config.content)\n .map(config => this._resolvePossibleFunction(config))\n .filter(Boolean)\n }\n\n hasContent() {\n return this.getContent().length > 0\n }\n\n changeContent(content) {\n this._checkContent(content)\n this._config.content = { ...this._config.content, ...content }\n return this\n }\n\n toHtml() {\n const templateWrapper = document.createElement('div')\n templateWrapper.innerHTML = this._maybeSanitize(this._config.template)\n\n for (const [selector, text] of Object.entries(this._config.content)) {\n this._setContent(templateWrapper, text, selector)\n }\n\n const template = templateWrapper.children[0]\n const extraClass = this._resolvePossibleFunction(this._config.extraClass)\n\n if (extraClass) {\n template.classList.add(...extraClass.split(' '))\n }\n\n return template\n }\n\n // Private\n _typeCheckConfig(config) {\n super._typeCheckConfig(config)\n this._checkContent(config.content)\n }\n\n _checkContent(arg) {\n for (const [selector, content] of Object.entries(arg)) {\n super._typeCheckConfig({ selector, entry: content }, DefaultContentType)\n }\n }\n\n _setContent(template, content, selector) {\n const templateElement = SelectorEngine.findOne(selector, template)\n\n if (!templateElement) {\n return\n }\n\n content = this._resolvePossibleFunction(content)\n\n if (!content) {\n templateElement.remove()\n return\n }\n\n if (isElement(content)) {\n this._putElementInTemplate(getElement(content), templateElement)\n return\n }\n\n if (this._config.html) {\n templateElement.innerHTML = this._maybeSanitize(content)\n return\n }\n\n templateElement.textContent = content\n }\n\n _maybeSanitize(arg) {\n return this._config.sanitize ? sanitizeHtml(arg, this._config.allowList, this._config.sanitizeFn) : arg\n }\n\n _resolvePossibleFunction(arg) {\n return execute(arg, [this])\n }\n\n _putElementInTemplate(element, templateElement) {\n if (this._config.html) {\n templateElement.innerHTML = ''\n templateElement.append(element)\n return\n }\n\n templateElement.textContent = element.textContent\n }\n}\n\nexport default TemplateFactory\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap tooltip.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport * as Popper from '@popperjs/core'\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport Manipulator from './dom/manipulator.js'\nimport { defineJQueryPlugin, execute, findShadowRoot, getElement, getUID, isRTL, noop } from './util/index.js'\nimport { DefaultAllowlist } from './util/sanitizer.js'\nimport TemplateFactory from './util/template-factory.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'tooltip'\nconst DISALLOWED_ATTRIBUTES = new Set(['sanitize', 'allowList', 'sanitizeFn'])\n\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_MODAL = 'modal'\nconst CLASS_NAME_SHOW = 'show'\n\nconst SELECTOR_TOOLTIP_INNER = '.tooltip-inner'\nconst SELECTOR_MODAL = `.${CLASS_NAME_MODAL}`\n\nconst EVENT_MODAL_HIDE = 'hide.bs.modal'\n\nconst TRIGGER_HOVER = 'hover'\nconst TRIGGER_FOCUS = 'focus'\nconst TRIGGER_CLICK = 'click'\nconst TRIGGER_MANUAL = 'manual'\n\nconst EVENT_HIDE = 'hide'\nconst EVENT_HIDDEN = 'hidden'\nconst EVENT_SHOW = 'show'\nconst EVENT_SHOWN = 'shown'\nconst EVENT_INSERTED = 'inserted'\nconst EVENT_CLICK = 'click'\nconst EVENT_FOCUSIN = 'focusin'\nconst EVENT_FOCUSOUT = 'focusout'\nconst EVENT_MOUSEENTER = 'mouseenter'\nconst EVENT_MOUSELEAVE = 'mouseleave'\n\nconst AttachmentMap = {\n AUTO: 'auto',\n TOP: 'top',\n RIGHT: isRTL() ? 'left' : 'right',\n BOTTOM: 'bottom',\n LEFT: isRTL() ? 'right' : 'left'\n}\n\nconst Default = {\n allowList: DefaultAllowlist,\n animation: true,\n boundary: 'clippingParents',\n container: false,\n customClass: '',\n delay: 0,\n fallbackPlacements: ['top', 'right', 'bottom', 'left'],\n html: false,\n offset: [0, 6],\n placement: 'top',\n popperConfig: null,\n sanitize: true,\n sanitizeFn: null,\n selector: false,\n template: '
' +\n '
' +\n '
' +\n '
',\n title: '',\n trigger: 'hover focus'\n}\n\nconst DefaultType = {\n allowList: 'object',\n animation: 'boolean',\n boundary: '(string|element)',\n container: '(string|element|boolean)',\n customClass: '(string|function)',\n delay: '(number|object)',\n fallbackPlacements: 'array',\n html: 'boolean',\n offset: '(array|string|function)',\n placement: '(string|function)',\n popperConfig: '(null|object|function)',\n sanitize: 'boolean',\n sanitizeFn: '(null|function)',\n selector: '(string|boolean)',\n template: 'string',\n title: '(string|element|function)',\n trigger: 'string'\n}\n\n/**\n * Class definition\n */\n\nclass Tooltip extends BaseComponent {\n constructor(element, config) {\n if (typeof Popper === 'undefined') {\n throw new TypeError('Bootstrap\\'s tooltips require Popper (https://popper.js.org)')\n }\n\n super(element, config)\n\n // Private\n this._isEnabled = true\n this._timeout = 0\n this._isHovered = null\n this._activeTrigger = {}\n this._popper = null\n this._templateFactory = null\n this._newContent = null\n\n // Protected\n this.tip = null\n\n this._setListeners()\n\n if (!this._config.selector) {\n this._fixTitle()\n }\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n enable() {\n this._isEnabled = true\n }\n\n disable() {\n this._isEnabled = false\n }\n\n toggleEnabled() {\n this._isEnabled = !this._isEnabled\n }\n\n toggle() {\n if (!this._isEnabled) {\n return\n }\n\n this._activeTrigger.click = !this._activeTrigger.click\n if (this._isShown()) {\n this._leave()\n return\n }\n\n this._enter()\n }\n\n dispose() {\n clearTimeout(this._timeout)\n\n EventHandler.off(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler)\n\n if (this._element.getAttribute('data-bs-original-title')) {\n this._element.setAttribute('title', this._element.getAttribute('data-bs-original-title'))\n }\n\n this._disposePopper()\n super.dispose()\n }\n\n show() {\n if (this._element.style.display === 'none') {\n throw new Error('Please use show on visible elements')\n }\n\n if (!(this._isWithContent() && this._isEnabled)) {\n return\n }\n\n const showEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOW))\n const shadowRoot = findShadowRoot(this._element)\n const isInTheDom = (shadowRoot || this._element.ownerDocument.documentElement).contains(this._element)\n\n if (showEvent.defaultPrevented || !isInTheDom) {\n return\n }\n\n // TODO: v6 remove this or make it optional\n this._disposePopper()\n\n const tip = this._getTipElement()\n\n this._element.setAttribute('aria-describedby', tip.getAttribute('id'))\n\n const { container } = this._config\n\n if (!this._element.ownerDocument.documentElement.contains(this.tip)) {\n container.append(tip)\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_INSERTED))\n }\n\n this._popper = this._createPopper(tip)\n\n tip.classList.add(CLASS_NAME_SHOW)\n\n // If this is a touch-enabled device we add extra\n // empty mouseover listeners to the body's immediate children;\n // only needed because of broken event delegation on iOS\n // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.on(element, 'mouseover', noop)\n }\n }\n\n const complete = () => {\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOWN))\n\n if (this._isHovered === false) {\n this._leave()\n }\n\n this._isHovered = false\n }\n\n this._queueCallback(complete, this.tip, this._isAnimated())\n }\n\n hide() {\n if (!this._isShown()) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDE))\n if (hideEvent.defaultPrevented) {\n return\n }\n\n const tip = this._getTipElement()\n tip.classList.remove(CLASS_NAME_SHOW)\n\n // If this is a touch-enabled device we remove the extra\n // empty mouseover listeners we added for iOS support\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.off(element, 'mouseover', noop)\n }\n }\n\n this._activeTrigger[TRIGGER_CLICK] = false\n this._activeTrigger[TRIGGER_FOCUS] = false\n this._activeTrigger[TRIGGER_HOVER] = false\n this._isHovered = null // it is a trick to support manual triggering\n\n const complete = () => {\n if (this._isWithActiveTrigger()) {\n return\n }\n\n if (!this._isHovered) {\n this._disposePopper()\n }\n\n this._element.removeAttribute('aria-describedby')\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDDEN))\n }\n\n this._queueCallback(complete, this.tip, this._isAnimated())\n }\n\n update() {\n if (this._popper) {\n this._popper.update()\n }\n }\n\n // Protected\n _isWithContent() {\n return Boolean(this._getTitle())\n }\n\n _getTipElement() {\n if (!this.tip) {\n this.tip = this._createTipElement(this._newContent || this._getContentForTemplate())\n }\n\n return this.tip\n }\n\n _createTipElement(content) {\n const tip = this._getTemplateFactory(content).toHtml()\n\n // TODO: remove this check in v6\n if (!tip) {\n return null\n }\n\n tip.classList.remove(CLASS_NAME_FADE, CLASS_NAME_SHOW)\n // TODO: v6 the following can be achieved with CSS only\n tip.classList.add(`bs-${this.constructor.NAME}-auto`)\n\n const tipId = getUID(this.constructor.NAME).toString()\n\n tip.setAttribute('id', tipId)\n\n if (this._isAnimated()) {\n tip.classList.add(CLASS_NAME_FADE)\n }\n\n return tip\n }\n\n setContent(content) {\n this._newContent = content\n if (this._isShown()) {\n this._disposePopper()\n this.show()\n }\n }\n\n _getTemplateFactory(content) {\n if (this._templateFactory) {\n this._templateFactory.changeContent(content)\n } else {\n this._templateFactory = new TemplateFactory({\n ...this._config,\n // the `content` var has to be after `this._config`\n // to override config.content in case of popover\n content,\n extraClass: this._resolvePossibleFunction(this._config.customClass)\n })\n }\n\n return this._templateFactory\n }\n\n _getContentForTemplate() {\n return {\n [SELECTOR_TOOLTIP_INNER]: this._getTitle()\n }\n }\n\n _getTitle() {\n return this._resolvePossibleFunction(this._config.title) || this._element.getAttribute('data-bs-original-title')\n }\n\n // Private\n _initializeOnDelegatedTarget(event) {\n return this.constructor.getOrCreateInstance(event.delegateTarget, this._getDelegateConfig())\n }\n\n _isAnimated() {\n return this._config.animation || (this.tip && this.tip.classList.contains(CLASS_NAME_FADE))\n }\n\n _isShown() {\n return this.tip && this.tip.classList.contains(CLASS_NAME_SHOW)\n }\n\n _createPopper(tip) {\n const placement = execute(this._config.placement, [this, tip, this._element])\n const attachment = AttachmentMap[placement.toUpperCase()]\n return Popper.createPopper(this._element, tip, this._getPopperConfig(attachment))\n }\n\n _getOffset() {\n const { offset } = this._config\n\n if (typeof offset === 'string') {\n return offset.split(',').map(value => Number.parseInt(value, 10))\n }\n\n if (typeof offset === 'function') {\n return popperData => offset(popperData, this._element)\n }\n\n return offset\n }\n\n _resolvePossibleFunction(arg) {\n return execute(arg, [this._element])\n }\n\n _getPopperConfig(attachment) {\n const defaultBsPopperConfig = {\n placement: attachment,\n modifiers: [\n {\n name: 'flip',\n options: {\n fallbackPlacements: this._config.fallbackPlacements\n }\n },\n {\n name: 'offset',\n options: {\n offset: this._getOffset()\n }\n },\n {\n name: 'preventOverflow',\n options: {\n boundary: this._config.boundary\n }\n },\n {\n name: 'arrow',\n options: {\n element: `.${this.constructor.NAME}-arrow`\n }\n },\n {\n name: 'preSetPlacement',\n enabled: true,\n phase: 'beforeMain',\n fn: data => {\n // Pre-set Popper's placement attribute in order to read the arrow sizes properly.\n // Otherwise, Popper mixes up the width and height dimensions since the initial arrow style is for top placement\n this._getTipElement().setAttribute('data-popper-placement', data.state.placement)\n }\n }\n ]\n }\n\n return {\n ...defaultBsPopperConfig,\n ...execute(this._config.popperConfig, [defaultBsPopperConfig])\n }\n }\n\n _setListeners() {\n const triggers = this._config.trigger.split(' ')\n\n for (const trigger of triggers) {\n if (trigger === 'click') {\n EventHandler.on(this._element, this.constructor.eventName(EVENT_CLICK), this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event)\n context.toggle()\n })\n } else if (trigger !== TRIGGER_MANUAL) {\n const eventIn = trigger === TRIGGER_HOVER ?\n this.constructor.eventName(EVENT_MOUSEENTER) :\n this.constructor.eventName(EVENT_FOCUSIN)\n const eventOut = trigger === TRIGGER_HOVER ?\n this.constructor.eventName(EVENT_MOUSELEAVE) :\n this.constructor.eventName(EVENT_FOCUSOUT)\n\n EventHandler.on(this._element, eventIn, this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event)\n context._activeTrigger[event.type === 'focusin' ? TRIGGER_FOCUS : TRIGGER_HOVER] = true\n context._enter()\n })\n EventHandler.on(this._element, eventOut, this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event)\n context._activeTrigger[event.type === 'focusout' ? TRIGGER_FOCUS : TRIGGER_HOVER] =\n context._element.contains(event.relatedTarget)\n\n context._leave()\n })\n }\n }\n\n this._hideModalHandler = () => {\n if (this._element) {\n this.hide()\n }\n }\n\n EventHandler.on(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler)\n }\n\n _fixTitle() {\n const title = this._element.getAttribute('title')\n\n if (!title) {\n return\n }\n\n if (!this._element.getAttribute('aria-label') && !this._element.textContent.trim()) {\n this._element.setAttribute('aria-label', title)\n }\n\n this._element.setAttribute('data-bs-original-title', title) // DO NOT USE IT. Is only for backwards compatibility\n this._element.removeAttribute('title')\n }\n\n _enter() {\n if (this._isShown() || this._isHovered) {\n this._isHovered = true\n return\n }\n\n this._isHovered = true\n\n this._setTimeout(() => {\n if (this._isHovered) {\n this.show()\n }\n }, this._config.delay.show)\n }\n\n _leave() {\n if (this._isWithActiveTrigger()) {\n return\n }\n\n this._isHovered = false\n\n this._setTimeout(() => {\n if (!this._isHovered) {\n this.hide()\n }\n }, this._config.delay.hide)\n }\n\n _setTimeout(handler, timeout) {\n clearTimeout(this._timeout)\n this._timeout = setTimeout(handler, timeout)\n }\n\n _isWithActiveTrigger() {\n return Object.values(this._activeTrigger).includes(true)\n }\n\n _getConfig(config) {\n const dataAttributes = Manipulator.getDataAttributes(this._element)\n\n for (const dataAttribute of Object.keys(dataAttributes)) {\n if (DISALLOWED_ATTRIBUTES.has(dataAttribute)) {\n delete dataAttributes[dataAttribute]\n }\n }\n\n config = {\n ...dataAttributes,\n ...(typeof config === 'object' && config ? config : {})\n }\n config = this._mergeConfigObj(config)\n config = this._configAfterMerge(config)\n this._typeCheckConfig(config)\n return config\n }\n\n _configAfterMerge(config) {\n config.container = config.container === false ? document.body : getElement(config.container)\n\n if (typeof config.delay === 'number') {\n config.delay = {\n show: config.delay,\n hide: config.delay\n }\n }\n\n if (typeof config.title === 'number') {\n config.title = config.title.toString()\n }\n\n if (typeof config.content === 'number') {\n config.content = config.content.toString()\n }\n\n return config\n }\n\n _getDelegateConfig() {\n const config = {}\n\n for (const [key, value] of Object.entries(this._config)) {\n if (this.constructor.Default[key] !== value) {\n config[key] = value\n }\n }\n\n config.selector = false\n config.trigger = 'manual'\n\n // In the future can be replaced with:\n // const keysWithDifferentValues = Object.entries(this._config).filter(entry => this.constructor.Default[entry[0]] !== this._config[entry[0]])\n // `Object.fromEntries(keysWithDifferentValues)`\n return config\n }\n\n _disposePopper() {\n if (this._popper) {\n this._popper.destroy()\n this._popper = null\n }\n\n if (this.tip) {\n this.tip.remove()\n this.tip = null\n }\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Tooltip.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n}\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Tooltip)\n\nexport default Tooltip\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap popover.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Tooltip from './tooltip.js'\nimport { defineJQueryPlugin } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'popover'\n\nconst SELECTOR_TITLE = '.popover-header'\nconst SELECTOR_CONTENT = '.popover-body'\n\nconst Default = {\n ...Tooltip.Default,\n content: '',\n offset: [0, 8],\n placement: 'right',\n template: '
' +\n '
' +\n '

' +\n '
' +\n '
',\n trigger: 'click'\n}\n\nconst DefaultType = {\n ...Tooltip.DefaultType,\n content: '(null|string|element|function)'\n}\n\n/**\n * Class definition\n */\n\nclass Popover extends Tooltip {\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Overrides\n _isWithContent() {\n return this._getTitle() || this._getContent()\n }\n\n // Private\n _getContentForTemplate() {\n return {\n [SELECTOR_TITLE]: this._getTitle(),\n [SELECTOR_CONTENT]: this._getContent()\n }\n }\n\n _getContent() {\n return this._resolvePossibleFunction(this._config.content)\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Popover.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n}\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Popover)\n\nexport default Popover\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap scrollspy.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport { defineJQueryPlugin, getElement, isDisabled, isVisible } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'scrollspy'\nconst DATA_KEY = 'bs.scrollspy'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst EVENT_ACTIVATE = `activate${EVENT_KEY}`\nconst EVENT_CLICK = `click${EVENT_KEY}`\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_DROPDOWN_ITEM = 'dropdown-item'\nconst CLASS_NAME_ACTIVE = 'active'\n\nconst SELECTOR_DATA_SPY = '[data-bs-spy=\"scroll\"]'\nconst SELECTOR_TARGET_LINKS = '[href]'\nconst SELECTOR_NAV_LIST_GROUP = '.nav, .list-group'\nconst SELECTOR_NAV_LINKS = '.nav-link'\nconst SELECTOR_NAV_ITEMS = '.nav-item'\nconst SELECTOR_LIST_ITEMS = '.list-group-item'\nconst SELECTOR_LINK_ITEMS = `${SELECTOR_NAV_LINKS}, ${SELECTOR_NAV_ITEMS} > ${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}`\nconst SELECTOR_DROPDOWN = '.dropdown'\nconst SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle'\n\nconst Default = {\n offset: null, // TODO: v6 @deprecated, keep it for backwards compatibility reasons\n rootMargin: '0px 0px -25%',\n smoothScroll: false,\n target: null,\n threshold: [0.1, 0.5, 1]\n}\n\nconst DefaultType = {\n offset: '(number|null)', // TODO v6 @deprecated, keep it for backwards compatibility reasons\n rootMargin: 'string',\n smoothScroll: 'boolean',\n target: 'element',\n threshold: 'array'\n}\n\n/**\n * Class definition\n */\n\nclass ScrollSpy extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n // this._element is the observablesContainer and config.target the menu links wrapper\n this._targetLinks = new Map()\n this._observableSections = new Map()\n this._rootElement = getComputedStyle(this._element).overflowY === 'visible' ? null : this._element\n this._activeTarget = null\n this._observer = null\n this._previousScrollData = {\n visibleEntryTop: 0,\n parentScrollTop: 0\n }\n this.refresh() // initialize\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n refresh() {\n this._initializeTargetsAndObservables()\n this._maybeEnableSmoothScroll()\n\n if (this._observer) {\n this._observer.disconnect()\n } else {\n this._observer = this._getNewObserver()\n }\n\n for (const section of this._observableSections.values()) {\n this._observer.observe(section)\n }\n }\n\n dispose() {\n this._observer.disconnect()\n super.dispose()\n }\n\n // Private\n _configAfterMerge(config) {\n // TODO: on v6 target should be given explicitly & remove the {target: 'ss-target'} case\n config.target = getElement(config.target) || document.body\n\n // TODO: v6 Only for backwards compatibility reasons. Use rootMargin only\n config.rootMargin = config.offset ? `${config.offset}px 0px -30%` : config.rootMargin\n\n if (typeof config.threshold === 'string') {\n config.threshold = config.threshold.split(',').map(value => Number.parseFloat(value))\n }\n\n return config\n }\n\n _maybeEnableSmoothScroll() {\n if (!this._config.smoothScroll) {\n return\n }\n\n // unregister any previous listeners\n EventHandler.off(this._config.target, EVENT_CLICK)\n\n EventHandler.on(this._config.target, EVENT_CLICK, SELECTOR_TARGET_LINKS, event => {\n const observableSection = this._observableSections.get(event.target.hash)\n if (observableSection) {\n event.preventDefault()\n const root = this._rootElement || window\n const height = observableSection.offsetTop - this._element.offsetTop\n if (root.scrollTo) {\n root.scrollTo({ top: height, behavior: 'smooth' })\n return\n }\n\n // Chrome 60 doesn't support `scrollTo`\n root.scrollTop = height\n }\n })\n }\n\n _getNewObserver() {\n const options = {\n root: this._rootElement,\n threshold: this._config.threshold,\n rootMargin: this._config.rootMargin\n }\n\n return new IntersectionObserver(entries => this._observerCallback(entries), options)\n }\n\n // The logic of selection\n _observerCallback(entries) {\n const targetElement = entry => this._targetLinks.get(`#${entry.target.id}`)\n const activate = entry => {\n this._previousScrollData.visibleEntryTop = entry.target.offsetTop\n this._process(targetElement(entry))\n }\n\n const parentScrollTop = (this._rootElement || document.documentElement).scrollTop\n const userScrollsDown = parentScrollTop >= this._previousScrollData.parentScrollTop\n this._previousScrollData.parentScrollTop = parentScrollTop\n\n for (const entry of entries) {\n if (!entry.isIntersecting) {\n this._activeTarget = null\n this._clearActiveClass(targetElement(entry))\n\n continue\n }\n\n const entryIsLowerThanPrevious = entry.target.offsetTop >= this._previousScrollData.visibleEntryTop\n // if we are scrolling down, pick the bigger offsetTop\n if (userScrollsDown && entryIsLowerThanPrevious) {\n activate(entry)\n // if parent isn't scrolled, let's keep the first visible item, breaking the iteration\n if (!parentScrollTop) {\n return\n }\n\n continue\n }\n\n // if we are scrolling up, pick the smallest offsetTop\n if (!userScrollsDown && !entryIsLowerThanPrevious) {\n activate(entry)\n }\n }\n }\n\n _initializeTargetsAndObservables() {\n this._targetLinks = new Map()\n this._observableSections = new Map()\n\n const targetLinks = SelectorEngine.find(SELECTOR_TARGET_LINKS, this._config.target)\n\n for (const anchor of targetLinks) {\n // ensure that the anchor has an id and is not disabled\n if (!anchor.hash || isDisabled(anchor)) {\n continue\n }\n\n const observableSection = SelectorEngine.findOne(decodeURI(anchor.hash), this._element)\n\n // ensure that the observableSection exists & is visible\n if (isVisible(observableSection)) {\n this._targetLinks.set(decodeURI(anchor.hash), anchor)\n this._observableSections.set(anchor.hash, observableSection)\n }\n }\n }\n\n _process(target) {\n if (this._activeTarget === target) {\n return\n }\n\n this._clearActiveClass(this._config.target)\n this._activeTarget = target\n target.classList.add(CLASS_NAME_ACTIVE)\n this._activateParents(target)\n\n EventHandler.trigger(this._element, EVENT_ACTIVATE, { relatedTarget: target })\n }\n\n _activateParents(target) {\n // Activate dropdown parents\n if (target.classList.contains(CLASS_NAME_DROPDOWN_ITEM)) {\n SelectorEngine.findOne(SELECTOR_DROPDOWN_TOGGLE, target.closest(SELECTOR_DROPDOWN))\n .classList.add(CLASS_NAME_ACTIVE)\n return\n }\n\n for (const listGroup of SelectorEngine.parents(target, SELECTOR_NAV_LIST_GROUP)) {\n // Set triggered links parents as active\n // With both