Compare commits
21 commits
master
...
paste-mana
Author | SHA1 | Date | |
---|---|---|---|
|
f66c690fc0 | ||
|
c37052fe82 | ||
|
7e30134d1f | ||
|
2b67f0369d | ||
|
b2751ca444 | ||
|
ddfccb338c | ||
|
85943a9ce1 | ||
|
2b9abb8db4 | ||
|
e531592b05 | ||
|
d027cb188d | ||
|
4e3348795c | ||
|
2d9666c5ef | ||
|
521f134638 | ||
|
ea93c474ea | ||
|
af02731002 | ||
|
8b170397a9 | ||
|
ac32826009 | ||
|
c4830044f7 | ||
|
daae0d0dca | ||
|
283d85c7cf | ||
|
8537f396b3 |
13 changed files with 467 additions and 111 deletions
1
.gitattributes
vendored
1
.gitattributes
vendored
|
@ -20,6 +20,7 @@ js/test/ export-ignore
|
||||||
.travis.yml export-ignore
|
.travis.yml export-ignore
|
||||||
composer.json export-ignore
|
composer.json export-ignore
|
||||||
composer.lock export-ignore
|
composer.lock export-ignore
|
||||||
|
crowdin.yml export-ignore
|
||||||
BADGES.md export-ignore
|
BADGES.md export-ignore
|
||||||
CODE_OF_CONDUCT.md export-ignore
|
CODE_OF_CONDUCT.md export-ignore
|
||||||
Makefile export-ignore
|
Makefile export-ignore
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
# PrivateBin version history
|
# PrivateBin version history
|
||||||
|
|
||||||
* **1.4 (not yet released)**
|
* **1.4 (not yet released)**
|
||||||
* CHANGED: Upgrading libraries to: DOMpurify 2.0.14
|
* ADDED: Translation for Hebrew
|
||||||
|
* CHANGED: Upgrading libraries to: DOMpurify 2.1.1
|
||||||
* **1.3.4 (2020-03-22)**
|
* **1.3.4 (2020-03-22)**
|
||||||
* CHANGED: Minimum required PHP version is 5.6, due to a change in the identicon library and to use php's native hash_equals()
|
* CHANGED: Minimum required PHP version is 5.6, due to a change in the identicon library and to use php's native hash_equals()
|
||||||
* CHANGED: Upgrading libraries to: identicon 2.0.0
|
* CHANGED: Upgrading libraries to: identicon 2.0.0
|
||||||
|
|
|
@ -46,3 +46,4 @@ Sébastien Sauvage - original idea and main developer
|
||||||
* info-path - Czech
|
* info-path - Czech
|
||||||
* BigWax - Bulgarian
|
* BigWax - Bulgarian
|
||||||
* AndriiZ - Ukrainian
|
* AndriiZ - Ukrainian
|
||||||
|
* Yaron Shahrabani - Hebrew
|
||||||
|
|
|
@ -205,3 +205,43 @@ li.L0, li.L1, li.L2, li.L3, li.L5, li.L6, li.L7, li.L8 {
|
||||||
.modal .modal-content button {
|
.modal .modal-content button {
|
||||||
margin: 0.5em 0;
|
margin: 0.5em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* sidebar */
|
||||||
|
#menu-toggle {
|
||||||
|
position: relative;
|
||||||
|
bottom: -94px;
|
||||||
|
left: -61px;
|
||||||
|
margin-bottom: -12px;
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
#menu-toggle {
|
||||||
|
position: static;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
transform: rotate(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
main.toggled {
|
||||||
|
padding-left: 450px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sidebar-wrapper {
|
||||||
|
position: fixed;
|
||||||
|
width: 450px;
|
||||||
|
height: 100%;
|
||||||
|
left: -450px;
|
||||||
|
padding-left: 4ch;
|
||||||
|
overflow-y: scroll;
|
||||||
|
overflow-x: hidden;
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
main.toggled #sidebar-wrapper {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
|
@ -8,6 +8,8 @@ global.cleanup = global.jsdom();
|
||||||
global.URL = require('jsdom-url').URL;
|
global.URL = require('jsdom-url').URL;
|
||||||
global.fs = require('fs');
|
global.fs = require('fs');
|
||||||
global.WebCrypto = require('@peculiar/webcrypto').Crypto;
|
global.WebCrypto = require('@peculiar/webcrypto').Crypto;
|
||||||
|
require('fake-indexeddb/auto');
|
||||||
|
global.FDBFactory = require('fake-indexeddb/lib/FDBFactory');
|
||||||
|
|
||||||
// application libraries to test
|
// application libraries to test
|
||||||
global.$ = global.jQuery = require('./jquery-3.4.1');
|
global.$ = global.jQuery = require('./jquery-3.4.1');
|
||||||
|
@ -17,7 +19,7 @@ require('./prettify');
|
||||||
global.prettyPrint = window.PR.prettyPrint;
|
global.prettyPrint = window.PR.prettyPrint;
|
||||||
global.prettyPrintOne = window.PR.prettyPrintOne;
|
global.prettyPrintOne = window.PR.prettyPrintOne;
|
||||||
global.showdown = require('./showdown-1.9.1');
|
global.showdown = require('./showdown-1.9.1');
|
||||||
global.DOMPurify = require('./purify-2.0.14');
|
global.DOMPurify = require('./purify-2.1.1');
|
||||||
global.baseX = require('./base-x-3.0.7').baseX;
|
global.baseX = require('./base-x-3.0.7').baseX;
|
||||||
global.Legacy = require('./legacy').Legacy;
|
global.Legacy = require('./legacy').Legacy;
|
||||||
require('./bootstrap-3.3.7');
|
require('./bootstrap-3.3.7');
|
||||||
|
|
|
@ -8,11 +8,12 @@
|
||||||
},
|
},
|
||||||
"dependencies": {},
|
"dependencies": {},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@peculiar/webcrypto": "^1.1.1",
|
||||||
|
"fake-indexeddb": "^3.0.2",
|
||||||
"jsdom": "^9.12.0",
|
"jsdom": "^9.12.0",
|
||||||
"jsdom-global": "^2.1.1",
|
"jsdom-global": "^2.1.1",
|
||||||
"jsdom-url": "^2.2.1",
|
"jsdom-url": "^2.2.1",
|
||||||
"jsverify": "^0.8.3",
|
"jsverify": "^0.8.3"
|
||||||
"@peculiar/webcrypto": "^1.1.1"
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "mocha"
|
"test": "mocha"
|
||||||
|
|
363
js/privatebin.js
363
js/privatebin.js
|
@ -12,9 +12,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// global Base64, DOMPurify, FileReader, RawDeflate, history, navigator, prettyPrint, prettyPrintOne, showdown, kjua
|
// global Base64, DOMPurify, FileReader, RawDeflate, history, navigator, prettyPrint, prettyPrintOne, showdown, kjua
|
||||||
|
'use strict';
|
||||||
|
|
||||||
jQuery.fn.draghover = function() {
|
jQuery.fn.draghover = function() {
|
||||||
'use strict';
|
|
||||||
return this.each(function() {
|
return this.each(function() {
|
||||||
let collection = $(),
|
let collection = $(),
|
||||||
self = $(this);
|
self = $(this);
|
||||||
|
@ -37,14 +37,11 @@ jQuery.fn.draghover = function() {
|
||||||
|
|
||||||
// main application start, called when DOM is fully loaded
|
// main application start, called when DOM is fully loaded
|
||||||
jQuery(document).ready(function() {
|
jQuery(document).ready(function() {
|
||||||
'use strict';
|
|
||||||
// run main controller
|
// run main controller
|
||||||
$.PrivateBin.Controller.init();
|
$.PrivateBin.Controller.init();
|
||||||
});
|
});
|
||||||
|
|
||||||
jQuery.PrivateBin = (function($, RawDeflate) {
|
jQuery.PrivateBin = (function($, RawDeflate) {
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* zlib library interface
|
* zlib library interface
|
||||||
*
|
*
|
||||||
|
@ -565,10 +562,11 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
||||||
*
|
*
|
||||||
* @name Helper.reset
|
* @name Helper.reset
|
||||||
* @function
|
* @function
|
||||||
|
* @param {string} uri - (optional) URI to reset to
|
||||||
*/
|
*/
|
||||||
me.reset = function()
|
me.reset = function(uri)
|
||||||
{
|
{
|
||||||
baseUri = null;
|
baseUri = typeof uri === 'string' ? uri : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
return me;
|
return me;
|
||||||
|
@ -3521,6 +3519,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
||||||
$emailLink,
|
$emailLink,
|
||||||
$sendButton,
|
$sendButton,
|
||||||
$retryButton,
|
$retryButton,
|
||||||
|
$rememberButton,
|
||||||
pasteExpiration = null,
|
pasteExpiration = null,
|
||||||
retryButtonCallback;
|
retryButtonCallback;
|
||||||
|
|
||||||
|
@ -3885,6 +3884,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
||||||
$cloneButton.removeClass('hidden');
|
$cloneButton.removeClass('hidden');
|
||||||
$rawTextButton.removeClass('hidden');
|
$rawTextButton.removeClass('hidden');
|
||||||
$qrCodeLink.removeClass('hidden');
|
$qrCodeLink.removeClass('hidden');
|
||||||
|
$rememberButton.removeClass('hidden');
|
||||||
|
|
||||||
viewButtonsDisplayed = true;
|
viewButtonsDisplayed = true;
|
||||||
};
|
};
|
||||||
|
@ -3905,6 +3905,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
||||||
$newButton.addClass('hidden');
|
$newButton.addClass('hidden');
|
||||||
$rawTextButton.addClass('hidden');
|
$rawTextButton.addClass('hidden');
|
||||||
$qrCodeLink.addClass('hidden');
|
$qrCodeLink.addClass('hidden');
|
||||||
|
$rememberButton.addClass('hidden');
|
||||||
me.hideEmailButton();
|
me.hideEmailButton();
|
||||||
|
|
||||||
viewButtonsDisplayed = false;
|
viewButtonsDisplayed = false;
|
||||||
|
@ -3970,17 +3971,6 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
||||||
createButtonsDisplayed = false;
|
createButtonsDisplayed = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* only shows the "new paste" button
|
|
||||||
*
|
|
||||||
* @name TopNav.showNewPasteButton
|
|
||||||
* @function
|
|
||||||
*/
|
|
||||||
me.showNewPasteButton = function()
|
|
||||||
{
|
|
||||||
$newButton.removeClass('hidden');
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* only shows the "retry" button
|
* only shows the "retry" button
|
||||||
*
|
*
|
||||||
|
@ -4043,17 +4033,6 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
||||||
$emailLink.off('click.sendEmail');
|
$emailLink.off('click.sendEmail');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* only hides the clone button
|
|
||||||
*
|
|
||||||
* @name TopNav.hideCloneButton
|
|
||||||
* @function
|
|
||||||
*/
|
|
||||||
me.hideCloneButton = function()
|
|
||||||
{
|
|
||||||
$cloneButton.addClass('hidden');
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* only hides the raw text button
|
* only hides the raw text button
|
||||||
*
|
*
|
||||||
|
@ -4065,17 +4044,6 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
||||||
$rawTextButton.addClass('hidden');
|
$rawTextButton.addClass('hidden');
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* only hides the qr code button
|
|
||||||
*
|
|
||||||
* @name TopNav.hideQrCodeButton
|
|
||||||
* @function
|
|
||||||
*/
|
|
||||||
me.hideQrCodeButton = function()
|
|
||||||
{
|
|
||||||
$qrCodeLink.addClass('hidden');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* hide all irrelevant buttons when viewing burn after reading paste
|
* hide all irrelevant buttons when viewing burn after reading paste
|
||||||
*
|
*
|
||||||
|
@ -4084,8 +4052,8 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
||||||
*/
|
*/
|
||||||
me.hideBurnAfterReadingButtons = function()
|
me.hideBurnAfterReadingButtons = function()
|
||||||
{
|
{
|
||||||
me.hideCloneButton();
|
$cloneButton.addClass('hidden');
|
||||||
me.hideQrCodeButton();
|
$qrCodeLink.addClass('hidden');
|
||||||
me.hideEmailButton();
|
me.hideEmailButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4325,6 +4293,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
||||||
$sendButton = $('#sendbutton');
|
$sendButton = $('#sendbutton');
|
||||||
$qrCodeLink = $('#qrcodelink');
|
$qrCodeLink = $('#qrcodelink');
|
||||||
$emailLink = $('#emaillink');
|
$emailLink = $('#emaillink');
|
||||||
|
$rememberButton = $('#rememberbutton');
|
||||||
|
|
||||||
// bootstrap template drop down
|
// bootstrap template drop down
|
||||||
$('#language ul.dropdown-menu li a').click(setLanguage);
|
$('#language ul.dropdown-menu li a').click(setLanguage);
|
||||||
|
@ -4360,6 +4329,312 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
||||||
return me;
|
return me;
|
||||||
})(window, document);
|
})(window, document);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (view) Handles the memory, storing paste URLs in the browser
|
||||||
|
*
|
||||||
|
* @name Memory
|
||||||
|
* @class
|
||||||
|
*/
|
||||||
|
const Memory = (function (document, window) {
|
||||||
|
const me = {};
|
||||||
|
|
||||||
|
let pastes = [],
|
||||||
|
db;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* checks if the given URL was already memorized
|
||||||
|
*
|
||||||
|
* @name Memory.isInMemory
|
||||||
|
* @private
|
||||||
|
* @function
|
||||||
|
* @param {string} pasteUrl
|
||||||
|
* @return {bool}
|
||||||
|
*/
|
||||||
|
function isInMemory(pasteUrl)
|
||||||
|
{
|
||||||
|
return pastes.filter(
|
||||||
|
function(memorizedPaste) {
|
||||||
|
return memorizedPaste.url === pasteUrl;
|
||||||
|
}
|
||||||
|
).length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sort the memory list by the given column offset
|
||||||
|
*
|
||||||
|
* @name Memory.sort
|
||||||
|
* @private
|
||||||
|
* @function
|
||||||
|
* @param {int} column
|
||||||
|
*/
|
||||||
|
function sort(column)
|
||||||
|
{
|
||||||
|
let i, x, y, shouldSwitch, dir, switchcount = 0;
|
||||||
|
let table = $('#sidebar-wrapper table')[0];
|
||||||
|
let header = $('#sidebar-wrapper table thead tr th')[column];
|
||||||
|
let switching = true;
|
||||||
|
if (header.dataset.direction == 'asc') {
|
||||||
|
header.dataset.direction = 'desc';
|
||||||
|
} else {
|
||||||
|
header.dataset.direction = 'asc';
|
||||||
|
}
|
||||||
|
while (switching) {
|
||||||
|
switching = false;
|
||||||
|
let rows = table.rows;
|
||||||
|
// skip first row, containing headers
|
||||||
|
for (i = 1; i < (rows.length - 1); i++) {
|
||||||
|
shouldSwitch = false;
|
||||||
|
x = rows[i].getElementsByTagName("td")[column];
|
||||||
|
y = rows[i + 1].getElementsByTagName("td")[column];
|
||||||
|
if (header.dataset.direction == 'asc') {
|
||||||
|
if (x.innerHTML.toLowerCase() > y.innerHTML.toLowerCase()) {
|
||||||
|
shouldSwitch = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (header.dataset.direction == 'desc') {
|
||||||
|
if (x.innerHTML.toLowerCase() < y.innerHTML.toLowerCase()) {
|
||||||
|
shouldSwitch = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (shouldSwitch) {
|
||||||
|
rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
|
||||||
|
switching = true;
|
||||||
|
switchcount ++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* called after successfully connecting to the indexedDB
|
||||||
|
*
|
||||||
|
* @name Memory.updateCacheFromDb
|
||||||
|
* @private
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
function updateCacheFromDb()
|
||||||
|
{
|
||||||
|
const memory = db.transaction('pastes').objectStore('pastes');
|
||||||
|
memory.openCursor().onsuccess = function(e) {
|
||||||
|
let cursor = e.target.result;
|
||||||
|
if (cursor) {
|
||||||
|
pastes.push(cursor.value);
|
||||||
|
cursor.continue();
|
||||||
|
} else {
|
||||||
|
me.refreshList();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* adds a paste URL to the memory
|
||||||
|
*
|
||||||
|
* @name Memory.add
|
||||||
|
* @function
|
||||||
|
* @param {string} pasteUrl
|
||||||
|
* @return {bool}
|
||||||
|
*/
|
||||||
|
me.add = function(pasteUrl)
|
||||||
|
{
|
||||||
|
const url = new URL(pasteUrl),
|
||||||
|
newPaste = {
|
||||||
|
'https': url.protocol == 'https:',
|
||||||
|
'service': url.hostname + (url.pathname.length > 1 ? url.pathname : ''),
|
||||||
|
'pasteid': url.search.replace(/^\?+/, ''),
|
||||||
|
'key': url.hash.replace(/^#+/, ''),
|
||||||
|
// we store the full URL as it may contain additonal information
|
||||||
|
// required to open the paste, like port, username and password
|
||||||
|
'url': pasteUrl
|
||||||
|
};
|
||||||
|
// don't add an already memorized paste
|
||||||
|
if (isInMemory(pasteUrl)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
pastes.push(newPaste);
|
||||||
|
if (!window.indexedDB || !db) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const memory = db.transaction('pastes', 'readwrite').objectStore('pastes'),
|
||||||
|
request = memory.add(newPaste);
|
||||||
|
request.onsuccess = function(e) {
|
||||||
|
me.refreshList();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* open a given paste URL using the current instance
|
||||||
|
*
|
||||||
|
* @name Memory.open
|
||||||
|
* @function
|
||||||
|
* @param {string} pasteUrl
|
||||||
|
*/
|
||||||
|
me.open = function(pasteUrl)
|
||||||
|
{
|
||||||
|
// parse URL
|
||||||
|
const url = new URL(pasteUrl);
|
||||||
|
const baseUri = Helper.baseUri();
|
||||||
|
history.pushState({type: 'viewpaste'}, document.title, url.search + url.hash);
|
||||||
|
Helper.reset(url.origin);
|
||||||
|
Model.reset();
|
||||||
|
PasteDecrypter.run();
|
||||||
|
Helper.reset(baseUri);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* refresh the state of the remember button
|
||||||
|
*
|
||||||
|
* @name Memory.refreshButton
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
me.refreshButton = function()
|
||||||
|
{
|
||||||
|
if (isInMemory(window.location.href)) {
|
||||||
|
$('#rememberbutton').addClass('disabled');
|
||||||
|
} else {
|
||||||
|
$('#rememberbutton').removeClass('disabled');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* refresh the displayed list from memory
|
||||||
|
*
|
||||||
|
* @name Memory.refreshList
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
me.refreshList = function()
|
||||||
|
{
|
||||||
|
me.refreshButton();
|
||||||
|
const $tbody = $('#sidebar-wrapper table tbody')[0];
|
||||||
|
if (!$tbody) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$tbody.textContent = '';
|
||||||
|
pastes.forEach(function(paste) {
|
||||||
|
const row = document.createElement('tr');
|
||||||
|
|
||||||
|
let cell = document.createElement('td');
|
||||||
|
let input = document.createElement('input');
|
||||||
|
input.setAttribute('type', 'checkbox');
|
||||||
|
input.setAttribute('name', 'memoryselect');
|
||||||
|
input.setAttribute('value', paste.pasteid);
|
||||||
|
cell.appendChild(input);
|
||||||
|
row.appendChild(cell);
|
||||||
|
|
||||||
|
cell = document.createElement('td');
|
||||||
|
cell.textContent = paste.https ? '✔' : '✘';
|
||||||
|
row.appendChild(cell);
|
||||||
|
|
||||||
|
cell = document.createElement('td');
|
||||||
|
cell.textContent = paste.service;
|
||||||
|
row.appendChild(cell);
|
||||||
|
|
||||||
|
cell = document.createElement('td');
|
||||||
|
cell.textContent = paste.pasteid;
|
||||||
|
row.appendChild(cell);
|
||||||
|
|
||||||
|
cell = document.createElement('td');
|
||||||
|
cell.addEventListener('click', function () {
|
||||||
|
navigator.clipboard.writeText(paste.url);
|
||||||
|
});
|
||||||
|
let button = document.createElement('button');
|
||||||
|
button.setAttribute('class', 'btn btn-info btn-xs');
|
||||||
|
button.setAttribute('title', I18n._('Copy paste URL'));
|
||||||
|
let span = document.createElement('span');
|
||||||
|
span.setAttribute('class', 'glyphicon glyphicon-duplicate');
|
||||||
|
span.setAttribute('aria-hidden', 'true');
|
||||||
|
button.appendChild(span);
|
||||||
|
cell.appendChild(button);
|
||||||
|
row.appendChild(cell);
|
||||||
|
|
||||||
|
$tbody.appendChild(row);
|
||||||
|
row.addEventListener('click', function () {
|
||||||
|
me.open(paste.url);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* initiate
|
||||||
|
*
|
||||||
|
* attaches click event to toggle memory sidepanel
|
||||||
|
*
|
||||||
|
* @name Memory.init
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
me.init = function()
|
||||||
|
{
|
||||||
|
pastes = [];
|
||||||
|
if (!window.indexedDB) {
|
||||||
|
$('#menu-toggle').hide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const request = window.indexedDB.open('privatebin', 1);
|
||||||
|
request.onerror = function(e) {
|
||||||
|
Alert.showWarning('Unable to open indexedDB, memory will not be available in this session.');
|
||||||
|
$('#menu-toggle').hide();
|
||||||
|
};
|
||||||
|
request.onupgradeneeded = function(event) {
|
||||||
|
const newDb = event.target.result;
|
||||||
|
const objectStore = newDb.createObjectStore('pastes', {keyPath: 'pasteid'});
|
||||||
|
objectStore.createIndex('https', 'https', {unique: false});
|
||||||
|
objectStore.createIndex('service', 'service', {unique: false});
|
||||||
|
objectStore.createIndex('pasteid', 'pasteid', {unique: true});
|
||||||
|
objectStore.transaction.oncomplete = function(e) {
|
||||||
|
db = newDb;
|
||||||
|
updateCacheFromDb();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
request.onsuccess = function(e) {
|
||||||
|
db = request.result;
|
||||||
|
db.onerror = function(e) {
|
||||||
|
Alert.showError(e.target.error.message);
|
||||||
|
}
|
||||||
|
updateCacheFromDb();
|
||||||
|
};
|
||||||
|
|
||||||
|
$('#forgetbutton').on('click', function(e) {
|
||||||
|
const memory = db.transaction('pastes', 'readwrite').objectStore('pastes');
|
||||||
|
for (const checkbox of document.getElementsByName('memoryselect')) {
|
||||||
|
if (checkbox.checked) {
|
||||||
|
const request = memory.delete(checkbox.value);
|
||||||
|
request.onsuccess = function(e) {
|
||||||
|
pastes = [];
|
||||||
|
$('#sidebar-wrapper table tbody').empty();
|
||||||
|
updateCacheFromDb();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#memoryselectall').on('click', function(e) {
|
||||||
|
const checkedState = document.getElementById('memoryselectall').checked;
|
||||||
|
for (const checkbox of document.getElementsByName('memoryselect')) {
|
||||||
|
checkbox.checked = checkedState;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#menu-toggle').on('click', function(e) {
|
||||||
|
$('main').toggleClass('toggled');
|
||||||
|
$('#menu-toggle .glyphicon').toggleClass('glyphicon glyphicon-menu-down glyphicon glyphicon-menu-up')
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#rememberbutton').on('click', function(e) {
|
||||||
|
if (me.add(window.location.href))
|
||||||
|
$('#menu-toggle').click();
|
||||||
|
});
|
||||||
|
const headers = $('#sidebar-wrapper table thead tr th');
|
||||||
|
for (let i = 1; i < headers.length; i++) {
|
||||||
|
headers[i].addEventListener('click', function(e) {
|
||||||
|
sort(i);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return me;
|
||||||
|
})(document, window);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Responsible for AJAX requests, transparently handles encryption…
|
* Responsible for AJAX requests, transparently handles encryption…
|
||||||
*
|
*
|
||||||
|
@ -4667,6 +4942,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
||||||
|
|
||||||
// show new URL in browser bar
|
// show new URL in browser bar
|
||||||
history.pushState({type: 'newpaste'}, document.title, url);
|
history.pushState({type: 'newpaste'}, document.title, url);
|
||||||
|
Memory.refreshButton();
|
||||||
|
|
||||||
TopNav.showViewButtons();
|
TopNav.showViewButtons();
|
||||||
|
|
||||||
|
@ -5337,8 +5613,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
||||||
I18n.loadTranslations();
|
I18n.loadTranslations();
|
||||||
|
|
||||||
DOMPurify.setConfig({
|
DOMPurify.setConfig({
|
||||||
ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|magnet):)/i,
|
ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|magnet):)/i
|
||||||
SAFE_FOR_JQUERY: true
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add a hook to make all links open a new window
|
// Add a hook to make all links open a new window
|
||||||
|
@ -5376,6 +5651,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
||||||
Prompt.init();
|
Prompt.init();
|
||||||
TopNav.init();
|
TopNav.init();
|
||||||
UiHelper.init();
|
UiHelper.init();
|
||||||
|
Memory.init();
|
||||||
|
|
||||||
// check for legacy browsers before going any further
|
// check for legacy browsers before going any further
|
||||||
if (!Legacy.Check.getInit()) {
|
if (!Legacy.Check.getInit()) {
|
||||||
|
@ -5429,6 +5705,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
||||||
AttachmentViewer: AttachmentViewer,
|
AttachmentViewer: AttachmentViewer,
|
||||||
DiscussionViewer: DiscussionViewer,
|
DiscussionViewer: DiscussionViewer,
|
||||||
TopNav: TopNav,
|
TopNav: TopNav,
|
||||||
|
Memory: Memory,
|
||||||
ServerInteraction: ServerInteraction,
|
ServerInteraction: ServerInteraction,
|
||||||
PasteEncrypter: PasteEncrypter,
|
PasteEncrypter: PasteEncrypter,
|
||||||
PasteDecrypter: PasteDecrypter,
|
PasteDecrypter: PasteDecrypter,
|
||||||
|
|
File diff suppressed because one or more lines are too long
2
js/purify-2.1.1.js
Normal file
2
js/purify-2.1.1.js
Normal file
File diff suppressed because one or more lines are too long
60
js/test/Memory.js
Normal file
60
js/test/Memory.js
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
'use strict';
|
||||||
|
const common = require('../common');
|
||||||
|
|
||||||
|
describe('Memory', function () {
|
||||||
|
describe('add & refreshList', function () {
|
||||||
|
this.timeout(30000);
|
||||||
|
|
||||||
|
jsc.property(
|
||||||
|
'allows adding valid paste URLs',
|
||||||
|
common.jscSchemas(),
|
||||||
|
jsc.nearray(common.jscA2zString()),
|
||||||
|
jsc.array(common.jscQueryString()),
|
||||||
|
'string',
|
||||||
|
function (schema, address, query, fragment) {
|
||||||
|
const expectedQuery = encodeURI(
|
||||||
|
query.join('').replace(/^&+|&+$/gm,'')
|
||||||
|
),
|
||||||
|
expected = schema + '://' + address.join('') + '/?' +
|
||||||
|
expectedQuery + '#' + fragment,
|
||||||
|
clean = jsdom();
|
||||||
|
$('body').html(
|
||||||
|
'<main><div id="sidebar-wrapper"><table><tbody>' +
|
||||||
|
'</tbody></table></div></main>'
|
||||||
|
);
|
||||||
|
// clear cache, then the first cell will match what we add
|
||||||
|
$.PrivateBin.Memory.init();
|
||||||
|
$.PrivateBin.Memory.add(expected);
|
||||||
|
$.PrivateBin.Memory.refreshList();
|
||||||
|
const result = $('#sidebar-wrapper table tbody tr td')[3].textContent;
|
||||||
|
clean();
|
||||||
|
return result === expectedQuery;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('init', function () {
|
||||||
|
it(
|
||||||
|
'enables toggling the memory sidebar',
|
||||||
|
function() {
|
||||||
|
$('body').html(
|
||||||
|
'<main><div id="sidebar-wrapper"></div>' +
|
||||||
|
'<button id="menu-toggle"></button></main>'
|
||||||
|
);
|
||||||
|
assert.ok(!$('main').hasClass('toggled'));
|
||||||
|
|
||||||
|
$('#menu-toggle').click();
|
||||||
|
assert.ok(!$('main').hasClass('toggled'));
|
||||||
|
|
||||||
|
$.PrivateBin.Memory.init();
|
||||||
|
assert.ok(!$('main').hasClass('toggled'));
|
||||||
|
|
||||||
|
$('#menu-toggle').click();
|
||||||
|
assert.ok($('main').hasClass('toggled'));
|
||||||
|
|
||||||
|
$('#menu-toggle').click();
|
||||||
|
assert.ok(!$('main').hasClass('toggled'));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
|
@ -118,62 +118,6 @@ describe('TopNav', function () {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('showNewPasteButton', function () {
|
|
||||||
before(function () {
|
|
||||||
cleanup();
|
|
||||||
});
|
|
||||||
|
|
||||||
it(
|
|
||||||
'displays the button for creating a paste',
|
|
||||||
function () {
|
|
||||||
var results = [];
|
|
||||||
$('body').html(
|
|
||||||
'<nav><div id="navbar"><ul><li><button id="newbutton" type=' +
|
|
||||||
'"button" class="hidden">New</button></li></ul></div></nav>'
|
|
||||||
);
|
|
||||||
$.PrivateBin.TopNav.init();
|
|
||||||
results.push(
|
|
||||||
$('#newbutton').hasClass('hidden')
|
|
||||||
);
|
|
||||||
$.PrivateBin.TopNav.showNewPasteButton();
|
|
||||||
results.push(
|
|
||||||
!$('#newbutton').hasClass('hidden')
|
|
||||||
);
|
|
||||||
cleanup();
|
|
||||||
assert.ok(results.every(element => element));
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('hideCloneButton', function () {
|
|
||||||
before(function () {
|
|
||||||
cleanup();
|
|
||||||
});
|
|
||||||
|
|
||||||
it(
|
|
||||||
'hides the button for cloning a paste',
|
|
||||||
function () {
|
|
||||||
var results = [];
|
|
||||||
$('body').html(
|
|
||||||
'<nav><div id="navbar"><ul><li><button id="clonebutton" ' +
|
|
||||||
'type="button" class="btn btn-warning navbar-btn">' +
|
|
||||||
'<span class="glyphicon glyphicon-duplicate" aria-hidden=' +
|
|
||||||
'"true"></span> Clone</button></li></ul></div></nav>'
|
|
||||||
);
|
|
||||||
$.PrivateBin.TopNav.init();
|
|
||||||
results.push(
|
|
||||||
!$('#clonebutton').hasClass('hidden')
|
|
||||||
);
|
|
||||||
$.PrivateBin.TopNav.hideCloneButton();
|
|
||||||
results.push(
|
|
||||||
$('#clonebutton').hasClass('hidden')
|
|
||||||
);
|
|
||||||
cleanup();
|
|
||||||
assert.ok(results.every(element => element));
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('hideRawButton', function () {
|
describe('hideRawButton', function () {
|
||||||
before(function () {
|
before(function () {
|
||||||
cleanup();
|
cleanup();
|
||||||
|
|
|
@ -70,9 +70,9 @@ if ($MARKDOWN) :
|
||||||
<?php
|
<?php
|
||||||
endif;
|
endif;
|
||||||
?>
|
?>
|
||||||
<script type="text/javascript" data-cfasync="false" src="js/purify-2.0.14.js" integrity="sha512-kbLhjIj/m/AW++o2eErCfqPueoX2btJo7VznhEC2YQRbVR/+Eup3w7thwDZwoCZ/gLrPxTX3W4H2KzupLg2PKA==" crossorigin="anonymous"></script>
|
<script type="text/javascript" data-cfasync="false" src="js/purify-2.1.1.js" integrity="sha512-0RqB620aQhcT40T4kxf/vx3J4DOmFsqcGu2mPha21ZqufRsth3MsiU35ffSHX0OIJbE92XSKyvNcL1I6sYhh4w==" crossorigin="anonymous"></script>
|
||||||
<script type="text/javascript" data-cfasync="false" src="js/legacy.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-LYos+qXHIRqFf5ZPNphvtTB0cgzHUizu2wwcOwcwz/VIpRv9lpcBgPYz4uq6jx0INwCAj6Fbnl5HoKiLufS2jg==" crossorigin="anonymous"></script>
|
<script type="text/javascript" data-cfasync="false" src="js/legacy.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-LYos+qXHIRqFf5ZPNphvtTB0cgzHUizu2wwcOwcwz/VIpRv9lpcBgPYz4uq6jx0INwCAj6Fbnl5HoKiLufS2jg==" crossorigin="anonymous"></script>
|
||||||
<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-GCiSgkYlcyJq3SOMOAh52rIlUAoGH8yDJzOm/NkzBorbk2qiBSjc289/RxpeZJcdu36fQObFTzLvz4Do/2LFsA==" crossorigin="anonymous"></script>
|
<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-9cJdKFvcsrk3G411+Wp5Y6ZvFE6UUMKVzCB6LLXhg1BaN/jkviL01Ox+4HzbYNflFuSYK0USVFLeCW89774A6w==" crossorigin="anonymous"></script>
|
||||||
<!-- icon -->
|
<!-- icon -->
|
||||||
<link rel="apple-touch-icon" href="<?php echo I18n::encode($BASEPATH); ?>img/apple-touch-icon.png" sizes="180x180" />
|
<link rel="apple-touch-icon" href="<?php echo I18n::encode($BASEPATH); ?>img/apple-touch-icon.png" sizes="180x180" />
|
||||||
<link rel="icon" type="image/png" href="img/favicon-32x32.png" sizes="32x32" />
|
<link rel="icon" type="image/png" href="img/favicon-32x32.png" sizes="32x32" />
|
||||||
|
@ -224,6 +224,9 @@ if ($QRCODE) :
|
||||||
<?php
|
<?php
|
||||||
endif;
|
endif;
|
||||||
?>
|
?>
|
||||||
|
<button id="rememberbutton" type="button" class="hidden btn btn-<?php echo $isDark ? 'warning' : 'default'; ?> navbar-btn">
|
||||||
|
<span class="glyphicon glyphicon-star" aria-hidden="true"></span> <?php echo I18n::_('Remember'), PHP_EOL; ?>
|
||||||
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li class="dropdown">
|
<li class="dropdown">
|
||||||
<select id="pasteExpiration" name="pasteExpiration" class="hidden">
|
<select id="pasteExpiration" name="pasteExpiration" class="hidden">
|
||||||
|
@ -447,6 +450,28 @@ if ($isCpct) :
|
||||||
endif;
|
endif;
|
||||||
?></nav>
|
?></nav>
|
||||||
<main>
|
<main>
|
||||||
|
<div id="sidebar-wrapper">
|
||||||
|
<h4><?php echo I18n::_('Memory'); ?></h4>
|
||||||
|
<p><?php echo I18n::_('The memory lets you remember different paste links. The memory is unique to each website and device.'); ?></p>
|
||||||
|
<table class="table<?php echo $isDark ? '' : ' table-striped'; ?>">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th title="<?php echo I18n::_('Select all'); ?>"><input type="checkbox" id="memoryselectall" /></th>
|
||||||
|
<th title="<?php echo I18n::_('HTTPS'); ?>">🔒</th>
|
||||||
|
<th><?php echo I18n::_('Service'); ?></th>
|
||||||
|
<th><?php echo I18n::_('ID'); ?></th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p>
|
||||||
|
<button id="forgetbutton" type="button" class="btn btn-danger navbar-btn">
|
||||||
|
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> <?php echo I18n::_('Forget'), PHP_EOL; ?>
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
<section class="container">
|
<section class="container">
|
||||||
<?php
|
<?php
|
||||||
if (strlen($NOTICE)) :
|
if (strlen($NOTICE)) :
|
||||||
|
@ -466,7 +491,7 @@ if ($FILEUPLOAD) :
|
||||||
?>
|
?>
|
||||||
<div id="attachment" role="alert" class="hidden alert alert-info">
|
<div id="attachment" role="alert" class="hidden alert alert-info">
|
||||||
<span class="glyphicon glyphicon-download-alt" aria-hidden="true"></span>
|
<span class="glyphicon glyphicon-download-alt" aria-hidden="true"></span>
|
||||||
<a class="alert-link"><?php echo I18n::_('Download attachment'), PHP_EOL; ?></a>
|
<a class="alert-link"><?php echo I18n::_('Download attachment'); ?></a>
|
||||||
</div>
|
</div>
|
||||||
<?php
|
<?php
|
||||||
endif;
|
endif;
|
||||||
|
@ -530,6 +555,10 @@ if (strlen($URLSHORTENER)) :
|
||||||
endif;
|
endif;
|
||||||
?>
|
?>
|
||||||
</div>
|
</div>
|
||||||
|
<button id="menu-toggle" class="btn btn-<?php echo $isDark ? 'warning' : 'default'; ?> btn-xs">
|
||||||
|
<span class="glyphicon glyphicon-menu-up" aria-hidden="true"></span>
|
||||||
|
<?php echo I18n::_('Memory'); ?>
|
||||||
|
</button>
|
||||||
<ul id="editorTabs" class="nav nav-tabs hidden">
|
<ul id="editorTabs" class="nav nav-tabs hidden">
|
||||||
<li role="presentation" class="active"><a role="tab" aria-selected="true" aria-controls="editorTabs" id="messageedit" href="#"><?php echo I18n::_('Editor'); ?></a></li>
|
<li role="presentation" class="active"><a role="tab" aria-selected="true" aria-controls="editorTabs" id="messageedit" href="#"><?php echo I18n::_('Editor'); ?></a></li>
|
||||||
<li role="presentation"><a role="tab" aria-selected="false" aria-controls="editorTabs" id="messagepreview" href="#"><?php echo I18n::_('Preview'); ?></a></li>
|
<li role="presentation"><a role="tab" aria-selected="false" aria-controls="editorTabs" id="messagepreview" href="#"><?php echo I18n::_('Preview'); ?></a></li>
|
||||||
|
|
|
@ -48,9 +48,9 @@ if ($MARKDOWN):
|
||||||
<?php
|
<?php
|
||||||
endif;
|
endif;
|
||||||
?>
|
?>
|
||||||
<script type="text/javascript" data-cfasync="false" src="js/purify-2.0.14.js" integrity="sha512-kbLhjIj/m/AW++o2eErCfqPueoX2btJo7VznhEC2YQRbVR/+Eup3w7thwDZwoCZ/gLrPxTX3W4H2KzupLg2PKA==" crossorigin="anonymous"></script>
|
<script type="text/javascript" data-cfasync="false" src="js/purify-2.1.1.js" integrity="sha512-0RqB620aQhcT40T4kxf/vx3J4DOmFsqcGu2mPha21ZqufRsth3MsiU35ffSHX0OIJbE92XSKyvNcL1I6sYhh4w==" crossorigin="anonymous"></script>
|
||||||
<script type="text/javascript" data-cfasync="false" src="js/legacy.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-LYos+qXHIRqFf5ZPNphvtTB0cgzHUizu2wwcOwcwz/VIpRv9lpcBgPYz4uq6jx0INwCAj6Fbnl5HoKiLufS2jg==" crossorigin="anonymous"></script>
|
<script type="text/javascript" data-cfasync="false" src="js/legacy.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-LYos+qXHIRqFf5ZPNphvtTB0cgzHUizu2wwcOwcwz/VIpRv9lpcBgPYz4uq6jx0INwCAj6Fbnl5HoKiLufS2jg==" crossorigin="anonymous"></script>
|
||||||
<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-GCiSgkYlcyJq3SOMOAh52rIlUAoGH8yDJzOm/NkzBorbk2qiBSjc289/RxpeZJcdu36fQObFTzLvz4Do/2LFsA==" crossorigin="anonymous"></script>
|
<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-9cJdKFvcsrk3G411+Wp5Y6ZvFE6UUMKVzCB6LLXhg1BaN/jkviL01Ox+4HzbYNflFuSYK0USVFLeCW89774A6w==" crossorigin="anonymous"></script>
|
||||||
<!-- icon -->
|
<!-- icon -->
|
||||||
<link rel="apple-touch-icon" href="img/apple-touch-icon.png?<?php echo rawurlencode($VERSION); ?>" sizes="180x180" />
|
<link rel="apple-touch-icon" href="img/apple-touch-icon.png?<?php echo rawurlencode($VERSION); ?>" sizes="180x180" />
|
||||||
<link rel="icon" type="image/png" href="img/favicon-32x32.png?<?php echo rawurlencode($VERSION); ?>" sizes="32x32" />
|
<link rel="icon" type="image/png" href="img/favicon-32x32.png?<?php echo rawurlencode($VERSION); ?>" sizes="32x32" />
|
||||||
|
|
Loading…
Reference in a new issue