{ "version": 3, "sources": ["../../../node_modules/@rails/actioncable/src/adapters.js", "../../../node_modules/@rails/actioncable/src/logger.js", "../../../node_modules/@rails/actioncable/src/connection_monitor.js", "../../../node_modules/@rails/actioncable/src/internal.js", "../../../node_modules/@rails/actioncable/src/connection.js", "../../../node_modules/@rails/actioncable/src/subscription.js", "../../../node_modules/@rails/actioncable/src/subscription_guarantor.js", "../../../node_modules/@rails/actioncable/src/subscriptions.js", "../../../node_modules/@rails/actioncable/src/consumer.js", "../../../node_modules/@rails/actioncable/src/index.js", "../../../node_modules/rangy/lib/rangy-core.js", "../../../node_modules/rangy/lib/rangy-serializer.js", "../../../node_modules/highlight.js/lib/core.js", "../../../node_modules/parameterize/parameterize.js", "../../../node_modules/@hotwired/turbo/dist/turbo.es2017-esm.js", "../../../node_modules/@hotwired/turbo-rails/app/javascript/turbo/cable.js", "../../../node_modules/@hotwired/turbo-rails/app/javascript/turbo/snakeize.js", "../../../node_modules/@hotwired/turbo-rails/app/javascript/turbo/cable_stream_source_element.js", "../../../node_modules/@hotwired/turbo-rails/app/javascript/turbo/fetch_requests.js", "../../../node_modules/@hotwired/turbo-rails/app/javascript/turbo/index.js", "../../../node_modules/@hotwired/stimulus/dist/stimulus.js", "../../javascript/controllers/application.js", "../../javascript/controllers/clipboard_controller.js", "../../javascript/controllers/confirmation_controller.js", "../../javascript/utilities/local_storage.js", "../../javascript/controllers/dark_mode_toggle_controller.js", "../../javascript/controllers/display_remembered_article_controller.js", "../../javascript/controllers/fade_controller.js", "../../javascript/controllers/menu_controller.js", "../../javascript/controllers/menu_navigation_controller.js", "../../../node_modules/body-scroll-lock/lib/bodyScrollLock.esm.js", "../../javascript/controllers/modal_controller.js", "../../javascript/controllers/scroll_controller.js", "../../javascript/controllers/search_controller.js", "../../javascript/controllers/tabs_controller.js", "../../javascript/controllers/toggle_class_controller.js", "../../javascript/controllers/index.js", "../../../node_modules/rough-notation/lib/rough-notation.esm.js", "../../javascript/utilities/dom_helpers.js", "../../javascript/utilities/ranges.js", "../../javascript/utilities/stimulus_helpers.js", "../../javascript/utilities/selection_toolbar.js", "../../javascript/article_controllers/annotation/annotation_controller.js", "../../../node_modules/@rails/request.js/src/fetch_response.js", "../../../node_modules/@rails/request.js/src/request_interceptor.js", "../../../node_modules/@rails/request.js/src/lib/utils.js", "../../../node_modules/@rails/request.js/src/fetch_request.js", "../../../node_modules/@rails/request.js/src/verbs.js", "../../javascript/utilities/annotation.js", "../../javascript/utilities/annotation_attributes.js", "../../javascript/article_controllers/annotation/color_selector_controller.js", "../../javascript/article_controllers/annotation/comment_controller.js", "../../javascript/article_controllers/annotation/display_controller.js", "../../javascript/article_controllers/annotation/selection_controller.js", "../../javascript/article_controllers/annotation/toolbar_controller.js", "../../javascript/utilities/feedback.js", "../../javascript/article_controllers/feedback/display_controller.js", "../../javascript/article_controllers/feedback/editor_controller.js", "../../javascript/article_controllers/feedback/feedback_controller.js", "../../javascript/article_controllers/duplicate_controller.js", "../../javascript/article_controllers/font_size_controller.js", "../../../node_modules/highlight.js/es/core.js", "../../../node_modules/highlight.js/es/languages/javascript.js", "../../../node_modules/highlight.js/es/languages/lua.js", "../../javascript/article_controllers/format_code_controller.js", "../../javascript/article_controllers/remember_article_controller.js", "../../javascript/article_controllers/text_to_speech_controller.js", "../../javascript/article_controllers/upgrade_headings_controller.js", "../../javascript/article_controllers/video_player_controller.js", "../../javascript/article_controllers/index.js"], "sourcesContent": ["export default {\n logger: self.console,\n WebSocket: self.WebSocket\n}\n", "import adapters from \"./adapters\"\n\n// The logger is disabled by default. You can enable it with:\n//\n// ActionCable.logger.enabled = true\n//\n// Example:\n//\n// import * as ActionCable from '@rails/actioncable'\n//\n// ActionCable.logger.enabled = true\n// ActionCable.logger.log('Connection Established.')\n//\n\nexport default {\n log(...messages) {\n if (this.enabled) {\n messages.push(Date.now())\n adapters.logger.log(\"[ActionCable]\", ...messages)\n }\n },\n}\n", "import logger from \"./logger\"\n\n// Responsible for ensuring the cable connection is in good health by validating the heartbeat pings sent from the server, and attempting\n// revival reconnections if things go astray. Internal class, not intended for direct user manipulation.\n\nconst now = () => new Date().getTime()\n\nconst secondsSince = time => (now() - time) / 1000\n\nclass ConnectionMonitor {\n constructor(connection) {\n this.visibilityDidChange = this.visibilityDidChange.bind(this)\n this.connection = connection\n this.reconnectAttempts = 0\n }\n\n start() {\n if (!this.isRunning()) {\n this.startedAt = now()\n delete this.stoppedAt\n this.startPolling()\n addEventListener(\"visibilitychange\", this.visibilityDidChange)\n logger.log(`ConnectionMonitor started. stale threshold = ${this.constructor.staleThreshold} s`)\n }\n }\n\n stop() {\n if (this.isRunning()) {\n this.stoppedAt = now()\n this.stopPolling()\n removeEventListener(\"visibilitychange\", this.visibilityDidChange)\n logger.log(\"ConnectionMonitor stopped\")\n }\n }\n\n isRunning() {\n return this.startedAt && !this.stoppedAt\n }\n\n recordPing() {\n this.pingedAt = now()\n }\n\n recordConnect() {\n this.reconnectAttempts = 0\n this.recordPing()\n delete this.disconnectedAt\n logger.log(\"ConnectionMonitor recorded connect\")\n }\n\n recordDisconnect() {\n this.disconnectedAt = now()\n logger.log(\"ConnectionMonitor recorded disconnect\")\n }\n\n // Private\n\n startPolling() {\n this.stopPolling()\n this.poll()\n }\n\n stopPolling() {\n clearTimeout(this.pollTimeout)\n }\n\n poll() {\n this.pollTimeout = setTimeout(() => {\n this.reconnectIfStale()\n this.poll()\n }\n , this.getPollInterval())\n }\n\n getPollInterval() {\n const { staleThreshold, reconnectionBackoffRate } = this.constructor\n const backoff = Math.pow(1 + reconnectionBackoffRate, Math.min(this.reconnectAttempts, 10))\n const jitterMax = this.reconnectAttempts === 0 ? 1.0 : reconnectionBackoffRate\n const jitter = jitterMax * Math.random()\n return staleThreshold * 1000 * backoff * (1 + jitter)\n }\n\n reconnectIfStale() {\n if (this.connectionIsStale()) {\n logger.log(`ConnectionMonitor detected stale connection. reconnectAttempts = ${this.reconnectAttempts}, time stale = ${secondsSince(this.refreshedAt)} s, stale threshold = ${this.constructor.staleThreshold} s`)\n this.reconnectAttempts++\n if (this.disconnectedRecently()) {\n logger.log(`ConnectionMonitor skipping reopening recent disconnect. time disconnected = ${secondsSince(this.disconnectedAt)} s`)\n } else {\n logger.log(\"ConnectionMonitor reopening\")\n this.connection.reopen()\n }\n }\n }\n\n get refreshedAt() {\n return this.pingedAt ? this.pingedAt : this.startedAt\n }\n\n connectionIsStale() {\n return secondsSince(this.refreshedAt) > this.constructor.staleThreshold\n }\n\n disconnectedRecently() {\n return this.disconnectedAt && (secondsSince(this.disconnectedAt) < this.constructor.staleThreshold)\n }\n\n visibilityDidChange() {\n if (document.visibilityState === \"visible\") {\n setTimeout(() => {\n if (this.connectionIsStale() || !this.connection.isOpen()) {\n logger.log(`ConnectionMonitor reopening stale connection on visibilitychange. visibilityState = ${document.visibilityState}`)\n this.connection.reopen()\n }\n }\n , 200)\n }\n }\n\n}\n\nConnectionMonitor.staleThreshold = 6 // Server::Connections::BEAT_INTERVAL * 2 (missed two pings)\nConnectionMonitor.reconnectionBackoffRate = 0.15\n\nexport default ConnectionMonitor\n", "export default {\n \"message_types\": {\n \"welcome\": \"welcome\",\n \"disconnect\": \"disconnect\",\n \"ping\": \"ping\",\n \"confirmation\": \"confirm_subscription\",\n \"rejection\": \"reject_subscription\"\n },\n \"disconnect_reasons\": {\n \"unauthorized\": \"unauthorized\",\n \"invalid_request\": \"invalid_request\",\n \"server_restart\": \"server_restart\"\n },\n \"default_mount_path\": \"/cable\",\n \"protocols\": [\n \"actioncable-v1-json\",\n \"actioncable-unsupported\"\n ]\n}\n", "import adapters from \"./adapters\"\nimport ConnectionMonitor from \"./connection_monitor\"\nimport INTERNAL from \"./internal\"\nimport logger from \"./logger\"\n\n// Encapsulate the cable connection held by the consumer. This is an internal class not intended for direct user manipulation.\n\nconst {message_types, protocols} = INTERNAL\nconst supportedProtocols = protocols.slice(0, protocols.length - 1)\n\nconst indexOf = [].indexOf\n\nclass Connection {\n constructor(consumer) {\n this.open = this.open.bind(this)\n this.consumer = consumer\n this.subscriptions = this.consumer.subscriptions\n this.monitor = new ConnectionMonitor(this)\n this.disconnected = true\n }\n\n send(data) {\n if (this.isOpen()) {\n this.webSocket.send(JSON.stringify(data))\n return true\n } else {\n return false\n }\n }\n\n open() {\n if (this.isActive()) {\n logger.log(`Attempted to open WebSocket, but existing socket is ${this.getState()}`)\n return false\n } else {\n logger.log(`Opening WebSocket, current state is ${this.getState()}, subprotocols: ${protocols}`)\n if (this.webSocket) { this.uninstallEventHandlers() }\n this.webSocket = new adapters.WebSocket(this.consumer.url, protocols)\n this.installEventHandlers()\n this.monitor.start()\n return true\n }\n }\n\n close({allowReconnect} = {allowReconnect: true}) {\n if (!allowReconnect) { this.monitor.stop() }\n // Avoid closing websockets in a \"connecting\" state due to Safari 15.1+ bug. See: https://github.com/rails/rails/issues/43835#issuecomment-1002288478\n if (this.isOpen()) {\n return this.webSocket.close()\n }\n }\n\n reopen() {\n logger.log(`Reopening WebSocket, current state is ${this.getState()}`)\n if (this.isActive()) {\n try {\n return this.close()\n } catch (error) {\n logger.log(\"Failed to reopen WebSocket\", error)\n }\n finally {\n logger.log(`Reopening WebSocket in ${this.constructor.reopenDelay}ms`)\n setTimeout(this.open, this.constructor.reopenDelay)\n }\n } else {\n return this.open()\n }\n }\n\n getProtocol() {\n if (this.webSocket) {\n return this.webSocket.protocol\n }\n }\n\n isOpen() {\n return this.isState(\"open\")\n }\n\n isActive() {\n return this.isState(\"open\", \"connecting\")\n }\n\n // Private\n\n isProtocolSupported() {\n return indexOf.call(supportedProtocols, this.getProtocol()) >= 0\n }\n\n isState(...states) {\n return indexOf.call(states, this.getState()) >= 0\n }\n\n getState() {\n if (this.webSocket) {\n for (let state in adapters.WebSocket) {\n if (adapters.WebSocket[state] === this.webSocket.readyState) {\n return state.toLowerCase()\n }\n }\n }\n return null\n }\n\n installEventHandlers() {\n for (let eventName in this.events) {\n const handler = this.events[eventName].bind(this)\n this.webSocket[`on${eventName}`] = handler\n }\n }\n\n uninstallEventHandlers() {\n for (let eventName in this.events) {\n this.webSocket[`on${eventName}`] = function() {}\n }\n }\n\n}\n\nConnection.reopenDelay = 500\n\nConnection.prototype.events = {\n message(event) {\n if (!this.isProtocolSupported()) { return }\n const {identifier, message, reason, reconnect, type} = JSON.parse(event.data)\n switch (type) {\n case message_types.welcome:\n this.monitor.recordConnect()\n return this.subscriptions.reload()\n case message_types.disconnect:\n logger.log(`Disconnecting. Reason: ${reason}`)\n return this.close({allowReconnect: reconnect})\n case message_types.ping:\n return this.monitor.recordPing()\n case message_types.confirmation:\n this.subscriptions.confirmSubscription(identifier)\n return this.subscriptions.notify(identifier, \"connected\")\n case message_types.rejection:\n return this.subscriptions.reject(identifier)\n default:\n return this.subscriptions.notify(identifier, \"received\", message)\n }\n },\n\n open() {\n logger.log(`WebSocket onopen event, using '${this.getProtocol()}' subprotocol`)\n this.disconnected = false\n if (!this.isProtocolSupported()) {\n logger.log(\"Protocol is unsupported. Stopping monitor and disconnecting.\")\n return this.close({allowReconnect: false})\n }\n },\n\n close(event) {\n logger.log(\"WebSocket onclose event\")\n if (this.disconnected) { return }\n this.disconnected = true\n this.monitor.recordDisconnect()\n return this.subscriptions.notifyAll(\"disconnected\", {willAttemptReconnect: this.monitor.isRunning()})\n },\n\n error() {\n logger.log(\"WebSocket onerror event\")\n }\n}\n\nexport default Connection\n", "// A new subscription is created through the ActionCable.Subscriptions instance available on the consumer.\n// It provides a number of callbacks and a method for calling remote procedure calls on the corresponding\n// Channel instance on the server side.\n//\n// An example demonstrates the basic functionality:\n//\n// App.appearance = App.cable.subscriptions.create(\"AppearanceChannel\", {\n// connected() {\n// // Called once the subscription has been successfully completed\n// },\n//\n// disconnected({ willAttemptReconnect: boolean }) {\n// // Called when the client has disconnected with the server.\n// // The object will have an `willAttemptReconnect` property which\n// // says whether the client has the intention of attempting\n// // to reconnect.\n// },\n//\n// appear() {\n// this.perform('appear', {appearing_on: this.appearingOn()})\n// },\n//\n// away() {\n// this.perform('away')\n// },\n//\n// appearingOn() {\n// $('main').data('appearing-on')\n// }\n// })\n//\n// The methods #appear and #away forward their intent to the remote AppearanceChannel instance on the server\n// by calling the `perform` method with the first parameter being the action (which maps to AppearanceChannel#appear/away).\n// The second parameter is a hash that'll get JSON encoded and made available on the server in the data parameter.\n//\n// This is how the server component would look:\n//\n// class AppearanceChannel < ApplicationActionCable::Channel\n// def subscribed\n// current_user.appear\n// end\n//\n// def unsubscribed\n// current_user.disappear\n// end\n//\n// def appear(data)\n// current_user.appear on: data['appearing_on']\n// end\n//\n// def away\n// current_user.away\n// end\n// end\n//\n// The \"AppearanceChannel\" name is automatically mapped between the client-side subscription creation and the server-side Ruby class name.\n// The AppearanceChannel#appear/away public methods are exposed automatically to client-side invocation through the perform method.\n\nconst extend = function(object, properties) {\n if (properties != null) {\n for (let key in properties) {\n const value = properties[key]\n object[key] = value\n }\n }\n return object\n}\n\nexport default class Subscription {\n constructor(consumer, params = {}, mixin) {\n this.consumer = consumer\n this.identifier = JSON.stringify(params)\n extend(this, mixin)\n }\n\n // Perform a channel action with the optional data passed as an attribute\n perform(action, data = {}) {\n data.action = action\n return this.send(data)\n }\n\n send(data) {\n return this.consumer.send({command: \"message\", identifier: this.identifier, data: JSON.stringify(data)})\n }\n\n unsubscribe() {\n return this.consumer.subscriptions.remove(this)\n }\n}\n", "import logger from \"./logger\"\n\n// Responsible for ensuring channel subscribe command is confirmed, retrying until confirmation is received.\n// Internal class, not intended for direct user manipulation.\n\nclass SubscriptionGuarantor {\n constructor(subscriptions) {\n this.subscriptions = subscriptions\n this.pendingSubscriptions = []\n }\n\n guarantee(subscription) {\n if(this.pendingSubscriptions.indexOf(subscription) == -1){ \n logger.log(`SubscriptionGuarantor guaranteeing ${subscription.identifier}`)\n this.pendingSubscriptions.push(subscription) \n }\n else {\n logger.log(`SubscriptionGuarantor already guaranteeing ${subscription.identifier}`)\n }\n this.startGuaranteeing()\n }\n\n forget(subscription) {\n logger.log(`SubscriptionGuarantor forgetting ${subscription.identifier}`)\n this.pendingSubscriptions = (this.pendingSubscriptions.filter((s) => s !== subscription))\n }\n\n startGuaranteeing() {\n this.stopGuaranteeing()\n this.retrySubscribing()\n }\n \n stopGuaranteeing() {\n clearTimeout(this.retryTimeout)\n }\n\n retrySubscribing() {\n this.retryTimeout = setTimeout(() => {\n if (this.subscriptions && typeof(this.subscriptions.subscribe) === \"function\") {\n this.pendingSubscriptions.map((subscription) => {\n logger.log(`SubscriptionGuarantor resubscribing ${subscription.identifier}`)\n this.subscriptions.subscribe(subscription)\n })\n }\n }\n , 500)\n }\n}\n\nexport default SubscriptionGuarantor", "import Subscription from \"./subscription\"\nimport SubscriptionGuarantor from \"./subscription_guarantor\"\nimport logger from \"./logger\"\n\n// Collection class for creating (and internally managing) channel subscriptions.\n// The only method intended to be triggered by the user is ActionCable.Subscriptions#create,\n// and it should be called through the consumer like so:\n//\n// App = {}\n// App.cable = ActionCable.createConsumer(\"ws://example.com/accounts/1\")\n// App.appearance = App.cable.subscriptions.create(\"AppearanceChannel\")\n//\n// For more details on how you'd configure an actual channel subscription, see ActionCable.Subscription.\n\nexport default class Subscriptions {\n constructor(consumer) {\n this.consumer = consumer\n this.guarantor = new SubscriptionGuarantor(this)\n this.subscriptions = []\n }\n\n create(channelName, mixin) {\n const channel = channelName\n const params = typeof channel === \"object\" ? channel : {channel}\n const subscription = new Subscription(this.consumer, params, mixin)\n return this.add(subscription)\n }\n\n // Private\n\n add(subscription) {\n this.subscriptions.push(subscription)\n this.consumer.ensureActiveConnection()\n this.notify(subscription, \"initialized\")\n this.subscribe(subscription)\n return subscription\n }\n\n remove(subscription) {\n this.forget(subscription)\n if (!this.findAll(subscription.identifier).length) {\n this.sendCommand(subscription, \"unsubscribe\")\n }\n return subscription\n }\n\n reject(identifier) {\n return this.findAll(identifier).map((subscription) => {\n this.forget(subscription)\n this.notify(subscription, \"rejected\")\n return subscription\n })\n }\n\n forget(subscription) {\n this.guarantor.forget(subscription)\n this.subscriptions = (this.subscriptions.filter((s) => s !== subscription))\n return subscription\n }\n\n findAll(identifier) {\n return this.subscriptions.filter((s) => s.identifier === identifier)\n }\n\n reload() {\n return this.subscriptions.map((subscription) =>\n this.subscribe(subscription))\n }\n\n notifyAll(callbackName, ...args) {\n return this.subscriptions.map((subscription) =>\n this.notify(subscription, callbackName, ...args))\n }\n\n notify(subscription, callbackName, ...args) {\n let subscriptions\n if (typeof subscription === \"string\") {\n subscriptions = this.findAll(subscription)\n } else {\n subscriptions = [subscription]\n }\n\n return subscriptions.map((subscription) =>\n (typeof subscription[callbackName] === \"function\" ? subscription[callbackName](...args) : undefined))\n }\n\n subscribe(subscription) {\n if (this.sendCommand(subscription, \"subscribe\")) {\n this.guarantor.guarantee(subscription)\n }\n }\n\n confirmSubscription(identifier) {\n logger.log(`Subscription confirmed ${identifier}`)\n this.findAll(identifier).map((subscription) =>\n this.guarantor.forget(subscription))\n }\n\n sendCommand(subscription, command) {\n const {identifier} = subscription\n return this.consumer.send({command, identifier})\n }\n}\n", "import Connection from \"./connection\"\nimport Subscriptions from \"./subscriptions\"\n\n// The ActionCable.Consumer establishes the connection to a server-side Ruby Connection object. Once established,\n// the ActionCable.ConnectionMonitor will ensure that its properly maintained through heartbeats and checking for stale updates.\n// The Consumer instance is also the gateway to establishing subscriptions to desired channels through the #createSubscription\n// method.\n//\n// The following example shows how this can be set up:\n//\n// App = {}\n// App.cable = ActionCable.createConsumer(\"ws://example.com/accounts/1\")\n// App.appearance = App.cable.subscriptions.create(\"AppearanceChannel\")\n//\n// For more details on how you'd configure an actual channel subscription, see ActionCable.Subscription.\n//\n// When a consumer is created, it automatically connects with the server.\n//\n// To disconnect from the server, call\n//\n// App.cable.disconnect()\n//\n// and to restart the connection:\n//\n// App.cable.connect()\n//\n// Any channel subscriptions which existed prior to disconnecting will\n// automatically resubscribe.\n\nexport default class Consumer {\n constructor(url) {\n this._url = url\n this.subscriptions = new Subscriptions(this)\n this.connection = new Connection(this)\n }\n\n get url() {\n return createWebSocketURL(this._url)\n }\n\n send(data) {\n return this.connection.send(data)\n }\n\n connect() {\n return this.connection.open()\n }\n\n disconnect() {\n return this.connection.close({allowReconnect: false})\n }\n\n ensureActiveConnection() {\n if (!this.connection.isActive()) {\n return this.connection.open()\n }\n }\n}\n\nexport function createWebSocketURL(url) {\n if (typeof url === \"function\") {\n url = url()\n }\n\n if (url && !/^wss?:/i.test(url)) {\n const a = document.createElement(\"a\")\n a.href = url\n // Fix populating Location properties in IE. Otherwise, protocol will be blank.\n a.href = a.href\n a.protocol = a.protocol.replace(\"http\", \"ws\")\n return a.href\n } else {\n return url\n }\n}\n", "import Connection from \"./connection\"\nimport ConnectionMonitor from \"./connection_monitor\"\nimport Consumer, { createWebSocketURL } from \"./consumer\"\nimport INTERNAL from \"./internal\"\nimport Subscription from \"./subscription\"\nimport Subscriptions from \"./subscriptions\"\nimport SubscriptionGuarantor from \"./subscription_guarantor\"\nimport adapters from \"./adapters\"\nimport logger from \"./logger\"\n\nexport {\n Connection,\n ConnectionMonitor,\n Consumer,\n INTERNAL,\n Subscription,\n Subscriptions,\n SubscriptionGuarantor,\n adapters,\n createWebSocketURL,\n logger,\n}\n\nexport function createConsumer(url = getConfig(\"url\") || INTERNAL.default_mount_path) {\n return new Consumer(url)\n}\n\nexport function getConfig(name) {\n const element = document.head.querySelector(`meta[name='action-cable-${name}']`)\n if (element) {\n return element.getAttribute(\"content\")\n }\n}\n", "/**\n * Rangy, a cross-browser JavaScript range and selection library\n * https://github.com/timdown/rangy\n *\n * Copyright 2022, Tim Down\n * Licensed under the MIT license.\n * Version: 1.3.1\n * Build date: 17 August 2022\n */\n\n(function(factory, root) {\n if (typeof define == \"function\" && define.amd) {\n // AMD. Register as an anonymous module.\n define(factory);\n } else if (typeof module != \"undefined\" && typeof exports == \"object\") {\n // Node/CommonJS style\n module.exports = factory();\n } else {\n // No AMD or CommonJS support so we place Rangy in (probably) the global variable\n root.rangy = factory();\n }\n})(function() {\n\n var OBJECT = \"object\", FUNCTION = \"function\", UNDEFINED = \"undefined\";\n\n // Minimal set of properties required for DOM Level 2 Range compliance. Comparison constants such as START_TO_START\n // are omitted because ranges in KHTML do not have them but otherwise work perfectly well. See issue 113.\n var domRangeProperties = [\"startContainer\", \"startOffset\", \"endContainer\", \"endOffset\", \"collapsed\",\n \"commonAncestorContainer\"];\n\n // Minimal set of methods required for DOM Level 2 Range compliance\n var domRangeMethods = [\"setStart\", \"setStartBefore\", \"setStartAfter\", \"setEnd\", \"setEndBefore\",\n \"setEndAfter\", \"collapse\", \"selectNode\", \"selectNodeContents\", \"compareBoundaryPoints\", \"deleteContents\",\n \"extractContents\", \"cloneContents\", \"insertNode\", \"surroundContents\", \"cloneRange\", \"toString\", \"detach\"];\n\n var textRangeProperties = [\"boundingHeight\", \"boundingLeft\", \"boundingTop\", \"boundingWidth\", \"htmlText\", \"text\"];\n\n // Subset of TextRange's full set of methods that we're interested in\n var textRangeMethods = [\"collapse\", \"compareEndPoints\", \"duplicate\", \"moveToElementText\", \"parentElement\", \"select\",\n \"setEndPoint\", \"getBoundingClientRect\"];\n\n /*----------------------------------------------------------------------------------------------------------------*/\n\n // Trio of functions taken from Peter Michaux's article:\n // http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting\n function isHostMethod(o, p) {\n var t = typeof o[p];\n return t == FUNCTION || (!!(t == OBJECT && o[p])) || t == \"unknown\";\n }\n\n function isHostObject(o, p) {\n return !!(typeof o[p] == OBJECT && o[p]);\n }\n\n function isHostProperty(o, p) {\n return typeof o[p] != UNDEFINED;\n }\n\n // Creates a convenience function to save verbose repeated calls to tests functions\n function createMultiplePropertyTest(testFunc) {\n return function(o, props) {\n var i = props.length;\n while (i--) {\n if (!testFunc(o, props[i])) {\n return false;\n }\n }\n return true;\n };\n }\n\n // Next trio of functions are a convenience to save verbose repeated calls to previous two functions\n var areHostMethods = createMultiplePropertyTest(isHostMethod);\n var areHostObjects = createMultiplePropertyTest(isHostObject);\n var areHostProperties = createMultiplePropertyTest(isHostProperty);\n\n function isTextRange(range) {\n return range && areHostMethods(range, textRangeMethods) && areHostProperties(range, textRangeProperties);\n }\n\n function getBody(doc) {\n return isHostObject(doc, \"body\") ? doc.body : doc.getElementsByTagName(\"body\")[0];\n }\n\n var forEach = [].forEach ?\n function(arr, func) {\n arr.forEach(func);\n } :\n function(arr, func) {\n for (var i = 0, len = arr.length; i < len; ++i) {\n func(arr[i], i);\n }\n };\n\n var modules = {};\n\n var isBrowser = (typeof window != UNDEFINED && typeof document != UNDEFINED);\n\n var util = {\n isHostMethod: isHostMethod,\n isHostObject: isHostObject,\n isHostProperty: isHostProperty,\n areHostMethods: areHostMethods,\n areHostObjects: areHostObjects,\n areHostProperties: areHostProperties,\n isTextRange: isTextRange,\n getBody: getBody,\n forEach: forEach\n };\n\n var api = {\n version: \"1.3.1\",\n initialized: false,\n isBrowser: isBrowser,\n supported: true,\n util: util,\n features: {},\n modules: modules,\n config: {\n alertOnFail: false,\n alertOnWarn: false,\n preferTextRange: false,\n autoInitialize: (typeof rangyAutoInitialize == UNDEFINED) ? true : rangyAutoInitialize\n }\n };\n\n function consoleLog(msg) {\n if (typeof console != UNDEFINED && isHostMethod(console, \"log\")) {\n console.log(msg);\n }\n }\n\n function alertOrLog(msg, shouldAlert) {\n if (isBrowser && shouldAlert) {\n alert(msg);\n } else {\n consoleLog(msg);\n }\n }\n\n function fail(reason) {\n api.initialized = true;\n api.supported = false;\n alertOrLog(\"Rangy is not supported in this environment. Reason: \" + reason, api.config.alertOnFail);\n }\n\n api.fail = fail;\n\n function warn(msg) {\n alertOrLog(\"Rangy warning: \" + msg, api.config.alertOnWarn);\n }\n\n api.warn = warn;\n\n // Add utility extend() method\n var extend;\n if ({}.hasOwnProperty) {\n util.extend = extend = function(obj, props, deep) {\n var o, p;\n for (var i in props) {\n if (props.hasOwnProperty(i)) {\n o = obj[i];\n p = props[i];\n if (deep && o !== null && typeof o == \"object\" && p !== null && typeof p == \"object\") {\n extend(o, p, true);\n }\n obj[i] = p;\n }\n }\n // Special case for toString, which does not show up in for...in loops in IE <= 8\n if (props.hasOwnProperty(\"toString\")) {\n obj.toString = props.toString;\n }\n return obj;\n };\n\n util.createOptions = function(optionsParam, defaults) {\n var options = {};\n extend(options, defaults);\n if (optionsParam) {\n extend(options, optionsParam);\n }\n return options;\n };\n } else {\n fail(\"hasOwnProperty not supported\");\n }\n\n // Test whether we're in a browser and bail out if not\n if (!isBrowser) {\n fail(\"Rangy can only run in a browser\");\n }\n\n // Test whether Array.prototype.slice can be relied on for NodeLists and use an alternative toArray() if not\n (function() {\n var toArray;\n\n if (isBrowser) {\n var el = document.createElement(\"div\");\n el.appendChild(document.createElement(\"span\"));\n var slice = [].slice;\n try {\n if (slice.call(el.childNodes, 0)[0].nodeType == 1) {\n toArray = function(arrayLike) {\n return slice.call(arrayLike, 0);\n };\n }\n } catch (e) {}\n }\n\n if (!toArray) {\n toArray = function(arrayLike) {\n var arr = [];\n for (var i = 0, len = arrayLike.length; i < len; ++i) {\n arr[i] = arrayLike[i];\n }\n return arr;\n };\n }\n\n util.toArray = toArray;\n })();\n\n // Very simple event handler wrapper function that doesn't attempt to solve issues such as \"this\" handling or\n // normalization of event properties because we don't need this.\n var addListener;\n if (isBrowser) {\n if (isHostMethod(document, \"addEventListener\")) {\n addListener = function(obj, eventType, listener) {\n obj.addEventListener(eventType, listener, false);\n };\n } else if (isHostMethod(document, \"attachEvent\")) {\n addListener = function(obj, eventType, listener) {\n obj.attachEvent(\"on\" + eventType, listener);\n };\n } else {\n fail(\"Document does not have required addEventListener or attachEvent method\");\n }\n\n util.addListener = addListener;\n }\n\n var initListeners = [];\n\n function getErrorDesc(ex) {\n return ex.message || ex.description || String(ex);\n }\n\n // Initialization\n function init() {\n if (!isBrowser || api.initialized) {\n return;\n }\n var testRange;\n var implementsDomRange = false, implementsTextRange = false;\n\n // First, perform basic feature tests\n\n if (isHostMethod(document, \"createRange\")) {\n testRange = document.createRange();\n if (areHostMethods(testRange, domRangeMethods) && areHostProperties(testRange, domRangeProperties)) {\n implementsDomRange = true;\n }\n }\n\n var body = getBody(document);\n if (!body || body.nodeName.toLowerCase() != \"body\") {\n fail(\"No body element found\");\n return;\n }\n\n if (body && isHostMethod(body, \"createTextRange\")) {\n testRange = body.createTextRange();\n if (isTextRange(testRange)) {\n implementsTextRange = true;\n }\n }\n\n if (!implementsDomRange && !implementsTextRange) {\n fail(\"Neither Range nor TextRange are available\");\n return;\n }\n\n api.initialized = true;\n api.features = {\n implementsDomRange: implementsDomRange,\n implementsTextRange: implementsTextRange\n };\n\n // Initialize modules\n var module, errorMessage;\n for (var moduleName in modules) {\n if ( (module = modules[moduleName]) instanceof Module ) {\n module.init(module, api);\n }\n }\n\n // Call init listeners\n for (var i = 0, len = initListeners.length; i < len; ++i) {\n try {\n initListeners[i](api);\n } catch (ex) {\n errorMessage = \"Rangy init listener threw an exception. Continuing. Detail: \" + getErrorDesc(ex);\n consoleLog(errorMessage);\n }\n }\n }\n\n function deprecationNotice(deprecated, replacement, module) {\n if (module) {\n deprecated += \" in module \" + module.name;\n }\n api.warn(\"DEPRECATED: \" + deprecated + \" is deprecated. Please use \" +\n replacement + \" instead.\");\n }\n\n function createAliasForDeprecatedMethod(owner, deprecated, replacement, module) {\n owner[deprecated] = function() {\n deprecationNotice(deprecated, replacement, module);\n return owner[replacement].apply(owner, util.toArray(arguments));\n };\n }\n\n util.deprecationNotice = deprecationNotice;\n util.createAliasForDeprecatedMethod = createAliasForDeprecatedMethod;\n\n // Allow external scripts to initialize this library in case it's loaded after the document has loaded\n api.init = init;\n\n // Execute listener immediately if already initialized\n api.addInitListener = function(listener) {\n if (api.initialized) {\n listener(api);\n } else {\n initListeners.push(listener);\n }\n };\n\n var shimListeners = [];\n\n api.addShimListener = function(listener) {\n shimListeners.push(listener);\n };\n\n function shim(win) {\n win = win || window;\n init();\n\n // Notify listeners\n for (var i = 0, len = shimListeners.length; i < len; ++i) {\n shimListeners[i](win);\n }\n }\n\n if (isBrowser) {\n api.shim = api.createMissingNativeApi = shim;\n createAliasForDeprecatedMethod(api, \"createMissingNativeApi\", \"shim\");\n }\n\n function Module(name, dependencies, initializer) {\n this.name = name;\n this.dependencies = dependencies;\n this.initialized = false;\n this.supported = false;\n this.initializer = initializer;\n }\n\n Module.prototype = {\n init: function() {\n var requiredModuleNames = this.dependencies || [];\n for (var i = 0, len = requiredModuleNames.length, requiredModule, moduleName; i < len; ++i) {\n moduleName = requiredModuleNames[i];\n\n requiredModule = modules[moduleName];\n if (!requiredModule || !(requiredModule instanceof Module)) {\n throw new Error(\"required module '\" + moduleName + \"' not found\");\n }\n\n requiredModule.init();\n\n if (!requiredModule.supported) {\n throw new Error(\"required module '\" + moduleName + \"' not supported\");\n }\n }\n\n // Now run initializer\n this.initializer(this);\n },\n\n fail: function(reason) {\n this.initialized = true;\n this.supported = false;\n throw new Error(reason);\n },\n\n warn: function(msg) {\n api.warn(\"Module \" + this.name + \": \" + msg);\n },\n\n deprecationNotice: function(deprecated, replacement) {\n api.warn(\"DEPRECATED: \" + deprecated + \" in module \" + this.name + \" is deprecated. Please use \" +\n replacement + \" instead\");\n },\n\n createError: function(msg) {\n return new Error(\"Error in Rangy \" + this.name + \" module: \" + msg);\n }\n };\n\n function createModule(name, dependencies, initFunc) {\n var newModule = new Module(name, dependencies, function(module) {\n if (!module.initialized) {\n module.initialized = true;\n try {\n initFunc(api, module);\n module.supported = true;\n } catch (ex) {\n var errorMessage = \"Module '\" + name + \"' failed to load: \" + getErrorDesc(ex);\n consoleLog(errorMessage);\n if (ex.stack) {\n consoleLog(ex.stack);\n }\n }\n }\n });\n modules[name] = newModule;\n return newModule;\n }\n\n api.createModule = function(name) {\n // Allow 2 or 3 arguments (second argument is an optional array of dependencies)\n var initFunc, dependencies;\n if (arguments.length == 2) {\n initFunc = arguments[1];\n dependencies = [];\n } else {\n initFunc = arguments[2];\n dependencies = arguments[1];\n }\n\n var module = createModule(name, dependencies, initFunc);\n\n // Initialize the module immediately if the core is already initialized\n if (api.initialized && api.supported) {\n module.init();\n }\n };\n\n api.createCoreModule = function(name, dependencies, initFunc) {\n createModule(name, dependencies, initFunc);\n };\n\n /*----------------------------------------------------------------------------------------------------------------*/\n\n // Ensure rangy.rangePrototype and rangy.selectionPrototype are available immediately\n\n function RangePrototype() {}\n api.RangePrototype = RangePrototype;\n api.rangePrototype = new RangePrototype();\n\n function SelectionPrototype() {}\n api.selectionPrototype = new SelectionPrototype();\n\n /*----------------------------------------------------------------------------------------------------------------*/\n\n // DOM utility methods used by Rangy\n api.createCoreModule(\"DomUtil\", [], function(api, module) {\n var UNDEF = \"undefined\";\n var util = api.util;\n var getBody = util.getBody;\n\n // Perform feature tests\n if (!util.areHostMethods(document, [\"createDocumentFragment\", \"createElement\", \"createTextNode\"])) {\n module.fail(\"document missing a Node creation method\");\n }\n\n if (!util.isHostMethod(document, \"getElementsByTagName\")) {\n module.fail(\"document missing getElementsByTagName method\");\n }\n\n var el = document.createElement(\"div\");\n if (!util.areHostMethods(el, [\"insertBefore\", \"appendChild\", \"cloneNode\"] ||\n !util.areHostObjects(el, [\"previousSibling\", \"nextSibling\", \"childNodes\", \"parentNode\"]))) {\n module.fail(\"Incomplete Element implementation\");\n }\n\n // innerHTML is required for Range's createContextualFragment method\n if (!util.isHostProperty(el, \"innerHTML\")) {\n module.fail(\"Element is missing innerHTML property\");\n }\n\n var textNode = document.createTextNode(\"test\");\n if (!util.areHostMethods(textNode, [\"splitText\", \"deleteData\", \"insertData\", \"appendData\", \"cloneNode\"] ||\n !util.areHostObjects(el, [\"previousSibling\", \"nextSibling\", \"childNodes\", \"parentNode\"]) ||\n !util.areHostProperties(textNode, [\"data\"]))) {\n module.fail(\"Incomplete Text Node implementation\");\n }\n\n /*----------------------------------------------------------------------------------------------------------------*/\n\n // Removed use of indexOf because of a bizarre bug in Opera that is thrown in one of the Acid3 tests. I haven't been\n // able to replicate it outside of the test. The bug is that indexOf returns -1 when called on an Array that\n // contains just the document as a single element and the value searched for is the document.\n var arrayContains = /*Array.prototype.indexOf ?\n function(arr, val) {\n return arr.indexOf(val) > -1;\n }:*/\n\n function(arr, val) {\n var i = arr.length;\n while (i--) {\n if (arr[i] === val) {\n return true;\n }\n }\n return false;\n };\n\n // Opera 11 puts HTML elements in the null namespace, it seems, and IE 7 has undefined namespaceURI\n function isHtmlNamespace(node) {\n var ns;\n return typeof node.namespaceURI == UNDEF || ((ns = node.namespaceURI) === null || ns == \"http://www.w3.org/1999/xhtml\");\n }\n\n function parentElement(node) {\n var parent = node.parentNode;\n return (parent.nodeType == 1) ? parent : null;\n }\n\n function getNodeIndex(node) {\n var i = 0;\n while( (node = node.previousSibling) ) {\n ++i;\n }\n return i;\n }\n\n function getNodeLength(node) {\n switch (node.nodeType) {\n case 7:\n case 10:\n return 0;\n case 3:\n case 8:\n return node.length;\n default:\n return node.childNodes.length;\n }\n }\n\n function getCommonAncestor(node1, node2) {\n var ancestors = [], n;\n for (n = node1; n; n = n.parentNode) {\n ancestors.push(n);\n }\n\n for (n = node2; n; n = n.parentNode) {\n if (arrayContains(ancestors, n)) {\n return n;\n }\n }\n\n return null;\n }\n\n function isAncestorOf(ancestor, descendant, selfIsAncestor) {\n var n = selfIsAncestor ? descendant : descendant.parentNode;\n while (n) {\n if (n === ancestor) {\n return true;\n } else {\n n = n.parentNode;\n }\n }\n return false;\n }\n\n function isOrIsAncestorOf(ancestor, descendant) {\n return isAncestorOf(ancestor, descendant, true);\n }\n\n function getClosestAncestorIn(node, ancestor, selfIsAncestor) {\n var p, n = selfIsAncestor ? node : node.parentNode;\n while (n) {\n p = n.parentNode;\n if (p === ancestor) {\n return n;\n }\n n = p;\n }\n return null;\n }\n\n function isCharacterDataNode(node) {\n var t = node.nodeType;\n return t == 3 || t == 4 || t == 8 ; // Text, CDataSection or Comment\n }\n\n function isTextOrCommentNode(node) {\n if (!node) {\n return false;\n }\n var t = node.nodeType;\n return t == 3 || t == 8 ; // Text or Comment\n }\n\n function insertAfter(node, precedingNode) {\n var nextNode = precedingNode.nextSibling, parent = precedingNode.parentNode;\n if (nextNode) {\n parent.insertBefore(node, nextNode);\n } else {\n parent.appendChild(node);\n }\n return node;\n }\n\n // Note that we cannot use splitText() because it is bugridden in IE 9.\n function splitDataNode(node, index, positionsToPreserve) {\n var newNode = node.cloneNode(false);\n newNode.deleteData(0, index);\n node.deleteData(index, node.length - index);\n insertAfter(newNode, node);\n\n // Preserve positions\n if (positionsToPreserve) {\n for (var i = 0, position; position = positionsToPreserve[i++]; ) {\n // Handle case where position was inside the portion of node after the split point\n if (position.node == node && position.offset > index) {\n position.node = newNode;\n position.offset -= index;\n }\n // Handle the case where the position is a node offset within node's parent\n else if (position.node == node.parentNode && position.offset > getNodeIndex(node)) {\n ++position.offset;\n }\n }\n }\n return newNode;\n }\n\n function getDocument(node) {\n if (node.nodeType == 9) {\n return node;\n } else if (typeof node.ownerDocument != UNDEF) {\n return node.ownerDocument;\n } else if (typeof node.document != UNDEF) {\n return node.document;\n } else if (node.parentNode) {\n return getDocument(node.parentNode);\n } else {\n throw module.createError(\"getDocument: no document found for node\");\n }\n }\n\n function getWindow(node) {\n var doc = getDocument(node);\n if (typeof doc.defaultView != UNDEF) {\n return doc.defaultView;\n } else if (typeof doc.parentWindow != UNDEF) {\n return doc.parentWindow;\n } else {\n throw module.createError(\"Cannot get a window object for node\");\n }\n }\n\n function getIframeDocument(iframeEl) {\n if (typeof iframeEl.contentDocument != UNDEF) {\n return iframeEl.contentDocument;\n } else if (typeof iframeEl.contentWindow != UNDEF) {\n return iframeEl.contentWindow.document;\n } else {\n throw module.createError(\"getIframeDocument: No Document object found for iframe element\");\n }\n }\n\n function getIframeWindow(iframeEl) {\n if (typeof iframeEl.contentWindow != UNDEF) {\n return iframeEl.contentWindow;\n } else if (typeof iframeEl.contentDocument != UNDEF) {\n return iframeEl.contentDocument.defaultView;\n } else {\n throw module.createError(\"getIframeWindow: No Window object found for iframe element\");\n }\n }\n\n // This looks bad. Is it worth it?\n function isWindow(obj) {\n return obj && util.isHostMethod(obj, \"setTimeout\") && util.isHostObject(obj, \"document\");\n }\n\n function getContentDocument(obj, module, methodName) {\n var doc;\n\n if (!obj) {\n doc = document;\n }\n\n // Test if a DOM node has been passed and obtain a document object for it if so\n else if (util.isHostProperty(obj, \"nodeType\")) {\n doc = (obj.nodeType == 1 && obj.tagName.toLowerCase() == \"iframe\") ?\n getIframeDocument(obj) : getDocument(obj);\n }\n\n // Test if the doc parameter appears to be a Window object\n else if (isWindow(obj)) {\n doc = obj.document;\n }\n\n if (!doc) {\n throw module.createError(methodName + \"(): Parameter must be a Window object or DOM node\");\n }\n\n return doc;\n }\n\n function getRootContainer(node) {\n var parent;\n while ( (parent = node.parentNode) ) {\n node = parent;\n }\n return node;\n }\n\n function comparePoints(nodeA, offsetA, nodeB, offsetB) {\n // See http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Comparing\n var nodeC, root, childA, childB, n;\n if (nodeA == nodeB) {\n // Case 1: nodes are the same\n return offsetA === offsetB ? 0 : (offsetA < offsetB) ? -1 : 1;\n } else if ( (nodeC = getClosestAncestorIn(nodeB, nodeA, true)) ) {\n // Case 2: node C (container B or an ancestor) is a child node of A\n return offsetA <= getNodeIndex(nodeC) ? -1 : 1;\n } else if ( (nodeC = getClosestAncestorIn(nodeA, nodeB, true)) ) {\n // Case 3: node C (container A or an ancestor) is a child node of B\n return getNodeIndex(nodeC) < offsetB ? -1 : 1;\n } else {\n root = getCommonAncestor(nodeA, nodeB);\n if (!root) {\n throw new Error(\"comparePoints error: nodes have no common ancestor\");\n }\n\n // Case 4: containers are siblings or descendants of siblings\n childA = (nodeA === root) ? root : getClosestAncestorIn(nodeA, root, true);\n childB = (nodeB === root) ? root : getClosestAncestorIn(nodeB, root, true);\n\n if (childA === childB) {\n // This shouldn't be possible\n throw module.createError(\"comparePoints got to case 4 and childA and childB are the same!\");\n } else {\n n = root.firstChild;\n while (n) {\n if (n === childA) {\n return -1;\n } else if (n === childB) {\n return 1;\n }\n n = n.nextSibling;\n }\n }\n }\n }\n\n /*----------------------------------------------------------------------------------------------------------------*/\n\n // Test for IE's crash (IE 6/7) or exception (IE >= 8) when a reference to garbage-collected text node is queried\n var crashyTextNodes = false;\n\n function isBrokenNode(node) {\n var n;\n try {\n n = node.parentNode;\n return false;\n } catch (e) {\n return true;\n }\n }\n\n (function() {\n var el = document.createElement(\"b\");\n el.innerHTML = \"1\";\n var textNode = el.firstChild;\n el.innerHTML = \"
\";\n crashyTextNodes = isBrokenNode(textNode);\n\n api.features.crashyTextNodes = crashyTextNodes;\n })();\n\n /*----------------------------------------------------------------------------------------------------------------*/\n\n function inspectNode(node) {\n if (!node) {\n return \"[No node]\";\n }\n if (crashyTextNodes && isBrokenNode(node)) {\n return \"[Broken node]\";\n }\n if (isCharacterDataNode(node)) {\n return '\"' + node.data + '\"';\n }\n if (node.nodeType == 1) {\n var idAttr = node.id ? ' id=\"' + node.id + '\"' : \"\";\n return \"<\" + node.nodeName + idAttr + \">[index:\" + getNodeIndex(node) + \",length:\" + node.childNodes.length + \"][\" + (node.innerHTML || \"[innerHTML not supported]\").slice(0, 25) + \"]\";\n }\n return node.nodeName;\n }\n\n function fragmentFromNodeChildren(node) {\n var fragment = getDocument(node).createDocumentFragment(), child;\n while ( (child = node.firstChild) ) {\n fragment.appendChild(child);\n }\n return fragment;\n }\n\n var getComputedStyleProperty;\n if (typeof window.getComputedStyle != UNDEF) {\n getComputedStyleProperty = function(el, propName) {\n return getWindow(el).getComputedStyle(el, null)[propName];\n };\n } else if (typeof document.documentElement.currentStyle != UNDEF) {\n getComputedStyleProperty = function(el, propName) {\n return el.currentStyle ? el.currentStyle[propName] : \"\";\n };\n } else {\n module.fail(\"No means of obtaining computed style properties found\");\n }\n\n function createTestElement(doc, html, contentEditable) {\n var body = getBody(doc);\n var el = doc.createElement(\"div\");\n el.contentEditable = \"\" + !!contentEditable;\n if (html) {\n el.innerHTML = html;\n }\n\n // Insert the test element at the start of the body to prevent scrolling to the bottom in iOS (issue #292)\n var bodyFirstChild = body.firstChild;\n if (bodyFirstChild) {\n body.insertBefore(el, bodyFirstChild);\n } else {\n body.appendChild(el);\n }\n\n return el;\n }\n\n function removeNode(node) {\n return node.parentNode.removeChild(node);\n }\n\n function NodeIterator(root) {\n this.root = root;\n this._next = root;\n }\n\n NodeIterator.prototype = {\n _current: null,\n\n hasNext: function() {\n return !!this._next;\n },\n\n next: function() {\n var n = this._current = this._next;\n var child, next;\n if (this._current) {\n child = n.firstChild;\n if (child) {\n this._next = child;\n } else {\n next = null;\n while ((n !== this.root) && !(next = n.nextSibling)) {\n n = n.parentNode;\n }\n this._next = next;\n }\n }\n return this._current;\n },\n\n detach: function() {\n this._current = this._next = this.root = null;\n }\n };\n\n function createIterator(root) {\n return new NodeIterator(root);\n }\n\n function DomPosition(node, offset) {\n this.node = node;\n this.offset = offset;\n }\n\n DomPosition.prototype = {\n equals: function(pos) {\n return !!pos && this.node === pos.node && this.offset == pos.offset;\n },\n\n inspect: function() {\n return \"[DomPosition(\" + inspectNode(this.node) + \":\" + this.offset + \")]\";\n },\n\n toString: function() {\n return this.inspect();\n }\n };\n\n function DOMException(codeName) {\n this.code = this[codeName];\n this.codeName = codeName;\n this.message = \"DOMException: \" + this.codeName;\n }\n\n DOMException.prototype = {\n INDEX_SIZE_ERR: 1,\n HIERARCHY_REQUEST_ERR: 3,\n WRONG_DOCUMENT_ERR: 4,\n NO_MODIFICATION_ALLOWED_ERR: 7,\n NOT_FOUND_ERR: 8,\n NOT_SUPPORTED_ERR: 9,\n INVALID_STATE_ERR: 11,\n INVALID_NODE_TYPE_ERR: 24\n };\n\n DOMException.prototype.toString = function() {\n return this.message;\n };\n\n api.dom = {\n arrayContains: arrayContains,\n isHtmlNamespace: isHtmlNamespace,\n parentElement: parentElement,\n getNodeIndex: getNodeIndex,\n getNodeLength: getNodeLength,\n getCommonAncestor: getCommonAncestor,\n isAncestorOf: isAncestorOf,\n isOrIsAncestorOf: isOrIsAncestorOf,\n getClosestAncestorIn: getClosestAncestorIn,\n isCharacterDataNode: isCharacterDataNode,\n isTextOrCommentNode: isTextOrCommentNode,\n insertAfter: insertAfter,\n splitDataNode: splitDataNode,\n getDocument: getDocument,\n getWindow: getWindow,\n getIframeWindow: getIframeWindow,\n getIframeDocument: getIframeDocument,\n getBody: getBody,\n isWindow: isWindow,\n getContentDocument: getContentDocument,\n getRootContainer: getRootContainer,\n comparePoints: comparePoints,\n isBrokenNode: isBrokenNode,\n inspectNode: inspectNode,\n getComputedStyleProperty: getComputedStyleProperty,\n createTestElement: createTestElement,\n removeNode: removeNode,\n fragmentFromNodeChildren: fragmentFromNodeChildren,\n createIterator: createIterator,\n DomPosition: DomPosition\n };\n\n api.DOMException = DOMException;\n });\n\n /*----------------------------------------------------------------------------------------------------------------*/\n\n // Pure JavaScript implementation of DOM Range\n api.createCoreModule(\"DomRange\", [\"DomUtil\"], function(api, module) {\n var dom = api.dom;\n var util = api.util;\n var DomPosition = dom.DomPosition;\n var DOMException = api.DOMException;\n\n var isCharacterDataNode = dom.isCharacterDataNode;\n var getNodeIndex = dom.getNodeIndex;\n var isOrIsAncestorOf = dom.isOrIsAncestorOf;\n var getDocument = dom.getDocument;\n var comparePoints = dom.comparePoints;\n var splitDataNode = dom.splitDataNode;\n var getClosestAncestorIn = dom.getClosestAncestorIn;\n var getNodeLength = dom.getNodeLength;\n var arrayContains = dom.arrayContains;\n var getRootContainer = dom.getRootContainer;\n var crashyTextNodes = api.features.crashyTextNodes;\n\n var removeNode = dom.removeNode;\n\n /*----------------------------------------------------------------------------------------------------------------*/\n\n // Utility functions\n\n function isNonTextPartiallySelected(node, range) {\n return (node.nodeType != 3) &&\n (isOrIsAncestorOf(node, range.startContainer) || isOrIsAncestorOf(node, range.endContainer));\n }\n\n function getRangeDocument(range) {\n return range.document || getDocument(range.startContainer);\n }\n\n function getRangeRoot(range) {\n return getRootContainer(range.startContainer);\n }\n\n function getBoundaryBeforeNode(node) {\n return new DomPosition(node.parentNode, getNodeIndex(node));\n }\n\n function getBoundaryAfterNode(node) {\n return new DomPosition(node.parentNode, getNodeIndex(node) + 1);\n }\n\n function insertNodeAtPosition(node, n, o) {\n var firstNodeInserted = node.nodeType == 11 ? node.firstChild : node;\n if (isCharacterDataNode(n)) {\n if (o == n.length) {\n dom.insertAfter(node, n);\n } else {\n n.parentNode.insertBefore(node, o == 0 ? n : splitDataNode(n, o));\n }\n } else if (o >= n.childNodes.length) {\n n.appendChild(node);\n } else {\n n.insertBefore(node, n.childNodes[o]);\n }\n return firstNodeInserted;\n }\n\n function rangesIntersect(rangeA, rangeB, touchingIsIntersecting) {\n assertRangeValid(rangeA);\n assertRangeValid(rangeB);\n\n if (getRangeDocument(rangeB) != getRangeDocument(rangeA)) {\n throw new DOMException(\"WRONG_DOCUMENT_ERR\");\n }\n\n var startComparison = comparePoints(rangeA.startContainer, rangeA.startOffset, rangeB.endContainer, rangeB.endOffset),\n endComparison = comparePoints(rangeA.endContainer, rangeA.endOffset, rangeB.startContainer, rangeB.startOffset);\n\n return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0;\n }\n\n function cloneSubtree(iterator) {\n var partiallySelected;\n for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) {\n partiallySelected = iterator.isPartiallySelectedSubtree();\n node = node.cloneNode(!partiallySelected);\n if (partiallySelected) {\n subIterator = iterator.getSubtreeIterator();\n node.appendChild(cloneSubtree(subIterator));\n subIterator.detach();\n }\n\n if (node.nodeType == 10) { // DocumentType\n throw new DOMException(\"HIERARCHY_REQUEST_ERR\");\n }\n frag.appendChild(node);\n }\n return frag;\n }\n\n function iterateSubtree(rangeIterator, func, iteratorState) {\n var it, n;\n iteratorState = iteratorState || { stop: false };\n for (var node, subRangeIterator; node = rangeIterator.next(); ) {\n if (rangeIterator.isPartiallySelectedSubtree()) {\n if (func(node) === false) {\n iteratorState.stop = true;\n return;\n } else {\n // The node is partially selected by the Range, so we can use a new RangeIterator on the portion of\n // the node selected by the Range.\n subRangeIterator = rangeIterator.getSubtreeIterator();\n iterateSubtree(subRangeIterator, func, iteratorState);\n subRangeIterator.detach();\n if (iteratorState.stop) {\n return;\n }\n }\n } else {\n // The whole node is selected, so we can use efficient DOM iteration to iterate over the node and its\n // descendants\n it = dom.createIterator(node);\n while ( (n = it.next()) ) {\n if (func(n) === false) {\n iteratorState.stop = true;\n return;\n }\n }\n }\n }\n }\n\n function deleteSubtree(iterator) {\n var subIterator;\n while (iterator.next()) {\n if (iterator.isPartiallySelectedSubtree()) {\n subIterator = iterator.getSubtreeIterator();\n deleteSubtree(subIterator);\n subIterator.detach();\n } else {\n iterator.remove();\n }\n }\n }\n\n function extractSubtree(iterator) {\n for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) {\n\n if (iterator.isPartiallySelectedSubtree()) {\n node = node.cloneNode(false);\n subIterator = iterator.getSubtreeIterator();\n node.appendChild(extractSubtree(subIterator));\n subIterator.detach();\n } else {\n iterator.remove();\n }\n if (node.nodeType == 10) { // DocumentType\n throw new DOMException(\"HIERARCHY_REQUEST_ERR\");\n }\n frag.appendChild(node);\n }\n return frag;\n }\n\n function getNodesInRange(range, nodeTypes, filter) {\n var filterNodeTypes = !!(nodeTypes && nodeTypes.length), regex;\n var filterExists = !!filter;\n if (filterNodeTypes) {\n regex = new RegExp(\"^(\" + nodeTypes.join(\"|\") + \")$\");\n }\n\n var nodes = [];\n iterateSubtree(new RangeIterator(range, false), function(node) {\n if (filterNodeTypes && !regex.test(node.nodeType)) {\n return;\n }\n if (filterExists && !filter(node)) {\n return;\n }\n // Don't include a boundary container if it is a character data node and the range does not contain any\n // of its character data. See issue 190.\n var sc = range.startContainer;\n if (node == sc && isCharacterDataNode(sc) && range.startOffset == sc.length) {\n return;\n }\n\n var ec = range.endContainer;\n if (node == ec && isCharacterDataNode(ec) && range.endOffset == 0) {\n return;\n }\n\n nodes.push(node);\n });\n return nodes;\n }\n\n function inspect(range) {\n var name = (typeof range.getName == \"undefined\") ? \"Range\" : range.getName();\n return \"[\" + name + \"(\" + dom.inspectNode(range.startContainer) + \":\" + range.startOffset + \", \" +\n dom.inspectNode(range.endContainer) + \":\" + range.endOffset + \")]\";\n }\n\n /*----------------------------------------------------------------------------------------------------------------*/\n\n // RangeIterator code partially borrows from IERange by Tim Ryan (http://github.com/timcameronryan/IERange)\n\n function RangeIterator(range, clonePartiallySelectedTextNodes) {\n this.range = range;\n this.clonePartiallySelectedTextNodes = clonePartiallySelectedTextNodes;\n\n\n if (!range.collapsed) {\n this.sc = range.startContainer;\n this.so = range.startOffset;\n this.ec = range.endContainer;\n this.eo = range.endOffset;\n var root = range.commonAncestorContainer;\n\n if (this.sc === this.ec && isCharacterDataNode(this.sc)) {\n this.isSingleCharacterDataNode = true;\n this._first = this._last = this._next = this.sc;\n } else {\n this._first = this._next = (this.sc === root && !isCharacterDataNode(this.sc)) ?\n this.sc.childNodes[this.so] : getClosestAncestorIn(this.sc, root, true);\n this._last = (this.ec === root && !isCharacterDataNode(this.ec)) ?\n this.ec.childNodes[this.eo - 1] : getClosestAncestorIn(this.ec, root, true);\n }\n }\n }\n\n RangeIterator.prototype = {\n _current: null,\n _next: null,\n _first: null,\n _last: null,\n isSingleCharacterDataNode: false,\n\n reset: function() {\n this._current = null;\n this._next = this._first;\n },\n\n hasNext: function() {\n return !!this._next;\n },\n\n next: function() {\n // Move to next node\n var current = this._current = this._next;\n if (current) {\n this._next = (current !== this._last) ? current.nextSibling : null;\n\n // Check for partially selected text nodes\n if (isCharacterDataNode(current) && this.clonePartiallySelectedTextNodes) {\n if (current === this.ec) {\n (current = current.cloneNode(true)).deleteData(this.eo, current.length - this.eo);\n }\n if (this._current === this.sc) {\n (current = current.cloneNode(true)).deleteData(0, this.so);\n }\n }\n }\n\n return current;\n },\n\n remove: function() {\n var current = this._current, start, end;\n\n if (isCharacterDataNode(current) && (current === this.sc || current === this.ec)) {\n start = (current === this.sc) ? this.so : 0;\n end = (current === this.ec) ? this.eo : current.length;\n if (start != end) {\n current.deleteData(start, end - start);\n }\n } else {\n if (current.parentNode) {\n removeNode(current);\n } else {\n }\n }\n },\n\n // Checks if the current node is partially selected\n isPartiallySelectedSubtree: function() {\n var current = this._current;\n return isNonTextPartiallySelected(current, this.range);\n },\n\n getSubtreeIterator: function() {\n var subRange;\n if (this.isSingleCharacterDataNode) {\n subRange = this.range.cloneRange();\n subRange.collapse(false);\n } else {\n subRange = new Range(getRangeDocument(this.range));\n var current = this._current;\n var startContainer = current, startOffset = 0, endContainer = current, endOffset = getNodeLength(current);\n\n if (isOrIsAncestorOf(current, this.sc)) {\n startContainer = this.sc;\n startOffset = this.so;\n }\n if (isOrIsAncestorOf(current, this.ec)) {\n endContainer = this.ec;\n endOffset = this.eo;\n }\n\n updateBoundaries(subRange, startContainer, startOffset, endContainer, endOffset);\n }\n return new RangeIterator(subRange, this.clonePartiallySelectedTextNodes);\n },\n\n detach: function() {\n this.range = this._current = this._next = this._first = this._last = this.sc = this.so = this.ec = this.eo = null;\n }\n };\n\n /*----------------------------------------------------------------------------------------------------------------*/\n\n var beforeAfterNodeTypes = [1, 3, 4, 5, 7, 8, 10];\n var rootContainerNodeTypes = [2, 9, 11];\n var readonlyNodeTypes = [5, 6, 10, 12];\n var insertableNodeTypes = [1, 3, 4, 5, 7, 8, 10, 11];\n var surroundNodeTypes = [1, 3, 4, 5, 7, 8];\n\n function createAncestorFinder(nodeTypes) {\n return function(node, selfIsAncestor) {\n var t, n = selfIsAncestor ? node : node.parentNode;\n while (n) {\n t = n.nodeType;\n if (arrayContains(nodeTypes, t)) {\n return n;\n }\n n = n.parentNode;\n }\n return null;\n };\n }\n\n var getDocumentOrFragmentContainer = createAncestorFinder( [9, 11] );\n var getReadonlyAncestor = createAncestorFinder(readonlyNodeTypes);\n var getDocTypeNotationEntityAncestor = createAncestorFinder( [6, 10, 12] );\n var getElementAncestor = createAncestorFinder( [1] );\n\n function assertNoDocTypeNotationEntityAncestor(node, allowSelf) {\n if (getDocTypeNotationEntityAncestor(node, allowSelf)) {\n throw new DOMException(\"INVALID_NODE_TYPE_ERR\");\n }\n }\n\n function assertValidNodeType(node, invalidTypes) {\n if (!arrayContains(invalidTypes, node.nodeType)) {\n throw new DOMException(\"INVALID_NODE_TYPE_ERR\");\n }\n }\n\n function assertValidOffset(node, offset) {\n if (offset < 0 || offset > (isCharacterDataNode(node) ? node.length : node.childNodes.length)) {\n throw new DOMException(\"INDEX_SIZE_ERR\");\n }\n }\n\n function assertSameDocumentOrFragment(node1, node2) {\n if (getDocumentOrFragmentContainer(node1, true) !== getDocumentOrFragmentContainer(node2, true)) {\n throw new DOMException(\"WRONG_DOCUMENT_ERR\");\n }\n }\n\n function assertNodeNotReadOnly(node) {\n if (getReadonlyAncestor(node, true)) {\n throw new DOMException(\"NO_MODIFICATION_ALLOWED_ERR\");\n }\n }\n\n function assertNode(node, codeName) {\n if (!node) {\n throw new DOMException(codeName);\n }\n }\n\n function isValidOffset(node, offset) {\n return offset <= (isCharacterDataNode(node) ? node.length : node.childNodes.length);\n }\n\n function isRangeValid(range) {\n return (!!range.startContainer && !!range.endContainer &&\n !(crashyTextNodes && (dom.isBrokenNode(range.startContainer) || dom.isBrokenNode(range.endContainer))) &&\n getRootContainer(range.startContainer) == getRootContainer(range.endContainer) &&\n isValidOffset(range.startContainer, range.startOffset) &&\n isValidOffset(range.endContainer, range.endOffset));\n }\n\n function assertRangeValid(range) {\n if (!isRangeValid(range)) {\n throw new Error(\"Range error: Range is not valid. This usually happens after DOM mutation. Range: (\" + range.inspect() + \")\");\n }\n }\n\n /*----------------------------------------------------------------------------------------------------------------*/\n\n // Test the browser's innerHTML support to decide how to implement createContextualFragment\n var styleEl = document.createElement(\"style\");\n var htmlParsingConforms = false;\n try {\n styleEl.innerHTML = \"x\";\n htmlParsingConforms = (styleEl.firstChild.nodeType == 3); // Pre-Blink Opera incorrectly creates an element node\n } catch (e) {\n // IE 6 and 7 throw\n }\n\n api.features.htmlParsingConforms = htmlParsingConforms;\n\n var createContextualFragment = htmlParsingConforms ?\n\n // Implementation as per HTML parsing spec, trusting in the browser's implementation of innerHTML. See\n // discussion and base code for this implementation at issue 67.\n // Spec: http://html5.org/specs/dom-parsing.html#extensions-to-the-range-interface\n // Thanks to Aleks Williams.\n function(fragmentStr) {\n // \"Let node the context object's start's node.\"\n var node = this.startContainer;\n var doc = getDocument(node);\n\n // \"If the context object's start's node is null, raise an INVALID_STATE_ERR\n // exception and abort these steps.\"\n if (!node) {\n throw new DOMException(\"INVALID_STATE_ERR\");\n }\n\n // \"Let element be as follows, depending on node's interface:\"\n // Document, Document Fragment: null\n var el = null;\n\n // \"Element: node\"\n if (node.nodeType == 1) {\n el = node;\n\n // \"Text, Comment: node's parentElement\"\n } else if (isCharacterDataNode(node)) {\n el = dom.parentElement(node);\n }\n\n // \"If either element is null or element's ownerDocument is an HTML document\n // and element's local name is \"html\" and element's namespace is the HTML\n // namespace\"\n if (el === null || (\n el.nodeName == \"HTML\" &&\n dom.isHtmlNamespace(getDocument(el).documentElement) &&\n dom.isHtmlNamespace(el)\n )) {\n\n // \"let element be a new Element with \"body\" as its local name and the HTML\n // namespace as its namespace.\"\"\n el = doc.createElement(\"body\");\n } else {\n el = el.cloneNode(false);\n }\n\n // \"If the node's document is an HTML document: Invoke the HTML fragment parsing algorithm.\"\n // \"If the node's document is an XML document: Invoke the XML fragment parsing algorithm.\"\n // \"In either case, the algorithm must be invoked with fragment as the input\n // and element as the context element.\"\n el.innerHTML = fragmentStr;\n\n // \"If this raises an exception, then abort these steps. Otherwise, let new\n // children be the nodes returned.\"\n\n // \"Let fragment be a new DocumentFragment.\"\n // \"Append all new children to fragment.\"\n // \"Return fragment.\"\n return dom.fragmentFromNodeChildren(el);\n } :\n\n // In this case, innerHTML cannot be trusted, so fall back to a simpler, non-conformant implementation that\n // previous versions of Rangy used (with the exception of using a body element rather than a div)\n function(fragmentStr) {\n var doc = getRangeDocument(this);\n var el = doc.createElement(\"body\");\n el.innerHTML = fragmentStr;\n\n return dom.fragmentFromNodeChildren(el);\n };\n\n function splitRangeBoundaries(range, positionsToPreserve) {\n assertRangeValid(range);\n\n var sc = range.startContainer, so = range.startOffset, ec = range.endContainer, eo = range.endOffset;\n var startEndSame = (sc === ec);\n\n if (isCharacterDataNode(ec) && eo > 0 && eo < ec.length) {\n splitDataNode(ec, eo, positionsToPreserve);\n }\n\n if (isCharacterDataNode(sc) && so > 0 && so < sc.length) {\n sc = splitDataNode(sc, so, positionsToPreserve);\n if (startEndSame) {\n eo -= so;\n ec = sc;\n } else if (ec == sc.parentNode && eo >= getNodeIndex(sc)) {\n eo++;\n }\n so = 0;\n }\n range.setStartAndEnd(sc, so, ec, eo);\n }\n\n function rangeToHtml(range) {\n assertRangeValid(range);\n var container = range.commonAncestorContainer.parentNode.cloneNode(false);\n container.appendChild( range.cloneContents() );\n return container.innerHTML;\n }\n\n /*----------------------------------------------------------------------------------------------------------------*/\n\n var rangeProperties = [\"startContainer\", \"startOffset\", \"endContainer\", \"endOffset\", \"collapsed\",\n \"commonAncestorContainer\"];\n\n var s2s = 0, s2e = 1, e2e = 2, e2s = 3;\n var n_b = 0, n_a = 1, n_b_a = 2, n_i = 3;\n\n util.extend(api.rangePrototype, {\n compareBoundaryPoints: function(how, range) {\n assertRangeValid(this);\n assertSameDocumentOrFragment(this.startContainer, range.startContainer);\n\n var nodeA, offsetA, nodeB, offsetB;\n var prefixA = (how == e2s || how == s2s) ? \"start\" : \"end\";\n var prefixB = (how == s2e || how == s2s) ? \"start\" : \"end\";\n nodeA = this[prefixA + \"Container\"];\n offsetA = this[prefixA + \"Offset\"];\n nodeB = range[prefixB + \"Container\"];\n offsetB = range[prefixB + \"Offset\"];\n return comparePoints(nodeA, offsetA, nodeB, offsetB);\n },\n\n insertNode: function(node) {\n assertRangeValid(this);\n assertValidNodeType(node, insertableNodeTypes);\n assertNodeNotReadOnly(this.startContainer);\n\n if (isOrIsAncestorOf(node, this.startContainer)) {\n throw new DOMException(\"HIERARCHY_REQUEST_ERR\");\n }\n\n // No check for whether the container of the start of the Range is of a type that does not allow\n // children of the type of node: the browser's DOM implementation should do this for us when we attempt\n // to add the node\n\n var firstNodeInserted = insertNodeAtPosition(node, this.startContainer, this.startOffset);\n this.setStartBefore(firstNodeInserted);\n },\n\n cloneContents: function() {\n assertRangeValid(this);\n\n var clone, frag;\n if (this.collapsed) {\n return getRangeDocument(this).createDocumentFragment();\n } else {\n if (this.startContainer === this.endContainer && isCharacterDataNode(this.startContainer)) {\n clone = this.startContainer.cloneNode(true);\n clone.data = clone.data.slice(this.startOffset, this.endOffset);\n frag = getRangeDocument(this).createDocumentFragment();\n frag.appendChild(clone);\n return frag;\n } else {\n var iterator = new RangeIterator(this, true);\n clone = cloneSubtree(iterator);\n iterator.detach();\n }\n return clone;\n }\n },\n\n canSurroundContents: function() {\n assertRangeValid(this);\n assertNodeNotReadOnly(this.startContainer);\n assertNodeNotReadOnly(this.endContainer);\n\n // Check if the contents can be surrounded. Specifically, this means whether the range partially selects\n // no non-text nodes.\n var iterator = new RangeIterator(this, true);\n var boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._first, this)) ||\n (iterator._last && isNonTextPartiallySelected(iterator._last, this)));\n iterator.detach();\n return !boundariesInvalid;\n },\n\n surroundContents: function(node) {\n assertValidNodeType(node, surroundNodeTypes);\n\n if (!this.canSurroundContents()) {\n throw new DOMException(\"INVALID_STATE_ERR\");\n }\n\n // Extract the contents\n var content = this.extractContents();\n\n // Clear the children of the node\n if (node.hasChildNodes()) {\n while (node.lastChild) {\n node.removeChild(node.lastChild);\n }\n }\n\n // Insert the new node and add the extracted contents\n insertNodeAtPosition(node, this.startContainer, this.startOffset);\n node.appendChild(content);\n\n this.selectNode(node);\n },\n\n cloneRange: function() {\n assertRangeValid(this);\n var range = new Range(getRangeDocument(this));\n var i = rangeProperties.length, prop;\n while (i--) {\n prop = rangeProperties[i];\n range[prop] = this[prop];\n }\n return range;\n },\n\n toString: function() {\n assertRangeValid(this);\n var sc = this.startContainer;\n if (sc === this.endContainer && isCharacterDataNode(sc)) {\n return (sc.nodeType == 3 || sc.nodeType == 4) ? sc.data.slice(this.startOffset, this.endOffset) : \"\";\n } else {\n var textParts = [], iterator = new RangeIterator(this, true);\n iterateSubtree(iterator, function(node) {\n // Accept only text or CDATA nodes, not comments\n if (node.nodeType == 3 || node.nodeType == 4) {\n textParts.push(node.data);\n }\n });\n iterator.detach();\n return textParts.join(\"\");\n }\n },\n\n // The methods below are all non-standard. The following batch were introduced by Mozilla but have since\n // been removed from Mozilla.\n\n compareNode: function(node) {\n assertRangeValid(this);\n\n var parent = node.parentNode;\n var nodeIndex = getNodeIndex(node);\n\n if (!parent) {\n throw new DOMException(\"NOT_FOUND_ERR\");\n }\n\n var startComparison = this.comparePoint(parent, nodeIndex),\n endComparison = this.comparePoint(parent, nodeIndex + 1);\n\n if (startComparison < 0) { // Node starts before\n return (endComparison > 0) ? n_b_a : n_b;\n } else {\n return (endComparison > 0) ? n_a : n_i;\n }\n },\n\n comparePoint: function(node, offset) {\n assertRangeValid(this);\n assertNode(node, \"HIERARCHY_REQUEST_ERR\");\n assertSameDocumentOrFragment(node, this.startContainer);\n\n if (comparePoints(node, offset, this.startContainer, this.startOffset) < 0) {\n return -1;\n } else if (comparePoints(node, offset, this.endContainer, this.endOffset) > 0) {\n return 1;\n }\n return 0;\n },\n\n createContextualFragment: createContextualFragment,\n\n toHtml: function() {\n return rangeToHtml(this);\n },\n\n // touchingIsIntersecting determines whether this method considers a node that borders a range intersects\n // with it (as in WebKit) or not (as in Gecko pre-1.9, and the default)\n intersectsNode: function(node, touchingIsIntersecting) {\n assertRangeValid(this);\n if (getRootContainer(node) != getRangeRoot(this)) {\n return false;\n }\n\n var parent = node.parentNode, offset = getNodeIndex(node);\n if (!parent) {\n return true;\n }\n\n var startComparison = comparePoints(parent, offset, this.endContainer, this.endOffset),\n endComparison = comparePoints(parent, offset + 1, this.startContainer, this.startOffset);\n\n return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0;\n },\n\n isPointInRange: function(node, offset) {\n assertRangeValid(this);\n assertNode(node, \"HIERARCHY_REQUEST_ERR\");\n assertSameDocumentOrFragment(node, this.startContainer);\n\n return (comparePoints(node, offset, this.startContainer, this.startOffset) >= 0) &&\n (comparePoints(node, offset, this.endContainer, this.endOffset) <= 0);\n },\n\n // The methods below are non-standard and invented by me.\n\n // Sharing a boundary start-to-end or end-to-start does not count as intersection.\n intersectsRange: function(range) {\n return rangesIntersect(this, range, false);\n },\n\n // Sharing a boundary start-to-end or end-to-start does count as intersection.\n intersectsOrTouchesRange: function(range) {\n return rangesIntersect(this, range, true);\n },\n\n intersection: function(range) {\n if (this.intersectsRange(range)) {\n var startComparison = comparePoints(this.startContainer, this.startOffset, range.startContainer, range.startOffset),\n endComparison = comparePoints(this.endContainer, this.endOffset, range.endContainer, range.endOffset);\n\n var intersectionRange = this.cloneRange();\n if (startComparison == -1) {\n intersectionRange.setStart(range.startContainer, range.startOffset);\n }\n if (endComparison == 1) {\n intersectionRange.setEnd(range.endContainer, range.endOffset);\n }\n return intersectionRange;\n }\n return null;\n },\n\n union: function(range) {\n if (this.intersectsOrTouchesRange(range)) {\n var unionRange = this.cloneRange();\n if (comparePoints(range.startContainer, range.startOffset, this.startContainer, this.startOffset) == -1) {\n unionRange.setStart(range.startContainer, range.startOffset);\n }\n if (comparePoints(range.endContainer, range.endOffset, this.endContainer, this.endOffset) == 1) {\n unionRange.setEnd(range.endContainer, range.endOffset);\n }\n return unionRange;\n } else {\n throw new DOMException(\"Ranges do not intersect\");\n }\n },\n\n containsNode: function(node, allowPartial) {\n if (allowPartial) {\n return this.intersectsNode(node, false);\n } else {\n return this.compareNode(node) == n_i;\n }\n },\n\n containsNodeContents: function(node) {\n return this.comparePoint(node, 0) >= 0 && this.comparePoint(node, getNodeLength(node)) <= 0;\n },\n\n containsRange: function(range) {\n var intersection = this.intersection(range);\n return intersection !== null && range.equals(intersection);\n },\n\n containsNodeText: function(node) {\n var nodeRange = this.cloneRange();\n nodeRange.selectNode(node);\n var textNodes = nodeRange.getNodes([3]);\n if (textNodes.length > 0) {\n nodeRange.setStart(textNodes[0], 0);\n var lastTextNode = textNodes.pop();\n nodeRange.setEnd(lastTextNode, lastTextNode.length);\n return this.containsRange(nodeRange);\n } else {\n return this.containsNodeContents(node);\n }\n },\n\n getNodes: function(nodeTypes, filter) {\n assertRangeValid(this);\n return getNodesInRange(this, nodeTypes, filter);\n },\n\n getDocument: function() {\n return getRangeDocument(this);\n },\n\n collapseBefore: function(node) {\n this.setEndBefore(node);\n this.collapse(false);\n },\n\n collapseAfter: function(node) {\n this.setStartAfter(node);\n this.collapse(true);\n },\n\n getBookmark: function(containerNode) {\n var doc = getRangeDocument(this);\n var preSelectionRange = api.createRange(doc);\n containerNode = containerNode || dom.getBody(doc);\n preSelectionRange.selectNodeContents(containerNode);\n var range = this.intersection(preSelectionRange);\n var start = 0, end = 0;\n if (range) {\n preSelectionRange.setEnd(range.startContainer, range.startOffset);\n start = preSelectionRange.toString().length;\n end = start + range.toString().length;\n }\n\n return {\n start: start,\n end: end,\n containerNode: containerNode\n };\n },\n\n moveToBookmark: function(bookmark) {\n var containerNode = bookmark.containerNode;\n var charIndex = 0;\n this.setStart(containerNode, 0);\n this.collapse(true);\n var nodeStack = [containerNode], node, foundStart = false, stop = false;\n var nextCharIndex, i, childNodes;\n\n while (!stop && (node = nodeStack.pop())) {\n if (node.nodeType == 3) {\n nextCharIndex = charIndex + node.length;\n if (!foundStart && bookmark.start >= charIndex && bookmark.start <= nextCharIndex) {\n this.setStart(node, bookmark.start - charIndex);\n foundStart = true;\n }\n if (foundStart && bookmark.end >= charIndex && bookmark.end <= nextCharIndex) {\n this.setEnd(node, bookmark.end - charIndex);\n stop = true;\n }\n charIndex = nextCharIndex;\n } else {\n childNodes = node.childNodes;\n i = childNodes.length;\n while (i--) {\n nodeStack.push(childNodes[i]);\n }\n }\n }\n },\n\n getName: function() {\n return \"DomRange\";\n },\n\n equals: function(range) {\n return Range.rangesEqual(this, range);\n },\n\n isValid: function() {\n return isRangeValid(this);\n },\n\n inspect: function() {\n return inspect(this);\n },\n\n detach: function() {\n // In DOM4, detach() is now a no-op.\n }\n });\n\n function copyComparisonConstantsToObject(obj) {\n obj.START_TO_START = s2s;\n obj.START_TO_END = s2e;\n obj.END_TO_END = e2e;\n obj.END_TO_START = e2s;\n\n obj.NODE_BEFORE = n_b;\n obj.NODE_AFTER = n_a;\n obj.NODE_BEFORE_AND_AFTER = n_b_a;\n obj.NODE_INSIDE = n_i;\n }\n\n function copyComparisonConstants(constructor) {\n copyComparisonConstantsToObject(constructor);\n copyComparisonConstantsToObject(constructor.prototype);\n }\n\n function createRangeContentRemover(remover, boundaryUpdater) {\n return function() {\n assertRangeValid(this);\n\n var sc = this.startContainer, so = this.startOffset, root = this.commonAncestorContainer;\n\n var iterator = new RangeIterator(this, true);\n\n // Work out where to position the range after content removal\n var node, boundary;\n if (sc !== root) {\n node = getClosestAncestorIn(sc, root, true);\n boundary = getBoundaryAfterNode(node);\n sc = boundary.node;\n so = boundary.offset;\n }\n\n // Check none of the range is read-only\n iterateSubtree(iterator, assertNodeNotReadOnly);\n\n iterator.reset();\n\n // Remove the content\n var returnValue = remover(iterator);\n iterator.detach();\n\n // Move to the new position\n boundaryUpdater(this, sc, so, sc, so);\n\n return returnValue;\n };\n }\n\n function createPrototypeRange(constructor, boundaryUpdater) {\n function createBeforeAfterNodeSetter(isBefore, isStart) {\n return function(node) {\n assertValidNodeType(node, beforeAfterNodeTypes);\n assertValidNodeType(getRootContainer(node), rootContainerNodeTypes);\n\n var boundary = (isBefore ? getBoundaryBeforeNode : getBoundaryAfterNode)(node);\n (isStart ? setRangeStart : setRangeEnd)(this, boundary.node, boundary.offset);\n };\n }\n\n function setRangeStart(range, node, offset) {\n var ec = range.endContainer, eo = range.endOffset;\n if (node !== range.startContainer || offset !== range.startOffset) {\n // Check the root containers of the range and the new boundary, and also check whether the new boundary\n // is after the current end. In either case, collapse the range to the new position\n if (getRootContainer(node) != getRootContainer(ec) || comparePoints(node, offset, ec, eo) == 1) {\n ec = node;\n eo = offset;\n }\n boundaryUpdater(range, node, offset, ec, eo);\n }\n }\n\n function setRangeEnd(range, node, offset) {\n var sc = range.startContainer, so = range.startOffset;\n if (node !== range.endContainer || offset !== range.endOffset) {\n // Check the root containers of the range and the new boundary, and also check whether the new boundary\n // is after the current end. In either case, collapse the range to the new position\n if (getRootContainer(node) != getRootContainer(sc) || comparePoints(node, offset, sc, so) == -1) {\n sc = node;\n so = offset;\n }\n boundaryUpdater(range, sc, so, node, offset);\n }\n }\n\n // Set up inheritance\n var F = function() {};\n F.prototype = api.rangePrototype;\n constructor.prototype = new F();\n\n util.extend(constructor.prototype, {\n setStart: function(node, offset) {\n assertNoDocTypeNotationEntityAncestor(node, true);\n assertValidOffset(node, offset);\n\n setRangeStart(this, node, offset);\n },\n\n setEnd: function(node, offset) {\n assertNoDocTypeNotationEntityAncestor(node, true);\n assertValidOffset(node, offset);\n\n setRangeEnd(this, node, offset);\n },\n\n /**\n * Convenience method to set a range's start and end boundaries. Overloaded as follows:\n * - Two parameters (node, offset) creates a collapsed range at that position\n * - Three parameters (node, startOffset, endOffset) creates a range contained with node starting at\n * startOffset and ending at endOffset\n * - Four parameters (startNode, startOffset, endNode, endOffset) creates a range starting at startOffset in\n * startNode and ending at endOffset in endNode\n */\n setStartAndEnd: function() {\n var args = arguments;\n var sc = args[0], so = args[1], ec = sc, eo = so;\n\n switch (args.length) {\n case 3:\n eo = args[2];\n break;\n case 4:\n ec = args[2];\n eo = args[3];\n break;\n }\n\n assertNoDocTypeNotationEntityAncestor(sc, true);\n assertValidOffset(sc, so);\n\n assertNoDocTypeNotationEntityAncestor(ec, true);\n assertValidOffset(ec, eo);\n\n boundaryUpdater(this, sc, so, ec, eo);\n },\n\n setBoundary: function(node, offset, isStart) {\n this[\"set\" + (isStart ? \"Start\" : \"End\")](node, offset);\n },\n\n setStartBefore: createBeforeAfterNodeSetter(true, true),\n setStartAfter: createBeforeAfterNodeSetter(false, true),\n setEndBefore: createBeforeAfterNodeSetter(true, false),\n setEndAfter: createBeforeAfterNodeSetter(false, false),\n\n collapse: function(isStart) {\n assertRangeValid(this);\n if (isStart) {\n boundaryUpdater(this, this.startContainer, this.startOffset, this.startContainer, this.startOffset);\n } else {\n boundaryUpdater(this, this.endContainer, this.endOffset, this.endContainer, this.endOffset);\n }\n },\n\n selectNodeContents: function(node) {\n assertNoDocTypeNotationEntityAncestor(node, true);\n\n boundaryUpdater(this, node, 0, node, getNodeLength(node));\n },\n\n selectNode: function(node) {\n assertNoDocTypeNotationEntityAncestor(node, false);\n assertValidNodeType(node, beforeAfterNodeTypes);\n\n var start = getBoundaryBeforeNode(node), end = getBoundaryAfterNode(node);\n boundaryUpdater(this, start.node, start.offset, end.node, end.offset);\n },\n\n extractContents: createRangeContentRemover(extractSubtree, boundaryUpdater),\n\n deleteContents: createRangeContentRemover(deleteSubtree, boundaryUpdater),\n\n canSurroundContents: function() {\n assertRangeValid(this);\n assertNodeNotReadOnly(this.startContainer);\n assertNodeNotReadOnly(this.endContainer);\n\n // Check if the contents can be surrounded. Specifically, this means whether the range partially selects\n // no non-text nodes.\n var iterator = new RangeIterator(this, true);\n var boundariesInvalid = (iterator._first && isNonTextPartiallySelected(iterator._first, this) ||\n (iterator._last && isNonTextPartiallySelected(iterator._last, this)));\n iterator.detach();\n return !boundariesInvalid;\n },\n\n splitBoundaries: function() {\n splitRangeBoundaries(this);\n },\n\n splitBoundariesPreservingPositions: function(positionsToPreserve) {\n splitRangeBoundaries(this, positionsToPreserve);\n },\n\n normalizeBoundaries: function() {\n assertRangeValid(this);\n\n var sc = this.startContainer, so = this.startOffset, ec = this.endContainer, eo = this.endOffset;\n\n var mergeForward = function(node) {\n var sibling = node.nextSibling;\n if (sibling && sibling.nodeType == node.nodeType) {\n ec = node;\n eo = node.length;\n node.appendData(sibling.data);\n removeNode(sibling);\n }\n };\n\n var mergeBackward = function(node) {\n var sibling = node.previousSibling;\n if (sibling && sibling.nodeType == node.nodeType) {\n sc = node;\n var nodeLength = node.length;\n so = sibling.length;\n node.insertData(0, sibling.data);\n removeNode(sibling);\n if (sc == ec) {\n eo += so;\n ec = sc;\n } else if (ec == node.parentNode) {\n var nodeIndex = getNodeIndex(node);\n if (eo == nodeIndex) {\n ec = node;\n eo = nodeLength;\n } else if (eo > nodeIndex) {\n eo--;\n }\n }\n }\n };\n\n var normalizeStart = true;\n var sibling;\n\n if (isCharacterDataNode(ec)) {\n if (eo == ec.length) {\n mergeForward(ec);\n } else if (eo == 0) {\n sibling = ec.previousSibling;\n if (sibling && sibling.nodeType == ec.nodeType) {\n eo = sibling.length;\n if (sc == ec) {\n normalizeStart = false;\n }\n sibling.appendData(ec.data);\n removeNode(ec);\n ec = sibling;\n }\n }\n } else {\n if (eo > 0) {\n var endNode = ec.childNodes[eo - 1];\n if (endNode && isCharacterDataNode(endNode)) {\n mergeForward(endNode);\n }\n }\n normalizeStart = !this.collapsed;\n }\n\n if (normalizeStart) {\n if (isCharacterDataNode(sc)) {\n if (so == 0) {\n mergeBackward(sc);\n } else if (so == sc.length) {\n sibling = sc.nextSibling;\n if (sibling && sibling.nodeType == sc.nodeType) {\n if (ec == sibling) {\n ec = sc;\n eo += sc.length;\n }\n sc.appendData(sibling.data);\n removeNode(sibling);\n }\n }\n } else {\n if (so < sc.childNodes.length) {\n var startNode = sc.childNodes[so];\n if (startNode && isCharacterDataNode(startNode)) {\n mergeBackward(startNode);\n }\n }\n }\n } else {\n sc = ec;\n so = eo;\n }\n\n boundaryUpdater(this, sc, so, ec, eo);\n },\n\n collapseToPoint: function(node, offset) {\n assertNoDocTypeNotationEntityAncestor(node, true);\n assertValidOffset(node, offset);\n this.setStartAndEnd(node, offset);\n },\n\n parentElement: function() {\n assertRangeValid(this);\n var parentNode = this.commonAncestorContainer;\n return parentNode ? getElementAncestor(this.commonAncestorContainer, true) : null;\n }\n });\n\n copyComparisonConstants(constructor);\n }\n\n /*----------------------------------------------------------------------------------------------------------------*/\n\n // Updates commonAncestorContainer and collapsed after boundary change\n function updateCollapsedAndCommonAncestor(range) {\n range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset);\n range.commonAncestorContainer = range.collapsed ?\n range.startContainer : dom.getCommonAncestor(range.startContainer, range.endContainer);\n }\n\n function updateBoundaries(range, startContainer, startOffset, endContainer, endOffset) {\n range.startContainer = startContainer;\n range.startOffset = startOffset;\n range.endContainer = endContainer;\n range.endOffset = endOffset;\n range.document = dom.getDocument(startContainer);\n updateCollapsedAndCommonAncestor(range);\n }\n\n function Range(doc) {\n updateBoundaries(this, doc, 0, doc, 0);\n }\n\n createPrototypeRange(Range, updateBoundaries);\n\n util.extend(Range, {\n rangeProperties: rangeProperties,\n RangeIterator: RangeIterator,\n copyComparisonConstants: copyComparisonConstants,\n createPrototypeRange: createPrototypeRange,\n inspect: inspect,\n toHtml: rangeToHtml,\n getRangeDocument: getRangeDocument,\n rangesEqual: function(r1, r2) {\n return r1.startContainer === r2.startContainer &&\n r1.startOffset === r2.startOffset &&\n r1.endContainer === r2.endContainer &&\n r1.endOffset === r2.endOffset;\n }\n });\n\n api.DomRange = Range;\n });\n\n /*----------------------------------------------------------------------------------------------------------------*/\n\n // Wrappers for the browser's native DOM Range and/or TextRange implementation\n api.createCoreModule(\"WrappedRange\", [\"DomRange\"], function(api, module) {\n var WrappedRange, WrappedTextRange;\n var dom = api.dom;\n var util = api.util;\n var DomPosition = dom.DomPosition;\n var DomRange = api.DomRange;\n var getBody = dom.getBody;\n var getContentDocument = dom.getContentDocument;\n var isCharacterDataNode = dom.isCharacterDataNode;\n\n\n /*----------------------------------------------------------------------------------------------------------------*/\n\n if (api.features.implementsDomRange) {\n // This is a wrapper around the browser's native DOM Range. It has two aims:\n // - Provide workarounds for specific browser bugs\n // - provide convenient extensions, which are inherited from Rangy's DomRange\n\n (function() {\n var rangeProto;\n var rangeProperties = DomRange.rangeProperties;\n\n function updateRangeProperties(range) {\n var i = rangeProperties.length, prop;\n while (i--) {\n prop = rangeProperties[i];\n range[prop] = range.nativeRange[prop];\n }\n // Fix for broken collapsed property in IE 9.\n range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset);\n }\n\n function updateNativeRange(range, startContainer, startOffset, endContainer, endOffset) {\n var startMoved = (range.startContainer !== startContainer || range.startOffset != startOffset);\n var endMoved = (range.endContainer !== endContainer || range.endOffset != endOffset);\n var nativeRangeDifferent = !range.equals(range.nativeRange);\n\n // Always set both boundaries for the benefit of IE9 (see issue 35)\n if (startMoved || endMoved || nativeRangeDifferent) {\n range.setEnd(endContainer, endOffset);\n range.setStart(startContainer, startOffset);\n }\n }\n\n var createBeforeAfterNodeSetter;\n\n WrappedRange = function(range) {\n if (!range) {\n throw module.createError(\"WrappedRange: Range must be specified\");\n }\n this.nativeRange = range;\n updateRangeProperties(this);\n };\n\n DomRange.createPrototypeRange(WrappedRange, updateNativeRange);\n\n rangeProto = WrappedRange.prototype;\n\n rangeProto.selectNode = function(node) {\n this.nativeRange.selectNode(node);\n updateRangeProperties(this);\n };\n\n rangeProto.cloneContents = function() {\n return this.nativeRange.cloneContents();\n };\n\n // Due to a long-standing Firefox bug that I have not been able to find a reliable way to detect,\n // insertNode() is never delegated to the native range.\n\n rangeProto.surroundContents = function(node) {\n this.nativeRange.surroundContents(node);\n updateRangeProperties(this);\n };\n\n rangeProto.collapse = function(isStart) {\n this.nativeRange.collapse(isStart);\n updateRangeProperties(this);\n };\n\n rangeProto.cloneRange = function() {\n return new WrappedRange(this.nativeRange.cloneRange());\n };\n\n rangeProto.refresh = function() {\n updateRangeProperties(this);\n };\n\n rangeProto.toString = function() {\n return this.nativeRange.toString();\n };\n\n // Create test range and node for feature detection\n\n var testTextNode = document.createTextNode(\"test\");\n getBody(document).appendChild(testTextNode);\n var range = document.createRange();\n\n /*--------------------------------------------------------------------------------------------------------*/\n\n // Test for Firefox 2 bug that prevents moving the start of a Range to a point after its current end and\n // correct for it\n\n range.setStart(testTextNode, 0);\n range.setEnd(testTextNode, 0);\n\n try {\n range.setStart(testTextNode, 1);\n\n rangeProto.setStart = function(node, offset) {\n this.nativeRange.setStart(node, offset);\n updateRangeProperties(this);\n };\n\n rangeProto.setEnd = function(node, offset) {\n this.nativeRange.setEnd(node, offset);\n updateRangeProperties(this);\n };\n\n createBeforeAfterNodeSetter = function(name) {\n return function(node) {\n this.nativeRange[name](node);\n updateRangeProperties(this);\n };\n };\n\n } catch(ex) {\n\n rangeProto.setStart = function(node, offset) {\n try {\n this.nativeRange.setStart(node, offset);\n } catch (ex) {\n this.nativeRange.setEnd(node, offset);\n this.nativeRange.setStart(node, offset);\n }\n updateRangeProperties(this);\n };\n\n rangeProto.setEnd = function(node, offset) {\n try {\n this.nativeRange.setEnd(node, offset);\n } catch (ex) {\n this.nativeRange.setStart(node, offset);\n this.nativeRange.setEnd(node, offset);\n }\n updateRangeProperties(this);\n };\n\n createBeforeAfterNodeSetter = function(name, oppositeName) {\n return function(node) {\n try {\n this.nativeRange[name](node);\n } catch (ex) {\n this.nativeRange[oppositeName](node);\n this.nativeRange[name](node);\n }\n updateRangeProperties(this);\n };\n };\n }\n\n rangeProto.setStartBefore = createBeforeAfterNodeSetter(\"setStartBefore\", \"setEndBefore\");\n rangeProto.setStartAfter = createBeforeAfterNodeSetter(\"setStartAfter\", \"setEndAfter\");\n rangeProto.setEndBefore = createBeforeAfterNodeSetter(\"setEndBefore\", \"setStartBefore\");\n rangeProto.setEndAfter = createBeforeAfterNodeSetter(\"setEndAfter\", \"setStartAfter\");\n\n /*--------------------------------------------------------------------------------------------------------*/\n\n // Always use DOM4-compliant selectNodeContents implementation: it's simpler and less code than testing\n // whether the native implementation can be trusted\n rangeProto.selectNodeContents = function(node) {\n this.setStartAndEnd(node, 0, dom.getNodeLength(node));\n };\n\n /*--------------------------------------------------------------------------------------------------------*/\n\n // Test for and correct WebKit bug that has the behaviour of compareBoundaryPoints round the wrong way for\n // constants START_TO_END and END_TO_START: https://bugs.webkit.org/show_bug.cgi?id=20738\n\n range.selectNodeContents(testTextNode);\n range.setEnd(testTextNode, 3);\n\n var range2 = document.createRange();\n range2.selectNodeContents(testTextNode);\n range2.setEnd(testTextNode, 4);\n range2.setStart(testTextNode, 2);\n\n if (range.compareBoundaryPoints(range.START_TO_END, range2) == -1 &&\n range.compareBoundaryPoints(range.END_TO_START, range2) == 1) {\n // This is the wrong way round, so correct for it\n\n rangeProto.compareBoundaryPoints = function(type, range) {\n range = range.nativeRange || range;\n if (type == range.START_TO_END) {\n type = range.END_TO_START;\n } else if (type == range.END_TO_START) {\n type = range.START_TO_END;\n }\n return this.nativeRange.compareBoundaryPoints(type, range);\n };\n } else {\n rangeProto.compareBoundaryPoints = function(type, range) {\n return this.nativeRange.compareBoundaryPoints(type, range.nativeRange || range);\n };\n }\n\n /*--------------------------------------------------------------------------------------------------------*/\n\n // Test for IE deleteContents() and extractContents() bug and correct it. See issue 107.\n\n var el = document.createElement(\"div\");\n el.innerHTML = \"123\";\n var textNode = el.firstChild;\n var body = getBody(document);\n body.appendChild(el);\n\n range.setStart(textNode, 1);\n range.setEnd(textNode, 2);\n range.deleteContents();\n\n if (textNode.data == \"13\") {\n // Behaviour is correct per DOM4 Range so wrap the browser's implementation of deleteContents() and\n // extractContents()\n rangeProto.deleteContents = function() {\n this.nativeRange.deleteContents();\n updateRangeProperties(this);\n };\n\n rangeProto.extractContents = function() {\n var frag = this.nativeRange.extractContents();\n updateRangeProperties(this);\n return frag;\n };\n } else {\n }\n\n body.removeChild(el);\n body = null;\n\n /*--------------------------------------------------------------------------------------------------------*/\n\n // Test for existence of createContextualFragment and delegate to it if it exists\n if (util.isHostMethod(range, \"createContextualFragment\")) {\n rangeProto.createContextualFragment = function(fragmentStr) {\n return this.nativeRange.createContextualFragment(fragmentStr);\n };\n }\n\n /*--------------------------------------------------------------------------------------------------------*/\n\n // Clean up\n getBody(document).removeChild(testTextNode);\n\n rangeProto.getName = function() {\n return \"WrappedRange\";\n };\n\n api.WrappedRange = WrappedRange;\n\n api.createNativeRange = function(doc) {\n doc = getContentDocument(doc, module, \"createNativeRange\");\n return doc.createRange();\n };\n })();\n }\n\n if (api.features.implementsTextRange) {\n /*\n This is a workaround for a bug where IE returns the wrong container element from the TextRange's parentElement()\n method. For example, in the following (where pipes denote the selection boundaries):\n\n \n\n var range = document.selection.createRange();\n alert(range.parentElement().id); // Should alert \"ul\" but alerts \"b\"\n\n This method returns the common ancestor node of the following:\n - the parentElement() of the textRange\n - the parentElement() of the textRange after calling collapse(true)\n - the parentElement() of the textRange after calling collapse(false)\n */\n var getTextRangeContainerElement = function(textRange) {\n var parentEl = textRange.parentElement();\n var range = textRange.duplicate();\n range.collapse(true);\n var startEl = range.parentElement();\n range = textRange.duplicate();\n range.collapse(false);\n var endEl = range.parentElement();\n var startEndContainer = (startEl == endEl) ? startEl : dom.getCommonAncestor(startEl, endEl);\n\n return startEndContainer == parentEl ? startEndContainer : dom.getCommonAncestor(parentEl, startEndContainer);\n };\n\n var textRangeIsCollapsed = function(textRange) {\n return textRange.compareEndPoints(\"StartToEnd\", textRange) == 0;\n };\n\n // Gets the boundary of a TextRange expressed as a node and an offset within that node. This function started\n // out as an improved version of code found in Tim Cameron Ryan's IERange (http://code.google.com/p/ierange/)\n // but has grown, fixing problems with line breaks in preformatted text, adding workaround for IE TextRange\n // bugs, handling for inputs and images, plus optimizations.\n var getTextRangeBoundaryPosition = function(textRange, wholeRangeContainerElement, isStart, isCollapsed, startInfo) {\n var workingRange = textRange.duplicate();\n workingRange.collapse(isStart);\n var containerElement = workingRange.parentElement();\n\n // Sometimes collapsing a TextRange that's at the start of a text node can move it into the previous node, so\n // check for that\n if (!dom.isOrIsAncestorOf(wholeRangeContainerElement, containerElement)) {\n containerElement = wholeRangeContainerElement;\n }\n\n\n // Deal with nodes that cannot \"contain rich HTML markup\". In practice, this means form inputs, images and\n // similar. See http://msdn.microsoft.com/en-us/library/aa703950%28VS.85%29.aspx\n if (!containerElement.canHaveHTML) {\n var pos = new DomPosition(containerElement.parentNode, dom.getNodeIndex(containerElement));\n return {\n boundaryPosition: pos,\n nodeInfo: {\n nodeIndex: pos.offset,\n containerElement: pos.node\n }\n };\n }\n\n var workingNode = dom.getDocument(containerElement).createElement(\"span\");\n\n // Workaround for HTML5 Shiv's insane violation of document.createElement(). See Rangy issue 104 and HTML5\n // Shiv issue 64: https://github.com/aFarkas/html5shiv/issues/64\n if (workingNode.parentNode) {\n dom.removeNode(workingNode);\n }\n\n var comparison, workingComparisonType = isStart ? \"StartToStart\" : \"StartToEnd\";\n var previousNode, nextNode, boundaryPosition, boundaryNode;\n var start = (startInfo && startInfo.containerElement == containerElement) ? startInfo.nodeIndex : 0;\n var childNodeCount = containerElement.childNodes.length;\n var end = childNodeCount;\n\n // Check end first. Code within the loop assumes that the endth child node of the container is definitely\n // after the range boundary.\n var nodeIndex = end;\n\n while (true) {\n if (nodeIndex == childNodeCount) {\n containerElement.appendChild(workingNode);\n } else {\n containerElement.insertBefore(workingNode, containerElement.childNodes[nodeIndex]);\n }\n workingRange.moveToElementText(workingNode);\n comparison = workingRange.compareEndPoints(workingComparisonType, textRange);\n if (comparison == 0 || start == end) {\n break;\n } else if (comparison == -1) {\n if (end == start + 1) {\n // We know the endth child node is after the range boundary, so we must be done.\n break;\n } else {\n start = nodeIndex;\n }\n } else {\n end = (end == start + 1) ? start : nodeIndex;\n }\n nodeIndex = Math.floor((start + end) / 2);\n containerElement.removeChild(workingNode);\n }\n\n\n // We've now reached or gone past the boundary of the text range we're interested in\n // so have identified the node we want\n boundaryNode = workingNode.nextSibling;\n\n if (comparison == -1 && boundaryNode && isCharacterDataNode(boundaryNode)) {\n // This is a character data node (text, comment, cdata). The working range is collapsed at the start of\n // the node containing the text range's boundary, so we move the end of the working range to the\n // boundary point and measure the length of its text to get the boundary's offset within the node.\n workingRange.setEndPoint(isStart ? \"EndToStart\" : \"EndToEnd\", textRange);\n\n var offset;\n\n if (/[\\r\\n]/.test(boundaryNode.data)) {\n /*\n For the particular case of a boundary within a text node containing rendered line breaks (within a\n
 element, for example), we need a slightly complicated approach to get the boundary's offset in\n                        IE. The facts:\n\n                        - Each line break is represented as \\r in the text node's data/nodeValue properties\n                        - Each line break is represented as \\r\\n in the TextRange's 'text' property\n                        - The 'text' property of the TextRange does not contain trailing line breaks\n\n                        To get round the problem presented by the final fact above, we can use the fact that TextRange's\n                        moveStart() and moveEnd() methods return the actual number of characters moved, which is not\n                        necessarily the same as the number of characters it was instructed to move. The simplest approach is\n                        to use this to store the characters moved when moving both the start and end of the range to the\n                        start of the document body and subtracting the start offset from the end offset (the\n                        \"move-negative-gazillion\" method). However, this is extremely slow when the document is large and\n                        the range is near the end of it. Clearly doing the mirror image (i.e. moving the range boundaries to\n                        the end of the document) has the same problem.\n\n                        Another approach that works is to use moveStart() to move the start boundary of the range up to the\n                        end boundary one character at a time and incrementing a counter with the value returned by the\n                        moveStart() call. However, the check for whether the start boundary has reached the end boundary is\n                        expensive, so this method is slow (although unlike \"move-negative-gazillion\" is largely unaffected\n                        by the location of the range within the document).\n\n                        The approach used below is a hybrid of the two methods above. It uses the fact that a string\n                        containing the TextRange's 'text' property with each \\r\\n converted to a single \\r character cannot\n                        be longer than the text of the TextRange, so the start of the range is moved that length initially\n                        and then a character at a time to make up for any trailing line breaks not contained in the 'text'\n                        property. This has good performance in most situations compared to the previous two methods.\n                        */\n                        var tempRange = workingRange.duplicate();\n                        var rangeLength = tempRange.text.replace(/\\r\\n/g, \"\\r\").length;\n\n                        offset = tempRange.moveStart(\"character\", rangeLength);\n                        while ( (comparison = tempRange.compareEndPoints(\"StartToEnd\", tempRange)) == -1) {\n                            offset++;\n                            tempRange.moveStart(\"character\", 1);\n                        }\n                    } else {\n                        offset = workingRange.text.length;\n                    }\n                    boundaryPosition = new DomPosition(boundaryNode, offset);\n                } else {\n\n                    // If the boundary immediately follows a character data node and this is the end boundary, we should favour\n                    // a position within that, and likewise for a start boundary preceding a character data node\n                    previousNode = (isCollapsed || !isStart) && workingNode.previousSibling;\n                    nextNode = (isCollapsed || isStart) && workingNode.nextSibling;\n                    if (nextNode && isCharacterDataNode(nextNode)) {\n                        boundaryPosition = new DomPosition(nextNode, 0);\n                    } else if (previousNode && isCharacterDataNode(previousNode)) {\n                        boundaryPosition = new DomPosition(previousNode, previousNode.data.length);\n                    } else {\n                        boundaryPosition = new DomPosition(containerElement, dom.getNodeIndex(workingNode));\n                    }\n                }\n\n                // Clean up\n                dom.removeNode(workingNode);\n\n                return {\n                    boundaryPosition: boundaryPosition,\n                    nodeInfo: {\n                        nodeIndex: nodeIndex,\n                        containerElement: containerElement\n                    }\n                };\n            };\n\n            // Returns a TextRange representing the boundary of a TextRange expressed as a node and an offset within that\n            // node. This function started out as an optimized version of code found in Tim Cameron Ryan's IERange\n            // (http://code.google.com/p/ierange/)\n            var createBoundaryTextRange = function(boundaryPosition, isStart) {\n                var boundaryNode, boundaryParent, boundaryOffset = boundaryPosition.offset;\n                var doc = dom.getDocument(boundaryPosition.node);\n                var workingNode, childNodes, workingRange = getBody(doc).createTextRange();\n                var nodeIsDataNode = isCharacterDataNode(boundaryPosition.node);\n\n                if (nodeIsDataNode) {\n                    boundaryNode = boundaryPosition.node;\n                    boundaryParent = boundaryNode.parentNode;\n                } else {\n                    childNodes = boundaryPosition.node.childNodes;\n                    boundaryNode = (boundaryOffset < childNodes.length) ? childNodes[boundaryOffset] : null;\n                    boundaryParent = boundaryPosition.node;\n                }\n\n                // Position the range immediately before the node containing the boundary\n                workingNode = doc.createElement(\"span\");\n\n                // Making the working element non-empty element persuades IE to consider the TextRange boundary to be within\n                // the element rather than immediately before or after it\n                workingNode.innerHTML = \"&#feff;\";\n\n                // insertBefore is supposed to work like appendChild if the second parameter is null. However, a bug report\n                // for IERange suggests that it can crash the browser: http://code.google.com/p/ierange/issues/detail?id=12\n                if (boundaryNode) {\n                    boundaryParent.insertBefore(workingNode, boundaryNode);\n                } else {\n                    boundaryParent.appendChild(workingNode);\n                }\n\n                workingRange.moveToElementText(workingNode);\n                workingRange.collapse(!isStart);\n\n                // Clean up\n                boundaryParent.removeChild(workingNode);\n\n                // Move the working range to the text offset, if required\n                if (nodeIsDataNode) {\n                    workingRange[isStart ? \"moveStart\" : \"moveEnd\"](\"character\", boundaryOffset);\n                }\n\n                return workingRange;\n            };\n\n            /*------------------------------------------------------------------------------------------------------------*/\n\n            // This is a wrapper around a TextRange, providing full DOM Range functionality using rangy's DomRange as a\n            // prototype\n\n            WrappedTextRange = function(textRange) {\n                this.textRange = textRange;\n                this.refresh();\n            };\n\n            WrappedTextRange.prototype = new DomRange(document);\n\n            WrappedTextRange.prototype.refresh = function() {\n                var start, end, startBoundary;\n\n                // TextRange's parentElement() method cannot be trusted. getTextRangeContainerElement() works around that.\n                var rangeContainerElement = getTextRangeContainerElement(this.textRange);\n\n                if (textRangeIsCollapsed(this.textRange)) {\n                    end = start = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true,\n                        true).boundaryPosition;\n                } else {\n                    startBoundary = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true, false);\n                    start = startBoundary.boundaryPosition;\n\n                    // An optimization used here is that if the start and end boundaries have the same parent element, the\n                    // search scope for the end boundary can be limited to exclude the portion of the element that precedes\n                    // the start boundary\n                    end = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, false, false,\n                        startBoundary.nodeInfo).boundaryPosition;\n                }\n\n                this.setStart(start.node, start.offset);\n                this.setEnd(end.node, end.offset);\n            };\n\n            WrappedTextRange.prototype.getName = function() {\n                return \"WrappedTextRange\";\n            };\n\n            DomRange.copyComparisonConstants(WrappedTextRange);\n\n            var rangeToTextRange = function(range) {\n                if (range.collapsed) {\n                    return createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);\n                } else {\n                    var startRange = createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);\n                    var endRange = createBoundaryTextRange(new DomPosition(range.endContainer, range.endOffset), false);\n                    var textRange = getBody( DomRange.getRangeDocument(range) ).createTextRange();\n                    textRange.setEndPoint(\"StartToStart\", startRange);\n                    textRange.setEndPoint(\"EndToEnd\", endRange);\n                    return textRange;\n                }\n            };\n\n            WrappedTextRange.rangeToTextRange = rangeToTextRange;\n\n            WrappedTextRange.prototype.toTextRange = function() {\n                return rangeToTextRange(this);\n            };\n\n            api.WrappedTextRange = WrappedTextRange;\n\n            // IE 9 and above have both implementations and Rangy makes both available. The next few lines sets which\n            // implementation to use by default.\n            if (!api.features.implementsDomRange || api.config.preferTextRange) {\n                // Add WrappedTextRange as the Range property of the global object to allow expression like Range.END_TO_END to work\n                var globalObj = (function(f) { return f(\"return this;\")(); })(Function);\n                if (typeof globalObj.Range == \"undefined\") {\n                    globalObj.Range = WrappedTextRange;\n                }\n\n                api.createNativeRange = function(doc) {\n                    doc = getContentDocument(doc, module, \"createNativeRange\");\n                    return getBody(doc).createTextRange();\n                };\n\n                api.WrappedRange = WrappedTextRange;\n            }\n        }\n\n        api.createRange = function(doc) {\n            doc = getContentDocument(doc, module, \"createRange\");\n            return new api.WrappedRange(api.createNativeRange(doc));\n        };\n\n        api.createRangyRange = function(doc) {\n            doc = getContentDocument(doc, module, \"createRangyRange\");\n            return new DomRange(doc);\n        };\n\n        util.createAliasForDeprecatedMethod(api, \"createIframeRange\", \"createRange\");\n        util.createAliasForDeprecatedMethod(api, \"createIframeRangyRange\", \"createRangyRange\");\n\n        api.addShimListener(function(win) {\n            var doc = win.document;\n            if (typeof doc.createRange == \"undefined\") {\n                doc.createRange = function() {\n                    return api.createRange(doc);\n                };\n            }\n            doc = win = null;\n        });\n    });\n\n    /*----------------------------------------------------------------------------------------------------------------*/\n\n    // This module creates a selection object wrapper that conforms as closely as possible to the Selection specification\n    // in the W3C Selection API spec (https://www.w3.org/TR/selection-api)\n    api.createCoreModule(\"WrappedSelection\", [\"DomRange\", \"WrappedRange\"], function(api, module) {\n        api.config.checkSelectionRanges = true;\n\n        var BOOLEAN = \"boolean\";\n        var NUMBER = \"number\";\n        var dom = api.dom;\n        var util = api.util;\n        var isHostMethod = util.isHostMethod;\n        var DomRange = api.DomRange;\n        var WrappedRange = api.WrappedRange;\n        var DOMException = api.DOMException;\n        var DomPosition = dom.DomPosition;\n        var getNativeSelection;\n        var selectionIsCollapsed;\n        var features = api.features;\n        var CONTROL = \"Control\";\n        var getDocument = dom.getDocument;\n        var getBody = dom.getBody;\n        var rangesEqual = DomRange.rangesEqual;\n\n\n        // Utility function to support direction parameters in the API that may be a string (\"backward\", \"backwards\",\n        // \"forward\" or \"forwards\") or a Boolean (true for backwards).\n        function isDirectionBackward(dir) {\n            return (typeof dir == \"string\") ? /^backward(s)?$/i.test(dir) : !!dir;\n        }\n\n        function getWindow(win, methodName) {\n            if (!win) {\n                return window;\n            } else if (dom.isWindow(win)) {\n                return win;\n            } else if (win instanceof WrappedSelection) {\n                return win.win;\n            } else {\n                var doc = dom.getContentDocument(win, module, methodName);\n                return dom.getWindow(doc);\n            }\n        }\n\n        function getWinSelection(winParam) {\n            return getWindow(winParam, \"getWinSelection\").getSelection();\n        }\n\n        function getDocSelection(winParam) {\n            return getWindow(winParam, \"getDocSelection\").document.selection;\n        }\n\n        function winSelectionIsBackward(sel) {\n            var backward = false;\n            if (sel.anchorNode) {\n                backward = (dom.comparePoints(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset) == 1);\n            }\n            return backward;\n        }\n\n        // Test for the Range/TextRange and Selection features required\n        // Test for ability to retrieve selection\n        var implementsWinGetSelection = isHostMethod(window, \"getSelection\"),\n            implementsDocSelection = util.isHostObject(document, \"selection\");\n\n        features.implementsWinGetSelection = implementsWinGetSelection;\n        features.implementsDocSelection = implementsDocSelection;\n\n        var useDocumentSelection = implementsDocSelection && (!implementsWinGetSelection || api.config.preferTextRange);\n\n        if (useDocumentSelection) {\n            getNativeSelection = getDocSelection;\n            api.isSelectionValid = function(winParam) {\n                var doc = getWindow(winParam, \"isSelectionValid\").document, nativeSel = doc.selection;\n\n                // Check whether the selection TextRange is actually contained within the correct document\n                return (nativeSel.type != \"None\" || getDocument(nativeSel.createRange().parentElement()) == doc);\n            };\n        } else if (implementsWinGetSelection) {\n            getNativeSelection = getWinSelection;\n            api.isSelectionValid = function() {\n                return true;\n            };\n        } else {\n            module.fail(\"Neither document.selection or window.getSelection() detected.\");\n            return false;\n        }\n\n        api.getNativeSelection = getNativeSelection;\n\n        var testSelection = getNativeSelection();\n\n        // In Firefox, the selection is null in an iframe with display: none. See issue #138.\n        if (!testSelection) {\n            module.fail(\"Native selection was null (possibly issue 138?)\");\n            return false;\n        }\n\n        var testRange = api.createNativeRange(document);\n        var body = getBody(document);\n\n        // Obtaining a range from a selection\n        var selectionHasAnchorAndFocus = util.areHostProperties(testSelection,\n            [\"anchorNode\", \"focusNode\", \"anchorOffset\", \"focusOffset\"]);\n\n        features.selectionHasAnchorAndFocus = selectionHasAnchorAndFocus;\n\n        // Test for existence of native selection extend() method\n        var selectionHasExtend = isHostMethod(testSelection, \"extend\");\n        features.selectionHasExtend = selectionHasExtend;\n\n        // Test for existence of native selection setBaseAndExtent() method\n        var selectionHasSetBaseAndExtent = isHostMethod(testSelection, \"setBaseAndExtent\");\n        features.selectionHasSetBaseAndExtent = selectionHasSetBaseAndExtent;\n\n        // Test if rangeCount exists\n        var selectionHasRangeCount = (typeof testSelection.rangeCount == NUMBER);\n        features.selectionHasRangeCount = selectionHasRangeCount;\n\n        var selectionSupportsMultipleRanges = false;\n        var collapsedNonEditableSelectionsSupported = true;\n\n        var addRangeBackwardToNative = selectionHasExtend ?\n            function(nativeSelection, range) {\n                var doc = DomRange.getRangeDocument(range);\n                var endRange = api.createRange(doc);\n                endRange.collapseToPoint(range.endContainer, range.endOffset);\n                nativeSelection.addRange(getNativeRange(endRange));\n                nativeSelection.extend(range.startContainer, range.startOffset);\n            } : null;\n\n        if (util.areHostMethods(testSelection, [\"addRange\", \"getRangeAt\", \"removeAllRanges\"]) &&\n                typeof testSelection.rangeCount == NUMBER && features.implementsDomRange) {\n\n            (function() {\n                // Previously an iframe was used but this caused problems in some circumstances in IE, so tests are\n                // performed on the current document's selection. See issue 109.\n\n                // Note also that if a selection previously existed, it is wiped and later restored by these tests. This\n                // will result in the selection direction being reversed if the original selection was backwards and the\n                // browser does not support setting backwards selections (Internet Explorer, I'm looking at you).\n                var sel = window.getSelection();\n                if (sel) {\n                    // Store the current selection\n                    var originalSelectionRangeCount = sel.rangeCount;\n                    var selectionHasMultipleRanges = (originalSelectionRangeCount > 1);\n                    var originalSelectionRanges = [];\n                    var originalSelectionBackward = winSelectionIsBackward(sel);\n                    for (var i = 0; i < originalSelectionRangeCount; ++i) {\n                        originalSelectionRanges[i] = sel.getRangeAt(i);\n                    }\n\n                    // Create some test elements\n                    var testEl = dom.createTestElement(document, \"\", false);\n                    var textNode = testEl.appendChild( document.createTextNode(\"\\u00a0\\u00a0\\u00a0\") );\n\n                    // Test whether the native selection will allow a collapsed selection within a non-editable element\n                    var r1 = document.createRange();\n\n                    r1.setStart(textNode, 1);\n                    r1.collapse(true);\n                    sel.removeAllRanges();\n                    sel.addRange(r1);\n                    collapsedNonEditableSelectionsSupported = (sel.rangeCount == 1);\n                    sel.removeAllRanges();\n\n                    // Test whether the native selection is capable of supporting multiple ranges.\n                    if (!selectionHasMultipleRanges) {\n                        // Doing the original feature test here in Chrome 36 (and presumably later versions) prints a\n                        // console error of \"Discontiguous selection is not supported.\" that cannot be suppressed. There's\n                        // nothing we can do about this while retaining the feature test so we have to resort to a browser\n                        // sniff. I'm not happy about it. See\n                        // https://code.google.com/p/chromium/issues/detail?id=399791\n                        var chromeMatch = window.navigator.appVersion.match(/Chrome\\/(.*?) /);\n                        if (chromeMatch && parseInt(chromeMatch[1]) >= 36) {\n                            selectionSupportsMultipleRanges = false;\n                        } else {\n                            var r2 = r1.cloneRange();\n                            r1.setStart(textNode, 0);\n                            r2.setEnd(textNode, 3);\n                            r2.setStart(textNode, 2);\n                            sel.addRange(r1);\n                            sel.addRange(r2);\n                            selectionSupportsMultipleRanges = (sel.rangeCount == 2);\n                        }\n                    }\n\n                    // Clean up\n                    dom.removeNode(testEl);\n                    sel.removeAllRanges();\n\n                    for (i = 0; i < originalSelectionRangeCount; ++i) {\n                        if (i == 0 && originalSelectionBackward) {\n                            if (addRangeBackwardToNative) {\n                                addRangeBackwardToNative(sel, originalSelectionRanges[i]);\n                            } else {\n                                api.warn(\"Rangy initialization: original selection was backwards but selection has been restored forwards because the browser does not support Selection.extend\");\n                                sel.addRange(originalSelectionRanges[i]);\n                            }\n                        } else {\n                            sel.addRange(originalSelectionRanges[i]);\n                        }\n                    }\n                }\n            })();\n        }\n\n        features.selectionSupportsMultipleRanges = selectionSupportsMultipleRanges;\n        features.collapsedNonEditableSelectionsSupported = collapsedNonEditableSelectionsSupported;\n\n        // ControlRanges\n        var implementsControlRange = false, testControlRange;\n\n        if (body && isHostMethod(body, \"createControlRange\")) {\n            testControlRange = body.createControlRange();\n            if (util.areHostProperties(testControlRange, [\"item\", \"add\"])) {\n                implementsControlRange = true;\n            }\n        }\n        features.implementsControlRange = implementsControlRange;\n\n        // Selection collapsedness\n        if (selectionHasAnchorAndFocus) {\n            selectionIsCollapsed = function(sel) {\n                return sel.anchorNode === sel.focusNode && sel.anchorOffset === sel.focusOffset;\n            };\n        } else {\n            selectionIsCollapsed = function(sel) {\n                return sel.rangeCount ? sel.getRangeAt(sel.rangeCount - 1).collapsed : false;\n            };\n        }\n\n        function updateAnchorAndFocusFromRange(sel, range, backward) {\n            var anchorPrefix = backward ? \"end\" : \"start\", focusPrefix = backward ? \"start\" : \"end\";\n            sel.anchorNode = range[anchorPrefix + \"Container\"];\n            sel.anchorOffset = range[anchorPrefix + \"Offset\"];\n            sel.focusNode = range[focusPrefix + \"Container\"];\n            sel.focusOffset = range[focusPrefix + \"Offset\"];\n        }\n\n        function updateAnchorAndFocusFromNativeSelection(sel) {\n            var nativeSel = sel.nativeSelection;\n            sel.anchorNode = nativeSel.anchorNode;\n            sel.anchorOffset = nativeSel.anchorOffset;\n            sel.focusNode = nativeSel.focusNode;\n            sel.focusOffset = nativeSel.focusOffset;\n        }\n\n        function updateEmptySelection(sel) {\n            sel.anchorNode = sel.focusNode = null;\n            sel.anchorOffset = sel.focusOffset = 0;\n            sel.rangeCount = 0;\n            sel.isCollapsed = true;\n            sel._ranges.length = 0;\n            updateType(sel);\n        }\n\n        function updateType(sel) {\n            sel.type = (sel.rangeCount == 0) ? \"None\" : (selectionIsCollapsed(sel) ? \"Caret\" : \"Range\");\n        }\n\n        function getNativeRange(range) {\n            var nativeRange;\n            if (range instanceof DomRange) {\n                nativeRange = api.createNativeRange(range.getDocument());\n                nativeRange.setEnd(range.endContainer, range.endOffset);\n                nativeRange.setStart(range.startContainer, range.startOffset);\n            } else if (range instanceof WrappedRange) {\n                nativeRange = range.nativeRange;\n            } else if (features.implementsDomRange && (range instanceof dom.getWindow(range.startContainer).Range)) {\n                nativeRange = range;\n            }\n            return nativeRange;\n        }\n\n        function rangeContainsSingleElement(rangeNodes) {\n            if (!rangeNodes.length || rangeNodes[0].nodeType != 1) {\n                return false;\n            }\n            for (var i = 1, len = rangeNodes.length; i < len; ++i) {\n                if (!dom.isAncestorOf(rangeNodes[0], rangeNodes[i])) {\n                    return false;\n                }\n            }\n            return true;\n        }\n\n        function getSingleElementFromRange(range) {\n            var nodes = range.getNodes();\n            if (!rangeContainsSingleElement(nodes)) {\n                throw module.createError(\"getSingleElementFromRange: range \" + range.inspect() + \" did not consist of a single element\");\n            }\n            return nodes[0];\n        }\n\n        // Simple, quick test which only needs to distinguish between a TextRange and a ControlRange\n        function isTextRange(range) {\n            return !!range && typeof range.text != \"undefined\";\n        }\n\n        function updateFromTextRange(sel, range) {\n            // Create a Range from the selected TextRange\n            var wrappedRange = new WrappedRange(range);\n            sel._ranges = [wrappedRange];\n\n            updateAnchorAndFocusFromRange(sel, wrappedRange, false);\n            sel.rangeCount = 1;\n            sel.isCollapsed = wrappedRange.collapsed;\n            updateType(sel);\n        }\n\n        function updateControlSelection(sel) {\n            // Update the wrapped selection based on what's now in the native selection\n            sel._ranges.length = 0;\n            if (sel.docSelection.type == \"None\") {\n                updateEmptySelection(sel);\n            } else {\n                var controlRange = sel.docSelection.createRange();\n                if (isTextRange(controlRange)) {\n                    // This case (where the selection type is \"Control\" and calling createRange() on the selection returns\n                    // a TextRange) can happen in IE 9. It happens, for example, when all elements in the selected\n                    // ControlRange have been removed from the ControlRange and removed from the document.\n                    updateFromTextRange(sel, controlRange);\n                } else {\n                    sel.rangeCount = controlRange.length;\n                    var range, doc = getDocument(controlRange.item(0));\n                    for (var i = 0; i < sel.rangeCount; ++i) {\n                        range = api.createRange(doc);\n                        range.selectNode(controlRange.item(i));\n                        sel._ranges.push(range);\n                    }\n                    sel.isCollapsed = sel.rangeCount == 1 && sel._ranges[0].collapsed;\n                    updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], false);\n                    updateType(sel);\n                }\n            }\n        }\n\n        function addRangeToControlSelection(sel, range) {\n            var controlRange = sel.docSelection.createRange();\n            var rangeElement = getSingleElementFromRange(range);\n\n            // Create a new ControlRange containing all the elements in the selected ControlRange plus the element\n            // contained by the supplied range\n            var doc = getDocument(controlRange.item(0));\n            var newControlRange = getBody(doc).createControlRange();\n            for (var i = 0, len = controlRange.length; i < len; ++i) {\n                newControlRange.add(controlRange.item(i));\n            }\n            try {\n                newControlRange.add(rangeElement);\n            } catch (ex) {\n                throw module.createError(\"addRange(): Element within the specified Range could not be added to control selection (does it have layout?)\");\n            }\n            newControlRange.select();\n\n            // Update the wrapped selection based on what's now in the native selection\n            updateControlSelection(sel);\n        }\n\n        var getSelectionRangeAt;\n\n        if (isHostMethod(testSelection, \"getRangeAt\")) {\n            // try/catch is present because getRangeAt() must have thrown an error in some browser and some situation.\n            // Unfortunately, I didn't write a comment about the specifics and am now scared to take it out. Let that be a\n            // lesson to us all, especially me.\n            getSelectionRangeAt = function(sel, index) {\n                try {\n                    return sel.getRangeAt(index);\n                } catch (ex) {\n                    return null;\n                }\n            };\n        } else if (selectionHasAnchorAndFocus) {\n            getSelectionRangeAt = function(sel) {\n                var doc = getDocument(sel.anchorNode);\n                var range = api.createRange(doc);\n                range.setStartAndEnd(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset);\n\n                // Handle the case when the selection was selected backwards (from the end to the start in the\n                // document)\n                if (range.collapsed !== this.isCollapsed) {\n                    range.setStartAndEnd(sel.focusNode, sel.focusOffset, sel.anchorNode, sel.anchorOffset);\n                }\n\n                return range;\n            };\n        }\n\n        function WrappedSelection(selection, docSelection, win) {\n            this.nativeSelection = selection;\n            this.docSelection = docSelection;\n            this._ranges = [];\n            this.win = win;\n            this.refresh();\n        }\n\n        WrappedSelection.prototype = api.selectionPrototype;\n\n        function deleteProperties(sel) {\n            sel.win = sel.anchorNode = sel.focusNode = sel._ranges = null;\n            sel.rangeCount = sel.anchorOffset = sel.focusOffset = 0;\n            sel.detached = true;\n            updateType(sel);\n        }\n\n        var cachedRangySelections = [];\n\n        function actOnCachedSelection(win, action) {\n            var i = cachedRangySelections.length, cached, sel;\n            while (i--) {\n                cached = cachedRangySelections[i];\n                sel = cached.selection;\n                if (action == \"deleteAll\") {\n                    deleteProperties(sel);\n                } else if (cached.win == win) {\n                    if (action == \"delete\") {\n                        cachedRangySelections.splice(i, 1);\n                        return true;\n                    } else {\n                        return sel;\n                    }\n                }\n            }\n            if (action == \"deleteAll\") {\n                cachedRangySelections.length = 0;\n            }\n            return null;\n        }\n\n        var getSelection = function(win) {\n            // Check if the parameter is a Rangy Selection object\n            if (win && win instanceof WrappedSelection) {\n                win.refresh();\n                return win;\n            }\n\n            win = getWindow(win, \"getNativeSelection\");\n\n            var sel = actOnCachedSelection(win);\n            var nativeSel = getNativeSelection(win), docSel = implementsDocSelection ? getDocSelection(win) : null;\n            if (sel) {\n                sel.nativeSelection = nativeSel;\n                sel.docSelection = docSel;\n                sel.refresh();\n            } else {\n                sel = new WrappedSelection(nativeSel, docSel, win);\n                cachedRangySelections.push( { win: win, selection: sel } );\n            }\n            return sel;\n        };\n\n        api.getSelection = getSelection;\n\n        util.createAliasForDeprecatedMethod(api, \"getIframeSelection\", \"getSelection\");\n\n        var selProto = WrappedSelection.prototype;\n\n        function createControlSelection(sel, ranges) {\n            // Ensure that the selection becomes of type \"Control\"\n            var doc = getDocument(ranges[0].startContainer);\n            var controlRange = getBody(doc).createControlRange();\n            for (var i = 0, el, len = ranges.length; i < len; ++i) {\n                el = getSingleElementFromRange(ranges[i]);\n                try {\n                    controlRange.add(el);\n                } catch (ex) {\n                    throw module.createError(\"setRanges(): Element within one of the specified Ranges could not be added to control selection (does it have layout?)\");\n                }\n            }\n            controlRange.select();\n\n            // Update the wrapped selection based on what's now in the native selection\n            updateControlSelection(sel);\n        }\n\n        // Selecting a range\n        if (!useDocumentSelection && selectionHasAnchorAndFocus && util.areHostMethods(testSelection, [\"removeAllRanges\", \"addRange\"])) {\n            selProto.removeAllRanges = function() {\n                this.nativeSelection.removeAllRanges();\n                updateEmptySelection(this);\n            };\n\n            var addRangeBackward = function(sel, range) {\n                addRangeBackwardToNative(sel.nativeSelection, range);\n                sel.refresh();\n            };\n\n            if (selectionHasRangeCount) {\n                selProto.addRange = function(range, direction) {\n                    if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {\n                        addRangeToControlSelection(this, range);\n                    } else {\n                        if (isDirectionBackward(direction) && selectionHasExtend) {\n                            addRangeBackward(this, range);\n                        } else {\n                            var previousRangeCount;\n                            if (selectionSupportsMultipleRanges) {\n                                previousRangeCount = this.rangeCount;\n                            } else {\n                                this.removeAllRanges();\n                                previousRangeCount = 0;\n                            }\n                            // Clone the native range so that changing the selected range does not affect the selection.\n                            // This is contrary to the spec but is the only way to achieve consistency between browsers. See\n                            // issue 80.\n                            var clonedNativeRange = getNativeRange(range).cloneRange();\n                            try {\n                                this.nativeSelection.addRange(clonedNativeRange);\n                            } catch (ex) {\n                            }\n\n                            // Check whether adding the range was successful\n                            this.rangeCount = this.nativeSelection.rangeCount;\n\n                            if (this.rangeCount == previousRangeCount + 1) {\n                                // The range was added successfully\n\n                                // Check whether the range that we added to the selection is reflected in the last range extracted from\n                                // the selection\n                                if (api.config.checkSelectionRanges) {\n                                    var nativeRange = getSelectionRangeAt(this.nativeSelection, this.rangeCount - 1);\n                                    if (nativeRange && !rangesEqual(nativeRange, range)) {\n                                        // Happens in WebKit with, for example, a selection placed at the start of a text node\n                                        range = new WrappedRange(nativeRange);\n                                    }\n                                }\n                                this._ranges[this.rangeCount - 1] = range;\n                                updateAnchorAndFocusFromRange(this, range, selectionIsBackward(this.nativeSelection));\n                                this.isCollapsed = selectionIsCollapsed(this);\n                                updateType(this);\n                            } else {\n                                // The range was not added successfully. The simplest thing is to refresh\n                                this.refresh();\n                            }\n                        }\n                    }\n                };\n            } else {\n                selProto.addRange = function(range, direction) {\n                    if (isDirectionBackward(direction) && selectionHasExtend) {\n                        addRangeBackward(this, range);\n                    } else {\n                        this.nativeSelection.addRange(getNativeRange(range));\n                        this.refresh();\n                    }\n                };\n            }\n\n            selProto.setRanges = function(ranges) {\n                if (implementsControlRange && implementsDocSelection && ranges.length > 1) {\n                    createControlSelection(this, ranges);\n                } else {\n                    this.removeAllRanges();\n                    for (var i = 0, len = ranges.length; i < len; ++i) {\n                        this.addRange(ranges[i]);\n                    }\n                }\n            };\n        } else if (isHostMethod(testSelection, \"empty\") && isHostMethod(testRange, \"select\") &&\n                   implementsControlRange && useDocumentSelection) {\n\n            selProto.removeAllRanges = function() {\n                // Added try/catch as fix for issue #21\n                try {\n                    this.docSelection.empty();\n\n                    // Check for empty() not working (issue #24)\n                    if (this.docSelection.type != \"None\") {\n                        // Work around failure to empty a control selection by instead selecting a TextRange and then\n                        // calling empty()\n                        var doc;\n                        if (this.anchorNode) {\n                            doc = getDocument(this.anchorNode);\n                        } else if (this.docSelection.type == CONTROL) {\n                            var controlRange = this.docSelection.createRange();\n                            if (controlRange.length) {\n                                doc = getDocument( controlRange.item(0) );\n                            }\n                        }\n                        if (doc) {\n                            var textRange = getBody(doc).createTextRange();\n                            textRange.select();\n                            this.docSelection.empty();\n                        }\n                    }\n                } catch(ex) {}\n                updateEmptySelection(this);\n            };\n\n            selProto.addRange = function(range) {\n                if (this.docSelection.type == CONTROL) {\n                    addRangeToControlSelection(this, range);\n                } else {\n                    api.WrappedTextRange.rangeToTextRange(range).select();\n                    this._ranges[0] = range;\n                    this.rangeCount = 1;\n                    this.isCollapsed = this._ranges[0].collapsed;\n                    updateAnchorAndFocusFromRange(this, range, false);\n                    updateType(this);\n                }\n            };\n\n            selProto.setRanges = function(ranges) {\n                this.removeAllRanges();\n                var rangeCount = ranges.length;\n                if (rangeCount > 1) {\n                    createControlSelection(this, ranges);\n                } else if (rangeCount) {\n                    this.addRange(ranges[0]);\n                }\n            };\n        } else {\n            module.fail(\"No means of selecting a Range or TextRange was found\");\n            return false;\n        }\n\n        selProto.getRangeAt = function(index) {\n            if (index < 0 || index >= this.rangeCount) {\n                throw new DOMException(\"INDEX_SIZE_ERR\");\n            } else {\n                // Clone the range to preserve selection-range independence. See issue 80.\n                return this._ranges[index].cloneRange();\n            }\n        };\n\n        var refreshSelection;\n\n        if (useDocumentSelection) {\n            refreshSelection = function(sel) {\n                var range;\n                if (api.isSelectionValid(sel.win)) {\n                    range = sel.docSelection.createRange();\n                } else {\n                    range = getBody(sel.win.document).createTextRange();\n                    range.collapse(true);\n                }\n\n                if (sel.docSelection.type == CONTROL) {\n                    updateControlSelection(sel);\n                } else if (isTextRange(range)) {\n                    updateFromTextRange(sel, range);\n                } else {\n                    updateEmptySelection(sel);\n                }\n            };\n        } else if (isHostMethod(testSelection, \"getRangeAt\") && typeof testSelection.rangeCount == NUMBER) {\n            refreshSelection = function(sel) {\n                if (implementsControlRange && implementsDocSelection && sel.docSelection.type == CONTROL) {\n                    updateControlSelection(sel);\n                } else {\n                    sel._ranges.length = sel.rangeCount = sel.nativeSelection.rangeCount;\n                    if (sel.rangeCount) {\n                        for (var i = 0, len = sel.rangeCount; i < len; ++i) {\n                            sel._ranges[i] = new api.WrappedRange(sel.nativeSelection.getRangeAt(i));\n                        }\n                        updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], selectionIsBackward(sel.nativeSelection));\n                        sel.isCollapsed = selectionIsCollapsed(sel);\n                        updateType(sel);\n                    } else {\n                        updateEmptySelection(sel);\n                    }\n                }\n            };\n        } else if (selectionHasAnchorAndFocus && typeof testSelection.isCollapsed == BOOLEAN && typeof testRange.collapsed == BOOLEAN && features.implementsDomRange) {\n            refreshSelection = function(sel) {\n                var range, nativeSel = sel.nativeSelection;\n                if (nativeSel.anchorNode) {\n                    range = getSelectionRangeAt(nativeSel, 0);\n                    sel._ranges = [range];\n                    sel.rangeCount = 1;\n                    updateAnchorAndFocusFromNativeSelection(sel);\n                    sel.isCollapsed = selectionIsCollapsed(sel);\n                    updateType(sel);\n                } else {\n                    updateEmptySelection(sel);\n                }\n            };\n        } else {\n            module.fail(\"No means of obtaining a Range or TextRange from the user's selection was found\");\n            return false;\n        }\n\n        selProto.refresh = function(checkForChanges) {\n            var oldRanges = checkForChanges ? this._ranges.slice(0) : null;\n            var oldAnchorNode = this.anchorNode, oldAnchorOffset = this.anchorOffset;\n\n            refreshSelection(this);\n            if (checkForChanges) {\n                // Check the range count first\n                var i = oldRanges.length;\n                if (i != this._ranges.length) {\n                    return true;\n                }\n\n                // Now check the direction. Checking the anchor position is the same is enough since we're checking all the\n                // ranges after this\n                if (this.anchorNode != oldAnchorNode || this.anchorOffset != oldAnchorOffset) {\n                    return true;\n                }\n\n                // Finally, compare each range in turn\n                while (i--) {\n                    if (!rangesEqual(oldRanges[i], this._ranges[i])) {\n                        return true;\n                    }\n                }\n                return false;\n            }\n        };\n\n        // Removal of a single range\n        var removeRangeManually = function(sel, range) {\n            var ranges = sel.getAllRanges();\n            sel.removeAllRanges();\n            for (var i = 0, len = ranges.length; i < len; ++i) {\n                if (!rangesEqual(range, ranges[i])) {\n                    sel.addRange(ranges[i]);\n                }\n            }\n            if (!sel.rangeCount) {\n                updateEmptySelection(sel);\n            }\n        };\n\n        if (implementsControlRange && implementsDocSelection) {\n            selProto.removeRange = function(range) {\n                if (this.docSelection.type == CONTROL) {\n                    var controlRange = this.docSelection.createRange();\n                    var rangeElement = getSingleElementFromRange(range);\n\n                    // Create a new ControlRange containing all the elements in the selected ControlRange minus the\n                    // element contained by the supplied range\n                    var doc = getDocument(controlRange.item(0));\n                    var newControlRange = getBody(doc).createControlRange();\n                    var el, removed = false;\n                    for (var i = 0, len = controlRange.length; i < len; ++i) {\n                        el = controlRange.item(i);\n                        if (el !== rangeElement || removed) {\n                            newControlRange.add(controlRange.item(i));\n                        } else {\n                            removed = true;\n                        }\n                    }\n                    newControlRange.select();\n\n                    // Update the wrapped selection based on what's now in the native selection\n                    updateControlSelection(this);\n                } else {\n                    removeRangeManually(this, range);\n                }\n            };\n        } else {\n            selProto.removeRange = function(range) {\n                removeRangeManually(this, range);\n            };\n        }\n\n        // Detecting if a selection is backward\n        var selectionIsBackward;\n        if (!useDocumentSelection && selectionHasAnchorAndFocus && features.implementsDomRange) {\n            selectionIsBackward = winSelectionIsBackward;\n\n            selProto.isBackward = function() {\n                return selectionIsBackward(this);\n            };\n        } else {\n            selectionIsBackward = selProto.isBackward = function() {\n                return false;\n            };\n        }\n\n        // Create an alias for backwards compatibility. From 1.3, everything is \"backward\" rather than \"backwards\"\n        selProto.isBackwards = selProto.isBackward;\n\n        // Selection stringifier\n        // This is conformant to the old HTML5 selections draft spec but differs from WebKit and Mozilla's implementation.\n        // The current spec does not yet define this method.\n        selProto.toString = function() {\n            var rangeTexts = [];\n            for (var i = 0, len = this.rangeCount; i < len; ++i) {\n                rangeTexts[i] = \"\" + this._ranges[i];\n            }\n            return rangeTexts.join(\"\");\n        };\n\n        function assertNodeInSameDocument(sel, node) {\n            if (sel.win.document != getDocument(node)) {\n                throw new DOMException(\"WRONG_DOCUMENT_ERR\");\n            }\n        }\n\n        function assertValidOffset(node, offset) {\n            if (offset < 0 || offset > (dom.isCharacterDataNode(node) ? node.length : node.childNodes.length)) {\n                throw new DOMException(\"INDEX_SIZE_ERR\");\n            }\n        }\n\n        // No current browser conforms fully to the spec for this method, so Rangy's own method is always used\n        selProto.collapse = function(node, offset) {\n            assertNodeInSameDocument(this, node);\n            var range = api.createRange(node);\n            range.collapseToPoint(node, offset);\n            this.setSingleRange(range);\n            this.isCollapsed = true;\n        };\n\n        selProto.collapseToStart = function() {\n            if (this.rangeCount) {\n                var range = this._ranges[0];\n                this.collapse(range.startContainer, range.startOffset);\n            } else {\n                throw new DOMException(\"INVALID_STATE_ERR\");\n            }\n        };\n\n        selProto.collapseToEnd = function() {\n            if (this.rangeCount) {\n                var range = this._ranges[this.rangeCount - 1];\n                this.collapse(range.endContainer, range.endOffset);\n            } else {\n                throw new DOMException(\"INVALID_STATE_ERR\");\n            }\n        };\n\n        // The spec is very specific on how selectAllChildren should be implemented and not all browsers implement it as\n        // specified so the native implementation is never used by Rangy.\n        selProto.selectAllChildren = function(node) {\n            assertNodeInSameDocument(this, node);\n            var range = api.createRange(node);\n            range.selectNodeContents(node);\n            this.setSingleRange(range);\n        };\n\n        if (selectionHasSetBaseAndExtent) {\n            selProto.setBaseAndExtent = function(anchorNode, anchorOffset, focusNode, focusOffset) {\n                this.nativeSelection.setBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset);\n                this.refresh();\n            };\n        } else if (selectionHasExtend) {\n            selProto.setBaseAndExtent = function(anchorNode, anchorOffset, focusNode, focusOffset) {\n                assertValidOffset(anchorNode, anchorOffset);\n                assertValidOffset(focusNode, focusOffset);\n                assertNodeInSameDocument(this, anchorNode);\n                assertNodeInSameDocument(this, focusNode);\n                var range = api.createRange(node);\n                var isBackwards = (dom.comparePoints(anchorNode, anchorOffset, focusNode, focusOffset) == -1);\n                if (isBackwards) {\n                    range.setStartAndEnd(focusNode, focusOffset, anchorNode, anchorOffset);\n                } else {\n                    range.setStartAndEnd(anchorNode, anchorOffset, focusNode, focusOffset);\n                }\n                this.setSingleRange(range, isBackwards);\n            };\n        }\n\n        selProto.deleteFromDocument = function() {\n            // Sepcial behaviour required for IE's control selections\n            if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {\n                var controlRange = this.docSelection.createRange();\n                var element;\n                while (controlRange.length) {\n                    element = controlRange.item(0);\n                    controlRange.remove(element);\n                    dom.removeNode(element);\n                }\n                this.refresh();\n            } else if (this.rangeCount) {\n                var ranges = this.getAllRanges();\n                if (ranges.length) {\n                    this.removeAllRanges();\n                    for (var i = 0, len = ranges.length; i < len; ++i) {\n                        ranges[i].deleteContents();\n                    }\n                    // The spec says nothing about what the selection should contain after calling deleteContents on each\n                    // range. Firefox moves the selection to where the final selected range was, so we emulate that\n                    this.addRange(ranges[len - 1]);\n                }\n            }\n        };\n\n        // The following are non-standard extensions\n        selProto.eachRange = function(func, returnValue) {\n            for (var i = 0, len = this._ranges.length; i < len; ++i) {\n                if ( func( this.getRangeAt(i) ) ) {\n                    return returnValue;\n                }\n            }\n        };\n\n        selProto.getAllRanges = function() {\n            var ranges = [];\n            this.eachRange(function(range) {\n                ranges.push(range);\n            });\n            return ranges;\n        };\n\n        selProto.setSingleRange = function(range, direction) {\n            this.removeAllRanges();\n            this.addRange(range, direction);\n        };\n\n        selProto.callMethodOnEachRange = function(methodName, params) {\n            var results = [];\n            this.eachRange( function(range) {\n                results.push( range[methodName].apply(range, params || []) );\n            } );\n            return results;\n        };\n\n        function createStartOrEndSetter(isStart) {\n            return function(node, offset) {\n                var range;\n                if (this.rangeCount) {\n                    range = this.getRangeAt(0);\n                    range[\"set\" + (isStart ? \"Start\" : \"End\")](node, offset);\n                } else {\n                    range = api.createRange(this.win.document);\n                    range.setStartAndEnd(node, offset);\n                }\n                this.setSingleRange(range, this.isBackward());\n            };\n        }\n\n        selProto.setStart = createStartOrEndSetter(true);\n        selProto.setEnd = createStartOrEndSetter(false);\n\n        // Add select() method to Range prototype. Any existing selection will be removed.\n        api.rangePrototype.select = function(direction) {\n            getSelection( this.getDocument() ).setSingleRange(this, direction);\n        };\n\n        selProto.changeEachRange = function(func) {\n            var ranges = [];\n            var backward = this.isBackward();\n\n            this.eachRange(function(range) {\n                func(range);\n                ranges.push(range);\n            });\n\n            this.removeAllRanges();\n            if (backward && ranges.length == 1) {\n                this.addRange(ranges[0], \"backward\");\n            } else {\n                this.setRanges(ranges);\n            }\n        };\n\n        selProto.containsNode = function(node, allowPartial) {\n            return this.eachRange( function(range) {\n                return range.containsNode(node, allowPartial);\n            }, true ) || false;\n        };\n\n        selProto.getBookmark = function(containerNode) {\n            return {\n                backward: this.isBackward(),\n                rangeBookmarks: this.callMethodOnEachRange(\"getBookmark\", [containerNode])\n            };\n        };\n\n        selProto.moveToBookmark = function(bookmark) {\n            var selRanges = [];\n            for (var i = 0, rangeBookmark, range; rangeBookmark = bookmark.rangeBookmarks[i++]; ) {\n                range = api.createRange(this.win);\n                range.moveToBookmark(rangeBookmark);\n                selRanges.push(range);\n            }\n            if (bookmark.backward) {\n                this.setSingleRange(selRanges[0], \"backward\");\n            } else {\n                this.setRanges(selRanges);\n            }\n        };\n\n        selProto.saveRanges = function() {\n            return {\n                backward: this.isBackward(),\n                ranges: this.callMethodOnEachRange(\"cloneRange\")\n            };\n        };\n\n        selProto.restoreRanges = function(selRanges) {\n            this.removeAllRanges();\n            for (var i = 0, range; range = selRanges.ranges[i]; ++i) {\n                this.addRange(range, (selRanges.backward && i == 0));\n            }\n        };\n\n        selProto.toHtml = function() {\n            var rangeHtmls = [];\n            this.eachRange(function(range) {\n                rangeHtmls.push( DomRange.toHtml(range) );\n            });\n            return rangeHtmls.join(\"\");\n        };\n\n        if (features.implementsTextRange) {\n            selProto.getNativeTextRange = function() {\n                var sel, textRange;\n                if ( (sel = this.docSelection) ) {\n                    var range = sel.createRange();\n                    if (isTextRange(range)) {\n                        return range;\n                    } else {\n                        throw module.createError(\"getNativeTextRange: selection is a control selection\");\n                    }\n                } else if (this.rangeCount > 0) {\n                    return api.WrappedTextRange.rangeToTextRange( this.getRangeAt(0) );\n                } else {\n                    throw module.createError(\"getNativeTextRange: selection contains no range\");\n                }\n            };\n        }\n\n        function inspect(sel) {\n            var rangeInspects = [];\n            var anchor = new DomPosition(sel.anchorNode, sel.anchorOffset);\n            var focus = new DomPosition(sel.focusNode, sel.focusOffset);\n            var name = (typeof sel.getName == \"function\") ? sel.getName() : \"Selection\";\n\n            if (typeof sel.rangeCount != \"undefined\") {\n                for (var i = 0, len = sel.rangeCount; i < len; ++i) {\n                    rangeInspects[i] = DomRange.inspect(sel.getRangeAt(i));\n                }\n            }\n            return \"[\" + name + \"(Ranges: \" + rangeInspects.join(\", \") +\n                    \")(anchor: \" + anchor.inspect() + \", focus: \" + focus.inspect() + \"]\";\n        }\n\n        selProto.getName = function() {\n            return \"WrappedSelection\";\n        };\n\n        selProto.inspect = function() {\n            return inspect(this);\n        };\n\n        selProto.detach = function() {\n            actOnCachedSelection(this.win, \"delete\");\n            deleteProperties(this);\n        };\n\n        WrappedSelection.detachAll = function() {\n            actOnCachedSelection(null, \"deleteAll\");\n        };\n\n        WrappedSelection.inspect = inspect;\n        WrappedSelection.isDirectionBackward = isDirectionBackward;\n\n        api.Selection = WrappedSelection;\n\n        api.selectionPrototype = selProto;\n\n        api.addShimListener(function(win) {\n            if (typeof win.getSelection == \"undefined\") {\n                win.getSelection = function() {\n                    return getSelection(win);\n                };\n            }\n            win = null;\n        });\n    });\n    \n\n    /*----------------------------------------------------------------------------------------------------------------*/\n\n    // Wait for document to load before initializing\n    var docReady = false;\n\n    var loadHandler = function(e) {\n        if (!docReady) {\n            docReady = true;\n            if (!api.initialized && api.config.autoInitialize) {\n                init();\n            }\n        }\n    };\n\n    if (isBrowser) {\n        // Test whether the document has already been loaded and initialize immediately if so\n        if (document.readyState == \"complete\") {\n            loadHandler();\n        } else {\n            if (isHostMethod(document, \"addEventListener\")) {\n                document.addEventListener(\"DOMContentLoaded\", loadHandler, false);\n            }\n\n            // Add a fallback in case the DOMContentLoaded event isn't supported\n            addListener(window, \"load\", loadHandler);\n        }\n    }\n\n    return api;\n}, this);", "/**\n * Serializer module for Rangy.\n * Serializes Ranges and Selections. An example use would be to store a user's selection on a particular page in a\n * cookie or local storage and restore it on the user's next visit to the same page.\n *\n * Part of Rangy, a cross-browser JavaScript range and selection library\n * https://github.com/timdown/rangy\n *\n * Depends on Rangy core.\n *\n * Copyright 2022, Tim Down\n * Licensed under the MIT license.\n * Version: 1.3.1\n * Build date: 17 August 2022\n */\n(function(factory, root) {\n    if (typeof define == \"function\" && define.amd) {\n        // AMD. Register as an anonymous module with a dependency on Rangy.\n        define([\"./rangy-core\"], factory);\n    } else if (typeof module != \"undefined\" && typeof exports == \"object\") {\n        // Node/CommonJS style\n        module.exports = factory( require(\"rangy\") );\n    } else {\n        // No AMD or CommonJS support so we use the rangy property of root (probably the global variable)\n        factory(root.rangy);\n    }\n})(function(rangy) {\n    rangy.createModule(\"Serializer\", [\"WrappedSelection\"], function(api, module) {\n        var UNDEF = \"undefined\";\n        var util = api.util;\n\n        // encodeURIComponent and decodeURIComponent are required for cookie handling\n        if (typeof encodeURIComponent == UNDEF || typeof decodeURIComponent == UNDEF) {\n            module.fail(\"encodeURIComponent and/or decodeURIComponent method is missing\");\n        }\n\n        // Checksum for checking whether range can be serialized\n        var crc32 = (function() {\n            function utf8encode(str) {\n                var utf8CharCodes = [];\n\n                for (var i = 0, len = str.length, c; i < len; ++i) {\n                    c = str.charCodeAt(i);\n                    if (c < 128) {\n                        utf8CharCodes.push(c);\n                    } else if (c < 2048) {\n                        utf8CharCodes.push((c >> 6) | 192, (c & 63) | 128);\n                    } else {\n                        utf8CharCodes.push((c >> 12) | 224, ((c >> 6) & 63) | 128, (c & 63) | 128);\n                    }\n                }\n                return utf8CharCodes;\n            }\n\n            var cachedCrcTable = null;\n\n            function buildCRCTable() {\n                var table = [];\n                for (var i = 0, j, crc; i < 256; ++i) {\n                    crc = i;\n                    j = 8;\n                    while (j--) {\n                        if ((crc & 1) == 1) {\n                            crc = (crc >>> 1) ^ 0xEDB88320;\n                        } else {\n                            crc >>>= 1;\n                        }\n                    }\n                    table[i] = crc >>> 0;\n                }\n                return table;\n            }\n\n            function getCrcTable() {\n                if (!cachedCrcTable) {\n                    cachedCrcTable = buildCRCTable();\n                }\n                return cachedCrcTable;\n            }\n\n            return function(str) {\n                var utf8CharCodes = utf8encode(str), crc = -1, crcTable = getCrcTable();\n                for (var i = 0, len = utf8CharCodes.length, y; i < len; ++i) {\n                    y = (crc ^ utf8CharCodes[i]) & 0xFF;\n                    crc = (crc >>> 8) ^ crcTable[y];\n                }\n                return (crc ^ -1) >>> 0;\n            };\n        })();\n\n        var dom = api.dom;\n\n        function escapeTextForHtml(str) {\n            return str.replace(//g, \">\");\n        }\n\n        function nodeToInfoString(node, infoParts) {\n            infoParts = infoParts || [];\n            var nodeType = node.nodeType, children = node.childNodes, childCount = children.length;\n            var nodeInfo = [nodeType, node.nodeName, childCount].join(\":\");\n            var start = \"\", end = \"\";\n            switch (nodeType) {\n                case 3: // Text node\n                    start = escapeTextForHtml(node.nodeValue);\n                    break;\n                case 8: // Comment\n                    start = \"\";\n                    break;\n                default:\n                    start = \"<\" + nodeInfo + \">\";\n                    end = \"\";\n                    break;\n            }\n            if (start) {\n                infoParts.push(start);\n            }\n            for (var i = 0; i < childCount; ++i) {\n                nodeToInfoString(children[i], infoParts);\n            }\n            if (end) {\n                infoParts.push(end);\n            }\n            return infoParts;\n        }\n\n        // Creates a string representation of the specified element's contents that is similar to innerHTML but omits all\n        // attributes and comments and includes child node counts. This is done instead of using innerHTML to work around\n        // IE <= 8's policy of including element properties in attributes, which ruins things by changing an element's\n        // innerHTML whenever the user changes an input within the element.\n        function getElementChecksum(el) {\n            var info = nodeToInfoString(el).join(\"\");\n            return crc32(info).toString(16);\n        }\n\n        function serializePosition(node, offset, rootNode) {\n            var pathParts = [], n = node;\n            rootNode = rootNode || dom.getDocument(node).documentElement;\n            while (n && n != rootNode) {\n                pathParts.push(dom.getNodeIndex(n, true));\n                n = n.parentNode;\n            }\n            return pathParts.join(\"/\") + \":\" + offset;\n        }\n\n        function deserializePosition(serialized, rootNode, doc) {\n            if (!rootNode) {\n                rootNode = (doc || document).documentElement;\n            }\n            var parts = serialized.split(\":\");\n            var node = rootNode;\n            var nodeIndices = parts[0] ? parts[0].split(\"/\") : [], i = nodeIndices.length, nodeIndex;\n\n            while (i--) {\n                nodeIndex = parseInt(nodeIndices[i], 10);\n                if (nodeIndex < node.childNodes.length) {\n                    node = node.childNodes[nodeIndex];\n                } else {\n                    throw module.createError(\"deserializePosition() failed: node \" + dom.inspectNode(node) +\n                            \" has no child with index \" + nodeIndex + \", \" + i);\n                }\n            }\n\n            return new dom.DomPosition(node, parseInt(parts[1], 10));\n        }\n\n        function serializeRange(range, omitChecksum, rootNode) {\n            rootNode = rootNode || api.DomRange.getRangeDocument(range).documentElement;\n            if (!dom.isOrIsAncestorOf(rootNode, range.commonAncestorContainer)) {\n                throw module.createError(\"serializeRange(): range \" + range.inspect() +\n                    \" is not wholly contained within specified root node \" + dom.inspectNode(rootNode));\n            }\n            var serialized = serializePosition(range.startContainer, range.startOffset, rootNode) + \",\" +\n                serializePosition(range.endContainer, range.endOffset, rootNode);\n            if (!omitChecksum) {\n                serialized += \"{\" + getElementChecksum(rootNode) + \"}\";\n            }\n            return serialized;\n        }\n\n        var deserializeRegex = /^([^,]+),([^,\\{]+)(\\{([^}]+)\\})?$/;\n\n        function deserializeRange(serialized, rootNode, doc) {\n            if (rootNode) {\n                doc = doc || dom.getDocument(rootNode);\n            } else {\n                doc = doc || document;\n                rootNode = doc.documentElement;\n            }\n            var result = deserializeRegex.exec(serialized);\n            var checksum = result[4];\n            if (checksum) {\n                var rootNodeChecksum = getElementChecksum(rootNode);\n                if (checksum !== rootNodeChecksum) {\n                    throw module.createError(\"deserializeRange(): checksums of serialized range root node (\" + checksum +\n                        \") and target root node (\" + rootNodeChecksum + \") do not match\");\n                }\n            }\n            var start = deserializePosition(result[1], rootNode, doc), end = deserializePosition(result[2], rootNode, doc);\n            var range = api.createRange(doc);\n            range.setStartAndEnd(start.node, start.offset, end.node, end.offset);\n            return range;\n        }\n\n        function canDeserializeRange(serialized, rootNode, doc) {\n            if (!rootNode) {\n                rootNode = (doc || document).documentElement;\n            }\n            var result = deserializeRegex.exec(serialized);\n            var checksum = result[3];\n            return !checksum || checksum === getElementChecksum(rootNode);\n        }\n\n        function serializeSelection(selection, omitChecksum, rootNode) {\n            selection = api.getSelection(selection);\n            var ranges = selection.getAllRanges(), serializedRanges = [];\n            for (var i = 0, len = ranges.length; i < len; ++i) {\n                serializedRanges[i] = serializeRange(ranges[i], omitChecksum, rootNode);\n            }\n            return serializedRanges.join(\"|\");\n        }\n\n        function deserializeSelection(serialized, rootNode, win) {\n            if (rootNode) {\n                win = win || dom.getWindow(rootNode);\n            } else {\n                win = win || window;\n                rootNode = win.document.documentElement;\n            }\n            var serializedRanges = serialized.split(\"|\");\n            var sel = api.getSelection(win);\n            var ranges = [];\n\n            for (var i = 0, len = serializedRanges.length; i < len; ++i) {\n                ranges[i] = deserializeRange(serializedRanges[i], rootNode, win.document);\n            }\n            sel.setRanges(ranges);\n\n            return sel;\n        }\n\n        function canDeserializeSelection(serialized, rootNode, win) {\n            var doc;\n            if (rootNode) {\n                doc = win ? win.document : dom.getDocument(rootNode);\n            } else {\n                win = win || window;\n                rootNode = win.document.documentElement;\n            }\n            var serializedRanges = serialized.split(\"|\");\n\n            for (var i = 0, len = serializedRanges.length; i < len; ++i) {\n                if (!canDeserializeRange(serializedRanges[i], rootNode, doc)) {\n                    return false;\n                }\n            }\n\n            return true;\n        }\n\n        var cookieName = \"rangySerializedSelection\";\n\n        function getSerializedSelectionFromCookie(cookie) {\n            var parts = cookie.split(/[;,]/);\n            for (var i = 0, len = parts.length, nameVal, val; i < len; ++i) {\n                nameVal = parts[i].split(\"=\");\n                if (nameVal[0].replace(/^\\s+/, \"\") == cookieName) {\n                    val = nameVal[1];\n                    if (val) {\n                        return decodeURIComponent(val.replace(/\\s+$/, \"\"));\n                    }\n                }\n            }\n            return null;\n        }\n\n        function restoreSelectionFromCookie(win) {\n            win = win || window;\n            var serialized = getSerializedSelectionFromCookie(win.document.cookie);\n            if (serialized) {\n                deserializeSelection(serialized, win.doc);\n            }\n        }\n\n        function saveSelectionCookie(win, props) {\n            win = win || window;\n            props = (typeof props == \"object\") ? props : {};\n            var expires = props.expires ? \";expires=\" + props.expires.toUTCString() : \"\";\n            var path = props.path ? \";path=\" + props.path : \"\";\n            var domain = props.domain ? \";domain=\" + props.domain : \"\";\n            var secure = props.secure ? \";secure\" : \"\";\n            var serialized = serializeSelection(api.getSelection(win));\n            win.document.cookie = encodeURIComponent(cookieName) + \"=\" + encodeURIComponent(serialized) + expires + path + domain + secure;\n        }\n\n        util.extend(api, {\n            serializePosition: serializePosition,\n            deserializePosition: deserializePosition,\n            serializeRange: serializeRange,\n            deserializeRange: deserializeRange,\n            canDeserializeRange: canDeserializeRange,\n            serializeSelection: serializeSelection,\n            deserializeSelection: deserializeSelection,\n            canDeserializeSelection: canDeserializeSelection,\n            restoreSelectionFromCookie: restoreSelectionFromCookie,\n            saveSelectionCookie: saveSelectionCookie,\n            getElementChecksum: getElementChecksum,\n            nodeToInfoString: nodeToInfoString\n        });\n\n        util.crc32 = crc32;\n    });\n    \n    return rangy;\n}, this);", "/* eslint-disable no-multi-assign */\n\nfunction deepFreeze(obj) {\n  if (obj instanceof Map) {\n    obj.clear =\n      obj.delete =\n      obj.set =\n        function () {\n          throw new Error('map is read-only');\n        };\n  } else if (obj instanceof Set) {\n    obj.add =\n      obj.clear =\n      obj.delete =\n        function () {\n          throw new Error('set is read-only');\n        };\n  }\n\n  // Freeze self\n  Object.freeze(obj);\n\n  Object.getOwnPropertyNames(obj).forEach((name) => {\n    const prop = obj[name];\n    const type = typeof prop;\n\n    // Freeze prop if it is an object or function and also not already frozen\n    if ((type === 'object' || type === 'function') && !Object.isFrozen(prop)) {\n      deepFreeze(prop);\n    }\n  });\n\n  return obj;\n}\n\n/** @typedef {import('highlight.js').CallbackResponse} CallbackResponse */\n/** @typedef {import('highlight.js').CompiledMode} CompiledMode */\n/** @implements CallbackResponse */\n\nclass Response {\n  /**\n   * @param {CompiledMode} mode\n   */\n  constructor(mode) {\n    // eslint-disable-next-line no-undefined\n    if (mode.data === undefined) mode.data = {};\n\n    this.data = mode.data;\n    this.isMatchIgnored = false;\n  }\n\n  ignoreMatch() {\n    this.isMatchIgnored = true;\n  }\n}\n\n/**\n * @param {string} value\n * @returns {string}\n */\nfunction escapeHTML(value) {\n  return value\n    .replace(/&/g, '&')\n    .replace(//g, '>')\n    .replace(/\"/g, '"')\n    .replace(/'/g, ''');\n}\n\n/**\n * performs a shallow merge of multiple objects into one\n *\n * @template T\n * @param {T} original\n * @param {Record[]} objects\n * @returns {T} a single new object\n */\nfunction inherit$1(original, ...objects) {\n  /** @type Record */\n  const result = Object.create(null);\n\n  for (const key in original) {\n    result[key] = original[key];\n  }\n  objects.forEach(function(obj) {\n    for (const key in obj) {\n      result[key] = obj[key];\n    }\n  });\n  return /** @type {T} */ (result);\n}\n\n/**\n * @typedef {object} Renderer\n * @property {(text: string) => void} addText\n * @property {(node: Node) => void} openNode\n * @property {(node: Node) => void} closeNode\n * @property {() => string} value\n */\n\n/** @typedef {{scope?: string, language?: string, sublanguage?: boolean}} Node */\n/** @typedef {{walk: (r: Renderer) => void}} Tree */\n/** */\n\nconst SPAN_CLOSE = '';\n\n/**\n * Determines if a node needs to be wrapped in \n *\n * @param {Node} node */\nconst emitsWrappingTags = (node) => {\n  // rarely we can have a sublanguage where language is undefined\n  // TODO: track down why\n  return !!node.scope;\n};\n\n/**\n *\n * @param {string} name\n * @param {{prefix:string}} options\n */\nconst scopeToCSSClass = (name, { prefix }) => {\n  // sub-language\n  if (name.startsWith(\"language:\")) {\n    return name.replace(\"language:\", \"language-\");\n  }\n  // tiered scope: comment.line\n  if (name.includes(\".\")) {\n    const pieces = name.split(\".\");\n    return [\n      `${prefix}${pieces.shift()}`,\n      ...(pieces.map((x, i) => `${x}${\"_\".repeat(i + 1)}`))\n    ].join(\" \");\n  }\n  // simple scope\n  return `${prefix}${name}`;\n};\n\n/** @type {Renderer} */\nclass HTMLRenderer {\n  /**\n   * Creates a new HTMLRenderer\n   *\n   * @param {Tree} parseTree - the parse tree (must support `walk` API)\n   * @param {{classPrefix: string}} options\n   */\n  constructor(parseTree, options) {\n    this.buffer = \"\";\n    this.classPrefix = options.classPrefix;\n    parseTree.walk(this);\n  }\n\n  /**\n   * Adds texts to the output stream\n   *\n   * @param {string} text */\n  addText(text) {\n    this.buffer += escapeHTML(text);\n  }\n\n  /**\n   * Adds a node open to the output stream (if needed)\n   *\n   * @param {Node} node */\n  openNode(node) {\n    if (!emitsWrappingTags(node)) return;\n\n    const className = scopeToCSSClass(node.scope,\n      { prefix: this.classPrefix });\n    this.span(className);\n  }\n\n  /**\n   * Adds a node close to the output stream (if needed)\n   *\n   * @param {Node} node */\n  closeNode(node) {\n    if (!emitsWrappingTags(node)) return;\n\n    this.buffer += SPAN_CLOSE;\n  }\n\n  /**\n   * returns the accumulated buffer\n  */\n  value() {\n    return this.buffer;\n  }\n\n  // helpers\n\n  /**\n   * Builds a span element\n   *\n   * @param {string} className */\n  span(className) {\n    this.buffer += ``;\n  }\n}\n\n/** @typedef {{scope?: string, language?: string, sublanguage?: boolean, children: Node[]} | string} Node */\n/** @typedef {{scope?: string, language?: string, sublanguage?: boolean, children: Node[]} } DataNode */\n/** @typedef {import('highlight.js').Emitter} Emitter */\n/**  */\n\n/** @returns {DataNode} */\nconst newNode = (opts = {}) => {\n  /** @type DataNode */\n  const result = { children: [] };\n  Object.assign(result, opts);\n  return result;\n};\n\nclass TokenTree {\n  constructor() {\n    /** @type DataNode */\n    this.rootNode = newNode();\n    this.stack = [this.rootNode];\n  }\n\n  get top() {\n    return this.stack[this.stack.length - 1];\n  }\n\n  get root() { return this.rootNode; }\n\n  /** @param {Node} node */\n  add(node) {\n    this.top.children.push(node);\n  }\n\n  /** @param {string} scope */\n  openNode(scope) {\n    /** @type Node */\n    const node = newNode({ scope });\n    this.add(node);\n    this.stack.push(node);\n  }\n\n  closeNode() {\n    if (this.stack.length > 1) {\n      return this.stack.pop();\n    }\n    // eslint-disable-next-line no-undefined\n    return undefined;\n  }\n\n  closeAllNodes() {\n    while (this.closeNode());\n  }\n\n  toJSON() {\n    return JSON.stringify(this.rootNode, null, 4);\n  }\n\n  /**\n   * @typedef { import(\"./html_renderer\").Renderer } Renderer\n   * @param {Renderer} builder\n   */\n  walk(builder) {\n    // this does not\n    return this.constructor._walk(builder, this.rootNode);\n    // this works\n    // return TokenTree._walk(builder, this.rootNode);\n  }\n\n  /**\n   * @param {Renderer} builder\n   * @param {Node} node\n   */\n  static _walk(builder, node) {\n    if (typeof node === \"string\") {\n      builder.addText(node);\n    } else if (node.children) {\n      builder.openNode(node);\n      node.children.forEach((child) => this._walk(builder, child));\n      builder.closeNode(node);\n    }\n    return builder;\n  }\n\n  /**\n   * @param {Node} node\n   */\n  static _collapse(node) {\n    if (typeof node === \"string\") return;\n    if (!node.children) return;\n\n    if (node.children.every(el => typeof el === \"string\")) {\n      // node.text = node.children.join(\"\");\n      // delete node.children;\n      node.children = [node.children.join(\"\")];\n    } else {\n      node.children.forEach((child) => {\n        TokenTree._collapse(child);\n      });\n    }\n  }\n}\n\n/**\n  Currently this is all private API, but this is the minimal API necessary\n  that an Emitter must implement to fully support the parser.\n\n  Minimal interface:\n\n  - addText(text)\n  - __addSublanguage(emitter, subLanguageName)\n  - startScope(scope)\n  - endScope()\n  - finalize()\n  - toHTML()\n\n*/\n\n/**\n * @implements {Emitter}\n */\nclass TokenTreeEmitter extends TokenTree {\n  /**\n   * @param {*} options\n   */\n  constructor(options) {\n    super();\n    this.options = options;\n  }\n\n  /**\n   * @param {string} text\n   */\n  addText(text) {\n    if (text === \"\") { return; }\n\n    this.add(text);\n  }\n\n  /** @param {string} scope */\n  startScope(scope) {\n    this.openNode(scope);\n  }\n\n  endScope() {\n    this.closeNode();\n  }\n\n  /**\n   * @param {Emitter & {root: DataNode}} emitter\n   * @param {string} name\n   */\n  __addSublanguage(emitter, name) {\n    /** @type DataNode */\n    const node = emitter.root;\n    if (name) node.scope = `language:${name}`;\n\n    this.add(node);\n  }\n\n  toHTML() {\n    const renderer = new HTMLRenderer(this, this.options);\n    return renderer.value();\n  }\n\n  finalize() {\n    this.closeAllNodes();\n    return true;\n  }\n}\n\n/**\n * @param {string} value\n * @returns {RegExp}\n * */\n\n/**\n * @param {RegExp | string } re\n * @returns {string}\n */\nfunction source(re) {\n  if (!re) return null;\n  if (typeof re === \"string\") return re;\n\n  return re.source;\n}\n\n/**\n * @param {RegExp | string } re\n * @returns {string}\n */\nfunction lookahead(re) {\n  return concat('(?=', re, ')');\n}\n\n/**\n * @param {RegExp | string } re\n * @returns {string}\n */\nfunction anyNumberOfTimes(re) {\n  return concat('(?:', re, ')*');\n}\n\n/**\n * @param {RegExp | string } re\n * @returns {string}\n */\nfunction optional(re) {\n  return concat('(?:', re, ')?');\n}\n\n/**\n * @param {...(RegExp | string) } args\n * @returns {string}\n */\nfunction concat(...args) {\n  const joined = args.map((x) => source(x)).join(\"\");\n  return joined;\n}\n\n/**\n * @param { Array } args\n * @returns {object}\n */\nfunction stripOptionsFromArgs(args) {\n  const opts = args[args.length - 1];\n\n  if (typeof opts === 'object' && opts.constructor === Object) {\n    args.splice(args.length - 1, 1);\n    return opts;\n  } else {\n    return {};\n  }\n}\n\n/** @typedef { {capture?: boolean} } RegexEitherOptions */\n\n/**\n * Any of the passed expresssions may match\n *\n * Creates a huge this | this | that | that match\n * @param {(RegExp | string)[] | [...(RegExp | string)[], RegexEitherOptions]} args\n * @returns {string}\n */\nfunction either(...args) {\n  /** @type { object & {capture?: boolean} }  */\n  const opts = stripOptionsFromArgs(args);\n  const joined = '('\n    + (opts.capture ? \"\" : \"?:\")\n    + args.map((x) => source(x)).join(\"|\") + \")\";\n  return joined;\n}\n\n/**\n * @param {RegExp | string} re\n * @returns {number}\n */\nfunction countMatchGroups(re) {\n  return (new RegExp(re.toString() + '|')).exec('').length - 1;\n}\n\n/**\n * Does lexeme start with a regular expression match at the beginning\n * @param {RegExp} re\n * @param {string} lexeme\n */\nfunction startsWith(re, lexeme) {\n  const match = re && re.exec(lexeme);\n  return match && match.index === 0;\n}\n\n// BACKREF_RE matches an open parenthesis or backreference. To avoid\n// an incorrect parse, it additionally matches the following:\n// - [...] elements, where the meaning of parentheses and escapes change\n// - other escape sequences, so we do not misparse escape sequences as\n//   interesting elements\n// - non-matching or lookahead parentheses, which do not capture. These\n//   follow the '(' with a '?'.\nconst BACKREF_RE = /\\[(?:[^\\\\\\]]|\\\\.)*\\]|\\(\\??|\\\\([1-9][0-9]*)|\\\\./;\n\n// **INTERNAL** Not intended for outside usage\n// join logically computes regexps.join(separator), but fixes the\n// backreferences so they continue to match.\n// it also places each individual regular expression into it's own\n// match group, keeping track of the sequencing of those match groups\n// is currently an exercise for the caller. :-)\n/**\n * @param {(string | RegExp)[]} regexps\n * @param {{joinWith: string}} opts\n * @returns {string}\n */\nfunction _rewriteBackreferences(regexps, { joinWith }) {\n  let numCaptures = 0;\n\n  return regexps.map((regex) => {\n    numCaptures += 1;\n    const offset = numCaptures;\n    let re = source(regex);\n    let out = '';\n\n    while (re.length > 0) {\n      const match = BACKREF_RE.exec(re);\n      if (!match) {\n        out += re;\n        break;\n      }\n      out += re.substring(0, match.index);\n      re = re.substring(match.index + match[0].length);\n      if (match[0][0] === '\\\\' && match[1]) {\n        // Adjust the backreference.\n        out += '\\\\' + String(Number(match[1]) + offset);\n      } else {\n        out += match[0];\n        if (match[0] === '(') {\n          numCaptures++;\n        }\n      }\n    }\n    return out;\n  }).map(re => `(${re})`).join(joinWith);\n}\n\n/** @typedef {import('highlight.js').Mode} Mode */\n/** @typedef {import('highlight.js').ModeCallback} ModeCallback */\n\n// Common regexps\nconst MATCH_NOTHING_RE = /\\b\\B/;\nconst IDENT_RE = '[a-zA-Z]\\\\w*';\nconst UNDERSCORE_IDENT_RE = '[a-zA-Z_]\\\\w*';\nconst NUMBER_RE = '\\\\b\\\\d+(\\\\.\\\\d+)?';\nconst C_NUMBER_RE = '(-?)(\\\\b0[xX][a-fA-F0-9]+|(\\\\b\\\\d+(\\\\.\\\\d*)?|\\\\.\\\\d+)([eE][-+]?\\\\d+)?)'; // 0x..., 0..., decimal, float\nconst BINARY_NUMBER_RE = '\\\\b(0b[01]+)'; // 0b...\nconst RE_STARTERS_RE = '!|!=|!==|%|%=|&|&&|&=|\\\\*|\\\\*=|\\\\+|\\\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\\\?|\\\\[|\\\\{|\\\\(|\\\\^|\\\\^=|\\\\||\\\\|=|\\\\|\\\\||~';\n\n/**\n* @param { Partial & {binary?: string | RegExp} } opts\n*/\nconst SHEBANG = (opts = {}) => {\n  const beginShebang = /^#![ ]*\\//;\n  if (opts.binary) {\n    opts.begin = concat(\n      beginShebang,\n      /.*\\b/,\n      opts.binary,\n      /\\b.*/);\n  }\n  return inherit$1({\n    scope: 'meta',\n    begin: beginShebang,\n    end: /$/,\n    relevance: 0,\n    /** @type {ModeCallback} */\n    \"on:begin\": (m, resp) => {\n      if (m.index !== 0) resp.ignoreMatch();\n    }\n  }, opts);\n};\n\n// Common modes\nconst BACKSLASH_ESCAPE = {\n  begin: '\\\\\\\\[\\\\s\\\\S]', relevance: 0\n};\nconst APOS_STRING_MODE = {\n  scope: 'string',\n  begin: '\\'',\n  end: '\\'',\n  illegal: '\\\\n',\n  contains: [BACKSLASH_ESCAPE]\n};\nconst QUOTE_STRING_MODE = {\n  scope: 'string',\n  begin: '\"',\n  end: '\"',\n  illegal: '\\\\n',\n  contains: [BACKSLASH_ESCAPE]\n};\nconst PHRASAL_WORDS_MODE = {\n  begin: /\\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\\b/\n};\n/**\n * Creates a comment mode\n *\n * @param {string | RegExp} begin\n * @param {string | RegExp} end\n * @param {Mode | {}} [modeOptions]\n * @returns {Partial}\n */\nconst COMMENT = function(begin, end, modeOptions = {}) {\n  const mode = inherit$1(\n    {\n      scope: 'comment',\n      begin,\n      end,\n      contains: []\n    },\n    modeOptions\n  );\n  mode.contains.push({\n    scope: 'doctag',\n    // hack to avoid the space from being included. the space is necessary to\n    // match here to prevent the plain text rule below from gobbling up doctags\n    begin: '[ ]*(?=(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):)',\n    end: /(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):/,\n    excludeBegin: true,\n    relevance: 0\n  });\n  const ENGLISH_WORD = either(\n    // list of common 1 and 2 letter words in English\n    \"I\",\n    \"a\",\n    \"is\",\n    \"so\",\n    \"us\",\n    \"to\",\n    \"at\",\n    \"if\",\n    \"in\",\n    \"it\",\n    \"on\",\n    // note: this is not an exhaustive list of contractions, just popular ones\n    /[A-Za-z]+['](d|ve|re|ll|t|s|n)/, // contractions - can't we'd they're let's, etc\n    /[A-Za-z]+[-][a-z]+/, // `no-way`, etc.\n    /[A-Za-z][a-z]{2,}/ // allow capitalized words at beginning of sentences\n  );\n  // looking like plain text, more likely to be a comment\n  mode.contains.push(\n    {\n      // TODO: how to include \", (, ) without breaking grammars that use these for\n      // comment delimiters?\n      // begin: /[ ]+([()\"]?([A-Za-z'-]{3,}|is|a|I|so|us|[tT][oO]|at|if|in|it|on)[.]?[()\":]?([.][ ]|[ ]|\\))){3}/\n      // ---\n\n      // this tries to find sequences of 3 english words in a row (without any\n      // \"programming\" type syntax) this gives us a strong signal that we've\n      // TRULY found a comment - vs perhaps scanning with the wrong language.\n      // It's possible to find something that LOOKS like the start of the\n      // comment - but then if there is no readable text - good chance it is a\n      // false match and not a comment.\n      //\n      // for a visual example please see:\n      // https://github.com/highlightjs/highlight.js/issues/2827\n\n      begin: concat(\n        /[ ]+/, // necessary to prevent us gobbling up doctags like /* @author Bob Mcgill */\n        '(',\n        ENGLISH_WORD,\n        /[.]?[:]?([.][ ]|[ ])/,\n        '){3}') // look for 3 words in a row\n    }\n  );\n  return mode;\n};\nconst C_LINE_COMMENT_MODE = COMMENT('//', '$');\nconst C_BLOCK_COMMENT_MODE = COMMENT('/\\\\*', '\\\\*/');\nconst HASH_COMMENT_MODE = COMMENT('#', '$');\nconst NUMBER_MODE = {\n  scope: 'number',\n  begin: NUMBER_RE,\n  relevance: 0\n};\nconst C_NUMBER_MODE = {\n  scope: 'number',\n  begin: C_NUMBER_RE,\n  relevance: 0\n};\nconst BINARY_NUMBER_MODE = {\n  scope: 'number',\n  begin: BINARY_NUMBER_RE,\n  relevance: 0\n};\nconst REGEXP_MODE = {\n  // this outer rule makes sure we actually have a WHOLE regex and not simply\n  // an expression such as:\n  //\n  //     3 / something\n  //\n  // (which will then blow up when regex's `illegal` sees the newline)\n  begin: /(?=\\/[^/\\n]*\\/)/,\n  contains: [{\n    scope: 'regexp',\n    begin: /\\//,\n    end: /\\/[gimuy]*/,\n    illegal: /\\n/,\n    contains: [\n      BACKSLASH_ESCAPE,\n      {\n        begin: /\\[/,\n        end: /\\]/,\n        relevance: 0,\n        contains: [BACKSLASH_ESCAPE]\n      }\n    ]\n  }]\n};\nconst TITLE_MODE = {\n  scope: 'title',\n  begin: IDENT_RE,\n  relevance: 0\n};\nconst UNDERSCORE_TITLE_MODE = {\n  scope: 'title',\n  begin: UNDERSCORE_IDENT_RE,\n  relevance: 0\n};\nconst METHOD_GUARD = {\n  // excludes method names from keyword processing\n  begin: '\\\\.\\\\s*' + UNDERSCORE_IDENT_RE,\n  relevance: 0\n};\n\n/**\n * Adds end same as begin mechanics to a mode\n *\n * Your mode must include at least a single () match group as that first match\n * group is what is used for comparison\n * @param {Partial} mode\n */\nconst END_SAME_AS_BEGIN = function(mode) {\n  return Object.assign(mode,\n    {\n      /** @type {ModeCallback} */\n      'on:begin': (m, resp) => { resp.data._beginMatch = m[1]; },\n      /** @type {ModeCallback} */\n      'on:end': (m, resp) => { if (resp.data._beginMatch !== m[1]) resp.ignoreMatch(); }\n    });\n};\n\nvar MODES = /*#__PURE__*/Object.freeze({\n  __proto__: null,\n  MATCH_NOTHING_RE: MATCH_NOTHING_RE,\n  IDENT_RE: IDENT_RE,\n  UNDERSCORE_IDENT_RE: UNDERSCORE_IDENT_RE,\n  NUMBER_RE: NUMBER_RE,\n  C_NUMBER_RE: C_NUMBER_RE,\n  BINARY_NUMBER_RE: BINARY_NUMBER_RE,\n  RE_STARTERS_RE: RE_STARTERS_RE,\n  SHEBANG: SHEBANG,\n  BACKSLASH_ESCAPE: BACKSLASH_ESCAPE,\n  APOS_STRING_MODE: APOS_STRING_MODE,\n  QUOTE_STRING_MODE: QUOTE_STRING_MODE,\n  PHRASAL_WORDS_MODE: PHRASAL_WORDS_MODE,\n  COMMENT: COMMENT,\n  C_LINE_COMMENT_MODE: C_LINE_COMMENT_MODE,\n  C_BLOCK_COMMENT_MODE: C_BLOCK_COMMENT_MODE,\n  HASH_COMMENT_MODE: HASH_COMMENT_MODE,\n  NUMBER_MODE: NUMBER_MODE,\n  C_NUMBER_MODE: C_NUMBER_MODE,\n  BINARY_NUMBER_MODE: BINARY_NUMBER_MODE,\n  REGEXP_MODE: REGEXP_MODE,\n  TITLE_MODE: TITLE_MODE,\n  UNDERSCORE_TITLE_MODE: UNDERSCORE_TITLE_MODE,\n  METHOD_GUARD: METHOD_GUARD,\n  END_SAME_AS_BEGIN: END_SAME_AS_BEGIN\n});\n\n/**\n@typedef {import('highlight.js').CallbackResponse} CallbackResponse\n@typedef {import('highlight.js').CompilerExt} CompilerExt\n*/\n\n// Grammar extensions / plugins\n// See: https://github.com/highlightjs/highlight.js/issues/2833\n\n// Grammar extensions allow \"syntactic sugar\" to be added to the grammar modes\n// without requiring any underlying changes to the compiler internals.\n\n// `compileMatch` being the perfect small example of now allowing a grammar\n// author to write `match` when they desire to match a single expression rather\n// than being forced to use `begin`.  The extension then just moves `match` into\n// `begin` when it runs.  Ie, no features have been added, but we've just made\n// the experience of writing (and reading grammars) a little bit nicer.\n\n// ------\n\n// TODO: We need negative look-behind support to do this properly\n/**\n * Skip a match if it has a preceding dot\n *\n * This is used for `beginKeywords` to prevent matching expressions such as\n * `bob.keyword.do()`. The mode compiler automatically wires this up as a\n * special _internal_ 'on:begin' callback for modes with `beginKeywords`\n * @param {RegExpMatchArray} match\n * @param {CallbackResponse} response\n */\nfunction skipIfHasPrecedingDot(match, response) {\n  const before = match.input[match.index - 1];\n  if (before === \".\") {\n    response.ignoreMatch();\n  }\n}\n\n/**\n *\n * @type {CompilerExt}\n */\nfunction scopeClassName(mode, _parent) {\n  // eslint-disable-next-line no-undefined\n  if (mode.className !== undefined) {\n    mode.scope = mode.className;\n    delete mode.className;\n  }\n}\n\n/**\n * `beginKeywords` syntactic sugar\n * @type {CompilerExt}\n */\nfunction beginKeywords(mode, parent) {\n  if (!parent) return;\n  if (!mode.beginKeywords) return;\n\n  // for languages with keywords that include non-word characters checking for\n  // a word boundary is not sufficient, so instead we check for a word boundary\n  // or whitespace - this does no harm in any case since our keyword engine\n  // doesn't allow spaces in keywords anyways and we still check for the boundary\n  // first\n  mode.begin = '\\\\b(' + mode.beginKeywords.split(' ').join('|') + ')(?!\\\\.)(?=\\\\b|\\\\s)';\n  mode.__beforeBegin = skipIfHasPrecedingDot;\n  mode.keywords = mode.keywords || mode.beginKeywords;\n  delete mode.beginKeywords;\n\n  // prevents double relevance, the keywords themselves provide\n  // relevance, the mode doesn't need to double it\n  // eslint-disable-next-line no-undefined\n  if (mode.relevance === undefined) mode.relevance = 0;\n}\n\n/**\n * Allow `illegal` to contain an array of illegal values\n * @type {CompilerExt}\n */\nfunction compileIllegal(mode, _parent) {\n  if (!Array.isArray(mode.illegal)) return;\n\n  mode.illegal = either(...mode.illegal);\n}\n\n/**\n * `match` to match a single expression for readability\n * @type {CompilerExt}\n */\nfunction compileMatch(mode, _parent) {\n  if (!mode.match) return;\n  if (mode.begin || mode.end) throw new Error(\"begin & end are not supported with match\");\n\n  mode.begin = mode.match;\n  delete mode.match;\n}\n\n/**\n * provides the default 1 relevance to all modes\n * @type {CompilerExt}\n */\nfunction compileRelevance(mode, _parent) {\n  // eslint-disable-next-line no-undefined\n  if (mode.relevance === undefined) mode.relevance = 1;\n}\n\n// allow beforeMatch to act as a \"qualifier\" for the match\n// the full match begin must be [beforeMatch][begin]\nconst beforeMatchExt = (mode, parent) => {\n  if (!mode.beforeMatch) return;\n  // starts conflicts with endsParent which we need to make sure the child\n  // rule is not matched multiple times\n  if (mode.starts) throw new Error(\"beforeMatch cannot be used with starts\");\n\n  const originalMode = Object.assign({}, mode);\n  Object.keys(mode).forEach((key) => { delete mode[key]; });\n\n  mode.keywords = originalMode.keywords;\n  mode.begin = concat(originalMode.beforeMatch, lookahead(originalMode.begin));\n  mode.starts = {\n    relevance: 0,\n    contains: [\n      Object.assign(originalMode, { endsParent: true })\n    ]\n  };\n  mode.relevance = 0;\n\n  delete originalMode.beforeMatch;\n};\n\n// keywords that should have no default relevance value\nconst COMMON_KEYWORDS = [\n  'of',\n  'and',\n  'for',\n  'in',\n  'not',\n  'or',\n  'if',\n  'then',\n  'parent', // common variable name\n  'list', // common variable name\n  'value' // common variable name\n];\n\nconst DEFAULT_KEYWORD_SCOPE = \"keyword\";\n\n/**\n * Given raw keywords from a language definition, compile them.\n *\n * @param {string | Record | Array} rawKeywords\n * @param {boolean} caseInsensitive\n */\nfunction compileKeywords(rawKeywords, caseInsensitive, scopeName = DEFAULT_KEYWORD_SCOPE) {\n  /** @type {import(\"highlight.js/private\").KeywordDict} */\n  const compiledKeywords = Object.create(null);\n\n  // input can be a string of keywords, an array of keywords, or a object with\n  // named keys representing scopeName (which can then point to a string or array)\n  if (typeof rawKeywords === 'string') {\n    compileList(scopeName, rawKeywords.split(\" \"));\n  } else if (Array.isArray(rawKeywords)) {\n    compileList(scopeName, rawKeywords);\n  } else {\n    Object.keys(rawKeywords).forEach(function(scopeName) {\n      // collapse all our objects back into the parent object\n      Object.assign(\n        compiledKeywords,\n        compileKeywords(rawKeywords[scopeName], caseInsensitive, scopeName)\n      );\n    });\n  }\n  return compiledKeywords;\n\n  // ---\n\n  /**\n   * Compiles an individual list of keywords\n   *\n   * Ex: \"for if when while|5\"\n   *\n   * @param {string} scopeName\n   * @param {Array} keywordList\n   */\n  function compileList(scopeName, keywordList) {\n    if (caseInsensitive) {\n      keywordList = keywordList.map(x => x.toLowerCase());\n    }\n    keywordList.forEach(function(keyword) {\n      const pair = keyword.split('|');\n      compiledKeywords[pair[0]] = [scopeName, scoreForKeyword(pair[0], pair[1])];\n    });\n  }\n}\n\n/**\n * Returns the proper score for a given keyword\n *\n * Also takes into account comment keywords, which will be scored 0 UNLESS\n * another score has been manually assigned.\n * @param {string} keyword\n * @param {string} [providedScore]\n */\nfunction scoreForKeyword(keyword, providedScore) {\n  // manual scores always win over common keywords\n  // so you can force a score of 1 if you really insist\n  if (providedScore) {\n    return Number(providedScore);\n  }\n\n  return commonKeyword(keyword) ? 0 : 1;\n}\n\n/**\n * Determines if a given keyword is common or not\n *\n * @param {string} keyword */\nfunction commonKeyword(keyword) {\n  return COMMON_KEYWORDS.includes(keyword.toLowerCase());\n}\n\n/*\n\nFor the reasoning behind this please see:\nhttps://github.com/highlightjs/highlight.js/issues/2880#issuecomment-747275419\n\n*/\n\n/**\n * @type {Record}\n */\nconst seenDeprecations = {};\n\n/**\n * @param {string} message\n */\nconst error = (message) => {\n  console.error(message);\n};\n\n/**\n * @param {string} message\n * @param {any} args\n */\nconst warn = (message, ...args) => {\n  console.log(`WARN: ${message}`, ...args);\n};\n\n/**\n * @param {string} version\n * @param {string} message\n */\nconst deprecated = (version, message) => {\n  if (seenDeprecations[`${version}/${message}`]) return;\n\n  console.log(`Deprecated as of ${version}. ${message}`);\n  seenDeprecations[`${version}/${message}`] = true;\n};\n\n/* eslint-disable no-throw-literal */\n\n/**\n@typedef {import('highlight.js').CompiledMode} CompiledMode\n*/\n\nconst MultiClassError = new Error();\n\n/**\n * Renumbers labeled scope names to account for additional inner match\n * groups that otherwise would break everything.\n *\n * Lets say we 3 match scopes:\n *\n *   { 1 => ..., 2 => ..., 3 => ... }\n *\n * So what we need is a clean match like this:\n *\n *   (a)(b)(c) => [ \"a\", \"b\", \"c\" ]\n *\n * But this falls apart with inner match groups:\n *\n * (a)(((b)))(c) => [\"a\", \"b\", \"b\", \"b\", \"c\" ]\n *\n * Our scopes are now \"out of alignment\" and we're repeating `b` 3 times.\n * What needs to happen is the numbers are remapped:\n *\n *   { 1 => ..., 2 => ..., 5 => ... }\n *\n * We also need to know that the ONLY groups that should be output\n * are 1, 2, and 5.  This function handles this behavior.\n *\n * @param {CompiledMode} mode\n * @param {Array} regexes\n * @param {{key: \"beginScope\"|\"endScope\"}} opts\n */\nfunction remapScopeNames(mode, regexes, { key }) {\n  let offset = 0;\n  const scopeNames = mode[key];\n  /** @type Record */\n  const emit = {};\n  /** @type Record */\n  const positions = {};\n\n  for (let i = 1; i <= regexes.length; i++) {\n    positions[i + offset] = scopeNames[i];\n    emit[i + offset] = true;\n    offset += countMatchGroups(regexes[i - 1]);\n  }\n  // we use _emit to keep track of which match groups are \"top-level\" to avoid double\n  // output from inside match groups\n  mode[key] = positions;\n  mode[key]._emit = emit;\n  mode[key]._multi = true;\n}\n\n/**\n * @param {CompiledMode} mode\n */\nfunction beginMultiClass(mode) {\n  if (!Array.isArray(mode.begin)) return;\n\n  if (mode.skip || mode.excludeBegin || mode.returnBegin) {\n    error(\"skip, excludeBegin, returnBegin not compatible with beginScope: {}\");\n    throw MultiClassError;\n  }\n\n  if (typeof mode.beginScope !== \"object\" || mode.beginScope === null) {\n    error(\"beginScope must be object\");\n    throw MultiClassError;\n  }\n\n  remapScopeNames(mode, mode.begin, { key: \"beginScope\" });\n  mode.begin = _rewriteBackreferences(mode.begin, { joinWith: \"\" });\n}\n\n/**\n * @param {CompiledMode} mode\n */\nfunction endMultiClass(mode) {\n  if (!Array.isArray(mode.end)) return;\n\n  if (mode.skip || mode.excludeEnd || mode.returnEnd) {\n    error(\"skip, excludeEnd, returnEnd not compatible with endScope: {}\");\n    throw MultiClassError;\n  }\n\n  if (typeof mode.endScope !== \"object\" || mode.endScope === null) {\n    error(\"endScope must be object\");\n    throw MultiClassError;\n  }\n\n  remapScopeNames(mode, mode.end, { key: \"endScope\" });\n  mode.end = _rewriteBackreferences(mode.end, { joinWith: \"\" });\n}\n\n/**\n * this exists only to allow `scope: {}` to be used beside `match:`\n * Otherwise `beginScope` would necessary and that would look weird\n\n  {\n    match: [ /def/, /\\w+/ ]\n    scope: { 1: \"keyword\" , 2: \"title\" }\n  }\n\n * @param {CompiledMode} mode\n */\nfunction scopeSugar(mode) {\n  if (mode.scope && typeof mode.scope === \"object\" && mode.scope !== null) {\n    mode.beginScope = mode.scope;\n    delete mode.scope;\n  }\n}\n\n/**\n * @param {CompiledMode} mode\n */\nfunction MultiClass(mode) {\n  scopeSugar(mode);\n\n  if (typeof mode.beginScope === \"string\") {\n    mode.beginScope = { _wrap: mode.beginScope };\n  }\n  if (typeof mode.endScope === \"string\") {\n    mode.endScope = { _wrap: mode.endScope };\n  }\n\n  beginMultiClass(mode);\n  endMultiClass(mode);\n}\n\n/**\n@typedef {import('highlight.js').Mode} Mode\n@typedef {import('highlight.js').CompiledMode} CompiledMode\n@typedef {import('highlight.js').Language} Language\n@typedef {import('highlight.js').HLJSPlugin} HLJSPlugin\n@typedef {import('highlight.js').CompiledLanguage} CompiledLanguage\n*/\n\n// compilation\n\n/**\n * Compiles a language definition result\n *\n * Given the raw result of a language definition (Language), compiles this so\n * that it is ready for highlighting code.\n * @param {Language} language\n * @returns {CompiledLanguage}\n */\nfunction compileLanguage(language) {\n  /**\n   * Builds a regex with the case sensitivity of the current language\n   *\n   * @param {RegExp | string} value\n   * @param {boolean} [global]\n   */\n  function langRe(value, global) {\n    return new RegExp(\n      source(value),\n      'm'\n      + (language.case_insensitive ? 'i' : '')\n      + (language.unicodeRegex ? 'u' : '')\n      + (global ? 'g' : '')\n    );\n  }\n\n  /**\n    Stores multiple regular expressions and allows you to quickly search for\n    them all in a string simultaneously - returning the first match.  It does\n    this by creating a huge (a|b|c) regex - each individual item wrapped with ()\n    and joined by `|` - using match groups to track position.  When a match is\n    found checking which position in the array has content allows us to figure\n    out which of the original regexes / match groups triggered the match.\n\n    The match object itself (the result of `Regex.exec`) is returned but also\n    enhanced by merging in any meta-data that was registered with the regex.\n    This is how we keep track of which mode matched, and what type of rule\n    (`illegal`, `begin`, end, etc).\n  */\n  class MultiRegex {\n    constructor() {\n      this.matchIndexes = {};\n      // @ts-ignore\n      this.regexes = [];\n      this.matchAt = 1;\n      this.position = 0;\n    }\n\n    // @ts-ignore\n    addRule(re, opts) {\n      opts.position = this.position++;\n      // @ts-ignore\n      this.matchIndexes[this.matchAt] = opts;\n      this.regexes.push([opts, re]);\n      this.matchAt += countMatchGroups(re) + 1;\n    }\n\n    compile() {\n      if (this.regexes.length === 0) {\n        // avoids the need to check length every time exec is called\n        // @ts-ignore\n        this.exec = () => null;\n      }\n      const terminators = this.regexes.map(el => el[1]);\n      this.matcherRe = langRe(_rewriteBackreferences(terminators, { joinWith: '|' }), true);\n      this.lastIndex = 0;\n    }\n\n    /** @param {string} s */\n    exec(s) {\n      this.matcherRe.lastIndex = this.lastIndex;\n      const match = this.matcherRe.exec(s);\n      if (!match) { return null; }\n\n      // eslint-disable-next-line no-undefined\n      const i = match.findIndex((el, i) => i > 0 && el !== undefined);\n      // @ts-ignore\n      const matchData = this.matchIndexes[i];\n      // trim off any earlier non-relevant match groups (ie, the other regex\n      // match groups that make up the multi-matcher)\n      match.splice(0, i);\n\n      return Object.assign(match, matchData);\n    }\n  }\n\n  /*\n    Created to solve the key deficiently with MultiRegex - there is no way to\n    test for multiple matches at a single location.  Why would we need to do\n    that?  In the future a more dynamic engine will allow certain matches to be\n    ignored.  An example: if we matched say the 3rd regex in a large group but\n    decided to ignore it - we'd need to started testing again at the 4th\n    regex... but MultiRegex itself gives us no real way to do that.\n\n    So what this class creates MultiRegexs on the fly for whatever search\n    position they are needed.\n\n    NOTE: These additional MultiRegex objects are created dynamically.  For most\n    grammars most of the time we will never actually need anything more than the\n    first MultiRegex - so this shouldn't have too much overhead.\n\n    Say this is our search group, and we match regex3, but wish to ignore it.\n\n      regex1 | regex2 | regex3 | regex4 | regex5    ' ie, startAt = 0\n\n    What we need is a new MultiRegex that only includes the remaining\n    possibilities:\n\n      regex4 | regex5                               ' ie, startAt = 3\n\n    This class wraps all that complexity up in a simple API... `startAt` decides\n    where in the array of expressions to start doing the matching. It\n    auto-increments, so if a match is found at position 2, then startAt will be\n    set to 3.  If the end is reached startAt will return to 0.\n\n    MOST of the time the parser will be setting startAt manually to 0.\n  */\n  class ResumableMultiRegex {\n    constructor() {\n      // @ts-ignore\n      this.rules = [];\n      // @ts-ignore\n      this.multiRegexes = [];\n      this.count = 0;\n\n      this.lastIndex = 0;\n      this.regexIndex = 0;\n    }\n\n    // @ts-ignore\n    getMatcher(index) {\n      if (this.multiRegexes[index]) return this.multiRegexes[index];\n\n      const matcher = new MultiRegex();\n      this.rules.slice(index).forEach(([re, opts]) => matcher.addRule(re, opts));\n      matcher.compile();\n      this.multiRegexes[index] = matcher;\n      return matcher;\n    }\n\n    resumingScanAtSamePosition() {\n      return this.regexIndex !== 0;\n    }\n\n    considerAll() {\n      this.regexIndex = 0;\n    }\n\n    // @ts-ignore\n    addRule(re, opts) {\n      this.rules.push([re, opts]);\n      if (opts.type === \"begin\") this.count++;\n    }\n\n    /** @param {string} s */\n    exec(s) {\n      const m = this.getMatcher(this.regexIndex);\n      m.lastIndex = this.lastIndex;\n      let result = m.exec(s);\n\n      // The following is because we have no easy way to say \"resume scanning at the\n      // existing position but also skip the current rule ONLY\". What happens is\n      // all prior rules are also skipped which can result in matching the wrong\n      // thing. Example of matching \"booger\":\n\n      // our matcher is [string, \"booger\", number]\n      //\n      // ....booger....\n\n      // if \"booger\" is ignored then we'd really need a regex to scan from the\n      // SAME position for only: [string, number] but ignoring \"booger\" (if it\n      // was the first match), a simple resume would scan ahead who knows how\n      // far looking only for \"number\", ignoring potential string matches (or\n      // future \"booger\" matches that might be valid.)\n\n      // So what we do: We execute two matchers, one resuming at the same\n      // position, but the second full matcher starting at the position after:\n\n      //     /--- resume first regex match here (for [number])\n      //     |/---- full match here for [string, \"booger\", number]\n      //     vv\n      // ....booger....\n\n      // Which ever results in a match first is then used. So this 3-4 step\n      // process essentially allows us to say \"match at this position, excluding\n      // a prior rule that was ignored\".\n      //\n      // 1. Match \"booger\" first, ignore. Also proves that [string] does non match.\n      // 2. Resume matching for [number]\n      // 3. Match at index + 1 for [string, \"booger\", number]\n      // 4. If #2 and #3 result in matches, which came first?\n      if (this.resumingScanAtSamePosition()) {\n        if (result && result.index === this.lastIndex) ; else { // use the second matcher result\n          const m2 = this.getMatcher(0);\n          m2.lastIndex = this.lastIndex + 1;\n          result = m2.exec(s);\n        }\n      }\n\n      if (result) {\n        this.regexIndex += result.position + 1;\n        if (this.regexIndex === this.count) {\n          // wrap-around to considering all matches again\n          this.considerAll();\n        }\n      }\n\n      return result;\n    }\n  }\n\n  /**\n   * Given a mode, builds a huge ResumableMultiRegex that can be used to walk\n   * the content and find matches.\n   *\n   * @param {CompiledMode} mode\n   * @returns {ResumableMultiRegex}\n   */\n  function buildModeRegex(mode) {\n    const mm = new ResumableMultiRegex();\n\n    mode.contains.forEach(term => mm.addRule(term.begin, { rule: term, type: \"begin\" }));\n\n    if (mode.terminatorEnd) {\n      mm.addRule(mode.terminatorEnd, { type: \"end\" });\n    }\n    if (mode.illegal) {\n      mm.addRule(mode.illegal, { type: \"illegal\" });\n    }\n\n    return mm;\n  }\n\n  /** skip vs abort vs ignore\n   *\n   * @skip   - The mode is still entered and exited normally (and contains rules apply),\n   *           but all content is held and added to the parent buffer rather than being\n   *           output when the mode ends.  Mostly used with `sublanguage` to build up\n   *           a single large buffer than can be parsed by sublanguage.\n   *\n   *             - The mode begin ands ends normally.\n   *             - Content matched is added to the parent mode buffer.\n   *             - The parser cursor is moved forward normally.\n   *\n   * @abort  - A hack placeholder until we have ignore.  Aborts the mode (as if it\n   *           never matched) but DOES NOT continue to match subsequent `contains`\n   *           modes.  Abort is bad/suboptimal because it can result in modes\n   *           farther down not getting applied because an earlier rule eats the\n   *           content but then aborts.\n   *\n   *             - The mode does not begin.\n   *             - Content matched by `begin` is added to the mode buffer.\n   *             - The parser cursor is moved forward accordingly.\n   *\n   * @ignore - Ignores the mode (as if it never matched) and continues to match any\n   *           subsequent `contains` modes.  Ignore isn't technically possible with\n   *           the current parser implementation.\n   *\n   *             - The mode does not begin.\n   *             - Content matched by `begin` is ignored.\n   *             - The parser cursor is not moved forward.\n   */\n\n  /**\n   * Compiles an individual mode\n   *\n   * This can raise an error if the mode contains certain detectable known logic\n   * issues.\n   * @param {Mode} mode\n   * @param {CompiledMode | null} [parent]\n   * @returns {CompiledMode | never}\n   */\n  function compileMode(mode, parent) {\n    const cmode = /** @type CompiledMode */ (mode);\n    if (mode.isCompiled) return cmode;\n\n    [\n      scopeClassName,\n      // do this early so compiler extensions generally don't have to worry about\n      // the distinction between match/begin\n      compileMatch,\n      MultiClass,\n      beforeMatchExt\n    ].forEach(ext => ext(mode, parent));\n\n    language.compilerExtensions.forEach(ext => ext(mode, parent));\n\n    // __beforeBegin is considered private API, internal use only\n    mode.__beforeBegin = null;\n\n    [\n      beginKeywords,\n      // do this later so compiler extensions that come earlier have access to the\n      // raw array if they wanted to perhaps manipulate it, etc.\n      compileIllegal,\n      // default to 1 relevance if not specified\n      compileRelevance\n    ].forEach(ext => ext(mode, parent));\n\n    mode.isCompiled = true;\n\n    let keywordPattern = null;\n    if (typeof mode.keywords === \"object\" && mode.keywords.$pattern) {\n      // we need a copy because keywords might be compiled multiple times\n      // so we can't go deleting $pattern from the original on the first\n      // pass\n      mode.keywords = Object.assign({}, mode.keywords);\n      keywordPattern = mode.keywords.$pattern;\n      delete mode.keywords.$pattern;\n    }\n    keywordPattern = keywordPattern || /\\w+/;\n\n    if (mode.keywords) {\n      mode.keywords = compileKeywords(mode.keywords, language.case_insensitive);\n    }\n\n    cmode.keywordPatternRe = langRe(keywordPattern, true);\n\n    if (parent) {\n      if (!mode.begin) mode.begin = /\\B|\\b/;\n      cmode.beginRe = langRe(cmode.begin);\n      if (!mode.end && !mode.endsWithParent) mode.end = /\\B|\\b/;\n      if (mode.end) cmode.endRe = langRe(cmode.end);\n      cmode.terminatorEnd = source(cmode.end) || '';\n      if (mode.endsWithParent && parent.terminatorEnd) {\n        cmode.terminatorEnd += (mode.end ? '|' : '') + parent.terminatorEnd;\n      }\n    }\n    if (mode.illegal) cmode.illegalRe = langRe(/** @type {RegExp | string} */ (mode.illegal));\n    if (!mode.contains) mode.contains = [];\n\n    mode.contains = [].concat(...mode.contains.map(function(c) {\n      return expandOrCloneMode(c === 'self' ? mode : c);\n    }));\n    mode.contains.forEach(function(c) { compileMode(/** @type Mode */ (c), cmode); });\n\n    if (mode.starts) {\n      compileMode(mode.starts, parent);\n    }\n\n    cmode.matcher = buildModeRegex(cmode);\n    return cmode;\n  }\n\n  if (!language.compilerExtensions) language.compilerExtensions = [];\n\n  // self is not valid at the top-level\n  if (language.contains && language.contains.includes('self')) {\n    throw new Error(\"ERR: contains `self` is not supported at the top-level of a language.  See documentation.\");\n  }\n\n  // we need a null object, which inherit will guarantee\n  language.classNameAliases = inherit$1(language.classNameAliases || {});\n\n  return compileMode(/** @type Mode */ (language));\n}\n\n/**\n * Determines if a mode has a dependency on it's parent or not\n *\n * If a mode does have a parent dependency then often we need to clone it if\n * it's used in multiple places so that each copy points to the correct parent,\n * where-as modes without a parent can often safely be re-used at the bottom of\n * a mode chain.\n *\n * @param {Mode | null} mode\n * @returns {boolean} - is there a dependency on the parent?\n * */\nfunction dependencyOnParent(mode) {\n  if (!mode) return false;\n\n  return mode.endsWithParent || dependencyOnParent(mode.starts);\n}\n\n/**\n * Expands a mode or clones it if necessary\n *\n * This is necessary for modes with parental dependenceis (see notes on\n * `dependencyOnParent`) and for nodes that have `variants` - which must then be\n * exploded into their own individual modes at compile time.\n *\n * @param {Mode} mode\n * @returns {Mode | Mode[]}\n * */\nfunction expandOrCloneMode(mode) {\n  if (mode.variants && !mode.cachedVariants) {\n    mode.cachedVariants = mode.variants.map(function(variant) {\n      return inherit$1(mode, { variants: null }, variant);\n    });\n  }\n\n  // EXPAND\n  // if we have variants then essentially \"replace\" the mode with the variants\n  // this happens in compileMode, where this function is called from\n  if (mode.cachedVariants) {\n    return mode.cachedVariants;\n  }\n\n  // CLONE\n  // if we have dependencies on parents then we need a unique\n  // instance of ourselves, so we can be reused with many\n  // different parents without issue\n  if (dependencyOnParent(mode)) {\n    return inherit$1(mode, { starts: mode.starts ? inherit$1(mode.starts) : null });\n  }\n\n  if (Object.isFrozen(mode)) {\n    return inherit$1(mode);\n  }\n\n  // no special dependency issues, just return ourselves\n  return mode;\n}\n\nvar version = \"11.8.0\";\n\nclass HTMLInjectionError extends Error {\n  constructor(reason, html) {\n    super(reason);\n    this.name = \"HTMLInjectionError\";\n    this.html = html;\n  }\n}\n\n/*\nSyntax highlighting with language autodetection.\nhttps://highlightjs.org/\n*/\n\n\n/**\n@typedef {import('highlight.js').Mode} Mode\n@typedef {import('highlight.js').CompiledMode} CompiledMode\n@typedef {import('highlight.js').CompiledScope} CompiledScope\n@typedef {import('highlight.js').Language} Language\n@typedef {import('highlight.js').HLJSApi} HLJSApi\n@typedef {import('highlight.js').HLJSPlugin} HLJSPlugin\n@typedef {import('highlight.js').PluginEvent} PluginEvent\n@typedef {import('highlight.js').HLJSOptions} HLJSOptions\n@typedef {import('highlight.js').LanguageFn} LanguageFn\n@typedef {import('highlight.js').HighlightedHTMLElement} HighlightedHTMLElement\n@typedef {import('highlight.js').BeforeHighlightContext} BeforeHighlightContext\n@typedef {import('highlight.js/private').MatchType} MatchType\n@typedef {import('highlight.js/private').KeywordData} KeywordData\n@typedef {import('highlight.js/private').EnhancedMatch} EnhancedMatch\n@typedef {import('highlight.js/private').AnnotatedError} AnnotatedError\n@typedef {import('highlight.js').AutoHighlightResult} AutoHighlightResult\n@typedef {import('highlight.js').HighlightOptions} HighlightOptions\n@typedef {import('highlight.js').HighlightResult} HighlightResult\n*/\n\n\nconst escape = escapeHTML;\nconst inherit = inherit$1;\nconst NO_MATCH = Symbol(\"nomatch\");\nconst MAX_KEYWORD_HITS = 7;\n\n/**\n * @param {any} hljs - object that is extended (legacy)\n * @returns {HLJSApi}\n */\nconst HLJS = function(hljs) {\n  // Global internal variables used within the highlight.js library.\n  /** @type {Record} */\n  const languages = Object.create(null);\n  /** @type {Record} */\n  const aliases = Object.create(null);\n  /** @type {HLJSPlugin[]} */\n  const plugins = [];\n\n  // safe/production mode - swallows more errors, tries to keep running\n  // even if a single syntax or parse hits a fatal error\n  let SAFE_MODE = true;\n  const LANGUAGE_NOT_FOUND = \"Could not find the language '{}', did you forget to load/include a language module?\";\n  /** @type {Language} */\n  const PLAINTEXT_LANGUAGE = { disableAutodetect: true, name: 'Plain text', contains: [] };\n\n  // Global options used when within external APIs. This is modified when\n  // calling the `hljs.configure` function.\n  /** @type HLJSOptions */\n  let options = {\n    ignoreUnescapedHTML: false,\n    throwUnescapedHTML: false,\n    noHighlightRe: /^(no-?highlight)$/i,\n    languageDetectRe: /\\blang(?:uage)?-([\\w-]+)\\b/i,\n    classPrefix: 'hljs-',\n    cssSelector: 'pre code',\n    languages: null,\n    // beta configuration options, subject to change, welcome to discuss\n    // https://github.com/highlightjs/highlight.js/issues/1086\n    __emitter: TokenTreeEmitter\n  };\n\n  /* Utility functions */\n\n  /**\n   * Tests a language name to see if highlighting should be skipped\n   * @param {string} languageName\n   */\n  function shouldNotHighlight(languageName) {\n    return options.noHighlightRe.test(languageName);\n  }\n\n  /**\n   * @param {HighlightedHTMLElement} block - the HTML element to determine language for\n   */\n  function blockLanguage(block) {\n    let classes = block.className + ' ';\n\n    classes += block.parentNode ? block.parentNode.className : '';\n\n    // language-* takes precedence over non-prefixed class names.\n    const match = options.languageDetectRe.exec(classes);\n    if (match) {\n      const language = getLanguage(match[1]);\n      if (!language) {\n        warn(LANGUAGE_NOT_FOUND.replace(\"{}\", match[1]));\n        warn(\"Falling back to no-highlight mode for this block.\", block);\n      }\n      return language ? match[1] : 'no-highlight';\n    }\n\n    return classes\n      .split(/\\s+/)\n      .find((_class) => shouldNotHighlight(_class) || getLanguage(_class));\n  }\n\n  /**\n   * Core highlighting function.\n   *\n   * OLD API\n   * highlight(lang, code, ignoreIllegals, continuation)\n   *\n   * NEW API\n   * highlight(code, {lang, ignoreIllegals})\n   *\n   * @param {string} codeOrLanguageName - the language to use for highlighting\n   * @param {string | HighlightOptions} optionsOrCode - the code to highlight\n   * @param {boolean} [ignoreIllegals] - whether to ignore illegal matches, default is to bail\n   *\n   * @returns {HighlightResult} Result - an object that represents the result\n   * @property {string} language - the language name\n   * @property {number} relevance - the relevance score\n   * @property {string} value - the highlighted HTML code\n   * @property {string} code - the original raw code\n   * @property {CompiledMode} top - top of the current mode stack\n   * @property {boolean} illegal - indicates whether any illegal matches were found\n  */\n  function highlight(codeOrLanguageName, optionsOrCode, ignoreIllegals) {\n    let code = \"\";\n    let languageName = \"\";\n    if (typeof optionsOrCode === \"object\") {\n      code = codeOrLanguageName;\n      ignoreIllegals = optionsOrCode.ignoreIllegals;\n      languageName = optionsOrCode.language;\n    } else {\n      // old API\n      deprecated(\"10.7.0\", \"highlight(lang, code, ...args) has been deprecated.\");\n      deprecated(\"10.7.0\", \"Please use highlight(code, options) instead.\\nhttps://github.com/highlightjs/highlight.js/issues/2277\");\n      languageName = codeOrLanguageName;\n      code = optionsOrCode;\n    }\n\n    // https://github.com/highlightjs/highlight.js/issues/3149\n    // eslint-disable-next-line no-undefined\n    if (ignoreIllegals === undefined) { ignoreIllegals = true; }\n\n    /** @type {BeforeHighlightContext} */\n    const context = {\n      code,\n      language: languageName\n    };\n    // the plugin can change the desired language or the code to be highlighted\n    // just be changing the object it was passed\n    fire(\"before:highlight\", context);\n\n    // a before plugin can usurp the result completely by providing it's own\n    // in which case we don't even need to call highlight\n    const result = context.result\n      ? context.result\n      : _highlight(context.language, context.code, ignoreIllegals);\n\n    result.code = context.code;\n    // the plugin can change anything in result to suite it\n    fire(\"after:highlight\", result);\n\n    return result;\n  }\n\n  /**\n   * private highlight that's used internally and does not fire callbacks\n   *\n   * @param {string} languageName - the language to use for highlighting\n   * @param {string} codeToHighlight - the code to highlight\n   * @param {boolean?} [ignoreIllegals] - whether to ignore illegal matches, default is to bail\n   * @param {CompiledMode?} [continuation] - current continuation mode, if any\n   * @returns {HighlightResult} - result of the highlight operation\n  */\n  function _highlight(languageName, codeToHighlight, ignoreIllegals, continuation) {\n    const keywordHits = Object.create(null);\n\n    /**\n     * Return keyword data if a match is a keyword\n     * @param {CompiledMode} mode - current mode\n     * @param {string} matchText - the textual match\n     * @returns {KeywordData | false}\n     */\n    function keywordData(mode, matchText) {\n      return mode.keywords[matchText];\n    }\n\n    function processKeywords() {\n      if (!top.keywords) {\n        emitter.addText(modeBuffer);\n        return;\n      }\n\n      let lastIndex = 0;\n      top.keywordPatternRe.lastIndex = 0;\n      let match = top.keywordPatternRe.exec(modeBuffer);\n      let buf = \"\";\n\n      while (match) {\n        buf += modeBuffer.substring(lastIndex, match.index);\n        const word = language.case_insensitive ? match[0].toLowerCase() : match[0];\n        const data = keywordData(top, word);\n        if (data) {\n          const [kind, keywordRelevance] = data;\n          emitter.addText(buf);\n          buf = \"\";\n\n          keywordHits[word] = (keywordHits[word] || 0) + 1;\n          if (keywordHits[word] <= MAX_KEYWORD_HITS) relevance += keywordRelevance;\n          if (kind.startsWith(\"_\")) {\n            // _ implied for relevance only, do not highlight\n            // by applying a class name\n            buf += match[0];\n          } else {\n            const cssClass = language.classNameAliases[kind] || kind;\n            emitKeyword(match[0], cssClass);\n          }\n        } else {\n          buf += match[0];\n        }\n        lastIndex = top.keywordPatternRe.lastIndex;\n        match = top.keywordPatternRe.exec(modeBuffer);\n      }\n      buf += modeBuffer.substring(lastIndex);\n      emitter.addText(buf);\n    }\n\n    function processSubLanguage() {\n      if (modeBuffer === \"\") return;\n      /** @type HighlightResult */\n      let result = null;\n\n      if (typeof top.subLanguage === 'string') {\n        if (!languages[top.subLanguage]) {\n          emitter.addText(modeBuffer);\n          return;\n        }\n        result = _highlight(top.subLanguage, modeBuffer, true, continuations[top.subLanguage]);\n        continuations[top.subLanguage] = /** @type {CompiledMode} */ (result._top);\n      } else {\n        result = highlightAuto(modeBuffer, top.subLanguage.length ? top.subLanguage : null);\n      }\n\n      // Counting embedded language score towards the host language may be disabled\n      // with zeroing the containing mode relevance. Use case in point is Markdown that\n      // allows XML everywhere and makes every XML snippet to have a much larger Markdown\n      // score.\n      if (top.relevance > 0) {\n        relevance += result.relevance;\n      }\n      emitter.__addSublanguage(result._emitter, result.language);\n    }\n\n    function processBuffer() {\n      if (top.subLanguage != null) {\n        processSubLanguage();\n      } else {\n        processKeywords();\n      }\n      modeBuffer = '';\n    }\n\n    /**\n     * @param {string} text\n     * @param {string} scope\n     */\n    function emitKeyword(keyword, scope) {\n      if (keyword === \"\") return;\n\n      emitter.startScope(scope);\n      emitter.addText(keyword);\n      emitter.endScope();\n    }\n\n    /**\n     * @param {CompiledScope} scope\n     * @param {RegExpMatchArray} match\n     */\n    function emitMultiClass(scope, match) {\n      let i = 1;\n      const max = match.length - 1;\n      while (i <= max) {\n        if (!scope._emit[i]) { i++; continue; }\n        const klass = language.classNameAliases[scope[i]] || scope[i];\n        const text = match[i];\n        if (klass) {\n          emitKeyword(text, klass);\n        } else {\n          modeBuffer = text;\n          processKeywords();\n          modeBuffer = \"\";\n        }\n        i++;\n      }\n    }\n\n    /**\n     * @param {CompiledMode} mode - new mode to start\n     * @param {RegExpMatchArray} match\n     */\n    function startNewMode(mode, match) {\n      if (mode.scope && typeof mode.scope === \"string\") {\n        emitter.openNode(language.classNameAliases[mode.scope] || mode.scope);\n      }\n      if (mode.beginScope) {\n        // beginScope just wraps the begin match itself in a scope\n        if (mode.beginScope._wrap) {\n          emitKeyword(modeBuffer, language.classNameAliases[mode.beginScope._wrap] || mode.beginScope._wrap);\n          modeBuffer = \"\";\n        } else if (mode.beginScope._multi) {\n          // at this point modeBuffer should just be the match\n          emitMultiClass(mode.beginScope, match);\n          modeBuffer = \"\";\n        }\n      }\n\n      top = Object.create(mode, { parent: { value: top } });\n      return top;\n    }\n\n    /**\n     * @param {CompiledMode } mode - the mode to potentially end\n     * @param {RegExpMatchArray} match - the latest match\n     * @param {string} matchPlusRemainder - match plus remainder of content\n     * @returns {CompiledMode | void} - the next mode, or if void continue on in current mode\n     */\n    function endOfMode(mode, match, matchPlusRemainder) {\n      let matched = startsWith(mode.endRe, matchPlusRemainder);\n\n      if (matched) {\n        if (mode[\"on:end\"]) {\n          const resp = new Response(mode);\n          mode[\"on:end\"](match, resp);\n          if (resp.isMatchIgnored) matched = false;\n        }\n\n        if (matched) {\n          while (mode.endsParent && mode.parent) {\n            mode = mode.parent;\n          }\n          return mode;\n        }\n      }\n      // even if on:end fires an `ignore` it's still possible\n      // that we might trigger the end node because of a parent mode\n      if (mode.endsWithParent) {\n        return endOfMode(mode.parent, match, matchPlusRemainder);\n      }\n    }\n\n    /**\n     * Handle matching but then ignoring a sequence of text\n     *\n     * @param {string} lexeme - string containing full match text\n     */\n    function doIgnore(lexeme) {\n      if (top.matcher.regexIndex === 0) {\n        // no more regexes to potentially match here, so we move the cursor forward one\n        // space\n        modeBuffer += lexeme[0];\n        return 1;\n      } else {\n        // no need to move the cursor, we still have additional regexes to try and\n        // match at this very spot\n        resumeScanAtSamePosition = true;\n        return 0;\n      }\n    }\n\n    /**\n     * Handle the start of a new potential mode match\n     *\n     * @param {EnhancedMatch} match - the current match\n     * @returns {number} how far to advance the parse cursor\n     */\n    function doBeginMatch(match) {\n      const lexeme = match[0];\n      const newMode = match.rule;\n\n      const resp = new Response(newMode);\n      // first internal before callbacks, then the public ones\n      const beforeCallbacks = [newMode.__beforeBegin, newMode[\"on:begin\"]];\n      for (const cb of beforeCallbacks) {\n        if (!cb) continue;\n        cb(match, resp);\n        if (resp.isMatchIgnored) return doIgnore(lexeme);\n      }\n\n      if (newMode.skip) {\n        modeBuffer += lexeme;\n      } else {\n        if (newMode.excludeBegin) {\n          modeBuffer += lexeme;\n        }\n        processBuffer();\n        if (!newMode.returnBegin && !newMode.excludeBegin) {\n          modeBuffer = lexeme;\n        }\n      }\n      startNewMode(newMode, match);\n      return newMode.returnBegin ? 0 : lexeme.length;\n    }\n\n    /**\n     * Handle the potential end of mode\n     *\n     * @param {RegExpMatchArray} match - the current match\n     */\n    function doEndMatch(match) {\n      const lexeme = match[0];\n      const matchPlusRemainder = codeToHighlight.substring(match.index);\n\n      const endMode = endOfMode(top, match, matchPlusRemainder);\n      if (!endMode) { return NO_MATCH; }\n\n      const origin = top;\n      if (top.endScope && top.endScope._wrap) {\n        processBuffer();\n        emitKeyword(lexeme, top.endScope._wrap);\n      } else if (top.endScope && top.endScope._multi) {\n        processBuffer();\n        emitMultiClass(top.endScope, match);\n      } else if (origin.skip) {\n        modeBuffer += lexeme;\n      } else {\n        if (!(origin.returnEnd || origin.excludeEnd)) {\n          modeBuffer += lexeme;\n        }\n        processBuffer();\n        if (origin.excludeEnd) {\n          modeBuffer = lexeme;\n        }\n      }\n      do {\n        if (top.scope) {\n          emitter.closeNode();\n        }\n        if (!top.skip && !top.subLanguage) {\n          relevance += top.relevance;\n        }\n        top = top.parent;\n      } while (top !== endMode.parent);\n      if (endMode.starts) {\n        startNewMode(endMode.starts, match);\n      }\n      return origin.returnEnd ? 0 : lexeme.length;\n    }\n\n    function processContinuations() {\n      const list = [];\n      for (let current = top; current !== language; current = current.parent) {\n        if (current.scope) {\n          list.unshift(current.scope);\n        }\n      }\n      list.forEach(item => emitter.openNode(item));\n    }\n\n    /** @type {{type?: MatchType, index?: number, rule?: Mode}}} */\n    let lastMatch = {};\n\n    /**\n     *  Process an individual match\n     *\n     * @param {string} textBeforeMatch - text preceding the match (since the last match)\n     * @param {EnhancedMatch} [match] - the match itself\n     */\n    function processLexeme(textBeforeMatch, match) {\n      const lexeme = match && match[0];\n\n      // add non-matched text to the current mode buffer\n      modeBuffer += textBeforeMatch;\n\n      if (lexeme == null) {\n        processBuffer();\n        return 0;\n      }\n\n      // we've found a 0 width match and we're stuck, so we need to advance\n      // this happens when we have badly behaved rules that have optional matchers to the degree that\n      // sometimes they can end up matching nothing at all\n      // Ref: https://github.com/highlightjs/highlight.js/issues/2140\n      if (lastMatch.type === \"begin\" && match.type === \"end\" && lastMatch.index === match.index && lexeme === \"\") {\n        // spit the \"skipped\" character that our regex choked on back into the output sequence\n        modeBuffer += codeToHighlight.slice(match.index, match.index + 1);\n        if (!SAFE_MODE) {\n          /** @type {AnnotatedError} */\n          const err = new Error(`0 width match regex (${languageName})`);\n          err.languageName = languageName;\n          err.badRule = lastMatch.rule;\n          throw err;\n        }\n        return 1;\n      }\n      lastMatch = match;\n\n      if (match.type === \"begin\") {\n        return doBeginMatch(match);\n      } else if (match.type === \"illegal\" && !ignoreIllegals) {\n        // illegal match, we do not continue processing\n        /** @type {AnnotatedError} */\n        const err = new Error('Illegal lexeme \"' + lexeme + '\" for mode \"' + (top.scope || '') + '\"');\n        err.mode = top;\n        throw err;\n      } else if (match.type === \"end\") {\n        const processed = doEndMatch(match);\n        if (processed !== NO_MATCH) {\n          return processed;\n        }\n      }\n\n      // edge case for when illegal matches $ (end of line) which is technically\n      // a 0 width match but not a begin/end match so it's not caught by the\n      // first handler (when ignoreIllegals is true)\n      if (match.type === \"illegal\" && lexeme === \"\") {\n        // advance so we aren't stuck in an infinite loop\n        return 1;\n      }\n\n      // infinite loops are BAD, this is a last ditch catch all. if we have a\n      // decent number of iterations yet our index (cursor position in our\n      // parsing) still 3x behind our index then something is very wrong\n      // so we bail\n      if (iterations > 100000 && iterations > match.index * 3) {\n        const err = new Error('potential infinite loop, way more iterations than matches');\n        throw err;\n      }\n\n      /*\n      Why might be find ourselves here?  An potential end match that was\n      triggered but could not be completed.  IE, `doEndMatch` returned NO_MATCH.\n      (this could be because a callback requests the match be ignored, etc)\n\n      This causes no real harm other than stopping a few times too many.\n      */\n\n      modeBuffer += lexeme;\n      return lexeme.length;\n    }\n\n    const language = getLanguage(languageName);\n    if (!language) {\n      error(LANGUAGE_NOT_FOUND.replace(\"{}\", languageName));\n      throw new Error('Unknown language: \"' + languageName + '\"');\n    }\n\n    const md = compileLanguage(language);\n    let result = '';\n    /** @type {CompiledMode} */\n    let top = continuation || md;\n    /** @type Record */\n    const continuations = {}; // keep continuations for sub-languages\n    const emitter = new options.__emitter(options);\n    processContinuations();\n    let modeBuffer = '';\n    let relevance = 0;\n    let index = 0;\n    let iterations = 0;\n    let resumeScanAtSamePosition = false;\n\n    try {\n      if (!language.__emitTokens) {\n        top.matcher.considerAll();\n\n        for (;;) {\n          iterations++;\n          if (resumeScanAtSamePosition) {\n            // only regexes not matched previously will now be\n            // considered for a potential match\n            resumeScanAtSamePosition = false;\n          } else {\n            top.matcher.considerAll();\n          }\n          top.matcher.lastIndex = index;\n\n          const match = top.matcher.exec(codeToHighlight);\n          // console.log(\"match\", match[0], match.rule && match.rule.begin)\n\n          if (!match) break;\n\n          const beforeMatch = codeToHighlight.substring(index, match.index);\n          const processedCount = processLexeme(beforeMatch, match);\n          index = match.index + processedCount;\n        }\n        processLexeme(codeToHighlight.substring(index));\n      } else {\n        language.__emitTokens(codeToHighlight, emitter);\n      }\n\n      emitter.finalize();\n      result = emitter.toHTML();\n\n      return {\n        language: languageName,\n        value: result,\n        relevance,\n        illegal: false,\n        _emitter: emitter,\n        _top: top\n      };\n    } catch (err) {\n      if (err.message && err.message.includes('Illegal')) {\n        return {\n          language: languageName,\n          value: escape(codeToHighlight),\n          illegal: true,\n          relevance: 0,\n          _illegalBy: {\n            message: err.message,\n            index,\n            context: codeToHighlight.slice(index - 100, index + 100),\n            mode: err.mode,\n            resultSoFar: result\n          },\n          _emitter: emitter\n        };\n      } else if (SAFE_MODE) {\n        return {\n          language: languageName,\n          value: escape(codeToHighlight),\n          illegal: false,\n          relevance: 0,\n          errorRaised: err,\n          _emitter: emitter,\n          _top: top\n        };\n      } else {\n        throw err;\n      }\n    }\n  }\n\n  /**\n   * returns a valid highlight result, without actually doing any actual work,\n   * auto highlight starts with this and it's possible for small snippets that\n   * auto-detection may not find a better match\n   * @param {string} code\n   * @returns {HighlightResult}\n   */\n  function justTextHighlightResult(code) {\n    const result = {\n      value: escape(code),\n      illegal: false,\n      relevance: 0,\n      _top: PLAINTEXT_LANGUAGE,\n      _emitter: new options.__emitter(options)\n    };\n    result._emitter.addText(code);\n    return result;\n  }\n\n  /**\n  Highlighting with language detection. Accepts a string with the code to\n  highlight. Returns an object with the following properties:\n\n  - language (detected language)\n  - relevance (int)\n  - value (an HTML string with highlighting markup)\n  - secondBest (object with the same structure for second-best heuristically\n    detected language, may be absent)\n\n    @param {string} code\n    @param {Array} [languageSubset]\n    @returns {AutoHighlightResult}\n  */\n  function highlightAuto(code, languageSubset) {\n    languageSubset = languageSubset || options.languages || Object.keys(languages);\n    const plaintext = justTextHighlightResult(code);\n\n    const results = languageSubset.filter(getLanguage).filter(autoDetection).map(name =>\n      _highlight(name, code, false)\n    );\n    results.unshift(plaintext); // plaintext is always an option\n\n    const sorted = results.sort((a, b) => {\n      // sort base on relevance\n      if (a.relevance !== b.relevance) return b.relevance - a.relevance;\n\n      // always award the tie to the base language\n      // ie if C++ and Arduino are tied, it's more likely to be C++\n      if (a.language && b.language) {\n        if (getLanguage(a.language).supersetOf === b.language) {\n          return 1;\n        } else if (getLanguage(b.language).supersetOf === a.language) {\n          return -1;\n        }\n      }\n\n      // otherwise say they are equal, which has the effect of sorting on\n      // relevance while preserving the original ordering - which is how ties\n      // have historically been settled, ie the language that comes first always\n      // wins in the case of a tie\n      return 0;\n    });\n\n    const [best, secondBest] = sorted;\n\n    /** @type {AutoHighlightResult} */\n    const result = best;\n    result.secondBest = secondBest;\n\n    return result;\n  }\n\n  /**\n   * Builds new class name for block given the language name\n   *\n   * @param {HTMLElement} element\n   * @param {string} [currentLang]\n   * @param {string} [resultLang]\n   */\n  function updateClassName(element, currentLang, resultLang) {\n    const language = (currentLang && aliases[currentLang]) || resultLang;\n\n    element.classList.add(\"hljs\");\n    element.classList.add(`language-${language}`);\n  }\n\n  /**\n   * Applies highlighting to a DOM node containing code.\n   *\n   * @param {HighlightedHTMLElement} element - the HTML element to highlight\n  */\n  function highlightElement(element) {\n    /** @type HTMLElement */\n    let node = null;\n    const language = blockLanguage(element);\n\n    if (shouldNotHighlight(language)) return;\n\n    fire(\"before:highlightElement\",\n      { el: element, language });\n\n    // we should be all text, no child nodes (unescaped HTML) - this is possibly\n    // an HTML injection attack - it's likely too late if this is already in\n    // production (the code has likely already done its damage by the time\n    // we're seeing it)... but we yell loudly about this so that hopefully it's\n    // more likely to be caught in development before making it to production\n    if (element.children.length > 0) {\n      if (!options.ignoreUnescapedHTML) {\n        console.warn(\"One of your code blocks includes unescaped HTML. This is a potentially serious security risk.\");\n        console.warn(\"https://github.com/highlightjs/highlight.js/wiki/security\");\n        console.warn(\"The element with unescaped HTML:\");\n        console.warn(element);\n      }\n      if (options.throwUnescapedHTML) {\n        const err = new HTMLInjectionError(\n          \"One of your code blocks includes unescaped HTML.\",\n          element.innerHTML\n        );\n        throw err;\n      }\n    }\n\n    node = element;\n    const text = node.textContent;\n    const result = language ? highlight(text, { language, ignoreIllegals: true }) : highlightAuto(text);\n\n    element.innerHTML = result.value;\n    updateClassName(element, language, result.language);\n    element.result = {\n      language: result.language,\n      // TODO: remove with version 11.0\n      re: result.relevance,\n      relevance: result.relevance\n    };\n    if (result.secondBest) {\n      element.secondBest = {\n        language: result.secondBest.language,\n        relevance: result.secondBest.relevance\n      };\n    }\n\n    fire(\"after:highlightElement\", { el: element, result, text });\n  }\n\n  /**\n   * Updates highlight.js global options with the passed options\n   *\n   * @param {Partial} userOptions\n   */\n  function configure(userOptions) {\n    options = inherit(options, userOptions);\n  }\n\n  // TODO: remove v12, deprecated\n  const initHighlighting = () => {\n    highlightAll();\n    deprecated(\"10.6.0\", \"initHighlighting() deprecated.  Use highlightAll() now.\");\n  };\n\n  // TODO: remove v12, deprecated\n  function initHighlightingOnLoad() {\n    highlightAll();\n    deprecated(\"10.6.0\", \"initHighlightingOnLoad() deprecated.  Use highlightAll() now.\");\n  }\n\n  let wantsHighlight = false;\n\n  /**\n   * auto-highlights all pre>code elements on the page\n   */\n  function highlightAll() {\n    // if we are called too early in the loading process\n    if (document.readyState === \"loading\") {\n      wantsHighlight = true;\n      return;\n    }\n\n    const blocks = document.querySelectorAll(options.cssSelector);\n    blocks.forEach(highlightElement);\n  }\n\n  function boot() {\n    // if a highlight was requested before DOM was loaded, do now\n    if (wantsHighlight) highlightAll();\n  }\n\n  // make sure we are in the browser environment\n  if (typeof window !== 'undefined' && window.addEventListener) {\n    window.addEventListener('DOMContentLoaded', boot, false);\n  }\n\n  /**\n   * Register a language grammar module\n   *\n   * @param {string} languageName\n   * @param {LanguageFn} languageDefinition\n   */\n  function registerLanguage(languageName, languageDefinition) {\n    let lang = null;\n    try {\n      lang = languageDefinition(hljs);\n    } catch (error$1) {\n      error(\"Language definition for '{}' could not be registered.\".replace(\"{}\", languageName));\n      // hard or soft error\n      if (!SAFE_MODE) { throw error$1; } else { error(error$1); }\n      // languages that have serious errors are replaced with essentially a\n      // \"plaintext\" stand-in so that the code blocks will still get normal\n      // css classes applied to them - and one bad language won't break the\n      // entire highlighter\n      lang = PLAINTEXT_LANGUAGE;\n    }\n    // give it a temporary name if it doesn't have one in the meta-data\n    if (!lang.name) lang.name = languageName;\n    languages[languageName] = lang;\n    lang.rawDefinition = languageDefinition.bind(null, hljs);\n\n    if (lang.aliases) {\n      registerAliases(lang.aliases, { languageName });\n    }\n  }\n\n  /**\n   * Remove a language grammar module\n   *\n   * @param {string} languageName\n   */\n  function unregisterLanguage(languageName) {\n    delete languages[languageName];\n    for (const alias of Object.keys(aliases)) {\n      if (aliases[alias] === languageName) {\n        delete aliases[alias];\n      }\n    }\n  }\n\n  /**\n   * @returns {string[]} List of language internal names\n   */\n  function listLanguages() {\n    return Object.keys(languages);\n  }\n\n  /**\n   * @param {string} name - name of the language to retrieve\n   * @returns {Language | undefined}\n   */\n  function getLanguage(name) {\n    name = (name || '').toLowerCase();\n    return languages[name] || languages[aliases[name]];\n  }\n\n  /**\n   *\n   * @param {string|string[]} aliasList - single alias or list of aliases\n   * @param {{languageName: string}} opts\n   */\n  function registerAliases(aliasList, { languageName }) {\n    if (typeof aliasList === 'string') {\n      aliasList = [aliasList];\n    }\n    aliasList.forEach(alias => { aliases[alias.toLowerCase()] = languageName; });\n  }\n\n  /**\n   * Determines if a given language has auto-detection enabled\n   * @param {string} name - name of the language\n   */\n  function autoDetection(name) {\n    const lang = getLanguage(name);\n    return lang && !lang.disableAutodetect;\n  }\n\n  /**\n   * Upgrades the old highlightBlock plugins to the new\n   * highlightElement API\n   * @param {HLJSPlugin} plugin\n   */\n  function upgradePluginAPI(plugin) {\n    // TODO: remove with v12\n    if (plugin[\"before:highlightBlock\"] && !plugin[\"before:highlightElement\"]) {\n      plugin[\"before:highlightElement\"] = (data) => {\n        plugin[\"before:highlightBlock\"](\n          Object.assign({ block: data.el }, data)\n        );\n      };\n    }\n    if (plugin[\"after:highlightBlock\"] && !plugin[\"after:highlightElement\"]) {\n      plugin[\"after:highlightElement\"] = (data) => {\n        plugin[\"after:highlightBlock\"](\n          Object.assign({ block: data.el }, data)\n        );\n      };\n    }\n  }\n\n  /**\n   * @param {HLJSPlugin} plugin\n   */\n  function addPlugin(plugin) {\n    upgradePluginAPI(plugin);\n    plugins.push(plugin);\n  }\n\n  /**\n   * @param {HLJSPlugin} plugin\n   */\n  function removePlugin(plugin) {\n    const index = plugins.indexOf(plugin);\n    if (index !== -1) {\n      plugins.splice(index, 1);\n    }\n  }\n\n  /**\n   *\n   * @param {PluginEvent} event\n   * @param {any} args\n   */\n  function fire(event, args) {\n    const cb = event;\n    plugins.forEach(function(plugin) {\n      if (plugin[cb]) {\n        plugin[cb](args);\n      }\n    });\n  }\n\n  /**\n   * DEPRECATED\n   * @param {HighlightedHTMLElement} el\n   */\n  function deprecateHighlightBlock(el) {\n    deprecated(\"10.7.0\", \"highlightBlock will be removed entirely in v12.0\");\n    deprecated(\"10.7.0\", \"Please use highlightElement now.\");\n\n    return highlightElement(el);\n  }\n\n  /* Interface definition */\n  Object.assign(hljs, {\n    highlight,\n    highlightAuto,\n    highlightAll,\n    highlightElement,\n    // TODO: Remove with v12 API\n    highlightBlock: deprecateHighlightBlock,\n    configure,\n    initHighlighting,\n    initHighlightingOnLoad,\n    registerLanguage,\n    unregisterLanguage,\n    listLanguages,\n    getLanguage,\n    registerAliases,\n    autoDetection,\n    inherit,\n    addPlugin,\n    removePlugin\n  });\n\n  hljs.debugMode = function() { SAFE_MODE = false; };\n  hljs.safeMode = function() { SAFE_MODE = true; };\n  hljs.versionString = version;\n\n  hljs.regex = {\n    concat: concat,\n    lookahead: lookahead,\n    either: either,\n    optional: optional,\n    anyNumberOfTimes: anyNumberOfTimes\n  };\n\n  for (const key in MODES) {\n    // @ts-ignore\n    if (typeof MODES[key] === \"object\") {\n      // @ts-ignore\n      deepFreeze(MODES[key]);\n    }\n  }\n\n  // merge all the modes/regexes into our main object\n  Object.assign(hljs, MODES);\n\n  return hljs;\n};\n\n// Other names for the variable may break build script\nconst highlight = HLJS({});\n\n// returns a new instance of the highlighter to be used for extensions\n// check https://github.com/wooorm/lowlight/issues/47\nhighlight.newInstance = () => HLJS({});\n\nmodule.exports = highlight;\nhighlight.HighlightJS = highlight;\nhighlight.default = highlight;\n", "var LATIN_MAP = {\n    '\u00C0': 'A', '\u00C1': 'A', '\u00C2': 'A', '\u00C3': 'A', '\u00C4': 'A', '\u00C5': 'A', '\u00C6': 'AE',\n    '\u00C7': 'C', '\u00C8': 'E', '\u00C9': 'E', '\u00CA': 'E', '\u00CB': 'E', '\u00CC': 'I', '\u00CD': 'I',\n    '\u00CE': 'I', '\u00CF': 'I', '\u00D0': 'D', '\u00D1': 'N', '\u00D2': 'O', '\u00D3': 'O', '\u00D4': 'O',\n    '\u00D5': 'O', '\u00D6': 'O', '\u0150': 'O', '\u00D8': 'O', '\u00D9': 'U', '\u00DA': 'U', '\u00DB': 'U',\n    '\u00DC': 'U', '\u0170': 'U', '\u00DD': 'Y', '\u00DE': 'TH', '\u0178': 'Y', '\u00DF': 'ss', '\u00E0': 'a',\n    '\u00E1': 'a', '\u00E2': 'a', '\u00E3': 'a', '\u00E4': 'a', '\u00E5': 'a', '\u00E6': 'ae', '\u00E7': 'c',\n    '\u00E8': 'e', '\u00E9': 'e', '\u00EA': 'e', '\u00EB': 'e', '\u00EC': 'i', '\u00ED': 'i', '\u00EE': 'i',\n    '\u00EF': 'i', '\u00F0': 'd', '\u00F1': 'n', '\u00F2': 'o', '\u00F3': 'o', '\u00F4': 'o', '\u00F5': 'o',\n    '\u00F6': 'o', '\u0151': 'o', '\u00F8': 'o', '\u00F9': 'u', '\u00FA': 'u', '\u00FB': 'u', '\u00FC': 'u',\n    '\u0171': 'u', '\u00FD': 'y', '\u00FE': 'th', '\u00FF': 'y'\n};\n\nvar LATIN_SYMBOLS_MAP = {\n    '\u00A9': '(c)'\n};\n\nvar GREEK_MAP = {\n    '\u03B1': 'a', '\u03B2': 'b', '\u03B3': 'g', '\u03B4': 'd', '\u03B5': 'e', '\u03B6': 'z', '\u03B7': 'h',\n    '\u03B8': '8', '\u03B9': 'i', '\u03BA': 'k', '\u03BB': 'l', '\u03BC': 'm', '\u03BD': 'n', '\u03BE': '3',\n    '\u03BF': 'o', '\u03C0': 'p', '\u03C1': 'r', '\u03C3': 's', '\u03C4': 't', '\u03C5': 'y', '\u03C6': 'f',\n    '\u03C7': 'x', '\u03C8': 'ps', '\u03C9': 'w', '\u03AC': 'a', '\u03AD': 'e', '\u03AF': 'i', '\u03CC': 'o',\n    '\u03CD': 'y', '\u03AE': 'h', '\u03CE': 'w', '\u03C2': 's', '\u03CA': 'i', '\u03B0': 'y', '\u03CB': 'y',\n    '\u0390': 'i', '\u0391': 'A', '\u0392': 'B', '\u0393': 'G', '\u0394': 'D', '\u0395': 'E', '\u0396': 'Z',\n    '\u0397': 'H', '\u0398': '8', '\u0399': 'I', '\u039A': 'K', '\u039B': 'L', '\u039C': 'M', '\u039D': 'N',\n    '\u039E': '3', '\u039F': 'O', '\u03A0': 'P', '\u03A1': 'R', '\u03A3': 'S', '\u03A4': 'T', '\u03A5': 'Y',\n    '\u03A6': 'F', '\u03A7': 'X', '\u03A8': 'PS', '\u03A9': 'W', '\u0386': 'A', '\u0388': 'E', '\u038A': 'I',\n    '\u038C': 'O', '\u038E': 'Y', '\u0389': 'H', '\u038F': 'W', '\u03AA': 'I', '\u03AB': 'Y'\n};\n\nvar TURKISH_MAP = {\n    '\u015F': 's', '\u015E': 'S', '\u0131': 'i', '\u0130': 'I', '\u00E7': 'c', '\u00C7': 'C', '\u00FC': 'u',\n    '\u00DC': 'U', '\u00F6': 'o', '\u00D6': 'O', '\u011F': 'g', '\u011E': 'G'\n};\n\nvar ROMANIAN_MAP = {\n    '\u0103': 'a', '\u00EE': 'i', '\u0219': 's', '\u021B': 't', '\u00E2': 'a',\n    '\u0102': 'A', '\u00CE': 'I', '\u0218': 'S', '\u021A': 'T', '\u00C2': 'A'\n};\n\nvar RUSSIAN_MAP = {\n    '\u0430': 'a', '\u0431': 'b', '\u0432': 'v', '\u0433': 'g', '\u0434': 'd', '\u0435': 'e', '\u0451': 'yo',\n    '\u0436': 'zh', '\u0437': 'z', '\u0438': 'i', '\u0439': 'j', '\u043A': 'k', '\u043B': 'l', '\u043C': 'm',\n    '\u043D': 'n', '\u043E': 'o', '\u043F': 'p', '\u0440': 'r', '\u0441': 's', '\u0442': 't', '\u0443': 'u',\n    '\u0444': 'f', '\u0445': 'h', '\u0446': 'c', '\u0447': 'ch', '\u0448': 'sh', '\u0449': 'sh', '\u044A': '',\n    '\u044B': 'y', '\u044C': '', '\u044D': 'e', '\u044E': 'yu', '\u044F': 'ya',\n    '\u0410': 'A', '\u0411': 'B', '\u0412': 'V', '\u0413': 'G', '\u0414': 'D', '\u0415': 'E', '\u0401': 'Yo',\n    '\u0416': 'Zh', '\u0417': 'Z', '\u0418': 'I', '\u0419': 'J', '\u041A': 'K', '\u041B': 'L', '\u041C': 'M',\n    '\u041D': 'N', '\u041E': 'O', '\u041F': 'P', '\u0420': 'R', '\u0421': 'S', '\u0422': 'T', '\u0423': 'U',\n    '\u0424': 'F', '\u0425': 'H', '\u0426': 'C', '\u0427': 'Ch', '\u0428': 'Sh', '\u0429': 'Sh', '\u042A': '',\n    '\u042B': 'Y', '\u042C': '', '\u042D': 'E', '\u042E': 'Yu', '\u042F': 'Ya'\n};\n\nvar UKRAINIAN_MAP = {\n    '\u0404': 'Ye', '\u0406': 'I', '\u0407': 'Yi', '\u0490': 'G', '\u0454': 'ye', '\u0456': 'i',\n    '\u0457': 'yi', '\u0491': 'g'\n};\n\nvar CZECH_MAP = {\n    '\u010D': 'c', '\u010F': 'd', '\u011B': 'e', '\u0148': 'n', '\u0159': 'r', '\u0161': 's', '\u0165': 't',\n    '\u016F': 'u', '\u017E': 'z', '\u010C': 'C', '\u010E': 'D', '\u011A': 'E', '\u0147': 'N', '\u0158': 'R',\n    '\u0160': 'S', '\u0164': 'T', '\u016E': 'U', '\u017D': 'Z'\n};\n\nvar POLISH_MAP = {\n    '\u0105': 'a', '\u0107': 'c', '\u0119': 'e', '\u0142': 'l', '\u0144': 'n', '\u00F3': 'o', '\u015B': 's',\n    '\u017A': 'z', '\u017C': 'z',\n    '\u0104': 'A', '\u0106': 'C', '\u0118': 'E', '\u0141': 'L', '\u0143': 'N', '\u00D3': 'O', '\u015A': 'S',\n    '\u0179': 'Z', '\u017B': 'Z'\n};\n\nvar LATVIAN_MAP = {\n    '\u0101': 'a', '\u010D': 'c', '\u0113': 'e', '\u0123': 'g', '\u012B': 'i', '\u0137': 'k', '\u013C': 'l',\n    '\u0146': 'n', '\u0161': 's', '\u016B': 'u', '\u017E': 'z',\n    '\u0100': 'A', '\u010C': 'C', '\u0112': 'E', '\u0122': 'G', '\u012A': 'I', '\u0136': 'K', '\u013B': 'L',\n    '\u0145': 'N', '\u0160': 'S', '\u016A': 'U', '\u017D': 'Z'\n};\n\nvar ARABIC_MAP = {\n    '\u0623': 'a', '\u0628': 'b', '\u062A': 't', '\u062B': 'th', '\u062C': 'g', '\u062D': 'h', '\u062E': 'kh', '\u062F': 'd',\n    '\u0630': 'th', '\u0631': 'r', '\u0632': 'z', '\u0633': 's', '\u0634': 'sh', '\u0635': 's', '\u0636': 'd', '\u0637': 't',\n    '\u0638': 'th', '\u0639': 'aa', '\u063A': 'gh', '\u0641': 'f', '\u0642': 'k', '\u0643': 'k', '\u0644': 'l', '\u0645': 'm',\n    '\u0646': 'n', '\u0647': 'h', '\u0648': 'o', '\u064A': 'y'\n};\nvar LITHUANIAN_MAP = {\n    '\u0105': 'a', '\u010D': 'c', '\u0119': 'e', '\u0117': 'e', '\u012F': 'i', '\u0161': 's', '\u0173': 'u',\n    '\u016B': 'u', '\u017E': 'z',\n    '\u0104': 'A', '\u010C': 'C', '\u0118': 'E', '\u0116': 'E', '\u012E': 'I', '\u0160': 'S', '\u0172': 'U',\n    '\u016A': 'U', '\u017D': 'Z'\n};\nvar SERBIAN_MAP = {\n    '\u0452': 'dj', '\u0458': 'j', '\u0459': 'lj', '\u045A': 'nj', '\u045B': 'c', '\u045F': 'dz',\n    '\u0111': 'dj', '\u0402': 'Dj', '\u0408': 'j', '\u0409': 'Lj', '\u040A': 'Nj', '\u040B': 'C',\n    '\u040F': 'Dz', '\u0110': 'Dj'\n};\nvar AZERBAIJANI_MAP = {\n    '\u00E7': 'c', '\u0259': 'e', '\u011F': 'g', '\u0131': 'i', '\u00F6': 'o', '\u015F': 's', '\u00FC': 'u',\n    '\u00C7': 'C', '\u018F': 'E', '\u011E': 'G', '\u0130': 'I', '\u00D6': 'O', '\u015E': 'S', '\u00DC': 'U'\n};\nvar GEORGIAN_MAP = {\n    '\u10D0': 'a', '\u10D1': 'b', '\u10D2': 'g', '\u10D3': 'd', '\u10D4': 'e', '\u10D5': 'v', '\u10D6': 'z',\n    '\u10D7': 't', '\u10D8': 'i', '\u10D9': 'k', '\u10DA': 'l', '\u10DB': 'm', '\u10DC': 'n', '\u10DD': 'o',\n    '\u10DE': 'p', '\u10DF': 'j', '\u10E0': 'r', '\u10E1': 's', '\u10E2': 't', '\u10E3': 'u', '\u10E4': 'f',\n    '\u10E5': 'q', '\u10E6': 'g', '\u10E7': 'y', '\u10E8': 'sh', '\u10E9': 'ch', '\u10EA': 'c', '\u10EB': 'dz',\n    '\u10EC': 'w', '\u10ED': 'ch', '\u10EE': 'x', '\u10EF': 'j', '\u10F0': 'h'\n};\n\nvar SLOVAK_MAP = {\n    '\u00E1': 'a', '\u00E4': 'a', '\u010D': 'c', '\u010F': 'd', '\u00E9': 'e', '\u00ED': 'i', '\u013E': 'l',\n    '\u013A': 'l', '\u0148': 'n', '\u00F3': 'o', '\u00F4': 'o', '\u0155': 'r', '\u0161': 's', '\u0165': 't',\n    '\u00FA': 'u', '\u00FD': 'y', '\u017E': 'z',\n    '\u00C1': 'a', '\u00C4': 'A', '\u010C': 'C', '\u010E': 'D', '\u00C9': 'E', '\u00CD': 'I', '\u013D': 'L',\n    '\u0139': 'L', '\u0147': 'N', '\u00D3': 'O', '\u00D4': 'O', '\u0154': 'R', '\u0160': 'S', '\u0164': 'T',\n    '\u00DA': 'U', '\u00DD': 'Y', '\u017D': 'Z'\n};\n\nvar ALL_DOWNCODE_MAPS = [\n    LATIN_MAP,\n    LATIN_SYMBOLS_MAP,\n    GREEK_MAP,\n    TURKISH_MAP,\n    ROMANIAN_MAP,\n    RUSSIAN_MAP,\n    UKRAINIAN_MAP,\n    CZECH_MAP,\n    POLISH_MAP,\n    LATVIAN_MAP,\n    ARABIC_MAP,\n    LITHUANIAN_MAP,\n    SERBIAN_MAP,\n    AZERBAIJANI_MAP,\n    GEORGIAN_MAP,\n    SLOVAK_MAP\n];\n\n\nvar Downcoder = new Object();\nDowncoder.Initialize = function()\n{\n    if (Downcoder.map) // already made\n        return ;\n    Downcoder.map ={}\n    Downcoder.chars = '' ;\n    for(var i in ALL_DOWNCODE_MAPS)\n    {\n        var lookup = ALL_DOWNCODE_MAPS[i]\n        for (var c in lookup)\n        {\n            Downcoder.map[c] = lookup[c] ;\n            Downcoder.chars += c ;\n        }\n     }\n    Downcoder.regex = new RegExp('[' + Downcoder.chars + ']|[^' + Downcoder.chars + ']+','g') ;\n}\n\nvar downcode = function(slug){\n    Downcoder.Initialize() ;\n    var downcoded =\"\"\n    var pieces = slug.match(Downcoder.regex);\n    if(pieces)\n    {\n        for (var i = 0 ; i < pieces.length ; i++)\n        {\n            if (pieces[i].length == 1)\n            {\n                var mapped = Downcoder.map[pieces[i]] ;\n                if (mapped != null)\n                {\n                    downcoded+=mapped;\n                    continue ;\n                }\n            }\n            downcoded+=pieces[i];\n        }\n    }\n    else\n    {\n        downcoded = slug;\n    }\n    return downcoded;\n}\n\nparameterize = function(s, num_chars, delimiter) {\n    delimiter = delimiter || '-'\n\n    // changes, e.g., \"Petty theft\" to \"petty_theft\"\n    // remove all these words from the string before urlifying\n    s = downcode(s);\n    //\n    // if downcode doesn't hit, the char will be stripped here\n    s = s.replace(/[^-\\w\\s\\u4E00-\\u9FA5]/g, '');  // remove unneeded chars\n    s = s.replace(/^\\s+|\\s+$/g, ''); // trim leading/trailing spaces\n    s = s.replace(/[-\\s]+/g, delimiter);   // convert spaces to hyphens\n    s = s.toLowerCase();             // convert to lowercase\n    return s.substring(0, num_chars);// trim to first num_chars chars\n}\n\n\nif (typeof module !== 'undefined' && module.exports) {\n    module.exports = parameterize;\n} else if (typeof define === 'function' && define.amd) {\n    define('parameterize', [], function(require, exports, module) {\n        return parameterize;\n    });\n}\n", "/*\nTurbo 7.3.0\nCopyright \u00A9 2023 37signals LLC\n */\n(function () {\n    if (window.Reflect === undefined ||\n        window.customElements === undefined ||\n        window.customElements.polyfillWrapFlushCallback) {\n        return;\n    }\n    const BuiltInHTMLElement = HTMLElement;\n    const wrapperForTheName = {\n        HTMLElement: function HTMLElement() {\n            return Reflect.construct(BuiltInHTMLElement, [], this.constructor);\n        },\n    };\n    window.HTMLElement = wrapperForTheName[\"HTMLElement\"];\n    HTMLElement.prototype = BuiltInHTMLElement.prototype;\n    HTMLElement.prototype.constructor = HTMLElement;\n    Object.setPrototypeOf(HTMLElement, BuiltInHTMLElement);\n})();\n\n/**\n * The MIT License (MIT)\n * \n * Copyright (c) 2019 Javan Makhmali\n * \n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n * \n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n(function(prototype) {\n  if (typeof prototype.requestSubmit == \"function\") return\n\n  prototype.requestSubmit = function(submitter) {\n    if (submitter) {\n      validateSubmitter(submitter, this);\n      submitter.click();\n    } else {\n      submitter = document.createElement(\"input\");\n      submitter.type = \"submit\";\n      submitter.hidden = true;\n      this.appendChild(submitter);\n      submitter.click();\n      this.removeChild(submitter);\n    }\n  };\n\n  function validateSubmitter(submitter, form) {\n    submitter instanceof HTMLElement || raise(TypeError, \"parameter 1 is not of type 'HTMLElement'\");\n    submitter.type == \"submit\" || raise(TypeError, \"The specified element is not a submit button\");\n    submitter.form == form || raise(DOMException, \"The specified element is not owned by this form element\", \"NotFoundError\");\n  }\n\n  function raise(errorConstructor, message, name) {\n    throw new errorConstructor(\"Failed to execute 'requestSubmit' on 'HTMLFormElement': \" + message + \".\", name)\n  }\n})(HTMLFormElement.prototype);\n\nconst submittersByForm = new WeakMap();\nfunction findSubmitterFromClickTarget(target) {\n    const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;\n    const candidate = element ? element.closest(\"input, button\") : null;\n    return (candidate === null || candidate === void 0 ? void 0 : candidate.type) == \"submit\" ? candidate : null;\n}\nfunction clickCaptured(event) {\n    const submitter = findSubmitterFromClickTarget(event.target);\n    if (submitter && submitter.form) {\n        submittersByForm.set(submitter.form, submitter);\n    }\n}\n(function () {\n    if (\"submitter\" in Event.prototype)\n        return;\n    let prototype = window.Event.prototype;\n    if (\"SubmitEvent\" in window && /Apple Computer/.test(navigator.vendor)) {\n        prototype = window.SubmitEvent.prototype;\n    }\n    else if (\"SubmitEvent\" in window) {\n        return;\n    }\n    addEventListener(\"click\", clickCaptured, true);\n    Object.defineProperty(prototype, \"submitter\", {\n        get() {\n            if (this.type == \"submit\" && this.target instanceof HTMLFormElement) {\n                return submittersByForm.get(this.target);\n            }\n        },\n    });\n})();\n\nvar FrameLoadingStyle;\n(function (FrameLoadingStyle) {\n    FrameLoadingStyle[\"eager\"] = \"eager\";\n    FrameLoadingStyle[\"lazy\"] = \"lazy\";\n})(FrameLoadingStyle || (FrameLoadingStyle = {}));\nclass FrameElement extends HTMLElement {\n    static get observedAttributes() {\n        return [\"disabled\", \"complete\", \"loading\", \"src\"];\n    }\n    constructor() {\n        super();\n        this.loaded = Promise.resolve();\n        this.delegate = new FrameElement.delegateConstructor(this);\n    }\n    connectedCallback() {\n        this.delegate.connect();\n    }\n    disconnectedCallback() {\n        this.delegate.disconnect();\n    }\n    reload() {\n        return this.delegate.sourceURLReloaded();\n    }\n    attributeChangedCallback(name) {\n        if (name == \"loading\") {\n            this.delegate.loadingStyleChanged();\n        }\n        else if (name == \"complete\") {\n            this.delegate.completeChanged();\n        }\n        else if (name == \"src\") {\n            this.delegate.sourceURLChanged();\n        }\n        else {\n            this.delegate.disabledChanged();\n        }\n    }\n    get src() {\n        return this.getAttribute(\"src\");\n    }\n    set src(value) {\n        if (value) {\n            this.setAttribute(\"src\", value);\n        }\n        else {\n            this.removeAttribute(\"src\");\n        }\n    }\n    get loading() {\n        return frameLoadingStyleFromString(this.getAttribute(\"loading\") || \"\");\n    }\n    set loading(value) {\n        if (value) {\n            this.setAttribute(\"loading\", value);\n        }\n        else {\n            this.removeAttribute(\"loading\");\n        }\n    }\n    get disabled() {\n        return this.hasAttribute(\"disabled\");\n    }\n    set disabled(value) {\n        if (value) {\n            this.setAttribute(\"disabled\", \"\");\n        }\n        else {\n            this.removeAttribute(\"disabled\");\n        }\n    }\n    get autoscroll() {\n        return this.hasAttribute(\"autoscroll\");\n    }\n    set autoscroll(value) {\n        if (value) {\n            this.setAttribute(\"autoscroll\", \"\");\n        }\n        else {\n            this.removeAttribute(\"autoscroll\");\n        }\n    }\n    get complete() {\n        return !this.delegate.isLoading;\n    }\n    get isActive() {\n        return this.ownerDocument === document && !this.isPreview;\n    }\n    get isPreview() {\n        var _a, _b;\n        return (_b = (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.documentElement) === null || _b === void 0 ? void 0 : _b.hasAttribute(\"data-turbo-preview\");\n    }\n}\nfunction frameLoadingStyleFromString(style) {\n    switch (style.toLowerCase()) {\n        case \"lazy\":\n            return FrameLoadingStyle.lazy;\n        default:\n            return FrameLoadingStyle.eager;\n    }\n}\n\nfunction expandURL(locatable) {\n    return new URL(locatable.toString(), document.baseURI);\n}\nfunction getAnchor(url) {\n    let anchorMatch;\n    if (url.hash) {\n        return url.hash.slice(1);\n    }\n    else if ((anchorMatch = url.href.match(/#(.*)$/))) {\n        return anchorMatch[1];\n    }\n}\nfunction getAction(form, submitter) {\n    const action = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute(\"formaction\")) || form.getAttribute(\"action\") || form.action;\n    return expandURL(action);\n}\nfunction getExtension(url) {\n    return (getLastPathComponent(url).match(/\\.[^.]*$/) || [])[0] || \"\";\n}\nfunction isHTML(url) {\n    return !!getExtension(url).match(/^(?:|\\.(?:htm|html|xhtml|php))$/);\n}\nfunction isPrefixedBy(baseURL, url) {\n    const prefix = getPrefix(url);\n    return baseURL.href === expandURL(prefix).href || baseURL.href.startsWith(prefix);\n}\nfunction locationIsVisitable(location, rootLocation) {\n    return isPrefixedBy(location, rootLocation) && isHTML(location);\n}\nfunction getRequestURL(url) {\n    const anchor = getAnchor(url);\n    return anchor != null ? url.href.slice(0, -(anchor.length + 1)) : url.href;\n}\nfunction toCacheKey(url) {\n    return getRequestURL(url);\n}\nfunction urlsAreEqual(left, right) {\n    return expandURL(left).href == expandURL(right).href;\n}\nfunction getPathComponents(url) {\n    return url.pathname.split(\"/\").slice(1);\n}\nfunction getLastPathComponent(url) {\n    return getPathComponents(url).slice(-1)[0];\n}\nfunction getPrefix(url) {\n    return addTrailingSlash(url.origin + url.pathname);\n}\nfunction addTrailingSlash(value) {\n    return value.endsWith(\"/\") ? value : value + \"/\";\n}\n\nclass FetchResponse {\n    constructor(response) {\n        this.response = response;\n    }\n    get succeeded() {\n        return this.response.ok;\n    }\n    get failed() {\n        return !this.succeeded;\n    }\n    get clientError() {\n        return this.statusCode >= 400 && this.statusCode <= 499;\n    }\n    get serverError() {\n        return this.statusCode >= 500 && this.statusCode <= 599;\n    }\n    get redirected() {\n        return this.response.redirected;\n    }\n    get location() {\n        return expandURL(this.response.url);\n    }\n    get isHTML() {\n        return this.contentType && this.contentType.match(/^(?:text\\/([^\\s;,]+\\b)?html|application\\/xhtml\\+xml)\\b/);\n    }\n    get statusCode() {\n        return this.response.status;\n    }\n    get contentType() {\n        return this.header(\"Content-Type\");\n    }\n    get responseText() {\n        return this.response.clone().text();\n    }\n    get responseHTML() {\n        if (this.isHTML) {\n            return this.response.clone().text();\n        }\n        else {\n            return Promise.resolve(undefined);\n        }\n    }\n    header(name) {\n        return this.response.headers.get(name);\n    }\n}\n\nfunction activateScriptElement(element) {\n    if (element.getAttribute(\"data-turbo-eval\") == \"false\") {\n        return element;\n    }\n    else {\n        const createdScriptElement = document.createElement(\"script\");\n        const cspNonce = getMetaContent(\"csp-nonce\");\n        if (cspNonce) {\n            createdScriptElement.nonce = cspNonce;\n        }\n        createdScriptElement.textContent = element.textContent;\n        createdScriptElement.async = false;\n        copyElementAttributes(createdScriptElement, element);\n        return createdScriptElement;\n    }\n}\nfunction copyElementAttributes(destinationElement, sourceElement) {\n    for (const { name, value } of sourceElement.attributes) {\n        destinationElement.setAttribute(name, value);\n    }\n}\nfunction createDocumentFragment(html) {\n    const template = document.createElement(\"template\");\n    template.innerHTML = html;\n    return template.content;\n}\nfunction dispatch(eventName, { target, cancelable, detail } = {}) {\n    const event = new CustomEvent(eventName, {\n        cancelable,\n        bubbles: true,\n        composed: true,\n        detail,\n    });\n    if (target && target.isConnected) {\n        target.dispatchEvent(event);\n    }\n    else {\n        document.documentElement.dispatchEvent(event);\n    }\n    return event;\n}\nfunction nextAnimationFrame() {\n    return new Promise((resolve) => requestAnimationFrame(() => resolve()));\n}\nfunction nextEventLoopTick() {\n    return new Promise((resolve) => setTimeout(() => resolve(), 0));\n}\nfunction nextMicrotask() {\n    return Promise.resolve();\n}\nfunction parseHTMLDocument(html = \"\") {\n    return new DOMParser().parseFromString(html, \"text/html\");\n}\nfunction unindent(strings, ...values) {\n    const lines = interpolate(strings, values).replace(/^\\n/, \"\").split(\"\\n\");\n    const match = lines[0].match(/^\\s+/);\n    const indent = match ? match[0].length : 0;\n    return lines.map((line) => line.slice(indent)).join(\"\\n\");\n}\nfunction interpolate(strings, values) {\n    return strings.reduce((result, string, i) => {\n        const value = values[i] == undefined ? \"\" : values[i];\n        return result + string + value;\n    }, \"\");\n}\nfunction uuid() {\n    return Array.from({ length: 36 })\n        .map((_, i) => {\n        if (i == 8 || i == 13 || i == 18 || i == 23) {\n            return \"-\";\n        }\n        else if (i == 14) {\n            return \"4\";\n        }\n        else if (i == 19) {\n            return (Math.floor(Math.random() * 4) + 8).toString(16);\n        }\n        else {\n            return Math.floor(Math.random() * 15).toString(16);\n        }\n    })\n        .join(\"\");\n}\nfunction getAttribute(attributeName, ...elements) {\n    for (const value of elements.map((element) => element === null || element === void 0 ? void 0 : element.getAttribute(attributeName))) {\n        if (typeof value == \"string\")\n            return value;\n    }\n    return null;\n}\nfunction hasAttribute(attributeName, ...elements) {\n    return elements.some((element) => element && element.hasAttribute(attributeName));\n}\nfunction markAsBusy(...elements) {\n    for (const element of elements) {\n        if (element.localName == \"turbo-frame\") {\n            element.setAttribute(\"busy\", \"\");\n        }\n        element.setAttribute(\"aria-busy\", \"true\");\n    }\n}\nfunction clearBusyState(...elements) {\n    for (const element of elements) {\n        if (element.localName == \"turbo-frame\") {\n            element.removeAttribute(\"busy\");\n        }\n        element.removeAttribute(\"aria-busy\");\n    }\n}\nfunction waitForLoad(element, timeoutInMilliseconds = 2000) {\n    return new Promise((resolve) => {\n        const onComplete = () => {\n            element.removeEventListener(\"error\", onComplete);\n            element.removeEventListener(\"load\", onComplete);\n            resolve();\n        };\n        element.addEventListener(\"load\", onComplete, { once: true });\n        element.addEventListener(\"error\", onComplete, { once: true });\n        setTimeout(resolve, timeoutInMilliseconds);\n    });\n}\nfunction getHistoryMethodForAction(action) {\n    switch (action) {\n        case \"replace\":\n            return history.replaceState;\n        case \"advance\":\n        case \"restore\":\n            return history.pushState;\n    }\n}\nfunction isAction(action) {\n    return action == \"advance\" || action == \"replace\" || action == \"restore\";\n}\nfunction getVisitAction(...elements) {\n    const action = getAttribute(\"data-turbo-action\", ...elements);\n    return isAction(action) ? action : null;\n}\nfunction getMetaElement(name) {\n    return document.querySelector(`meta[name=\"${name}\"]`);\n}\nfunction getMetaContent(name) {\n    const element = getMetaElement(name);\n    return element && element.content;\n}\nfunction setMetaContent(name, content) {\n    let element = getMetaElement(name);\n    if (!element) {\n        element = document.createElement(\"meta\");\n        element.setAttribute(\"name\", name);\n        document.head.appendChild(element);\n    }\n    element.setAttribute(\"content\", content);\n    return element;\n}\nfunction findClosestRecursively(element, selector) {\n    var _a;\n    if (element instanceof Element) {\n        return (element.closest(selector) ||\n            findClosestRecursively(element.assignedSlot || ((_a = element.getRootNode()) === null || _a === void 0 ? void 0 : _a.host), selector));\n    }\n}\n\nvar FetchMethod;\n(function (FetchMethod) {\n    FetchMethod[FetchMethod[\"get\"] = 0] = \"get\";\n    FetchMethod[FetchMethod[\"post\"] = 1] = \"post\";\n    FetchMethod[FetchMethod[\"put\"] = 2] = \"put\";\n    FetchMethod[FetchMethod[\"patch\"] = 3] = \"patch\";\n    FetchMethod[FetchMethod[\"delete\"] = 4] = \"delete\";\n})(FetchMethod || (FetchMethod = {}));\nfunction fetchMethodFromString(method) {\n    switch (method.toLowerCase()) {\n        case \"get\":\n            return FetchMethod.get;\n        case \"post\":\n            return FetchMethod.post;\n        case \"put\":\n            return FetchMethod.put;\n        case \"patch\":\n            return FetchMethod.patch;\n        case \"delete\":\n            return FetchMethod.delete;\n    }\n}\nclass FetchRequest {\n    constructor(delegate, method, location, body = new URLSearchParams(), target = null) {\n        this.abortController = new AbortController();\n        this.resolveRequestPromise = (_value) => { };\n        this.delegate = delegate;\n        this.method = method;\n        this.headers = this.defaultHeaders;\n        this.body = body;\n        this.url = location;\n        this.target = target;\n    }\n    get location() {\n        return this.url;\n    }\n    get params() {\n        return this.url.searchParams;\n    }\n    get entries() {\n        return this.body ? Array.from(this.body.entries()) : [];\n    }\n    cancel() {\n        this.abortController.abort();\n    }\n    async perform() {\n        const { fetchOptions } = this;\n        this.delegate.prepareRequest(this);\n        await this.allowRequestToBeIntercepted(fetchOptions);\n        try {\n            this.delegate.requestStarted(this);\n            const response = await fetch(this.url.href, fetchOptions);\n            return await this.receive(response);\n        }\n        catch (error) {\n            if (error.name !== \"AbortError\") {\n                if (this.willDelegateErrorHandling(error)) {\n                    this.delegate.requestErrored(this, error);\n                }\n                throw error;\n            }\n        }\n        finally {\n            this.delegate.requestFinished(this);\n        }\n    }\n    async receive(response) {\n        const fetchResponse = new FetchResponse(response);\n        const event = dispatch(\"turbo:before-fetch-response\", {\n            cancelable: true,\n            detail: { fetchResponse },\n            target: this.target,\n        });\n        if (event.defaultPrevented) {\n            this.delegate.requestPreventedHandlingResponse(this, fetchResponse);\n        }\n        else if (fetchResponse.succeeded) {\n            this.delegate.requestSucceededWithResponse(this, fetchResponse);\n        }\n        else {\n            this.delegate.requestFailedWithResponse(this, fetchResponse);\n        }\n        return fetchResponse;\n    }\n    get fetchOptions() {\n        var _a;\n        return {\n            method: FetchMethod[this.method].toUpperCase(),\n            credentials: \"same-origin\",\n            headers: this.headers,\n            redirect: \"follow\",\n            body: this.isSafe ? null : this.body,\n            signal: this.abortSignal,\n            referrer: (_a = this.delegate.referrer) === null || _a === void 0 ? void 0 : _a.href,\n        };\n    }\n    get defaultHeaders() {\n        return {\n            Accept: \"text/html, application/xhtml+xml\",\n        };\n    }\n    get isSafe() {\n        return this.method === FetchMethod.get;\n    }\n    get abortSignal() {\n        return this.abortController.signal;\n    }\n    acceptResponseType(mimeType) {\n        this.headers[\"Accept\"] = [mimeType, this.headers[\"Accept\"]].join(\", \");\n    }\n    async allowRequestToBeIntercepted(fetchOptions) {\n        const requestInterception = new Promise((resolve) => (this.resolveRequestPromise = resolve));\n        const event = dispatch(\"turbo:before-fetch-request\", {\n            cancelable: true,\n            detail: {\n                fetchOptions,\n                url: this.url,\n                resume: this.resolveRequestPromise,\n            },\n            target: this.target,\n        });\n        if (event.defaultPrevented)\n            await requestInterception;\n    }\n    willDelegateErrorHandling(error) {\n        const event = dispatch(\"turbo:fetch-request-error\", {\n            target: this.target,\n            cancelable: true,\n            detail: { request: this, error: error },\n        });\n        return !event.defaultPrevented;\n    }\n}\n\nclass AppearanceObserver {\n    constructor(delegate, element) {\n        this.started = false;\n        this.intersect = (entries) => {\n            const lastEntry = entries.slice(-1)[0];\n            if (lastEntry === null || lastEntry === void 0 ? void 0 : lastEntry.isIntersecting) {\n                this.delegate.elementAppearedInViewport(this.element);\n            }\n        };\n        this.delegate = delegate;\n        this.element = element;\n        this.intersectionObserver = new IntersectionObserver(this.intersect);\n    }\n    start() {\n        if (!this.started) {\n            this.started = true;\n            this.intersectionObserver.observe(this.element);\n        }\n    }\n    stop() {\n        if (this.started) {\n            this.started = false;\n            this.intersectionObserver.unobserve(this.element);\n        }\n    }\n}\n\nclass StreamMessage {\n    static wrap(message) {\n        if (typeof message == \"string\") {\n            return new this(createDocumentFragment(message));\n        }\n        else {\n            return message;\n        }\n    }\n    constructor(fragment) {\n        this.fragment = importStreamElements(fragment);\n    }\n}\nStreamMessage.contentType = \"text/vnd.turbo-stream.html\";\nfunction importStreamElements(fragment) {\n    for (const element of fragment.querySelectorAll(\"turbo-stream\")) {\n        const streamElement = document.importNode(element, true);\n        for (const inertScriptElement of streamElement.templateElement.content.querySelectorAll(\"script\")) {\n            inertScriptElement.replaceWith(activateScriptElement(inertScriptElement));\n        }\n        element.replaceWith(streamElement);\n    }\n    return fragment;\n}\n\nvar FormSubmissionState;\n(function (FormSubmissionState) {\n    FormSubmissionState[FormSubmissionState[\"initialized\"] = 0] = \"initialized\";\n    FormSubmissionState[FormSubmissionState[\"requesting\"] = 1] = \"requesting\";\n    FormSubmissionState[FormSubmissionState[\"waiting\"] = 2] = \"waiting\";\n    FormSubmissionState[FormSubmissionState[\"receiving\"] = 3] = \"receiving\";\n    FormSubmissionState[FormSubmissionState[\"stopping\"] = 4] = \"stopping\";\n    FormSubmissionState[FormSubmissionState[\"stopped\"] = 5] = \"stopped\";\n})(FormSubmissionState || (FormSubmissionState = {}));\nvar FormEnctype;\n(function (FormEnctype) {\n    FormEnctype[\"urlEncoded\"] = \"application/x-www-form-urlencoded\";\n    FormEnctype[\"multipart\"] = \"multipart/form-data\";\n    FormEnctype[\"plain\"] = \"text/plain\";\n})(FormEnctype || (FormEnctype = {}));\nfunction formEnctypeFromString(encoding) {\n    switch (encoding.toLowerCase()) {\n        case FormEnctype.multipart:\n            return FormEnctype.multipart;\n        case FormEnctype.plain:\n            return FormEnctype.plain;\n        default:\n            return FormEnctype.urlEncoded;\n    }\n}\nclass FormSubmission {\n    static confirmMethod(message, _element, _submitter) {\n        return Promise.resolve(confirm(message));\n    }\n    constructor(delegate, formElement, submitter, mustRedirect = false) {\n        this.state = FormSubmissionState.initialized;\n        this.delegate = delegate;\n        this.formElement = formElement;\n        this.submitter = submitter;\n        this.formData = buildFormData(formElement, submitter);\n        this.location = expandURL(this.action);\n        if (this.method == FetchMethod.get) {\n            mergeFormDataEntries(this.location, [...this.body.entries()]);\n        }\n        this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body, this.formElement);\n        this.mustRedirect = mustRedirect;\n    }\n    get method() {\n        var _a;\n        const method = ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute(\"formmethod\")) || this.formElement.getAttribute(\"method\") || \"\";\n        return fetchMethodFromString(method.toLowerCase()) || FetchMethod.get;\n    }\n    get action() {\n        var _a;\n        const formElementAction = typeof this.formElement.action === \"string\" ? this.formElement.action : null;\n        if ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.hasAttribute(\"formaction\")) {\n            return this.submitter.getAttribute(\"formaction\") || \"\";\n        }\n        else {\n            return this.formElement.getAttribute(\"action\") || formElementAction || \"\";\n        }\n    }\n    get body() {\n        if (this.enctype == FormEnctype.urlEncoded || this.method == FetchMethod.get) {\n            return new URLSearchParams(this.stringFormData);\n        }\n        else {\n            return this.formData;\n        }\n    }\n    get enctype() {\n        var _a;\n        return formEnctypeFromString(((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute(\"formenctype\")) || this.formElement.enctype);\n    }\n    get isSafe() {\n        return this.fetchRequest.isSafe;\n    }\n    get stringFormData() {\n        return [...this.formData].reduce((entries, [name, value]) => {\n            return entries.concat(typeof value == \"string\" ? [[name, value]] : []);\n        }, []);\n    }\n    async start() {\n        const { initialized, requesting } = FormSubmissionState;\n        const confirmationMessage = getAttribute(\"data-turbo-confirm\", this.submitter, this.formElement);\n        if (typeof confirmationMessage === \"string\") {\n            const answer = await FormSubmission.confirmMethod(confirmationMessage, this.formElement, this.submitter);\n            if (!answer) {\n                return;\n            }\n        }\n        if (this.state == initialized) {\n            this.state = requesting;\n            return this.fetchRequest.perform();\n        }\n    }\n    stop() {\n        const { stopping, stopped } = FormSubmissionState;\n        if (this.state != stopping && this.state != stopped) {\n            this.state = stopping;\n            this.fetchRequest.cancel();\n            return true;\n        }\n    }\n    prepareRequest(request) {\n        if (!request.isSafe) {\n            const token = getCookieValue(getMetaContent(\"csrf-param\")) || getMetaContent(\"csrf-token\");\n            if (token) {\n                request.headers[\"X-CSRF-Token\"] = token;\n            }\n        }\n        if (this.requestAcceptsTurboStreamResponse(request)) {\n            request.acceptResponseType(StreamMessage.contentType);\n        }\n    }\n    requestStarted(_request) {\n        var _a;\n        this.state = FormSubmissionState.waiting;\n        (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.setAttribute(\"disabled\", \"\");\n        this.setSubmitsWith();\n        dispatch(\"turbo:submit-start\", {\n            target: this.formElement,\n            detail: { formSubmission: this },\n        });\n        this.delegate.formSubmissionStarted(this);\n    }\n    requestPreventedHandlingResponse(request, response) {\n        this.result = { success: response.succeeded, fetchResponse: response };\n    }\n    requestSucceededWithResponse(request, response) {\n        if (response.clientError || response.serverError) {\n            this.delegate.formSubmissionFailedWithResponse(this, response);\n        }\n        else if (this.requestMustRedirect(request) && responseSucceededWithoutRedirect(response)) {\n            const error = new Error(\"Form responses must redirect to another location\");\n            this.delegate.formSubmissionErrored(this, error);\n        }\n        else {\n            this.state = FormSubmissionState.receiving;\n            this.result = { success: true, fetchResponse: response };\n            this.delegate.formSubmissionSucceededWithResponse(this, response);\n        }\n    }\n    requestFailedWithResponse(request, response) {\n        this.result = { success: false, fetchResponse: response };\n        this.delegate.formSubmissionFailedWithResponse(this, response);\n    }\n    requestErrored(request, error) {\n        this.result = { success: false, error };\n        this.delegate.formSubmissionErrored(this, error);\n    }\n    requestFinished(_request) {\n        var _a;\n        this.state = FormSubmissionState.stopped;\n        (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.removeAttribute(\"disabled\");\n        this.resetSubmitterText();\n        dispatch(\"turbo:submit-end\", {\n            target: this.formElement,\n            detail: Object.assign({ formSubmission: this }, this.result),\n        });\n        this.delegate.formSubmissionFinished(this);\n    }\n    setSubmitsWith() {\n        if (!this.submitter || !this.submitsWith)\n            return;\n        if (this.submitter.matches(\"button\")) {\n            this.originalSubmitText = this.submitter.innerHTML;\n            this.submitter.innerHTML = this.submitsWith;\n        }\n        else if (this.submitter.matches(\"input\")) {\n            const input = this.submitter;\n            this.originalSubmitText = input.value;\n            input.value = this.submitsWith;\n        }\n    }\n    resetSubmitterText() {\n        if (!this.submitter || !this.originalSubmitText)\n            return;\n        if (this.submitter.matches(\"button\")) {\n            this.submitter.innerHTML = this.originalSubmitText;\n        }\n        else if (this.submitter.matches(\"input\")) {\n            const input = this.submitter;\n            input.value = this.originalSubmitText;\n        }\n    }\n    requestMustRedirect(request) {\n        return !request.isSafe && this.mustRedirect;\n    }\n    requestAcceptsTurboStreamResponse(request) {\n        return !request.isSafe || hasAttribute(\"data-turbo-stream\", this.submitter, this.formElement);\n    }\n    get submitsWith() {\n        var _a;\n        return (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute(\"data-turbo-submits-with\");\n    }\n}\nfunction buildFormData(formElement, submitter) {\n    const formData = new FormData(formElement);\n    const name = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute(\"name\");\n    const value = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute(\"value\");\n    if (name) {\n        formData.append(name, value || \"\");\n    }\n    return formData;\n}\nfunction getCookieValue(cookieName) {\n    if (cookieName != null) {\n        const cookies = document.cookie ? document.cookie.split(\"; \") : [];\n        const cookie = cookies.find((cookie) => cookie.startsWith(cookieName));\n        if (cookie) {\n            const value = cookie.split(\"=\").slice(1).join(\"=\");\n            return value ? decodeURIComponent(value) : undefined;\n        }\n    }\n}\nfunction responseSucceededWithoutRedirect(response) {\n    return response.statusCode == 200 && !response.redirected;\n}\nfunction mergeFormDataEntries(url, entries) {\n    const searchParams = new URLSearchParams();\n    for (const [name, value] of entries) {\n        if (value instanceof File)\n            continue;\n        searchParams.append(name, value);\n    }\n    url.search = searchParams.toString();\n    return url;\n}\n\nclass Snapshot {\n    constructor(element) {\n        this.element = element;\n    }\n    get activeElement() {\n        return this.element.ownerDocument.activeElement;\n    }\n    get children() {\n        return [...this.element.children];\n    }\n    hasAnchor(anchor) {\n        return this.getElementForAnchor(anchor) != null;\n    }\n    getElementForAnchor(anchor) {\n        return anchor ? this.element.querySelector(`[id='${anchor}'], a[name='${anchor}']`) : null;\n    }\n    get isConnected() {\n        return this.element.isConnected;\n    }\n    get firstAutofocusableElement() {\n        const inertDisabledOrHidden = \"[inert], :disabled, [hidden], details:not([open]), dialog:not([open])\";\n        for (const element of this.element.querySelectorAll(\"[autofocus]\")) {\n            if (element.closest(inertDisabledOrHidden) == null)\n                return element;\n            else\n                continue;\n        }\n        return null;\n    }\n    get permanentElements() {\n        return queryPermanentElementsAll(this.element);\n    }\n    getPermanentElementById(id) {\n        return getPermanentElementById(this.element, id);\n    }\n    getPermanentElementMapForSnapshot(snapshot) {\n        const permanentElementMap = {};\n        for (const currentPermanentElement of this.permanentElements) {\n            const { id } = currentPermanentElement;\n            const newPermanentElement = snapshot.getPermanentElementById(id);\n            if (newPermanentElement) {\n                permanentElementMap[id] = [currentPermanentElement, newPermanentElement];\n            }\n        }\n        return permanentElementMap;\n    }\n}\nfunction getPermanentElementById(node, id) {\n    return node.querySelector(`#${id}[data-turbo-permanent]`);\n}\nfunction queryPermanentElementsAll(node) {\n    return node.querySelectorAll(\"[id][data-turbo-permanent]\");\n}\n\nclass FormSubmitObserver {\n    constructor(delegate, eventTarget) {\n        this.started = false;\n        this.submitCaptured = () => {\n            this.eventTarget.removeEventListener(\"submit\", this.submitBubbled, false);\n            this.eventTarget.addEventListener(\"submit\", this.submitBubbled, false);\n        };\n        this.submitBubbled = ((event) => {\n            if (!event.defaultPrevented) {\n                const form = event.target instanceof HTMLFormElement ? event.target : undefined;\n                const submitter = event.submitter || undefined;\n                if (form &&\n                    submissionDoesNotDismissDialog(form, submitter) &&\n                    submissionDoesNotTargetIFrame(form, submitter) &&\n                    this.delegate.willSubmitForm(form, submitter)) {\n                    event.preventDefault();\n                    event.stopImmediatePropagation();\n                    this.delegate.formSubmitted(form, submitter);\n                }\n            }\n        });\n        this.delegate = delegate;\n        this.eventTarget = eventTarget;\n    }\n    start() {\n        if (!this.started) {\n            this.eventTarget.addEventListener(\"submit\", this.submitCaptured, true);\n            this.started = true;\n        }\n    }\n    stop() {\n        if (this.started) {\n            this.eventTarget.removeEventListener(\"submit\", this.submitCaptured, true);\n            this.started = false;\n        }\n    }\n}\nfunction submissionDoesNotDismissDialog(form, submitter) {\n    const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute(\"formmethod\")) || form.getAttribute(\"method\");\n    return method != \"dialog\";\n}\nfunction submissionDoesNotTargetIFrame(form, submitter) {\n    if ((submitter === null || submitter === void 0 ? void 0 : submitter.hasAttribute(\"formtarget\")) || form.hasAttribute(\"target\")) {\n        const target = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute(\"formtarget\")) || form.target;\n        for (const element of document.getElementsByName(target)) {\n            if (element instanceof HTMLIFrameElement)\n                return false;\n        }\n        return true;\n    }\n    else {\n        return true;\n    }\n}\n\nclass View {\n    constructor(delegate, element) {\n        this.resolveRenderPromise = (_value) => { };\n        this.resolveInterceptionPromise = (_value) => { };\n        this.delegate = delegate;\n        this.element = element;\n    }\n    scrollToAnchor(anchor) {\n        const element = this.snapshot.getElementForAnchor(anchor);\n        if (element) {\n            this.scrollToElement(element);\n            this.focusElement(element);\n        }\n        else {\n            this.scrollToPosition({ x: 0, y: 0 });\n        }\n    }\n    scrollToAnchorFromLocation(location) {\n        this.scrollToAnchor(getAnchor(location));\n    }\n    scrollToElement(element) {\n        element.scrollIntoView();\n    }\n    focusElement(element) {\n        if (element instanceof HTMLElement) {\n            if (element.hasAttribute(\"tabindex\")) {\n                element.focus();\n            }\n            else {\n                element.setAttribute(\"tabindex\", \"-1\");\n                element.focus();\n                element.removeAttribute(\"tabindex\");\n            }\n        }\n    }\n    scrollToPosition({ x, y }) {\n        this.scrollRoot.scrollTo(x, y);\n    }\n    scrollToTop() {\n        this.scrollToPosition({ x: 0, y: 0 });\n    }\n    get scrollRoot() {\n        return window;\n    }\n    async render(renderer) {\n        const { isPreview, shouldRender, newSnapshot: snapshot } = renderer;\n        if (shouldRender) {\n            try {\n                this.renderPromise = new Promise((resolve) => (this.resolveRenderPromise = resolve));\n                this.renderer = renderer;\n                await this.prepareToRenderSnapshot(renderer);\n                const renderInterception = new Promise((resolve) => (this.resolveInterceptionPromise = resolve));\n                const options = { resume: this.resolveInterceptionPromise, render: this.renderer.renderElement };\n                const immediateRender = this.delegate.allowsImmediateRender(snapshot, options);\n                if (!immediateRender)\n                    await renderInterception;\n                await this.renderSnapshot(renderer);\n                this.delegate.viewRenderedSnapshot(snapshot, isPreview);\n                this.delegate.preloadOnLoadLinksForView(this.element);\n                this.finishRenderingSnapshot(renderer);\n            }\n            finally {\n                delete this.renderer;\n                this.resolveRenderPromise(undefined);\n                delete this.renderPromise;\n            }\n        }\n        else {\n            this.invalidate(renderer.reloadReason);\n        }\n    }\n    invalidate(reason) {\n        this.delegate.viewInvalidated(reason);\n    }\n    async prepareToRenderSnapshot(renderer) {\n        this.markAsPreview(renderer.isPreview);\n        await renderer.prepareToRender();\n    }\n    markAsPreview(isPreview) {\n        if (isPreview) {\n            this.element.setAttribute(\"data-turbo-preview\", \"\");\n        }\n        else {\n            this.element.removeAttribute(\"data-turbo-preview\");\n        }\n    }\n    async renderSnapshot(renderer) {\n        await renderer.render();\n    }\n    finishRenderingSnapshot(renderer) {\n        renderer.finishRendering();\n    }\n}\n\nclass FrameView extends View {\n    missing() {\n        this.element.innerHTML = `Content missing`;\n    }\n    get snapshot() {\n        return new Snapshot(this.element);\n    }\n}\n\nclass LinkInterceptor {\n    constructor(delegate, element) {\n        this.clickBubbled = (event) => {\n            if (this.respondsToEventTarget(event.target)) {\n                this.clickEvent = event;\n            }\n            else {\n                delete this.clickEvent;\n            }\n        };\n        this.linkClicked = ((event) => {\n            if (this.clickEvent && this.respondsToEventTarget(event.target) && event.target instanceof Element) {\n                if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url, event.detail.originalEvent)) {\n                    this.clickEvent.preventDefault();\n                    event.preventDefault();\n                    this.delegate.linkClickIntercepted(event.target, event.detail.url, event.detail.originalEvent);\n                }\n            }\n            delete this.clickEvent;\n        });\n        this.willVisit = ((_event) => {\n            delete this.clickEvent;\n        });\n        this.delegate = delegate;\n        this.element = element;\n    }\n    start() {\n        this.element.addEventListener(\"click\", this.clickBubbled);\n        document.addEventListener(\"turbo:click\", this.linkClicked);\n        document.addEventListener(\"turbo:before-visit\", this.willVisit);\n    }\n    stop() {\n        this.element.removeEventListener(\"click\", this.clickBubbled);\n        document.removeEventListener(\"turbo:click\", this.linkClicked);\n        document.removeEventListener(\"turbo:before-visit\", this.willVisit);\n    }\n    respondsToEventTarget(target) {\n        const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;\n        return element && element.closest(\"turbo-frame, html\") == this.element;\n    }\n}\n\nclass LinkClickObserver {\n    constructor(delegate, eventTarget) {\n        this.started = false;\n        this.clickCaptured = () => {\n            this.eventTarget.removeEventListener(\"click\", this.clickBubbled, false);\n            this.eventTarget.addEventListener(\"click\", this.clickBubbled, false);\n        };\n        this.clickBubbled = (event) => {\n            if (event instanceof MouseEvent && this.clickEventIsSignificant(event)) {\n                const target = (event.composedPath && event.composedPath()[0]) || event.target;\n                const link = this.findLinkFromClickTarget(target);\n                if (link && doesNotTargetIFrame(link)) {\n                    const location = this.getLocationForLink(link);\n                    if (this.delegate.willFollowLinkToLocation(link, location, event)) {\n                        event.preventDefault();\n                        this.delegate.followedLinkToLocation(link, location);\n                    }\n                }\n            }\n        };\n        this.delegate = delegate;\n        this.eventTarget = eventTarget;\n    }\n    start() {\n        if (!this.started) {\n            this.eventTarget.addEventListener(\"click\", this.clickCaptured, true);\n            this.started = true;\n        }\n    }\n    stop() {\n        if (this.started) {\n            this.eventTarget.removeEventListener(\"click\", this.clickCaptured, true);\n            this.started = false;\n        }\n    }\n    clickEventIsSignificant(event) {\n        return !((event.target && event.target.isContentEditable) ||\n            event.defaultPrevented ||\n            event.which > 1 ||\n            event.altKey ||\n            event.ctrlKey ||\n            event.metaKey ||\n            event.shiftKey);\n    }\n    findLinkFromClickTarget(target) {\n        return findClosestRecursively(target, \"a[href]:not([target^=_]):not([download])\");\n    }\n    getLocationForLink(link) {\n        return expandURL(link.getAttribute(\"href\") || \"\");\n    }\n}\nfunction doesNotTargetIFrame(anchor) {\n    if (anchor.hasAttribute(\"target\")) {\n        for (const element of document.getElementsByName(anchor.target)) {\n            if (element instanceof HTMLIFrameElement)\n                return false;\n        }\n        return true;\n    }\n    else {\n        return true;\n    }\n}\n\nclass FormLinkClickObserver {\n    constructor(delegate, element) {\n        this.delegate = delegate;\n        this.linkInterceptor = new LinkClickObserver(this, element);\n    }\n    start() {\n        this.linkInterceptor.start();\n    }\n    stop() {\n        this.linkInterceptor.stop();\n    }\n    willFollowLinkToLocation(link, location, originalEvent) {\n        return (this.delegate.willSubmitFormLinkToLocation(link, location, originalEvent) &&\n            link.hasAttribute(\"data-turbo-method\"));\n    }\n    followedLinkToLocation(link, location) {\n        const form = document.createElement(\"form\");\n        const type = \"hidden\";\n        for (const [name, value] of location.searchParams) {\n            form.append(Object.assign(document.createElement(\"input\"), { type, name, value }));\n        }\n        const action = Object.assign(location, { search: \"\" });\n        form.setAttribute(\"data-turbo\", \"true\");\n        form.setAttribute(\"action\", action.href);\n        form.setAttribute(\"hidden\", \"\");\n        const method = link.getAttribute(\"data-turbo-method\");\n        if (method)\n            form.setAttribute(\"method\", method);\n        const turboFrame = link.getAttribute(\"data-turbo-frame\");\n        if (turboFrame)\n            form.setAttribute(\"data-turbo-frame\", turboFrame);\n        const turboAction = getVisitAction(link);\n        if (turboAction)\n            form.setAttribute(\"data-turbo-action\", turboAction);\n        const turboConfirm = link.getAttribute(\"data-turbo-confirm\");\n        if (turboConfirm)\n            form.setAttribute(\"data-turbo-confirm\", turboConfirm);\n        const turboStream = link.hasAttribute(\"data-turbo-stream\");\n        if (turboStream)\n            form.setAttribute(\"data-turbo-stream\", \"\");\n        this.delegate.submittedFormLinkToLocation(link, location, form);\n        document.body.appendChild(form);\n        form.addEventListener(\"turbo:submit-end\", () => form.remove(), { once: true });\n        requestAnimationFrame(() => form.requestSubmit());\n    }\n}\n\nclass Bardo {\n    static async preservingPermanentElements(delegate, permanentElementMap, callback) {\n        const bardo = new this(delegate, permanentElementMap);\n        bardo.enter();\n        await callback();\n        bardo.leave();\n    }\n    constructor(delegate, permanentElementMap) {\n        this.delegate = delegate;\n        this.permanentElementMap = permanentElementMap;\n    }\n    enter() {\n        for (const id in this.permanentElementMap) {\n            const [currentPermanentElement, newPermanentElement] = this.permanentElementMap[id];\n            this.delegate.enteringBardo(currentPermanentElement, newPermanentElement);\n            this.replaceNewPermanentElementWithPlaceholder(newPermanentElement);\n        }\n    }\n    leave() {\n        for (const id in this.permanentElementMap) {\n            const [currentPermanentElement] = this.permanentElementMap[id];\n            this.replaceCurrentPermanentElementWithClone(currentPermanentElement);\n            this.replacePlaceholderWithPermanentElement(currentPermanentElement);\n            this.delegate.leavingBardo(currentPermanentElement);\n        }\n    }\n    replaceNewPermanentElementWithPlaceholder(permanentElement) {\n        const placeholder = createPlaceholderForPermanentElement(permanentElement);\n        permanentElement.replaceWith(placeholder);\n    }\n    replaceCurrentPermanentElementWithClone(permanentElement) {\n        const clone = permanentElement.cloneNode(true);\n        permanentElement.replaceWith(clone);\n    }\n    replacePlaceholderWithPermanentElement(permanentElement) {\n        const placeholder = this.getPlaceholderById(permanentElement.id);\n        placeholder === null || placeholder === void 0 ? void 0 : placeholder.replaceWith(permanentElement);\n    }\n    getPlaceholderById(id) {\n        return this.placeholders.find((element) => element.content == id);\n    }\n    get placeholders() {\n        return [...document.querySelectorAll(\"meta[name=turbo-permanent-placeholder][content]\")];\n    }\n}\nfunction createPlaceholderForPermanentElement(permanentElement) {\n    const element = document.createElement(\"meta\");\n    element.setAttribute(\"name\", \"turbo-permanent-placeholder\");\n    element.setAttribute(\"content\", permanentElement.id);\n    return element;\n}\n\nclass Renderer {\n    constructor(currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {\n        this.activeElement = null;\n        this.currentSnapshot = currentSnapshot;\n        this.newSnapshot = newSnapshot;\n        this.isPreview = isPreview;\n        this.willRender = willRender;\n        this.renderElement = renderElement;\n        this.promise = new Promise((resolve, reject) => (this.resolvingFunctions = { resolve, reject }));\n    }\n    get shouldRender() {\n        return true;\n    }\n    get reloadReason() {\n        return;\n    }\n    prepareToRender() {\n        return;\n    }\n    finishRendering() {\n        if (this.resolvingFunctions) {\n            this.resolvingFunctions.resolve();\n            delete this.resolvingFunctions;\n        }\n    }\n    async preservingPermanentElements(callback) {\n        await Bardo.preservingPermanentElements(this, this.permanentElementMap, callback);\n    }\n    focusFirstAutofocusableElement() {\n        const element = this.connectedSnapshot.firstAutofocusableElement;\n        if (elementIsFocusable(element)) {\n            element.focus();\n        }\n    }\n    enteringBardo(currentPermanentElement) {\n        if (this.activeElement)\n            return;\n        if (currentPermanentElement.contains(this.currentSnapshot.activeElement)) {\n            this.activeElement = this.currentSnapshot.activeElement;\n        }\n    }\n    leavingBardo(currentPermanentElement) {\n        if (currentPermanentElement.contains(this.activeElement) && this.activeElement instanceof HTMLElement) {\n            this.activeElement.focus();\n            this.activeElement = null;\n        }\n    }\n    get connectedSnapshot() {\n        return this.newSnapshot.isConnected ? this.newSnapshot : this.currentSnapshot;\n    }\n    get currentElement() {\n        return this.currentSnapshot.element;\n    }\n    get newElement() {\n        return this.newSnapshot.element;\n    }\n    get permanentElementMap() {\n        return this.currentSnapshot.getPermanentElementMapForSnapshot(this.newSnapshot);\n    }\n}\nfunction elementIsFocusable(element) {\n    return element && typeof element.focus == \"function\";\n}\n\nclass FrameRenderer extends Renderer {\n    static renderElement(currentElement, newElement) {\n        var _a;\n        const destinationRange = document.createRange();\n        destinationRange.selectNodeContents(currentElement);\n        destinationRange.deleteContents();\n        const frameElement = newElement;\n        const sourceRange = (_a = frameElement.ownerDocument) === null || _a === void 0 ? void 0 : _a.createRange();\n        if (sourceRange) {\n            sourceRange.selectNodeContents(frameElement);\n            currentElement.appendChild(sourceRange.extractContents());\n        }\n    }\n    constructor(delegate, currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {\n        super(currentSnapshot, newSnapshot, renderElement, isPreview, willRender);\n        this.delegate = delegate;\n    }\n    get shouldRender() {\n        return true;\n    }\n    async render() {\n        await nextAnimationFrame();\n        this.preservingPermanentElements(() => {\n            this.loadFrameElement();\n        });\n        this.scrollFrameIntoView();\n        await nextAnimationFrame();\n        this.focusFirstAutofocusableElement();\n        await nextAnimationFrame();\n        this.activateScriptElements();\n    }\n    loadFrameElement() {\n        this.delegate.willRenderFrame(this.currentElement, this.newElement);\n        this.renderElement(this.currentElement, this.newElement);\n    }\n    scrollFrameIntoView() {\n        if (this.currentElement.autoscroll || this.newElement.autoscroll) {\n            const element = this.currentElement.firstElementChild;\n            const block = readScrollLogicalPosition(this.currentElement.getAttribute(\"data-autoscroll-block\"), \"end\");\n            const behavior = readScrollBehavior(this.currentElement.getAttribute(\"data-autoscroll-behavior\"), \"auto\");\n            if (element) {\n                element.scrollIntoView({ block, behavior });\n                return true;\n            }\n        }\n        return false;\n    }\n    activateScriptElements() {\n        for (const inertScriptElement of this.newScriptElements) {\n            const activatedScriptElement = activateScriptElement(inertScriptElement);\n            inertScriptElement.replaceWith(activatedScriptElement);\n        }\n    }\n    get newScriptElements() {\n        return this.currentElement.querySelectorAll(\"script\");\n    }\n}\nfunction readScrollLogicalPosition(value, defaultValue) {\n    if (value == \"end\" || value == \"start\" || value == \"center\" || value == \"nearest\") {\n        return value;\n    }\n    else {\n        return defaultValue;\n    }\n}\nfunction readScrollBehavior(value, defaultValue) {\n    if (value == \"auto\" || value == \"smooth\") {\n        return value;\n    }\n    else {\n        return defaultValue;\n    }\n}\n\nclass ProgressBar {\n    static get defaultCSS() {\n        return unindent `\n      .turbo-progress-bar {\n        position: fixed;\n        display: block;\n        top: 0;\n        left: 0;\n        height: 3px;\n        background: #0076ff;\n        z-index: 2147483647;\n        transition:\n          width ${ProgressBar.animationDuration}ms ease-out,\n          opacity ${ProgressBar.animationDuration / 2}ms ${ProgressBar.animationDuration / 2}ms ease-in;\n        transform: translate3d(0, 0, 0);\n      }\n    `;\n    }\n    constructor() {\n        this.hiding = false;\n        this.value = 0;\n        this.visible = false;\n        this.trickle = () => {\n            this.setValue(this.value + Math.random() / 100);\n        };\n        this.stylesheetElement = this.createStylesheetElement();\n        this.progressElement = this.createProgressElement();\n        this.installStylesheetElement();\n        this.setValue(0);\n    }\n    show() {\n        if (!this.visible) {\n            this.visible = true;\n            this.installProgressElement();\n            this.startTrickling();\n        }\n    }\n    hide() {\n        if (this.visible && !this.hiding) {\n            this.hiding = true;\n            this.fadeProgressElement(() => {\n                this.uninstallProgressElement();\n                this.stopTrickling();\n                this.visible = false;\n                this.hiding = false;\n            });\n        }\n    }\n    setValue(value) {\n        this.value = value;\n        this.refresh();\n    }\n    installStylesheetElement() {\n        document.head.insertBefore(this.stylesheetElement, document.head.firstChild);\n    }\n    installProgressElement() {\n        this.progressElement.style.width = \"0\";\n        this.progressElement.style.opacity = \"1\";\n        document.documentElement.insertBefore(this.progressElement, document.body);\n        this.refresh();\n    }\n    fadeProgressElement(callback) {\n        this.progressElement.style.opacity = \"0\";\n        setTimeout(callback, ProgressBar.animationDuration * 1.5);\n    }\n    uninstallProgressElement() {\n        if (this.progressElement.parentNode) {\n            document.documentElement.removeChild(this.progressElement);\n        }\n    }\n    startTrickling() {\n        if (!this.trickleInterval) {\n            this.trickleInterval = window.setInterval(this.trickle, ProgressBar.animationDuration);\n        }\n    }\n    stopTrickling() {\n        window.clearInterval(this.trickleInterval);\n        delete this.trickleInterval;\n    }\n    refresh() {\n        requestAnimationFrame(() => {\n            this.progressElement.style.width = `${10 + this.value * 90}%`;\n        });\n    }\n    createStylesheetElement() {\n        const element = document.createElement(\"style\");\n        element.type = \"text/css\";\n        element.textContent = ProgressBar.defaultCSS;\n        if (this.cspNonce) {\n            element.nonce = this.cspNonce;\n        }\n        return element;\n    }\n    createProgressElement() {\n        const element = document.createElement(\"div\");\n        element.className = \"turbo-progress-bar\";\n        return element;\n    }\n    get cspNonce() {\n        return getMetaContent(\"csp-nonce\");\n    }\n}\nProgressBar.animationDuration = 300;\n\nclass HeadSnapshot extends Snapshot {\n    constructor() {\n        super(...arguments);\n        this.detailsByOuterHTML = this.children\n            .filter((element) => !elementIsNoscript(element))\n            .map((element) => elementWithoutNonce(element))\n            .reduce((result, element) => {\n            const { outerHTML } = element;\n            const details = outerHTML in result\n                ? result[outerHTML]\n                : {\n                    type: elementType(element),\n                    tracked: elementIsTracked(element),\n                    elements: [],\n                };\n            return Object.assign(Object.assign({}, result), { [outerHTML]: Object.assign(Object.assign({}, details), { elements: [...details.elements, element] }) });\n        }, {});\n    }\n    get trackedElementSignature() {\n        return Object.keys(this.detailsByOuterHTML)\n            .filter((outerHTML) => this.detailsByOuterHTML[outerHTML].tracked)\n            .join(\"\");\n    }\n    getScriptElementsNotInSnapshot(snapshot) {\n        return this.getElementsMatchingTypeNotInSnapshot(\"script\", snapshot);\n    }\n    getStylesheetElementsNotInSnapshot(snapshot) {\n        return this.getElementsMatchingTypeNotInSnapshot(\"stylesheet\", snapshot);\n    }\n    getElementsMatchingTypeNotInSnapshot(matchedType, snapshot) {\n        return Object.keys(this.detailsByOuterHTML)\n            .filter((outerHTML) => !(outerHTML in snapshot.detailsByOuterHTML))\n            .map((outerHTML) => this.detailsByOuterHTML[outerHTML])\n            .filter(({ type }) => type == matchedType)\n            .map(({ elements: [element] }) => element);\n    }\n    get provisionalElements() {\n        return Object.keys(this.detailsByOuterHTML).reduce((result, outerHTML) => {\n            const { type, tracked, elements } = this.detailsByOuterHTML[outerHTML];\n            if (type == null && !tracked) {\n                return [...result, ...elements];\n            }\n            else if (elements.length > 1) {\n                return [...result, ...elements.slice(1)];\n            }\n            else {\n                return result;\n            }\n        }, []);\n    }\n    getMetaValue(name) {\n        const element = this.findMetaElementByName(name);\n        return element ? element.getAttribute(\"content\") : null;\n    }\n    findMetaElementByName(name) {\n        return Object.keys(this.detailsByOuterHTML).reduce((result, outerHTML) => {\n            const { elements: [element], } = this.detailsByOuterHTML[outerHTML];\n            return elementIsMetaElementWithName(element, name) ? element : result;\n        }, undefined);\n    }\n}\nfunction elementType(element) {\n    if (elementIsScript(element)) {\n        return \"script\";\n    }\n    else if (elementIsStylesheet(element)) {\n        return \"stylesheet\";\n    }\n}\nfunction elementIsTracked(element) {\n    return element.getAttribute(\"data-turbo-track\") == \"reload\";\n}\nfunction elementIsScript(element) {\n    const tagName = element.localName;\n    return tagName == \"script\";\n}\nfunction elementIsNoscript(element) {\n    const tagName = element.localName;\n    return tagName == \"noscript\";\n}\nfunction elementIsStylesheet(element) {\n    const tagName = element.localName;\n    return tagName == \"style\" || (tagName == \"link\" && element.getAttribute(\"rel\") == \"stylesheet\");\n}\nfunction elementIsMetaElementWithName(element, name) {\n    const tagName = element.localName;\n    return tagName == \"meta\" && element.getAttribute(\"name\") == name;\n}\nfunction elementWithoutNonce(element) {\n    if (element.hasAttribute(\"nonce\")) {\n        element.setAttribute(\"nonce\", \"\");\n    }\n    return element;\n}\n\nclass PageSnapshot extends Snapshot {\n    static fromHTMLString(html = \"\") {\n        return this.fromDocument(parseHTMLDocument(html));\n    }\n    static fromElement(element) {\n        return this.fromDocument(element.ownerDocument);\n    }\n    static fromDocument({ head, body }) {\n        return new this(body, new HeadSnapshot(head));\n    }\n    constructor(element, headSnapshot) {\n        super(element);\n        this.headSnapshot = headSnapshot;\n    }\n    clone() {\n        const clonedElement = this.element.cloneNode(true);\n        const selectElements = this.element.querySelectorAll(\"select\");\n        const clonedSelectElements = clonedElement.querySelectorAll(\"select\");\n        for (const [index, source] of selectElements.entries()) {\n            const clone = clonedSelectElements[index];\n            for (const option of clone.selectedOptions)\n                option.selected = false;\n            for (const option of source.selectedOptions)\n                clone.options[option.index].selected = true;\n        }\n        for (const clonedPasswordInput of clonedElement.querySelectorAll('input[type=\"password\"]')) {\n            clonedPasswordInput.value = \"\";\n        }\n        return new PageSnapshot(clonedElement, this.headSnapshot);\n    }\n    get headElement() {\n        return this.headSnapshot.element;\n    }\n    get rootLocation() {\n        var _a;\n        const root = (_a = this.getSetting(\"root\")) !== null && _a !== void 0 ? _a : \"/\";\n        return expandURL(root);\n    }\n    get cacheControlValue() {\n        return this.getSetting(\"cache-control\");\n    }\n    get isPreviewable() {\n        return this.cacheControlValue != \"no-preview\";\n    }\n    get isCacheable() {\n        return this.cacheControlValue != \"no-cache\";\n    }\n    get isVisitable() {\n        return this.getSetting(\"visit-control\") != \"reload\";\n    }\n    getSetting(name) {\n        return this.headSnapshot.getMetaValue(`turbo-${name}`);\n    }\n}\n\nvar TimingMetric;\n(function (TimingMetric) {\n    TimingMetric[\"visitStart\"] = \"visitStart\";\n    TimingMetric[\"requestStart\"] = \"requestStart\";\n    TimingMetric[\"requestEnd\"] = \"requestEnd\";\n    TimingMetric[\"visitEnd\"] = \"visitEnd\";\n})(TimingMetric || (TimingMetric = {}));\nvar VisitState;\n(function (VisitState) {\n    VisitState[\"initialized\"] = \"initialized\";\n    VisitState[\"started\"] = \"started\";\n    VisitState[\"canceled\"] = \"canceled\";\n    VisitState[\"failed\"] = \"failed\";\n    VisitState[\"completed\"] = \"completed\";\n})(VisitState || (VisitState = {}));\nconst defaultOptions = {\n    action: \"advance\",\n    historyChanged: false,\n    visitCachedSnapshot: () => { },\n    willRender: true,\n    updateHistory: true,\n    shouldCacheSnapshot: true,\n    acceptsStreamResponse: false,\n};\nvar SystemStatusCode;\n(function (SystemStatusCode) {\n    SystemStatusCode[SystemStatusCode[\"networkFailure\"] = 0] = \"networkFailure\";\n    SystemStatusCode[SystemStatusCode[\"timeoutFailure\"] = -1] = \"timeoutFailure\";\n    SystemStatusCode[SystemStatusCode[\"contentTypeMismatch\"] = -2] = \"contentTypeMismatch\";\n})(SystemStatusCode || (SystemStatusCode = {}));\nclass Visit {\n    constructor(delegate, location, restorationIdentifier, options = {}) {\n        this.identifier = uuid();\n        this.timingMetrics = {};\n        this.followedRedirect = false;\n        this.historyChanged = false;\n        this.scrolled = false;\n        this.shouldCacheSnapshot = true;\n        this.acceptsStreamResponse = false;\n        this.snapshotCached = false;\n        this.state = VisitState.initialized;\n        this.delegate = delegate;\n        this.location = location;\n        this.restorationIdentifier = restorationIdentifier || uuid();\n        const { action, historyChanged, referrer, snapshot, snapshotHTML, response, visitCachedSnapshot, willRender, updateHistory, shouldCacheSnapshot, acceptsStreamResponse, } = Object.assign(Object.assign({}, defaultOptions), options);\n        this.action = action;\n        this.historyChanged = historyChanged;\n        this.referrer = referrer;\n        this.snapshot = snapshot;\n        this.snapshotHTML = snapshotHTML;\n        this.response = response;\n        this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action);\n        this.visitCachedSnapshot = visitCachedSnapshot;\n        this.willRender = willRender;\n        this.updateHistory = updateHistory;\n        this.scrolled = !willRender;\n        this.shouldCacheSnapshot = shouldCacheSnapshot;\n        this.acceptsStreamResponse = acceptsStreamResponse;\n    }\n    get adapter() {\n        return this.delegate.adapter;\n    }\n    get view() {\n        return this.delegate.view;\n    }\n    get history() {\n        return this.delegate.history;\n    }\n    get restorationData() {\n        return this.history.getRestorationDataForIdentifier(this.restorationIdentifier);\n    }\n    get silent() {\n        return this.isSamePage;\n    }\n    start() {\n        if (this.state == VisitState.initialized) {\n            this.recordTimingMetric(TimingMetric.visitStart);\n            this.state = VisitState.started;\n            this.adapter.visitStarted(this);\n            this.delegate.visitStarted(this);\n        }\n    }\n    cancel() {\n        if (this.state == VisitState.started) {\n            if (this.request) {\n                this.request.cancel();\n            }\n            this.cancelRender();\n            this.state = VisitState.canceled;\n        }\n    }\n    complete() {\n        if (this.state == VisitState.started) {\n            this.recordTimingMetric(TimingMetric.visitEnd);\n            this.state = VisitState.completed;\n            this.followRedirect();\n            if (!this.followedRedirect) {\n                this.adapter.visitCompleted(this);\n                this.delegate.visitCompleted(this);\n            }\n        }\n    }\n    fail() {\n        if (this.state == VisitState.started) {\n            this.state = VisitState.failed;\n            this.adapter.visitFailed(this);\n        }\n    }\n    changeHistory() {\n        var _a;\n        if (!this.historyChanged && this.updateHistory) {\n            const actionForHistory = this.location.href === ((_a = this.referrer) === null || _a === void 0 ? void 0 : _a.href) ? \"replace\" : this.action;\n            const method = getHistoryMethodForAction(actionForHistory);\n            this.history.update(method, this.location, this.restorationIdentifier);\n            this.historyChanged = true;\n        }\n    }\n    issueRequest() {\n        if (this.hasPreloadedResponse()) {\n            this.simulateRequest();\n        }\n        else if (this.shouldIssueRequest() && !this.request) {\n            this.request = new FetchRequest(this, FetchMethod.get, this.location);\n            this.request.perform();\n        }\n    }\n    simulateRequest() {\n        if (this.response) {\n            this.startRequest();\n            this.recordResponse();\n            this.finishRequest();\n        }\n    }\n    startRequest() {\n        this.recordTimingMetric(TimingMetric.requestStart);\n        this.adapter.visitRequestStarted(this);\n    }\n    recordResponse(response = this.response) {\n        this.response = response;\n        if (response) {\n            const { statusCode } = response;\n            if (isSuccessful(statusCode)) {\n                this.adapter.visitRequestCompleted(this);\n            }\n            else {\n                this.adapter.visitRequestFailedWithStatusCode(this, statusCode);\n            }\n        }\n    }\n    finishRequest() {\n        this.recordTimingMetric(TimingMetric.requestEnd);\n        this.adapter.visitRequestFinished(this);\n    }\n    loadResponse() {\n        if (this.response) {\n            const { statusCode, responseHTML } = this.response;\n            this.render(async () => {\n                if (this.shouldCacheSnapshot)\n                    this.cacheSnapshot();\n                if (this.view.renderPromise)\n                    await this.view.renderPromise;\n                if (isSuccessful(statusCode) && responseHTML != null) {\n                    await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML), false, this.willRender, this);\n                    this.performScroll();\n                    this.adapter.visitRendered(this);\n                    this.complete();\n                }\n                else {\n                    await this.view.renderError(PageSnapshot.fromHTMLString(responseHTML), this);\n                    this.adapter.visitRendered(this);\n                    this.fail();\n                }\n            });\n        }\n    }\n    getCachedSnapshot() {\n        const snapshot = this.view.getCachedSnapshotForLocation(this.location) || this.getPreloadedSnapshot();\n        if (snapshot && (!getAnchor(this.location) || snapshot.hasAnchor(getAnchor(this.location)))) {\n            if (this.action == \"restore\" || snapshot.isPreviewable) {\n                return snapshot;\n            }\n        }\n    }\n    getPreloadedSnapshot() {\n        if (this.snapshotHTML) {\n            return PageSnapshot.fromHTMLString(this.snapshotHTML);\n        }\n    }\n    hasCachedSnapshot() {\n        return this.getCachedSnapshot() != null;\n    }\n    loadCachedSnapshot() {\n        const snapshot = this.getCachedSnapshot();\n        if (snapshot) {\n            const isPreview = this.shouldIssueRequest();\n            this.render(async () => {\n                this.cacheSnapshot();\n                if (this.isSamePage) {\n                    this.adapter.visitRendered(this);\n                }\n                else {\n                    if (this.view.renderPromise)\n                        await this.view.renderPromise;\n                    await this.view.renderPage(snapshot, isPreview, this.willRender, this);\n                    this.performScroll();\n                    this.adapter.visitRendered(this);\n                    if (!isPreview) {\n                        this.complete();\n                    }\n                }\n            });\n        }\n    }\n    followRedirect() {\n        var _a;\n        if (this.redirectedToLocation && !this.followedRedirect && ((_a = this.response) === null || _a === void 0 ? void 0 : _a.redirected)) {\n            this.adapter.visitProposedToLocation(this.redirectedToLocation, {\n                action: \"replace\",\n                response: this.response,\n                shouldCacheSnapshot: false,\n                willRender: false,\n            });\n            this.followedRedirect = true;\n        }\n    }\n    goToSamePageAnchor() {\n        if (this.isSamePage) {\n            this.render(async () => {\n                this.cacheSnapshot();\n                this.performScroll();\n                this.changeHistory();\n                this.adapter.visitRendered(this);\n            });\n        }\n    }\n    prepareRequest(request) {\n        if (this.acceptsStreamResponse) {\n            request.acceptResponseType(StreamMessage.contentType);\n        }\n    }\n    requestStarted() {\n        this.startRequest();\n    }\n    requestPreventedHandlingResponse(_request, _response) { }\n    async requestSucceededWithResponse(request, response) {\n        const responseHTML = await response.responseHTML;\n        const { redirected, statusCode } = response;\n        if (responseHTML == undefined) {\n            this.recordResponse({\n                statusCode: SystemStatusCode.contentTypeMismatch,\n                redirected,\n            });\n        }\n        else {\n            this.redirectedToLocation = response.redirected ? response.location : undefined;\n            this.recordResponse({ statusCode: statusCode, responseHTML, redirected });\n        }\n    }\n    async requestFailedWithResponse(request, response) {\n        const responseHTML = await response.responseHTML;\n        const { redirected, statusCode } = response;\n        if (responseHTML == undefined) {\n            this.recordResponse({\n                statusCode: SystemStatusCode.contentTypeMismatch,\n                redirected,\n            });\n        }\n        else {\n            this.recordResponse({ statusCode: statusCode, responseHTML, redirected });\n        }\n    }\n    requestErrored(_request, _error) {\n        this.recordResponse({\n            statusCode: SystemStatusCode.networkFailure,\n            redirected: false,\n        });\n    }\n    requestFinished() {\n        this.finishRequest();\n    }\n    performScroll() {\n        if (!this.scrolled && !this.view.forceReloaded) {\n            if (this.action == \"restore\") {\n                this.scrollToRestoredPosition() || this.scrollToAnchor() || this.view.scrollToTop();\n            }\n            else {\n                this.scrollToAnchor() || this.view.scrollToTop();\n            }\n            if (this.isSamePage) {\n                this.delegate.visitScrolledToSamePageLocation(this.view.lastRenderedLocation, this.location);\n            }\n            this.scrolled = true;\n        }\n    }\n    scrollToRestoredPosition() {\n        const { scrollPosition } = this.restorationData;\n        if (scrollPosition) {\n            this.view.scrollToPosition(scrollPosition);\n            return true;\n        }\n    }\n    scrollToAnchor() {\n        const anchor = getAnchor(this.location);\n        if (anchor != null) {\n            this.view.scrollToAnchor(anchor);\n            return true;\n        }\n    }\n    recordTimingMetric(metric) {\n        this.timingMetrics[metric] = new Date().getTime();\n    }\n    getTimingMetrics() {\n        return Object.assign({}, this.timingMetrics);\n    }\n    getHistoryMethodForAction(action) {\n        switch (action) {\n            case \"replace\":\n                return history.replaceState;\n            case \"advance\":\n            case \"restore\":\n                return history.pushState;\n        }\n    }\n    hasPreloadedResponse() {\n        return typeof this.response == \"object\";\n    }\n    shouldIssueRequest() {\n        if (this.isSamePage) {\n            return false;\n        }\n        else if (this.action == \"restore\") {\n            return !this.hasCachedSnapshot();\n        }\n        else {\n            return this.willRender;\n        }\n    }\n    cacheSnapshot() {\n        if (!this.snapshotCached) {\n            this.view.cacheSnapshot(this.snapshot).then((snapshot) => snapshot && this.visitCachedSnapshot(snapshot));\n            this.snapshotCached = true;\n        }\n    }\n    async render(callback) {\n        this.cancelRender();\n        await new Promise((resolve) => {\n            this.frame = requestAnimationFrame(() => resolve());\n        });\n        await callback();\n        delete this.frame;\n    }\n    cancelRender() {\n        if (this.frame) {\n            cancelAnimationFrame(this.frame);\n            delete this.frame;\n        }\n    }\n}\nfunction isSuccessful(statusCode) {\n    return statusCode >= 200 && statusCode < 300;\n}\n\nclass BrowserAdapter {\n    constructor(session) {\n        this.progressBar = new ProgressBar();\n        this.showProgressBar = () => {\n            this.progressBar.show();\n        };\n        this.session = session;\n    }\n    visitProposedToLocation(location, options) {\n        this.navigator.startVisit(location, (options === null || options === void 0 ? void 0 : options.restorationIdentifier) || uuid(), options);\n    }\n    visitStarted(visit) {\n        this.location = visit.location;\n        visit.loadCachedSnapshot();\n        visit.issueRequest();\n        visit.goToSamePageAnchor();\n    }\n    visitRequestStarted(visit) {\n        this.progressBar.setValue(0);\n        if (visit.hasCachedSnapshot() || visit.action != \"restore\") {\n            this.showVisitProgressBarAfterDelay();\n        }\n        else {\n            this.showProgressBar();\n        }\n    }\n    visitRequestCompleted(visit) {\n        visit.loadResponse();\n    }\n    visitRequestFailedWithStatusCode(visit, statusCode) {\n        switch (statusCode) {\n            case SystemStatusCode.networkFailure:\n            case SystemStatusCode.timeoutFailure:\n            case SystemStatusCode.contentTypeMismatch:\n                return this.reload({\n                    reason: \"request_failed\",\n                    context: {\n                        statusCode,\n                    },\n                });\n            default:\n                return visit.loadResponse();\n        }\n    }\n    visitRequestFinished(_visit) {\n        this.progressBar.setValue(1);\n        this.hideVisitProgressBar();\n    }\n    visitCompleted(_visit) { }\n    pageInvalidated(reason) {\n        this.reload(reason);\n    }\n    visitFailed(_visit) { }\n    visitRendered(_visit) { }\n    formSubmissionStarted(_formSubmission) {\n        this.progressBar.setValue(0);\n        this.showFormProgressBarAfterDelay();\n    }\n    formSubmissionFinished(_formSubmission) {\n        this.progressBar.setValue(1);\n        this.hideFormProgressBar();\n    }\n    showVisitProgressBarAfterDelay() {\n        this.visitProgressBarTimeout = window.setTimeout(this.showProgressBar, this.session.progressBarDelay);\n    }\n    hideVisitProgressBar() {\n        this.progressBar.hide();\n        if (this.visitProgressBarTimeout != null) {\n            window.clearTimeout(this.visitProgressBarTimeout);\n            delete this.visitProgressBarTimeout;\n        }\n    }\n    showFormProgressBarAfterDelay() {\n        if (this.formProgressBarTimeout == null) {\n            this.formProgressBarTimeout = window.setTimeout(this.showProgressBar, this.session.progressBarDelay);\n        }\n    }\n    hideFormProgressBar() {\n        this.progressBar.hide();\n        if (this.formProgressBarTimeout != null) {\n            window.clearTimeout(this.formProgressBarTimeout);\n            delete this.formProgressBarTimeout;\n        }\n    }\n    reload(reason) {\n        var _a;\n        dispatch(\"turbo:reload\", { detail: reason });\n        window.location.href = ((_a = this.location) === null || _a === void 0 ? void 0 : _a.toString()) || window.location.href;\n    }\n    get navigator() {\n        return this.session.navigator;\n    }\n}\n\nclass CacheObserver {\n    constructor() {\n        this.selector = \"[data-turbo-temporary]\";\n        this.deprecatedSelector = \"[data-turbo-cache=false]\";\n        this.started = false;\n        this.removeTemporaryElements = ((_event) => {\n            for (const element of this.temporaryElements) {\n                element.remove();\n            }\n        });\n    }\n    start() {\n        if (!this.started) {\n            this.started = true;\n            addEventListener(\"turbo:before-cache\", this.removeTemporaryElements, false);\n        }\n    }\n    stop() {\n        if (this.started) {\n            this.started = false;\n            removeEventListener(\"turbo:before-cache\", this.removeTemporaryElements, false);\n        }\n    }\n    get temporaryElements() {\n        return [...document.querySelectorAll(this.selector), ...this.temporaryElementsWithDeprecation];\n    }\n    get temporaryElementsWithDeprecation() {\n        const elements = document.querySelectorAll(this.deprecatedSelector);\n        if (elements.length) {\n            console.warn(`The ${this.deprecatedSelector} selector is deprecated and will be removed in a future version. Use ${this.selector} instead.`);\n        }\n        return [...elements];\n    }\n}\n\nclass FrameRedirector {\n    constructor(session, element) {\n        this.session = session;\n        this.element = element;\n        this.linkInterceptor = new LinkInterceptor(this, element);\n        this.formSubmitObserver = new FormSubmitObserver(this, element);\n    }\n    start() {\n        this.linkInterceptor.start();\n        this.formSubmitObserver.start();\n    }\n    stop() {\n        this.linkInterceptor.stop();\n        this.formSubmitObserver.stop();\n    }\n    shouldInterceptLinkClick(element, _location, _event) {\n        return this.shouldRedirect(element);\n    }\n    linkClickIntercepted(element, url, event) {\n        const frame = this.findFrameElement(element);\n        if (frame) {\n            frame.delegate.linkClickIntercepted(element, url, event);\n        }\n    }\n    willSubmitForm(element, submitter) {\n        return (element.closest(\"turbo-frame\") == null &&\n            this.shouldSubmit(element, submitter) &&\n            this.shouldRedirect(element, submitter));\n    }\n    formSubmitted(element, submitter) {\n        const frame = this.findFrameElement(element, submitter);\n        if (frame) {\n            frame.delegate.formSubmitted(element, submitter);\n        }\n    }\n    shouldSubmit(form, submitter) {\n        var _a;\n        const action = getAction(form, submitter);\n        const meta = this.element.ownerDocument.querySelector(`meta[name=\"turbo-root\"]`);\n        const rootLocation = expandURL((_a = meta === null || meta === void 0 ? void 0 : meta.content) !== null && _a !== void 0 ? _a : \"/\");\n        return this.shouldRedirect(form, submitter) && locationIsVisitable(action, rootLocation);\n    }\n    shouldRedirect(element, submitter) {\n        const isNavigatable = element instanceof HTMLFormElement\n            ? this.session.submissionIsNavigatable(element, submitter)\n            : this.session.elementIsNavigatable(element);\n        if (isNavigatable) {\n            const frame = this.findFrameElement(element, submitter);\n            return frame ? frame != element.closest(\"turbo-frame\") : false;\n        }\n        else {\n            return false;\n        }\n    }\n    findFrameElement(element, submitter) {\n        const id = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute(\"data-turbo-frame\")) || element.getAttribute(\"data-turbo-frame\");\n        if (id && id != \"_top\") {\n            const frame = this.element.querySelector(`#${id}:not([disabled])`);\n            if (frame instanceof FrameElement) {\n                return frame;\n            }\n        }\n    }\n}\n\nclass History {\n    constructor(delegate) {\n        this.restorationIdentifier = uuid();\n        this.restorationData = {};\n        this.started = false;\n        this.pageLoaded = false;\n        this.onPopState = (event) => {\n            if (this.shouldHandlePopState()) {\n                const { turbo } = event.state || {};\n                if (turbo) {\n                    this.location = new URL(window.location.href);\n                    const { restorationIdentifier } = turbo;\n                    this.restorationIdentifier = restorationIdentifier;\n                    this.delegate.historyPoppedToLocationWithRestorationIdentifier(this.location, restorationIdentifier);\n                }\n            }\n        };\n        this.onPageLoad = async (_event) => {\n            await nextMicrotask();\n            this.pageLoaded = true;\n        };\n        this.delegate = delegate;\n    }\n    start() {\n        if (!this.started) {\n            addEventListener(\"popstate\", this.onPopState, false);\n            addEventListener(\"load\", this.onPageLoad, false);\n            this.started = true;\n            this.replace(new URL(window.location.href));\n        }\n    }\n    stop() {\n        if (this.started) {\n            removeEventListener(\"popstate\", this.onPopState, false);\n            removeEventListener(\"load\", this.onPageLoad, false);\n            this.started = false;\n        }\n    }\n    push(location, restorationIdentifier) {\n        this.update(history.pushState, location, restorationIdentifier);\n    }\n    replace(location, restorationIdentifier) {\n        this.update(history.replaceState, location, restorationIdentifier);\n    }\n    update(method, location, restorationIdentifier = uuid()) {\n        const state = { turbo: { restorationIdentifier } };\n        method.call(history, state, \"\", location.href);\n        this.location = location;\n        this.restorationIdentifier = restorationIdentifier;\n    }\n    getRestorationDataForIdentifier(restorationIdentifier) {\n        return this.restorationData[restorationIdentifier] || {};\n    }\n    updateRestorationData(additionalData) {\n        const { restorationIdentifier } = this;\n        const restorationData = this.restorationData[restorationIdentifier];\n        this.restorationData[restorationIdentifier] = Object.assign(Object.assign({}, restorationData), additionalData);\n    }\n    assumeControlOfScrollRestoration() {\n        var _a;\n        if (!this.previousScrollRestoration) {\n            this.previousScrollRestoration = (_a = history.scrollRestoration) !== null && _a !== void 0 ? _a : \"auto\";\n            history.scrollRestoration = \"manual\";\n        }\n    }\n    relinquishControlOfScrollRestoration() {\n        if (this.previousScrollRestoration) {\n            history.scrollRestoration = this.previousScrollRestoration;\n            delete this.previousScrollRestoration;\n        }\n    }\n    shouldHandlePopState() {\n        return this.pageIsLoaded();\n    }\n    pageIsLoaded() {\n        return this.pageLoaded || document.readyState == \"complete\";\n    }\n}\n\nclass Navigator {\n    constructor(delegate) {\n        this.delegate = delegate;\n    }\n    proposeVisit(location, options = {}) {\n        if (this.delegate.allowsVisitingLocationWithAction(location, options.action)) {\n            if (locationIsVisitable(location, this.view.snapshot.rootLocation)) {\n                this.delegate.visitProposedToLocation(location, options);\n            }\n            else {\n                window.location.href = location.toString();\n            }\n        }\n    }\n    startVisit(locatable, restorationIdentifier, options = {}) {\n        this.stop();\n        this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, Object.assign({ referrer: this.location }, options));\n        this.currentVisit.start();\n    }\n    submitForm(form, submitter) {\n        this.stop();\n        this.formSubmission = new FormSubmission(this, form, submitter, true);\n        this.formSubmission.start();\n    }\n    stop() {\n        if (this.formSubmission) {\n            this.formSubmission.stop();\n            delete this.formSubmission;\n        }\n        if (this.currentVisit) {\n            this.currentVisit.cancel();\n            delete this.currentVisit;\n        }\n    }\n    get adapter() {\n        return this.delegate.adapter;\n    }\n    get view() {\n        return this.delegate.view;\n    }\n    get history() {\n        return this.delegate.history;\n    }\n    formSubmissionStarted(formSubmission) {\n        if (typeof this.adapter.formSubmissionStarted === \"function\") {\n            this.adapter.formSubmissionStarted(formSubmission);\n        }\n    }\n    async formSubmissionSucceededWithResponse(formSubmission, fetchResponse) {\n        if (formSubmission == this.formSubmission) {\n            const responseHTML = await fetchResponse.responseHTML;\n            if (responseHTML) {\n                const shouldCacheSnapshot = formSubmission.isSafe;\n                if (!shouldCacheSnapshot) {\n                    this.view.clearSnapshotCache();\n                }\n                const { statusCode, redirected } = fetchResponse;\n                const action = this.getActionForFormSubmission(formSubmission);\n                const visitOptions = {\n                    action,\n                    shouldCacheSnapshot,\n                    response: { statusCode, responseHTML, redirected },\n                };\n                this.proposeVisit(fetchResponse.location, visitOptions);\n            }\n        }\n    }\n    async formSubmissionFailedWithResponse(formSubmission, fetchResponse) {\n        const responseHTML = await fetchResponse.responseHTML;\n        if (responseHTML) {\n            const snapshot = PageSnapshot.fromHTMLString(responseHTML);\n            if (fetchResponse.serverError) {\n                await this.view.renderError(snapshot, this.currentVisit);\n            }\n            else {\n                await this.view.renderPage(snapshot, false, true, this.currentVisit);\n            }\n            this.view.scrollToTop();\n            this.view.clearSnapshotCache();\n        }\n    }\n    formSubmissionErrored(formSubmission, error) {\n        console.error(error);\n    }\n    formSubmissionFinished(formSubmission) {\n        if (typeof this.adapter.formSubmissionFinished === \"function\") {\n            this.adapter.formSubmissionFinished(formSubmission);\n        }\n    }\n    visitStarted(visit) {\n        this.delegate.visitStarted(visit);\n    }\n    visitCompleted(visit) {\n        this.delegate.visitCompleted(visit);\n    }\n    locationWithActionIsSamePage(location, action) {\n        const anchor = getAnchor(location);\n        const currentAnchor = getAnchor(this.view.lastRenderedLocation);\n        const isRestorationToTop = action === \"restore\" && typeof anchor === \"undefined\";\n        return (action !== \"replace\" &&\n            getRequestURL(location) === getRequestURL(this.view.lastRenderedLocation) &&\n            (isRestorationToTop || (anchor != null && anchor !== currentAnchor)));\n    }\n    visitScrolledToSamePageLocation(oldURL, newURL) {\n        this.delegate.visitScrolledToSamePageLocation(oldURL, newURL);\n    }\n    get location() {\n        return this.history.location;\n    }\n    get restorationIdentifier() {\n        return this.history.restorationIdentifier;\n    }\n    getActionForFormSubmission({ submitter, formElement }) {\n        return getVisitAction(submitter, formElement) || \"advance\";\n    }\n}\n\nvar PageStage;\n(function (PageStage) {\n    PageStage[PageStage[\"initial\"] = 0] = \"initial\";\n    PageStage[PageStage[\"loading\"] = 1] = \"loading\";\n    PageStage[PageStage[\"interactive\"] = 2] = \"interactive\";\n    PageStage[PageStage[\"complete\"] = 3] = \"complete\";\n})(PageStage || (PageStage = {}));\nclass PageObserver {\n    constructor(delegate) {\n        this.stage = PageStage.initial;\n        this.started = false;\n        this.interpretReadyState = () => {\n            const { readyState } = this;\n            if (readyState == \"interactive\") {\n                this.pageIsInteractive();\n            }\n            else if (readyState == \"complete\") {\n                this.pageIsComplete();\n            }\n        };\n        this.pageWillUnload = () => {\n            this.delegate.pageWillUnload();\n        };\n        this.delegate = delegate;\n    }\n    start() {\n        if (!this.started) {\n            if (this.stage == PageStage.initial) {\n                this.stage = PageStage.loading;\n            }\n            document.addEventListener(\"readystatechange\", this.interpretReadyState, false);\n            addEventListener(\"pagehide\", this.pageWillUnload, false);\n            this.started = true;\n        }\n    }\n    stop() {\n        if (this.started) {\n            document.removeEventListener(\"readystatechange\", this.interpretReadyState, false);\n            removeEventListener(\"pagehide\", this.pageWillUnload, false);\n            this.started = false;\n        }\n    }\n    pageIsInteractive() {\n        if (this.stage == PageStage.loading) {\n            this.stage = PageStage.interactive;\n            this.delegate.pageBecameInteractive();\n        }\n    }\n    pageIsComplete() {\n        this.pageIsInteractive();\n        if (this.stage == PageStage.interactive) {\n            this.stage = PageStage.complete;\n            this.delegate.pageLoaded();\n        }\n    }\n    get readyState() {\n        return document.readyState;\n    }\n}\n\nclass ScrollObserver {\n    constructor(delegate) {\n        this.started = false;\n        this.onScroll = () => {\n            this.updatePosition({ x: window.pageXOffset, y: window.pageYOffset });\n        };\n        this.delegate = delegate;\n    }\n    start() {\n        if (!this.started) {\n            addEventListener(\"scroll\", this.onScroll, false);\n            this.onScroll();\n            this.started = true;\n        }\n    }\n    stop() {\n        if (this.started) {\n            removeEventListener(\"scroll\", this.onScroll, false);\n            this.started = false;\n        }\n    }\n    updatePosition(position) {\n        this.delegate.scrollPositionChanged(position);\n    }\n}\n\nclass StreamMessageRenderer {\n    render({ fragment }) {\n        Bardo.preservingPermanentElements(this, getPermanentElementMapForFragment(fragment), () => document.documentElement.appendChild(fragment));\n    }\n    enteringBardo(currentPermanentElement, newPermanentElement) {\n        newPermanentElement.replaceWith(currentPermanentElement.cloneNode(true));\n    }\n    leavingBardo() { }\n}\nfunction getPermanentElementMapForFragment(fragment) {\n    const permanentElementsInDocument = queryPermanentElementsAll(document.documentElement);\n    const permanentElementMap = {};\n    for (const permanentElementInDocument of permanentElementsInDocument) {\n        const { id } = permanentElementInDocument;\n        for (const streamElement of fragment.querySelectorAll(\"turbo-stream\")) {\n            const elementInStream = getPermanentElementById(streamElement.templateElement.content, id);\n            if (elementInStream) {\n                permanentElementMap[id] = [permanentElementInDocument, elementInStream];\n            }\n        }\n    }\n    return permanentElementMap;\n}\n\nclass StreamObserver {\n    constructor(delegate) {\n        this.sources = new Set();\n        this.started = false;\n        this.inspectFetchResponse = ((event) => {\n            const response = fetchResponseFromEvent(event);\n            if (response && fetchResponseIsStream(response)) {\n                event.preventDefault();\n                this.receiveMessageResponse(response);\n            }\n        });\n        this.receiveMessageEvent = (event) => {\n            if (this.started && typeof event.data == \"string\") {\n                this.receiveMessageHTML(event.data);\n            }\n        };\n        this.delegate = delegate;\n    }\n    start() {\n        if (!this.started) {\n            this.started = true;\n            addEventListener(\"turbo:before-fetch-response\", this.inspectFetchResponse, false);\n        }\n    }\n    stop() {\n        if (this.started) {\n            this.started = false;\n            removeEventListener(\"turbo:before-fetch-response\", this.inspectFetchResponse, false);\n        }\n    }\n    connectStreamSource(source) {\n        if (!this.streamSourceIsConnected(source)) {\n            this.sources.add(source);\n            source.addEventListener(\"message\", this.receiveMessageEvent, false);\n        }\n    }\n    disconnectStreamSource(source) {\n        if (this.streamSourceIsConnected(source)) {\n            this.sources.delete(source);\n            source.removeEventListener(\"message\", this.receiveMessageEvent, false);\n        }\n    }\n    streamSourceIsConnected(source) {\n        return this.sources.has(source);\n    }\n    async receiveMessageResponse(response) {\n        const html = await response.responseHTML;\n        if (html) {\n            this.receiveMessageHTML(html);\n        }\n    }\n    receiveMessageHTML(html) {\n        this.delegate.receivedMessageFromStream(StreamMessage.wrap(html));\n    }\n}\nfunction fetchResponseFromEvent(event) {\n    var _a;\n    const fetchResponse = (_a = event.detail) === null || _a === void 0 ? void 0 : _a.fetchResponse;\n    if (fetchResponse instanceof FetchResponse) {\n        return fetchResponse;\n    }\n}\nfunction fetchResponseIsStream(response) {\n    var _a;\n    const contentType = (_a = response.contentType) !== null && _a !== void 0 ? _a : \"\";\n    return contentType.startsWith(StreamMessage.contentType);\n}\n\nclass ErrorRenderer extends Renderer {\n    static renderElement(currentElement, newElement) {\n        const { documentElement, body } = document;\n        documentElement.replaceChild(newElement, body);\n    }\n    async render() {\n        this.replaceHeadAndBody();\n        this.activateScriptElements();\n    }\n    replaceHeadAndBody() {\n        const { documentElement, head } = document;\n        documentElement.replaceChild(this.newHead, head);\n        this.renderElement(this.currentElement, this.newElement);\n    }\n    activateScriptElements() {\n        for (const replaceableElement of this.scriptElements) {\n            const parentNode = replaceableElement.parentNode;\n            if (parentNode) {\n                const element = activateScriptElement(replaceableElement);\n                parentNode.replaceChild(element, replaceableElement);\n            }\n        }\n    }\n    get newHead() {\n        return this.newSnapshot.headSnapshot.element;\n    }\n    get scriptElements() {\n        return document.documentElement.querySelectorAll(\"script\");\n    }\n}\n\nclass PageRenderer extends Renderer {\n    static renderElement(currentElement, newElement) {\n        if (document.body && newElement instanceof HTMLBodyElement) {\n            document.body.replaceWith(newElement);\n        }\n        else {\n            document.documentElement.appendChild(newElement);\n        }\n    }\n    get shouldRender() {\n        return this.newSnapshot.isVisitable && this.trackedElementsAreIdentical;\n    }\n    get reloadReason() {\n        if (!this.newSnapshot.isVisitable) {\n            return {\n                reason: \"turbo_visit_control_is_reload\",\n            };\n        }\n        if (!this.trackedElementsAreIdentical) {\n            return {\n                reason: \"tracked_element_mismatch\",\n            };\n        }\n    }\n    async prepareToRender() {\n        await this.mergeHead();\n    }\n    async render() {\n        if (this.willRender) {\n            await this.replaceBody();\n        }\n    }\n    finishRendering() {\n        super.finishRendering();\n        if (!this.isPreview) {\n            this.focusFirstAutofocusableElement();\n        }\n    }\n    get currentHeadSnapshot() {\n        return this.currentSnapshot.headSnapshot;\n    }\n    get newHeadSnapshot() {\n        return this.newSnapshot.headSnapshot;\n    }\n    get newElement() {\n        return this.newSnapshot.element;\n    }\n    async mergeHead() {\n        const mergedHeadElements = this.mergeProvisionalElements();\n        const newStylesheetElements = this.copyNewHeadStylesheetElements();\n        this.copyNewHeadScriptElements();\n        await mergedHeadElements;\n        await newStylesheetElements;\n    }\n    async replaceBody() {\n        await this.preservingPermanentElements(async () => {\n            this.activateNewBody();\n            await this.assignNewBody();\n        });\n    }\n    get trackedElementsAreIdentical() {\n        return this.currentHeadSnapshot.trackedElementSignature == this.newHeadSnapshot.trackedElementSignature;\n    }\n    async copyNewHeadStylesheetElements() {\n        const loadingElements = [];\n        for (const element of this.newHeadStylesheetElements) {\n            loadingElements.push(waitForLoad(element));\n            document.head.appendChild(element);\n        }\n        await Promise.all(loadingElements);\n    }\n    copyNewHeadScriptElements() {\n        for (const element of this.newHeadScriptElements) {\n            document.head.appendChild(activateScriptElement(element));\n        }\n    }\n    async mergeProvisionalElements() {\n        const newHeadElements = [...this.newHeadProvisionalElements];\n        for (const element of this.currentHeadProvisionalElements) {\n            if (!this.isCurrentElementInElementList(element, newHeadElements)) {\n                document.head.removeChild(element);\n            }\n        }\n        for (const element of newHeadElements) {\n            document.head.appendChild(element);\n        }\n    }\n    isCurrentElementInElementList(element, elementList) {\n        for (const [index, newElement] of elementList.entries()) {\n            if (element.tagName == \"TITLE\") {\n                if (newElement.tagName != \"TITLE\") {\n                    continue;\n                }\n                if (element.innerHTML == newElement.innerHTML) {\n                    elementList.splice(index, 1);\n                    return true;\n                }\n            }\n            if (newElement.isEqualNode(element)) {\n                elementList.splice(index, 1);\n                return true;\n            }\n        }\n        return false;\n    }\n    removeCurrentHeadProvisionalElements() {\n        for (const element of this.currentHeadProvisionalElements) {\n            document.head.removeChild(element);\n        }\n    }\n    copyNewHeadProvisionalElements() {\n        for (const element of this.newHeadProvisionalElements) {\n            document.head.appendChild(element);\n        }\n    }\n    activateNewBody() {\n        document.adoptNode(this.newElement);\n        this.activateNewBodyScriptElements();\n    }\n    activateNewBodyScriptElements() {\n        for (const inertScriptElement of this.newBodyScriptElements) {\n            const activatedScriptElement = activateScriptElement(inertScriptElement);\n            inertScriptElement.replaceWith(activatedScriptElement);\n        }\n    }\n    async assignNewBody() {\n        await this.renderElement(this.currentElement, this.newElement);\n    }\n    get newHeadStylesheetElements() {\n        return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot);\n    }\n    get newHeadScriptElements() {\n        return this.newHeadSnapshot.getScriptElementsNotInSnapshot(this.currentHeadSnapshot);\n    }\n    get currentHeadProvisionalElements() {\n        return this.currentHeadSnapshot.provisionalElements;\n    }\n    get newHeadProvisionalElements() {\n        return this.newHeadSnapshot.provisionalElements;\n    }\n    get newBodyScriptElements() {\n        return this.newElement.querySelectorAll(\"script\");\n    }\n}\n\nclass SnapshotCache {\n    constructor(size) {\n        this.keys = [];\n        this.snapshots = {};\n        this.size = size;\n    }\n    has(location) {\n        return toCacheKey(location) in this.snapshots;\n    }\n    get(location) {\n        if (this.has(location)) {\n            const snapshot = this.read(location);\n            this.touch(location);\n            return snapshot;\n        }\n    }\n    put(location, snapshot) {\n        this.write(location, snapshot);\n        this.touch(location);\n        return snapshot;\n    }\n    clear() {\n        this.snapshots = {};\n    }\n    read(location) {\n        return this.snapshots[toCacheKey(location)];\n    }\n    write(location, snapshot) {\n        this.snapshots[toCacheKey(location)] = snapshot;\n    }\n    touch(location) {\n        const key = toCacheKey(location);\n        const index = this.keys.indexOf(key);\n        if (index > -1)\n            this.keys.splice(index, 1);\n        this.keys.unshift(key);\n        this.trim();\n    }\n    trim() {\n        for (const key of this.keys.splice(this.size)) {\n            delete this.snapshots[key];\n        }\n    }\n}\n\nclass PageView extends View {\n    constructor() {\n        super(...arguments);\n        this.snapshotCache = new SnapshotCache(10);\n        this.lastRenderedLocation = new URL(location.href);\n        this.forceReloaded = false;\n    }\n    renderPage(snapshot, isPreview = false, willRender = true, visit) {\n        const renderer = new PageRenderer(this.snapshot, snapshot, PageRenderer.renderElement, isPreview, willRender);\n        if (!renderer.shouldRender) {\n            this.forceReloaded = true;\n        }\n        else {\n            visit === null || visit === void 0 ? void 0 : visit.changeHistory();\n        }\n        return this.render(renderer);\n    }\n    renderError(snapshot, visit) {\n        visit === null || visit === void 0 ? void 0 : visit.changeHistory();\n        const renderer = new ErrorRenderer(this.snapshot, snapshot, ErrorRenderer.renderElement, false);\n        return this.render(renderer);\n    }\n    clearSnapshotCache() {\n        this.snapshotCache.clear();\n    }\n    async cacheSnapshot(snapshot = this.snapshot) {\n        if (snapshot.isCacheable) {\n            this.delegate.viewWillCacheSnapshot();\n            const { lastRenderedLocation: location } = this;\n            await nextEventLoopTick();\n            const cachedSnapshot = snapshot.clone();\n            this.snapshotCache.put(location, cachedSnapshot);\n            return cachedSnapshot;\n        }\n    }\n    getCachedSnapshotForLocation(location) {\n        return this.snapshotCache.get(location);\n    }\n    get snapshot() {\n        return PageSnapshot.fromElement(this.element);\n    }\n}\n\nclass Preloader {\n    constructor(delegate) {\n        this.selector = \"a[data-turbo-preload]\";\n        this.delegate = delegate;\n    }\n    get snapshotCache() {\n        return this.delegate.navigator.view.snapshotCache;\n    }\n    start() {\n        if (document.readyState === \"loading\") {\n            return document.addEventListener(\"DOMContentLoaded\", () => {\n                this.preloadOnLoadLinksForView(document.body);\n            });\n        }\n        else {\n            this.preloadOnLoadLinksForView(document.body);\n        }\n    }\n    preloadOnLoadLinksForView(element) {\n        for (const link of element.querySelectorAll(this.selector)) {\n            this.preloadURL(link);\n        }\n    }\n    async preloadURL(link) {\n        const location = new URL(link.href);\n        if (this.snapshotCache.has(location)) {\n            return;\n        }\n        try {\n            const response = await fetch(location.toString(), { headers: { \"VND.PREFETCH\": \"true\", Accept: \"text/html\" } });\n            const responseText = await response.text();\n            const snapshot = PageSnapshot.fromHTMLString(responseText);\n            this.snapshotCache.put(location, snapshot);\n        }\n        catch (_) {\n        }\n    }\n}\n\nclass Session {\n    constructor() {\n        this.navigator = new Navigator(this);\n        this.history = new History(this);\n        this.preloader = new Preloader(this);\n        this.view = new PageView(this, document.documentElement);\n        this.adapter = new BrowserAdapter(this);\n        this.pageObserver = new PageObserver(this);\n        this.cacheObserver = new CacheObserver();\n        this.linkClickObserver = new LinkClickObserver(this, window);\n        this.formSubmitObserver = new FormSubmitObserver(this, document);\n        this.scrollObserver = new ScrollObserver(this);\n        this.streamObserver = new StreamObserver(this);\n        this.formLinkClickObserver = new FormLinkClickObserver(this, document.documentElement);\n        this.frameRedirector = new FrameRedirector(this, document.documentElement);\n        this.streamMessageRenderer = new StreamMessageRenderer();\n        this.drive = true;\n        this.enabled = true;\n        this.progressBarDelay = 500;\n        this.started = false;\n        this.formMode = \"on\";\n    }\n    start() {\n        if (!this.started) {\n            this.pageObserver.start();\n            this.cacheObserver.start();\n            this.formLinkClickObserver.start();\n            this.linkClickObserver.start();\n            this.formSubmitObserver.start();\n            this.scrollObserver.start();\n            this.streamObserver.start();\n            this.frameRedirector.start();\n            this.history.start();\n            this.preloader.start();\n            this.started = true;\n            this.enabled = true;\n        }\n    }\n    disable() {\n        this.enabled = false;\n    }\n    stop() {\n        if (this.started) {\n            this.pageObserver.stop();\n            this.cacheObserver.stop();\n            this.formLinkClickObserver.stop();\n            this.linkClickObserver.stop();\n            this.formSubmitObserver.stop();\n            this.scrollObserver.stop();\n            this.streamObserver.stop();\n            this.frameRedirector.stop();\n            this.history.stop();\n            this.started = false;\n        }\n    }\n    registerAdapter(adapter) {\n        this.adapter = adapter;\n    }\n    visit(location, options = {}) {\n        const frameElement = options.frame ? document.getElementById(options.frame) : null;\n        if (frameElement instanceof FrameElement) {\n            frameElement.src = location.toString();\n            frameElement.loaded;\n        }\n        else {\n            this.navigator.proposeVisit(expandURL(location), options);\n        }\n    }\n    connectStreamSource(source) {\n        this.streamObserver.connectStreamSource(source);\n    }\n    disconnectStreamSource(source) {\n        this.streamObserver.disconnectStreamSource(source);\n    }\n    renderStreamMessage(message) {\n        this.streamMessageRenderer.render(StreamMessage.wrap(message));\n    }\n    clearCache() {\n        this.view.clearSnapshotCache();\n    }\n    setProgressBarDelay(delay) {\n        this.progressBarDelay = delay;\n    }\n    setFormMode(mode) {\n        this.formMode = mode;\n    }\n    get location() {\n        return this.history.location;\n    }\n    get restorationIdentifier() {\n        return this.history.restorationIdentifier;\n    }\n    historyPoppedToLocationWithRestorationIdentifier(location, restorationIdentifier) {\n        if (this.enabled) {\n            this.navigator.startVisit(location, restorationIdentifier, {\n                action: \"restore\",\n                historyChanged: true,\n            });\n        }\n        else {\n            this.adapter.pageInvalidated({\n                reason: \"turbo_disabled\",\n            });\n        }\n    }\n    scrollPositionChanged(position) {\n        this.history.updateRestorationData({ scrollPosition: position });\n    }\n    willSubmitFormLinkToLocation(link, location) {\n        return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation);\n    }\n    submittedFormLinkToLocation() { }\n    willFollowLinkToLocation(link, location, event) {\n        return (this.elementIsNavigatable(link) &&\n            locationIsVisitable(location, this.snapshot.rootLocation) &&\n            this.applicationAllowsFollowingLinkToLocation(link, location, event));\n    }\n    followedLinkToLocation(link, location) {\n        const action = this.getActionForLink(link);\n        const acceptsStreamResponse = link.hasAttribute(\"data-turbo-stream\");\n        this.visit(location.href, { action, acceptsStreamResponse });\n    }\n    allowsVisitingLocationWithAction(location, action) {\n        return this.locationWithActionIsSamePage(location, action) || this.applicationAllowsVisitingLocation(location);\n    }\n    visitProposedToLocation(location, options) {\n        extendURLWithDeprecatedProperties(location);\n        this.adapter.visitProposedToLocation(location, options);\n    }\n    visitStarted(visit) {\n        if (!visit.acceptsStreamResponse) {\n            markAsBusy(document.documentElement);\n        }\n        extendURLWithDeprecatedProperties(visit.location);\n        if (!visit.silent) {\n            this.notifyApplicationAfterVisitingLocation(visit.location, visit.action);\n        }\n    }\n    visitCompleted(visit) {\n        clearBusyState(document.documentElement);\n        this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());\n    }\n    locationWithActionIsSamePage(location, action) {\n        return this.navigator.locationWithActionIsSamePage(location, action);\n    }\n    visitScrolledToSamePageLocation(oldURL, newURL) {\n        this.notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL);\n    }\n    willSubmitForm(form, submitter) {\n        const action = getAction(form, submitter);\n        return (this.submissionIsNavigatable(form, submitter) &&\n            locationIsVisitable(expandURL(action), this.snapshot.rootLocation));\n    }\n    formSubmitted(form, submitter) {\n        this.navigator.submitForm(form, submitter);\n    }\n    pageBecameInteractive() {\n        this.view.lastRenderedLocation = this.location;\n        this.notifyApplicationAfterPageLoad();\n    }\n    pageLoaded() {\n        this.history.assumeControlOfScrollRestoration();\n    }\n    pageWillUnload() {\n        this.history.relinquishControlOfScrollRestoration();\n    }\n    receivedMessageFromStream(message) {\n        this.renderStreamMessage(message);\n    }\n    viewWillCacheSnapshot() {\n        var _a;\n        if (!((_a = this.navigator.currentVisit) === null || _a === void 0 ? void 0 : _a.silent)) {\n            this.notifyApplicationBeforeCachingSnapshot();\n        }\n    }\n    allowsImmediateRender({ element }, options) {\n        const event = this.notifyApplicationBeforeRender(element, options);\n        const { defaultPrevented, detail: { render }, } = event;\n        if (this.view.renderer && render) {\n            this.view.renderer.renderElement = render;\n        }\n        return !defaultPrevented;\n    }\n    viewRenderedSnapshot(_snapshot, _isPreview) {\n        this.view.lastRenderedLocation = this.history.location;\n        this.notifyApplicationAfterRender();\n    }\n    preloadOnLoadLinksForView(element) {\n        this.preloader.preloadOnLoadLinksForView(element);\n    }\n    viewInvalidated(reason) {\n        this.adapter.pageInvalidated(reason);\n    }\n    frameLoaded(frame) {\n        this.notifyApplicationAfterFrameLoad(frame);\n    }\n    frameRendered(fetchResponse, frame) {\n        this.notifyApplicationAfterFrameRender(fetchResponse, frame);\n    }\n    applicationAllowsFollowingLinkToLocation(link, location, ev) {\n        const event = this.notifyApplicationAfterClickingLinkToLocation(link, location, ev);\n        return !event.defaultPrevented;\n    }\n    applicationAllowsVisitingLocation(location) {\n        const event = this.notifyApplicationBeforeVisitingLocation(location);\n        return !event.defaultPrevented;\n    }\n    notifyApplicationAfterClickingLinkToLocation(link, location, event) {\n        return dispatch(\"turbo:click\", {\n            target: link,\n            detail: { url: location.href, originalEvent: event },\n            cancelable: true,\n        });\n    }\n    notifyApplicationBeforeVisitingLocation(location) {\n        return dispatch(\"turbo:before-visit\", {\n            detail: { url: location.href },\n            cancelable: true,\n        });\n    }\n    notifyApplicationAfterVisitingLocation(location, action) {\n        return dispatch(\"turbo:visit\", { detail: { url: location.href, action } });\n    }\n    notifyApplicationBeforeCachingSnapshot() {\n        return dispatch(\"turbo:before-cache\");\n    }\n    notifyApplicationBeforeRender(newBody, options) {\n        return dispatch(\"turbo:before-render\", {\n            detail: Object.assign({ newBody }, options),\n            cancelable: true,\n        });\n    }\n    notifyApplicationAfterRender() {\n        return dispatch(\"turbo:render\");\n    }\n    notifyApplicationAfterPageLoad(timing = {}) {\n        return dispatch(\"turbo:load\", {\n            detail: { url: this.location.href, timing },\n        });\n    }\n    notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL) {\n        dispatchEvent(new HashChangeEvent(\"hashchange\", {\n            oldURL: oldURL.toString(),\n            newURL: newURL.toString(),\n        }));\n    }\n    notifyApplicationAfterFrameLoad(frame) {\n        return dispatch(\"turbo:frame-load\", { target: frame });\n    }\n    notifyApplicationAfterFrameRender(fetchResponse, frame) {\n        return dispatch(\"turbo:frame-render\", {\n            detail: { fetchResponse },\n            target: frame,\n            cancelable: true,\n        });\n    }\n    submissionIsNavigatable(form, submitter) {\n        if (this.formMode == \"off\") {\n            return false;\n        }\n        else {\n            const submitterIsNavigatable = submitter ? this.elementIsNavigatable(submitter) : true;\n            if (this.formMode == \"optin\") {\n                return submitterIsNavigatable && form.closest('[data-turbo=\"true\"]') != null;\n            }\n            else {\n                return submitterIsNavigatable && this.elementIsNavigatable(form);\n            }\n        }\n    }\n    elementIsNavigatable(element) {\n        const container = findClosestRecursively(element, \"[data-turbo]\");\n        const withinFrame = findClosestRecursively(element, \"turbo-frame\");\n        if (this.drive || withinFrame) {\n            if (container) {\n                return container.getAttribute(\"data-turbo\") != \"false\";\n            }\n            else {\n                return true;\n            }\n        }\n        else {\n            if (container) {\n                return container.getAttribute(\"data-turbo\") == \"true\";\n            }\n            else {\n                return false;\n            }\n        }\n    }\n    getActionForLink(link) {\n        return getVisitAction(link) || \"advance\";\n    }\n    get snapshot() {\n        return this.view.snapshot;\n    }\n}\nfunction extendURLWithDeprecatedProperties(url) {\n    Object.defineProperties(url, deprecatedLocationPropertyDescriptors);\n}\nconst deprecatedLocationPropertyDescriptors = {\n    absoluteURL: {\n        get() {\n            return this.toString();\n        },\n    },\n};\n\nclass Cache {\n    constructor(session) {\n        this.session = session;\n    }\n    clear() {\n        this.session.clearCache();\n    }\n    resetCacheControl() {\n        this.setCacheControl(\"\");\n    }\n    exemptPageFromCache() {\n        this.setCacheControl(\"no-cache\");\n    }\n    exemptPageFromPreview() {\n        this.setCacheControl(\"no-preview\");\n    }\n    setCacheControl(value) {\n        setMetaContent(\"turbo-cache-control\", value);\n    }\n}\n\nconst StreamActions = {\n    after() {\n        this.targetElements.forEach((e) => { var _a; return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e.nextSibling); });\n    },\n    append() {\n        this.removeDuplicateTargetChildren();\n        this.targetElements.forEach((e) => e.append(this.templateContent));\n    },\n    before() {\n        this.targetElements.forEach((e) => { var _a; return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e); });\n    },\n    prepend() {\n        this.removeDuplicateTargetChildren();\n        this.targetElements.forEach((e) => e.prepend(this.templateContent));\n    },\n    remove() {\n        this.targetElements.forEach((e) => e.remove());\n    },\n    replace() {\n        this.targetElements.forEach((e) => e.replaceWith(this.templateContent));\n    },\n    update() {\n        this.targetElements.forEach((targetElement) => {\n            targetElement.innerHTML = \"\";\n            targetElement.append(this.templateContent);\n        });\n    },\n};\n\nconst session = new Session();\nconst cache = new Cache(session);\nconst { navigator: navigator$1 } = session;\nfunction start() {\n    session.start();\n}\nfunction registerAdapter(adapter) {\n    session.registerAdapter(adapter);\n}\nfunction visit(location, options) {\n    session.visit(location, options);\n}\nfunction connectStreamSource(source) {\n    session.connectStreamSource(source);\n}\nfunction disconnectStreamSource(source) {\n    session.disconnectStreamSource(source);\n}\nfunction renderStreamMessage(message) {\n    session.renderStreamMessage(message);\n}\nfunction clearCache() {\n    console.warn(\"Please replace `Turbo.clearCache()` with `Turbo.cache.clear()`. The top-level function is deprecated and will be removed in a future version of Turbo.`\");\n    session.clearCache();\n}\nfunction setProgressBarDelay(delay) {\n    session.setProgressBarDelay(delay);\n}\nfunction setConfirmMethod(confirmMethod) {\n    FormSubmission.confirmMethod = confirmMethod;\n}\nfunction setFormMode(mode) {\n    session.setFormMode(mode);\n}\n\nvar Turbo = /*#__PURE__*/Object.freeze({\n    __proto__: null,\n    navigator: navigator$1,\n    session: session,\n    cache: cache,\n    PageRenderer: PageRenderer,\n    PageSnapshot: PageSnapshot,\n    FrameRenderer: FrameRenderer,\n    start: start,\n    registerAdapter: registerAdapter,\n    visit: visit,\n    connectStreamSource: connectStreamSource,\n    disconnectStreamSource: disconnectStreamSource,\n    renderStreamMessage: renderStreamMessage,\n    clearCache: clearCache,\n    setProgressBarDelay: setProgressBarDelay,\n    setConfirmMethod: setConfirmMethod,\n    setFormMode: setFormMode,\n    StreamActions: StreamActions\n});\n\nclass TurboFrameMissingError extends Error {\n}\n\nclass FrameController {\n    constructor(element) {\n        this.fetchResponseLoaded = (_fetchResponse) => { };\n        this.currentFetchRequest = null;\n        this.resolveVisitPromise = () => { };\n        this.connected = false;\n        this.hasBeenLoaded = false;\n        this.ignoredAttributes = new Set();\n        this.action = null;\n        this.visitCachedSnapshot = ({ element }) => {\n            const frame = element.querySelector(\"#\" + this.element.id);\n            if (frame && this.previousFrameElement) {\n                frame.replaceChildren(...this.previousFrameElement.children);\n            }\n            delete this.previousFrameElement;\n        };\n        this.element = element;\n        this.view = new FrameView(this, this.element);\n        this.appearanceObserver = new AppearanceObserver(this, this.element);\n        this.formLinkClickObserver = new FormLinkClickObserver(this, this.element);\n        this.linkInterceptor = new LinkInterceptor(this, this.element);\n        this.restorationIdentifier = uuid();\n        this.formSubmitObserver = new FormSubmitObserver(this, this.element);\n    }\n    connect() {\n        if (!this.connected) {\n            this.connected = true;\n            if (this.loadingStyle == FrameLoadingStyle.lazy) {\n                this.appearanceObserver.start();\n            }\n            else {\n                this.loadSourceURL();\n            }\n            this.formLinkClickObserver.start();\n            this.linkInterceptor.start();\n            this.formSubmitObserver.start();\n        }\n    }\n    disconnect() {\n        if (this.connected) {\n            this.connected = false;\n            this.appearanceObserver.stop();\n            this.formLinkClickObserver.stop();\n            this.linkInterceptor.stop();\n            this.formSubmitObserver.stop();\n        }\n    }\n    disabledChanged() {\n        if (this.loadingStyle == FrameLoadingStyle.eager) {\n            this.loadSourceURL();\n        }\n    }\n    sourceURLChanged() {\n        if (this.isIgnoringChangesTo(\"src\"))\n            return;\n        if (this.element.isConnected) {\n            this.complete = false;\n        }\n        if (this.loadingStyle == FrameLoadingStyle.eager || this.hasBeenLoaded) {\n            this.loadSourceURL();\n        }\n    }\n    sourceURLReloaded() {\n        const { src } = this.element;\n        this.ignoringChangesToAttribute(\"complete\", () => {\n            this.element.removeAttribute(\"complete\");\n        });\n        this.element.src = null;\n        this.element.src = src;\n        return this.element.loaded;\n    }\n    completeChanged() {\n        if (this.isIgnoringChangesTo(\"complete\"))\n            return;\n        this.loadSourceURL();\n    }\n    loadingStyleChanged() {\n        if (this.loadingStyle == FrameLoadingStyle.lazy) {\n            this.appearanceObserver.start();\n        }\n        else {\n            this.appearanceObserver.stop();\n            this.loadSourceURL();\n        }\n    }\n    async loadSourceURL() {\n        if (this.enabled && this.isActive && !this.complete && this.sourceURL) {\n            this.element.loaded = this.visit(expandURL(this.sourceURL));\n            this.appearanceObserver.stop();\n            await this.element.loaded;\n            this.hasBeenLoaded = true;\n        }\n    }\n    async loadResponse(fetchResponse) {\n        if (fetchResponse.redirected || (fetchResponse.succeeded && fetchResponse.isHTML)) {\n            this.sourceURL = fetchResponse.response.url;\n        }\n        try {\n            const html = await fetchResponse.responseHTML;\n            if (html) {\n                const document = parseHTMLDocument(html);\n                const pageSnapshot = PageSnapshot.fromDocument(document);\n                if (pageSnapshot.isVisitable) {\n                    await this.loadFrameResponse(fetchResponse, document);\n                }\n                else {\n                    await this.handleUnvisitableFrameResponse(fetchResponse);\n                }\n            }\n        }\n        finally {\n            this.fetchResponseLoaded = () => { };\n        }\n    }\n    elementAppearedInViewport(element) {\n        this.proposeVisitIfNavigatedWithAction(element, element);\n        this.loadSourceURL();\n    }\n    willSubmitFormLinkToLocation(link) {\n        return this.shouldInterceptNavigation(link);\n    }\n    submittedFormLinkToLocation(link, _location, form) {\n        const frame = this.findFrameElement(link);\n        if (frame)\n            form.setAttribute(\"data-turbo-frame\", frame.id);\n    }\n    shouldInterceptLinkClick(element, _location, _event) {\n        return this.shouldInterceptNavigation(element);\n    }\n    linkClickIntercepted(element, location) {\n        this.navigateFrame(element, location);\n    }\n    willSubmitForm(element, submitter) {\n        return element.closest(\"turbo-frame\") == this.element && this.shouldInterceptNavigation(element, submitter);\n    }\n    formSubmitted(element, submitter) {\n        if (this.formSubmission) {\n            this.formSubmission.stop();\n        }\n        this.formSubmission = new FormSubmission(this, element, submitter);\n        const { fetchRequest } = this.formSubmission;\n        this.prepareRequest(fetchRequest);\n        this.formSubmission.start();\n    }\n    prepareRequest(request) {\n        var _a;\n        request.headers[\"Turbo-Frame\"] = this.id;\n        if ((_a = this.currentNavigationElement) === null || _a === void 0 ? void 0 : _a.hasAttribute(\"data-turbo-stream\")) {\n            request.acceptResponseType(StreamMessage.contentType);\n        }\n    }\n    requestStarted(_request) {\n        markAsBusy(this.element);\n    }\n    requestPreventedHandlingResponse(_request, _response) {\n        this.resolveVisitPromise();\n    }\n    async requestSucceededWithResponse(request, response) {\n        await this.loadResponse(response);\n        this.resolveVisitPromise();\n    }\n    async requestFailedWithResponse(request, response) {\n        await this.loadResponse(response);\n        this.resolveVisitPromise();\n    }\n    requestErrored(request, error) {\n        console.error(error);\n        this.resolveVisitPromise();\n    }\n    requestFinished(_request) {\n        clearBusyState(this.element);\n    }\n    formSubmissionStarted({ formElement }) {\n        markAsBusy(formElement, this.findFrameElement(formElement));\n    }\n    formSubmissionSucceededWithResponse(formSubmission, response) {\n        const frame = this.findFrameElement(formSubmission.formElement, formSubmission.submitter);\n        frame.delegate.proposeVisitIfNavigatedWithAction(frame, formSubmission.formElement, formSubmission.submitter);\n        frame.delegate.loadResponse(response);\n        if (!formSubmission.isSafe) {\n            session.clearCache();\n        }\n    }\n    formSubmissionFailedWithResponse(formSubmission, fetchResponse) {\n        this.element.delegate.loadResponse(fetchResponse);\n        session.clearCache();\n    }\n    formSubmissionErrored(formSubmission, error) {\n        console.error(error);\n    }\n    formSubmissionFinished({ formElement }) {\n        clearBusyState(formElement, this.findFrameElement(formElement));\n    }\n    allowsImmediateRender({ element: newFrame }, options) {\n        const event = dispatch(\"turbo:before-frame-render\", {\n            target: this.element,\n            detail: Object.assign({ newFrame }, options),\n            cancelable: true,\n        });\n        const { defaultPrevented, detail: { render }, } = event;\n        if (this.view.renderer && render) {\n            this.view.renderer.renderElement = render;\n        }\n        return !defaultPrevented;\n    }\n    viewRenderedSnapshot(_snapshot, _isPreview) { }\n    preloadOnLoadLinksForView(element) {\n        session.preloadOnLoadLinksForView(element);\n    }\n    viewInvalidated() { }\n    willRenderFrame(currentElement, _newElement) {\n        this.previousFrameElement = currentElement.cloneNode(true);\n    }\n    async loadFrameResponse(fetchResponse, document) {\n        const newFrameElement = await this.extractForeignFrameElement(document.body);\n        if (newFrameElement) {\n            const snapshot = new Snapshot(newFrameElement);\n            const renderer = new FrameRenderer(this, this.view.snapshot, snapshot, FrameRenderer.renderElement, false, false);\n            if (this.view.renderPromise)\n                await this.view.renderPromise;\n            this.changeHistory();\n            await this.view.render(renderer);\n            this.complete = true;\n            session.frameRendered(fetchResponse, this.element);\n            session.frameLoaded(this.element);\n            this.fetchResponseLoaded(fetchResponse);\n        }\n        else if (this.willHandleFrameMissingFromResponse(fetchResponse)) {\n            this.handleFrameMissingFromResponse(fetchResponse);\n        }\n    }\n    async visit(url) {\n        var _a;\n        const request = new FetchRequest(this, FetchMethod.get, url, new URLSearchParams(), this.element);\n        (_a = this.currentFetchRequest) === null || _a === void 0 ? void 0 : _a.cancel();\n        this.currentFetchRequest = request;\n        return new Promise((resolve) => {\n            this.resolveVisitPromise = () => {\n                this.resolveVisitPromise = () => { };\n                this.currentFetchRequest = null;\n                resolve();\n            };\n            request.perform();\n        });\n    }\n    navigateFrame(element, url, submitter) {\n        const frame = this.findFrameElement(element, submitter);\n        frame.delegate.proposeVisitIfNavigatedWithAction(frame, element, submitter);\n        this.withCurrentNavigationElement(element, () => {\n            frame.src = url;\n        });\n    }\n    proposeVisitIfNavigatedWithAction(frame, element, submitter) {\n        this.action = getVisitAction(submitter, element, frame);\n        if (this.action) {\n            const pageSnapshot = PageSnapshot.fromElement(frame).clone();\n            const { visitCachedSnapshot } = frame.delegate;\n            frame.delegate.fetchResponseLoaded = (fetchResponse) => {\n                if (frame.src) {\n                    const { statusCode, redirected } = fetchResponse;\n                    const responseHTML = frame.ownerDocument.documentElement.outerHTML;\n                    const response = { statusCode, redirected, responseHTML };\n                    const options = {\n                        response,\n                        visitCachedSnapshot,\n                        willRender: false,\n                        updateHistory: false,\n                        restorationIdentifier: this.restorationIdentifier,\n                        snapshot: pageSnapshot,\n                    };\n                    if (this.action)\n                        options.action = this.action;\n                    session.visit(frame.src, options);\n                }\n            };\n        }\n    }\n    changeHistory() {\n        if (this.action) {\n            const method = getHistoryMethodForAction(this.action);\n            session.history.update(method, expandURL(this.element.src || \"\"), this.restorationIdentifier);\n        }\n    }\n    async handleUnvisitableFrameResponse(fetchResponse) {\n        console.warn(`The response (${fetchResponse.statusCode}) from  is performing a full page visit due to turbo-visit-control.`);\n        await this.visitResponse(fetchResponse.response);\n    }\n    willHandleFrameMissingFromResponse(fetchResponse) {\n        this.element.setAttribute(\"complete\", \"\");\n        const response = fetchResponse.response;\n        const visit = async (url, options = {}) => {\n            if (url instanceof Response) {\n                this.visitResponse(url);\n            }\n            else {\n                session.visit(url, options);\n            }\n        };\n        const event = dispatch(\"turbo:frame-missing\", {\n            target: this.element,\n            detail: { response, visit },\n            cancelable: true,\n        });\n        return !event.defaultPrevented;\n    }\n    handleFrameMissingFromResponse(fetchResponse) {\n        this.view.missing();\n        this.throwFrameMissingError(fetchResponse);\n    }\n    throwFrameMissingError(fetchResponse) {\n        const message = `The response (${fetchResponse.statusCode}) did not contain the expected  and will be ignored. To perform a full page visit instead, set turbo-visit-control to reload.`;\n        throw new TurboFrameMissingError(message);\n    }\n    async visitResponse(response) {\n        const wrapped = new FetchResponse(response);\n        const responseHTML = await wrapped.responseHTML;\n        const { location, redirected, statusCode } = wrapped;\n        return session.visit(location, { response: { redirected, statusCode, responseHTML } });\n    }\n    findFrameElement(element, submitter) {\n        var _a;\n        const id = getAttribute(\"data-turbo-frame\", submitter, element) || this.element.getAttribute(\"target\");\n        return (_a = getFrameElementById(id)) !== null && _a !== void 0 ? _a : this.element;\n    }\n    async extractForeignFrameElement(container) {\n        let element;\n        const id = CSS.escape(this.id);\n        try {\n            element = activateElement(container.querySelector(`turbo-frame#${id}`), this.sourceURL);\n            if (element) {\n                return element;\n            }\n            element = activateElement(container.querySelector(`turbo-frame[src][recurse~=${id}]`), this.sourceURL);\n            if (element) {\n                await element.loaded;\n                return await this.extractForeignFrameElement(element);\n            }\n        }\n        catch (error) {\n            console.error(error);\n            return new FrameElement();\n        }\n        return null;\n    }\n    formActionIsVisitable(form, submitter) {\n        const action = getAction(form, submitter);\n        return locationIsVisitable(expandURL(action), this.rootLocation);\n    }\n    shouldInterceptNavigation(element, submitter) {\n        const id = getAttribute(\"data-turbo-frame\", submitter, element) || this.element.getAttribute(\"target\");\n        if (element instanceof HTMLFormElement && !this.formActionIsVisitable(element, submitter)) {\n            return false;\n        }\n        if (!this.enabled || id == \"_top\") {\n            return false;\n        }\n        if (id) {\n            const frameElement = getFrameElementById(id);\n            if (frameElement) {\n                return !frameElement.disabled;\n            }\n        }\n        if (!session.elementIsNavigatable(element)) {\n            return false;\n        }\n        if (submitter && !session.elementIsNavigatable(submitter)) {\n            return false;\n        }\n        return true;\n    }\n    get id() {\n        return this.element.id;\n    }\n    get enabled() {\n        return !this.element.disabled;\n    }\n    get sourceURL() {\n        if (this.element.src) {\n            return this.element.src;\n        }\n    }\n    set sourceURL(sourceURL) {\n        this.ignoringChangesToAttribute(\"src\", () => {\n            this.element.src = sourceURL !== null && sourceURL !== void 0 ? sourceURL : null;\n        });\n    }\n    get loadingStyle() {\n        return this.element.loading;\n    }\n    get isLoading() {\n        return this.formSubmission !== undefined || this.resolveVisitPromise() !== undefined;\n    }\n    get complete() {\n        return this.element.hasAttribute(\"complete\");\n    }\n    set complete(value) {\n        this.ignoringChangesToAttribute(\"complete\", () => {\n            if (value) {\n                this.element.setAttribute(\"complete\", \"\");\n            }\n            else {\n                this.element.removeAttribute(\"complete\");\n            }\n        });\n    }\n    get isActive() {\n        return this.element.isActive && this.connected;\n    }\n    get rootLocation() {\n        var _a;\n        const meta = this.element.ownerDocument.querySelector(`meta[name=\"turbo-root\"]`);\n        const root = (_a = meta === null || meta === void 0 ? void 0 : meta.content) !== null && _a !== void 0 ? _a : \"/\";\n        return expandURL(root);\n    }\n    isIgnoringChangesTo(attributeName) {\n        return this.ignoredAttributes.has(attributeName);\n    }\n    ignoringChangesToAttribute(attributeName, callback) {\n        this.ignoredAttributes.add(attributeName);\n        callback();\n        this.ignoredAttributes.delete(attributeName);\n    }\n    withCurrentNavigationElement(element, callback) {\n        this.currentNavigationElement = element;\n        callback();\n        delete this.currentNavigationElement;\n    }\n}\nfunction getFrameElementById(id) {\n    if (id != null) {\n        const element = document.getElementById(id);\n        if (element instanceof FrameElement) {\n            return element;\n        }\n    }\n}\nfunction activateElement(element, currentURL) {\n    if (element) {\n        const src = element.getAttribute(\"src\");\n        if (src != null && currentURL != null && urlsAreEqual(src, currentURL)) {\n            throw new Error(`Matching  element has a source URL which references itself`);\n        }\n        if (element.ownerDocument !== document) {\n            element = document.importNode(element, true);\n        }\n        if (element instanceof FrameElement) {\n            element.connectedCallback();\n            element.disconnectedCallback();\n            return element;\n        }\n    }\n}\n\nclass StreamElement extends HTMLElement {\n    static async renderElement(newElement) {\n        await newElement.performAction();\n    }\n    async connectedCallback() {\n        try {\n            await this.render();\n        }\n        catch (error) {\n            console.error(error);\n        }\n        finally {\n            this.disconnect();\n        }\n    }\n    async render() {\n        var _a;\n        return ((_a = this.renderPromise) !== null && _a !== void 0 ? _a : (this.renderPromise = (async () => {\n            const event = this.beforeRenderEvent;\n            if (this.dispatchEvent(event)) {\n                await nextAnimationFrame();\n                await event.detail.render(this);\n            }\n        })()));\n    }\n    disconnect() {\n        try {\n            this.remove();\n        }\n        catch (_a) { }\n    }\n    removeDuplicateTargetChildren() {\n        this.duplicateChildren.forEach((c) => c.remove());\n    }\n    get duplicateChildren() {\n        var _a;\n        const existingChildren = this.targetElements.flatMap((e) => [...e.children]).filter((c) => !!c.id);\n        const newChildrenIds = [...(((_a = this.templateContent) === null || _a === void 0 ? void 0 : _a.children) || [])].filter((c) => !!c.id).map((c) => c.id);\n        return existingChildren.filter((c) => newChildrenIds.includes(c.id));\n    }\n    get performAction() {\n        if (this.action) {\n            const actionFunction = StreamActions[this.action];\n            if (actionFunction) {\n                return actionFunction;\n            }\n            this.raise(\"unknown action\");\n        }\n        this.raise(\"action attribute is missing\");\n    }\n    get targetElements() {\n        if (this.target) {\n            return this.targetElementsById;\n        }\n        else if (this.targets) {\n            return this.targetElementsByQuery;\n        }\n        else {\n            this.raise(\"target or targets attribute is missing\");\n        }\n    }\n    get templateContent() {\n        return this.templateElement.content.cloneNode(true);\n    }\n    get templateElement() {\n        if (this.firstElementChild === null) {\n            const template = this.ownerDocument.createElement(\"template\");\n            this.appendChild(template);\n            return template;\n        }\n        else if (this.firstElementChild instanceof HTMLTemplateElement) {\n            return this.firstElementChild;\n        }\n        this.raise(\"first child element must be a