Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save GaurangTandon/75af82646b40be43a368860dcf22f43d to your computer and use it in GitHub Desktop.
Save GaurangTandon/75af82646b40be43a368860dcf22f43d to your computer and use it in GitHub Desktop.
beta version of Citation Helper for StackExchange userscript
// ==UserScript==
// @name Citation Helper for StackExchange
// @description Helps insert citations easily on StackExchange
// @author Gaurang Tandon
// @match *://*.askubuntu.com/*
// @match *://*.mathoverflow.net/*
// @match *://*.serverfault.com/*
// @match *://*.stackapps.com/*
// @match *://*.stackexchange.com/*
// @match *://*.stackoverflow.com/*
// @match *://*.superuser.com/*
// @exclude *://api.stackexchange.com/*
// @exclude *://blog.stackexchange.com/*
// @exclude *://blog.stackoverflow.com/*
// @exclude *://chat.stackexchange.com/*
// @exclude *://chat.stackoverflow.com/*
// @exclude *://data.stackexchange.com/*
// @exclude *://elections.stackexchange.com/*
// @exclude *://openid.stackexchange.com/*
// @exclude *://stackexchange.com/*
// @exclude *://*/review
// @grant none
// @version 0.1
// @history 0.1 Hello world!
// ==/UserScript==
// jshint -W014
// https://repl.it/repls/CloudyFearfulTrust
// https://repl.it/repls/QuickImmenseActionscript
// https://repl.it/repls/DiscretePastelErrors <-- latest working
/**
* PROBLEMS:
this is missing the date_parts property (the one i'm using)
http://pubs.rsc.org/en/content/articlelanding/2016/cc/c5cc08252h
https://doi.org/10.1002/recl.1964083121
issue raised - https://github.com/CrossRef/rest-api-doc/issues/381
1. pressing enter automatically submits my form to submit Short citation, also, it is hijacking the enter key on title and edit summary fields
awaiting response - https://stackoverflow.com/questions/50771160/prevent-onclick-event-on-a-button-from-firing-when-hitting-enter-in-a-different
*/
//https://github.com/LeaVerou/awesomplete/blob/gh-pages/awesomplete.min.js
// Awesomplete - Lea Verou - MIT license
!function(){var t=function(e,i){var s=this;t.count=(t.count||0)+1,this.count=t.count,this.isOpened=!1,this.input=n(e),this.input.setAttribute("autocomplete","off"),this.input.setAttribute("aria-owns","awesomplete_list_"+this.count),this.input.setAttribute("role","combobox"),this.options=i=i||{},function(t,e,i){for(var n in e){var s=e[n],r=t.input.getAttribute("data-"+n.toLowerCase());"number"==typeof s?t[n]=parseInt(r):!1===s?t[n]=null!==r:s instanceof Function?t[n]=null:t[n]=r,t[n]||0===t[n]||(t[n]=n in i?i[n]:s)}}(this,{minChars:2,maxItems:10,autoFirst:!1,data:t.DATA,filter:t.FILTER_CONTAINS,sort:!1!==i.sort&&t.SORT_BYLENGTH,container:t.CONTAINER,item:t.ITEM,replace:t.REPLACE,tabSelect:!1},i),this.index=-1,this.container=this.container(e),this.ul=n.create("ul",{hidden:"hidden",role:"listbox",id:"awesomplete_list_"+this.count,inside:this.container}),this.status=n.create("span",{className:"visually-hidden",role:"status","aria-live":"assertive","aria-atomic":!0,inside:this.container,textContent:0!=this.minChars?"Type "+this.minChars+" or more characters for results.":"Begin typing for results."}),this._events={input:{input:this.evaluate.bind(this),blur:this.close.bind(this,{reason:"blur"}),keydown:function(t){var e=t.keyCode;s.opened&&(13===e&&s.selected?(t.preventDefault(),s.select()):9===e&&s.selected&&s.tabSelect?s.select():27===e?s.close({reason:"esc"}):38!==e&&40!==e||(t.preventDefault(),s[38===e?"previous":"next"]()))}},form:{submit:this.close.bind(this,{reason:"submit"})},ul:{mousedown:function(t){t.preventDefault()},click:function(t){var e=t.target;if(e!==this){for(;e&&!/li/i.test(e.nodeName);)e=e.parentNode;e&&0===t.button&&(t.preventDefault(),s.select(e,t.target))}}}},n.bind(this.input,this._events.input),n.bind(this.input.form,this._events.form),n.bind(this.ul,this._events.ul),this.input.hasAttribute("list")?(this.list="#"+this.input.getAttribute("list"),this.input.removeAttribute("list")):this.list=this.input.getAttribute("data-list")||i.list||[],t.all.push(this)};function e(t){var e=Array.isArray(t)?{label:t[0],value:t[1]}:"object"==typeof t&&"label"in t&&"value"in t?t:{label:t,value:t};this.label=e.label||e.value,this.value=e.value}t.prototype={set list(t){if(Array.isArray(t))this._list=t;else if("string"==typeof t&&t.indexOf(",")>-1)this._list=t.split(/\s*,\s*/);else if((t=n(t))&&t.children){var e=[];i.apply(t.children).forEach(function(t){if(!t.disabled){var i=t.textContent.trim(),n=t.value||i,s=t.label||i;""!==n&&e.push({label:s,value:n})}}),this._list=e}document.activeElement===this.input&&this.evaluate()},get selected(){return this.index>-1},get opened(){return this.isOpened},close:function(t){this.opened&&(this.ul.setAttribute("hidden",""),this.isOpened=!1,this.index=-1,this.status.setAttribute("hidden",""),n.fire(this.input,"awesomplete-close",t||{}))},open:function(){this.ul.removeAttribute("hidden"),this.isOpened=!0,this.status.removeAttribute("hidden"),this.autoFirst&&-1===this.index&&this.goto(0),n.fire(this.input,"awesomplete-open")},destroy:function(){if(n.unbind(this.input,this._events.input),n.unbind(this.input.form,this._events.form),!this.options.container){var e=this.container.parentNode;e.insertBefore(this.input,this.container),e.removeChild(this.container)}this.input.removeAttribute("autocomplete"),this.input.removeAttribute("aria-autocomplete");var i=t.all.indexOf(this);-1!==i&&t.all.splice(i,1)},next:function(){var t=this.ul.children.length;this.goto(this.index<t-1?this.index+1:t?0:-1)},previous:function(){var t=this.ul.children.length,e=this.index-1;this.goto(this.selected&&-1!==e?e:t-1)},goto:function(t){var e=this.ul.children;this.selected&&e[this.index].setAttribute("aria-selected","false"),this.index=t,t>-1&&e.length>0&&(e[t].setAttribute("aria-selected","true"),this.status.textContent=e[t].textContent+", list item "+(t+1)+" of "+e.length,this.input.setAttribute("aria-activedescendant",this.ul.id+"_item_"+this.index),this.ul.scrollTop=e[t].offsetTop-this.ul.clientHeight+e[t].clientHeight,n.fire(this.input,"awesomplete-highlight",{text:this.suggestions[this.index]}))},select:function(t,e){if(t?this.index=n.siblingIndex(t):t=this.ul.children[this.index],t){var i=this.suggestions[this.index];n.fire(this.input,"awesomplete-select",{text:i,origin:e||t})&&(this.replace(i),this.close({reason:"select"}),n.fire(this.input,"awesomplete-selectcomplete",{text:i}))}},evaluate:function(){var t=this,i=this.input.value;i.length>=this.minChars&&this._list&&this._list.length>0?(this.index=-1,this.ul.innerHTML="",this.suggestions=this._list.map(function(n){return new e(t.data(n,i))}).filter(function(e){return t.filter(e,i)}),!1!==this.sort&&(this.suggestions=this.suggestions.sort(this.sort)),this.suggestions=this.suggestions.slice(0,this.maxItems),this.suggestions.forEach(function(e,n){t.ul.appendChild(t.item(e,i,n))}),0===this.ul.children.length?(this.status.textContent="No results found",this.close({reason:"nomatches"})):(this.open(),this.status.textContent=this.ul.children.length+" results found")):(this.close({reason:"nomatches"}),this.status.textContent="No results found")}},t.all=[],t.FILTER_CONTAINS=function(t,e){return RegExp(n.regExpEscape(e.trim()),"i").test(t)},t.FILTER_STARTSWITH=function(t,e){return RegExp("^"+n.regExpEscape(e.trim()),"i").test(t)},t.SORT_BYLENGTH=function(t,e){return t.length!==e.length?t.length-e.length:t<e?-1:1},t.CONTAINER=function(t){return n.create("div",{className:"awesomplete",around:t})},t.ITEM=function(t,e,i){var s=""===e.trim()?t:t.replace(RegExp(n.regExpEscape(e.trim()),"gi"),"<mark>$&</mark>");return n.create("li",{innerHTML:s,"aria-selected":"false",id:"awesomplete_list_"+this.count+"_item_"+i})},t.REPLACE=function(t){this.input.value=t.value},t.DATA=function(t){return t},Object.defineProperty(e.prototype=Object.create(String.prototype),"length",{get:function(){return this.label.length}}),e.prototype.toString=e.prototype.valueOf=function(){return""+this.label};var i=Array.prototype.slice;function n(t,e){return"string"==typeof t?(e||document).querySelector(t):t||null}function s(t,e){return i.call((e||document).querySelectorAll(t))}function r(){s("input.awesomplete").forEach(function(e){new t(e)})}n.create=function(t,e){var i=document.createElement(t);for(var s in e){var r=e[s];if("inside"===s)n(r).appendChild(i);else if("around"===s){var o=n(r);o.parentNode.insertBefore(i,o),i.appendChild(o),null!=o.getAttribute("autofocus")&&o.focus()}else s in i?i[s]=r:i.setAttribute(s,r)}return i},n.bind=function(t,e){if(t)for(var i in e){var n=e[i];i.split(/\s+/).forEach(function(e){t.addEventListener(e,n)})}},n.unbind=function(t,e){if(t)for(var i in e){var n=e[i];i.split(/\s+/).forEach(function(e){t.removeEventListener(e,n)})}},n.fire=function(t,e,i){var n=document.createEvent("HTMLEvents");n.initEvent(e,!0,!0);for(var s in i)n[s]=i[s];return t.dispatchEvent(n)},n.regExpEscape=function(t){return t.replace(/[-\\^$*+?.()|[\]{}]/g,"\\$&")},n.siblingIndex=function(t){for(var e=0;t=t.previousElementSibling;e++);return e},"undefined"!=typeof self&&(self.Awesomplete=t),"undefined"!=typeof Document&&("loading"!==document.readyState?r():document.addEventListener("DOMContentLoaded",r)),t.$=n,t.$$=s,"undefined"!=typeof self&&(self.Awesomplete=r)}();
var LS_KEY = "cachedEntries", J_KEY = "journalList";
if(!localStorage.getItem(LS_KEY)) localStorage.setItem(LS_KEY, "{}");
(function() {
(function handleModals() {
var PLACEHOLDER = "reference (DOI/URL/plain text)";
function addButton(ul) {
var li = document.createElement("LI"),
// different userscripts insert their own elements into the mix
// so make sure to insert just after the redo button for consistency
lastChild = ul.querySelector("li[id^=wmd-redo-button]").nextElementSibling;
ul.insertBefore(li, lastChild);
li.className = "wmd-button wmd-doi tmAdded";
// tmAdded required when also running this userscript https://github.com/BrockA/SE-misc
li.innerHTML = "<span>doi</span>";
li.title = "insert doi";
li.onclick = function () {
toggleModal(ul.parentElement);
};
return li;
}
function createModal(container) {
var div = document.createElement("div"),
input = document.createElement("input"),
shortBtn = document.createElement("button"),
longBtn = document.createElement("button");
div.id = "doi-box";
input.type = "text";
input.className = "awesomplete";
input.setAttribute("placeholder", PLACEHOLDER);
input.onkeydown = function(e){
if(e.keyCode === 13){
e.preventDefault();
e.stopPropagation();
console.log("I tried!");
return false;
}
};
shortBtn.innerHTML = "Short";
longBtn.innerHTML = "Long";
function commonInsertCitation(type){
var fn = type === 1 ? insertShortCitation : insertLongCitation;
return function(e){
e.preventDefault();
TYPE = type;
var val = input.value, source = getSource(val);
switch(source[0]){
case "doi": case "paperWeb":
citeDOI(source[1], function (citation) {
fn(container, citation);
});
break;
case "web":
citeWebsite(val, function(citation){
fn(container, citation);
});
// it is not a DOI and there is no need to cache it (because it isn't fetched via an XHR)
// but adding it to the localStorage list helps autocomplete it later
// (think of a person referencing Vogel multiple times)
cacheDOI(val, val);
break;
// alternate of Manual citation
default:
cacheDOI(val, val);
fn(container, val);
}
};
}
shortBtn.onclick = commonInsertCitation(1);
longBtn.onclick = commonInsertCitation(2);
div.appendChild(input);
div.appendChild(shortBtn);
div.appendChild(longBtn);
return div;
}
function insertShortCitation(container, citation) {
var textarea = container.parentNode.querySelector("textarea"),
selS = textarea.selectionStart,
selE = textarea.selectionEnd,
value = textarea.value,
valBefore = value.substring(0, selS),
valMid = value.substring(selS, selE),
valAfter = value.substring(selE);
textarea.value = valBefore + citation + valAfter;
textarea.selectionStart = textarea.selectionEnd = (valBefore + citation).length;
toggleModal(container);
textarea.focus();
// couldn't find the documentation for this, but it works ---v
// (widely used https://github.com/search?q=refreshAllPreviews&type=Code)
StackExchange.MarkdownEditor.refreshAllPreviews();
}
function getCurrentReferenceCount(value){
var match = value.match(/Reference(.|\n)+(\d)\. [a-zA-Z]/);
// without the ` [a-zA-Z]`, this match also extends to even digits inside DOI URLs
if(!match) return 0;
else return +match[2];
}
function insertLongCitation(container, citation) {
var textarea = container.parentNode.querySelector("textarea"),
selS = textarea.selectionStart,
selE = textarea.selectionEnd,
value = textarea.value,
currRefCount = getCurrentReferenceCount(value),
superscriptedCite = "<sup>\\[" + (currRefCount + 1) + "\\]</sup>",
valBefore = value.substring(0, selS),
valMid = value.substring(selS, selE),
valAfter = value.substring(selE);
value = valBefore + superscriptedCite + valAfter;
if(currRefCount === 0){
value += "\n### References:\n\n1. " + citation;
textarea.value = value;
}else{
var position = value.match(/Reference(.|\n)+\d\..+(\n|$)/),
startOfReferences = position.index,
lastReferenceNewline = startOfReferences + position[0].length,
textBeforeLastRefNewLine = value.substring(0, lastReferenceNewline),
textAfterLastRefNewLine = value.substring(lastReferenceNewline),
valToInsert = "\n" + (currRefCount + 1) + ". " + citation + "\n",
newValue = textBeforeLastRefNewLine + valToInsert + textAfterLastRefNewLine;
textarea.value = newValue;
}
textarea.selectionStart = textarea.selectionEnd = (valBefore + superscriptedCite).length;
toggleModal(container);
textarea.focus();
StackExchange.MarkdownEditor.refreshAllPreviews();
}
function toggleModal(container) {
var div = container.querySelector("#doi-box"),
input = div.querySelector("input");
if (div.style.height === "0px") {
div.style.height = "45px";
input.value = "";
input.focus();
}
else {
div.style.height = "0px";
}
console.trace();
}
var cachedKeys = Object.keys(JSON.parse(localStorage.getItem(LS_KEY))),
cachedKeysString;
if(cachedKeys.length !== 0){
cachedKeysString = cachedKeys[0];
for(var i = 1, len = cachedKeys.length; i < len; i++) cachedKeysString += ", " + cachedKeys[i];
}
setInterval(function () {
var cont = document.querySelector(".wmd-container:not(.doi-processed)"), ul,
div, buttonBar, input;
if (cont && (ul = cont.querySelector(".wmd-button-bar ul"))) {
addButton(ul);
cont.classList.add("doi-processed");
buttonBar = cont.querySelector("div[id^=wmd-button-bar]");
div = createModal(buttonBar);
div.style.height = "0px";
buttonBar.appendChild(div);
// only call the constructor after the input element
// is inside the DOM
input = div.querySelector("input");
// for some unknown reason, using the properties in the instantiation object
// are not working, hence use dataset
input.dataset.list = cachedKeysString;
input.dataset.minchars = 1;
input.dataset.maxitems = 5;
input.dataset.autofirst = true;
new Awesomplete(input);
}
}, 500);
// there's no way to automatically read a clipboard content
// think of some other UI
// https://stackoverflow.com/questions/400212/how-do-i-copy-to-the-clipboard-in-javascript
})();
})();
function citeWebsite(URL, callback) {
// I cannot retrieve author/title of website without using my own external server :(
// so just do the bare citation
var citation = URL + " (accessed ",
time = new Date(),
months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
citation += months[time.getMonth()] + " " + time.getDate() + ", " + time.getFullYear() + ".";
callback.call(this, citation);
}
function cacheDOI(doi, metadata){
var object = JSON.parse(localStorage.getItem(LS_KEY));
object[doi] = metadata;
localStorage.setItem(LS_KEY, JSON.stringify(object));
}
function getDOIMetaData(doi, callback) {
var cachedObject = JSON.parse(localStorage.getItem(LS_KEY)), cachedMetadata;
if(cachedObject && (cachedMetadata = cachedObject[doi])){
citeDOI(doi, callback, cachedMetadata);
return;
}
// ISSUE: I'm supposed to send my email along with this API call, how'd I do that?
var xhttp = new XMLHttpRequest();
xhttp.open("GET", "https://api.crossref.org/works/" + doi, true);
xhttp.setRequestHeader("Content-type", "text/plain");
xhttp.send();
xhttp.onload = function (e) {
var response = JSON.parse(e.srcElement.response), metadata;
if (response.status !== "ok") {
// possibly not a cross-ref DOI
alert("Couldn't fetch citation for DOI: " + doi + ". Please report the doi to the userscript author.");
return;
}
metadata = response.message;
cacheDOI(doi, metadata);
citeDOI(doi, callback, metadata);
};
}
// *J. Am. Chem. Soc.* **2018,** *140,* 1855
// or *J. Am. Chem. Soc.* **2018,** *140* (2), 1855
// Jounral name Year, Volume (Issue), Pages
function shortCiteDOI(doi, metadata) {
var output = "[" + getTitleYearIssuePagesForCitation(metadata) + "](https://doi.org/" + doi + ")";
return output;
}
// doi must be the doi (10(.\d+)+) and nothing else
function citeDOI(doi, callback, metadata) {
if (metadata === undefined) { getDOIMetaData(doi, callback); return; }
var output = "";
console.log(metadata);
if (TYPE === 1) {
callback.call(this, shortCiteDOI(doi, metadata));
return;
}
output += citeAuthors(metadata.author);
output += citeTitle(metadata.title[0]) + " ";
output += getTitleYearIssuePagesForCitation(metadata);
output += " [DOI: " + doi + "](https://doi.org/" + doi + ").";
callback.call(this, output);
}
function getTitleYearIssuePagesForCitation(metadata){
var issue = metadata.issue,
output = "*" + getShortJournalTitle(metadata) + "*",
volume = metadata.volume, page = metadata.page;
output += " **" + getPublishedYear(metadata); // issue: 10.1021/ci00024a006 gives back 2005, though it was published in 1995 (legacy archives)
// books may not have volumes (10.1007/0-306-48639-3_12)
if(volume){
output += ",** *" + volume;
output += (issue && "* (" + issue + ")") + (page ? issue ? "," : "" : ".");
}
console.log(output);
// page numbers are absent in ACS Article ASAP service or some other papers (10.1371/journal.pone.0068486)
if(page) output += (volume ? " " : ",** ") + getPageRange(page) + ".";
console.log(output);
if(!page && !volume) output += ".**";
return output;
}
function getPublishedYear(metadata){
// the last two are not always accurate (github.com/CrossRef/rest-api-doc/issues/381)
var usableDateParts = metadata["published-print"] || metadata["published-online"] || metadata["created"];
return usableDateParts["date-parts"][0][0];
}
function getShortJournalTitle(metadata){
// user needs to install this via a GitHub Gist
var journalList = localStorage.getItem(J_KEY), title = metadata["container-title"], shortTitle = metadata["short-container-title"];
// fallback to sometimes inaccurate CrossRef results in case user didn't install Gist
// (eg: missing the short-container-title field (10.1023/A:1008989800098); incorrect short form (Tetrahedron Letters instead of Tetrahedron Lett.)
// fallback to unabbrev. title in case neither list has the abbrev., or in case it's a book (not a journal - 10.1007/0-306-48639-3_12)
return journalList ? journalList[title] :
shortTitle.length !== 0 ? shortTitle[0] : title;
}
function citeAuthors(authors) {
// there needn't be authors all the time; (10.1007/0-306-48639-3_12)
if(!authors || authors.length === 0) return "";
var citation = "";
for (var i = 0, len = authors.length; i < len; i++) {
// 10.1248/cpb.49.1102 has all its author names in ALL CAPS; capitalize its only first letter
citation += capitalizeFirstLetter(authors[i].family) + "," + getInitials(authors[i].given);
citation += "; ";
}
// final two characters ("; ") are unnecessary
var strLen = citation.length;
citation = citation.substring(0, strLen - 2) + " ";
return citation;
}
function getInitials(givenName) {
return givenName.split(" ").reduce((citation, name) => citation + " " + name[0] + ".", "");
}
var toTitleCase, capitalizeFirstLetter;
(function caseHelpers(){
// from https://github.com/ianstormtaylor/to-no-case
var hasSpace = /\s/;
var hasSeparator = /(_|-|\.|:)/;
var hasCamel = /([a-z][A-Z]|[A-Z][a-z])/;
/**
* Remove any starting case from a `string`, like camel or snake, but keep
* spaces and punctuation that may be important otherwise.
*
* @param {String} string
* @return {String}
*/
function toNoCase(string) {
if (hasSpace.test(string)) return string.toLowerCase();
if (hasSeparator.test(string)) return (unseparate(string) || string).toLowerCase();
if (hasCamel.test(string)) return uncamelize(string).toLowerCase();
return string.toLowerCase();
}
/**
* Separator splitter.
*/
var separatorSplitter = /[\W_]+(.|$)/g;
/**
* Un-separate a `string`.
*
* @param {String} string
* @return {String}
*/
function unseparate(string) {
return string.replace(separatorSplitter, function (m, next) {
return next ? ' ' + next : '';
});
}
/**
* Camelcase splitter.
*/
var camelSplitter = /(.)([A-Z]+)/g;
/**
* Un-camelcase a `string`.
*
* @param {String} string
* @return {String}
*/
function uncamelize(string) {
return string.replace(camelSplitter, function (m, previous, uppers) {
return previous + ' ' + uppers.toLowerCase().split('').join(' ');
});
}
// via https://github.com/ianstormtaylor/title-case-minors and https://github.com/ianstormtaylor/to-title-case
var minors = ['a', 'an', 'and', 'as', 'at', 'but', 'by', 'en', 'for',' from',
'how', 'if', 'in', 'neither', 'nor', 'of', 'on', 'only', 'onto','out',
'or', 'per', 'so', 'than', 'that', 'the', 'to', 'until','up', 'upon',
'v', 'v.', 'versus', 'vs', 'vs.', 'via', 'when', 'with', 'without', 'yet'],
escaped = minors.map(function(str){
return String(str).replace(/([.*+?=^!:${}()|[\]\/\\])/g, '\\$1');
}),
minorMatcher = new RegExp('[^^]\\b(' + escaped.join('|') + ')\\b', 'ig'),
punctuationMatcher = /:\s*(\w)/g;
function toSentenceCase(string) {
return toNoCase(string).replace(/[a-z]/i, function (letter) {
return letter.toUpperCase();
}).trim();
}
toTitleCase = function(string) {
return toSentenceCase(string)
.replace(/(^|\s)(\w)/g, function (matches, previous, letter) {
return previous + letter.toUpperCase();
})
.replace(minorMatcher, function (minor) {
return minor.toLowerCase();
})
.replace(punctuationMatcher, function (letter) {
return letter.toUpperCase();
});
};
capitalizeFirstLetter = function(word){
return word.charAt(0) + word.substring(1).toLowerCase();
};
})();
function isAllUpcase(string){
for(var i = 0, len = string.length, ch, isLowerCaseCharacter; i < len; i++){
ch = string.charAt(i);
// logs false for up case characters, numbers, symbols
isLowerCaseCharacter = ch === ch.toLowerCase() && ch !== ch.toUpperCase();
if(isLowerCaseCharacter) return false;
}
return true;
}
function citeTitle(title) {
// some titles are received in ALL CAPS (10.1021/ja01532a066)
// fix them to titlecase
if(isAllUpcase(title))
title = toTitleCase(title);
var len = title.length;
// some paper titles don't end with a .
// example: 10.1021/ci00024a006
if (title.charAt(len - 1) !== ".") title += ".";
return title;
}
function getPageRange(pages){
return pages.replace(/[-]/, "–");
}
// general DOI format: http://www.doi.org/doi_handbook/2_Numbering.html#2.2.2
/*
The following web URLs are being checked: (they follow the format specified here https://webhome.weizmann.ac.il/home/comartin/doi.html)
- Wiley - https://onlinelibrary.wiley.com/doi/pdf/10.1002/9780470682531.pat0081
- Springer Verlag - https://link.springer.com/article/10.1007%2Fs002140050256
- Elsevier (ScienceDirect) - https://www.sciencedirect.com/science/article/pii/S0013468602000476
- ACS - https://pubs.acs.org/doi/abs/10.1021/ed029p167
- Nature - https://www.nature.com/articles/s41586-018-0058-6
- RSC - http://pubs.rsc.org/en/content/articlelanding/2011/dt/c0dt01244k/unauth#!divAbstract
The following websites are unsupported:
- American Press IDEAL (10.1006) (absorbed by Elsevier; web URLs non existent; DOIs still work)
- Cambridge UP - https://www.cambridge.org/core/journals/ageing-and-society/article/social-engagement-from-childhood-to-middle-age-and-the-effect-of-childhood-socioeconomic-status-on-middle-age-social-engagement-results-from-the-national-child-development-study/2512DF65E696B95F028BF9209A9FD2DA#
can anyone tell how to extract DOI from this URL?! --^
- i can work on these later
10.1046 Blackwell Publishers. Details to follow.
10.1055 G. Thieme Verlag (Synthesis).Structure: s-YEAR-XXXXX, where XXXXX is a 5-digit article code.
10.1063 American Institute of Physics. The DOI of recent articles (example: http://dx.doi.org/10.1063/1.1385363) can be found on the online abstract of the paper. However, AIP offers a very convenient alternative (a kind-of "OpenURL avant-la-lettre"): http://link.aip.org/link/?jou/vol/firstpage, where "jou" is the three-letter journal abbreviation (e.g. jcp for Journal of Chemical Physics), "vol" is the volume number and "firstpage" the first page number. Example: http://link.aip.org/link/?jcp/115/2051 will link to J. Chem. Phys. 115, 2051 (2001).
10.1073 PNAS (Proceedings of the National Academy of Sciences [USA]). pnas.XXXXXXXXX, where XXXXXXXXX is a 9-digit manuscript number.
10.1074 Journal of Biological Chemistry. Structure: jbc.MXXXXXXXXX, where XXXXXXXXX is a 9-digit manuscript number.
10.1080 Taylor and Francis. This publisher uses 15-character PIIs like Elsevier; again, the PII can generally be found on the online abstract of the journal paper or on the first page of the printed paper. An example DOI URL is http://dx.doi.org/10.1080/002689799163172
10.1083 Rockefeller University Press (e.g. Journal of Cell Biology)
10.1092 Laser Pages Publishing (=Israel Journal of [fill in subject]). Structure: e.g. V0Q8-T3XM-N68W-D8NL.
10.1093 Oxford University Press, EMBO (European Molecular Biology Organisation). Structure: emboj/aaaXXX, where emboj stands for EMBO Journal and aaaXXX is a 3-letter, 3-digit article code.
10.1103 American Physical Society. Example: http://dx.doi.org/10.1103/PhysRevA.68.021801. As you see, JournalName.Volume.ArticleNumber. Like for AIP, an alternative URL is given as follows: http://link.aps.org/abstract/PRA/v68/e021801.
10.1107 International Union of Crystallography (e.g. Acta Crystallographica series). Uses PIIs in same way as Elsevier (see above).
10.1126 SCIENCE magazine. Structure: science.XXXXXXX, where XXXXXXX is a 7-digit article code.
10.1161 American Heart Association.
10.1182 American Society of hematology (e.g. the journal Bloo
*/
function getDOIFromPaperWebURL(originalURL) {
// remove query parameters
var queryMatch = originalURL.match(/[\?#]/), URL = originalURL;
if (queryMatch)
URL = URL.substring(0, queryMatch.index);
if (/wiley/.test(URL)) {
return URL.match(/10\.\d+(\.\d+)?\/.+/)[0];
} else if (/springer/.test(URL)) {
var matcher = URL.match(/(10\.\d+(\.\d+)*?)(%2F)?(.+)/i);
return matcher[1] + "/" + matcher[4];
}
else if (/sciencedirect/.test(URL)) {
var PII = URL.match(/pii\/(.+)\/?/i)[1], sPresent = /pii\/s/i.test(URL), doi = sPresent ? "S" : "";
// the doi substring logic below requires the S to be removed if present
if(sPresent) PII = PII.substring(1);
// PII has to be split like "Sxxxx-xxxx(yy)zzzzz-c" (S can be upcase/lowercase/absent altogether)
doi += PII.substring(0, 4) + "-" + PII.substring(4, 8) + "(" + PII.substring(8, 10) + ")" + PII.substring(10, 15) + "-" + PII.substring(15, 16);
return "10.1016/" + doi;
} else if (/acs/.test(URL)) {
return URL.match(/10\.\d+(\.\d+)?\/.+/)[0];
} else if (/nature/.test(URL)) {
return "10.1038/" + URL.match(/\/(s.+)/)[1];
} else if (/rsc/.test(URL)) {
// rsc url may not always have unauth at its end
return "10.1039/" + (URL.match(/\/([\w]+)\/unAuth$/i) || URL.match(/\/([\w]+)$/))[1];
}else if(/plos/.test(URL)){ // http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0068486
// the id? query parameter gets stripped away in the URL
return "10.1371/journal.pone." + originalURL.match(/pone\.(\d+)/)[1];
}
return null;
}
function getSource(URI) {
// URIs supported: "website"/"doi"/"paperWeb"
// "website" is any website like HyperPhysics
// "doi" is literally a DOI url
// "paperWeb" is a wiley/elsevier/nature/etc. URL
URI = URI.trim();
var validDOIRegexes =
[/^DOI: ?(10\.\d+\/.+)$/i, /^(10\.\d+\/.+)$/i,
/^(https?:?\/?\/?)?(dx.)?doi.org\/(10\.\d+\/.+)$/i];
for (var doiRegex, i = 0, len = validDOIRegexes.length; i < len; i++) {
doiRegex = validDOIRegexes[i];
var match = URI.match(doiRegex);
if (match) {
return ["doi", match[match.length - 1]];
}
}
var DOI = getDOIFromPaperWebURL(URI);
if (DOI) return ["paperWeb", DOI];
var validWebsiteRegexes = [/^(https?:\/?\/?)?(www\.)?[\w\.]+?\.[a-z]+[\w\d\.\/]*$/];
// port://www.completeDomain/path
for (var webRegex, j = 0, l = validWebsiteRegexes.length; j < l; j++) {
webRegex = validWebsiteRegexes[j];
if (webRegex.test(URI)) return ["web", URI];
}
return [null, null];
}
// type of citation to insert; used by citeDOI
// short citation = 1; long citation = 2;
var TYPE = 1;
// insert css
var styleEl = document.createElement('style'),
cssToUse =
`
/**https://cdnjs.com/libraries/awesomplete*/
.awesomplete [hidden]{display:none}.awesomplete .visually-hidden{position:absolute;clip:rect(0,0,0,0)}.awesomplete{display:inline-block;position:relative}.awesomplete>input{display:block}.awesomplete>ul{position:absolute;left:0;z-index:1;min-width:100%;box-sizing:border-box;list-style:none;padding:0;border-radius:.3em;margin:.2em 0 0;background:hsla(0,0%,100%,.9);background:linear-gradient(to bottom right,#fff,hsla(0,0%,100%,.8));border:1px solid rgba(0,0,0,.3);box-shadow:.05em .2em .6em rgba(0,0,0,.2);text-shadow:none}.awesomplete>ul:empty{display:none}@supports (transform:scale(0)){.awesomplete>ul{transition:.3s cubic-bezier(.4,.2,.5,1.4);transform-origin:1.43em -.43em}.awesomplete>ul:empty,.awesomplete>ul[hidden]{opacity:0;transform:scale(0);display:block;transition-timing-function:ease}}.awesomplete>ul:before{content:"";position:absolute;top:-.43em;left:1em;width:0;height:0;padding:.4em;background:#fff;border:inherit;border-right:0;border-bottom:0;-webkit-transform:rotate(45deg);transform:rotate(45deg)}.awesomplete>ul>li{position:relative;padding:.2em .5em;cursor:pointer}.awesomplete>ul>li:hover{background:#b7d2e0;color:#000}.awesomplete>ul>li[aria-selected=true]{background:#3d6c8e;color:#fff}.awesomplete mark{background:#e9ff00}.awesomplete li:hover mark{background:#b5d100}.awesomplete li[aria-selected=true] mark{background:#3c6b00;color:inherit}
.awesomplete{
position: inherit !important;
/* required to keep the input element hidden while modal is collapsed*/
}
#doi-box{
transition: 0.25s ease;
}
#doi-box input{
display: inline-block;
width: 500px;
font-size: 14px;
padding: 8px;
position: inherit;
}
#doi-box button{
position: inherit; /*allows buttons to flow in and out*/
margin: 5px;
}
`;
styleEl.setAttribute('type', 'text/css');
styleEl.textContent = cssToUse;
document.head.appendChild(styleEl);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment