{"version":3,"file":"js/3802-fa481774e7caea54fdde.chunk.js","mappings":"2GAAA,M,+VAIA,MAAMA,UAAwBC,YAA9B,kCACE,SAAc,IACd,SAAe,IAEf,iBAEAC,oBAEOC,KAAKC,MAKV,EAAAD,KAAK,EAAcA,KAAKC,KAGnB,EAAAD,KAAK,IACR,EAAAA,KAAK,EAyDX,SAAwBE,GACtB,MAAMC,EAAW,IAAIC,sBACnB,EAAEC,MACIA,GAASA,EAAMC,iBACjBJ,IACAC,EAASI,UAAUF,EAAMG,QAC3B,GAEF,CAAEC,WAAY,cAGhB,OAAON,CACT,CArEuBO,EAAe,IAAMV,KAAKW,cAI7C,EAAAX,KAAK,GAAUY,QAAQZ,KAAKa,sBAC9B,CAEAC,iBACM,EAAAd,KAAK,KAAgB,EAAAA,KAAK,GAE5B,EAAAA,KAAK,GAAUe,oBAETf,KAAKgB,yBACX,EAAAhB,KAAK,GAAUY,QAAQZ,KAAKiB,kBAEhC,CAEAH,+BACE,MAAMI,EAAa,EAAAlB,KAAK,GAElBmB,QA0DVL,eAA6Bb,GAC3B,MAAMmB,EAAM,IAAIC,IAAI,iBAAkBC,OAAOC,SAASC,QAGtD,OAFAJ,EAAIK,aAAaC,IAAI,MAAOzB,GAErB,QAASmB,GAAKO,MACvB,CA/DyBC,CAAcV,GAE/BC,EAAOU,OACT,OAAwB7B,KAAMmB,EAAOU,MAGvC,EAAA7B,KAAK,EAAcmB,EAAOW,SAAWX,EAAOW,SAAWZ,GACvD,EAAAlB,KAAK,EAAekB,EACtB,CAEAL,qBAnDF,QAsDI,OAAO,kBAAAb,KAAK+B,cAAc/B,KAAKgC,WAAxB,EAAqChC,KAAKiB,kBAA1C,EAA8DjB,IACvE,CAEIC,UACF,OAAOD,KAAKiC,aAAa,MAC3B,CAEID,eACF,OAAOhC,KAAKiC,aAAa,WAC3B,EA1DA,cACA,cAEA,cA0DGX,OAAOY,eAAeC,IAAI,gBAC7Bb,OAAOzB,gBAAkBA,EACzByB,OAAOY,eAAeE,OAAO,aAAcvC,G,iBC9DtC,SAASwC,EAAwBC,EAASC,GAE/C,MAAMC,EAAWC,SAASC,cAAcC,yBAAyBJ,GACjED,EAAQM,YAAYJ,EACtB,CASO,SAASK,EAASP,EAASQ,EAAUC,GAC1CT,EAAQU,MAAMC,YAAYH,EAAUC,EACtC,C,yCCZO,SAASG,IAThB,MAUE,OAAO,SAAAT,SACJV,cAAc,iCADV,IAEHE,aAAa,UACnB,C,yBAwCA,Q,QAAe,UAAU,CACvBkB,MAAO,CAAEC,cAAe,CAX1B,SAAiCC,GAER,QAAnBA,EAAQC,QArBd,SAAyBlC,GAEvB,IAAKA,EAAImC,WAAW,QAClB,OAAO,EAGT,IAEE,OAAO,IAAIlC,IAAID,GAAKoC,WAAalC,OAAOC,SAASiC,QACnD,CAAE,MAAOC,GAGP,OAAO,CACT,CACF,CASQC,CAAgBL,EAAQjC,MAC1BiC,EAAQM,QAAQjC,IAAI,eAAgBwB,IAG1C,K","sources":["webpack://endrun/./app/components/stream_component.js","webpack://endrun/./app/javascript/app/util/dom.js","webpack://endrun/./app/javascript/shared/http.js"],"sourcesContent":["// local\nimport http from '$shared/http.js';\nimport { appendStringIntoElement } from '$app/util/dom.js';\n\nclass StreamComponent extends HTMLElement {\n #currentKey = '';\n #previousKey = '';\n /** @type {IntersectionObserver} */\n #observer;\n\n connectedCallback() {\n // sometimes the entire stream is already loaded and passes no key, so just stop here\n if (!this.key) {\n return;\n }\n\n // establish the provided key as the current key\n this.#currentKey = this.key;\n\n // if we don't already have an observer, create one\n if (!this.#observer) {\n this.#observer = createObserver(() => this.listener());\n }\n\n // observe our sentinel\n this.#observer.observe(this.getSentinelElement());\n }\n\n async listener() {\n if (this.#currentKey === this.#previousKey) {\n // if there's no more to load remove the listener and disconnect the observer\n this.#observer.disconnect();\n } else {\n await this.requestAndInjectStream();\n this.#observer.observe(this.lastElementChild);\n }\n }\n\n async requestAndInjectStream() {\n const currentKey = this.#currentKey;\n\n const stream = await requestStream(currentKey);\n\n if (stream.html) {\n appendStringIntoElement(this, stream.html);\n }\n\n this.#currentKey = stream.next_key ? stream.next_key : currentKey;\n this.#previousKey = currentKey;\n }\n\n getSentinelElement() {\n // if no valid selector was passed, use the last element child as the sentinel\n // if there are no children, use the element itself\n return this.querySelector(this.sentinel) ?? this.lastElementChild ?? this;\n }\n\n get key() {\n return this.getAttribute('key');\n }\n\n get sentinel() {\n return this.getAttribute('sentinel');\n }\n}\n\nif (!window.customElements.get('tmp-stream')) {\n window.StreamComponent = StreamComponent;\n window.customElements.define('tmp-stream', StreamComponent);\n}\n\n/**\n * Creates an IntersectionObserver that dispatches our custom stream load event\n * when an element intersects.\n *\n * @param {() => void} callback\n * @returns {IntersectionObserver}\n */\nfunction createObserver(callback) {\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (entry && entry.isIntersecting) {\n callback();\n observer.unobserve(entry.target);\n }\n },\n { rootMargin: '600px 0px' } // move the top/bottom triggers by 600px\n );\n\n return observer;\n}\n\n/**\n * Uses fetch to request the stream data from the server and returns it as\n * an object.\n *\n * @param {string} key\n * @returns {Promise}\n */\nasync function requestStream(key) {\n const url = new URL('/api/v2/stream', window.location.origin);\n url.searchParams.set('key', key);\n\n return http.get(url).json();\n}\n","/**\n * Accepts an element and a string of HTML and injects the string into the element.\n *\n * @param {Element} element\n * @param {string} string\n */\nexport function appendStringIntoElement(element, string) {\n // TODO: Use a template element instead of a document fragment once we are done with IE11\n const fragment = document.createRange().createContextualFragment(string);\n element.appendChild(fragment);\n}\n\n/**\n * Sets the CSS style of the specified element.\n *\n * @param {HTMLElement} element\n * @param {string} property\n * @param {any} value\n */\nexport function setStyle(element, property, value) {\n element.style.setProperty(property, value);\n}\n","// packages\nimport ky from 'ky';\n\n/**\n * Returns the CSRF token from the meta tag. This is injected by Rails using\n * the `csrf_meta_tags` helper.\n *\n * @returns {string}\n */\nexport function getCsrfToken() {\n return document\n .querySelector(\"meta[name='csrf-token']\")\n ?.getAttribute('content');\n}\n\n/**\n * Audits whether a given URL is on the same origin as the current page. We do\n * not want to send the CSRF token to different origins because sometimes third-party\n * services will throw errors if they receive a header they don't expect.\n *\n * @private\n * @param {string} url\n * @returns {boolean}\n */\nfunction isUrlSameOrigin(url) {\n // it is a relative URL so same origin\n if (!url.startsWith('http')) {\n return true;\n }\n\n try {\n // if the hostname matches, it's the same origin\n return new URL(url).hostname === window.location.hostname;\n } catch (_) {\n // if something goes wrong with the URL parsing we err on the side of\n // caution and assume it's the same origin\n return true;\n }\n}\n\n/**\n * @param {Request} request\n */\nfunction applyCsrfTokenToRequest(request) {\n // only need the CSRF token for non-GET requests\n if (request.method !== 'GET') {\n // send CSRF token only with same origin requests\n if (isUrlSameOrigin(request.url)) {\n request.headers.set('X-CSRF-Token', getCsrfToken());\n }\n }\n}\n\nexport default ky.create({\n hooks: { beforeRequest: [applyCsrfTokenToRequest] },\n});\n"],"names":["StreamComponent","HTMLElement","connectedCallback","this","key","callback","observer","IntersectionObserver","entry","isIntersecting","unobserve","target","rootMargin","createObserver","listener","observe","getSentinelElement","async","disconnect","requestAndInjectStream","lastElementChild","currentKey","stream","url","URL","window","location","origin","searchParams","set","json","requestStream","html","next_key","querySelector","sentinel","getAttribute","customElements","get","define","appendStringIntoElement","element","string","fragment","document","createRange","createContextualFragment","appendChild","setStyle","property","value","style","setProperty","getCsrfToken","hooks","beforeRequest","request","method","startsWith","hostname","_","isUrlSameOrigin","headers"],"sourceRoot":""}