Long Comment: Imgur
Allows for longer comments on imgur
Click here to install
Browse More Scripts
// ==UserScript==
// @name Long Comment: Imgur
// @namespace 9e1cfcee9335690743c0d228475ec98d966d935f
// @version 1.1
// @description Allows for longer comments on imgur
// @author longcomment.com
// @match https://imgur.com/*
// @grant none
// @run-at document-body
// ==/UserScript==
//Version history
//1.0 Initial Version
//1.1 An issue with the old design was addressed
(function () {
'use strict';
//Sensible defaults. These are later set by the API
var minlen = 100;
var maxlen = 10000;
var root = "longcomment.com";
var $$ = document.querySelectorAll.bind(document);
var $ = document.querySelector.bind(document);
var isNewDesign = null;
//Gets the UTF-8 byte length of a text
var getByteLength = function (str) {
if (typeof(str) === typeof("")) {
var te = new TextEncoder();
return te.encode(str).length;
}
return undefined;
};
//Finds a node by going up the DOM tree until `querySelector()` hits.
var findNode = function (base, selector) {
var node;
while (base) {
if (node = base.querySelector(selector)) {
return node;
}
base = base.parentNode;
}
return null;
};
//Adds styles depending on old or new design
var addStyle = function () {
var style = document.createElement("style");
var rules;
if (isNewDesign) {
rules = [
"#long-comment{height:26px;margin-left:1em;padding-left:12px;box-shadow:0 3px 4px rgba(0,0,0,.12);border-radius:3px;border:none;font-size:14px;line-height:14px;color:#fff;text-transform:capitalize;text-align:center;outline-style:none;cursor:pointer;font-family:Proxima Nova ExtraBold,Helvetica Neue,Helvetica,Arial,sans-serif}",
"#long-comment[disabled]{color:#b4b9c2;background:#464b57;cursor:default}",
".via-lc,.lc-pending{font-style:italic;font-size:small;}"
];
} else {
rules = [
"#long-comment{padding:10px 25px;margin-left:1em;font-weight:700;float:right;}",
".via-lc,.lc-pending{font-style:italic;font-size:small;}"
];
}
style.textContent = rules.join("\r\n");
$("head").appendChild(style);
//Prevent multiple calls
addStyle = function () {
console.warn("LC API: Duplicate call to addStyles()");
};
};
//Triggers a builtin event
var triggerDomEvent = function (ele, eventName) {
var e = new Event(eventName, {
bubbles: true,
cancelable: true
});
return ele.dispatchEvent(e);
};
//Triggers a custom event
var triggerCustomEvent = function (ele, eventName) {
//== This used to be necessary in the past but is no longer done this way. ==//
//var e = document.createEvent('CustomEvent');
//e.initEvent(eventName, true, false);
//ele.dispatchEvent(e);
return triggerDomEvent(ele, eventName);
};
//Sets the value of a textbox and its neighbors.
//This is for the old design because it uses a hidden textbox that throws "querySelector" off.
var setMultiText = function (tb, value) {
var boxes = tb.parentNode.querySelectorAll("textarea");
boxes.forEach(function (v) {
v.value = value;
if (v._valueTracker) {
v._valueTracker.setValue(value);
}
var react = Object.getOwnPropertyNames(v).filter(function (prop) {
return prop.indexOf("__reactInternalInstance$") === 0;
})[0];
if (react) {
//Get root react component
console.log("Trying to set react value in internal property");
var component = v[react].return;
while (component && typeof(component.type) === typeof("")) {
component = component.return;
}
if(component){
component.stateNode.setState({
value: value
});
}
}
});
//Call all sorts of events
boxes.forEach(function (v) {
triggerCustomEvent(v, "onKeyDown");
triggerDomEvent(v, "input");
triggerDomEvent(v, "change");
triggerCustomEvent(v, "onChange");
});
};
//Creates the "Long Comment" button with all properties and events set.
//You need to add it to the DOM manually.
var createLongBtn = function () {
var btn = document.createElement("input");
btn.type = "button";
btn.value = "Long Comment";
btn.id = "long-comment";
//The comment box is empty by default
btn.disabled = true;
if (isNewDesign) {
btn.classList.add("Button");
} else {
btn.classList.add("btn");
btn.classList.add("btn-main");
}
btn.addEventListener("click", function () {
var box = findNode(this, "textarea");
var form = box.form;
var btn = form.querySelector("#submit-comment") || form.querySelector(".Create-submitBtn");
if (!box || !btn) {
console.warn("Invalid form state. Did imgur change something?");
return false;
}
//This is already a long comment
if (box.value.match(/^LC:[\S]+/)) {
triggerDomEvent(form, "submit");
return true;
}
var bl = getByteLength(box.value);
if (bl < minlen) {
alert("Text too short. Needs to be at least " + minlen + " bytes. Yours is " + bl);
return false;
}
if (bl > maxlen) {
alert("Text too long. Needs to be at most " + minlen + " bytes. Yours is " + bl);
return false;
}
console.log("Longcomment API: Storing comment");
var xhr = new XMLHttpRequest();
xhr.open("POST", "https://" + root + "/add");
xhr.addEventListener("load", function () {
var data = {
success: false,
msg: "Invalid JSON response"
};
try {
data = JSON.parse(xhr.responseText);
} catch (e) {
console.error("Invalid response from longcomment API:", xhr.responseText);
}
if (data.success) {
//ID is contained in the field "data.id" if we want to use it directly
setMultiText(box, data.placeholder);
console.debug("LC API: Set text:", box.value);
if (isNewDesign) {
btn.disabled = false;
btn.classList.remove("disabled");
triggerDomEvent(btn, "click");
triggerCustomEvent(btn, "onClick");
} else {
triggerDomEvent(form, "submit");
}
} else {
console.error("Longcomment API error message:", data.msg);
if (data.msg) {
alert("There was an error creating your longcomment:\r\n" + data.msg);
}
}
});
xhr.addEventListener("error", function () {
console.error("Longcomment API error:", arguments);
});
var fd = new FormData();
fd.append("comment", box.value);
xhr.send(fd);
return true;
});
return btn;
};
//Gets a longcomment text from the API
var getLongComment = function (id, callback) {
if (typeof(callback) !== typeof(function () {})) {
callback = function () {
console.warn("Longcomment API: No callback supplied");
};
}
console.log("Longcomment API: Obtaining id", id);
var xhr = new XMLHttpRequest();
xhr.open("GET", "https://" + root + "/comment/" + encodeURIComponent(id));
xhr.addEventListener("load", function () {
var data = {
success: false,
msg: "Invalid JSON response"
};
try {
data = JSON.parse(xhr.responseText);
} catch (e) {
console.error("Invalid response from longcomment API:", xhr.responseText);
}
callback(data);
});
xhr.addEventListener("error", function () {
console.error("Longcomment API error:", arguments);
callback(null);
});
xhr.send(null);
return xhr;
};
//Obtain API limits from the LC api.
//This will cache the response for a day.
var getInfo = function (callback) {
if (typeof(callback) !== typeof(function () {})) {
callback = function () {
console.warn("Longcomment API: No callback supplied");
};
}
var cache = localStorage.getItem("lc-properties");
if (cache) {
try {
cache = JSON.parse(cache);
//Update at most once per day
if (cache.cachedate > Date.now() - 86400 * 1000) {
minlen = cache.minlen | 0;
maxlen = cache.maxlen | 0;
//sanity check
if (maxlen === 0 || maxlen <= minlen) {
minlen = 100;
maxlen = 10000;
}
//Exit early
console.debug("Longcomment: Taking cached copy of info:", cache);
callback(cache);
return true;
}
} catch (e) {
//Item is invalid. Discard and continue
localStorage.removeItem("lc-properties");
}
}
var xhr = new XMLHttpRequest();
xhr.open("GET", "https://" + root + "/info");
xhr.addEventListener("load", function () {
var data = {
msg: "Invalid JSON response"
};
try {
data = JSON.parse(xhr.responseText);
} catch (e) {
console.error("Invalid response from longcomment API:", xhr.responseText);
}
callback(data);
});
xhr.addEventListener("error", function () {
console.error("Longcomment API error:", arguments);
callback(null);
});
xhr.send(null);
return xhr;
};
//Replaces the LC id with the text content
var replaceLongComment = function (element) {
var id = getLongCommentId(element.textContent);
if (typeof(id) === typeof("")) {
//Replacing the text prevents weird loops or multiple attempts from the observer.
element.innerHTML = "<i class=\"lc-pending\">Decoding Longcomment " + id + "...</i>";
getLongComment(id, function (data) {
if (data.success) {
//Clear the text
element.innerHTML = "";
var lines = data.comment.split('\n').map(function (v) {
return v.trimEnd();
});
//Try to restore the lines from the comment
for (var i = 0; i < lines.length - 1; i++) {
//Do not add multiple empty lines
if (i === 0 || lines[i].length > 0 || lines[i - 1].length > 0) {
element.insertAdjacentText('beforeend', lines[i]);
element.appendChild(document.createElement("br"));
}
}
element.insertAdjacentText('beforeend', lines[lines.length - 1]);
//element.textContent = data.comment;
element.appendChild(document.createElement("br"));
element.appendChild(document.createElement("hr"));
var hint = element.appendChild(document.createElement("i"));
hint.classList.add("via-lc");
hint.innerHTML = "Via <a href=\"https://longcomment.com/\" target=\"_blank\">Longcomment</a>. Id: " + id;
} else {
//Reset text
element.innerHTML = id;
element.insertAdjacentHTML("afterbegin", "<span style=\"color:#F00;\">Invalid or unavailable Longcomment:</span><br />");
console.error("Longcomment API error message:", data.msg);
}
});
}
};
//Extracts the LC id from a text
var getLongCommentId = function (text) {
if (typeof(text) === typeof("")) {
var m = text.split(' ')[0].match(/^LC:([\w\-]{10}[AEIMQUYcgkosw048])$/);
if (m) {
return m[1];
}
}
return null;
};
//Checks if the given value is an LC id
var isLongComment = function (text) {
return typeof(getLongCommentId(text) === typeof(""));
};
//Checks for new Longcomments
//This works identical for old and new design
var checkLC = function () {
//Supports old and new design
$$(".comment .usertext .linkified,.GalleryComment .Linkify").forEach(function (v) {
if (isLongComment(v.textContent)) {
replaceLongComment(v);
} else {
console.log("Not a Longcomment:", v.textContent);
}
});
};
//Enables or disables the Long Comment button depending on text length
//This works identical for old and new design
var onTextareaChange = function (tb) {
var btn;
if (isNewDesign) {
btn = findNode(tb, "form").querySelector("#long-comment");
} else {
btn = tb.parentNode.querySelector("#long-comment");
}
if (btn) {
var l = getByteLength(tb.value);
btn.disabled = l < minlen || l > maxlen;
} else {
console.warn("Long comment button was deleted");
}
};
//This watches for text changes in a textbox
var watchChange = function (e) {
e.addEventListener("input", function () {
onTextareaChange(this);
});
e.addEventListener("change", function () {
onTextareaChange(this);
});
};
//Adds the long comment buttons where needed
var addButtons = function () {
$$("#submit-comment,.Create-submitBtn").forEach(function (v) {
if (!v.parentNode.querySelector("#long-comment")) {
var container = findNode(v, "textarea").parentNode;
console.log(container);
container.querySelectorAll("textarea").forEach(watchChange);
if (isNewDesign) {
v.parentNode.appendChild(createLongBtn());
} else {
v.parentNode.insertBefore(createLongBtn(), v);
}
}
});
};
//This watches for changes in the DOM
var mo = new MutationObserver(function () {
if (isNewDesign === null) {
var mode = {
"old": $$(".post-title").length > 0,
"beta": $$(".Gallery-Title").length > 0
}
if (mode.old || mode.beta) {
isNewDesign = mode.beta;
console.log("New design:", isNewDesign);
//Add style only after we decided on the mode
addStyle();
}
}
if (isNewDesign !== null) {
//Add longcomment button where needed
addButtons();
//Parse longcomment text
checkLC();
}
});
//Obtain current API limits
getInfo(function (data) {
//Do not update the cache if the item has the cachedate property.
//This property is from a cached copy
if (data && !data.cachedate) {
data.cachedate = Date.now();
localStorage.setItem("lc-properties", JSON.stringify(data));
minlen = data.minlen;
maxlen = data.maxlen;
}
mo.observe(document.body, {
subtree: true,
childList: true
});
});
})();
/*
LICENSE:
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
The full license text can be found here: http://creativecommons.org/licenses/by-nc-sa/4.0/
The link has an easy to understand version of the license and the full license text.
DISCLAIMER:
THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE.
*/
User Script Managers
A userscript manager is the browser extension that injects scripts into websites
to change their behavior to your liking.
Recommendation
All scripts on this site have been developed and tested with Tampermonkey on firefox.
Try other browsers and other script managers at your own risk.
No script should use firefox or chrome specific features,
which means they should also work in other modern browsers.
If you prefer, you can use greasemonkey.
Get Tampermonkey,
Get Greasemonkey (Firefox only)
Script Installation
Once you have obtained a user script manager,
clicking on the install button will pop up an installation prompt.
To allow script manager detection,
you can install this helper script.
It's not necessary but simplifies your future visits to this site.
Script Installation
We detected, that you have a script manager installed and active.
Click the "Install Script" button to obtain the script.