diff options
author | WingLim <643089849@qq.com> | 2021-07-05 15:56:38 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-07-05 15:56:38 +0300 |
commit | 5618eca561d8d5a8230b50f7f90c41bc2453d76e (patch) | |
tree | 5dee9892fc1cb3f3b3282c6b67a894b2a13ae8c7 | |
parent | 0f7d522ceac50cbfd31e189497c6c4ec007c1eeb (diff) |
refactor: rewrite script (#24)v1.6.0
* docs: add thanks
* refactor: rewrite footnotes
* refactor: rewrite search
* chore: rename to el
-rw-r--r-- | README.md | 4 | ||||
-rw-r--r-- | assets/ts/features.ts | 72 | ||||
-rw-r--r-- | assets/ts/footnotes.ts | 56 | ||||
-rw-r--r-- | assets/ts/search.ts | 285 | ||||
-rw-r--r-- | layouts/_default/archives.html | 2 |
5 files changed, 216 insertions, 203 deletions
@@ -149,8 +149,8 @@ Some content... ``` ## Thanks to -- [你好黑暗,我的老朋友 —— 为网站添加用户友好的深色模式支持](https://blog.skk.moe/post/hello-darkmode-my-old-friend/) -- [Footnotes, citations, and sidenotes](https://prose.yihui.org/about/#footnotes-citations-and-sidenotes) +- [hugo-theme-stack](https://github.com/CaiJimmy/hugo-theme-stack) for dark mode switch +- [hugo-prose](https://github.com/yihui/hugo-prose) for float footnotes ## License diff --git a/assets/ts/features.ts b/assets/ts/features.ts index 417c169..cfe676d 100644 --- a/assets/ts/features.ts +++ b/assets/ts/features.ts @@ -1,74 +1,16 @@ import ThemeColorScheme from "ts/colorScheme"; +import { renderFootnotes } from "ts/footnotes" -(function (d) { - let enableFootnotes = false - if (d.currentScript) { - enableFootnotes = d.currentScript.dataset['enableFootnotes'] == 'true' - } - let renderFootnotes = function () { - const removeEl = (el: Element) => { - if (!el) return; - el.remove ? el.remove() : el.parentNode.removeChild(el); - }; - - const insertAfter = (target: HTMLElement, sib: Element) => { - target.after ? target.after(sib) : ( - target.parentNode.insertBefore(sib, target.nextSibling) - ); - }; - - const insideOut = (el: Element) => { - var p = el.parentNode as HTMLElement, - x = el.innerHTML, - c = document.createElement('div'); // a tmp container - insertAfter(p, c); - c.appendChild(el); - el.innerHTML = ''; - el.appendChild(p); - p.innerHTML = x; // let the original parent have the content of its child - insertAfter(c, c.firstElementChild); - removeEl(c); - }; +let enableFootnotes = false +if (document.currentScript) { + enableFootnotes = document.currentScript.dataset.enableFootnotes == 'true' +} - document.querySelectorAll('.footnotes > ol > li[id^="fn"], #refs > div[id^="ref-"]').forEach(function (fn) { - let a = document.querySelectorAll('a[href="#' + fn.id + '"]'); - if (a.length === 0) return; - a.forEach(function (el) { el.removeAttribute('href') }); - let newA = a[0] as HTMLElement; - let side = document.createElement('div'); - side.className = 'side side-right'; - if (/^fn/.test(fn.id)) { - side.innerHTML = fn.innerHTML; - var number = newA.innerHTML; // footnote number - side.firstElementChild.innerHTML = '<span class="bg-number">' + number + - '</span> ' + side.firstElementChild.innerHTML; - removeEl(side.querySelector('a[href^="#fnref"]')); // remove backreference - let newAParent = newA.parentNode as HTMLElement - newAParent.tagName === 'SUP' && insideOut(newA); - } else { - side.innerHTML = fn.outerHTML; - newA = newA.parentNode as HTMLElement; - } - insertAfter(newA, side); - newA.classList.add('note-ref'); - removeEl(fn); - }) - document.querySelectorAll('.footnotes, #refs').forEach(function (fn) { - var items = fn.children; - if (fn.id === 'refs') return items.length === 0 && removeEl(fn); - // there must be a <hr> and an <ol> left - if (items.length !== 2 || items[0].tagName !== 'HR' || items[1].tagName !== 'OL') return; - items[1].childElementCount === 0 && removeEl(fn); - }); - }; +const init = () => { + new ThemeColorScheme(document.getElementById('dark-mode-button')) if (enableFootnotes) { renderFootnotes() } -})(document); - - -const init = () => { - new ThemeColorScheme(document.getElementById('dark-mode-button')) } window.addEventListener('load', () => { diff --git a/assets/ts/footnotes.ts b/assets/ts/footnotes.ts new file mode 100644 index 0000000..889c2be --- /dev/null +++ b/assets/ts/footnotes.ts @@ -0,0 +1,56 @@ +const removeEl = (el: Element) => { + if (!el) return; + el.remove ? el.remove() : el.parentNode.removeChild(el); +}; + +const insertAfter = (target: HTMLElement, sib: Element) => { + target.after ? target.after(sib) : ( + target.parentNode.insertBefore(sib, target.nextSibling) + ); +}; + +const insideOut = (el: Element) => { + var p = el.parentNode as HTMLElement, + x = el.innerHTML, + c = document.createElement('div'); // a tmp container + insertAfter(p, c); + c.appendChild(el); + el.innerHTML = ''; + el.appendChild(p); + p.innerHTML = x; // let the original parent have the content of its child + insertAfter(c, c.firstElementChild); + removeEl(c); +}; + +export let renderFootnotes = function () { + document.querySelectorAll('.footnotes > ol > li[id^="fn"], #refs > div[id^="ref-"]').forEach(function (fn) { + let a = document.querySelectorAll('a[href="#' + fn.id + '"]'); + if (a.length === 0) return; + a.forEach(function (el) { el.removeAttribute('href') }); + let newA = a[0] as HTMLElement; + let side = document.createElement('div'); + side.className = 'side side-right'; + if (/^fn/.test(fn.id)) { + side.innerHTML = fn.innerHTML; + var number = newA.innerHTML; // footnote number + side.firstElementChild.innerHTML = '<span class="bg-number">' + number + + '</span> ' + side.firstElementChild.innerHTML; + removeEl(side.querySelector('a[href^="#fnref"]')); // remove backreference + let newAParent = newA.parentNode as HTMLElement + newAParent.tagName === 'SUP' && insideOut(newA); + } else { + side.innerHTML = fn.outerHTML; + newA = newA.parentNode as HTMLElement; + } + insertAfter(newA, side); + newA.classList.add('note-ref'); + removeEl(fn); + }) + document.querySelectorAll('.footnotes, #refs').forEach(function (fn) { + var items = fn.children; + if (fn.id === 'refs') return items.length === 0 && removeEl(fn); + // there must be a <hr> and an <ol> left + if (items.length !== 2 || items[0].tagName !== 'HR' || items[1].tagName !== 'OL') return; + items[1].childElementCount === 0 && removeEl(fn); + }); +}; diff --git a/assets/ts/search.ts b/assets/ts/search.ts index ef982d1..ef6c39d 100644 --- a/assets/ts/search.ts +++ b/assets/ts/search.ts @@ -1,153 +1,168 @@ import Fuse from '../js/fuse' -declare global { - interface Window { - fuse: any; - filterSelect: any; - } +interface searchItem { + item: { + categorise: Array<string>, + contents: string, + date: string, + permalink: string, + tags: Array<string>, + title: string + }, + refIndex: number } -let show = function (elem: HTMLElement) { - elem.style.display = 'block'; +const show = function (el: HTMLElement) { + el.style.display = 'block'; }; -let hide = function (elem: HTMLElement) { - elem.style.display = 'none'; +const hide = function (el: HTMLElement) { + el.style.display = 'none'; }; -let initFuse = function () { - const fuseOptions = { - shouldSort: true, - threshold: 0.3, - location: 0, - distance: 100, - maxPatternLength: 32, - minMatchCharLength: 1, - useExtendedSearch: true, - keys: [ - { name: "title", weight: 0.8 }, - { name: "contents", weight: 0.5 }, - { name: "tags", weight: 0.3 }, - { name: "categories", weight: 0.3 } - ] - }; +class Search { + private searchInput = document.getElementById('search-query') as HTMLInputElement; + private searchResults = document.getElementById('search-results'); + private articlesList = document.getElementById('articles-list'); + private filterItems = document.getElementsByClassName('filter-item') as HTMLCollectionOf<HTMLElement>; + private searchFilter = new Map(); + private fuse: any - fetch('/index.json') - .then(function (response) { - if (response.status !== 200) { - console.error('[' + response.status + ']Error:', response.statusText); - return; - } - response.json().then(function (pages) { - let fuse = new Fuse(pages, fuseOptions); - window.fuse = fuse; - }) - - }) - .catch(function (err) { - console.error('[Fetch]Error:', err); - }); -} -initFuse(); + constructor() { + this.initFuse() + this.bindInput() + this.bindFilters() + } -const searchInput = document.getElementById('search-query') as HTMLInputElement; -const searchResults = document.getElementById('search-results'); -const articlesList = document.getElementById('articles-list'); -if (searchInput != undefined) { - searchInput.addEventListener("input", function () { - let value = searchInput.value; - executeSearch(buildSearchValue(value)); - }) -} + private initFuse() { + const fuseOptions = { + shouldSort: true, + threshold: 0.3, + location: 0, + distance: 100, + maxPatternLength: 32, + minMatchCharLength: 1, + useExtendedSearch: true, + keys: [ + { name: "title", weight: 0.8 }, + { name: "contents", weight: 0.5 }, + { name: "tags", weight: 0.3 }, + { name: "categories", weight: 0.3 } + ] + }; + fetch('/index.json') + .then((response) => { + if (response.status !== 200) { + console.error('[' + response.status + ']Error:', response.statusText); + return; + } + return response.json() + }) + .then((pages) => { + let fuse = new Fuse(pages, fuseOptions) + this.fuse = fuse + }) + .catch(function (err) { + console.error('[Fetch]Error:', err); + }); + } -let searchFilter = new Map(); -let buildSearchValue = function(value: string) { - let filter = []; - if (searchFilter.size == 0 && value.length == 0) { - return ""; - } - searchFilter.forEach((v: string, k: string) => { - let object = {}; - if (v == "categories") { - object = { - categories: k - } - } - filter.push(object); - }) - if (value != undefined && value.length != 0) { - let orObject = { - $or: [ - {title: value}, - // fuse extended search, 'value is include-match - // more details: https://fusejs.io/examples.html#extended-search - {contents: "'"+value} - ] - } - filter.push(orObject); - } - return { - $and: filter - } -} + private bindInput() { + this.searchInput.addEventListener("input", () => { + let value = this.searchInput.value; + this.executeSearch(this.buildSearchValue(value)); + }) + } -let filterSelect = function(element: HTMLElement) { - let value = element.dataset.value; - let type = element.dataset.type; - if (element.classList.contains('active')) { - searchFilter.delete(value); - element.classList.remove('active'); - } else { - searchFilter.set(value, type); - element.classList.add('active'); - } - executeSearch(buildSearchValue("")); -} -window.filterSelect = filterSelect; + private bindFilters() { + Array.from(this.filterItems).forEach((el) => { + el.addEventListener('click', () => { + this.filterSelect(el) + }) + }) + } -let executeSearch = function(value: string|object) { - if ((typeof value === "string" && value.length != 0) || typeof value === "object") { - hide(articlesList); - show(searchResults); - } else { - hide(searchResults); - show(articlesList); - } + private executeSearch(value: string | object) { + if ((typeof value === "string" && value.length != 0) || typeof value === "object") { + hide(this.articlesList); + show(this.searchResults); + } else { + hide(this.searchResults); + show(this.articlesList); + } - let result = window.fuse.search(value); - if (result.length > 0) { - populateResults(result); - } else { - searchResults.innerHTML = '<p>Sorry, nothing matched that search.</p>'; - } + let result = this.fuse.search(value); + if (result.length > 0) { + this.populateResults(result); + } else { + this.searchResults.innerHTML = '<p>Sorry, nothing matched that search.</p>'; + } + } - interface searchItem { - item: { - categorise: Array<string>, - contents: string, - date: string, - permalink: string, - tags: Array<string>, - title: string - }, - refIndex: number - } + private populateResults(results: Array<searchItem>) { + this.searchResults.innerHTML = ""; + + results.forEach((value) => { + let item = value.item + let html = ` + <div class="post"> + <a href="${item.permalink}"> + <div class="post-row"> + <time>${item.date}</time> + <h3>${item.title}</h3> + </div> + </a> + </div>` + this.searchResults.innerHTML += html; + }); + } - function populateResults(results: Array<searchItem>) { - searchResults.innerHTML = ""; + private buildSearchValue = function (value: string) { + let filter = []; + if (this.searchFilter.size == 0 && value.length == 0) { + return ""; + } + this.searchFilter.forEach((v: string, k: string) => { + let object = {}; + if (v == "categories") { + object = { + categories: k + } + } + filter.push(object); + }) + if (value != undefined && value.length != 0) { + let orObject = { + $or: [ + { title: value }, + // fuse extended search, 'value is include-match + // more details: https://fusejs.io/examples.html#extended-search + { contents: "'" + value } + ] + } + filter.push(orObject); + } + return { + $and: filter + } + } - results.forEach(function (value) { - let item = value.item - let html = ` - <div class="post"> - <a href="${item.permalink}"> - <div class="post-row"> - <time>${item.date}</time> - <h3>${item.title}</h3> - </div> - </a> - </div>` - searchResults.innerHTML += html; - }); - } + private filterSelect(el: HTMLElement) { + let value = el.dataset.value; + let type = el.dataset.type; + if (el.classList.contains('active')) { + this.searchFilter.delete(value); + el.classList.remove('active'); + } else { + this.searchFilter.set(value, type); + el.classList.add('active'); + } + this.executeSearch(this.buildSearchValue("")); + } } + +window.addEventListener('load', () => { + setTimeout(function () { + new Search() + }, 0) +}) diff --git a/layouts/_default/archives.html b/layouts/_default/archives.html index 60d2749..d9e0cda 100644 --- a/layouts/_default/archives.html +++ b/layouts/_default/archives.html @@ -14,7 +14,7 @@ <div class="filter-container"> {{ $maxCategoryToShow := $.Site.Params.maxCategoryToShow | default 5 }} {{ range .Site.Taxonomies.categories.TaxonomyArray | first $maxCategoryToShow }} - <div class="filter-item" data-value="{{ .Page.Title }}" data-type="categories" onclick="filterSelect(this)"> + <div class="filter-item" data-value="{{ .Page.Title }}" data-type="categories"> {{ .Page.Title }}<sup>{{ .Count }}</sup> </div> {{ end }} |