diff --git a/assets/js/bioconductor.js b/assets/js/bioconductor.js index 6c86338b..fdbe6427 100644 --- a/assets/js/bioconductor.js +++ b/assets/js/bioconductor.js @@ -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(); @@ -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", @@ -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("") + .attr("href", doiHref) + .attr("title", "Preferred citation DOI"); + var $badgeImg = jQuery("") + .attr("src", badgeSrc) + .attr("alt", "DOI badge"); + jQuery("#citation-doi-badge").empty().append($badgeLink.append($badgeImg)); + + // Add copy action buttons + var actionsHtml = + '' + + ''; + 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) { @@ -361,6 +504,7 @@ jQuery(function () { }); jQuery(".rpack").tooltip({ tip: "#tooltip" }); //{ effect: 'slide'}); handleCitations(); + handleStaticCitationButtons(); }); var submit_tryitnow = function () { diff --git a/assets/style/pages/packages.css b/assets/style/pages/packages.css index 850a6315..8c2e1159 100644 --- a/assets/style/pages/packages.css +++ b/assets/style/pages/packages.css @@ -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; diff --git a/layouts/_bioc_views_package_detail.html b/layouts/_bioc_views_package_detail.html index 4575078e..d2c40995 100644 --- a/layouts/_bioc_views_package_detail.html +++ b/layouts/_bioc_views_package_detail.html @@ -34,9 +34,34 @@

<%= @package[:Title]%>

Maintainer: <%= filter_emails(@package[:Maintainer])%>

-
+
Citation (from within R, enter citation("<%=@package[:Package]%>")):
+
+
+
+
+ Seminal Bioconductor project articles: +

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, 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." Nature Methods, 12(2), + 115–121. doi:10.1038/nmeth.3252.

+
+ + +
+

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.

+
+ + +
@@ -47,4 +72,3 @@

<%= @package[:Title]%>

<%= render("/components/packages/details/", :package => @package) %> <%= render("/components/packages/archives/", :package => @package) %> - diff --git a/layouts/components/packages/badges.html b/layouts/components/packages/badges.html index 9a8490d0..dfd203ec 100644 --- a/layouts/components/packages/badges.html +++ b/layouts/components/packages/badges.html @@ -161,4 +161,5 @@ 10.18129/B9.bioc.<%=@item[:Package]%>

+