Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,18 @@ desc "Run nanoc compile"
task :compile => [:pre_compile,
:real_compile, :post_compile]

desc "Run nanoc compile for a predefined set of test packages"
task :compile_test do
ENV["TEST_PACKAGES"] = "true"
Rake::Task[:compile].invoke
end

desc "Build the bioconductor.org site for test packages"
task :build_test do
ENV["TEST_PACKAGES"] = "true"
Rake::Task[:build].invoke
end

desc "Pre-compilation tasks"
task :pre_compile do
FileUtils.mkdir_p "content/packages"
Expand Down
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
25 changes: 23 additions & 2 deletions layouts/_bioc_views_package_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,31 @@ <h2><%= @package[:Title]%></h2>
<p>
<strong>Maintainer:</strong> <%= filter_emails(@package[:Maintainer])%>
</p>
<div id="bioc-citation-outer">

<!-- Package-specific citation (loaded from citations directory) -->

<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>
<div class="bioc-citation">
<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>
<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>
</div>
</div>

Expand All @@ -47,4 +69,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>
10 changes: 10 additions & 0 deletions lib/data_sources/bioc_views.rb
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,16 @@ def items
packages.delete(bad)
end

pkg_filter = ENV["ONLY_PACKAGES"] || ENV["PKG"]
if (ENV["TEST_PACKAGES"] == "true" || ENV["DEBUG_PACKAGES"] == "true") && (!pkg_filter || pkg_filter.empty?)
pkg_filter = "SummarizedExperiment,Biobase,BiocBaseUtils,BiocGenerics,DelayedArray,GenomicRanges,IRanges,S4Vectors"
end

if pkg_filter && !pkg_filter.empty?
allowed = pkg_filter.split(",").map(&:strip)
packages.select! { |k, _| allowed.include?(k) }
end

items.push(get_index_page(packages, v, version))

for package in packages.keys
Expand Down