diff --git a/css/custom_linehighlighting.css b/css/custom_linehighlighting.css
new file mode 100644
index 00000000..3541f4dc
--- /dev/null
+++ b/css/custom_linehighlighting.css
@@ -0,0 +1,99 @@
+li.line-selected,
+li.line-selected-extra,
+li.line-highlighted {
+ background: #FFF8C5 !important;
+ position: relative !important;
+}
+
+li .highlighter-selected {
+ position: absolute;
+ left: -52px;
+ display: none;
+ color: #5CB85C;
+}
+
+li .highlighter-controls {
+ position: absolute;
+ left: -35px;
+ display: none;
+}
+
+li.line-selected .highlighter-selected {
+ display: inline-flex;
+}
+
+li.line-selected-extra:before {
+ display: inline-flex;
+ width: 3px;
+ height: 19px;
+ background: #FF5500;
+ content: "";
+ left: -52px;
+ position: absolute;
+}
+
+li.line-selected .highlighter-selected .left-line {
+ display: inline-flex;
+ width: 3px;
+ height: 19px;
+ background: #FF5500;
+}
+
+li.line-highlighted .highlighter-controls {
+ display: inline-flex;
+ z-index: 999;
+}
+
+li.line-highlighted .highlighter-controls .hc-toggle {
+ color: #333;
+ background-color: #f5f5f5;
+ border: 1px solid #ccc;
+ padding: 2px 8px;
+ border-radius: 3px;
+ display: inline-flex;
+ cursor: pointer;
+}
+
+li.line-highlighted .highlighter-controls .hc-toggle:hover {
+ background-color: #E4E4E4;
+}
+
+li.line-highlighted .highlighter-controls .hc-dropdown {
+ display: none;
+ position: absolute;
+ color: #333;
+ background-color: #f5f5f5;
+ border: 1px solid #ccc;
+ padding: 2px 0;
+ border-radius: 3px;
+ left: 35px;
+}
+
+li.line-highlighted .highlighter-controls .hc-dropdown ul {
+ display: flex;
+ flex-direction: column;
+ padding: 0px;
+ margin: 0px;
+ list-style: none;
+}
+
+li.line-highlighted .highlighter-controls .hc-dropdown ul li {
+ display: inline-flex;
+ cursor: pointer;
+ white-space: nowrap;
+ width: 100%;
+ padding: 2px 8px;
+}
+
+li.line-highlighted .highlighter-controls .hc-dropdown ul li:hover {
+ background-color: #E4E4E4;
+}
+
+li.line-highlighted .highlighter-controls.hc-open .hc-dropdown {
+ display: inline-flex;
+}
+
+li.line-highlighted .highlighter-controls .hc-dropdown ul li.extend-line,
+li.line-highlighted .highlighter-controls .hc-dropdown ul li.unselect-line {
+ display: none;
+}
\ No newline at end of file
diff --git a/js/privatebin.js b/js/privatebin.js
index 20e8c5e8..4f2fece1 100644
--- a/js/privatebin.js
+++ b/js/privatebin.js
@@ -13,19 +13,24 @@
// global Base64, DOMPurify, FileReader, RawDeflate, history, navigator, prettyPrint, prettyPrintOne, showdown, kjua
+var globalScrollPosition;
+var globalSecondPositionMultiple;
+var selectedLine;
+var allowedReset = false;
+
jQuery.fn.draghover = function() {
'use strict';
return this.each(function() {
let collection = $(),
self = $(this);
-
+
self.on('dragenter', function(e) {
if (collection.length === 0) {
self.trigger('draghoverstart');
}
collection = collection.add(e.target);
});
-
+
self.on('dragleave drop', function(e) {
collection = collection.not(e.target);
if (collection.length === 0) {
@@ -42,6 +47,187 @@ jQuery(document).ready(function() {
$.PrivateBin.Controller.init();
});
+function parse_query_string(query) {
+ var vars = query.split("&");
+ var query_string = {};
+ for (var i = 0; i < vars.length; i++) {
+ var pair = vars[i].split("=");
+ var key = decodeURIComponent(pair[0]);
+ var value = decodeURIComponent(pair[1]);
+ // If first entry with this name
+ if (typeof query_string[key] === "undefined") {
+ query_string[key] = decodeURIComponent(value);
+ // If second entry with this name
+ } else if (typeof query_string[key] === "string") {
+ var arr = [query_string[key], decodeURIComponent(value)];
+ query_string[key] = arr;
+ // If third or later entry with this name
+ } else {
+ query_string[key].push(decodeURIComponent(value));
+ }
+ }
+ return query_string;
+}
+
+function markLines(a, b) {
+ for (var i = a+1; i < b+1; i++) {
+ $(".linenums").children().eq(i).addClass("line-selected-extra");
+ }
+}
+
+function selectLinesBetween() {
+ var a = globalScrollPosition < globalSecondPositionMultiple ? globalScrollPosition : globalSecondPositionMultiple;
+ var b = globalScrollPosition > globalSecondPositionMultiple ? globalScrollPosition : globalSecondPositionMultiple;
+
+ globalScrollPosition = a;
+ globalSecondPositionMultiple = b;
+
+ allowedReset = false;
+ $(".linenums").children().eq(globalScrollPosition).find(".select-line").click();
+ allowedReset = true;
+
+ markLines(a, b);
+}
+
+function attachLineHighlighter() {
+ var optsOpen = false;
+
+ if ($("#prettyprint").length < 1 || $("#prettyprint").find("li").length < 1) return;
+
+ $("#prettyprint").find("li").append(`
+
- Scroll here
- Do not scroll here
- Extend here
+ `.trim());
+
+ setTimeout(function(){
+ $("#prettyprint").find("li").hover(function () {
+ if (optsOpen) return;
+
+ $(".line-highlighted").removeClass("line-highlighted");
+ $(this).addClass("line-highlighted");
+ });
+
+ $(".hc-toggle").click(function(e) {
+ e.stopPropagation();
+
+ optsOpen = true;
+ $(this).parent().addClass("hc-open");
+
+ if (!globalScrollPosition) $(this).find(".extend-line").hide();
+ else {
+ var selectS = $(".linenums").children().eq(globalScrollPosition);
+ selectS.find(".extend-line").hide();
+ }
+ });
+
+ $(".hc-toggle").one("click", function() {
+ $(".extend-line").show();
+
+ var qs = parse_query_string(window.location.href);
+ if (!globalScrollPosition && !qs.s) $(".extend-line").hide();
+ });
+
+ $(document).on('click', function(e) {
+ optsOpen = false;
+ $(".highlighter-controls").removeClass("hc-open");
+ });
+
+ $(".extend-line").click(function() {
+ globalSecondPositionMultiple = $(this).parents("li").index();
+
+ if (!globalScrollPosition) {
+ var qs = parse_query_string(window.location.href);
+ globalScrollPosition = Number(qs.s);
+ }
+
+ selectLinesBetween();
+
+ var freshURL = window.location.href.split('&')[0];
+
+ if (!freshURL.includes("#")) return;
+
+ var URLA = "";
+ if (globalScrollPosition && globalScrollPosition!="") URLA+=`&s=${globalScrollPosition+1}`;
+ if (globalSecondPositionMultiple && globalSecondPositionMultiple!="") URLA+=`&e=${globalSecondPositionMultiple+1}`;
+
+ window.history.pushState('page2', 'Title', freshURL + URLA);
+ });
+
+ $(".select-line").click(function() {
+ $(".select-line").show();
+ $(".extend-line").show();
+ $(".unselect-line").hide();
+
+ $(this).hide();
+ $(this).siblings(".extend-line").hide();
+ $(this).siblings(".unselect-line").show();
+
+ globalScrollPosition = $(this).parents("li").index();
+ if (allowedReset) globalSecondPositionMultiple = null;
+
+ $(".line-selected").removeClass("line-selected");
+ $(".line-selected-extra").removeClass("line-selected-extra");
+
+ $(this).parents("li").addClass("line-selected");
+
+ optsOpen = false;
+ $(".highlighter-controls").removeClass("hc-open");
+
+ var freshURL = window.location.href.split('&')[0];
+
+ if (!freshURL.includes("#")) return;
+
+ var URLA = "";
+ if (globalScrollPosition && globalScrollPosition!="") URLA+=`&s=${globalScrollPosition+1}`;
+ if (globalSecondPositionMultiple && globalSecondPositionMultiple!="") URLA+=`&e=${globalSecondPositionMultiple+1}`;
+
+ window.history.pushState('page2', 'Title', freshURL + URLA);
+ });
+
+ $(".unselect-line").click(function() {
+ $(this).hide();
+ $(this).siblings(".select-line").show();
+ $(".extend-line").hide();
+
+ globalScrollPosition = null;
+ globalSecondPositionMultiple = null;
+
+ $(".line-selected").removeClass("line-selected");
+ $(".line-selected-extra").removeClass("line-selected-extra");
+
+ optsOpen = false;
+ $(".highlighter-controls").removeClass("hc-open");
+
+ var freshURL = window.location.href.split('&')[0];
+ window.history.pushState('page2', 'Title', freshURL);
+ });
+
+ $(document).ready(function() {
+ var qs = parse_query_string(window.location.href);
+
+ if (qs.s && qs.s != "") {
+ $(".line-selected").removeClass("line-selected");
+ $(".line-selected-extra").removeClass("line-selected-extra");
+
+ selectedLine = $(".linenums").children().eq(qs.s)
+ selectedLine.addClass("line-selected");
+
+ $(".line-selected").find(".hc-toggle").one('click', function(event) {
+ selectedLine.find(".unselect-line").show();
+ selectedLine.find(".select-line").hide();
+ });
+
+ if (qs.e && qs.e != "") {
+ markLines(Number(qs.s)-2, Number(qs.e)-1);
+ }
+
+ $([document.documentElement, document.body]).animate({
+ scrollTop: selectedLine.offset().top - 50
+ }, 500);
+ }
+ })
+ }, 150);
+}
+
jQuery.PrivateBin = (function($, RawDeflate) {
'use strict';
@@ -243,18 +429,6 @@ jQuery.PrivateBin = (function($, RawDeflate) {
*/
const day = 86400;
- /**
- * number of seconds in a week
- *
- * = 60 * 60 * 24 * 7 seconds
- *
- * @name Helper.week
- * @private
- * @enum {number}
- * @readonly
- */
- const week = 604800;
-
/**
* number of seconds in a month (30 days, an approximation)
*
@@ -338,7 +512,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
*/
me.durationToSeconds = function(duration)
{
- let pieces = duration.split(/(\D+)/),
+ let pieces = duration.split(/\d+/),
factor = pieces[0] || 0,
timespan = pieces[1] || pieces[0];
switch (timespan)
@@ -349,8 +523,6 @@ jQuery.PrivateBin = (function($, RawDeflate) {
return factor * hour;
case 'day':
return factor * day;
- case 'week':
- return factor * week;
case 'month':
return factor * month;
case 'year':
@@ -405,11 +577,9 @@ jQuery.PrivateBin = (function($, RawDeflate) {
me.urls2links = function(element)
{
element.html(
- DOMPurify.sanitize(
- element.html().replace(
- /(((https?|ftp):\/\/[\w?!=&.\/-;#@~%+*-]+(?![\w\s?!&.\/;#~%"=-]>))|((magnet):[\w?=&.\/-;#@~%+*-]+))/ig,
- '$1'
- )
+ element.html().replace(
+ /(((https?|ftp):\/\/[\w?!=&.\/-;#@~%+*-]+(?![\w\s?!&.\/;#~%"=-]>))|((magnet):[\w?=&.\/-;#@~%+*-]+))/ig,
+ '$1'
)
);
};
@@ -535,7 +705,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
/**
* calculate expiration date given initial date and expiration period
- *
+ *
* @name Helper.calculateExpirationDate
* @function
* @param {Date} initialDate - may not be empty
@@ -548,7 +718,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
if (typeof expirationDisplayStringOrSecondsToExpire === 'string') {
secondsToExpiration = me.durationToSeconds(expirationDisplayStringOrSecondsToExpire);
}
-
+
if (typeof secondsToExpiration !== 'number') {
throw new Error('Cannot calculate expiration date.');
}
@@ -601,7 +771,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
* @prop {string[]}
* @readonly
*/
- const supportedLanguages = ['bg', 'cs', 'de', 'es', 'fr', 'he', 'hu', 'it', 'lt', 'no', 'nl', 'pl', 'pt', 'oc', 'ru', 'sl', 'uk', 'zh'];
+ const supportedLanguages = ['bg', 'cs', 'de', 'es', 'fr', 'it', 'hu', 'no', 'nl', 'pl', 'pt', 'oc', 'ru', 'sl', 'uk', 'zh'];
/**
* built in language
@@ -782,10 +952,6 @@ jQuery.PrivateBin = (function($, RawDeflate) {
case 'oc':
case 'zh':
return n > 1 ? 1 : 0;
- case 'he':
- return n === 1 ? 0 : (n === 2 ? 1 : ((n < 0 || n > 10) && (n % 10 === 0) ? 2 : 3));
- case 'lt':
- return n % 10 === 1 && n % 100 !== 11 ? 0 : ((n % 10 >= 2 && n % 100 < 10 || n % 100 >= 20) ? 1 : 2);
case 'pl':
return n === 1 ? 0 : (n % 10 >= 2 && n %10 <=4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2);
case 'ru':
@@ -1995,11 +2161,19 @@ jQuery.PrivateBin = (function($, RawDeflate) {
return a.length - b.length;
})[0];
if (typeof shortUrl === 'string' && shortUrl.length > 0) {
+ var URLA = "";
+ if (globalScrollPosition && globalScrollPosition!="") URLA+=`&s=${globalScrollPosition+1}`;
+ if (globalSecondPositionMultiple && globalSecondPositionMultiple!="") URLA+=`&e=${globalSecondPositionMultiple+1}`;
+
+ I18n._(
+ $('#pastelink'),
+ `Your paste is %s${URLA} (Hit [Ctrl]+[c] to copy)`,
+ shortUrl, shortUrl
+ );
// we disable the button to avoid calling shortener again
$shortenButton.addClass('buttondisabled');
- // update link
- $pasteUrl.text(shortUrl);
- $pasteUrl.prop('href', shortUrl);
+ // save newly created element
+ $pasteUrl = $('#pasteurl');
// we pre-select the link so that the user only has to [Ctrl]+[c] the link
Helper.selectText($pasteUrl[0]);
return;
@@ -2049,9 +2223,13 @@ jQuery.PrivateBin = (function($, RawDeflate) {
*/
me.createPasteNotification = function(url, deleteUrl)
{
+ var URLA = "";
+ if (globalScrollPosition && globalScrollPosition!="") URLA+=`&s=${globalScrollPosition+1}`;
+ if (globalSecondPositionMultiple && globalSecondPositionMultiple!="") URLA+=`&e=${globalSecondPositionMultiple+1}`;
+
I18n._(
$('#pastelink'),
- 'Your paste is %s (Hit [Ctrl]+[c] to copy)',
+ `Your paste is %s${URLA} (Hit [Ctrl]+[c] to copy)`,
url, url
);
// save newly created element
@@ -2420,7 +2598,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
/**
* hides the Editor
*
- * @name Editor.hide
+ * @name Editor.reset
* @function
*/
me.hide = function()
@@ -2550,6 +2728,8 @@ jQuery.PrivateBin = (function($, RawDeflate) {
Helper.htmlEntities(text), null, true
)
);
+
+ attachLineHighlighter();
} else {
// = 'plaintext'
$prettyPrint.text(text);
@@ -2767,8 +2947,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
// extract mediaType
const mediaType = attachmentData.substring(5, mediaTypeEnd);
// extract data and convert to binary
- const rawData = attachmentData.substring(base64Start);
- const decodedData = rawData.length > 0 ? atob(rawData) : '';
+ const decodedData = atob(attachmentData.substring(base64Start));
// Transform into a Blob
const buf = new Uint8Array(decodedData.length);
@@ -3127,15 +3306,19 @@ jQuery.PrivateBin = (function($, RawDeflate) {
*/
function addClipboardEventHandler() {
$(document).on('paste', function (event) {
+ if (TopNav.isAttachmentReadonly()) {
+ event.stopPropagation();
+ event.preventDefault();
+ return false;
+ }
const items = (event.clipboardData || event.originalEvent.clipboardData).items;
- const lastItem = items[items.length - 1];
- if (lastItem.kind === 'file') {
- if (TopNav.isAttachmentReadonly()) {
- event.stopPropagation();
- event.preventDefault();
- return false;
- } else {
- readFileData(lastItem.getAsFile());
+ for (let i = 0; i < items.length; ++i) {
+ if (items[i].kind === 'file') {
+ //Clear the file input:
+ $fileInput.wrap('