Hello, redux-doctitle!
This commit is contained in:
commit
64c18f7eba
17
.babelrc
Normal file
17
.babelrc
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"presets": [
|
||||
[
|
||||
"@babel/preset-env", {
|
||||
"targets": {
|
||||
"chrome": 60,
|
||||
"edge": 14,
|
||||
"firefox": 54,
|
||||
"ie": 9,
|
||||
"opera": 46,
|
||||
"safari": 9
|
||||
}
|
||||
}
|
||||
],
|
||||
"@babel/preset-react"
|
||||
]
|
||||
}
|
55
.eslintrc.json
Normal file
55
.eslintrc.json
Normal file
|
@ -0,0 +1,55 @@
|
|||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"commonjs": true,
|
||||
"jasmine": true,
|
||||
"es6": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"parser": "babel-eslint",
|
||||
"parserOptions": {
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"modules": true
|
||||
}
|
||||
},
|
||||
"plugins": ["jasmine"],
|
||||
"rules": {
|
||||
"no-undef": "error",
|
||||
"quotes": [
|
||||
2, "double", {"avoidEscape": true, "allowTemplateLiterals": true}
|
||||
],
|
||||
"no-unused-vars": [0],
|
||||
"no-console": [2],
|
||||
"no-empty": ["error", {"allowEmptyCatch": true}],
|
||||
"array-bracket-spacing": ["error", "never"],
|
||||
"block-spacing": ["error", "always"],
|
||||
"brace-style": ["error", "1tbs", {"allowSingleLine": true}],
|
||||
"camelcase": ["error", {"properties": "never"}],
|
||||
"comma-dangle": ["error", "never"],
|
||||
"comma-spacing": ["error", {"before": false, "after": true}],
|
||||
"comma-style": ["error", "last"],
|
||||
"computed-property-spacing": ["error", "never"],
|
||||
"key-spacing": [
|
||||
"error", {"beforeColon": false, "afterColon": true, "mode": "strict"}
|
||||
],
|
||||
"keyword-spacing": ["error", { "before": true, "after": true }],
|
||||
"linebreak-style": ["error", "unix"],
|
||||
"max-len": ["error", 120],
|
||||
"no-multiple-empty-lines": ["error"],
|
||||
"no-spaced-func": ["error"],
|
||||
"no-trailing-spaces": ["error"],
|
||||
"no-unreachable": [1],
|
||||
"no-whitespace-before-property": ["error"],
|
||||
"object-curly-spacing": ["error", "never"],
|
||||
"one-var-declaration-per-line": ["error", "always"],
|
||||
"one-var": ["error", "never"],
|
||||
"semi-spacing": ["error", {"before": false, "after": true}],
|
||||
"semi": ["error", "always"],
|
||||
"space-before-function-paren": ["error", "always"],
|
||||
"space-before-blocks": ["error", "always"],
|
||||
"space-in-parens": ["error", "never"],
|
||||
"space-infix-ops": ["error"],
|
||||
"unicode-bom": ["error", "never"]
|
||||
}
|
||||
}
|
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
lib/
|
||||
node_modules/
|
||||
redux-doctitle-*.tgz
|
78
Gruntfile.js
Normal file
78
Gruntfile.js
Normal file
|
@ -0,0 +1,78 @@
|
|||
var path = require("path");
|
||||
|
||||
var underscore = require("underscore");
|
||||
var webpack = require("webpack");
|
||||
|
||||
var defs = require("./package.defs.js");
|
||||
|
||||
var devWebpackConfig = require("./webpack.config.js");
|
||||
var distWebpackConfig = underscore.extend(
|
||||
underscore.clone(devWebpackConfig),
|
||||
{
|
||||
devtool: false,
|
||||
output: {
|
||||
path: defs.LIB_DIR,
|
||||
filename: defs.FILENAME_DIST,
|
||||
library: defs.LIBRARY_NAME,
|
||||
libraryTarget: "umd",
|
||||
umdNamedDefine: true
|
||||
},
|
||||
plugins: [
|
||||
new webpack.optimize.UglifyJsPlugin({compress: false})
|
||||
]
|
||||
}
|
||||
);
|
||||
|
||||
module.exports = function (grunt) {
|
||||
grunt.loadNpmTasks("grunt-contrib-clean");
|
||||
grunt.loadNpmTasks("grunt-karma");
|
||||
grunt.loadNpmTasks("grunt-webpack");
|
||||
grunt.loadNpmTasks("gruntify-eslint");
|
||||
|
||||
grunt.initConfig({
|
||||
clean: {
|
||||
options: {
|
||||
force: true,
|
||||
},
|
||||
all: [defs.LIB_DIR]
|
||||
},
|
||||
eslint: {
|
||||
sources: {
|
||||
src: ["./src/**/*.js"]
|
||||
},
|
||||
tests: {
|
||||
src: ["./tests/**/*.spec.js"]
|
||||
}
|
||||
},
|
||||
karma: {
|
||||
options: {
|
||||
configFile: "karma.conf.js"
|
||||
},
|
||||
dist: {
|
||||
browsers: ["ChromiumHeadless"],
|
||||
reporters: ["progress"],
|
||||
singleRun: true,
|
||||
webpackMiddleware: {
|
||||
noInfo: true,
|
||||
stats: "errors-only"
|
||||
}
|
||||
},
|
||||
dev: {
|
||||
browsers: ["ChromiumHeadless"],
|
||||
mochaReporter: {
|
||||
ignoreSkipped: false
|
||||
},
|
||||
reporters: ["mocha"],
|
||||
singleRun: true
|
||||
}
|
||||
},
|
||||
webpack: {
|
||||
dev: devWebpackConfig,
|
||||
dist: distWebpackConfig
|
||||
}
|
||||
});
|
||||
|
||||
grunt.registerTask("dist", [
|
||||
"clean", "eslint", "karma:dist", "webpack"
|
||||
]);
|
||||
};
|
19
LICENSE
Normal file
19
LICENSE
Normal file
|
@ -0,0 +1,19 @@
|
|||
Copyright (c) 2018 Tomek Wójcik <tomek@bthlabs.pl>
|
||||
|
||||
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 NONINFRINGEMENT. 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.
|
126
README.md
Normal file
126
README.md
Normal file
|
@ -0,0 +1,126 @@
|
|||
# redux-doctitle
|
||||
|
||||
Redux middleware for managing document title.
|
||||
|
||||
## Installation
|
||||
|
||||
redux-doctitle requires **Redux 3.0 or later**.
|
||||
|
||||
```
|
||||
npm install --save-dev redux-doctitle
|
||||
```
|
||||
|
||||
This assumes that you’re using [npm](http://npmjs.com/) package manager with a
|
||||
module bundler like [Webpack](https://webpack.js.org/) or
|
||||
[Browserify](http://browserify.org/).
|
||||
|
||||
## Usage
|
||||
|
||||
In order to use redux-doctitle in your React components you need to install the
|
||||
middleware, first.
|
||||
|
||||
```
|
||||
import {applyMiddleware, createStore} from "redux";
|
||||
import {documentTitleMiddlewareFactory} from "redux-doctitle";
|
||||
|
||||
const documentTitleMiddleware = documentTitleMiddlewareFactory(["Example"]);
|
||||
const store = createStore(
|
||||
reducer, initialState, applyMiddleware(documentTitleMiddleware)
|
||||
);
|
||||
```
|
||||
|
||||
Once this is done, you can use the provided action to change document title
|
||||
from your React components.
|
||||
|
||||
```
|
||||
import PropTypes from "prop-types";
|
||||
import React from "react";
|
||||
import {connect} from "react-redux";
|
||||
import {bindActionCreators} from "redux";
|
||||
import {setDocumentTitle} from "redux-doctitle";
|
||||
|
||||
class HelloWorldComponent extends React.Component {
|
||||
componentDidMount () {
|
||||
this.props.actions.setDocumentTitle("Hello, World!");
|
||||
}
|
||||
render () {
|
||||
return <div>Hello, World!</div>;
|
||||
}
|
||||
}
|
||||
|
||||
HelloWorldComponent.propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
setDocumentTitle: PropTypes.func.isRequired
|
||||
});
|
||||
};
|
||||
|
||||
const mapStateToProps = (state, props) => {
|
||||
return {};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch, props) => {
|
||||
const actions = bindActionCreators(
|
||||
{
|
||||
setDocumentTitle: setDocumentTitle
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
};
|
||||
|
||||
const reduxContainer = connect(
|
||||
mapStateToProps, mapDispatchToProps
|
||||
)(HelloWorldComponent);
|
||||
|
||||
export default reduxContainer;
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### `documentMiddlewareFactory(defaultTitleParts = [])`
|
||||
|
||||
This is the middleware factory function. The *defaultTitleParts* argument is an
|
||||
optional array of title parts which will be appended to custom titles set
|
||||
through the `setDocumentTitle` action creator. If it's ommited, nothing will be
|
||||
appended.
|
||||
|
||||
Returns Redux-compatible middleware function.
|
||||
|
||||
### `setDocumentTitle(title = "")`
|
||||
|
||||
This is the action creator that allows changing the document title.
|
||||
|
||||
Returns Redux action.
|
||||
|
||||
## Development
|
||||
|
||||
To bootstrap the development environment, clone the repo and run `npm install`
|
||||
from the root directory.
|
||||
|
||||
The `package.json` file provides the following scripts:
|
||||
|
||||
* `build` - builds the library modules (minified and development with source
|
||||
map),
|
||||
* `dev` - starts Webpack watcher configured to build development library,
|
||||
* `lint` - performs an eslint run over the source code,
|
||||
* `test` - performs a single test run,
|
||||
* `test:watch` - starts karma with watcher.
|
||||
|
||||
**NOTE**: Tests require *Chromium* to be installed and available in the path.
|
||||
Consult
|
||||
[karma-chrome-launcher](https://github.com/karma-runner/karma-chrome-launcher)
|
||||
docs for more info.
|
||||
|
||||
## Contributing
|
||||
|
||||
If you think you found a bug or want to send a patch, feel free to contact
|
||||
me through e-mail.
|
||||
|
||||
If you're sending a patch, make sure it passes eslint checks and is tested.
|
||||
|
||||
## Author
|
||||
|
||||
redux-doctitle is developed by [Tomek Wójcik](https://www.bthlabs.pl/).
|
||||
|
||||
## License
|
||||
|
||||
redux-doctitle is licensed under the MIT License.
|
39
karma.conf.js
Normal file
39
karma.conf.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
var path = require("path");
|
||||
|
||||
var defs = require("./package.defs.js");
|
||||
|
||||
module.exports = function(config) {
|
||||
config.set({
|
||||
basePath: ".",
|
||||
browsers: ["Chromium"],
|
||||
frameworks: ["jasmine"],
|
||||
files: [
|
||||
"tests/__entry__.js"
|
||||
],
|
||||
mochaReporter: {
|
||||
ignoreSkipped: true
|
||||
},
|
||||
preprocessors: {
|
||||
"tests/__entry__.js": ["webpack", "sourcemap"]
|
||||
},
|
||||
reporters: ["mocha"],
|
||||
singleRun: false,
|
||||
webpack: {
|
||||
devtool: "inline-source-map",
|
||||
module: {
|
||||
loaders: [
|
||||
{
|
||||
test: /\.js?/,
|
||||
include: defs.SRC_DIR,
|
||||
loader: ["babel-loader"]
|
||||
}
|
||||
]
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
"src": defs.SRC_DIR
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
16
package.defs.js
Normal file
16
package.defs.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
var path = require("path");
|
||||
|
||||
var LIB_DIR = path.resolve(__dirname, "lib");
|
||||
var SRC_DIR = path.resolve(__dirname, "src");
|
||||
|
||||
var LIBRARY_NAME = "redux-doctitle";
|
||||
var FILENAME_DEV = "redux-doctitle.js";
|
||||
var FILENAME_DIST = "redux-doctitle.min.js";
|
||||
|
||||
module.exports = {
|
||||
LIB_DIR: LIB_DIR,
|
||||
SRC_DIR: SRC_DIR,
|
||||
LIBRARY_NAME: LIBRARY_NAME,
|
||||
FILENAME_DEV: FILENAME_DEV,
|
||||
FILENAME_DIST: FILENAME_DIST
|
||||
};
|
55
package.json
Normal file
55
package.json
Normal file
|
@ -0,0 +1,55 @@
|
|||
{
|
||||
"name": "redux-doctitle",
|
||||
"version": "1.0.0",
|
||||
"description": "Redux Middleware for managing document title",
|
||||
"main": "./lib/redux-doctitle.js",
|
||||
"scripts": {
|
||||
"build": "./node_modules/.bin/grunt dist",
|
||||
"dev": "./node_modules/.bin/grunt clean && ./node_modules/.bin/webpack --watch",
|
||||
"lint": "./node_modules/.bin/grunt eslint",
|
||||
"test": "./node_modules/.bin/grunt karma:dev",
|
||||
"test:watch": "./node_modules/.bin/karma start"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://git.bthlabs.pl/tomekwojcik/redux-doctitle.git"
|
||||
},
|
||||
"files": [
|
||||
"lib",
|
||||
"src"
|
||||
],
|
||||
"keywords": [
|
||||
"redux",
|
||||
"middleware",
|
||||
"document",
|
||||
"title"
|
||||
],
|
||||
"author": "Tomek Wójcik <contact@bthlabs.pl> (https://www.bthlabs.pl/)",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.0.0-beta.37",
|
||||
"@babel/preset-env": "^7.0.0-beta.37",
|
||||
"@babel/preset-react": "^7.0.0-beta.37",
|
||||
"babel-eslint": "^8.2.1",
|
||||
"babel-loader": "^8.0.0-beta.0",
|
||||
"eslint-plugin-jasmine": "^2.9.1",
|
||||
"grunt": "^1.0.1",
|
||||
"grunt-contrib-clean": "^1.1.0",
|
||||
"grunt-karma": "^2.0.0",
|
||||
"grunt-webpack": "^3.0.2",
|
||||
"gruntify-eslint": "^4.0.0",
|
||||
"jasmine-core": "^2.8.0",
|
||||
"karma": "^2.0.0",
|
||||
"karma-chrome-launcher": "^2.2.0",
|
||||
"karma-jasmine": "^1.1.1",
|
||||
"karma-mocha-reporter": "^2.2.5",
|
||||
"karma-sourcemap-loader": "^0.3.7",
|
||||
"karma-webpack": "^2.0.9",
|
||||
"underscore": "^1.8.3",
|
||||
"webpack": "^3.10.0",
|
||||
"webpack-uglify-js-plugin": "^1.1.9"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"redux": "^3.0.0"
|
||||
}
|
||||
}
|
8
src/actions.js
Normal file
8
src/actions.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
import {TITLE_ACTIONS_SET} from "./defs";
|
||||
|
||||
export const setDocumentTitle = (title = "") => {
|
||||
return {
|
||||
type: TITLE_ACTIONS_SET,
|
||||
title: title
|
||||
};
|
||||
};
|
1
src/defs.js
Normal file
1
src/defs.js
Normal file
|
@ -0,0 +1 @@
|
|||
export const TITLE_ACTIONS_SET = "BTHLABS/REDUX_DOCTITLE_MW_TITLE_ACTIONS_SET";
|
2
src/index.js
Normal file
2
src/index.js
Normal file
|
@ -0,0 +1,2 @@
|
|||
export {setDocumentTitle} from "./actions";
|
||||
export {documentTitleMiddlewareFactory} from "./middleware";
|
16
src/middleware.js
Normal file
16
src/middleware.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
import {TITLE_ACTIONS_SET} from "./defs";
|
||||
|
||||
export const documentTitleMiddlewareFactory = (defaultTitleParts = []) => {
|
||||
return (store, getState) => next => action => {
|
||||
if (action.type == TITLE_ACTIONS_SET) {
|
||||
let titleParts = [].concat(defaultTitleParts);
|
||||
if (action.title) {
|
||||
titleParts.unshift(action.title);
|
||||
}
|
||||
|
||||
window.document.title = titleParts.join(" | ");
|
||||
} else {
|
||||
next(action);
|
||||
}
|
||||
};
|
||||
};
|
2
tests/__entry__.js
Normal file
2
tests/__entry__.js
Normal file
|
@ -0,0 +1,2 @@
|
|||
let testsContext = require.context(".", true, /\.spec\.js$/);
|
||||
testsContext.keys().forEach(testsContext);
|
18
tests/actions.spec.js
Normal file
18
tests/actions.spec.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
import {TITLE_ACTIONS_SET} from "src/defs";
|
||||
import * as doctitleActions from "src/actions";
|
||||
|
||||
describe("actions", () => {
|
||||
describe("setDocumentTitle", () => {
|
||||
it("should create the action", () => {
|
||||
let result = doctitleActions.setDocumentTitle("Spam");
|
||||
expect(result.type).toEqual(TITLE_ACTIONS_SET);
|
||||
expect(result.title).toEqual("Spam");
|
||||
});
|
||||
|
||||
it("should create the action without the custom part", () => {
|
||||
let result = doctitleActions.setDocumentTitle();
|
||||
expect(result.type).toEqual(TITLE_ACTIONS_SET);
|
||||
expect(result.title).toEqual("");
|
||||
});
|
||||
});
|
||||
});
|
77
tests/middleware.spec.js
Normal file
77
tests/middleware.spec.js
Normal file
|
@ -0,0 +1,77 @@
|
|||
import {TITLE_ACTIONS_SET} from "src/defs";
|
||||
|
||||
import * as doctitleMiddleware from "src/middleware";
|
||||
|
||||
describe("middleware", () => {
|
||||
describe("documentTitleMiddleware", () => {
|
||||
let origTitle = null;
|
||||
let next = null;
|
||||
|
||||
beforeAll(() => {
|
||||
origTitle = window.document.title;
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
next = jasmine.createSpy("next");
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
window.document.title = origTitle;
|
||||
});
|
||||
|
||||
it("should set title to empty if no default parts are specified and custom part is empty", () => {
|
||||
let middleware = doctitleMiddleware.documentTitleMiddlewareFactory()(
|
||||
undefined, undefined
|
||||
);
|
||||
|
||||
let action = {type: TITLE_ACTIONS_SET, title: undefined};
|
||||
middleware(next)(action);
|
||||
|
||||
expect(window.document.title).toEqual("");
|
||||
});
|
||||
|
||||
it("should not set custom document title if it isn't specified", () => {
|
||||
let middleware = doctitleMiddleware.documentTitleMiddlewareFactory(["Test"])(
|
||||
undefined, undefined
|
||||
);
|
||||
|
||||
let action = {type: TITLE_ACTIONS_SET, title: undefined};
|
||||
middleware(next)(action);
|
||||
|
||||
expect(window.document.title).toEqual("Test");
|
||||
});
|
||||
|
||||
it("should set custom document title", () => {
|
||||
let middleware = doctitleMiddleware.documentTitleMiddlewareFactory(["Test"])(
|
||||
undefined, undefined
|
||||
);
|
||||
|
||||
let action = {type: TITLE_ACTIONS_SET, title: "Spam"};
|
||||
middleware(next)(action);
|
||||
|
||||
expect(window.document.title).toEqual("Spam | Test");
|
||||
});
|
||||
|
||||
it("should not call the next middleware if got the setDocumentTitle action", () => {
|
||||
let middleware = doctitleMiddleware.documentTitleMiddlewareFactory(["Test"])(
|
||||
undefined, undefined
|
||||
);
|
||||
|
||||
let action = {type: TITLE_ACTIONS_SET, title: "Spam"};
|
||||
middleware(next)(action);
|
||||
|
||||
expect(next).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should call the next middleware if didn't get the setDocumentTitle action", () => {
|
||||
let middleware = doctitleMiddleware.documentTitleMiddlewareFactory(["Test"])(
|
||||
undefined, undefined
|
||||
);
|
||||
|
||||
let action = {type: "SPAM"};
|
||||
middleware(next)(action);
|
||||
|
||||
expect(next).toHaveBeenCalledWith(action);
|
||||
});
|
||||
});
|
||||
});
|
28
webpack.config.js
Normal file
28
webpack.config.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
var path = require("path");
|
||||
|
||||
var webpack = require("webpack");
|
||||
|
||||
var defs = require("./package.defs.js");
|
||||
|
||||
var config = {
|
||||
entry: path.resolve(defs.SRC_DIR, "index.js"),
|
||||
devtool: "source-map",
|
||||
output: {
|
||||
path: defs.LIB_DIR,
|
||||
filename: defs.FILENAME_DEV,
|
||||
library: defs.LIBRARY_NAME,
|
||||
libraryTarget: "umd",
|
||||
umdNamedDefine: true
|
||||
},
|
||||
module: {
|
||||
loaders: [
|
||||
{
|
||||
test: /\.js?/,
|
||||
include: defs.SRC_DIR,
|
||||
loader: ["babel-loader"]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = config;
|
Loading…
Reference in New Issue
Block a user