First commit

This commit is contained in:
Yoander Valdés Rodríguez 2022-05-26 17:09:38 -05:00
commit 76f7d9d0f6
17 changed files with 2940 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
node_modules/
.env
data.db

50
app.js Normal file
View file

@ -0,0 +1,50 @@
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var indexRouter = require('./routes/index');
var postRouter = require('./routes/post');
var nunjucks = require('nunjucks');
require('dotenv').config();
var app = express();
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
nunjucks.configure('views', {
autoescape: true,
express: app
});
app.set('view engine', 'html');
//var env = new nunjucks.Environment();
app.use('/', indexRouter);
app.use('/post', postRouter);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error.njk', {'res': res, 'err': err});
});
module.exports = app;

90
bin/www Executable file
View file

@ -0,0 +1,90 @@
#!/usr/bin/env node
/**
* Module dependencies.
*/
var app = require('../app');
var debug = require('debug')('loro-nota:server');
var http = require('http');
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
/**
* Create HTTP server.
*/
var server = http.createServer(app);
/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
/**
* Normalize a port into a number, string, or false.
*/
function normalizePort(val) {
var port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
return val;
}
if (port >= 0) {
// port number
return port;
}
return false;
}
/**
* Event listener for HTTP server "error" event.
*/
function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}
var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}
/**
* Event listener for HTTP server "listening" event.
*/
function onListening() {
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}

11
connection.js Normal file
View file

@ -0,0 +1,11 @@
const sqlite3 = require("sqlite3")
const db = new sqlite3.Database('./data.db', (err) => {
if (err) {
console.log('Could not connect to database', err)
} else {
console.log('Connected to database')
}
});
module.exports = db

22
data.sql Normal file
View file

@ -0,0 +1,22 @@
--
-- File generated with SQLiteStudio v3.3.3 on Thu May 26 17:08:57 2022
--
-- Text encoding used: UTF-8
--
PRAGMA foreign_keys = off;
BEGIN TRANSACTION;
-- Table: posts
DROP TABLE IF EXISTS posts;
CREATE TABLE posts (id INTEGER PRIMARY KEY ASC ON CONFLICT FAIL AUTOINCREMENT NOT NULL ON CONFLICT FAIL, title VARCHAR (150) NOT NULL UNIQUE, html TEXT NOT NULL DEFAULT (''), markdown TEXT NOT NULL DEFAULT (''), tags VARCHAR (500) NOT NULL DEFAULT (''), language CHAR (2) NOT NULL DEFAULT ES, slug VARCHAR (350) NOT NULL DEFAULT (''), status VARCHAR (15) DEFAULT DRAFT CHECK (status IN ('DRAFT', 'PUBLISHED', 'SYNCHRONIZE')) NOT NULL, translation_id INT REFERENCES translations (id) ON DELETE RESTRICT ON UPDATE CASCADE NOT NULL DEFAULT (0), created_at DATETIME DEFAULT (CURRENT_TIMESTAMP) NOT NULL, updated_at DATETIME DEFAULT (CURRENT_TIMESTAMP));
-- Table: translations
DROP TABLE IF EXISTS translations;
CREATE TABLE translations (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, count INT DEFAULT (0) NOT NULL);
-- Trigger: before_upd
DROP TRIGGER IF EXISTS before_upd;
CREATE TRIGGER before_upd BEFORE UPDATE OF id, title, html, markdown, tags, language, slug, translation_id ON posts BEGIN UPDATE posts SET updated_at = DATETIME('NOW') WHERE id = OLD.id; END;
COMMIT TRANSACTION;
PRAGMA foreign_keys = on;

2182
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

23
package.json Normal file
View file

@ -0,0 +1,23 @@
{
"name": "loro-nota",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./bin/www"
},
"dependencies": {
"cookie-parser": "~1.4.4",
"debug": "~2.6.9",
"dotenv": "^16.0.1",
"express": "~4.16.1",
"http-errors": "~1.6.3",
"morgan": "~1.9.1",
"nunjucks": "^3.2.3",
"sqlite3": "^5.0.8"
},
"devDependencies": {
"@types/node": "^17.0.33",
"nodemon": "^2.0.16"
},
"optionalDependencies": {}
}

