Merge pull request #2273 from squidfunk/refactor/architecture

Material for MkDocs 7.x
This commit is contained in:
Martin Donath
2021-02-12 17:24:16 +01:00
committed by GitHub
192 changed files with 20999 additions and 4100 deletions

54
.eslintignore Normal file
View File

@@ -0,0 +1,54 @@
# Copyright (c) 2016-2020 Martin Donath <martin.donath@squidfunk.com>
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
# -----------------------------------------------------------------------------
# Node, TypeScript, Python
# -----------------------------------------------------------------------------
# Dependencies
node_modules
__pycache__
venv
# Build files
build
MANIFEST
manifest.json
site
# Configuration
webpack.config.ts
# Distribution files
dist
mkdocs_material.egg-info
# Caches and logs
*.cpuprofile
*.log
*.tsbuildinfo
.eslintcache
# -----------------------------------------------------------------------------
# General
# -----------------------------------------------------------------------------
# Temporary files
tmp

351
.eslintrc Normal file
View File

@@ -0,0 +1,351 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "tsconfig.json"
},
"plugins": [
"@typescript-eslint",
"eslint-plugin-eslint-comments",
"eslint-plugin-import",
"eslint-plugin-jsdoc",
"eslint-plugin-no-null"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
"settings": {
"import/extensions": [
".ts",
".tsx",
".js",
".jsx",
".json"
],
"import/parsers": {
"@typescript-eslint/parser": [
".ts",
".tsx"
]
}
},
"rules": {
"array-bracket-spacing": "warn",
"arrow-parens": [
"warn",
"as-needed"
],
"block-spacing": "warn",
"brace-style": [
"warn",
"1tbs",
{
"allowSingleLine": true
}
],
"comma-dangle": [
"error",
"never"
],
"comma-spacing": "warn",
"comma-style": "error",
"computed-property-spacing": "warn",
"curly": "off",
"eqeqeq": [
"error",
"smart"
],
"func-call-spacing": "warn",
"keyword-spacing": "warn",
"lines-around-comment": [
"error",
{
"allowBlockStart": true,
"allowBlockEnd": true,
"beforeBlockComment": true,
"ignorePattern": "@ts-ignore"
}
],
"lines-between-class-members": "warn",
"max-classes-per-file": "error",
"new-parens": "error",
"no-caller": "error",
"no-case-declarations": "off",
"no-console": "error",
"no-duplicate-imports": "error",
"no-eval": "error",
"no-extra-bind": "error",
"no-multiple-empty-lines": [
"error",
{
"max": 1
}
],
"no-new-func": "error",
"no-new-wrappers": "error",
"no-restricted-globals": [
"error",
{
"name": "fdescribe",
"message": "Did you mean 'describe'?"
},
{
"name": "xdescribe",
"message": "Did you mean 'describe'?"
},
{
"name": "fit",
"message": "Did you mean 'it'?"
},
{
"name": "xit",
"message": "Did you mean 'xit'?"
}
],
"no-return-await": "error",
"no-sequences": "error",
"no-shadow": "off",
"no-tabs": "error",
"no-template-curly-in-string": "error",
"no-throw-literal": "off",
"no-trailing-spaces": "warn",
"no-undef-init": "error",
"no-underscore-dangle": "error",
"no-var": "error",
"no-whitespace-before-property": "warn",
"object-shorthand": "error",
"one-var": [
"error",
"never"
],
"prefer-exponentiation-operator": "error",
"prefer-object-spread": "error",
"prefer-template": "error",
"quote-props": [
"error",
"consistent-as-needed"
],
"quotes": [
"error",
"double",
{
"avoidEscape": true
}
],
"radix": "error",
"semi": "off",
"sort-imports": [
"error",
{
"ignoreDeclarationSort": true
}
],
"space-before-blocks": "warn",
"space-before-function-paren": [
"warn",
{
"anonymous": "always",
"named": "never",
"asyncArrow": "always"
}
],
"space-in-parens": "warn",
"space-infix-ops": "warn",
"space-unary-ops": "warn",
"spaced-comment": "warn",
"switch-colon-spacing": "warn",
"template-tag-spacing": "warn",
/* Plugin: @typescript-eslint */
"@typescript-eslint/array-type": "off",
"@typescript-eslint/await-thenable": "error",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/ban-types": "off",
"@typescript-eslint/consistent-type-assertions": "error",
"@typescript-eslint/dot-notation": "error",
"@typescript-eslint/explicit-member-accessibility": "error",
"@typescript-eslint/indent": [
"warn",
2,
{
"FunctionDeclaration": {
"parameters": 1,
"body": 1
},
"FunctionExpression": {
"parameters": 1,
"body": 1
},
"MemberExpression": "off",
"ObjectExpression": 1,
"SwitchCase": 1,
"ignoreComments": true,
"ignoredNodes": [
"ArrowFunctionExpression > *",
"CallExpression > ObjectExpression",
"ConditionalExpression > ConditionalExpression",
"TSTypeReference > *"
],
"offsetTernaryExpressions": true
}
],
"@typescript-eslint/member-delimiter-style": [
"error",
{
"multiline": {
"delimiter": "none"
},
"singleline": {
"delimiter": "comma",
"requireLast": false
}
}
],
"@typescript-eslint/naming-convention": [
"error",
{
"selector": "enumMember",
"format": [
"UPPER_CASE"
]
}
],
"@typescript-eslint/no-empty-interface": "off",
"@typescript-eslint/no-extraneous-class": "error",
"@typescript-eslint/no-misused-promises": "error",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-parameter-properties": "off",
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-shadow": [
"error",
{
"hoist": "never"
}
],
"@typescript-eslint/no-throw-literal": "error",
"@typescript-eslint/no-unnecessary-type-assertion": "error",
"@typescript-eslint/no-unused-expressions": "error",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/prefer-for-of": "off",
"@typescript-eslint/prefer-function-type": "error",
"@typescript-eslint/semi": [
"error",
"never"
],
"@typescript-eslint/triple-slash-reference": "off",
"@typescript-eslint/type-annotation-spacing": "error",
"@typescript-eslint/unbound-method": "error",
"@typescript-eslint/unified-signatures": "error",
/* Plugin: eslint-plugin-eslint-comments */
"eslint-comments/no-unused-disable": "error",
"eslint-comments/no-unused-enable": "error",
"eslint-comments/no-use": [
"warn",
{
"allow": [
"eslint-disable-line",
"eslint-disable-next-line"
]
}
],
/* Plugin: eslint-plugin-import */
"import/first": "error",
"import/newline-after-import": "error",
"import/no-default-export": "error",
"import/no-duplicates": "error",
"import/no-mutable-exports": "error",
"import/no-self-import": "error",
"import/order": [
"error",
{
"alphabetize": {
"order": "asc"
},
"newlines-between": "always",
"groups": [
[
"builtin",
"external"
],
[
"internal"
],
[
"index"
],
[
"parent"
],
[
"sibling"
]
],
"pathGroups": [
{
"pattern": "~/**",
"group": "index",
"position": "before"
},
{
"pattern": "_/**",
"group": "index",
"position": "after"
}
],
"pathGroupsExcludedImportTypes": [
"builtin"
]
}
],
/* Plugin: eslint-plugin-jsdoc */
"jsdoc/check-alignment": "warn",
"jsdoc/check-param-names": [
"warn",
{
"checkDestructured": false
}
],
"jsdoc/check-syntax": "warn",
"jsdoc/check-tag-names": [
"warn",
{
"definedTags": [
"internal"
]
}
],
"jsdoc/empty-tags": "warn",
"jsdoc/newline-after-description": "warn",
"jsdoc/no-bad-blocks": "warn",
"jsdoc/no-defaults": "warn",
"jsdoc/no-types": "warn",
"jsdoc/require-hyphen-before-param-description": "warn",
"jsdoc/require-jsdoc": "warn",
"jsdoc/require-param-description": "warn",
"jsdoc/require-param-name": "warn",
"jsdoc/require-param": [
"warn",
{
"checkDestructured": false,
"checkDestructuredRoots": true
}
],
"jsdoc/require-returns-check": "warn",
"jsdoc/require-returns-description": "warn",
"jsdoc/require-returns": [
"warn",
{
"checkGetters": false,
"forceReturnsWithAsync": true
}
],
/* Plugin: eslint-plugin-no-null */
"no-null/no-null": "error"
}
}

49
.gitignore vendored
View File

@@ -18,26 +18,47 @@
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
# macOS internals
.DS_Store
# -----------------------------------------------------------------------------
# Node, TypeScript, Python
# -----------------------------------------------------------------------------
# NPM-related
/node_modules
/npm-debug.log*
# Python-related
# Dependencies
node_modules
__pycache__
venv
# Files generated by build
/build
/MANIFEST
/manifest.json
/site
# Build files
build
MANIFEST
manifest.json
site
# Distribution files
/dist
/mkdocs_material.egg-info
dist
mkdocs_material.egg-info
# Caches and logs
*.cpuprofile
*.log
*.tsbuildinfo
.eslintcache
# -----------------------------------------------------------------------------
# General
# -----------------------------------------------------------------------------
# Never ignore .gitkeep files
!**/.gitkeep
# Husky hooks
.husky/.gitignore
.husky/_
# macOS internals
.DS_Store
# Temporary files
tmp
# IDEs
.vscode

View File

@@ -1,41 +1,131 @@
{
"extends": [
"stylelint-config-recommended",
"stylelint-config-standard",
"stylelint-config-rational-order"
],
"plugins": [
"stylelint-order",
"stylelint-scss"
],
"rules": {
"at-rule-empty-line-before": null,
"at-rule-empty-line-before": [
"always",
{
"except": [
"blockless-after-same-name-blockless",
"first-nested"
],
"ignore": [
"after-comment"
],
"ignoreAtRules": [
"if",
"each",
"else",
"elseif",
"for",
"import",
"return"
]
}
],
"at-rule-no-unknown": null,
"at-rule-no-vendor-prefix": true,
"block-opening-brace-space-before": null,
"block-closing-brace-newline-after": ["always", {
"ignoreAtRules": [
"if",
"else",
"elseif"
]
}],
"color-hex-case": "upper",
"block-closing-brace-newline-after": [
"always",
{
"ignoreAtRules": [
"if",
"else",
"elseif"
]
}
],
"color-hex-length": "long",
"color-named": "never",
"comment-empty-line-before": ["always", {
"ignore": ["stylelint-commands"]
}],
"comment-empty-line-before": [
"always",
{
"ignore": [
"stylelint-commands"
]
}
],
"custom-property-empty-line-before": null,
"declaration-colon-space-after": null,
"declaration-no-important": true,
"declaration-block-single-line-max-declarations": 0,
"function-url-no-scheme-relative": true,
"function-url-quotes": "always",
"font-family-name-quotes": "always-where-recommended",
"font-weight-notation": "numeric",
"function-url-quotes": "always",
"linebreaks": "unix",
"media-feature-name-no-unknown": null,
"no-descending-specificity": null,
"no-empty-first-line": true,
"no-unknown-animations": true,
"property-no-vendor-prefix": [true, {"ignoreProperties": ["line-clamp", "box-orient"]}],
"selector-class-pattern": "^[a-z0-9]+(-[a-z0-9]+)*(__[a-z]+)?(--[a-z]+)?$",
"property-no-unknown": null,
"property-no-vendor-prefix": [
true,
{
"ignoreProperties": [
"line-clamp",
"box-orient"
]
}
],
"selector-combinator-space-before": null,
"selector-descendant-combinator-no-non-space": null,
"selector-max-empty-lines": 0,
"selector-max-id": 0,
"selector-no-qualifying-type": null,
"selector-pseudo-class-no-unknown": null,
"selector-pseudo-element-no-unknown": null,
"string-quotes": "double",
"unit-allowed-list": ["px", "em", "deg", "ms", "%", "mm", "vh", "vw", "dppx"],
"value-keyword-case": "lower",
"value-no-vendor-prefix": [true, {"ignoreValues": ["box"]}]
"unicode-bom": "never",
"unit-allowed-list": [
"%",
"dppx",
"deg",
"em",
"mm",
"ms",
"px",
"vh",
"vw"
],
"value-list-comma-newline-after": null,
"scss/at-each-key-value-single-line": true,
"scss/at-else-closing-brace-newline-after": "always-last-in-chain",
"scss/at-function-parentheses-space-before": "never",
"scss/at-function-pattern": "^[a-z][a-z0-9]*(-[a-z0-9]+)*$",
"scss/at-if-closing-brace-newline-after": "always-last-in-chain",
"scss/at-if-no-null": true,
"scss/at-import-no-partial-leading-underscore": true,
"scss/at-import-partial-extension": "never",
"scss/at-mixin-argumentless-call-parentheses": "always",
"scss/at-mixin-parentheses-space-before": "never",
"scss/at-mixin-pattern": "^[a-z][a-z0-9]*(-[a-z0-9]+)*$",
"scss/at-rule-conditional-no-parentheses": true,
"scss/comment-no-loud": true,
"scss/declaration-nested-properties": "never",
"scss/dimension-no-non-numeric-values": true,
"scss/dollar-variable-colon-newline-after": "always-multi-line",
"scss/dollar-variable-colon-space-after": "always-single-line",
"scss/dollar-variable-colon-space-before": "never",
"scss/dollar-variable-first-in-block": [
true,
{
"ignore": ["comments"],
"except": ["function"]
}
],
"scss/dollar-variable-no-missing-interpolation": true,
"scss/dollar-variable-pattern": "^[a-z][a-z0-9]*(-[a-z0-9]+)*$",
"scss/double-slash-comment-whitespace-inside": "always",
"scss/no-duplicate-mixins": true,
"scss/operator-no-unspaced": true,
"scss/partial-no-import": true,
"scss/percent-placeholder-pattern": "^[a-z][a-z0-9]*(-[a-z0-9]+)*$",
"scss/selector-no-redundant-nesting-selector": true
}
}

