UI improvements and bug fixes #8
43
server.js
43
server.js
|
@ -48,18 +48,7 @@ app.set('view engine', 'spy');
|
||||||
// GET
|
// GET
|
||||||
|
|
||||||
app.get('/', requireLoggedIn, (req, res) => {
|
app.get('/', requireLoggedIn, (req, res) => {
|
||||||
let data = {
|
res.render('index.spy', {title: 'Soyuz home', writeNew: getSavedFile(req.session.user.username)? 'Return to draft' : 'New'})
|
||||||
title: 'Home',
|
|
||||||
disabled: '',
|
|
||||||
message: getSavedFile(req.session.user.username)
|
|
||||||
}
|
|
||||||
let today = getNow().toISOString().slice(0,10)
|
|
||||||
let latestPost = req.session.user.latest_post
|
|
||||||
if (today === latestPost) {
|
|
||||||
data.disabled = 'disabled'
|
|
||||||
data.message = `Relax, ${req.session.user.username}, you have already posted today.`
|
|
||||||
}
|
|
||||||
res.render('index.spy', data)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
app.get('/login', (req, res) => {
|
app.get('/login', (req, res) => {
|
||||||
|
@ -70,9 +59,33 @@ app.get('/login', (req, res) => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
app.get('/new', requireLoggedIn, (req, res) => {
|
||||||
|
let message = getSavedFile(req.session.user.username) || "# Title of my note"
|
||||||
|
let data = {
|
||||||
|
title: 'New post',
|
||||||
|
disabled: '',
|
||||||
|
message: message
|
||||||
|
}
|
||||||
|
let today = getNow().toISOString().slice(0,10)
|
||||||
|
// check whether user has already posted today
|
||||||
|
return getLatestPost(req.session.user.directory, true, (dateString)=> {
|
||||||
|
if (today === dateString) {
|
||||||
|
data.disabled = 'disabled'
|
||||||
|
data.message = `Relax, ${req.session.user.username}, you have already posted today.`
|
||||||
|
}
|
||||||
|
res.render('new.spy', data)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
app.get('/edit', requireLoggedIn, (req, res) => {
|
app.get('/edit', requireLoggedIn, (req, res) => {
|
||||||
getLatestPost( req.session.user.directory, (data, path) => {
|
return getLatestPost(req.session.user.directory, true, (dateString) => {
|
||||||
res.render('edit.spy', {data: data, path: path, title: 'Edit'})
|
if (dateString) {
|
||||||
|
return getLatestPost( req.session.user.directory, false, (message, path) => {
|
||||||
|
res.render('edit.spy', {message: message, path: path, title: 'Edit'})
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
res.redirect('/new')
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -118,7 +131,7 @@ app.post('/publish', requireLoggedIn, (req, res) => {
|
||||||
|
|
||||||
app.post('/save', requireLoggedIn, (req, res) => {
|
app.post('/save', requireLoggedIn, (req, res) => {
|
||||||
saveFile(req.session.user.username, req.body.textarea, () => {
|
saveFile(req.session.user.username, req.body.textarea, () => {
|
||||||
res.redirect('/')
|
res.redirect('/new')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -77,11 +77,34 @@ main {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.home-menu {
|
||||||
|
margin: auto;
|
||||||
|
padding: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-button {
|
||||||
|
background: none;
|
||||||
|
color: inherit;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
font: 2em sans-serif;
|
||||||
|
cursor: pointer;
|
||||||
|
outline: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-block;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
.disabled {
|
.disabled {
|
||||||
background-color: #999;
|
background-color: #999;
|
||||||
color: #000;
|
color: #000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
section.disabled {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.password-reset {
|
.password-reset {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
|
@ -100,3 +123,9 @@ main {
|
||||||
.menu-help {
|
.menu-help {
|
||||||
font-weight: bolder;
|
font-weight: bolder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
margin: auto;
|
||||||
|
color: red;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
<< partials/head >>
|
<< partials/head >>
|
||||||
<body>
|
<body>
|
||||||
<< partials/header >>
|
<header>
|
||||||
|
<a class="action-button" href="/">Home</a> |
|
||||||
|
<a class="action-button" href="/settings">Settings</a> |
|
||||||
|
<a class="action-button" href="/help">Help</a>
|
||||||
|
</header>
|
||||||
|
|
||||||
<section class="textarea">
|
<section class="textarea">
|
||||||
<form method="post">
|
<form method="post">
|
||||||
<textarea name="textarea" autofocus>{{ data }}</textarea>
|
<textarea name="textarea" autofocus class="{{ disabled }}" {{ disabled }}>{{ message }}</textarea>
|
||||||
<input type="text" name="path" value="{{ path }}" hidden>
|
<input type="text" name="path" value="{{ path }}" hidden>
|
||||||
<section class="post-buttons">
|
<section class="post-buttons">
|
||||||
<input class="action-button" type="submit" name="save" value="Save" formaction="/save"> |
|
|
||||||
<input class="action-button" type="submit" name="publish" value="Update" formaction="/update">
|
<input class="action-button" type="submit" name="publish" value="Update" formaction="/update">
|
||||||
</section>
|
</section>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
<< partials/head >>
|
<< partials/head >>
|
||||||
<body>
|
<body>
|
||||||
<< partials/header >>
|
<header>
|
||||||
|
<a class="action-button" href="/">Home</a> |
|
||||||
|
<a class="action-button" href="/settings">Settings</a>
|
||||||
|
</header>
|
||||||
<main id="settings">
|
<main id="settings">
|
||||||
<h1>Help</h1>
|
<h1>Help</h1>
|
||||||
<p>Soyuz is a web app for writing Gemini posts.</p>
|
<p>Soyuz is a web app for writing Gemini posts.</p>
|
||||||
|
|
|
@ -1,17 +1,10 @@
|
||||||
<< partials/head >>
|
<< partials/head >>
|
||||||
<body class="{{ disabled }}">
|
<body class="{{ disabled }}">
|
||||||
<< partials/header >>
|
<menu class="home-menu">
|
||||||
|
<a class="home-button" href="/new">{{ writeNew }}</a><br/>
|
||||||
<section class="textarea">
|
<a class="home-button" href="/edit">Edit previous</a><br/>
|
||||||
<form method="post">
|
<a class="home-button" href="/settings">Settings</a><br/>
|
||||||
<textarea name="textarea" autofocus class="{{ disabled }}" {{ disabled }}>{{ message }}</textarea>
|
<a class="home-button" href="/help">Help</a>
|
||||||
<section class="post-buttons">
|
</menu>
|
||||||
<input class="action-button" type="submit" name="save" value="Save" formaction="/save"> |
|
|
||||||
<input class="action-button" type="submit" name="publish" value="Publish" formaction="/publish">
|
|
||||||
</section>
|
|
||||||
</form>
|
|
||||||
</section>
|
|
||||||
<footer>
|
|
||||||
</footer>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
21
templates/new.spy
Normal file
21
templates/new.spy
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<< partials/head >>
|
||||||
|
<body class="{{ disabled }}">
|
||||||
|
<header>
|
||||||
|
<a class="action-button" href="/">Home</a> |
|
||||||
|
<a class="action-button" href="/settings">Settings</a> |
|
||||||
|
<a class="action-button" href="/help">Help</a>
|
||||||
|
</header>
|
||||||
|
<section class="textarea">
|
||||||
|
<section class="alert">{{ alert }}</section>
|
||||||
|
<form method="post">
|
||||||
|
<textarea name="textarea" autofocus class="{{ disabled }}" {{ disabled }}>{{ message }}</textarea>
|
||||||
|
<section class="post-buttons {{ disabled }}">
|
||||||
|
<input class="action-button" type="submit" name="save" value="Save draft" formaction="/save"> |
|
||||||
|
<input class="action-button" type="submit" name="publish" value="Publish" formaction="/publish">
|
||||||
|
</section>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
<footer>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1,6 +0,0 @@
|
||||||
<header>
|
|
||||||
<a class="action-button" href="/">New</a> |
|
|
||||||
<a class="action-button" href="/edit">Edit</a> |
|
|
||||||
<a class="action-button" href="/settings">Settings</a> |
|
|
||||||
<a class="action-button" href="/help">Help</a>
|
|
||||||
</header>
|
|
|
@ -1,7 +1,12 @@
|
||||||
<< partials/head >>
|
<< partials/head >>
|
||||||
<body>
|
<body>
|
||||||
<section>
|
<section>
|
||||||
<< partials/header >>
|
<header>
|
||||||
|
<a class="action-button" href="/">Home</a> |
|
||||||
|
<a class="action-button" href="/edit">Edit</a> |
|
||||||
|
<a class="action-button" href="/settings">Settings</a> |
|
||||||
|
<a class="action-button" href="/help">Help</a>
|
||||||
|
</header>
|
||||||
<main>
|
<main>
|
||||||
<h2> {{ title }}</h2>
|
<h2> {{ title }}</h2>
|
||||||
<p>Hooray 🎉</p>
|
<p>Hooray 🎉</p>
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
<< partials/head >>
|
<< partials/head >>
|
||||||
<body>
|
<body>
|
||||||
<< partials/header >>
|
<header>
|
||||||
|
<a class="action-button" href="/">Home</a> |
|
||||||
|
<a class="action-button" href="/help">Help</a>
|
||||||
|
</header>
|
||||||
<main id="settings">
|
<main id="settings">
|
||||||
<form method="post">
|
<form method="post">
|
||||||
<section>
|
<section>
|
||||||
|
|
56
utilities.js
56
utilities.js
|
@ -35,7 +35,7 @@ const addUser = function(username, directory, callback){
|
||||||
let stmt = db.prepare(
|
let stmt = db.prepare(
|
||||||
'INSERT INTO users (username, directory, password, salt, saved_post) VALUES (?, ?, ?, ?, ?)'
|
'INSERT INTO users (username, directory, password, salt, saved_post) VALUES (?, ?, ?, ?, ?)'
|
||||||
);
|
);
|
||||||
stmt.run(username, directory, hash, salt, '# Title of my note');
|
stmt.run(username, directory, hash, salt, null);
|
||||||
return callback(password)
|
return callback(password)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -63,17 +63,6 @@ const resetPassword = function(username, pass, callback) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// update latest post in db
|
|
||||||
const updateLatestPostDate = function(username, callback) {
|
|
||||||
|
|
||||||
let dateString = getNow().toISOString().slice(0,10)
|
|
||||||
let stmt = db.prepare(
|
|
||||||
'UPDATE users SET latest_post = ? WHERE username = ?'
|
|
||||||
);
|
|
||||||
stmt.run(dateString, username);
|
|
||||||
callback(dateString)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AUTHORISATION MIDDLEWARE
|
// AUTHORISATION MIDDLEWARE
|
||||||
const verifyUser = function (req, res, next) {
|
const verifyUser = function (req, res, next) {
|
||||||
let username = req.body.username
|
let username = req.body.username
|
||||||
|
@ -96,9 +85,8 @@ const verifyUser = function (req, res, next) {
|
||||||
}
|
}
|
||||||
req.session.user = {
|
req.session.user = {
|
||||||
username: user.username,
|
username: user.username,
|
||||||
directory: user.directory,
|
directory: user.directory
|
||||||
latest_post: user.latest_post,
|
}
|
||||||
};
|
|
||||||
next()
|
next()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -147,12 +135,9 @@ const publishNewPost = function(req, cb) {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
// clear any saved post now that it is published
|
// clear any saved post now that it is published
|
||||||
saveFile(req.session.user.username, '# Title of my note', () => {
|
saveFile(req.session.user.username, null, () => {
|
||||||
return updateLatestPostDate(req.session.user.username, datestring => {
|
|
||||||
req.session.user.latest_post = datestring
|
|
||||||
return cb()
|
return cb()
|
||||||
})
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateIndexListing() {
|
function updateIndexListing() {
|
||||||
|
@ -176,7 +161,6 @@ const publishNewPost = function(req, cb) {
|
||||||
newlines.push(line)
|
newlines.push(line)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lines[0] = '## Latest notes'
|
|
||||||
newlines.unshift(`## Latest notes\n\n=> /${year}/${dateString}.gmi ${dateString} (${title})`)
|
newlines.unshift(`## Latest notes\n\n=> /${year}/${dateString}.gmi ${dateString} (${title})`)
|
||||||
updated = newlines.join('\n')
|
updated = newlines.join('\n')
|
||||||
writeFile(indexFile, updated, (err) => {
|
writeFile(indexFile, updated, (err) => {
|
||||||
|
@ -213,22 +197,38 @@ const publishNewPost = function(req, cb) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
let getLatestPost = function(directory, callback) {
|
let getLatestPost = function(directory, dateOnly, callback) {
|
||||||
// we check the index file because
|
// we check the index file because
|
||||||
// a new post could have come from
|
// a new post could have come from
|
||||||
// somewhere other than the app
|
// somewhere other than the app
|
||||||
// e.g. from a CLI on a laptop etc
|
// e.g. from a CLI on a laptop etc
|
||||||
let indexFile = `${GEMINI_PATH}/${directory}/index.gmi`
|
let indexFile = `${GEMINI_PATH}/${directory}/index.gmi`
|
||||||
readFile(indexFile, {encoding: 'utf8'}, (err, data) => {
|
readFile(indexFile, {encoding: 'utf8'}, (err, data) => {
|
||||||
if (err) throw err;
|
if (err) {
|
||||||
|
if (err.code == 'ENOENT') {
|
||||||
|
return callback(null)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.length == 0 || data == null) {
|
||||||
|
return callback(null)
|
||||||
|
}
|
||||||
|
|
||||||
let links = data.split('## Latest notes')
|
let links = data.split('## Latest notes')
|
||||||
let parts = links[1].split('\n')[2].split(' ')
|
let parts = links[1].split('\n')[2].split(' ')
|
||||||
let filePath = `${GEMINI_PATH}/${directory}/${parts[1]}`
|
let filePath = `${GEMINI_PATH}/${directory}/${parts[1]}`
|
||||||
|
if (dateOnly) {
|
||||||
|
let dateString = filePath.slice(-14,-4)
|
||||||
|
return callback(dateString)
|
||||||
|
} else {
|
||||||
readFile(filePath, {encoding: 'utf8'}, (err, file) => {
|
readFile(filePath, {encoding: 'utf8'}, (err, file) => {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
return callback(file, filePath)
|
return callback(file, filePath)
|
||||||
})
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -237,9 +237,12 @@ let updatePost = function(req, callback) {
|
||||||
let path = req.body.path
|
let path = req.body.path
|
||||||
let title = contents.split('\n')[0].split('# ')[1].trim()
|
let title = contents.split('\n')[0].split('# ')[1].trim()
|
||||||
let year = getNow().toISOString().slice(0,4)
|
let year = getNow().toISOString().slice(0,4)
|
||||||
let dateString = getNow().toISOString().slice(0,10)
|
|
||||||
let indexFile = `${GEMINI_PATH}/${req.session.user.directory}/index.gmi`
|
let indexFile = `${GEMINI_PATH}/${req.session.user.directory}/index.gmi`
|
||||||
let yearIndex = `${GEMINI_PATH}/${req.session.user.directory}/${year}/index.gmi`
|
let yearIndex = `${GEMINI_PATH}/${req.session.user.directory}/${year}/index.gmi`
|
||||||
|
let relative_path = path.slice(-20)
|
||||||
|
let post_date = relative_path.slice(6,16)
|
||||||
|
let prefix = `=> ${relative_path} ${post_date}`
|
||||||
|
let archivePrefix = `=> ${relative_path.slice(6)} ${post_date}`
|
||||||
let updated = ''
|
let updated = ''
|
||||||
|
|
||||||
// we update the index and archive listings in case the title has changed
|
// we update the index and archive listings in case the title has changed
|
||||||
|
@ -250,7 +253,7 @@ let updatePost = function(req, callback) {
|
||||||
let links = data.split('## Latest notes')
|
let links = data.split('## Latest notes')
|
||||||
let lines = links[1].split('\n')
|
let lines = links[1].split('\n')
|
||||||
lines[0] = '## Latest notes'
|
lines[0] = '## Latest notes'
|
||||||
lines[2] = `=> /${year}/${dateString}.gmi ${dateString} (${title})`
|
lines[2] = `${prefix} (${title})`
|
||||||
updated = links[0] + lines.join('\n')
|
updated = links[0] + lines.join('\n')
|
||||||
// update index on homepage
|
// update index on homepage
|
||||||
writeFile(indexFile, updated, (err) => {
|
writeFile(indexFile, updated, (err) => {
|
||||||
|
@ -260,7 +263,7 @@ let updatePost = function(req, callback) {
|
||||||
throw err
|
throw err
|
||||||
} else {
|
} else {
|
||||||
let lines = data.split('\n')
|
let lines = data.split('\n')
|
||||||
lines[2] = `=> ${dateString}.gmi ${dateString} (${title})`
|
lines[2] = `${archivePrefix} (${title})`
|
||||||
updated = lines.join('\n')
|
updated = lines.join('\n')
|
||||||
// update archive page
|
// update archive page
|
||||||
writeFile(yearIndex, updated, (err) => {
|
writeFile(yearIndex, updated, (err) => {
|
||||||
|
@ -280,6 +283,7 @@ let updatePost = function(req, callback) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: this refers to saving the file on the database, not publishing the file to the server
|
||||||
let saveFile = function(user, text, callback) {
|
let saveFile = function(user, text, callback) {
|
||||||
let stmt = db.prepare(
|
let stmt = db.prepare(
|
||||||
'UPDATE users SET saved_post = ? WHERE username = ?'
|
'UPDATE users SET saved_post = ? WHERE username = ?'
|
||||||
|
|
Loading…
Reference in a new issue