image uploads, changed design

This commit is contained in:
James Shiffer 2020-06-24 10:52:01 -07:00
parent 5f7779f99b
commit 26799bc5bc
No known key found for this signature in database
GPG Key ID: C0DB8774A1B3BA45
12 changed files with 86 additions and 48 deletions

View File

@ -15,13 +15,16 @@
<style>
nav {
font-weight: bold;
padding: 1rem 0 0 0;
position: fixed;
width: 100%;
position: absolute;
padding: 0.5rem;
margin: 0 auto;
width: 85%;
z-index: 100;
background-color: #fff;
top: 0;
box-shadow: 0 -2px 5px #000;
background-color: white;
top: 1rem;
left: 0;
right: 0;
box-shadow: 0 2px 0.5rem black;
}
div.items {
margin: 0;
@ -29,12 +32,10 @@
display: flex;
flex-direction: column;
text-transform: uppercase;
padding: 0 1.5rem 0.25rem;
align-items: start;
align-items: center;
}
div.search {
flex: 1 0 0;
margin: auto 0;
display: flex;
}
div.link a {
@ -50,7 +51,6 @@
@media (min-width: 800px) {
div.items {
flex-direction: row;
align-items: end;
}
div.link a {
font-size: 2rem !important;
@ -62,10 +62,18 @@
}
input.search {
width: 100%;
height: 1.5rem;
font-size: 1rem;
height: 2.75rem;
font-size: 2rem;
margin: 0 auto;
text-align: center;
border: 0.25rem solid black;
}
input.search:focus::placeholder {
opacity: 0;
}
input.search::placeholder {
opacity: 1;
font-weight: bold;
font-family: Roboto, -apple-system, BlinkMacSystemFont, Segoe UI, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}
</style>

View File

@ -55,8 +55,14 @@ export async function del(req, res, next) {
const article = await Article.findOneAndDelete({ slug });
if (article) {
res.writeHead(204);
res.end();
const articles = await Article.find()
.sort({ created_at: 'desc' })
.populate({ path: 'category' })
.populate({ path: 'author', select: 'realname' });
res.writeHead(200, {
'Content-Type': 'application/json'
});
res.end(JSON.stringify({ category: 'all', articles }));
} else {
res.writeHead(404, {
'Content-Type': 'application/json'

View File

@ -142,7 +142,7 @@
<div class="content">
<figure class="article-image">
<img alt={article.title} src={article.image}>
<img alt={article.title} src={`/a/${article.image}`}>
</figure>
<div class="article-meta">
<h1 class="article-title">{article.title}</h1>

View File

@ -24,7 +24,6 @@
<style>
h1 {
margin: 0 auto;
color: whitesmoke;
margin-top: 1rem;
font-size: 2rem;
font-size: 3rem;
@ -55,7 +54,7 @@
{#each articles as {title, slug, image, created_at}}
<a rel="prefetch" href={`/a/${slug}`}>
<figure class="article-image">
<img src={image || '/logo.png'} alt={title}>
<img src={image ? `/a/${image}` : '/logo.png'} alt={title}>
</figure>
<div class="article-meta">
<p class="article-title">{title}</p>

View File

@ -17,8 +17,8 @@
const { session } = stores();
let editor;
let title = '', image = '', category = '';
let editor, form;
let title = '', category = '';
export let categories;
let actions = [
@ -28,7 +28,6 @@
title: 'Save',
result: function save() {
window.localStorage['title'] = title;
window.localStorage['image'] = image;
window.localStorage['category'] = category;
window.localStorage['html'] = editor.getHtml(true);
alert('Successfully saved draft to browser local storage');
@ -56,7 +55,6 @@
onMount(function load() {
title = window.localStorage['title'] || '';
image = window.localStorage['image'] || '';
category = window.localStorage['category'] || '';
editor.setHtml(window.localStorage['html'] || '', false);
});
@ -64,13 +62,14 @@
async function submit()
{
let html = editor.getHtml(true);
let fd = new FormData(form);
fd.append('html', html);
const res = await fetch(`/cms/article`, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
'Accept': 'application/json'
},
body: JSON.stringify({ html, image, title, category })
body: fd
});
const json = await res.json();
if (res.status === 200) {
@ -116,19 +115,19 @@
<div class="content">
<a href="/cms">&lt; Back to Dashboard</a>
<h1>HowFeed Publisher</h1>
<form method="POST" action="/cms/article">
<form enctype="multipart/form-data" method="POST" action="/cms/article" bind:this={form}>
<p>Article Title: <input type="text" name="title" bind:value={title} required placeholder="How to Assassinate the Governor of California"></p>
<p>Article Author: <strong>{$session.user.realname}</strong></p>
<p>Article Category:
{#if categories.length}
<select bind:value={category}>
<select name="category" bind:value={category}>
{#each categories as { name, slug }}
<option value={slug}>{name}</option>
{/each}
</select>
{/if}
<button on:click|preventDefault={addCategory}>+</button></p>
<p>Article Header Image URL: <input type="text" name="image" bind:value={image} required placeholder="http:// ..."></p>
<p>Article Header Image: <input type="file" name="image" accept="image/*" required placeholder="http:// ..."></p>
</form>
<Editor bind:this={editor} {actions} />
<button on:click={submit}>Submit</button>

View File

@ -25,11 +25,15 @@
async function del(article)
{
if (confirm(`Are you sure you want to delete "${article.title}"?`)) {
await fetch(`/a/${article.slug}.json`, {
const res = await fetch(`/a/${article.slug}.json`, {
method: 'DELETE'
});
const res = await fetch(`/c/all.json`);
articles = await res.json();
const json = await res.json();
if (res.status === 200) {
articles = json.articles;
} else {
alert(`Error ${res.status}: ${json.message}`);
}
}
}
</script>

View File

@ -35,11 +35,8 @@
font-size: 3.5rem !important;
}
}
h1.welcome, h2.desc {
color: whitesmoke;
}
h1.welcome {
margin-top: 1rem;
margin: 1rem 0;
font-size: 3.75rem;
font-size: 3rem;
text-transform: uppercase;
@ -58,12 +55,11 @@
<div class="background"></div>
<div class="floaty">
<h1 class="welcome">Welcome</h1>
<h2 class="desc">Find an Article</h2>
<div class="article-list">
{#each articles as {title, slug, image, created_at}}
<a rel="prefetch" href={`/a/${slug}`}>
<figure class="article-image">
<img src={image || '/logo.png'} alt={title}>
<img src={image ? `/a/${image}` : '/logo.png'} alt={title}>
</figure>
<div class="article-meta">
<p class="article-title">{title}</p>

View File

@ -20,7 +20,6 @@
<style>
h1 {
margin: 0 auto;
color: whitesmoke;
margin-top: 1rem;
font-size: 2rem;
font-size: 3rem;
@ -41,7 +40,7 @@
{#each results as {title, slug, image, created_at}}
<a rel="prefetch" href={`/a/${slug}`}>
<figure class="article-image">
<img src={image || '/logo.png'} alt={title}>
<img src={image ? `/a/${image}` : '/logo.png'} alt={title}>
</figure>
<div class="article-meta">
<p class="article-title">{title}</p>

View File

@ -11,6 +11,7 @@ import sessionFileStore from 'session-file-store';
import { RateLimiterMemory } from 'rate-limiter-flexible';
import fileUpload from 'express-fileupload';
import helmet from 'helmet';
import crypto from 'crypto';
import Article from './models/article.js';
import Category from './models/category.js';
import User from './models/user.js';
@ -109,7 +110,9 @@ express()
.use(helmet())
.use(bodyParser.json())
.use(bodyParser.urlencoded({ extended: true }))
.use(fileUpload())
.use(fileUpload({
limits: { fileSize: 16000000 }
}))
.use(session({
secret: SESSION_SECRET,
resave: false,
@ -248,14 +251,34 @@ express()
isAuthor,
async function(req, res, next) {
try {
const { html, title, image, category } = req.body;
const { html, title, category } = req.body;
const { image } = req.files;
if (!title || !image || !html || !category) {
res.writeHead(422, {
'Content-Type': 'application/json'
});
res.end(JSON.stringify({
message: `You must supply an article title, image URL, category, and content.`
message: `You must supply an article title, header image file, category, and content.`
}));
return false;
}
if (!/^image\//.test(image.mimetype)) {
res.writeHead(422, {
'Content-Type': 'application/json'
});
res.end(JSON.stringify({
message: `Invalid MIME type for the header image file.`
}));
return false;
}
if (image.truncated) {
res.writeHead(422, {
'Content-Type': 'application/json'
});
res.end(JSON.stringify({
message: `Received truncated image file. Try again with a smaller file.`
}));
return false;
}
const cat = await Category.findOne({ slug: category });
if (!cat) {
@ -265,8 +288,12 @@ express()
res.end(JSON.stringify({
message: `That category does not exist.`
}));
return false;
}
const article = await new Article({ html, title, image, category: cat, author: req.user._id });
const ext = image.name.match(/(\.[^.]+)$/)[0];
const filename = crypto.randomBytes(20).toString('hex') + ext;
await image.mv('./static/a/' + filename);
const article = await new Article({ html, title, image: filename, category: cat, author: req.user._id });
await article.save();
res.writeHead(200, {
'Content-Type': 'application/json'

View File

@ -3,8 +3,8 @@
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width,initial-scale=1.0'>
<meta name='description' content='HOWFEED.BIZ: Where we break the news.'>
<meta name='keywords' content='news, satire, blog'>
<meta name='description' content='HOWFEED.BIZ: Where we break the news.'>
<meta name='keywords' content='news, satire, blog'>
<meta name='theme-color' content='#508FC3'>
%sapper.base%
<link rel='stylesheet' href='global.css'>

BIN
static/background.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

@ -113,7 +113,7 @@ div.article-meta {
}
div.background {
background: url('/cityscape.jpg') no-repeat center;
background: url('/background.png') no-repeat center;
background-size: cover;
background-attachment: fixed;
position: fixed;
@ -124,14 +124,14 @@ div.background {
}
div.article-list {
box-shadow: 0 0 5px #000;
box-shadow: 0 -5px 0.75rem black;
}
div.article-meta {
font-weight: bold;
}
div.floaty {
padding-top: 8rem;
padding-top: 16rem;
padding-bottom: 4rem;
position: absolute;
z-index: 1;