Browse Source

now uses markdown, and admin interface mostly done

master
mortie 7 years ago
parent
commit
fbe22a3d9f

+ 1
- 0
.gitignore View File

@@ -1,2 +1,3 @@
conf.json
slides
node_modules

+ 0
- 3
exampleSlides/1/index.html View File

@@ -1,3 +0,0 @@
<h1> Viktig informasjon om greier </h1>

<p> Et eller annet </p>

+ 3
- 0
exampleSlides/1/index.md View File

@@ -0,0 +1,3 @@
# Viktig informasjon om greier

Et eller annet

+ 0
- 1
exampleSlides/2/index.html View File

@@ -1 +0,0 @@
<h1> This is the second slide </h1>

+ 1
- 0
exampleSlides/2/index.md View File

@@ -0,0 +1 @@
# This is the second slide

+ 0
- 1
exampleSlides/3/index.html View File

@@ -1 +0,0 @@
<h1> This is the third slide </h1>

+ 1
- 0
exampleSlides/3/index.md View File

@@ -0,0 +1 @@
# This is the third slide

+ 0
- 1
exampleSlides/4/index.html View File

@@ -1 +0,0 @@
<img class="fullscreen" src="image.svg">

+ 1
- 0
exampleSlides/4/index.md View File

@@ -0,0 +1 @@
![fullscreen](image.svg)

+ 41
- 8
js/admin.js View File

@@ -1,6 +1,7 @@
var querystring = require("querystring");
var fs = require("fs");
var pathlib = require("path");
var formidable = require("formidable");

var basepath = "/admin/api/";

@@ -71,10 +72,10 @@ var methods = {
},

