var fs = require("fs"); var pathlib = require("path"); var marked = require("marked"); var mimetype = require("./mimetype"); var error = require("./error"); module.exports = Slideshow; marked.setOptions({ sanitize: true }); var htmlPre = ""+ ""+ ""+ ""+ ""+ ""+ "
"; var htmlPost = ""+ "
"+ ""+ ""; function sendFile(path, res) { res.writeHead(200, { "content-type": mimetype(path) }); fs.createReadStream(path) .on("error", err => error(err, res)) .pipe(res); } function sendIndex(html, res) { res.writeHead(200, { "content-type": "text/html" }); res.write(htmlPre); res.write(html); res.end(htmlPost); } // The individual slide function Slide(dir) { var self = {}; self.dir = dir; self.name = pathlib.parse(dir).name; self.stat = null; self.html = ""; self.sendIndex = function(res) { var path = pathlib.join(self.dir, "index.md"); fs.stat(pathlib.join(self.dir, "index.md"), (err, stat) => { if (err) { sendIndex("", res); console.log(err); return; } if (!self.stat || !stat || self.stat.mtime !== stat.mtime) { fs.readFile(path, "utf-8", (err, md) => { if (err) { sendIndex(err); console.log(err); return; } self.html = marked(md); sendIndex(self.html, res); }); } else { sendIndex(self.html, res); } self.stat = stat; }); } self.sendFile = function(name, res) { sendFile(self.html, res); } self.serveFiles = function(parts, res) { // Serve index if /{name} is requested if (parts.pathname.replace(/\//g, "") === self.name) { // Redirect from /{name} to /{name}/ if (parts.pathname[parts.pathname.length - 1] !== '/') { res.writeHead(302, { location: parts.pathname+"/" }); res.end(); // Serve index if it's already /{name}/ } else { self.sendIndex(res); } } else { // Serve other files var name = parts.pathname.substring(1).replace(self.name, ""); var path = pathlib.join(dir, name); sendFile(path, res); } } self.indexExists = function() { try { fs.accessSync(pathlib.join(dir, "index.md")); return true; } catch (err) { return false; } } try { self.meta = JSON.parse( fs.readFileSync(pathlib.join(dir, "meta.json"))); } catch (err) { if (err.code !== "ENOENT") error(err); self.meta = {}; } return self; } // The slideshow, whose job it is to manage all slides // and tell the client whether it has to update or not function Slideshow(dir, changeInterval) { var self = {}; var currentSlide = null; var awaiters = []; var slides = []; var slideIndex = 0; var nextTimeout; self.sendEvent = function(evt, args) { var str = JSON.stringify({ evt: evt, args: args }); awaiters.forEach(res => res.end(str)); awaiters = []; } // Get the current slide's interval function currentInterval() { var itv = currentSlide.meta.interval; if (itv === undefined) itv = changeInterval; return itv; } // Return if the current slide is enabled and valid function currentEnabled() { return currentSlide.indexExists() && !currentSlide.disabled; } self.getSlideName = getSlideName; function getSlideName() { return currentSlide ? currentSlide.name : "" } self.pushAwaiter = pushAwaiter; function pushAwaiter(res) { awaiters.push(res); } self.getSlides = getSlides; function getSlides() { return slides; } self.next = next; function next() { slideIndex += 1; // Go to the next slide, or restart if (slideIndex >= slides.length) { clearTimeout(nextTimeout); init(); } else { currentSlide = slides[slideIndex]; nextTimeout = setTimeout(next, currentInterval()); } // End all awaiting connections to notify slide change, if (currentEnabled()) { self.sendEvent("next", { name: currentSlide.name }); // Or go to the next slide if the current one doesn't have an index.html // or if the slide is disabled } else { clearTimeout(nextTimeout); setTimeout(next, 0); } } // This function starts the slideshow and goes through the slides // one by one. When done, it starts again by calling this function again. function init() { slides = fs.readdirSync(dir) .sort() .map(file => Slide(pathlib.join(dir, file))); slideIndex = 0; currentSlide = slides[slideIndex]; nextTimeout = setTimeout(next, currentInterval()); } init(); return self; }