Compare commits

...

21 commits

Author SHA1 Message Date
El RIDO
f66c690fc0
upgrade DOMpurify to 2.1.1 - obsoleting the SAFE_FOR_JQUERY flag 2020-10-08 19:00:04 +02:00
El RIDO
c37052fe82
crediting Hebrew translation 2020-10-08 18:50:43 +02:00
El RIDO
7e30134d1f
exclude crowdin configuration from releases 2020-10-04 17:45:12 +02:00
El RIDO
2b67f0369d
Merge branch 'master' into paste-manager 2020-10-04 14:29:12 +02:00
El RIDO
b2751ca444
memory list sort by column 2020-09-03 20:28:48 +02:00
El RIDO
ddfccb338c
Merge branch 'master' into paste-manager 2020-08-30 08:35:53 +02:00
El RIDO
85943a9ce1
adding functionality to forget pastes in memory 2020-07-10 21:18:04 +02:00
El RIDO
2b9abb8db4
memory display in columns, adding copy function 2020-07-07 21:53:28 +02:00
El RIDO
e531592b05
Merge branch 'master' into paste-manager 2020-07-03 21:04:55 +02:00
El RIDO
d027cb188d
allow switching to a memorized paste by clicking it 2020-07-01 21:02:45 +02:00
El RIDO
4e3348795c
Merge branch 'master' into paste-manager 2020-06-30 20:17:06 +02:00
El RIDO
2d9666c5ef
refactor button refresh 2020-06-29 21:22:27 +02:00
El RIDO
521f134638
edge cases: re-enable remember button when creating a new paste, refresh list and disble button upon memorizing the current paste 2020-06-29 21:13:26 +02:00
El RIDO
ea93c474ea
ensure code can be tested, handle async indexedDB, don't let user add same URL more then once 2020-06-29 21:00:59 +02:00
El RIDO
af02731002
remove tests for dead code 2020-06-29 20:58:53 +02:00
El RIDO
8b170397a9
adding a remember button 2020-06-28 21:47:32 +02:00
El RIDO
ac32826009
initial work to add IndexedDB support, including npm module to mock its API in unit tests 2020-06-28 14:52:40 +02:00
El RIDO
c4830044f7
adding some simple non-stored logic to test the interface 2020-06-28 13:05:47 +02:00
El RIDO
daae0d0dca
initial memory boilerplate, move sidebar toggle into it's init, test it behaves as it should 2020-06-28 11:25:09 +02:00
El RIDO
283d85c7cf
simplify sidebar CSS, position it relative to the editor tabs 2020-06-27 14:17:48 +02:00
El RIDO
8537f396b3
add a crude responsive collapsible sidebar 2020-06-27 13:36:53 +02:00
13 changed files with 467 additions and 111 deletions

1
.gitattributes vendored
View file

@ -20,6 +20,7 @@ js/test/ export-ignore
.travis.yml export-ignore
composer.json export-ignore
composer.lock export-ignore
crowdin.yml export-ignore
BADGES.md export-ignore
CODE_OF_CONDUCT.md export-ignore
Makefile export-ignore

View file

@ -1,7 +1,8 @@
# PrivateBin version history
* **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)**
* 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

View file

@ -46,3 +46,4 @@ Sébastien Sauvage - original idea and main developer
* info-path - Czech
* BigWax - Bulgarian
* AndriiZ - Ukrainian
* Yaron Shahrabani - Hebrew

View file