// Get a slide's HTML
slide_html: function(query, conf, req, respond) {
slide_content: function(query, conf, req, respond) {
if (!hasargs(query, respond, [ "slide" ])) return;

var path = pathlib.join(conf.slides, query.slide, "index.html");
var path = pathlib.join(conf.slides, query.slide, "index.md");
fs.readFile(path, "utf-8", (err, text) => {
if (err && err.code === "ENOENT")
return respond(null, { text: "" });
@@ -86,17 +87,49 @@ var methods = {
},

// Update a slide's HTML
slide_html_update: function(query, conf, req, respond) {
slide_content_update: function(query, conf, req, respond) {
if (!hasargs(query, respond, [ "slide", "text" ])) return;

var path = pathlib.join(conf.slides, query.slide, "index.html");
var path = pathlib.join(conf.slides, query.slide, "index.md");
fs.writeFile(path, query.text, err => {
if (err)
respond(err);
else
respond();
respond(err);
});
},

// Rename a file
slide_file_rename: function(query, conf, req, respond) {
if (!hasargs(query, respond, [ "slide", "from", "to" ])) return;

var op = pathlib.join(conf.slides, query.slide, query.from);
var np = pathlib.join(conf.slides, query.slide, query.to);

fs.rename(op, np, err => respond(err));
},

// Delete a file
slide_file_delete: function(query, conf, req, respond) {
if (!hasargs(query, respond, [ "slide", "file" ])) return;

var path = pathlib.join(conf.slides, query.slide, query.file);
fs.unlink(path, err => respond(err));
},

// Upload a file to a slide
slide_file_upload: function(query, conf, req, respond) {
if (!hasargs(query, respond, [ "slide" ])) return;

var form = new formidable.IncomingForm();
form.uploadDir = pathlib.join(conf.slides, query.slide);
form.keepExtensions = true;

form.on("fileBegin", (name, file) => {
file.path = pathlib.join(form.uploadDir, file.name);
});

form.parse(req, (err, fields, files) => {
respond();
});
}
}

exports.canServe = function(parts) {

+ 1
- 0
js/fileserver.js View File

@@ -14,6 +14,7 @@ function file(wpath, fpath) {
file("/polyfills.js");
file("/script.js");
file("/slide.css");
file("/slide.js");
file("/admin/", "/admin/index.html");
file("/admin/style.css");
file("/admin/lib.js");

+ 40
- 14
js/slideshow.js View File

@@ -1,11 +1,16 @@
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 =
"<html>"+
"<head>"+
@@ -16,6 +21,7 @@ var htmlPre =
"<div id='_wrapper'>";

var htmlPost =
"<script src='/slide.js'></script>"+
"</div>"+
"</body>"+
"</html>";
@@ -30,20 +36,13 @@ function sendFile(path, res) {
.pipe(res);
}

function sendIndex(path, res) {
function sendIndex(html, res) {
res.writeHead(200, {
"content-type": "text/html"
});
res.write(htmlPre);

fs.readFile(path, (err, text) => {
if (err)
text.write(err.toString());
else
res.write(text);

res.end(htmlPost);
});
res.write(html);
res.end(htmlPost);
}

// The individual slide
@@ -52,18 +51,45 @@ function Slide(dir) {

self.dir = dir;
self.name = pathlib.parse(dir).name;
self.stat = null;
self.html = "";

self.sendIndex = function(res) {
sendIndex(pathlib.join(self.dir, "index.html"), 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(pathlib.join(self.dir, name), res);
sendFile(self.html, res);
}

self.serveFiles = function(parts, res) {

// Serve index if /{name} is reuested
// Serve index if /{name} is requested
if (parts.pathname.replace(/\//g, "") === self.name) {

// Redirect from /{name} to /{name}/
@@ -88,7 +114,7 @@ function Slide(dir) {

self.indexExists = function() {
try {
fs.accessSync(pathlib.join(dir, "index.html"));
fs.accessSync(pathlib.join(dir, "index.md"));
return true;
} catch (err) {
return false;

+ 21
- 0
package.json View File

@@ -0,0 +1,21 @@
{
"name": "pipic",
"version": "1.0.0",
"description": "Pipic is software for making slideshows. The idea is that you have one server, running a pipic server, and have as many clients as necessary which just display the website hosted by the pipic server.",
"main": "server.js",
"dependencies": {
"formidable": "^1.0.17",
"marked": "^0.3.6"
},
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node server.js"
},
"repository": {
"type": "git",
"url": "http://git.mort.coffee/mort/pipic.git"
},
"author": "Martin Dørum Nygaard <martid0311@gmail.com> (http://mort.coffee)",
"license": "ISC"
}

+ 35
- 12
web/admin/lib.js View File

@@ -1,9 +1,11 @@
function api(method, args, cb) {
var argstr = "?" + Object.keys(args).map(function(key) {
function argstr(args) {
return "?" + Object.keys(args).map(function(key) {
return encodeURIComponent(key)+"="+encodeURIComponent(args[key]);
}).join("&");
}

fetch("/admin/api/"+method+argstr, { method: "POST" })
function api(method, args, cb) {
fetch("/admin/api/"+method+argstr(args), { method: "POST" })
.then(response => response.json())
.then(res => cb(res.err, res.obj));
}
@@ -32,8 +34,10 @@ function elem(tag, props, children) {
return e;
}

e.on = function() {
e.addEventListener.apply(e, arguments);
e.on = function(name, fn) {
e.addEventListener(name, function(evt) {
fn.call(e, evt);
}, false);
return e;
}

@@ -63,19 +67,38 @@ function elem(tag, props, children) {
return e;
}

function uploadEl() {
var uploadElId = 0;
function uploadEl(args, cb) {
var id = uploadElId++;
var frame;
var form;
var fileEl;
elem("div", { className: "uploader" }, [
return form = elem("form", {
className: "uploader",
action: "/admin/api/slide_file_upload"+argstr(args),
method: "post",
enctype: "multipart/form-data",
target: "upload-form-"+id
}, [
frame = elem("iframe", {
style: "display: none",
name: "upload-form-"+id
}).on("load", cb),

fileEl = elem("input", {
type: "file",
style: "display: none"
});
style: "display: none",
name: "file"
}).on("change", () => {
form.submit();
}),

elem("button", {
innerHTML: "Upload"
innerHTML: "Upload",
type: "button"
}).on("click", () => {
fileEl.click();
});
})
]);
}

@@ -92,7 +115,7 @@ function async(n, cb) {

function debounce(fn, ms) {
if (ms === undefined)
ms = 100;
ms = 300;

var timeout;
return function() {

+ 16
- 0
web/admin/style.css View File

@@ -62,6 +62,21 @@

#root.edit #fileList {
text-align: left;
position: relative;
}
#root.edit #fileList .file {
line-height: 30px;
border-bottom: 1px solid #ccc;
}
#root.edit #fileList .file:last-child {
border-bottom: none;
}
#root.edit #fileList .file .name {
margin-left: 10px;
}
#root.edit #fileList .file .controls {
position: absolute;
right: 0px;
}

#root.edit #html,
@@ -73,6 +88,7 @@
}

#root.edit #fileList,
#root.edit #slide > .uploader,
#root.edit #html {
margin-bottom: 24px;
}

+ 42
- 11
web/admin/view.js View File

@@ -8,13 +8,10 @@
}

function setView(name, args) {
args = args || [];
root.className = name;
location.hash = name+"/"+args.join("/");
}

function viewMain(root) {

// Get a list of the slides
api("list_slides", {}, (err, slides) => {
if (err)
@@ -73,10 +70,40 @@

res.files.forEach(f => {
elem("div", { className: "file" }, [
elem("div", {
elem("span", {
className: "name",
innerText: f
})
}),

elem("span", { className: "controls" }, [
elem("button", {
className: "rename",
innerText: "Rename"
}).on("click", () => {
var nwname = prompt("New name?", f);
if (!nwname)
return;

api("slide_file_rename", {
slide: slide,
from: f,
to: nwname
}, populateFileList);
}),

elem("button", {
className: "delete",
innerText: "Delete"
}).on("click", () => {
if (!confirm("Are you sure you want to delete "+f+"?"))
return;

api("slide_file_delete", {
slide: slide,
file: f
}, populateFileList);
})
])
]).appendTo(fileListEl);
});
});
@@ -97,11 +124,15 @@

fileListEl = elem("div", { id: "fileList" }),

uploadEl({
slide: slide
}, populateFileList),

htmlTextEl = elem("textarea", {
id: "html"
}).on("keydown", debounce(() => {
console.log(htmlTextEl.value);
api("slide_html_update", {
api("slide_content_update", {
slide: slide,
text: htmlTextEl.value
}, err => {
@@ -129,7 +160,7 @@
//metaDisabledEl.checked = !!res.disabled;
});

api("slide_html", { slide: slide }, (err, html) => {
api("slide_content", { slide: slide }, (err, html) => {
if (err)
return error(err);

@@ -150,16 +181,16 @@
name = "main";
}

setView(name, args);

args = args || [];
args.splice(0, 0, root);
root.clear();
root.className = name;


views[name].apply(null, args);
}

route();

window.onpopstate = route;

window.onhashchange = route;
})();

+ 3
- 0
web/slide.js View File

@@ -0,0 +1,3 @@
[].forEach.call(document.getElementsByTagName("img"), function(img) {
img.className = img.alt;
});

Loading…
Cancel
Save