87
public/icons/icons.svg Normal file
View file

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<symbol xmlns="http://www.w3.org/2000/svg" id="clipboard" class="bi-clipboard" viewBox="0 0 16 16">
<path d="M4 1.5H3a2 2 0 0 0-2 2V14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3.5a2 2 0 0 0-2-2h-1v1h1a1 1 0 0 1 1 1V14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3.5a1 1 0 0 1 1-1h1v-1z"/>
<path d="M9.5 1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5h3zm-3-1A1.5 1.5 0 0 0 5 1.5v1A1.5 1.5 0 0 0 6.5 4h3A1.5 1.5 0 0 0 11 2.5v-1A1.5 1.5 0 0 0 9.5 0h-3z"/>
</symbol>
<symbol xmlns="http://www.w3.org/2000/svg" id="clipboard-check" class="bi-clipboard-check" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M10.854 7.146a.5.5 0 0 1 0 .708l-3 3a.5.5 0 0 1-.708 0l-1.5-1.5a.5.5 0 1 1 .708-.708L7.5 9.793l2.646-2.647a.5.5 0 0 1 .708 0z"/>
<path d="M4 1.5H3a2 2 0 0 0-2 2V14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3.5a2 2 0 0 0-2-2h-1v1h1a1 1 0 0 1 1 1V14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3.5a1 1 0 0 1 1-1h1v-1z"/>
<path d="M9.5 1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5h3zm-3-1A1.5 1.5 0 0 0 5 1.5v1A1.5 1.5 0 0 0 6.5 4h3A1.5 1.5 0 0 0 11 2.5v-1A1.5 1.5 0 0 0 9.5 0h-3z"/>
</symbol>
<symbol xmlns="http://www.w3.org/2000/svg" id="clipboard-x" class="bi-clipboard-x" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M6.146 7.146a.5.5 0 0 1 .708 0L8 8.293l1.146-1.147a.5.5 0 1 1 .708.708L8.707 9l1.147 1.146a.5.5 0 0 1-.708.708L8 9.707l-1.146 1.147a.5.5 0 0 1-.708-.708L7.293 9 6.146 7.854a.5.5 0 0 1 0-.708z"/>
<path d="M4 1.5H3a2 2 0 0 0-2 2V14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3.5a2 2 0 0 0-2-2h-1v1h1a1 1 0 0 1 1 1V14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3.5a1 1 0 0 1 1-1h1v-1z"/>
<path d="M9.5 1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5h3zm-3-1A1.5 1.5 0 0 0 5 1.5v1A1.5 1.5 0 0 0 6.5 4h3A1.5 1.5 0 0 0 11 2.5v-1A1.5 1.5 0 0 0 9.5 0h-3z"/>
</symbol>
<symbol xmlns="http://www.w3.org/2000/svg" id="pencil-square" class="bi-pencil-square" viewBox="0 0 16 16">
<path d="M15.502 1.94a.5.5 0 0 1 0 .706L14.459 3.69l-2-2L13.502.646a.5.5 0 0 1 .707 0l1.293 1.293zm-1.75 2.456l-2-2L4.939 9.21a.5.5 0 0 0-.121.196l-.805 2.414a.25.25 0 0 0 .316.316l2.414-.805a.5.5 0 0 0 .196-.12l6.813-6.814z"/>
<path fill-rule="evenodd" d="M1 13.5A1.5 1.5 0 0 0 2.5 15h11a1.5 1.5 0 0 0 1.5-1.5v-6a.5.5 0 0 0-1 0v6a.5.5 0 0 1-.5.5h-11a.5.5 0 0 1-.5-.5v-11a.5.5 0 0 1 .5-.5H9a.5.5 0 0 0 0-1H2.5A1.5 1.5 0 0 0 1 2.5v11z"/>
</symbol>
<symbol xmlns="http://www.w3.org/2000/svg" id="trash" class="bi-trash" viewBox="0 0 16 16">
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/>
<path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/>
</symbol>
<symbol xmlns="http://www.w3.org/2000/svg" id="save" class="bi-save" viewBox="0 0 16 16">
<path d="M2 1a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1H9.5a1 1 0 0 0-1 1v7.293l2.646-2.647a.5.5 0 0 1 .708.708l-3.5 3.5a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L7.5 9.293V2a2 2 0 0 1 2-2H14a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h2.5a.5.5 0 0 1 0 1H2z"/>
</symbol>
<symbol xmlns="http://www.w3.org/2000/svg" id="tag" class="bi-tag" viewBox="0 0 16 16">
<path d="M6 4.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm-1 0a.5.5 0 1 0-1 0 .5.5 0 0 0 1 0z"/>
<path d="M2 1h4.586a1 1 0 0 1 .707.293l7 7a1 1 0 0 1 0 1.414l-4.586 4.586a1 1 0 0 1-1.414 0l-7-7A1 1 0 0 1 1 6.586V2a1 1 0 0 1 1-1zm0 5.586l7 7L13.586 9l-7-7H2v4.586z"/>
</symbol>
<symbol xmlns="http://www.w3.org/2000/svg" id="file-code" class="bi-file-code" viewBox="0 0 16 16">
<path d="M6.646 5.646a.5.5 0 1 1 .708.708L5.707 8l1.647 1.646a.5.5 0 0 1-.708.708l-2-2a.5.5 0 0 1 0-.708l2-2zm2.708 0a.5.5 0 1 0-.708.708L10.293 8 8.646 9.646a.5.5 0 0 0 .708.708l2-2a.5.5 0 0 0 0-.708l-2-2z"/>
<path d="M2 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2zm10-1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1z"/>
</symbol>
<symbol xmlns="http://www.w3.org/2000/svg" id="pencil-square" class="bi-pencil-square" viewBox="0 0 16 16">
<path d="M15.502 1.94a.5.5 0 0 1 0 .706L14.459 3.69l-2-2L13.502.646a.5.5 0 0 1 .707 0l1.293 1.293zm-1.75 2.456l-2-2L4.939 9.21a.5.5 0 0 0-.121.196l-.805 2.414a.25.25 0 0 0 .316.316l2.414-.805a.5.5 0 0 0 .196-.12l6.813-6.814z"/>
<path fill-rule="evenodd" d="M1 13.5A1.5 1.5 0 0 0 2.5 15h11a1.5 1.5 0 0 0 1.5-1.5v-6a.5.5 0 0 0-1 0v6a.5.5 0 0 1-.5.5h-11a.5.5 0 0 1-.5-.5v-11a.5.5 0 0 1 .5-.5H9a.5.5 0 0 0 0-1H2.5A1.5 1.5 0 0 0 1 2.5v11z"/>
</symbol>
<symbol xmlns="http://www.w3.org/2000/svg" id="house" class="bi-house" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M2 13.5V7h1v6.5a.5.5 0 0 0 .5.5h9a.5.5 0 0 0 .5-.5V7h1v6.5a1.5 1.5 0 0 1-1.5 1.5h-9A1.5 1.5 0 0 1 2 13.5zm11-11V6l-2-2V2.5a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5z"/>
<path fill-rule="evenodd" d="M7.293 1.5a1 1 0 0 1 1.414 0l6.647 6.646a.5.5 0 0 1-.708.708L8 2.207 1.354 8.854a.5.5 0 1 1-.708-.708L7.293 1.5z"/>
</symbol>
<symbol xmlns="http://www.w3.org/2000/svg" id="eye" class="bi-eye" viewBox="0 0 16 16">
<path d="M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8zM1.173 8a13.133 13.133 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13.133 13.133 0 0 1 14.828 8c-.058.087-.122.183-.195.288-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5c-2.12 0-3.879-1.168-5.168-2.457A13.134 13.134 0 0 1 1.172 8z"/>
<path d="M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5zM4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0z"/>
</symbol>
<symbol xmlns="http://www.w3.org/2000/svg" id="file-plus" class="bi-file-plus" viewBox="0 0 16 16">
<path d="M8.5 6a.5.5 0 0 0-1 0v1.5H6a.5.5 0 0 0 0 1h1.5V10a.5.5 0 0 0 1 0V8.5H10a.5.5 0 0 0 0-1H8.5V6z"/>
<path d="M2 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2zm10-1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1z"/>
</symbol>
<symbol xmlns="http://www.w3.org/2000/svg" id="card-text" class="bi-card-text" viewBox="0 0 16 16">
<path d="M14.5 3a.5.5 0 0 1 .5.5v9a.5.5 0 0 1-.5.5h-13a.5.5 0 0 1-.5-.5v-9a.5.5 0 0 1 .5-.5h13zm-13-1A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h13a1.5 1.5 0 0 0 1.5-1.5v-9A1.5 1.5 0 0 0 14.5 2h-13z"/>
<path d="M3 5.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zM3 8a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9A.5.5 0 0 1 3 8zm0 2.5a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5z"/>
</symbol>
<symbol xmlns="http://www.w3.org/2000/svg" id="file-text" class="bi-file-text" viewBox="0 0 16 16">
<path d="M5 4a.5.5 0 0 0 0 1h6a.5.5 0 0 0 0-1H5zm-.5 2.5A.5.5 0 0 1 5 6h6a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zM5 8a.5.5 0 0 0 0 1h6a.5.5 0 0 0 0-1H5zm0 2a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1H5z"/>
<path d="M2 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2zm10-1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1z"/>
</symbol>
<symbol xmlns="http://www.w3.org/2000/svg" id="backspace" class="bi-backspace" viewBox="0 0 16 16">
<path d="M5.83 5.146a.5.5 0 0 0 0 .708L7.975 8l-2.147 2.146a.5.5 0 0 0 .707.708l2.147-2.147 2.146 2.147a.5.5 0 0 0 .707-.708L9.39 8l2.146-2.146a.5.5 0 0 0-.707-.708L8.683 7.293 6.536 5.146a.5.5 0 0 0-.707 0z"/>
<path d="M13.683 1a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-7.08a2 2 0 0 1-1.519-.698L.241 8.65a1 1 0 0 1 0-1.302L5.084 1.7A2 2 0 0 1 6.603 1h7.08zm-7.08 1a1 1 0 0 0-.76.35L1 8l4.844 5.65a1 1 0 0 0 .759.35h7.08a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1h-7.08z"/>
</symbol>
<symbol xmlns="http://www.w3.org/2000/svg" id="box-arrow-right" class="bi-box-arrow-right" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M10 12.5a.5.5 0 0 1-.5.5h-8a.5.5 0 0 1-.5-.5v-9a.5.5 0 0 1 .5-.5h8a.5.5 0 0 1 .5.5v2a.5.5 0 0 0 1 0v-2A1.5 1.5 0 0 0 9.5 2h-8A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h8a1.5 1.5 0 0 0 1.5-1.5v-2a.5.5 0 0 0-1 0v2z"/>
<path fill-rule="evenodd" d="M15.854 8.354a.5.5 0 0 0 0-.708l-3-3a.5.5 0 0 0-.708.708L14.293 7.5H5.5a.5.5 0 0 0 0 1h8.793l-2.147 2.146a.5.5 0 0 0 .708.708l3-3z"/>
</symbol>
<symbol xmlns="http://www.w3.org/2000/svg" id="stopwatch" class="bi-stopwatch" viewBox="0 0 16 16">
<path d="M8.5 5.6a.5.5 0 1 0-1 0v2.9h-3a.5.5 0 0 0 0 1H8a.5.5 0 0 0 .5-.5V5.6z"/>
<path d="M6.5 1A.5.5 0 0 1 7 .5h2a.5.5 0 0 1 0 1v.57c1.36.196 2.594.78 3.584 1.64a.715.715 0 0 1 .012-.013l.354-.354-.354-.353a.5.5 0 0 1 .707-.708l1.414 1.415a.5.5 0 1 1-.707.707l-.353-.354-.354.354a.512.512 0 0 1-.013.012A7 7 0 1 1 7 2.071V1.5a.5.5 0 0 1-.5-.5zM8 3a6 6 0 1 0 .001 12A6 6 0 0 0 8 3z"/>
</symbol>
<symbol xmlns="http://www.w3.org/2000/svg" id="clock-history" class="bi-clock-history" viewBox="0 0 16 16">
<path d="M8.515 1.019A7 7 0 0 0 8 1V0a8 8 0 0 1 .589.022l-.074.997zm2.004.45a7.003 7.003 0 0 0-.985-.299l.219-.976c.383.086.76.2 1.126.342l-.36.933zm1.37.71a7.01 7.01 0 0 0-.439-.27l.493-.87a8.025 8.025 0 0 1 .979.654l-.615.789a6.996 6.996 0 0 0-.418-.302zm1.834 1.79a6.99 6.99 0 0 0-.653-.796l.724-.69c.27.285.52.59.747.91l-.818.576zm.744 1.352a7.08 7.08 0 0 0-.214-.468l.893-.45a7.976 7.976 0 0 1 .45 1.088l-.95.313a7.023 7.023 0 0 0-.179-.483zm.53 2.507a6.991 6.991 0 0 0-.1-1.025l.985-.17c.067.386.106.778.116 1.17l-1 .025zm-.131 1.538c.033-.17.06-.339.081-.51l.993.123a7.957 7.957 0 0 1-.23 1.155l-.964-.267c.046-.165.086-.332.12-.501zm-.952 2.379c.184-.29.346-.594.486-.908l.914.405c-.16.36-.345.706-.555 1.038l-.845-.535zm-.964 1.205c.122-.122.239-.248.35-.378l.758.653a8.073 8.073 0 0 1-.401.432l-.707-.707z"/>
<path d="M8 1a7 7 0 1 0 4.95 11.95l.707.707A8.001 8.001 0 1 1 8 0v1z"/>
<path d="M7.5 3a.5.5 0 0 1 .5.5v5.21l3.248 1.856a.5.5 0 0 1-.496.868l-3.5-2A.5.5 0 0 1 7 9V3.5a.5.5 0 0 1 .5-.5z"/>
</symbol>
<symbol xmlns="http://www.w3.org/2000/svg" id="published-with-changes" viewBox="0 0 24 24">
<rect fill="none" height="24" width="24"/>
<path d="M17.66,9.53l-7.07,7.07l-4.24-4.24l1.41-1.41l2.83,2.83l5.66-5.66L17.66,9.53z M4,12c0-2.33,1.02-4.42,2.62-5.88L9,8.5v-6H3 l2.2,2.2C3.24,6.52,2,9.11,2,12c0,5.19,3.95,9.45,9,9.95v-2.02C7.06,19.44,4,16.07,4,12z M22,12c0-5.19-3.95-9.45-9-9.95v2.02 c3.94,0.49,7,3.86,7,7.93c0,2.33-1.02,4.42-2.62,5.88L15,15.5v6h6l-2.2-2.2C20.76,17.48,22,14.89,22,12z"/>
</symbol>
<symbol xmlns="http://www.w3.org/2000/svg" id="globe" class="bi-globe" viewBox="0 0 16 16">
<path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm7.5-6.923c-.67.204-1.335.82-1.887 1.855A7.97 7.97 0 0 0 5.145 4H7.5V1.077zM4.09 4a9.267 9.267 0 0 1 .64-1.539 6.7 6.7 0 0 1 .597-.933A7.025 7.025 0 0 0 2.255 4H4.09zm-.582 3.5c.03-.877.138-1.718.312-2.5H1.674a6.958 6.958 0 0 0-.656 2.5h2.49zM4.847 5a12.5 12.5 0 0 0-.338 2.5H7.5V5H4.847zM8.5 5v2.5h2.99a12.495 12.495 0 0 0-.337-2.5H8.5zM4.51 8.5a12.5 12.5 0 0 0 .337 2.5H7.5V8.5H4.51zm3.99 0V11h2.653c.187-.765.306-1.608.338-2.5H8.5zM5.145 12c.138.386.295.744.468 1.068.552 1.035 1.218 1.65 1.887 1.855V12H5.145zm.182 2.472a6.696 6.696 0 0 1-.597-.933A9.268 9.268 0 0 1 4.09 12H2.255a7.024 7.024 0 0 0 3.072 2.472zM3.82 11a13.652 13.652 0 0 1-.312-2.5h-2.49c.062.89.291 1.733.656 2.5H3.82zm6.853 3.472A7.024 7.024 0 0 0 13.745 12H11.91a9.27 9.27 0 0 1-.64 1.539 6.688 6.688 0 0 1-.597.933zM8.5 12v2.923c.67-.204 1.335-.82 1.887-1.855.173-.324.33-.682.468-1.068H8.5zm3.68-1h2.146c.365-.767.594-1.61.656-2.5h-2.49a13.65 13.65 0 0 1-.312 2.5zm2.802-3.5a6.959 6.959 0 0 0-.656-2.5H12.18c.174.782.282 1.623.312 2.5h2.49zM11.27 2.461c.247.464.462.98.64 1.539h1.835a7.024 7.024 0 0 0-3.072-2.472c.218.284.418.598.597.933zM10.855 4a7.966 7.966 0 0 0-.468-1.068C9.835 1.897 9.17 1.282 8.5 1.077V4h2.355z"/>
</symbol>
<symbol xmlns="http://www.w3.org/2000/svg" id="arrow-bar-left" class="bi-arrow-bar-left" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M12.5 15a.5.5 0 0 1-.5-.5v-13a.5.5 0 0 1 1 0v13a.5.5 0 0 1-.5.5zM10 8a.5.5 0 0 1-.5.5H3.707l2.147 2.146a.5.5 0 0 1-.708.708l-3-3a.5.5 0 0 1 0-.708l3-3a.5.5 0 1 1 .708.708L3.707 7.5H9.5a.5.5 0 0 1 .5.5z"/>
</symbol>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -0,0 +1,44 @@
// Fetch Post content via AJAX
document.querySelectorAll('.item-container').forEach((el) => {
el.addEventListener('click', (event) => {
document.querySelector('.item-selected').classList.remove('item-selected');
event.currentTarget.classList.add('item-selected');
fetch('/post/' + event.currentTarget.id)
.then(response => response.text())
.then(data => document.getElementById('main').innerHTML = data);
});
});
// Copy HTML to clipboard
let icon = (name, width = 14, height = 14, color = 'currentColor') => {
return `
<svg class="bi" width="${width}" height="${height}" fill="${color}">
<use xlink:href="/icons/icons.svg#${name}"/>
</svg>
`;
}
document.querySelector('#copyHTML').addEventListener('click', function (e) {
let iconContainer = e.currentTarget.querySelector('.icon');
navigator.clipboard.writeText(document.querySelector('.content-body').innerHTML.trim()).then(() => {
iconContainer.innerHTML = icon('clipboard-check');
setTimeout(
() => {
iconContainer.innerHTML = icon('clipboard');
},
3000
);
}, function() {
iconContainer.innerHTML = icon('clipboard-x');
setTimeout(
() => {
iconContainer.innerHTML = icon('clipboard');
},
3000
);
});
});

