hastepad 0.5

This commit is contained in:
Matthieu Tudury 2020-11-21 22:16:32 +01:00
commit 4618680737
14 changed files with 422 additions and 166 deletions

View file

@ -3,56 +3,24 @@ FROM node:14.8.0-stretch
RUN mkdir -p /usr/src/app && \ RUN mkdir -p /usr/src/app && \
chown node:node /usr/src/app chown node:node /usr/src/app
USER node:node
WORKDIR /usr/src/app WORKDIR /usr/src/app
COPY --chown=node:node . . COPY . .
RUN npm install && \ RUN mv config.json base.config.json && touch config.json && chown node:node config.json ./static/application.min.js && mkdir data && chown node:node data
npm install redis@0.8.1 && \
npm install pg@4.1.1 && \
npm install memcached@2.2.2 && \
npm install aws-sdk@2.738.0 && \
npm install rethinkdbdash@2.3.31
ENV STORAGE_TYPE=memcached \ RUN npm install
STORAGE_HOST=127.0.0.1 \ # && \
STORAGE_PORT=11211\ # npm install redis@0.8.1 && \
STORAGE_EXPIRE_SECONDS=2592000\ # npm install pg@4.1.1 && \
STORAGE_DB=2 \ # npm install memcached@2.2.2 && \
STORAGE_AWS_BUCKET= \ # npm install aws-sdk@2.738.0 && \
STORAGE_AWS_REGION= \ # npm install rethinkdbdash@2.3.31
STORAGE_USENAMER= \
STORAGE_PASSWORD= \
STORAGE_FILEPATH=
ENV LOGGING_LEVEL=verbose \
LOGGING_TYPE=Console \
LOGGING_COLORIZE=true
ENV HOST=0.0.0.0\ ENV HOST=0.0.0.0\
PORT=7777\ PORT=7777
KEY_LENGTH=10\
MAX_LENGTH=400000\
STATIC_MAX_AGE=86400\
RECOMPRESS_STATIC_ASSETS=true
ENV KEYGENERATOR_TYPE=phonetic \ USER node:node
KEYGENERATOR_KEYSPACE=
ENV RATELIMITS_NORMAL_TOTAL_REQUESTS=500\
RATELIMITS_NORMAL_EVERY_MILLISECONDS=60000 \
RATELIMITS_WHITELIST_TOTAL_REQUESTS= \
RATELIMITS_WHITELIST_EVERY_MILLISECONDS= \
# comma separated list for the whitelisted \
RATELIMITS_WHITELIST=example1.whitelist,example2.whitelist \
\
RATELIMITS_BLACKLIST_TOTAL_REQUESTS= \
RATELIMITS_BLACKLIST_EVERY_MILLISECONDS= \
# comma separated list for the blacklisted \
RATELIMITS_BLACKLIST=example1.blacklist,example2.blacklist
ENV DOCUMENTS=about=./about.md
EXPOSE ${PORT} EXPOSE ${PORT}
STOPSIGNAL SIGINT STOPSIGNAL SIGINT

View file

@ -1,3 +1,13 @@
# TestNow
Test it in seconds :
```bash
docker run --rm -d -p 7777:7777 hastepad:0.5
```
Open your browser and type url : http://127.0.0.1:7777/
# Haste # Haste
Haste is an open-source pastebin software written in node.js, which is easily Haste is an open-source pastebin software written in node.js, which is easily
@ -20,6 +30,26 @@ to do things like:
which will output a URL to share containing the contents of `cat something`'s which will output a URL to share containing the contents of `cat something`'s
STDOUT. Check the README there for more details and usages. STDOUT. Check the README there for more details and usages.
# Customized version (HastePad)
This is not the original version, you'll find the original version at : https://github.com/seejohnrun/haste-server
This version is from here : https://github.com/mtudury/haste-server
This version is customized in order to add/change some features :
- Live saving : Do not loose your work when closing your browser
- List documents
- Delete document
It currently only works with storage type : file.
It main usage would be like a notepad online (mono user)
It does not have exactly the same purpose of original haste (no edit/no delete needed nor wanted in original version) so choose accordingly to your needs
If you need security, i would recommend : add a reverse-proxy in front (Caddy/Nginx), HTTPS and authentication using reverse proxy features, only expose the reverse proxy.
## Tested Browsers ## Tested Browsers
* Firefox 8 * Firefox 8

