1import currentlyInCanvasEditor from "../../lib/currentlyInCanvasEditor.js"; 2 3/** 4 * "Read more" for a Text component whose author typed [more] in the rich text. 5 * 6 * Progressive enhancement, deliberately: the server renders BOTH halves visible, 7 * and this collapses the tail. With JS off â or if this file fails â the reader 8 * still gets the whole passage rather than a permanently truncated one, and 9 * search engines always see the full text. 10 * 11 * In the Canvas editor nothing is collapsed: an author editing the passage needs 12 * to see what they are writing. That has to be decided CLIENT-side, because the 13 * editor and the Preview share the same cached server render. 14 */ 15 16const EXPANDED = "ct-text--expanded"; 17 18function label(text) { 19 const button = document.createElement("button"); 20 button.type = "button"; 21 button.className = "ct-text__toggle"; 22 button.textContent = text; 23 return button; 24} 25 26function enhance(root) { 27 if (root.dataset.ctTruncateReady) return; 28 root.dataset.ctTruncateReady = "1"; 29 30 const preview = root.querySelector(".ct-text__preview"); 31 const rest = root.querySelector(".ct-text__rest"); 32 if (!preview || !rest) return; 33 34 // The inline "⦠Read more" goes INSIDE the preview's last block, so it reads 35 // as a continuation of the sentence rather than a button on its own line. 36 const tail = preview.lastElementChild || preview; 37 const inline = document.createElement("span"); 38 inline.className = "ct-text__more"; 39 40 // Only add an ellipsis when the preview stops mid-sentence. If the author's 41 // last line already ends in punctuation, "world." + "â¦" reads as four dots. 42 if (!/[.!?â¦:;,]["'ââ)\]]?\s*$/.test(tail.textContent || "")) { 43 inline.append("⦠"); 44 } else { 45 inline.append(" "); 46 } 47 48 const more = label(Drupal.t("Read more")); 49 more.setAttribute("aria-expanded", "false"); 50 if (rest.id) more.setAttribute("aria-controls", rest.id); 51 inline.appendChild(more); 52 tail.appendChild(inline); 53 54 const less = label(Drupal.t("Show less")); 55 less.classList.add("ct-text__toggle--less"); 56 rest.appendChild(less); 57 58 const setExpanded = (open) => { 59 root.classList.toggle(EXPANDED, open); 60 more.setAttribute("aria-expanded", String(open)); 61 inline.hidden = open; 62 if (open) { 63 // Move focus to the revealed text so keyboard and screen-reader users 64 // land on the new content instead of staying at the collapsed point. 65 rest.setAttribute("tabindex", "-1"); 66 rest.focus({ preventScroll: true }); 67 } else { 68 more.focus(); 69 } 70 }; 71 72 more.addEventListener("click", () => setExpanded(true)); 73 less.addEventListener("click", () => setExpanded(false)); 74 75 // This class â not the server-rendered attribute â is what the CSS hides on, 76 // so the tail only ever collapses once this script has run successfully. 77 root.classList.add("ct-text--collapsible"); 78 root.classList.remove(EXPANDED); 79} 80 81Drupal.behaviors.sitethemeTextTruncate = { 82 attach(context) { 83 if (currentlyInCanvasEditor()) return; 84 const scope = context && context.querySelectorAll ? context : document; 85 scope.querySelectorAll("[data-ct-truncate]").forEach(enhance); 86 }, 87};
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.