View file

@ -0,0 +1,248 @@
/*
* -- BASE STYLES --
* Most of these are inherited from Base, but I want to change a few.
*/
body {
color: #333;
}
a {
text-decoration: none;
color: #1b98f8;
}
pre {
overflow-x: auto;
}
/*
* -- LAYOUT STYLES --
* This layout consists of three main elements, `#nav` (navigation bar), `#list` (email list), and `#main` (email content). All 3 elements are within `#layout`
*/
#layout, #nav, #list, #main {
margin: 0;
padding: 0;
}
/* Make the navigation 100% width on phones */
#nav {
width: 100%;
height: 40px;
position: relative;
background: rgb(37, 42, 58);
text-align: center;
}
/* Show the "Menu" button on phones */
#nav .nav-menu-button {
display: block;
top: 0.5em;
right: 0.5em;
position: absolute;
}
/* When "Menu" is clicked, the navbar should be 80% height */
#nav.active {
height: 80%;
}
/* Don't show the navigation items... */
.nav-inner {
display: none;
}
/* ...until the "Menu" button is clicked */
#nav.active .nav-inner {
display: block;
padding: 2em 0;
}
/*
* -- NAV BAR STYLES --
* Styling the default .pure-menu to look a little more unique.
*/
#nav .pure-menu {
background: transparent;
border: none;
text-align: left;
}
#nav .pure-menu-link:hover,
#nav .pure-menu-link:focus {
background: rgb(55, 60, 90);
}
#nav .pure-menu-link {
color: #fff;
margin-left: 0.5em;
}
#nav .pure-menu-heading {
border-bottom: none;
font-size:110%;
color: rgb(75, 113, 151);
}
/* Item Styles */
.item {
padding: 0.9em 1em;
border-bottom: 1px solid #ddd;
border-left: 6px solid transparent;
cursor: pointer;
}
.avatar {
border-radius: 3px;
margin-right: 0.5em;
}
.name,
.subject {
margin: 0;
}
.name {
text-transform: uppercase;
color: #999;
}
.desc {
font-size: 80%;
margin: 0.4em 0;
}
.item-selected {
background: #eee;
border-left: 6px solid #1b98f8;
}
/* Content Styles */
.content-header, .content-body, .content-footer {
padding: 1em 2em;
}
.content-header {
border-bottom: 1px solid #ddd;
}
.content-title {
margin: 0.5em 0 0;
}
.content-subtitle {
font-size: 1em;
margin: 0;
font-weight: normal;
}
.content-subtitle span {
color: #999;
}
.content-controls {
margin-top: 2em;
text-align: right;
}
.content-controls .pure-button-variant {
margin-bottom: 0.3em;
color: white;
/*border-radius: 4px;*/
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
}
.pure-button-variant-success {
background: rgb(28, 184, 65);
/* this is a green */
}
.pure-button-variant-error {
background: rgb(202, 60, 60);
/* this is a maroon */
}
.pure-button-variant-warning {
background: rgb(223, 117, 20);
/* this is an orange */
}
.pure-button-variant-secondary {
background: rgb(66, 184, 221);
/* this is a light blue */
}
.avatar {
width: 40px;
height: 40px;
}
/*
* -- TABLET (AND UP) MEDIA QUERIES --
* On tablets and other medium-sized devices, we want to customize some
* of the mobile styles.
*/
@media (min-width: 40em) {
/* Move the layout over so we can fit the nav + list in on the left */
#layout {
padding-left:500px; /* "left col (nav + list)" width */
position: relative;
}
/* These are position:fixed; elements that will be in the left 500px of the screen */
#nav, #list {
position: fixed;
top: 0;
bottom: 0;
overflow: auto;
}
#nav {
margin-left:-500px; /* "left col (nav + list)" width */
width:150px;
height: 100%;
}
/* Show the menu items on the larger screen */
.nav-inner {
display: block;
padding: 2em 0;
}
/* Hide the "Menu" button on larger screens */
#nav .nav-menu-button {
display: none;
}
#list {
margin-left: -350px;
width: 100%;
height: 33%;
border-bottom: 1px solid #ddd;
}
#main {
position: fixed;
top: 33%;
right: 0;
bottom: 0;
left: 150px;
overflow: auto;
width: auto; /* so that it's not 100% */
}
}
/*
* -- DESKTOP (AND UP) MEDIA QUERIES --
* On desktops and other large-sized devices, we want to customize some
* of the mobile styles.
*/
@media (min-width: 60em) {
/* This will take up the entire height, and be a little thinner */
#list {
margin-left: -350px;
width:350px;
height: 100%;
border-right: 1px solid #ddd;
}
/* This will now take up it's own column, so don't need position: fixed; */
#main {
position: static;
margin: 0;
padding: 0;
}
}

