Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updating outdated library + Supporting page InjectApp without CSP problem. #98

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,20 @@
"rules": {
"react/forbid-prop-types": 0,
"react/prefer-stateless-function": 0,
"react/jsx-one-expression-per-line": 0,
"react/destructuring-assignment": 0,
"react/require-default-props": 0,
"react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }],
"jsx-a11y/no-static-element-interactions": 0,
"jsx-a11y/click-events-have-key-events": 0,
"jsx-a11y/anchor-is-valid": 0,
"jsx-a11y/label-has-for": 0,
"consistent-return": 0,
"comma-dangle": 0,
"spaced-comment": 0,
"global-require": 0
"global-require": 0,
"max-len": 0,
"no-trailing-spaces": 0
},
"plugins": [
"react"
Expand Down
40 changes: 31 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,12 @@ The context menu is created by [chrome/extension/background/contextMenus.js](chr

#### Inject page

The inject script is being run by [chrome/extension/background/inject.js](chrome/extension/background/inject.js). A simple example will be inject bottom of page(`https://github.com/*`) if you visit.
The background script will load [content script](https://developer.chrome.com/extensions/content_scripts), and content script will inject page script into context of currently opened page. A open TodoApp button will be injected top of page(`https://github.com/*`) if you visit.

If you are receiving the error message `Failed to load resource: net::ERR_INSECURE_RESPONSE`, you need to allow invalid certificates for resources loaded from localhost. You can do this by visiting the following URL: `chrome://flags/#allow-insecure-localhost` and enabling **Allow invalid certificates for resources loaded from localhost**.
- Content script can only manipulate DOM of current page. (It is running on different context of currently opened page)
- If you want to interact with a javascript code running in currently opened page, you will have to inject page script into it from content script.

- If you are receiving the error message `Failed to load resource: net::ERR_INSECURE_RESPONSE`, you need to allow invalid certificates for resources loaded from localhost. You can do this by visiting the following URL: `chrome://flags/#allow-insecure-localhost` and enabling **Allow invalid certificates for resources loaded from localhost**.

## Installation

Expand All @@ -49,18 +52,37 @@ $ npm install

## Development

* Run script
### Running scripts

Two seprates command are provided for developing Chrome extension.

#### dev-r

```bash
# build asset & html files to './dev'
# start webpack development server for background, window, popup script.
$ npm run dev-r
```
If you are developing Chrome extension that uses backgroun script, window, and popup, then running `dev-r` will be enough.

#### dev-s

```bash
# build files to './dev'
# start webpack development server
$ npm run dev
# build content & page script and watch for their changes.
$ npm run dev-s
```
* If you're developing Inject page, please allow `https://localhost:3000` connections. (Because `injectpage` injected GitHub (https) pages, so webpack server procotol must be https.)
* [Load unpacked extensions](https://developer.chrome.com/extensions/getstarted#unpacked) with `./dev` folder.

If you are developing Chrome extension that uses content and page script, you have to run `dev-r` command first, then run `dev-s` command from **different terminal window**.

#### How to load chrome extension?

- [Load unpacked extensions](https://developer.chrome.com/extensions/getstarted#unpacked) with `./dev` folder.

#### React/Redux hot reload

This boilerplate uses `Webpack` and `react-transform`, and use `Redux`. You can hot reload by editing related files of Popup & Window & Inject page.
- This boilerplate uses `Webpack` and `react-transform`, and use `Redux`. You can hot reload by editing related files of Popup & Window and background scripts.

- Content script & page script (injected by content script) cannot use hot reload, because many web sites use strict Content Security Policy (CSP) which allows loading resources from only specified domains. When Chrome Extension's content script & page script run on these web sites, loading from webpack-dev-server (http://localhost:3000) is not possible.

#### Using Redux DevTools Extension

Expand Down
9 changes: 5 additions & 4 deletions app/components/Footer.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { PropTypes, Component } from 'react';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } from '../constants/TodoFilters';
import style from './Footer.css';
Expand All @@ -11,7 +12,6 @@ const FILTER_TITLES = {
};

export default class Footer extends Component {

static propTypes = {
completedCount: PropTypes.number.isRequired,
activeCount: PropTypes.number.isRequired,
Expand Down Expand Up @@ -64,6 +64,7 @@ export default class Footer extends Component {
if (completedCount > 0) {
return (
<button
type="button"
className={style.clearCompleted}
onClick={onClearCompleted}
>
Expand All @@ -78,11 +79,11 @@ export default class Footer extends Component {
<footer className={style.footer}>
{this.renderTodoCount()}
<ul className={style.filters}>
{FILTERS.map((filter, i) =>
{FILTERS.map((filter, i) => (
<li key={filter}>
{this.renderFilterLink(filter, this.filterHandlers[i])}
</li>
)}
))}
</ul>
{this.renderClearButton()}
</footer>
Expand Down
4 changes: 2 additions & 2 deletions app/components/Header.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React, { PropTypes, Component } from 'react';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import TodoTextInput from './TodoTextInput';

export default class Header extends Component {

static propTypes = {
addTodo: PropTypes.func.isRequired
};
Expand Down
8 changes: 3 additions & 5 deletions app/components/MainSection.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { Component, PropTypes } from 'react';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import TodoItem from './TodoItem';
import Footer from './Footer';
import { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } from '../constants/TodoFilters';
Expand All @@ -11,7 +12,6 @@ const TODO_FILTERS = {
};

export default class MainSection extends Component {

static propTypes = {
todos: PropTypes.array.isRequired,
actions: PropTypes.object.isRequired
Expand Down Expand Up @@ -79,9 +79,7 @@ export default class MainSection extends Component {
<section className={style.main}>
{this.renderToggleAll(completedCount)}
<ul className={style.todoList}>
{filteredTodos.map(todo =>
<TodoItem key={todo.id} todo={todo} {...actions} />
)}
{filteredTodos.map(todo => <TodoItem key={todo.id} todo={todo} {...actions} />)}
</ul>
{this.renderFooter(completedCount)}
</section>
Expand Down
1 change: 0 additions & 1 deletion app/components/TodoItem.css
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@
.editing {
border-bottom: none;
padding: 0;
composes: normal;
}

.editing:last-child {
Expand Down
5 changes: 3 additions & 2 deletions app/components/TodoItem.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React, { Component, PropTypes } from 'react';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import TodoTextInput from './TodoTextInput';
import style from './TodoItem.css';

export default class TodoItem extends Component {

static propTypes = {
todo: PropTypes.object.isRequired,
editTodo: PropTypes.func.isRequired,
Expand Down Expand Up @@ -68,6 +68,7 @@ export default class TodoItem extends Component {
{todo.text}
</label>
<button
type="button"
className={style.destroy}
onClick={this.handleDelete}
/>
Expand Down
5 changes: 2 additions & 3 deletions app/components/TodoTextInput.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React, { Component, PropTypes } from 'react';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import style from './TodoTextInput.css';

export default class TodoTextInput extends Component {

static propTypes = {
onSave: PropTypes.func.isRequired,
text: PropTypes.string,
Expand Down Expand Up @@ -48,7 +48,6 @@ export default class TodoTextInput extends Component {
})}
type="text"
placeholder={this.props.placeholder}
autoFocus="true"
value={this.state.text}
onBlur={this.handleBlur}
onChange={this.handleChange}
Expand Down
24 changes: 13 additions & 11 deletions app/containers/App.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,13 @@
import React, { Component, PropTypes } from 'react';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import Header from '../components/Header';
import MainSection from '../components/MainSection';
import * as TodoActions from '../actions/todos';
import style from './App.css';

@connect(
state => ({
todos: state.todos
}),
dispatch => ({
actions: bindActionCreators(TodoActions, dispatch)
})
)
export default class App extends Component {

class App extends Component {
static propTypes = {
todos: PropTypes.array.isRequired,
actions: PropTypes.object.isRequired
Expand All @@ -32,3 +24,13 @@ export default class App extends Component {
);
}
}

function mapStateToProps(state) {
return { todos: state.todos };
}

function mapDispatchToProps(dispatch) {
return { actions: bindActionCreators(TodoActions, dispatch) };
}

export default connect(mapStateToProps, mapDispatchToProps)(App);
5 changes: 3 additions & 2 deletions app/containers/Root.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import React, { Component, PropTypes } from 'react';
import React, { Component } from 'react';
import PropTypes from 'prop-types';

import { Provider } from 'react-redux';
import App from './App';

export default class Root extends Component {

static propTypes = {
store: PropTypes.object.isRequired
};
Expand Down
20 changes: 7 additions & 13 deletions app/reducers/todos.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,17 @@ const actionsMap = {
}, ...state];
},
[ActionTypes.DELETE_TODO](state, action) {
return state.filter(todo =>
todo.id !== action.id
);
return state.filter(todo => todo.id !== action.id);
},
[ActionTypes.EDIT_TODO](state, action) {
return state.map(todo =>
(todo.id === action.id ?
Object.assign({}, todo, { text: action.text }) :
todo)
);
return state.map(todo => (todo.id === action.id
? Object.assign({}, todo, { text: action.text })
: todo));
},
[ActionTypes.COMPLETE_TODO](state, action) {
return state.map(todo =>
(todo.id === action.id ?
Object.assign({}, todo, { completed: !todo.completed }) :
todo)
);
return state.map(todo => (todo.id === action.id
? Object.assign({}, todo, { completed: !todo.completed })
: todo));
},
[ActionTypes.COMPLETE_ALL](state/*, action*/) {
const areAllCompleted = state.every(todo => todo.completed);
Expand Down
8 changes: 4 additions & 4 deletions app/store/configureStore.dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import storage from '../utils/storage';

// If Redux DevTools Extension is installed use it, otherwise use Redux compose
/* eslint-disable no-underscore-dangle */
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
// Options: http://zalmoxisus.github.io/redux-devtools-extension/API/Arguments.html
}) :
compose;
})
: compose;
/* eslint-enable no-underscore-dangle */

const enhancer = composeEnhancers(
Expand Down
File renamed without changes.
35 changes: 35 additions & 0 deletions app/todoapp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from 'react';
import ReactDOM from 'react-dom';
// i18n
import {
addLocaleData,
IntlProvider,
} from 'react-intl';


import en from 'react-intl/locale-data/en';
import ko from 'react-intl/locale-data/ko';

import { language, messages } from '../chrome/extension/common/i18n';

import Root from './containers/Root';
import './todoapp.css';

addLocaleData([...en, ...ko]);

chrome.storage.local.get('state', (obj) => {
const { state } = obj;
const initialState = JSON.parse(state || '{}');

const createStore = require('./store/configureStore');

ReactDOM.render(
<IntlProvider
locale={language}
messages={messages}
>
<Root store={createStore(initialState)} />
</IntlProvider>,
document.querySelector('#root')
);
});
8 changes: 8 additions & 0 deletions chrome/assets/_locales/en/messages.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"appName": {
"message": "react-chrome-extension-example"
},
"appDesc": {
"message": "Example for react-chrome-extension-boilerplate"
}
}
8 changes: 8 additions & 0 deletions chrome/assets/_locales/ko/messages.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"appName": {
"message": "react-chrome-extension-example"
},
"appDesc": {
"message": "Example for react-chrome-extension-boilerplate"
}
}
Loading