1import { Controller } from "@hotwired/stimulus"; 2import Player from "@vimeo/player"; 3import urlify from "utils/urlify"; 4 5// Connects to data-controller="numiko--vimeo" 6export default class extends Controller { 7 static targets = [ 8 "vimeoModal", 9 "iframe", 10 "ifrmContainer", 11 "modalName", 12 "modalDescription", 13 ]; 14 15 connect() { 16 const vimeoModalElem = this.vimeoModalTarget; 17 new bootstrap.Modal(vimeoModalElem, { keyboard: false }); 18 19 vimeoModalElem.addEventListener("show.bs.modal", (event) => { 20 const selectedVimeoObj = event.relatedTarget; 21 if (selectedVimeoObj.dataset.vimeoVideoId) { 22 const vimeoId = selectedVimeoObj.dataset.vimeoVideoId; 23 const name = selectedVimeoObj.dataset.vimeoVideoName; 24 const description = selectedVimeoObj.dataset.vimeoVideoDescription; 25 this.#createVimeoVideoIframe(vimeoId, name, description); 26 } else { 27 const vimeoId = selectedVimeoObj.dataset.vimeoShowcaseId; 28 const name = selectedVimeoObj.dataset.vimeoShowcaseName; 29 const description = selectedVimeoObj.dataset.vimeoShowcaseDescription; 30 const videosJson = selectedVimeoObj.dataset.vimeoShowcaseVideos; 31 const videosMap = videosJson ? JSON.parse(videosJson) : {}; 32 this.#createVimeoShowcaseIframe(vimeoId, name, description, videosMap); 33 } 34 }); 35 36 vimeoModalElem.addEventListener("hide.bs.modal", (_event) => { 37 if (this.player) { 38 this.player.destroy(); 39 this.player = null; 40 } 41 this.showcaseVideosMap = null; 42 this.iframeTarget.remove(); 43 }); 44 } 45 46 #createVimeoVideoIframe(vimeoId, name, description) { 47 const ifrm = document.createElement("iframe"); 48 ifrm.setAttribute( 49 "src", 50 `https://player.vimeo.com/video/${vimeoId}?color=B33D26&autoplay=1&texttrack=${locale}`, 51 ); 52 ifrm.setAttribute("frameborder", "0"); 53 ifrm.setAttribute("webkitallowfullscreen", true); 54 ifrm.setAttribute("mozallowfullscreen", true); 55 ifrm.setAttribute("allowfullscreen", true); 56 ifrm.dataset["numiko-VimeoTarget"] = "iframe"; // Note: how this translates to: data-numiko--vimeo-target="iframe" 57 ifrm.style.width = "100%"; 58 ifrm.style.height = "400px"; 59 ifrm.style.top = 0; 60 ifrm.style.left = 0; 61 this.ifrmContainerTarget.appendChild(ifrm); 62 this.modalNameTarget.innerHTML = name; 63 this.modalDescriptionTarget.innerHTML = urlify(description); 64 } 65 66 #createVimeoShowcaseIframe(vimeoId, name, description, videosMap) { 67 this.showcaseVideosMap = videosMap; 68 69 const ifrm = document.createElement("iframe"); 70 ifrm.setAttribute( 71 "src", 72 `https://vimeo.com/showcase/${vimeoId}/embed?color=B33D26&autoplay=1&texttrack=${locale}`, 73 ); 74 ifrm.setAttribute("frameborder", "0"); 75 ifrm.setAttribute("webkitallowfullscreen", true); 76 ifrm.setAttribute("mozallowfullscreen", true); 77 ifrm.setAttribute("allowfullscreen", true); 78 ifrm.dataset["numiko-VimeoTarget"] = "iframe"; // Note: how tis translates to: data-numiko--vimeo-target="iframe" 79 ifrm.style.width = "100%"; 80 ifrm.style.height = "400px"; 81 ifrm.style.top = 0; 82 ifrm.style.left = 0; 83 this.ifrmContainerTarget.appendChild(ifrm); 84 this.modalNameTarget.innerHTML = name; 85 this.modalDescriptionTarget.innerHTML = urlify(description); 86 87 this.player = new Player(ifrm); 88 this.player.on("loaded", () => { 89 this.#updateDescriptionFromCurrentVideo(); 90 }); 91 } 92 93 #updateDescriptionFromCurrentVideo() { 94 if (!this.player || !this.showcaseVideosMap) return; 95 96 this.player.getVideoId().then((videoId) => { 97 const videoDescription = this.showcaseVideosMap[videoId]; 98 if (videoDescription !== undefined) { 99 this.modalDescriptionTarget.innerHTML = 100 this.#formatDescription(videoDescription); 101 } 102 }); 103 } 104 105 #formatDescription(description) { 106 const parts = description.split(/\n\n--\n\n/); 107 108 if (parts.length === 1) { 109 return urlify(description); 110 } 111 112 const mainContent = parts[0]; 113 const ctaSection = parts[1].trim(); 114 const additionalContent = parts.slice(2).join("\n\n"); 115 116 // Match "Action Text: url" where URL may or may not have protocol 117 const linkMatch = ctaSection.match( 118 /^(.+?):\s*((?:https?:\/\/)?[\w.-]+(?:\/\S*)?)\s*$/, 119 ); 120 121 if (!linkMatch) { 122 return urlify(description); 123 } 124 125 const [, linkText, rawUrl] = linkMatch; 126 const linkUrl = rawUrl.startsWith("http") ? rawUrl : `https://${rawUrl}`; 127 const buttonHtml = `<a href="${linkUrl}" class="btn btn-primary" target="_blank" rel="noopener noreferrer">${linkText.trim()}</a>`; 128 129 let fullDescription = mainContent.trim(); 130 if (additionalContent) { 131 fullDescription += "\n\n" + additionalContent.trim(); 132 } 133 134 fullDescription = fullDescription.replace(/\n/g, "<br>"); 135 return urlify(fullDescription) + "<br><br>" + buttonHtml; 136 } 137}
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.