1import { Controller } from "@hotwired/stimulus" 2 3// Form-wide ergonomics for the blog post editor: 4// 1. Warn before leaving when there are unsaved changes. 5// 2. On page load with a validation error block present, smooth-scroll the 6// first errored field into view. 7// 3. Derived post-title readout â reflects the first Title section's current 8// Trix content so the admin can see what the saved title will be. 9export default class extends Controller { 10 static targets = ["errorBlock", "titleReadout"] 11 12 connect() { 13 this.dirty = false 14 this.boundMarkDirty = () => { this.dirty = true } 15 this.boundTrixChange = this.handleTrixChange.bind(this) 16 this.boundBeforeUnload = this.handleBeforeUnload.bind(this) 17 this.boundSubmit = () => { this.dirty = false } 18 19 this.element.addEventListener("input", this.boundMarkDirty) 20 this.element.addEventListener("change", this.boundMarkDirty) 21 this.element.addEventListener("submit", this.boundSubmit) 22 document.addEventListener("trix-change", this.boundTrixChange) 23 window.addEventListener("beforeunload", this.boundBeforeUnload) 24 25 this.#scrollToFirstError() 26 this.#updateTitleReadout() 27 } 28 29 disconnect() { 30 this.element.removeEventListener("input", this.boundMarkDirty) 31 this.element.removeEventListener("change", this.boundMarkDirty) 32 this.element.removeEventListener("submit", this.boundSubmit) 33 document.removeEventListener("trix-change", this.boundTrixChange) 34 window.removeEventListener("beforeunload", this.boundBeforeUnload) 35 } 36 37 handleBeforeUnload(event) { 38 if (!this.dirty) return 39 event.preventDefault() 40 event.returnValue = "" 41 } 42 43 handleTrixChange() { 44 this.dirty = true 45 this.#updateTitleReadout() 46 } 47 48 #scrollToFirstError() { 49 if (!this.hasErrorBlockTarget) return 50 const errored = this.element.querySelector(".input-field-error, trix-editor.ring-red-300") 51 errored?.scrollIntoView({ behavior: "smooth", block: "center" }) 52 } 53 54 #updateTitleReadout() { 55 if (!this.hasTitleReadoutTarget) return 56 const sections = this.element.querySelectorAll("[data-section]") 57 let text = "" 58 for (const section of sections) { 59 const kindInput = section.querySelector("input[name*='[kind]']") 60 if (kindInput && kindInput.value === "title") { 61 const editor = section.querySelector("trix-editor") 62 text = (editor && editor.innerText ? editor.innerText : "").trim() 63 if (text) break 64 } 65 } 66 this.titleReadoutTarget.textContent = text ? `ð ${text}` : "" 67 } 68}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.