View file

@ -6,6 +6,20 @@ use pastebins.
Haste is the prettiest, easiest to use pastebin ever made. Haste is the prettiest, easiest to use pastebin ever made.
## Customized version (HastePad)
This is not the original version, you'll find the original version at : https://github.com/seejohnrun/haste-server
This version is from here : https://github.com/mtudury/haste-server
This version is customized in order to add/change some features :
- Live saving : Do not loose your work when closing your browser
- List document
- Delete document
It main usage would be like a notepad online (mono user)
## Basic Usage ## Basic Usage
Type what you want me to see, click "Save", and then copy the URL. Send that Type what you want me to see, click "Save", and then copy the URL. Send that

View file

@ -33,11 +33,13 @@
}, },
"storage": { "storage": {
"type": "file" "type": "file",
"allowList": true,
"allowDelete": true
}, },
"documents": { "documents": {
"about": "./about.md" "about.md": "./about.md"
} }
} }

View file

@ -1,19 +1,20 @@
version: '3.0' version: '3.6'
services: services:
haste-server: haste-server:
build: . build: .
networks: image: hastepad:0.5
- db-network
environment:
- STORAGE_TYPE=memcached
- STORAGE_HOST=memcached
- STORAGE_PORT=11211
ports: ports:
- 7777:7777 - 7777:7777
memcached: volumes:
image: memcached:latest - type: tmpfs
networks: target: /usr/src/app/data
- db-network tmpfs:
size: 128M
logging:
driver: "json-file"
options:
max-size: "1M"
max-file: "10"
networks: networks:
db-network: db-network:

View file

