mirror of
https://github.com/MrUnknownDE/unknownbin.git
synced 2026-04-06 00:32:08 +02:00
add dynamic seo settings
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"clean-css": "^5.3.3",
|
||||
"ejs": "^3.1.10",
|
||||
"express": "^4.19.2",
|
||||
"helmet": "^7.1.0",
|
||||
"uglify-js": "^3.17.4",
|
||||
|
||||
65
server.js
65
server.js
@@ -4,6 +4,7 @@ const path = require('path');
|
||||
const express = require('express');
|
||||
const helmet = require('helmet');
|
||||
const winston = require('winston');
|
||||
const ejs = require('ejs');
|
||||
|
||||
const DocumentHandler = require('./lib/document_handler.js');
|
||||
const FileStorage = require('./lib/file_storage.js');
|
||||
@@ -37,25 +38,6 @@ logger.info('Welcome to unknownBIN!');
|
||||
// init file-storage
|
||||
const fileStorage = new FileStorage({ path: config.dataPath, logger: logger });
|
||||
|
||||
// load static documents into file-storage
|
||||
if (config.documents) {
|
||||
for (const name in config.documents) {
|
||||
const docPath = config.documents[name];
|
||||
try {
|
||||
const data = fs.readFileSync(docPath, 'utf8');
|
||||
fileStorage.set(name, data, (success) => {
|
||||
if (success) {
|
||||
logger.verbose(`Loaded document: ${name} from ${docPath}`);
|
||||
} else {
|
||||
logger.warn(`Failed to store document: ${name}`);
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
logger.warn(`Unable to find or read document: ${name} at ${docPath}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// configure the document handler
|
||||
const documentHandler = new DocumentHandler({
|
||||
store: fileStorage,
|
||||
@@ -68,9 +50,14 @@ const documentHandler = new DocumentHandler({
|
||||
// setup routes and request-handling
|
||||
const app = express();
|
||||
|
||||
// Configure EJS as the view engine
|
||||
app.set('view engine', 'ejs');
|
||||
app.set('views', path.join(__dirname, 'views'));
|
||||
|
||||
// Use helmet for basic security headers
|
||||
app.use(helmet());
|
||||
|
||||
// API routes
|
||||
app.get('/raw/:id', (req, res) => {
|
||||
return documentHandler.handleRawGet(req.params.id, res);
|
||||
});
|
||||
@@ -81,17 +68,47 @@ app.get('/documents/:id', (req, res) => {
|
||||
return documentHandler.handleGet(req.params.id, res);
|
||||
});
|
||||
|
||||
// Static files
|
||||
app.use(express.static(path.join(__dirname, 'static')));
|
||||
|
||||
app.get('/:id', (req, res, next) => {
|
||||
res.sendFile(path.join(__dirname, '/static/index.html'));
|
||||
// Frontend routes (SSR)
|
||||
app.get('/:id', (req, res) => {
|
||||
const key = req.params.id;
|
||||
|
||||
// Don't treat static files as paste IDs
|
||||
if (key.includes('.')) {
|
||||
return res.status(404).send('Not Found');
|
||||
}
|
||||
|
||||
fileStorage.get(key, (data) => {
|
||||
if (data) {
|
||||
// Paste found, render it with dynamic SEO tags
|
||||
const title = `Paste ${key} - unknownBIN`;
|
||||
const description = data.substring(0, 160).replace(/\n/g, ' ');
|
||||
res.render('index', {
|
||||
paste: {
|
||||
key: key,
|
||||
data: data,
|
||||
title: title,
|
||||
description: description
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Paste not found, render the homepage
|
||||
res.render('index', {
|
||||
paste: null
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/', (req, res, next) => {
|
||||
res.sendFile(path.join(__dirname, '/static/index.html'));
|
||||
app.get('/', (req, res) => {
|
||||
// Render the homepage
|
||||
res.render('index', {
|
||||
paste: null
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
app.listen(config.port, config.host, () => {
|
||||
logger.info(`Listening on ${config.host}:${config.port}`);
|
||||
});
|
||||
@@ -53,20 +53,40 @@ Haste.prototype.newDocument = function(hideHistory) {
|
||||
|
||||
// load an existing document
|
||||
Haste.prototype.loadDocument = function(key) {
|
||||
const _this = this;
|
||||
_this.doc = new HasteDocument();
|
||||
_this.doc.load(key, function(ret) {
|
||||
if (ret) {
|
||||
_this.$code.html(ret.value);
|
||||
_this.setTitle(ret.key);
|
||||
window.history.pushState(null, `${_this.appName}-${ret.key}`, `/${ret.key}`);
|
||||
_this.fullKey();
|
||||
_this.$textarea.val('').hide();
|
||||
_this.$box.show();
|
||||
} else {
|
||||
_this.newDocument();
|
||||
}
|
||||
});
|
||||
// Check if data is pre-rendered by the server
|
||||
const $preloaded = $('#preloaded-data');
|
||||
if ($preloaded.length > 0 && $preloaded.text().trim().length > 0) {
|
||||
this.doc = new HasteDocument();
|
||||
this.doc.locked = true;
|
||||
this.doc.key = key;
|
||||
this.doc.data = $preloaded.text();
|
||||
const high = hljs.highlightAuto(this.doc.data).value;
|
||||
const ret = {
|
||||
value: high.replace(/.+/g, "<span class=\"line\">$&</span>").replace(/^\s*[\r\n]/gm, "<span class=\"line\"></span>\n"),
|
||||
key: key,
|
||||
};
|
||||
this.$code.html(ret.value);
|
||||
this.setTitle(ret.key);
|
||||
this.fullKey();
|
||||
this.$textarea.val('').hide();
|
||||
this.$box.show();
|
||||
} else {
|
||||
// Data is not pre-rendered, fetch from API
|
||||
const _this = this;
|
||||
_this.doc = new HasteDocument();
|
||||
_this.doc.load(key, function(ret) {
|
||||
if (ret) {
|
||||
_this.$code.html(ret.value);
|
||||
_this.setTitle(ret.key);
|
||||
window.history.pushState(null, `${_this.appName}-${ret.key}`, `/${ret.key}`);
|
||||
_this.fullKey();
|
||||
_this.$textarea.val('').hide();
|
||||
_this.$box.show();
|
||||
} else {
|
||||
_this.newDocument();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// duplicate the current document
|
||||
@@ -83,7 +103,10 @@ Haste.prototype.lockDocument = function() {
|
||||
const _this = this;
|
||||
this.doc.save(this.$textarea.val(), function(err, ret) {
|
||||
if (!err && ret) {
|
||||
_this.$code.html(ret.value.trim().replace(/.+/g, "<span class=\"line\">$&</span>").replace(/^\s*[\r\n]/gm, "<span class=\"line\"></span>\n"));
|
||||
// Manually highlight and display after saving
|
||||
const high = hljs.highlightAuto(ret.data).value;
|
||||
const formatted = high.replace(/.+/g, "<span class=\"line\">$&</span>").replace(/^\s*[\r\n]/gm, "<span class=\"line\"></span>\n");
|
||||
_this.$code.html(formatted);
|
||||
_this.setTitle(ret.key);
|
||||
window.history.pushState(null, `${_this.appName}-${ret.key}`, `/${ret.key}`);
|
||||
_this.fullKey();
|
||||
@@ -114,7 +137,7 @@ Haste.prototype.configureButtons = function() {
|
||||
return evt.ctrlKey && evt.keyCode === 32;
|
||||
},
|
||||
action: function() {
|
||||
_this.newDocument(!_this.doc.key);
|
||||
window.location.href = '/';
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -172,15 +195,6 @@ const HasteDocument = function() {
|
||||
this.locked = false;
|
||||
};
|
||||
|
||||
// escape HTML-characters
|
||||
HasteDocument.prototype.htmlEscape = function(s) {
|
||||
return s
|
||||
.replace(/&/g, '&')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/</g, '<')
|
||||
.replace(/"/g, '"');
|
||||
};
|
||||
|
||||
// load a document from the server
|
||||
HasteDocument.prototype.load = function(key, callback) {
|
||||
const _this = this;
|
||||
@@ -207,14 +221,18 @@ HasteDocument.prototype.load = function(key, callback) {
|
||||
HasteDocument.prototype.save = function(data, callback) {
|
||||
if (this.locked) return false;
|
||||
|
||||
this.data = data;
|
||||
const _this = this;
|
||||
_this.data = data;
|
||||
$.ajax('/documents', {
|
||||
type: 'post',
|
||||
data: data,
|
||||
dataType: 'json',
|
||||
contentType: 'text/plain; charset=utf-8',
|
||||
success: function(res) {
|
||||
new Haste().loadDocument(res.key);
|
||||
_this.locked = true;
|
||||
_this.key = res.key;
|
||||
// Pass the saved data back to the callback
|
||||
callback(null, { key: res.key, data: _this.data });
|
||||
},
|
||||
error: function(res) {
|
||||
try {
|
||||
@@ -256,11 +274,11 @@ $(function() {
|
||||
|
||||
const app = new Haste();
|
||||
const path = window.location.pathname;
|
||||
if (path === '/') {
|
||||
if (path === '/' || path === '') {
|
||||
app.newDocument(true);
|
||||
} else {
|
||||
app.loadDocument(path.substring(1, path.length));
|
||||
app.loadDocument(path.substring(1));
|
||||
}
|
||||
});
|
||||
|
||||
hljs.initHighlightingOnLoad();
|
||||
// No need for hljs.initHighlightingOnLoad() as we do it manually
|
||||
37
views/index.ejs
Normal file
37
views/index.ejs
Normal file
@@ -0,0 +1,37 @@
|
||||
// views/index.ejs
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<% if (paste) { %>
|
||||
<title><%= paste.title %></title>
|
||||
<meta name="description" content="<%= paste.description %>">
|
||||
<meta property="og:title" content="<%= paste.title %>">
|
||||
<meta property="og:description" content="<%= paste.description %>">
|
||||
<% } else { %>
|
||||
<title>unknownBIN - A Secure Pastebin</title>
|
||||
<meta name="description" content="unknownBIN is a secure and modern open-source Pastebin software written in Node.js.">
|
||||
<meta property="og:title" content="unknownBIN - A Secure Pastebin">
|
||||
<meta property="og:description" content="unknownBIN is a secure and modern open-source Pastebin software written in Node.js.">
|
||||
<% } %>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<link rel="stylesheet" type="text/css" href="/application.min.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="/highlight.min.css"/>
|
||||
<link rel="shortcut icon" href="/favicon.png">
|
||||
<script src="/jquery.min.js"></script>
|
||||
<script src="/highlight.min.js"></script>
|
||||
<script src="/application.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="tools">
|
||||
<div class="save function" title="Save [Ctrl + S]">Save</div>
|
||||
<div class="new function" title="New [Ctrl + Space]">New</div>
|
||||
<div class="duplicate function" title="Duplicate [Ctrl + D]">Duplicate</div>
|
||||
<div class="raw function" title="Raw [Ctrl + Shift + R]">Raw</div>
|
||||
</div>
|
||||
<div id="content">
|
||||
<pre id="code" style="display:none;" tabindex="0"><code <% if (paste) { %>id="preloaded-data"<% } %>><% if (paste) { %><%- paste.data %><% } %></code></pre>
|
||||
<textarea spellcheck="false" style="display:none;"></textarea>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user