tagliatelle

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

timestamps.js (3718B)


      1 function parseTimestamp(ts) {
      2   const parts = ts.split(":").map(Number).reverse();
      3   let seconds = 0;
      4   if (parts[0]) seconds += parts[0];          // seconds
      5   if (parts[1]) seconds += parts[1] * 60;     // minutes
      6   if (parts[2]) seconds += parts[2] * 3600;   // hours
      7   return seconds;
      8 }
      9 
     10 function makeTimestampsClickable(containerId, videoId, imageId) {
     11   const container = document.getElementById(containerId);
     12   const video = document.getElementById(videoId);
     13   const image = document.getElementById(imageId);
     14   const videoContainer = document.getElementById('videoContainer');
     15   const imageContainer = document.getElementById('imageContainer');
     16 
     17   // Regex for timestamps: [h:mm:ss] or [mm:ss] or [ss]
     18   const timestampRegex = /\[(\d{1,2}(?::\d{2}){0,2})\]/g;
     19   // Regex for rotations: [rotate90], [rotate180], [rotate270], [rotate0]
     20   const rotateRegex = /\[rotate(0|90|180|270)\]/g;
     21   // Regex for URLs: http(s):// or www. links
     22   const urlRegex = /\b((?:https?:\/\/|www\.)[^\s<]+)/gi;
     23   // Regex for markdown-style links: [link text](https://example.com)
     24   const markdownLinkRegex = /\[([^\[\]]+)\]\(((?:https?:\/\/|www\.)[^)\s]+)\)/gi;
     25 
     26   // Pull out markdown links first and swap in placeholders, so the raw URL
     27   // inside them doesn't get double-processed by the bare-URL regex below.
     28   const mdLinks = [];
     29   let html = container.innerHTML.replace(markdownLinkRegex, (match, text, url) => {
     30     const href = url.startsWith("http") ? url : `https://${url}`;
     31     const placeholder = `\u0000MDLINK${mdLinks.length}\u0000`;
     32     mdLinks.push(`<a href="${href}" class="external-link" target="_blank" rel="noopener noreferrer">${text}</a>`);
     33     return placeholder;
     34   });
     35 
     36   // Replace bare URLs, then timestamps, then rotations
     37   html = html
     38     .replace(urlRegex, (match) => {
     39       // Strip common trailing punctuation that isn't part of the URL
     40       const trailingPunct = /[.,;:!?)\]"'>]+$/;
     41       const trimmed = match.match(trailingPunct) ? match.replace(trailingPunct, "") : match;
     42       const suffix = match.slice(trimmed.length);
     43       const href = trimmed.startsWith("http") ? trimmed : `https://${trimmed}`;
     44       return `<a href="${href}" class="external-link" target="_blank" rel="noopener noreferrer">${trimmed}</a>${suffix}`;
     45     })
     46     .replace(timestampRegex, (match, ts) => {
     47       const seconds = parseTimestamp(ts);
     48       return `<a href="#" class="timestamp" data-time="${seconds}">${match}</a>`;
     49     })
     50     .replace(rotateRegex, (match, angle) => {
     51       return `<a href="#" class="rotate" data-angle="${angle}">${match}</a>`;
     52     });
     53 
     54   // Restore the markdown links now that no other regex can touch them
     55   container.innerHTML = html.replace(/\u0000MDLINK(\d+)\u0000/g, (_, i) => mdLinks[Number(i)]);
     56   // Handle clicks
     57   if (container.dataset.timestampClickBound) return;
     58   container.dataset.timestampClickBound = 'true';
     59 
     60   container.addEventListener("click", e => {
     61     if (e.target.classList.contains("timestamp")) {
     62       e.preventDefault();
     63       const time = Number(e.target.dataset.time);
     64       if (video) {
     65         video.currentTime = time;
     66         video.play();
     67         window.scrollTo({ top: 0, behavior: "smooth" });
     68       }
     69     } else if (e.target.classList.contains("rotate")) {
     70       e.preventDefault();
     71       const angle = Number(e.target.dataset.angle);
     72 
     73       if (video) {
     74 		applyRotation(video, angle);
     75 	  } else if (image) {
     76 		applyRotation(image, angle);
     77 	  }
     78 	}
     79   });
     80 }
     81 
     82 function applyRotation(element, angle) {
     83   element.style.transform = `rotate(${angle}deg)`;
     84   element.style.transformOrigin = "center center";
     85 }
     86 
     87 // Run it
     88 document.addEventListener("DOMContentLoaded", () => {
     89   makeTimestampsClickable("current-description", "videoPlayer", "imageViewer");
     90 });