@ -29,80 +29,58 @@ const {
RATE_LIMITS_BLACKLIST_EVERY_MILLISECONDS, RATE_LIMITS_BLACKLIST_EVERY_MILLISECONDS,
RATE_LIMITS_BLACKLIST, RATE_LIMITS_BLACKLIST,
DOCUMENTS, DOCUMENTS,
STORAGE_ALLOWLIST,
STORAGE_ALLOWDELETE
} = process.env; } = process.env;
const config = { const config = require("./base.config.json");
host: HOST,
port: PORT,
keyLength: KEY_LENGTH, if (HOST) config.host = HOST;
if (PORT) config.port = PORT;
maxLength: MAX_LENGTH, if (KEY_LENGTH) config.keyLength = KEY_LENGTH;
staticMaxAge: STATIC_MAX_AGE, if (MAX_LENGTH) config.maxLength = MAX_LENGTH;
recompressStaticAssets: RECOMPRESS_STATIC_ASSETS, if (PORT) config.staticMaxAge = STATIC_MAX_AGE;
logging: [ if (PORT) config.ecompressStaticAssets = RECOMPRESS_STATIC_ASSETS;
{
level: LOGGING_LEVEL,
type: LOGGING_TYPE,
colorize: LOGGING_COLORIZE,
},
],
keyGenerator: { if (LOGGING_LEVEL) config.logging[0].level = LOGGING_LEVEL;
type: KEYGENERATOR_TYPE, if (LOGGING_TYPE) config.logging[0].type = LOGGING_TYPE;
keyspace: KEY_GENERATOR_KEYSPACE, if (LOGGING_COLORIZE) config.logging[0].colorize = LOGGING_COLORIZE;
},
rateLimits: { if (KEYGENERATOR_TYPE) config.keyGenerator.type = KEYGENERATOR_TYPE;
whitelist: RATE_LIMITS_WHITELIST ? RATE_LIMITS_WHITELIST.split(",") : [], if (KEY_GENERATOR_KEYSPACE) config.keyGenerator.keyspace = KEY_GENERATOR_KEYSPACE;
blacklist: RATE_LIMITS_BLACKLIST ? RATE_LIMITS_BLACKLIST.split(",") : [],
categories: {
normal: {
totalRequests: RATE_LIMITS_NORMAL_TOTAL_REQUESTS,
every: RATE_LIMITS_NORMAL_EVERY_MILLISECONDS,
},
whitelist:
RATE_LIMITS_WHITELIST_EVERY_MILLISECONDS ||
RATE_LIMITS_WHITELIST_TOTAL_REQUESTS
? {
totalRequests: RATE_LIMITS_WHITELIST_TOTAL_REQUESTS,
every: RATE_LIMITS_WHITELIST_EVERY_MILLISECONDS,
}
: null,
blacklist:
RATE_LIMITS_BLACKLIST_EVERY_MILLISECONDS ||
RATE_LIMITS_BLACKLIST_TOTAL_REQUESTS
? {
totalRequests: RATE_LIMITS_WHITELIST_TOTAL_REQUESTS,
every: RATE_LIMITS_BLACKLIST_EVERY_MILLISECONDS,
}
: null,
},
},
storage: { if (RATE_LIMITS_WHITELIST) config.rateLimits.whitelist = RATE_LIMITS_WHITELIST.split(",");
type: STORAGE_TYPE, if (RATE_LIMITS_BLACKLIST) config.rateLimits.blacklist = RATE_LIMITS_BLACKLIST.split(",");
host: STORAGE_HOST,
port: STORAGE_PORT,
expire: STORAGE_EXPIRE_SECONDS,
bucket: STORAGE_AWS_BUCKET,
region: STORAGE_AWS_REGION,
connectionUrl: `postgres://${STORAGE_USERNAME}:${STORAGE_PASSWORD}@${STORAGE_HOST}:${STORAGE_PORT}/${STORAGE_DB}`,
db: STORAGE_DB,
user: STORAGE_USERNAME,
password: STORAGE_PASSWORD,
path: STORAGE_FILEPATH,
},
documents: DOCUMENTS if (RATE_LIMITS_NORMAL_TOTAL_REQUESTS) config.rateLimits.categories.normal.totalRequests = RATE_LIMITS_NORMAL_TOTAL_REQUESTS;
? DOCUMENTS.split(",").reduce((acc, item) => { if (RATE_LIMITS_NORMAL_EVERY_MILLISECONDS) config.rateLimits.categories.normal.every = RATE_LIMITS_NORMAL_EVERY_MILLISECONDS;
const keyAndValueArray = item.replace(/\s/g, "").split("=");
return { ...acc, [keyAndValueArray[0]]: keyAndValueArray[1] }; if (RATE_LIMITS_WHITELIST_EVERY_MILLISECONDS || RATE_LIMITS_WHITELIST_TOTAL_REQUESTS) config.rateLimits.categories.whitelist = { totalRequests: RATE_LIMITS_WHITELIST_TOTAL_REQUESTS, every: RATE_LIMITS_WHITELIST_EVERY_MILLISECONDS };
}, {}) if (RATE_LIMITS_BLACKLIST_EVERY_MILLISECONDS || RATE_LIMITS_BLACKLIST_TOTAL_REQUESTS) config.rateLimits.categories.blacklist = { totalRequests: RATE_LIMITS_BLACKLIST_TOTAL_REQUESTS, every: RATE_LIMITS_BLACKLIST_EVERY_MILLISECONDS };
: null,
}; if (STORAGE_TYPE) config.storage.type = STORAGE_TYPE;
if (STORAGE_HOST) config.storage.host = STORAGE_HOST;
if (STORAGE_PORT) config.storage.port = STORAGE_PORT;
if (STORAGE_EXPIRE_SECONDS) config.storage.expire = STORAGE_EXPIRE_SECONDS;
if (STORAGE_AWS_BUCKET) config.storage.bucket = STORAGE_AWS_BUCKET;
if (STORAGE_AWS_REGION) config.storage.region = STORAGE_AWS_REGION;
if (STORAGE_DB) config.storage.connectionUrl = `postgres://${STORAGE_USERNAME}:${STORAGE_PASSWORD}@${STORAGE_HOST}:${STORAGE_PORT}/${STORAGE_DB}`;
if (STORAGE_DB) config.storage.db = STORAGE_DB;
if (STORAGE_USERNAME) config.storage.user = STORAGE_USERNAME;
if (STORAGE_PASSWORD) config.storage.password = STORAGE_PASSWORD;
if (STORAGE_FILEPATH) config.storage.path = STORAGE_FILEPATH;
if (STORAGE_ALLOWLIST) config.storage.allowList = STORAGE_ALLOWLIST == "true";
if (STORAGE_ALLOWDELETE) config.storage.allowDelete = STORAGE_ALLOWDELETE == "true";
if (DOCUMENTS) {
config.documents = DOCUMENTS.split(",").reduce((acc, item) => {
const keyAndValueArray = item.replace(/\s/g, "").split("=");
return { ...acc, [keyAndValueArray[0]]: keyAndValueArray[1] };
}, {});
}
console.log(JSON.stringify(config)); console.log(JSON.stringify(config));

