nerd-fonts/cheat-sheet.js
Fini Jastrow 4fde75a4b8 cheat-sheet: Allow direct url to search result
Will be used by the Wiki for example to show the glyph-sets.

Signed-off-by: Fini Jastrow <ulf.fini.jastrow@desy.de>
2024-04-04 18:28:55 +02:00

301 lines
12 KiB
JavaScript

document.addEventListener('DOMContentLoaded', function () {
const elementGlyphSearch = document.getElementById('glyphSearch');
const elementGlyphCheatSheet = document.getElementById('glyphCheatSheet');
const maxSearchResults = 250;
// Find the value of one query string
function querySt(ji) {
const hu = window.location.search.substring(1);
const gy = hu.split('&');
for (i = 0; i < gy.length; i++) {
ft = gy[i].split('=');
if (ft[0] == ji) {
return ft[1];
}
}
}
// Jump to search for provided query key, e.g. https://www.nerdfonts.com/cheat-sheet?q=cpu
const fieldName = querySt('q');
if (fieldName != null) {
elementGlyphSearch.value = fieldName;
}
// Storage for not-yet-rendered search results. More results will are rendered when scrolled to the bottom.
let remainingSearchResults = [];
// Index all glyphs/icons
let miniSearch = new MiniSearch({
fields: ['id', 'code', 'name'], // fields to index for full-text search
storeFields: ['id', 'code', 'isRemoved'], // fields to return with search results
})
miniSearch.addAll(Object.entries(glyphs).map(
([key, value]) => {
return {
id: key,
code: value,
name: key.substring(key.lastIndexOf('-') + 1),
isRemoved: key.startsWith('nfold'),
}
}
));
document.onscroll = function () {
const elementDoc = document.documentElement;
const elementIconsTop = elementGlyphCheatSheet.getBoundingClientRect().top + elementDoc.scrollTop;
if (elementDoc.clientHeight + elementDoc.scrollTop >=
elementGlyphCheatSheet.clientHeight + elementIconsTop) {
console.log("load more search results");
loadMoreSearchResults();
}
};
// search via typing in input (debounced)
elementGlyphSearch && elementGlyphSearch.addEventListener(
'input',
debounce(function (e) {
gtag('event', 'search-via-input', {
event_category: 'glyph-search',
event_label: 'Cheat Sheet : ' + (e.target && e.target.value),
value: e.target && e.target.value
});
searchGlyphs();
}, 500)
);
// Credit David Walsh (https://davidwalsh.name/javascript-debounce-function)
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
function debounce(func, wait, immediate) {
let timeout;
return function executedFunction() {
let context = this;
let args = arguments;
let later = function () {
timeout = null;
if (!immediate) func.apply(context, args);
};
let callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
}
function addSearchResultItemToFragment(fragment, iconClassName, codepoint) {
let parentDiv = document.createElement("div");
parentDiv.setAttribute("class", "column");
let index = iconClassName.indexOf('-');
let namespace = iconClassName.substring(0, index);
let iconClassNameBeforeDeprecation = iconClassName;
if (namespace === "nfold") {
let rest = iconClassName.substring(index + 1);
iconClassNameBeforeDeprecation = `nf-${rest}`;
let span1 = document.createElement("span");
span1.setAttribute("class", "corner-red");
parentDiv.appendChild(span1);
let span2 = document.createElement("span");
span2.setAttribute("class", "corner-text");
span2.textContent = "removed";
parentDiv.appendChild(span2);
}
let innerDiv1 = document.createElement("div");
innerDiv1.setAttribute("class", `center ${namespace} ${iconClassName}`);
parentDiv.appendChild(innerDiv1);
let innerDiv2 = document.createElement("div");
innerDiv2.setAttribute("class", "class-name");
innerDiv2.textContent = iconClassNameBeforeDeprecation;
parentDiv.appendChild(innerDiv2);
let innerDiv3 = document.createElement("div");
innerDiv3.setAttribute("class", "codepoint");
innerDiv3.setAttribute("title", "Copy Hex Code to Clipboard");
innerDiv3.textContent = codepoint;
parentDiv.appendChild(innerDiv3);
fragment.appendChild(parentDiv);
}
function loadMoreSearchResults() {
let fragment = document.createDocumentFragment();
remainingSearchResults.slice(0, maxSearchResults).forEach(result => {
addSearchResultItemToFragment(fragment, result.id, result.code);
});
elementGlyphCheatSheet.append(fragment);
remainingSearchResults = remainingSearchResults.slice(maxSearchResults, -1);
}
function searchGlyphs() {
// Convert high codepoint-chars to codepoint text - enabling search for pasted icon
let searchTerm = [...elementGlyphSearch.value]
.map((char) => char.codePointAt(0) > 255 ? char.codePointAt(0).toString(16) : char)
.join("");
let prefixSearchEnabled = true;
let emptyResultsMessage = `<span style="font-size: 30px;">No matches found</span>`;
if (searchTerm === "") {
// MiniSearch doesn't allow empty searches and we want to show the bottom info
emptyResultsMessage = "Enter (part) of a word to search for or paste the icon or enter space / blank (' ') to show all icons"
} else {
if (searchTerm === " ") {
prefixSearchEnabled = false;
searchTerm = "nf";
}
}
// TODO: search suggestions
remainingSearchResults = miniSearch.search(searchTerm,
{
prefix: prefixSearchEnabled,
combineWith: "AND",
boost: {id: 0.001},
boostDocument: ((documentId, term, storedFields) => {
return storedFields.isRemoved ? 0.00001 : 1
}),
}
);
console.log(`search: ${remainingSearchResults.length} results found`);
elementGlyphCheatSheet.replaceChildren([]);
elementGlyphCheatSheet.scrollTop = 0;
if (remainingSearchResults.length != 0) {
loadMoreSearchResults();
} else {
let notFoundElem = document.createElement("div");
notFoundElem.setAttribute("style", "padding: 10px;");
notFoundElem.innerHTML = emptyResultsMessage;
elementGlyphCheatSheet.appendChild(notFoundElem);
}
}
elementGlyphCheatSheet && elementGlyphCheatSheet.addEventListener(
'mouseenter',
function (e) {
if (e.target.classList.contains('column')) {
// add Node
const newNode = document.createElement('span');
const copyTextNode = document.createElement('span');
const copyGlyphNode = document.createElement('span');
const copyClassNode = document.createElement('span');
const copyCodePoint = document.createElement('span');
newNode.className = 'glyph-popout-copy-clipboard';
copyTextNode.innerText = 'Copy';
copyGlyphNode.innerText = 'Icon';
copyClassNode.innerText = 'Class';
copyCodePoint.innerText = 'UTF';
copyGlyphNode.title = 'Copy Icon to Clipboard';
copyClassNode.title = 'Copy Class Name to Clipboard';
copyCodePoint.title = 'Copy UTF-16 Codes to Clipboard';
copyGlyphNode.className = 'copy-glyph';
copyClassNode.className = 'copy-class';
copyCodePoint.className = 'copy-utf16';
newNode.appendChild(copyTextNode);
newNode.appendChild(copyGlyphNode);
newNode.appendChild(copyClassNode);
newNode.appendChild(copyCodePoint);
e.target.children[0].before(newNode);
}
},
true
);
elementGlyphCheatSheet && elementGlyphCheatSheet.addEventListener(
'mouseleave',
function (e) {
if (e.target.classList.contains('column')) {
e.target.querySelector('.glyph-popout-copy-clipboard').remove();
}
},
true
);
elementGlyphCheatSheet && elementGlyphCheatSheet.addEventListener('click', function (event) {
let textToCopy = '';
let target = event.target;
let parent = target.parentNode;
if (parent.className === 'glyph-popout-copy-clipboard') {
if (target.className === 'copy-class') {
textToCopy = parent.parentNode.querySelector('.class-name').innerText;
} else if (target.className === 'copy-glyph') {
textToCopy = window
.getComputedStyle(document.querySelector(`.${parent.parentNode.querySelector('.class-name').innerText}`), ':before')
.getPropertyValue('content')
.replace(/"/g, '');
} else if (target.className === 'copy-utf16') {
const glyph = window
.getComputedStyle(document.querySelector(`.${parent.parentNode.querySelector('.class-name').innerText}`), ':before')
.getPropertyValue('content')
.replace(/"/g, '');
textToCopy = '';
let i = 0;
while (i < 10) {
let c = glyph.charCodeAt(i++); // js strings are utf16 already :-)
if (!(c > 0)) break;
if (c <= 0x32) continue;
textToCopy += '\\u' + c.toString(16);
}
}
} else if (parent.className.startsWith('column') && target.className === 'codepoint') {
textToCopy = target.innerText;
}
if (textToCopy.length > 0) {
gtag('event', event.target.className, {
event_category: 'clipboard-copy',
event_label: 'Copy To Clipboard : ' + textToCopy,
value: textToCopy
});
copyToClipboard(textToCopy);
}
});
// Copies a string to the clipboard. Must be called from within an
// event handler such as click. May return false if it failed, but
// this is not always possible. Browser support for Chrome 43+,
// Firefox 42+, Safari 10+, Edge and IE 10+.
// IE: The clipboard feature may be disabled by an administrator. By
// default a prompt is shown the first time the clipboard is
// used (per session).
function copyToClipboard(text) {
if (window.clipboardData && window.clipboardData.setData) {
// IE specific code path to prevent textarea being shown while dialog is visible.
return clipboardData.setData('Text', text);
} else if (document.queryCommandSupported && document.queryCommandSupported('copy')) {
let textarea = document.createElement('textarea');
textarea.textContent = text;
textarea.style.position = 'fixed'; // Prevent scrolling to bottom of page in MS Edge.
document.body.appendChild(textarea);
textarea.select();
try {
return document.execCommand('copy'); // Security exception may be thrown by some browsers.
} catch (ex) {
console.warn('Copy to clipboard failed.', ex);
return false;
} finally {
document.body.removeChild(textarea);
}
}
}
searchGlyphs(); // shows all glyphs at first load
})