Skip to content
144 changes: 144 additions & 0 deletions assets/js/bioconductor.js
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,84 @@ var getHrefForSymlinks = function (href) {
}
};

var fallbackCopyText = function (text) {
return new Promise(function (resolve, reject) {
var textarea = document.createElement("textarea");
textarea.value = text;
textarea.setAttribute("readonly", "");
textarea.style.position = "absolute";
textarea.style.left = "-9999px";
document.body.appendChild(textarea);
textarea.select();
textarea.setSelectionRange(0, textarea.value.length);
try {
if (document.execCommand("copy")) {
resolve();
} else {
reject(new Error("Copy command failed"));
}
} catch (e) {
reject(e);
} finally {
document.body.removeChild(textarea);
}
});
};

var writeTextToClipboard = function (text) {
if (navigator.clipboard && window.isSecureContext) {
return navigator.clipboard.writeText(text).catch(function () {
return fallbackCopyText(text);
});
}
return fallbackCopyText(text);
};

var CLIPBOARD_EMOJI = "\uD83D\uDCCB";
var DOI_BODY_PATTERN = "[\\w.()/_-]+";
var DOI_PATTERN = "10\\.\\d{4,}\\/" + DOI_BODY_PATTERN;

var encodeForShieldsIO = function (text) {
return text
.replace(/-/g, "--")
.replace(/_/g, "__")
.replace(/ /g, "_")
.replace(/[^A-Za-z0-9._-]/g, function (c) { return encodeURIComponent(c); });
};

// Copy text to clipboard and show brief feedback on the button
var copyToClipboardWithFeedback = function (btn, text, label, statusEl) {
writeTextToClipboard(text).then(function () {
btn.text("Copied!").addClass("copied");
if (statusEl) { statusEl.text("Copied to clipboard."); }
setTimeout(function () {
btn.text(label).removeClass("copied");
if (statusEl) { statusEl.text(""); }
}, 2000);
}).catch(function () {
btn.text("Copy failed");
if (statusEl) { statusEl.text("Copy failed. Please copy the text manually."); }
setTimeout(function () {
btn.text(label);
if (statusEl) { statusEl.text(""); }
}, 3000);
});
};

// Attach event listeners to static citation copy buttons that use data attributes.
// Handles .citation-btn elements with data-citation-text or data-bibtex attributes,
// replacing the need for inline onclick handlers in content pages.
var handleStaticCitationButtons = function () {
jQuery(document).on("click", ".citation-btn[data-citation-text]", function () {
var btn = jQuery(this);
copyToClipboardWithFeedback(btn, btn.data("citation-text"), btn.text(), null);
});
jQuery(document).on("click", ".citation-btn[data-bibtex]", function () {
var btn = jQuery(this);
copyToClipboardWithFeedback(btn, btn.data("bibtex"), btn.text(), null);
});
};