View file

@ -4,6 +4,6 @@
set -e set -e
node ./docker-entrypoint.js > ./config.js node ./docker-entrypoint.js > ./config.json
exec "$@" exec "$@"

View file

@ -3,6 +3,8 @@ var Busboy = require('busboy');
// For handling serving stored documents // For handling serving stored documents
const checkkeyregex = /^[a-zA-Z0-9-_.]+$/;
var DocumentHandler = function(options) { var DocumentHandler = function(options) {
if (!options) { if (!options) {
options = {}; options = {};
@ -17,9 +19,19 @@ DocumentHandler.defaultKeyLength = 10;
// Handle retrieving a document // Handle retrieving a document
DocumentHandler.prototype.handleGet = function(request, response, config) { DocumentHandler.prototype.handleGet = function(request, response, config) {
const key = request.params.id.split('.')[0]; const key = request.params.id;
const skipExpire = !!config.documents[key]; const skipExpire = !!config.documents[key];
if ((key)&&(!checkkeyregex.test(key))) {
winston.warn('invalid key', { key: key });
response.writeHead(400, { 'content-type': 'application/json' });
response.end(
JSON.stringify({ message: 'Invalid key', key: key })
);
return;
}
this.store.get(key, function(ret) { this.store.get(key, function(ret) {
if (ret) { if (ret) {
winston.verbose('retrieved document', { key: key }); winston.verbose('retrieved document', { key: key });
@ -44,9 +56,19 @@ DocumentHandler.prototype.handleGet = function(request, response, config) {
// Handle retrieving the raw version of a document // Handle retrieving the raw version of a document
DocumentHandler.prototype.handleRawGet = function(request, response, config) { DocumentHandler.prototype.handleRawGet = function(request, response, config) {
const key = request.params.id.split('.')[0]; const key = request.params.id;
const skipExpire = !!config.documents[key]; const skipExpire = !!config.documents[key];
if ((key)&&(!checkkeyregex.test(key))) {
winston.warn('invalid key', { key: key });
response.writeHead(400, { 'content-type': 'application/json' });
response.end(
JSON.stringify({ message: 'Invalid key', key: key })
);
return;
}
this.store.get(key, function(ret) { this.store.get(key, function(ret) {
if (ret) { if (ret) {
winston.verbose('retrieved raw document', { key: key }); winston.verbose('retrieved raw document', { key: key });
@ -75,6 +97,17 @@ DocumentHandler.prototype.handlePost = function (request, response) {
var buffer = ''; var buffer = '';
var cancelled = false; var cancelled = false;
const key = request.params.id ? request.params.id : null;
if ((key)&&(!checkkeyregex.test(key))) {
winston.warn('invalid key', { key: key });
response.writeHead(400, { 'content-type': 'application/json' });
response.end(
JSON.stringify({ message: 'Invalid key', key: key })
);
return;
}
// What to do when done // What to do when done
var onSuccess = function () { var onSuccess = function () {
// Check length // Check length
@ -87,9 +120,9 @@ DocumentHandler.prototype.handlePost = function (request, response) {
); );
return; return;
} }
// And then save if we should
_this.chooseKey(function (key) { const store = function (key) {
_this.store.set(key, buffer, function (res) { _this.store.set(key, buffer, function (res) {
if (res) { if (res) {
winston.verbose('added document', { key: key }); winston.verbose('added document', { key: key });
response.writeHead(200, { 'content-type': 'application/json' }); response.writeHead(200, { 'content-type': 'application/json' });
@ -101,7 +134,16 @@ DocumentHandler.prototype.handlePost = function (request, response) {
response.end(JSON.stringify({ message: 'Error adding document.' })); response.end(JSON.stringify({ message: 'Error adding document.' }));
} }
}); });
}); }
// And then save if we should
if (!key) {
_this.chooseKey(function (key) {
store(key);
});
} else {
store(key);
}
}; };
// If we should, parse a form to grab the data // If we should, parse a form to grab the data
@ -135,6 +177,69 @@ DocumentHandler.prototype.handlePost = function (request, response) {
} }
}; };
// Handle deleting a document
DocumentHandler.prototype.handleDelete = function(request, response, config) {
const key = request.params.id;
const allowdelete = config.storage.allowDelete;
if ((key)&&(!checkkeyregex.test(key))) {
winston.warn('invalid key', { key: key });
response.writeHead(400, { 'content-type': 'application/json' });
response.end(
JSON.stringify({ message: 'Invalid key', key: key })
);
return;
}
if (!this.store.delete||!allowdelete) {
winston.warn('document provider does not support delete', { key: key });
response.writeHead(405, { 'content-type': 'application/json' });
response.end(JSON.stringify({ message: 'Delete not supported.' }));
return;
}
this.store.delete(key, function(ret) {
if (ret) {
winston.verbose('deleted document', { key: key });
response.writeHead(200, { 'content-type': 'application/json' });
response.end(JSON.stringify({ message: 'Document deleted', key: key }));
}
else {
winston.warn('raw document not found', { key: key });
response.writeHead(404, { 'content-type': 'application/json' });
response.end(JSON.stringify({ message: 'Document not found.' }));
}
});
};
// Handle listing documents
DocumentHandler.prototype.handleList = function(request, response, config) {
const allowlist = config.storage.allowList;
if (!this.store.list||!allowlist) {
winston.warn('document provider does not support list');
response.writeHead(405, { 'content-type': 'application/json' });
response.end(JSON.stringify({ message: 'Delete not supported.' }));
return;
}
this.store.list(function(ret) {
winston.verbose('list documents');
response.writeHead(200, { 'content-type': 'application/json' });
response.end(JSON.stringify(ret));
});
};
// Handle listing documents
DocumentHandler.prototype.handleGetKey = function(request, response, config) {
this.chooseKey(function(ret) {
response.writeHead(200, { 'content-type': 'text/plain' });
response.end(ret);
});
};
// Keep choosing keys until one isn't taken // Keep choosing keys until one isn't taken
DocumentHandler.prototype.chooseKey = function(callback) { DocumentHandler.prototype.chooseKey = function(callback) {
var key = this.acceptableKey(); var key = this.acceptableKey();

View file

@ -25,7 +25,7 @@ FileDocumentStore.prototype.set = function(key, data, callback, skipExpire) {
try { try {
var _this = this; var _this = this;
fs.mkdir(this.basePath, '700', function() { fs.mkdir(this.basePath, '700', function() {
var fn = _this.basePath + '/' + FileDocumentStore.md5(key); var fn = _this.basePath + '/' + key;
fs.writeFile(fn, data, 'utf8', function(err) { fs.writeFile(fn, data, 'utf8', function(err) {
if (err) { if (err) {
callback(false); callback(false);
@ -46,7 +46,7 @@ FileDocumentStore.prototype.set = function(key, data, callback, skipExpire) {
// Get data from a file from key // Get data from a file from key
FileDocumentStore.prototype.get = function(key, callback, skipExpire) { FileDocumentStore.prototype.get = function(key, callback, skipExpire) {
var _this = this; var _this = this;
var fn = this.basePath + '/' + FileDocumentStore.md5(key); var fn = this.basePath + '/' + key;
fs.readFile(fn, 'utf8', function(err, data) { fs.readFile(fn, 'utf8', function(err, data) {
if (err) { if (err) {
callback(false); callback(false);
@ -60,4 +60,32 @@ FileDocumentStore.prototype.get = function(key, callback, skipExpire) {
}); });
}; };
// delete a file from key
FileDocumentStore.prototype.delete = function(key, callback) {
var _this = this;
var fn = this.basePath + '/' + key;
fs.unlink(fn, function(err) {
if (err) {
callback(false);
}
else {
callback(true);
}
});
};
// list files
FileDocumentStore.prototype.list = function(callback) {
var _this = this;
var fn = this.basePath + '/';
fs.readdir(fn, function(err, listfiles) {
if (err) {
callback(false);
}
else {
callback(listfiles);
}
});
};
module.exports = FileDocumentStore; module.exports = FileDocumentStore;

View file

@ -1,18 +1,17 @@
{ {
"name": "haste", "name": "hastepad",
"version": "0.1.0", "version": "0.1.1",
"private": true, "private": true,
"description": "Private Pastebin Server", "description": "Private OnlineNotepad Server",
"keywords": [ "keywords": [
"paste", "Notepad"
"pastebin"
], ],
"author": { "author": {
"name": "John Crepezzi", "name": "John Crepezzi",
"email": "john.crepezzi@gmail.com", "email": "john.crepezzi@gmail.com",
"url": "http://seejohncode.com/" "url": "http://seejohncode.com/"
}, },
"main": "haste", "main": "hastepad",
"dependencies": { "dependencies": {
"busboy": "0.2.4", "busboy": "0.2.4",
"connect": "^3.7.0", "connect": "^3.7.0",

View file

@ -11,7 +11,7 @@ var connect_rate_limit = require('connect-ratelimit');
var DocumentHandler = require('./lib/document_handler'); var DocumentHandler = require('./lib/document_handler');
// Load the configuration and set some defaults // Load the configuration and set some defaults
const configPath = process.argv.length <= 2 ? 'config.js' : process.argv[2]; const configPath = process.argv.length <= 2 ? 'config.json' : process.argv[2];
const config = JSON.parse(fs.readFileSync(configPath, 'utf8')); const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
config.port = process.env.PORT || config.port || 7777; config.port = process.env.PORT || config.port || 7777;
config.host = process.env.HOST || config.host || 'localhost'; config.host = process.env.HOST || config.host || 'localhost';
@ -125,14 +125,35 @@ app.use(route(function(router) {
return documentHandler.handlePost(request, response); return documentHandler.handlePost(request, response);
}); });
// update documents
router.post('/documents/:id', function(request, response) {
return documentHandler.handlePost(request, response);
});
// get documents // get documents
router.get('/documents/:id', function(request, response) { router.get('/documents/:id', function(request, response) {
return documentHandler.handleGet(request, response, config); return documentHandler.handleGet(request, response, config);
}); });
// get documents
router.delete('/documents/:id', function(request, response) {
return documentHandler.handleDelete(request, response, config);
});
router.head('/documents/:id', function(request, response) { router.head('/documents/:id', function(request, response) {
return documentHandler.handleGet(request, response, config); return documentHandler.handleGet(request, response, config);
}); });
// list documents
router.get('/documents', function(request, response) {
return documentHandler.handleList(request, response, config);
});
// get key
router.get('/key', function(request, response) {
return documentHandler.handleGetKey(request, response, config);
});
})); }));
// Otherwise, try to match static files // Otherwise, try to match static files

View file

@ -54,19 +54,18 @@ haste_document.prototype.load = function(key, callback, lang) {
}; };
// Save this document to the server and lock it here // Save this document to the server and lock it here
haste_document.prototype.save = function(data, callback) { haste_document.prototype.save = function(key, data, callback) {
if (this.locked) { if (this.locked) {
return false; return false;
} }
this.data = data; this.data = data;
var _this = this; var _this = this;
$.ajax('/documents', { $.ajax('/documents/' + key, {
type: 'post', type: 'post',
data: data, data: data,
dataType: 'json', dataType: 'json',
contentType: 'text/plain; charset=utf-8', contentType: 'text/plain; charset=utf-8',
success: function(res) { success: function(res) {
_this.locked = true;
_this.key = res.key; _this.key = res.key;
var high = hljs.highlightAuto(data); var high = hljs.highlightAuto(data);
callback(null, { callback(null, {
@ -87,6 +86,24 @@ haste_document.prototype.save = function(data, callback) {
}); });
}; };
// get a valid key from server
haste_document.prototype.getkey = function(callback) {
$.ajax('/key/', {
type: 'get',
success: function(res) {
callback(null, res);
},
error: function(res) {
try {
callback($.parseJSON(res.responseText));
}
catch (e) {
callback({message: 'Something went wrong!'});
}
}
});
};
///// represents the paste application ///// represents the paste application
var haste = function(appName, options) { var haste = function(appName, options) {
@ -129,6 +146,11 @@ haste.prototype.fullKey = function() {
this.configureKey(['new', 'duplicate', 'twitter', 'raw']); this.configureKey(['new', 'duplicate', 'twitter', 'raw']);
}; };
// Show all the keys
haste.prototype.allKey = function() {
this.configureKey(['new', 'save', 'duplicate', 'twitter', 'raw']);
};
// Set the key up for certain things to be enabled // Set the key up for certain things to be enabled
haste.prototype.configureKey = function(enable) { haste.prototype.configureKey = function(enable) {
var $this, i = 0; var $this, i = 0;
@ -144,20 +166,60 @@ haste.prototype.configureKey = function(enable) {
}); });
}; };
haste.prototype.getCurrentKey = function () {
var key = window.location.pathname;
if (key == "/")
key = null;
else
key = key.substr(1);
return key;
}
// Get this document from the server and lock it here
haste.prototype.getList = function(callback) {
var _this = this;
$.ajax('/documents/', {
type: 'get',
dataType: 'json',
success: function(res) {
callback(res);
},
error: function() {
callback(false);
}
});
};
// Remove the current document (if there is one) // Remove the current document (if there is one)
// and set up for a new one // and set up for a new one
haste.prototype.newDocument = function(hideHistory) { haste.prototype.newDocument = function(forcenewkey, callback) {
this.$box.hide(); var _this = this;
this.doc = new haste_document();
if (!hideHistory) { var key = this.getCurrentKey();
window.history.pushState(null, this.appName, '/'); var newdoc = function(key) {
window.history.pushState(null, _this.appName, '/'+key);
_this.$box.hide();
_this.doc = new haste_document();
_this.setTitle();
_this.lightKey();
_this.$textarea.val('').show('fast', function() {
this.focus();
});
_this.removeLineNumbers();
if (callback) {
callback(_this.doc);
}
} }
this.setTitle(); if (key&&!forcenewkey) {
this.lightKey(); newdoc(key);
this.$textarea.val('').show('fast', function() { } else {
this.focus(); haste_document.prototype.getkey(function (err, key) {
}); newdoc(key);
this.removeLineNumbers(); });
}
_this.updateList();
}; };
// Map of common extensions // Map of common extensions
@ -210,7 +272,7 @@ haste.prototype.loadDocument = function(key) {
// Ask for what we want // Ask for what we want
var _this = this; var _this = this;
_this.doc = new haste_document(); _this.doc = new haste_document();
_this.doc.load(parts[0], function(ret) { _this.doc.load(key, function(ret) {
if (ret) { if (ret) {
_this.$code.html(ret.value); _this.$code.html(ret.value);
_this.setTitle(ret.key); _this.setTitle(ret.key);
@ -223,32 +285,31 @@ haste.prototype.loadDocument = function(key) {
_this.newDocument(); _this.newDocument();
} }
}, this.lookupTypeByExtension(parts[1])); }, this.lookupTypeByExtension(parts[1]));
this.updateList();
}; };
// Duplicate the current document - only if locked // Duplicate the current document - only if locked
haste.prototype.duplicateDocument = function() { haste.prototype.duplicateDocument = function() {
if (this.doc.locked) { var _this = this;
var currentData = this.doc.data; if (_this.doc.locked) {
this.newDocument(); var currentData = _this.doc.data;
this.$textarea.val(currentData); _this.newDocument(true, function () {
_this.$textarea.val(currentData);
});
} }
}; };
// Lock the current document // Lock the current document
haste.prototype.lockDocument = function() { haste.prototype.lockDocument = function() {
var _this = this; var _this = this;
this.doc.save(this.$textarea.val(), function(err, ret) { this.doc.save(this.getCurrentKey(), this.$textarea.val(), function(err, ret) {
if (err) { if (err) {
_this.showMessage(err.message, 'error'); _this.showMessage(err.message, 'error');
} }
else if (ret) { else if (ret) {
_this.doc.locked = true;
_this.$code.html(ret.value); _this.$code.html(ret.value);
_this.setTitle(ret.key); _this.setTitle(ret.key);
var file = '/' + ret.key;
if (ret.language) {
file += '.' + _this.lookupExtensionByType(ret.language);
}
window.history.pushState(null, _this.appName + '-' + ret.key, file);
_this.fullKey(); _this.fullKey();
_this.$textarea.val('').hide(); _this.$textarea.val('').hide();
_this.$box.show().focus(); _this.$box.show().focus();
@ -257,6 +318,16 @@ haste.prototype.lockDocument = function() {
}); });
}; };
// UnLock the current document
haste.prototype.unlockDocument = function() {
var _this = this;
_this.$textarea.val(_this.$code.text()).show().focus();
_this.$box.hide();
_this.allKey();
_this.removeLineNumbers();
_this.doc.locked = false;
};
haste.prototype.configureButtons = function() { haste.prototype.configureButtons = function() {
var _this = this; var _this = this;
this.buttons = [ this.buttons = [
@ -281,7 +352,7 @@ haste.prototype.configureButtons = function() {
}, },
shortcutDescription: 'control + n', shortcutDescription: 'control + n',
action: function() { action: function() {
_this.newDocument(!_this.doc.key); _this.newDocument(true);
} }
}, },
{ {
@ -308,13 +379,14 @@ haste.prototype.configureButtons = function() {
}, },
{ {
$where: $('#box2 .twitter'), $where: $('#box2 .twitter'),
label: 'Twitter', label: 'Edit',
shortcut: function(evt) { shortcut: function(evt) {
return _this.options.twitter && _this.doc.locked && evt.shiftKey && evt.ctrlKey && evt.keyCode == 84; return _this.options.twitter && _this.doc.locked && evt.shiftKey && evt.ctrlKey && evt.keyCode == 84;
}, },
shortcutDescription: 'control + shift + t', shortcutDescription: 'control + shift + t',
action: function() { action: function() {
window.open('https://twitter.com/share?url=' + encodeURI(window.location.href)); //window.open('https://twitter.com/share?url=' + encodeURI(window.location.href));
_this.unlockDocument();
} }
} }
]; ];
@ -345,6 +417,19 @@ haste.prototype.configureButton = function(options) {
}); });
}; };
haste.prototype.updateList = function () {
var _this = this;
_this.getList(function (lst) {
if (lst) {
var lis = "";
lst.forEach(function (file) {
lis+= '<li><a href="'+file+'">'+file+'</a></li>';
});
$('#list').html(lis);
}
});
};
// Configure keyboard shortcuts for the textarea // Configure keyboard shortcuts for the textarea
haste.prototype.configureShortcuts = function() { haste.prototype.configureShortcuts = function() {
var _this = this; var _this = this;
@ -361,6 +446,27 @@ haste.prototype.configureShortcuts = function() {
}); });
}; };
haste.prototype.autosave = function() {
var _this = this;
_this.$textarea.on('keydown', function () {
_this.doc.changed = true;
});
var cycle = function () {
if ((_this.doc)&&(!_this.doc.locked)&&(_this.doc.changed)) {
_this.doc.save(_this.getCurrentKey(), _this.$textarea.val(), function (err, data) {
if (err) {
_this.showMessage("Error "+err);
}
});
_this.doc.changed = false;
}
window.setTimeout(cycle, 15000);
};
window.setTimeout(cycle, 5000);
};
///// Tab behavior in the textarea - 2 spaces per tab ///// Tab behavior in the textarea - 2 spaces per tab
$(function() { $(function() {

File diff suppressed because one or more lines are too long

View file

@ -32,6 +32,7 @@
// Construct app and load initial path // Construct app and load initial path
$(function() { $(function() {
app = new haste('hastebin', { twitter: true }); app = new haste('hastebin', { twitter: true });
app.autosave();
handlePop({ target: window }); handlePop({ target: window });
}); });
</script> </script>
@ -51,12 +52,15 @@
<button class="new function button-picture">New</button> <button class="new function button-picture">New</button>
<button class="duplicate function button-picture">Duplicate & Edit</button> <button class="duplicate function button-picture">Duplicate & Edit</button>
<button class="raw function button-picture">Just Text</button> <button class="raw function button-picture">Just Text</button>
<button class="twitter function button-picture">Twitter</button> <button class="twitter function button-picture">Edit</button>
</div> </div>
<div id="box3" style="display:none;"> <div id="box3" style="display:none;">
<div class="label"></div> <div class="label"></div>
<div class="shortcut"></div> <div class="shortcut"></div>
</div> </div>
<div id="box4">
<ul id="list"></ul>
</div>
</div> </div>
<div id="linenos"></div> <div id="linenos"></div>