Merge pull request #2273 from squidfunk/refactor/architecture
Material for MkDocs 7.x
54
.eslintignore
Normal 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
@@ -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
@@ -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
|
||||
|
||||
132
.stylelintrc
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 |
1
material/.icons/octicons/bug-24.svg
Normal 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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
1
material/.icons/octicons/multi-select-16.svg
Normal 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 |
1
material/.icons/octicons/multi-select-24.svg
Normal 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 |
1
material/.icons/octicons/number-16.svg
Normal 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 |
1
material/.icons/octicons/number-24.svg
Normal 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 |
|
Before Width: | Height: | Size: 466 B After Width: | Height: | Size: 466 B |
@@ -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 |
1
material/.icons/octicons/video-16.svg
Normal 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 |
1
material/.icons/octicons/video-24.svg
Normal 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 |
2
material/assets/javascripts/bundle.6b6cf576.min.js
vendored
Normal file
1
material/assets/javascripts/bundle.6b6cf576.min.js.map
Normal file
16
material/assets/javascripts/vendor.e32ed4d0.min.js
vendored
Normal file
1
material/assets/javascripts/vendor.e32ed4d0.min.js.map
Normal 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"
|
||||
}
|
||||
3
material/assets/stylesheets/main.a86e6725.min.css
vendored
Normal file
1
material/assets/stylesheets/main.a86e6725.min.css.map
Normal 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 %}
|
||||
|
||||
2
material/overrides/assets/javascripts/bundle.63116035.min.js
vendored
Normal 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
|
||||
2
material/overrides/assets/javascripts/vendor.1aa446d9.min.js
vendored
Normal 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 %}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" %}
|
||||
|
||||
@@ -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
33
package.json
@@ -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"
|
||||
},
|
||||
|
||||
125
src/assets/javascripts/_/index.ts
Normal 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]
|
||||
}
|
||||
76
src/assets/javascripts/actions/_/index.ts
Normal 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)
|
||||
}
|
||||
@@ -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")
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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")
|
||||
@@ -21,4 +21,4 @@
|
||||
*/
|
||||
|
||||
export * from "./_"
|
||||
export * from "./react"
|
||||
export * from "./title"
|
||||
@@ -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")
|
||||
30
src/assets/javascripts/actions/index.ts
Normal 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"
|
||||
24
src/assets/javascripts/actions/search/index.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (c) 2016-2020 Martin Donath <martin.donath@squidfunk.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
export * from "./query"
|
||||
export * from "./result"
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
@@ -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
|
||||
49
src/assets/javascripts/actions/source/index.ts
Normal 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)
|
||||
}
|
||||
@@ -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")
|
||||
@@ -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>()
|
||||
|
||||
5
src/assets/javascripts/browser/element/_/.eslintrc
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"rules": {
|
||||
"jsdoc/require-jsdoc": "off"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -23,5 +23,5 @@
|
||||
export * from "./_"
|
||||
export * from "./focus"
|
||||
export * from "./offset"
|
||||
export * from "./select"
|
||||
export * from "./selection"
|
||||
export * from "./size"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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}"]`)!))
|
||||
)
|
||||
}
|
||||
|
||||
@@ -21,5 +21,4 @@
|
||||
*/
|
||||
|
||||
export * from "./_"
|
||||
export * from "./base"
|
||||
export * from "./hash"
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
@@ -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]
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 })
|
||||
|
||||
@@ -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$
|
||||
|
||||
@@ -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 */
|
||||
}
|
||||
|
||||
@@ -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$ }))
|
||||
)
|
||||
}
|
||||
154
src/assets/javascripts/components/content/code/index.ts
Normal 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 }))
|
||||
)
|
||||
}
|
||||
@@ -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 })
|
||||
)
|
||||
}
|
||||
@@ -21,5 +21,5 @@
|
||||
*/
|
||||
|
||||
export * from "./_"
|
||||
export * from "./react"
|
||||
export * from "./set"
|
||||
export * from "./code"
|
||||
export * from "./table"
|
||||
67
src/assets/javascripts/components/content/table/index.ts
Normal 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 })
|
||||
}
|
||||
@@ -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 }))
|
||||
)
|
||||
}
|
||||
@@ -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 }))
|
||||
)
|
||||
}
|
||||
|
||||
@@ -21,5 +21,4 @@
|
||||
*/
|
||||
|
||||
export * from "./_"
|
||||
export * from "./react"
|
||||
export * from "./set"
|
||||
export * from "./title"
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
)
|
||||
}
|
||||
139
src/assets/javascripts/components/header/title/index.ts
Normal 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 }))
|
||||
)
|
||||
}
|
||||