├── .gitignore
├── LICENSE
├── README.md
├── build
├── favicon.ico
├── index.html
└── static
│ └── js
│ ├── main.4c684789.js
│ └── main.4c684789.js.map
├── config
├── babel.dev.js
├── babel.prod.js
├── env.js
├── eslint.js
├── flow
│ ├── css.js.flow
│ └── file.js.flow
├── paths.js
├── polyfills.js
├── webpack.config.dev.js
└── webpack.config.prod.js
├── favicon.ico
├── index.html
├── package.json
├── scripts
├── build.js
├── start.js
└── utils
│ ├── WatchMissingNodeModulesPlugin.js
│ ├── chrome.applescript
│ └── prompt.js
└── src
├── Todo.js
├── TodoView.js
├── TodoView.material.js
├── TodoViewModel.js
└── index.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # nyc test coverage
18 | .nyc_output
19 |
20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
21 | .grunt
22 |
23 | # node-waf configuration
24 | .lock-wscript
25 |
26 | # Compiled binary addons (http://nodejs.org/api/addons.html)
27 | build/Release
28 |
29 | # Dependency directories
30 | node_modules
31 | jspm_packages
32 |
33 | # Optional npm cache directory
34 | .npm
35 |
36 | # Optional REPL history
37 | .node_repl_history
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-mobx-todo-editor
2 | An example of React & MobX MVVM approach.
3 |
4 | 
5 |
6 | * [Read the Medium Article](https://medium.com/@MattiaManzati/building-a-react-mobx-application-with-mvvm-ec0b3e3c8786#.ta4m76g20)
7 | * [Live Example](https://mattiamanzati.github.io/react-mobx-todo-editor/build)
8 |
--------------------------------------------------------------------------------
/build/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattiamanzati/react-mobx-todo-editor/819eff780750a472537d03263116075c0e55cc74/build/favicon.ico
--------------------------------------------------------------------------------
/build/index.html:
--------------------------------------------------------------------------------
1 |
React App
--------------------------------------------------------------------------------
/config/babel.dev.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | // Don't try to find .babelrc because we want to force this configuration.
3 | babelrc: false,
4 | // This is a feature of `babel-loader` for webpack (not Babel itself).
5 | // It enables caching results in OS temporary directory for faster rebuilds.
6 | cacheDirectory: true,
7 | presets: [
8 | // Latest stable ECMAScript features
9 | require.resolve('babel-preset-latest'),
10 | // JSX, Flow
11 | require.resolve('babel-preset-react')
12 | ],
13 | plugins: [
14 | // handles @decorator
15 | require.resolve('babel-plugin-transform-decorators-legacy'),
16 | // class { handleClick = () => { } }
17 | require.resolve('babel-plugin-transform-class-properties'),
18 | // { ...todo, completed: true }
19 | require.resolve('babel-plugin-transform-object-rest-spread'),
20 | // function* () { yield 42; yield 43; }
21 | [require.resolve('babel-plugin-transform-regenerator'), {
22 | // Async functions are converted to generators by babel-preset-latest
23 | async: false
24 | }],
25 | // Polyfills the runtime needed for async/await and generators
26 | [require.resolve('babel-plugin-transform-runtime'), {
27 | helpers: false,
28 | polyfill: false,
29 | regenerator: true
30 | }]
31 | ]
32 | };
33 |
--------------------------------------------------------------------------------
/config/babel.prod.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | // Don't try to find .babelrc because we want to force this configuration.
3 | babelrc: false,
4 | presets: [
5 | // Latest stable ECMAScript features
6 | require.resolve('babel-preset-latest'),
7 | // JSX, Flow
8 | require.resolve('babel-preset-react')
9 | ],
10 | plugins: [
11 | // handles @decorator
12 | require.resolve('babel-plugin-transform-decorators-legacy'),
13 | // class { handleClick = () => { } }
14 | require.resolve('babel-plugin-transform-class-properties'),
15 | // { ...todo, completed: true }
16 | require.resolve('babel-plugin-transform-object-rest-spread'),
17 | // function* () { yield 42; yield 43; }
18 | [require.resolve('babel-plugin-transform-regenerator'), {
19 | // Async functions are converted to generators by babel-preset-latest
20 | async: false
21 | }],
22 | // Polyfills the runtime needed for async/await and generators
23 | [require.resolve('babel-plugin-transform-runtime'), {
24 | helpers: false,
25 | polyfill: false,
26 | regenerator: true
27 | }],
28 | // Optimization: hoist JSX that never changes out of render()
29 | require.resolve('babel-plugin-transform-react-constant-elements')
30 | ],
31 | };
32 |
--------------------------------------------------------------------------------
/config/env.js:
--------------------------------------------------------------------------------
1 | // Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be
2 | // injected into the application via DefinePlugin in Webpack configuration.
3 |
4 | var REACT_APP = /^REACT_APP_/i;
5 | var NODE_ENV = JSON.stringify(process.env.NODE_ENV || 'development');
6 |
7 | module.exports = Object
8 | .keys(process.env)
9 | .filter(key => REACT_APP.test(key))
10 | .reduce((env, key) => {
11 | env['process.env.' + key] = JSON.stringify(process.env[key]);
12 | return env;
13 | }, {
14 | 'process.env.NODE_ENV': NODE_ENV
15 | });
16 |
--------------------------------------------------------------------------------
/config/eslint.js:
--------------------------------------------------------------------------------
1 | // Inspired by https://github.com/airbnb/javascript but less opinionated.
2 |
3 | // We use eslint-loader so even warnings are very visibile.
4 | // This is why we only use "WARNING" level for potential errors,
5 | // and we don't use "ERROR" level at all.
6 |
7 | // In the future, we might create a separate list of rules for production.
8 | // It would probably be more strict.
9 |
10 | module.exports = {
11 | root: true,
12 |
13 | parser: 'babel-eslint',
14 |
15 | // import plugin is termporarily disabled, scroll below to see why
16 | plugins: [/*'import', */'flowtype', 'jsx-a11y', 'react'],
17 |
18 | env: {
19 | browser: true,
20 | commonjs: true,
21 | es6: true,
22 | node: true
23 | },
24 |
25 | parserOptions: {
26 | ecmaVersion: 6,
27 | sourceType: 'module',
28 | ecmaFeatures: {
29 | jsx: true,
30 | generators: true,
31 | experimentalObjectRestSpread: true
32 | }
33 | },
34 |
35 | settings: {
36 | 'import/ignore': [
37 | 'node_modules',
38 | '\\.(json|css|jpg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm)$',
39 | ],
40 | 'import/extensions': ['.js'],
41 | 'import/resolver': {
42 | node: {
43 | extensions: ['.js', '.json']
44 | }
45 | }
46 | },
47 |
48 | rules: {
49 | // http://eslint.org/docs/rules/
50 | 'array-callback-return': 'warn',
51 | 'default-case': ['warn', { commentPattern: '^no default$' }],
52 | 'dot-location': ['warn', 'property'],
53 | eqeqeq: ['warn', 'allow-null'],
54 | 'guard-for-in': 'warn',
55 | 'new-parens': 'warn',
56 | 'no-array-constructor': 'warn',
57 | 'no-caller': 'warn',
58 | 'no-cond-assign': ['warn', 'always'],
59 | 'no-const-assign': 'warn',
60 | 'no-control-regex': 'warn',
61 | 'no-delete-var': 'warn',
62 | 'no-dupe-args': 'warn',
63 | 'no-dupe-class-members': 'warn',
64 | 'no-dupe-keys': 'warn',
65 | 'no-duplicate-case': 'warn',
66 | 'no-empty-character-class': 'warn',
67 | 'no-empty-pattern': 'warn',
68 | 'no-eval': 'warn',
69 | 'no-ex-assign': 'warn',
70 | 'no-extend-native': 'warn',
71 | 'no-extra-bind': 'warn',
72 | 'no-extra-label': 'warn',
73 | 'no-fallthrough': 'warn',
74 | 'no-func-assign': 'warn',
75 | 'no-implied-eval': 'warn',
76 | 'no-invalid-regexp': 'warn',
77 | 'no-iterator': 'warn',
78 | 'no-label-var': 'warn',
79 | 'no-labels': ['warn', { allowLoop: false, allowSwitch: false }],
80 | 'no-lone-blocks': 'warn',
81 | 'no-loop-func': 'warn',
82 | 'no-mixed-operators': ['warn', {
83 | groups: [
84 | ['&', '|', '^', '~', '<<', '>>', '>>>'],
85 | ['==', '!=', '===', '!==', '>', '>=', '<', '<='],
86 | ['&&', '||'],
87 | ['in', 'instanceof']
88 | ],
89 | allowSamePrecedence: false
90 | }],
91 | 'no-multi-str': 'warn',
92 | 'no-native-reassign': 'warn',
93 | 'no-negated-in-lhs': 'warn',
94 | 'no-new-func': 'warn',
95 | 'no-new-object': 'warn',
96 | 'no-new-symbol': 'warn',
97 | 'no-new-wrappers': 'warn',
98 | 'no-obj-calls': 'warn',
99 | 'no-octal': 'warn',
100 | 'no-octal-escape': 'warn',
101 | 'no-redeclare': 'warn',
102 | 'no-regex-spaces': 'warn',
103 | 'no-restricted-syntax': [
104 | 'warn',
105 | 'LabeledStatement',
106 | 'WithStatement',
107 | ],
108 | 'no-return-assign': 'warn',
109 | 'no-script-url': 'warn',
110 | 'no-self-assign': 'warn',
111 | 'no-self-compare': 'warn',
112 | 'no-sequences': 'warn',
113 | 'no-shadow-restricted-names': 'warn',
114 | 'no-sparse-arrays': 'warn',
115 | 'no-this-before-super': 'warn',
116 | 'no-throw-literal': 'warn',
117 | 'no-undef': 'warn',
118 | 'no-unexpected-multiline': 'warn',
119 | 'no-unreachable': 'warn',
120 | 'no-unused-expressions': 'warn',
121 | 'no-unused-labels': 'warn',
122 | 'no-unused-vars': ['warn', { vars: 'local', args: 'none' }],
123 | 'no-use-before-define': ['warn', 'nofunc'],
124 | 'no-useless-computed-key': 'warn',
125 | 'no-useless-concat': 'warn',
126 | 'no-useless-constructor': 'warn',
127 | 'no-useless-escape': 'warn',
128 | 'no-useless-rename': ['warn', {
129 | ignoreDestructuring: false,
130 | ignoreImport: false,
131 | ignoreExport: false,
132 | }],
133 | 'no-with': 'warn',
134 | 'no-whitespace-before-property': 'warn',
135 | 'operator-assignment': ['warn', 'always'],
136 | radix: 'warn',
137 | 'require-yield': 'warn',
138 | 'rest-spread-spacing': ['warn', 'never'],
139 | strict: ['warn', 'never'],
140 | 'unicode-bom': ['warn', 'never'],
141 | 'use-isnan': 'warn',
142 | 'valid-typeof': 'warn',
143 |
144 | // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/
145 |
146 | // TODO: import rules are temporarily disabled because they don't play well
147 | // with how eslint-loader only checks the file you change. So if module A
148 | // imports module B, and B is missing a default export, the linter will
149 | // record this as an issue in module A. Now if you fix module B, the linter
150 | // will not be aware that it needs to re-lint A as well, so the error
151 | // will stay until the next restart, which is really confusing.
152 |
153 | // This is probably fixable with a patch to eslint-loader.
154 | // When file A is saved, we want to invalidate all files that import it
155 | // *and* that currently have lint errors. This should fix the problem.
156 |
157 | // 'import/default': 'warn',
158 | // 'import/export': 'warn',
159 | // 'import/named': 'warn',
160 | // 'import/namespace': 'warn',
161 | // 'import/no-amd': 'warn',
162 | // 'import/no-duplicates': 'warn',
163 | // 'import/no-extraneous-dependencies': 'warn',
164 | // 'import/no-named-as-default': 'warn',
165 | // 'import/no-named-as-default-member': 'warn',
166 | // 'import/no-unresolved': ['warn', { commonjs: true }],
167 |
168 | // https://github.com/yannickcr/eslint-plugin-react/tree/master/docs/rules
169 | 'react/jsx-equals-spacing': ['warn', 'never'],
170 | 'react/jsx-no-duplicate-props': ['warn', { ignoreCase: true }],
171 | 'react/jsx-no-undef': 'warn',
172 | 'react/jsx-pascal-case': ['warn', {
173 | allowAllCaps: true,
174 | ignore: [],
175 | }],
176 | 'react/jsx-uses-react': 'warn',
177 | 'react/jsx-uses-vars': 'warn',
178 | 'react/no-deprecated': 'warn',
179 | 'react/no-direct-mutation-state': 'warn',
180 | 'react/no-is-mounted': 'warn',
181 | 'react/react-in-jsx-scope': 'warn',
182 | 'react/require-render-return': 'warn',
183 |
184 | // https://github.com/evcohen/eslint-plugin-jsx-a11y/tree/master/docs/rules
185 | 'jsx-a11y/aria-role': 'warn',
186 | 'jsx-a11y/img-has-alt': 'warn',
187 | 'jsx-a11y/img-redundant-alt': 'warn',
188 | 'jsx-a11y/no-access-key': 'warn',
189 |
190 | // https://github.com/gajus/eslint-plugin-flowtype
191 | 'flowtype/define-flow-type': 'warn',
192 | 'flowtype/require-valid-file-annotation': 'warn',
193 | 'flowtype/use-flow-type': 'warn'
194 | }
195 | };
196 |
--------------------------------------------------------------------------------
/config/flow/css.js.flow:
--------------------------------------------------------------------------------
1 | // @flow
2 |
--------------------------------------------------------------------------------
/config/flow/file.js.flow:
--------------------------------------------------------------------------------
1 | // @flow
2 | declare export default string;
3 |
--------------------------------------------------------------------------------
/config/paths.js:
--------------------------------------------------------------------------------
1 | // TODO: we can split this file into several files (pre-eject, post-eject, test)
2 | // and use those instead. This way we don't need to branch here.
3 |
4 | var path = require('path');
5 |
6 | // True after ejecting, false when used as a dependency
7 | var isEjected = (
8 | path.resolve(path.join(__dirname, '..')) ===
9 | path.resolve(process.cwd())
10 | );
11 |
12 | // Are we developing create-react-app locally?
13 | var isInCreateReactAppSource = (
14 | process.argv.some(arg => arg.indexOf('--debug-template') > -1)
15 | );
16 |
17 | function resolveOwn(relativePath) {
18 | return path.resolve(__dirname, relativePath);
19 | }
20 |
21 | function resolveApp(relativePath) {
22 | return path.resolve(relativePath);
23 | }
24 |
25 | if (isInCreateReactAppSource) {
26 | // create-react-app development: we're in ./config/
27 | module.exports = {
28 | appBuild: resolveOwn('../build'),
29 | appHtml: resolveOwn('../template/index.html'),
30 | appFavicon: resolveOwn('../template/favicon.ico'),
31 | appPackageJson: resolveOwn('../package.json'),
32 | appSrc: resolveOwn('../template/src'),
33 | appNodeModules: resolveOwn('../node_modules'),
34 | ownNodeModules: resolveOwn('../node_modules')
35 | };
36 | } else if (!isEjected) {
37 | // before eject: we're in ./node_modules/react-scripts/config/
38 | module.exports = {
39 | appBuild: resolveApp('build'),
40 | appHtml: resolveApp('index.html'),
41 | appFavicon: resolveApp('favicon.ico'),
42 | appPackageJson: resolveApp('package.json'),
43 | appSrc: resolveApp('src'),
44 | appNodeModules: resolveApp('node_modules'),
45 | // this is empty with npm3 but node resolution searches higher anyway:
46 | ownNodeModules: resolveOwn('../node_modules')
47 | };
48 | } else {
49 | // after eject: we're in ./config/
50 | module.exports = {
51 | appBuild: resolveApp('build'),
52 | appHtml: resolveApp('index.html'),
53 | appFavicon: resolveApp('favicon.ico'),
54 | appPackageJson: resolveApp('package.json'),
55 | appSrc: resolveApp('src'),
56 | appNodeModules: resolveApp('node_modules'),
57 | ownNodeModules: resolveApp('node_modules')
58 | };
59 | }
60 |
--------------------------------------------------------------------------------
/config/polyfills.js:
--------------------------------------------------------------------------------
1 | if (typeof Promise === 'undefined') {
2 | // Rejection tracking prevents a common issue where React gets into an
3 | // inconsistent state due to an error, but it gets swallowed by a Promise,
4 | // and the user has no idea what causes React's erratic future behavior.
5 | require('promise/lib/rejection-tracking').enable();
6 | window.Promise = require('promise/lib/es6-extensions.js');
7 | }
8 |
9 | // fetch() polyfill for making API calls.
10 | require('whatwg-fetch');
11 |
12 | // Object.assign() is commonly used with React.
13 | // It will use the native implementation if it's present and isn't buggy.
14 | Object.assign = require('object-assign');
15 |
--------------------------------------------------------------------------------
/config/webpack.config.dev.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var autoprefixer = require('autoprefixer');
3 | var webpack = require('webpack');
4 | var HtmlWebpackPlugin = require('html-webpack-plugin');
5 | var CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
6 | var WatchMissingNodeModulesPlugin = require('../scripts/utils/WatchMissingNodeModulesPlugin');
7 | var paths = require('./paths');
8 | var env = require('./env');
9 |
10 | // This is the development configuration.
11 | // It is focused on developer experience and fast rebuilds.
12 | // The production configuration is different and lives in a separate file.
13 | module.exports = {
14 | // This makes the bundle appear split into separate modules in the devtools.
15 | // We don't use source maps here because they can be confusing:
16 | // https://github.com/facebookincubator/create-react-app/issues/343#issuecomment-237241875
17 | // You may want 'cheap-module-source-map' instead if you prefer source maps.
18 | devtool: 'eval',
19 | // These are the "entry points" to our application.
20 | // This means they will be the "root" imports that are included in JS bundle.
21 | // The first two entry points enable "hot" CSS and auto-refreshes for JS.
22 | entry: [
23 | // Include WebpackDevServer client. It connects to WebpackDevServer via
24 | // sockets and waits for recompile notifications. When WebpackDevServer
25 | // recompiles, it sends a message to the client by socket. If only CSS
26 | // was changed, the app reload just the CSS. Otherwise, it will refresh.
27 | // The "?/" bit at the end tells the client to look for the socket at
28 | // the root path, i.e. /sockjs-node/. Otherwise visiting a client-side
29 | // route like /todos/42 would make it wrongly request /todos/42/sockjs-node.
30 | // The socket server is a part of WebpackDevServer which we are using.
31 | // The /sockjs-node/ path I'm referring to is hardcoded in WebpackDevServer.
32 | require.resolve('webpack-dev-server/client') + '?/',
33 | // Include Webpack hot module replacement runtime. Webpack is pretty
34 | // low-level so we need to put all the pieces together. The runtime listens
35 | // to the events received by the client above, and applies updates (such as
36 | // new CSS) to the running application.
37 | require.resolve('webpack/hot/dev-server'),
38 | // We ship a few polyfills by default.
39 | require.resolve('./polyfills'),
40 | // Finally, this is your app's code:
41 | path.join(paths.appSrc, 'index')
42 | // We include the app code last so that if there is a runtime error during
43 | // initialization, it doesn't blow up the WebpackDevServer client, and
44 | // changing JS code would still trigger a refresh.
45 | ],
46 | output: {
47 | // Next line is not used in dev but WebpackDevServer crashes without it:
48 | path: paths.appBuild,
49 | // Add /* filename */ comments to generated require()s in the output.
50 | pathinfo: true,
51 | // This does not produce a real file. It's just the virtual path that is
52 | // served by WebpackDevServer in development. This is the JS bundle
53 | // containing code from all our entry points, and the Webpack runtime.
54 | filename: 'static/js/bundle.js',
55 | // In development, we always serve from the root. This makes config easier.
56 | publicPath: '/'
57 | },
58 | resolve: {
59 | // These are the reasonable defaults supported by the Node ecosystem.
60 | extensions: ['.js', '.json', ''],
61 | alias: {
62 | // This `alias` section can be safely removed after ejection.
63 | // We do this because `babel-runtime` may be inside `react-scripts`,
64 | // so when `babel-plugin-transform-runtime` imports it, it will not be
65 | // available to the app directly. This is a temporary solution that lets
66 | // us ship support for generators. However it is far from ideal, and
67 | // if we don't have a good solution, we should just make `babel-runtime`
68 | // a dependency in generated projects.
69 | // See https://github.com/facebookincubator/create-react-app/issues/255
70 | 'babel-runtime/regenerator': require.resolve('babel-runtime/regenerator'),
71 | 'react-native': 'react-native-web'
72 | }
73 | },
74 | // Resolve loaders (webpack plugins for CSS, images, transpilation) from the
75 | // directory of `react-scripts` itself rather than the project directory.
76 | // You can remove this after ejecting.
77 | resolveLoader: {
78 | root: paths.ownNodeModules,
79 | moduleTemplates: ['*-loader']
80 | },
81 | module: {
82 | // First, run the linter.
83 | // It's important to do this before Babel processes the JS.
84 | preLoaders: [
85 | {
86 | test: /\.js$/,
87 | loader: 'eslint',
88 | include: paths.appSrc,
89 | }
90 | ],
91 | loaders: [
92 | // Process JS with Babel.
93 | {
94 | test: /\.js$/,
95 | include: paths.appSrc,
96 | loader: 'babel',
97 | query: require('./babel.dev')
98 | },
99 | // "postcss" loader applies autoprefixer to our CSS.
100 | // "css" loader resolves paths in CSS and adds assets as dependencies.
101 | // "style" loader turns CSS into JS modules that inject