mirror of
https://github.com/squidfunk/mkdocs-material.git
synced 2024-06-14 11:52:32 +03:00
Merge pull request #186 from squidfunk/chore/flow-type-checking
Integrate static type-checking with JSDoc and Flow
This commit is contained in:
commit
968516a9c8
@ -23,6 +23,10 @@
|
||||
/material
|
||||
/site
|
||||
|
||||
# Files used and generated by flow
|
||||
/lib/declarations
|
||||
/tmp
|
||||
|
||||
# Files generated by visual tests
|
||||
/gemini-report
|
||||
/tests/visual/data
|
||||
|
@ -169,7 +169,7 @@
|
||||
"space-unary-ops": 2,
|
||||
"spaced-comment": [2, "always", {
|
||||
"line": {
|
||||
"markers": ["/"],
|
||||
"markers": ["/", ":"],
|
||||
"exceptions": ["-", "+"]
|
||||
},
|
||||
"block": {
|
||||
|
8
.flowconfig
Normal file
8
.flowconfig
Normal file
@ -0,0 +1,8 @@
|
||||
[ignore]
|
||||
.*/node_modules/.*
|
||||
|
||||
[libs]
|
||||
lib/declarations/
|
||||
|
||||
[options]
|
||||
strip_root=true
|
@ -45,17 +45,25 @@ git checkout -- .
|
||||
FILES=$(git diff --cached --name-only --diff-filter=ACMR | \
|
||||
grep "\.\(js\|jsx\|scss\)$")
|
||||
|
||||
# Run the check and print indicator
|
||||
# Run check and print indicator
|
||||
if [ "$FILES" ]; then
|
||||
npm run lint --silent
|
||||
|
||||
# If we're on master, abort commit
|
||||
# If linter terminated with errors, abort commit
|
||||
if [ $? -gt 0 ]; then
|
||||
echo -e "\x1B[31m✗\x1B[0m Linter - \x1B[31m$MESSAGE\x1B[0m"
|
||||
exit 1
|
||||
else
|
||||
echo -e "\x1B[32m✓\x1B[0m Linter"
|
||||
fi
|
||||
|
||||
# If flow terminated with errors, abort commit
|
||||
npm run flow --silent > /dev/null
|
||||
if [ $? -gt 0 ]; then
|
||||
echo -e "\x1B[31m✗\x1B[0m Flow - \x1B[31m$MESSAGE\x1B[0m"
|
||||
exit 1
|
||||
else
|
||||
echo -e "\x1B[32m✓\x1B[0m Flow"
|
||||
fi
|
||||
fi
|
||||
|
||||
# We're good
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -24,6 +24,7 @@
|
||||
# NPM-related
|
||||
/node_modules
|
||||
/npm-debug.log*
|
||||
/yarn-error.log
|
||||
|
||||
# Files generated by build
|
||||
/build
|
||||
@ -31,6 +32,9 @@
|
||||
/MANIFEST
|
||||
/site
|
||||
|
||||
# Files generated by flow typechecker
|
||||
/tmp
|
||||
|
||||
# Files generated by visual tests
|
||||
/gemini-report
|
||||
/tests/visual/baseline/local
|
||||
|
@ -55,9 +55,11 @@ let args = yargs
|
||||
.default("sourcemaps", false) /* Create sourcemaps */
|
||||
.argv
|
||||
|
||||
/* Only use the last value seen, so overrides are possible */
|
||||
/* Only use the last seen value if boolean, so overrides are possible */
|
||||
args = Object.keys(args).reduce((result, arg) => {
|
||||
result[arg] = [].concat(args[arg]).pop()
|
||||
result[arg] = Array.isArray(args[arg]) && typeof args[arg][0] === "boolean"
|
||||
? [].concat(args[arg]).pop()
|
||||
: args[arg]
|
||||
return result
|
||||
}, {})
|
||||
|
||||
@ -147,19 +149,28 @@ gulp.task("assets:images:clean",
|
||||
|
||||
/*
|
||||
* Build application logic
|
||||
*
|
||||
* When revisioning, the build must be serialized due to race conditions
|
||||
* happening when two tasks try to write manifest.json simultaneously
|
||||
*/
|
||||
gulp.task("assets:javascripts:build:application",
|
||||
load("assets/javascripts/build/application"))
|
||||
gulp.task("assets:javascripts:build:application", args.revision ? [
|
||||
"assets:stylesheets:build"
|
||||
] : [], load("assets/javascripts/build/application"))
|
||||
|
||||
/*
|
||||
* Build custom modernizr
|
||||
*
|
||||
* When revisioning, the build must be serialized due to race conditions
|
||||
* happening when two tasks try to write manifest.json simultaneously
|
||||
*/
|
||||
gulp.task("assets:javascripts:build:modernizr", [
|
||||
"assets:stylesheets:build"
|
||||
], load("assets/javascripts/build/modernizr"))
|
||||
].concat(args.revision ? [
|
||||
"assets:javascripts:build:application"
|
||||
] : []), load("assets/javascripts/build/modernizr"))
|
||||
|
||||
/*
|
||||
* Build application logic and modernizr
|
||||
* Build application logic and Modernizr
|
||||
*/
|
||||
gulp.task("assets:javascripts:build", (args.clean ? [
|
||||
"assets:javascripts:clean"
|
||||
@ -178,6 +189,12 @@ gulp.task("assets:javascripts:build", (args.clean ? [
|
||||
gulp.task("assets:javascripts:clean",
|
||||
load("assets/javascripts/clean"))
|
||||
|
||||
/*
|
||||
* Annotate JavaScript
|
||||
*/
|
||||
gulp.task("assets:javascripts:annotate",
|
||||
load("assets/javascripts/annotate"))
|
||||
|
||||
/*
|
||||
* Lint JavaScript
|
||||
*/
|
||||
|
36
lib/declarations/fastclick.js
Normal file
36
lib/declarations/fastclick.js
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2016-2017 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.
|
||||
*/
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Declarations
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
declare module "fastclick" {
|
||||
|
||||
/* Type: FastClick */
|
||||
declare type FastClick = {
|
||||
attach(name: HTMLElement): void
|
||||
}
|
||||
|
||||
/* Exports */
|
||||
declare export default FastClick
|
||||
}
|
43
lib/declarations/js-cookie.js
Normal file
43
lib/declarations/js-cookie.js
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) 2016-2017 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.
|
||||
*/
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Declarations
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
declare module "js-cookie" {
|
||||
|
||||
/* Type: Options for setting cookie values */
|
||||
declare type Options = {
|
||||
path?: string,
|
||||
expires?: number | string
|
||||
}
|
||||
|
||||
/* Type: Cookie */
|
||||
declare type Cookie = {
|
||||
getJSON(json: string): Object,
|
||||
set(key: string, value: string, options?: Options): string
|
||||
}
|
||||
|
||||
/* Exports */
|
||||
declare export default Cookie
|
||||
}
|
34
lib/declarations/jsx.js
Normal file
34
lib/declarations/jsx.js
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) 2016-2017 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.
|
||||
*/
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Declarations
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
declare class Jsx {
|
||||
static createElement(tag: string, properties?: Object,
|
||||
...children?: Array<string | number | Array<HTMLElement>>
|
||||
): HTMLElement
|
||||
}
|
||||
|
||||
/* Exports */
|
||||
declare export default Jsx
|
34
lib/declarations/lunr.js
Normal file
34
lib/declarations/lunr.js
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) 2016-2017 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.
|
||||
*/
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Declarations
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/*
|
||||
* Currently, it's not possible to export a function that returns a class type,
|
||||
* as the imports just don't correctly work with flow. As a workaround we
|
||||
* export an object until this error is fixed.
|
||||
*/
|
||||
declare module "lunr" {
|
||||
declare function exports(name: () => void): Object
|
||||
}
|
32
lib/declarations/modernizr.js
Normal file
32
lib/declarations/modernizr.js
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2016-2017 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.
|
||||
*/
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Declarations
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
declare class Modernizr {
|
||||
static addTest(name: string, test: () => boolean): void
|
||||
}
|
||||
|
||||
/* Exports */
|
||||
declare export default Modernizr
|
@ -24,13 +24,13 @@
|
||||
* Module
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
export default /* JSX */ {
|
||||
export default /* Jsx */ {
|
||||
|
||||
/**
|
||||
* Create a native DOM node from JSX's intermediate representation
|
||||
*
|
||||
* @param {string} tag - Tag name
|
||||
* @param {object} properties - Properties
|
||||
* @param {?Object} properties - Properties
|
||||
* @param {...(string|number|Array)} children - Child nodes
|
||||
* @return {HTMLElement} Native DOM node
|
||||
*/
|
||||
|
63
lib/tasks/assets/javascripts/annotate.js
Normal file
63
lib/tasks/assets/javascripts/annotate.js
Normal file
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (c) 2016-2017 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 { transform } from "babel-core"
|
||||
import jsdoc2flow from "flow-jsdoc"
|
||||
import through from "through2"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Task: annotate JavaScript
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
export default (gulp, config) => {
|
||||
return () => {
|
||||
return gulp.src(`${config.assets.src}/javascripts/**/*.{js,jsx}`)
|
||||
|
||||
/* Linting */
|
||||
.pipe(
|
||||
through.obj(function(file, enc, done) {
|
||||
if (file.isNull() || file.isStream())
|
||||
return done()
|
||||
|
||||
/* Perform Babel transformation to resolve JSX calls */
|
||||
const transformed = transform(file.contents.toString(), {
|
||||
plugins: [
|
||||
["transform-react-jsx", {
|
||||
"pragma": "Jsx.createElement"
|
||||
}]
|
||||
]
|
||||
})
|
||||
|
||||
/* Annotate contents */
|
||||
file.contents = new Buffer(jsdoc2flow(
|
||||
`/* @flow */\n\n${transformed.code}`
|
||||
).toString())
|
||||
|
||||
/* Push file to next stage */
|
||||
this.push(file)
|
||||
done()
|
||||
}))
|
||||
|
||||
/* Print errors */
|
||||
.pipe(gulp.dest("tmp/assets/javascripts"))
|
||||
}
|
||||
}
|
@ -70,7 +70,7 @@ export default (gulp, config, args) => {
|
||||
|
||||
/* Provide JSX helper */
|
||||
new webpack.ProvidePlugin({
|
||||
JSX: path.join(process.cwd(), `${config.lib}/providers/jsx.js`)
|
||||
Jsx: path.join(process.cwd(), `${config.lib}/providers/jsx.js`)
|
||||
})
|
||||
].concat(
|
||||
|
||||
|
@ -39,7 +39,7 @@ const format = eslint.getFormatter()
|
||||
|
||||
export default (gulp, config) => {
|
||||
return () => {
|
||||
return gulp.src(`${config.assets.src}/javascripts/**/*.js`)
|
||||
return gulp.src(`${config.assets.src}/javascripts/**/*.{js,jsx}`)
|
||||
|
||||
/* Linting */
|
||||
.pipe(
|
||||
|
File diff suppressed because one or more lines are too long
3
material/assets/javascripts/application-74bac261f2.js
Normal file
3
material/assets/javascripts/application-74bac261f2.js
Normal file
File diff suppressed because one or more lines are too long
@ -141,7 +141,7 @@
|
||||
{% endblock %}
|
||||
</div>
|
||||
{% block scripts %}
|
||||
<script src="{{ base_url }}/assets/javascripts/application-0dae3d4464.js"></script>
|
||||
<script src="{{ base_url }}/assets/javascripts/application-8dc3dfc020.js"></script>
|
||||
<script>app.initialize({url:{base:"{{ base_url }}"}})</script>
|
||||
{% for path in extra_javascript %}
|
||||
<script src="{{ path }}"></script>
|
||||
|
@ -3,7 +3,7 @@
|
||||
<div class="md-search__overlay"></div>
|
||||
<div class="md-search__inner">
|
||||
<form class="md-search__form" name="search">
|
||||
<input type="text" class="md-search__input" name="query" placeholder="{{ lang.t('search.placeholder') }}" accesskey="s" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false">
|
||||
<input type="text" class="md-search__input" name="query" placeholder="{{ lang.t('search.placeholder') }}" accesskey="s" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="query">
|
||||
<label class="md-icon md-search__icon" for="search"></label>
|
||||
</form>
|
||||
<div class="md-search__output">
|
||||
|
15
package.json
15
package.json
@ -25,6 +25,7 @@
|
||||
"scripts": {
|
||||
"build": "scripts/build",
|
||||
"clean": "scripts/clean",
|
||||
"flow": "scripts/flow",
|
||||
"lint": "scripts/lint",
|
||||
"start": "scripts/start",
|
||||
"test:visual:run": "scripts/test/visual/run",
|
||||
@ -34,7 +35,7 @@
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^6.7.3",
|
||||
"babel-core": "^6.0.0",
|
||||
"babel-core": "^6.23.0",
|
||||
"babel-eslint": "^7.1.1",
|
||||
"babel-loader": "^6.3.1",
|
||||
"babel-plugin-add-module-exports": "^0.2.1",
|
||||
@ -48,8 +49,10 @@
|
||||
"custom-event-polyfill": "^0.3.0",
|
||||
"del": "^2.2.2",
|
||||
"ecstatic": "^2.1.0",
|
||||
"eslint": "^3.14.0",
|
||||
"eslint": "^3.16.0",
|
||||
"fastclick": "^1.0.6",
|
||||
"flow-bin": "^0.39.0",
|
||||
"flow-jsdoc": "^0.2.2",
|
||||
"git-hooks": "^1.1.7",
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-changed": "^2.0.0",
|
||||
@ -93,14 +96,6 @@
|
||||
"chai": "^3.5.0",
|
||||
"eslint-plugin-mocha": "^4.8.0",
|
||||
"gemini": "^4.14.3",
|
||||
"karma": "^1.3.0",
|
||||
"karma-chrome-launcher": "^2.0.0",
|
||||
"karma-coverage": "^1.1.1",
|
||||
"karma-mocha": "^1.3.0",
|
||||
"karma-notify-reporter": "^1.0.1",
|
||||
"karma-sourcemap-loader": "^0.3.7",
|
||||
"karma-spec-reporter": "0.0.26",
|
||||
"karma-webpack": "^2.0.1",
|
||||
"mocha": "^3.2.0",
|
||||
"moniker": "^0.1.2",
|
||||
"saucelabs": "^1.4.0",
|
||||
|
44
scripts/flow
Executable file
44
scripts/flow
Executable file
@ -0,0 +1,44 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright (c) 2016-2017 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.
|
||||
|
||||
# Check if "npm install" was executed
|
||||
if [[ ! -d `npm bin` ]]; then
|
||||
echo "\"node_modules\" not found:"
|
||||
echo "npm install"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Annotate source files
|
||||
`npm bin`/gulp assets:javascripts:annotate "$@"
|
||||
FLOW_JSDOC=$?
|
||||
|
||||
# Run flow typecheck
|
||||
`npm bin`/flow check tmp
|
||||
FLOW=$?
|
||||
|
||||
# If one command failed, exit with error
|
||||
if [ $FLOW_JSDOC -gt 0 ] || [ $FLOW -gt 0 ]; then
|
||||
exit 1
|
||||
fi;
|
||||
|
||||
# Otherwise return with success
|
||||
exit 0
|
@ -4,7 +4,7 @@
|
||||
],
|
||||
"plugins": [
|
||||
["transform-react-jsx", {
|
||||
"pragma": "JSX.createElement"
|
||||
"pragma": "Jsx.createElement"
|
||||
}]
|
||||
]
|
||||
}
|
||||
|
@ -27,24 +27,26 @@ import Material from "./components/Material"
|
||||
* Application
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
export const initialize = config => {
|
||||
/**
|
||||
* Initialize Material for MkDocs
|
||||
*
|
||||
* @param {Object} config - Configuration
|
||||
*/
|
||||
function initialize(config) { // eslint-disable-line func-style
|
||||
|
||||
/* Initialize Modernizr and FastClick */
|
||||
new Material.Event.Listener(document, "DOMContentLoaded", () => {
|
||||
if (!(document.body instanceof HTMLElement))
|
||||
throw new ReferenceError
|
||||
|
||||
/* Attach FastClick to mitigate 300ms delay on touch devices */
|
||||
FastClick.attach(document.body)
|
||||
|
||||
/* Test for iOS */
|
||||
Modernizr.addTest("ios", () => {
|
||||
return !!navigator.userAgent.match(/(iPad|iPhone|iPod)/g)
|
||||
})
|
||||
|
||||
/* Test for web application context */
|
||||
Modernizr.addTest("standalone", () => {
|
||||
return !!navigator.standalone
|
||||
})
|
||||
|
||||
/* Attach FastClick to mitigate 300ms delay on touch devices */
|
||||
FastClick.attach(document.body)
|
||||
|
||||
/* Wrap all data tables for better overflow scrolling */
|
||||
const tables = document.querySelectorAll("table:not([class])")
|
||||
Array.prototype.forEach.call(tables, table => {
|
||||
@ -119,7 +121,7 @@ export const initialize = config => {
|
||||
new Material.Search.Lock("[data-md-toggle=search]")))
|
||||
|
||||
/* Component: search results */
|
||||
new Material.Event.Listener(document.forms.search.query, [
|
||||
new Material.Event.Listener("[data-md-component=query]", [
|
||||
"focus", "keyup"
|
||||
], new Material.Search.Result("[data-md-component=result]", () => {
|
||||
return fetch(`${config.url.base}/mkdocs/search_index.json`, {
|
||||
@ -143,6 +145,8 @@ export const initialize = config => {
|
||||
new Material.Event.Listener("[data-md-component=navigation] [href^='#']",
|
||||
"click", () => {
|
||||
const toggle = document.querySelector("[data-md-toggle=drawer]")
|
||||
if (!(toggle instanceof HTMLInputElement))
|
||||
throw new ReferenceError
|
||||
if (toggle.checked) {
|
||||
toggle.checked = false
|
||||
toggle.dispatchEvent(new CustomEvent("change"))
|
||||
@ -152,16 +156,23 @@ export const initialize = config => {
|
||||
/* Listener: focus input after opening search */
|
||||
new Material.Event.Listener("[data-md-toggle=search]", "change", ev => {
|
||||
setTimeout(toggle => {
|
||||
const query = document.forms.search.query
|
||||
if (toggle.checked)
|
||||
if (!(toggle instanceof HTMLInputElement))
|
||||
throw new ReferenceError
|
||||
if (toggle.checked) {
|
||||
const query = document.querySelector("[data-md-component=query]")
|
||||
if (!(query instanceof HTMLInputElement))
|
||||
throw new ReferenceError
|
||||
query.focus()
|
||||
}
|
||||
}, 400, ev.target)
|
||||
}).listen()
|
||||
|
||||
/* Listener: open search on focus */
|
||||
new Material.Event.MatchMedia("(min-width: 960px)",
|
||||
new Material.Event.Listener(document.forms.search.query, "focus", () => {
|
||||
new Material.Event.Listener("[data-md-component=query]", "focus", () => {
|
||||
const toggle = document.querySelector("[data-md-toggle=search]")
|
||||
if (!(toggle instanceof HTMLInputElement))
|
||||
throw new ReferenceError
|
||||
if (!toggle.checked) {
|
||||
toggle.checked = true
|
||||
toggle.dispatchEvent(new CustomEvent("change"))
|
||||
@ -172,6 +183,8 @@ export const initialize = config => {
|
||||
new Material.Event.MatchMedia("(min-width: 960px)",
|
||||
new Material.Event.Listener(document.body, "click", () => {
|
||||
const toggle = document.querySelector("[data-md-toggle=search]")
|
||||
if (!(toggle instanceof HTMLInputElement))
|
||||
throw new ReferenceError
|
||||
if (toggle.checked) {
|
||||
toggle.checked = false
|
||||
toggle.dispatchEvent(new CustomEvent("change"))
|
||||
@ -183,10 +196,15 @@ export const initialize = config => {
|
||||
const code = ev.keyCode || ev.which
|
||||
if (code === 27) {
|
||||
const toggle = document.querySelector("[data-md-toggle=search]")
|
||||
if (!(toggle instanceof HTMLInputElement))
|
||||
throw new ReferenceError
|
||||
if (toggle.checked) {
|
||||
toggle.checked = false
|
||||
toggle.dispatchEvent(new CustomEvent("change"))
|
||||
document.forms.search.query.blur()
|
||||
const query = document.querySelector("[data-md-component=query]")
|
||||
if (!(query instanceof HTMLInputElement))
|
||||
throw new ReferenceError
|
||||
query.focus()
|
||||
}
|
||||
}
|
||||
}).listen()
|
||||
@ -204,13 +222,16 @@ export const initialize = config => {
|
||||
/* Retrieve facts for the given repository type */
|
||||
;(() => {
|
||||
const el = document.querySelector("[data-md-source]")
|
||||
if (!el) return Promise.resolve([])
|
||||
if (!el)
|
||||
return Promise.resolve([])
|
||||
else if (!(el instanceof HTMLAnchorElement))
|
||||
throw new ReferenceError
|
||||
switch (el.dataset.mdSource) {
|
||||
case "github": return new Material.Source.Adapter.GitHub(el).fetch()
|
||||
default: return Promise.resolve([])
|
||||
}
|
||||
|
||||
/* Render repository source information */
|
||||
/* Render repository information */
|
||||
})().then(facts => {
|
||||
const sources = document.querySelectorAll("[data-md-source]")
|
||||
Array.prototype.forEach.call(sources, source => {
|
||||
@ -219,3 +240,11 @@ export const initialize = config => {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Exports
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
export {
|
||||
initialize
|
||||
}
|
||||
|
@ -30,14 +30,22 @@ export default class Listener {
|
||||
* Generic event listener
|
||||
*
|
||||
* @constructor
|
||||
* @param {(string|NodeList<HTMLElement>)} els - Selector or HTML elements
|
||||
* @param {Array.<string>} events - Event names
|
||||
* @param {(object|function)} handler - Handler to be invoked
|
||||
*
|
||||
* @property {(Array<EventTarget>)} els_ - Event targets
|
||||
* @property {Object} handler_- Event handlers
|
||||
* @property {Array<string>} events_ - Event names
|
||||
* @property {Function} update_ - Update handler
|
||||
*
|
||||
* @param {?(string|EventTarget|NodeList<EventTarget>)} els -
|
||||
* Selector or Event targets
|
||||
* @param {(string|Array<string>)} events - Event names
|
||||
* @param {(Object|Function)} handler - Handler to be invoked
|
||||
*/
|
||||
constructor(els, events, handler) {
|
||||
this.els_ = (typeof els === "string")
|
||||
this.els_ = Array.prototype.slice.call(
|
||||
(typeof els === "string")
|
||||
? document.querySelectorAll(els)
|
||||
: [].concat(els)
|
||||
: [].concat(els))
|
||||
|
||||
/* Set handler as function or directly as object */
|
||||
this.handler_ = typeof handler === "function"
|
||||
@ -53,7 +61,7 @@ export default class Listener {
|
||||
* Register listener for all relevant events
|
||||
*/
|
||||
listen() {
|
||||
Array.prototype.forEach.call(this.els_, el => {
|
||||
this.els_.forEach(el => {
|
||||
this.events_.forEach(event => {
|
||||
el.addEventListener(event, this.update_, false)
|
||||
})
|
||||
@ -68,7 +76,7 @@ export default class Listener {
|
||||
* Unregister listener for all relevant events
|
||||
*/
|
||||
unlisten() {
|
||||
Array.prototype.forEach.call(this.els_, el => {
|
||||
this.els_.forEach(el => {
|
||||
this.events_.forEach(event => {
|
||||
el.removeEventListener(event, this.update_)
|
||||
})
|
||||
|
@ -20,6 +20,8 @@
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Listener from "./Listener" // eslint-disable-line no-unused-vars
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Class
|
||||
* ------------------------------------------------------------------------- */
|
||||
@ -33,6 +35,9 @@ export default class MatchMedia {
|
||||
* switches the given listeners on or off.
|
||||
*
|
||||
* @constructor
|
||||
*
|
||||
* @property {Function} handler_ - Media query event handler
|
||||
*
|
||||
* @param {string} query - Media query to test for
|
||||
* @param {Listener} listener - Event listener
|
||||
*/
|
||||
|
@ -27,9 +27,16 @@
|
||||
export default class Blur {
|
||||
|
||||
/**
|
||||
* Blur anchors within the navigation above current page y-offset
|
||||
* Blur links within the table of contents above current page y-offset
|
||||
*
|
||||
* @constructor
|
||||
*
|
||||
* @property {NodeList<HTMLElement>} els_ - Table of contents links
|
||||
* @property {Array<HTMLElement>} anchors_ - Referenced anchor nodes
|
||||
* @property {number} index_ - Current link index
|
||||
* @property {number} offset_ - Current page y-offset
|
||||
* @property {boolean} dir_ - Scroll direction change
|
||||
*
|
||||
* @param {(string|NodeList<HTMLElement>)} els - Selector or HTML elements
|
||||
*/
|
||||
constructor(els) {
|
||||
@ -45,20 +52,21 @@ export default class Blur {
|
||||
this.dir_ = false
|
||||
|
||||
/* Index anchor node offsets for fast lookup */
|
||||
this.anchors_ = [].map.call(this.els_, el => {
|
||||
return document.getElementById(el.hash.substring(1))
|
||||
})
|
||||
this.anchors_ = [].reduce.call(this.els_, (anchors, el) => {
|
||||
return anchors.concat(
|
||||
document.getElementById(el.hash.substring(1)) || [])
|
||||
}, [])
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize anchor states
|
||||
* Initialize blur states
|
||||
*/
|
||||
setup() {
|
||||
this.update()
|
||||
}
|
||||
|
||||
/**
|
||||
* Update anchor states
|
||||
* Update blur states
|
||||
*
|
||||
* Deduct the static offset of the header (56px) and sidebar offset (24px),
|
||||
* see _permalinks.scss for more information.
|
||||
@ -67,7 +75,7 @@ export default class Blur {
|
||||
const offset = window.pageYOffset
|
||||
const dir = this.offset_ - offset < 0
|
||||
|
||||
/* Hack: reset index if direction changed, to catch very fast scrolling,
|
||||
/* Hack: reset index if direction changed to catch very fast scrolling,
|
||||
because otherwise we would have to register a timer and that sucks */
|
||||
if (this.dir_ !== dir)
|
||||
this.index_ = dir
|
||||
@ -109,7 +117,7 @@ export default class Blur {
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset anchor states
|
||||
* Reset blur states
|
||||
*/
|
||||
reset() {
|
||||
Array.prototype.forEach.call(this.els_, el => {
|
||||
|
@ -30,12 +30,18 @@ export default class Collapse {
|
||||
* Expand or collapse navigation on toggle
|
||||
*
|
||||
* @constructor
|
||||
*
|
||||
* @property {HTMLElement} el_ - Navigation list
|
||||
*
|
||||
* @param {(string|HTMLElement)} el - Selector or HTML element
|
||||
*/
|
||||
constructor(el) {
|
||||
this.el_ = (typeof el === "string")
|
||||
const ref = (typeof el === "string")
|
||||
? document.querySelector(el)
|
||||
: el
|
||||
if (!(ref instanceof HTMLElement))
|
||||
throw new ReferenceError
|
||||
this.el_ = ref
|
||||
}
|
||||
|
||||
/**
|
||||
@ -75,11 +81,16 @@ export default class Collapse {
|
||||
|
||||
/* Remove state on end of transition */
|
||||
const end = ev => {
|
||||
ev.target.removeAttribute("data-md-state")
|
||||
ev.target.style.maxHeight = ""
|
||||
const target = ev.target
|
||||
if (!(target instanceof HTMLElement))
|
||||
throw new ReferenceError
|
||||
|
||||
/* Reset height and state */
|
||||
target.removeAttribute("data-md-state")
|
||||
target.style.maxHeight = ""
|
||||
|
||||
/* Only fire once, so directly remove event listener */
|
||||
ev.target.removeEventListener("transitionend", end)
|
||||
target.removeEventListener("transitionend", end)
|
||||
}
|
||||
this.el_.addEventListener("transitionend", end, false)
|
||||
}
|
||||
|
@ -30,12 +30,18 @@ export default class Scrolling {
|
||||
* Set overflow scrolling on the current active pane (for iOS)
|
||||
*
|
||||
* @constructor
|
||||
*
|
||||
* @property {HTMLElement} el_ - Primary navigation
|
||||
*
|
||||
* @param {(string|HTMLElement)} el - Selector or HTML element
|
||||
*/
|
||||
constructor(el) {
|
||||
this.el_ = (typeof el === "string")
|
||||
const ref = (typeof el === "string")
|
||||
? document.querySelector(el)
|
||||
: el
|
||||
if (!(ref instanceof HTMLElement))
|
||||
throw new ReferenceError
|
||||
this.el_ = ref
|
||||
}
|
||||
|
||||
/**
|
||||
@ -49,13 +55,22 @@ export default class Scrolling {
|
||||
/* Find all toggles and check which one is active */
|
||||
const toggles = this.el_.querySelectorAll("[data-md-toggle]")
|
||||
Array.prototype.forEach.call(toggles, toggle => {
|
||||
if (!(toggle instanceof HTMLInputElement))
|
||||
throw new ReferenceError
|
||||
if (toggle.checked) {
|
||||
|
||||
/* Find corresponding navigational pane */
|
||||
let pane = toggle.nextElementSibling
|
||||
while (pane.tagName !== "NAV")
|
||||
if (!(pane instanceof HTMLElement))
|
||||
throw new ReferenceError
|
||||
while (pane.tagName !== "NAV" && pane.nextElementSibling)
|
||||
pane = pane.nextElementSibling
|
||||
|
||||
/* Check references */
|
||||
if (!(toggle.parentNode instanceof HTMLElement) ||
|
||||
!(toggle.parentNode.parentNode instanceof HTMLElement))
|
||||
throw new ReferenceError
|
||||
|
||||
/* Find current and parent list elements */
|
||||
const parent = toggle.parentNode.parentNode
|
||||
const target = pane.children[pane.children.length - 1]
|
||||
@ -73,34 +88,48 @@ export default class Scrolling {
|
||||
* @param {Event} ev - Change event
|
||||
*/
|
||||
update(ev) {
|
||||
const target = ev.target
|
||||
if (!(target instanceof HTMLElement))
|
||||
throw new ReferenceError
|
||||
|
||||
/* Find corresponding navigational pane */
|
||||
let pane = ev.target.nextElementSibling
|
||||
while (pane.tagName !== "NAV")
|
||||
let pane = target.nextElementSibling
|
||||
if (!(pane instanceof HTMLElement))
|
||||
throw new ReferenceError
|
||||
while (pane.tagName !== "NAV" && pane.nextElementSibling)
|
||||
pane = pane.nextElementSibling
|
||||
|
||||
/* Find current and parent list elements */
|
||||
const parent = ev.target.parentNode.parentNode
|
||||
const target = pane.children[pane.children.length - 1]
|
||||
/* Check references */
|
||||
if (!(target.parentNode instanceof HTMLElement) ||
|
||||
!(target.parentNode.parentNode instanceof HTMLElement))
|
||||
throw new ReferenceError
|
||||
|
||||
/* Find parent and active panes */
|
||||
const parent = target.parentNode.parentNode
|
||||
const active = pane.children[pane.children.length - 1]
|
||||
|
||||
/* Always reset all lists when transitioning */
|
||||
parent.style.webkitOverflowScrolling = ""
|
||||
target.style.webkitOverflowScrolling = ""
|
||||
active.style.webkitOverflowScrolling = ""
|
||||
|
||||
/* Set overflow scrolling on parent */
|
||||
if (!ev.target.checked) {
|
||||
/* Set overflow scrolling on parent pane */
|
||||
if (!target.checked) {
|
||||
const end = () => {
|
||||
if (pane instanceof HTMLElement) {
|
||||
parent.style.webkitOverflowScrolling = "touch"
|
||||
pane.removeEventListener("transitionend", end)
|
||||
}
|
||||
}
|
||||
pane.addEventListener("transitionend", end, false)
|
||||
}
|
||||
|
||||
/* Set overflow scrolling on target */
|
||||
if (ev.target.checked) {
|
||||
/* Set overflow scrolling on active pane */
|
||||
if (target.checked) {
|
||||
const end = () => {
|
||||
target.style.webkitOverflowScrolling = "touch"
|
||||
pane.removeEventListener("transitionend", end, false)
|
||||
if (pane instanceof HTMLElement) {
|
||||
active.style.webkitOverflowScrolling = "touch"
|
||||
pane.removeEventListener("transitionend", end)
|
||||
}
|
||||
}
|
||||
pane.addEventListener("transitionend", end, false)
|
||||
}
|
||||
@ -117,20 +146,29 @@ export default class Scrolling {
|
||||
/* Find all toggles and check which one is active */
|
||||
const toggles = this.el_.querySelectorAll("[data-md-toggle]")
|
||||
Array.prototype.forEach.call(toggles, toggle => {
|
||||
if (!(toggle instanceof HTMLInputElement))
|
||||
throw new ReferenceError
|
||||
if (toggle.checked) {
|
||||
|
||||
/* Find corresponding navigational pane */
|
||||
let pane = toggle.nextElementSibling
|
||||
while (pane.tagName !== "NAV")
|
||||
if (!(pane instanceof HTMLElement))
|
||||
throw new ReferenceError
|
||||
while (pane.tagName !== "NAV" && pane.nextElementSibling)
|
||||
pane = pane.nextElementSibling
|
||||
|
||||
/* Find current and parent list elements */
|
||||
/* Check references */
|
||||
if (!(toggle.parentNode instanceof HTMLElement) ||
|
||||
!(toggle.parentNode.parentNode instanceof HTMLElement))
|
||||
throw new ReferenceError
|
||||
|
||||
/* Find parent and active panes */
|
||||
const parent = toggle.parentNode.parentNode
|
||||
const target = pane.children[pane.children.length - 1]
|
||||
const active = pane.children[pane.children.length - 1]
|
||||
|
||||
/* Always reset all lists when transitioning */
|
||||
parent.style.webkitOverflowScrolling = ""
|
||||
target.style.webkitOverflowScrolling = ""
|
||||
active.style.webkitOverflowScrolling = ""
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -30,12 +30,25 @@ export default class Lock {
|
||||
* Lock body for full-screen search modal
|
||||
*
|
||||
* @constructor
|
||||
*
|
||||
* @property {HTMLInputElement} el_ - Lock toggle
|
||||
* @property {HTMLElement} lock_ - Element to lock (document body)
|
||||
* @property {number} offset_ - Current page y-offset
|
||||
*
|
||||
* @param {(string|HTMLElement)} el - Selector or HTML element
|
||||
*/
|
||||
constructor(el) {
|
||||
this.el_ = (typeof el === "string")
|
||||
const ref = (typeof el === "string")
|
||||
? document.querySelector(el)
|
||||
: el
|
||||
if (!(ref instanceof HTMLInputElement))
|
||||
throw new ReferenceError
|
||||
this.el_ = ref
|
||||
|
||||
/* Retrieve element to lock (= body) */
|
||||
if (!document.body)
|
||||
throw new ReferenceError
|
||||
this.lock_ = document.body
|
||||
}
|
||||
|
||||
/**
|
||||
@ -60,13 +73,13 @@ export default class Lock {
|
||||
|
||||
/* Lock body after finishing transition */
|
||||
if (this.el_.checked) {
|
||||
document.body.dataset.mdState = "lock"
|
||||
this.lock_.dataset.mdState = "lock"
|
||||
}
|
||||
}, 400)
|
||||
|
||||
/* Exiting search mode */
|
||||
} else {
|
||||
document.body.dataset.mdState = ""
|
||||
this.lock_.dataset.mdState = ""
|
||||
|
||||
/* Scroll to former position, but wait for 100ms to prevent flashes on
|
||||
iOS. A short timeout seems to do the trick */
|
||||
@ -81,8 +94,8 @@ export default class Lock {
|
||||
* Reset locked state and page y-offset
|
||||
*/
|
||||
reset() {
|
||||
if (document.body.dataset.mdState === "lock")
|
||||
if (this.lock_.dataset.mdState === "lock")
|
||||
window.scrollTo(0, this.offset_)
|
||||
document.body.dataset.mdState = ""
|
||||
this.lock_.dataset.mdState = ""
|
||||
}
|
||||
}
|
||||
|
@ -32,13 +32,24 @@ export default class Result {
|
||||
* Perform search and update results on keyboard events
|
||||
*
|
||||
* @constructor
|
||||
*
|
||||
* @property {HTMLElement} el_ - Search result container
|
||||
* @property {(Array<Object>|Function)} data_ - Raw document data
|
||||
* @property {Object} docs_ - Indexed documents
|
||||
* @property {HTMLElement} meta_ - Search meta information
|
||||
* @property {HTMLElement} list_ - Search result list
|
||||
* @property {Object} index_ - Search index
|
||||
*
|
||||
* @param {(string|HTMLElement)} el - Selector or HTML element
|
||||
* @param {(Array.<object>|Function)} data - Promise or array providing data
|
||||
* @param {(Array<Object>|Function)} data - Function providing data or array
|
||||
*/
|
||||
constructor(el, data) {
|
||||
this.el_ = (typeof el === "string")
|
||||
const ref = (typeof el === "string")
|
||||
? document.querySelector(el)
|
||||
: el
|
||||
if (!(ref instanceof HTMLElement))
|
||||
throw new ReferenceError
|
||||
this.el_ = ref
|
||||
|
||||
/* Set data and create metadata and list elements */
|
||||
this.data_ = data
|
||||
@ -54,12 +65,20 @@ export default class Result {
|
||||
/* Inject created elements */
|
||||
this.el_.appendChild(this.meta_)
|
||||
this.el_.appendChild(this.list_)
|
||||
}
|
||||
|
||||
/* Truncate a string after the given number of characters - this is not
|
||||
a reasonable approach, since the summaries kind of suck. It would be
|
||||
better to create something more intelligent, highlighting the search
|
||||
occurrences and making a better summary out of it */
|
||||
this.truncate_ = function(string, n) {
|
||||
/**
|
||||
* Truncate a string after the given number of character
|
||||
*
|
||||
* This is not a reasonable approach, since the summaries kind of suck. It
|
||||
* would be better to create something more intelligent, highlighting the
|
||||
* search occurrences and making a better summary out of it
|
||||
*
|
||||
* @param {string} string - String to be truncated
|
||||
* @param {number} n - Number of characters
|
||||
* @return {string} Truncated string
|
||||
*/
|
||||
truncate_(string, n) {
|
||||
let i = n
|
||||
if (string.length > i) {
|
||||
while (string[i] !== " " && --i > 0);
|
||||
@ -67,7 +86,6 @@ export default class Result {
|
||||
}
|
||||
return string
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update search results
|
||||
@ -90,7 +108,7 @@ export default class Result {
|
||||
})
|
||||
|
||||
/* Index documents */
|
||||
this.data_ = data.reduce((docs, doc) => {
|
||||
this.docs_ = data.reduce((docs, doc) => {
|
||||
this.index_.add(doc)
|
||||
docs[doc.location] = doc
|
||||
return docs
|
||||
@ -104,15 +122,20 @@ export default class Result {
|
||||
: init(this.data_)
|
||||
}, 250)
|
||||
|
||||
/* Execute search on new input event after clearing current list */
|
||||
/* Execute search on new input event */
|
||||
} else if (ev.type === "keyup") {
|
||||
const target = ev.target
|
||||
if (!(target instanceof HTMLInputElement))
|
||||
throw new ReferenceError
|
||||
|
||||
/* Clear current list */
|
||||
while (this.list_.firstChild)
|
||||
this.list_.removeChild(this.list_.firstChild)
|
||||
|
||||
/* Perform search on index and render documents */
|
||||
const result = this.index_.search(ev.target.value)
|
||||
const result = this.index_.search(target.value)
|
||||
result.forEach(item => {
|
||||
const doc = this.data_[item.ref]
|
||||
const doc = this.docs_[item.ref]
|
||||
|
||||
/* Check if it's a anchor link on the current page */
|
||||
let [pathname] = doc.location.split("#")
|
||||
@ -143,6 +166,8 @@ export default class Result {
|
||||
Array.prototype.forEach.call(anchors, anchor => {
|
||||
anchor.addEventListener("click", ev2 => {
|
||||
const toggle = document.querySelector("[data-md-toggle=search]")
|
||||
if (!(toggle instanceof HTMLInputElement))
|
||||
throw new ReferenceError
|
||||
if (toggle.checked) {
|
||||
toggle.checked = false
|
||||
toggle.dispatchEvent(new CustomEvent("change"))
|
||||
|
@ -30,15 +30,25 @@ export default class Position {
|
||||
* Set sidebars to locked state and limit height to parent node
|
||||
*
|
||||
* @constructor
|
||||
*
|
||||
* @property {HTMLElement} el_ - Sidebar
|
||||
* @property {HTMLElement} parent_ - Sidebar container
|
||||
* @property {number} height_ - Current sidebar height
|
||||
* @property {number} offset_ - Current page y-offset
|
||||
*
|
||||
* @param {(string|HTMLElement)} el - Selector or HTML element
|
||||
*/
|
||||
constructor(el) {
|
||||
this.el_ = (typeof el === "string")
|
||||
const ref = (typeof el === "string")
|
||||
? document.querySelector(el)
|
||||
: el
|
||||
if (!(ref instanceof HTMLElement) ||
|
||||
!(ref.parentNode instanceof HTMLElement))
|
||||
throw new ReferenceError
|
||||
this.el_ = ref
|
||||
|
||||
/* Initialize parent container and current height */
|
||||
this.parent_ = this.el_.parentNode
|
||||
this.parent_ = ref.parentNode
|
||||
this.height_ = 0
|
||||
}
|
||||
|
||||
@ -65,15 +75,15 @@ export default class Position {
|
||||
const visible = window.innerHeight
|
||||
|
||||
/* Calculate bounds of sidebar container */
|
||||
this.bounds_ = {
|
||||
const bounds = {
|
||||
top: this.parent_.offsetTop,
|
||||
bottom: this.parent_.offsetTop + this.parent_.offsetHeight
|
||||
}
|
||||
|
||||
/* Calculate new offset and height */
|
||||
const height = visible - this.bounds_.top
|
||||
const height = visible - bounds.top
|
||||
- Math.max(0, this.offset_ - offset)
|
||||
- Math.max(0, offset + visible - this.bounds_.bottom)
|
||||
- Math.max(0, offset + visible - bounds.bottom)
|
||||
|
||||
/* If height changed, update element */
|
||||
if (height !== this.height_)
|
||||
|
@ -29,16 +29,25 @@ import Cookies from "js-cookie"
|
||||
export default class Abstract {
|
||||
|
||||
/**
|
||||
* Retrieve source information
|
||||
* Retrieve repository information
|
||||
*
|
||||
* @constructor
|
||||
* @param {(string|HTMLElement)} el - Selector or HTML element
|
||||
*
|
||||
* @property {HTMLAnchorElement} el_ - Link to repository
|
||||
* @property {string} base_ - API base URL
|
||||
* @property {number} salt_ - Unique identifier
|
||||
*
|
||||
* @param {(string|HTMLAnchorElement)} el - Selector or HTML element
|
||||
*/
|
||||
constructor(el) {
|
||||
this.el_ = (typeof el === "string")
|
||||
const ref = (typeof el === "string")
|
||||
? document.querySelector(el)
|
||||
: el
|
||||
|
||||
if (!(ref instanceof HTMLAnchorElement))
|
||||
throw new ReferenceError
|
||||
this.el_ = ref
|
||||
|
||||
/* Retrieve base URL */
|
||||
this.base_ = this.el_.href
|
||||
this.salt_ = this.hash_(this.base_)
|
||||
@ -47,7 +56,7 @@ export default class Abstract {
|
||||
/**
|
||||
* Retrieve data from Cookie or fetch from respective API
|
||||
*
|
||||
* @return {Promise} Promise that returns an array of facts
|
||||
* @return {Promise<Array<string>>} Promise that returns an array of facts
|
||||
*/
|
||||
fetch() {
|
||||
return new Promise(resolve => {
|
||||
@ -70,7 +79,6 @@ export default class Abstract {
|
||||
* Abstract private function that fetches relevant repository information
|
||||
*
|
||||
* @abstract
|
||||
* @return {Promise} Promise that provides the facts in an array
|
||||
*/
|
||||
fetch_() {
|
||||
throw new Error("fetch_(): Not implemented")
|
||||
@ -79,15 +87,15 @@ export default class Abstract {
|
||||
/**
|
||||
* Format a number with suffix
|
||||
*
|
||||
* @param {Number} number - Number to format
|
||||
* @return {Number} Formatted number
|
||||
* @param {number} number - Number to format
|
||||
* @return {string} Formatted number
|
||||
*/
|
||||
format_(number) {
|
||||
if (number > 10000)
|
||||
return `${(number / 1000).toFixed(0)}k`
|
||||
else if (number > 1000)
|
||||
return `${(number / 1000).toFixed(1)}k`
|
||||
return number
|
||||
return `${number}`
|
||||
}
|
||||
|
||||
/**
|
||||
@ -96,7 +104,7 @@ export default class Abstract {
|
||||
* Taken from http://stackoverflow.com/a/7616484/1065584
|
||||
*
|
||||
* @param {string} str - Input string
|
||||
* @return {string} Hashed string
|
||||
* @return {number} Hashed string
|
||||
*/
|
||||
hash_(str) {
|
||||
let hash = 0
|
||||
|
@ -29,10 +29,10 @@ import Abstract from "./Abstract"
|
||||
export default class GitHub extends Abstract {
|
||||
|
||||
/**
|
||||
* Retrieve source information from GitHub
|
||||
* Retrieve repository information from GitHub
|
||||
*
|
||||
* @constructor
|
||||
* @param {(string|HTMLElement)} el - Selector or HTML element
|
||||
* @param {(string|HTMLAnchorElement)} el - Selector or HTML element
|
||||
*/
|
||||
constructor(el) {
|
||||
super(el)
|
||||
@ -42,9 +42,9 @@ export default class GitHub extends Abstract {
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch relevant source information from GitHub
|
||||
* Fetch relevant repository information from GitHub
|
||||
*
|
||||
* @return {function} Promise returning an array of facts
|
||||
* @return {Promise<Array<string>>} Promise returning an array of facts
|
||||
*/
|
||||
fetch_() {
|
||||
return fetch(this.base_)
|
||||
|
@ -30,21 +30,27 @@ export default class Repository {
|
||||
* Render repository information
|
||||
*
|
||||
* @constructor
|
||||
*
|
||||
* @property {HTMLElement} el_ - Repository information
|
||||
*
|
||||
* @param {(string|HTMLElement)} el - Selector or HTML element
|
||||
*/
|
||||
constructor(el) {
|
||||
this.el_ = (typeof el === "string")
|
||||
const ref = (typeof el === "string")
|
||||
? document.querySelector(el)
|
||||
: el
|
||||
if (!(ref instanceof HTMLElement))
|
||||
throw new ReferenceError
|
||||
this.el_ = ref
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the source repository
|
||||
* Initialize the repository
|
||||
*
|
||||
* @param {Array.<string>} facts - Facts to be rendered
|
||||
* @param {Array<string>} facts - Facts to be rendered
|
||||
*/
|
||||
initialize(facts) {
|
||||
if (facts.length)
|
||||
if (facts.length && this.el_.children.length)
|
||||
this.el_.children[this.el_.children.length - 1].appendChild(
|
||||
<ul class="md-source__facts">
|
||||
{facts.map(fact => <li class="md-source__fact">{fact}</li>)}
|
||||
|
@ -64,7 +64,7 @@
|
||||
line-height: 1.2;
|
||||
white-space: nowrap;
|
||||
|
||||
// Hovered source information
|
||||
// Hovered source container
|
||||
&:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
@ -30,7 +30,7 @@
|
||||
<input type="text" class="md-search__input" name="query"
|
||||
placeholder="{{ lang.t('search.placeholder') }}"
|
||||
accesskey="s" autocapitalize="off" autocorrect="off"
|
||||
autocomplete="off" spellcheck="false" />
|
||||
autocomplete="off" spellcheck="false" data-md-component="query" />
|
||||
<label class="md-icon md-search__icon" for="search"></label>
|
||||
</form>
|
||||
<div class="md-search__output">
|
||||
|
Loading…
Reference in New Issue
Block a user