Merge branch 'master' into refactor/sidebar-height-spacing
5
.babelrc
@ -2,9 +2,6 @@
|
||||
"presets": ["es2015"],
|
||||
"plugins": [
|
||||
"add-module-exports",
|
||||
"babel-root-import",
|
||||
["transform-react-jsx", {
|
||||
"pragma": "JSX.createElement"
|
||||
}]
|
||||
"babel-root-import"
|
||||
]
|
||||
}
|
||||
|
@ -18,7 +18,11 @@
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
# Build files
|
||||
# Files generated by build
|
||||
/build
|
||||
/material
|
||||
/site
|
||||
|
||||
# Files generated by visual tests
|
||||
/gemini-report
|
||||
/tests/visual/data
|
||||
|
@ -25,6 +25,6 @@ CHANGED="$(git diff-tree -r --name-only --no-commit-id ORIG_HEAD HEAD)"
|
||||
|
||||
# Perform install and prune of NPM dependencies if package.json changed
|
||||
if $(echo "$CHANGED" | grep --quiet package.json); then
|
||||
echo "Hook[post-merge]: Updating dependencies..."
|
||||
echo "Hook[post-merge]: Updating dependencies"
|
||||
npm install && npm prune
|
||||
fi
|
||||
|
@ -22,7 +22,7 @@
|
||||
|
||||
# Determine current branch
|
||||
BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
||||
echo "Hook[pre-commit]: Checking branch..."
|
||||
echo "Hook[pre-commit]: Checking branch"
|
||||
|
||||
# If we're on master, abort commit
|
||||
if [[ "$BRANCH" == "master" ]]; then
|
||||
|
@ -46,6 +46,6 @@ FILES=$(git diff --cached --name-only --diff-filter=ACMR | \
|
||||
|
||||
# Run the check and print indicator
|
||||
if [ "$FILES" ]; then
|
||||
echo "Hook[pre-commit]: Running linter..."
|
||||
echo "Hook[pre-commit]: Running linter"
|
||||
npm run lint --silent || exit 1
|
||||
fi
|
||||
|
9
.gitignore
vendored
@ -23,14 +23,19 @@
|
||||
|
||||
# NPM-related
|
||||
/node_modules
|
||||
/npm-debug.log
|
||||
/npm-debug.log*
|
||||
|
||||
# Build files
|
||||
# Files generated by build
|
||||
/build
|
||||
/manifest.json
|
||||
/MANIFEST
|
||||
/site
|
||||
|
||||
# Files generated by visual tests
|
||||
/gemini-report
|
||||
/tests/visual/baseline/local
|
||||
/tests/visual/data
|
||||
|
||||
# Distribution files
|
||||
/dist
|
||||
/mkdocs_material.egg-info
|
||||
|
@ -180,7 +180,6 @@
|
||||
"z-index"
|
||||
],
|
||||
"property-no-vendor-prefix": true,
|
||||
"root-no-standard-properties": true,
|
||||
"selector-class-pattern": "^[a-z0-9]+(-[a-z0-9]+)*(__[a-z]+)?(--[a-z]+)?$",
|
||||
"selector-descendant-combinator-no-non-space": null,
|
||||
"string-quotes": "double",
|
||||
|
37
.travis.yml
@ -23,19 +23,48 @@ sudo: false
|
||||
|
||||
# Node.js versions
|
||||
node_js:
|
||||
- 4
|
||||
- 5
|
||||
- 6
|
||||
- 7
|
||||
|
||||
# Build visual tests separately
|
||||
matrix:
|
||||
include:
|
||||
- node_js: 5
|
||||
addons:
|
||||
artifacts:
|
||||
paths:
|
||||
- gemini-report
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- gcc-4.8
|
||||
- g++-4.8
|
||||
env:
|
||||
- CXX=g++-4.8
|
||||
install: yarn install
|
||||
script: yarn run test:visual:run
|
||||
|
||||
# Limit clone depth to 5, to speed up build
|
||||
git:
|
||||
depth: 5
|
||||
|
||||
# Cache dependencies
|
||||
cache:
|
||||
pip: true
|
||||
yarn: true
|
||||
directories:
|
||||
- node_modules
|
||||
|
||||
# Install yarn as Travis doesn't support it out of the box
|
||||
before_install: npm install -g yarn
|
||||
|
||||
# Do not install optional dependencies by default
|
||||
install: yarn install --ignore-optional
|
||||
|
||||
# Install dependencies
|
||||
before_script:
|
||||
- pip install --user -r requirements.txt
|
||||
before_script: pip install --user -r requirements.txt
|
||||
|
||||
# Perform build and tests
|
||||
script: npm run build
|
||||
script: yarn run build
|
||||
|
@ -35,14 +35,17 @@ const config = {
|
||||
src: "src/assets", /* Source directory for assets */
|
||||
build: "material/assets" /* Target directory for assets */
|
||||
},
|
||||
lib: "lib", /* Libraries */
|
||||
lib: "lib", /* Libraries and tasks */
|
||||
tests: {
|
||||
visual: "tests/visual" /* Base directory for visual tests */
|
||||
},
|
||||
views: {
|
||||
src: "src", /* Source directory for views */
|
||||
build: "material" /* Target directory for views */
|
||||
}
|
||||
}
|
||||
|
||||
const args = yargs
|
||||
let args = yargs
|
||||
.default("clean", false) /* Clean before build */
|
||||
.default("karma", true) /* Karma watchdog */
|
||||
.default("lint", true) /* Lint sources */
|
||||
@ -52,6 +55,12 @@ const args = yargs
|
||||
.default("sourcemaps", false) /* Create sourcemaps */
|
||||
.argv
|
||||
|
||||
/* Only use the last value seen, so overrides are possible */
|
||||
args = Object.keys(args).reduce((result, arg) => {
|
||||
result[arg] = [].concat(args[arg]).pop()
|
||||
return result
|
||||
}, {})
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Overrides and helpers
|
||||
* ------------------------------------------------------------------------- */
|
||||
@ -110,7 +119,7 @@ const load = task => {
|
||||
* Copy favicon
|
||||
*/
|
||||
gulp.task("assets:images:build:ico", [
|
||||
args.clean ? "assets:images:clean" : null
|
||||
args.clean ? "assets:images:clean" : false
|
||||
].filter(t => t),
|
||||
load("assets/images/build/ico"))
|
||||
|
||||
@ -118,7 +127,7 @@ gulp.task("assets:images:build:ico", [
|
||||
* Copy and minify vector graphics
|
||||
*/
|
||||
gulp.task("assets:images:build:svg", [
|
||||
args.clean ? "assets:images:clean" : null
|
||||
args.clean ? "assets:images:clean" : false
|
||||
].filter(t => t),
|
||||
load("assets/images/build/svg"))
|
||||
|
||||
@ -145,8 +154,8 @@ gulp.task("assets:images:clean",
|
||||
*/
|
||||
|
||||
gulp.task("assets:javascripts:build:application", [
|
||||
args.clean ? "assets:javascripts:clean" : null,
|
||||
args.lint ? "assets:javascripts:lint" : null
|
||||
args.clean ? "assets:javascripts:clean" : false,
|
||||
args.lint ? "assets:javascripts:lint" : false
|
||||
].filter(t => t),
|
||||
load("assets/javascripts/build/application"))
|
||||
|
||||
@ -155,8 +164,8 @@ gulp.task("assets:javascripts:build:application", [
|
||||
*/
|
||||
gulp.task("assets:javascripts:build:modernizr", [
|
||||
"assets:stylesheets:build",
|
||||
args.clean ? "assets:javascripts:clean" : null,
|
||||
args.lint ? "assets:javascripts:lint" : null
|
||||
args.clean ? "assets:javascripts:clean" : false,
|
||||
args.lint ? "assets:javascripts:lint" : false
|
||||
].filter(t => t),
|
||||
load("assets/javascripts/build/modernizr"))
|
||||
|
||||
@ -188,8 +197,8 @@ gulp.task("assets:javascripts:lint",
|
||||
* Build stylesheets from SASS source
|
||||
*/
|
||||
gulp.task("assets:stylesheets:build", [
|
||||
args.clean ? "assets:stylesheets:clean" : null,
|
||||
args.lint ? "assets:stylesheets:lint" : null
|
||||
args.clean ? "assets:stylesheets:clean" : false,
|
||||
args.lint ? "assets:stylesheets:lint" : false
|
||||
].filter(t => t),
|
||||
load("assets/stylesheets/build"))
|
||||
|
||||
@ -236,10 +245,10 @@ gulp.task("assets:clean", [
|
||||
*/
|
||||
|
||||
gulp.task("views:build", [
|
||||
args.revision ? "assets:images:build" : null,
|
||||
args.revision ? "assets:stylesheets:build" : null,
|
||||
args.revision ? "assets:javascripts:build" : null,
|
||||
args.clean ? "views:clean" : null
|
||||
args.revision ? "assets:images:build" : false,
|
||||
args.revision ? "assets:stylesheets:build" : false,
|
||||
args.revision ? "assets:javascripts:build" : false,
|
||||
args.clean ? "views:clean" : false
|
||||
].filter(t => t),
|
||||
load("views/build"))
|
||||
|
||||
@ -260,7 +269,7 @@ gulp.task("mkdocs:build", [
|
||||
"assets:build",
|
||||
"views:build",
|
||||
"mkdocs:clean"
|
||||
],
|
||||
].filter(t => t),
|
||||
load("mkdocs/build"))
|
||||
|
||||
/*
|
||||
@ -276,14 +285,44 @@ gulp.task("mkdocs:serve",
|
||||
load("mkdocs/serve"))
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Tests
|
||||
* Visual tests
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/*
|
||||
* Start karma test runner
|
||||
* Generate visual tests
|
||||
*/
|
||||
gulp.task("tests:unit:watch",
|
||||
load("tests/unit/watch"))
|
||||
gulp.task("tests:visual:generate", [
|
||||
args.clean ? "tests:visual:clean" : false,
|
||||
args.clean ? "assets:build" : false,
|
||||
args.clean ? "views:build" : false
|
||||
].filter(t => t),
|
||||
load("tests/visual/generate"))
|
||||
|
||||
/*
|
||||
* Run visual tests
|
||||
*/
|
||||
gulp.task("tests:visual:run", [
|
||||
"tests:visual:generate"
|
||||
], load("tests/visual/run"))
|
||||
|
||||
/*
|
||||
* Update reference images for visual tests
|
||||
*/
|
||||
gulp.task("tests:visual:update",
|
||||
load("tests/visual/update"))
|
||||
|
||||
/*
|
||||
* Clean files generated by visual tests
|
||||
*/
|
||||
gulp.task("tests:visual:clean",
|
||||
load("tests/visual/clean"))
|
||||
|
||||
/*
|
||||
* Open a SauceConnect session for manual testing
|
||||
*/
|
||||
gulp.task("tests:visual:session", [
|
||||
"tests:visual:generate"
|
||||
], load("tests/visual/session"))
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Interface
|
||||
@ -295,7 +334,7 @@ gulp.task("tests:unit:watch",
|
||||
gulp.task("build", [
|
||||
"assets:build",
|
||||
"views:build",
|
||||
args.mkdocs ? "mkdocs:build" : null
|
||||
args.mkdocs ? "mkdocs:build" : false
|
||||
].filter(f => f))
|
||||
|
||||
/*
|
||||
|
6
lib/.eslintrc
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"rules": {
|
||||
"no-invalid-this": 0,
|
||||
"max-params": 0
|
||||
}
|
||||
}
|
@ -21,7 +21,7 @@
|
||||
*/
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Definition
|
||||
* Module
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
export default /* JSX */ {
|
||||
|
65
lib/servers/ecstatic.js
Normal file
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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 ecstatic from "ecstatic"
|
||||
import * as http from "http"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Locals
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/* Static file server */
|
||||
let server = null
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Start static file server
|
||||
*
|
||||
* @param {string} directory - Directory to serve
|
||||
* @param {number} port - Port to listen on
|
||||
* @param {Function} done - Resolve callback
|
||||
*/
|
||||
export const start = (directory, port, done) => {
|
||||
server = http.createServer(ecstatic({
|
||||
root: directory
|
||||
}))
|
||||
|
||||
/* Listen and register signal handlers */
|
||||
server.listen(port, "127.0.0.1", done)
|
||||
for (const signal of ["SIGTERM", "SIGINT", "exit"])
|
||||
process.on(signal, stop)
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop static file server
|
||||
*
|
||||
* @param {Function} done - Resolve callback
|
||||
*/
|
||||
export const stop = done => {
|
||||
if (server) {
|
||||
server.close(done)
|
||||
server = null
|
||||
}
|
||||
}
|
71
lib/servers/sauce-connect.js
Normal file
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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 launcher from "sauce-connect-launcher"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Locals
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/* SauceConnect process */
|
||||
let server = null
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Open SauceConnect tunnel
|
||||
*
|
||||
* @param {string} id - Unique identifier
|
||||
* @param {string} username - SauceConnect username
|
||||
* @param {string} accesskey - SauceConnect accesskey
|
||||
* @param {Function} done - Resolve callback
|
||||
*/
|
||||
export const start = (id, username, accesskey, done) => {
|
||||
launcher({
|
||||
username,
|
||||
accessKey: accesskey,
|
||||
tunnelIdentifier: id
|
||||
}, (err, proc) => {
|
||||
if (err)
|
||||
throw new Error(err)
|
||||
server = proc
|
||||
done()
|
||||
})
|
||||
|
||||
/* Register signal handlers */
|
||||
for (const signal of ["SIGTERM", "SIGINT", "exit"])
|
||||
process.on(signal, stop)
|
||||
}
|
||||
|
||||
/**
|
||||
* Close SauceConnect tunnel
|
||||
*
|
||||
* @param {Function} done - Resolve callback
|
||||
*/
|
||||
export const stop = done => {
|
||||
if (server) {
|
||||
server.close(done)
|
||||
server = null
|
||||
}
|
||||
}
|
@ -33,19 +33,33 @@ let server = null
|
||||
* Definition
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Start Selenium
|
||||
*
|
||||
* @param {Function} done - Resolve callback
|
||||
*/
|
||||
export const start = done => {
|
||||
selenium.start({}, (err, proc) => {
|
||||
|
||||
/* Register signal handlers */
|
||||
for (const signal of ["SIGTERM", "SIGINT", "exit"])
|
||||
process.on(signal, stop)
|
||||
if (err) {
|
||||
|
||||
/* Install selenium, if not present */
|
||||
if (/^Missing(.*)chromedriver$/.test(err.message)) {
|
||||
selenium.install(done)
|
||||
|
||||
/* Start selenium again */
|
||||
selenium.start({}, (err_, proc_) => {
|
||||
server = proc_
|
||||
new Promise(resolve => {
|
||||
selenium.install({}, resolve)
|
||||
})
|
||||
|
||||
/* Start selenium again */
|
||||
.then(() => {
|
||||
selenium.start({}, (err_, proc_) => {
|
||||
server = proc_
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
/* Otherwise, throw error */
|
||||
} else {
|
||||
throw err
|
||||
@ -53,20 +67,21 @@ export const start = done => {
|
||||
}
|
||||
|
||||
/* Remember process handle */
|
||||
server = server || proc
|
||||
server = proc
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
export const stop = () => {
|
||||
if (server)
|
||||
/**
|
||||
* Stop Selenium
|
||||
*
|
||||
* @param {Function} done - Resolve callback
|
||||
*/
|
||||
export const stop = done => {
|
||||
if (server) {
|
||||
if (typeof done === "function")
|
||||
server.on("exit", done)
|
||||
server.kill()
|
||||
server = null
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Signal handler
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/* Register signal handler for all relevant events */
|
||||
for (const signal of ["SIGTERM", "SIGINT", "exit"])
|
||||
process.on(signal, stop)
|
@ -1,5 +0,0 @@
|
||||
{
|
||||
"rules": {
|
||||
"no-invalid-this": 0
|
||||
}
|
||||
}
|
@ -50,12 +50,13 @@ export default (gulp, config, args) => {
|
||||
],
|
||||
output: {
|
||||
filename: "application.js",
|
||||
library: "Application"
|
||||
library: "app",
|
||||
libraryTarget: "window"
|
||||
},
|
||||
module: {
|
||||
|
||||
/* Transpile ES6 to ES5 with Babel */
|
||||
loaders: [
|
||||
rules: [
|
||||
{
|
||||
loader: "babel-loader",
|
||||
test: /\.jsx?$/
|
||||
@ -65,7 +66,7 @@ export default (gulp, config, args) => {
|
||||
plugins: [
|
||||
|
||||
/* Don't emit assets that include errors */
|
||||
new webpack.NoErrorsPlugin(),
|
||||
new webpack.NoEmitOnErrorsPlugin(),
|
||||
|
||||
/* Provide JSX helper */
|
||||
new webpack.ProvidePlugin({
|
||||
@ -77,19 +78,30 @@ export default (gulp, config, args) => {
|
||||
args.optimize ? [
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
compress: {
|
||||
warnings: false
|
||||
warnings: false,
|
||||
screw_ie8: true, // eslint-disable-line camelcase
|
||||
conditionals: true,
|
||||
unused: true,
|
||||
comparisons: true,
|
||||
sequences: true,
|
||||
dead_code: true, // eslint-disable-line camelcase
|
||||
evaluate: true,
|
||||
if_return: true, // eslint-disable-line camelcase
|
||||
join_vars: true // eslint-disable-line camelcase
|
||||
},
|
||||
output: {
|
||||
comments: false
|
||||
}
|
||||
})
|
||||
] : []),
|
||||
|
||||
/* Module resolver */
|
||||
resolve: {
|
||||
modulesDirectories: [
|
||||
modules: [
|
||||
"src/assets/javascripts",
|
||||
"node_modules"
|
||||
],
|
||||
extensions: [
|
||||
"",
|
||||
".js",
|
||||
".jsx"
|
||||
]
|
||||
@ -101,8 +113,8 @@ export default (gulp, config, args) => {
|
||||
},
|
||||
|
||||
/* Sourcemap support */
|
||||
devtool: args.sourcemaps ? "source-map" : ""
|
||||
}))
|
||||
devtool: args.sourcemaps ? "inline-source-map" : ""
|
||||
}, webpack))
|
||||
|
||||
/* Revisioning */
|
||||
.pipe(gulpif(args.revision, rev()))
|
||||
|
@ -23,6 +23,7 @@
|
||||
import path from "path"
|
||||
import through from "through2"
|
||||
import util from "gulp-util"
|
||||
|
||||
import { CLIEngine } from "eslint"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
|
@ -25,6 +25,7 @@ import gulpif from "gulp-if"
|
||||
import mincss from "gulp-cssnano"
|
||||
import mqpacker from "css-mqpacker"
|
||||
import postcss from "gulp-postcss"
|
||||
import pseudoclasses from "postcss-pseudo-classes"
|
||||
import rev from "gulp-rev"
|
||||
import sass from "gulp-sass"
|
||||
import sourcemaps from "gulp-sourcemaps"
|
||||
@ -54,7 +55,11 @@ export default (gulp, config, args) => {
|
||||
postcss([
|
||||
autoprefixer(),
|
||||
mqpacker
|
||||
]))
|
||||
].concat(!args.optimize ? [
|
||||
pseudoclasses({
|
||||
"restrictTo": ["hover", "focus"]
|
||||
})
|
||||
] : [])))
|
||||
|
||||
/* Minify sources */
|
||||
.pipe(gulpif(args.optimize, mincss()))
|
||||
@ -63,7 +68,7 @@ export default (gulp, config, args) => {
|
||||
.pipe(gulpif(args.revision, rev()))
|
||||
.pipe(gulpif(args.revision,
|
||||
version({ manifest: gulp.src("manifest.json") })))
|
||||
.pipe(gulpif(args.sourcemaps, sourcemaps.write(".")))
|
||||
.pipe(gulpif(args.sourcemaps, sourcemaps.write()))
|
||||
.pipe(gulp.dest(`${config.assets.build}/stylesheets`))
|
||||
.pipe(gulpif(args.revision,
|
||||
rev.manifest("manifest.json", {
|
||||
|
@ -39,7 +39,7 @@ export default () => {
|
||||
server.kill()
|
||||
|
||||
/* Spawn MkDocs server */
|
||||
server = child.spawn("mkdocs", ["serve", "-a", "0.0.0.0:8000"], {
|
||||
server = child.spawn("mkdocs", ["serve", "--dev-addr", "0.0.0.0:8000"], {
|
||||
stdio: "inherit"
|
||||
})
|
||||
}
|
||||
|
38
lib/tasks/tests/visual/clean.js
Normal file
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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 clean from "del"
|
||||
import vinyl from "vinyl-paths"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Task: clean files generated by visual tests
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
export default (gulp, config) => {
|
||||
return () => {
|
||||
return gulp.src([
|
||||
`${config.tests.visual}/data`,
|
||||
"./gemini-report"
|
||||
])
|
||||
.pipe(vinyl(clean))
|
||||
}
|
||||
}
|
63
lib/tasks/tests/visual/generate.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 child from "child_process"
|
||||
import path from "path"
|
||||
import through from "through2"
|
||||
import util from "gulp-util"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Task: generate visual tests
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
export default (gulp, config) => {
|
||||
const theme = path.resolve(process.cwd(), config.views.build)
|
||||
return () => {
|
||||
return gulp.src(`${config.tests.visual}/suites/**/mkdocs.yml`)
|
||||
.pipe(
|
||||
through.obj(function(file, enc, done) {
|
||||
if (file.isNull() || file.isStream())
|
||||
return done()
|
||||
|
||||
/* Resolve test name and destination */
|
||||
const name = path.relative(`${config.tests.visual}/suites`,
|
||||
path.dirname(file.path))
|
||||
const site = path.resolve(process.cwd(),
|
||||
`${config.tests.visual}/data`, name, "_")
|
||||
|
||||
/* Generate test fixtures with freshly built theme */
|
||||
const proc = child.spawnSync("mkdocs", [
|
||||
"build", "--site-dir", site, "--theme-dir", theme
|
||||
], {
|
||||
cwd: path.dirname(file.path)
|
||||
})
|
||||
|
||||
/* Emit error, if any */
|
||||
if (proc.status)
|
||||
this.emit("error", new util.PluginError("mkdocs",
|
||||
`Terminated with errors: ${proc.stderr.toString()}`))
|
||||
|
||||
/* Terminate */
|
||||
done()
|
||||
}))
|
||||
}
|
||||
}
|
166
lib/tasks/tests/visual/run.js
Normal file
@ -0,0 +1,166 @@
|
||||
/*
|
||||
* 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 moniker from "moniker"
|
||||
import path from "path"
|
||||
import * as ecstatic from "~/lib/servers/ecstatic"
|
||||
import * as sauce from "~/lib/servers/sauce-connect"
|
||||
import * as selenium from "~/lib/servers/selenium"
|
||||
|
||||
import Gemini from "gemini"
|
||||
import SauceLabs from "saucelabs"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Locals
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/* SauceLabs job name */
|
||||
const id = process.env.TRAVIS
|
||||
? `Travis #${process.env.TRAVIS_BUILD_NUMBER}`
|
||||
: `Local #${moniker.choose()}`
|
||||
|
||||
/* SauceLabs test results */
|
||||
const passed = {}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Task: run visual tests
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
export default (gulp, config, args) => {
|
||||
return done => {
|
||||
|
||||
/* Start static file server */
|
||||
let error = false
|
||||
new Promise(resolve => {
|
||||
ecstatic.start(`${config.tests.visual}/data`, 8000, resolve)
|
||||
|
||||
/* Create and start test runner */
|
||||
}).then(() => {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
/* Start SauceConnect tunnel */
|
||||
if (process.env.CI || process.env.SAUCE) {
|
||||
if (!process.env.SAUCE_USERNAME ||
|
||||
!process.env.SAUCE_ACCESS_KEY)
|
||||
throw new Error(
|
||||
"SauceConnect: please provide SAUCE_USERNAME " +
|
||||
"and SAUCE_ACCESS_KEY")
|
||||
|
||||
/* Start tunnel, if credentials are given */
|
||||
sauce.start(
|
||||
id,
|
||||
process.env.SAUCE_USERNAME,
|
||||
process.env.SAUCE_ACCESS_KEY,
|
||||
err => {
|
||||
return err ? reject(err) : resolve(sauce)
|
||||
})
|
||||
|
||||
/* Start Selenium */
|
||||
} else {
|
||||
selenium.start(() => resolve(selenium))
|
||||
}
|
||||
})
|
||||
|
||||
/* Setup and run Gemini */
|
||||
.then(runner => {
|
||||
const setup = require(
|
||||
path.join(process.cwd(), `${config.tests.visual}/config`,
|
||||
process.env.CI || process.env.SAUCE
|
||||
? "gemini.sauce-connect.json"
|
||||
: "gemini.selenium.json"))
|
||||
|
||||
/* Add dynamic configuration to capabilities */
|
||||
for (const key of Object.keys(setup.browsers)) {
|
||||
const caps = setup.browsers[key].desiredCapabilities
|
||||
caps.tunnelIdentifier = id
|
||||
caps.public = "private"
|
||||
caps.name = id
|
||||
|
||||
/* Adjust configuration for Travis CI */
|
||||
if (process.env.CI && process.env.TRAVIS)
|
||||
caps.public = "public"
|
||||
}
|
||||
|
||||
/* Setup Gemini and test listeners */
|
||||
const gemini = new Gemini(setup)
|
||||
if (process.env.CI || process.env.SAUCE) {
|
||||
|
||||
/* Initialize test run */
|
||||
gemini.on(gemini.events.START_BROWSER, job => {
|
||||
passed[job.sessionId] = true
|
||||
})
|
||||
|
||||
/* Update state of test run */
|
||||
gemini.on(gemini.events.TEST_RESULT, job => {
|
||||
passed[job.sessionId] = passed[job.sessionId] && job.equal
|
||||
})
|
||||
}
|
||||
|
||||
/* Run tests */
|
||||
return gemini.test(`${config.tests.visual}/suites`, {
|
||||
reporters: ["flat", "html"],
|
||||
browsers: args.browsers ? [].concat(args.browsers) : null
|
||||
})
|
||||
|
||||
/* Return runner for graceful stop */
|
||||
.then(status => {
|
||||
error = status.failed + status.errored > 0
|
||||
return runner
|
||||
})
|
||||
})
|
||||
|
||||
/* Stop test runner */
|
||||
.then(runner => {
|
||||
return new Promise(resolve => {
|
||||
runner.stop(resolve)
|
||||
})
|
||||
})
|
||||
|
||||
/* Update SauceLabs jobs with test results */
|
||||
.then(() => {
|
||||
const saucelabs = new SauceLabs({
|
||||
username: process.env.SAUCE_USERNAME,
|
||||
password: process.env.SAUCE_ACCESS_KEY
|
||||
})
|
||||
const updates = Object.keys(passed).map(sessionId => {
|
||||
return new Promise(resolve => {
|
||||
saucelabs.updateJob(sessionId, {
|
||||
passed: passed[sessionId]
|
||||
}, resolve)
|
||||
})
|
||||
})
|
||||
return Promise.all(updates)
|
||||
})
|
||||
|
||||
/* Stop static file server */
|
||||
})
|
||||
.then(() => {
|
||||
ecstatic.stop(() => {
|
||||
return error
|
||||
? done(new Error("Gemini terminated with errors"))
|
||||
: done()
|
||||
})
|
||||
}, err => {
|
||||
return done(new Error(err))
|
||||
})
|
||||
}
|
||||
}
|
74
lib/tasks/tests/visual/session.js
Normal file
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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 moniker from "moniker"
|
||||
import * as ecstatic from "~/lib/servers/ecstatic"
|
||||
import * as sauce from "~/lib/servers/sauce-connect"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Task: run visual tests
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
export default (gulp, config) => {
|
||||
return done => {
|
||||
|
||||
/* Start static file server */
|
||||
new Promise(resolve => {
|
||||
ecstatic.start(`${config.tests.visual}/data`, 8000, resolve)
|
||||
|
||||
/* Open SauceConnect tunnel */
|
||||
}).then(() => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!process.env.SAUCE_USERNAME ||
|
||||
!process.env.SAUCE_ACCESS_KEY)
|
||||
throw new Error(
|
||||
"SauceConnect: please provide SAUCE_USERNAME " +
|
||||
"and SAUCE_ACCESS_KEY")
|
||||
|
||||
/* Open tunnel */
|
||||
sauce.start(
|
||||
`Local #${moniker.choose()}`,
|
||||
process.env.SAUCE_USERNAME,
|
||||
process.env.SAUCE_ACCESS_KEY,
|
||||
err => {
|
||||
return err ? reject(err) : resolve(sauce)
|
||||
})
|
||||
})
|
||||
|
||||
/* Close tunnel on CTRL-C */
|
||||
.then(() => {
|
||||
return new Promise(resolve => {
|
||||
process.on("SIGINT", () => {
|
||||
sauce.stop(resolve)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
/* Stop static file server */
|
||||
})
|
||||
.then(() => {
|
||||
ecstatic.stop(done)
|
||||
}, err => {
|
||||
return done(err)
|
||||
})
|
||||
}
|
||||
}
|
72
lib/tasks/tests/visual/update.js
Normal file
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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 fs from "fs"
|
||||
import path from "path"
|
||||
import through from "through2"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Task: update reference images for visual tests
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
export default (gulp, config) => {
|
||||
return () => {
|
||||
const base = path.join(
|
||||
process.cwd(), `${config.tests.visual}/config`)
|
||||
|
||||
/* Read Gemini configs and map browsers to screenshot directories */
|
||||
const mapping = fs.readdirSync(base)
|
||||
.reduce((result, filename) => {
|
||||
return Object.assign(result, (gemini => {
|
||||
return Object.keys(gemini.browsers)
|
||||
.reduce((browsers, name) => {
|
||||
browsers[name] = gemini.screenshotsDir
|
||||
return browsers
|
||||
}, {})
|
||||
})(require(path.join(base, filename))))
|
||||
}, {})
|
||||
|
||||
/* Prepare filenames */
|
||||
const dest = path.join(process.cwd(), `${config.tests.visual}/baseline`)
|
||||
return gulp.src("gemini-report/images/**/*~current.png")
|
||||
.pipe(
|
||||
through.obj(function(file, enc, done) {
|
||||
if (file.isNull() || file.isStream())
|
||||
return done()
|
||||
|
||||
/* Remove the state from the filename */
|
||||
file.path = file.path.replace("~current", "")
|
||||
|
||||
/* Retrieve the folder for the environment of the baseline */
|
||||
const folder = path.relative(dest,
|
||||
mapping[path.basename(file.path, ".png")])
|
||||
file.path = file.path.replace("images", `images/${folder}`)
|
||||
|
||||
/* Push file to next stage */
|
||||
this.push(file)
|
||||
done()
|
||||
}))
|
||||
|
||||
/* Update reference images */
|
||||
.pipe(gulp.dest(dest))
|
||||
}
|
||||
}
|
3
material/assets/javascripts/application-c7c31dee65.js
Normal file
1
material/assets/javascripts/modernizr-5b0c41c2b5.js
Normal file
1
material/assets/stylesheets/application-932e030699.css
Normal file
@ -19,7 +19,7 @@
|
||||
{% else %}
|
||||
<link rel="shortcut icon" href="{{ base_url }}/assets/images/favicon.png">
|
||||
{% endif %}
|
||||
<meta name="generator" content="mkdocs+mkdocs-material#1.0.3">
|
||||
<meta name="generator" content="mkdocs-{{ mkdocs_version }}, mkdocs-material-1.0.3">
|
||||
{% endblock %}
|
||||
{% block htmltitle %}
|
||||
{% if page.title and not page.is_homepage %}
|
||||
@ -29,7 +29,13 @@
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block libs %}
|
||||
<script src="{{ base_url }}/assets/javascripts/modernizr-facb31f4a3.js"></script>
|
||||
<script src="{{ base_url }}/assets/javascripts/modernizr-5b0c41c2b5.js"></script>
|
||||
{% endblock %}
|
||||
{% block styles %}
|
||||
<link rel="stylesheet" href="{{ base_url }}/assets/stylesheets/application-932e030699.css">
|
||||
{% if config.extra.palette %}
|
||||
<link rel="stylesheet" href="{{ base_url }}/assets/stylesheets/application-02ce7adcc2.palette.css">
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block fonts %}
|
||||
{% if config.extra.font != "none" %}
|
||||
@ -42,15 +48,9 @@
|
||||
{% endif %}
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
|
||||
{% endblock %}
|
||||
{% block styles %}
|
||||
<link rel="stylesheet" href="{{ base_url }}/assets/stylesheets/application-22ac52ce22.css">
|
||||
{% if config.extra.palette %}
|
||||
<link rel="stylesheet" href="{{ base_url }}/assets/stylesheets/application-02ce7adcc2.palette.css">
|
||||
{% endif %}
|
||||
{% for path in extra_css %}
|
||||
<link rel="stylesheet" href="{{ path }}">
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
{% for path in extra_css %}
|
||||
<link rel="stylesheet" href="{{ path }}">
|
||||
{% endfor %}
|
||||
{% block extrahead %}{% endblock %}
|
||||
</head>
|
||||
{% set palette = config.extra.get("palette", {}) %}
|
||||
@ -112,7 +112,7 @@
|
||||
<a href="{{ page.edit_url }}" title="{{ lang.t('edit.link.title') }}" class="md-icon md-content__edit">edit</a>
|
||||
{% endif %}
|
||||
{% block content %}
|
||||
{% if not "\x3ch1 id=" in page.content %}
|
||||
{% if not "\x3ch1" in page.content %}
|
||||
<h1>{{ page.title | default(config.site_name, true)}}</h1>
|
||||
{% endif %}
|
||||
{{ page.content }}
|
||||
@ -126,8 +126,8 @@
|
||||
{% endblock %}
|
||||
</div>
|
||||
{% block scripts %}
|
||||
<script src="{{ base_url }}/assets/javascripts/application-baa758dc1b.js"></script>
|
||||
<script>var config={url:{base:"{{ base_url }}"}},app=new Application(config);app.initialize()</script>
|
||||
<script src="{{ base_url }}/assets/javascripts/application-c7c31dee65.js"></script>
|
||||
<script>app.initialize({url:{base:"{{ base_url }}"}})</script>
|
||||
{% for path in extra_javascript %}
|
||||
<script src="{{ path }}"></script>
|
||||
{% endfor %}
|
||||
|
@ -26,10 +26,10 @@
|
||||
<li class="md-nav__item">
|
||||
{% set toc_ = page.toc %}
|
||||
<input class="md-toggle md-nav__toggle" data-md-toggle="toc" type="checkbox" id="toc">
|
||||
{% if "\x3ch1 id=" in page.content %}
|
||||
{% if toc_ | first is defined %}
|
||||
{% set toc_ = (toc_ | first).children %}
|
||||
{% endif %}
|
||||
{% if toc_ and (toc_ | first) %}
|
||||
{% if toc_ | first is defined %}
|
||||
<label class="md-nav__link md-nav__link--active" for="toc">
|
||||
{{ nav_item.title }}
|
||||
</label>
|
||||
@ -37,7 +37,7 @@
|
||||
<a href="{{ nav_item.url }}" title="{{ nav_item.title }}" class="md-nav__link md-nav__link--active">
|
||||
{{ nav_item.title }}
|
||||
</a>
|
||||
{% if page.toc %}
|
||||
{% if toc_ | first is defined %}
|
||||
{% include "partials/toc.html" %}
|
||||
{% endif %}
|
||||
</li>
|
||||
|
@ -1,10 +1,10 @@
|
||||
{% import "partials/language.html" as lang %}
|
||||
<nav class="md-nav md-nav--secondary">
|
||||
{% set toc_ = page.toc %}
|
||||
{% if "\x3ch1 id=" in page.content %}
|
||||
{% if toc_ | first is defined and "\x3ch1 id=" in page.content %}
|
||||
{% set toc_ = (toc_ | first).children %}
|
||||
{% endif %}
|
||||
{% if toc_ and (toc_ | first) %}
|
||||
{% if toc_ | first is defined %}
|
||||
<label class="md-nav__title" for="toc">{{ lang.t('toc.title') }}</label>
|
||||
<ul class="md-nav__list" data-md-scrollfix>
|
||||
{% for toc_item in toc_ %}
|
||||
|
@ -31,7 +31,7 @@ repo_url: https://github.com/squidfunk/mkdocs-material
|
||||
# Copyright
|
||||
copyright: 'Copyright © 2016 - 2017 Martin Donath'
|
||||
|
||||
# Documentation and theme
|
||||
# Theme directory
|
||||
theme_dir: material
|
||||
|
||||
# Options
|
||||
|
51
package.json
@ -27,11 +27,14 @@
|
||||
"clean": "scripts/clean",
|
||||
"lint": "scripts/lint",
|
||||
"start": "scripts/start",
|
||||
"test": "scripts/test"
|
||||
"test:visual:run": "scripts/test/visual/run",
|
||||
"test:visual:update": "scripts/test/visual/update",
|
||||
"test:visual:session": "scripts/test/visual/session"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^6.6.1",
|
||||
"babel-core": "^6.0.0",
|
||||
"babel-eslint": "^7.1.1",
|
||||
"babel-loader": "^6.2.10",
|
||||
"babel-plugin-add-module-exports": "^0.2.1",
|
||||
@ -40,13 +43,12 @@
|
||||
"babel-preset-es2015": "^6.22.0",
|
||||
"babel-register": "^6.18.0",
|
||||
"babel-root-import": "^4.1.5",
|
||||
"chai": "^3.5.0",
|
||||
"core-js": "^2.4.1",
|
||||
"css-mqpacker": "^5.0.1",
|
||||
"custom-event-polyfill": "^0.3.0",
|
||||
"del": "^2.2.2",
|
||||
"ecstatic": "^2.1.0",
|
||||
"eslint": "^3.14.0",
|
||||
"eslint-plugin-mocha": "^4.8.0",
|
||||
"fastclick": "^1.0.6",
|
||||
"git-hooks": "^1.1.7",
|
||||
"gulp": "^3.9.1",
|
||||
@ -70,6 +72,27 @@
|
||||
"gulp-uglify": "^2.0.0",
|
||||
"gulp-util": "^3.0.8",
|
||||
"js-cookie": "^2.1.3",
|
||||
"lunr": "^0.7.2",
|
||||
"material-design-color": "^2.3.2",
|
||||
"material-shadows": "^3.0.1",
|
||||
"modularscale-sass": "^2.1.1",
|
||||
"node-notifier": "^5.0.0",
|
||||
"postcss-pseudo-classes": "^0.1.0",
|
||||
"stylelint": "^7.8.0",
|
||||
"stylelint-config-standard": "^16.0.0",
|
||||
"stylelint-order": "^0.2.2",
|
||||
"stylelint-scss": "^1.4.1",
|
||||
"through2": "^2.0.3",
|
||||
"vinyl-paths": "^2.1.0",
|
||||
"webpack": "^2.2.1",
|
||||
"webpack-stream": "^3.2.0",
|
||||
"whatwg-fetch": "^2.0.1",
|
||||
"yargs": "^6.6.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"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",
|
||||
@ -78,26 +101,14 @@
|
||||
"karma-sourcemap-loader": "^0.3.7",
|
||||
"karma-spec-reporter": "0.0.26",
|
||||
"karma-webpack": "^2.0.1",
|
||||
"lunr": "^0.7.2",
|
||||
"material-design-color": "^2.3.2",
|
||||
"material-shadows": "^3.0.1",
|
||||
"mocha": "^3.2.0",
|
||||
"modularscale-sass": "^2.1.1",
|
||||
"node-notifier": "^5.0.0",
|
||||
"selenium-standalone": "^5.9.1",
|
||||
"stylelint": "^7.7.1",
|
||||
"stylelint-config-standard": "^15.0.1",
|
||||
"stylelint-order": "^0.2.2",
|
||||
"stylelint-scss": "^1.4.1",
|
||||
"through2": "^2.0.3",
|
||||
"vinyl-paths": "^2.1.0",
|
||||
"webpack": "^1.14.0",
|
||||
"webpack-stream": "^3.2.0",
|
||||
"whatwg-fetch": "^2.0.1",
|
||||
"yargs": "^6.6.0"
|
||||
"moniker": "^0.1.2",
|
||||
"saucelabs": "^1.4.0",
|
||||
"sauce-connect-launcher": "^1.2.0",
|
||||
"selenium-standalone": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 4.5.0"
|
||||
"node": ">= 5.0.0"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
|
@ -20,4 +20,4 @@
|
||||
|
||||
mkdocs>=0.16
|
||||
pygments
|
||||
pymdown-extensions
|
||||
pymdown-extensions>=1.2
|
||||
|
@ -28,4 +28,4 @@ if [[ ! -d `npm bin` ]]; then
|
||||
fi
|
||||
|
||||
# Run command
|
||||
`npm bin`/gulp build --clean --optimize --revision
|
||||
`npm bin`/gulp build --clean --optimize --revision "$@"
|
||||
|
@ -28,4 +28,4 @@ if [[ ! -d `npm bin` ]]; then
|
||||
fi
|
||||
|
||||
# Run command
|
||||
`npm bin`/gulp clean
|
||||
`npm bin`/gulp clean "$@"
|
||||
|
@ -28,4 +28,4 @@ if [[ ! -d `npm bin` ]]; then
|
||||
fi
|
||||
|
||||
# Run command
|
||||
`npm bin`/gulp watch --no-lint
|
||||
`npm bin`/gulp watch --no-lint "$@"
|
||||
|
31
scripts/test/visual/run
Executable file
@ -0,0 +1,31 @@
|
||||
#!/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
|
||||
|
||||
# Run command
|
||||
`npm bin`/gulp tests:visual:run --clean --no-optimize "$@"
|
31
scripts/test/visual/session
Executable file
@ -0,0 +1,31 @@
|
||||
#!/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
|
||||
|
||||
# Run command
|
||||
`npm bin`/gulp tests:visual:session "$@"
|
@ -28,4 +28,4 @@ if [[ ! -d `npm bin` ]]; then
|
||||
fi
|
||||
|
||||
# Run command
|
||||
`npm bin`/gulp test
|
||||
`npm bin`/gulp tests:visual:update "$@"
|
10
src/.babelrc
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"presets": [
|
||||
["es2015", { "modules": false }]
|
||||
],
|
||||
"plugins": [
|
||||
["transform-react-jsx", {
|
||||
"pragma": "JSX.createElement"
|
||||
}]
|
||||
]
|
||||
}
|
@ -27,231 +27,215 @@ import Material from "./components/Material"
|
||||
* Application
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
export default class Application {
|
||||
export const initialize = config => {
|
||||
|
||||
/**
|
||||
* Create the application
|
||||
*
|
||||
* @constructor
|
||||
* @param {object} config Configuration object
|
||||
*/
|
||||
constructor(config) {
|
||||
this.config_ = config
|
||||
}
|
||||
/* Initialize Modernizr and FastClick */
|
||||
new Material.Event.Listener(document, "DOMContentLoaded", () => {
|
||||
|
||||
/**
|
||||
* Initialize all components and listeners
|
||||
*/
|
||||
initialize() {
|
||||
/* Test for iOS */
|
||||
Modernizr.addTest("ios", () => {
|
||||
return !!navigator.userAgent.match(/(iPad|iPhone|iPod)/g)
|
||||
})
|
||||
|
||||
/* Initialize Modernizr and FastClick */
|
||||
new Material.Event.Listener(document, "DOMContentLoaded", () => {
|
||||
/* Test for web application context */
|
||||
Modernizr.addTest("standalone", () => {
|
||||
return !!navigator.standalone
|
||||
})
|
||||
|
||||
/* Test for iOS */
|
||||
Modernizr.addTest("ios", () => {
|
||||
return !!navigator.userAgent.match(/(iPad|iPhone|iPod)/g)
|
||||
})
|
||||
/* Attach FastClick to mitigate 300ms delay on touch devices */
|
||||
FastClick.attach(document.body)
|
||||
|
||||
/* Test for web application context */
|
||||
Modernizr.addTest("standalone", () => {
|
||||
return !!navigator.standalone
|
||||
})
|
||||
/* Wrap all data tables for better overflow scrolling */
|
||||
const tables = document.querySelectorAll("table:not([class])")
|
||||
Array.prototype.forEach.call(tables, table => {
|
||||
const wrap = document.createElement("div")
|
||||
wrap.classList.add("md-typeset__table")
|
||||
if (table.nextSibling) {
|
||||
table.parentNode.insertBefore(wrap, table.nextSibling)
|
||||
} else {
|
||||
table.parentNode.appendChild(wrap)
|
||||
}
|
||||
wrap.appendChild(table)
|
||||
})
|
||||
|
||||
/* Attack FastClick to mitigate 300ms delay on touch devices */
|
||||
FastClick.attach(document.body)
|
||||
/* Force 1px scroll offset to trigger overflow scrolling */
|
||||
if (Modernizr.ios) {
|
||||
const scrollable = document.querySelectorAll("[data-md-scrollfix]")
|
||||
Array.prototype.forEach.call(scrollable, item => {
|
||||
item.addEventListener("touchstart", () => {
|
||||
const top = item.scrollTop
|
||||
|
||||
/* Wrap all data tables for better overflow scrolling */
|
||||
const tables = document.querySelectorAll("table:not([class])")
|
||||
Array.prototype.forEach.call(tables, table => {
|
||||
const wrap = document.createElement("div")
|
||||
wrap.classList.add("md-typeset__table")
|
||||
if (table.nextSibling) {
|
||||
table.parentNode.insertBefore(wrap, table.nextSibling)
|
||||
} else {
|
||||
table.parentNode.appendChild(wrap)
|
||||
}
|
||||
wrap.appendChild(table)
|
||||
})
|
||||
|
||||
/* Force 1px scroll offset to trigger overflow scrolling */
|
||||
if (Modernizr.ios) {
|
||||
const scrollable = document.querySelectorAll("[data-md-scrollfix]")
|
||||
Array.prototype.forEach.call(scrollable, item => {
|
||||
item.addEventListener("touchstart", () => {
|
||||
const top = item.scrollTop
|
||||
|
||||
/* We're at the top of the container */
|
||||
if (top === 0) {
|
||||
item.scrollTop = 1
|
||||
/* We're at the top of the container */
|
||||
if (top === 0) {
|
||||
item.scrollTop = 1
|
||||
|
||||
/* We're at the bottom of the container */
|
||||
} else if (top + item.offsetHeight === item.scrollHeight) {
|
||||
item.scrollTop = top - 1
|
||||
}
|
||||
})
|
||||
} else if (top + item.offsetHeight === item.scrollHeight) {
|
||||
item.scrollTop = top - 1
|
||||
}
|
||||
})
|
||||
}
|
||||
}).listen()
|
||||
})
|
||||
}
|
||||
}).listen()
|
||||
|
||||
/* Component: header shadow toggle */
|
||||
new Material.Event.MatchMedia("(min-width: 1220px)",
|
||||
new Material.Event.Listener(window, [
|
||||
"scroll", "resize", "orientationchange"
|
||||
], new Material.Header.Shadow("[data-md-component=container]")))
|
||||
|
||||
/* Component: tabs visibility toggle */
|
||||
/* Component: header shadow toggle */
|
||||
new Material.Event.MatchMedia("(min-width: 1220px)",
|
||||
new Material.Event.Listener(window, [
|
||||
"scroll", "resize", "orientationchange"
|
||||
], new Material.Tabs.Toggle("[data-md-component=tabs]")).listen()
|
||||
], new Material.Header.Shadow("[data-md-component=container]")))
|
||||
|
||||
/* Component: sidebar container */
|
||||
if (!Modernizr.csscalc)
|
||||
new Material.Event.MatchMedia("(min-width: 960px)",
|
||||
new Material.Event.Listener(window, [
|
||||
"resize", "orientationchange"
|
||||
], new Material.Sidebar.Container("[data-md-component=container]")))
|
||||
/* Component: tabs visibility toggle */
|
||||
new Material.Event.Listener(window, [
|
||||
"scroll", "resize", "orientationchange"
|
||||
], new Material.Tabs.Toggle("[data-md-component=tabs]")).listen()
|
||||
|
||||
/* Component: sidebar with navigation */
|
||||
new Material.Event.MatchMedia("(min-width: 1220px)",
|
||||
new Material.Event.Listener(window, [
|
||||
"scroll", "resize", "orientationchange"
|
||||
], new Material.Sidebar.Position("[data-md-component=navigation]")))
|
||||
|
||||
/* Component: sidebar with table of contents - register two separate
|
||||
listeners, as the offset at the top might change */
|
||||
new Material.Event.MatchMedia("(min-width: 960px) and (max-width: 1219px)",
|
||||
new Material.Event.Listener(window, [
|
||||
"scroll", "resize", "orientationchange"
|
||||
], new Material.Sidebar.Position("[data-md-component=toc]")))
|
||||
new Material.Event.MatchMedia("(min-width: 1220px)",
|
||||
new Material.Event.Listener(window, [
|
||||
"scroll", "resize", "orientationchange"
|
||||
], new Material.Sidebar.Position("[data-md-component=toc]")))
|
||||
|
||||
/* Component: link blurring for table of contents */
|
||||
/* Component: sidebar container */
|
||||
if (!Modernizr.csscalc)
|
||||
new Material.Event.MatchMedia("(min-width: 960px)",
|
||||
new Material.Event.Listener(window, "scroll",
|
||||
new Material.Nav.Blur("[data-md-component=toc] [href]")))
|
||||
new Material.Event.Listener(window, [
|
||||
"resize", "orientationchange"
|
||||
], new Material.Sidebar.Container("[data-md-component=container]")))
|
||||
|
||||
/* Component: collapsible elements for navigation */
|
||||
const collapsibles =
|
||||
document.querySelectorAll("[data-md-component=collapsible]")
|
||||
Array.prototype.forEach.call(collapsibles, collapse => {
|
||||
new Material.Event.MatchMedia("(min-width: 1220px)",
|
||||
new Material.Event.Listener(collapse.previousElementSibling, "click",
|
||||
new Material.Nav.Collapse(collapse)))
|
||||
})
|
||||
/* Component: sidebar with navigation */
|
||||
new Material.Event.MatchMedia("(min-width: 1220px)",
|
||||
new Material.Event.Listener(window, [
|
||||
"scroll", "resize", "orientationchange"
|
||||
], new Material.Sidebar.Position("[data-md-component=navigation]")))
|
||||
|
||||
/* Component: active pane monitor for iOS scrolling fixes */
|
||||
new Material.Event.MatchMedia("(max-width: 1219px)",
|
||||
new Material.Event.Listener(
|
||||
"[data-md-component=navigation] [data-md-toggle]", "change",
|
||||
new Material.Nav.Scrolling("[data-md-component=navigation] nav")))
|
||||
/* Component: sidebar with table of contents - register two separate
|
||||
listeners, as the offset at the top might change */
|
||||
new Material.Event.MatchMedia("(min-width: 960px) and (max-width: 1219px)",
|
||||
new Material.Event.Listener(window, [
|
||||
"scroll", "resize", "orientationchange"
|
||||
], new Material.Sidebar.Position("[data-md-component=toc]")))
|
||||
new Material.Event.MatchMedia("(min-width: 1220px)",
|
||||
new Material.Event.Listener(window, [
|
||||
"scroll", "resize", "orientationchange"
|
||||
], new Material.Sidebar.Position("[data-md-component=toc]")))
|
||||
|
||||
/* Component: search body lock for mobile */
|
||||
new Material.Event.MatchMedia("(max-width: 959px)",
|
||||
new Material.Event.Listener("[data-md-toggle=search]", "change",
|
||||
new Material.Search.Lock("[data-md-toggle=search]")))
|
||||
/* Component: link blurring for table of contents */
|
||||
new Material.Event.MatchMedia("(min-width: 960px)",
|
||||
new Material.Event.Listener(window, "scroll",
|
||||
new Material.Nav.Blur("[data-md-component=toc] [href]")))
|
||||
|
||||
/* Component: search results */
|
||||
new Material.Event.Listener(document.forms.search.query, [
|
||||
"focus", "keyup"
|
||||
], new Material.Search.Result("[data-md-component=result]", () => {
|
||||
return fetch(`${this.config_.url.base}/mkdocs/search_index.json`, {
|
||||
credentials: "same-origin"
|
||||
}).then(response => response.json())
|
||||
.then(data => {
|
||||
return data.docs.map(doc => {
|
||||
doc.location = this.config_.url.base + doc.location
|
||||
return doc
|
||||
})
|
||||
/* Component: collapsible elements for navigation */
|
||||
const collapsibles =
|
||||
document.querySelectorAll("[data-md-component=collapsible]")
|
||||
Array.prototype.forEach.call(collapsibles, collapse => {
|
||||
new Material.Event.MatchMedia("(min-width: 1220px)",
|
||||
new Material.Event.Listener(collapse.previousElementSibling, "click",
|
||||
new Material.Nav.Collapse(collapse)))
|
||||
})
|
||||
|
||||
/* Component: active pane monitor for iOS scrolling fixes */
|
||||
new Material.Event.MatchMedia("(max-width: 1219px)",
|
||||
new Material.Event.Listener(
|
||||
"[data-md-component=navigation] [data-md-toggle]", "change",
|
||||
new Material.Nav.Scrolling("[data-md-component=navigation] nav")))
|
||||
|
||||
/* Component: search body lock for mobile */
|
||||
new Material.Event.MatchMedia("(max-width: 959px)",
|
||||
new Material.Event.Listener("[data-md-toggle=search]", "change",
|
||||
new Material.Search.Lock("[data-md-toggle=search]")))
|
||||
|
||||
/* Component: search results */
|
||||
new Material.Event.Listener(document.forms.search.query, [
|
||||
"focus", "keyup"
|
||||
], new Material.Search.Result("[data-md-component=result]", () => {
|
||||
return fetch(`${config.url.base}/mkdocs/search_index.json`, {
|
||||
credentials: "same-origin"
|
||||
}).then(response => response.json())
|
||||
.then(data => {
|
||||
return data.docs.map(doc => {
|
||||
doc.location = config.url.base + doc.location
|
||||
return doc
|
||||
})
|
||||
})).listen()
|
||||
|
||||
/* Listener: prevent touches on overlay if navigation is active */
|
||||
new Material.Event.MatchMedia("(max-width: 1219px)",
|
||||
new Material.Event.Listener("[data-md-component=overlay]", "touchstart",
|
||||
ev => ev.preventDefault()))
|
||||
|
||||
/* Listener: close drawer when anchor links are clicked */
|
||||
new Material.Event.MatchMedia("(max-width: 959px)",
|
||||
new Material.Event.Listener("[data-md-component=navigation] [href^='#']",
|
||||
"click", () => {
|
||||
const toggle = document.querySelector("[data-md-toggle=drawer]")
|
||||
if (toggle.checked) {
|
||||
toggle.checked = false
|
||||
toggle.dispatchEvent(new CustomEvent("change"))
|
||||
}
|
||||
}))
|
||||
|
||||
/* 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)
|
||||
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", () => {
|
||||
const toggle = document.querySelector("[data-md-toggle=search]")
|
||||
if (!toggle.checked) {
|
||||
toggle.checked = true
|
||||
toggle.dispatchEvent(new CustomEvent("change"))
|
||||
}
|
||||
}))
|
||||
|
||||
/* Listener: close search when clicking outside */
|
||||
new Material.Event.MatchMedia("(min-width: 960px)",
|
||||
new Material.Event.Listener(document.body, "click", () => {
|
||||
const toggle = document.querySelector("[data-md-toggle=search]")
|
||||
if (toggle.checked) {
|
||||
toggle.checked = false
|
||||
toggle.dispatchEvent(new CustomEvent("change"))
|
||||
}
|
||||
}))
|
||||
|
||||
/* Listener: disable search when ESC key is pressed */
|
||||
new Material.Event.Listener(window, "keyup", ev => {
|
||||
const code = ev.keyCode || ev.which
|
||||
if (code === 27) {
|
||||
const toggle = document.querySelector("[data-md-toggle=search]")
|
||||
if (toggle.checked) {
|
||||
toggle.checked = false
|
||||
toggle.dispatchEvent(new CustomEvent("change"))
|
||||
document.forms.search.query.blur()
|
||||
}
|
||||
}
|
||||
}).listen()
|
||||
|
||||
/* Listener: fix unclickable toggle due to blur handler */
|
||||
new Material.Event.MatchMedia("(min-width: 960px)",
|
||||
new Material.Event.Listener("[data-md-toggle=search]", "click",
|
||||
ev => ev.stopPropagation()))
|
||||
|
||||
/* Listener: prevent search from closing when clicking */
|
||||
new Material.Event.MatchMedia("(min-width: 960px)",
|
||||
new Material.Event.Listener("[data-md-component=search]", "click",
|
||||
ev => ev.stopPropagation()))
|
||||
|
||||
/* Retrieve facts for the given repository type */
|
||||
;(() => {
|
||||
const el = document.querySelector("[data-md-source]")
|
||||
if (!el) return Promise.resolve([])
|
||||
switch (el.dataset.mdSource) {
|
||||
case "github": return new Material.Source.Adapter.GitHub(el).fetch()
|
||||
default: return Promise.resolve([])
|
||||
}
|
||||
|
||||
/* Render repository source information */
|
||||
})().then(facts => {
|
||||
const sources = document.querySelectorAll("[data-md-source]")
|
||||
Array.prototype.forEach.call(sources, source => {
|
||||
new Material.Source.Repository(source)
|
||||
.initialize(facts)
|
||||
})
|
||||
})).listen()
|
||||
|
||||
/* Listener: prevent touches on overlay if navigation is active */
|
||||
new Material.Event.MatchMedia("(max-width: 1219px)",
|
||||
new Material.Event.Listener("[data-md-component=overlay]", "touchstart",
|
||||
ev => ev.preventDefault()))
|
||||
|
||||
/* Listener: close drawer when anchor links are clicked */
|
||||
new Material.Event.MatchMedia("(max-width: 959px)",
|
||||
new Material.Event.Listener("[data-md-component=navigation] [href^='#']",
|
||||
"click", () => {
|
||||
const toggle = document.querySelector("[data-md-toggle=drawer]")
|
||||
if (toggle.checked) {
|
||||
toggle.checked = false
|
||||
toggle.dispatchEvent(new CustomEvent("change"))
|
||||
}
|
||||
}))
|
||||
|
||||
/* 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)
|
||||
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", () => {
|
||||
const toggle = document.querySelector("[data-md-toggle=search]")
|
||||
if (!toggle.checked) {
|
||||
toggle.checked = true
|
||||
toggle.dispatchEvent(new CustomEvent("change"))
|
||||
}
|
||||
}))
|
||||
|
||||
/* Listener: close search when clicking outside */
|
||||
new Material.Event.MatchMedia("(min-width: 960px)",
|
||||
new Material.Event.Listener(document.body, "click", () => {
|
||||
const toggle = document.querySelector("[data-md-toggle=search]")
|
||||
if (toggle.checked) {
|
||||
toggle.checked = false
|
||||
toggle.dispatchEvent(new CustomEvent("change"))
|
||||
}
|
||||
}))
|
||||
|
||||
/* Listener: disable search when ESC key is pressed */
|
||||
new Material.Event.Listener(window, "keyup", ev => {
|
||||
const code = ev.keyCode || ev.which
|
||||
if (code === 27) {
|
||||
const toggle = document.querySelector("[data-md-toggle=search]")
|
||||
if (toggle.checked) {
|
||||
toggle.checked = false
|
||||
toggle.dispatchEvent(new CustomEvent("change"))
|
||||
document.forms.search.query.blur()
|
||||
}
|
||||
}
|
||||
}).listen()
|
||||
|
||||
/* Listener: fix unclickable toggle due to blur handler */
|
||||
new Material.Event.MatchMedia("(min-width: 960px)",
|
||||
new Material.Event.Listener("[data-md-toggle=search]", "click",
|
||||
ev => ev.stopPropagation()))
|
||||
|
||||
/* Listener: prevent search from closing when clicking */
|
||||
new Material.Event.MatchMedia("(min-width: 960px)",
|
||||
new Material.Event.Listener("[data-md-component=search]", "click",
|
||||
ev => ev.stopPropagation()))
|
||||
|
||||
/* Retrieve facts for the given repository type */
|
||||
;(() => {
|
||||
const el = document.querySelector("[data-md-source]")
|
||||
if (!el) return Promise.resolve([])
|
||||
switch (el.dataset.mdSource) {
|
||||
case "github": return new Material.Source.Adapter.GitHub(el).fetch()
|
||||
default: return Promise.resolve([])
|
||||
}
|
||||
|
||||
/* Render repository source information */
|
||||
})().then(facts => {
|
||||
const sources = document.querySelectorAll("[data-md-source]")
|
||||
Array.prototype.forEach.call(sources, source => {
|
||||
new Material.Source.Repository(source)
|
||||
.initialize(facts)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -34,14 +34,9 @@ body {
|
||||
body,
|
||||
input {
|
||||
color: $md-color-black;
|
||||
// font-family: "Roboto", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
font-feature-settings: "kern", "onum", "liga";
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
font-weight: 400;
|
||||
|
||||
// Use system fonts, if browser doesn't support webfonts
|
||||
.no-fontface & {
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
// Proportionally spaced fonts
|
||||
@ -49,14 +44,9 @@ pre,
|
||||
code,
|
||||
kbd {
|
||||
color: $md-color-black;
|
||||
// font-family: "Roboto Mono", "Courier New", Courier, monospace;
|
||||
font-feature-settings: "kern", "onum", "liga";
|
||||
font-family: "Courier New", Courier, monospace;
|
||||
font-weight: 400;
|
||||
|
||||
// Use system fonts, if browser doesn't support webfonts
|
||||
.no-fontface & {
|
||||
font-family: "Courier New", Courier, monospace;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
@ -50,8 +50,8 @@
|
||||
padding-bottom: 0.8rem;
|
||||
transition: opacity 0.25s;
|
||||
|
||||
// [mobile landscape +]: Set proportional width
|
||||
@include break-from-device(mobile landscape) {
|
||||
// [tablet +]: Set proportional width
|
||||
@include break-from-device(tablet) {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
@ -68,8 +68,8 @@
|
||||
// Title
|
||||
.md-footer-nav__title {
|
||||
|
||||
// [mobile portrait -]: Hide title for previous page
|
||||
@include break-to-device(mobile portrait) {
|
||||
// [mobile -]: Hide title for previous page
|
||||
@include break-to-device(mobile) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
@ -84,7 +84,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Icon buttons
|
||||
// Button with logo
|
||||
&__button {
|
||||
@extend %md-icon, %md-icon__button;
|
||||
|
||||
@ -141,7 +141,8 @@
|
||||
color: $md-color-primary;
|
||||
}
|
||||
|
||||
// Hovered link
|
||||
// Focused or hovered item
|
||||
&:focus,
|
||||
&:hover {
|
||||
color: $md-color-accent;
|
||||
}
|
||||
|
@ -56,7 +56,8 @@
|
||||
{% endif %}
|
||||
|
||||
<!-- Generator banner -->
|
||||
<meta name="generator" content="mkdocs+$theme-name$#$theme-version$" />
|
||||
<meta name="generator"
|
||||
content="mkdocs-{{ mkdocs_version }}, $theme-name$-$theme-version$" />
|
||||
{% endblock %}
|
||||
|
||||
<!-- Block: site title -->
|
||||
@ -73,6 +74,20 @@
|
||||
<script src="{{ base_url }}/assets/javascripts/modernizr.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
<!-- Block: stylesheets -->
|
||||
{% block styles %}
|
||||
|
||||
<!-- Theme-related stylesheets -->
|
||||
<link rel="stylesheet" type="text/css"
|
||||
href="{{ base_url }}/assets/stylesheets/application.css" />
|
||||
|
||||
<!-- Extra color palette -->
|
||||
{% if config.extra.palette %}
|
||||
<link rel="stylesheet" type="text/css"
|
||||
href="{{ base_url }}/assets/stylesheets/application.palette.css" />
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
<!-- Block: webfonts -->
|
||||
{% block fonts %}
|
||||
{% if config.extra.font != "none" %}
|
||||
@ -99,24 +114,10 @@
|
||||
href="https://fonts.googleapis.com/icon?family=Material+Icons" />
|
||||
{% endblock %}
|
||||
|
||||
<!-- Block: stylesheets -->
|
||||
{% block styles %}
|
||||
|
||||
<!-- Theme-related stylesheets -->
|
||||
<link rel="stylesheet" type="text/css"
|
||||
href="{{ base_url }}/assets/stylesheets/application.css" />
|
||||
|
||||
<!-- Extra color palette -->
|
||||
{% if config.extra.palette %}
|
||||
<link rel="stylesheet" type="text/css"
|
||||
href="{{ base_url }}/assets/stylesheets/application.palette.css" />
|
||||
{% endif %}
|
||||
|
||||
<!-- Custom stylesheets -->
|
||||
{% for path in extra_css %}
|
||||
<link rel="stylesheet" type="text/css" href="{{ path }}" />
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
<!-- Custom stylesheets -->
|
||||
{% for path in extra_css %}
|
||||
<link rel="stylesheet" type="text/css" href="{{ path }}" />
|
||||
{% endfor %}
|
||||
|
||||
<!-- Block: custom front matter -->
|
||||
{% block extrahead %}{% endblock %}
|
||||
@ -220,11 +221,11 @@
|
||||
{% block content %}
|
||||
|
||||
<!--
|
||||
This is a nasty hack that checks whether the content contains
|
||||
a h1 headline. If it doesn't, the page title (or respectively
|
||||
site name) is used as the main headline.
|
||||
Hack: check whether the content contains a h1 headline. If it
|
||||
doesn't, the page title (or respectively site name) is used
|
||||
as the main headline.
|
||||
-->
|
||||
{% if not "\x3ch1 id=" in page.content %}
|
||||
{% if not "\x3ch1" in page.content %}
|
||||
<h1>{{ page.title | default(config.site_name, true)}}</h1>
|
||||
{% endif %}
|
||||
|
||||
@ -246,17 +247,7 @@
|
||||
{% block scripts %}
|
||||
<script src="{{ base_url }}/assets/javascripts/application.js"></script>
|
||||
<script>
|
||||
|
||||
/* Configuration for application */
|
||||
var config = {
|
||||
url: {
|
||||
base: "{{ base_url }}",
|
||||
}
|
||||
};
|
||||
|
||||
/* Initialize application */
|
||||
var app = new Application(config);
|
||||
app.initialize();
|
||||
app.initialize({ url: { base: "{{ base_url }}", } });
|
||||
</script>
|
||||
{% for path in extra_javascript %}
|
||||
<script src="{{ path }}"></script>
|
||||
|
@ -64,13 +64,13 @@
|
||||
<input class="md-toggle md-nav__toggle" data-md-toggle="toc"
|
||||
type="checkbox" id="toc" />
|
||||
|
||||
<!-- Nasty hack - see partials/toc.html for more information -->
|
||||
{% if "\x3ch1 id=" in page.content %}
|
||||
<!-- Hack: see partials/toc.html for more information -->
|
||||
{% if toc_ | first is defined %}
|
||||
{% set toc_ = (toc_ | first).children %}
|
||||
{% endif %}
|
||||
|
||||
<!-- Render table of contents, if not empty -->
|
||||
{% if toc_ and (toc_ | first) %}
|
||||
{% if toc_ | first is defined %}
|
||||
<label class="md-nav__link md-nav__link--active" for="toc">
|
||||
{{ nav_item.title }}
|
||||
</label>
|
||||
@ -81,7 +81,7 @@
|
||||
</a>
|
||||
|
||||
<!-- Show table of contents -->
|
||||
{% if page.toc %}
|
||||
{% if toc_ | first is defined %}
|
||||
{% include "partials/toc.html" %}
|
||||
{% endif %}
|
||||
</li>
|
||||
|
@ -27,17 +27,17 @@
|
||||
{% set toc_ = page.toc %}
|
||||
|
||||
<!--
|
||||
This is a nasty hack that checks whether the content contains a h1
|
||||
headline. If it does, the top-level anchor must be skipped, since it would
|
||||
be redundant to the link to the current page that is located just above the
|
||||
anchor. Therefore we directly continue with the children of the anchor.
|
||||
Hack: check whether the content contains a h1 headline. If it does, the
|
||||
top-level anchor must be skipped, since it would be redundant to the link
|
||||
to the current page that is located just above the anchor. Therefore we
|
||||
directly continue with the children of the anchor.
|
||||
-->
|
||||
{% if "\x3ch1 id=" in page.content %}
|
||||
{% if toc_ | first is defined and "\x3ch1 id=" in page.content %}
|
||||
{% set toc_ = (toc_ | first).children %}
|
||||
{% endif %}
|
||||
|
||||
<!-- Render item list -->
|
||||
{% if toc_ and (toc_ | first) %}
|
||||
{% if toc_ | first is defined %}
|
||||
<label class="md-nav__title" for="toc">{{ lang.t('toc.title') }}</label>
|
||||
<ul class="md-nav__list" data-md-scrollfix>
|
||||
{% for toc_item in toc_ %}
|
||||
|
8
tests/visual/.eslintrc
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"globals": {
|
||||
"gemini": true
|
||||
},
|
||||
"rules": {
|
||||
"no-loop-func": 0
|
||||
}
|
||||
}
|
BIN
tests/visual/baseline/ci/admonition/#bug/@screen/chrome.png
Normal file
After Width: | Height: | Size: 5.7 KiB |
BIN
tests/visual/baseline/ci/admonition/#bug/@screen/edge.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
BIN
tests/visual/baseline/ci/admonition/#bug/@screen/firefox.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
BIN
tests/visual/baseline/ci/admonition/#bug/@screen/ie11.png
Normal file
After Width: | Height: | Size: 7.2 KiB |
After Width: | Height: | Size: 6.3 KiB |
After Width: | Height: | Size: 8.2 KiB |
After Width: | Height: | Size: 5.5 KiB |
After Width: | Height: | Size: 8.1 KiB |
BIN
tests/visual/baseline/ci/admonition/#danger/@screen/chrome.png
Normal file
After Width: | Height: | Size: 5.6 KiB |
BIN
tests/visual/baseline/ci/admonition/#danger/@screen/edge.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
BIN
tests/visual/baseline/ci/admonition/#danger/@screen/firefox.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
BIN
tests/visual/baseline/ci/admonition/#danger/@screen/ie11.png
Normal file
After Width: | Height: | Size: 7.2 KiB |
After Width: | Height: | Size: 4.8 KiB |
After Width: | Height: | Size: 6.7 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 6.5 KiB |
BIN
tests/visual/baseline/ci/admonition/#failure/@screen/chrome.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
tests/visual/baseline/ci/admonition/#failure/@screen/edge.png
Normal file
After Width: | Height: | Size: 6.9 KiB |
BIN
tests/visual/baseline/ci/admonition/#failure/@screen/firefox.png
Normal file
After Width: | Height: | Size: 4.8 KiB |
BIN
tests/visual/baseline/ci/admonition/#failure/@screen/ie11.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
After Width: | Height: | Size: 8.8 KiB |
BIN
tests/visual/baseline/ci/admonition/#long-title/@screen/edge.png
Normal file
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 7.7 KiB |
BIN
tests/visual/baseline/ci/admonition/#long-title/@screen/ie11.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
tests/visual/baseline/ci/admonition/#note/@screen/chrome.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
tests/visual/baseline/ci/admonition/#note/@screen/edge.png
Normal file
After Width: | Height: | Size: 7.2 KiB |
BIN
tests/visual/baseline/ci/admonition/#note/@screen/firefox.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
tests/visual/baseline/ci/admonition/#note/@screen/ie11.png
Normal file
After Width: | Height: | Size: 7.1 KiB |
BIN
tests/visual/baseline/ci/admonition/#success/@screen/chrome.png
Normal file
After Width: | Height: | Size: 5.7 KiB |
BIN
tests/visual/baseline/ci/admonition/#success/@screen/edge.png
Normal file
After Width: | Height: | Size: 7.5 KiB |
BIN
tests/visual/baseline/ci/admonition/#success/@screen/firefox.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
BIN
tests/visual/baseline/ci/admonition/#success/@screen/ie11.png
Normal file
After Width: | Height: | Size: 7.2 KiB |
BIN
tests/visual/baseline/ci/admonition/#summary/@screen/chrome.png
Normal file
After Width: | Height: | Size: 5.6 KiB |
BIN
tests/visual/baseline/ci/admonition/#summary/@screen/edge.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
BIN
tests/visual/baseline/ci/admonition/#summary/@screen/firefox.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
BIN
tests/visual/baseline/ci/admonition/#summary/@screen/ie11.png
Normal file
After Width: | Height: | Size: 7.1 KiB |
BIN
tests/visual/baseline/ci/admonition/#tip/@screen/chrome.png
Normal file
After Width: | Height: | Size: 5.6 KiB |
BIN
tests/visual/baseline/ci/admonition/#tip/@screen/edge.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
BIN
tests/visual/baseline/ci/admonition/#tip/@screen/firefox.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
BIN
tests/visual/baseline/ci/admonition/#tip/@screen/ie11.png
Normal file
After Width: | Height: | Size: 7.2 KiB |
BIN
tests/visual/baseline/ci/admonition/#warning/@screen/chrome.png
Normal file
After Width: | Height: | Size: 5.8 KiB |
BIN
tests/visual/baseline/ci/admonition/#warning/@screen/edge.png
Normal file
After Width: | Height: | Size: 7.6 KiB |
BIN
tests/visual/baseline/ci/admonition/#warning/@screen/firefox.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
tests/visual/baseline/ci/admonition/#warning/@screen/ie11.png
Normal file
After Width: | Height: | Size: 7.4 KiB |
BIN
tests/visual/baseline/ci/admonition/@mobile-landscape/chrome.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
tests/visual/baseline/ci/admonition/@mobile-landscape/edge.png
Normal file
After Width: | Height: | Size: 7.7 KiB |
After Width: | Height: | Size: 5.4 KiB |
BIN
tests/visual/baseline/ci/admonition/@mobile-landscape/ie11.png
Normal file
After Width: | Height: | Size: 7.6 KiB |
BIN
tests/visual/baseline/ci/admonition/@mobile-portrait/chrome.png
Normal file
After Width: | Height: | Size: 5.0 KiB |