View File

@@ -294,7 +294,7 @@ following transformations, which can be customized by [extending the theme][20]:
*
* @param query - Query value
*
* @return Transformed query value
* @returns Transformed query value
*/
export function defaultTransform(query: string): string {
return query

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2020 GitHub Inc.
Copyright (c) 2021 GitHub Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M11 10h3V9h-3V8l3.17-1.03-.34-.94L11 7V6c0-.55-.45-1-1-1V4c0-.48-.36-.88-.83-.97L10.2 2H12V1H9.8l-2 2h-.59L5.2 1H3v1h1.8l1.03 1.03C5.36 3.12 5 3.51 5 4v1c-.55 0-1 .45-1 1v1l-2.83-.97-.34.94L4 8v1H1v1h3v1L.83 12.03l.34.94L4 12v1c0 .55.45 1 1 1h1l1-1V6h1v7l1 1h1c.55 0 1-.45 1-1v-1l2.83.97.34-.94L11 11v-1zM9 5H6V4h3v1z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M4.72.22a.75.75 0 011.06 0l1 .999a3.492 3.492 0 012.441 0l.999-1a.75.75 0 111.06 1.061l-.775.776c.616.63.995 1.493.995 2.444v.327c0 .1-.009.197-.025.292.408.14.764.392 1.029.722l1.968-.787a.75.75 0 01.556 1.392L13 7.258V9h2.25a.75.75 0 010 1.5H13v.5c0 .409-.049.806-.141 1.186l2.17.868a.75.75 0 01-.557 1.392l-2.184-.873A4.997 4.997 0 018 16a4.997 4.997 0 01-4.288-2.427l-2.183.873a.75.75 0 01-.558-1.392l2.17-.868A5.013 5.013 0 013 11v-.5H.75a.75.75 0 010-1.5H3V7.258L.971 6.446a.75.75 0 01.558-1.392l1.967.787c.265-.33.62-.583 1.03-.722a1.684 1.684 0 01-.026-.292V4.5c0-.951.38-1.814.995-2.444L4.72 1.28a.75.75 0 010-1.06zM6.173 5h3.654A.173.173 0 0010 4.827V4.5a2 2 0 10-4 0v.327c0 .096.077.173.173.173zM5.25 6.5a.75.75 0 00-.75.75V11a3.5 3.5 0 107 0V7.25a.75.75 0 00-.75-.75h-5.5z"/></svg>

Before

Width:  |  Height:  |  Size: 415 B

After

Width:  |  Height:  |  Size: 882 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill-rule="evenodd" d="M7.72.22a.75.75 0 011.06 0l1.204 1.203A4.983 4.983 0 0112 1c.717 0 1.4.151 2.016.423L15.22.22a.75.75 0 011.06 1.06l-.971.972A4.988 4.988 0 0117 6v1.104a2.755 2.755 0 011.917 1.974l1.998-.999a.75.75 0 01.67 1.342L19 10.714V13.5l3.25.003a.75.75 0 110 1.5L19 15.001V16a7.02 7.02 0 01-.204 1.686.833.833 0 01.04.018l2.75 1.375a.75.75 0 11-.671 1.342l-2.638-1.319A7 7 0 0112 23a7 7 0 01-6.197-3.742l-2.758 1.181a.75.75 0 11-.59-1.378l2.795-1.199A7.007 7.007 0 015 16v-.996H1.75a.75.75 0 010-1.5H5v-2.79L2.415 9.42a.75.75 0 01.67-1.342l1.998.999A2.755 2.755 0 017 7.104V6a4.99 4.99 0 011.69-3.748l-.97-.972a.75.75 0 010-1.06zM8.5 7h7V6a3.5 3.5 0 10-7 0v1zm-2 3.266V9.75c0-.69.56-1.25 1.25-1.25h8.5c.69 0 1.25.56 1.25 1.25V16a5.5 5.5 0 01-11 0v-5.734z"/></svg>

After

Width:  |  Height:  |  Size: 842 B

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.75 5V.75a.75.75 0 00-1.5 0V5H5.104a.25.25 0 00-.177.427l2.896 2.896a.25.25 0 00.354 0l2.896-2.896A.25.25 0 0010.896 5H8.75zM1.5 2.75a.25.25 0 01.25-.25h3a.75.75 0 000-1.5h-3A1.75 1.75 0 000 2.75v7.5C0 11.216.784 12 1.75 12h3.727c-.1 1.041-.52 1.872-1.292 2.757A.75.75 0 004.75 16h6.5a.75.75 0 00.565-1.243c-.772-.885-1.193-1.716-1.292-2.757h3.727A1.75 1.75 0 0016 10.25v-7.5A1.75 1.75 0 0014.25 1h-3a.75.75 0 000 1.5h3a.25.25 0 01.25.25v7.5a.25.25 0 01-.25.25H1.75a.25.25 0 01-.25-.25v-7.5zM9.018 12H6.982a5.72 5.72 0 01-.765 2.5h3.566a5.72 5.72 0 01-.765-2.5z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M4.927 5.427l2.896 2.896a.25.25 0 00.354 0l2.896-2.896A.25.25 0 0010.896 5H8.75V.75a.75.75 0 10-1.5 0V5H5.104a.25.25 0 00-.177.427z"/><path d="M1.573 2.573a.25.25 0 00-.073.177v7.5a.25.25 0 00.25.25h12.5a.25.25 0 00.25-.25v-7.5a.25.25 0 00-.25-.25h-3a.75.75 0 110-1.5h3A1.75 1.75 0 0116 2.75v7.5A1.75 1.75 0 0114.25 12h-3.727c.099 1.041.52 1.872 1.292 2.757A.75.75 0 0111.25 16h-6.5a.75.75 0 01-.565-1.243c.772-.885 1.192-1.716 1.292-2.757H1.75A1.75 1.75 0 010 10.25v-7.5A1.75 1.75 0 011.75 1h3a.75.75 0 010 1.5h-3a.25.25 0 00-.177.073zM6.982 12a5.72 5.72 0 01-.765 2.5h3.566a5.72 5.72 0 01-.765-2.5H6.982z"/></svg>

Before

Width:  |  Height:  |  Size: 661 B

After

Width:  |  Height:  |  Size: 684 B

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M10.896 2H8.75V.75a.75.75 0 00-1.5 0V2H5.104a.25.25 0 00-.177.427l2.896 2.896a.25.25 0 00.354 0l2.896-2.896A.25.25 0 0010.896 2zM8.75 15.25a.75.75 0 01-1.5 0V14H5.104a.25.25 0 01-.177-.427l2.896-2.896a.25.25 0 01.354 0l2.896 2.896a.25.25 0 01-.177.427H8.75v1.25zm-6.5-6.5a.75.75 0 000-1.5h-.5a.75.75 0 000 1.5h.5zM6 8a.75.75 0 01-.75.75h-.5a.75.75 0 010-1.5h.5A.75.75 0 016 8zm2.25.75a.75.75 0 000-1.5h-.5a.75.75 0 000 1.5h.5zM12 8a.75.75 0 01-.75.75h-.5a.75.75 0 010-1.5h.5A.75.75 0 0112 8zm2.25.75a.75.75 0 000-1.5h-.5a.75.75 0 000 1.5h.5z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M10.896 2H8.75V.75a.75.75 0 00-1.5 0V2H5.104a.25.25 0 00-.177.427l2.896 2.896a.25.25 0 00.354 0l2.896-2.896A.25.25 0 0010.896 2zM8.75 15.25a.75.75 0 01-1.5 0V14H5.104a.25.25 0 01-.177-.427l2.896-2.896a.25.25 0 01.354 0l2.896 2.896a.25.25 0 01-.177.427H8.75v1.25zm-6.5-6.5a.75.75 0 000-1.5h-.5a.75.75 0 000 1.5h.5zM6 8a.75.75 0 01-.75.75h-.5a.75.75 0 010-1.5h.5A.75.75 0 016 8zm2.25.75a.75.75 0 000-1.5h-.5a.75.75 0 000 1.5h.5zM12 8a.75.75 0 01-.75.75h-.5a.75.75 0 010-1.5h.5A.75.75 0 0112 8zm2.25.75a.75.75 0 000-1.5h-.5a.75.75 0 000 1.5h.5z"/></svg>

Before

Width:  |  Height:  |  Size: 639 B

After

Width:  |  Height:  |  Size: 619 B

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.177 14.323l2.896-2.896a.25.25 0 00-.177-.427H8.75V7.764a.75.75 0 10-1.5 0V11H5.104a.25.25 0 00-.177.427l2.896 2.896a.25.25 0 00.354 0zM2.25 5a.75.75 0 000-1.5h-.5a.75.75 0 000 1.5h.5zM6 4.25a.75.75 0 01-.75.75h-.5a.75.75 0 010-1.5h.5a.75.75 0 01.75.75zM8.25 5a.75.75 0 000-1.5h-.5a.75.75 0 000 1.5h.5zM12 4.25a.75.75 0 01-.75.75h-.5a.75.75 0 010-1.5h.5a.75.75 0 01.75.75zm2.25.75a.75.75 0 000-1.5h-.5a.75.75 0 000 1.5h.5z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M8.177 14.323l2.896-2.896a.25.25 0 00-.177-.427H8.75V7.764a.75.75 0 10-1.5 0V11H5.104a.25.25 0 00-.177.427l2.896 2.896a.25.25 0 00.354 0zM2.25 5a.75.75 0 000-1.5h-.5a.75.75 0 000 1.5h.5zM6 4.25a.75.75 0 01-.75.75h-.5a.75.75 0 010-1.5h.5a.75.75 0 01.75.75zM8.25 5a.75.75 0 000-1.5h-.5a.75.75 0 000 1.5h.5zM12 4.25a.75.75 0 01-.75.75h-.5a.75.75 0 010-1.5h.5a.75.75 0 01.75.75zm2.25.75a.75.75 0 000-1.5h-.5a.75.75 0 000 1.5h.5z"/></svg>

Before

Width:  |  Height:  |  Size: 522 B

After

Width:  |  Height:  |  Size: 502 B

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M7.823 1.677L4.927 4.573A.25.25 0 005.104 5H7.25v3.236a.75.75 0 101.5 0V5h2.146a.25.25 0 00.177-.427L8.177 1.677a.25.25 0 00-.354 0zM13.75 11a.75.75 0 000 1.5h.5a.75.75 0 000-1.5h-.5zm-3.75.75a.75.75 0 01.75-.75h.5a.75.75 0 010 1.5h-.5a.75.75 0 01-.75-.75zM7.75 11a.75.75 0 000 1.5h.5a.75.75 0 000-1.5h-.5zM4 11.75a.75.75 0 01.75-.75h.5a.75.75 0 010 1.5h-.5a.75.75 0 01-.75-.75zM1.75 11a.75.75 0 000 1.5h.5a.75.75 0 000-1.5h-.5z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M7.823 1.677L4.927 4.573A.25.25 0 005.104 5H7.25v3.236a.75.75 0 101.5 0V5h2.146a.25.25 0 00.177-.427L8.177 1.677a.25.25 0 00-.354 0zM13.75 11a.75.75 0 000 1.5h.5a.75.75 0 000-1.5h-.5zm-3.75.75a.75.75 0 01.75-.75h.5a.75.75 0 010 1.5h-.5a.75.75 0 01-.75-.75zM7.75 11a.75.75 0 000 1.5h.5a.75.75 0 000-1.5h-.5zM4 11.75a.75.75 0 01.75-.75h.5a.75.75 0 010 1.5h-.5a.75.75 0 01-.75-.75zM1.75 11a.75.75 0 000 1.5h.5a.75.75 0 000-1.5h-.5z"/></svg>

Before

Width:  |  Height:  |  Size: 526 B

After

Width:  |  Height:  |  Size: 506 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M1.75 2.5a.75.75 0 000 1.5h6.5a.75.75 0 000-1.5h-6.5zm4 5a.75.75 0 000 1.5h7.5a.75.75 0 000-1.5h-7.5zm0 5a.75.75 0 000 1.5h7.5a.75.75 0 000-1.5h-7.5zM3 8a1 1 0 11-2 0 1 1 0 012 0zm-1 6a1 1 0 100-2 1 1 0 000 2z"/><path d="M13.314 4.918L11.07 2.417A.25.25 0 0111.256 2h4.488a.25.25 0 01.186.417l-2.244 2.5a.25.25 0 01-.372 0z"/></svg>

After

Width:  |  Height:  |  Size: 421 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill-rule="evenodd" d="M3.75 5.5a.75.75 0 000 1.5h10a.75.75 0 000-1.5h-10zm5 6a.75.75 0 000 1.5h11.5a.75.75 0 000-1.5H8.75zm0 6a.75.75 0 000 1.5h11.5a.75.75 0 000-1.5H8.75zM5 12a1 1 0 11-2 0 1 1 0 012 0zm-1 7a1 1 0 100-2 1 1 0 000 2z"/><path d="M19.309 7.918l-2.245-2.501A.25.25 0 0117.25 5h4.49a.25.25 0 01.185.417l-2.244 2.5a.25.25 0 01-.372 0z"/></svg>

After

Width:  |  Height:  |  Size: 421 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M1 5.25a.75.75 0 01.75-.75h12.5a.75.75 0 010 1.5H1.75A.75.75 0 011 5.25zm0 5.5a.75.75 0 01.75-.75h12.5a.75.75 0 010 1.5H1.75a.75.75 0 01-.75-.75z"/><path fill-rule="evenodd" d="M6.368 1.01a.75.75 0 01.623.859l-2 12.5a.75.75 0 01-1.482-.237l2-12.5a.75.75 0 01.86-.622zm5.5 0a.75.75 0 01.623.859l-2 12.5a.75.75 0 01-1.482-.237l2-12.5a.75.75 0 01.86-.622z"/></svg>

After

Width:  |  Height:  |  Size: 450 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill-rule="evenodd" d="M2 8.25a.75.75 0 01.75-.75h18.5a.75.75 0 010 1.5H2.75A.75.75 0 012 8.25zm-.005 7.5a.75.75 0 01.75-.75v1.5a.75.75 0 01-.75-.75zm.75 0v.75H21.25a.75.75 0 000-1.5H2.745v.75z"/><path fill-rule="evenodd" d="M9.62 2.01a.75.75 0 01.62.86l-3 18.5a.75.75 0 01-1.48-.24l3-18.5a.75.75 0 01.86-.62zm8 0a.75.75 0 01.62.86l-3 18.5a.75.75 0 01-1.48-.24l3-18.5a.75.75 0 01.86-.62z"/></svg>

After

Width:  |  Height:  |  Size: 462 B

View File

Before

Width:  |  Height:  |  Size: 466 B

After

Width:  |  Height:  |  Size: 466 B

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.177.677l2.896 2.896a.25.25 0 01-.177.427H8.75v1.25a.75.75 0 01-1.5 0V4H5.104a.25.25 0 01-.177-.427L7.823.677a.25.25 0 01.354 0zM7.25 10.75a.75.75 0 011.5 0V12h2.146a.25.25 0 01.177.427l-2.896 2.896a.25.25 0 01-.354 0l-2.896-2.896A.25.25 0 015.104 12H7.25v-1.25zm-5-2a.75.75 0 000-1.5h-.5a.75.75 0 000 1.5h.5zM6 8a.75.75 0 01-.75.75h-.5a.75.75 0 010-1.5h.5A.75.75 0 016 8zm2.25.75a.75.75 0 000-1.5h-.5a.75.75 0 000 1.5h.5zM12 8a.75.75 0 01-.75.75h-.5a.75.75 0 010-1.5h.5A.75.75 0 0112 8zm2.25.75a.75.75 0 000-1.5h-.5a.75.75 0 000 1.5h.5z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M8.177.677l2.896 2.896a.25.25 0 01-.177.427H8.75v1.25a.75.75 0 01-1.5 0V4H5.104a.25.25 0 01-.177-.427L7.823.677a.25.25 0 01.354 0zM7.25 10.75a.75.75 0 011.5 0V12h2.146a.25.25 0 01.177.427l-2.896 2.896a.25.25 0 01-.354 0l-2.896-2.896A.25.25 0 015.104 12H7.25v-1.25zm-5-2a.75.75 0 000-1.5h-.5a.75.75 0 000 1.5h.5zM6 8a.75.75 0 01-.75.75h-.5a.75.75 0 010-1.5h.5A.75.75 0 016 8zm2.25.75a.75.75 0 000-1.5h-.5a.75.75 0 000 1.5h.5zM12 8a.75.75 0 01-.75.75h-.5a.75.75 0 010-1.5h.5A.75.75 0 0112 8zm2.25.75a.75.75 0 000-1.5h-.5a.75.75 0 000 1.5h.5z"/></svg>

Before

Width:  |  Height:  |  Size: 637 B

After

Width:  |  Height:  |  Size: 617 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M1.75 3.5a.25.25 0 00-.25.25v8.5c0 .138.112.25.25.25h12.5a.25.25 0 00.25-.25v-8.5a.25.25 0 00-.25-.25H1.75zM0 3.75C0 2.784.784 2 1.75 2h12.5c.966 0 1.75.784 1.75 1.75v8.5A1.75 1.75 0 0114.25 14H1.75A1.75 1.75 0 010 12.25v-8.5z"/><path d="M6 10.559V5.442a.25.25 0 01.379-.215l4.264 2.559a.25.25 0 010 .428l-4.264 2.559A.25.25 0 016 10.559z"/></svg>

After

Width:  |  Height:  |  Size: 436 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill-rule="evenodd" d="M1.75 4.5a.25.25 0 00-.25.25v14.5c0 .138.112.25.25.25h20.5a.25.25 0 00.25-.25V4.75a.25.25 0 00-.25-.25H1.75zM0 4.75C0 3.784.784 3 1.75 3h20.5c.966 0 1.75.784 1.75 1.75v14.5A1.75 1.75 0 0122.25 21H1.75A1.75 1.75 0 010 19.25V4.75z"/><path d="M9 15.584V8.416a.5.5 0 01.77-.42l5.576 3.583a.5.5 0 010 .842L9.77 16.005a.5.5 0 01-.77-.42z"/></svg>

After

Width:  |  Height:  |  Size: 429 B

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,16 +1,18 @@
{
"assets/javascripts/bundle.js": "assets/javascripts/bundle.d3f6ab33.min.js",
"assets/javascripts/bundle.js.map": "assets/javascripts/bundle.d3f6ab33.min.js.map",
"assets/javascripts/vendor.js": "assets/javascripts/vendor.f03b12a7.min.js",
"assets/javascripts/vendor.js.map": "assets/javascripts/vendor.f03b12a7.min.js.map",
"assets/javascripts/worker/search.js": "assets/javascripts/worker/search.9c0e82ba.min.js",
"assets/javascripts/worker/search.js.map": "assets/javascripts/worker/search.9c0e82ba.min.js.map",
"assets/stylesheets/main.css": "assets/stylesheets/main.8e656da0.min.css",
"assets/stylesheets/main.css.map": "assets/stylesheets/main.8e656da0.min.css.map",
"assets/stylesheets/palette.css": "assets/stylesheets/palette.936e7021.min.css",
"assets/stylesheets/palette.css.map": "assets/stylesheets/palette.936e7021.min.css.map",
"overrides/assets/javascripts/bundle.js": "overrides/assets/javascripts/bundle.576c0d5a.min.js",
"overrides/assets/javascripts/bundle.js.map": "overrides/assets/javascripts/bundle.576c0d5a.min.js.map",
"overrides/assets/stylesheets/main.css": "overrides/assets/stylesheets/main.2aa486e8.min.css",
"overrides/assets/stylesheets/main.css.map": "overrides/assets/stylesheets/main.2aa486e8.min.css.map"
"assets/javascripts/bundle.js": "assets/javascripts/bundle.6b6cf576.min.js",
"assets/javascripts/bundle.js.map": "assets/javascripts/bundle.6b6cf576.min.js.map",
"assets/javascripts/vendor.js": "assets/javascripts/vendor.e32ed4d0.min.js",
"assets/javascripts/vendor.js.map": "assets/javascripts/vendor.e32ed4d0.min.js.map",
"assets/javascripts/worker/search.js": "assets/javascripts/worker/search.b9424174.min.js",
"assets/javascripts/worker/search.js.map": "assets/javascripts/worker/search.b9424174.min.js.map",
"assets/stylesheets/main.css": "assets/stylesheets/main.a86e6725.min.css",
"assets/stylesheets/main.css.map": "assets/stylesheets/main.a86e6725.min.css.map",
"assets/stylesheets/palette.css": "assets/stylesheets/palette.e70b70b6.min.css",
"assets/stylesheets/palette.css.map": "assets/stylesheets/palette.e70b70b6.min.css.map",
"overrides/assets/javascripts/bundle.js": "overrides/assets/javascripts/bundle.63116035.min.js",
"overrides/assets/javascripts/bundle.js.map": "overrides/assets/javascripts/bundle.63116035.min.js.map",
"overrides/assets/javascripts/vendor.js": "overrides/assets/javascripts/vendor.1aa446d9.min.js",
"overrides/assets/javascripts/vendor.js.map": "overrides/assets/javascripts/vendor.1aa446d9.min.js.map",
"overrides/assets/stylesheets/main.css": "overrides/assets/stylesheets/main.8e98f424.min.css",
"overrides/assets/stylesheets/main.css.map": "overrides/assets/stylesheets/main.8e98f424.min.css.map"
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -27,7 +27,7 @@
<link rel="canonical" href="{{ page.canonical_url }}">
{% endif %}
<link rel="shortcut icon" href="{{ config.theme.favicon | url }}">
<meta name="generator" content="mkdocs-{{ mkdocs_version }}, mkdocs-material-6.2.8">
<meta name="generator" content="mkdocs-{{ mkdocs_version }}, mkdocs-material-7.0.0b1">
{% endblock %}
{% block htmltitle %}
{% if page and page.meta and page.meta.title %}
@@ -39,10 +39,10 @@
{% endif %}
{% endblock %}
{% block styles %}
<link rel="stylesheet" href="{{ 'assets/stylesheets/main.8e656da0.min.css' | url }}">
<link rel="stylesheet" href="{{ 'assets/stylesheets/main.a86e6725.min.css' | url }}">
{% if config.theme.palette %}
{% set palette = config.theme.palette %}
<link rel="stylesheet" href="{{ 'assets/stylesheets/palette.936e7021.min.css' | url }}">
<link rel="stylesheet" href="{{ 'assets/stylesheets/palette.e70b70b6.min.css' | url }}">
{% if palette.primary %}
{% import "partials/palette.html" as map %}
{% set primary = map.primary(
@@ -131,7 +131,7 @@
{% if page and page.meta and page.meta.hide %}
{% set hidden = "hidden" if "navigation" in page.meta.hide %}
{% endif %}
<div class="md-sidebar md-sidebar--primary" data-md-component="navigation" {{ hidden }}>
<div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" {{ hidden }}>
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
{% include "partials/nav.html" %}
@@ -143,7 +143,7 @@
{% if page and page.meta and page.meta.hide %}
{% set hidden = "hidden" if "toc" in page.meta.hide %}
{% endif %}
<div class="md-sidebar md-sidebar--secondary" data-md-component="toc" {{ hidden }}>
<div class="md-sidebar md-sidebar--secondary" data-md-component="sidebar" data-md-type="toc" {{ hidden }}>
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
{% include "partials/toc.html" %}
@@ -152,7 +152,7 @@
</div>
{% endif %}
{% endblock %}
<div class="md-content">
<div class="md-content" data-md-component="content">
<article class="md-content__inner md-typeset">
{% block content %}
{% if page.edit_url %}
@@ -183,10 +183,17 @@
{% include "partials/footer.html" %}
{% endblock %}
</div>
{% block scripts %}
<script src="{{ 'assets/javascripts/vendor.f03b12a7.min.js' | url }}"></script>
<script src="{{ 'assets/javascripts/bundle.d3f6ab33.min.js' | url }}"></script>
{%- set translations = {} -%}
<div class="md-dialog" data-md-component="dialog">
<div class="md-dialog__inner md-typeset"></div>
</div>
{% block config %}
{%- set app = {
"base": base_url,
"features": features,
"translations": {},
"search": "assets/javascripts/worker/search.b9424174.min.js" | url,
} -%}
{%- set translations = app.translations -%}
{%- for key in [
"clipboard.copy",
"clipboard.copied",
@@ -204,19 +211,13 @@
] -%}
{%- set _ = translations.update({ key: lang.t(key) }) -%}
{%- endfor -%}
<script id="__lang" type="application/json">
{{- translations | tojson -}}
</script>
{% block config %}{% endblock %}
<script>
app = initialize({
base: "{{ base_url }}",
features: {{ features or [] | tojson }},
search: Object.assign({
worker: "{{ 'assets/javascripts/worker/search.9c0e82ba.min.js' | url }}"
}, typeof search !== "undefined" && search)
})
<script id="__config" type="application/json">
{{- app | tojson -}}
</script>
{% endblock %}
{% block scripts %}
<script src="{{ 'assets/javascripts/vendor.e32ed4d0.min.js' | url }}"></script>
<script src="{{ 'assets/javascripts/bundle.6b6cf576.min.js' | url }}"></script>
{% for path in config["extra_javascript"] %}
<script src="{{ path | url }}"></script>
{% endfor %}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2 @@
!function(e,t){for(var n in t)e[n]=t[n]}(window,function(e){function t(t){for(var r,i,a=t[0],s=t[1],u=t[2],f=0,p=[];f<a.length;f++)i=a[f],Object.prototype.hasOwnProperty.call(o,i)&&o[i]&&p.push(o[i][0]),o[i]=0;for(r in s)Object.prototype.hasOwnProperty.call(s,r)&&(e[r]=s[r]);for(l&&l(t);p.length;)p.shift()();return c.push.apply(c,u||[]),n()}function n(){for(var e,t=0;t<c.length;t++){for(var n=c[t],r=!0,a=1;a<n.length;a++){var s=n[a];0!==o[s]&&(r=!1)}r&&(c.splice(t--,1),e=i(i.s=n[0]))}return e}var r={},o={0:0},c=[];function i(t){if(r[t])return r[t].exports;var n=r[t]={i:t,l:!1,exports:{}};return e[t].call(n.exports,n,n.exports,i),n.l=!0,n.exports}i.m=e,i.c=r,i.d=function(e,t,n){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,t){if(1&t&&(e=i(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(i.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)i.d(n,r,function(t){return e[t]}.bind(null,r));return n},i.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="";var a=window.webpackJsonp=window.webpackJsonp||[],s=a.push.bind(a);a.push=t,a=a.slice();for(var u=0;u<a.length;u++)t(a[u]);var l=s;return c.push([37,1]),n()}({37:function(e,t,n){"use strict";n.r(t);var r=n(20),o=n(3),c=n(38),i=n(29),a=n(25);n(32),n(39);function s(e,t=document){return t.querySelector(e)||void 0}function u(e,t=document){const n=s(e,t);if(void 0===n)throw new ReferenceError(`Missing element: expected "${e}" to be present`);return n}n(50);var l=n(51);var f=n(17),p=n(40),d=n(41),b=n(42),g=n(43),h=n(44);n(45),n(46);const v=new f.a;Object(p.a)(()=>Object(d.a)(new ResizeObserver(e=>{for(const t of e)v.next(t)}))).pipe(Object(i.a)(e=>b.a.pipe(Object(l.a)(e)).pipe(Object(g.a)(()=>e.disconnect()))),Object(h.a)(1));n(31);u("[data-md-toggle=drawer]"),u("[data-md-toggle=search]");n(47);n(52),n(53);n(48),n(49);function O(e,t){if("string"==typeof t||"number"==typeof t)e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(const n of t)O(e,n)}function j(e,t,...n){const r=document.createElement(e);if(t)for(const e of Object.keys(t))"boolean"!=typeof t[e]?r.setAttribute(e,t[e]):t[e]&&r.setAttribute(e,"");for(const e of n)O(r,e);return r}function m(e,t){return t.length?j("div",{class:""},j("span",null,function(e){if(e>999){return((e+1e-6)/1e3).toFixed(+((e-950)%1e3>99))+"k"}return e.toString()}(e.length)," results"),j("ul",{class:"tx-icon-search__list"},e.slice(0,10).map(e=>j("li",{class:"tx-icon-search__item"},j("span",{class:"twemoji"},j("img",{src:"https://raw.githubusercontent.com/squidfunk/mkdocs-material/master/material/.icons/"+e,style:"width: 18px; height: 18px"}))," ",j("button",{class:"md-clipboard--inline","data-clipboard-text":":"+e.replace(/\.svg$/,"").replace(/\//g,"-")+":"},j("code",null,function(e,t){return`:${Object(r.wrap)(e.replace(/\.svg$/,"").replace(/\//g,"-"),t,{wrap:{tagOpen:"<b>",tagClose:"</b>"}})}:`}(e,t))))))):j("div",{class:""})}const y=u("#__config"),w=JSON.parse(y.textContent),x=Object(o.a)(fetch(w.base+"/overrides/assets/javascripts/icons.json").then(e=>e.json())),_=s("#icon-search");_&&x.pipe(Object(i.a)(e=>Object(c.a)(_,"keyup").pipe(Object(a.a)(()=>Object(r.filter)(e,_.value))))).subscribe(e=>{const t=u(".tx-icon-result");t.innerHTML="",t.appendChild(m(e,_.value))}),Object(c.a)(document.body,"click").subscribe(e=>{if(e.target instanceof HTMLElement){e.target.closest("a[href^=http]")instanceof HTMLLinkElement&&ga("send","event","outbound","click",y.href)}})}}));
//# sourceMappingURL=bundle.63116035.min.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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

@@ -22,7 +22,7 @@
<meta name="twitter:title" content="{{ title }}">
<meta name="twitter:description" content="{{ config.site_description }}">
<meta name="twitter:image" content="{{ image }}">
<link rel="stylesheet" href="{{ 'overrides/assets/stylesheets/main.2aa486e8.min.css' | url }}">
<link rel="stylesheet" href="{{ 'overrides/assets/stylesheets/main.8e98f424.min.css' | url }}">
{% endblock %}
{% block announce %}
<a href="https://twitter.com/squidfunk">
@@ -53,14 +53,6 @@
{% endblock %}
{% block scripts %}
{{ super() }}
{% block config %}
{%- set configuration = {
"base": base_url,
"features": features
} -%}
<script id="__config" type="application/json">
{{- configuration | tojson -}}
</script>
{% endblock %}
<script src="{{ 'overrides/assets/javascripts/bundle.576c0d5a.min.js' | url }}"></script>
<script src="{{ 'overrides/assets/javascripts/vendor.1aa446d9.min.js' | url }}"></script>
<script src="{{ 'overrides/assets/javascripts/bundle.63116035.min.js' | url }}"></script>
{% endblock %}

View File

@@ -11,7 +11,7 @@
{% include ".icons/material/magnify.svg" %}
{% include ".icons/material/arrow-left.svg" %}
</label>
<button type="reset" class="md-search__icon md-icon" aria-label="{{ lang.t('search.reset') }}" data-md-component="search-reset" tabindex="-1">
<button type="reset" class="md-search__icon md-icon" aria-label="{{ lang.t('search.reset') }}" tabindex="-1">
{% include ".icons/material/close.svg" %}
</button>
</form>

View File

@@ -2,7 +2,7 @@
This file was automatically generated - do not edit
-#}
{% import "partials/language.html" as lang with context %}
<a href="{{ config.repo_url }}" title="{{ lang.t('source.link.title') }}" class="md-source">
<a href="{{ config.repo_url }}" title="{{ lang.t('source.link.title') }}" class="md-source" data-md-component="source">
<div class="md-source__icon md-icon">
{% set icon = config.theme.icon.repo or "fontawesome/brands/git-alt" %}
{% include ".icons/" ~ icon ~ ".svg" %}

View File

@@ -12,7 +12,7 @@
<span class="md-nav__icon md-icon"></span>
{{ lang.t("toc.title") }}
</label>
<ul class="md-nav__list" data-md-scrollfix>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
{% for toc_item in toc %}
{% include "partials/toc-item.html" %}
{% endfor %}

17523
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "mkdocs-material",
"version": "6.2.8",
"version": "7.0.0b1",
"description": "A Material Design theme for MkDocs",
"keywords": [
"mkdocs",
@@ -24,18 +24,18 @@
"url": "https://github.com/squidfunk/mkdocs-material.git"
},
"scripts": {
"build": "npm run clean && npx webpack --mode production",
"clean": "npx rimraf material",
"lint": "npm run lint:ts && npm run lint:scss",
"lint:scss": "npx stylelint \"src/assets/**/*.scss\"",
"lint:ts": "npx tslint -p tsconfig.json \"src/**/*.ts\"",
"start": "npx webpack --mode development --watch"
"build": "npm run clean && webpack --mode production",
"clean": "rimraf material",
"lint": "npm run lint:scss && npm run lint:ts",
"lint:scss": "stylelint \"src/assets/**/*.scss\"",
"lint:ts": "eslint --cache \"src/**/*.ts\"",
"start": "webpack --mode development --watch"
},
"dependencies": {
"clipboard": "^2.0.6",
"escape-html": "^1.0.3",
"fuzzaldrin-plus": "^0.6.0",
"focus-visible": "^5.2.0",
"fuzzaldrin-plus": "^0.6.0",
"lunr": "^2.3.9",
"lunr-languages": "^1.4.0",
"rxjs": "^7.0.0-beta.10"
@@ -57,9 +57,16 @@
"@types/resize-observer-browser": "^0.1.5",
"@types/webpack": "^4.41.26",
"@types/webpack-assets-manifest": "^3.0.1",
"@typescript-eslint/eslint-plugin": "^4.15.0",
"@typescript-eslint/parser": "^4.15.0",
"autoprefixer": "10.2.4",
"copy-webpack-plugin": "^6.4.1",
"css-loader": "^5.0.1",
"css-loader": "^5.0.2",
"eslint": "^7.19.0",
"eslint-plugin-eslint-comments": "^3.2.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jsdoc": "^31.0.8",
"eslint-plugin-no-null": "^1.0.2",
"event-hooks-webpack-plugin": "^2.2.0",
"expose-loader": "^1.0.3",
"github-types": "^1.0.0",
@@ -81,18 +88,16 @@
"sass-loader": "^10.1.1",
"stylelint": "^13.9.0",
"stylelint-config-rational-order": "^0.1.2",
"stylelint-config-recommended": "^3.0.0",
"stylelint-config-standard": "^20.0.0",
"stylelint-order": "^4.1.0",
"stylelint-scss": "^3.18.0",
"tiny-glob": "^0.2.8",
"ts-loader": "^8.0.15",
"ts-node": "^9.1.1",
"tsconfig-paths-webpack-plugin": "^3.3.0",
"tslib": "^2.1.0",
"tslint": "^6.1.3",
"tslint-sonarts": "^1.9.0",
"typescript": "^4.1.3",
"webpack": "^4.44.2",
"typescript": "^4.1.5",
"webpack": "^4.46.0",
"webpack-assets-manifest": "3.1.1",
"webpack-cli": "^4.5.0"
},

View File

@@ -0,0 +1,125 @@
/*
* Copyright (c) 2016-2020 Martin Donath <martin.donath@squidfunk.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
import { getElementOrThrow, getLocation } from "~/browser"
/* ----------------------------------------------------------------------------
* Types
* ------------------------------------------------------------------------- */
/**
* Feature flag
*/
export type Flag =
| "header.autohide" /* Hide header */
| "navigation.tabs" /* Tabs navigation */
| "navigation.instant" /* Instant loading */
/* ------------------------------------------------------------------------- */
/**
* Translation
*/
export type Translation =
| "clipboard.copy" /* Copy to clipboard */
| "clipboard.copied" /* Copied to clipboard */
| "search.config.lang" /* Search language */
| "search.config.pipeline" /* Search pipeline */
| "search.config.separator" /* Search separator */
| "search.placeholder" /* Search */
| "search.result.placeholder" /* Type to start searching */
| "search.result.none" /* No matching documents */
| "search.result.one" /* 1 matching document */
| "search.result.other" /* # matching documents */
| "search.result.more.one" /* 1 more on this page */
| "search.result.more.other" /* # more on this page */
| "search.result.term.missing" /* Missing */
/**
* Translations
*/
export type Translations = Record<Translation, string>
/* ------------------------------------------------------------------------- */
/**
* Configuration
*/
export interface Config {
base: string /* Base URL */
features: Flag[] /* Feature flags */
translations: Translations /* Translations */
search: string /* Search worker URL */
}
/* ----------------------------------------------------------------------------
* Data
* ------------------------------------------------------------------------- */
/**
* Retrieve global configuration and make base URL absolute
*/
const script = getElementOrThrow("#__config")
const config: Config = JSON.parse(script.textContent!)
config.base = new URL(config.base, getLocation())
.toString()
.replace(/\/$/, "")
/* ----------------------------------------------------------------------------
* Functions
* ------------------------------------------------------------------------- */
/**
* Retrieve global configuration
*
* @returns Global configuration
*/
export function configuration(): Config {
return config
}
/**
* Check whether a feature flag is enabled
*
* @param flag - Feature flag
*
* @returns Test result
*/
export function feature(flag: Flag): boolean {
return config.features.includes(flag)
}
/**
* Retrieve the translation for the given key
*
* @param key - Key to be translated
* @param value - Value to be replaced
*
* @returns Translation
*/
export function translation(
key: Translation, value?: string | number
): string {
return typeof value !== "undefined"
? config.translations[key].replace("#", value.toString())
: config.translations[key]
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright (c) 2016-2020 Martin Donath <martin.donath@squidfunk.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
/* ----------------------------------------------------------------------------
* Functions
* ------------------------------------------------------------------------- */
/**
* Set focusable property
*
* @param el - Element
* @param value - Tabindex value
*/
export function setFocusable(
el: HTMLElement, value = 0
): void {
el.setAttribute("tabindex", value.toString())
}
/**
* Reset focusable property
*
* @param el - Element
*/
export function resetFocusable(
el: HTMLElement
): void {
el.removeAttribute("tabindex")
}
/**
* Set scroll lock
*
* @param el - Scrollable element
* @param value - Vertical offset
*/
export function setScrollLock(
el: HTMLElement, value: number
): void {
el.setAttribute("data-md-state", "lock")
el.style.top = `-${value}px`
}
/**
* Reset scroll lock
*
* @param el - Scrollable element
*/
export function resetScrollLock(
el: HTMLElement
): void {
const value = -1 * parseInt(el.style.top, 10)
el.removeAttribute("data-md-state")
el.style.top = ""
if (value)
window.scrollTo(0, value)
}

View File

@@ -25,23 +25,23 @@
* ------------------------------------------------------------------------- */
/**
* Set anchor blur
* Set anchor state
*
* @param el - Anchor element
* @param value - Whether the anchor is blurred
* @param state - Anchor state
*/
export function setAnchorBlur(
el: HTMLElement, value: boolean
export function setAnchorState(
el: HTMLElement, state: "blur"
): void {
el.setAttribute("data-md-state", value ? "blur" : "")
el.setAttribute("data-md-state", state)
}
/**
* Reset anchor blur
* Reset anchor state
*
* @param el - Anchor element
*/
export function resetAnchorBlur(
export function resetAnchorState(
el: HTMLElement
): void {
el.removeAttribute("data-md-state")

View File

@@ -20,37 +20,43 @@
* IN THE SOFTWARE.
*/
import { Observable } from "rxjs"
import { map, shareReplay, take } from "rxjs/operators"
/* ----------------------------------------------------------------------------
* Helper types
* Functions
* ------------------------------------------------------------------------- */
/**
* Watch options
* Set dialog message
*
* @param el - Dialog element
* @param value - Dialog message
*/
interface WatchOptions {
location$: Observable<URL> /* Location observable */
export function setDialogMessage(
el: HTMLElement, value: string
): void {
el.firstElementChild!.innerHTML = value
}
/* ------------------------------------------------------------------------- */
/**
* Watch location base
* Set dialog state
*
* @return Location base observable
* @param el - Dialog element
* @param state - Dialog state
*/
export function watchLocationBase(
base: string, { location$ }: WatchOptions
): Observable<string> {
return location$
.pipe(
take(1),
map(({ href }) => new URL(base, href)
.toString()
.replace(/\/$/, "")
),
shareReplay({ bufferSize: 1, refCount: true })
)
export function setDialogState(
el: HTMLElement, state: "open"
): void {
el.setAttribute("data-md-state", state)
}
/**
* Reset dialog state
*
* @param el - Dialog element
*/
export function resetDialogState(
el: HTMLElement
): void {
el.removeAttribute("data-md-state")
}

View File

@@ -25,23 +25,23 @@
* ------------------------------------------------------------------------- */
/**
* Set header shadow
* Set header state
*
* @param el - Header element
* @param value - Whether the shadow is shown
* @param state - Header state
*/
export function setHeaderShadow(
el: HTMLElement, value: boolean
export function setHeaderState(
el: HTMLElement, state: "shadow" | "hidden"
): void {
el.setAttribute("data-md-state", value ? "shadow" : "")
el.setAttribute("data-md-state", state)
}
/**
* Reset header shadow
* Reset header state
*
* @param el - Header element
*/
export function resetHeaderShadow(
export function resetHeaderState(
el: HTMLElement
): void {
el.removeAttribute("data-md-state")

View File

@@ -21,4 +21,4 @@
*/
export * from "./_"
export * from "./react"
export * from "./title"

View File

@@ -25,23 +25,23 @@
* ------------------------------------------------------------------------- */
/**
* Set header title active
* Set header title state
*
* @param el - Header title element
* @param value - Whether the title is shown
* @param state - Header title state
*/
export function setHeaderTitleActive(
el: HTMLElement, value: boolean
export function setHeaderTitleState(
el: HTMLElement, state: "active"
): void {
el.setAttribute("data-md-state", value ? "active" : "")
el.setAttribute("data-md-state", state)
}
/**
* Reset header title active
* Reset header title state
*
* @param el - Header title element
*/
export function resetHeaderTitleActive(
export function resetHeaderTitleState(
el: HTMLElement
): void {
el.removeAttribute("data-md-state")

View File

@@ -0,0 +1,30 @@
/*
* Copyright (c) 2016-2020 Martin Donath <martin.donath@squidfunk.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
export * from "./_"
export * from "./anchor"
export * from "./dialog"
export * from "./header"
export * from "./search"
export * from "./sidebar"
export * from "./source"
export * from "./tabs"

View File

@@ -0,0 +1,24 @@
/*
* Copyright (c) 2016-2020 Martin Donath <martin.donath@squidfunk.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
export * from "./query"
export * from "./result"

View File

@@ -20,7 +20,7 @@
* IN THE SOFTWARE.
*/
import { translate } from "utilities"
import { translation } from "~/_"
/* ----------------------------------------------------------------------------
* Functions
@@ -46,5 +46,5 @@ export function setSearchQueryPlaceholder(
export function resetSearchQueryPlaceholder(
el: HTMLInputElement
): void {
el.placeholder = translate("search.placeholder")
el.placeholder = translation("search.placeholder")
}

View File

@@ -20,7 +20,7 @@
* IN THE SOFTWARE.
*/
import { translate } from "utilities"
import { translation } from "~/_"
/* ----------------------------------------------------------------------------
* Functions
@@ -39,17 +39,17 @@ export function setSearchResultMeta(
/* No results */
case 0:
el.textContent = translate("search.result.none")
el.textContent = translation("search.result.none")
break
/* One result */
case 1:
el.textContent = translate("search.result.one")
el.textContent = translation("search.result.one")
break
/* Multiple result */
default:
el.textContent = translate("search.result.other", value)
el.textContent = translation("search.result.other", value)
}
}
@@ -61,7 +61,7 @@ export function setSearchResultMeta(
export function resetSearchResultMeta(
el: HTMLElement
): void {
el.textContent = translate("search.result.placeholder")
el.textContent = translation("search.result.placeholder")
}
/* ------------------------------------------------------------------------- */

View File

@@ -55,17 +55,15 @@ export function resetSidebarOffset(
* This function doesn't set the height of the actual sidebar, but of its first
* child the `.md-sidebar__scrollwrap` element in order to mitigiate jittery
* sidebars when the footer is scrolled into view. At some point we switched
* from `absolute` / `fixed` positioning to `sticky` positioning, which greatly
* reduced jitter in some browsers (respectively Firefox and Safari) when
* from `absolute` / `fixed` positioning to `sticky` positioning, significantly
* reducing jitter in some browsers (respectively Firefox and Safari) when
* scrolling from the top. However, top-aligned sticky positioning means that
* the sidebar snaps to the bottom when the end of the container is reached.
* This is what leads to the mentioned jitter, as the sidebar's height may be
* updated to slowly.
* updated too slowly.
*
* By setting the height of the sidebar to zero (while preserving `padding`),
* and the height on its first element, this behaviour can be mitigiated. We
* must assume that the top- and bottom offset (`padding`) are equal, as the
* `offsetBottom` value is `undefined`.
* This behaviour can be mitigiated by setting the height of the sidebar to `0`
* while preserving the padding, and the height on its first element.
*
* @param el - Sidebar element
* @param value - Sidebar height

View File

@@ -0,0 +1,49 @@
/*
* Copyright (c) 2016-2020 Martin Donath <martin.donath@squidfunk.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
/* ----------------------------------------------------------------------------
* Functions
* ------------------------------------------------------------------------- */
/**
* Set repository facts
*
* @param el - Repository element
* @param child - Repository facts element
*/
export function setSourceFacts(
el: HTMLElement, child: Element
): void {
el.lastElementChild!.appendChild(child)
}
/**
* Set repository state
*
* @param el - Repository element
* @param state - Repository state
*/
export function setSourceState(
el: HTMLElement, state: "done"
): void {
el.lastElementChild!.setAttribute("data-md-state", state)
}

View File

@@ -25,23 +25,23 @@
* ------------------------------------------------------------------------- */
/**
* Set tabs hidden
* Set tabs state
*
* @param el - Tabs element
* @param value - Whether the element is hidden
* @param state - Tabs state
*/
export function setTabsHidden(
el: HTMLElement, value: boolean
export function setTabsState(
el: HTMLElement, state: "hidden"
): void {
el.setAttribute("data-md-state", value ? "hidden" : "")
el.setAttribute("data-md-state", state)
}
/**
* Reset tabs hidden
* Reset tabs state
*
* @param el - Tabs element
*/
export function resetTabsHidden(
export function resetTabsState(
el: HTMLElement
): void {
el.removeAttribute("data-md-state")

View File

@@ -34,7 +34,7 @@ import { mapTo } from "rxjs/operators"
* automatically updated when a new document is emitted. This enabled features
* like instant loading.
*
* @return Document subject
* @returns Document subject
*/
export function watchDocument(): Subject<Document> {
const document$ = new ReplaySubject<Document>()

View File

@@ -0,0 +1,5 @@
{
"rules": {
"jsdoc/require-jsdoc": "off"
}
}

View File

@@ -32,8 +32,16 @@
* @param selector - Query selector
* @param node - Node of reference
*
* @return Element or nothing
* @returns Element or nothing
*/
export function getElement<T extends keyof HTMLElementTagNameMap>(
selector: T, node?: ParentNode
): HTMLElementTagNameMap[T]
export function getElement<T extends HTMLElement>(
selector: string, node?: ParentNode
): T | undefined
export function getElement<T extends HTMLElement>(
selector: string, node: ParentNode = document
): T | undefined {
@@ -48,8 +56,16 @@ export function getElement<T extends HTMLElement>(
* @param selector - Query selector
* @param node - Node of reference
*
* @return Element
* @returns Element
*/
export function getElementOrThrow<T extends keyof HTMLElementTagNameMap>(
selector: T, node?: ParentNode
): HTMLElementTagNameMap[T]
export function getElementOrThrow<T extends HTMLElement>(
selector: string, node?: ParentNode
): T
export function getElementOrThrow<T extends HTMLElement>(
selector: string, node: ParentNode = document
): T {
@@ -64,7 +80,7 @@ export function getElementOrThrow<T extends HTMLElement>(
/**
* Retrieve the currently active element
*
* @return Element or nothing
* @returns Element or nothing
*/
export function getActiveElement(): HTMLElement | undefined {
return document.activeElement instanceof HTMLElement
@@ -80,8 +96,16 @@ export function getActiveElement(): HTMLElement | undefined {
* @param selector - Query selector
* @param node - Node of reference
*
* @return Elements
* @returns Elements
*/
export function getElements<T extends keyof HTMLElementTagNameMap>(
selector: T, node?: ParentNode
): HTMLElementTagNameMap[T][]
export function getElements<T extends HTMLElement>(
selector: string, node?: ParentNode
): T[]
export function getElements<T extends HTMLElement>(
selector: string, node: ParentNode = document
): T[] {
@@ -97,7 +121,7 @@ export function getElements<T extends HTMLElement>(
*
* @param tagName - Tag name
*
* @return Element
* @returns Element
*/
export function createElement<T extends keyof HTMLElementTagNameMap>(
tagName: T

View File

@@ -36,7 +36,7 @@ import { getActiveElement } from "../_"
* @param value - Whether the element should be focused
*/
export function setElementFocus(
el: HTMLElement, value: boolean = true
el: HTMLElement, value = true
): void {
if (value)
el.focus()
@@ -51,7 +51,7 @@ export function setElementFocus(
*
* @param el - Element
*
* @return Element focus observable
* @returns Element focus observable
*/
export function watchElementFocus(
el: HTMLElement

View File

@@ -23,5 +23,5 @@
export * from "./_"
export * from "./focus"
export * from "./offset"
export * from "./select"
export * from "./selection"
export * from "./size"

View File

@@ -44,7 +44,7 @@ export interface ElementOffset {
*
* @param el - Element
*
* @return Element offset
* @returns Element offset
*/
export function getElementOffset(el: HTMLElement): ElementOffset {
return {
@@ -60,7 +60,7 @@ export function getElementOffset(el: HTMLElement): ElementOffset {
*
* @param el - Element
*
* @return Element offset observable
* @returns Element offset observable
*/
export function watchElementOffset(
el: HTMLElement

View File

@@ -80,7 +80,7 @@ const observer$ = defer(() => of(
finalize(() => resize.disconnect())
)
),
shareReplay({ bufferSize: 1, refCount: true })
shareReplay(1)
)
/* ----------------------------------------------------------------------------
@@ -92,7 +92,7 @@ const observer$ = defer(() => of(
*
* @param el - Element
*
* @return Element size
* @returns Element size
*/
export function getElementSize(el: HTMLElement): ElementSize {
return {
@@ -101,6 +101,20 @@ export function getElementSize(el: HTMLElement): ElementSize {
}
}
/**
* Retrieve element content size, i.e. including overflowing content
*
* @param el - Element
*
* @returns Element size
*/
export function getElementContentSize(el: HTMLElement): ElementSize {
return {
width: el.scrollWidth,
height: el.scrollHeight
}
}
/* ------------------------------------------------------------------------- */
/**
@@ -113,7 +127,7 @@ export function getElementSize(el: HTMLElement): ElementSize {
*
* @param el - Element
*
* @return Element size observable
* @returns Element size observable
*/
export function watchElementSize(
el: HTMLElement

View File

@@ -25,6 +25,7 @@ export * from "./element"
export * from "./keyboard"
export * from "./location"
export * from "./media"
export * from "./request"
export * from "./toggle"
export * from "./viewport"
export * from "./worker"

View File

@@ -23,20 +23,33 @@
import { Observable, fromEvent } from "rxjs"
import { filter, map, share } from "rxjs/operators"
import { getActiveElement } from "../element"
import { getToggle } from "../toggle"
/* ----------------------------------------------------------------------------
* Types
* ------------------------------------------------------------------------- */
/**
* Key
* Keyboard mode
*/
export interface Key {
export type KeyboardMode =
| "global" /* Global */
| "search" /* Search is open */
/* ------------------------------------------------------------------------- */
/**
* Keyboard
*/
export interface Keyboard {
mode: KeyboardMode /* Keyboard mode */
type: string /* Key type */
claim(): void /* Key claim */
}
/* ----------------------------------------------------------------------------
* Functions
* Helper functions
* ------------------------------------------------------------------------- */
/**
@@ -44,9 +57,9 @@ export interface Key {
*
* @param el - Element
*
* @return Test result
* @returns Test result
*/
export function isSusceptibleToKeyboard(el: HTMLElement): boolean {
function isSusceptibleToKeyboard(el: HTMLElement): boolean {
switch (el.tagName) {
/* Form elements */
@@ -61,24 +74,35 @@ export function isSusceptibleToKeyboard(el: HTMLElement): boolean {
}
}
/* ------------------------------------------------------------------------- */
/* ----------------------------------------------------------------------------
* Functions
* ------------------------------------------------------------------------- */
/**
* Watch keyboard
*
* @return Keyboard observable
* @returns Keyboard observable
*/
export function watchKeyboard(): Observable<Key> {
export function watchKeyboard(): Observable<Keyboard> {
return fromEvent<KeyboardEvent>(window, "keydown")
.pipe(
filter(ev => !(ev.metaKey || ev.ctrlKey)),
map(ev => ({
mode: getToggle("search") ? "search" : "global",
type: ev.key,
claim() {
ev.preventDefault()
ev.stopPropagation()
}
})),
} as Keyboard)),
filter(({ mode }) => {
if (mode === "global") {
const active = getActiveElement()
if (typeof active !== "undefined")
return !isSusceptibleToKeyboard(active)
}
return true
}),
share()
)
}

View File

@@ -34,7 +34,7 @@ import { BehaviorSubject, Subject } from "rxjs"
* tracked without setting them and `Location` is a singleton which represents
* the current location.
*
* @return URL
* @returns URL
*/
export function getLocation(): URL {
return new URL(location.href)
@@ -51,44 +51,10 @@ export function setLocation(url: URL): void {
/* ------------------------------------------------------------------------- */
/**
* Check whether a URL is a local link or a file (except `.html`)
*
* @param url - URL or HTML anchor element
* @param ref - Reference URL
*
* @return Test result
*/
export function isLocalLocation(
url: URL | HTMLAnchorElement,
ref: URL | Location = location
): boolean {
return url.host === ref.host
&& /^(?:\/[\w-]+)*(?:\/?|\.html)$/i.test(url.pathname)
}
/**
* Check whether a URL is an anchor link on the current page
*
* @param url - URL or HTML anchor element
* @param ref - Reference URL
*
* @return Test result
*/
export function isAnchorLocation(
url: URL | HTMLAnchorElement,
ref: URL | Location = location
): boolean {
return url.pathname === ref.pathname
&& url.hash.length > 0
}
/* ------------------------------------------------------------------------- */
/**
* Watch location
*
* @return Location subject
* @returns Location subject
*/
export function watchLocation(): Subject<URL> {
return new BehaviorSubject<URL>(getLocation())

View File

@@ -20,10 +20,10 @@
* IN THE SOFTWARE.
*/
import { Observable, fromEvent } from "rxjs"
import { filter, map, share, startWith } from "rxjs/operators"
import { Observable, fromEvent, of } from "rxjs"
import { filter, map, share, startWith, switchMap } from "rxjs/operators"
import { createElement } from "browser"
import { createElement, getElement } from "~/browser"
/* ----------------------------------------------------------------------------
* Functions
@@ -32,7 +32,7 @@ import { createElement } from "browser"
/**
* Retrieve location hash
*
* @return Location hash
* @returns Location hash
*/
export function getLocationHash(): string {
return location.hash.substring(1)
@@ -60,7 +60,7 @@ export function setLocationHash(hash: string): void {
/**
* Watch location hash
*
* @return Location hash observable
* @returns Location hash observable
*/
export function watchLocationHash(): Observable<string> {
return fromEvent<HashChangeEvent>(window, "hashchange")
@@ -71,3 +71,15 @@ export function watchLocationHash(): Observable<string> {
share()
)
}
/**
* Watch location target
*
* @returns Location target observable
*/
export function watchLocationTarget(): Observable<HTMLElement> {
return watchLocationHash()
.pipe(
switchMap(id => of(getElement(`[id="${id}"]`)!))
)
}

View File

@@ -21,5 +21,4 @@
*/
export * from "./_"
export * from "./base"
export * from "./hash"

View File

@@ -20,8 +20,14 @@
* IN THE SOFTWARE.
*/
import { Observable } from "rxjs"
import { shareReplay, startWith } from "rxjs/operators"
import { NEVER, Observable, fromEvent, merge } from "rxjs"
import {
filter,
map,
mapTo,
startWith,
switchMap
} from "rxjs/operators"
/* ----------------------------------------------------------------------------
* Functions
@@ -32,15 +38,49 @@ import { shareReplay, startWith } from "rxjs/operators"
*
* @param query - Media query
*
* @return Media observable
* @returns Media observable
*/
export function watchMedia(query: string): Observable<boolean> {
const media = matchMedia(query)
return new Observable<boolean>(subscriber => {
media.addListener(ev => subscriber.next(ev.matches))
})
return fromEvent<MediaQueryListEvent>(media, "change")
.pipe(
startWith(media.matches),
shareReplay({ bufferSize: 1, refCount: true })
map(ev => ev.matches),
startWith(media.matches)
)
}
/**
* Watch print mode, cross-browser
*
* @returns Print observable
*/
export function watchPrint(): Observable<void> {
return merge(
watchMedia("print").pipe(filter(Boolean)), /* Webkit */
fromEvent(window, "beforeprint") /* IE, FF */
)
.pipe(
mapTo(undefined)
)
}
/* ------------------------------------------------------------------------- */
/**
* Toggle an observable with another one
*
* @template T - Data type
*
* @param toggle$ - Toggle observable
* @param factory - Observable factory
*
* @returns Toggled observable
*/
export function at<T>(
toggle$: Observable<boolean>, factory: () => Observable<T>
): Observable<T> {
return toggle$
.pipe(
switchMap(active => active ? factory() : NEVER)
)
}

View File

@@ -20,38 +20,71 @@
* IN THE SOFTWARE.
*/
import { OperatorFunction, pipe } from "rxjs"
import { Observable, from } from "rxjs"
import {
mapTo,
startWith,
switchMap,
switchMapTo,
tap
filter,
map,
shareReplay,
switchMap
} from "rxjs/operators"
import { setElementFocus } from "browser"
import { useComponent } from "../../../_"
import { watchSearchReset } from "../react"
/* ----------------------------------------------------------------------------
* Functions
* ------------------------------------------------------------------------- */
/**
* Mount search reset from source observable
* Fetch the given URL
*
* @return Operator function
* @param url - Request URL
* @param options - Request options
*
* @returns Response observable
*/
export function mountSearchReset(): OperatorFunction<HTMLElement, void> {
return pipe(
switchMap(el => watchSearchReset(el)
.pipe(
switchMapTo(useComponent("search-query")),
tap(setElementFocus),
mapTo(undefined)
)
),
startWith(undefined)
)
export function request(
url: string, options: RequestInit = { credentials: "same-origin" }
): Observable<Response> {
return from(fetch(url, options))
.pipe(
filter(res => res.status === 200),
)
}
/**
* Fetch JSON from the given URL
*
* @template T - Data type
*
* @param url - Request URL
* @param options - Request options
*
* @returns Data observable
*/
export function requestJSON<T>(
url: string, options?: RequestInit
): Observable<T> {
return request(url, options)
.pipe(
switchMap(res => res.json()),
shareReplay(1)
)
}
/**
* Fetch XML from the given URL
*
* @param url - Request URL
* @param options - Request options
*
* @returns Data observable
*/
export function requestXML(
url: string, options?: RequestInit
): Observable<Document> {
const dom = new DOMParser()
return request(url, options)
.pipe(
switchMap(res => res.text()),
map(res => dom.parseFromString(res, "text/xml")),
shareReplay(1)
)
}

View File

@@ -44,8 +44,8 @@ export type Toggle =
* Toggle map
*/
const toggles: Record<Toggle, HTMLInputElement> = {
drawer: getElementOrThrow(`[data-md-toggle=drawer]`),
search: getElementOrThrow(`[data-md-toggle=search]`)
drawer: getElementOrThrow("[data-md-toggle=drawer]"),
search: getElementOrThrow("[data-md-toggle=search]")
}
/* ----------------------------------------------------------------------------
@@ -57,7 +57,7 @@ const toggles: Record<Toggle, HTMLInputElement> = {
*
* @param name - Toggle
*
* @return Toggle value
* @returns Toggle value
*/
export function getToggle(name: Toggle): boolean {
return toggles[name].checked
@@ -86,7 +86,7 @@ export function setToggle(name: Toggle, value: boolean): void {
*
* @param name - Toggle
*
* @return Toggle value observable
* @returns Toggle value observable
*/
export function watchToggle(name: Toggle): Observable<boolean> {
const el = toggles[name]

View File

@@ -27,7 +27,7 @@ import {
shareReplay
} from "rxjs/operators"
import { Header } from "components"
import { Header } from "~/components"
import {
ViewportOffset,
@@ -58,8 +58,8 @@ export interface Viewport {
* Watch at options
*/
interface WatchAtOptions {
header$: Observable<Header> /* Header observable */
viewport$: Observable<Viewport> /* Viewport observable */
header$: Observable<Header> /* Header observable */
}
/* ----------------------------------------------------------------------------
@@ -69,7 +69,7 @@ interface WatchAtOptions {
/**
* Watch viewport
*
* @return Viewport observable
* @returns Viewport observable
*/
export function watchViewport(): Observable<Viewport> {
return combineLatest([
@@ -78,7 +78,7 @@ export function watchViewport(): Observable<Viewport> {
])
.pipe(
map(([offset, size]) => ({ offset, size })),
shareReplay({ bufferSize: 1, refCount: true })
shareReplay(1)
)
}
@@ -88,10 +88,10 @@ export function watchViewport(): Observable<Viewport> {
* @param el - Element
* @param options - Options
*
* @return Viewport observable
* @returns Viewport observable
*/
export function watchViewportAt(
el: HTMLElement, { header$, viewport$ }: WatchAtOptions
el: HTMLElement, { viewport$, header$ }: WatchAtOptions
): Observable<Viewport> {
const size$ = viewport$
.pipe(

View File

@@ -45,7 +45,7 @@ export interface ViewportOffset {
* On iOS Safari, viewport offset can be negative due to overflow scrolling.
* As this may induce strange behaviors downstream, we'll just limit it to 0.
*
* @return Viewport offset
* @returns Viewport offset
*/
export function getViewportOffset(): ViewportOffset {
return {
@@ -70,7 +70,7 @@ export function setViewportOffset(
/**
* Watch viewport offset
*
* @return Viewport offset observable
* @returns Viewport offset observable
*/
export function watchViewportOffset(): Observable<ViewportOffset> {
return merge(

View File

@@ -42,7 +42,7 @@ export interface ViewportSize {
/**
* Retrieve viewport size
*
* @return Viewport size
* @returns Viewport size
*/
export function getViewportSize(): ViewportSize {
return {
@@ -56,7 +56,7 @@ export function getViewportSize(): ViewportSize {
/**
* Watch viewport size
*
* @return Viewport size observable
* @returns Viewport size observable
*/
export function watchViewportSize(): Observable<ViewportSize> {
return fromEvent(window, "resize", { passive: true })

View File

@@ -81,7 +81,7 @@ interface WatchOptions<T extends WorkerMessage> {
* @param worker - Web worker
* @param options - Options
*
* @return Worker message observable
* @returns Worker message observable
*/
export function watchWorker<T extends WorkerMessage>(
worker: Worker, { tx$ }: WatchOptions<T>
@@ -90,8 +90,8 @@ export function watchWorker<T extends WorkerMessage>(
/* Intercept messages from worker-like objects */
const rx$ = fromEvent<MessageEvent>(worker, "message")
.pipe<T>(
map(({ data }) => data)
)
map(({ data }) => data)
)
/* Send and receive messages, return hot observable */
return tx$

View File

@@ -20,17 +20,6 @@
* IN THE SOFTWARE.
*/
import { EMPTY, Observable, of } from "rxjs"
import {
distinctUntilChanged,
map,
scan,
shareReplay,
switchMap
} from "rxjs/operators"
import { getElement, replaceElement } from "browser"
/* ----------------------------------------------------------------------------
* Types
* ------------------------------------------------------------------------- */
@@ -38,130 +27,31 @@ import { getElement, replaceElement } from "browser"
/**
* Component
*/
export type Component =
export type ComponentType =
| "announce" /* Announcement bar */
| "container" /* Container */
| "content" /* Content */
| "header" /* Header */
| "header-title" /* Header title */
| "main" /* Main area */
| "navigation" /* Navigation */
| "search" /* Search */
| "search-query" /* Search input */
| "search-reset" /* Search reset */
| "search-result" /* Search results */
| "skip" /* Skip link */
| "tabs" /* Tabs */
| "source" /* Repository information */
| "tabs" /* Navigation tabs */
| "toc" /* Table of contents */
/**
* Component map
* A component
*
* @template T - Component type
* @template U - Reference type
*/
export type ComponentMap = {
[P in Component]?: HTMLElement
}
/* ----------------------------------------------------------------------------
* Helper types
* ------------------------------------------------------------------------- */
/**
* Watch options
*/
interface WatchOptions {
document$: Observable<Document> /* Document observable */
}
/* ----------------------------------------------------------------------------
* Data
* ------------------------------------------------------------------------- */
/**
* Component map observable
*/
let components$: Observable<ComponentMap>
/* ----------------------------------------------------------------------------
* Functions
* ------------------------------------------------------------------------- */
/**
* Set up bindings to components with given names
*
* This function will maintain bindings to the elements identified by the given
* names in-between document switches and update the elements in-place.
*
* @param names - Component names
* @param options - Options
*/
export function setupComponents(
names: Component[], { document$ }: WatchOptions
): void {
components$ = document$
.pipe(
/* Build component map */
map(document => names.reduce<ComponentMap>((components, name) => {
const el = getElement(`[data-md-component=${name}]`, document)
return {
...components,
...typeof el !== "undefined" ? { [name]: el } : {}
}
}, {})),
/* Re-compute component map on document switch */
scan((prev, next) => {
for (const name of names) {
switch (name) {
/* Top-level components: update */
case "announce":
case "header-title":
case "container":
case "skip":
if (name in prev && typeof prev[name] !== "undefined") {
replaceElement(prev[name]!, next[name]!)
prev[name] = next[name]
}
break
/* All other components: rebind */
default:
if (typeof next[name] !== "undefined")
prev[name] = getElement(`[data-md-component=${name}]`)
else
delete prev[name]
}
}
return prev
}),
/* Convert to hot observable */
shareReplay({ bufferSize: 1, refCount: true })
)
}
/**
* Retrieve a component
*
* The returned observable will only re-emit if the element changed, i.e. if
* it was replaced from a document which was switched to.
*
* @template T - Element type
*
* @param name - Component name
*
* @return Component observable
*/
export function useComponent<T extends HTMLElement>(
name: Component
): Observable<T> {
return components$
.pipe(
switchMap(components => (
typeof components[name] !== "undefined"
? of(components[name] as T)
: EMPTY
)),
distinctUntilChanged()
)
}
export type Component<
T extends {} = {},
U extends HTMLElement = HTMLElement
> =
T & {
ref: U /* Component reference */
}

View File

@@ -20,28 +20,26 @@
* IN THE SOFTWARE.
*/
import { Observable, OperatorFunction, of, pipe } from "rxjs"
import {
distinctUntilKeyChanged,
map,
switchMap
} from "rxjs/operators"
import { Observable, merge } from "rxjs"
import { Viewport, watchViewportAt } from "browser"
import { Viewport, getElements } from "~/browser"
import { Header } from "../../header"
import { applyTabs } from "../react"
import { Component } from "../../_"
import { CodeBlock, mountCodeBlock } from "../code"
import { Details, mountDetails } from "../details"
import { DataTable, mountDataTable } from "../table"
/* ----------------------------------------------------------------------------
* Types
* ------------------------------------------------------------------------- */
/**
* Tabs
* Content
*/
export interface Tabs {
hidden: boolean /* Whether the tabs are hidden */
}
export type Content =
| CodeBlock
| DataTable
| Details
/* ----------------------------------------------------------------------------
* Helper types
@@ -51,9 +49,9 @@ export interface Tabs {
* Mount options
*/
interface MountOptions {
header$: Observable<Header> /* Header observable */
target$: Observable<HTMLElement> /* Location target observable */
viewport$: Observable<Viewport> /* Viewport observable */
screen$: Observable<boolean> /* Media screen observable */
print$: Observable<void> /* Print observable */
}
/* ----------------------------------------------------------------------------
@@ -61,35 +59,28 @@ interface MountOptions {
* ------------------------------------------------------------------------- */
/**
* Mount tabs from source observable
* Mount content
*
* @param el - Content element
* @param options - Options
*
* @return Operator function
* @returns Content component observable
*/
export function mountTabs(
{ header$, viewport$, screen$ }: MountOptions
): OperatorFunction<HTMLElement, Tabs> {
return pipe(
switchMap(el => screen$
.pipe(
switchMap(screen => {
export function mountContent(
el: HTMLElement, { target$, viewport$, print$ }: MountOptions
): Observable<Component<Content>> {
return merge(
/* [screen +]: Mount tabs above screen breakpoint */
if (screen) {
return watchViewportAt(el, { header$, viewport$ })
.pipe(
map(({ offset: { y } }) => ({ hidden: y >= 10 })),
distinctUntilKeyChanged("hidden"),
applyTabs(el)
)
/* Code blocks */
...getElements("pre > code", el)
.map(child => mountCodeBlock(child, { viewport$ })),
/* [screen -]: Unmount tabs below screen breakpoint */
} else {
return of({ hidden: true })
}
})
)
)
/* Data tables */
...getElements("table:not([class])", el)
.map(child => mountDataTable(child)),
/* Details */
...getElements("details", el)
.map(child => mountDetails(child, { target$, print$ }))
)
}

View File

@@ -0,0 +1,154 @@
/*
* Copyright (c) 2016-2020 Martin Donath <martin.donath@squidfunk.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
import ClipboardJS from "clipboard"
import { Observable, Subject } from "rxjs"
import {
distinctUntilKeyChanged,
finalize,
map,
tap,
withLatestFrom
} from "rxjs/operators"
import { resetFocusable, setFocusable } from "~/actions"
import {
Viewport,
getElementContentSize,
getElementSize,
watchMedia
} from "~/browser"
import { renderClipboardButton } from "~/templates"
import { Component } from "../../_"
/* ----------------------------------------------------------------------------
* Types
* ------------------------------------------------------------------------- */
/**
* Code block
*/
export interface CodeBlock {
scroll: boolean /* Code block overflows */
}
/* ----------------------------------------------------------------------------
* Helper types
* ------------------------------------------------------------------------- */
/**
* Watch options
*/
interface WatchOptions {
viewport$: Observable<Viewport> /* Viewport observable */
}
/**
* Mount options
*/
interface MountOptions {
viewport$: Observable<Viewport> /* Viewport observable */
}
/* ----------------------------------------------------------------------------
* Data
* ------------------------------------------------------------------------- */
/**
* Global index for Clipboard.js integration
*/
let index = 0
/* ----------------------------------------------------------------------------
* Functions
* ------------------------------------------------------------------------- */
/**
* Watch code block
*
* @param el - Code block element
* @param options - Options
*
* @returns Code block observable
*/
export function watchCodeBlock(
el: HTMLElement, { viewport$ }: WatchOptions
): Observable<CodeBlock> {
return viewport$
.pipe(
distinctUntilKeyChanged("size"),
map(() => {
const visible = getElementSize(el)
const content = getElementContentSize(el)
return {
scroll: content.width > visible.width
}
}),
distinctUntilKeyChanged("scroll")
)
}
/**
* Mount code block
*
* This function ensures that overflowing code blocks are focusable by keyboard,
* so they can be scrolled without a mouse to improve on accessibility.
*
* @param el - Code block element
* @param options - Options
*
* @returns Code block component observable
*/
export function mountCodeBlock(
el: HTMLElement, options: MountOptions
): Observable<Component<CodeBlock>> {
const internal$ = new Subject<CodeBlock>()
internal$
.pipe(
withLatestFrom(watchMedia("(hover)"))
)
.subscribe(([{ scroll }, hover]) => {
if (scroll && hover)
setFocusable(el)
else
resetFocusable(el)
})
/* Inject button for Clipboard.js integration */
if (ClipboardJS.isSupported()) {
const parent = el.closest("pre")!
parent.id = `__code_${index++}`
parent.insertBefore(
renderClipboardButton(parent.id),
el
)
}
/* Create and return component */
return watchCodeBlock(el, options)
.pipe(
tap(internal$),
finalize(() => internal$.complete()),
map(state => ({ ref: el, ...state }))
)
}

View File

@@ -20,47 +20,45 @@
* IN THE SOFTWARE.
*/
import { OperatorFunction, pipe } from "rxjs"
import { Observable, Subject } from "rxjs"
import {
distinctUntilKeyChanged,
filter,
finalize,
map,
switchMap
mapTo,
mergeWith,
tap
} from "rxjs/operators"
import { WorkerHandler, setToggle } from "browser"
import {
SearchMessage,
SearchMessageType,
SearchQueryMessage,
SearchTransformFn
} from "integrations"
import {
applySearchQuery,
watchSearchQuery
} from "../react"
import { Component } from "../../_"
/* ----------------------------------------------------------------------------
* Types
* ------------------------------------------------------------------------- */
/**
* Search query
* Details
*/
export interface SearchQuery {
value: string /* Query value */
focus: boolean /* Query focus */
}
export interface Details {}
/* ----------------------------------------------------------------------------
* Helper types
* ------------------------------------------------------------------------- */
/**
* Watch options
*/
interface WatchOptions {
target$: Observable<HTMLElement> /* Location target observable */
print$: Observable<void> /* Print observable */
}
/**
* Mount options
*/
interface MountOptions {
transform?: SearchTransformFn /* Transformation function */
target$: Observable<HTMLElement> /* Location target observable */
print$: Observable<void> /* Print observable */
}
/* ----------------------------------------------------------------------------
@@ -68,46 +66,50 @@ interface MountOptions {
* ------------------------------------------------------------------------- */
/**
* Mount search query from source observable
* Watch details
*
* @param handler - Worker handler
* @param el - Details element
* @param options - Options
*
* @return Operator function
* @returns Details observable
*/
export function mountSearchQuery(
{ tx$ }: WorkerHandler<SearchMessage>, options: MountOptions = {}
): OperatorFunction<HTMLInputElement, SearchQuery> {
return pipe(
switchMap(el => {
const query$ = watchSearchQuery(el, options)
/* Subscribe worker to search query */
query$
.pipe(
distinctUntilKeyChanged("value"),
map(({ value }): SearchQueryMessage => ({
type: SearchMessageType.QUERY,
data: value
}))
)
.subscribe(tx$.next.bind(tx$))
/* Toggle search on focus */
query$
.pipe(
distinctUntilKeyChanged("focus")
)
.subscribe(({ focus }) => {
if (focus)
setToggle("search", focus)
})
/* Return search query */
return query$
.pipe(
applySearchQuery(el)
)
})
)
export function watchDetails(
el: HTMLDetailsElement, { target$, print$ }: WatchOptions
): Observable<Details> {
return target$
.pipe(
map(target => target.closest("details:not([open])")!),
filter(details => el === details),
mergeWith(print$),
mapTo(el)
)
}
/**
* Mount details
*
* This function ensures that `details` tags are opened prior to printing, so
* the whole content of the page is included and on anchor jumps.
*
* @param el - Details element
* @param options - Options
*
* @returns Details component observable
*/
export function mountDetails(
el: HTMLDetailsElement, options: MountOptions
): Observable<Component<Details>> {
const internal$ = new Subject<Details>()
internal$.subscribe(() => {
el.setAttribute("open", "")
el.scrollIntoView()
})
/* Create and return component */
return watchDetails(el, options)
.pipe(
tap(internal$),
finalize(() => internal$.complete()),
mapTo({ ref: el })
)
}

View File

@@ -21,5 +21,5 @@
*/
export * from "./_"
export * from "./react"
export * from "./set"
export * from "./code"
export * from "./table"

View File

@@ -0,0 +1,67 @@
/*
* Copyright (c) 2016-2020 Martin Donath <martin.donath@squidfunk.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
import { Observable, of } from "rxjs"
import { createElement, replaceElement } from "~/browser"
import { renderTable } from "~/templates"
import { Component } from "../../_"
/* ----------------------------------------------------------------------------
* Types
* ------------------------------------------------------------------------- */
/**
* Data table
*/
export interface DataTable {}
/* ----------------------------------------------------------------------------
* Data
* ------------------------------------------------------------------------- */
/**
* Sentinel for replacement
*/
const sentinel = createElement("table")
/* ----------------------------------------------------------------------------
* Functions
* ------------------------------------------------------------------------- */
/**
* Mount data table
*
* @param el - Data table element
*
* @returns Data table component observable
*/
export function mountDataTable(
el: HTMLElement
): Observable<Component<DataTable>> {
replaceElement(el, sentinel)
replaceElement(sentinel, renderTable(el))
/* Create and return component */
return of({ ref: el })
}

View File

@@ -22,50 +22,56 @@
import {
Observable,
OperatorFunction,
Subject,
noop,
pipe
animationFrameScheduler,
merge,
of
} from "rxjs"
import {
distinctUntilKeyChanged,
delay,
finalize,
map,
observeOn,
switchMap,
tap
} from "rxjs/operators"
import { Viewport } from "browser"
import { useComponent } from "../../_"
import { Header } from "../../header"
import {
applyHeaderShadow,
watchMain
} from "../react"
resetDialogState,
setDialogMessage,
setDialogState
} from "~/actions"
import { Component } from "../_"
/* ----------------------------------------------------------------------------
* Types
* ------------------------------------------------------------------------- */
/**
* Main area
* Dialog
*/
export interface Main {
offset: number /* Main area top offset */
height: number /* Main area visible height */
active: boolean /* Scrolled past top offset */
export interface Dialog {
message: string /* Dialog message */
open: boolean /* Dialog is visible */
}
/* ----------------------------------------------------------------------------
* Helper types
* ------------------------------------------------------------------------- */
/**
* Watch options
*/
interface WatchOptions {
alert$: Subject<string> /* Alert subject */
}
/**
* Mount options
*/
interface MountOptions {
header$: Observable<Header> /* Header observable */
viewport$: Observable<Viewport> /* Viewport observable */
alert$: Subject<string> /* Alert subject */
}
/* ----------------------------------------------------------------------------
@@ -73,38 +79,58 @@ interface MountOptions {
* ------------------------------------------------------------------------- */
/**
* Mount main area from source observable
*
* The header must be connected to the main area observable outside of the
* operator function, as the header will persist in-between document switches
* while the main area is replaced. However, the header observable must be
* passed to this function, so we connect both via a long-living subject.
* Watch dialog
*
* @param _el - Dialog element
* @param options - Options
*
* @return Operator function
* @returns Dialog observable
*/
export function mountMain(
{ header$, viewport$ }: MountOptions
): OperatorFunction<HTMLElement, Main> {
const main$ = new Subject<Main>()
/* Connect to main area observable via long-living subject */
useComponent("header")
export function watchDialog(
_el: HTMLElement, { alert$ }: WatchOptions
): Observable<Dialog> {
return alert$
.pipe(
switchMap(header => main$
switchMap(message => merge(
of(true),
of(false).pipe(delay(2000))
)
.pipe(
distinctUntilKeyChanged("active"),
applyHeaderShadow(header)
map(open => ({ message, open }))
)
)
)
.subscribe(noop)
/* Return operator */
return pipe(
switchMap(el => watchMain(el, { header$, viewport$ })),
tap(main => main$.next(main)),
finalize(() => main$.complete())
)
}
/**
* Mount dialog
*
* @param el - Dialog element
* @param options - Options
*
* @returns Dialog component observable
*/
export function mountDialog(
el: HTMLElement, options: MountOptions
): Observable<Component<Dialog>> {
const internal$ = new Subject<Dialog>()
internal$
.pipe(
observeOn(animationFrameScheduler)
)
.subscribe(({ message, open }) => {
setDialogMessage(el, message)
if (open)
setDialogState(el, "open")
else
resetDialogState(el)
})
/* Create and return component */
return watchDialog(el, options)
.pipe(
tap(internal$),
finalize(() => internal$.complete()),
map(state => ({ ref: el, ...state }))
)
}

View File

@@ -20,60 +20,116 @@
* IN THE SOFTWARE.
*/
import { Observable, OperatorFunction, combineLatest, pipe } from "rxjs"
import {
NEVER,
Observable,
Subject,
animationFrameScheduler,
combineLatest,
defer,
of
} from "rxjs"
import {
bufferCount,
combineLatestWith,
distinctUntilChanged,
distinctUntilKeyChanged,
filter,
map,
observeOn,
shareReplay,
startWith,
switchMap,
zipWith
switchMap
} from "rxjs/operators"
import { feature } from "~/_"
import { resetHeaderState, setHeaderState } from "~/actions"
import {
Viewport,
getElement,
watchViewportAt
} from "browser"
watchElementSize,
watchToggle
} from "~/browser"
import { useComponent } from "../../_"
import {
applyHeaderType,
watchHeader
} from "../react"
import { Component } from "../../_"
import { Main } from "../../main"
/* ----------------------------------------------------------------------------
* Types
* ------------------------------------------------------------------------- */
/**
* Header type
*/
export type HeaderType =
| "site" /* Header shows site title */
| "page" /* Header shows page title */
/* ------------------------------------------------------------------------- */
/**
* Header
*/
export interface Header {
type: HeaderType /* Header type */
sticky: boolean /* Header stickyness */
height: number /* Header visible height */
sticky: boolean /* Header stickyness */
hidden: boolean /* User scrolled past threshold */
}
/* ----------------------------------------------------------------------------
* Helper types
* ------------------------------------------------------------------------- */
/**
* Watch options
*/
interface WatchOptions {
viewport$: Observable<Viewport> /* Viewport observable */
}
/**
* Mount options
*/
interface MountOptions {
document$: Observable<Document> /* Document observable */
viewport$: Observable<Viewport> /* Viewport observable */
header$: Observable<Header> /* Header observable */
main$: Observable<Main> /* Main area observable */
}
/* ----------------------------------------------------------------------------
* Helper functions
* ------------------------------------------------------------------------- */
/**
* Compute whether the header is hidden
*
* If the user scrolls past a certain threshold, the header can be hidden when
* scrolling down, and shown when scrolling up.
*
* @param options - Options
*
* @returns Toggle observable
*/
function isHidden({ viewport$ }: WatchOptions): Observable<boolean> {
if (!feature("header.autohide"))
return of(false)
/* Compute direction and turning point */
const direction$ = viewport$
.pipe(
map(({ offset: { y } }) => y),
bufferCount(2, 1),
map(([a, b]) => [a < b, b] as const),
distinctUntilKeyChanged(0)
)
/* Compute whether header should be hidden */
const hidden$ = combineLatest([viewport$, direction$])
.pipe(
filter(([{ offset }, [, y]]) => Math.abs(y - offset.y) > 100),
map(([, [direction]]) => direction),
distinctUntilChanged(),
)
/* Compute threshold for autohiding */
const search$ = watchToggle("search")
return combineLatest([viewport$, search$])
.pipe(
map(([{ offset }, search]) => offset.y > 400 && !search),
distinctUntilChanged(),
switchMap(active => active ? hidden$ : NEVER),
startWith(false)
)
}
/* ----------------------------------------------------------------------------
@@ -81,42 +137,73 @@ interface MountOptions {
* ------------------------------------------------------------------------- */
/**
* Mount header from source observable
* Watch header
*
* @param el - Header element
* @param options - Options
*
* @return Operator function
* @returns Header observable
*/
export function watchHeader(
el: HTMLElement, options: WatchOptions
): Observable<Header> {
return defer(() => {
const styles = getComputedStyle(el)
return of(
styles.position === "sticky" ||
styles.position === "-webkit-sticky"
)
})
.pipe(
combineLatestWith(watchElementSize(el), isHidden(options)),
map(([sticky, { height }, hidden]) => ({
height: sticky ? height : 0,
sticky,
hidden
})),
distinctUntilChanged((a, b) => (
a.sticky === b.sticky &&
a.height === b.height &&
a.hidden === b.hidden
)),
shareReplay(1)
)
}
/**
* Mount header
*
* The header must be connected to the main area observable outside of the
* operator function, as the header will persist in-between document switches
* while the main area is replaced. However, the header observable must be
* passed to this function, so we connect both via a long-living subject.
*
* @param el - Header element
* @param options - Options
*
* @returns Header component observable
*/
export function mountHeader(
{ document$, viewport$ }: MountOptions
): OperatorFunction<HTMLElement, Header> {
return pipe(
switchMap(el => {
const header$ = watchHeader(el, { document$ })
el: HTMLElement, { header$, main$ }: MountOptions
): Observable<Component<Header>> {
const internal$ = new Subject<Main>()
internal$
.pipe(
distinctUntilKeyChanged("active"),
combineLatestWith(header$),
observeOn(animationFrameScheduler)
)
.subscribe(([{ active }, { hidden }]) => {
if (active)
setHeaderState(el, hidden ? "hidden" : "shadow")
else
resetHeaderState(el)
})
/* Compute whether the header should switch to page header */
const type$ = useComponent("main")
.pipe(
map(main => getElement("h1, h2, h3, h4, h5, h6", main)!),
filter(hx => typeof hx !== "undefined"),
zipWith(useComponent("header-title")),
switchMap(([hx, title]) => watchViewportAt(hx, { header$, viewport$ })
.pipe(
map(({ offset: { y } }) => {
return y >= hx.offsetHeight ? "page" : "site"
}),
distinctUntilChanged(),
applyHeaderType(title)
)
),
startWith<HeaderType>("site")
)
/* Combine into single observable */
return combineLatest([header$, type$])
.pipe(
map(([header, type]): Header => ({ type, ...header }))
)
})
)
/* Connect to long-living subject and return component */
main$.subscribe(main => internal$.next(main))
return header$
.pipe(
map(state => ({ ref: el, ...state }))
)
}

View File

@@ -21,5 +21,4 @@
*/
export * from "./_"
export * from "./react"
export * from "./set"
export * from "./title"

View File

@@ -1,128 +0,0 @@
/*
* Copyright (c) 2016-2020 Martin Donath <martin.donath@squidfunk.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
import {
MonoTypeOperatorFunction,
Observable,
animationFrameScheduler,
of,
pipe
} from "rxjs"
import {
distinctUntilChanged,
finalize,
map,
observeOn,
shareReplay,
switchMap,
tap
} from "rxjs/operators"
import { watchElementSize } from "browser"
import { Header, HeaderType } from "../_"
import {
resetHeaderTitleActive,
setHeaderTitleActive
} from "../set"
/* ----------------------------------------------------------------------------
* Helper types
* ------------------------------------------------------------------------- */
/**
* Watch options
*/
interface WatchOptions {
document$: Observable<Document> /* Document observable */
}
/* ----------------------------------------------------------------------------
* Functions
* ------------------------------------------------------------------------- */
/**
* Watch header
*
* @param el - Header element
*
* @return Header observable
*/
export function watchHeader(
el: HTMLElement, { document$ }: WatchOptions
): Observable<Omit<Header, "type">> {
return document$
.pipe(
map(() => {
const styles = getComputedStyle(el)
return [
"sticky", /* Modern browsers */
"-webkit-sticky" /* Safari */
].includes(styles.position)
}),
distinctUntilChanged(),
switchMap(sticky => {
if (sticky) {
return watchElementSize(el)
.pipe(
map(({ height }) => ({
sticky: true,
height
}))
)
} else {
return of({
sticky: false,
height: 0
})
}
}),
shareReplay({ bufferSize: 1, refCount: true })
)
}
/* ------------------------------------------------------------------------- */
/**
* Apply header title type
*
* @param el - Header title element
*
* @return Operator function
*/
export function applyHeaderType(
el: HTMLElement
): MonoTypeOperatorFunction<HeaderType> {
return pipe(
/* Defer repaint to next animation frame */
observeOn(animationFrameScheduler),
tap(type => {
setHeaderTitleActive(el, type === "page")
}),
/* Reset on complete or error */
finalize(() => {
resetHeaderTitleActive(el)
})
)
}

View File

@@ -0,0 +1,139 @@
/*
* Copyright (c) 2016-2020 Martin Donath <martin.donath@squidfunk.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
import { NEVER, Observable, Subject, animationFrameScheduler } from "rxjs"
import {
distinctUntilKeyChanged,
finalize,
map,
observeOn,
tap
} from "rxjs/operators"
import {
resetHeaderTitleState,
setHeaderTitleState
} from "~/actions"
import {
Viewport,
getElement,
getElementSize,
watchViewportAt
} from "~/browser"
import { Component } from "../../_"
import { Header } from "../_"
/* ----------------------------------------------------------------------------
* Types
* ------------------------------------------------------------------------- */
/**
* Header
*/
export interface HeaderTitle {
active: boolean /* User scrolled past first headline */
}
/* ----------------------------------------------------------------------------
* Helper types
* ------------------------------------------------------------------------- */
/**
* Watch options
*/
interface WatchOptions {
viewport$: Observable<Viewport> /* Viewport observable */
header$: Observable<Header> /* Header observable */
}
/**
* Mount options
*/
interface MountOptions {
viewport$: Observable<Viewport> /* Viewport observable */
header$: Observable<Header> /* Header observable */
}
/* ----------------------------------------------------------------------------
* Functions
* ------------------------------------------------------------------------- */
/**
* Watch header title
*
* @param el - Heading element
* @param options - Options
*
* @returns Header title observable
*/
export function watchHeaderTitle(
el: HTMLHeadingElement, { viewport$, header$ }: WatchOptions
): Observable<HeaderTitle> {
return watchViewportAt(el, { header$, viewport$ })
.pipe(
map(({ offset: { y } }) => {
const { height } = getElementSize(el)
return {
active: y >= height
}
}),
distinctUntilKeyChanged("active")
)
}
/**
* Mount header title
*
* @param el - Header title element
* @param options - Options
*
* @returns Header title component observable
*/
export function mountHeaderTitle(
el: HTMLElement, options: MountOptions
): Observable<Component<HeaderTitle>> {
const internal$ = new Subject<HeaderTitle>()
internal$
.pipe(
observeOn(animationFrameScheduler),
)
.subscribe(({ active }) => {
if (active)
setHeaderTitleState(el, "active")
else
resetHeaderTitleState(el)
})
/* Obtain headline, if any */
const headline = getElement<HTMLHeadingElement>("article h1")
if (typeof headline === "undefined")
return NEVER
/* Create and return component */
return watchHeaderTitle(headline, options)
.pipe(
tap(internal$),
finalize(() => internal$.complete()),
map(state => ({ ref: el, ...state }))
)
}

Some files were not shown because too many files have changed in this diff Show More