27
routes/index.js Normal file
View file

@ -0,0 +1,27 @@
var express = require('express');
const app = require('../app');
var router = express.Router();
const db = require('../connection');
/* GET home page. */
router.get('/', function(req, res, next) {
const stmt = db.prepare("SELECT id, title, tags FROM posts WHERE language = $language ORDER BY id ASC");
stmt.all(
{
$language: process.env.DEFAULT_LANGUAGE
},
function(err, rows) {
const stmt = db.prepare("SELECT id, title, html, tags FROM posts WHERE language = $language ORDER BY id ASC");
stmt.get(
{
$language: process.env.DEFAULT_LANGUAGE
},
function (err, row) {
res.render('index.njk', { rows: rows, selected: row });
}
);
}
)
});
module.exports = router;

18
routes/post.js Normal file
View file

@ -0,0 +1,18 @@
var express = require('express');
var router = express.Router();
const db = require('../connection');
/* post view */
router.get('/:id', function(req, res, next) {
const stmt = db.prepare("SELECT id, title, html, tags FROM posts WHERE id = $id");
stmt.get(
{
$id: req.params.id
},
function (err, row) {
res.render('partial/content.njk', { selected: row });
}
)
});
module.exports = router;

1
views/error.njk Normal file
View file

@ -0,0 +1 @@
{{ res.locals.message }}

