mirror of
https://github.com/squidfunk/mkdocs-material.git
synced 2024-06-14 11:52:32 +03:00
Improved search result rendering and icon search
This commit is contained in:
parent
a8b5cafa73
commit
4ef15bd440
@ -34,6 +34,7 @@ manifest.json
|
|||||||
site
|
site
|
||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
|
typings
|
||||||
webpack.config.ts
|
webpack.config.ts
|
||||||
|
|
||||||
# Distribution files
|
# Distribution files
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -58,6 +58,7 @@ mkdocs_material.egg-info
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
# Temporary files
|
# Temporary files
|
||||||
|
TODO
|
||||||
tmp
|
tmp
|
||||||
|
|
||||||
# IDEs
|
# IDEs
|
||||||
|
@ -11,15 +11,22 @@ and used in `mkdocs.yml`, documents and templates.
|
|||||||
|
|
||||||
## Search
|
## Search
|
||||||
|
|
||||||
<input id="icon-search" class="md-input" placeholder="Search the icon database" />
|
<div class="mdx-icon-search" data-mdx-component="icon-search">
|
||||||
|
<input
|
||||||
<div class="tx-icon-result" markdown="1">
|
class="md-input md-input--stretch mdx-icon-search__input"
|
||||||
<small>
|
placeholder="Search the icon database"
|
||||||
|
data-mdx-component="icon-search-query"
|
||||||
|
/>
|
||||||
|
<div class="mdx-icon-search-result" data-mdx-component="icon-search-result">
|
||||||
|
<div class="mdx-icon-search-result__meta"></div>
|
||||||
|
<ol class="mdx-icon-search-result__list"></ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<small>
|
||||||
:octicons-light-bulb-16:
|
:octicons-light-bulb-16:
|
||||||
**Tip:** Enter some keywords to find the perfect icon and click on the
|
**Tip:** Enter some keywords to find the perfect icon and click on the
|
||||||
shortcode to copy it to your clipboard.
|
shortcode to copy it to your clipboard.
|
||||||
</small>
|
</small>
|
||||||
</div>
|
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
|
2
material/assets/javascripts/bundle.3e5f7fbe.min.js
vendored
Normal file
2
material/assets/javascripts/bundle.3e5f7fbe.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
material/assets/javascripts/bundle.3e5f7fbe.min.js.map
Normal file
1
material/assets/javascripts/bundle.3e5f7fbe.min.js.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
material/assets/javascripts/vendor.00ecb175.min.js.map
Normal file
1
material/assets/javascripts/vendor.00ecb175.min.js.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,18 +1,18 @@
|
|||||||
{
|
{
|
||||||
"assets/javascripts/bundle.js": "assets/javascripts/bundle.b0aa5de8.min.js",
|
"assets/javascripts/bundle.js": "assets/javascripts/bundle.3e5f7fbe.min.js",
|
||||||
"assets/javascripts/bundle.js.map": "assets/javascripts/bundle.b0aa5de8.min.js.map",
|
"assets/javascripts/bundle.js.map": "assets/javascripts/bundle.3e5f7fbe.min.js.map",
|
||||||
"assets/javascripts/vendor.js": "assets/javascripts/vendor.e32ed4d0.min.js",
|
"assets/javascripts/vendor.js": "assets/javascripts/vendor.00ecb175.min.js",
|
||||||
"assets/javascripts/vendor.js.map": "assets/javascripts/vendor.e32ed4d0.min.js.map",
|
"assets/javascripts/vendor.js.map": "assets/javascripts/vendor.00ecb175.min.js.map",
|
||||||
"assets/javascripts/worker/search.js": "assets/javascripts/worker/search.b9424174.min.js",
|
"assets/javascripts/worker/search.js": "assets/javascripts/worker/search.b9424174.min.js",
|
||||||
"assets/javascripts/worker/search.js.map": "assets/javascripts/worker/search.b9424174.min.js.map",
|
"assets/javascripts/worker/search.js.map": "assets/javascripts/worker/search.b9424174.min.js.map",
|
||||||
"assets/stylesheets/main.css": "assets/stylesheets/main.25519675.min.css",
|
"assets/stylesheets/main.css": "assets/stylesheets/main.f080134c.min.css",
|
||||||
"assets/stylesheets/main.css.map": "assets/stylesheets/main.25519675.min.css.map",
|
"assets/stylesheets/main.css.map": "assets/stylesheets/main.f080134c.min.css.map",
|
||||||
"assets/stylesheets/palette.css": "assets/stylesheets/palette.e70b70b6.min.css",
|
"assets/stylesheets/palette.css": "assets/stylesheets/palette.e70b70b6.min.css",
|
||||||
"assets/stylesheets/palette.css.map": "assets/stylesheets/palette.e70b70b6.min.css.map",
|
"assets/stylesheets/palette.css.map": "assets/stylesheets/palette.e70b70b6.min.css.map",
|
||||||
"overrides/assets/javascripts/bundle.js": "overrides/assets/javascripts/bundle.c607e7b3.min.js",
|
"overrides/assets/javascripts/bundle.js": "overrides/assets/javascripts/bundle.b60297a2.min.js",
|
||||||
"overrides/assets/javascripts/bundle.js.map": "overrides/assets/javascripts/bundle.c607e7b3.min.js.map",
|
"overrides/assets/javascripts/bundle.js.map": "overrides/assets/javascripts/bundle.b60297a2.min.js.map",
|
||||||
"overrides/assets/javascripts/vendor.js": "overrides/assets/javascripts/vendor.1aa446d9.min.js",
|
"overrides/assets/javascripts/vendor.js": "overrides/assets/javascripts/vendor.be34bb11.min.js",
|
||||||
"overrides/assets/javascripts/vendor.js.map": "overrides/assets/javascripts/vendor.1aa446d9.min.js.map",
|
"overrides/assets/javascripts/vendor.js.map": "overrides/assets/javascripts/vendor.be34bb11.min.js.map",
|
||||||
"overrides/assets/stylesheets/main.css": "overrides/assets/stylesheets/main.d67efea2.min.css",
|
"overrides/assets/stylesheets/main.css": "overrides/assets/stylesheets/main.552eceec.min.css",
|
||||||
"overrides/assets/stylesheets/main.css.map": "overrides/assets/stylesheets/main.d67efea2.min.css.map"
|
"overrides/assets/stylesheets/main.css.map": "overrides/assets/stylesheets/main.552eceec.min.css.map"
|
||||||
}
|
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
3
material/assets/stylesheets/main.f080134c.min.css
vendored
Normal file
3
material/assets/stylesheets/main.f080134c.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
material/assets/stylesheets/main.f080134c.min.css.map
Normal file
1
material/assets/stylesheets/main.f080134c.min.css.map
Normal file
File diff suppressed because one or more lines are too long
@ -39,7 +39,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block styles %}
|
{% block styles %}
|
||||||
<link rel="stylesheet" href="{{ 'assets/stylesheets/main.25519675.min.css' | url }}">
|
<link rel="stylesheet" href="{{ 'assets/stylesheets/main.f080134c.min.css' | url }}">
|
||||||
{% if config.theme.palette %}
|
{% if config.theme.palette %}
|
||||||
{% set palette = config.theme.palette %}
|
{% set palette = config.theme.palette %}
|
||||||
<link rel="stylesheet" href="{{ 'assets/stylesheets/palette.e70b70b6.min.css' | url }}">
|
<link rel="stylesheet" href="{{ 'assets/stylesheets/palette.e70b70b6.min.css' | url }}">
|
||||||
@ -216,8 +216,8 @@
|
|||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script src="{{ 'assets/javascripts/vendor.e32ed4d0.min.js' | url }}"></script>
|
<script src="{{ 'assets/javascripts/vendor.00ecb175.min.js' | url }}"></script>
|
||||||
<script src="{{ 'assets/javascripts/bundle.b0aa5de8.min.js' | url }}"></script>
|
<script src="{{ 'assets/javascripts/bundle.3e5f7fbe.min.js' | url }}"></script>
|
||||||
{% for path in config["extra_javascript"] %}
|
{% for path in config["extra_javascript"] %}
|
||||||
<script src="{{ path | url }}"></script>
|
<script src="{{ path | url }}"></script>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
2
material/overrides/assets/javascripts/bundle.b60297a2.min.js
vendored
Normal file
2
material/overrides/assets/javascripts/bundle.b60297a2.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,2 +0,0 @@
|
|||||||
!function(e,t){for(var n in t)e[n]=t[n]}(window,function(e){function t(t){for(var r,i,a=t[0],s=t[1],u=t[2],f=0,p=[];f<a.length;f++)i=a[f],Object.prototype.hasOwnProperty.call(o,i)&&o[i]&&p.push(o[i][0]),o[i]=0;for(r in s)Object.prototype.hasOwnProperty.call(s,r)&&(e[r]=s[r]);for(l&&l(t);p.length;)p.shift()();return c.push.apply(c,u||[]),n()}function n(){for(var e,t=0;t<c.length;t++){for(var n=c[t],r=!0,a=1;a<n.length;a++){var s=n[a];0!==o[s]&&(r=!1)}r&&(c.splice(t--,1),e=i(i.s=n[0]))}return e}var r={},o={0:0},c=[];function i(t){if(r[t])return r[t].exports;var n=r[t]={i:t,l:!1,exports:{}};return e[t].call(n.exports,n,n.exports,i),n.l=!0,n.exports}i.m=e,i.c=r,i.d=function(e,t,n){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,t){if(1&t&&(e=i(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(i.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)i.d(n,r,function(t){return e[t]}.bind(null,r));return n},i.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="";var a=window.webpackJsonp=window.webpackJsonp||[],s=a.push.bind(a);a.push=t,a=a.slice();for(var u=0;u<a.length;u++)t(a[u]);var l=s;return c.push([37,1]),n()}({37:function(e,t,n){"use strict";n.r(t);var r=n(20),o=n(3),c=n(38),i=n(29),a=n(25);n(32),n(39);function s(e,t=document){return t.querySelector(e)||void 0}function u(e,t=document){const n=s(e,t);if(void 0===n)throw new ReferenceError(`Missing element: expected "${e}" to be present`);return n}n(50);var l=n(51);var f=n(17),p=n(40),b=n(41),d=n(42),g=n(43),h=n(44);n(45),n(46);const v=new f.a;Object(p.a)(()=>Object(b.a)(new ResizeObserver(e=>{for(const t of e)v.next(t)}))).pipe(Object(i.a)(e=>d.a.pipe(Object(l.a)(e)).pipe(Object(g.a)(()=>e.disconnect()))),Object(h.a)(1));n(31);u("[data-md-toggle=drawer]"),u("[data-md-toggle=search]");n(47);function O(){return new URL(location.href)}n(52),n(53);n(48),n(49);const j=u("#__config"),m=JSON.parse(j.textContent);function y(e,t){if("string"==typeof t||"number"==typeof t)e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(const n of t)y(e,n)}function w(e,t,...n){const r=document.createElement(e);if(t)for(const e of Object.keys(t))"boolean"!=typeof t[e]?r.setAttribute(e,t[e]):t[e]&&r.setAttribute(e,"");for(const e of n)y(r,e);return r}m.base=new URL(m.base,O()).toString().replace(/\/$/,"");function x(e,t){return t.length?w("div",{class:""},w("span",null,function(e){if(e>999){return((e+1e-6)/1e3).toFixed(+((e-950)%1e3>99))+"k"}return e.toString()}(e.length)," results"),w("ul",{class:"tx-icon-search__list"},e.slice(0,10).map(e=>w("li",{class:"tx-icon-search__item"},w("span",{class:"twemoji"},w("img",{src:"https://raw.githubusercontent.com/squidfunk/mkdocs-material/master/material/.icons/"+e,style:"width: 18px; height: 18px"}))," – ",w("button",{class:"md-clipboard--inline","data-clipboard-text":":"+e.replace(/\.svg$/,"").replace(/\//g,"-")+":"},w("code",null,function(e,t){return`:${Object(r.wrap)(e.replace(/\.svg$/,"").replace(/\//g,"-"),t,{wrap:{tagOpen:"<b>",tagClose:"</b>"}})}:`}(e,t))))))):w("div",{class:""})}const _=m,k=Object(o.a)(fetch(_.base+"/overrides/assets/javascripts/icons.json").then(e=>e.json())),S=s("#icon-search");S&&k.pipe(Object(i.a)(e=>Object(c.a)(S,"keyup").pipe(Object(a.a)(()=>Object(r.filter)(e,S.value))))).subscribe(e=>{const t=u(".tx-icon-result");t.innerHTML="",t.appendChild(x(e,S.value))}),Object(c.a)(document.body,"click").subscribe(e=>{if(e.target instanceof HTMLElement){e.target.closest("a[href^=http]")instanceof HTMLLinkElement&&ga("send","event","outbound","click",el.href)}})}}));
|
|
||||||
//# sourceMappingURL=bundle.c607e7b3.min.js.map
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
material/overrides/assets/javascripts/vendor.be34bb11.min.js
vendored
Normal file
2
material/overrides/assets/javascripts/vendor.be34bb11.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -22,7 +22,7 @@
|
|||||||
<meta name="twitter:title" content="{{ title }}">
|
<meta name="twitter:title" content="{{ title }}">
|
||||||
<meta name="twitter:description" content="{{ config.site_description }}">
|
<meta name="twitter:description" content="{{ config.site_description }}">
|
||||||
<meta name="twitter:image" content="{{ image }}">
|
<meta name="twitter:image" content="{{ image }}">
|
||||||
<link rel="stylesheet" href="{{ 'overrides/assets/stylesheets/main.d67efea2.min.css' | url }}">
|
<link rel="stylesheet" href="{{ 'overrides/assets/stylesheets/main.552eceec.min.css' | url }}">
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block announce %}
|
{% block announce %}
|
||||||
<a href="https://twitter.com/squidfunk">
|
<a href="https://twitter.com/squidfunk">
|
||||||
@ -53,6 +53,6 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
<script src="{{ 'overrides/assets/javascripts/vendor.1aa446d9.min.js' | url }}"></script>
|
<script src="{{ 'overrides/assets/javascripts/vendor.be34bb11.min.js' | url }}"></script>
|
||||||
<script src="{{ 'overrides/assets/javascripts/bundle.c607e7b3.min.js' | url }}"></script>
|
<script src="{{ 'overrides/assets/javascripts/bundle.b60297a2.min.js' | url }}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { translation } from "~/_"
|
import { translation } from "~/_"
|
||||||
|
import { round } from "~/utilities"
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
/* ----------------------------------------------------------------------------
|
||||||
* Functions
|
* Functions
|
||||||
@ -49,7 +50,7 @@ export function setSearchResultMeta(
|
|||||||
|
|
||||||
/* Multiple result */
|
/* Multiple result */
|
||||||
default:
|
default:
|
||||||
el.textContent = translation("search.result.other", value)
|
el.textContent = translation("search.result.other", round(value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Observable, fromEvent, merge } from "rxjs"
|
import { Observable, fromEvent, merge } from "rxjs"
|
||||||
import { map, startWith } from "rxjs/operators"
|
import { distinctUntilChanged, map, startWith } from "rxjs/operators"
|
||||||
|
|
||||||
|
import { getElementContentSize, getElementSize } from "../size"
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
/* ----------------------------------------------------------------------------
|
||||||
* Types
|
* Types
|
||||||
@ -74,3 +76,30 @@ export function watchElementOffset(
|
|||||||
startWith(getElementOffset(el))
|
startWith(getElementOffset(el))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Watch element threshold
|
||||||
|
*
|
||||||
|
* This function returns an observable which emits whether the bottom scroll
|
||||||
|
* offset of an elements is within a certain threshold.
|
||||||
|
*
|
||||||
|
* @param el - Element
|
||||||
|
* @param threshold - Threshold
|
||||||
|
*
|
||||||
|
* @returns Element threshold observable
|
||||||
|
*/
|
||||||
|
export function watchElementThreshold(
|
||||||
|
el: HTMLElement, threshold = 16
|
||||||
|
): Observable<boolean> {
|
||||||
|
return watchElementOffset(el)
|
||||||
|
.pipe(
|
||||||
|
map(({ y }) => {
|
||||||
|
const visible = getElementSize(el)
|
||||||
|
const content = getElementContentSize(el)
|
||||||
|
return y >= (
|
||||||
|
content.height - visible.height - threshold
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
distinctUntilChanged()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -120,7 +120,7 @@ export function getElementContentSize(el: HTMLElement): ElementSize {
|
|||||||
/**
|
/**
|
||||||
* Watch element size
|
* Watch element size
|
||||||
*
|
*
|
||||||
* This function returns an observable that will subscribe to a single internal
|
* This function returns an observable that subscribes to a single internal
|
||||||
* instance of `ResizeObserver` upon subscription, and emit resize events until
|
* instance of `ResizeObserver` upon subscription, and emit resize events until
|
||||||
* termination. Note that this function should not be called with the same
|
* termination. Note that this function should not be called with the same
|
||||||
* element twice, as the first unsubscription will terminate observation.
|
* element twice, as the first unsubscription will terminate observation.
|
||||||
|
@ -73,7 +73,7 @@ interface WatchOptions<T extends WorkerMessage> {
|
|||||||
/**
|
/**
|
||||||
* Watch a web worker
|
* Watch a web worker
|
||||||
*
|
*
|
||||||
* This function returns an observable that will send all values emitted by the
|
* This function returns an observable that sends all values emitted by the
|
||||||
* message observable to the web worker. Web worker communication is expected
|
* message observable to the web worker. Web worker communication is expected
|
||||||
* to be bidirectional (request-response) and synchronous. Messages that are
|
* to be bidirectional (request-response) and synchronous. Messages that are
|
||||||
* emitted during a pending request are throttled, the last one is emitted.
|
* emitted during a pending request are throttled, the last one is emitted.
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
* IN THE SOFTWARE.
|
* IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Observable, merge } from "rxjs"
|
import { Observable, ObservableInput, merge } from "rxjs"
|
||||||
import { filter, sample, take } from "rxjs/operators"
|
import { filter, sample, take } from "rxjs/operators"
|
||||||
|
|
||||||
import { configuration } from "~/_"
|
import { configuration } from "~/_"
|
||||||
@ -75,9 +75,9 @@ interface MountOptions {
|
|||||||
*
|
*
|
||||||
* @param url - Search index URL
|
* @param url - Search index URL
|
||||||
*
|
*
|
||||||
* @returns Promise resolving with search index
|
* @returns Promise or observable
|
||||||
*/
|
*/
|
||||||
function fetchSearchIndex(url: string) {
|
function fetchSearchIndex(url: string): ObservableInput<SearchIndex> {
|
||||||
return __search?.index || requestJSON<SearchIndex>(url)
|
return __search?.index || requestJSON<SearchIndex>(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,7 +104,7 @@ export function mountSearch(
|
|||||||
`${config.base}/search/search_index.json`
|
`${config.base}/search/search_index.json`
|
||||||
))
|
))
|
||||||
|
|
||||||
/* Retrieve elements */
|
/* Retrieve nested components */
|
||||||
const query = getComponentElement("search-query", el)
|
const query = getComponentElement("search-query", el)
|
||||||
const result = getComponentElement("search-result", el)
|
const result = getComponentElement("search-result", el)
|
||||||
|
|
||||||
|
@ -20,14 +20,25 @@
|
|||||||
* IN THE SOFTWARE.
|
* IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Observable, Subject } from "rxjs"
|
|
||||||
import {
|
import {
|
||||||
|
Observable,
|
||||||
|
Subject,
|
||||||
|
animationFrameScheduler,
|
||||||
|
merge,
|
||||||
|
of
|
||||||
|
} from "rxjs"
|
||||||
|
import {
|
||||||
|
bufferCount,
|
||||||
|
distinctUntilKeyChanged,
|
||||||
filter,
|
filter,
|
||||||
finalize,
|
finalize,
|
||||||
map,
|
map,
|
||||||
|
observeOn,
|
||||||
startWith,
|
startWith,
|
||||||
|
switchMap,
|
||||||
tap,
|
tap,
|
||||||
withLatestFrom
|
withLatestFrom,
|
||||||
|
zipWith
|
||||||
} from "rxjs/operators"
|
} from "rxjs/operators"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -36,7 +47,10 @@ import {
|
|||||||
resetSearchResultMeta,
|
resetSearchResultMeta,
|
||||||
setSearchResultMeta
|
setSearchResultMeta
|
||||||
} from "~/actions"
|
} from "~/actions"
|
||||||
import { getElementOrThrow } from "~/browser"
|
import {
|
||||||
|
getElementOrThrow,
|
||||||
|
watchElementThreshold
|
||||||
|
} from "~/browser"
|
||||||
import {
|
import {
|
||||||
SearchResult as SearchResultData,
|
SearchResult as SearchResultData,
|
||||||
SearchWorker,
|
SearchWorker,
|
||||||
@ -76,6 +90,9 @@ interface MountOptions {
|
|||||||
/**
|
/**
|
||||||
* Mount search result list
|
* Mount search result list
|
||||||
*
|
*
|
||||||
|
* This function will perform a lazy rendering of the search results, depending
|
||||||
|
* on the vertical offset of the search result container.
|
||||||
|
*
|
||||||
* @param el - Search result list element
|
* @param el - Search result list element
|
||||||
* @param worker - Search worker
|
* @param worker - Search worker
|
||||||
* @param options - Options
|
* @param options - Options
|
||||||
@ -86,11 +103,16 @@ export function mountSearchResult(
|
|||||||
el: HTMLElement, { rx$ }: SearchWorker, { query$ }: MountOptions
|
el: HTMLElement, { rx$ }: SearchWorker, { query$ }: MountOptions
|
||||||
): Observable<Component<SearchResult>> {
|
): Observable<Component<SearchResult>> {
|
||||||
const internal$ = new Subject<SearchResult>()
|
const internal$ = new Subject<SearchResult>()
|
||||||
|
const boundary$ = watchElementThreshold(el.parentElement!)
|
||||||
|
.pipe(
|
||||||
|
filter(Boolean)
|
||||||
|
)
|
||||||
|
|
||||||
/* Update search result metadata */
|
/* Update search result metadata */
|
||||||
const meta = getElementOrThrow(":scope > :first-child", el)
|
const meta = getElementOrThrow(":scope > :first-child", el)
|
||||||
internal$
|
internal$
|
||||||
.pipe(
|
.pipe(
|
||||||
|
observeOn(animationFrameScheduler),
|
||||||
withLatestFrom(query$)
|
withLatestFrom(query$)
|
||||||
)
|
)
|
||||||
.subscribe(([{ data }, { value }]) => {
|
.subscribe(([{ data }, { value }]) => {
|
||||||
@ -103,15 +125,21 @@ export function mountSearchResult(
|
|||||||
/* Update search result list */
|
/* Update search result list */
|
||||||
const list = getElementOrThrow(":scope > :last-child", el)
|
const list = getElementOrThrow(":scope > :last-child", el)
|
||||||
internal$
|
internal$
|
||||||
.subscribe(({ data }) => {
|
.pipe(
|
||||||
resetSearchResultList(list)
|
observeOn(animationFrameScheduler),
|
||||||
|
tap(() => resetSearchResultList(list)),
|
||||||
/* Compute thresholds and search results */
|
switchMap(({ data }) => merge(
|
||||||
const thresholds = [...data.map(([best]) => best.score), 0]
|
of(...data.slice(0, 10)),
|
||||||
for (let index = 0; index < data.length; index++)
|
of(...data.slice(10))
|
||||||
addToSearchResultList(list, renderSearchResult(
|
.pipe(
|
||||||
data[index++], thresholds[index]
|
bufferCount(4),
|
||||||
|
zipWith(boundary$),
|
||||||
|
switchMap(([chunk]) => of(...chunk))
|
||||||
|
)
|
||||||
))
|
))
|
||||||
|
)
|
||||||
|
.subscribe(result => {
|
||||||
|
addToSearchResultList(list, renderSearchResult(result))
|
||||||
})
|
})
|
||||||
|
|
||||||
/* Filter search result list */
|
/* Filter search result list */
|
||||||
|
@ -54,7 +54,7 @@ const enum Flag {
|
|||||||
*/
|
*/
|
||||||
function renderSearchDocument(
|
function renderSearchDocument(
|
||||||
document: SearchDocument & SearchMetadata, flag: Flag
|
document: SearchDocument & SearchMetadata, flag: Flag
|
||||||
) {
|
): HTMLElement {
|
||||||
const parent = flag & Flag.PARENT
|
const parent = flag & Flag.PARENT
|
||||||
const teaser = flag & Flag.TEASER
|
const teaser = flag & Flag.TEASER
|
||||||
|
|
||||||
@ -101,13 +101,13 @@ function renderSearchDocument(
|
|||||||
* Render a search result
|
* Render a search result
|
||||||
*
|
*
|
||||||
* @param result - Search result
|
* @param result - Search result
|
||||||
* @param threshold - Score threshold
|
|
||||||
*
|
*
|
||||||
* @returns Element
|
* @returns Element
|
||||||
*/
|
*/
|
||||||
export function renderSearchResult(
|
export function renderSearchResult(
|
||||||
result: SearchResult, threshold = Infinity
|
result: SearchResult
|
||||||
): HTMLElement {
|
): HTMLElement {
|
||||||
|
const threshold = result[0].score
|
||||||
const docs = [...result]
|
const docs = [...result]
|
||||||
|
|
||||||
/* Find and extract parent article */
|
/* Find and extract parent article */
|
||||||
|
@ -230,9 +230,11 @@
|
|||||||
padding: 0 px2rem(44px) 0 px2rem(72px);
|
padding: 0 px2rem(44px) 0 px2rem(72px);
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
background-color: var(--md-default-bg-color);
|
background-color: var(--md-default-bg-color);
|
||||||
|
box-shadow: 0 0 px2rem(12px) transparent;
|
||||||
transition:
|
transition:
|
||||||
color 250ms,
|
color 250ms,
|
||||||
background-color 250ms;
|
background-color 250ms,
|
||||||
|
box-shadow 250ms;
|
||||||
|
|
||||||
// Adjust for right-to-left languages
|
// Adjust for right-to-left languages
|
||||||
[dir="rtl"] & {
|
[dir="rtl"] & {
|
||||||
@ -255,6 +257,11 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Adjust appearance when search is active
|
||||||
|
[data-md-toggle="search"]:checked ~ .md-header & {
|
||||||
|
box-shadow: 0 0 px2rem(12px) var(--md-default-fg-color--lightest);
|
||||||
|
}
|
||||||
|
|
||||||
// [tablet portrait -]: Search modal
|
// [tablet portrait -]: Search modal
|
||||||
@include break-to-device(tablet portrait) {
|
@include break-to-device(tablet portrait) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -448,7 +455,9 @@
|
|||||||
background-color: var(--md-default-bg-color);
|
background-color: var(--md-default-bg-color);
|
||||||
// Hack: promote to own layer to reduce jitter
|
// Hack: promote to own layer to reduce jitter
|
||||||
backface-visibility: hidden;
|
backface-visibility: hidden;
|
||||||
scroll-snap-type: y mandatory;
|
// Hack: Chrome 88+ has weird overscroll behavior. Overall, scroll snapping
|
||||||
|
// seems to be something that is not ready for prime time on some browsers.
|
||||||
|
// scroll-snap-type: y mandatory;
|
||||||
touch-action: pan-y;
|
touch-action: pan-y;
|
||||||
|
|
||||||
// Mitigiate excessive repaints on non-retina devices
|
// Mitigiate excessive repaints on non-retina devices
|
||||||
|
85
src/overrides/assets/javascripts/components/_/index.ts
Normal file
85
src/overrides/assets/javascripts/components/_/index.ts
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2016-2020 Martin Donath <martin.donath@squidfunk.com>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to
|
||||||
|
* deal in the Software without restriction, including without limitation the
|
||||||
|
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
* sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
* IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { getElementOrThrow, getElements } from "~/browser"
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
* Types
|
||||||
|
* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component
|
||||||
|
*/
|
||||||
|
export type ComponentType =
|
||||||
|
| "icon-search" /* Icon search */
|
||||||
|
| "icon-search-query" /* Icon search input */
|
||||||
|
| "icon-search-result" /* Icon search results */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A component
|
||||||
|
*
|
||||||
|
* @template T - Component type
|
||||||
|
* @template U - Reference type
|
||||||
|
*/
|
||||||
|
export type Component<
|
||||||
|
T extends {} = {},
|
||||||
|
U extends HTMLElement = HTMLElement
|
||||||
|
> =
|
||||||
|
T & {
|
||||||
|
ref: U /* Component reference */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
* Functions
|
||||||
|
* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the element for a given component or throw a reference error
|
||||||
|
*
|
||||||
|
* @template T - Element type
|
||||||
|
*
|
||||||
|
* @param type - Component type
|
||||||
|
* @param node - Node of reference
|
||||||
|
*
|
||||||
|
* @returns Element
|
||||||
|
*/
|
||||||
|
export function getComponentElement<T extends HTMLElement>(
|
||||||
|
type: ComponentType, node: ParentNode = document
|
||||||
|
): T {
|
||||||
|
return getElementOrThrow(`[data-mdx-component=${type}]`, node)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve all elements for a given component
|
||||||
|
*
|
||||||
|
* @template T - Element type
|
||||||
|
*
|
||||||
|
* @param type - Component type
|
||||||
|
* @param node - Node of reference
|
||||||
|
*
|
||||||
|
* @returns Elements
|
||||||
|
*/
|
||||||
|
export function getComponentElements<T extends HTMLElement>(
|
||||||
|
type: ComponentType, node: ParentNode = document
|
||||||
|
): T[] {
|
||||||
|
return getElements(`[data-mdx-component=${type}]`, node)
|
||||||
|
}
|
@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2016-2020 Martin Donath <martin.donath@squidfunk.com>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to
|
||||||
|
* deal in the Software without restriction, including without limitation the
|
||||||
|
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
* sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
* IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Observable, merge } from "rxjs"
|
||||||
|
|
||||||
|
import { configuration } from "~/_"
|
||||||
|
import { requestJSON } from "~/browser"
|
||||||
|
|
||||||
|
import { Component, getComponentElement } from "../../_"
|
||||||
|
import {
|
||||||
|
IconSearchQuery,
|
||||||
|
mountIconSearchQuery
|
||||||
|
} from "../query"
|
||||||
|
import {
|
||||||
|
IconSearchResult,
|
||||||
|
mountIconSearchResult
|
||||||
|
} from "../result"
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
* Types
|
||||||
|
* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Icon search index
|
||||||
|
*/
|
||||||
|
export type IconSearchIndex = string[]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Icon search
|
||||||
|
*/
|
||||||
|
export type IconSearch =
|
||||||
|
| IconSearchQuery
|
||||||
|
| IconSearchResult
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
* Functions
|
||||||
|
* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mount icon search
|
||||||
|
*
|
||||||
|
* @param el - Icon search element
|
||||||
|
*
|
||||||
|
* @returns Icon search component observable
|
||||||
|
*/
|
||||||
|
export function mountIconSearch(
|
||||||
|
el: HTMLElement
|
||||||
|
): Observable<Component<IconSearch>> {
|
||||||
|
const config = configuration()
|
||||||
|
const index$ = requestJSON<IconSearchIndex>(
|
||||||
|
`${config.base}/overrides/assets/javascripts/icons.json`
|
||||||
|
)
|
||||||
|
|
||||||
|
/* Retrieve nested components */
|
||||||
|
const query = getComponentElement("icon-search-query", el)
|
||||||
|
const result = getComponentElement("icon-search-result", el)
|
||||||
|
|
||||||
|
/* Create and return component */
|
||||||
|
const query$ = mountIconSearchQuery(query as HTMLInputElement)
|
||||||
|
return merge(
|
||||||
|
query$,
|
||||||
|
mountIconSearchResult(result, { index$, query$ })
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2016-2020 Martin Donath <martin.donath@squidfunk.com>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to
|
||||||
|
* deal in the Software without restriction, including without limitation the
|
||||||
|
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
* sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
* IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from "./_"
|
||||||
|
export * from "./query"
|
||||||
|
export * from "./result"
|
@ -20,50 +20,60 @@
|
|||||||
* IN THE SOFTWARE.
|
* IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* eslint-disable */
|
import { Observable, combineLatest, fromEvent, merge } from "rxjs"
|
||||||
|
import {
|
||||||
|
delay,
|
||||||
|
distinctUntilChanged,
|
||||||
|
map,
|
||||||
|
startWith
|
||||||
|
} from "rxjs/operators"
|
||||||
|
|
||||||
import { wrap } from "fuzzaldrin-plus"
|
import { watchElementFocus } from "~/browser"
|
||||||
import { h, round } from "utilities"
|
|
||||||
|
import { Component } from "../../_"
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
* Types
|
||||||
|
* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Icon search query
|
||||||
|
*/
|
||||||
|
export interface IconSearchQuery {
|
||||||
|
value: string /* Query value */
|
||||||
|
focus: boolean /* Query focus */
|
||||||
|
}
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
/* ----------------------------------------------------------------------------
|
||||||
* Functions
|
* Functions
|
||||||
* ------------------------------------------------------------------------- */
|
* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
function transform(value: string, query: string) {
|
/**
|
||||||
return `:${wrap(value.replace(/\.svg$/, "").replace(/\//g, "-"), query, {
|
* Mount icon search query
|
||||||
wrap: {
|
*
|
||||||
tagOpen: "<b>",
|
* @param el - Icon search query element
|
||||||
tagClose: "</b>"
|
*
|
||||||
}
|
* @returns Icon search query component observable
|
||||||
})}:`
|
*/
|
||||||
}
|
export function mountIconSearchQuery(
|
||||||
|
el: HTMLInputElement
|
||||||
|
): Observable<Component<IconSearchQuery, HTMLInputElement>> {
|
||||||
|
|
||||||
const base = "https://raw.githubusercontent.com/squidfunk/mkdocs-material/master/material/.icons/"
|
/* Intercept focus and input events */
|
||||||
|
const focus$ = watchElementFocus(el)
|
||||||
|
const value$ = merge(
|
||||||
|
fromEvent(el, "keyup"),
|
||||||
|
fromEvent(el, "focus").pipe(delay(1))
|
||||||
|
)
|
||||||
|
.pipe(
|
||||||
|
map(() => el.value),
|
||||||
|
startWith(el.value),
|
||||||
|
distinctUntilChanged()
|
||||||
|
)
|
||||||
|
|
||||||
export function renderIconSearch(
|
/* Combine into single observable */
|
||||||
results: string[], query: string
|
return combineLatest([value$, focus$])
|
||||||
) {
|
.pipe(
|
||||||
if (!query.length)
|
map(([value, focus]) => ({ ref: el, value, focus })),
|
||||||
return <div class=""></div>
|
|
||||||
return (
|
|
||||||
<div class="">
|
|
||||||
<span>{round(results.length)} results</span>
|
|
||||||
<ul class="tx-icon-search__list">
|
|
||||||
{results.slice(0, 10).map(result => (
|
|
||||||
<li class="tx-icon-search__item">
|
|
||||||
<span class="twemoji">
|
|
||||||
<img src={base + result} style="width: 18px; height: 18px" />
|
|
||||||
</span> – <button
|
|
||||||
class="md-clipboard--inline"
|
|
||||||
data-clipboard-text={
|
|
||||||
":" + result.replace(/\.svg$/, "").replace(/\//g, "-") + ":"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<code>{transform(result, query)}</code>
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -0,0 +1,151 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2016-2020 Martin Donath <martin.donath@squidfunk.com>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to
|
||||||
|
* deal in the Software without restriction, including without limitation the
|
||||||
|
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
* sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
* IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { filter as search } from "fuzzaldrin-plus"
|
||||||
|
import {
|
||||||
|
Observable,
|
||||||
|
Subject,
|
||||||
|
animationFrameScheduler,
|
||||||
|
combineLatest,
|
||||||
|
merge,
|
||||||
|
of
|
||||||
|
} from "rxjs"
|
||||||
|
import {
|
||||||
|
bufferCount,
|
||||||
|
distinctUntilKeyChanged,
|
||||||
|
filter,
|
||||||
|
finalize,
|
||||||
|
map,
|
||||||
|
observeOn,
|
||||||
|
switchMap,
|
||||||
|
tap,
|
||||||
|
withLatestFrom,
|
||||||
|
zipWith
|
||||||
|
} from "rxjs/operators"
|
||||||
|
|
||||||
|
import {
|
||||||
|
addToSearchResultList,
|
||||||
|
resetSearchResultList,
|
||||||
|
resetSearchResultMeta,
|
||||||
|
setSearchResultMeta
|
||||||
|
} from "~/actions"
|
||||||
|
import {
|
||||||
|
getElementOrThrow,
|
||||||
|
watchElementThreshold
|
||||||
|
} from "~/browser"
|
||||||
|
|
||||||
|
import { renderIconSearchResult } from "../../../templates"
|
||||||
|
import { Component } from "../../_"
|
||||||
|
import { IconSearchIndex } from "../_"
|
||||||
|
import { IconSearchQuery } from "../query"
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
* Types
|
||||||
|
* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Icon search result
|
||||||
|
*/
|
||||||
|
export interface IconSearchResult {
|
||||||
|
data: string[] /* Search result data */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
* Helper types
|
||||||
|
* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mount options
|
||||||
|
*/
|
||||||
|
interface MountOptions {
|
||||||
|
index$: Observable<IconSearchIndex> /* Search index observable */
|
||||||
|
query$: Observable<IconSearchQuery> /* Search query observable */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
* Functions
|
||||||
|
* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mount icon search result
|
||||||
|
*
|
||||||
|
* @param el - Icon search result element
|
||||||
|
* @param options - Options
|
||||||
|
*
|
||||||
|
* @returns Icon search result component observable
|
||||||
|
*/
|
||||||
|
export function mountIconSearchResult(
|
||||||
|
el: HTMLElement, { index$, query$ }: MountOptions
|
||||||
|
): Observable<Component<IconSearchResult, HTMLElement>> {
|
||||||
|
const internal$ = new Subject<IconSearchResult>()
|
||||||
|
const boundary$ = watchElementThreshold(el)
|
||||||
|
.pipe(
|
||||||
|
filter(Boolean)
|
||||||
|
)
|
||||||
|
|
||||||
|
/* Update search result metadata */
|
||||||
|
const meta = getElementOrThrow(":scope > :first-child", el)
|
||||||
|
internal$
|
||||||
|
.pipe(
|
||||||
|
observeOn(animationFrameScheduler),
|
||||||
|
withLatestFrom(query$)
|
||||||
|
)
|
||||||
|
.subscribe(([{ data }, { value }]) => {
|
||||||
|
if (value)
|
||||||
|
setSearchResultMeta(meta, data.length)
|
||||||
|
else
|
||||||
|
resetSearchResultMeta(meta)
|
||||||
|
})
|
||||||
|
|
||||||
|
/* Update icon search result list */
|
||||||
|
const list = getElementOrThrow(":scope > :last-child", el)
|
||||||
|
internal$
|
||||||
|
.pipe(
|
||||||
|
observeOn(animationFrameScheduler),
|
||||||
|
tap(() => resetSearchResultList(list)),
|
||||||
|
switchMap(({ data }) => merge(
|
||||||
|
of(...data.slice(0, 10)),
|
||||||
|
of(...data.slice(10))
|
||||||
|
.pipe(
|
||||||
|
bufferCount(10),
|
||||||
|
zipWith(boundary$),
|
||||||
|
switchMap(([chunk]) => of(...chunk))
|
||||||
|
)
|
||||||
|
)),
|
||||||
|
withLatestFrom(query$)
|
||||||
|
)
|
||||||
|
.subscribe(([result, { value }]) => {
|
||||||
|
addToSearchResultList(list, renderIconSearchResult(result, value))
|
||||||
|
})
|
||||||
|
|
||||||
|
/* Crate and return component */
|
||||||
|
return combineLatest([
|
||||||
|
index$,
|
||||||
|
query$.pipe(distinctUntilKeyChanged("value"))
|
||||||
|
])
|
||||||
|
.pipe(
|
||||||
|
map(([index, { value }]) => ({ data: search(index, value) })),
|
||||||
|
tap(internal$),
|
||||||
|
finalize(() => internal$.complete()),
|
||||||
|
map(state => ({ ref: el, ...state }))
|
||||||
|
)
|
||||||
|
}
|
24
src/overrides/assets/javascripts/components/index.ts
Normal file
24
src/overrides/assets/javascripts/components/index.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2016-2020 Martin Donath <martin.donath@squidfunk.com>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to
|
||||||
|
* deal in the Software without restriction, including without limitation the
|
||||||
|
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
* sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
* IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from "./_"
|
||||||
|
export * from "./icon-search"
|
@ -20,42 +20,30 @@
|
|||||||
* IN THE SOFTWARE.
|
* IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* eslint-disable */
|
import { Observable, fromEvent, merge } from "rxjs"
|
||||||
import { filter } from "fuzzaldrin-plus"
|
import { switchMap } from "rxjs/operators"
|
||||||
import { from, fromEvent } from "rxjs"
|
|
||||||
import { map, switchMap } from "rxjs/operators"
|
|
||||||
|
|
||||||
import { configuration } from "~/_"
|
import {
|
||||||
import { getElement, getElementOrThrow } from "~/browser"
|
getComponentElements,
|
||||||
|
mountIconSearch
|
||||||
|
} from "./components"
|
||||||
|
|
||||||
import { renderIconSearch } from "./templates/icon"
|
/* ----------------------------------------------------------------------------
|
||||||
|
* Application
|
||||||
|
* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
// Obtain configuration
|
/* Set up extra component observables */
|
||||||
const config = configuration()
|
declare const document$: Observable<Document>
|
||||||
|
document$
|
||||||
// Now, load icons.json
|
|
||||||
const icons$ =
|
|
||||||
from(fetch(`${config.base}/overrides/assets/javascripts/icons.json`)
|
|
||||||
.then(res => res.json())
|
|
||||||
)
|
|
||||||
|
|
||||||
// Render icon search, if present
|
|
||||||
const search = getElement<HTMLInputElement>("#icon-search")
|
|
||||||
if (search) {
|
|
||||||
icons$
|
|
||||||
.pipe(
|
.pipe(
|
||||||
switchMap(icons => fromEvent<InputEvent>(search, "keyup")
|
switchMap(() => merge(
|
||||||
.pipe(
|
|
||||||
map(() => filter(icons, search.value))
|
/* Icon search */
|
||||||
|
...getComponentElements("icon-search")
|
||||||
|
.map(child => mountIconSearch(child))
|
||||||
|
))
|
||||||
)
|
)
|
||||||
)
|
.subscribe()
|
||||||
)
|
|
||||||
.subscribe((result: any[]) => {
|
|
||||||
const list = getElementOrThrow(".tx-icon-result")
|
|
||||||
list.innerHTML = ""
|
|
||||||
list.appendChild(renderIconSearch(result, search.value))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Track click events
|
// Track click events
|
||||||
fromEvent(document.body, "click")
|
fromEvent(document.body, "click")
|
||||||
@ -63,8 +51,7 @@ fromEvent(document.body, "click")
|
|||||||
if (ev.target instanceof HTMLElement) {
|
if (ev.target instanceof HTMLElement) {
|
||||||
const el2 = ev.target.closest("a[href^=http]")
|
const el2 = ev.target.closest("a[href^=http]")
|
||||||
if (el2 instanceof HTMLLinkElement)
|
if (el2 instanceof HTMLLinkElement)
|
||||||
// @ts-ignore
|
ga("send", "event", "outbound", "click", el2.href)
|
||||||
ga("send", "event", "outbound", "click", el.href)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
23
src/overrides/assets/javascripts/integrations/index.ts
Normal file
23
src/overrides/assets/javascripts/integrations/index.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2016-2020 Martin Donath <martin.donath@squidfunk.com>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to
|
||||||
|
* deal in the Software without restriction, including without limitation the
|
||||||
|
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
* sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
* IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from "./analytics"
|
101
src/overrides/assets/javascripts/templates/icon-search/index.tsx
Normal file
101
src/overrides/assets/javascripts/templates/icon-search/index.tsx
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2016-2020 Martin Donath <martin.donath@squidfunk.com>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to
|
||||||
|
* deal in the Software without restriction, including without limitation the
|
||||||
|
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
* sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
* IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { wrap } from "fuzzaldrin-plus"
|
||||||
|
|
||||||
|
import { h } from "~/utilities"
|
||||||
|
import { translation } from "~/_"
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
* Data
|
||||||
|
* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Icon CDN URL
|
||||||
|
*/
|
||||||
|
const base =
|
||||||
|
"https://raw.githubusercontent.com/" +
|
||||||
|
"squidfunk/mkdocs-material/" +
|
||||||
|
"master/material/.icons/"
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
* Helper functions
|
||||||
|
* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert icon search result to shortcode
|
||||||
|
*
|
||||||
|
* @param value - Icon search result
|
||||||
|
*
|
||||||
|
* @returns Shortcode
|
||||||
|
*/
|
||||||
|
function shortcode(value: string): string {
|
||||||
|
return `:${value.replace(/\.svg$/, "").replace(/\//g, "-")}:`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Highlight an icon search result
|
||||||
|
*
|
||||||
|
* @param value - Icon search result
|
||||||
|
* @param query - Icon search query
|
||||||
|
*
|
||||||
|
* @returns Highlighted result
|
||||||
|
*/
|
||||||
|
function highlight(value: string, query: string) {
|
||||||
|
return wrap(shortcode(value), query, {
|
||||||
|
wrap: {
|
||||||
|
tagOpen: "<b>",
|
||||||
|
tagClose: "</b>"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
* Functions
|
||||||
|
* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render an icon search result
|
||||||
|
*
|
||||||
|
* @param value - Icon search result
|
||||||
|
* @param query - Icon search query
|
||||||
|
*
|
||||||
|
* @returns Element
|
||||||
|
*/
|
||||||
|
export function renderIconSearchResult(
|
||||||
|
value: string, query: string
|
||||||
|
): HTMLElement {
|
||||||
|
return (
|
||||||
|
<li class="mdx-icon-search-result__item">
|
||||||
|
<span class="twemoji">
|
||||||
|
<img src={base + value} />
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
class="md-clipboard--inline"
|
||||||
|
title={translation("clipboard.copy")}
|
||||||
|
data-clipboard-text={shortcode(value)}
|
||||||
|
>
|
||||||
|
<code>{highlight(value, query)}</code>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
23
src/overrides/assets/javascripts/templates/index.ts
Normal file
23
src/overrides/assets/javascripts/templates/index.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2016-2020 Martin Donath <martin.donath@squidfunk.com>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to
|
||||||
|
* deal in the Software without restriction, including without limitation the
|
||||||
|
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
* sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
* IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from "./icon-search"
|
@ -23,3 +23,97 @@
|
|||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// Nothing to see here, move along
|
// Nothing to see here, move along
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Scoped in typesetted content to match specificity of regular content
|
||||||
|
.md-typeset {
|
||||||
|
|
||||||
|
// Icon search
|
||||||
|
.mdx-icon-search {
|
||||||
|
position: relative;
|
||||||
|
background-color: var(--md-default-bg-color);
|
||||||
|
border-radius: px2rem(2px);
|
||||||
|
box-shadow:
|
||||||
|
0 px2rem(4px) px2rem(10px) hsla(0, 0%, 0%, 0.1),
|
||||||
|
0 px2rem(0.5px) px2rem(1px) hsla(0, 0%, 0%, 0.1);
|
||||||
|
transition: box-shadow 125ms;
|
||||||
|
|
||||||
|
// Icon search on focus/hover
|
||||||
|
&:focus-within,
|
||||||
|
&:hover {
|
||||||
|
box-shadow:
|
||||||
|
0 px2rem(8px) px2rem(20px) hsla(0, 0%, 0%, 0.15),
|
||||||
|
0 px2rem(0.5px) px2rem(1px) hsla(0, 0%, 0%, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Icon search input
|
||||||
|
.md-input {
|
||||||
|
box-shadow: 0 0 px2rem(12px) var(--md-default-fg-color--lightest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Icon search result
|
||||||
|
.mdx-icon-search-result {
|
||||||
|
max-height: 50vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
// Hack: promote to own layer to reduce jitter
|
||||||
|
backface-visibility: hidden;
|
||||||
|
touch-action: pan-y;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: var(--md-default-fg-color--lighter) transparent;
|
||||||
|
|
||||||
|
// Webkit scrollbar
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: px2rem(4px);
|
||||||
|
height: px2rem(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Webkit scrollbar thumb
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background-color: var(--md-default-fg-color--lighter);
|
||||||
|
|
||||||
|
// Webkit scrollbar thumb on hover
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--md-accent-fg-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Icon search result metadata
|
||||||
|
&__meta {
|
||||||
|
position: absolute;
|
||||||
|
top: px2rem(8px);
|
||||||
|
right: px2rem(12px);
|
||||||
|
color: var(--md-default-fg-color--lighter);
|
||||||
|
font-size: px2rem(12.8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Icon search result list
|
||||||
|
&__list {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Icon search result item
|
||||||
|
&__item {
|
||||||
|
margin: 0;
|
||||||
|
padding: px2rem(4px) px2rem(12px);
|
||||||
|
border-bottom: px2rem(1px) solid var(--md-default-fg-color--lightest);
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Item content
|
||||||
|
> * {
|
||||||
|
margin-right: px2rem(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set icon dimensions to fit
|
||||||
|
img {
|
||||||
|
width: px2rem(18px);
|
||||||
|
height: px2rem(18px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
9
typings/_/index.d.ts
vendored
9
typings/_/index.d.ts
vendored
@ -37,5 +37,14 @@ export interface GlobalSearchConfig {
|
|||||||
/* ------------------------------------------------------------------------- */
|
/* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GLobal search configuration
|
||||||
|
*/
|
||||||
var __search: GlobalSearchConfig | undefined
|
var __search: GlobalSearchConfig | undefined
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Google Analytics
|
||||||
|
*/
|
||||||
|
function ga(...args: string[]): void
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ import ImageminPlugin from "imagemin-webpack-plugin"
|
|||||||
import MiniCssExtractPlugin = require("mini-css-extract-plugin")
|
import MiniCssExtractPlugin = require("mini-css-extract-plugin")
|
||||||
import * as path from "path"
|
import * as path from "path"
|
||||||
import { toPairs } from "ramda"
|
import { toPairs } from "ramda"
|
||||||
import glob = require("tiny-glob")
|
import glob from "tiny-glob"
|
||||||
import { minify as minjs } from "terser"
|
import { minify as minjs } from "terser"
|
||||||
import { TsconfigPathsPlugin } from "tsconfig-paths-webpack-plugin"
|
import { TsconfigPathsPlugin } from "tsconfig-paths-webpack-plugin"
|
||||||
import { Configuration } from "webpack"
|
import { Configuration } from "webpack"
|
||||||
|
Loading…
Reference in New Issue
Block a user