cleanup, better media ig
This commit is contained in:
parent
7089b6f39e
commit
1076402221
8 changed files with 75 additions and 34 deletions
|
@ -1,16 +1,17 @@
|
|||
.posts {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
grid-template-rows: repeat(auto-fill, 300px);
|
||||
}
|
||||
|
||||
.post {
|
||||
margin: 1em;
|
||||
border: solid var(--accent1) 2px;
|
||||
padding: 1em;
|
||||
overflow-wrap: break-word;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.post > h2 > a {
|
||||
.post h2 > a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
|
@ -19,3 +20,12 @@
|
|||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.preview {
|
||||
padding: 1em;
|
||||
background: #111111bb;
|
||||
}
|
||||
|
||||
.attachment {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ they find throughout the day. Name comes from banal and otiose.
|
|||
- [x] authentication
|
||||
- [x] user config
|
||||
- [x] basic posting
|
||||
- [ ] media posts
|
||||
- [x] media posts
|
||||
- [ ] post config
|
||||
- [ ] user profiles
|
||||
- [ ] tagging
|
||||
|
|
|
@ -1,22 +1,19 @@
|
|||
import busboy from "busboy";
|
||||
|
||||
// TODO: this code is messy! redo it
|
||||
async function parse(req, files) {
|
||||
const bb = busboy({ headers: req.headers, limits: { files: 1 } });
|
||||
return new Promise((res) => {
|
||||
const post = {};
|
||||
let hasFile = false;
|
||||
bb.on("field", (key, val) => post[key] = val);
|
||||
bb.on("file", async (_name, file, info) => {
|
||||
bb.on("file", async (_name, stream, info) => {
|
||||
hasFile = true;
|
||||
res({
|
||||
post,
|
||||
file: {
|
||||
attachHash: await files.insert(file),
|
||||
attachName: info.filename,
|
||||
attachType: info.mimeType,
|
||||
},
|
||||
});
|
||||
const file = {
|
||||
attachHash: await files.insert(stream),
|
||||
attachName: info.filename,
|
||||
attachType: info.mimeType,
|
||||
};
|
||||
res({ post, file });
|
||||
});
|
||||
bb.on("close", () => {
|
||||
if(!hasFile) res({ post });
|
||||
|
|
|
@ -25,9 +25,14 @@ async function init(schema, log) {
|
|||
table.integer("author").unsigned();
|
||||
table.string("title");
|
||||
table.string("body");
|
||||
table.string("attachHash");
|
||||
table.string("attachName");
|
||||
table.string("attachType");
|
||||
table.integer("attachment").unsigned();
|
||||
}).createTable("files", table => {
|
||||
// map of the files
|
||||
table.increments("fileId").primary();
|
||||
table.string("hash");
|
||||
table.string("name");
|
||||
table.string("type");
|
||||
table.binary("preview");
|
||||
}).createTable("log", table => {
|
||||
// log of everything that happens
|
||||
table.increments("auditId");
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// save files to a hash-based filestore
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import crypto from "crypto";
|
||||
|
@ -5,9 +6,9 @@ import crypto from "crypto";
|
|||
class Filestore {
|
||||
where = path.join(process.cwd(), ".data");
|
||||
|
||||
constructor(log, db, options) {
|
||||
this.log = log;
|
||||
this.db = db;
|
||||
constructor(ctx, options) {
|
||||
this.log = ctx.log;
|
||||
this.db = ctx.db;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
|
@ -28,7 +29,7 @@ class Filestore {
|
|||
const h = hash.digest();
|
||||
await tmpFd.close();
|
||||
await fs.rename(tmpName, path.join(this.where, "files", h.toString("hex")));
|
||||
this.log.info("saved file with hash " + h.toString("hex"));
|
||||
this.log.debug("saved file with hash " + h.toString("hex"));
|
||||
res(h);
|
||||
});
|
||||
});
|
||||
|
@ -47,6 +48,6 @@ async function exists(where) {
|
|||
export default async function(ctx, options) {
|
||||
if(!await exists(".data/files")) await fs.mkdir(".data/files");
|
||||
if(!await exists(".data/tmp")) await fs.mkdir(".data/tmp");
|
||||
return new Filestore(ctx.log, ctx.db, options);
|
||||
return new Filestore(ctx, options);
|
||||
}
|
||||
|
||||
|
|
|
@ -6,16 +6,19 @@ const md = markdown({
|
|||
linkify: true,
|
||||
});
|
||||
|
||||
// get a post by id
|
||||
export async function getPost(db, id) {
|
||||
const [post] = await db("posts").select().where("postId", id);
|
||||
return post ?? null;
|
||||
}
|
||||
|
||||
// get a user by id
|
||||
export async function getUser(db, id) {
|
||||
const [user] = await db("users").select().where("userId", id);
|
||||
return user ?? null;
|
||||
}
|
||||
|
||||
// render a post
|
||||
export function render(post, author, trim = false) {
|
||||
const date = new Date(post.createdAt);
|
||||
const body = (trim && post.body.length > 200) ? post.body.slice(0, 200) + "..." : post.body;
|
||||
|
@ -30,13 +33,36 @@ export function render(post, author, trim = false) {
|
|||
};
|
||||
}
|
||||
|
||||
// TODO: add support for other content types
|
||||
export function attachment(post) {
|
||||
switch(post.attachType?.split("/")[0]) {
|
||||
case "image":
|
||||
return `<img src="/media/${post.attachHash.toString("hex")}/${escape(post.attachName)}" alt="main image" class="attachment" />`;
|
||||
default:
|
||||
return null;
|
||||
// render a post's attachment to html
|
||||
function attachment(post) {
|
||||
if(!post.attachHash) return null;
|
||||
const location = `/media/${post.attachHash.toString("hex")}/${escape(post.attachName)}`;
|
||||
return {
|
||||
html: toHtml(),
|
||||
preview: getPreview(),
|
||||
url: location,
|
||||
};
|
||||
|
||||
function toHtml() {
|
||||
switch(post.attachType.split("/")[0]) {
|
||||
case "image":
|
||||
return `<img src="${location}" alt="main image" class="attachment" />`;
|
||||
case "audio":
|
||||
return `<audio controls src="${location}" alt="main audio" class="attachment"></audio>`;
|
||||
case "video":
|
||||
return `<video controls src="${location}" alt="main video" class="attachment"></video>`;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getPreview() {
|
||||
switch(post.attachType.split("/")[0]) {
|
||||
case "image":
|
||||
return location;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -57,6 +83,7 @@ const units = [
|
|||
{ name: "internet explorer page load", size: 1000 },
|
||||
];
|
||||
|
||||
// format dates to a relative time
|
||||
function format(date) {
|
||||
const delta = Date.now() - date;
|
||||
let size = 1;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<div class="post">
|
||||
<h2><a href="/post/{{id}}">{{title}}</a></h2>
|
||||
<div class="fineprint"><i><time datetime="{{time}}">{{timefmt}}</time> - by {{author}}</i></div>
|
||||
<p>{{{body}}}</p>
|
||||
{{#attachment}}{{{this}}}{{/attachment}}
|
||||
<div class="post" {{#attachment}}{{#preview}}style="background-image: url({{this}})"{{/preview}}{{/attachment}}>
|
||||
<div class="preview">
|
||||
<h2><a href="/post/{{id}}">{{title}}</a></h2>
|
||||
<div class="fineprint"><i><time datetime="{{time}}">{{timefmt}}</time> - by {{author}}</i></div>
|
||||
<p>{{{body}}}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<main class="center">
|
||||
<h1>{{title}}</h1>
|
||||
<div class="fineprint"><i><time datetime="{{time}}">{{timefmt}}</time> - by {{author}}</i></div>
|
||||
{{#attachment}}{{{this}}}{{/attachment}}
|
||||
{{#attachment}}<a href="{{url}}">{{{html}}}</a>{{/attachment}}
|
||||
<p>{{{body}}}</p>
|
||||
</main>
|
||||
|
|
Loading…
Reference in a new issue