1
views/index.njk Normal file
View file

@ -0,0 +1 @@
{% extends 'layout.njk' %}

106
views/layout.njk Normal file
View file

@ -0,0 +1,106 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="A layout example that shows off a responsive email layout.">
<title>Email &ndash; Layout Examples &ndash; Pure</title>
<link rel="stylesheet" href="https://unpkg.com/purecss@2.1.0/build/pure-min.css" integrity="sha384-yHIFVG6ClnONEA5yB5DJXfW2/KC173DIQrYoZMEtBvGzmf0PKiGyNEqe9N6BNDBH" crossorigin="anonymous">
<link rel="stylesheet" href="/stylesheets/style.css">
</head>
<body>
<div id="layout" class="content pure-g">
<div id="nav" class="pure-u">
<a href="#" id="menuLink" class="nav-menu-button">Menu</a>
<div class="nav-inner">
<button class="pure-button pure-button-primary">New</button>
<div class="pure-menu">
<ul class="pure-menu-list">
<li class="pure-menu-heading">Languages</li>
<li class="pure-menu-item"><a href="#" class="pure-menu-link"><span class="label-personal"></span>Personal</a></li>
<li class="pure-menu-item"><a href="#" class="pure-menu-link"><span class="label-work"></span>Work</a></li>
<li class="pure-menu-item"><a href="#" class="pure-menu-link"><span class="label-travel"></span>Travel</a></li>
</ul>
</div>
</div>
</div>
<div id="list" class="pure-u-1">
{% for row in rows %}
<div id="{{ row.id }}" class="item-container">
<div class="item pure-g {% if row.id == selected.id %}item-selected{% endif %}">
<div class="pure-u-3-4">
<h4 class="subject">{{ row.title }}</h4>
<p class="desc">
{% for tag in row.tags.split(',') %}
#{{ tag.trim() }}
{% endfor %}
</p>
</div>
</div>
</div>
{% endfor %}
</div>
<div id="main" class="pure-u-1">
{% include "./partial/content.njk" %}
</div>
</div>
<!-- Script to make the Menu link work -->
<!-- Just stripped down version of the js/ui.js script for the side-menu layout -->
<script>
function getElements() {
return {
menu: document.getElementById('nav'),
menuLink: document.getElementById('menuLink')
};
}
function toggleClass(element, className) {
var classes = element.className.split(/\s+/);
var length = classes.length;
var i = 0;
for (; i < length; i++) {
if (classes[i] === className) {
classes.splice(i, 1);
break;
}
}
// The className is not found
if (length === classes.length) {
classes.push(className);
}
element.className = classes.join(' ');
}
function toggleMenu() {
var active = 'active';
var elements = getElements();
toggleClass(elements.menu, active);
}
function handleEvent(e) {
var elements = getElements();
if (e.target.id === elements.menuLink.id) {
toggleMenu();
e.preventDefault();
} else if (elements.menu.className.indexOf('active') !== -1) {
toggleMenu();
}
}
document.addEventListener('DOMContentLoaded', function () {
document.addEventListener('click', handleEvent);
});
</script>
<script type="text/javascript" src="/javascripts/main.js"></script>
</body>
</html>

