pidgin/nest

Add pgp public key and refer to it in our security disclosure method

The idea behind this is to make sure people that choose to use email to let us
known about a potential security issue in Pidgin encrypt the content of the
emails using a set of pgp keys of trusted Pidgin developers. For now we only
have the pgp key of grim but more keys can easily be added later on.

At the same time, since Hugo does not currently have a built-in shortcode for
linking directly to static resources, I had to add a new shortcode for doing so

Testing Done:
Ran `dev-server.sh` and made sure content looked as intended and links worked.

Bugs closed: NEST-46

Reviewed at https://reviews.imfreedom.org/r/860/
/**
* Array representing each plugin row
* @type {{
* elem: HTMLElement,
* head: string,
* info: string,
* repo: string,
* isTrusted: boolean
* }[]}
*/
var pluginRow;
/**
* String Search element
* @type {HTMLInputElement}
*/
var searchTextBox;
var url = new URL(window.location);
var params = url.searchParams;
var searchState = {
/**
* @type {"All" | "Trusted" | "Community"}
*/
publisher: params.get("publisher") || "all",
/**
* String to search for
*/
query: params.get("query") || "",
/**
* What type of plugin has been selected
*/
type: new Set(
(params.get("type") || "")
.split(",")
.map(function(str) {
return str.trim();
})
.filter(Boolean)
),
};
document.addEventListener("DOMContentLoaded", function() {
// Iterate through rows and populate
pluginRow = Array.from($("#plugin-table>tbody>tr")).map(function(elem) {
var contents = function(selectorQuery) {
return $(elem)
.find(selectorQuery)
.text()
.trim();
};
return {
elem,
head: contents(".plugin-heading"),
info: contents(".plugin-info"),
repo: contents(".plugin-repo"),
isTrusted: elem.getAttribute("isTrusted") == "true",
};
});
// Set up publisher selector interface
var publisherSelectors = $("#publisher-selector");
var publisherChecked = $('input[id="' + searchState.publisher + '"]', publisherSelectors);
if (publisherChecked) {
publisherChecked.prop('checked', true);
} else {
searchState.publisher = "all";
$('input[id="all"]', publisherSelectors).prop('checked', true);
}
publisherSelectors.click(updateFilters);
// Set up search box interface
searchTextBox = document.querySelector("#plugin-filter-search");
searchTextBox.value = searchState.query;
searchTextBox.addEventListener(
"input",
// Wait half a second
debounce(500, updateFilters)
);
// Set up plugin type selector interface
Array.from(
document.querySelectorAll("#plugin-selector>label>input")
).forEach(function(elem) {
if (searchState.type.has(elem.dataset.type)) {
elem.checked = true;
}
elem.addEventListener("click", pluginTypeSelectorClickEvent);
});
updateFilters();
$("#plugin-filters,#search-link-wrap").removeClass("hidden");
});
/**
* Event handler for plugin type checkboxes
* @param {MouseEvent} event
* @param {HTMLInputElement} event.target
*/
function pluginTypeSelectorClickEvent({ target }) {
searchState.type[target.checked ? "add" : "delete"](target.dataset.type);
updateFilters();
}
/**
* Visually filters Plugins by toggling visibility
*/
function updateFilters() {
searchState.query = (searchTextBox.value || "").toLowerCase();
searchState.publisher = $('input[name="publisher"]:checked')[0].value;
pluginRow.forEach(function(row) {
if (shouldFilter(row)) {
row.elem.classList.remove("hidden");
} else {
row.elem.classList.add("hidden");
}
});
// Updates query string
params.set("publisher", searchState.publisher);
params.set("query", searchState.query);
params.set("type", Array.from(searchState.type).join(","));
var urlString = String(url);
if (history.pushState) {
history.pushState({ path: urlString }, "", urlString);
}
var link = $("#search-link");
link.attr("href", urlString);
link.text(urlString);
}
/**
* Decides if a plugin row should be filtered
* @param {*} row
* @returns boolean
*/
function shouldFilter(row) {
return (
// Checkboxes
(!searchState.type.size ||
searchState.type.has(row.elem.dataset.type)) &&
// Provider
(searchState.publisher == "all" ||
(searchState.publisher == "pidgin" && row.isTrusted) ||
(searchState.publisher == "community" && !row.isTrusted)) &&
// Search box
(!searchState.query ||
row.head.toLowerCase().includes(searchState.query) ||
row.info.toLowerCase().includes(searchState.query) ||
row.repo.toLowerCase().includes(searchState.query))
);
}
/**
* Simple Debounce function
* @param {time} time
* @param {function} callback
*/
function debounce(time, callback) {
var timer;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(function() {
callback(...args);
}, time);
};
}