@ -205,3 +205,43 @@ li.L0, li.L1, li.L2, li.L3, li.L5, li.L6, li.L7, li.L8 {
.modal .modal-content button {
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;
}

View file

@ -8,6 +8,8 @@ global.cleanup = global.jsdom();
global.URL = require('jsdom-url').URL;
global.fs = require('fs');
global.WebCrypto = require('@peculiar/webcrypto').Crypto;
require('fake-indexeddb/auto');
global.FDBFactory = require('fake-indexeddb/lib/FDBFactory');
// application libraries to test
global.$ = global.jQuery = require('./jquery-3.4.1');
@ -17,7 +19,7 @@ require('./prettify');
global.prettyPrint = window.PR.prettyPrint;
global.prettyPrintOne = window.PR.prettyPrintOne;
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.Legacy = require('./legacy').Legacy;
require('./bootstrap-3.3.7');

View file

@ -8,11 +8,12 @@
},
"dependencies": {},
"devDependencies": {
"@peculiar/webcrypto": "^1.1.1",
"fake-indexeddb": "^3.0.2",
"jsdom": "^9.12.0",
"jsdom-global": "^2.1.1",
"jsdom-url": "^2.2.1",
"jsverify": "^0.8.3",
"@peculiar/webcrypto": "^1.1.1"
"jsverify": "^0.8.3"
},
"scripts": {
"test": "mocha"

View file

@ -12,9 +12,9 @@
*/
// global Base64, DOMPurify, FileReader, RawDeflate, history, navigator, prettyPrint, prettyPrintOne, showdown, kjua
'use strict';
jQuery.fn.draghover = function() {
'use strict';
return this.each(function() {
let collection = $(),
self = $(this);
@ -37,14 +37,11 @@ jQuery.fn.draghover = function() {
// main application start, called when DOM is fully loaded
jQuery(document).ready(function() {
'use strict';
// run main controller
$.PrivateBin.Controller.init();
});
jQuery.PrivateBin = (function($, RawDeflate) {
'use strict';
/**
* zlib library interface
*
@ -565,10 +562,11 @@ jQuery.PrivateBin = (function($, RawDeflate) {
*
* @name Helper.reset
* @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;
@ -3521,6 +3519,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
$emailLink,
$sendButton,
$retryButton,
$rememberButton,
pasteExpiration = null,
retryButtonCallback;
@ -3885,6 +3884,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
$cloneButton.removeClass('hidden');
$rawTextButton.removeClass('hidden');
$qrCodeLink.removeClass('hidden');
$rememberButton.removeClass('hidden');
viewButtonsDisplayed = true;
};
@ -3905,6 +3905,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
$newButton.addClass('hidden');
$rawTextButton.addClass('hidden');
$qrCodeLink.addClass('hidden');
$rememberButton.addClass('hidden');
me.hideEmailButton();
viewButtonsDisplayed = false;
@ -3970,17 +3971,6 @@ jQuery.PrivateBin = (function($, RawDeflate) {
createButtonsDisplayed = false;
};
/**
* only shows the "new paste" button
*
* @name TopNav.showNewPasteButton
* @function
*/
me.showNewPasteButton = function()
{
$newButton.removeClass('hidden');
};
/**
* only shows the "retry" button
*
@ -4043,17 +4033,6 @@ jQuery.PrivateBin = (function($, RawDeflate) {
$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
*
@ -4065,17 +4044,6 @@ jQuery.PrivateBin = (function($, RawDeflate) {
$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
*
@ -4084,8 +4052,8 @@ jQuery.PrivateBin = (function($, RawDeflate) {
*/
me.hideBurnAfterReadingButtons = function()
{
me.hideCloneButton();
me.hideQrCodeButton();
$cloneButton.addClass('hidden');
$qrCodeLink.addClass('hidden');
me.hideEmailButton();
}
@ -4325,6 +4293,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
$sendButton = $('#sendbutton');
$qrCodeLink = $('#qrcodelink');
$emailLink = $('#emaillink');
$rememberButton = $('#rememberbutton');
// bootstrap template drop down
$('#language ul.dropdown-menu li a').click(setLanguage);
@ -4360,6 +4329,312 @@ jQuery.PrivateBin = (function($, RawDeflate) {
return me;
})(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
*
@ -4667,6 +4942,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
// show new URL in browser bar
history.pushState({type: 'newpaste'}, document.title, url);
Memory.refreshButton();
TopNav.showViewButtons();
@ -5337,8 +5613,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
I18n.loadTranslations();
DOMPurify.setConfig({
ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|magnet):)/i,
SAFE_FOR_JQUERY: true
ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|magnet):)/i
});
// Add a hook to make all links open a new window
@ -5376,6 +5651,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
Prompt.init();
TopNav.init();
UiHelper.init();
Memory.init();
// check for legacy browsers before going any further
if (!Legacy.Check.getInit()) {
@ -5429,6 +5705,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
AttachmentViewer: AttachmentViewer,
DiscussionViewer: DiscussionViewer,
TopNav: TopNav,
Memory: Memory,
ServerInteraction: ServerInteraction,
PasteEncrypter: PasteEncrypter,
PasteDecrypter: PasteDecrypter,

File diff suppressed because one or more lines are too long

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
View 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'));
}
);
});
});

View file

@ -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 () {
before(function () {
cleanup();

View file

@ -70,9 +70,9 @@ if ($MARKDOWN) :
<?php
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/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 -->
<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" />
@ -224,6 +224,9 @@ if ($QRCODE) :
<?php
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 class="dropdown">
<select id="pasteExpiration" name="pasteExpiration" class="hidden">
@ -447,6 +450,28 @@ if ($isCpct) :
endif;
?></nav>
<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">
<?php
if (strlen($NOTICE)) :
@ -466,7 +491,7 @@ if ($FILEUPLOAD) :
?>
<div id="attachment" role="alert" class="hidden alert alert-info">
<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>
<?php
endif;
@ -530,6 +555,10 @@ if (strlen($URLSHORTENER)) :
endif;
?>
</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">
<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>

View file

@ -48,9 +48,9 @@ if ($MARKDOWN):
<?php
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/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 -->
<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" />