5
views/macros/icon.njk Normal file
View file

@ -0,0 +1,5 @@
{% macro svg(name, width = 14, height = 14, color = 'currentColor') %}
<svg class="bi" width="{{ width }}" height="{{ height }}" fill="currentColor">
<use xlink:href="/icons/icons.svg#{{ name }}"/>
</svg>
{% endmacro %}

22
views/partial/content.njk Normal file
View file

@ -0,0 +1,22 @@
{% import '../macros/icon.njk' as icon %}
<div class="content">
<div class="content-header pure-g">
<div class="pure-u-1-2">
<h1 class="content-title">{{ selected.title }}</h1>
<p class="content-subtitle">
{% for tag in selected.tags.split(',') %}
#{{ tag.trim() }}
{% endfor %}
</p>
</div>
<div class="content-controls pure-u-1-2">
<button id="copyHTML" class="pure-button pure-button-variant pure-button-variant-success"><span class="icon">{{ icon.svg('clipboard') }}</span></button>
<button class="pure-button pure-button-variant pure-button-variant pure-button-variant-warning">{{ icon.svg('pencil-square') }}</button>
<button class="pure-button pure-button-variant pure-button-variant pure-button-variant-secondary">{{ icon.svg('globe') }}</button>
<button class="pure-button pure-button-variant pure-button-variant pure-button-variant-error">{{ icon.svg('trash') }}</button>
</div>
</div>
<div class="content-body"> {{ selected.html|safe }}</div>
</div>