1
0
mirror of https://github.com/squidfunk/mkdocs-material.git synced 2024-09-24 19:38:27 +02:00

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 Tiancheng "Timothy" Gu
* 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

File diff suppressed because one or more lines are too long

View File

@ -1002,8 +1002,7 @@ hr {
margin: 0 0.2rem;
overflow-y: auto;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
-webkit-overflow-scrolling: touch; }
backface-visibility: hidden; }
.md-sidebar__scrollwrap::-webkit-scrollbar {
width: 0.2rem;
height: 0.2rem; }
@ -2542,7 +2541,9 @@ hr {
margin-right: 100%;
margin-left: initial;
-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) {
.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 }}">
{% endif %}
<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 %}
{% block htmltitle %}
{% if page and page.meta and page.meta.title %}
@ -236,7 +236,7 @@
{%- endfor -%}
{{ translations | tojson }}
</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"] %}
<script src="{{ path | url }}"></script>
{% endfor %}

View File

@ -1,6 +1,6 @@
{
"name": "mkdocs-material",
"version": "4.6.0",
"version": "5.0.0-preview",
"description": "A Material Design theme for MkDocs",
"keywords": [
"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 */
height: number /* Header visible height */
}
@ -46,11 +46,11 @@ export interface Header {
*
* @param el - Header element
*
* @return Header observable
* @return Header state observable
*/
export function watchHeader(
el: HTMLElement
): Observable<Header> {
): Observable<HeaderState> {
return defer(() => {
const sticky = getComputedStyle(el)
.getPropertyValue("position") === "fixed"

View File

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

View File

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

View File

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

View File

@ -31,16 +31,16 @@ import {
import { Agent } from "utilities"
import { Header } from "../../header"
import { HeaderState } from "../../header"
/* ----------------------------------------------------------------------------
* Types
* ------------------------------------------------------------------------- */
/**
* Main area
* Main area state
*/
export interface Main {
export interface MainState {
offset: number /* Main area top offset */
height: number /* Main area visible height */
active: boolean /* Scrolled past top offset */
@ -54,7 +54,7 @@ export interface Main {
* Options
*/
interface Options {
header$: Observable<Header> /* Header observable */
header$: Observable<HeaderState> /* Header state observable */
}
/* ----------------------------------------------------------------------------
@ -62,64 +62,78 @@ interface Options {
* ------------------------------------------------------------------------- */
/**
* Setup main area from source observable
* Watch main area
*
* 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
* height of the header element. The height of the main area is corrected by
* the height of the header (if fixed) and footer element.
* the main area which depends on the viewport height and vertical offset, as
* well as the height of the header element, if the header is fixed.
*
* @param el - Main area element
* @param agent - Agent
* @param options - Options
*
* @return Main area state observable
*/
export function watchMain(
el: HTMLElement, { viewport }: Agent, { header$ }: Options
): Observable<MainState> {
/* Compute necessary adjustment for header */
const adjust$ = header$
.pipe(
pluck("height")
)
/* Compute the main area's visible height */
const height$ = combineLatest([
viewport.offset$,
viewport.size$,
adjust$
])
.pipe(
map(([{ y }, { height }, adjust]) => {
const top = el.offsetTop
const bottom = el.offsetHeight + top
return height
- Math.max(0, top - y, adjust)
- Math.max(0, height + y - bottom)
}),
distinctUntilChanged()
)
/* Compute whether the viewport offset is past the main area's top */
const active$ = combineLatest([viewport.offset$, adjust$])
.pipe(
map(([{ y }, adjust]) => y >= el.offsetTop - adjust),
distinctUntilChanged()
)
/* Combine into a single hot observable */
return combineLatest([height$, adjust$, active$])
.pipe(
map(([height, adjust, active]) => ({
offset: el.offsetTop - adjust,
height,
active
}))
)
}
/* ------------------------------------------------------------------------- */
/**
* Mount main area from source observable
*
* @param agent - Agent
* @param options - Options
*
* @return Operator function
*/
export function setupMain(
{ viewport }: Agent, { header$ }: Options
): OperatorFunction<HTMLElement, Main> {
export function mountMain(
agent: Agent, options: Options
): OperatorFunction<HTMLElement, MainState> {
return pipe(
switchMap(el => {
/* Compute necessary adjustment for header */
const adjust$ = header$
.pipe(
pluck("height")
)
/* Compute the main area's visible height */
const height$ = combineLatest([
viewport.offset$,
viewport.size$,
adjust$
])
.pipe(
map(([{ y }, { height }, adjust]) => {
const top = el.offsetTop
const bottom = el.offsetHeight + top
return height
- Math.max(0, top - y, adjust)
- Math.max(0, height + y - bottom)
}),
distinctUntilChanged()
)
/* Compute whether the viewport offset is past the main area's top */
const active$ = combineLatest([viewport.offset$, adjust$])
.pipe(
map(([{ y }, adjust]) => y >= el.offsetTop - adjust),
distinctUntilChanged()
)
/* Combine into a single hot observable */
return combineLatest([height$, adjust$, active$])
.pipe(
map(([height, adjust, active]) => ({
offset: el.offsetTop - adjust,
height,
active
}))
)
}),
switchMap(el => watchMain(el, agent, options)),
shareReplay(1)
)
}

View File

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

View File

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

View File

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

View File

@ -19,3 +19,71 @@
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* 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(
el: HTMLElement
): Observable<boolean> {
): Observable<void> {
return fromEvent(el, "click")
.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 agent - Agent
* @param options - Options
*
* @return Search result state observable
*/
export function watchSearchResult(
el: HTMLElement, agent: Agent, { result$, query$ }: Options
): Observable<SearchResult[]> {
const container = el.parentElement!
/* Compute whether there are more search results elements */
const render$ = watchElementOffset(container, agent)
.pipe(
map(({ y }) => y >= container.scrollHeight - container.offsetHeight - 16),
distinctUntilChanged(),
filter(identity)
)
// combine into search result observable...
/* Paint search results */
return result$
.pipe(
tap(x => { console.log("watchSearchResult", x) }),
paintSearchResultMeta(el, { query$ }),
paintSearchResultList(el, { render$ })
)
}
/* ------------------------------------------------------------------------- */
/**
* Mount search result from source observable
*
* @param agent - Agent
* @param options - Options
*
* @return Operator function
*/
export function setupSearchResult(
agent: Agent, { result$, query$ }: Options
export function mountSearchResult(
agent: Agent, options: Options
): OperatorFunction<HTMLElement, SearchResult[]> {
return pipe(
switchMap(el => {
const parent = el.parentElement!
/* Compute whether more elements need to be rendered */
const render$ = watchElementOffset(parent, agent)
.pipe(
map(({ y }) => y >= parent.scrollHeight - parent.offsetHeight - 16),
distinctUntilChanged(),
filter(identity)
)
/* Paint search results */
return result$
.pipe(
paintSearchResultMeta(el, { query$ }),
paintSearchResultList(el, { render$ })
)
})
switchMap(el => watchSearchResult(el, agent, options)),
shareReplay(1)
)
}

View File

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

View File

@ -26,16 +26,16 @@ import { map, shareReplay } from "rxjs/operators"
import { switchMapIf } from "extensions"
import { Agent, paintHidden } from "utilities"
import { Header, watchHeaderOffsetToTopOf } from "../header"
import { HeaderState, watchViewportOffsetFromTopOf } from "../header"
/* ----------------------------------------------------------------------------
* Types
* ------------------------------------------------------------------------- */
/**
* Tabs
* Tabs state
*/
export interface Tabs {
export interface TabsState {
hidden: boolean /* Whether the tabs are hidden */
}
@ -47,7 +47,7 @@ export interface Tabs {
* Options
*/
interface Options {
header$: Observable<Header> /* Header observable */
header$: Observable<HeaderState> /* Header state observable */
}
/* ----------------------------------------------------------------------------
@ -55,32 +55,50 @@ 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 options - Options
*
* @return Tabs state
*/
export function watchTabs(
el: HTMLElement, agent: Agent, { header$ }: Options
): Observable<TabsState> {
/* Watch and paint visibility */
const hidden$ = watchViewportOffsetFromTopOf(el, agent, { header$ })
.pipe(
paintHidden(el, 8)
)
/* Combine into a single hot observable */
return hidden$
.pipe(
map(hidden => ({ hidden }))
)
}
/* ------------------------------------------------------------------------- */
/**
* Mount tabs from source observable
*
* @param agent - Agent
* @param options - Options
*
* @return Operator function
*/
export function setupTabs(
agent: Agent, { header$ }: Options
): OperatorFunction<HTMLElement, Tabs> {
export function mountTabs(
agent: Agent, options: Options
): OperatorFunction<HTMLElement, TabsState> {
const { media } = agent
return pipe(
switchMapIf(media.screen$, el => {
/* Watch and paint visibility */
const hidden$ = watchHeaderOffsetToTopOf(el, agent, { header$ })
.pipe(
paintHidden(el, 8)
)
/* Combine into a single hot observable */
return hidden$
.pipe(
map(hidden => ({ hidden }))
)
}),
switchMapIf(media.screen$, el => watchTabs(el, agent, options)),
shareReplay(1)
)
}

View File

@ -26,10 +26,10 @@ import { map, shareReplay } from "rxjs/operators"
import { switchMapIf } from "extensions"
import { Agent, getElements } from "utilities"
import { Header } from "../../header"
import { HeaderState } from "../../header"
import {
Main,
Sidebar,
MainState,
SidebarState,
paintSidebar,
watchSidebar
} from "../../main"
@ -44,10 +44,10 @@ import {
* ------------------------------------------------------------------------- */
/**
* Table of contents
* Table of contents state
*/
export interface TableOfContents {
sidebar: Sidebar /* Sidebar */
export interface TableOfContentsState {
sidebar: SidebarState /* Sidebar state */
anchors: AnchorList /* Anchor list */
}
@ -59,8 +59,8 @@ export interface TableOfContents {
* Options
*/
interface Options {
header$: Observable<Header> /* Header observable */
main$: Observable<Main> /* Main observable */
header$: Observable<HeaderState> /* Header state observable */
main$: Observable<MainState> /* Main area state observable */
}
/* ----------------------------------------------------------------------------
@ -68,39 +68,54 @@ interface Options {
* ------------------------------------------------------------------------- */
/**
* Setup table of contents from source observable
* Watch table of contents
*
* @param el - Table of contents element
* @param agent - Agent
* @param options - Options
*
* @return Table of contents state observable
*/
export function watchTableOfContents(
el: HTMLElement, agent: Agent, { header$, main$ }: Options
): Observable<TableOfContentsState> {
/* Watch and paint sidebar */
const sidebar$ = watchSidebar(el, agent, { main$ })
.pipe(
paintSidebar(el)
)
/* Watch and paint anchor list (scroll spy) */
const els = getElements<HTMLAnchorElement>(".md-nav__link", el)
const anchors$ = watchAnchorList(els, agent, { header$ })
.pipe(
paintAnchorList(els)
)
/* Combine into a single hot observable */
return combineLatest([sidebar$, anchors$])
.pipe(
map(([sidebar, anchors]) => ({ sidebar, anchors }))
)
}
/* ------------------------------------------------------------------------- */
/**
* Mount table of contents from source observable
*
* @param agent - Agent
* @param options - Options
*
* @return Operator function
*/
export function setupTableOfContents(
agent: Agent, { header$, main$ }: Options
): OperatorFunction<HTMLElement, TableOfContents> {
export function mountTableOfContents(
agent: Agent, options: Options
): OperatorFunction<HTMLElement, TableOfContentsState> {
const { media } = agent
return pipe(
switchMapIf(media.tablet$, el => {
/* Watch and paint sidebar */
const sidebar$ = watchSidebar(el, agent, { main$ })
.pipe(
paintSidebar(el)
)
/* Watch and paint anchor list (scroll spy) */
const els = getElements<HTMLAnchorElement>(".md-nav__link", el)
const anchors$ = watchAnchorList(els, agent, { header$ })
.pipe(
paintAnchorList(els)
)
/* Combine into a single hot observable */
return combineLatest([sidebar$, anchors$])
.pipe(
map(([sidebar, anchors]) => ({ sidebar, anchors }))
)
}),
switchMapIf(media.tablet$, el => watchTableOfContents(el, agent, options)),
shareReplay(1)
)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -44,7 +44,7 @@ const css = {
/**
* Render a search result
*
* @param article - Search result
* @param result - Search result
*
* @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 "./focus"
export * from "./offset"

View File

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

View File

@ -27,6 +27,26 @@ import { pluck } from "rxjs/operators"
* 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
*

View File

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

View File

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

View File

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

View File

@ -82,7 +82,14 @@ function config(args: Configuration): Configuration {
},
/* 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 args - Command-line arguments
*
* @return Webpack configuration
* @return Webpack configurations
*/
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",
output: {
path: path.resolve(__dirname, "material/assets/javascripts"),
filename: "search.js",
filename: "worker/search.js",
libraryTarget: "var"
}
},
@ -128,7 +135,7 @@ export default (_env: never, args: Configuration): Configuration[] => ([
entry: "src/assets/javascripts/workers/packer/main",
output: {
path: path.resolve(__dirname, "material/assets/javascripts"),
filename: "packer.js",
filename: "worker/packer.js",
libraryTarget: "var"
}
}