summaryrefslogtreecommitdiffstats
path: root/static
diff options
context:
space:
mode:
authorRoman <59285587+codeandmedia@users.noreply.github.com>2020-03-02 20:29:30 +0300
committerGitHub <noreply@github.com>2020-03-02 20:29:30 +0300
commit7c8d3f1566b8bc22b9c788ef9dd4f31b647fcd69 (patch)
tree023b92cd9ffbdb5c57aab7cb44b15686c6fc6c00 /static
parenta92cc979ee3d0699e8b0ebb81c2901d570f91c09 (diff)
Add files via upload
Diffstat (limited to 'static')
-rw-r--r--static/search.js193
-rw-r--r--static/themes.js136
2 files changed, 329 insertions, 0 deletions
diff --git a/static/search.js b/static/search.js
new file mode 100644
index 0000000..93ca7ca
--- /dev/null
+++ b/static/search.js
@@ -0,0 +1,193 @@
+function debounce(func, wait) {
+ var timeout;
+
+ return function () {
+ var context = this;
+ var args = arguments;
+ clearTimeout(timeout);
+
+ timeout = setTimeout(function () {
+ timeout = null;
+ func.apply(context, args);
+ }, wait);
+ };
+ }
+
+ // Taken from mdbook
+ // The strategy is as follows:
+ // First, assign a value to each word in the document:
+ // Words that correspond to search terms (stemmer aware): 40
+ // Normal words: 2
+ // First word in a sentence: 8
+ // Then use a sliding window with a constant number of words and count the
+ // sum of the values of the words within the window. Then use the window that got the
+ // maximum sum. If there are multiple maximas, then get the last one.
+ // Enclose the terms in <b>.
+ function makeTeaser(body, terms) {
+ var TERM_WEIGHT = 40;
+ var NORMAL_WORD_WEIGHT = 2;
+ var FIRST_WORD_WEIGHT = 8;
+ var TEASER_MAX_WORDS = 30;
+
+ var stemmedTerms = terms.map(function (w) {
+ return elasticlunr.stemmer(w.toLowerCase());
+ });
+ var termFound = false;
+ var index = 0;
+ var weighted = []; // contains elements of ["word", weight, index_in_document]
+
+ // split in sentences, then words
+ var sentences = body.toLowerCase().split(". ");
+
+ for (var i in sentences) {
+ var words = sentences[i].split(" ");
+ var value = FIRST_WORD_WEIGHT;
+
+ for (var j in words) {
+ var word = words[j];
+
+ if (word.length > 0) {
+ for (var k in stemmedTerms) {
+ if (elasticlunr.stemmer(word).startsWith(stemmedTerms[k])) {
+ value = TERM_WEIGHT;
+ termFound = true;
+ }
+ }
+ weighted.push([word, value, index]);
+ value = NORMAL_WORD_WEIGHT;
+ }
+
+ index += word.length;
+ index += 1; // ' ' or '.' if last word in sentence
+ }
+
+ index += 1; // because we split at a two-char boundary '. '
+ }
+
+ if (weighted.length === 0) {
+ return body;
+ }
+
+ var windowWeights = [];
+ var windowSize = Math.min(weighted.length, TEASER_MAX_WORDS);
+ // We add a window with all the weights first
+ var curSum = 0;
+ for (var i = 0; i < windowSize; i++) {
+ curSum += weighted[i][1];
+ }
+ windowWeights.push(curSum);
+
+ for (var i = 0; i < weighted.length - windowSize; i++) {
+ curSum -= weighted[i][1];
+ curSum += weighted[i + windowSize][1];
+ windowWeights.push(curSum);
+ }
+
+ // If we didn't find the term, just pick the first window
+ var maxSumIndex = 0;
+ if (termFound) {
+ var maxFound = 0;
+ // backwards
+ for (var i = windowWeights.length - 1; i >= 0; i--) {
+ if (windowWeights[i] > maxFound) {
+ maxFound = windowWeights[i];
+ maxSumIndex = i;
+ }
+ }
+ }
+
+ var teaser = [];
+ var startIndex = weighted[maxSumIndex][2];
+ for (var i = maxSumIndex; i < maxSumIndex + windowSize; i++) {
+ var word = weighted[i];
+ if (startIndex < word[2]) {
+ // missing text from index to start of `word`
+ teaser.push(body.substring(startIndex, word[2]));
+ startIndex = word[2];
+ }
+
+ // add <em/> around search terms
+ if (word[1] === TERM_WEIGHT) {
+ teaser.push("<b>");
+ }
+ startIndex = word[2] + word[0].length;
+ teaser.push(body.substring(word[2], startIndex));
+
+ if (word[1] === TERM_WEIGHT) {
+ teaser.push("</b>");
+ }
+ }
+ teaser.push("…");
+ return teaser.join("");
+ }
+
+ function formatSearchResultItem(item, terms) {
+ var li = document.createElement("li");
+ li.classList.add("search-results__item");
+ li.innerHTML = `<a href="${item.ref}">${item.doc.title}</a>`;
+ li.innerHTML += `<div class="search-results__teaser">${makeTeaser(item.doc.body, terms)}</div>`;
+ return li;
+ }
+
+ function initSearch() {
+ var $searchInput = document.getElementById("search");
+ if (!$searchInput) {
+ return;
+ }
+
+
+ var $searchResults = document.querySelector(".search-results");
+ var $searchResultsHeader = document.querySelector(".search-results__header");
+ var $searchResultsItems = document.querySelector(".search-results__items");
+ var MAX_ITEMS = 100;
+
+ var options = {
+ bool: "AND",
+ fields: {
+ title: {boost: 2},
+ body: {boost: 1},
+ }
+ };
+ var currentTerm = "";
+ var index = elasticlunr.Index.load(window.searchIndex);
+
+ $searchInput.addEventListener("keyup", debounce(function() {
+ var term = $searchInput.value.trim();
+ if (term === currentTerm || !index) {
+ return;
+ }
+
+ $searchResultsItems.innerHTML = "";
+ if (term === "") {
+ return;
+ }
+
+ var results = index.search(term, options).filter(function (r) {
+ return r.doc.body !== "";
+ });
+ if (results.length === 0) {
+ $searchResultsHeader.innerText = `Ничего похожего на «${term}» не найдено`;
+ return;
+ }
+
+ currentTerm = term;
+ $searchResultsHeader.innerText = `${results.length} for «${term}»:`;
+ for (var i = 0; i < Math.min(results.length, MAX_ITEMS); i++) {
+ if (!results[i].doc.body) {
+ continue;
+ }
+ // var item = document.createElement("li");
+ // item.innerHTML = formatSearchResultItem(results[i], term.split(" "));
+ console.log(results[i]);
+ $searchResultsItems.appendChild(formatSearchResultItem(results[i], term.split(" ")));
+ }
+ }, 150));
+ }
+
+ if (document.readyState === "complete" ||
+ (document.readyState !== "loading" && !document.documentElement.doScroll)
+ ) {
+ initSearch();
+ } else {
+ document.addEventListener("DOMContentLoaded", initSearch);
+ } \ No newline at end of file
diff --git a/static/themes.js b/static/themes.js
new file mode 100644
index 0000000..23cab33
--- /dev/null
+++ b/static/themes.js
@@ -0,0 +1,136 @@
+
+ // Copyright Koos Looijesteijn https://www.kooslooijesteijn.net/blog/add-dark-mode-to-website
+ // Find if user has set a preference and react to changes
+ (function initializeTheme(){
+ syncBetweenTabs()
+ listenToOSChanges()
+ enableTheme(
+ returnThemeBasedOnLocalStorage() ||
+ returnThemeBasedOnOS() ||
+ returnThemeBasedOnTime(),
+ false)
+ }())
+
+ // Listen to preference changes. The event only fires in inactive tabs, so theme changes aren't applied twice.
+ function syncBetweenTabs(){
+ window.addEventListener('storage', (e) => {
+ const root = document.documentElement
+ if (e.key === 'preference-theme'){
+ if (e.newValue === 'light') enableTheme('light', true, false)
+ else if (e.newValue === 'dark') enableTheme('dark', true, false) // The third argument makes sure the state isn't saved again.
+ }
+ })
+ }
+
+ // Add a listener in case OS-level preference changes.
+ function listenToOSChanges(){
+ let mediaQueryList = window.matchMedia('(prefers-color-scheme: dark)')
+
+ mediaQueryList.addListener( (m)=> {
+ const root = document.documentElement
+ if (m.matches !== true){
+ if (!root.classList.contains('theme-light')){
+ enableTheme('light', true)
+ }
+ }
+ else{
+ if(!root.classList.contains('theme-dark')) enableTheme('dark', true)
+ }
+ })
+ }
+
+ // If no preference was set, check what the OS pref is.
+ function returnThemeBasedOnOS() {
+ let mediaQueryList = window.matchMedia('(prefers-color-scheme: dark)')
+ if (mediaQueryList.matches) return 'dark'
+ else {
+ mediaQueryList = window.matchMedia('(prefers-color-scheme: light)')
+ if (mediaQueryList.matches) return 'light'
+ else return undefined
+ }
+ }
+
+ // For subsequent page loads
+ function returnThemeBasedOnLocalStorage() {
+ const pref = localStorage.getItem('preference-theme')
+ const lastChanged = localStorage.getItem('preference-theme-last-change')
+ let now = new Date()
+ now = now.getTime()
+ const minutesPassed = (now - lastChanged)/(1000*60)
+
+ if (
+ minutesPassed < 120 &&
+ pref === "light"
+ ) return 'light'
+ else if (
+ minutesPassed < 120 &&
+ pref === "dark"
+ ) return 'dark'
+ else return undefined
+ }
+
+ // Fallback for when OS preference isn't available
+ function returnThemeBasedOnTime(){
+ let date = new Date
+ const hour = date.getHours()
+ if (hour > 20 || hour < 5) return 'dark'
+ else return 'light'
+ }
+
+ // Switch to another theme
+ function enableTheme(newTheme = 'light', withTransition = false, save = true){
+ const root = document.documentElement
+ let otherTheme
+ newTheme === 'light' ? otherTheme = 'dark' : otherTheme = 'light'
+ let currentTheme
+ (root.classList.contains('theme-dark')) ? currentTheme = 'dark' : 'light'
+
+ if (withTransition === true && newTheme !== currentTheme) animateThemeTransition()
+
+ root.classList.add('theme-' + newTheme)
+ root.classList.remove('theme-' + otherTheme)
+
+ let button = document.getElementById('theme-' + otherTheme + '-button')
+ button.classList.add('enabled')
+ button.setAttribute('aria-pressed', false)
+
+ button = document.getElementById('theme-' + newTheme + '-button')
+ button.classList.remove('enabled')
+ button.setAttribute('aria-pressed', true)
+
+ if (save) saveToLocalStorage('preference-theme', newTheme)
+ }
+
+ // Save the state for subsequent page loads
+ function saveToLocalStorage(key, value){
+ let now = new Date()
+ now = now.getTime()
+ localStorage.setItem(key, value)
+ localStorage.setItem(key+"-last-change", now)
+ }
+
+ // Add class to smoothly transition between themes
+ function animateThemeTransition(){
+ const root = document.documentElement
+ root.classList.remove('theme-change-active')
+ void root.offsetWidth // Trigger reflow to cancel the animation
+ root.classList.add('theme-change-active')
+ }
+ (function removeAnimationClass(){
+ const root = document.documentElement
+ root.addEventListener(supportedAnimationEvent(), ()=>root.classList.remove('theme-change-active'), false)
+ }())
+
+ function supportedAnimationEvent(){
+ const el = document.createElement("f")
+ const animations = {
+ "animation" : "animationend",
+ "OAnimation" : "oAnimationEnd",
+ "MozAnimation" : "animationend",
+ "WebkitAnimation": "webkitAnimationEnd"
+ }
+
+ for (t in animations){
+ if (el.style[t] !== undefined) return animations[t] // Return the name of the event fired by the browser to indicate a CSS animation has ended
+ }
+ } \ No newline at end of file