\n// This content is searchable\n//
\n//\n// This content is not searchable (but still visible)\n//
\n// \n// This content is searchable\n// \n//{ "version": 3, "sources": ["../../../javascript/bc/global.js", "../../../javascript/bc/helpers/url_helpers.js", "../../../javascript/bc/helpers/performance_timeline.js", "../../../javascript/bc/helpers/object_helpers.js", "../../../javascript/bc/helpers/scroll_event_helpers.js", "../../../javascript/bc/helpers/sound_helpers.js", "../../../javascript/bc/helpers/array_helpers.js", "../../../javascript/bc/helpers/page_title_helpers.js", "../../../javascript/vendor/selector_observer.js", "../../../javascript/all/initializers/selector_observer.js", "../../../javascript/bc/helpers/remote_form_validation.js", "../../../javascript/bc/helpers/function_helpers.js", "../../../javascript/bc/helpers/turbolinks_helpers.js", "../../../javascript/bc/helpers/string_helpers.js", "../../../javascript/bc/helpers/templates.js", "../../../javascript/bc/helpers/request_helpers.js", "../../../javascript/bc/helpers/regexp_helpers.js", "../../../javascript/bc/helpers/platform_helpers.js", "../../../javascript/bc/helpers/document_helpers.js", "../../../javascript/bc/helpers/utf16_string.js", "../../../javascript/bc/helpers/animation_helpers.js", "../../../javascript/bc/helpers/element_helpers.js", "../../../javascript/bc/helpers/emoji_helper.js", "../../../javascript/all/behaviors/element/flash_notice.jst.ejs", "../../../javascript/bc/helpers/flash_message_helper.js", "../../../javascript/bc/helpers/event_helpers.js", "../../../javascript/bc/helpers/uuid.js", "../../../javascript/bc/helpers/focus_mode_helpers.js", "../../../javascript/bc/helpers/input_type_helpers.js", "../../../javascript/bc/helpers/dom_helpers.js", "../../../javascript/bc/helpers/debug_helpers.js", "../../../javascript/bc/helpers/ready.js", "../../../javascript/bc/helpers/keycodes.js", "../../../javascript/bc/helpers/color_helpers.js", "../../../javascript/lib/cookie.js", "../../../javascript/helpers/meta_helpers.js", "../../../javascript/bc/initializers/authenticity_token.js", "../../../javascript/bc/initializers/current.js", "../../../javascript/bc/initializers/env.js", "../../../javascript/bc/file_uploads/file_upload_preview_completion.js", "../../../javascript/bc/file_uploads/navigation_warning.js", "../../../javascript/bc/file_uploads/picker.js", "../../../javascript/bc/file_uploads/file_upload_manager.js", "../../../javascript/bc/file_uploads/file_upload.js", "../../../javascript/lib/direct_upload_error_reporting.js", "../../../javascript/bc/file_uploads/direct_file_upload.js", "../../../javascript/bc/file_uploads/index.js", "../../../javascript/bc/background_operation_monitor.js", "../../../javascript/bc/elements/sticky.js", "../../../javascript/bc/elements/pie_chart.js", "../../../javascript/bc/elements/content_filter/config.js", "../../../javascript/bc/elements/content_filter/element_index.js", "../../../javascript/bc/elements/content_filter/query_results.js", "../../../javascript/helpers/announcement_helpers.js", "../../../javascript/bc/elements/content_filter/controller.js", "../../../javascript/bc/elements/chat_room.js", "../../../javascript/bc/elements/require.js", "../../../javascript/bc/elements/infinite_page.js", "../../../javascript/helpers/timing_helpers.js", "../../../javascript/helpers/focus_trap_helpers.js", "../../../javascript/bc/elements/modal.js", "../../../javascript/bc/elements/content_filter.js", "../../../javascript/bc/elements/grouped_dates.js", "../../../javascript/bc/inputs/suggestion/select_element.js", "../../../javascript/bc/inputs/suggestion/option_element.js", "../../../javascript/bc/inputs/suggestion/controller.js", "../../../javascript/bc/inputs/suggestion/results_controller.js", "../../../javascript/bc/inputs/suggestion/context.js", "../../../javascript/bc/inputs/autocomplete/controller.js", "../../../javascript/bc/inputs/autocomplete/element.js", "../../../javascript/bc/inputs/autocomplete/templates/autocompletable.jst.ejs", "../../../javascript/bc/inputs/autocomplete/templates/suggestions.jst.ejs", "../../../javascript/bc/inputs/autocomplete/helpers.js", "../../../javascript/bc/inputs/autocomplete/collection.js", "../../../javascript/bc/inputs/datetime/element.js", "../../../javascript/bc/inputs/segmented/controller.js", "../../../javascript/bc/inputs/segmented/element.js", "../../../javascript/bc/inputs/date/element.js", "../../../javascript/bc/inputs/time/element.js", "../../../javascript/bc/inputs/time/controller.js", "../../../javascript/bc/inputs/relative_date/element.js", "../../../javascript/bc/inputs/relative_date/picker_element.js", "../../../javascript/bc/inputs/relative_date/picker.jst.ejs", "../../../javascript/bc/inputs/relative_date/controller.js", "../../../javascript/bc/inputs/relative_date/picker_controller.js", "../../../javascript/bc/sortable/controller.js", "../../../javascript/bc/sortable/mouse_controller.js", "../../../javascript/bc/sortable/scroll_manager.js", "../../../javascript/bc/sortable/touch_controller.js", "../../../javascript/bc/sortable/revert.js", "../../../javascript/bc/sortable/index.js", "../../../javascript/sessions/populate_time_zone.js", "../../../javascript/all/behaviors/page/apply_email_validation_pattern.js"], "sourcesContent": ["window.BC = { Timeline: {} }\n", "BC.getQueryParams = function(location = window.location) {\n const params = {}\n const url = new URL(location.toString(), window.location.origin)\n for (const [key, value] of url.searchParams) {\n params[key] = value\n }\n return params\n}\n\nBC.formatURL = (url, params = {}) => expandPath(BC.formatPath(url, params))\n\nBC.formatPath = function(path, params = {}) {\n let queryString = $.param(params)\n if (queryString.length) {\n if (/\\?/.test(path)) {\n queryString = `&${queryString}`\n } else {\n queryString = `?${queryString}`\n }\n }\n\n return `${path}${queryString}`\n}\n\nBC.accountPath = (path, params = {}) => formatPathWithPrefix(path, BC.current.account.slugPath, params)\n\nBC.bucketPath = (path, params = {}) => formatPathWithPrefix(path, BC.current.bucket.path, params)\n\nBC.isUrlWithinCurrentAccount = (url) => {\n try {\n const path = new URL(url, document.baseURI).pathname\n const accountPath = BC.accountPath()\n return path === accountPath || path.startsWith(`${accountPath}/`)\n } catch {\n return false\n }\n}\n\nconst formatPathWithPrefix = function(path = \"\", prefix, params = {}) {\n if (prefix == null) { return }\n\n if (path.slice(0, prefix.length) !== prefix) {\n path = prefix + path\n }\n\n return BC.formatPath(path, params)\n}\n\nconst expandPath = function(path) {\n const element = document.createElement(\"a\")\n element.setAttribute(\"href\", path)\n return element.href\n}\n\nexport function withSearchParams(url, params) {\n const result = new URL(url)\n for (const [ name, value ] of Object.entries(params)) {\n result.searchParams.set(name, value)\n }\n return result\n}\n\nexport function consumeQueryParam(name) {\n const url = new URL(location)\n const value = url.searchParams.get(name)\n\n if (value !== null) {\n url.searchParams.delete(name)\n history.replaceState(history.state, \"\", url)\n }\n\n return value\n}\n", "if (BC.debug && window.performance) {\n\n window.addEventListener(\"load\", () => BC.defer(function() {\n console.group(\"[Timeline]\")\n console.table(formatEntriesForTable(getPerformanceEntries()))\n return console.groupEnd()\n }))\n\n const windowMarkers = \"navigationStart requestStart responseEnd \\\n domLoading domInteractive domContentLoadedEventStart domContentLoadedEventEnd \\\n loadEventStart loadEventEnd\".split(\" \")\n\n const resourceMarkers = \"requestStart responseEnd\".split(\" \")\n const timing = window.performance.timing\n\n const getPerformanceEntries = function() {\n const entries = []\n\n const start = timing.navigationStart\n\n Array.from(windowMarkers).forEach((marker) => {\n const time = timing[marker] - start\n entries.push({time, marker})\n })\n\n Array.from(getTimingEntriesForResourceType(\"script\", \"link\", \"xmlhttprequest\", \"\")).forEach((timing) => {\n const resource = formatFilename(timing.name)\n\n Array.from(resourceMarkers).forEach((marker) => {\n const time = Math.round(timing[marker])\n entries.push({time, marker, resource})\n })\n })\n\n const loadTimes = window.chrome?.loadTimes?.()\n if (loadTimes) {\n const marker = \"firstPaintTime\"\n const time = Math.round((loadTimes[marker] * 1000) - start)\n entries.push({time, marker})\n }\n\n return entries.sort((a, b) => a.time - b.time)\n }\n\n const getTimingEntriesForResourceType = function(...types) {\n const entries = window.performance?.getEntries?.()\n if (entries) {\n return Array.from(entries).filter((entry) => Array.from(types).includes(entry.initiatorType))\n } else {\n return []\n }\n }\n\n const formatFilename = function(name) {\n const parts = name.split(\"/\")\n let filename = parts[parts.length - 1]\n if (filename.length > 20) {\n filename = `${filename.slice(0, 10)}\u2026${filename.slice(-10)}`\n }\n return filename\n }\n\n const formatEntriesForTable = function(entries) {\n const data = {}\n\n Array.from(entries).forEach(({time, resource, marker}) => {\n if (resource == null) { resource = \"window\" }\n data[`${time}ms`] = {marker, resource}\n })\n\n return data\n }\n}\n", "BC.extendObject = function(object, properties) {\n for (const key in properties) {\n const value = properties[key]\n object[key] = value\n }\n return object\n}\n\nBC.copyObject = object => BC.extendObject({}, object)\n\nBC.sliceObject = function(object, ...keys) {\n const result = {}\n\n Array.from(keys).forEach((key) => {\n result[key] = object[key] != null ? object[key] : null\n })\n\n return result\n}\n\nBC.objectsAreEqual = function(a = {}, b = {}) {\n if (Object.keys(a).length !== Object.keys(b).length) { return false }\n for (const key in a) {\n const value = a[key]\n if (value !== b[key]) { return false }\n }\n return true\n}\n", "BC.onScroll = function(callback) {\n const throttledCallback = _.throttle(callback, 200)\n const handler = () => requestAnimationFrame(throttledCallback)\n\n const install = function() {\n window.addEventListener(\"scroll\", handler)\n return document.addEventListener(\"bc-infinite-page:scroll\", handler)\n }\n\n const uninstall = function() {\n window.removeEventListener(\"scroll\", handler)\n return document.removeEventListener(\"bc-infinite-page:scroll\", handler)\n }\n\n install()\n return uninstall\n}\n", "const sounds = {}\n\nBC.playSound = (url) => {\n if (!sounds[url]) sounds[url] = new Audio(url)\n sounds[url].play()\n}\n\n", "BC.arrayWithout = function(array, object) {\n if (Array.isArray(object)) {\n return array.filter(value => !object.includes(value))\n } else {\n return array.filter(value => value !== object)\n }\n}\n\nBC.uniqueValues = function(array) {\n const set = new Set()\n Array.from(array).forEach(value => set.add(value))\n return Array.from(set)\n}\n", "BC.fetchTitleForURL = function(url) {\n const request = new BC.PageTitleRequest(url)\n return request.perform()\n}\n\n\nconst TIMEOUT = 5000\nBC.PageTitleRequest = class PageTitleRequest {\n\n static titleExtractors = {\n json(data) {\n return `${data.title} - ${data.bucket.name}`\n },\n\n document(document) {\n const page = BC.readObjectFromMetaTags(\"current-page\", document)\n let {\n title\n } = page\n if (title) {\n if (page.subtitle) { title += ` - ${page.subtitle}` }\n return title\n } else {\n return document.querySelector(\"title\")?.textContent\n }\n }\n }\n\n constructor(url) {\n this.xhrDidLoad = this.xhrDidLoad.bind(this)\n this.url = url\n this.xhr = this.createXHR()\n this.xhr.addEventListener(\"load\", this.xhrDidLoad)\n this.promise = new Promise(resolve => {\n this.resolve = resolve\n\n })\n }\n\n perform() {\n this.xhr?.send()\n return this.promise\n }\n\n abort() {\n this.xhr?.abort()\n this.xhr = null\n }\n\n // Private\n\n createXHR() {\n const {url, responseType} = this.getRequestDetails()\n const xhr = new XMLHttpRequest\n xhr.open(\"get\", url, true)\n xhr.timeout = TIMEOUT\n xhr.responseType = responseType\n return xhr\n }\n\n xhrDidLoad(event) {\n const title = this.getTitle()\n return this.resolve(title)\n }\n\n getTitle() {\n const {responseType, response} = this.getResponseDetails()\n return PageTitleRequest.titleExtractors[responseType](response)\n }\n\n getResponseDetails() {\n let {responseType, response} = this.xhr\n // Prop up lack of `xhr.responseType = \"json\"` support\n if (!responseType && (this.getRequestDetails().responseType === \"json\")) {\n responseType = \"json\"\n response = JSON.parse(response)\n }\n return {responseType, response}\n }\n\n getRequestDetails() {\n const dataURL = this.getURLForRecordingData()\n if (dataURL) {\n return {\n url: dataURL,\n responseType: \"json\"\n }\n } else {\n return {\n url: this.url,\n responseType: \"document\"\n }\n }\n }\n\n getURLForRecordingData() {\n const recordingId = this.getRecordingIdFromURL()\n if (recordingId) {\n const bucketId = this.getBucketIdFromURL()\n if (bucketId) {\n return BC.accountPath(`/buckets/${bucketId}/recordings/${recordingId}.json`)\n }\n }\n }\n\n getRecordingIdFromURL() {\n const hash = getHashFromURL(this.url)\n if (hash) {\n return hash.match(/recording_(\\d+)/)?.[1]\n }\n }\n\n getBucketIdFromURL() {\n return this.url.match(/buckets\\/(\\d+)\\//)?.[1]\n }\n}\n\nconst getHashFromURL = function(url) {\n const a = document.createElement(\"a\")\n a.href = url\n return a.hash\n}\n", "window.SelectorObserver = class SelectorObserver {\n constructor() {\n this.mutationObserver = new MutationObserver(document.documentElement, this);\n this.definitionsBySelector = new SelectorSet;\n this.definitionsByElement = new WeakMap;\n }\n\n observe(selector, definition) {\n if (typeof definition === \"function\") {\n definition = {selectorMatched: definition};\n }\n\n this.definitionsBySelector.add(selector, definition);\n\n afterDOMContentLoaded(() => {\n for (const element of document.querySelectorAll(selector)) {\n const definitions = this.getDefinitionsForElement(element);\n if (!definitions.has(definition)) {\n definitions.add(definition);\n definition.selectorMatched?.(element);\n }\n }\n });\n }\n\n matchElement(element) {\n return this.definitionsBySelector.matches(element);\n }\n\n findMatchingElementsInTree(element) {\n return this.definitionsBySelector.findMatchingElementsInTree(element);\n }\n\n elementAttached(element) {\n const definitions = this.getDefinitionsForElement(element);\n this.definitionsBySelector.matchEachSelector(element, function(selector, definition, matched) {\n if (matched && !definitions.has(definition)) {\n definitions.add(definition);\n definition.selectorMatched?.(element);\n }\n });\n }\n\n elementAttributeChanged(element, attributeName) {\n const definitions = this.getDefinitionsForElement(element);\n this.definitionsBySelector.matchEachSelector(element, function(selector, definition, matched) {\n const present = definitions.has(definition);\n if (matched && !present) {\n definitions.add(definition);\n definition.selectorMatched?.(element);\n } else if (present && !matched) {\n definitions.delete(definition);\n definition.selectorUnmatched?.(element);\n }\n });\n }\n\n elementDetached(element) {\n const definitions = this.getDefinitionsForElement(element);\n for (const definition of definitions) {\n definition.selectorUnmatched?.(element);\n }\n definitions.clear();\n }\n\n getDefinitionsForElement(element) {\n let definitions = this.definitionsByElement.get(element);\n if (!definitions) {\n this.definitionsByElement.set(element, (definitions = new Set));\n }\n return definitions;\n }\n};\n\n\nclass MutationObserver {\n constructor(element, definition) {\n this.processMutations = this.processMutations.bind(this);\n this.element = element;\n this.definition = definition;\n this.elements = new Set;\n this.observer = new window.MutationObserver(this.processMutations);\n this.observer.observe(this.element, {attributes: true, childList: true, subtree: true});\n }\n\n processMutations(mutations) {\n for (const mutation of mutations) {\n switch (mutation.type) {\n case \"attributes\":\n this.processAttributeChange(mutation.target, mutation.attributeName);\n break;\n case \"childList\":\n if (mutation.removedNodes.length) this.processRemovedNodes(mutation.removedNodes);\n if (mutation.addedNodes.length) this.processAddedNodes(mutation.addedNodes);\n break;\n }\n }\n }\n\n processAttributeChange(element, attributeName) {\n if (this.elements.has(element)) {\n if (this.nodeIsProcessable(element)) {\n this.definition.elementAttributeChanged?.(element, attributeName);\n } else {\n this.removeElement(element);\n }\n } else if (this.nodeIsProcessable(element)) {\n this.addElement(element);\n }\n }\n\n processRemovedNodes(nodes) {\n for (const node of nodes) {\n this.forEachProcessableElementInTree(node, this.removeElement);\n }\n }\n\n processAddedNodes(nodes) {\n for (const node of nodes) {\n this.forEachProcessableElementInTree(node, this.addElement);\n }\n }\n\n nodeIsProcessable(node) {\n return (node.nodeType === Node.ELEMENT_NODE) && this.definition.matchElement?.(node);\n }\n\n forEachProcessableElementInTree(parentElement, callback) {\n for (const element of this.definition.findMatchingElementsInTree?.(parentElement) ?? []) {\n callback.call(this, element);\n }\n }\n\n addElement(element) {\n // There is an edge cases where a detached element is passed as argument, to protect from this make sure\n // that the element to add exists in the DOM before adding it.\n //\n // See https://3.basecamp.com/2914079/buckets/29375700/todos/5454764252\n if (!this.elements.has(element) && document.contains(element)) {\n this.elements.add(element);\n this.definition.elementAttached?.(element);\n }\n }\n\n removeElement(element) {\n if (this.elements.has(element)) {\n this.elements.delete(element);\n this.definition.elementDetached?.(element);\n }\n }\n}\n\n\nclass SelectorSet {\n constructor() {\n this.values = {};\n this.compositeSelector = null;\n }\n\n add(selector, value) {\n const values = this.values[selector] ?? [];\n if (value != null && !values.includes(value)) {\n this.values[selector] = [...values, value];\n this.compositeSelector = null;\n }\n return value;\n }\n\n remove(selector, value) {\n if (value != null) {\n this.values[selector] = (this.values[selector] ?? []).filter(v => v !== value);\n this.compositeSelector = null;\n }\n return value;\n }\n\n get(selector) {\n return this.values[selector] ?? [];\n }\n\n matchEachSelector(element, callback) {\n for (const selector in this.values) {\n const matched = elementMatchesSelector(element, selector);\n for (const value of this.values[selector]) {\n callback(selector, value, matched);\n }\n }\n }\n\n matches(element) {\n return elementMatchesSelector(element, this.getCompositeSelector());\n }\n\n findMatchingElementsInTree(element) {\n const composite = this.getCompositeSelector();\n const result = this.matches(element) ? [element] : [];\n for (const child of element?.querySelectorAll?.(composite) ?? []) {\n result.push(child);\n }\n return result;\n }\n\n getCompositeSelector() {\n return this.compositeSelector ??= Object.keys(this.values).join(\", \");\n }\n}\n\n\nfunction elementMatchesSelector(element, selector) {\n return element?.nodeType === Node.ELEMENT_NODE && element.matches(selector);\n}\n\nvar afterDOMContentLoaded = (function() {\n const readyCallbacks = [];\n\n document.addEventListener(\"DOMContentLoaded\", () => {\n let callback;\n while ((callback = readyCallbacks.shift())) {\n callback();\n }\n }, false);\n\n return function(callback) {\n if (document.readyState === \"loading\") {\n readyCallbacks.push(callback);\n } else {\n requestAnimationFrame(callback);\n }\n };\n})();\n", "import \"../../vendor/selector_observer\"\n\nBC.selectorObserver = new window.SelectorObserver()\n\nBC.observe = (selector, definition) => BC.selectorObserver.observe(selector, definition)\n", "import \"../../all/initializers/selector_observer\"\n\nBC.observe(\"form[data-behavior~=remote_form_validation]\", form => new RemoteFormValidator(form))\n\nclass RemoteFormValidator {\n constructor(form) {\n this.onInput = this.onInput.bind(this)\n this.onSubmit = this.onSubmit.bind(this)\n this.$form = $(form)\n this.inputs = {}\n\n this.setInputs()\n this.installEventHandlers()\n }\n\n setInputs() {\n return Array.from(this.$form.find(\"[data-validation-name]\")).map((input) =>\n (this.inputs[input.dataset.validationName] = input))\n }\n\n installEventHandlers() {\n this.$form.on(\"input\", this.onInput)\n return this.$form.on(\"submit\", this.onSubmit)\n }\n\n onInput(event) {\n return this.resetElement(event.target)\n }\n\n onSubmit(event) {\n event.preventDefault()\n\n const $submit = this.$form.find(\":submit\")\n $submit.prop(\"disabled\", true)\n\n return this.validate(valid => {\n if (valid) {\n return this.$form[0].submit()\n } else {\n return $submit.prop(\"disabled\", false)\n }\n })\n }\n\n invalidateElement(element, message) {\n $(element).addClass(\"error\")\n return $(element).closest(\"div\").find(\"[data-role~=error_message]\").text(message)\n }\n\n resetElement(element) {\n $(element).removeClass(\"error\")\n return $(element).closest(\"div\").find(\"[data-role~=error_message]\").empty()\n }\n\n validate(callback) {\n if (!this.validateRequiredInputs()) { return callback(false) }\n return this.performValidationRequest(errors => {\n for (const name in this.inputs) {\n const element = this.inputs[name]\n const message = errors[name]?.[0]\n if (message) {\n this.invalidateElement(element, message)\n }\n }\n return callback(this.isValid())\n })\n }\n\n validateRequiredInputs() {\n for (const required of Array.from(this.$form.find(\"[required]\"))) {\n if (!required.value.trim()) { return false }\n }\n return true\n }\n\n performValidationRequest(callback) {\n return $.post(this.$form.data(\"validation-url\"), this.$form.serialize(), data => callback(data.errors))\n }\n\n isValid() {\n return this.$form.find(\".error\").length === 0\n }\n}\n", "BC.defer = callback => setTimeout(callback, 1)\n\nBC.synchronize = function(fn) {\n const monitorCallbacks = new WeakMap\n return function(callback) {\n let callbacks = monitorCallbacks.get(this)\n if (!callbacks) {\n monitorCallbacks.set(this, (callbacks = []))\n }\n callbacks.push(callback)\n\n if (callbacks.length === 1) {\n return fn.call(this, () => {\n Array.from(callbacks).forEach((callback) => { callback?.() })\n return monitorCallbacks.delete(this)\n })\n }\n }\n}\n\nBC.measure =\n (window.performance?.now != null) ?\n function(block) {\n const started = performance.now()\n block()\n return performance.now() - started\n }\n :\n function(block) {\n const started = new Date().getTime()\n block()\n return new Date().getTime() - started\n }\n\nBC.throttle = function(fn) {\n let request = null\n return function(...args) {\n if (request) return request\n request = requestAnimationFrame(() => {\n request = null\n return fn.apply(this, args)\n })\n }\n}\n\nBC.throttleWithDelay = function(fn, delay = 0) {\n let throttled = false\n return function(...args) {\n if (!throttled) {\n throttled = true\n fn.apply(this, args)\n\n setTimeout(() => throttled = false, delay)\n }\n }\n}\n\nBC.debounce = function(fn, delay = 10) {\n let timeoutId = null\n const debounced = function(...args) {\n const callback = () => fn.apply(this, args)\n clearTimeout(timeoutId)\n timeoutId = setTimeout(callback, delay)\n }\n debounced.cancel = () => clearTimeout(timeoutId)\n return debounced\n}\n", "BC.displayingTurboPreview = () => document.documentElement.dataset.turboPreview != null\n", "/* eslint-disable\n no-useless-escape,\n*/\nexport function camelize(dashString) {\n const element = document.createElement(\"span\")\n element.setAttribute(`data-${dashString}`, \"\")\n return Object.keys(element.dataset)[0]\n}\n\nexport function dasherize(camelString) {\n const element = document.createElement(\"span\")\n element.dataset[camelString] = \"\"\n return element.attributes[0]?.name.slice(5)\n}\n\nexport function normalize(string) {\n return string.normalize(\"NFKD\").replace(/\\p{Diacritic}/gu, \"\")\n}\n\nexport const regexpEscape = string => string.toString().replace(/[\\-\\[\\]\\/\\{\\}\\(\\)\\*\\+\\?\\.\\\\\\^\\$\\|]/g, \"\\\\$&\")\n\nexport function stringIsEntirelyEmoji(string) {\n if (!string?.trim()) { return false }\n const content = string.replace(BC.EMOJI_PATTERN, \"\")\n return !content.match(/\\S/)\n}\n\nexport function truncate(string, length, omission = \"\u2026\") {\n if (string.length <= length) {\n return string\n } else {\n return string.slice(0, length - omission.length) + omission\n }\n}\n\nexport function toSentence(strings) {\n if (strings.length === 0) {\n return \"\"\n } else if (strings.length === 1) {\n return strings[0]\n } else {\n return strings.slice(0, -1).join(\", \") + \" and \" + strings.at(-1)\n }\n}\n\nBC.camelize = camelize\nBC.dasherize = dasherize\nBC.normalize = normalize\nBC.regexpEscape = regexpEscape\nBC.stringIsEntirelyEmoji = stringIsEntirelyEmoji\nBC.truncate = truncate\nBC.toSentence = toSentence\n", "// Keep in sync with Template::RelativeDates::BASE_DATE\nBC.TEMPLATE_BASE_DATE = CalendarDate.parse(\"1978-01-01\")\n", "BC.fetch = function(url, options = {}) {\n let fetch, headers\n if (!url?.length) { throw new TypeError(\"Missing required URL argument\") }\n\n const contentType = options.contentType != null ? options.contentType : getContentTypeForBody(options.body)\n const responseType = options.as != null ? options.as : \"text\"\n\n const fetchOptions = {\n credentials: options.credentials != null ? options.credentials : \"same-origin\",\n method: options.method != null ? options.method : \"get\",\n headers: (headers = BC.copyObject(options.headers != null ? options.headers : {})),\n body: ((options.body != null) ? options.body : undefined),\n keepalive: options.keepalive\n }\n\n if (options.signal) {\n fetchOptions.signal = options.signal\n }\n\n headers[\"X-Requested-With\"] = \"XMLHttpRequest\"\n headers[\"X-CSRF-Token\"] = BC.getAuthenticityToken()\n if (contentType != null) { headers[\"Content-Type\"] = contentType }\n\n if ((responseType === \"json\") || (contentType === \"application/json\")) {\n if (headers.Accept == null) { headers.Accept = \"application/json\" }\n }\n\n if (nativeFetchIsBuggyForMethod(fetchOptions.method)) {\n headers[\"X-Fetch-Type\"] = \"polyfill-forced\"\n fetch = window.fetchPolyfill\n } else {\n headers[\"X-Fetch-Type\"] = fetchIsNative() ? \"native\" : \"polyfill\";\n ({\n fetch\n } = window)\n }\n\n return fetch(url, fetchOptions)\n .then(checkStatus)\n .then(parseBody(responseType))\n}\n\nconst nativeFetchIsBuggyForMethod = function(method = \"\") {\n return fetchIsNative() &&\n /Windows.*Edge/.test(navigator.userAgent) &&\n [\"put\", \"patch\"].includes(method.toLowerCase())\n}\n\nconst fetchIsNative = () => window.fetch !== window.fetchPolyfill\n\nconst getContentTypeForBody = function(body) {\n if ((body == null) || body instanceof FormData) {\n return null\n } else if (body instanceof File) {\n return body.type\n } else {\n return \"application/octet-stream\"\n }\n}\n\nconst checkStatus = function(response) {\n let authUrl\n if (200 <= response.status && response.status < 300) {\n return response\n } else if ((response.status === 401) && (authUrl = response.getResponseHeader(\"WWW-Authenticate\"))) {\n return window.location.replace(authUrl)\n } else {\n const error = new Error(response.statusText)\n error.response = response\n throw error\n }\n}\n\nconst parseBody = responseType => (function(response) {\n switch (responseType) {\n case \"blob\": return response.blob()\n case \"json\": return response.json()\n case \"text\": return response.text()\n }\n})\n\nBC.xhr = function(url, options = {}) {\n if (!url?.length) { throw new TypeError(\"Missing required URL argument\") }\n const xhr = new XMLHttpRequest\n const method = options.method != null ? options.method : \"get\"\n xhr.open(method, url, true)\n xhr.setRequestHeader(\"X-Requested-With\", \"XMLHttpRequest\")\n xhr.setRequestHeader(\"X-CSRF-Token\", BC.getAuthenticityToken())\n if (options.contentType != null) { xhr.setRequestHeader(\"Content-Type\", options.contentType) }\n if (options.credentials) { xhr.withCredentials = true }\n return xhr\n}\n", "/* eslint-disable\n no-useless-escape,\n no-misleading-character-class,\n*/\n// Unicode letter, number, and punctuation regular expressions generated by XRegExp\n\nBC.LETTER_PATTERN = /[\\u0041-\\u005A\\u0061-\\u007A\\u00AA\\u00B5\\u00BA\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02C1\\u02C6-\\u02D1\\u02E0-\\u02E4\\u02EC\\u02EE\\u0370-\\u0374\\u0376\\u0377\\u037A-\\u037D\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03F5\\u03F7-\\u0481\\u048A-\\u0527\\u0531-\\u0556\\u0559\\u0561-\\u0587\\u05D0-\\u05EA\\u05F0-\\u05F2\\u0620-\\u064A\\u066E\\u066F\\u0671-\\u06D3\\u06D5\\u06E5\\u06E6\\u06EE\\u06EF\\u06FA-\\u06FC\\u06FF\\u0710\\u0712-\\u072F\\u074D-\\u07A5\\u07B1\\u07CA-\\u07EA\\u07F4\\u07F5\\u07FA\\u0800-\\u0815\\u081A\\u0824\\u0828\\u0840-\\u0858\\u08A0\\u08A2-\\u08AC\\u0904-\\u0939\\u093D\\u0950\\u0958-\\u0961\\u0971-\\u0977\\u0979-\\u097F\\u0985-\\u098C\\u098F\\u0990\\u0993-\\u09A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09BD\\u09CE\\u09DC\\u09DD\\u09DF-\\u09E1\\u09F0\\u09F1\\u0A05-\\u0A0A\\u0A0F\\u0A10\\u0A13-\\u0A28\\u0A2A-\\u0A30\\u0A32\\u0A33\\u0A35\\u0A36\\u0A38\\u0A39\\u0A59-\\u0A5C\\u0A5E\\u0A72-\\u0A74\\u0A85-\\u0A8D\\u0A8F-\\u0A91\\u0A93-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2\\u0AB3\\u0AB5-\\u0AB9\\u0ABD\\u0AD0\\u0AE0\\u0AE1\\u0B05-\\u0B0C\\u0B0F\\u0B10\\u0B13-\\u0B28\\u0B2A-\\u0B30\\u0B32\\u0B33\\u0B35-\\u0B39\\u0B3D\\u0B5C\\u0B5D\\u0B5F-\\u0B61\\u0B71\\u0B83\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99\\u0B9A\\u0B9C\\u0B9E\\u0B9F\\u0BA3\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB9\\u0BD0\\u0C05-\\u0C0C\\u0C0E-\\u0C10\\u0C12-\\u0C28\\u0C2A-\\u0C33\\u0C35-\\u0C39\\u0C3D\\u0C58\\u0C59\\u0C60\\u0C61\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CBD\\u0CDE\\u0CE0\\u0CE1\\u0CF1\\u0CF2\\u0D05-\\u0D0C\\u0D0E-\\u0D10\\u0D12-\\u0D3A\\u0D3D\\u0D4E\\u0D60\\u0D61\\u0D7A-\\u0D7F\\u0D85-\\u0D96\\u0D9A-\\u0DB1\\u0DB3-\\u0DBB\\u0DBD\\u0DC0-\\u0DC6\\u0E01-\\u0E30\\u0E32\\u0E33\\u0E40-\\u0E46\\u0E81\\u0E82\\u0E84\\u0E87\\u0E88\\u0E8A\\u0E8D\\u0E94-\\u0E97\\u0E99-\\u0E9F\\u0EA1-\\u0EA3\\u0EA5\\u0EA7\\u0EAA\\u0EAB\\u0EAD-\\u0EB0\\u0EB2\\u0EB3\\u0EBD\\u0EC0-\\u0EC4\\u0EC6\\u0EDC-\\u0EDF\\u0F00\\u0F40-\\u0F47\\u0F49-\\u0F6C\\u0F88-\\u0F8C\\u1000-\\u102A\\u103F\\u1050-\\u1055\\u105A-\\u105D\\u1061\\u1065\\u1066\\u106E-\\u1070\\u1075-\\u1081\\u108E\\u10A0-\\u10C5\\u10C7\\u10CD\\u10D0-\\u10FA\\u10FC-\\u1248\\u124A-\\u124D\\u1250-\\u1256\\u1258\\u125A-\\u125D\\u1260-\\u1288\\u128A-\\u128D\\u1290-\\u12B0\\u12B2-\\u12B5\\u12B8-\\u12BE\\u12C0\\u12C2-\\u12C5\\u12C8-\\u12D6\\u12D8-\\u1310\\u1312-\\u1315\\u1318-\\u135A\\u1380-\\u138F\\u13A0-\\u13F4\\u1401-\\u166C\\u166F-\\u167F\\u1681-\\u169A\\u16A0-\\u16EA\\u1700-\\u170C\\u170E-\\u1711\\u1720-\\u1731\\u1740-\\u1751\\u1760-\\u176C\\u176E-\\u1770\\u1780-\\u17B3\\u17D7\\u17DC\\u1820-\\u1877\\u1880-\\u18A8\\u18AA\\u18B0-\\u18F5\\u1900-\\u191C\\u1950-\\u196D\\u1970-\\u1974\\u1980-\\u19AB\\u19C1-\\u19C7\\u1A00-\\u1A16\\u1A20-\\u1A54\\u1AA7\\u1B05-\\u1B33\\u1B45-\\u1B4B\\u1B83-\\u1BA0\\u1BAE\\u1BAF\\u1BBA-\\u1BE5\\u1C00-\\u1C23\\u1C4D-\\u1C4F\\u1C5A-\\u1C7D\\u1CE9-\\u1CEC\\u1CEE-\\u1CF1\\u1CF5\\u1CF6\\u1D00-\\u1DBF\\u1E00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FBC\\u1FBE\\u1FC2-\\u1FC4\\u1FC6-\\u1FCC\\u1FD0-\\u1FD3\\u1FD6-\\u1FDB\\u1FE0-\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FFC\\u2071\\u207F\\u2090-\\u209C\\u2102\\u2107\\u210A-\\u2113\\u2115\\u2119-\\u211D\\u2124\\u2126\\u2128\\u212A-\\u212D\\u212F-\\u2139\\u213C-\\u213F\\u2145-\\u2149\\u214E\\u2183\\u2184\\u2C00-\\u2C2E\\u2C30-\\u2C5E\\u2C60-\\u2CE4\\u2CEB-\\u2CEE\\u2CF2\\u2CF3\\u2D00-\\u2D25\\u2D27\\u2D2D\\u2D30-\\u2D67\\u2D6F\\u2D80-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0-\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u2E2F\\u3005\\u3006\\u3031-\\u3035\\u303B\\u303C\\u3041-\\u3096\\u309D-\\u309F\\u30A1-\\u30FA\\u30FC-\\u30FF\\u3105-\\u312D\\u3131-\\u318E\\u31A0-\\u31BA\\u31F0-\\u31FF\\u3400-\\u4DB5\\u4E00-\\u9FCC\\uA000-\\uA48C\\uA4D0-\\uA4FD\\uA500-\\uA60C\\uA610-\\uA61F\\uA62A\\uA62B\\uA640-\\uA66E\\uA67F-\\uA697\\uA6A0-\\uA6E5\\uA717-\\uA71F\\uA722-\\uA788\\uA78B-\\uA78E\\uA790-\\uA793\\uA7A0-\\uA7AA\\uA7F8-\\uA801\\uA803-\\uA805\\uA807-\\uA80A\\uA80C-\\uA822\\uA840-\\uA873\\uA882-\\uA8B3\\uA8F2-\\uA8F7\\uA8FB\\uA90A-\\uA925\\uA930-\\uA946\\uA960-\\uA97C\\uA984-\\uA9B2\\uA9CF\\uAA00-\\uAA28\\uAA40-\\uAA42\\uAA44-\\uAA4B\\uAA60-\\uAA76\\uAA7A\\uAA80-\\uAAAF\\uAAB1\\uAAB5\\uAAB6\\uAAB9-\\uAABD\\uAAC0\\uAAC2\\uAADB-\\uAADD\\uAAE0-\\uAAEA\\uAAF2-\\uAAF4\\uAB01-\\uAB06\\uAB09-\\uAB0E\\uAB11-\\uAB16\\uAB20-\\uAB26\\uAB28-\\uAB2E\\uABC0-\\uABE2\\uAC00-\\uD7A3\\uD7B0-\\uD7C6\\uD7CB-\\uD7FB\\uF900-\\uFA6D\\uFA70-\\uFAD9\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFB1D\\uFB1F-\\uFB28\\uFB2A-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40\\uFB41\\uFB43\\uFB44\\uFB46-\\uFBB1\\uFBD3-\\uFD3D\\uFD50-\\uFD8F\\uFD92-\\uFDC7\\uFDF0-\\uFDFB\\uFE70-\\uFE74\\uFE76-\\uFEFC\\uFF21-\\uFF3A\\uFF41-\\uFF5A\\uFF66-\\uFFBE\\uFFC2-\\uFFC7\\uFFCA-\\uFFCF\\uFFD2-\\uFFD7\\uFFDA-\\uFFDC]/\n\nBC.NUMBER_PATTERN = /[\\u0030-\\u0039\\u00B2\\u00B3\\u00B9\\u00BC-\\u00BE\\u0660-\\u0669\\u06F0-\\u06F9\\u07C0-\\u07C9\\u0966-\\u096F\\u09E6-\\u09EF\\u09F4-\\u09F9\\u0A66-\\u0A6F\\u0AE6-\\u0AEF\\u0B66-\\u0B6F\\u0B72-\\u0B77\\u0BE6-\\u0BF2\\u0C66-\\u0C6F\\u0C78-\\u0C7E\\u0CE6-\\u0CEF\\u0D66-\\u0D75\\u0E50-\\u0E59\\u0ED0-\\u0ED9\\u0F20-\\u0F33\\u1040-\\u1049\\u1090-\\u1099\\u1369-\\u137C\\u16EE-\\u16F0\\u17E0-\\u17E9\\u17F0-\\u17F9\\u1810-\\u1819\\u1946-\\u194F\\u19D0-\\u19DA\\u1A80-\\u1A89\\u1A90-\\u1A99\\u1B50-\\u1B59\\u1BB0-\\u1BB9\\u1C40-\\u1C49\\u1C50-\\u1C59\\u2070\\u2074-\\u2079\\u2080-\\u2089\\u2150-\\u2182\\u2185-\\u2189\\u2460-\\u249B\\u24EA-\\u24FF\\u2776-\\u2793\\u2CFD\\u3007\\u3021-\\u3029\\u3038-\\u303A\\u3192-\\u3195\\u3220-\\u3229\\u3248-\\u324F\\u3251-\\u325F\\u3280-\\u3289\\u32B1-\\u32BF\\uA620-\\uA629\\uA6E6-\\uA6EF\\uA830-\\uA835\\uA8D0-\\uA8D9\\uA900-\\uA909\\uA9D0-\\uA9D9\\uAA50-\\uAA59\\uABF0-\\uABF9\\uFF10-\\uFF19]/\n\nBC.PUNCTUATION_PATTERN = /[\\u0021-\\u0023\\u0025-\\u002A\\u002C-\\u002F\\u003A\\u003B\\u003F\\u0040\\u005B-\\u005D\\u005F\\u007B\\u007D\\u00A1\\u00A7\\u00AB\\u00B6\\u00B7\\u00BB\\u00BF\\u037E\\u0387\\u055A-\\u055F\\u0589\\u058A\\u05BE\\u05C0\\u05C3\\u05C6\\u05F3\\u05F4\\u0609\\u060A\\u060C\\u060D\\u061B\\u061E\\u061F\\u066A-\\u066D\\u06D4\\u0700-\\u070D\\u07F7-\\u07F9\\u0830-\\u083E\\u085E\\u0964\\u0965\\u0970\\u0AF0\\u0DF4\\u0E4F\\u0E5A\\u0E5B\\u0F04-\\u0F12\\u0F14\\u0F3A-\\u0F3D\\u0F85\\u0FD0-\\u0FD4\\u0FD9\\u0FDA\\u104A-\\u104F\\u10FB\\u1360-\\u1368\\u1400\\u166D\\u166E\\u169B\\u169C\\u16EB-\\u16ED\\u1735\\u1736\\u17D4-\\u17D6\\u17D8-\\u17DA\\u1800-\\u180A\\u1944\\u1945\\u1A1E\\u1A1F\\u1AA0-\\u1AA6\\u1AA8-\\u1AAD\\u1B5A-\\u1B60\\u1BFC-\\u1BFF\\u1C3B-\\u1C3F\\u1C7E\\u1C7F\\u1CC0-\\u1CC7\\u1CD3\\u2010-\\u2027\\u2030-\\u2043\\u2045-\\u2051\\u2053-\\u205E\\u207D\\u207E\\u208D\\u208E\\u2329\\u232A\\u2768-\\u2775\\u27C5\\u27C6\\u27E6-\\u27EF\\u2983-\\u2998\\u29D8-\\u29DB\\u29FC\\u29FD\\u2CF9-\\u2CFC\\u2CFE\\u2CFF\\u2D70\\u2E00-\\u2E2E\\u2E30-\\u2E3B\\u3001-\\u3003\\u3008-\\u3011\\u3014-\\u301F\\u3030\\u303D\\u30A0\\u30FB\\uA4FE\\uA4FF\\uA60D-\\uA60F\\uA673\\uA67E\\uA6F2-\\uA6F7\\uA874-\\uA877\\uA8CE\\uA8CF\\uA8F8-\\uA8FA\\uA92E\\uA92F\\uA95F\\uA9C1-\\uA9CD\\uA9DE\\uA9DF\\uAA5C-\\uAA5F\\uAADE\\uAADF\\uAAF0\\uAAF1\\uABEB\\uFD3E\\uFD3F\\uFE10-\\uFE19\\uFE30-\\uFE52\\uFE54-\\uFE61\\uFE63\\uFE68\\uFE6A\\uFE6B\\uFF01-\\uFF03\\uFF05-\\uFF0A\\uFF0C-\\uFF0F\\uFF1A\\uFF1B\\uFF1F\\uFF20\\uFF3B-\\uFF3D\\uFF3F\\uFF5B\\uFF5D\\uFF5F-\\uFF65]/\n\n// One emoji, spelled however a system picker spells it: a flag's pair of\n// regional indicators, a subdivision flag's tag characters, a keycap, a modifier\n// base carrying a skin tone (some keyboards put U+FE0F between them), a\n// character that renders as emoji on its own, or a character that needs U+FE0F\n// to render as emoji. Only a zero-width joiner reaches past one of those. What\n// follows a joiner is spelled loosely in practice, so there the U+FE0F is\n// optional and the joiner may dangle with nothing after it. The lookaheads\n// carry the exclusions: regional indicators and skin tones only lead an emoji\n// as part of a flag or a toned base, U+FE0E asks for the text form of a\n// symbol, and \\p{Extended_Pictographic} on its own would admit pictographs\n// nobody calls an emoji, the chess pieces among them.\n//\n// \\p{RGI_Emoji} says all of this in one property but needs the `v` flag, which\n// iOS 16 WebViews lack. They bypass the browser version gate; see the Safari\n// 16.x note in sidebar_controller.js.\nconst emojiCore = ({ joined }) => [\n String.raw`\\p{Regional_Indicator}{2}`,\n String.raw`\\u{1F3F4}[\\u{E0020}-\\u{E007E}]+\\u{E007F}`,\n String.raw`[0-9#*]\\uFE0F\\u20E3`,\n String.raw`\\p{Emoji_Modifier_Base}\\uFE0F?\\p{Emoji_Modifier}`,\n String.raw`(?![\\p{Regional_Indicator}\\p{Emoji_Modifier}])\\p{Emoji_Presentation}(?!\\uFE0E)\\uFE0F?`,\n String.raw`(?=\\p{Emoji})\\p{Extended_Pictographic}(?!\\uFE0E)\\uFE0F` + (joined ? \"?\" : \"\")\n].join(\"|\")\n\nBC.EMOJI_PATTERN = new RegExp(String.raw`(?:${emojiCore({ joined: false })})(?:\\u200D(?:${emojiCore({ joined: true })})?)*`, \"gu\")\n\nBC.EMOJI_SKIN_TONE_PATTERN = /(?:\\uD83C[\\uDFFB-\\uDFFF])/g\n", "const {userAgent} = window.navigator\n\nconst ios = /iPhone|iPad|iOS/.test(userAgent)\nconst android = /Android/.test(userAgent)\nconst mobile = ios || android\n\nconst iosApp = /BC3 iOS/.test(userAgent)\nconst androidApp = /BC3 Android/.test(userAgent)\nconst mobileApp = iosApp || androidApp\nconst desktopApp = /Electron/.test(userAgent)\n\nconst mac = /Mac/.test(userAgent)\nconst windows = /Windows/.test(userAgent)\n\nconst isChrome = userAgent.match(/chrome/i)\nconst isSafari = (userAgent.match(/safari/i) && !isChrome) || iosApp\nconst isFirefox = userAgent.match(/firefox/i)\n\nBC.platform = {\n isIOS: () => ios,\n isAndroid: () => android,\n isMobile: () => mobile ,\n isIOSApp: () => iosApp,\n isAndroidApp: () => androidApp,\n isMobileApp: () => mobileApp,\n isDesktopApp: () => desktopApp,\n isMac: () => mac,\n isWindows: () => windows,\n isSafari: () => isSafari,\n isChrome: () => isChrome,\n os: (() => {\n if (mac) {\n return \"mac\"\n } else if (windows) {\n return \"windows\"\n } else {\n return \"\"\n }\n })(),\n browser: (() => {\n if (isChrome) {\n return \"chrome\"\n } else if (isSafari) {\n return \"safari\"\n } else if (isFirefox) {\n return \"firefox\"\n } else {\n return \"\"\n }\n })(),\n buildNumber() {\n const match = userAgent.match(/\\(build (\\d+);/)\n if (match) { return match[1] } else { return 0 }\n }\n}\n\nBC.platformConfigurationIncludes = configuration => BC.matches(document.documentElement, `[data-bridge-configuration~=${configuration}]`)\n", "import \"./platform_helpers\"\n\nBC.desktopWindowFocused = true\n\nBC.documentIsActive = (function() {\n const isVisible = () => document.visibilityState === \"visible\"\n const isFocused = () => document.hasFocus()\n\n if (BC.platform.isMobile()) {\n return () => isVisible()\n } else if (BC.platform.isDesktopApp()) {\n return () => isVisible() && BC.desktopWindowFocused\n } else {\n return () => isVisible() && isFocused()\n }\n})()\n", "// Copied from Trix. Useful independently as we lazy-load trix\n// so can't guarantee the equivalent class will be available\n// when trix hasn't loaded.\n\nlet ucs2decode, ucs2encode\nBC.UTF16String = class UTF16String {\n static box(value = \"\") {\n if (value instanceof this) {\n return value\n } else {\n return this.fromUCS2String(value?.toString())\n }\n }\n\n static fromUCS2String(ucs2String) {\n return new (this)(ucs2String, ucs2decode(ucs2String))\n }\n\n static fromCodepoints(codepoints) {\n return new (this)(ucs2encode(codepoints), codepoints)\n }\n\n constructor(ucs2String, codepoints) {\n this.ucs2String = ucs2String\n this.codepoints = codepoints\n this.length = this.codepoints.length\n this.ucs2Length = this.ucs2String.length\n }\n\n offsetToUCS2Offset(offset) {\n return ucs2encode(this.codepoints.slice(0, Math.max(0, offset))).length\n }\n\n offsetFromUCS2Offset(ucs2Offset) {\n return ucs2decode(this.ucs2String.slice(0, Math.max(0, ucs2Offset))).length\n }\n\n slice() {\n return this.constructor.fromCodepoints(this.codepoints.slice(...arguments))\n }\n\n charAt(offset) {\n return this.slice(offset, offset + 1)\n }\n\n isEqualTo(value) {\n return this.constructor.box(value).ucs2String === this.ucs2String\n }\n\n toJSON() {\n return this.ucs2String\n }\n\n getCacheKey() {\n return this.ucs2String\n }\n\n toString() {\n return this.ucs2String\n }\n}\n\nconst hasArrayFrom = Array.from?.(\"\\ud83d\\udc7c\").length === 1\nconst hasStringCodePointAt = (\" \".codePointAt?.(0) != null)\nconst hasStringFromCodePoint = String.fromCodePoint?.(32, 128124) === \" \\ud83d\\udc7c\"\n\n// UCS-2 conversion helpers ported from Mathias Bynens' Punycode.js:\n// https://github.com/bestiejs/punycode.js#punycodeucs2\n\n// Creates an array containing the numeric code points of each Unicode\n// character in the string. While JavaScript uses UCS-2 internally,\n// this function will convert a pair of surrogate halves (each of which\n// UCS-2 exposes as separate characters) into a single code point,\n// matching UTF-16.\nif (hasArrayFrom && hasStringCodePointAt) {\n ucs2decode = string => Array.from(string).map(char => char.codePointAt(0))\n} else {\n ucs2decode = function(string) {\n const output = []\n let counter = 0\n const {\n length\n } = string\n\n while (counter < length) {\n let value = string.charCodeAt(counter++)\n if ((0xD800 <= value && value <= 0xDBFF) && (counter < length)) {\n // high surrogate, and there is a next character\n const extra = string.charCodeAt(counter++)\n if ((extra & 0xFC00) === 0xDC00) {\n // low surrogate\n value = ((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000\n } else {\n // unmatched surrogate; only append this code unit, in case the\n // next code unit is the high surrogate of a surrogate pair\n counter--\n }\n }\n output.push(value)\n }\n\n return output\n }\n}\n\n// Creates a string based on an array of numeric code points.\nif (hasStringFromCodePoint) {\n ucs2encode = array => String.fromCodePoint(...Array.from(array || []))\n} else {\n ucs2encode = function(array) {\n const characters = []\n\n Array.from(array).forEach((value) => {\n let output = \"\"\n if (value > 0xFFFF) {\n value -= 0x10000\n output += String.fromCharCode(((value >>> 10) & 0x3FF) | 0xD800)\n value = 0xDC00 | (value & 0x3FF)\n }\n characters.push(output + String.fromCharCode(value))\n })\n\n return characters.join(\"\")\n }\n}\n", "BC.animateElementWithClass = (element, className, callback) => applyClassAwaitingEvent(element, className, \"animationend\", callback)\n\nBC.transitionElementWithClass = (element, className, callback) => applyClassAwaitingEvent(element, className, \"transitionend\", callback)\n\nBC.shakeElement = element => BC.animateElementWithClass(element, \"oops-shake-it\")\n\nconst applyClassAwaitingEvent = function(element, className, eventName, callback) {\n let timeout\n let uninstalled = false\n\n const uninstall = function() {\n if (!uninstalled) {\n uninstalled = true\n element.removeEventListener(eventName, uninstall)\n return requestAnimationFrame(function() {\n element.classList.remove(className)\n return callback?.()\n })\n }\n }\n\n element.addEventListener(eventName, uninstall)\n element.classList.add(className)\n\n // Failsafe: If we don't receive a {transition,animation}end event\n // for some reason, ensure that uninstall is still called.\n const duration = getDuration(element, eventName)\n if (duration) {\n timeout = duration + 200\n } else {\n timeout = 500\n }\n\n return setTimeout(uninstall, timeout)\n}\n\nconst getDuration = function(element, eventName) {\n const type = eventName === \"animationend\" ? \"animation\" : \"transition\"\n const duration = getComputedStyle(element)[`${type}Duration`]\n if (duration) {\n if (/ms/.test(duration)) {\n return parseInt(duration, 10)\n } else {\n return parseFloat(duration) * 1000\n }\n }\n}\n", "BC.makeElement = function(tagName, attributes) {\n const element = document.createElement(tagName)\n for (const name in attributes) { const value = attributes[name]; element.setAttribute(name, value) }\n return element\n}\n", "BC.EMOJI = {\n \"\uD83E\uDD29\": \"Face with starry eyes\",\n \"\uD83E\uDD73\": \"Partying face\",\n \"\uD83D\uDE0A\": \"Smiling face with flush cheeks\",\n \"\uD83D\uDE00\": \"Grinning face\",\n \"\uD83D\uDE05\": \"Grinning face with sweat drop\",\n \"\uD83D\uDE02\": \"Face with tears of joy\",\n \"\uD83D\uDE0E\": \"Smiling face with sunglasses\",\n \"\uD83D\uDE09\": \"Winking face\",\n \"\uD83D\uDE1C\": \"Winking face with stuck out tongue\",\n \"\uD83D\uDE2C\": \"Grimacing face\",\n \"\uD83D\uDE2E\": \"Surprised face with open mouth\",\n \"\uD83D\uDE33\": \"Flushed face\",\n \"\uD83E\uDD14\": \"Thinking face\",\n \"\uD83D\uDE12\": \"Unamused face\",\n \"\uD83D\uDE22\": \"Crying face\",\n \"\uD83D\uDE2D\": \"Loudly crying face\",\n \"\uD83D\uDE31\": \"Face screaming in fear\",\n \"\uD83D\uDC4D\": \"Thumbs up\",\n \"\uD83D\uDC4F\": \"Clapping\",\n \"\uD83D\uDC4B\": \"Waving hand\",\n \"\uD83D\uDE4C\": \"Hands raised in celebration\",\n \"\uD83D\uDE4F\": \"Hands pressed together\",\n \"\uD83D\uDCAA\": \"Flexed bicep\",\n \"\uD83E\uDD18\": \"Sign of the horns\",\n \"\u270A\": \"Raised fist\",\n \"\uD83D\uDC4E\": \"Thumbs down\",\n \"\u270C\uFE0F\": \"Peace\",\n \"\uD83D\uDC48\": \"Finger pointing left\",\n \"\uD83D\uDC46\": \"Finger pointing Up\",\n \"\u270B\": \"Raised hand\",\n \"\uD83C\uDF89\": \"Party popper\",\n \"\u2728\": \"Sparkles\",\n \"\u2764\uFE0F\": \"Red heart\",\n \"\uD83D\uDCAF\": \"100 points\",\n \"\uD83D\uDC40\": \"Eyes\",\n \"\uD83D\uDCA9\": \"Pile of poop\",\n \"\u2600\uFE0F\": \"Sun\",\n \"\uD83C\uDF19\": \"Moon\",\n \"\uD83D\uDCA5\": \"Collision\",\n \"\uD83D\uDD25\": \"Fire\",\n \"\uD83C\uDF82\": \"Birthday cake\",\n \"\uD83C\uDF74\": \"Fork and knife\",\n \"\uD83D\uDCB0\": \"Money bag\",\n \"\uD83E\uDD47\": \"Gold medal\",\n \"\uD83D\uDEA8\": \"Red flashing light\",\n \"\uD83D\uDCA1\": \"Light bulb\",\n \"\uD83D\uDEE0\": \"Hammer and wrench\",\n \"\uD83D\uDCC8\": \"Chart with upward trend\",\n \"\u2705\": \"Check mark\",\n \"\uD83D\uDCE2\": \"Public address loudspeaker\"\n}\n", "if (!window.JST) window.JST = {}\nwindow.JST['all/behaviors/element/flash_notice'] = function(object){\nvar __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n__p+='
\n// This content is not searchable (but still visible)\n//
\n// \n// This content is searchable\n// \n//\n// Hide me when searching\n//
\n//\n//\n// Show me when there are no search results\n//
\n//