var handleCitations = function () {
if (jQuery("#bioc-citation").length) {
jQuery("#bioc-citation-outer").hide();
Expand All @@ -331,6 +409,8 @@ var handleCitations = function () {
segs.push(pkg);
segs.push("citation.html");
url = segs.join("/");
var bibUrl = url.replace("citation.html", "citation.bib");
var pkgName = jQuery("#bioc-citation-outer").data("package") || pkg;
jQuery.ajax({
url: url,
dataType: "html",
Expand All @@ -342,6 +422,69 @@ var handleCitations = function () {

data = data.replace(" (????)", "");
jQuery("#bioc-citation").html(data);

// Extract preferred DOI from citation text.
// Use a pattern matching only valid DOI characters per the DOI specification.
var citationText = jQuery("#bioc-citation").text();
var doiPattern = new RegExp("\\bdoi:?(" + DOI_PATTERN + ")", "i");
var urlPattern = new RegExp("https?:\\/\\/doi\\.org\\/(" + DOI_PATTERN + ")", "i");
var doiMatch = citationText.match(doiPattern) || citationText.match(urlPattern);
var preferredDoi = doiMatch ? doiMatch[1] : "10.18129/B9.bioc." + pkgName;

// Sanitize DOI: only allow characters valid in a DOI (alphanumeric and
// DOI-permitted punctuation). Falls back to the package landing page DOI.
if (!(new RegExp("^" + DOI_PATTERN + "$")).test(preferredDoi)) {
preferredDoi = "10.18129/B9.bioc." + pkgName;
}

var encodedDoi = encodeForShieldsIO(preferredDoi);

var doiHref = "https://doi.org/" + encodeURIComponent(preferredDoi);
var badgeSrc = "https://img.shields.io/badge/DOI-" + encodedDoi + "-blue";
var $badgeLink = jQuery("<a>")
.attr("href", doiHref)
.attr("title", "Preferred citation DOI");
var $badgeImg = jQuery("<img>")
.attr("src", badgeSrc)
.attr("alt", "DOI badge");
jQuery("#citation-doi-badge").empty().append($badgeLink.append($badgeImg));

// Add copy action buttons
var actionsHtml =
'<button class="citation-btn" id="bioc-copy-text-btn" aria-label="Copy citation as text">' + CLIPBOARD_EMOJI + ' Copy Text</button>' +
'<button class="citation-btn" id="bioc-copy-bibtex-btn" aria-label="Copy citation as BibTeX">' + CLIPBOARD_EMOJI + ' Copy BibTeX</button>';
jQuery("#bioc-citation-actions").html(actionsHtml);

jQuery("#bioc-copy-text-btn").on("click", function () {
var btn = jQuery(this);
var statusEl = jQuery("#bioc-citation-status");
var text = jQuery("#bioc-citation").text().trim();
copyToClipboardWithFeedback(btn, text, CLIPBOARD_EMOJI + " Copy Text", statusEl);
});

jQuery("#bioc-copy-bibtex-btn").on("click", function () {
var btn = jQuery(this);
var statusEl = jQuery("#bioc-citation-status");
var origLabel = CLIPBOARD_EMOJI + " Copy BibTeX";
btn.prop("disabled", true);
jQuery.ajax({
url: bibUrl,
dataType: "text",
success: function (bibData) {
copyToClipboardWithFeedback(btn, bibData, origLabel, statusEl);
btn.prop("disabled", false);
},
error: function () {
btn.text("BibTeX unavailable");
if (statusEl) { statusEl.text("BibTeX format is not available for this package."); }
setTimeout(function () {
btn.text(origLabel).prop("disabled", false);
if (statusEl) { statusEl.text(""); }
}, 3000);
},
});
});

jQuery("#bioc-citation-outer").show();
},
error: function (data, textStatus, jqXHR) {
Expand All @@ -361,6 +504,7 @@ jQuery(function () {
});
jQuery(".rpack").tooltip({ tip: "#tooltip" }); //{ effect: 'slide'});
handleCitations();
handleStaticCitationButtons();
});

var submit_tryitnow = function () {
Expand Down
37 changes: 37 additions & 0 deletions assets/style/pages/packages.css
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,43 @@ ul.ui-autocomplete li::marker {
margin: 0.5rem 0 1rem;
}

.citation-actions {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin: 0.75rem 0 0.25rem;
}

.citation-btn {
background: var(--neutral-n50);
color: var(--primary-p400);
border: 1px solid var(--neutral-n75);
border-radius: 8rem;
cursor: pointer;
font-size: 0.85rem;
padding: 0.2rem 0.75rem;
transition: background 0.2s;
}

.citation-btn:hover {
background: var(--primary-p50);
}

.citation-btn:active {
background: var(--primary-p200);
}

.citation-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}

.citation-btn.copied {
background: #d4edda;
color: #155724;
border-color: #c3e6cb;
}

@media (max-device-width: 1450px), (max-width: 1450px) {
.biocViewsTreeContainer {
display: flex;
Expand Down
28 changes: 26 additions & 2 deletions layouts/_bioc_views_package_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,34 @@ <h2><%= @package[:Title]%></h2>
<p>
<strong>Maintainer:</strong> <%= filter_emails(@package[:Maintainer])%>
</p>
<div id="bioc-citation-outer">
<div id="bioc-citation-outer" data-package="<%=@package[:Package]%>">
<strong>Citation (from within R, enter <code>citation("<%=@package[:Package]%>")</code>):</strong>
<div id="bioc-citation" class="bioc-citation"></div>
<div class="citation-actions" id="bioc-citation-actions"></div>
<div id="bioc-citation-status" aria-live="polite" aria-atomic="true" class="sr-only"></div>
</div>
<div class="bioc-project-citations">
<strong>Seminal Bioconductor project articles:</strong>
<p><strong>Huber W</strong>, Carey VJ, Gentleman R, Anders S, Carlson M, Carvalho BS,
Bravo HC, Davis S, Gatto L, Girke T, Gottardo R, Hahne F, Hansen KD, Irizarry RA,
Lawrence M, Love MI, MacDonald J, Obenchain V, Oleś AK, Pagès H, Reyes A, Shannon P,
Smyth GK, Tenenbaum D, Waldron L, Morgan M (2015). "Orchestrating high-throughput
genomic analysis with Bioconductor." <em>Nature Methods</em>, <strong>12</strong>(2),
115–121. doi:<a href="https://doi.org/10.1038/nmeth.3252">10.1038/nmeth.3252</a>.</p>
<div class="citation-actions">
<button class="citation-btn" aria-label="Copy Huber 2015 citation as text" data-citation-text="Huber W, Carey VJ, Gentleman R, Anders S, Carlson M, Carvalho BS, Bravo HC, Davis S, Gatto L, Girke T, Gottardo R, Hahne F, Hansen KD, Irizarry RA, Lawrence M, Love MI, MacDonald J, Obenchain V, Oles AK, Pages H, Reyes A, Shannon P, Smyth GK, Tenenbaum D, Waldron L, Morgan M (2015). Orchestrating high-throughput genomic analysis with Bioconductor. Nature Methods, 12(2), 115-121. doi:10.1038/nmeth.3252.">📋 Copy Text</button>
<button class="citation-btn" aria-label="Copy Huber 2015 citation as BibTeX" data-bibtex="@Article{Huber2015-bp,&#10; title = {Orchestrating high-throughput genomic analysis with Bioconductor},&#10; author = {Huber, Wolfgang and Carey, Vincent J and Gentleman, Robert and Anders, Simon and Carlson, Marc and Carvalho, Benilton S and Bravo, Hector Corrada and Davis, Sean and Gatto, Laurent and Girke, Thomas and Gottardo, Raphael and Hahne, Florian and Hansen, Kasper D and Irizarry, Rafael A and Lawrence, Michael and Love, Michael I and MacDonald, James and Obenchain, Valerie and Oles, Andrzej K and Pages, Herve and Reyes, Alejandro and Shannon, Paul and Smyth, Gordon K and Tenenbaum, Dan and Waldron, Levi and Morgan, Martin},&#10; journal = {Nat. Methods},&#10; volume = 12,&#10; number = 2,&#10; pages = {115--121},&#10; month = feb,&#10; year = 2015,&#10; language = {en}&#10;}">📋 Copy BibTeX</button>
</div>
<p><strong>Gentleman RC</strong>, Carey VJ, Bates DM, Bolstad B, Dettling M, Dudoit S,
Ellis B, Gautier L, Ge Y, Gentry J, Hornik K, Hothorn T, Huber W, Iacus S, Irizarry R,
Leisch F, Li C, Maechler M, Rossini AJ, Sawitzki G, Smith C, Smyth G, Tierney L, Yang JYH,
Zhang J (2004). "Bioconductor: open software development for computational biology and
bioinformatics." <em>Genome Biology</em>, <strong>5</strong>(10), R80.
doi:<a href="https://doi.org/10.1186/gb-2004-5-10-r80">10.1186/gb-2004-5-10-r80</a>.</p>
<div class="citation-actions">
<button class="citation-btn" aria-label="Copy Gentleman 2004 citation as text" data-citation-text="Gentleman RC, Carey VJ, Bates DM, Bolstad B, Dettling M, Dudoit S, Ellis B, Gautier L, Ge Y, Gentry J, Hornik K, Hothorn T, Huber W, Iacus S, Irizarry R, Leisch F, Li C, Maechler M, Rossini AJ, Sawitzki G, Smith C, Smyth G, Tierney L, Yang JYH, Zhang J (2004). Bioconductor: open software development for computational biology and bioinformatics. Genome Biology, 5(10), R80. doi:10.1186/gb-2004-5-10-r80.">📋 Copy Text</button>
<button class="citation-btn" aria-label="Copy Gentleman 2004 citation as BibTeX" data-bibtex="@Article{Gentleman2004-wd,&#10; title = {Bioconductor: open software development for computational biology and bioinformatics},&#10; author = {Gentleman, Robert C and Carey, Vincent J and Bates, Douglas M and Bolstad, Ben and Dettling, Marcel and Dudoit, Sandrine and Ellis, Byron and Gautier, Laurent and Ge, Yongchao and Gentry, Jeff and Hornik, Kurt and Hothorn, Torsten and Huber, Wolfgang and Iacus, Stefano and Irizarry, Rafael and Leisch, Friedrich and Li, Cheng and Maechler, Martin and Rossini, Anthony J and Sawitzki, Gunther and Smith, Colin and Smyth, Gordon and Tierney, Luke and Yang, Jean Y H and Zhang, Jianhua},&#10; journal = {Genome Biol.},&#10; publisher = {Springer Nature},&#10; volume = 5,&#10; number = 10,&#10; pages = {R80},&#10; month = sep,&#10; year = 2004,&#10; language = {en}&#10;}">📋 Copy BibTeX</button>
</div>
</div>
</div>

Expand All @@ -47,4 +72,3 @@ <h2><%= @package[:Title]%></h2>
<%= render("/components/packages/details/", :package => @package) %>
<%= render("/components/packages/archives/", :package => @package) %>
</div>

1 change: 1 addition & 0 deletions layouts/components/packages/badges.html
Original file line number Diff line number Diff line change
Expand Up @@ -161,4 +161,5 @@
10.18129/B9.bioc.<%=@item[:Package]%>
</a>
</p>
<div id="citation-doi-badge"></div>
</div>