Browse Source

now uses markdown, and admin interface mostly done

master
mortie 7 years ago
parent
commit
fbe22a3d9f

+ 1
- 0
.gitignore View File

conf.json conf.json
slides slides
node_modules

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

<h1> Viktig informasjon om greier </h1>

<p> Et eller annet </p>

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

# Viktig informasjon om greier

Et eller annet

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

<h1> This is the second slide </h1>

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

# This is the second slide

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

<h1> This is the third slide </h1>

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

# This is the third slide

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

<img class="fullscreen" src="image.svg">

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

![fullscreen](image.svg)

+ 41
- 8
js/admin.js View File

var querystring = require("querystring"); var querystring = require("querystring");
var fs = require("fs"); var fs = require("fs");
var pathlib = require("path"); var pathlib = require("path");
var formidable = require("formidable");


var basepath = "/admin/api/"; var basepath = "/admin/api/";


}, },


// Get a slide's HTML // 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; 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) => { fs.readFile(path, "utf-8", (err, text) => {
if (err && err.code === "ENOENT") if (err && err.code === "ENOENT")
return respond(null, { text: "" }); return respond(null, { text: "" });
}, },


// Update a slide's HTML // 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; 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 => { 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) { exports.canServe = function(parts) {

+ 1
- 0
js/fileserver.js View File

file("/polyfills.js"); file("/polyfills.js");
file("/script.js"); file("/script.js");
file("/slide.css"); file("/slide.css");
file("/slide.js");
file("/admin/", "/admin/index.html"); file("/admin/", "/admin/index.html");
file("/admin/style.css"); file("/admin/style.css");
file("/admin/lib.js"); file("/admin/lib.js");

+ 40
- 14
js/slideshow.js View File

var fs = require("fs"); var fs = require("fs");
var pathlib = require("path"); var pathlib = require("path");
var marked = require("marked");


var mimetype = require("./mimetype"); var mimetype = require("./mimetype");
var error = require("./error"); var error = require("./error");


module.exports = Slideshow; module.exports = Slideshow;


marked.setOptions({
sanitize: true
});

var htmlPre = var htmlPre =
"<html>"+ "<html>"+
"<head>"+ "<head>"+
"<div id='_wrapper'>"; "<div id='_wrapper'>";


var htmlPost = var htmlPost =
"<script src='/slide.js'></script>"+
"</div>"+ "</div>"+
"</body>"+ "</body>"+
"</html>"; "</html>";
.pipe(res); .pipe(res);
} }


function sendIndex(path, res) {
function sendIndex(html, res) {
res.writeHead(200, { res.writeHead(200, {
"content-type": "text/html" "content-type": "text/html"
}); });
res.write(htmlPre); 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 // The individual slide


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


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


self.serveFiles = function(parts, 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) { if (parts.pathname.replace(/\//g, "") === self.name) {


// Redirect from /{name} to /{name}/ // Redirect from /{name} to /{name}/


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

+ 21
- 0
package.json View File

{
"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

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]); return encodeURIComponent(key)+"="+encodeURIComponent(args[key]);
}).join("&"); }).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(response => response.json())
.then(res => cb(res.err, res.obj)); .then(res => cb(res.err, res.obj));
} }
return e; 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; return e;
} }


return e; return e;
} }


function uploadEl() {
var uploadElId = 0;
function uploadEl(args, cb) {
var id = uploadElId++;
var frame;
var form;
var fileEl; 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", { fileEl = elem("input", {
type: "file", type: "file",
style: "display: none"
});
style: "display: none",
name: "file"
}).on("change", () => {
form.submit();
}),


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




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


var timeout; var timeout;
return function() { return function() {

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



#root.edit #fileList { #root.edit #fileList {
text-align: left; 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, #root.edit #html,
} }


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

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

} }


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


function viewMain(root) { function viewMain(root) {

// Get a list of the slides // Get a list of the slides
api("list_slides", {}, (err, slides) => { api("list_slides", {}, (err, slides) => {
if (err) if (err)


res.files.forEach(f => { res.files.forEach(f => {
elem("div", { className: "file" }, [ elem("div", { className: "file" }, [
elem("div", {
elem("span", {
className: "name", className: "name",
innerText: f 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); ]).appendTo(fileListEl);
}); });
}); });


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


uploadEl({
slide: slide
}, populateFileList),

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


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


name = "main"; name = "main";
} }


setView(name, args);

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


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


route(); route();


window.onpopstate = route;

window.onhashchange = route;
})(); })();

+ 3
- 0
web/slide.js View File

[].forEach.call(document.getElementsByTagName("img"), function(img) {
img.className = img.alt;
});

Loading…
Cancel
Save