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

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