Pictures!
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

slideshow.js 4.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. var fs = require("fs");
  2. var pathlib = require("path");
  3. var marked = require("marked");
  4. var mimetype = require("./mimetype");
  5. var error = require("./error");
  6. module.exports = Slideshow;
  7. marked.setOptions({
  8. sanitize: true
  9. });
  10. var htmlPre =
  11. "<html>"+
  12. "<head>"+
  13. "<link rel='stylesheet' href='/slide.css'>"+
  14. "<meta charset='utf-8'>"+
  15. "</head>"+
  16. "<body>"+
  17. "<div id='_wrapper'>";
  18. var htmlPost =
  19. "<script src='/slide.js'></script>"+
  20. "</div>"+
  21. "</body>"+
  22. "</html>";
  23. function sendFile(path, res) {
  24. res.writeHead(200, {
  25. "content-type": mimetype(path)
  26. });
  27. fs.createReadStream(path)
  28. .on("error", err => {
  29. res.writeHead(404);
  30. res.end(err.toString());
  31. })
  32. .pipe(res);
  33. }
  34. function sendIndex(html, res) {
  35. res.writeHead(200, {
  36. "content-type": "text/html"
  37. });
  38. res.write(htmlPre);
  39. res.write(html);
  40. res.end(htmlPost);
  41. }
  42. // The individual slide
  43. function Slide(dir) {
  44. var self = {};
  45. self.dir = dir;
  46. self.name = pathlib.parse(dir).name;
  47. self.stat = null;
  48. self.html = "";
  49. self.sendIndex = function(res) {
  50. var path = pathlib.join(self.dir, "index.md");
  51. fs.stat(pathlib.join(self.dir, "index.md"), (err, stat) => {
  52. if (err) {
  53. sendIndex("", res);
  54. console.log(err);
  55. return;
  56. }
  57. if (!self.stat || !stat || self.stat.mtime !== stat.mtime) {
  58. fs.readFile(path, "utf-8", (err, md) => {
  59. if (err) {
  60. sendIndex(err);
  61. console.log(err);
  62. return;
  63. }
  64. self.html = marked(md);
  65. sendIndex(self.html, res);
  66. });
  67. } else {
  68. sendIndex(self.html, res);
  69. }
  70. self.stat = stat;
  71. });
  72. }
  73. self.sendFile = function(name, res) {
  74. sendFile(self.html, res);
  75. }
  76. self.serveFiles = function(parts, res) {
  77. // Serve index if /{name} is requested
  78. if (parts.pathname.replace(/\//g, "") === self.name) {
  79. // Redirect from /{name} to /{name}/
  80. if (parts.pathname[parts.pathname.length - 1] !== '/') {
  81. res.writeHead(302, {
  82. location: parts.pathname+"/"
  83. });
  84. res.end();
  85. // Serve index if it's already /{name}/
  86. } else {
  87. self.sendIndex(res);
  88. }
  89. } else {
  90. // Serve other files
  91. var name = parts.pathname.substring(1).replace(self.name, "");
  92. var path = pathlib.join(dir, name);
  93. sendFile(path, res);
  94. }
  95. }
  96. self.indexExists = function() {
  97. try {
  98. fs.accessSync(pathlib.join(dir, "index.md"));
  99. return true;
  100. } catch (err) {
  101. return false;
  102. }
  103. }
  104. try {
  105. self.meta = JSON.parse(
  106. fs.readFileSync(pathlib.join(dir, "meta.json")));
  107. } catch (err) {
  108. if (err.code !== "ENOENT")
  109. error(err);
  110. self.meta = {};
  111. }
  112. return self;
  113. }
  114. // The slideshow, whose job it is to manage all slides
  115. // and tell the client whether it has to update or not
  116. function Slideshow(dir, changeInterval) {
  117. var self = {};
  118. var currentSlide = null;
  119. var awaiters = [];
  120. var slides = [];
  121. var slideIndex = 0;
  122. var nextTimeout;
  123. self.sendEvent = function(evt, args) {
  124. var str = JSON.stringify({ evt: evt, args: args });
  125. awaiters.forEach(res => res.end(str));
  126. awaiters = [];
  127. }
  128. // Get the current slide's interval
  129. function currentInterval() {
  130. var itv = currentSlide.meta.interval;
  131. if (itv === undefined)
  132. itv = changeInterval;
  133. return itv;
  134. }
  135. // Return if the current slide is enabled and valid
  136. function currentEnabled() {
  137. return currentSlide.indexExists() && !currentSlide.meta.disabled;
  138. }
  139. self.getSlideName = getSlideName;
  140. function getSlideName() {
  141. return currentSlide ? currentSlide.name : ""
  142. }
  143. self.pushAwaiter = pushAwaiter;
  144. function pushAwaiter(res) {
  145. awaiters.push(res);
  146. }
  147. self.getSlides = getSlides;
  148. function getSlides() {
  149. return slides;
  150. }
  151. self.next = next;
  152. function next() {
  153. slideIndex += 1;
  154. // Go to the next slide, or restart
  155. if (slideIndex >= slides.length) {
  156. clearTimeout(nextTimeout);
  157. init();
  158. } else {
  159. currentSlide = slides[slideIndex];
  160. nextTimeout = setTimeout(next, currentInterval());
  161. }
  162. // End all awaiting connections to notify slide change,
  163. if (currentEnabled()) {
  164. self.sendEvent("next", { name: currentSlide.name });
  165. // Or go to the next slide if the current one doesn't have an index.html
  166. // or if the slide is disabled
  167. } else {
  168. clearTimeout(nextTimeout);
  169. setTimeout(next, 0);
  170. }
  171. }
  172. self.updateSlides = updateSlides;
  173. function updateSlides() {
  174. slides = fs.readdirSync(dir)
  175. .sort()
  176. .map(file => Slide(pathlib.join(dir, file)));
  177. }
  178. self.serve = serve;
  179. function serve(parts, res) {
  180. for (var slide of slides) {
  181. if (slide.name === parts.pathname.substr(1, slide.name.length)) {
  182. slide.serveFiles(parts, res);
  183. return;
  184. }
  185. }
  186. // We haven't found any matching slides
  187. res.writeHead(404);
  188. res.end("404");
  189. }
  190. // This function starts the slideshow and goes through the slides
  191. // one by one. When done, it starts again by calling this function again.
  192. function init() {
  193. updateSlides();
  194. slideIndex = 0;
  195. currentSlide = slides[slideIndex];
  196. nextTimeout = setTimeout(next, currentInterval());
  197. }
  198. init();
  199. return self;
  200. }