UI improvements and bug fixes #8

Merged
hughrun merged 1 commit from publishing-fixes into main 2023-03-12 15:05:13 +11:00
10 changed files with 139 additions and 71 deletions

View file

@ -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')
}) })
}) })

View file

@ -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;
}

View file

@ -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>

View file

@ -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>

View file

@ -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
View 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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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 = ?'