Restructured project

This commit is contained in:
squidfunk 2019-12-22 16:52:28 +01:00
parent d1928cc31f
commit e04387902c
45 changed files with 533 additions and 270 deletions

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

View File

@ -55,5 +55,5 @@
* Copyright(c) 2015 Andreas Lubbe * Copyright(c) 2015 Andreas Lubbe
* Copyright(c) 2015 Tiancheng "Timothy" Gu * Copyright(c) 2015 Tiancheng "Timothy" Gu
* MIT Licensed * MIT Licensed
*/var n=/["'&<>]/;e.exports=function(e){var t,r=""+e,i=n.exec(r);if(!i)return r;var s="",o=0,a=0;for(o=i.index;o<r.length;o++){switch(r.charCodeAt(o)){case 34:t="&quot;";break;case 38:t="&amp;";break;case 39:t="&#39;";break;case 60:t="&lt;";break;case 62:t="&gt;";break;default:continue}a!==o&&(s+=r.substring(a,o)),a=o+1,s+=t}return a!==o?s+r.substring(a,o):s}},function(e,t,r){"use strict";const n=/[|\\{}()[\]^$+*?.-]/g;e.exports=e=>{if("string"!=typeof e)throw new TypeError("Expected a string");return e.replace(n,"\\$&")}},function(e,t,r){"use strict";r.r(t);var n=r(0),i=r(1),s=function(e){var t="function"==typeof Symbol&&Symbol.iterator,r=t&&e[t],n=0;if(r)return r.call(e);if(e&&"number"==typeof e.length)return{next:function(){return e&&n>=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")},o=function(e,t){var r="function"==typeof Symbol&&e[Symbol.iterator];if(!r)return e;var n,i,s=r.call(e),o=[];try{for(;(void 0===t||t-- >0)&&!(n=s.next()).done;)o.push(n.value)}catch(e){i={error:e}}finally{try{n&&!n.done&&(r=s.return)&&r.call(s)}finally{if(i)throw i.error}}return o};var a=r(2),u=function(){return(u=Object.assign||function(e){for(var t,r=1,n=arguments.length;r<n;r++)for(var i in t=arguments[r])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)};var l,c,h=function(e){var t="function"==typeof Symbol&&Symbol.iterator,r=t&&e[t],n=0;if(r)return r.call(e);if(e&&"number"==typeof e.length)return{next:function(){return e&&n>=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")},d=function(e,t){var r="function"==typeof Symbol&&e[Symbol.iterator];if(!r)return e;var n,i,s=r.call(e),o=[];try{for(;(void 0===t||t-- >0)&&!(n=s.next()).done;)o.push(n.value)}catch(e){i={error:e}}finally{try{n&&!n.done&&(r=s.return)&&r.call(s)}finally{if(i)throw i.error}}return o},f=function(){for(var e=[],t=0;t<arguments.length;t++)e=e.concat(d(arguments[t]));return e},p=function(){function e(e){var t=e.config,r=e.docs,l=e.pipeline,c=e.index;this.documents=function(e){var t,r,n=new Map;try{for(var a=s(e),u=a.next();!u.done;u=a.next()){var l=u.value,c=o(l.location.split("#"),2),h=c[0],d=c[1],f=l.location,p=l.title,y=i(l.text).replace(/\s+(?=[,.:;!?])/g,"").replace(/\s+/g," ");if(d){var m=n.get(h);m.section?n.set(f,{location:f,title:p,text:y,article:m}):(m.title=l.title,m.text=y,m.section=!0)}else n.set(f,{location:f,title:p,text:y,section:!1})}}catch(e){t={error:e}}finally{try{u&&!u.done&&(r=a.return)&&r.call(a)}finally{if(t)throw t.error}}return n}(r),this.highlight=function(e){var t=new RegExp(e.separator,"img"),r=function(e,t,r){return t+"<em>"+r+"</em>"};return function(n){n=n.replace(/[\s*+-:~^]+/g," ").trim();var i=new RegExp("(^|"+e.separator+")("+a(n).replace(t,"|")+")","img");return function(e){return u(u({},e),{title:e.title.replace(i,r),text:e.text.replace(i,r)})}}}(t),this.index=void 0===c?n((function(){var e,t;l=l||{trimmer:!0,stopwords:!0},this.pipeline.reset(),l.trimmer&&this.pipeline.add(n.trimmer),l.stopwords&&this.pipeline.add(n.stopWordFilter),this.field("title",{boost:10}),this.field("text"),this.ref("location");try{for(var i=h(r),s=i.next();!s.done;s=i.next()){var o=s.value;this.add(o)}}catch(t){e={error:t}}finally{try{s&&!s.done&&(t=i.return)&&t.call(i)}finally{if(e)throw e.error}}})):n.Index.load("string"==typeof c?JSON.parse(c):c)}return e.prototype.search=function(e){var t=this;if(e)try{var r=this.index.search(e).reduce((function(e,r){var n=t.documents.get(r.ref);if(void 0!==n)if("article"in n){var i=n.article.location;e.set(i,f(e.get(i)||[],[r]))}else{i=n.location;e.set(i,e.get(i)||[])}return e}),new Map),n=this.highlight(e);return f(r).map((function(e){var r=d(e,2),i=r[0],s=r[1];return{article:n(t.documents.get(i)),sections:s.map((function(e){return n(t.documents.get(e.ref))}))}}))}catch(t){console.warn("Invalid query: "+e+" see https://bit.ly/2s3ChXG")}return[]},e.prototype.toString=function(){return JSON.stringify(this.index)},e}();function y(e){switch(e.type){case l.SETUP:return c=new p(e.data),{type:l.DUMP,data:c.toString()};case l.QUERY:return{type:l.RESULT,data:c?c.search(e.data):[]};default:throw new TypeError("Invalid message type")}}!function(e){e[e.SETUP=0]="SETUP",e[e.DUMP=1]="DUMP",e[e.QUERY=2]="QUERY",e[e.RESULT=3]="RESULT"}(l||(l={})),r.d(t,"handler",(function(){return y})),addEventListener("message",(function(e){postMessage(y(e.data))}))}]); */var n=/["'&<>]/;e.exports=function(e){var t,r=""+e,i=n.exec(r);if(!i)return r;var s="",o=0,a=0;for(o=i.index;o<r.length;o++){switch(r.charCodeAt(o)){case 34:t="&quot;";break;case 38:t="&amp;";break;case 39:t="&#39;";break;case 60:t="&lt;";break;case 62:t="&gt;";break;default:continue}a!==o&&(s+=r.substring(a,o)),a=o+1,s+=t}return a!==o?s+r.substring(a,o):s}},function(e,t,r){"use strict";const n=/[|\\{}()[\]^$+*?.-]/g;e.exports=e=>{if("string"!=typeof e)throw new TypeError("Expected a string");return e.replace(n,"\\$&")}},function(e,t,r){"use strict";r.r(t);var n=r(0),i=r(1),s=function(e){var t="function"==typeof Symbol&&Symbol.iterator,r=t&&e[t],n=0;if(r)return r.call(e);if(e&&"number"==typeof e.length)return{next:function(){return e&&n>=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")},o=function(e,t){var r="function"==typeof Symbol&&e[Symbol.iterator];if(!r)return e;var n,i,s=r.call(e),o=[];try{for(;(void 0===t||t-- >0)&&!(n=s.next()).done;)o.push(n.value)}catch(e){i={error:e}}finally{try{n&&!n.done&&(r=s.return)&&r.call(s)}finally{if(i)throw i.error}}return o};var a=r(2),u=function(){return(u=Object.assign||function(e){for(var t,r=1,n=arguments.length;r<n;r++)for(var i in t=arguments[r])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)};var l,c,h=function(e){var t="function"==typeof Symbol&&Symbol.iterator,r=t&&e[t],n=0;if(r)return r.call(e);if(e&&"number"==typeof e.length)return{next:function(){return e&&n>=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")},d=function(e,t){var r="function"==typeof Symbol&&e[Symbol.iterator];if(!r)return e;var n,i,s=r.call(e),o=[];try{for(;(void 0===t||t-- >0)&&!(n=s.next()).done;)o.push(n.value)}catch(e){i={error:e}}finally{try{n&&!n.done&&(r=s.return)&&r.call(s)}finally{if(i)throw i.error}}return o},f=function(){for(var e=[],t=0;t<arguments.length;t++)e=e.concat(d(arguments[t]));return e},p=function(){function e(e){var t=e.config,r=e.docs,l=e.pipeline,c=e.index;this.documents=function(e){var t,r,n=new Map;try{for(var a=s(e),u=a.next();!u.done;u=a.next()){var l=u.value,c=o(l.location.split("#"),2),h=c[0],d=c[1],f=l.location,p=l.title,y=i(l.text).replace(/\s+(?=[,.:;!?])/g,"").replace(/\s+/g," ");if(d){var m=n.get(h);m.linked?n.set(f,{location:f,title:p,text:y,parent:m}):(m.title=l.title,m.text=y,m.linked=!0)}else n.set(f,{location:f,title:p,text:y,linked:!1})}}catch(e){t={error:e}}finally{try{u&&!u.done&&(r=a.return)&&r.call(a)}finally{if(t)throw t.error}}return n}(r),this.highlight=function(e){var t=new RegExp(e.separator,"img"),r=function(e,t,r){return t+"<em>"+r+"</em>"};return function(n){n=n.replace(/[\s*+-:~^]+/g," ").trim();var i=new RegExp("(^|"+e.separator+")("+a(n).replace(t,"|")+")","img");return function(e){return u(u({},e),{title:e.title.replace(i,r),text:e.text.replace(i,r)})}}}(t),this.index=void 0===c?n((function(){var e,t;l=l||{trimmer:!0,stopwords:!0},this.pipeline.reset(),l.trimmer&&this.pipeline.add(n.trimmer),l.stopwords&&this.pipeline.add(n.stopWordFilter),this.field("title",{boost:10}),this.field("text"),this.ref("location");try{for(var i=h(r),s=i.next();!s.done;s=i.next()){var o=s.value;this.add(o)}}catch(t){e={error:t}}finally{try{s&&!s.done&&(t=i.return)&&t.call(i)}finally{if(e)throw e.error}}})):n.Index.load("string"==typeof c?JSON.parse(c):c)}return e.prototype.search=function(e){var t=this;if(e)try{var r=this.index.search(e).reduce((function(e,r){var n=t.documents.get(r.ref);if(void 0!==n)if("parent"in n){var i=n.parent.location;e.set(i,f(e.get(i)||[],[r]))}else{i=n.location;e.set(i,e.get(i)||[])}return e}),new Map),n=this.highlight(e);return f(r).map((function(e){var r=d(e,2),i=r[0],s=r[1];return{article:n(t.documents.get(i)),sections:s.map((function(e){return n(t.documents.get(e.ref))}))}}))}catch(t){console.warn("Invalid query: "+e+" see https://bit.ly/2s3ChXG")}return[]},e.prototype.toString=function(){return JSON.stringify(this.index)},e}();function y(e){switch(e.type){case l.SETUP:return c=new p(e.data),{type:l.DUMP,data:c.toString()};case l.QUERY:return{type:l.RESULT,data:c?c.search(e.data):[]};default:throw new TypeError("Invalid message type")}}!function(e){e[e.SETUP=0]="SETUP",e[e.DUMP=1]="DUMP",e[e.QUERY=2]="QUERY",e[e.RESULT=3]="RESULT"}(l||(l={})),r.d(t,"handler",(function(){return y})),addEventListener("message",(function(e){postMessage(y(e.data))}))}]);
//# sourceMappingURL=search.js.map //# sourceMappingURL=search.js.map

File diff suppressed because one or more lines are too long

View File

@ -1002,8 +1002,7 @@ hr {
margin: 0 0.2rem; margin: 0 0.2rem;
overflow-y: auto; overflow-y: auto;
-webkit-backface-visibility: hidden; -webkit-backface-visibility: hidden;
backface-visibility: hidden; backface-visibility: hidden; }
-webkit-overflow-scrolling: touch; }
.md-sidebar__scrollwrap::-webkit-scrollbar { .md-sidebar__scrollwrap::-webkit-scrollbar {
width: 0.2rem; width: 0.2rem;
height: 0.2rem; } height: 0.2rem; }
@ -2542,7 +2541,9 @@ hr {
margin-right: 100%; margin-right: 100%;
margin-left: initial; margin-left: initial;
-webkit-transform: translate(100%, 0); -webkit-transform: translate(100%, 0);
transform: translate(100%, 0); } } transform: translate(100%, 0); }
.md-sidebar--secondary .md-sidebar__scrollwrap {
-webkit-overflow-scrolling: touch; } }
@media only screen and (min-width: 76.25em) { @media only screen and (min-width: 76.25em) {
.md-content { .md-content {

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

View File

@ -31,7 +31,7 @@
<meta name="author" content="{{ config.site_author }}"> <meta name="author" content="{{ config.site_author }}">
{% endif %} {% endif %}
<link rel="shortcut icon" href="{{ config.theme.favicon | url }}"> <link rel="shortcut icon" href="{{ config.theme.favicon | url }}">
<meta name="generator" content="mkdocs-{{ mkdocs_version }}, mkdocs-material-4.6.0"> <meta name="generator" content="mkdocs-{{ mkdocs_version }}, mkdocs-material-5.0.0-preview">
{% endblock %} {% endblock %}
{% block htmltitle %} {% block htmltitle %}
{% if page and page.meta and page.meta.title %} {% if page and page.meta and page.meta.title %}
@ -236,7 +236,7 @@
{%- endfor -%} {%- endfor -%}
{{ translations | tojson }} {{ translations | tojson }}
</script> </script>
<script>app=initialize({base:"{{ base_url }}",worker:{search:"{{ 'assets/javascripts/search.js' | url }}",packer:"{{ 'assets/javascripts/packer.js' | url }}"}})</script> <script>app=initialize({base:"{{ base_url }}",worker:{search:"{{ 'assets/javascripts/worker/search.js' | url }}",packer:"{{ 'assets/javascripts/worker/packer.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 %}

View File

@ -1,6 +1,6 @@
{ {
"name": "mkdocs-material", "name": "mkdocs-material",
"version": "4.6.0", "version": "5.0.0-preview",
"description": "A Material Design theme for MkDocs", "description": "A Material Design theme for MkDocs",
"keywords": [ "keywords": [
"mkdocs", "mkdocs",

View File

@ -27,9 +27,9 @@ import { Observable, defer, of } from "rxjs"
* ------------------------------------------------------------------------- */ * ------------------------------------------------------------------------- */
/** /**
* Header * Header state
*/ */
export interface Header { export interface HeaderState {
sticky: boolean /* Header stickyness */ sticky: boolean /* Header stickyness */
height: number /* Header visible height */ height: number /* Header visible height */
} }
@ -46,11 +46,11 @@ export interface Header {
* *
* @param el - Header element * @param el - Header element
* *
* @return Header observable * @return Header state observable
*/ */
export function watchHeader( export function watchHeader(
el: HTMLElement el: HTMLElement
): Observable<Header> { ): Observable<HeaderState> {
return defer(() => { return defer(() => {
const sticky = getComputedStyle(el) const sticky = getComputedStyle(el)
.getPropertyValue("position") === "fixed" .getPropertyValue("position") === "fixed"

View File

@ -30,7 +30,7 @@ import {
import { Agent, ViewportOffset } from "utilities" import { Agent, ViewportOffset } from "utilities"
import { Header } from "../_" import { HeaderState } from "../_"
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
* Helper types * Helper types
@ -40,7 +40,7 @@ import { Header } from "../_"
* Options * Options
*/ */
interface Options { interface Options {
header$: Observable<Header> /* Header observable */ header$: Observable<HeaderState> /* Header state observable */
} }
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
@ -59,7 +59,7 @@ interface Options {
* *
* @return Viewport offset observable * @return Viewport offset observable
*/ */
export function watchHeaderOffsetToTopOf( export function watchViewportOffsetFromTopOf(
el: HTMLElement, { viewport }: Agent, { header$ }: Options el: HTMLElement, { viewport }: Agent, { header$ }: Options
): Observable<ViewportOffset> { ): Observable<ViewportOffset> {
@ -91,7 +91,7 @@ export function watchHeaderOffsetToTopOf(
* *
* @return Viewport offset observable * @return Viewport offset observable
*/ */
export function watchHeaderOffsetToBottomOf( export function watchViewportOffsetFromBottomOf(
el: HTMLElement, { viewport }: Agent, { header$ }: Options el: HTMLElement, { viewport }: Agent, { header$ }: Options
): Observable<ViewportOffset> { ): Observable<ViewportOffset> {

View File

@ -34,7 +34,7 @@ import {
import { resetHeaderShadow, setHeaderShadow } from "actions" import { resetHeaderShadow, setHeaderShadow } from "actions"
import { Main } from "../../main" import { MainState } from "../../main"
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
* Functions * Functions
@ -49,7 +49,7 @@ import { Main } from "../../main"
*/ */
export function paintHeaderShadow( export function paintHeaderShadow(
el: HTMLElement el: HTMLElement
): MonoTypeOperatorFunction<Main> { ): MonoTypeOperatorFunction<MainState> {
return pipe( return pipe(
distinctUntilKeyChanged("active"), distinctUntilKeyChanged("active"),

View File

@ -26,16 +26,16 @@ import { map, shareReplay } from "rxjs/operators"
import { switchMapIf } from "extensions" import { switchMapIf } from "extensions"
import { Agent, paintHidden } from "utilities" import { Agent, paintHidden } from "utilities"
import { Header, watchHeaderOffsetToTopOf } from "../header" import { HeaderState, watchViewportOffsetFromTopOf } from "../header"
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
* Types * Types
* ------------------------------------------------------------------------- */ * ------------------------------------------------------------------------- */
/** /**
* Hero * Hero state
*/ */
export interface Hero { export interface HeroState {
hidden: boolean /* Whether the hero is hidden */ hidden: boolean /* Whether the hero is hidden */
} }
@ -47,7 +47,7 @@ export interface Hero {
* Options * Options
*/ */
interface Options { interface Options {
header$: Observable<Header> /* Header observable */ header$: Observable<HeaderState> /* Header state observable */
} }
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
@ -55,22 +55,20 @@ interface Options {
* ------------------------------------------------------------------------- */ * ------------------------------------------------------------------------- */
/** /**
* Setup hero from source observable * Watch hero
* *
* @param el - Hero element
* @param agent - Agent * @param agent - Agent
* @param options - Options * @param options - Options
* *
* @return Operator function * @return Hero state
*/ */
export function setupHero( export function watchHero(
agent: Agent, { header$ }: Options el: HTMLElement, agent: Agent, { header$ }: Options
): OperatorFunction<HTMLElement, Hero> { ): Observable<HeroState> {
const { media } = agent
return pipe(
switchMapIf(media.screen$, el => {
/* Watch and paint visibility */ /* Watch and paint visibility */
const hidden$ = watchHeaderOffsetToTopOf(el, agent, { header$ }) const hidden$ = watchViewportOffsetFromTopOf(el, agent, { header$ })
.pipe( .pipe(
paintHidden(el, 20) paintHidden(el, 20)
) )
@ -80,7 +78,24 @@ export function setupHero(
.pipe( .pipe(
map(hidden => ({ hidden })) map(hidden => ({ hidden }))
) )
}), }
/* ------------------------------------------------------------------------- */
/**
* Mount hero from source observable
*
* @param agent - Agent
* @param options - Options
*
* @return Operator function
*/
export function mountHero(
agent: Agent, options: Options
): OperatorFunction<HTMLElement, HeroState> {
const { media } = agent
return pipe(
switchMapIf(media.screen$, el => watchHero(el, agent, options)),
shareReplay(1) shareReplay(1)
) )
} }

View File

@ -31,16 +31,16 @@ import {
import { Agent } from "utilities" import { Agent } from "utilities"
import { Header } from "../../header" import { HeaderState } from "../../header"
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
* Types * Types
* ------------------------------------------------------------------------- */ * ------------------------------------------------------------------------- */
/** /**
* Main area * Main area state
*/ */
export interface Main { export interface MainState {
offset: number /* Main area top offset */ offset: number /* Main area top offset */
height: number /* Main area visible height */ height: number /* Main area visible height */
active: boolean /* Scrolled past top offset */ active: boolean /* Scrolled past top offset */
@ -54,7 +54,7 @@ export interface Main {
* Options * Options
*/ */
interface Options { interface Options {
header$: Observable<Header> /* Header observable */ header$: Observable<HeaderState> /* Header state observable */
} }
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
@ -62,23 +62,21 @@ interface Options {
* ------------------------------------------------------------------------- */ * ------------------------------------------------------------------------- */
/** /**
* Setup main area from source observable * Watch main area
* *
* This function returns an observable that computes the visual parameters of * This function returns an observable that computes the visual parameters of
* the main area from the viewport height and vertical offset, as well as the * the main area which depends on the viewport height and vertical offset, as
* height of the header element. The height of the main area is corrected by * well as the height of the header element, if the header is fixed.
* the height of the header (if fixed) and footer element.
* *
* @param el - Main area element
* @param agent - Agent * @param agent - Agent
* @param options - Options * @param options - Options
* *
* @return Operator function * @return Main area state observable
*/ */
export function setupMain( export function watchMain(
{ viewport }: Agent, { header$ }: Options el: HTMLElement, { viewport }: Agent, { header$ }: Options
): OperatorFunction<HTMLElement, Main> { ): Observable<MainState> {
return pipe(
switchMap(el => {
/* Compute necessary adjustment for header */ /* Compute necessary adjustment for header */
const adjust$ = header$ const adjust$ = header$
@ -119,7 +117,23 @@ export function setupMain(
active active
})) }))
) )
}), }
/* ------------------------------------------------------------------------- */
/**
* Mount main area from source observable
*
* @param agent - Agent
* @param options - Options
*
* @return Operator function
*/
export function mountMain(
agent: Agent, options: Options
): OperatorFunction<HTMLElement, MainState> {
return pipe(
switchMap(el => watchMain(el, agent, options)),
shareReplay(1) shareReplay(1)
) )
} }

View File

@ -45,16 +45,16 @@ import {
} from "actions" } from "actions"
import { Agent } from "utilities" import { Agent } from "utilities"
import { Main } from "../main" import { MainState } from "../_"
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
* Types * Types
* ------------------------------------------------------------------------- */ * ------------------------------------------------------------------------- */
/** /**
* Sidebar * Sidebar state
*/ */
export interface Sidebar { export interface SidebarState {
height: number /* Sidebar height */ height: number /* Sidebar height */
lock: boolean /* Sidebar lock */ lock: boolean /* Sidebar lock */
} }
@ -67,7 +67,7 @@ export interface Sidebar {
* Options * Options
*/ */
interface Options { interface Options {
main$: Observable<Main> /* Main area observable */ main$: Observable<MainState> /* Main area state observable */
} }
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
@ -78,19 +78,19 @@ interface Options {
* Watch sidebar * Watch sidebar
* *
* This function returns an observable that computes the visual parameters of * This function returns an observable that computes the visual parameters of
* the given element (a sidebar) from the vertical viewport offset, as well as * the sidebar which depends on the vertical viewport offset, as well as the
* the height of the main area. When the page is scrolled beyond the header, * height of the main area. When the page is scrolled beyond the header, the
* the sidebar is locked and fills the remaining space. * sidebar is locked and fills the remaining space.
* *
* @param el - Sidebar element * @param el - Sidebar element
* @param agent - Agent * @param agent - Agent
* @param options - Options * @param options - Options
* *
* @return Sidebar observable * @return Sidebar state observable
*/ */
export function watchSidebar( export function watchSidebar(
el: HTMLElement, { viewport }: Agent, { main$ }: Options el: HTMLElement, { viewport }: Agent, { main$ }: Options
): Observable<Sidebar> { ): Observable<SidebarState> {
/* Adjust for internal main area offset */ /* Adjust for internal main area offset */
const adjust = parseFloat( const adjust = parseFloat(
@ -116,7 +116,7 @@ export function watchSidebar(
return combineLatest([height$, lock$]) return combineLatest([height$, lock$])
.pipe( .pipe(
map(([height, lock]) => ({ height, lock })), map(([height, lock]) => ({ height, lock })),
distinctUntilChanged<Sidebar>(equals), distinctUntilChanged<SidebarState>(equals),
shareReplay(1) shareReplay(1)
) )
} }
@ -132,7 +132,7 @@ export function watchSidebar(
*/ */
export function paintSidebar( export function paintSidebar(
el: HTMLElement el: HTMLElement
): MonoTypeOperatorFunction<Sidebar> { ): MonoTypeOperatorFunction<SidebarState> {
return pipe( return pipe(
/* Defer repaint to next animation frame */ /* Defer repaint to next animation frame */

View File

@ -27,8 +27,8 @@ import { switchMapIf } from "extensions"
import { Agent } from "utilities" import { Agent } from "utilities"
import { import {
Main, MainState,
Sidebar, SidebarState,
paintSidebar, paintSidebar,
watchSidebar watchSidebar
} from "../../main" } from "../../main"
@ -38,10 +38,10 @@ import {
* ------------------------------------------------------------------------- */ * ------------------------------------------------------------------------- */
/** /**
* Navigation * Navigation state
*/ */
export interface Navigation { export interface NavigationState {
sidebar: Sidebar /* Sidebar */ sidebar: SidebarState /* Sidebar state */
} }
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
@ -52,7 +52,7 @@ export interface Navigation {
* Options * Options
*/ */
interface Options { interface Options {
main$: Observable<Main> /* Main observable */ main$: Observable<MainState> /* Main area state observable */
} }
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
@ -60,19 +60,17 @@ interface Options {
* ------------------------------------------------------------------------- */ * ------------------------------------------------------------------------- */
/** /**
* Setup navigation from source observable * Watch navigation
* *
* @param el - Navigation element
* @param agent - Agent * @param agent - Agent
* @param options - Options * @param options - Options
* *
* @return Operator function * @return Navigation state observable
*/ */
export function setupNavigation( export function watchNavigation(
agent: Agent, { main$ }: Options el: HTMLElement, agent: Agent, { main$ }: Options
): OperatorFunction<HTMLElement, Navigation> { ): Observable<NavigationState> {
const { media } = agent
return pipe(
switchMapIf(media.screen$, el => {
/* Watch and paint sidebar */ /* Watch and paint sidebar */
const sidebar$ = watchSidebar(el, agent, { main$ }) const sidebar$ = watchSidebar(el, agent, { main$ })
@ -85,7 +83,24 @@ export function setupNavigation(
.pipe( .pipe(
map(sidebar => ({ sidebar })) map(sidebar => ({ sidebar }))
) )
}), }
/* ------------------------------------------------------------------------- */
/**
* Mount navigation from source observable
*
* @param agent - Agent
* @param options - Options
*
* @return Operator function
*/
export function mountNavigation(
agent: Agent, options: Options
): OperatorFunction<HTMLElement, NavigationState> {
const { media } = agent
return pipe(
switchMapIf(media.screen$, el => watchNavigation(el, agent, options)),
shareReplay(1) shareReplay(1)
) )
} }

View File

@ -20,7 +20,6 @@
* IN THE SOFTWARE. * IN THE SOFTWARE.
*/ */
// tslint:disable-next-line export * from "./query"
// export * from "./query"
export * from "./reset" export * from "./reset"
export * from "./result" export * from "./result"

View File

@ -19,3 +19,71 @@
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE. * IN THE SOFTWARE.
*/ */
import { Observable, combineLatest, fromEvent } from "rxjs"
import {
distinctUntilChanged,
map,
shareReplay,
startWith
} from "rxjs/operators"
import { watchElementFocus } from "utilities"
/* ----------------------------------------------------------------------------
* Types
* ------------------------------------------------------------------------- */
/**
* Search query state
*/
export interface SearchQueryState {
value: string /* Query value */
focus: boolean /* Query focus state */
}
/* ----------------------------------------------------------------------------
* Helper types
* ------------------------------------------------------------------------- */
/**
* Options
*/
interface Options {
prepare(value: string): string /* Preparation function */
}
/* ----------------------------------------------------------------------------
* Functions
* ------------------------------------------------------------------------- */
/**
* Watch search query
*
* @param el - Search query element
* @param options - Options
*
* @return Search query state observable
*/
export function watchSearchQuery(
el: HTMLInputElement, { prepare }: Options
): Observable<SearchQueryState> {
/* Intercept keyboard events */
const value$ = fromEvent(el, "keyup")
.pipe(
map(() => prepare(el.value)),
startWith(""),
distinctUntilChanged()
)
/* Intercept focus events */
const focus$ = watchElementFocus(el)
/* Combine into a single hot observable */
return combineLatest([value$, focus$])
.pipe(
map(([value, focus]) => ({ value, focus })),
shareReplay(1)
)
}

View File

@ -36,9 +36,9 @@ import { mapTo } from "rxjs/operators"
*/ */
export function watchSearchReset( export function watchSearchReset(
el: HTMLElement el: HTMLElement
): Observable<boolean> { ): Observable<void> {
return fromEvent(el, "click") return fromEvent(el, "click")
.pipe( .pipe(
mapTo(true) mapTo(undefined)
) )
} }

View File

@ -52,34 +52,53 @@ interface Options {
* ------------------------------------------------------------------------- */ * ------------------------------------------------------------------------- */
/** /**
* Setup search result from source observable * Watch search result
* *
* @param el - Search result element * @param el - Search result element
* @param agent - Agent * @param agent - Agent
* @param options - Options
* *
* @return Operator function * @return Search result state observable
*/ */
export function setupSearchResult( export function watchSearchResult(
agent: Agent, { result$, query$ }: Options el: HTMLElement, agent: Agent, { result$, query$ }: Options
): OperatorFunction<HTMLElement, SearchResult[]> { ): Observable<SearchResult[]> {
return pipe( const container = el.parentElement!
switchMap(el => {
const parent = el.parentElement!
/* Compute whether more elements need to be rendered */ /* Compute whether there are more search results elements */
const render$ = watchElementOffset(parent, agent) const render$ = watchElementOffset(container, agent)
.pipe( .pipe(
map(({ y }) => y >= parent.scrollHeight - parent.offsetHeight - 16), map(({ y }) => y >= container.scrollHeight - container.offsetHeight - 16),
distinctUntilChanged(), distinctUntilChanged(),
filter(identity) filter(identity)
) )
// combine into search result observable...
/* Paint search results */ /* Paint search results */
return result$ return result$
.pipe( .pipe(
tap(x => { console.log("watchSearchResult", x) }),
paintSearchResultMeta(el, { query$ }), paintSearchResultMeta(el, { query$ }),
paintSearchResultList(el, { render$ }) paintSearchResultList(el, { render$ })
) )
}) }
/* ------------------------------------------------------------------------- */
/**
* Mount search result from source observable
*
* @param agent - Agent
* @param options - Options
*
* @return Operator function
*/
export function mountSearchResult(
agent: Agent, options: Options
): OperatorFunction<HTMLElement, SearchResult[]> {
return pipe(
switchMap(el => watchSearchResult(el, agent, options)),
shareReplay(1)
) )
} }

View File

@ -68,7 +68,7 @@ interface Options {
export function paintSearchResultList( export function paintSearchResultList(
el: HTMLElement, { render$ }: Options el: HTMLElement, { render$ }: Options
): MonoTypeOperatorFunction<SearchResult[]> { ): MonoTypeOperatorFunction<SearchResult[]> {
const parent = el.parentElement! const container = el.parentElement!
const list = getElement(".md-search-result__list", el)! const list = getElement(".md-search-result__list", el)!
return pipe( return pipe(
switchMap(result => render$ switchMap(result => render$
@ -79,7 +79,7 @@ export function paintSearchResultList(
scan(index => { scan(index => {
while (index < result.length) { while (index < result.length) {
addToSearchResultList(list, renderSearchResult(result[index++])) addToSearchResultList(list, renderSearchResult(result[index++]))
if (parent.scrollHeight - parent.offsetHeight > 16) if (container.scrollHeight - container.offsetHeight > 16)
break break
} }
return index return index

View File

@ -26,16 +26,16 @@ import { map, shareReplay } from "rxjs/operators"
import { switchMapIf } from "extensions" import { switchMapIf } from "extensions"
import { Agent, paintHidden } from "utilities" import { Agent, paintHidden } from "utilities"
import { Header, watchHeaderOffsetToTopOf } from "../header" import { HeaderState, watchViewportOffsetFromTopOf } from "../header"
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
* Types * Types
* ------------------------------------------------------------------------- */ * ------------------------------------------------------------------------- */
/** /**
* Tabs * Tabs state
*/ */
export interface Tabs { export interface TabsState {
hidden: boolean /* Whether the tabs are hidden */ hidden: boolean /* Whether the tabs are hidden */
} }
@ -47,7 +47,7 @@ export interface Tabs {
* Options * Options
*/ */
interface Options { interface Options {
header$: Observable<Header> /* Header observable */ header$: Observable<HeaderState> /* Header state observable */
} }
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
@ -55,22 +55,23 @@ interface Options {
* ------------------------------------------------------------------------- */ * ------------------------------------------------------------------------- */
/** /**
* Setup tabs from source observable * Watch tabs
* *
* This function returns an observable that computes the visual parameters of
* the tabs, currently only denoting whether the tabs are hidden or not.
*
* @param el - Tabs element
* @param agent - Agent * @param agent - Agent
* @param options - Options * @param options - Options
* *
* @return Operator function * @return Tabs state
*/ */
export function setupTabs( export function watchTabs(
agent: Agent, { header$ }: Options el: HTMLElement, agent: Agent, { header$ }: Options
): OperatorFunction<HTMLElement, Tabs> { ): Observable<TabsState> {
const { media } = agent
return pipe(
switchMapIf(media.screen$, el => {
/* Watch and paint visibility */ /* Watch and paint visibility */
const hidden$ = watchHeaderOffsetToTopOf(el, agent, { header$ }) const hidden$ = watchViewportOffsetFromTopOf(el, agent, { header$ })
.pipe( .pipe(
paintHidden(el, 8) paintHidden(el, 8)
) )
@ -80,7 +81,24 @@ export function setupTabs(
.pipe( .pipe(
map(hidden => ({ hidden })) map(hidden => ({ hidden }))
) )
}), }
/* ------------------------------------------------------------------------- */
/**
* Mount tabs from source observable
*
* @param agent - Agent
* @param options - Options
*
* @return Operator function
*/
export function mountTabs(
agent: Agent, options: Options
): OperatorFunction<HTMLElement, TabsState> {
const { media } = agent
return pipe(
switchMapIf(media.screen$, el => watchTabs(el, agent, options)),
shareReplay(1) shareReplay(1)
) )
} }

View File

@ -26,10 +26,10 @@ import { map, shareReplay } from "rxjs/operators"
import { switchMapIf } from "extensions" import { switchMapIf } from "extensions"
import { Agent, getElements } from "utilities" import { Agent, getElements } from "utilities"
import { Header } from "../../header" import { HeaderState } from "../../header"
import { import {
Main, MainState,
Sidebar, SidebarState,
paintSidebar, paintSidebar,
watchSidebar watchSidebar
} from "../../main" } from "../../main"
@ -44,10 +44,10 @@ import {
* ------------------------------------------------------------------------- */ * ------------------------------------------------------------------------- */
/** /**
* Table of contents * Table of contents state
*/ */
export interface TableOfContents { export interface TableOfContentsState {
sidebar: Sidebar /* Sidebar */ sidebar: SidebarState /* Sidebar state */
anchors: AnchorList /* Anchor list */ anchors: AnchorList /* Anchor list */
} }
@ -59,8 +59,8 @@ export interface TableOfContents {
* Options * Options
*/ */
interface Options { interface Options {
header$: Observable<Header> /* Header observable */ header$: Observable<HeaderState> /* Header state observable */
main$: Observable<Main> /* Main observable */ main$: Observable<MainState> /* Main area state observable */
} }
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
@ -68,19 +68,17 @@ interface Options {
* ------------------------------------------------------------------------- */ * ------------------------------------------------------------------------- */
/** /**
* Setup table of contents from source observable * Watch table of contents
* *
* @param el - Table of contents element
* @param agent - Agent * @param agent - Agent
* @param options - Options * @param options - Options
* *
* @return Operator function * @return Table of contents state observable
*/ */
export function setupTableOfContents( export function watchTableOfContents(
agent: Agent, { header$, main$ }: Options el: HTMLElement, agent: Agent, { header$, main$ }: Options
): OperatorFunction<HTMLElement, TableOfContents> { ): Observable<TableOfContentsState> {
const { media } = agent
return pipe(
switchMapIf(media.tablet$, el => {
/* Watch and paint sidebar */ /* Watch and paint sidebar */
const sidebar$ = watchSidebar(el, agent, { main$ }) const sidebar$ = watchSidebar(el, agent, { main$ })
@ -100,7 +98,24 @@ export function setupTableOfContents(
.pipe( .pipe(
map(([sidebar, anchors]) => ({ sidebar, anchors })) map(([sidebar, anchors]) => ({ sidebar, anchors }))
) )
}), }
/* ------------------------------------------------------------------------- */
/**
* Mount table of contents from source observable
*
* @param agent - Agent
* @param options - Options
*
* @return Operator function
*/
export function mountTableOfContents(
agent: Agent, options: Options
): OperatorFunction<HTMLElement, TableOfContentsState> {
const { media } = agent
return pipe(
switchMapIf(media.tablet$, el => watchTableOfContents(el, agent, options)),
shareReplay(1) shareReplay(1)
) )
} }

View File

@ -47,7 +47,7 @@ import {
} from "actions" } from "actions"
import { Agent, getElement } from "utilities" import { Agent, getElement } from "utilities"
import { Header } from "../../header" import { HeaderState } from "../../header"
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
* Types * Types
@ -69,7 +69,7 @@ export interface AnchorList {
* Options * Options
*/ */
interface Options { interface Options {
header$: Observable<Header> /* Header observable */ header$: Observable<HeaderState> /* Header state observable */
} }
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------

View File

@ -21,4 +21,3 @@
*/ */
export * from "./_" export * from "./_"
export * from "./anchor"

View File

@ -55,25 +55,25 @@ export interface SearchIndexDocument {
} }
/** /**
* Search index options * Search index pipeline
*/ */
export interface SearchIndexOptions { export interface SearchIndexPipeline {
pipeline: {
trimmer: boolean /* Add trimmer to pipeline */ trimmer: boolean /* Add trimmer to pipeline */
stopwords: boolean /* Add stopword filter to pipeline */ stopwords: boolean /* Add stopword filter to pipeline */
}
} }
/* ------------------------------------------------------------------------- */
/** /**
* Search index * Search index options
* *
* This interfaces describes the format of the `search_index.json` file which * This interfaces describes the format of the `search_index.json` file which
* is automatically built by the MkDocs search plugin. * is automatically built by the MkDocs search plugin.
*/ */
export interface SearchIndex { export interface SearchIndexOptions {
config: SearchIndexConfig /* Search index configuration */ config: SearchIndexConfig /* Search index configuration */
docs: SearchIndexDocument[] /* Search index documents */ docs: SearchIndexDocument[] /* Search index documents */
options?: SearchIndexOptions /* Search index options */ pipeline?: SearchIndexPipeline /* Search index pipeline */
index?: object | string /* Prebuilt or serialized index */ index?: object | string /* Prebuilt or serialized index */
} }
@ -87,25 +87,11 @@ export interface SearchResult {
sections: SectionDocument[] /* Section documents */ sections: SectionDocument[] /* Section documents */
} }
/* ----------------------------------------------------------------------------
* Data
* ------------------------------------------------------------------------- */
/**
* Default options
*/
const defaultOptions: SearchIndexOptions = {
pipeline: {
trimmer: true,
stopwords: true
}
}
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
* Class * Class
* ------------------------------------------------------------------------- */ * ------------------------------------------------------------------------- */
export class Search { export class SearchIndex {
/** /**
* Search document mapping * Search document mapping
@ -130,17 +116,19 @@ export class Search {
/** /**
* Create a search index * Create a search index
* *
* @param index - Search index
* @param options - Options * @param options - Options
*/ */
public constructor({ config, docs, options, index }: SearchIndex) { public constructor({ config, docs, pipeline, index }: SearchIndexOptions) {
this.documents = setupSearchDocumentMap(docs) this.documents = setupSearchDocumentMap(docs)
this.highlight = setupSearchHighlighter(config) this.highlight = setupSearchHighlighter(config)
/* If no index was given, create it */ /* If no index was given, create it */
if (typeof index === "undefined") { if (typeof index === "undefined") {
this.index = lunr(function() { this.index = lunr(function() {
const { pipeline } = options || defaultOptions pipeline = pipeline || {
trimmer: true,
stopwords: true
}
/* Remove stemmer, as it cripples search experience */ /* Remove stemmer, as it cripples search experience */
this.pipeline.reset() this.pipeline.reset()
@ -194,8 +182,8 @@ export class Search {
.reduce((results, result) => { .reduce((results, result) => {
const document = this.documents.get(result.ref) const document = this.documents.get(result.ref)
if (typeof document !== "undefined") { if (typeof document !== "undefined") {
if ("article" in document) { if ("parent" in document) {
const ref = document.article.location const ref = document.parent.location
results.set(ref, [...results.get(ref) || [], result]) results.set(ref, [...results.get(ref) || [], result])
} else { } else {
const ref = document.location const ref = document.location
@ -228,7 +216,7 @@ export class Search {
} }
/** /**
* Serialize index * Serialize search index
* *
* @return String representation * @return String representation
*/ */

View File

@ -32,14 +32,14 @@ import { SearchIndexDocument } from "../_"
* A top-level article * A top-level article
*/ */
export interface ArticleDocument extends SearchIndexDocument { export interface ArticleDocument extends SearchIndexDocument {
section: boolean /* Whether the section was linked */ linked: boolean /* Whether the section was linked */
} }
/** /**
* A section of an article * A section of an article
*/ */
export interface SectionDocument extends SearchIndexDocument { export interface SectionDocument extends SearchIndexDocument {
article: ArticleDocument /* Parent article */ parent: ArticleDocument /* Parent article */
} }
/* ------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------- */
@ -85,22 +85,22 @@ export function setupSearchDocumentMap(
/* Handle section */ /* Handle section */
if (hash) { if (hash) {
const article = documents.get(path) as ArticleDocument const parent = documents.get(path) as ArticleDocument
/* Ignore first section, override article */ /* Ignore first section, override article */
if (!article.section) { if (!parent.linked) {
article.title = doc.title parent.title = doc.title
article.text = text parent.text = text
article.section = true parent.linked = true
/* Add subsequent section */ /* Add subsequent section */
} else { } else {
documents.set(location, { location, title, text, article }) documents.set(location, { location, title, text, parent })
} }
/* Add article */ /* Add article */
} else { } else {
documents.set(location, { location, title, text, section: false }) documents.set(location, { location, title, text, linked: false })
} }
} }
return documents return documents

View File

@ -21,4 +21,8 @@
*/ */
export * from "./_" export * from "./_"
export * from "./document" export {
ArticleDocument,
SearchDocument,
SectionDocument
} from "./document"

View File

@ -20,4 +20,4 @@
* IN THE SOFTWARE. * IN THE SOFTWARE.
*/ */
export * from "./_" export * from "./result"

View File

@ -44,7 +44,7 @@ const css = {
/** /**
* Render a search result * Render a search result
* *
* @param article - Search result * @param result - Search result
* *
* @return HTML element * @return HTML element
*/ */

View File

@ -0,0 +1,23 @@
/*
* Copyright (c) 2016-2019 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 "./_"

View File

@ -0,0 +1,52 @@
/*
* Copyright (c) 2016-2019 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, fromEvent, merge } from "rxjs"
import { mapTo, shareReplay, startWith } from "rxjs/operators"
/* ----------------------------------------------------------------------------
* Functions
* ------------------------------------------------------------------------- */
/**
* Watch element focus
*
* @param el - Element
*
* @return Element focus observable
*/
export function watchElementFocus(
el: HTMLElement
): Observable<boolean> {
const focus$ = fromEvent(el, "focus")
const blur$ = fromEvent(el, "blur")
/* Map events to boolean state */
return merge(
focus$.pipe(mapTo(true)),
blur$.pipe(mapTo(false))
)
.pipe(
startWith(el === document.activeElement),
shareReplay(1)
)
}

View File

@ -21,4 +21,5 @@
*/ */
export * from "./_" export * from "./_"
export * from "./focus"
export * from "./offset" export * from "./offset"

View File

@ -68,7 +68,7 @@ export function watchWorker<T extends WorkerMessage>(
worker: Worker, { send$ }: Options<T> worker: Worker, { send$ }: Options<T>
): Observable<T> { ): Observable<T> {
/* Observable for messages from web worker */ /* Intercept messages from web worker */
const recv$ = fromEvent(worker, "message") const recv$ = fromEvent(worker, "message")
.pipe( .pipe(
pluck<Event, T>("data"), pluck<Event, T>("data"),

View File

@ -27,6 +27,26 @@ import { pluck } from "rxjs/operators"
* Functions * Functions
* ------------------------------------------------------------------------- */ * ------------------------------------------------------------------------- */
/**
* Set toggle
*
* Simulating a click event seems to be the most cross-browser compatible way
* of changing the value while also emitting a `change` event. Before, Material
* used `CustomEvent` to programatically change the value of a toggle, but this
* is a much simpler and cleaner solution.
*
* @param el - Toggle element
* @param value - Toggle value
*/
export function setToggle(
el: HTMLInputElement, value: boolean
): void {
if (el.checked !== value)
el.click()
}
/* ------------------------------------------------------------------------- */
/** /**
* Watch toggle * Watch toggle
* *

View File

@ -20,7 +20,7 @@
* IN THE SOFTWARE. * IN THE SOFTWARE.
*/ */
import { SearchIndex, SearchResult } from "modules" import { SearchIndexOptions, SearchResult } from "modules"
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
* Types * Types
@ -43,7 +43,7 @@ export const enum SearchMessageType {
*/ */
export interface SearchSetupMessage { export interface SearchSetupMessage {
type: SearchMessageType.SETUP /* Message type */ type: SearchMessageType.SETUP /* Message type */
data: SearchIndex /* Message data */ data: SearchIndexOptions /* Message data */
} }
/** /**

View File

@ -20,7 +20,7 @@
* IN THE SOFTWARE. * IN THE SOFTWARE.
*/ */
import { Search } from "modules" import { SearchIndex } from "modules"
import { SearchMessage, SearchMessageType } from "../_" import { SearchMessage, SearchMessageType } from "../_"
@ -31,7 +31,7 @@ import { SearchMessage, SearchMessageType } from "../_"
/** /**
* Search index * Search index
*/ */
let index: Search let index: SearchIndex
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
* Functions * Functions
@ -49,7 +49,7 @@ export function handler(message: SearchMessage): SearchMessage {
/* Setup search index */ /* Setup search index */
case SearchMessageType.SETUP: case SearchMessageType.SETUP:
index = new Search(message.data) index = new SearchIndex(message.data)
return { return {
type: SearchMessageType.DUMP, type: SearchMessageType.DUMP,
data: index.toString() data: index.toString()

View File

@ -118,6 +118,11 @@ $md-toggle__drawer--checked:
margin-left: initial; margin-left: initial;
transform: translate(100%, 0); transform: translate(100%, 0);
} }
// Ensure smooth scrolling on iOS
.md-sidebar__scrollwrap {
-webkit-overflow-scrolling: touch;
}
} }
// [screen +]: Limit to grid // [screen +]: Limit to grid

View File

@ -82,7 +82,14 @@ function config(args: Configuration): Configuration {
}, },
/* Source maps */ /* Source maps */
devtool: "source-map" devtool: "source-map",
/* Filter false positives */
stats: {
warningsFilter: [
/export '.*' was not found in/
]
}
} }
} }
@ -96,7 +103,7 @@ function config(args: Configuration): Configuration {
* @param env - Webpack environment arguments * @param env - Webpack environment arguments
* @param args - Command-line arguments * @param args - Command-line arguments
* *
* @return Webpack configuration * @return Webpack configurations
*/ */
export default (_env: never, args: Configuration): Configuration[] => ([ export default (_env: never, args: Configuration): Configuration[] => ([
@ -117,7 +124,7 @@ export default (_env: never, args: Configuration): Configuration[] => ([
entry: "src/assets/javascripts/workers/search/main", entry: "src/assets/javascripts/workers/search/main",
output: { output: {
path: path.resolve(__dirname, "material/assets/javascripts"), path: path.resolve(__dirname, "material/assets/javascripts"),
filename: "search.js", filename: "worker/search.js",
libraryTarget: "var" libraryTarget: "var"
} }
}, },
@ -128,7 +135,7 @@ export default (_env: never, args: Configuration): Configuration[] => ([
entry: "src/assets/javascripts/workers/packer/main", entry: "src/assets/javascripts/workers/packer/main",
output: { output: {
path: path.resolve(__dirname, "material/assets/javascripts"), path: path.resolve(__dirname, "material/assets/javascripts"),
filename: "packer.js", filename: "worker/packer.js",
libraryTarget: "var" libraryTarget: "var"
} }
} }